forked from ebhomengo/niki
Merge pull request 'vendorize project' (#150) from stage/hossein/vendorize into develop
Reviewed-on: ebhomengo/niki#150
This commit is contained in:
commit
4f4ae64100
|
@ -18,7 +18,6 @@ activate.mise.toml
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
vendor/
|
|
||||||
.idea
|
.idea
|
||||||
bin
|
bin
|
||||||
|
|
||||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,6 +1,6 @@
|
||||||
# Build Stage
|
# Build Stage
|
||||||
# First pull Golang image
|
# First pull Golang image
|
||||||
FROM golang:1.21.3-alpine as builder
|
FROM golang:1.23.0-alpine as builder
|
||||||
|
|
||||||
# Set environment variable
|
# Set environment variable
|
||||||
ENV APP_NAME niki
|
ENV APP_NAME niki
|
||||||
|
@ -17,11 +17,11 @@ WORKDIR /$APP_NAME
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Budild application
|
# Budild application
|
||||||
RUN CGO_ENABLED=0 go build -mod=mod -v -o $APP_NAME .
|
RUN CGO_ENABLED=0 go build -mod=vendor -v -o $APP_NAME .
|
||||||
|
|
||||||
# Run Stage
|
# Run Stage
|
||||||
|
|
||||||
FROM alpine:3.18 as development
|
FROM alpine:3.20 as Runtime
|
||||||
|
|
||||||
|
|
||||||
# Set environment variable
|
# Set environment variable
|
||||||
|
@ -31,7 +31,7 @@ ENV APP_NAME niki
|
||||||
COPY --from=builder /$APP_NAME .
|
COPY --from=builder /$APP_NAME .
|
||||||
|
|
||||||
# Expose application port
|
# Expose application port
|
||||||
EXPOSE 1313
|
EXPOSE 8313
|
||||||
|
|
||||||
# Start app
|
# Start app
|
||||||
CMD ./$APP_NAME
|
CMD ./$APP_NAME
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -1,8 +1,8 @@
|
||||||
module git.gocasts.ir/ebhomengo/niki
|
module git.gocasts.ir/ebhomengo/niki
|
||||||
|
|
||||||
go 1.22
|
go 1.23
|
||||||
|
|
||||||
toolchain go1.22.4
|
toolchain go1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
|
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
test_patterns = [
|
||||||
|
"*_test.go"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "go"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[analyzers.meta]
|
||||||
|
import_path = "dario.cat/mergo"
|
|
@ -0,0 +1,33 @@
|
||||||
|
#### joe made this: http://goel.io/joe
|
||||||
|
|
||||||
|
#### go ####
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
#### vim ####
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-v][a-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
|
@ -0,0 +1,12 @@
|
||||||
|
language: go
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
- ppc64le
|
||||||
|
install:
|
||||||
|
- go get -t
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- go test -race -v ./...
|
||||||
|
after_script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -0,0 +1,112 @@
|
||||||
|
<!-- omit in toc -->
|
||||||
|
# Contributing to mergo
|
||||||
|
|
||||||
|
First off, thanks for taking the time to contribute! ❤️
|
||||||
|
|
||||||
|
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||||
|
|
||||||
|
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||||
|
> - Star the project
|
||||||
|
> - Tweet about it
|
||||||
|
> - Refer this project in your project's readme
|
||||||
|
> - Mention the project at local meetups and tell your friends/colleagues
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Code of Conduct](#code-of-conduct)
|
||||||
|
- [I Have a Question](#i-have-a-question)
|
||||||
|
- [I Want To Contribute](#i-want-to-contribute)
|
||||||
|
- [Reporting Bugs](#reporting-bugs)
|
||||||
|
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project and everyone participating in it is governed by the
|
||||||
|
[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md).
|
||||||
|
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||||
|
to <>.
|
||||||
|
|
||||||
|
|
||||||
|
## I Have a Question
|
||||||
|
|
||||||
|
> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo).
|
||||||
|
|
||||||
|
Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||||
|
|
||||||
|
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||||
|
|
||||||
|
- Open an [Issue](https://github.com/imdario/mergo/issues/new).
|
||||||
|
- Provide as much context as you can about what you're running into.
|
||||||
|
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
|
||||||
|
|
||||||
|
We will then take care of the issue as soon as possible.
|
||||||
|
|
||||||
|
## I Want To Contribute
|
||||||
|
|
||||||
|
> ### Legal Notice <!-- omit in toc -->
|
||||||
|
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### Before Submitting a Bug Report
|
||||||
|
|
||||||
|
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||||
|
|
||||||
|
- Make sure that you are using the latest version.
|
||||||
|
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||||
|
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug).
|
||||||
|
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||||
|
- Collect information about the bug:
|
||||||
|
- Stack trace (Traceback)
|
||||||
|
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||||
|
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||||
|
- Possibly your input and the output
|
||||||
|
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### How Do I Submit a Good Bug Report?
|
||||||
|
|
||||||
|
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
|
||||||
|
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||||
|
|
||||||
|
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||||
|
|
||||||
|
- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||||
|
- Explain the behavior you would expect and the actual behavior.
|
||||||
|
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||||
|
- Provide the information you collected in the previous section.
|
||||||
|
|
||||||
|
Once it's filed:
|
||||||
|
|
||||||
|
- The project team will label the issue accordingly.
|
||||||
|
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||||
|
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
|
||||||
|
|
||||||
|
### Suggesting Enhancements
|
||||||
|
|
||||||
|
This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### Before Submitting an Enhancement
|
||||||
|
|
||||||
|
- Make sure that you are using the latest version.
|
||||||
|
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||||
|
- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||||
|
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
#### How Do I Submit a Good Enhancement Suggestion?
|
||||||
|
|
||||||
|
Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues).
|
||||||
|
|
||||||
|
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||||
|
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||||
|
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||||
|
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||||
|
- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||||
|
|
||||||
|
<!-- omit in toc -->
|
||||||
|
## Attribution
|
||||||
|
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,248 @@
|
||||||
|
# Mergo
|
||||||
|
|
||||||
|
[![GitHub release][5]][6]
|
||||||
|
[![GoCard][7]][8]
|
||||||
|
[![Test status][1]][2]
|
||||||
|
[![OpenSSF Scorecard][21]][22]
|
||||||
|
[![OpenSSF Best Practices][19]][20]
|
||||||
|
[![Coverage status][9]][10]
|
||||||
|
[![Sourcegraph][11]][12]
|
||||||
|
[![FOSSA status][13]][14]
|
||||||
|
|
||||||
|
[![GoDoc][3]][4]
|
||||||
|
[![Become my sponsor][15]][16]
|
||||||
|
[![Tidelift][17]][18]
|
||||||
|
|
||||||
|
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
|
||||||
|
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
|
||||||
|
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||||
|
[4]: https://godoc.org/github.com/imdario/mergo
|
||||||
|
[5]: https://img.shields.io/github/release/imdario/mergo.svg
|
||||||
|
[6]: https://github.com/imdario/mergo/releases
|
||||||
|
[7]: https://goreportcard.com/badge/imdario/mergo
|
||||||
|
[8]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||||
|
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
||||||
|
[10]: https://coveralls.io/github/imdario/mergo?branch=master
|
||||||
|
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
||||||
|
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
||||||
|
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
|
||||||
|
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
|
||||||
|
[15]: https://img.shields.io/github/sponsors/imdario
|
||||||
|
[16]: https://github.com/sponsors/imdario
|
||||||
|
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
|
||||||
|
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
|
||||||
|
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
|
||||||
|
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
|
||||||
|
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
|
||||||
|
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
|
||||||
|
|
||||||
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
||||||
|
|
||||||
|
### Important notes
|
||||||
|
|
||||||
|
#### 1.0.0
|
||||||
|
|
||||||
|
In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||||
|
|
||||||
|
#### 0.3.9
|
||||||
|
|
||||||
|
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules.
|
||||||
|
|
||||||
|
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
|
||||||
|
|
||||||
|
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||||
|
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||||
|
<a href='https://github.com/sponsors/imdario' target='_blank'><img alt="Become my sponsor" src="https://img.shields.io/github/sponsors/imdario?style=for-the-badge" /></a>
|
||||||
|
|
||||||
|
### Mergo in the wild
|
||||||
|
|
||||||
|
- [moby/moby](https://github.com/moby/moby)
|
||||||
|
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||||
|
- [vmware/dispatch](https://github.com/vmware/dispatch)
|
||||||
|
- [Shopify/themekit](https://github.com/Shopify/themekit)
|
||||||
|
- [imdario/zas](https://github.com/imdario/zas)
|
||||||
|
- [matcornic/hermes](https://github.com/matcornic/hermes)
|
||||||
|
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
|
||||||
|
- [kataras/iris](https://github.com/kataras/iris)
|
||||||
|
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
|
||||||
|
- [go-task/task](https://github.com/go-task/task)
|
||||||
|
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
|
||||||
|
- [ory/hydra](https://github.com/ory/hydra)
|
||||||
|
- [sisatech/vcli](https://github.com/sisatech/vcli)
|
||||||
|
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
|
||||||
|
- [projectcalico/felix](https://github.com/projectcalico/felix)
|
||||||
|
- [resin-os/balena](https://github.com/resin-os/balena)
|
||||||
|
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
|
||||||
|
- [Telefonica/govice](https://github.com/Telefonica/govice)
|
||||||
|
- [supergiant/supergiant](supergiant/supergiant)
|
||||||
|
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
|
||||||
|
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
|
||||||
|
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
|
||||||
|
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
|
||||||
|
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
|
||||||
|
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
|
||||||
|
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
|
||||||
|
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
|
||||||
|
- [divshot/gitling](https://github.com/divshot/gitling)
|
||||||
|
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
|
||||||
|
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
|
||||||
|
- [elwinar/rambler](https://github.com/elwinar/rambler)
|
||||||
|
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
|
||||||
|
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
|
||||||
|
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
|
||||||
|
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
|
||||||
|
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
|
||||||
|
- [thoas/picfit](https://github.com/thoas/picfit)
|
||||||
|
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||||
|
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||||
|
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
||||||
|
- [containerssh/containerssh](https://github.com/containerssh/containerssh)
|
||||||
|
- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
|
||||||
|
- [tjpnz/structbot](https://github.com/tjpnz/structbot)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get dario.cat/mergo
|
||||||
|
|
||||||
|
// use in your .go code
|
||||||
|
import (
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, you can merge overwriting values using the transformer `WithOverride`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
||||||
|
|
||||||
|
Here is a nice example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Foo{
|
||||||
|
A: "one",
|
||||||
|
B: 2,
|
||||||
|
}
|
||||||
|
dest := Foo{
|
||||||
|
A: "two",
|
||||||
|
}
|
||||||
|
mergo.Merge(&dest, src)
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// {two 2}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: if test are failing due missing package, please execute:
|
||||||
|
|
||||||
|
go get gopkg.in/yaml.v3
|
||||||
|
|
||||||
|
### Transformers
|
||||||
|
|
||||||
|
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() {
|
||||||
|
isZero := dst.MethodByName("IsZero")
|
||||||
|
result := isZero.Call([]reflect.Value{})
|
||||||
|
if result[0].Bool() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
Time time.Time
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Snapshot{time.Now()}
|
||||||
|
dest := Snapshot{}
|
||||||
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact me
|
||||||
|
|
||||||
|
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Written by [Dario Castañé](http://dario.im).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||||
|
|
||||||
|
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 0.3.x | :white_check_mark: |
|
||||||
|
| < 0.3 | :x: |
|
||||||
|
|
||||||
|
## Security contact information
|
||||||
|
|
||||||
|
To report a security vulnerability, please use the
|
||||||
|
[Tidelift security contact](https://tidelift.com/security).
|
||||||
|
Tidelift will coordinate the fix and disclosure.
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
# Status
|
||||||
|
|
||||||
|
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
|
||||||
|
|
||||||
|
# Important notes
|
||||||
|
|
||||||
|
1.0.0
|
||||||
|
|
||||||
|
In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||||
|
|
||||||
|
0.3.9
|
||||||
|
|
||||||
|
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
|
||||||
|
|
||||||
|
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Do your usual installation procedure:
|
||||||
|
|
||||||
|
go get dario.cat/mergo
|
||||||
|
|
||||||
|
// use in your .go code
|
||||||
|
import (
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Also, you can merge overwriting values using the transformer WithOverride.
|
||||||
|
|
||||||
|
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
|
||||||
|
|
||||||
|
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
|
||||||
|
|
||||||
|
Here is a nice example:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Foo{
|
||||||
|
A: "one",
|
||||||
|
B: 2,
|
||||||
|
}
|
||||||
|
dest := Foo{
|
||||||
|
A: "two",
|
||||||
|
}
|
||||||
|
mergo.Merge(&dest, src)
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// {two 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Transformers
|
||||||
|
|
||||||
|
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dario.cat/mergo"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() {
|
||||||
|
isZero := dst.MethodByName("IsZero")
|
||||||
|
result := isZero.Call([]reflect.Value{})
|
||||||
|
if result[0].Bool() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
Time time.Time
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Snapshot{time.Now()}
|
||||||
|
dest := Snapshot{}
|
||||||
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Contact me
|
||||||
|
|
||||||
|
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
|
||||||
|
|
||||||
|
# About
|
||||||
|
|
||||||
|
Written by Dario Castañé: https://da.rio.hn
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
BSD 3-Clause license, as Go language.
|
||||||
|
*/
|
||||||
|
package mergo
|
|
@ -0,0 +1,178 @@
|
||||||
|
// Copyright 2014 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func changeInitialCase(s string, mapper func(rune) rune) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
return string(mapper(r)) + s[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExported(field reflect.StructField) bool {
|
||||||
|
r, _ := utf8.DecodeRuneInString(field.Name)
|
||||||
|
return r >= 'A' && r <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverses recursively both values, assigning src's fields values to dst.
|
||||||
|
// The map argument tracks comparisons that have already been seen, which allows
|
||||||
|
// short circuiting on recursive types.
|
||||||
|
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||||
|
overwrite := config.Overwrite
|
||||||
|
if dst.CanAddr() {
|
||||||
|
addr := dst.UnsafeAddr()
|
||||||
|
h := 17 * addr
|
||||||
|
seen := visited[h]
|
||||||
|
typ := dst.Type()
|
||||||
|
for p := seen; p != nil; p = p.next {
|
||||||
|
if p.ptr == addr && p.typ == typ {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remember, remember...
|
||||||
|
visited[h] = &visit{typ, seen, addr}
|
||||||
|
}
|
||||||
|
zeroValue := reflect.Value{}
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
dstMap := dst.Interface().(map[string]interface{})
|
||||||
|
for i, n := 0, src.NumField(); i < n; i++ {
|
||||||
|
srcType := src.Type()
|
||||||
|
field := srcType.Field(i)
|
||||||
|
if !isExported(field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldName := field.Name
|
||||||
|
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||||
|
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
|
||||||
|
dstMap[fieldName] = src.Field(i).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if dst.IsNil() {
|
||||||
|
v := reflect.New(dst.Type().Elem())
|
||||||
|
dst.Set(v)
|
||||||
|
}
|
||||||
|
dst = dst.Elem()
|
||||||
|
fallthrough
|
||||||
|
case reflect.Struct:
|
||||||
|
srcMap := src.Interface().(map[string]interface{})
|
||||||
|
for key := range srcMap {
|
||||||
|
config.overwriteWithEmptyValue = true
|
||||||
|
srcValue := srcMap[key]
|
||||||
|
fieldName := changeInitialCase(key, unicode.ToUpper)
|
||||||
|
dstElement := dst.FieldByName(fieldName)
|
||||||
|
if dstElement == zeroValue {
|
||||||
|
// We discard it because the field doesn't exist.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
srcElement := reflect.ValueOf(srcValue)
|
||||||
|
dstKind := dstElement.Kind()
|
||||||
|
srcKind := srcElement.Kind()
|
||||||
|
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
|
||||||
|
srcElement = srcElement.Elem()
|
||||||
|
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
|
||||||
|
} else if dstKind == reflect.Ptr {
|
||||||
|
// Can this work? I guess it can't.
|
||||||
|
if srcKind != reflect.Ptr && srcElement.CanAddr() {
|
||||||
|
srcPtr := srcElement.Addr()
|
||||||
|
srcElement = reflect.ValueOf(srcPtr)
|
||||||
|
srcKind = reflect.Ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if srcKind == dstKind {
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if srcKind == reflect.Map {
|
||||||
|
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map sets fields' values in dst from src.
|
||||||
|
// src can be a map with string keys or a struct. dst must be the opposite:
|
||||||
|
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
|
||||||
|
// dst must be map[string]interface{}.
|
||||||
|
// It won't merge unexported (private) fields and will do recursively
|
||||||
|
// any exported field.
|
||||||
|
// If dst is a map, keys will be src fields' names in lower camel case.
|
||||||
|
// Missing key in src that doesn't match a field in dst will be skipped. This
|
||||||
|
// doesn't apply if dst is a map.
|
||||||
|
// This is separated method from Merge because it is cleaner and it keeps sane
|
||||||
|
// semantics: merging equal types, mapping different (restricted) types.
|
||||||
|
func Map(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return _map(dst, src, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
|
||||||
|
// non-empty src attribute values.
|
||||||
|
// Deprecated: Use Map(…) with WithOverride
|
||||||
|
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return _map(dst, src, append(opts, WithOverride)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerArgument
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
vDst, vSrc reflect.Value
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// To be friction-less, we redirect equal-type arguments
|
||||||
|
// to deepMerge. Only because arguments can be anything.
|
||||||
|
if vSrc.Kind() == vDst.Kind() {
|
||||||
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
||||||
|
switch vSrc.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
if vDst.Kind() != reflect.Map {
|
||||||
|
return ErrExpectedMapAsDestination
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if vDst.Kind() != reflect.Struct {
|
||||||
|
return ErrExpectedStructAsDestination
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrNotSupported
|
||||||
|
}
|
||||||
|
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
|
@ -0,0 +1,409 @@
|
||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hasMergeableFields(dst reflect.Value) (exported bool) {
|
||||||
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
|
field := dst.Type().Field(i)
|
||||||
|
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||||
|
exported = exported || hasMergeableFields(dst.Field(i))
|
||||||
|
} else if isExportedComponent(&field) {
|
||||||
|
exported = exported || len(field.PkgPath) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExportedComponent(field *reflect.StructField) bool {
|
||||||
|
pkgPath := field.PkgPath
|
||||||
|
if len(pkgPath) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := field.Name[0]
|
||||||
|
if 'a' <= c && c <= 'z' || c == '_' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Transformers Transformers
|
||||||
|
Overwrite bool
|
||||||
|
ShouldNotDereference bool
|
||||||
|
AppendSlice bool
|
||||||
|
TypeCheck bool
|
||||||
|
overwriteWithEmptyValue bool
|
||||||
|
overwriteSliceWithEmptyValue bool
|
||||||
|
sliceDeepCopy bool
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transformers interface {
|
||||||
|
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverses recursively both values, assigning src's fields values to dst.
|
||||||
|
// The map argument tracks comparisons that have already been seen, which allows
|
||||||
|
// short circuiting on recursive types.
|
||||||
|
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||||
|
overwrite := config.Overwrite
|
||||||
|
typeCheck := config.TypeCheck
|
||||||
|
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
||||||
|
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
||||||
|
sliceDeepCopy := config.sliceDeepCopy
|
||||||
|
|
||||||
|
if !src.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dst.CanAddr() {
|
||||||
|
addr := dst.UnsafeAddr()
|
||||||
|
h := 17 * addr
|
||||||
|
seen := visited[h]
|
||||||
|
typ := dst.Type()
|
||||||
|
for p := seen; p != nil; p = p.next {
|
||||||
|
if p.ptr == addr && p.typ == typ {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remember, remember...
|
||||||
|
visited[h] = &visit{typ, seen, addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
|
||||||
|
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
||||||
|
err = fn(dst, src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
if hasMergeableFields(dst) {
|
||||||
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
|
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if dst.IsNil() && !src.IsNil() {
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Kind() != reflect.Map {
|
||||||
|
if overwrite && dst.CanSet() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range src.MapKeys() {
|
||||||
|
srcElement := src.MapIndex(key)
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dstElement := dst.MapIndex(key)
|
||||||
|
switch srcElement.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
||||||
|
if srcElement.IsNil() {
|
||||||
|
if overwrite {
|
||||||
|
dst.SetMapIndex(key, srcElement)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if !srcElement.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Ptr:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Map:
|
||||||
|
srcMapElm := srcElement
|
||||||
|
dstMapElm := dstElement
|
||||||
|
if srcMapElm.CanInterface() {
|
||||||
|
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
||||||
|
if dstMapElm.IsValid() {
|
||||||
|
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
srcSlice := reflect.ValueOf(srcElement.Interface())
|
||||||
|
|
||||||
|
var dstSlice reflect.Value
|
||||||
|
if !dstElement.IsValid() || dstElement.IsNil() {
|
||||||
|
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
||||||
|
} else {
|
||||||
|
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
|
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
||||||
|
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
|
}
|
||||||
|
dstSlice = srcSlice
|
||||||
|
} else if config.AppendSlice {
|
||||||
|
if srcSlice.Type() != dstSlice.Type() {
|
||||||
|
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
|
}
|
||||||
|
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
i := 0
|
||||||
|
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
|
||||||
|
srcElement := srcSlice.Index(i)
|
||||||
|
dstElement := dstSlice.Index(i)
|
||||||
|
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
dst.SetMapIndex(key, dstSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
|
||||||
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
|
||||||
|
if dst.IsNil() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
}
|
||||||
|
dst.SetMapIndex(key, srcElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that all keys in dst are deleted if they are not in src.
|
||||||
|
if overwriteWithEmptySrc {
|
||||||
|
for _, key := range dst.MapKeys() {
|
||||||
|
srcElement := src.MapIndex(key)
|
||||||
|
if !srcElement.IsValid() {
|
||||||
|
dst.SetMapIndex(key, reflect.Value{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if !dst.CanSet() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
|
dst.Set(src)
|
||||||
|
} else if config.AppendSlice {
|
||||||
|
if src.Type() != dst.Type() {
|
||||||
|
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
||||||
|
}
|
||||||
|
dst.Set(reflect.AppendSlice(dst, src))
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
for i := 0; i < src.Len() && i < dst.Len(); i++ {
|
||||||
|
srcElement := src.Index(i)
|
||||||
|
dstElement := dst.Index(i)
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Interface:
|
||||||
|
if isReflectNil(src) {
|
||||||
|
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Kind() != reflect.Interface {
|
||||||
|
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
|
||||||
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
} else if src.Kind() == reflect.Ptr {
|
||||||
|
if !config.ShouldNotDereference {
|
||||||
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if dst.Elem().Type() == src.Type() {
|
||||||
|
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ErrDifferentArgumentsTypes
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.IsNil() || overwrite {
|
||||||
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.Elem().Kind() == src.Elem().Kind() {
|
||||||
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
|
||||||
|
if mustSet {
|
||||||
|
if dst.CanSet() {
|
||||||
|
dst.Set(src)
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||||
|
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
||||||
|
// and dst must be a pointer to struct.
|
||||||
|
// It won't merge unexported (private) fields and will do recursively any exported field.
|
||||||
|
func Merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return merge(dst, src, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
|
||||||
|
// non-empty src attribute values.
|
||||||
|
// Deprecated: use Merge(…) with WithOverride
|
||||||
|
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
return merge(dst, src, append(opts, WithOverride)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
||||||
|
func WithTransformers(transformers Transformers) func(*Config) {
|
||||||
|
return func(config *Config) {
|
||||||
|
config.Transformers = transformers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
||||||
|
func WithOverride(config *Config) {
|
||||||
|
config.Overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
|
||||||
|
func WithOverwriteWithEmptyValue(config *Config) {
|
||||||
|
config.Overwrite = true
|
||||||
|
config.overwriteWithEmptyValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
|
||||||
|
func WithOverrideEmptySlice(config *Config) {
|
||||||
|
config.overwriteSliceWithEmptyValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
|
||||||
|
// (i.e. a non-nil pointer is never considered empty).
|
||||||
|
func WithoutDereference(config *Config) {
|
||||||
|
config.ShouldNotDereference = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAppendSlice will make merge append slices instead of overwriting it.
|
||||||
|
func WithAppendSlice(config *Config) {
|
||||||
|
config.AppendSlice = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
|
||||||
|
func WithTypeCheck(config *Config) {
|
||||||
|
config.TypeCheck = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
|
||||||
|
func WithSliceDeepCopy(config *Config) {
|
||||||
|
config.sliceDeepCopy = true
|
||||||
|
config.Overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerArgument
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
vDst, vSrc reflect.Value
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
config := &Config{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vDst.Type() != vSrc.Type() {
|
||||||
|
return ErrDifferentArgumentsTypes
|
||||||
|
}
|
||||||
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReflectNil is the reflect value provided nil
|
||||||
|
func isReflectNil(v reflect.Value) bool {
|
||||||
|
k := v.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
|
||||||
|
// Both interface and slice are nil if first word is 0.
|
||||||
|
// Both are always bigger than a word; assume flagIndir.
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Based on src/pkg/reflect/deepequal.go from official
|
||||||
|
// golang's stdlib.
|
||||||
|
|
||||||
|
package mergo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors reported by Mergo when it finds invalid arguments.
|
||||||
|
var (
|
||||||
|
ErrNilArguments = errors.New("src and dst must not be nil")
|
||||||
|
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
|
||||||
|
ErrNotSupported = errors.New("only structs, maps, and slices are supported")
|
||||||
|
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||||
|
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||||
|
ErrNonPointerArgument = errors.New("dst must be a pointer")
|
||||||
|
)
|
||||||
|
|
||||||
|
// During deepMerge, must keep track of checks that are
|
||||||
|
// in progress. The comparison algorithm assumes that all
|
||||||
|
// checks in progress are true when it reencounters them.
|
||||||
|
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||||
|
type visit struct {
|
||||||
|
typ reflect.Type
|
||||||
|
next *visit
|
||||||
|
ptr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// From src/pkg/encoding/json/encode.go.
|
||||||
|
func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if shouldDereference {
|
||||||
|
return isEmptyValue(v.Elem(), shouldDereference)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Func:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Invalid:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||||
|
if dst == nil || src == nil {
|
||||||
|
err = ErrNilArguments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vDst = reflect.ValueOf(dst).Elem()
|
||||||
|
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice {
|
||||||
|
err = ErrNotSupported
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vSrc = reflect.ValueOf(src)
|
||||||
|
// We check if vSrc is a pointer to dereference it.
|
||||||
|
if vSrc.Kind() == reflect.Ptr {
|
||||||
|
vSrc = vSrc.Elem()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,14 @@
|
||||||
|
# filippo.io/edwards25519
|
||||||
|
|
||||||
|
```
|
||||||
|
import "filippo.io/edwards25519"
|
||||||
|
```
|
||||||
|
|
||||||
|
This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
|
||||||
|
Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
|
||||||
|
|
||||||
|
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
|
||||||
|
|
||||||
|
Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/internal/edwards25519`/`crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
|
||||||
|
|
||||||
|
Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package edwards25519 implements group logic for the twisted Edwards curve
|
||||||
|
//
|
||||||
|
// -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2
|
||||||
|
//
|
||||||
|
// This is better known as the Edwards curve equivalent to Curve25519, and is
|
||||||
|
// the curve used by the Ed25519 signature scheme.
|
||||||
|
//
|
||||||
|
// Most users don't need this package, and should instead use crypto/ed25519 for
|
||||||
|
// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
|
||||||
|
// github.com/gtank/ristretto255 for prime order group logic.
|
||||||
|
//
|
||||||
|
// However, developers who do need to interact with low-level edwards25519
|
||||||
|
// operations can use this package, which is an extended version of
|
||||||
|
// crypto/internal/edwards25519 from the standard library repackaged as
|
||||||
|
// an importable module.
|
||||||
|
package edwards25519
|
|
@ -0,0 +1,427 @@
|
||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"filippo.io/edwards25519/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Point types.
|
||||||
|
|
||||||
|
type projP1xP1 struct {
|
||||||
|
X, Y, Z, T field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type projP2 struct {
|
||||||
|
X, Y, Z field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point represents a point on the edwards25519 curve.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is NOT valid, and it may be used only as a receiver.
|
||||||
|
type Point struct {
|
||||||
|
// Make the type not comparable (i.e. used with == or as a map key), as
|
||||||
|
// equivalent points can be represented by different Go values.
|
||||||
|
_ incomparable
|
||||||
|
|
||||||
|
// The point is internally represented in extended coordinates (X, Y, Z, T)
|
||||||
|
// where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522.
|
||||||
|
x, y, z, t field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type incomparable [0]func()
|
||||||
|
|
||||||
|
func checkInitialized(points ...*Point) {
|
||||||
|
for _, p := range points {
|
||||||
|
if p.x == (field.Element{}) && p.y == (field.Element{}) {
|
||||||
|
panic("edwards25519: use of uninitialized Point")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type projCached struct {
|
||||||
|
YplusX, YminusX, Z, T2d field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
type affineCached struct {
|
||||||
|
YplusX, YminusX, T2d field.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors.
|
||||||
|
|
||||||
|
func (v *projP2) Zero() *projP2 {
|
||||||
|
v.X.Zero()
|
||||||
|
v.Y.One()
|
||||||
|
v.Z.One()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// identity is the point at infinity.
|
||||||
|
var identity, _ = new(Point).SetBytes([]byte{
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||||
|
|
||||||
|
// NewIdentityPoint returns a new Point set to the identity.
|
||||||
|
func NewIdentityPoint() *Point {
|
||||||
|
return new(Point).Set(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generator is the canonical curve basepoint. See TestGenerator for the
|
||||||
|
// correspondence of this encoding with the values in RFC 8032.
|
||||||
|
var generator, _ = new(Point).SetBytes([]byte{
|
||||||
|
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||||
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})
|
||||||
|
|
||||||
|
// NewGeneratorPoint returns a new Point set to the canonical generator.
|
||||||
|
func NewGeneratorPoint() *Point {
|
||||||
|
return new(Point).Set(generator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projCached) Zero() *projCached {
|
||||||
|
v.YplusX.One()
|
||||||
|
v.YminusX.One()
|
||||||
|
v.Z.One()
|
||||||
|
v.T2d.Zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *affineCached) Zero() *affineCached {
|
||||||
|
v.YplusX.One()
|
||||||
|
v.YminusX.One()
|
||||||
|
v.T2d.Zero()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignments.
|
||||||
|
|
||||||
|
// Set sets v = u, and returns v.
|
||||||
|
func (v *Point) Set(u *Point) *Point {
|
||||||
|
*v = *u
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding.
|
||||||
|
|
||||||
|
// Bytes returns the canonical 32-byte encoding of v, according to RFC 8032,
|
||||||
|
// Section 5.1.2.
|
||||||
|
func (v *Point) Bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var buf [32]byte
|
||||||
|
return v.bytes(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) bytes(buf *[32]byte) []byte {
|
||||||
|
checkInitialized(v)
|
||||||
|
|
||||||
|
var zInv, x, y field.Element
|
||||||
|
zInv.Invert(&v.z) // zInv = 1 / Z
|
||||||
|
x.Multiply(&v.x, &zInv) // x = X / Z
|
||||||
|
y.Multiply(&v.y, &zInv) // y = Y / Z
|
||||||
|
|
||||||
|
out := copyFieldElement(buf, &y)
|
||||||
|
out[31] |= byte(x.IsNegative() << 7)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
var feOne = new(field.Element).One()
|
||||||
|
|
||||||
|
// SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not
|
||||||
|
// represent a valid point on the curve, SetBytes returns nil and an error and
|
||||||
|
// the receiver is unchanged. Otherwise, SetBytes returns v.
|
||||||
|
//
|
||||||
|
// Note that SetBytes accepts all non-canonical encodings of valid points.
|
||||||
|
// That is, it follows decoding rules that match most implementations in
|
||||||
|
// the ecosystem rather than RFC 8032.
|
||||||
|
func (v *Point) SetBytes(x []byte) (*Point, error) {
|
||||||
|
// Specifically, the non-canonical encodings that are accepted are
|
||||||
|
// 1) the ones where the field element is not reduced (see the
|
||||||
|
// (*field.Element).SetBytes docs) and
|
||||||
|
// 2) the ones where the x-coordinate is zero and the sign bit is set.
|
||||||
|
//
|
||||||
|
// Read more at https://hdevalence.ca/blog/2020-10-04-its-25519am,
|
||||||
|
// specifically the "Canonical A, R" section.
|
||||||
|
|
||||||
|
y, err := new(field.Element).SetBytes(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("edwards25519: invalid point encoding length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// x² + dx²y² = x²(dy² + 1) = y² - 1
|
||||||
|
// x² = (y² - 1) / (dy² + 1)
|
||||||
|
|
||||||
|
// u = y² - 1
|
||||||
|
y2 := new(field.Element).Square(y)
|
||||||
|
u := new(field.Element).Subtract(y2, feOne)
|
||||||
|
|
||||||
|
// v = dy² + 1
|
||||||
|
vv := new(field.Element).Multiply(y2, d)
|
||||||
|
vv = vv.Add(vv, feOne)
|
||||||
|
|
||||||
|
// x = +√(u/v)
|
||||||
|
xx, wasSquare := new(field.Element).SqrtRatio(u, vv)
|
||||||
|
if wasSquare == 0 {
|
||||||
|
return nil, errors.New("edwards25519: invalid point encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the negative square root if the sign bit is set.
|
||||||
|
xxNeg := new(field.Element).Negate(xx)
|
||||||
|
xx = xx.Select(xxNeg, xx, int(x[31]>>7))
|
||||||
|
|
||||||
|
v.x.Set(xx)
|
||||||
|
v.y.Set(y)
|
||||||
|
v.z.One()
|
||||||
|
v.t.Multiply(xx, y) // xy = T / Z
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFieldElement(buf *[32]byte, v *field.Element) []byte {
|
||||||
|
copy(buf[:], v.Bytes())
|
||||||
|
return buf[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversions.
|
||||||
|
|
||||||
|
func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 {
|
||||||
|
v.X.Multiply(&p.X, &p.T)
|
||||||
|
v.Y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.Z.Multiply(&p.Z, &p.T)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP2) FromP3(p *Point) *projP2 {
|
||||||
|
v.X.Set(&p.x)
|
||||||
|
v.Y.Set(&p.y)
|
||||||
|
v.Z.Set(&p.z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) fromP1xP1(p *projP1xP1) *Point {
|
||||||
|
v.x.Multiply(&p.X, &p.T)
|
||||||
|
v.y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.z.Multiply(&p.Z, &p.T)
|
||||||
|
v.t.Multiply(&p.X, &p.Y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) fromP2(p *projP2) *Point {
|
||||||
|
v.x.Multiply(&p.X, &p.Z)
|
||||||
|
v.y.Multiply(&p.Y, &p.Z)
|
||||||
|
v.z.Square(&p.Z)
|
||||||
|
v.t.Multiply(&p.X, &p.Y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// d is a constant in the curve equation.
|
||||||
|
var d, _ = new(field.Element).SetBytes([]byte{
|
||||||
|
0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75,
|
||||||
|
0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00,
|
||||||
|
0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c,
|
||||||
|
0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52})
|
||||||
|
var d2 = new(field.Element).Add(d, d)
|
||||||
|
|
||||||
|
func (v *projCached) FromP3(p *Point) *projCached {
|
||||||
|
v.YplusX.Add(&p.y, &p.x)
|
||||||
|
v.YminusX.Subtract(&p.y, &p.x)
|
||||||
|
v.Z.Set(&p.z)
|
||||||
|
v.T2d.Multiply(&p.t, d2)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *affineCached) FromP3(p *Point) *affineCached {
|
||||||
|
v.YplusX.Add(&p.y, &p.x)
|
||||||
|
v.YminusX.Subtract(&p.y, &p.x)
|
||||||
|
v.T2d.Multiply(&p.t, d2)
|
||||||
|
|
||||||
|
var invZ field.Element
|
||||||
|
invZ.Invert(&p.z)
|
||||||
|
v.YplusX.Multiply(&v.YplusX, &invZ)
|
||||||
|
v.YminusX.Multiply(&v.YminusX, &invZ)
|
||||||
|
v.T2d.Multiply(&v.T2d, &invZ)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Re)addition and subtraction.
|
||||||
|
|
||||||
|
// Add sets v = p + q, and returns v.
|
||||||
|
func (v *Point) Add(p, q *Point) *Point {
|
||||||
|
checkInitialized(p, q)
|
||||||
|
qCached := new(projCached).FromP3(q)
|
||||||
|
result := new(projP1xP1).Add(p, qCached)
|
||||||
|
return v.fromP1xP1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets v = p - q, and returns v.
|
||||||
|
func (v *Point) Subtract(p, q *Point) *Point {
|
||||||
|
checkInitialized(p, q)
|
||||||
|
qCached := new(projCached).FromP3(q)
|
||||||
|
result := new(projP1xP1).Sub(p, qCached)
|
||||||
|
return v.fromP1xP1(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YplusX)
|
||||||
|
MM.Multiply(&YminusX, &q.YminusX)
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
ZZ2.Multiply(&p.z, &q.Z)
|
||||||
|
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Add(&ZZ2, &TT2d)
|
||||||
|
v.T.Subtract(&ZZ2, &TT2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||||
|
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
ZZ2.Multiply(&p.z, &q.Z)
|
||||||
|
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Subtract(&ZZ2, &TT2d) // flipped sign
|
||||||
|
v.T.Add(&ZZ2, &TT2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YplusX)
|
||||||
|
MM.Multiply(&YminusX, &q.YminusX)
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
|
||||||
|
Z2.Add(&p.z, &p.z)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Add(&Z2, &TT2d)
|
||||||
|
v.T.Subtract(&Z2, &TT2d)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||||
|
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||||
|
|
||||||
|
YplusX.Add(&p.y, &p.x)
|
||||||
|
YminusX.Subtract(&p.y, &p.x)
|
||||||
|
|
||||||
|
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||||
|
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||||
|
TT2d.Multiply(&p.t, &q.T2d)
|
||||||
|
|
||||||
|
Z2.Add(&p.z, &p.z)
|
||||||
|
|
||||||
|
v.X.Subtract(&PP, &MM)
|
||||||
|
v.Y.Add(&PP, &MM)
|
||||||
|
v.Z.Subtract(&Z2, &TT2d) // flipped sign
|
||||||
|
v.T.Add(&Z2, &TT2d) // flipped sign
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doubling.
|
||||||
|
|
||||||
|
func (v *projP1xP1) Double(p *projP2) *projP1xP1 {
|
||||||
|
var XX, YY, ZZ2, XplusYsq field.Element
|
||||||
|
|
||||||
|
XX.Square(&p.X)
|
||||||
|
YY.Square(&p.Y)
|
||||||
|
ZZ2.Square(&p.Z)
|
||||||
|
ZZ2.Add(&ZZ2, &ZZ2)
|
||||||
|
XplusYsq.Add(&p.X, &p.Y)
|
||||||
|
XplusYsq.Square(&XplusYsq)
|
||||||
|
|
||||||
|
v.Y.Add(&YY, &XX)
|
||||||
|
v.Z.Subtract(&YY, &XX)
|
||||||
|
|
||||||
|
v.X.Subtract(&XplusYsq, &v.Y)
|
||||||
|
v.T.Subtract(&ZZ2, &v.Z)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negation.
|
||||||
|
|
||||||
|
// Negate sets v = -p, and returns v.
|
||||||
|
func (v *Point) Negate(p *Point) *Point {
|
||||||
|
checkInitialized(p)
|
||||||
|
v.x.Negate(&p.x)
|
||||||
|
v.y.Set(&p.y)
|
||||||
|
v.z.Set(&p.z)
|
||||||
|
v.t.Negate(&p.t)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns 1 if v is equivalent to u, and 0 otherwise.
|
||||||
|
func (v *Point) Equal(u *Point) int {
|
||||||
|
checkInitialized(v, u)
|
||||||
|
|
||||||
|
var t1, t2, t3, t4 field.Element
|
||||||
|
t1.Multiply(&v.x, &u.z)
|
||||||
|
t2.Multiply(&u.x, &v.z)
|
||||||
|
t3.Multiply(&v.y, &u.z)
|
||||||
|
t4.Multiply(&u.y, &v.z)
|
||||||
|
|
||||||
|
return t1.Equal(&t2) & t3.Equal(&t4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant-time operations
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
func (v *projCached) Select(a, b *projCached, cond int) *projCached {
|
||||||
|
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||||
|
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||||
|
v.Z.Select(&a.Z, &b.Z, cond)
|
||||||
|
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||||
|
func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached {
|
||||||
|
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||||
|
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||||
|
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
func (v *projCached) CondNeg(cond int) *projCached {
|
||||||
|
v.YplusX.Swap(&v.YminusX, cond)
|
||||||
|
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||||
|
func (v *affineCached) CondNeg(cond int) *affineCached {
|
||||||
|
v.YplusX.Swap(&v.YminusX, cond)
|
||||||
|
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,349 @@
|
||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
// This file contains additional functionality that is not included in the
|
||||||
|
// upstream crypto/internal/edwards25519 package.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"filippo.io/edwards25519/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap. Don't change the style without making
|
||||||
|
// sure it doesn't increase the inliner cost.
|
||||||
|
var e [4]field.Element
|
||||||
|
X, Y, Z, T = v.extendedCoordinates(&e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) {
|
||||||
|
checkInitialized(v)
|
||||||
|
X = e[0].Set(&v.x)
|
||||||
|
Y = e[1].Set(&v.y)
|
||||||
|
Z = e[2].Set(&v.z)
|
||||||
|
T = e[3].Set(&v.t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where
|
||||||
|
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||||
|
//
|
||||||
|
// If the coordinates are invalid or don't represent a valid point on the curve,
|
||||||
|
// SetExtendedCoordinates returns nil and an error and the receiver is
|
||||||
|
// unchanged. Otherwise, SetExtendedCoordinates returns v.
|
||||||
|
func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) {
|
||||||
|
if !isOnCurve(X, Y, Z, T) {
|
||||||
|
return nil, errors.New("edwards25519: invalid point coordinates")
|
||||||
|
}
|
||||||
|
v.x.Set(X)
|
||||||
|
v.y.Set(Y)
|
||||||
|
v.z.Set(Z)
|
||||||
|
v.t.Set(T)
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOnCurve(X, Y, Z, T *field.Element) bool {
|
||||||
|
var lhs, rhs field.Element
|
||||||
|
XX := new(field.Element).Square(X)
|
||||||
|
YY := new(field.Element).Square(Y)
|
||||||
|
ZZ := new(field.Element).Square(Z)
|
||||||
|
TT := new(field.Element).Square(T)
|
||||||
|
// -x² + y² = 1 + dx²y²
|
||||||
|
// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)²
|
||||||
|
// -X² + Y² = Z² + dT²
|
||||||
|
lhs.Subtract(YY, XX)
|
||||||
|
rhs.Multiply(d, TT).Add(&rhs, ZZ)
|
||||||
|
if lhs.Equal(&rhs) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// xy = T/Z
|
||||||
|
// XY/Z² = T/Z
|
||||||
|
// XY = TZ
|
||||||
|
lhs.Multiply(X, Y)
|
||||||
|
rhs.Multiply(T, Z)
|
||||||
|
return lhs.Equal(&rhs) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesMontgomery converts v to a point on the birationally-equivalent
|
||||||
|
// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding
|
||||||
|
// according to RFC 7748.
|
||||||
|
//
|
||||||
|
// Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode
|
||||||
|
// to the same value. If v is the identity point, BytesMontgomery returns 32
|
||||||
|
// zero bytes, analogously to the X25519 function.
|
||||||
|
//
|
||||||
|
// The lack of an inverse operation (such as SetMontgomeryBytes) is deliberate:
|
||||||
|
// while every valid edwards25519 point has a unique u-coordinate Montgomery
|
||||||
|
// encoding, X25519 accepts inputs on the quadratic twist, which don't correspond
|
||||||
|
// to any edwards25519 point, and every other X25519 input corresponds to two
|
||||||
|
// edwards25519 points.
|
||||||
|
func (v *Point) BytesMontgomery() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var buf [32]byte
|
||||||
|
return v.bytesMontgomery(&buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
|
||||||
|
checkInitialized(v)
|
||||||
|
|
||||||
|
// RFC 7748, Section 4.1 provides the bilinear map to calculate the
|
||||||
|
// Montgomery u-coordinate
|
||||||
|
//
|
||||||
|
// u = (1 + y) / (1 - y)
|
||||||
|
//
|
||||||
|
// where y = Y / Z.
|
||||||
|
|
||||||
|
var y, recip, u field.Element
|
||||||
|
|
||||||
|
y.Multiply(&v.y, y.Invert(&v.z)) // y = Y / Z
|
||||||
|
recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y)
|
||||||
|
u.Multiply(u.Add(feOne, &y), &recip) // u = (1 + y)*r
|
||||||
|
|
||||||
|
return copyFieldElement(buf, &u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultByCofactor sets v = 8 * p, and returns v.
|
||||||
|
func (v *Point) MultByCofactor(p *Point) *Point {
|
||||||
|
checkInitialized(p)
|
||||||
|
result := projP1xP1{}
|
||||||
|
pp := (&projP2{}).FromP3(p)
|
||||||
|
result.Double(pp)
|
||||||
|
pp.FromP1xP1(&result)
|
||||||
|
result.Double(pp)
|
||||||
|
pp.FromP1xP1(&result)
|
||||||
|
result.Double(pp)
|
||||||
|
return v.fromP1xP1(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given k > 0, set s = s**(2*i).
|
||||||
|
func (s *Scalar) pow2k(k int) {
|
||||||
|
for i := 0; i < k; i++ {
|
||||||
|
s.Multiply(s, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert sets s to the inverse of a nonzero scalar v, and returns s.
|
||||||
|
//
|
||||||
|
// If t is zero, Invert returns zero.
|
||||||
|
func (s *Scalar) Invert(t *Scalar) *Scalar {
|
||||||
|
// Uses a hardcoded sliding window of width 4.
|
||||||
|
var table [8]Scalar
|
||||||
|
var tt Scalar
|
||||||
|
tt.Multiply(t, t)
|
||||||
|
table[0] = *t
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
table[i+1].Multiply(&table[i], &tt)
|
||||||
|
}
|
||||||
|
// Now table = [t**1, t**3, t**5, t**7, t**9, t**11, t**13, t**15]
|
||||||
|
// so t**k = t[k/2] for odd k
|
||||||
|
|
||||||
|
// To compute the sliding window digits, use the following Sage script:
|
||||||
|
|
||||||
|
// sage: import itertools
|
||||||
|
// sage: def sliding_window(w,k):
|
||||||
|
// ....: digits = []
|
||||||
|
// ....: while k > 0:
|
||||||
|
// ....: if k % 2 == 1:
|
||||||
|
// ....: kmod = k % (2**w)
|
||||||
|
// ....: digits.append(kmod)
|
||||||
|
// ....: k = k - kmod
|
||||||
|
// ....: else:
|
||||||
|
// ....: digits.append(0)
|
||||||
|
// ....: k = k // 2
|
||||||
|
// ....: return digits
|
||||||
|
|
||||||
|
// Now we can compute s roughly as follows:
|
||||||
|
|
||||||
|
// sage: s = 1
|
||||||
|
// sage: for coeff in reversed(sliding_window(4,l-2)):
|
||||||
|
// ....: s = s*s
|
||||||
|
// ....: if coeff > 0 :
|
||||||
|
// ....: s = s*t**coeff
|
||||||
|
|
||||||
|
// This works on one bit at a time, with many runs of zeros.
|
||||||
|
// The digits can be collapsed into [(count, coeff)] as follows:
|
||||||
|
|
||||||
|
// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))]
|
||||||
|
|
||||||
|
// Entries of the form (k, 0) turn into pow2k(k)
|
||||||
|
// Entries of the form (1, coeff) turn into a squaring and then a table lookup.
|
||||||
|
// We can fold the squaring into the previous pow2k(k) as pow2k(k+1).
|
||||||
|
|
||||||
|
*s = table[1/2]
|
||||||
|
s.pow2k(127 + 1)
|
||||||
|
s.Multiply(s, &table[1/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[13/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[5/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[1/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(5 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
s.pow2k(9 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[3/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[13/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[7/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[9/2])
|
||||||
|
s.pow2k(3 + 1)
|
||||||
|
s.Multiply(s, &table[15/2])
|
||||||
|
s.pow2k(4 + 1)
|
||||||
|
s.Multiply(s, &table[11/2])
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends only on the lengths of the two slices, which must match.
|
||||||
|
func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||||
|
if len(scalars) != len(points) {
|
||||||
|
panic("edwards25519: called MultiScalarMult with different size inputs")
|
||||||
|
}
|
||||||
|
checkInitialized(points...)
|
||||||
|
|
||||||
|
// Proceed as in the single-base case, but share doublings
|
||||||
|
// between each point in the multiscalar equation.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
tables := make([]projLookupTable, len(points))
|
||||||
|
for i := range tables {
|
||||||
|
tables[i].FromP3(points[i])
|
||||||
|
}
|
||||||
|
// Compute signed radix-16 digits for each scalar
|
||||||
|
digits := make([][64]int8, len(scalars))
|
||||||
|
for i := range digits {
|
||||||
|
digits[i] = scalars[i].signedRadix16()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for j := range tables {
|
||||||
|
tables[j].SelectInto(multiple, digits[j][63])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
for i := 62; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
// Lookup-and-add the appropriate multiple of each input point
|
||||||
|
for j := range tables {
|
||||||
|
tables[j].SelectInto(multiple, digits[j][i])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // update v
|
||||||
|
}
|
||||||
|
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||||
|
if len(scalars) != len(points) {
|
||||||
|
panic("edwards25519: called VarTimeMultiScalarMult with different size inputs")
|
||||||
|
}
|
||||||
|
checkInitialized(points...)
|
||||||
|
|
||||||
|
// Generalize double-base NAF computation to arbitrary sizes.
|
||||||
|
// Here all the points are dynamic, so we only use the smaller
|
||||||
|
// tables.
|
||||||
|
|
||||||
|
// Build lookup tables for each point
|
||||||
|
tables := make([]nafLookupTable5, len(points))
|
||||||
|
for i := range tables {
|
||||||
|
tables[i].FromP3(points[i])
|
||||||
|
}
|
||||||
|
// Compute a NAF for each scalar
|
||||||
|
nafs := make([][256]int8, len(scalars))
|
||||||
|
for i := range nafs {
|
||||||
|
nafs[i] = scalars[i].nonAdjacentForm(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
tmp2.Zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
//
|
||||||
|
// Skip trying to find the first nonzero coefficent, because
|
||||||
|
// searching might be more work than a few extra doublings.
|
||||||
|
for i := 255; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2)
|
||||||
|
|
||||||
|
for j := range nafs {
|
||||||
|
if nafs[j][i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
tables[j].SelectInto(multiple, nafs[j][i])
|
||||||
|
tmp1.Add(v, multiple)
|
||||||
|
} else if nafs[j][i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
tables[j].SelectInto(multiple, -nafs[j][i])
|
||||||
|
tmp1.Sub(v, multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.FromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.fromP2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package field implements fast arithmetic modulo 2^255-19.
|
||||||
|
package field
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Element represents an element of the field GF(2^255-19). Note that this
|
||||||
|
// is not a cryptographically secure group, and should only be used to interact
|
||||||
|
// with edwards25519.Point coordinates.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||||
|
// are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is a valid zero element.
|
||||||
|
type Element struct {
|
||||||
|
// An element t represents the integer
|
||||||
|
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
|
||||||
|
//
|
||||||
|
// Between operations, all limbs are expected to be lower than 2^52.
|
||||||
|
l0 uint64
|
||||||
|
l1 uint64
|
||||||
|
l2 uint64
|
||||||
|
l3 uint64
|
||||||
|
l4 uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
const maskLow51Bits uint64 = (1 << 51) - 1
|
||||||
|
|
||||||
|
var feZero = &Element{0, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
// Zero sets v = 0, and returns v.
|
||||||
|
func (v *Element) Zero() *Element {
|
||||||
|
*v = *feZero
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
var feOne = &Element{1, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
// One sets v = 1, and returns v.
|
||||||
|
func (v *Element) One() *Element {
|
||||||
|
*v = *feOne
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce reduces v modulo 2^255 - 19 and returns it.
|
||||||
|
func (v *Element) reduce() *Element {
|
||||||
|
v.carryPropagate()
|
||||||
|
|
||||||
|
// After the light reduction we now have a field element representation
|
||||||
|
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
|
||||||
|
|
||||||
|
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
|
||||||
|
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
|
||||||
|
c := (v.l0 + 19) >> 51
|
||||||
|
c = (v.l1 + c) >> 51
|
||||||
|
c = (v.l2 + c) >> 51
|
||||||
|
c = (v.l3 + c) >> 51
|
||||||
|
c = (v.l4 + c) >> 51
|
||||||
|
|
||||||
|
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
|
||||||
|
// effectively applying the reduction identity to the carry.
|
||||||
|
v.l0 += 19 * c
|
||||||
|
|
||||||
|
v.l1 += v.l0 >> 51
|
||||||
|
v.l0 = v.l0 & maskLow51Bits
|
||||||
|
v.l2 += v.l1 >> 51
|
||||||
|
v.l1 = v.l1 & maskLow51Bits
|
||||||
|
v.l3 += v.l2 >> 51
|
||||||
|
v.l2 = v.l2 & maskLow51Bits
|
||||||
|
v.l4 += v.l3 >> 51
|
||||||
|
v.l3 = v.l3 & maskLow51Bits
|
||||||
|
// no additional carry
|
||||||
|
v.l4 = v.l4 & maskLow51Bits
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets v = a + b, and returns v.
|
||||||
|
func (v *Element) Add(a, b *Element) *Element {
|
||||||
|
v.l0 = a.l0 + b.l0
|
||||||
|
v.l1 = a.l1 + b.l1
|
||||||
|
v.l2 = a.l2 + b.l2
|
||||||
|
v.l3 = a.l3 + b.l3
|
||||||
|
v.l4 = a.l4 + b.l4
|
||||||
|
// Using the generic implementation here is actually faster than the
|
||||||
|
// assembly. Probably because the body of this function is so simple that
|
||||||
|
// the compiler can figure out better optimizations by inlining the carry
|
||||||
|
// propagation.
|
||||||
|
return v.carryPropagateGeneric()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets v = a - b, and returns v.
|
||||||
|
func (v *Element) Subtract(a, b *Element) *Element {
|
||||||
|
// We first add 2 * p, to guarantee the subtraction won't underflow, and
|
||||||
|
// then subtract b (which can be up to 2^255 + 2^13 * 19).
|
||||||
|
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
|
||||||
|
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
|
||||||
|
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
|
||||||
|
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
|
||||||
|
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
|
||||||
|
return v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negate sets v = -a, and returns v.
|
||||||
|
func (v *Element) Negate(a *Element) *Element {
|
||||||
|
return v.Subtract(feZero, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert sets v = 1/z mod p, and returns v.
|
||||||
|
//
|
||||||
|
// If z == 0, Invert returns v = 0.
|
||||||
|
func (v *Element) Invert(z *Element) *Element {
|
||||||
|
// Inversion is implemented as exponentiation with exponent p − 2. It uses the
|
||||||
|
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
|
||||||
|
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
|
||||||
|
|
||||||
|
z2.Square(z) // 2
|
||||||
|
t.Square(&z2) // 4
|
||||||
|
t.Square(&t) // 8
|
||||||
|
z9.Multiply(&t, z) // 9
|
||||||
|
z11.Multiply(&z9, &z2) // 11
|
||||||
|
t.Square(&z11) // 22
|
||||||
|
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_5_0) // 2^6 - 2^1
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
t.Square(&t) // 2^10 - 2^5
|
||||||
|
}
|
||||||
|
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_10_0) // 2^11 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.Square(&t) // 2^20 - 2^10
|
||||||
|
}
|
||||||
|
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_20_0) // 2^21 - 2^1
|
||||||
|
for i := 0; i < 19; i++ {
|
||||||
|
t.Square(&t) // 2^40 - 2^20
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^41 - 2^1
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
t.Square(&t) // 2^50 - 2^10
|
||||||
|
}
|
||||||
|
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_50_0) // 2^51 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.Square(&t) // 2^100 - 2^50
|
||||||
|
}
|
||||||
|
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
|
||||||
|
|
||||||
|
t.Square(&z2_100_0) // 2^101 - 2^1
|
||||||
|
for i := 0; i < 99; i++ {
|
||||||
|
t.Square(&t) // 2^200 - 2^100
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^201 - 2^1
|
||||||
|
for i := 0; i < 49; i++ {
|
||||||
|
t.Square(&t) // 2^250 - 2^50
|
||||||
|
}
|
||||||
|
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
|
||||||
|
|
||||||
|
t.Square(&t) // 2^251 - 2^1
|
||||||
|
t.Square(&t) // 2^252 - 2^2
|
||||||
|
t.Square(&t) // 2^253 - 2^3
|
||||||
|
t.Square(&t) // 2^254 - 2^4
|
||||||
|
t.Square(&t) // 2^255 - 2^5
|
||||||
|
|
||||||
|
return v.Multiply(&t, &z11) // 2^255 - 21
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets v = a, and returns v.
|
||||||
|
func (v *Element) Set(a *Element) *Element {
|
||||||
|
*v = *a
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is
|
||||||
|
// not of the right length, SetBytes returns nil and an error, and the
|
||||||
|
// receiver is unchanged.
|
||||||
|
//
|
||||||
|
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||||
|
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||||
|
// are accepted. Note that this is laxer than specified by RFC 8032, but
|
||||||
|
// consistent with most Ed25519 implementations.
|
||||||
|
func (v *Element) SetBytes(x []byte) (*Element, error) {
|
||||||
|
if len(x) != 32 {
|
||||||
|
return nil, errors.New("edwards25519: invalid field element input size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
|
||||||
|
v.l0 = binary.LittleEndian.Uint64(x[0:8])
|
||||||
|
v.l0 &= maskLow51Bits
|
||||||
|
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
|
||||||
|
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
|
||||||
|
v.l1 &= maskLow51Bits
|
||||||
|
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
|
||||||
|
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
|
||||||
|
v.l2 &= maskLow51Bits
|
||||||
|
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||||
|
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
|
||||||
|
v.l3 &= maskLow51Bits
|
||||||
|
// Bits 204:255 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||||
|
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||||
|
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
|
||||||
|
v.l4 &= maskLow51Bits
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the canonical 32-byte little-endian encoding of v.
|
||||||
|
func (v *Element) Bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var out [32]byte
|
||||||
|
return v.bytes(&out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Element) bytes(out *[32]byte) []byte {
|
||||||
|
t := *v
|
||||||
|
t.reduce()
|
||||||
|
|
||||||
|
var buf [8]byte
|
||||||
|
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
|
||||||
|
bitsOffset := i * 51
|
||||||
|
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
|
||||||
|
for i, bb := range buf {
|
||||||
|
off := bitsOffset/8 + i
|
||||||
|
if off >= len(out) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out[off] |= bb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns 1 if v and u are equal, and 0 otherwise.
|
||||||
|
func (v *Element) Equal(u *Element) int {
|
||||||
|
sa, sv := u.Bytes(), v.Bytes()
|
||||||
|
return subtle.ConstantTimeCompare(sa, sv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
|
||||||
|
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
|
||||||
|
|
||||||
|
// Select sets v to a if cond == 1, and to b if cond == 0.
|
||||||
|
func (v *Element) Select(a, b *Element, cond int) *Element {
|
||||||
|
m := mask64Bits(cond)
|
||||||
|
v.l0 = (m & a.l0) | (^m & b.l0)
|
||||||
|
v.l1 = (m & a.l1) | (^m & b.l1)
|
||||||
|
v.l2 = (m & a.l2) | (^m & b.l2)
|
||||||
|
v.l3 = (m & a.l3) | (^m & b.l3)
|
||||||
|
v.l4 = (m & a.l4) | (^m & b.l4)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
|
||||||
|
func (v *Element) Swap(u *Element, cond int) {
|
||||||
|
m := mask64Bits(cond)
|
||||||
|
t := m & (v.l0 ^ u.l0)
|
||||||
|
v.l0 ^= t
|
||||||
|
u.l0 ^= t
|
||||||
|
t = m & (v.l1 ^ u.l1)
|
||||||
|
v.l1 ^= t
|
||||||
|
u.l1 ^= t
|
||||||
|
t = m & (v.l2 ^ u.l2)
|
||||||
|
v.l2 ^= t
|
||||||
|
u.l2 ^= t
|
||||||
|
t = m & (v.l3 ^ u.l3)
|
||||||
|
v.l3 ^= t
|
||||||
|
u.l3 ^= t
|
||||||
|
t = m & (v.l4 ^ u.l4)
|
||||||
|
v.l4 ^= t
|
||||||
|
u.l4 ^= t
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNegative returns 1 if v is negative, and 0 otherwise.
|
||||||
|
func (v *Element) IsNegative() int {
|
||||||
|
return int(v.Bytes()[0] & 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute sets v to |u|, and returns v.
|
||||||
|
func (v *Element) Absolute(u *Element) *Element {
|
||||||
|
return v.Select(new(Element).Negate(u), u, u.IsNegative())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply sets v = x * y, and returns v.
|
||||||
|
func (v *Element) Multiply(x, y *Element) *Element {
|
||||||
|
feMul(v, x, y)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Square sets v = x * x, and returns v.
|
||||||
|
func (v *Element) Square(x *Element) *Element {
|
||||||
|
feSquare(v, x)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mult32 sets v = x * y, and returns v.
|
||||||
|
func (v *Element) Mult32(x *Element, y uint32) *Element {
|
||||||
|
x0lo, x0hi := mul51(x.l0, y)
|
||||||
|
x1lo, x1hi := mul51(x.l1, y)
|
||||||
|
x2lo, x2hi := mul51(x.l2, y)
|
||||||
|
x3lo, x3hi := mul51(x.l3, y)
|
||||||
|
x4lo, x4hi := mul51(x.l4, y)
|
||||||
|
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
|
||||||
|
v.l1 = x1lo + x0hi
|
||||||
|
v.l2 = x2lo + x1hi
|
||||||
|
v.l3 = x3lo + x2hi
|
||||||
|
v.l4 = x4lo + x3hi
|
||||||
|
// The hi portions are going to be only 32 bits, plus any previous excess,
|
||||||
|
// so we can skip the carry propagation.
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul51 returns lo + hi * 2⁵¹ = a * b.
|
||||||
|
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
|
||||||
|
mh, ml := bits.Mul64(a, uint64(b))
|
||||||
|
lo = ml & maskLow51Bits
|
||||||
|
hi = (mh << 13) | (ml >> 51)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
|
||||||
|
func (v *Element) Pow22523(x *Element) *Element {
|
||||||
|
var t0, t1, t2 Element
|
||||||
|
|
||||||
|
t0.Square(x) // x^2
|
||||||
|
t1.Square(&t0) // x^4
|
||||||
|
t1.Square(&t1) // x^8
|
||||||
|
t1.Multiply(x, &t1) // x^9
|
||||||
|
t0.Multiply(&t0, &t1) // x^11
|
||||||
|
t0.Square(&t0) // x^22
|
||||||
|
t0.Multiply(&t1, &t0) // x^31
|
||||||
|
t1.Square(&t0) // x^62
|
||||||
|
for i := 1; i < 5; i++ { // x^992
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
|
||||||
|
t1.Square(&t0) // 2^11 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^20 - 2^10
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t1, &t0) // 2^20 - 1
|
||||||
|
t2.Square(&t1) // 2^21 - 2
|
||||||
|
for i := 1; i < 20; i++ { // 2^40 - 2^20
|
||||||
|
t2.Square(&t2)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t2, &t1) // 2^40 - 1
|
||||||
|
t1.Square(&t1) // 2^41 - 2
|
||||||
|
for i := 1; i < 10; i++ { // 2^50 - 2^10
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // 2^50 - 1
|
||||||
|
t1.Square(&t0) // 2^51 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^100 - 2^50
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t1, &t0) // 2^100 - 1
|
||||||
|
t2.Square(&t1) // 2^101 - 2
|
||||||
|
for i := 1; i < 100; i++ { // 2^200 - 2^100
|
||||||
|
t2.Square(&t2)
|
||||||
|
}
|
||||||
|
t1.Multiply(&t2, &t1) // 2^200 - 1
|
||||||
|
t1.Square(&t1) // 2^201 - 2
|
||||||
|
for i := 1; i < 50; i++ { // 2^250 - 2^50
|
||||||
|
t1.Square(&t1)
|
||||||
|
}
|
||||||
|
t0.Multiply(&t1, &t0) // 2^250 - 1
|
||||||
|
t0.Square(&t0) // 2^251 - 2
|
||||||
|
t0.Square(&t0) // 2^252 - 4
|
||||||
|
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
|
||||||
|
var sqrtM1 = &Element{1718705420411056, 234908883556509,
|
||||||
|
2233514472574048, 2117202627021982, 765476049583133}
|
||||||
|
|
||||||
|
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
|
||||||
|
//
|
||||||
|
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
|
||||||
|
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||||
|
// and returns r and 0.
|
||||||
|
func (r *Element) SqrtRatio(u, v *Element) (R *Element, wasSquare int) {
|
||||||
|
t0 := new(Element)
|
||||||
|
|
||||||
|
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||||
|
v2 := new(Element).Square(v)
|
||||||
|
uv3 := new(Element).Multiply(u, t0.Multiply(v2, v))
|
||||||
|
uv7 := new(Element).Multiply(uv3, t0.Square(v2))
|
||||||
|
rr := new(Element).Multiply(uv3, t0.Pow22523(uv7))
|
||||||
|
|
||||||
|
check := new(Element).Multiply(v, t0.Square(rr)) // check = v * r^2
|
||||||
|
|
||||||
|
uNeg := new(Element).Negate(u)
|
||||||
|
correctSignSqrt := check.Equal(u)
|
||||||
|
flippedSignSqrt := check.Equal(uNeg)
|
||||||
|
flippedSignSqrtI := check.Equal(t0.Multiply(uNeg, sqrtM1))
|
||||||
|
|
||||||
|
rPrime := new(Element).Multiply(rr, sqrtM1) // r_prime = SQRT_M1 * r
|
||||||
|
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||||
|
rr.Select(rPrime, rr, flippedSignSqrt|flippedSignSqrtI)
|
||||||
|
|
||||||
|
r.Absolute(rr) // Choose the nonnegative square root.
|
||||||
|
return r, correctSignSqrt | flippedSignSqrt
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build amd64 && gc && !purego
|
||||||
|
// +build amd64,gc,!purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
// feMul sets out = a * b. It works like feMulGeneric.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func feMul(out *Element, a *Element, b *Element)
|
||||||
|
|
||||||
|
// feSquare sets out = a * a. It works like feSquareGeneric.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func feSquare(out *Element, a *Element)
|
|
@ -0,0 +1,379 @@
|
||||||
|
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build amd64 && gc && !purego
|
||||||
|
// +build amd64,gc,!purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func feMul(out *Element, a *Element, b *Element)
|
||||||
|
TEXT ·feMul(SB), NOSPLIT, $0-24
|
||||||
|
MOVQ a+8(FP), CX
|
||||||
|
MOVQ b+16(FP), BX
|
||||||
|
|
||||||
|
// r0 = a0×b0
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
MOVQ AX, DI
|
||||||
|
MOVQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a1×b4
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a2×b3
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a3×b2
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r0 += 19×a4×b1
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, DI
|
||||||
|
ADCQ DX, SI
|
||||||
|
|
||||||
|
// r1 = a0×b1
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
MOVQ AX, R9
|
||||||
|
MOVQ DX, R8
|
||||||
|
|
||||||
|
// r1 += a1×b0
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a2×b4
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a3×b3
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r1 += 19×a4×b2
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R9
|
||||||
|
ADCQ DX, R8
|
||||||
|
|
||||||
|
// r2 = a0×b2
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
MOVQ AX, R11
|
||||||
|
MOVQ DX, R10
|
||||||
|
|
||||||
|
// r2 += a1×b1
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += a2×b0
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += 19×a3×b4
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r2 += 19×a4×b3
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R11
|
||||||
|
ADCQ DX, R10
|
||||||
|
|
||||||
|
// r3 = a0×b3
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
MOVQ AX, R13
|
||||||
|
MOVQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a1×b2
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a2×b1
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += a3×b0
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r3 += 19×a4×b4
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
ADDQ AX, R13
|
||||||
|
ADCQ DX, R12
|
||||||
|
|
||||||
|
// r4 = a0×b4
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ 32(BX)
|
||||||
|
MOVQ AX, R15
|
||||||
|
MOVQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a1×b3
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 24(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a2×b2
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 16(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a3×b1
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
MULQ 8(BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// r4 += a4×b0
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
MULQ (BX)
|
||||||
|
ADDQ AX, R15
|
||||||
|
ADCQ DX, R14
|
||||||
|
|
||||||
|
// First reduction chain
|
||||||
|
MOVQ $0x0007ffffffffffff, AX
|
||||||
|
SHLQ $0x0d, DI, SI
|
||||||
|
SHLQ $0x0d, R9, R8
|
||||||
|
SHLQ $0x0d, R11, R10
|
||||||
|
SHLQ $0x0d, R13, R12
|
||||||
|
SHLQ $0x0d, R15, R14
|
||||||
|
ANDQ AX, DI
|
||||||
|
IMUL3Q $0x13, R14, R14
|
||||||
|
ADDQ R14, DI
|
||||||
|
ANDQ AX, R9
|
||||||
|
ADDQ SI, R9
|
||||||
|
ANDQ AX, R11
|
||||||
|
ADDQ R8, R11
|
||||||
|
ANDQ AX, R13
|
||||||
|
ADDQ R10, R13
|
||||||
|
ANDQ AX, R15
|
||||||
|
ADDQ R12, R15
|
||||||
|
|
||||||
|
// Second reduction chain (carryPropagate)
|
||||||
|
MOVQ DI, SI
|
||||||
|
SHRQ $0x33, SI
|
||||||
|
MOVQ R9, R8
|
||||||
|
SHRQ $0x33, R8
|
||||||
|
MOVQ R11, R10
|
||||||
|
SHRQ $0x33, R10
|
||||||
|
MOVQ R13, R12
|
||||||
|
SHRQ $0x33, R12
|
||||||
|
MOVQ R15, R14
|
||||||
|
SHRQ $0x33, R14
|
||||||
|
ANDQ AX, DI
|
||||||
|
IMUL3Q $0x13, R14, R14
|
||||||
|
ADDQ R14, DI
|
||||||
|
ANDQ AX, R9
|
||||||
|
ADDQ SI, R9
|
||||||
|
ANDQ AX, R11
|
||||||
|
ADDQ R8, R11
|
||||||
|
ANDQ AX, R13
|
||||||
|
ADDQ R10, R13
|
||||||
|
ANDQ AX, R15
|
||||||
|
ADDQ R12, R15
|
||||||
|
|
||||||
|
// Store output
|
||||||
|
MOVQ out+0(FP), AX
|
||||||
|
MOVQ DI, (AX)
|
||||||
|
MOVQ R9, 8(AX)
|
||||||
|
MOVQ R11, 16(AX)
|
||||||
|
MOVQ R13, 24(AX)
|
||||||
|
MOVQ R15, 32(AX)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func feSquare(out *Element, a *Element)
|
||||||
|
TEXT ·feSquare(SB), NOSPLIT, $0-16
|
||||||
|
MOVQ a+8(FP), CX
|
||||||
|
|
||||||
|
// r0 = l0×l0
|
||||||
|
MOVQ (CX), AX
|
||||||
|
MULQ (CX)
|
||||||
|
MOVQ AX, SI
|
||||||
|
MOVQ DX, BX
|
||||||
|
|
||||||
|
// r0 += 38×l1×l4
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, SI
|
||||||
|
ADCQ DX, BX
|
||||||
|
|
||||||
|
// r0 += 38×l2×l3
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, SI
|
||||||
|
ADCQ DX, BX
|
||||||
|
|
||||||
|
// r1 = 2×l0×l1
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
MOVQ AX, R8
|
||||||
|
MOVQ DX, DI
|
||||||
|
|
||||||
|
// r1 += 38×l2×l4
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R8
|
||||||
|
ADCQ DX, DI
|
||||||
|
|
||||||
|
// r1 += 19×l3×l3
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, R8
|
||||||
|
ADCQ DX, DI
|
||||||
|
|
||||||
|
// r2 = 2×l0×l2
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
MOVQ AX, R10
|
||||||
|
MOVQ DX, R9
|
||||||
|
|
||||||
|
// r2 += l1×l1
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
MULQ 8(CX)
|
||||||
|
ADDQ AX, R10
|
||||||
|
ADCQ DX, R9
|
||||||
|
|
||||||
|
// r2 += 38×l3×l4
|
||||||
|
MOVQ 24(CX), AX
|
||||||
|
IMUL3Q $0x26, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R10
|
||||||
|
ADCQ DX, R9
|
||||||
|
|
||||||
|
// r3 = 2×l0×l3
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
MOVQ AX, R12
|
||||||
|
MOVQ DX, R11
|
||||||
|
|
||||||
|
// r3 += 2×l1×l2
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x02, AX, AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX, R12
|
||||||
|
ADCQ DX, R11
|
||||||
|
|
||||||
|
// r3 += 19×l4×l4
|
||||||
|
MOVQ 32(CX), AX
|
||||||
|
IMUL3Q $0x13, AX, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
ADDQ AX, R12
|
||||||
|
ADCQ DX, R11
|
||||||
|
|
||||||
|
// r4 = 2×l0×l4
|
||||||
|
MOVQ (CX), AX
|
||||||
|
SHLQ $0x01, AX
|
||||||
|
MULQ 32(CX)
|
||||||
|
MOVQ AX, R14
|
||||||
|
MOVQ DX, R13
|
||||||
|
|
||||||
|
// r4 += 2×l1×l3
|
||||||
|
MOVQ 8(CX), AX
|
||||||
|
IMUL3Q $0x02, AX, AX
|
||||||
|
MULQ 24(CX)
|
||||||
|
ADDQ AX, R14
|
||||||
|
ADCQ DX, R13
|
||||||
|
|
||||||
|
// r4 += l2×l2
|
||||||
|
MOVQ 16(CX), AX
|
||||||
|
MULQ 16(CX)
|
||||||
|
ADDQ AX, R14
|
||||||
|
ADCQ DX, R13
|
||||||
|
|
||||||
|
// First reduction chain
|
||||||
|
MOVQ $0x0007ffffffffffff, AX
|
||||||
|
SHLQ $0x0d, SI, BX
|
||||||
|
SHLQ $0x0d, R8, DI
|
||||||
|
SHLQ $0x0d, R10, R9
|
||||||
|
SHLQ $0x0d, R12, R11
|
||||||
|
SHLQ $0x0d, R14, R13
|
||||||
|
ANDQ AX, SI
|
||||||
|
IMUL3Q $0x13, R13, R13
|
||||||
|
ADDQ R13, SI
|
||||||
|
ANDQ AX, R8
|
||||||
|
ADDQ BX, R8
|
||||||
|
ANDQ AX, R10
|
||||||
|
ADDQ DI, R10
|
||||||
|
ANDQ AX, R12
|
||||||
|
ADDQ R9, R12
|
||||||
|
ANDQ AX, R14
|
||||||
|
ADDQ R11, R14
|
||||||
|
|
||||||
|
// Second reduction chain (carryPropagate)
|
||||||
|
MOVQ SI, BX
|
||||||
|
SHRQ $0x33, BX
|
||||||
|
MOVQ R8, DI
|
||||||
|
SHRQ $0x33, DI
|
||||||
|
MOVQ R10, R9
|
||||||
|
SHRQ $0x33, R9
|
||||||
|
MOVQ R12, R11
|
||||||
|
SHRQ $0x33, R11
|
||||||
|
MOVQ R14, R13
|
||||||
|
SHRQ $0x33, R13
|
||||||
|
ANDQ AX, SI
|
||||||
|
IMUL3Q $0x13, R13, R13
|
||||||
|
ADDQ R13, SI
|
||||||
|
ANDQ AX, R8
|
||||||
|
ADDQ BX, R8
|
||||||
|
ANDQ AX, R10
|
||||||
|
ADDQ DI, R10
|
||||||
|
ANDQ AX, R12
|
||||||
|
ADDQ R9, R12
|
||||||
|
ANDQ AX, R14
|
||||||
|
ADDQ R11, R14
|
||||||
|
|
||||||
|
// Store output
|
||||||
|
MOVQ out+0(FP), AX
|
||||||
|
MOVQ SI, (AX)
|
||||||
|
MOVQ R8, 8(AX)
|
||||||
|
MOVQ R10, 16(AX)
|
||||||
|
MOVQ R12, 24(AX)
|
||||||
|
MOVQ R14, 32(AX)
|
||||||
|
RET
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !amd64 || !gc || purego
|
||||||
|
// +build !amd64 !gc purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
|
||||||
|
|
||||||
|
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build arm64 && gc && !purego
|
||||||
|
// +build arm64,gc,!purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func carryPropagate(v *Element)
|
||||||
|
|
||||||
|
func (v *Element) carryPropagate() *Element {
|
||||||
|
carryPropagate(v)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build arm64 && gc && !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// carryPropagate works exactly like carryPropagateGeneric and uses the
|
||||||
|
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
|
||||||
|
// avoids loading R0-R4 twice and uses LDP and STP.
|
||||||
|
//
|
||||||
|
// See https://golang.org/issues/43145 for the main compiler issue.
|
||||||
|
//
|
||||||
|
// func carryPropagate(v *Element)
|
||||||
|
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
|
||||||
|
MOVD v+0(FP), R20
|
||||||
|
|
||||||
|
LDP 0(R20), (R0, R1)
|
||||||
|
LDP 16(R20), (R2, R3)
|
||||||
|
MOVD 32(R20), R4
|
||||||
|
|
||||||
|
AND $0x7ffffffffffff, R0, R10
|
||||||
|
AND $0x7ffffffffffff, R1, R11
|
||||||
|
AND $0x7ffffffffffff, R2, R12
|
||||||
|
AND $0x7ffffffffffff, R3, R13
|
||||||
|
AND $0x7ffffffffffff, R4, R14
|
||||||
|
|
||||||
|
ADD R0>>51, R11, R11
|
||||||
|
ADD R1>>51, R12, R12
|
||||||
|
ADD R2>>51, R13, R13
|
||||||
|
ADD R3>>51, R14, R14
|
||||||
|
// R4>>51 * 19 + R10 -> R10
|
||||||
|
LSR $51, R4, R21
|
||||||
|
MOVD $19, R22
|
||||||
|
MADD R22, R10, R21, R10
|
||||||
|
|
||||||
|
STP (R10, R11), 0(R20)
|
||||||
|
STP (R12, R13), 16(R20)
|
||||||
|
MOVD R14, 32(R20)
|
||||||
|
|
||||||
|
RET
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !arm64 || !gc || purego
|
||||||
|
// +build !arm64 !gc purego
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
func (v *Element) carryPropagate() *Element {
|
||||||
|
return v.carryPropagateGeneric()
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// This file contains additional functionality that is not included in the
|
||||||
|
// upstream crypto/ed25519/edwards25519/field package.
|
||||||
|
|
||||||
|
// SetWideBytes sets v to x, where x is a 64-byte little-endian encoding, which
|
||||||
|
// is reduced modulo the field order. If x is not of the right length,
|
||||||
|
// SetWideBytes returns nil and an error, and the receiver is unchanged.
|
||||||
|
//
|
||||||
|
// SetWideBytes is not necessary to select a uniformly distributed value, and is
|
||||||
|
// only provided for compatibility: SetBytes can be used instead as the chance
|
||||||
|
// of bias is less than 2⁻²⁵⁰.
|
||||||
|
func (v *Element) SetWideBytes(x []byte) (*Element, error) {
|
||||||
|
if len(x) != 64 {
|
||||||
|
return nil, errors.New("edwards25519: invalid SetWideBytes input size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the 64 bytes into two elements, and extract the most significant
|
||||||
|
// bit of each, which is ignored by SetBytes.
|
||||||
|
lo, _ := new(Element).SetBytes(x[:32])
|
||||||
|
loMSB := uint64(x[31] >> 7)
|
||||||
|
hi, _ := new(Element).SetBytes(x[32:])
|
||||||
|
hiMSB := uint64(x[63] >> 7)
|
||||||
|
|
||||||
|
// The output we want is
|
||||||
|
//
|
||||||
|
// v = lo + loMSB * 2²⁵⁵ + hi * 2²⁵⁶ + hiMSB * 2⁵¹¹
|
||||||
|
//
|
||||||
|
// which applying the reduction identity comes out to
|
||||||
|
//
|
||||||
|
// v = lo + loMSB * 19 + hi * 2 * 19 + hiMSB * 2 * 19²
|
||||||
|
//
|
||||||
|
// l0 will be the sum of a 52 bits value (lo.l0), plus a 5 bits value
|
||||||
|
// (loMSB * 19), a 6 bits value (hi.l0 * 2 * 19), and a 10 bits value
|
||||||
|
// (hiMSB * 2 * 19²), so it fits in a uint64.
|
||||||
|
|
||||||
|
v.l0 = lo.l0 + loMSB*19 + hi.l0*2*19 + hiMSB*2*19*19
|
||||||
|
v.l1 = lo.l1 + hi.l1*2*19
|
||||||
|
v.l2 = lo.l2 + hi.l2*2*19
|
||||||
|
v.l3 = lo.l3 + hi.l3*2*19
|
||||||
|
v.l4 = lo.l4 + hi.l4*2*19
|
||||||
|
|
||||||
|
return v.carryPropagate(), nil
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
// Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package field
|
||||||
|
|
||||||
|
import "math/bits"
|
||||||
|
|
||||||
|
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
|
||||||
|
// bits.Mul64 and bits.Add64 intrinsics.
|
||||||
|
type uint128 struct {
|
||||||
|
lo, hi uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul64 returns a * b.
|
||||||
|
func mul64(a, b uint64) uint128 {
|
||||||
|
hi, lo := bits.Mul64(a, b)
|
||||||
|
return uint128{lo, hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMul64 returns v + a * b.
|
||||||
|
func addMul64(v uint128, a, b uint64) uint128 {
|
||||||
|
hi, lo := bits.Mul64(a, b)
|
||||||
|
lo, c := bits.Add64(lo, v.lo, 0)
|
||||||
|
hi, _ = bits.Add64(hi, v.hi, c)
|
||||||
|
return uint128{lo, hi}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
|
||||||
|
func shiftRightBy51(a uint128) uint64 {
|
||||||
|
return (a.hi << (64 - 51)) | (a.lo >> 51)
|
||||||
|
}
|
||||||
|
|
||||||
|
func feMulGeneric(v, a, b *Element) {
|
||||||
|
a0 := a.l0
|
||||||
|
a1 := a.l1
|
||||||
|
a2 := a.l2
|
||||||
|
a3 := a.l3
|
||||||
|
a4 := a.l4
|
||||||
|
|
||||||
|
b0 := b.l0
|
||||||
|
b1 := b.l1
|
||||||
|
b2 := b.l2
|
||||||
|
b3 := b.l3
|
||||||
|
b4 := b.l4
|
||||||
|
|
||||||
|
// Limb multiplication works like pen-and-paper columnar multiplication, but
|
||||||
|
// with 51-bit limbs instead of digits.
|
||||||
|
//
|
||||||
|
// a4 a3 a2 a1 a0 x
|
||||||
|
// b4 b3 b2 b1 b0 =
|
||||||
|
// ------------------------
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a4b1 a3b1 a2b1 a1b1 a0b1 +
|
||||||
|
// a4b2 a3b2 a2b2 a1b2 a0b2 +
|
||||||
|
// a4b3 a3b3 a2b3 a1b3 a0b3 +
|
||||||
|
// a4b4 a3b4 a2b4 a1b4 a0b4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
|
||||||
|
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
|
||||||
|
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
|
||||||
|
//
|
||||||
|
// Reduction can be carried out simultaneously to multiplication. For
|
||||||
|
// example, we do not compute r5: whenever the result of a multiplication
|
||||||
|
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
|
||||||
|
//
|
||||||
|
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||||
|
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
|
||||||
|
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
|
||||||
|
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
|
||||||
|
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// Finally we add up the columns into wide, overlapping limbs.
|
||||||
|
|
||||||
|
a1_19 := a1 * 19
|
||||||
|
a2_19 := a2 * 19
|
||||||
|
a3_19 := a3 * 19
|
||||||
|
a4_19 := a4 * 19
|
||||||
|
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
r0 := mul64(a0, b0)
|
||||||
|
r0 = addMul64(r0, a1_19, b4)
|
||||||
|
r0 = addMul64(r0, a2_19, b3)
|
||||||
|
r0 = addMul64(r0, a3_19, b2)
|
||||||
|
r0 = addMul64(r0, a4_19, b1)
|
||||||
|
|
||||||
|
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||||
|
r1 := mul64(a0, b1)
|
||||||
|
r1 = addMul64(r1, a1, b0)
|
||||||
|
r1 = addMul64(r1, a2_19, b4)
|
||||||
|
r1 = addMul64(r1, a3_19, b3)
|
||||||
|
r1 = addMul64(r1, a4_19, b2)
|
||||||
|
|
||||||
|
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||||
|
r2 := mul64(a0, b2)
|
||||||
|
r2 = addMul64(r2, a1, b1)
|
||||||
|
r2 = addMul64(r2, a2, b0)
|
||||||
|
r2 = addMul64(r2, a3_19, b4)
|
||||||
|
r2 = addMul64(r2, a4_19, b3)
|
||||||
|
|
||||||
|
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||||
|
r3 := mul64(a0, b3)
|
||||||
|
r3 = addMul64(r3, a1, b2)
|
||||||
|
r3 = addMul64(r3, a2, b1)
|
||||||
|
r3 = addMul64(r3, a3, b0)
|
||||||
|
r3 = addMul64(r3, a4_19, b4)
|
||||||
|
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
r4 := mul64(a0, b4)
|
||||||
|
r4 = addMul64(r4, a1, b3)
|
||||||
|
r4 = addMul64(r4, a2, b2)
|
||||||
|
r4 = addMul64(r4, a3, b1)
|
||||||
|
r4 = addMul64(r4, a4, b0)
|
||||||
|
|
||||||
|
// After the multiplication, we need to reduce (carry) the five coefficients
|
||||||
|
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
|
||||||
|
// to respect the Element invariant.
|
||||||
|
//
|
||||||
|
// Overall, the reduction works the same as carryPropagate, except with
|
||||||
|
// wider inputs: we take the carry for each coefficient by shifting it right
|
||||||
|
// by 51, and add it to the limb above it. The top carry is multiplied by 19
|
||||||
|
// according to the reduction identity and added to the lowest limb.
|
||||||
|
//
|
||||||
|
// The largest coefficient (r0) will be at most 111 bits, which guarantees
|
||||||
|
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
|
||||||
|
//
|
||||||
|
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||||
|
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
|
||||||
|
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2⁷ × 2⁵² × 2⁵²
|
||||||
|
// r0 < 2¹¹¹
|
||||||
|
//
|
||||||
|
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
|
||||||
|
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
|
||||||
|
// allows us to easily apply the reduction identity.
|
||||||
|
//
|
||||||
|
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||||
|
// r4 < 5 × 2⁵² × 2⁵²
|
||||||
|
// r4 < 2¹⁰⁷
|
||||||
|
//
|
||||||
|
|
||||||
|
c0 := shiftRightBy51(r0)
|
||||||
|
c1 := shiftRightBy51(r1)
|
||||||
|
c2 := shiftRightBy51(r2)
|
||||||
|
c3 := shiftRightBy51(r3)
|
||||||
|
c4 := shiftRightBy51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||||
|
rr1 := r1.lo&maskLow51Bits + c0
|
||||||
|
rr2 := r2.lo&maskLow51Bits + c1
|
||||||
|
rr3 := r3.lo&maskLow51Bits + c2
|
||||||
|
rr4 := r4.lo&maskLow51Bits + c3
|
||||||
|
|
||||||
|
// Now all coefficients fit into 64-bit registers but are still too large to
|
||||||
|
// be passed around as an Element. We therefore do one last carry chain,
|
||||||
|
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
|
||||||
|
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||||
|
v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func feSquareGeneric(v, a *Element) {
|
||||||
|
l0 := a.l0
|
||||||
|
l1 := a.l1
|
||||||
|
l2 := a.l2
|
||||||
|
l3 := a.l3
|
||||||
|
l4 := a.l4
|
||||||
|
|
||||||
|
// Squaring works precisely like multiplication above, but thanks to its
|
||||||
|
// symmetry we get to group a few terms together.
|
||||||
|
//
|
||||||
|
// l4 l3 l2 l1 l0 x
|
||||||
|
// l4 l3 l2 l1 l0 =
|
||||||
|
// ------------------------
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l4l1 l3l1 l2l1 l1l1 l0l1 +
|
||||||
|
// l4l2 l3l2 l2l2 l1l2 l0l2 +
|
||||||
|
// l4l3 l3l3 l2l3 l1l3 l0l3 +
|
||||||
|
// l4l4 l3l4 l2l4 l1l4 l0l4 =
|
||||||
|
// ----------------------------------------------
|
||||||
|
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||||
|
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
|
||||||
|
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
|
||||||
|
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
|
||||||
|
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
|
||||||
|
// --------------------------------------
|
||||||
|
// r4 r3 r2 r1 r0
|
||||||
|
//
|
||||||
|
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
|
||||||
|
// only three Mul64 and four Add64, instead of five and eight.
|
||||||
|
|
||||||
|
l0_2 := l0 * 2
|
||||||
|
l1_2 := l1 * 2
|
||||||
|
|
||||||
|
l1_38 := l1 * 38
|
||||||
|
l2_38 := l2 * 38
|
||||||
|
l3_38 := l3 * 38
|
||||||
|
|
||||||
|
l3_19 := l3 * 19
|
||||||
|
l4_19 := l4 * 19
|
||||||
|
|
||||||
|
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||||
|
r0 := mul64(l0, l0)
|
||||||
|
r0 = addMul64(r0, l1_38, l4)
|
||||||
|
r0 = addMul64(r0, l2_38, l3)
|
||||||
|
|
||||||
|
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||||
|
r1 := mul64(l0_2, l1)
|
||||||
|
r1 = addMul64(r1, l2_38, l4)
|
||||||
|
r1 = addMul64(r1, l3_19, l3)
|
||||||
|
|
||||||
|
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||||
|
r2 := mul64(l0_2, l2)
|
||||||
|
r2 = addMul64(r2, l1, l1)
|
||||||
|
r2 = addMul64(r2, l3_38, l4)
|
||||||
|
|
||||||
|
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||||
|
r3 := mul64(l0_2, l3)
|
||||||
|
r3 = addMul64(r3, l1_2, l2)
|
||||||
|
r3 = addMul64(r3, l4_19, l4)
|
||||||
|
|
||||||
|
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||||
|
r4 := mul64(l0_2, l4)
|
||||||
|
r4 = addMul64(r4, l1_2, l3)
|
||||||
|
r4 = addMul64(r4, l2, l2)
|
||||||
|
|
||||||
|
c0 := shiftRightBy51(r0)
|
||||||
|
c1 := shiftRightBy51(r1)
|
||||||
|
c2 := shiftRightBy51(r2)
|
||||||
|
c3 := shiftRightBy51(r3)
|
||||||
|
c4 := shiftRightBy51(r4)
|
||||||
|
|
||||||
|
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||||
|
rr1 := r1.lo&maskLow51Bits + c0
|
||||||
|
rr2 := r2.lo&maskLow51Bits + c1
|
||||||
|
rr3 := r3.lo&maskLow51Bits + c2
|
||||||
|
rr4 := r4.lo&maskLow51Bits + c3
|
||||||
|
|
||||||
|
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||||
|
v.carryPropagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
|
||||||
|
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
|
||||||
|
func (v *Element) carryPropagateGeneric() *Element {
|
||||||
|
c0 := v.l0 >> 51
|
||||||
|
c1 := v.l1 >> 51
|
||||||
|
c2 := v.l2 >> 51
|
||||||
|
c3 := v.l3 >> 51
|
||||||
|
c4 := v.l4 >> 51
|
||||||
|
|
||||||
|
// c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and
|
||||||
|
// the final l0 will be at most 52 bits. Similarly for the rest.
|
||||||
|
v.l0 = v.l0&maskLow51Bits + c4*19
|
||||||
|
v.l1 = v.l1&maskLow51Bits + c0
|
||||||
|
v.l2 = v.l2&maskLow51Bits + c1
|
||||||
|
v.l3 = v.l3&maskLow51Bits + c2
|
||||||
|
v.l4 = v.l4&maskLow51Bits + c3
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,343 @@
|
||||||
|
// Copyright (c) 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Scalar is an integer modulo
|
||||||
|
//
|
||||||
|
// l = 2^252 + 27742317777372353535851937790883648493
|
||||||
|
//
|
||||||
|
// which is the prime order of the edwards25519 group.
|
||||||
|
//
|
||||||
|
// This type works similarly to math/big.Int, and all arguments and
|
||||||
|
// receivers are allowed to alias.
|
||||||
|
//
|
||||||
|
// The zero value is a valid zero element.
|
||||||
|
type Scalar struct {
|
||||||
|
// s is the scalar in the Montgomery domain, in the format of the
|
||||||
|
// fiat-crypto implementation.
|
||||||
|
s fiatScalarMontgomeryDomainFieldElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// The field implementation in scalar_fiat.go is generated by the fiat-crypto
|
||||||
|
// project (https://github.com/mit-plv/fiat-crypto) at version v0.0.9 (23d2dbc)
|
||||||
|
// from a formally verified model.
|
||||||
|
//
|
||||||
|
// fiat-crypto code comes under the following license.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2015-2020 The fiat-crypto Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY the fiat-crypto authors "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design,
|
||||||
|
// Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
|
||||||
|
// NewScalar returns a new zero Scalar.
|
||||||
|
func NewScalar() *Scalar {
|
||||||
|
return &Scalar{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiplyAdd sets s = x * y + z mod l, and returns s. It is equivalent to
|
||||||
|
// using Multiply and then Add.
|
||||||
|
func (s *Scalar) MultiplyAdd(x, y, z *Scalar) *Scalar {
|
||||||
|
// Make a copy of z in case it aliases s.
|
||||||
|
zCopy := new(Scalar).Set(z)
|
||||||
|
return s.Multiply(x, y).Add(s, zCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets s = x + y mod l, and returns s.
|
||||||
|
func (s *Scalar) Add(x, y *Scalar) *Scalar {
|
||||||
|
// s = 1 * x + y mod l
|
||||||
|
fiatScalarAdd(&s.s, &x.s, &y.s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtract sets s = x - y mod l, and returns s.
|
||||||
|
func (s *Scalar) Subtract(x, y *Scalar) *Scalar {
|
||||||
|
// s = -1 * y + x mod l
|
||||||
|
fiatScalarSub(&s.s, &x.s, &y.s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negate sets s = -x mod l, and returns s.
|
||||||
|
func (s *Scalar) Negate(x *Scalar) *Scalar {
|
||||||
|
// s = -1 * x + 0 mod l
|
||||||
|
fiatScalarOpp(&s.s, &x.s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply sets s = x * y mod l, and returns s.
|
||||||
|
func (s *Scalar) Multiply(x, y *Scalar) *Scalar {
|
||||||
|
// s = x * y + 0 mod l
|
||||||
|
fiatScalarMul(&s.s, &x.s, &y.s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets s = x, and returns s.
|
||||||
|
func (s *Scalar) Set(x *Scalar) *Scalar {
|
||||||
|
*s = *x
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUniformBytes sets s = x mod l, where x is a 64-byte little-endian integer.
|
||||||
|
// If x is not of the right length, SetUniformBytes returns nil and an error,
|
||||||
|
// and the receiver is unchanged.
|
||||||
|
//
|
||||||
|
// SetUniformBytes can be used to set s to a uniformly distributed value given
|
||||||
|
// 64 uniformly distributed random bytes.
|
||||||
|
func (s *Scalar) SetUniformBytes(x []byte) (*Scalar, error) {
|
||||||
|
if len(x) != 64 {
|
||||||
|
return nil, errors.New("edwards25519: invalid SetUniformBytes input length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a value x of 512 bits, but our fiatScalarFromBytes function
|
||||||
|
// expects an input lower than l, which is a little over 252 bits.
|
||||||
|
//
|
||||||
|
// Instead of writing a reduction function that operates on wider inputs, we
|
||||||
|
// can interpret x as the sum of three shorter values a, b, and c.
|
||||||
|
//
|
||||||
|
// x = a + b * 2^168 + c * 2^336 mod l
|
||||||
|
//
|
||||||
|
// We then precompute 2^168 and 2^336 modulo l, and perform the reduction
|
||||||
|
// with two multiplications and two additions.
|
||||||
|
|
||||||
|
s.setShortBytes(x[:21])
|
||||||
|
t := new(Scalar).setShortBytes(x[21:42])
|
||||||
|
s.Add(s, t.Multiply(t, scalarTwo168))
|
||||||
|
t.setShortBytes(x[42:])
|
||||||
|
s.Add(s, t.Multiply(t, scalarTwo336))
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scalarTwo168 and scalarTwo336 are 2^168 and 2^336 modulo l, encoded as a
|
||||||
|
// fiatScalarMontgomeryDomainFieldElement, which is a little-endian 4-limb value
|
||||||
|
// in the 2^256 Montgomery domain.
|
||||||
|
var scalarTwo168 = &Scalar{s: [4]uint64{0x5b8ab432eac74798, 0x38afddd6de59d5d7,
|
||||||
|
0xa2c131b399411b7c, 0x6329a7ed9ce5a30}}
|
||||||
|
var scalarTwo336 = &Scalar{s: [4]uint64{0xbd3d108e2b35ecc5, 0x5c3a3718bdf9c90b,
|
||||||
|
0x63aa97a331b4f2ee, 0x3d217f5be65cb5c}}
|
||||||
|
|
||||||
|
// setShortBytes sets s = x mod l, where x is a little-endian integer shorter
|
||||||
|
// than 32 bytes.
|
||||||
|
func (s *Scalar) setShortBytes(x []byte) *Scalar {
|
||||||
|
if len(x) >= 32 {
|
||||||
|
panic("edwards25519: internal error: setShortBytes called with a long string")
|
||||||
|
}
|
||||||
|
var buf [32]byte
|
||||||
|
copy(buf[:], x)
|
||||||
|
fiatScalarFromBytes((*[4]uint64)(&s.s), &buf)
|
||||||
|
fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s))
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanonicalBytes sets s = x, where x is a 32-byte little-endian encoding of
|
||||||
|
// s, and returns s. If x is not a canonical encoding of s, SetCanonicalBytes
|
||||||
|
// returns nil and an error, and the receiver is unchanged.
|
||||||
|
func (s *Scalar) SetCanonicalBytes(x []byte) (*Scalar, error) {
|
||||||
|
if len(x) != 32 {
|
||||||
|
return nil, errors.New("invalid scalar length")
|
||||||
|
}
|
||||||
|
if !isReduced(x) {
|
||||||
|
return nil, errors.New("invalid scalar encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
fiatScalarFromBytes((*[4]uint64)(&s.s), (*[32]byte)(x))
|
||||||
|
fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s))
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scalarMinusOneBytes is l - 1 in little endian.
|
||||||
|
var scalarMinusOneBytes = [32]byte{236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16}
|
||||||
|
|
||||||
|
// isReduced returns whether the given scalar in 32-byte little endian encoded
|
||||||
|
// form is reduced modulo l.
|
||||||
|
func isReduced(s []byte) bool {
|
||||||
|
if len(s) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
switch {
|
||||||
|
case s[i] > scalarMinusOneBytes[i]:
|
||||||
|
return false
|
||||||
|
case s[i] < scalarMinusOneBytes[i]:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBytesWithClamping applies the buffer pruning described in RFC 8032,
|
||||||
|
// Section 5.1.5 (also known as clamping) and sets s to the result. The input
|
||||||
|
// must be 32 bytes, and it is not modified. If x is not of the right length,
|
||||||
|
// SetBytesWithClamping returns nil and an error, and the receiver is unchanged.
|
||||||
|
//
|
||||||
|
// Note that since Scalar values are always reduced modulo the prime order of
|
||||||
|
// the curve, the resulting value will not preserve any of the cofactor-clearing
|
||||||
|
// properties that clamping is meant to provide. It will however work as
|
||||||
|
// expected as long as it is applied to points on the prime order subgroup, like
|
||||||
|
// in Ed25519. In fact, it is lost to history why RFC 8032 adopted the
|
||||||
|
// irrelevant RFC 7748 clamping, but it is now required for compatibility.
|
||||||
|
func (s *Scalar) SetBytesWithClamping(x []byte) (*Scalar, error) {
|
||||||
|
// The description above omits the purpose of the high bits of the clamping
|
||||||
|
// for brevity, but those are also lost to reductions, and are also
|
||||||
|
// irrelevant to edwards25519 as they protect against a specific
|
||||||
|
// implementation bug that was once observed in a generic Montgomery ladder.
|
||||||
|
if len(x) != 32 {
|
||||||
|
return nil, errors.New("edwards25519: invalid SetBytesWithClamping input length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to use the wide reduction from SetUniformBytes, since clamping
|
||||||
|
// sets the 2^254 bit, making the value higher than the order.
|
||||||
|
var wideBytes [64]byte
|
||||||
|
copy(wideBytes[:], x[:])
|
||||||
|
wideBytes[0] &= 248
|
||||||
|
wideBytes[31] &= 63
|
||||||
|
wideBytes[31] |= 64
|
||||||
|
return s.SetUniformBytes(wideBytes[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the canonical 32-byte little-endian encoding of s.
|
||||||
|
func (s *Scalar) Bytes() []byte {
|
||||||
|
// This function is outlined to make the allocations inline in the caller
|
||||||
|
// rather than happen on the heap.
|
||||||
|
var encoded [32]byte
|
||||||
|
return s.bytes(&encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scalar) bytes(out *[32]byte) []byte {
|
||||||
|
var ss fiatScalarNonMontgomeryDomainFieldElement
|
||||||
|
fiatScalarFromMontgomery(&ss, &s.s)
|
||||||
|
fiatScalarToBytes(out, (*[4]uint64)(&ss))
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns 1 if s and t are equal, and 0 otherwise.
|
||||||
|
func (s *Scalar) Equal(t *Scalar) int {
|
||||||
|
var diff fiatScalarMontgomeryDomainFieldElement
|
||||||
|
fiatScalarSub(&diff, &s.s, &t.s)
|
||||||
|
var nonzero uint64
|
||||||
|
fiatScalarNonzero(&nonzero, (*[4]uint64)(&diff))
|
||||||
|
nonzero |= nonzero >> 32
|
||||||
|
nonzero |= nonzero >> 16
|
||||||
|
nonzero |= nonzero >> 8
|
||||||
|
nonzero |= nonzero >> 4
|
||||||
|
nonzero |= nonzero >> 2
|
||||||
|
nonzero |= nonzero >> 1
|
||||||
|
return int(^nonzero) & 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonAdjacentForm computes a width-w non-adjacent form for this scalar.
|
||||||
|
//
|
||||||
|
// w must be between 2 and 8, or nonAdjacentForm will panic.
|
||||||
|
func (s *Scalar) nonAdjacentForm(w uint) [256]int8 {
|
||||||
|
// This implementation is adapted from the one
|
||||||
|
// in curve25519-dalek and is documented there:
|
||||||
|
// https://github.com/dalek-cryptography/curve25519-dalek/blob/f630041af28e9a405255f98a8a93adca18e4315b/src/scalar.rs#L800-L871
|
||||||
|
b := s.Bytes()
|
||||||
|
if b[31] > 127 {
|
||||||
|
panic("scalar has high bit set illegally")
|
||||||
|
}
|
||||||
|
if w < 2 {
|
||||||
|
panic("w must be at least 2 by the definition of NAF")
|
||||||
|
} else if w > 8 {
|
||||||
|
panic("NAF digits must fit in int8")
|
||||||
|
}
|
||||||
|
|
||||||
|
var naf [256]int8
|
||||||
|
var digits [5]uint64
|
||||||
|
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
digits[i] = binary.LittleEndian.Uint64(b[i*8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
width := uint64(1 << w)
|
||||||
|
windowMask := uint64(width - 1)
|
||||||
|
|
||||||
|
pos := uint(0)
|
||||||
|
carry := uint64(0)
|
||||||
|
for pos < 256 {
|
||||||
|
indexU64 := pos / 64
|
||||||
|
indexBit := pos % 64
|
||||||
|
var bitBuf uint64
|
||||||
|
if indexBit < 64-w {
|
||||||
|
// This window's bits are contained in a single u64
|
||||||
|
bitBuf = digits[indexU64] >> indexBit
|
||||||
|
} else {
|
||||||
|
// Combine the current 64 bits with bits from the next 64
|
||||||
|
bitBuf = (digits[indexU64] >> indexBit) | (digits[1+indexU64] << (64 - indexBit))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add carry into the current window
|
||||||
|
window := carry + (bitBuf & windowMask)
|
||||||
|
|
||||||
|
if window&1 == 0 {
|
||||||
|
// If the window value is even, preserve the carry and continue.
|
||||||
|
// Why is the carry preserved?
|
||||||
|
// If carry == 0 and window & 1 == 0,
|
||||||
|
// then the next carry should be 0
|
||||||
|
// If carry == 1 and window & 1 == 0,
|
||||||
|
// then bit_buf & 1 == 1 so the next carry should be 1
|
||||||
|
pos += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if window < width/2 {
|
||||||
|
carry = 0
|
||||||
|
naf[pos] = int8(window)
|
||||||
|
} else {
|
||||||
|
carry = 1
|
||||||
|
naf[pos] = int8(window) - int8(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += w
|
||||||
|
}
|
||||||
|
return naf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scalar) signedRadix16() [64]int8 {
|
||||||
|
b := s.Bytes()
|
||||||
|
if b[31] > 127 {
|
||||||
|
panic("scalar has high bit set illegally")
|
||||||
|
}
|
||||||
|
|
||||||
|
var digits [64]int8
|
||||||
|
|
||||||
|
// Compute unsigned radix-16 digits:
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
digits[2*i] = int8(b[i] & 15)
|
||||||
|
digits[2*i+1] = int8((b[i] >> 4) & 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recenter coefficients:
|
||||||
|
for i := 0; i < 63; i++ {
|
||||||
|
carry := (digits[i] + 8) >> 4
|
||||||
|
digits[i] -= carry << 4
|
||||||
|
digits[i+1] += carry
|
||||||
|
}
|
||||||
|
|
||||||
|
return digits
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,214 @@
|
||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// basepointTable is a set of 32 affineLookupTables, where table i is generated
|
||||||
|
// from 256i * basepoint. It is precomputed the first time it's used.
|
||||||
|
func basepointTable() *[32]affineLookupTable {
|
||||||
|
basepointTablePrecomp.initOnce.Do(func() {
|
||||||
|
p := NewGeneratorPoint()
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
basepointTablePrecomp.table[i].FromP3(p)
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
p.Add(p, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return &basepointTablePrecomp.table
|
||||||
|
}
|
||||||
|
|
||||||
|
var basepointTablePrecomp struct {
|
||||||
|
table [32]affineLookupTable
|
||||||
|
initOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarBaseMult sets v = x * B, where B is the canonical generator, and
|
||||||
|
// returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
func (v *Point) ScalarBaseMult(x *Scalar) *Point {
|
||||||
|
basepointTable := basepointTable()
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
|
||||||
|
// as described in the Ed25519 paper
|
||||||
|
//
|
||||||
|
// Group even and odd coefficients
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
|
||||||
|
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||||
|
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
|
||||||
|
//
|
||||||
|
// We use a lookup table for each i to get x_i*16^(2*i)*B
|
||||||
|
// and do four doublings to multiply by 16.
|
||||||
|
digits := x.signedRadix16()
|
||||||
|
|
||||||
|
multiple := &affineCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
|
||||||
|
// Accumulate the odd components first
|
||||||
|
v.Set(NewIdentityPoint())
|
||||||
|
for i := 1; i < 64; i += 2 {
|
||||||
|
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||||
|
tmp1.AddAffine(v, multiple)
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply by 16
|
||||||
|
tmp2.FromP3(v) // tmp2 = v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // now v = 16*(odd components)
|
||||||
|
|
||||||
|
// Accumulate the even components
|
||||||
|
for i := 0; i < 64; i += 2 {
|
||||||
|
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||||
|
tmp1.AddAffine(v, multiple)
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarMult sets v = x * q, and returns v.
|
||||||
|
//
|
||||||
|
// The scalar multiplication is done in constant time.
|
||||||
|
func (v *Point) ScalarMult(x *Scalar, q *Point) *Point {
|
||||||
|
checkInitialized(q)
|
||||||
|
|
||||||
|
var table projLookupTable
|
||||||
|
table.FromP3(q)
|
||||||
|
|
||||||
|
// Write x = sum(x_i * 16^i)
|
||||||
|
// so x*Q = sum( Q*x_i*16^i )
|
||||||
|
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
|
||||||
|
// <------compute inside out---------
|
||||||
|
//
|
||||||
|
// We use the lookup table to get the x_i*Q values
|
||||||
|
// and do four doublings to compute 16*Q
|
||||||
|
digits := x.signedRadix16()
|
||||||
|
|
||||||
|
// Unwrap first loop iteration to save computing 16*identity
|
||||||
|
multiple := &projCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
table.SelectInto(multiple, digits[63])
|
||||||
|
|
||||||
|
v.Set(NewIdentityPoint())
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
|
||||||
|
for i := 62; i >= 0; i-- {
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||||
|
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||||
|
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||||
|
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||||
|
table.SelectInto(multiple, digits[i])
|
||||||
|
tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
|
||||||
|
}
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// basepointNafTable is the nafLookupTable8 for the basepoint.
|
||||||
|
// It is precomputed the first time it's used.
|
||||||
|
func basepointNafTable() *nafLookupTable8 {
|
||||||
|
basepointNafTablePrecomp.initOnce.Do(func() {
|
||||||
|
basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint())
|
||||||
|
})
|
||||||
|
return &basepointNafTablePrecomp.table
|
||||||
|
}
|
||||||
|
|
||||||
|
var basepointNafTablePrecomp struct {
|
||||||
|
table nafLookupTable8
|
||||||
|
initOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical
|
||||||
|
// generator, and returns v.
|
||||||
|
//
|
||||||
|
// Execution time depends on the inputs.
|
||||||
|
func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point {
|
||||||
|
checkInitialized(A)
|
||||||
|
|
||||||
|
// Similarly to the single variable-base approach, we compute
|
||||||
|
// digits and use them with a lookup table. However, because
|
||||||
|
// we are allowed to do variable-time operations, we don't
|
||||||
|
// need constant-time lookups or constant-time digit
|
||||||
|
// computations.
|
||||||
|
//
|
||||||
|
// So we use a non-adjacent form of some width w instead of
|
||||||
|
// radix 16. This is like a binary representation (one digit
|
||||||
|
// for each binary place) but we allow the digits to grow in
|
||||||
|
// magnitude up to 2^{w-1} so that the nonzero digits are as
|
||||||
|
// sparse as possible. Intuitively, this "condenses" the
|
||||||
|
// "mass" of the scalar onto sparse coefficients (meaning
|
||||||
|
// fewer additions).
|
||||||
|
|
||||||
|
basepointNafTable := basepointNafTable()
|
||||||
|
var aTable nafLookupTable5
|
||||||
|
aTable.FromP3(A)
|
||||||
|
// Because the basepoint is fixed, we can use a wider NAF
|
||||||
|
// corresponding to a bigger table.
|
||||||
|
aNaf := a.nonAdjacentForm(5)
|
||||||
|
bNaf := b.nonAdjacentForm(8)
|
||||||
|
|
||||||
|
// Find the first nonzero coefficient.
|
||||||
|
i := 255
|
||||||
|
for j := i; j >= 0; j-- {
|
||||||
|
if aNaf[j] != 0 || bNaf[j] != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multA := &projCached{}
|
||||||
|
multB := &affineCached{}
|
||||||
|
tmp1 := &projP1xP1{}
|
||||||
|
tmp2 := &projP2{}
|
||||||
|
tmp2.Zero()
|
||||||
|
|
||||||
|
// Move from high to low bits, doubling the accumulator
|
||||||
|
// at each iteration and checking whether there is a nonzero
|
||||||
|
// coefficient to look up a multiple of.
|
||||||
|
for ; i >= 0; i-- {
|
||||||
|
tmp1.Double(tmp2)
|
||||||
|
|
||||||
|
// Only update v if we have a nonzero coeff to add in.
|
||||||
|
if aNaf[i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
aTable.SelectInto(multA, aNaf[i])
|
||||||
|
tmp1.Add(v, multA)
|
||||||
|
} else if aNaf[i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
aTable.SelectInto(multA, -aNaf[i])
|
||||||
|
tmp1.Sub(v, multA)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bNaf[i] > 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
basepointNafTable.SelectInto(multB, bNaf[i])
|
||||||
|
tmp1.AddAffine(v, multB)
|
||||||
|
} else if bNaf[i] < 0 {
|
||||||
|
v.fromP1xP1(tmp1)
|
||||||
|
basepointNafTable.SelectInto(multB, -bNaf[i])
|
||||||
|
tmp1.SubAffine(v, multB)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp2.FromP1xP1(tmp1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.fromP2(tmp2)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package edwards25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, constant-time scalar muls.
|
||||||
|
type projLookupTable struct {
|
||||||
|
points [8]projCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, constant-time scalar muls.
|
||||||
|
type affineLookupTable struct {
|
||||||
|
points [8]affineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dynamic lookup table for variable-base, variable-time scalar muls.
|
||||||
|
type nafLookupTable5 struct {
|
||||||
|
points [8]projCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precomputed lookup table for fixed-base, variable-time scalar muls.
|
||||||
|
type nafLookupTable8 struct {
|
||||||
|
points [64]affineCached
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors.
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
func (v *projLookupTable) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to a projCached
|
||||||
|
// This is needlessly complicated because the API has explicit
|
||||||
|
// receivers instead of creating stack objects and relying on RVO
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
func (v *affineLookupTable) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||||
|
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
// Compute (i+1)*Q as Q + i*Q and convert to affineCached
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a lookup table at runtime. Fast.
|
||||||
|
func (v *nafLookupTable5) FromP3(q *Point) {
|
||||||
|
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
|
||||||
|
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
q2 := Point{}
|
||||||
|
q2.Add(q, q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||||
|
func (v *nafLookupTable8) FromP3(q *Point) {
|
||||||
|
v.points[0].FromP3(q)
|
||||||
|
q2 := Point{}
|
||||||
|
q2.Add(q, q)
|
||||||
|
tmpP3 := Point{}
|
||||||
|
tmpP1xP1 := projP1xP1{}
|
||||||
|
for i := 0; i < 63; i++ {
|
||||||
|
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectors.
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := uint8((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.Zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||||
|
dest.Select(&v.points[j-1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.CondNeg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||||
|
func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
|
||||||
|
// Compute xabs = |x|
|
||||||
|
xmask := x >> 7
|
||||||
|
xabs := uint8((x + xmask) ^ xmask)
|
||||||
|
|
||||||
|
dest.Zero()
|
||||||
|
for j := 1; j <= 8; j++ {
|
||||||
|
// Set dest = j*Q if |x| = j
|
||||||
|
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||||
|
dest.Select(&v.points[j-1], dest, cond)
|
||||||
|
}
|
||||||
|
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||||
|
dest.CondNeg(int(xmask & 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
|
||||||
|
func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) {
|
||||||
|
*dest = v.points[x/2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
|
||||||
|
func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) {
|
||||||
|
*dest = v.points[x/2]
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft Corporation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,12 @@
|
||||||
|
# go-ansiterm
|
||||||
|
|
||||||
|
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
|
||||||
|
|
||||||
|
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
|
||||||
|
|
||||||
|
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
|
||||||
|
|
||||||
|
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
|
||||||
|
|
||||||
|
-----
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||||
|
|
||||||
|
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||||
|
|
||||||
|
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||||
|
|
||||||
|
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||||
|
|
||||||
|
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||||
|
|
||||||
|
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||||
|
|
||||||
|
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||||
|
* Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
* Any special configuration required to reproduce the issue
|
||||||
|
* Step-by-step instructions to reproduce the issue
|
||||||
|
* Proof-of-concept or exploit code (if possible)
|
||||||
|
* Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||||
|
|
||||||
|
## Preferred Languages
|
||||||
|
|
||||||
|
We prefer all communications to be in English.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||||
|
|
||||||
|
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
@ -0,0 +1,188 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
const LogEnv = "DEBUG_TERMINAL"
|
||||||
|
|
||||||
|
// ANSI constants
|
||||||
|
// References:
|
||||||
|
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||||
|
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||||
|
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
// -- http://vt100.net/emu/dec_ansi_parser
|
||||||
|
// -- http://vt100.net/emu/vt500_parser.svg
|
||||||
|
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
// -- http://www.inwap.com/pdp10/ansicode.txt
|
||||||
|
const (
|
||||||
|
// ECMA-48 Set Graphics Rendition
|
||||||
|
// Note:
|
||||||
|
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
|
||||||
|
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
|
||||||
|
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
|
||||||
|
ANSI_SGR_RESET = 0
|
||||||
|
ANSI_SGR_BOLD = 1
|
||||||
|
ANSI_SGR_DIM = 2
|
||||||
|
_ANSI_SGR_ITALIC = 3
|
||||||
|
ANSI_SGR_UNDERLINE = 4
|
||||||
|
_ANSI_SGR_BLINKSLOW = 5
|
||||||
|
_ANSI_SGR_BLINKFAST = 6
|
||||||
|
ANSI_SGR_REVERSE = 7
|
||||||
|
_ANSI_SGR_INVISIBLE = 8
|
||||||
|
_ANSI_SGR_LINETHROUGH = 9
|
||||||
|
_ANSI_SGR_FONT_00 = 10
|
||||||
|
_ANSI_SGR_FONT_01 = 11
|
||||||
|
_ANSI_SGR_FONT_02 = 12
|
||||||
|
_ANSI_SGR_FONT_03 = 13
|
||||||
|
_ANSI_SGR_FONT_04 = 14
|
||||||
|
_ANSI_SGR_FONT_05 = 15
|
||||||
|
_ANSI_SGR_FONT_06 = 16
|
||||||
|
_ANSI_SGR_FONT_07 = 17
|
||||||
|
_ANSI_SGR_FONT_08 = 18
|
||||||
|
_ANSI_SGR_FONT_09 = 19
|
||||||
|
_ANSI_SGR_FONT_10 = 20
|
||||||
|
_ANSI_SGR_DOUBLEUNDERLINE = 21
|
||||||
|
ANSI_SGR_BOLD_DIM_OFF = 22
|
||||||
|
_ANSI_SGR_ITALIC_OFF = 23
|
||||||
|
ANSI_SGR_UNDERLINE_OFF = 24
|
||||||
|
_ANSI_SGR_BLINK_OFF = 25
|
||||||
|
_ANSI_SGR_RESERVED_00 = 26
|
||||||
|
ANSI_SGR_REVERSE_OFF = 27
|
||||||
|
_ANSI_SGR_INVISIBLE_OFF = 28
|
||||||
|
_ANSI_SGR_LINETHROUGH_OFF = 29
|
||||||
|
ANSI_SGR_FOREGROUND_BLACK = 30
|
||||||
|
ANSI_SGR_FOREGROUND_RED = 31
|
||||||
|
ANSI_SGR_FOREGROUND_GREEN = 32
|
||||||
|
ANSI_SGR_FOREGROUND_YELLOW = 33
|
||||||
|
ANSI_SGR_FOREGROUND_BLUE = 34
|
||||||
|
ANSI_SGR_FOREGROUND_MAGENTA = 35
|
||||||
|
ANSI_SGR_FOREGROUND_CYAN = 36
|
||||||
|
ANSI_SGR_FOREGROUND_WHITE = 37
|
||||||
|
_ANSI_SGR_RESERVED_01 = 38
|
||||||
|
ANSI_SGR_FOREGROUND_DEFAULT = 39
|
||||||
|
ANSI_SGR_BACKGROUND_BLACK = 40
|
||||||
|
ANSI_SGR_BACKGROUND_RED = 41
|
||||||
|
ANSI_SGR_BACKGROUND_GREEN = 42
|
||||||
|
ANSI_SGR_BACKGROUND_YELLOW = 43
|
||||||
|
ANSI_SGR_BACKGROUND_BLUE = 44
|
||||||
|
ANSI_SGR_BACKGROUND_MAGENTA = 45
|
||||||
|
ANSI_SGR_BACKGROUND_CYAN = 46
|
||||||
|
ANSI_SGR_BACKGROUND_WHITE = 47
|
||||||
|
_ANSI_SGR_RESERVED_02 = 48
|
||||||
|
ANSI_SGR_BACKGROUND_DEFAULT = 49
|
||||||
|
// 50 - 65: Unsupported
|
||||||
|
|
||||||
|
ANSI_MAX_CMD_LENGTH = 4096
|
||||||
|
|
||||||
|
MAX_INPUT_EVENTS = 128
|
||||||
|
DEFAULT_WIDTH = 80
|
||||||
|
DEFAULT_HEIGHT = 24
|
||||||
|
|
||||||
|
ANSI_BEL = 0x07
|
||||||
|
ANSI_BACKSPACE = 0x08
|
||||||
|
ANSI_TAB = 0x09
|
||||||
|
ANSI_LINE_FEED = 0x0A
|
||||||
|
ANSI_VERTICAL_TAB = 0x0B
|
||||||
|
ANSI_FORM_FEED = 0x0C
|
||||||
|
ANSI_CARRIAGE_RETURN = 0x0D
|
||||||
|
ANSI_ESCAPE_PRIMARY = 0x1B
|
||||||
|
ANSI_ESCAPE_SECONDARY = 0x5B
|
||||||
|
ANSI_OSC_STRING_ENTRY = 0x5D
|
||||||
|
ANSI_COMMAND_FIRST = 0x40
|
||||||
|
ANSI_COMMAND_LAST = 0x7E
|
||||||
|
DCS_ENTRY = 0x90
|
||||||
|
CSI_ENTRY = 0x9B
|
||||||
|
OSC_STRING = 0x9D
|
||||||
|
ANSI_PARAMETER_SEP = ";"
|
||||||
|
ANSI_CMD_G0 = '('
|
||||||
|
ANSI_CMD_G1 = ')'
|
||||||
|
ANSI_CMD_G2 = '*'
|
||||||
|
ANSI_CMD_G3 = '+'
|
||||||
|
ANSI_CMD_DECPNM = '>'
|
||||||
|
ANSI_CMD_DECPAM = '='
|
||||||
|
ANSI_CMD_OSC = ']'
|
||||||
|
ANSI_CMD_STR_TERM = '\\'
|
||||||
|
|
||||||
|
KEY_CONTROL_PARAM_2 = ";2"
|
||||||
|
KEY_CONTROL_PARAM_3 = ";3"
|
||||||
|
KEY_CONTROL_PARAM_4 = ";4"
|
||||||
|
KEY_CONTROL_PARAM_5 = ";5"
|
||||||
|
KEY_CONTROL_PARAM_6 = ";6"
|
||||||
|
KEY_CONTROL_PARAM_7 = ";7"
|
||||||
|
KEY_CONTROL_PARAM_8 = ";8"
|
||||||
|
KEY_ESC_CSI = "\x1B["
|
||||||
|
KEY_ESC_N = "\x1BN"
|
||||||
|
KEY_ESC_O = "\x1BO"
|
||||||
|
|
||||||
|
FILL_CHARACTER = ' '
|
||||||
|
)
|
||||||
|
|
||||||
|
func getByteRange(start byte, end byte) []byte {
|
||||||
|
bytes := make([]byte, 0, 32)
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
bytes = append(bytes, byte(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var toGroundBytes = getToGroundBytes()
|
||||||
|
var executors = getExecuteBytes()
|
||||||
|
|
||||||
|
// SPACE 20+A0 hex Always and everywhere a blank space
|
||||||
|
// Intermediate 20-2F hex !"#$%&'()*+,-./
|
||||||
|
var intermeds = getByteRange(0x20, 0x2F)
|
||||||
|
|
||||||
|
// Parameters 30-3F hex 0123456789:;<=>?
|
||||||
|
// CSI Parameters 30-39, 3B hex 0123456789;
|
||||||
|
var csiParams = getByteRange(0x30, 0x3F)
|
||||||
|
|
||||||
|
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
|
||||||
|
|
||||||
|
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||||
|
var upperCase = getByteRange(0x40, 0x5F)
|
||||||
|
|
||||||
|
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||||
|
var lowerCase = getByteRange(0x60, 0x7E)
|
||||||
|
|
||||||
|
// Alphabetics 40-7E hex (all of upper and lower case)
|
||||||
|
var alphabetics = append(upperCase, lowerCase...)
|
||||||
|
|
||||||
|
var printables = getByteRange(0x20, 0x7F)
|
||||||
|
|
||||||
|
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
|
||||||
|
var escapeToGroundBytes = getEscapeToGroundBytes()
|
||||||
|
|
||||||
|
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
|
||||||
|
// byte ranges below
|
||||||
|
|
||||||
|
func getEscapeToGroundBytes() []byte {
|
||||||
|
escapeToGroundBytes := getByteRange(0x30, 0x4F)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
|
||||||
|
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
|
||||||
|
return escapeToGroundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecuteBytes() []byte {
|
||||||
|
executeBytes := getByteRange(0x00, 0x17)
|
||||||
|
executeBytes = append(executeBytes, 0x19)
|
||||||
|
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
|
||||||
|
return executeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getToGroundBytes() []byte {
|
||||||
|
groundBytes := []byte{0x18}
|
||||||
|
groundBytes = append(groundBytes, 0x1A)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
groundBytes = append(groundBytes, 0x99)
|
||||||
|
groundBytes = append(groundBytes, 0x9A)
|
||||||
|
groundBytes = append(groundBytes, 0x9C)
|
||||||
|
return groundBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 7F hex Always and everywhere ignored
|
||||||
|
// C1 Control 80-9F hex 32 additional control characters
|
||||||
|
// G1 Displayable A1-FE hex 94 additional displayable characters
|
||||||
|
// Special A0+FF hex Same as SPACE and DELETE
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type ansiContext struct {
|
||||||
|
currentChar byte
|
||||||
|
paramBuffer []byte
|
||||||
|
interBuffer []byte
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||||
|
csiState.parser.logf("CsiEntry::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
return csiState.parser.csiParam, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Transition(s state) error {
|
||||||
|
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
case csiState.parser.csiParam:
|
||||||
|
switch {
|
||||||
|
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||||
|
csiState.parser.collectInter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiEntryState) Enter() error {
|
||||||
|
csiState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type csiParamState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||||
|
csiState.parser.logf("CsiParam::Handle %#x", b)
|
||||||
|
|
||||||
|
nextState, err := csiState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(alphabetics, b):
|
||||||
|
return csiState.parser.ground, nil
|
||||||
|
case sliceContains(csiCollectables, b):
|
||||||
|
csiState.parser.collectParam()
|
||||||
|
return csiState, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return csiState, csiState.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return csiState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (csiState csiParamState) Transition(s state) error {
|
||||||
|
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
|
||||||
|
csiState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case csiState.parser.ground:
|
||||||
|
return csiState.parser.csiDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeIntermediateState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||||
|
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState, escState.parser.collectInter()
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeIntermediateToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeIntermediateState) Transition(s state) error {
|
||||||
|
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type escapeState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||||
|
escState.parser.logf("escapeState::Handle %#x", b)
|
||||||
|
nextState, err := escState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == ANSI_ESCAPE_SECONDARY:
|
||||||
|
return escState.parser.csiEntry, nil
|
||||||
|
case b == ANSI_OSC_STRING_ENTRY:
|
||||||
|
return escState.parser.oscString, nil
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return escState, escState.parser.execute()
|
||||||
|
case sliceContains(escapeToGroundBytes, b):
|
||||||
|
return escState.parser.ground, nil
|
||||||
|
case sliceContains(intermeds, b):
|
||||||
|
return escState.parser.escapeIntermediate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return escState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Transition(s state) error {
|
||||||
|
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
|
||||||
|
escState.baseState.Transition(s)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case escState.parser.ground:
|
||||||
|
return escState.parser.escDispatch()
|
||||||
|
case escState.parser.escapeIntermediate:
|
||||||
|
return escState.parser.collectInter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (escState escapeState) Enter() error {
|
||||||
|
escState.parser.clear()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type AnsiEventHandler interface {
|
||||||
|
// Print
|
||||||
|
Print(b byte) error
|
||||||
|
|
||||||
|
// Execute C0 commands
|
||||||
|
Execute(b byte) error
|
||||||
|
|
||||||
|
// CUrsor Up
|
||||||
|
CUU(int) error
|
||||||
|
|
||||||
|
// CUrsor Down
|
||||||
|
CUD(int) error
|
||||||
|
|
||||||
|
// CUrsor Forward
|
||||||
|
CUF(int) error
|
||||||
|
|
||||||
|
// CUrsor Backward
|
||||||
|
CUB(int) error
|
||||||
|
|
||||||
|
// Cursor to Next Line
|
||||||
|
CNL(int) error
|
||||||
|
|
||||||
|
// Cursor to Previous Line
|
||||||
|
CPL(int) error
|
||||||
|
|
||||||
|
// Cursor Horizontal position Absolute
|
||||||
|
CHA(int) error
|
||||||
|
|
||||||
|
// Vertical line Position Absolute
|
||||||
|
VPA(int) error
|
||||||
|
|
||||||
|
// CUrsor Position
|
||||||
|
CUP(int, int) error
|
||||||
|
|
||||||
|
// Horizontal and Vertical Position (depends on PUM)
|
||||||
|
HVP(int, int) error
|
||||||
|
|
||||||
|
// Text Cursor Enable Mode
|
||||||
|
DECTCEM(bool) error
|
||||||
|
|
||||||
|
// Origin Mode
|
||||||
|
DECOM(bool) error
|
||||||
|
|
||||||
|
// 132 Column Mode
|
||||||
|
DECCOLM(bool) error
|
||||||
|
|
||||||
|
// Erase in Display
|
||||||
|
ED(int) error
|
||||||
|
|
||||||
|
// Erase in Line
|
||||||
|
EL(int) error
|
||||||
|
|
||||||
|
// Insert Line
|
||||||
|
IL(int) error
|
||||||
|
|
||||||
|
// Delete Line
|
||||||
|
DL(int) error
|
||||||
|
|
||||||
|
// Insert Character
|
||||||
|
ICH(int) error
|
||||||
|
|
||||||
|
// Delete Character
|
||||||
|
DCH(int) error
|
||||||
|
|
||||||
|
// Set Graphics Rendition
|
||||||
|
SGR([]int) error
|
||||||
|
|
||||||
|
// Pan Down
|
||||||
|
SU(int) error
|
||||||
|
|
||||||
|
// Pan Up
|
||||||
|
SD(int) error
|
||||||
|
|
||||||
|
// Device Attributes
|
||||||
|
DA([]string) error
|
||||||
|
|
||||||
|
// Set Top and Bottom Margins
|
||||||
|
DECSTBM(int, int) error
|
||||||
|
|
||||||
|
// Index
|
||||||
|
IND() error
|
||||||
|
|
||||||
|
// Reverse Index
|
||||||
|
RI() error
|
||||||
|
|
||||||
|
// Flush updates from previous commands
|
||||||
|
Flush() error
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type groundState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gs groundState) Handle(b byte) (s state, e error) {
|
||||||
|
gs.parser.context.currentChar = b
|
||||||
|
|
||||||
|
nextState, err := gs.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case sliceContains(printables, b):
|
||||||
|
return gs, gs.parser.print()
|
||||||
|
|
||||||
|
case sliceContains(executors, b):
|
||||||
|
return gs, gs.parser.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return gs, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type oscStringState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oscState oscStringState) Handle(b byte) (s state, e error) {
|
||||||
|
oscState.parser.logf("OscString::Handle %#x", b)
|
||||||
|
nextState, err := oscState.baseState.Handle(b)
|
||||||
|
if nextState != nil || err != nil {
|
||||||
|
return nextState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isOscStringTerminator(b):
|
||||||
|
return oscState.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return oscState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// See below for OSC string terminators for linux
|
||||||
|
// http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||||
|
func isOscStringTerminator(b byte) bool {
|
||||||
|
|
||||||
|
if b == ANSI_BEL || b == 0x5C {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnsiParser struct {
|
||||||
|
currState state
|
||||||
|
eventHandler AnsiEventHandler
|
||||||
|
context *ansiContext
|
||||||
|
csiEntry state
|
||||||
|
csiParam state
|
||||||
|
dcsEntry state
|
||||||
|
escape state
|
||||||
|
escapeIntermediate state
|
||||||
|
error state
|
||||||
|
ground state
|
||||||
|
oscString state
|
||||||
|
stateMap []state
|
||||||
|
|
||||||
|
logf func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*AnsiParser)
|
||||||
|
|
||||||
|
func WithLogf(f func(string, ...interface{})) Option {
|
||||||
|
return func(ap *AnsiParser) {
|
||||||
|
ap.logf = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
|
||||||
|
ap := &AnsiParser{
|
||||||
|
eventHandler: evtHandler,
|
||||||
|
context: &ansiContext{},
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(ap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ := os.Create("ansiParser.log")
|
||||||
|
logger := log.New(logFile, "", log.LstdFlags)
|
||||||
|
if ap.logf != nil {
|
||||||
|
l := ap.logf
|
||||||
|
ap.logf = func(s string, v ...interface{}) {
|
||||||
|
l(s, v...)
|
||||||
|
logger.Printf(s, v...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ap.logf = logger.Printf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap.logf == nil {
|
||||||
|
ap.logf = func(string, ...interface{}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
|
||||||
|
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
|
||||||
|
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
|
||||||
|
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
|
||||||
|
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
|
||||||
|
ap.error = errorState{baseState{name: "Error", parser: ap}}
|
||||||
|
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
|
||||||
|
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
|
||||||
|
|
||||||
|
ap.stateMap = []state{
|
||||||
|
ap.csiEntry,
|
||||||
|
ap.csiParam,
|
||||||
|
ap.dcsEntry,
|
||||||
|
ap.escape,
|
||||||
|
ap.escapeIntermediate,
|
||||||
|
ap.error,
|
||||||
|
ap.ground,
|
||||||
|
ap.oscString,
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.currState = getState(initialState, ap.stateMap)
|
||||||
|
|
||||||
|
ap.logf("CreateParser: parser %p", ap)
|
||||||
|
return ap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(name string, states []state) state {
|
||||||
|
for _, el := range states {
|
||||||
|
if el.Name() == name {
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
|
||||||
|
for i, b := range bytes {
|
||||||
|
if err := ap.handle(b); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(bytes), ap.eventHandler.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) handle(b byte) error {
|
||||||
|
ap.context.currentChar = b
|
||||||
|
newState, err := ap.currState.Handle(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState == nil {
|
||||||
|
ap.logf("WARNING: newState is nil")
|
||||||
|
return errors.New("New state of 'nil' is invalid.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState != ap.currState {
|
||||||
|
if err := ap.changeState(newState); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) changeState(newState state) error {
|
||||||
|
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
|
||||||
|
|
||||||
|
// Exit old state
|
||||||
|
if err := ap.currState.Exit(); err != nil {
|
||||||
|
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform transition action
|
||||||
|
if err := ap.currState.Transition(newState); err != nil {
|
||||||
|
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter new state
|
||||||
|
if err := newState.Enter(); err != nil {
|
||||||
|
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.currState = newState
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseParams(bytes []byte) ([]string, error) {
|
||||||
|
paramBuff := make([]byte, 0, 0)
|
||||||
|
params := []string{}
|
||||||
|
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == ';' {
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
// Completed parameter, append it to the list
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
paramBuff = make([]byte, 0, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paramBuff = append(paramBuff, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last parameter may not be terminated with ';'
|
||||||
|
if len(paramBuff) > 0 {
|
||||||
|
s := string(paramBuff)
|
||||||
|
params = append(params, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCmd(context ansiContext) (string, error) {
|
||||||
|
return string(context.currentChar), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(params []string, dflt int) int {
|
||||||
|
i := getInts(params, 1, dflt)[0]
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInts(params []string, minCount int, dflt int) []int {
|
||||||
|
ints := []int{}
|
||||||
|
|
||||||
|
for _, v := range params {
|
||||||
|
i, _ := strconv.Atoi(v)
|
||||||
|
// Zero is mapped to the default value in VT100.
|
||||||
|
if i == 0 {
|
||||||
|
i = dflt
|
||||||
|
}
|
||||||
|
ints = append(ints, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ints) < minCount {
|
||||||
|
remaining := minCount - len(ints)
|
||||||
|
for i := 0; i < remaining; i++ {
|
||||||
|
ints = append(ints, dflt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ints
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
|
||||||
|
switch param {
|
||||||
|
case "?3":
|
||||||
|
return ap.eventHandler.DECCOLM(set)
|
||||||
|
case "?6":
|
||||||
|
return ap.eventHandler.DECOM(set)
|
||||||
|
case "?25":
|
||||||
|
return ap.eventHandler.DECTCEM(set)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) hDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) lDispatch(params []string) error {
|
||||||
|
if len(params) == 1 {
|
||||||
|
return ap.modeDispatch(params[0], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEraseParam(params []string) int {
|
||||||
|
param := getInt(params, 0)
|
||||||
|
if param < 0 || 3 < param {
|
||||||
|
param = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return param
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectParam() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
ap.logf("collectParam %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) collectInter() error {
|
||||||
|
currChar := ap.context.currentChar
|
||||||
|
ap.logf("collectInter %#x", currChar)
|
||||||
|
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) escDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
intermeds := ap.context.interBuffer
|
||||||
|
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
|
||||||
|
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "D": // IND
|
||||||
|
return ap.eventHandler.IND()
|
||||||
|
case "E": // NEL, equivalent to CRLF
|
||||||
|
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
|
||||||
|
if err == nil {
|
||||||
|
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case "M": // RI
|
||||||
|
return ap.eventHandler.RI()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) csiDispatch() error {
|
||||||
|
cmd, _ := parseCmd(*ap.context)
|
||||||
|
params, _ := parseParams(ap.context.paramBuffer)
|
||||||
|
ap.logf("Parsed params: %v with length: %d", params, len(params))
|
||||||
|
|
||||||
|
ap.logf("csiDispatch: %v(%v)", cmd, params)
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "@":
|
||||||
|
return ap.eventHandler.ICH(getInt(params, 1))
|
||||||
|
case "A":
|
||||||
|
return ap.eventHandler.CUU(getInt(params, 1))
|
||||||
|
case "B":
|
||||||
|
return ap.eventHandler.CUD(getInt(params, 1))
|
||||||
|
case "C":
|
||||||
|
return ap.eventHandler.CUF(getInt(params, 1))
|
||||||
|
case "D":
|
||||||
|
return ap.eventHandler.CUB(getInt(params, 1))
|
||||||
|
case "E":
|
||||||
|
return ap.eventHandler.CNL(getInt(params, 1))
|
||||||
|
case "F":
|
||||||
|
return ap.eventHandler.CPL(getInt(params, 1))
|
||||||
|
case "G":
|
||||||
|
return ap.eventHandler.CHA(getInt(params, 1))
|
||||||
|
case "H":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.CUP(x, y)
|
||||||
|
case "J":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.ED(param)
|
||||||
|
case "K":
|
||||||
|
param := getEraseParam(params)
|
||||||
|
return ap.eventHandler.EL(param)
|
||||||
|
case "L":
|
||||||
|
return ap.eventHandler.IL(getInt(params, 1))
|
||||||
|
case "M":
|
||||||
|
return ap.eventHandler.DL(getInt(params, 1))
|
||||||
|
case "P":
|
||||||
|
return ap.eventHandler.DCH(getInt(params, 1))
|
||||||
|
case "S":
|
||||||
|
return ap.eventHandler.SU(getInt(params, 1))
|
||||||
|
case "T":
|
||||||
|
return ap.eventHandler.SD(getInt(params, 1))
|
||||||
|
case "c":
|
||||||
|
return ap.eventHandler.DA(params)
|
||||||
|
case "d":
|
||||||
|
return ap.eventHandler.VPA(getInt(params, 1))
|
||||||
|
case "f":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
x, y := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.HVP(x, y)
|
||||||
|
case "h":
|
||||||
|
return ap.hDispatch(params)
|
||||||
|
case "l":
|
||||||
|
return ap.lDispatch(params)
|
||||||
|
case "m":
|
||||||
|
return ap.eventHandler.SGR(getInts(params, 1, 0))
|
||||||
|
case "r":
|
||||||
|
ints := getInts(params, 2, 1)
|
||||||
|
top, bottom := ints[0], ints[1]
|
||||||
|
return ap.eventHandler.DECSTBM(top, bottom)
|
||||||
|
default:
|
||||||
|
ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) print() error {
|
||||||
|
return ap.eventHandler.Print(ap.context.currentChar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) clear() error {
|
||||||
|
ap.context = &ansiContext{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap *AnsiParser) execute() error {
|
||||||
|
return ap.eventHandler.Execute(ap.context.currentChar)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
type stateID int
|
||||||
|
|
||||||
|
type state interface {
|
||||||
|
Enter() error
|
||||||
|
Exit() error
|
||||||
|
Handle(byte) (state, error)
|
||||||
|
Name() string
|
||||||
|
Transition(state) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseState struct {
|
||||||
|
name string
|
||||||
|
parser *AnsiParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Enter() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Exit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Handle(b byte) (s state, e error) {
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == CSI_ENTRY:
|
||||||
|
return base.parser.csiEntry, nil
|
||||||
|
case b == DCS_ENTRY:
|
||||||
|
return base.parser.dcsEntry, nil
|
||||||
|
case b == ANSI_ESCAPE_PRIMARY:
|
||||||
|
return base.parser.escape, nil
|
||||||
|
case b == OSC_STRING:
|
||||||
|
return base.parser.oscString, nil
|
||||||
|
case sliceContains(toGroundBytes, b):
|
||||||
|
return base.parser.ground, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Name() string {
|
||||||
|
return base.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (base baseState) Transition(s state) error {
|
||||||
|
if s == base.parser.ground {
|
||||||
|
execBytes := []byte{0x18}
|
||||||
|
execBytes = append(execBytes, 0x1A)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
|
||||||
|
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
|
||||||
|
execBytes = append(execBytes, 0x99)
|
||||||
|
execBytes = append(execBytes, 0x9A)
|
||||||
|
|
||||||
|
if sliceContains(execBytes, base.parser.context.currentChar) {
|
||||||
|
return base.parser.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dcsEntryState struct {
|
||||||
|
baseState
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorState struct {
|
||||||
|
baseState
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package ansiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sliceContains(bytes []byte, b byte) bool {
|
||||||
|
for _, v := range bytes {
|
||||||
|
if v == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertBytesToInteger(bytes []byte) int {
|
||||||
|
s := string(bytes)
|
||||||
|
i, _ := strconv.Atoi(s)
|
||||||
|
return i
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm"
|
||||||
|
windows "golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows keyboard constants
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
|
||||||
|
const (
|
||||||
|
VK_PRIOR = 0x21 // PAGE UP key
|
||||||
|
VK_NEXT = 0x22 // PAGE DOWN key
|
||||||
|
VK_END = 0x23 // END key
|
||||||
|
VK_HOME = 0x24 // HOME key
|
||||||
|
VK_LEFT = 0x25 // LEFT ARROW key
|
||||||
|
VK_UP = 0x26 // UP ARROW key
|
||||||
|
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||||
|
VK_DOWN = 0x28 // DOWN ARROW key
|
||||||
|
VK_SELECT = 0x29 // SELECT key
|
||||||
|
VK_PRINT = 0x2A // PRINT key
|
||||||
|
VK_EXECUTE = 0x2B // EXECUTE key
|
||||||
|
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||||
|
VK_INSERT = 0x2D // INS key
|
||||||
|
VK_DELETE = 0x2E // DEL key
|
||||||
|
VK_HELP = 0x2F // HELP key
|
||||||
|
VK_F1 = 0x70 // F1 key
|
||||||
|
VK_F2 = 0x71 // F2 key
|
||||||
|
VK_F3 = 0x72 // F3 key
|
||||||
|
VK_F4 = 0x73 // F4 key
|
||||||
|
VK_F5 = 0x74 // F5 key
|
||||||
|
VK_F6 = 0x75 // F6 key
|
||||||
|
VK_F7 = 0x76 // F7 key
|
||||||
|
VK_F8 = 0x77 // F8 key
|
||||||
|
VK_F9 = 0x78 // F9 key
|
||||||
|
VK_F10 = 0x79 // F10 key
|
||||||
|
VK_F11 = 0x7A // F11 key
|
||||||
|
VK_F12 = 0x7B // F12 key
|
||||||
|
|
||||||
|
RIGHT_ALT_PRESSED = 0x0001
|
||||||
|
LEFT_ALT_PRESSED = 0x0002
|
||||||
|
RIGHT_CTRL_PRESSED = 0x0004
|
||||||
|
LEFT_CTRL_PRESSED = 0x0008
|
||||||
|
SHIFT_PRESSED = 0x0010
|
||||||
|
NUMLOCK_ON = 0x0020
|
||||||
|
SCROLLLOCK_ON = 0x0040
|
||||||
|
CAPSLOCK_ON = 0x0080
|
||||||
|
ENHANCED_KEY = 0x0100
|
||||||
|
)
|
||||||
|
|
||||||
|
type ansiCommand struct {
|
||||||
|
CommandBytes []byte
|
||||||
|
Command string
|
||||||
|
Parameters []string
|
||||||
|
IsSpecial bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnsiCommand(command []byte) *ansiCommand {
|
||||||
|
|
||||||
|
if isCharacterSelectionCmdChar(command[1]) {
|
||||||
|
// Is Character Set Selection commands
|
||||||
|
return &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command),
|
||||||
|
IsSpecial: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last char is command character
|
||||||
|
lastCharIndex := len(command) - 1
|
||||||
|
|
||||||
|
ac := &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command[lastCharIndex]),
|
||||||
|
IsSpecial: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than a single escape
|
||||||
|
if lastCharIndex != 0 {
|
||||||
|
start := 1
|
||||||
|
// skip if double char escape sequence
|
||||||
|
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
// convert this to GetNextParam method
|
||||||
|
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
|
||||||
|
if index < 0 || index >= len(ac.Parameters) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) String() string {
|
||||||
|
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||||
|
bytesToHex(ac.CommandBytes),
|
||||||
|
ac.Command,
|
||||||
|
strings.Join(ac.Parameters, "\",\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
|
||||||
|
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
|
||||||
|
func isAnsiCommandChar(b byte) bool {
|
||||||
|
switch {
|
||||||
|
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
|
||||||
|
// non-CSI escape sequence terminator
|
||||||
|
return true
|
||||||
|
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
|
||||||
|
// String escape sequence terminator
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isXtermOscSequence(command []byte, current byte) bool {
|
||||||
|
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCharacterSelectionCmdChar(b byte) bool {
|
||||||
|
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToHex converts a slice of bytes to a human-readable string.
|
||||||
|
func bytesToHex(b []byte) string {
|
||||||
|
hex := make([]string, len(b))
|
||||||
|
for i, ch := range b {
|
||||||
|
hex[i] = fmt.Sprintf("%X", ch)
|
||||||
|
}
|
||||||
|
return strings.Join(hex, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
|
||||||
|
// the passed min / max range.
|
||||||
|
func ensureInRange(n int16, min int16, max int16) int16 {
|
||||||
|
if n < min {
|
||||||
|
return min
|
||||||
|
} else if n > max {
|
||||||
|
return max
|
||||||
|
} else {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStdFile(nFile int) (*os.File, uintptr) {
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
// syscall uses negative numbers
|
||||||
|
// windows package uses very big uint32
|
||||||
|
// Keep these switches split so we don't have to convert ints too much.
|
||||||
|
switch uint32(nFile) {
|
||||||
|
case windows.STD_INPUT_HANDLE:
|
||||||
|
file = os.Stdin
|
||||||
|
case windows.STD_OUTPUT_HANDLE:
|
||||||
|
file = os.Stdout
|
||||||
|
case windows.STD_ERROR_HANDLE:
|
||||||
|
file = os.Stderr
|
||||||
|
default:
|
||||||
|
switch nFile {
|
||||||
|
case syscall.STD_INPUT_HANDLE:
|
||||||
|
file = os.Stdin
|
||||||
|
case syscall.STD_OUTPUT_HANDLE:
|
||||||
|
file = os.Stdout
|
||||||
|
case syscall.STD_ERROR_HANDLE:
|
||||||
|
file = os.Stderr
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := syscall.GetStdHandle(nFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, uintptr(fd)
|
||||||
|
}
|
|
@ -0,0 +1,327 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//===========================================================================================================
|
||||||
|
// IMPORTANT NOTE:
|
||||||
|
//
|
||||||
|
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
|
||||||
|
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
|
||||||
|
// variables) the pointers reference *before* the API completes.
|
||||||
|
//
|
||||||
|
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
|
||||||
|
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
|
||||||
|
// require unsafe pointers.
|
||||||
|
//
|
||||||
|
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
|
||||||
|
// the garbage collector the variables remain in use if:
|
||||||
|
//
|
||||||
|
// -- The value is not a pointer (e.g., int32, struct)
|
||||||
|
// -- The value is not referenced by the method after passing the pointer to Windows
|
||||||
|
//
|
||||||
|
// See http://golang.org/doc/go1.3.
|
||||||
|
//===========================================================================================================
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
|
||||||
|
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
|
||||||
|
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
|
||||||
|
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||||
|
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
|
||||||
|
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
|
||||||
|
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
|
||||||
|
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
|
||||||
|
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
|
||||||
|
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
|
||||||
|
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows Console constants
|
||||||
|
const (
|
||||||
|
// Console modes
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
ENABLE_PROCESSED_INPUT = 0x0001
|
||||||
|
ENABLE_LINE_INPUT = 0x0002
|
||||||
|
ENABLE_ECHO_INPUT = 0x0004
|
||||||
|
ENABLE_WINDOW_INPUT = 0x0008
|
||||||
|
ENABLE_MOUSE_INPUT = 0x0010
|
||||||
|
ENABLE_INSERT_MODE = 0x0020
|
||||||
|
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||||
|
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||||
|
ENABLE_AUTO_POSITION = 0x0100
|
||||||
|
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
|
||||||
|
|
||||||
|
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||||
|
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||||
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||||
|
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
|
||||||
|
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
|
||||||
|
|
||||||
|
// Character attributes
|
||||||
|
// Note:
|
||||||
|
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
|
||||||
|
// Clearing all foreground or background colors results in black; setting all creates white.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
|
||||||
|
FOREGROUND_BLUE uint16 = 0x0001
|
||||||
|
FOREGROUND_GREEN uint16 = 0x0002
|
||||||
|
FOREGROUND_RED uint16 = 0x0004
|
||||||
|
FOREGROUND_INTENSITY uint16 = 0x0008
|
||||||
|
FOREGROUND_MASK uint16 = 0x000F
|
||||||
|
|
||||||
|
BACKGROUND_BLUE uint16 = 0x0010
|
||||||
|
BACKGROUND_GREEN uint16 = 0x0020
|
||||||
|
BACKGROUND_RED uint16 = 0x0040
|
||||||
|
BACKGROUND_INTENSITY uint16 = 0x0080
|
||||||
|
BACKGROUND_MASK uint16 = 0x00F0
|
||||||
|
|
||||||
|
COMMON_LVB_MASK uint16 = 0xFF00
|
||||||
|
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
|
||||||
|
COMMON_LVB_UNDERSCORE uint16 = 0x8000
|
||||||
|
|
||||||
|
// Input event types
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
KEY_EVENT = 0x0001
|
||||||
|
MOUSE_EVENT = 0x0002
|
||||||
|
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
||||||
|
MENU_EVENT = 0x0008
|
||||||
|
FOCUS_EVENT = 0x0010
|
||||||
|
|
||||||
|
// WaitForSingleObject return codes
|
||||||
|
WAIT_ABANDONED = 0x00000080
|
||||||
|
WAIT_FAILED = 0xFFFFFFFF
|
||||||
|
WAIT_SIGNALED = 0x0000000
|
||||||
|
WAIT_TIMEOUT = 0x00000102
|
||||||
|
|
||||||
|
// WaitForSingleObject wait duration
|
||||||
|
WAIT_INFINITE = 0xFFFFFFFF
|
||||||
|
WAIT_ONE_SECOND = 1000
|
||||||
|
WAIT_HALF_SECOND = 500
|
||||||
|
WAIT_QUARTER_SECOND = 250
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows API Console types
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
|
||||||
|
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
|
||||||
|
type (
|
||||||
|
CHAR_INFO struct {
|
||||||
|
UnicodeChar uint16
|
||||||
|
Attributes uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_CURSOR_INFO struct {
|
||||||
|
Size uint32
|
||||||
|
Visible int32
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||||
|
Size COORD
|
||||||
|
CursorPosition COORD
|
||||||
|
Attributes uint16
|
||||||
|
Window SMALL_RECT
|
||||||
|
MaximumWindowSize COORD
|
||||||
|
}
|
||||||
|
|
||||||
|
COORD struct {
|
||||||
|
X int16
|
||||||
|
Y int16
|
||||||
|
}
|
||||||
|
|
||||||
|
SMALL_RECT struct {
|
||||||
|
Left int16
|
||||||
|
Top int16
|
||||||
|
Right int16
|
||||||
|
Bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||||
|
INPUT_RECORD struct {
|
||||||
|
EventType uint16
|
||||||
|
KeyEvent KEY_EVENT_RECORD
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY_EVENT_RECORD struct {
|
||||||
|
KeyDown int32
|
||||||
|
RepeatCount uint16
|
||||||
|
VirtualKeyCode uint16
|
||||||
|
VirtualScanCode uint16
|
||||||
|
UnicodeChar uint16
|
||||||
|
ControlKeyState uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
WINDOW_BUFFER_SIZE struct {
|
||||||
|
Size COORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// boolToBOOL converts a Go bool into a Windows int32.
|
||||||
|
func boolToBOOL(f bool) int32 {
|
||||||
|
if f {
|
||||||
|
return int32(1)
|
||||||
|
} else {
|
||||||
|
return int32(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
|
||||||
|
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||||
|
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleCursorPosition location of the console cursor.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
|
||||||
|
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleMode gets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
|
||||||
|
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
|
||||||
|
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleMode sets the console mode for given file descriptor
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||||
|
func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||||
|
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
|
||||||
|
use(mode)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
|
||||||
|
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
info := CONSOLE_SCREEN_BUFFER_INFO{}
|
||||||
|
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
|
||||||
|
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
|
||||||
|
use(scrollRect)
|
||||||
|
use(clipRect)
|
||||||
|
use(destOrigin)
|
||||||
|
use(char)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
|
||||||
|
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||||
|
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
|
||||||
|
use(coord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleTextAttribute sets the attributes of characters written to the
|
||||||
|
// console screen buffer by the WriteFile or WriteConsole function.
|
||||||
|
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
|
||||||
|
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||||
|
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
|
||||||
|
use(attribute)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
|
||||||
|
// Note that the size and location must be within and no larger than the backing console screen buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
|
||||||
|
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
|
||||||
|
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
|
||||||
|
use(isAbsolute)
|
||||||
|
use(rect)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
|
||||||
|
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
|
||||||
|
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
|
||||||
|
use(buffer)
|
||||||
|
use(bufferSize)
|
||||||
|
use(bufferCoord)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConsoleInput reads (and removes) data from the console input buffer.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
|
||||||
|
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
|
||||||
|
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
|
||||||
|
use(buffer)
|
||||||
|
return checkError(r1, r2, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForSingleObject waits for the passed handle to be signaled.
|
||||||
|
// It returns true if the handle was signaled; false otherwise.
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
|
||||||
|
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
|
||||||
|
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
|
||||||
|
switch r1 {
|
||||||
|
case WAIT_ABANDONED, WAIT_TIMEOUT:
|
||||||
|
return false, nil
|
||||||
|
case WAIT_SIGNALED:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
use(msWait)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String helpers
|
||||||
|
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
|
||||||
|
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (coord COORD) String() string {
|
||||||
|
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rect SMALL_RECT) String() string {
|
||||||
|
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkError evaluates the results of a Windows API call and returns the error if it failed.
|
||||||
|
func checkError(r1, r2 uintptr, err error) error {
|
||||||
|
// Windows APIs return non-zero to indicate success
|
||||||
|
if r1 != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the error if provided, otherwise default to EINVAL
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
|
||||||
|
func coordToPointer(c COORD) uintptr {
|
||||||
|
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
|
||||||
|
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// use is a no-op, but the compiler cannot see that it is.
|
||||||
|
// Calling use(p) ensures that p is kept live until that point.
|
||||||
|
func use(p interface{}) {}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
)
|
||||||
|
|
||||||
|
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
|
||||||
|
// request represented by the passed ANSI mode.
|
||||||
|
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
|
||||||
|
switch ansiMode {
|
||||||
|
|
||||||
|
// Mode styles
|
||||||
|
case ansiterm.ANSI_SGR_BOLD:
|
||||||
|
windowsMode = windowsMode | FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
|
||||||
|
windowsMode &^= FOREGROUND_INTENSITY
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE:
|
||||||
|
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE:
|
||||||
|
inverted = true
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_REVERSE_OFF:
|
||||||
|
inverted = false
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
|
||||||
|
windowsMode &^= COMMON_LVB_UNDERSCORE
|
||||||
|
|
||||||
|
// Foreground colors
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
|
||||||
|
// Black with no intensity
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_RED:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
|
||||||
|
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
|
||||||
|
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
return windowsMode, inverted
|
||||||
|
}
|
||||||
|
|
||||||
|
// invertAttributes inverts the foreground and background colors of a Windows attributes value
|
||||||
|
func invertAttributes(windowsMode uint16) uint16 {
|
||||||
|
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
const (
|
||||||
|
horizontal = iota
|
||||||
|
vertical
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
|
||||||
|
if h.originMode {
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SMALL_RECT{
|
||||||
|
Top: info.Window.Top,
|
||||||
|
Bottom: info.Window.Bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
|
||||||
|
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
|
||||||
|
position.X = ensureInRange(position.X, window.Left, window.Right)
|
||||||
|
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
|
||||||
|
err := SetConsoleCursorPosition(h.fd, position)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
|
||||||
|
return h.moveCursor(vertical, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
|
||||||
|
return h.moveCursor(horizontal, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
switch moveMode {
|
||||||
|
case horizontal:
|
||||||
|
position.X += int16(param)
|
||||||
|
case vertical:
|
||||||
|
position.Y += int16(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = 0
|
||||||
|
position.Y += int16(param)
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.X = int16(param) - 1
|
||||||
|
|
||||||
|
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import "github.com/Azure/go-ansiterm"
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
// Ignore an invalid (negative area) request
|
||||||
|
if toCoord.Y < fromCoord.Y {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var coordStart = COORD{}
|
||||||
|
var coordEnd = COORD{}
|
||||||
|
|
||||||
|
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
|
||||||
|
xEnd, yEnd := toCoord.X, toCoord.Y
|
||||||
|
|
||||||
|
// Clear any partial initial line
|
||||||
|
if xCurrent > 0 {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yCurrent
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear intervening rectangular section
|
||||||
|
if yCurrent < yEnd {
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xCurrent = 0
|
||||||
|
yCurrent = yEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remaining partial ending line
|
||||||
|
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||||
|
coordEnd.X, coordEnd.Y = xEnd, yEnd
|
||||||
|
|
||||||
|
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||||
|
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
|
||||||
|
width := toCoord.X - fromCoord.X + 1
|
||||||
|
height := toCoord.Y - fromCoord.Y + 1
|
||||||
|
size := uint32(width) * uint32(height)
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]CHAR_INFO, size)
|
||||||
|
|
||||||
|
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
buffer[i] = char
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
// effectiveSr gets the current effective scroll region in buffer coordinates
|
||||||
|
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
|
||||||
|
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
|
||||||
|
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
|
||||||
|
if top >= bottom {
|
||||||
|
top = window.Top
|
||||||
|
bottom = window.Bottom
|
||||||
|
}
|
||||||
|
return scrollRegion{top: top, bottom: bottom}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
|
||||||
|
return h.scrollUp(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
start := info.CursorPosition.Y
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
// Lines cannot be inserted or deleted outside the scrolling region.
|
||||||
|
if start >= sr.top && start <= sr.bottom {
|
||||||
|
sr.top = start
|
||||||
|
return h.scroll(param, sr, info)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertLines(param int) error {
|
||||||
|
return h.deleteLines(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
|
||||||
|
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
|
||||||
|
h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
|
||||||
|
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: sr.top,
|
||||||
|
Bottom: sr.bottom,
|
||||||
|
Left: 0,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: 0,
|
||||||
|
Y: sr.top - int16(param),
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.scrollLine(param, info.CursorPosition, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
|
||||||
|
return h.deleteCharacters(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
|
||||||
|
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
// Copy from and clip to the scroll region (full buffer width)
|
||||||
|
scrollRect := SMALL_RECT{
|
||||||
|
Top: position.Y,
|
||||||
|
Bottom: position.Y,
|
||||||
|
Left: position.X,
|
||||||
|
Right: info.Size.X - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin to which area should be copied
|
||||||
|
destOrigin := COORD{
|
||||||
|
X: position.X - int16(columns),
|
||||||
|
Y: position.Y,
|
||||||
|
}
|
||||||
|
|
||||||
|
char := CHAR_INFO{
|
||||||
|
UnicodeChar: ' ',
|
||||||
|
Attributes: h.attributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
// AddInRange increments a value by the passed quantity while ensuring the values
|
||||||
|
// always remain within the supplied min / max range.
|
||||||
|
func addInRange(n int16, increment int16, min int16, max int16) int16 {
|
||||||
|
return ensureInRange(n+increment, min, max)
|
||||||
|
}
|
|
@ -0,0 +1,743 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Azure/go-ansiterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type windowsAnsiEventHandler struct {
|
||||||
|
fd uintptr
|
||||||
|
file *os.File
|
||||||
|
infoReset *CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
sr scrollRegion
|
||||||
|
buffer bytes.Buffer
|
||||||
|
attributes uint16
|
||||||
|
inverted bool
|
||||||
|
wrapNext bool
|
||||||
|
drewMarginByte bool
|
||||||
|
originMode bool
|
||||||
|
marginByte byte
|
||||||
|
curInfo *CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
curPos COORD
|
||||||
|
logf func(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*windowsAnsiEventHandler)
|
||||||
|
|
||||||
|
func WithLogf(f func(string, ...interface{})) Option {
|
||||||
|
return func(w *windowsAnsiEventHandler) {
|
||||||
|
w.logf = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
|
||||||
|
infoReset, err := GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &windowsAnsiEventHandler{
|
||||||
|
fd: fd,
|
||||||
|
file: file,
|
||||||
|
infoReset: infoReset,
|
||||||
|
attributes: infoReset.Attributes,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
||||||
|
logFile, _ := os.Create("winEventHandler.log")
|
||||||
|
logger := log.New(logFile, "", log.LstdFlags)
|
||||||
|
if h.logf != nil {
|
||||||
|
l := h.logf
|
||||||
|
h.logf = func(s string, v ...interface{}) {
|
||||||
|
l(s, v...)
|
||||||
|
logger.Printf(s, v...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.logf = logger.Printf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.logf == nil {
|
||||||
|
h.logf = func(string, ...interface{}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
type scrollRegion struct {
|
||||||
|
top int16
|
||||||
|
bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
|
||||||
|
// current cursor position and scroll region settings, in which case it returns
|
||||||
|
// true. If no special handling is necessary, then it does nothing and returns
|
||||||
|
// false.
|
||||||
|
//
|
||||||
|
// In the false case, the caller should ensure that a carriage return
|
||||||
|
// and line feed are inserted or that the text is otherwise wrapped.
|
||||||
|
func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
if pos.Y == sr.bottom {
|
||||||
|
// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
|
||||||
|
// is the full window.
|
||||||
|
if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
h.updatePos(pos)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom scroll region is active. Scroll the window manually to simulate
|
||||||
|
// the LF.
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
h.logf("Simulating LF inside scroll region")
|
||||||
|
if err := h.scrollUp(1); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
|
||||||
|
} else if pos.Y < info.Window.Bottom {
|
||||||
|
// Let Windows handle the LF.
|
||||||
|
pos.Y++
|
||||||
|
if includeCR {
|
||||||
|
pos.X = 0
|
||||||
|
}
|
||||||
|
h.updatePos(pos)
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
// The cursor is at the bottom of the screen but outside the scroll
|
||||||
|
// region. Skip the LF.
|
||||||
|
h.logf("Simulating LF outside scroll region")
|
||||||
|
if includeCR {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
pos.X = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeLF executes a LF without a CR.
|
||||||
|
func (h *windowsAnsiEventHandler) executeLF() error {
|
||||||
|
handled, err := h.simulateLF(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !handled {
|
||||||
|
// Windows LF will reset the cursor column position. Write the LF
|
||||||
|
// and restore the cursor position.
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||||
|
if pos.X != 0 {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("Resetting cursor position for LF without CR")
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Print(b byte) error {
|
||||||
|
if h.wrapNext {
|
||||||
|
h.buffer.WriteByte(h.marginByte)
|
||||||
|
h.clearWrap()
|
||||||
|
if _, err := h.simulateLF(true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X == info.Size.X-1 {
|
||||||
|
h.wrapNext = true
|
||||||
|
h.marginByte = b
|
||||||
|
} else {
|
||||||
|
pos.X++
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(b)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Execute(b byte) error {
|
||||||
|
switch b {
|
||||||
|
case ansiterm.ANSI_TAB:
|
||||||
|
h.logf("Execute(TAB)")
|
||||||
|
// Move to the next tab stop, but preserve auto-wrap if already set.
|
||||||
|
if !h.wrapNext {
|
||||||
|
pos, info, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pos.X = (pos.X + 8) - pos.X%8
|
||||||
|
if pos.X >= info.Size.X {
|
||||||
|
pos.X = info.Size.X - 1
|
||||||
|
}
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_BEL:
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_BEL)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_BACKSPACE:
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X > 0 {
|
||||||
|
pos.X--
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
|
||||||
|
// Treat as true LF.
|
||||||
|
return h.executeLF()
|
||||||
|
|
||||||
|
case ansiterm.ANSI_LINE_FEED:
|
||||||
|
// Simulate a CR and LF for now since there is no way in go-ansiterm
|
||||||
|
// to tell if the LF should include CR (and more things break when it's
|
||||||
|
// missing than when it's incorrectly added).
|
||||||
|
handled, err := h.simulateLF(true)
|
||||||
|
if handled || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||||
|
|
||||||
|
case ansiterm.ANSI_CARRIAGE_RETURN:
|
||||||
|
if h.wrapNext {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.clearWrap()
|
||||||
|
}
|
||||||
|
pos, _, err := h.getCurrentInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pos.X != 0 {
|
||||||
|
pos.X = 0
|
||||||
|
h.updatePos(pos)
|
||||||
|
h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUU(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorVertical(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUD(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorVertical(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUF(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorHorizontal(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUB(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorHorizontal(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CNL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorLine(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CPL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorLine(-param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CHA(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.moveCursorColumn(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) VPA(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("VPA: [[%d]]", param)
|
||||||
|
h.clearWrap()
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
window := h.getCursorWindow(info)
|
||||||
|
position := info.CursorPosition
|
||||||
|
position.Y = window.Top + int16(param) - 1
|
||||||
|
return h.setCursorPosition(position, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("CUP: [[%d %d]]", row, col)
|
||||||
|
h.clearWrap()
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
window := h.getCursorWindow(info)
|
||||||
|
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
|
||||||
|
return h.setCursorPosition(position, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("HVP: [[%d %d]]", row, col)
|
||||||
|
h.clearWrap()
|
||||||
|
return h.CUP(row, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
|
||||||
|
h.clearWrap()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
|
||||||
|
h.clearWrap()
|
||||||
|
h.originMode = enable
|
||||||
|
return h.CUP(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
|
||||||
|
h.clearWrap()
|
||||||
|
if err := h.ED(2); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetWidth := int16(80)
|
||||||
|
if use132 {
|
||||||
|
targetWidth = 132
|
||||||
|
}
|
||||||
|
if info.Size.X < targetWidth {
|
||||||
|
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||||
|
h.logf("set buffer failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window := info.Window
|
||||||
|
window.Left = 0
|
||||||
|
window.Right = targetWidth - 1
|
||||||
|
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||||
|
h.logf("set window failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Size.X > targetWidth {
|
||||||
|
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||||
|
h.logf("set buffer failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("ED: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
// [J -- Erases from the cursor to the end of the screen, including the cursor position.
|
||||||
|
// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
|
||||||
|
// [2J -- Erases the complete display. The cursor does not move.
|
||||||
|
// Notes:
|
||||||
|
// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var start COORD
|
||||||
|
var end COORD
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case 0:
|
||||||
|
start = info.CursorPosition
|
||||||
|
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
start = COORD{0, 0}
|
||||||
|
end = info.CursorPosition
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
start = COORD{0, 0}
|
||||||
|
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.clearRange(h.attributes, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the whole buffer was cleared, move the window to the top while preserving
|
||||||
|
// the window-relative cursor position.
|
||||||
|
if param == 2 {
|
||||||
|
pos := info.CursorPosition
|
||||||
|
window := info.Window
|
||||||
|
pos.Y -= window.Top
|
||||||
|
window.Bottom -= window.Top
|
||||||
|
window.Top = 0
|
||||||
|
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) EL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("EL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
// [K -- Erases from the cursor to the end of the line, including the cursor position.
|
||||||
|
// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
|
||||||
|
// [2K -- Erases the complete line.
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var start COORD
|
||||||
|
var end COORD
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case 0:
|
||||||
|
start = info.CursorPosition
|
||||||
|
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
start = COORD{0, info.CursorPosition.Y}
|
||||||
|
end = info.CursorPosition
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
start = COORD{0, info.CursorPosition.Y}
|
||||||
|
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.clearRange(h.attributes, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) IL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("IL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.insertLines(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DL(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DL: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.deleteLines(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) ICH(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("ICH: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.insertCharacters(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DCH(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DCH: [%v]", strconv.Itoa(param))
|
||||||
|
h.clearWrap()
|
||||||
|
return h.deleteCharacters(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SGR(params []int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
strings := []string{}
|
||||||
|
for _, v := range params {
|
||||||
|
strings = append(strings, strconv.Itoa(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logf("SGR: [%v]", strings)
|
||||||
|
|
||||||
|
if len(params) <= 0 {
|
||||||
|
h.attributes = h.infoReset.Attributes
|
||||||
|
h.inverted = false
|
||||||
|
} else {
|
||||||
|
for _, attr := range params {
|
||||||
|
|
||||||
|
if attr == ansiterm.ANSI_SGR_RESET {
|
||||||
|
h.attributes = h.infoReset.Attributes
|
||||||
|
h.inverted = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes := h.attributes
|
||||||
|
if h.inverted {
|
||||||
|
attributes = invertAttributes(attributes)
|
||||||
|
}
|
||||||
|
err := SetConsoleTextAttribute(h.fd, attributes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SU(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("SU: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.scrollUp(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) SD(param int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("SD: [%v]", []string{strconv.Itoa(param)})
|
||||||
|
h.clearWrap()
|
||||||
|
return h.scrollDown(param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DA(params []string) error {
|
||||||
|
h.logf("DA: [%v]", params)
|
||||||
|
// DA cannot be implemented because it must send data on the VT100 input stream,
|
||||||
|
// which is not available to go-ansiterm.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("DECSTBM: [%d, %d]", top, bottom)
|
||||||
|
|
||||||
|
// Windows is 0 indexed, Linux is 1 indexed
|
||||||
|
h.sr.top = int16(top - 1)
|
||||||
|
h.sr.bottom = int16(bottom - 1)
|
||||||
|
|
||||||
|
// This command also moves the cursor to the origin.
|
||||||
|
h.clearWrap()
|
||||||
|
return h.CUP(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) RI() error {
|
||||||
|
if err := h.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logf("RI: []")
|
||||||
|
h.clearWrap()
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := h.effectiveSr(info.Window)
|
||||||
|
if info.CursorPosition.Y == sr.top {
|
||||||
|
return h.scrollDown(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.moveCursorVertical(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) IND() error {
|
||||||
|
h.logf("IND: []")
|
||||||
|
return h.executeLF()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) Flush() error {
|
||||||
|
h.curInfo = nil
|
||||||
|
if h.buffer.Len() > 0 {
|
||||||
|
h.logf("Flush: [%s]", h.buffer.Bytes())
|
||||||
|
if _, err := h.buffer.WriteTo(h.file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.wrapNext && !h.drewMarginByte {
|
||||||
|
h.logf("Flush: drawing margin byte '%c'", h.marginByte)
|
||||||
|
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
|
||||||
|
size := COORD{1, 1}
|
||||||
|
position := COORD{0, 0}
|
||||||
|
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
|
||||||
|
if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.drewMarginByte = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheConsoleInfo ensures that the current console screen information has been queried
|
||||||
|
// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
|
||||||
|
func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
if h.curInfo == nil {
|
||||||
|
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||||
|
if err != nil {
|
||||||
|
return COORD{}, nil, err
|
||||||
|
}
|
||||||
|
h.curInfo = info
|
||||||
|
h.curPos = info.CursorPosition
|
||||||
|
}
|
||||||
|
return h.curPos, h.curInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
|
||||||
|
if h.curInfo == nil {
|
||||||
|
panic("failed to call getCurrentInfo before calling updatePos")
|
||||||
|
}
|
||||||
|
h.curPos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearWrap clears the state where the cursor is in the margin
|
||||||
|
// waiting for the next character before wrapping the line. This must
|
||||||
|
// be done before most operations that act on the cursor.
|
||||||
|
func (h *windowsAnsiEventHandler) clearWrap() {
|
||||||
|
h.wrapNext = false
|
||||||
|
h.drewMarginByte = false
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
bin/
|
|
@ -0,0 +1,9 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.9.x
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||||
|
#script: go test $(go list ./... | grep -v vendor/)
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Kyle Banks
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,32 @@
|
||||||
|
VERSION = 1.2.1
|
||||||
|
|
||||||
|
RELEASE_PKG = ./cmd/depth
|
||||||
|
INSTALL_PKG = $(RELEASE_PKG)
|
||||||
|
|
||||||
|
|
||||||
|
# Remote includes require 'mmake'
|
||||||
|
# github.com/tj/mmake
|
||||||
|
include github.com/KyleBanks/make/go/install
|
||||||
|
include github.com/KyleBanks/make/go/sanity
|
||||||
|
include github.com/KyleBanks/make/go/release
|
||||||
|
include github.com/KyleBanks/make/go/bench
|
||||||
|
include github.com/KyleBanks/make/git/precommit
|
||||||
|
|
||||||
|
# Runs a number of depth commands as examples of what's possible.
|
||||||
|
example: | install
|
||||||
|
depth github.com/KyleBanks/depth/cmd/depth strings ./
|
||||||
|
|
||||||
|
depth -internal strings
|
||||||
|
|
||||||
|
depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test -internal strings
|
||||||
|
|
||||||
|
depth -test -internal -max 3 strings
|
||||||
|
|
||||||
|
depth .
|
||||||
|
|
||||||
|
depth ./cmd/depth
|
||||||
|
.PHONY: example
|
|
@ -0,0 +1,232 @@
|
||||||
|
# depth
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/KyleBanks/depth?status.svg)](https://godoc.org/github.com/KyleBanks/depth)
|
||||||
|
[![Build Status](https://travis-ci.org/KyleBanks/depth.svg?branch=master)](https://travis-ci.org/KyleBanks/depth)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/KyleBanks/depth)](https://goreportcard.com/report/github.com/KyleBanks/depth)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/KyleBanks/depth/badge.svg?branch=master)](https://coveralls.io/github/KyleBanks/depth?branch=master)
|
||||||
|
|
||||||
|
`depth` is tool to retrieve and visualize Go source code dependency trees.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Download the appropriate binary for your platform from the [Releases](https://github.com/KyleBanks/depth/releases) page, or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/KyleBanks/depth/cmd/depth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`depth` can be used as a standalone command-line application, or as a package within your own project.
|
||||||
|
|
||||||
|
### Command-Line
|
||||||
|
|
||||||
|
Simply execute `depth` with one or more package names to visualize. You can use the fully qualified import path of the package, like so:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
12 dependencies (11 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can use a relative path, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth .
|
||||||
|
$ depth ./cmd/depth
|
||||||
|
$ depth ../
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `depth` on the Go standard library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Visualizing multiple packages at a time is supported by simply naming the packages you'd like to visualize:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings github.com/KyleBanks/depth
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
7 dependencies (7 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-internal`
|
||||||
|
|
||||||
|
By default, `depth` only resolves the top level of dependencies for standard library packages, however you can use the `-internal` flag to visualize all internal dependencies:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -internal strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ errors
|
||||||
|
└ sync
|
||||||
|
├ internal/race
|
||||||
|
└ unsafe
|
||||||
|
├ runtime
|
||||||
|
├ runtime/internal/atomic
|
||||||
|
└ unsafe
|
||||||
|
├ runtime/internal/sys
|
||||||
|
└ unsafe
|
||||||
|
├ sync/atomic
|
||||||
|
└ unsafe
|
||||||
|
└ unsafe
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
12 dependencies (12 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-max`
|
||||||
|
|
||||||
|
The `-max` flag limits the dependency tree to the maximum depth provided. For example, if you supply `-max 1` on the `depth` package, your output would look like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ depth -max 1 github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
7 dependencies (6 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-max` flag is particularly useful in conjunction with the `-internal` flag which can lead to very deep dependency trees.
|
||||||
|
|
||||||
|
#### `-test`
|
||||||
|
|
||||||
|
By default, `depth` ignores dependencies that are only required for testing. However, you can view test dependencies using the `-test` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -test strings
|
||||||
|
strings
|
||||||
|
├ bytes
|
||||||
|
├ errors
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ io/ioutil
|
||||||
|
├ math/rand
|
||||||
|
├ reflect
|
||||||
|
├ sync
|
||||||
|
├ testing
|
||||||
|
├ unicode
|
||||||
|
├ unicode/utf8
|
||||||
|
└ unsafe
|
||||||
|
13 dependencies (13 internal, 0 external, 8 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-explain target-package`
|
||||||
|
|
||||||
|
The `-explain` flag instructs `depth` to print import chains in which the
|
||||||
|
`target-package` is found:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -explain strings github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> strings
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> github.com/KyleBanks/depth -> strings
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-json`
|
||||||
|
|
||||||
|
The `-json` flag instructs `depth` to output dependencies in JSON format:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth/cmd/depth",
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "encoding/json",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth",
|
||||||
|
"internal": false,
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "go/build",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integrating With Your Project
|
||||||
|
|
||||||
|
The `depth` package can easily be used to retrieve the dependency tree for a particular package in your own project. For example, here's how you would retrieve the dependency tree for the `strings` package:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
var t depth.Tree
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: "'strings' has 4 dependencies."
|
||||||
|
log.Printf("'%v' has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
```
|
||||||
|
|
||||||
|
For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
t := depth.Tree {
|
||||||
|
ResolveInternal: true,
|
||||||
|
ResolveTest: true,
|
||||||
|
MaxDepth: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
`depth` was developed by [Kyle Banks](https://twitter.com/kylewbanks).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`depth` is available under the [MIT](./LICENSE) license.
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of
|
||||||
|
// internal and external packages.
|
||||||
|
//
|
||||||
|
// For example, the dependencies of the stdlib `strings` package can be resolved like so:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// var t depth.Tree
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Output: "strings has 4 dependencies."
|
||||||
|
// log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
//
|
||||||
|
// For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// t := depth.Tree {
|
||||||
|
// ResolveInternal: true,
|
||||||
|
// ResolveTest: true,
|
||||||
|
// MaxDepth: 10,
|
||||||
|
// }
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved,
|
||||||
|
// typically because it does not exist.
|
||||||
|
var ErrRootPkgNotResolved = errors.New("unable to resolve root package")
|
||||||
|
|
||||||
|
// Importer defines a type that can import a package and return its details.
|
||||||
|
type Importer interface {
|
||||||
|
Import(name, srcDir string, im build.ImportMode) (*build.Package, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree represents the top level of a Pkg and the configuration used to
|
||||||
|
// initialize and represent its contents.
|
||||||
|
type Tree struct {
|
||||||
|
Root *Pkg
|
||||||
|
|
||||||
|
ResolveInternal bool
|
||||||
|
ResolveTest bool
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
Importer Importer
|
||||||
|
|
||||||
|
importCache map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the root Pkg name provided,
|
||||||
|
// and the packages it depends on.
|
||||||
|
func (t *Tree) Resolve(name string) error {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root = &Pkg{
|
||||||
|
Name: name,
|
||||||
|
Tree: t,
|
||||||
|
SrcDir: pwd,
|
||||||
|
Test: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the import cache each time to ensure a reused Tree doesn't
|
||||||
|
// reuse the same cache.
|
||||||
|
t.importCache = nil
|
||||||
|
|
||||||
|
// Allow custom importers, but use build.Default if none is provided.
|
||||||
|
if t.Importer == nil {
|
||||||
|
t.Importer = &build.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root.Resolve(t.Importer)
|
||||||
|
if !t.Root.Resolved {
|
||||||
|
return ErrRootPkgNotResolved
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldResolveInternal determines if internal packages should be further resolved beyond the
|
||||||
|
// current parent.
|
||||||
|
//
|
||||||
|
// For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the
|
||||||
|
// internal dependencies it relies on will be resolved. If for example `strings` is one of those
|
||||||
|
// dependencies, and it is passed as the parent here, false may be returned and its internal
|
||||||
|
// dependencies will not be resolved.
|
||||||
|
func (t *Tree) shouldResolveInternal(parent *Pkg) bool {
|
||||||
|
if t.ResolveInternal {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent == t.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum
|
||||||
|
// depth allowed by the tree.
|
||||||
|
//
|
||||||
|
// If the Tree has a MaxDepth of zero, true is never returned.
|
||||||
|
func (t *Tree) isAtMaxDepth(p *Pkg) bool {
|
||||||
|
if t.MaxDepth == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.depth() >= t.MaxDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasSeenImport returns true if the import name provided has already been seen within the tree.
|
||||||
|
// This function only returns false for a name once.
|
||||||
|
func (t *Tree) hasSeenImport(name string) bool {
|
||||||
|
if t.importCache == nil {
|
||||||
|
t.importCache = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := t.importCache[name]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t.importCache[name] = struct{}{}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/build"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pkg represents a Go source package, and its dependencies.
|
||||||
|
type Pkg struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SrcDir string `json:"-"`
|
||||||
|
|
||||||
|
Internal bool `json:"internal"`
|
||||||
|
Resolved bool `json:"resolved"`
|
||||||
|
Test bool `json:"-"`
|
||||||
|
|
||||||
|
Tree *Tree `json:"-"`
|
||||||
|
Parent *Pkg `json:"-"`
|
||||||
|
Deps []Pkg `json:"deps"`
|
||||||
|
|
||||||
|
Raw *build.Package `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the Pkg and the packages it depends on.
|
||||||
|
func (p *Pkg) Resolve(i Importer) {
|
||||||
|
// Resolved is always true, regardless of if we skip the import,
|
||||||
|
// it is only false if there is an error while importing.
|
||||||
|
p.Resolved = true
|
||||||
|
|
||||||
|
name := p.cleanName()
|
||||||
|
if name == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop resolving imports if we've reached max depth or found a duplicate.
|
||||||
|
var importMode build.ImportMode
|
||||||
|
if p.Tree.hasSeenImport(name) || p.Tree.isAtMaxDepth(p) {
|
||||||
|
importMode = build.FindOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg, err := i.Import(name, p.SrcDir, importMode)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check the error type?
|
||||||
|
p.Resolved = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Raw = pkg
|
||||||
|
|
||||||
|
// Update the name with the fully qualified import path.
|
||||||
|
p.Name = pkg.ImportPath
|
||||||
|
|
||||||
|
// If this is an internal dependency, we may need to skip it.
|
||||||
|
if pkg.Goroot {
|
||||||
|
p.Internal = true
|
||||||
|
if !p.Tree.shouldResolveInternal(p) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//first we set the regular dependencies, then we add the test dependencies
|
||||||
|
//sharing the same set. This allows us to mark all test-only deps linearly
|
||||||
|
unique := make(map[string]struct{})
|
||||||
|
p.setDeps(i, pkg.Imports, pkg.Dir, unique, false)
|
||||||
|
if p.Tree.ResolveTest {
|
||||||
|
p.setDeps(i, append(pkg.TestImports, pkg.XTestImports...), pkg.Dir, unique, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDeps takes a slice of import paths and the source directory they are relative to,
|
||||||
|
// and creates the Deps of the Pkg. Each dependency is also further resolved prior to being added
|
||||||
|
// to the Pkg.
|
||||||
|
func (p *Pkg) setDeps(i Importer, imports []string, srcDir string, unique map[string]struct{}, isTest bool) {
|
||||||
|
for _, imp := range imports {
|
||||||
|
// Mostly for testing files where cyclic imports are allowed.
|
||||||
|
if imp == p.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip duplicates.
|
||||||
|
if _, ok := unique[imp]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[imp] = struct{}{}
|
||||||
|
|
||||||
|
p.addDep(i, imp, srcDir, isTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byInternalAndName(p.Deps))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDep creates a Pkg and it's dependencies from an imported package name.
|
||||||
|
func (p *Pkg) addDep(i Importer, name string, srcDir string, isTest bool) {
|
||||||
|
dep := Pkg{
|
||||||
|
Name: name,
|
||||||
|
SrcDir: srcDir,
|
||||||
|
Tree: p.Tree,
|
||||||
|
Parent: p,
|
||||||
|
Test: isTest,
|
||||||
|
}
|
||||||
|
dep.Resolve(i)
|
||||||
|
|
||||||
|
p.Deps = append(p.Deps, dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isParent goes recursively up the chain of Pkgs to determine if the name provided is ever a
|
||||||
|
// parent of the current Pkg.
|
||||||
|
func (p *Pkg) isParent(name string) bool {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Parent.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.isParent(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// depth returns the depth of the Pkg within the Tree.
|
||||||
|
func (p *Pkg) depth() int {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.depth() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanName returns a cleaned version of the Pkg name used for resolving dependencies.
|
||||||
|
//
|
||||||
|
// If an empty string is returned, dependencies should not be resolved.
|
||||||
|
func (p *Pkg) cleanName() string {
|
||||||
|
name := p.Name
|
||||||
|
|
||||||
|
// C 'package' cannot be resolved.
|
||||||
|
if name == "C" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal golang_org/* packages must be prefixed with vendor/
|
||||||
|
//
|
||||||
|
// Thanks to @davecheney for this:
|
||||||
|
// https://github.com/davecheney/graphpkg/blob/master/main.go#L46
|
||||||
|
if strings.HasPrefix(name, "golang_org") {
|
||||||
|
name = path.Join("vendor", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Pkg containing the Pkg name and status.
|
||||||
|
func (p *Pkg) String() string {
|
||||||
|
b := bytes.NewBufferString(p.Name)
|
||||||
|
|
||||||
|
if !p.Resolved {
|
||||||
|
b.Write([]byte(" (unresolved)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// byInternalAndName ensures a slice of Pkgs are sorted such that the internal stdlib
|
||||||
|
// packages are always above external packages (ie. github.com/whatever).
|
||||||
|
type byInternalAndName []Pkg
|
||||||
|
|
||||||
|
func (b byInternalAndName) Len() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Swap(i, j int) {
|
||||||
|
b[i], b[j] = b[j], b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Less(i, j int) bool {
|
||||||
|
if b[i].Internal && !b[j].Internal {
|
||||||
|
return true
|
||||||
|
} else if !b[i].Internal && b[j].Internal {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return b[i].Name < b[j].Name
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
|
@ -0,0 +1,10 @@
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# testing
|
||||||
|
testdata
|
||||||
|
|
||||||
|
# go workspaces
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
|
@ -0,0 +1,147 @@
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
# style
|
||||||
|
- containedctx # struct contains a context
|
||||||
|
- dupl # duplicate code
|
||||||
|
- errname # erorrs are named correctly
|
||||||
|
- nolintlint # "//nolint" directives are properly explained
|
||||||
|
- revive # golint replacement
|
||||||
|
- unconvert # unnecessary conversions
|
||||||
|
- wastedassign
|
||||||
|
|
||||||
|
# bugs, performance, unused, etc ...
|
||||||
|
- contextcheck # function uses a non-inherited context
|
||||||
|
- errorlint # errors not wrapped for 1.13
|
||||||
|
- exhaustive # check exhaustiveness of enum switch statements
|
||||||
|
- gofmt # files are gofmt'ed
|
||||||
|
- gosec # security
|
||||||
|
- nilerr # returns nil even with non-nil error
|
||||||
|
- thelper # test helpers without t.Helper()
|
||||||
|
- unparam # unused function params
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-dirs:
|
||||||
|
- pkg/etw/sample
|
||||||
|
|
||||||
|
exclude-rules:
|
||||||
|
# err is very often shadowed in nested scopes
|
||||||
|
- linters:
|
||||||
|
- govet
|
||||||
|
text: '^shadow: declaration of "err" shadows declaration'
|
||||||
|
|
||||||
|
# ignore long lines for skip autogen directives
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "^line-length-limit: "
|
||||||
|
source: "^//(go:generate|sys) "
|
||||||
|
|
||||||
|
#TODO: remove after upgrading to go1.18
|
||||||
|
# ignore comment spacing for nolint and sys directives
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||||
|
source: "//(cspell:|nolint:|sys |todo)"
|
||||||
|
|
||||||
|
# not on go 1.18 yet, so no any
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
||||||
|
|
||||||
|
# allow unjustified ignores of error checks in defer statements
|
||||||
|
- linters:
|
||||||
|
- nolintlint
|
||||||
|
text: "^directive `//nolint:errcheck` should provide explanation"
|
||||||
|
source: '^\s*defer '
|
||||||
|
|
||||||
|
# allow unjustified ignores of error lints for io.EOF
|
||||||
|
- linters:
|
||||||
|
- nolintlint
|
||||||
|
text: "^directive `//nolint:errorlint` should provide explanation"
|
||||||
|
source: '[=|!]= io.EOF'
|
||||||
|
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
exhaustive:
|
||||||
|
default-signifies-exhaustive: true
|
||||||
|
govet:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
# struct order is often for Win32 compat
|
||||||
|
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||||
|
- fieldalignment
|
||||||
|
nolintlint:
|
||||||
|
require-explanation: true
|
||||||
|
require-specific: true
|
||||||
|
revive:
|
||||||
|
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
||||||
|
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
||||||
|
enable-all-rules:
|
||||||
|
true
|
||||||
|
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||||
|
rules:
|
||||||
|
# rules with required arguments
|
||||||
|
- name: argument-limit
|
||||||
|
disabled: true
|
||||||
|
- name: banned-characters
|
||||||
|
disabled: true
|
||||||
|
- name: cognitive-complexity
|
||||||
|
disabled: true
|
||||||
|
- name: cyclomatic
|
||||||
|
disabled: true
|
||||||
|
- name: file-header
|
||||||
|
disabled: true
|
||||||
|
- name: function-length
|
||||||
|
disabled: true
|
||||||
|
- name: function-result-limit
|
||||||
|
disabled: true
|
||||||
|
- name: max-public-structs
|
||||||
|
disabled: true
|
||||||
|
# geneally annoying rules
|
||||||
|
- name: add-constant # complains about any and all strings and integers
|
||||||
|
disabled: true
|
||||||
|
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
||||||
|
disabled: true
|
||||||
|
- name: flag-parameter # excessive, and a common idiom we use
|
||||||
|
disabled: true
|
||||||
|
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||||
|
disabled: true
|
||||||
|
# general config
|
||||||
|
- name: line-length-limit
|
||||||
|
arguments:
|
||||||
|
- 140
|
||||||
|
- name: var-naming
|
||||||
|
arguments:
|
||||||
|
- []
|
||||||
|
- - CID
|
||||||
|
- CRI
|
||||||
|
- CTRD
|
||||||
|
- DACL
|
||||||
|
- DLL
|
||||||
|
- DOS
|
||||||
|
- ETW
|
||||||
|
- FSCTL
|
||||||
|
- GCS
|
||||||
|
- GMSA
|
||||||
|
- HCS
|
||||||
|
- HV
|
||||||
|
- IO
|
||||||
|
- LCOW
|
||||||
|
- LDAP
|
||||||
|
- LPAC
|
||||||
|
- LTSC
|
||||||
|
- MMIO
|
||||||
|
- NT
|
||||||
|
- OCI
|
||||||
|
- PMEM
|
||||||
|
- PWSH
|
||||||
|
- RX
|
||||||
|
- SACl
|
||||||
|
- SID
|
||||||
|
- SMB
|
||||||
|
- TX
|
||||||
|
- VHD
|
||||||
|
- VHDX
|
||||||
|
- VMID
|
||||||
|
- VPCI
|
||||||
|
- WCOW
|
||||||
|
- WIM
|
|
@ -0,0 +1 @@
|
||||||
|
* @microsoft/containerplat
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
|
||||||
|
|
||||||
|
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||||
|
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||||
|
for using named pipes as a net transport.
|
||||||
|
|
||||||
|
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||||
|
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||||
|
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||||
|
package.
|
||||||
|
|
||||||
|
Please see the LICENSE file for licensing information.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This project welcomes contributions and suggestions.
|
||||||
|
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that
|
||||||
|
you have the right to, and actually do, grant us the rights to use your contribution.
|
||||||
|
For details, visit [Microsoft CLA](https://cla.microsoft.com).
|
||||||
|
|
||||||
|
When you submit a pull request, a CLA-bot will automatically determine whether you need to
|
||||||
|
provide a CLA and decorate the PR appropriately (e.g., label, comment).
|
||||||
|
Simply follow the instructions provided by the bot.
|
||||||
|
You will only need to do this once across all repos using our CLA.
|
||||||
|
|
||||||
|
Additionally, the pull request pipeline requires the following steps to be performed before
|
||||||
|
mergining.
|
||||||
|
|
||||||
|
### Code Sign-Off
|
||||||
|
|
||||||
|
We require that contributors sign their commits using [`git commit --signoff`][git-commit-s]
|
||||||
|
to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
||||||
|
|
||||||
|
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
|
||||||
|
|
||||||
|
Please see [the developer certificate](https://developercertificate.org) for more info,
|
||||||
|
as well as to make sure that you can attest to the rules listed.
|
||||||
|
Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
Code must pass a linting stage, which uses [`golangci-lint`][lint].
|
||||||
|
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
|
||||||
|
automatically with VSCode by adding the following to your workspace or folder settings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"go.lintTool": "golangci-lint",
|
||||||
|
"go.lintOnSave": "package",
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional editor [integrations options are also available][lint-ide].
|
||||||
|
|
||||||
|
Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# use . or specify a path to only lint a package
|
||||||
|
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
|
||||||
|
> golangci-lint run ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go Generate
|
||||||
|
|
||||||
|
The pipeline checks that auto-generated code, via `go generate`, are up to date.
|
||||||
|
|
||||||
|
This can be done for the entire repo:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
> go generate ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||||
|
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||||
|
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
|
## Special Thanks
|
||||||
|
|
||||||
|
Thanks to [natefinch][natefinch] for the inspiration for this library.
|
||||||
|
See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation.
|
||||||
|
|
||||||
|
[lint]: https://golangci-lint.run/
|
||||||
|
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
|
||||||
|
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
|
||||||
|
|
||||||
|
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
|
||||||
|
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
|
||||||
|
|
||||||
|
[natefinch]: https://github.com/natefinch
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||||
|
|
||||||
|
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||||
|
|
||||||
|
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||||
|
|
||||||
|
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||||
|
|
||||||
|
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||||
|
|
||||||
|
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||||
|
|
||||||
|
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||||
|
* Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
* Any special configuration required to reproduce the issue
|
||||||
|
* Step-by-step instructions to reproduce the issue
|
||||||
|
* Proof-of-concept or exploit code (if possible)
|
||||||
|
* Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||||
|
|
||||||
|
## Preferred Languages
|
||||||
|
|
||||||
|
We prefer all communications to be in English.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||||
|
|
||||||
|
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
@ -0,0 +1,287 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/fs"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||||
|
//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackupData = uint32(iota + 1)
|
||||||
|
BackupEaData
|
||||||
|
BackupSecurity
|
||||||
|
BackupAlternateData
|
||||||
|
BackupLink
|
||||||
|
BackupPropertyData
|
||||||
|
BackupObjectId //revive:disable-line:var-naming ID, not Id
|
||||||
|
BackupReparseData
|
||||||
|
BackupSparseBlock
|
||||||
|
BackupTxfsData
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StreamSparseAttributes = uint32(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:revive // var-naming: ALL_CAPS
|
||||||
|
const (
|
||||||
|
WRITE_DAC = windows.WRITE_DAC
|
||||||
|
WRITE_OWNER = windows.WRITE_OWNER
|
||||||
|
ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupHeader represents a backup stream of a file.
|
||||||
|
type BackupHeader struct {
|
||||||
|
//revive:disable-next-line:var-naming ID, not Id
|
||||||
|
Id uint32 // The backup stream ID
|
||||||
|
Attributes uint32 // Stream attributes
|
||||||
|
Size int64 // The size of the stream in bytes
|
||||||
|
Name string // The name of the stream (for BackupAlternateData only).
|
||||||
|
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32StreamID struct {
|
||||||
|
StreamID uint32
|
||||||
|
Attributes uint32
|
||||||
|
Size uint64
|
||||||
|
NameSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||||
|
// of BackupHeader values.
|
||||||
|
type BackupStreamReader struct {
|
||||||
|
r io.Reader
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||||
|
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||||
|
return &BackupStreamReader{r, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
||||||
|
// it was not completely read.
|
||||||
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
|
if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this
|
||||||
|
if s, ok := r.r.(io.Seeker); ok {
|
||||||
|
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
||||||
|
// before trying the actual seek.
|
||||||
|
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
||||||
|
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.bytesLeft = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(io.Discard, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wsi win32StreamID
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr := &BackupHeader{
|
||||||
|
Id: wsi.StreamID,
|
||||||
|
Attributes: wsi.Attributes,
|
||||||
|
Size: int64(wsi.Size),
|
||||||
|
}
|
||||||
|
if wsi.NameSize != 0 {
|
||||||
|
name := make([]uint16, int(wsi.NameSize/2))
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Name = windows.UTF16ToString(name)
|
||||||
|
}
|
||||||
|
if wsi.StreamID == BackupSparseBlock {
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Size -= 8
|
||||||
|
}
|
||||||
|
r.bytesLeft = hdr.Size
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the current backup stream.
|
||||||
|
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||||
|
if r.bytesLeft == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(b)) > r.bytesLeft {
|
||||||
|
b = b[:r.bytesLeft]
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(b)
|
||||||
|
r.bytesLeft -= int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else if r.bytesLeft == 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||||
|
type BackupStreamWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||||
|
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||||
|
return &BackupStreamWriter{w, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||||
|
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||||
|
if w.bytesLeft != 0 {
|
||||||
|
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||||
|
}
|
||||||
|
name := utf16.Encode([]rune(hdr.Name))
|
||||||
|
wsi := win32StreamID{
|
||||||
|
StreamID: hdr.Id,
|
||||||
|
Attributes: hdr.Attributes,
|
||||||
|
Size: uint64(hdr.Size),
|
||||||
|
NameSize: uint32(len(name) * 2),
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
// Include space for the int64 block offset
|
||||||
|
wsi.Size += 8
|
||||||
|
}
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(name) != 0 {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytesLeft = hdr.Size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current backup stream.
|
||||||
|
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.bytesLeft < int64(len(b)) {
|
||||||
|
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||||
|
}
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
w.bytesLeft -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||||
|
type BackupFileReader struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||||
|
// Read will attempt to read the security descriptor of the file.
|
||||||
|
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||||
|
r := &BackupFileReader{f, includeSecurity, 0}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||||
|
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||||
|
var bytesRead uint32
|
||||||
|
err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
if bytesRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return int(bytesRead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||||
|
// the underlying file.
|
||||||
|
func (r *BackupFileReader) Close() error {
|
||||||
|
if r.ctx != 0 {
|
||||||
|
_ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
r.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||||
|
type BackupFileWriter struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write restores a portion of the file using the provided backup stream.
|
||||||
|
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||||
|
var bytesWritten uint32
|
||||||
|
err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
if int(bytesWritten) != len(b) {
|
||||||
|
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||||
|
// close the underlying file.
|
||||||
|
func (w *BackupFileWriter) Close() error {
|
||||||
|
if w.ctx != 0 {
|
||||||
|
_ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
w.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
||||||
|
// or restore privileges have been acquired.
|
||||||
|
//
|
||||||
|
// If the file opened was a directory, it cannot be used with Readdir().
|
||||||
|
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||||
|
h, err := fs.CreateFile(path,
|
||||||
|
fs.AccessMask(access),
|
||||||
|
fs.FileShareMode(share),
|
||||||
|
nil,
|
||||||
|
fs.FileCreationDisposition(createmode),
|
||||||
|
fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(h), path), nil
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// This package provides utilities for efficiently performing Win32 IO operations in Go.
|
||||||
|
// Currently, this package is provides support for genreal IO and management of
|
||||||
|
// - named pipes
|
||||||
|
// - files
|
||||||
|
// - [Hyper-V sockets]
|
||||||
|
//
|
||||||
|
// This code is similar to Go's [net] package, and uses IO completion ports to avoid
|
||||||
|
// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines.
|
||||||
|
//
|
||||||
|
// This limits support to Windows Vista and newer operating systems.
|
||||||
|
//
|
||||||
|
// Additionally, this package provides support for:
|
||||||
|
// - creating and managing GUIDs
|
||||||
|
// - writing to [ETW]
|
||||||
|
// - opening and manageing VHDs
|
||||||
|
// - parsing [Windows Image files]
|
||||||
|
// - auto-generating Win32 API code
|
||||||
|
//
|
||||||
|
// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service
|
||||||
|
// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-
|
||||||
|
// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images
|
||||||
|
package winio
|
|
@ -0,0 +1,137 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileFullEaInformation struct {
|
||||||
|
NextEntryOffset uint32
|
||||||
|
Flags uint8
|
||||||
|
NameLength uint8
|
||||||
|
ValueLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||||
|
|
||||||
|
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||||
|
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||||
|
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedAttribute represents a single Windows EA.
|
||||||
|
type ExtendedAttribute struct {
|
||||||
|
Name string
|
||||||
|
Value []byte
|
||||||
|
Flags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||||
|
var info fileFullEaInformation
|
||||||
|
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return ea, nb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nameOffset := fileFullEaInformationSize
|
||||||
|
nameLen := int(info.NameLength)
|
||||||
|
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||||
|
valueLen := int(info.ValueLength)
|
||||||
|
nextOffset := int(info.NextEntryOffset)
|
||||||
|
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return ea, nb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||||
|
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||||
|
ea.Flags = info.Flags
|
||||||
|
if info.NextEntryOffset != 0 {
|
||||||
|
nb = b[info.NextEntryOffset:]
|
||||||
|
}
|
||||||
|
return ea, nb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||||
|
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||||
|
for len(b) != 0 {
|
||||||
|
ea, nb, err := parseEa(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eas = append(eas, ea)
|
||||||
|
b = nb
|
||||||
|
}
|
||||||
|
return eas, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||||
|
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||||
|
return errEaNameTooLarge
|
||||||
|
}
|
||||||
|
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||||
|
return errEaValueTooLarge
|
||||||
|
}
|
||||||
|
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||||
|
withPadding := (entrySize + 3) &^ 3
|
||||||
|
nextOffset := uint32(0)
|
||||||
|
if !last {
|
||||||
|
nextOffset = withPadding
|
||||||
|
}
|
||||||
|
info := fileFullEaInformation{
|
||||||
|
NextEntryOffset: nextOffset,
|
||||||
|
Flags: ea.Flags,
|
||||||
|
NameLength: uint8(len(ea.Name)),
|
||||||
|
ValueLength: uint16(len(ea.Value)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte(ea.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write(ea.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||||
|
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := range eas {
|
||||||
|
last := false
|
||||||
|
if i == len(eas)-1 {
|
||||||
|
last = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeEa(&buf, &eas[i], last)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx
|
||||||
|
//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort
|
||||||
|
//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
|
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("file has already been closed")
|
||||||
|
ErrTimeout = &timeoutError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (*timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (*timeoutError) Timeout() bool { return true }
|
||||||
|
func (*timeoutError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
type timeoutChan chan struct{}
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort windows.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation.
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO.
|
||||||
|
type ioOperation struct {
|
||||||
|
o windows.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIO() {
|
||||||
|
h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type win32File struct {
|
||||||
|
handle windows.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
wgLock sync.RWMutex
|
||||||
|
closing atomic.Bool
|
||||||
|
socket bool
|
||||||
|
readDeadline deadlineHandler
|
||||||
|
writeDeadline deadlineHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type deadlineHandler struct {
|
||||||
|
setLock sync.Mutex
|
||||||
|
channel timeoutChan
|
||||||
|
channelLock sync.RWMutex
|
||||||
|
timer *time.Timer
|
||||||
|
timedout atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeWin32File makes a new win32File from an existing file handle.
|
||||||
|
func makeWin32File(h windows.Handle) (*win32File, error) {
|
||||||
|
f := &win32File{handle: h}
|
||||||
|
ioInitOnce.Do(initIO)
|
||||||
|
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.readDeadline.channel = make(timeoutChan)
|
||||||
|
f.writeDeadline.channel = make(timeoutChan)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use NewOpenFile instead.
|
||||||
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
return NewOpenFile(windows.Handle(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
// If we return the result of makeWin32File directly, it can result in an
|
||||||
|
// interface-wrapped nil, rather than a nil interface value.
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle.
|
||||||
|
func (f *win32File) closeHandle() {
|
||||||
|
f.wgLock.Lock()
|
||||||
|
// Atomically set that we are closing, releasing the resources only once.
|
||||||
|
if !f.closing.Swap(true) {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
_ = cancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
windows.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
} else {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a win32File.
|
||||||
|
func (f *win32File) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed checks if the file has been closed.
|
||||||
|
func (f *win32File) IsClosed() bool {
|
||||||
|
return f.closing.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIO prepares for a new IO operation.
|
||||||
|
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||||
|
func (f *win32File) prepareIO() (*ioOperation, error) {
|
||||||
|
f.wgLock.RLock()
|
||||||
|
if f.closing.Load() {
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
return nil, ErrFileClosed
|
||||||
|
}
|
||||||
|
f.wg.Add(1)
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever.
|
||||||
|
func ioCompletionProcessor(h windows.Handle) {
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: helsaawy - create an asyncIO version that takes a context
|
||||||
|
|
||||||
|
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||||
|
if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
|
||||||
|
return int(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.closing.Load() {
|
||||||
|
_ = cancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout timeoutChan
|
||||||
|
if d != nil {
|
||||||
|
d.channelLock.Lock()
|
||||||
|
timeout = d.channel
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ioResult
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
err = r.err
|
||||||
|
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||||
|
if f.closing.Load() {
|
||||||
|
err = ErrFileClosed
|
||||||
|
}
|
||||||
|
} else if err != nil && f.socket {
|
||||||
|
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||||
|
var bytes, flags uint32
|
||||||
|
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
_ = cancelIoEx(f.handle, &c.o)
|
||||||
|
r = <-c.ch
|
||||||
|
err = r.err
|
||||||
|
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||||
|
err = ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime.KeepAlive is needed, as c is passed via native
|
||||||
|
// code to ioCompletionProcessor, c must remain alive
|
||||||
|
// until the channel read is complete.
|
||||||
|
// todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive?
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *win32File) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.readDeadline.timedout.Load() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *win32File) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.writeDeadline.timedout.Load() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetReadDeadline(deadline time.Time) error {
|
||||||
|
return f.readDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
||||||
|
return f.writeDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Flush() error {
|
||||||
|
return windows.FlushFileBuffers(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Fd() uintptr {
|
||||||
|
return uintptr(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||||
|
d.setLock.Lock()
|
||||||
|
defer d.setLock.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
if !d.timer.Stop() {
|
||||||
|
<-d.channel
|
||||||
|
}
|
||||||
|
d.timer = nil
|
||||||
|
}
|
||||||
|
d.timedout.Store(false)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-d.channel:
|
||||||
|
d.channelLock.Lock()
|
||||||
|
d.channel = make(chan struct{})
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutIO := func() {
|
||||||
|
d.timedout.Store(true)
|
||||||
|
close(d.channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := deadline.Sub(now)
|
||||||
|
if deadline.After(now) {
|
||||||
|
// Deadline is in the future, set a timer to wait
|
||||||
|
d.timer = time.AfterFunc(duration, timeoutIO)
|
||||||
|
} else {
|
||||||
|
// Deadline is in the past. Cancel all pending IO now.
|
||||||
|
timeoutIO()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileBasicInfo contains file access time and file attributes information.
|
||||||
|
type FileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
|
||||||
|
FileAttributes uint32
|
||||||
|
_ uint32 // padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing
|
||||||
|
// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64
|
||||||
|
// alignment is necessary to pass this as FILE_BASIC_INFO.
|
||||||
|
type alignedFileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64
|
||||||
|
FileAttributes uint32
|
||||||
|
_ uint32 // padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||||
|
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||||
|
bi := &alignedFileBasicInfo{}
|
||||||
|
if err := windows.GetFileInformationByHandleEx(
|
||||||
|
windows.Handle(f.Fd()),
|
||||||
|
windows.FileBasicInfo,
|
||||||
|
(*byte)(unsafe.Pointer(bi)),
|
||||||
|
uint32(unsafe.Sizeof(*bi)),
|
||||||
|
); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
// Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the
|
||||||
|
// public API of this module. The data may be unnecessarily aligned.
|
||||||
|
return (*FileBasicInfo)(unsafe.Pointer(bi)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileBasicInfo sets times and attributes for a file.
|
||||||
|
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||||
|
// Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is
|
||||||
|
// suitable to pass to GetFileInformationByHandleEx.
|
||||||
|
biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi))
|
||||||
|
if err := windows.SetFileInformationByHandle(
|
||||||
|
windows.Handle(f.Fd()),
|
||||||
|
windows.FileBasicInfo,
|
||||||
|
(*byte)(unsafe.Pointer(&biAligned)),
|
||||||
|
uint32(unsafe.Sizeof(biAligned)),
|
||||||
|
); err != nil {
|
||||||
|
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileStandardInfo contains extended information for the file.
|
||||||
|
// FILE_STANDARD_INFO in WinBase.h
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
|
||||||
|
type FileStandardInfo struct {
|
||||||
|
AllocationSize, EndOfFile int64
|
||||||
|
NumberOfLinks uint32
|
||||||
|
DeletePending, Directory bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileStandardInfo retrieves ended information for the file.
|
||||||
|
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
|
||||||
|
si := &FileStandardInfo{}
|
||||||
|
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()),
|
||||||
|
windows.FileStandardInfo,
|
||||||
|
(*byte)(unsafe.Pointer(si)),
|
||||||
|
uint32(unsafe.Sizeof(*si))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return si, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||||
|
// unique on a system.
|
||||||
|
type FileIDInfo struct {
|
||||||
|
VolumeSerialNumber uint64
|
||||||
|
FileID [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||||
|
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||||
|
fileID := &FileIDInfo{}
|
||||||
|
if err := windows.GetFileInformationByHandleEx(
|
||||||
|
windows.Handle(f.Fd()),
|
||||||
|
windows.FileIdInfo,
|
||||||
|
(*byte)(unsafe.Pointer(fileID)),
|
||||||
|
uint32(unsafe.Sizeof(*fileID)),
|
||||||
|
); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return fileID, nil
|
||||||
|
}
|
|
@ -0,0 +1,582 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/socket"
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const afHVSock = 34 // AF_HYPERV
|
||||||
|
|
||||||
|
// Well known Service and VM IDs
|
||||||
|
// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
||||||
|
|
||||||
|
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
|
||||||
|
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
||||||
|
return guid.GUID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
|
||||||
|
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xffffffff,
|
||||||
|
Data2: 0xffff,
|
||||||
|
Data3: 0xffff,
|
||||||
|
Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector.
|
||||||
|
func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xe0e16197,
|
||||||
|
Data2: 0xdd56,
|
||||||
|
Data3: 0x4a10,
|
||||||
|
Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDSiloHost is the address of a silo's host partition:
|
||||||
|
// - The silo host of a hosted silo is the utility VM.
|
||||||
|
// - The silo host of a silo on a physical host is the physical host.
|
||||||
|
func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0x36bd0c5c,
|
||||||
|
Data2: 0x7276,
|
||||||
|
Data3: 0x4223,
|
||||||
|
Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions.
|
||||||
|
func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0x90db8b89,
|
||||||
|
Data2: 0xd35,
|
||||||
|
Data3: 0x4f79,
|
||||||
|
Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition.
|
||||||
|
// Listening on this VmId accepts connection from:
|
||||||
|
// - Inside silos: silo host partition.
|
||||||
|
// - Inside hosted silo: host of the VM.
|
||||||
|
// - Inside VM: VM host.
|
||||||
|
// - Physical host: Not supported.
|
||||||
|
func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xa42e7cda,
|
||||||
|
Data2: 0xd03f,
|
||||||
|
Data3: 0x480c,
|
||||||
|
Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol.
|
||||||
|
func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3
|
||||||
|
return guid.GUID{
|
||||||
|
Data2: 0xfacb,
|
||||||
|
Data3: 0x11e6,
|
||||||
|
Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||||
|
type HvsockAddr struct {
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawHvsockAddr struct {
|
||||||
|
Family uint16
|
||||||
|
_ uint16
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ socket.RawSockaddr = &rawHvsockAddr{}
|
||||||
|
|
||||||
|
// Network returns the address's network name, "hvsock".
|
||||||
|
func (*HvsockAddr) Network() string {
|
||||||
|
return "hvsock"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) String() string {
|
||||||
|
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||||
|
func VsockServiceID(port uint32) guid.GUID {
|
||||||
|
g := hvsockVsockServiceTemplate() // make a copy
|
||||||
|
g.Data1 = port
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
||||||
|
return rawHvsockAddr{
|
||||||
|
Family: afHVSock,
|
||||||
|
VMID: addr.VMID,
|
||||||
|
ServiceID: addr.ServiceID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
||||||
|
addr.VMID = raw.VMID
|
||||||
|
addr.ServiceID = raw.ServiceID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sockaddr returns a pointer to and the size of this struct.
|
||||||
|
//
|
||||||
|
// Implements the [socket.RawSockaddr] interface, and allows use in
|
||||||
|
// [socket.Bind] and [socket.ConnectEx].
|
||||||
|
func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) {
|
||||||
|
return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`.
|
||||||
|
func (r *rawHvsockAddr) FromBytes(b []byte) error {
|
||||||
|
n := int(unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
|
||||||
|
if len(b) < n {
|
||||||
|
return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n])
|
||||||
|
if r.Family != afHVSock {
|
||||||
|
return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||||
|
type HvsockListener struct {
|
||||||
|
sock *win32File
|
||||||
|
addr HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Listener = &HvsockListener{}
|
||||||
|
|
||||||
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||||
|
type HvsockConn struct {
|
||||||
|
sock *win32File
|
||||||
|
local, remote HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = &HvsockConn{}
|
||||||
|
|
||||||
|
func newHVSocket() (*win32File, error) {
|
||||||
|
fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(fd)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.socket = true
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenHvsock listens for connections on the specified hvsock address.
|
||||||
|
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
||||||
|
l := &HvsockListener{addr: *addr}
|
||||||
|
|
||||||
|
var sock *win32File
|
||||||
|
sock, err = newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sa := addr.raw()
|
||||||
|
err = socket.Bind(sock.handle, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||||
|
}
|
||||||
|
err = windows.Listen(sock.handle, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
||||||
|
}
|
||||||
|
return &HvsockListener{sock: sock, addr: *addr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *HvsockListener) opErr(op string, err error) error {
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the listener's network address.
|
||||||
|
func (l *HvsockListener) Addr() net.Addr {
|
||||||
|
return &l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept waits for the next connection and returns it.
|
||||||
|
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
||||||
|
sock, err := newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c, err := l.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer l.sock.wg.Done()
|
||||||
|
|
||||||
|
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||||
|
//
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
|
||||||
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
var addrbuf [addrlen * 2]byte
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
|
||||||
|
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
|
||||||
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
}
|
||||||
|
// The local address returned in the AcceptEx buffer is the same as the Listener socket's
|
||||||
|
// address. However, the service GUID reported by GetSockName is different from the Listeners
|
||||||
|
// socket, and is sometimes the same as the local address of the socket that dialed the
|
||||||
|
// address, with the service GUID.Data1 incremented, but othertimes is different.
|
||||||
|
// todo: does the local address matter? is the listener's address or the actual address appropriate?
|
||||||
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||||
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||||
|
|
||||||
|
// initialize the accepted socket and update its properties with those of the listening socket
|
||||||
|
if err = windows.Setsockopt(sock.handle,
|
||||||
|
windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT,
|
||||||
|
(*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil {
|
||||||
|
return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
sock = nil
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the listener, causing any pending Accept calls to fail.
|
||||||
|
func (l *HvsockListener) Close() error {
|
||||||
|
return l.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]).
|
||||||
|
type HvsockDialer struct {
|
||||||
|
// Deadline is the time the Dial operation must connect before erroring.
|
||||||
|
Deadline time.Time
|
||||||
|
|
||||||
|
// Retries is the number of additional connects to try if the connection times out, is refused,
|
||||||
|
// or the host is unreachable
|
||||||
|
Retries uint
|
||||||
|
|
||||||
|
// RetryWait is the time to wait after a connection error to retry
|
||||||
|
RetryWait time.Duration
|
||||||
|
|
||||||
|
rt *time.Timer // redial wait timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial the Hyper-V socket at addr.
|
||||||
|
//
|
||||||
|
// See [HvsockDialer.Dial] for more information.
|
||||||
|
func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
||||||
|
return (&HvsockDialer{}).Dial(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful.
|
||||||
|
// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between
|
||||||
|
// retries.
|
||||||
|
//
|
||||||
|
// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx.
|
||||||
|
func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
||||||
|
op := "dial"
|
||||||
|
// create the conn early to use opErr()
|
||||||
|
conn = &HvsockConn{
|
||||||
|
remote: *addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.Deadline.IsZero() {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithDeadline(ctx, d.Deadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// preemptive timeout/cancellation check
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, err := newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sa := addr.raw()
|
||||||
|
err = socket.Bind(sock.handle, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("bind", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
defer sock.wg.Done()
|
||||||
|
var bytes uint32
|
||||||
|
for i := uint(0); i <= d.Retries; i++ {
|
||||||
|
err = socket.ConnectEx(
|
||||||
|
sock.handle,
|
||||||
|
&sa,
|
||||||
|
nil, // sendBuf
|
||||||
|
0, // sendDataLen
|
||||||
|
&bytes,
|
||||||
|
(*windows.Overlapped)(unsafe.Pointer(&c.o)))
|
||||||
|
_, err = sock.asyncIO(c, nil, bytes, err)
|
||||||
|
if i < d.Retries && canRedial(err) {
|
||||||
|
if err = d.redialWait(ctx); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("connectex", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the connection properties, so shutdown can be used
|
||||||
|
if err = windows.Setsockopt(
|
||||||
|
sock.handle,
|
||||||
|
windows.SOL_SOCKET,
|
||||||
|
windows.SO_UPDATE_CONNECT_CONTEXT,
|
||||||
|
nil, // optvalue
|
||||||
|
0, // optlen
|
||||||
|
); err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the local name
|
||||||
|
var sal rawHvsockAddr
|
||||||
|
err = socket.GetSockName(sock.handle, &sal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("getsockname", err))
|
||||||
|
}
|
||||||
|
conn.local.fromRaw(&sal)
|
||||||
|
|
||||||
|
// one last check for timeout, since asyncIO doesn't check the context
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.sock = sock
|
||||||
|
sock = nil
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// redialWait waits before attempting to redial, resetting the timer as appropriate.
|
||||||
|
func (d *HvsockDialer) redialWait(ctx context.Context) (err error) {
|
||||||
|
if d.RetryWait == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.rt == nil {
|
||||||
|
d.rt = time.NewTimer(d.RetryWait)
|
||||||
|
} else {
|
||||||
|
// should already be stopped and drained
|
||||||
|
d.rt.Reset(d.RetryWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-d.rt.C:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop and drain the timer
|
||||||
|
if !d.rt.Stop() {
|
||||||
|
<-d.rt.C
|
||||||
|
}
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes error is a plain, unwrapped windows.Errno provided by direct syscall.
|
||||||
|
func canRedial(err error) bool {
|
||||||
|
//nolint:errorlint // guaranteed to be an Errno
|
||||||
|
switch err {
|
||||||
|
case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT,
|
||||||
|
windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||||
|
// translate from "file closed" to "socket closed"
|
||||||
|
if errors.Is(err, ErrFileClosed) {
|
||||||
|
err = socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var flags, bytes uint32
|
||||||
|
err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
var eno windows.Errno
|
||||||
|
if errors.As(err, &eno) {
|
||||||
|
err = os.NewSyscallError("wsarecv", eno)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
} else if n == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
||||||
|
t := 0
|
||||||
|
for len(b) != 0 {
|
||||||
|
n, err := conn.write(b)
|
||||||
|
if err != nil {
|
||||||
|
return t + n, err
|
||||||
|
}
|
||||||
|
t += n
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) write(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
var eno windows.Errno
|
||||||
|
if errors.As(err, &eno) {
|
||||||
|
err = os.NewSyscallError("wsasend", eno)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the socket connection, failing any pending read or write calls.
|
||||||
|
func (conn *HvsockConn) Close() error {
|
||||||
|
return conn.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) IsClosed() bool {
|
||||||
|
return conn.sock.IsClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown disables sending or receiving on a socket.
|
||||||
|
func (conn *HvsockConn) shutdown(how int) error {
|
||||||
|
if conn.IsClosed() {
|
||||||
|
return socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.Shutdown(conn.sock.handle, how)
|
||||||
|
if err != nil {
|
||||||
|
// If the connection was closed, shutdowns fail with "not connected"
|
||||||
|
if errors.Is(err, windows.WSAENOTCONN) ||
|
||||||
|
errors.Is(err, windows.WSAESHUTDOWN) {
|
||||||
|
err = socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
return os.NewSyscallError("shutdown", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRead shuts down the read end of the socket, preventing future read operations.
|
||||||
|
func (conn *HvsockConn) CloseRead() error {
|
||||||
|
err := conn.shutdown(windows.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("closeread", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite shuts down the write end of the socket, preventing future write operations and
|
||||||
|
// notifying the other endpoint that no more data will be written.
|
||||||
|
func (conn *HvsockConn) CloseWrite() error {
|
||||||
|
err := conn.shutdown(windows.SHUT_WR)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("closewrite", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local address of the connection.
|
||||||
|
func (conn *HvsockConn) LocalAddr() net.Addr {
|
||||||
|
return &conn.local
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remote address of the connection.
|
||||||
|
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
||||||
|
return &conn.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline implements the net.Conn SetDeadline method.
|
||||||
|
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
||||||
|
// todo: implement `SetDeadline` for `win32File`
|
||||||
|
if err := conn.SetReadDeadline(t); err != nil {
|
||||||
|
return fmt.Errorf("set read deadline: %w", err)
|
||||||
|
}
|
||||||
|
if err := conn.SetWriteDeadline(t); err != nil {
|
||||||
|
return fmt.Errorf("set write deadline: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||||
|
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||||
|
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetWriteDeadline(t)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// This package contains Win32 filesystem functionality.
|
||||||
|
package fs
|
|
@ -0,0 +1,262 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/stringbuffer"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||||
|
//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW
|
||||||
|
|
||||||
|
const NullHandle windows.Handle = 0
|
||||||
|
|
||||||
|
// AccessMask defines standard, specific, and generic rights.
|
||||||
|
//
|
||||||
|
// Used with CreateFile and NtCreateFile (and co.).
|
||||||
|
//
|
||||||
|
// Bitmask:
|
||||||
|
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
|
||||||
|
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|
||||||
|
// +---------------+---------------+-------------------------------+
|
||||||
|
// |G|G|G|G|Resvd|A| StandardRights| SpecificRights |
|
||||||
|
// |R|W|E|A| |S| | |
|
||||||
|
// +-+-------------+---------------+-------------------------------+
|
||||||
|
//
|
||||||
|
// GR Generic Read
|
||||||
|
// GW Generic Write
|
||||||
|
// GE Generic Exectue
|
||||||
|
// GA Generic All
|
||||||
|
// Resvd Reserved
|
||||||
|
// AS Access Security System
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
|
||||||
|
type AccessMask = windows.ACCESS_MASK
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// Not actually any.
|
||||||
|
//
|
||||||
|
// For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device"
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters
|
||||||
|
FILE_ANY_ACCESS AccessMask = 0
|
||||||
|
|
||||||
|
GENERIC_READ AccessMask = 0x8000_0000
|
||||||
|
GENERIC_WRITE AccessMask = 0x4000_0000
|
||||||
|
GENERIC_EXECUTE AccessMask = 0x2000_0000
|
||||||
|
GENERIC_ALL AccessMask = 0x1000_0000
|
||||||
|
ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000
|
||||||
|
|
||||||
|
// Specific Object Access
|
||||||
|
// from ntioapi.h
|
||||||
|
|
||||||
|
FILE_READ_DATA AccessMask = (0x0001) // file & pipe
|
||||||
|
FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory
|
||||||
|
|
||||||
|
FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe
|
||||||
|
FILE_ADD_FILE AccessMask = (0x0002) // directory
|
||||||
|
|
||||||
|
FILE_APPEND_DATA AccessMask = (0x0004) // file
|
||||||
|
FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory
|
||||||
|
FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe
|
||||||
|
|
||||||
|
FILE_READ_EA AccessMask = (0x0008) // file & directory
|
||||||
|
FILE_READ_PROPERTIES AccessMask = FILE_READ_EA
|
||||||
|
|
||||||
|
FILE_WRITE_EA AccessMask = (0x0010) // file & directory
|
||||||
|
FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA
|
||||||
|
|
||||||
|
FILE_EXECUTE AccessMask = (0x0020) // file
|
||||||
|
FILE_TRAVERSE AccessMask = (0x0020) // directory
|
||||||
|
|
||||||
|
FILE_DELETE_CHILD AccessMask = (0x0040) // directory
|
||||||
|
|
||||||
|
FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all
|
||||||
|
|
||||||
|
FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all
|
||||||
|
|
||||||
|
FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
|
||||||
|
FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE)
|
||||||
|
FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE)
|
||||||
|
FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE)
|
||||||
|
|
||||||
|
SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF
|
||||||
|
|
||||||
|
// Standard Access
|
||||||
|
// from ntseapi.h
|
||||||
|
|
||||||
|
DELETE AccessMask = 0x0001_0000
|
||||||
|
READ_CONTROL AccessMask = 0x0002_0000
|
||||||
|
WRITE_DAC AccessMask = 0x0004_0000
|
||||||
|
WRITE_OWNER AccessMask = 0x0008_0000
|
||||||
|
SYNCHRONIZE AccessMask = 0x0010_0000
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_READ AccessMask = READ_CONTROL
|
||||||
|
STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL
|
||||||
|
STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileShareMode uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
FILE_SHARE_NONE FileShareMode = 0x00
|
||||||
|
FILE_SHARE_READ FileShareMode = 0x01
|
||||||
|
FILE_SHARE_WRITE FileShareMode = 0x02
|
||||||
|
FILE_SHARE_DELETE FileShareMode = 0x04
|
||||||
|
FILE_SHARE_VALID_FLAGS FileShareMode = 0x07
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileCreationDisposition uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winbase.h
|
||||||
|
|
||||||
|
CREATE_NEW FileCreationDisposition = 0x01
|
||||||
|
CREATE_ALWAYS FileCreationDisposition = 0x02
|
||||||
|
OPEN_EXISTING FileCreationDisposition = 0x03
|
||||||
|
OPEN_ALWAYS FileCreationDisposition = 0x04
|
||||||
|
TRUNCATE_EXISTING FileCreationDisposition = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create disposition values for NtCreate*
|
||||||
|
type NTFileCreationDisposition uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// From ntioapi.h
|
||||||
|
|
||||||
|
FILE_SUPERSEDE NTFileCreationDisposition = 0x00
|
||||||
|
FILE_OPEN NTFileCreationDisposition = 0x01
|
||||||
|
FILE_CREATE NTFileCreationDisposition = 0x02
|
||||||
|
FILE_OPEN_IF NTFileCreationDisposition = 0x03
|
||||||
|
FILE_OVERWRITE NTFileCreationDisposition = 0x04
|
||||||
|
FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05
|
||||||
|
FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateFile and co. take flags or attributes together as one parameter.
|
||||||
|
// Define alias until we can use generics to allow both
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||||
|
type FileFlagOrAttribute uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winnt.h
|
||||||
|
|
||||||
|
FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000
|
||||||
|
FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000
|
||||||
|
FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000
|
||||||
|
FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000
|
||||||
|
FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000
|
||||||
|
FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000
|
||||||
|
FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000
|
||||||
|
FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000
|
||||||
|
FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
// NtCreate* functions take a dedicated CreateOptions parameter.
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file
|
||||||
|
type NTCreateOptions uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// From ntioapi.h
|
||||||
|
|
||||||
|
FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001
|
||||||
|
FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002
|
||||||
|
FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004
|
||||||
|
FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008
|
||||||
|
|
||||||
|
FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010
|
||||||
|
FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020
|
||||||
|
FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040
|
||||||
|
FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080
|
||||||
|
|
||||||
|
FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100
|
||||||
|
FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200
|
||||||
|
FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400
|
||||||
|
FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800
|
||||||
|
|
||||||
|
FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000
|
||||||
|
FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000
|
||||||
|
FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000
|
||||||
|
FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileSQSFlag = FileFlagOrAttribute
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winbase.h
|
||||||
|
|
||||||
|
SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16)
|
||||||
|
SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16)
|
||||||
|
SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16)
|
||||||
|
SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16)
|
||||||
|
|
||||||
|
SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000
|
||||||
|
SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFinalPathNameByHandle flags
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters
|
||||||
|
type GetFinalPathFlag uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
GetFinalPathDefaultFlag GetFinalPathFlag = 0x0
|
||||||
|
|
||||||
|
FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0
|
||||||
|
FILE_NAME_OPENED GetFinalPathFlag = 0x8
|
||||||
|
|
||||||
|
VOLUME_NAME_DOS GetFinalPathFlag = 0x0
|
||||||
|
VOLUME_NAME_GUID GetFinalPathFlag = 0x1
|
||||||
|
VOLUME_NAME_NT GetFinalPathFlag = 0x2
|
||||||
|
VOLUME_NAME_NONE GetFinalPathFlag = 0x4
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle
|
||||||
|
// with the given handle and flags. It transparently takes care of creating a buffer of the
|
||||||
|
// correct size for the call.
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
|
||||||
|
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) {
|
||||||
|
b := stringbuffer.NewWString()
|
||||||
|
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
|
||||||
|
for {
|
||||||
|
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// If the buffer wasn't large enough, n will be the total size needed (including null terminator).
|
||||||
|
// Resize and try again.
|
||||||
|
if n > b.Cap() {
|
||||||
|
b.ResizeTo(n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the buffer is large enough, n will be the size not including the null terminator.
|
||||||
|
// Convert to a Go string and return.
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
|
||||||
|
type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32`
|
||||||
|
|
||||||
|
// Impersonation levels
|
||||||
|
const (
|
||||||
|
SecurityAnonymous SecurityImpersonationLevel = 0
|
||||||
|
SecurityIdentification SecurityImpersonationLevel = 1
|
||||||
|
SecurityImpersonation SecurityImpersonationLevel = 2
|
||||||
|
SecurityDelegation SecurityImpersonationLevel = 3
|
||||||
|
)
|
61
vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal file
61
vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile))
|
||||||
|
handle = windows.Handle(r0)
|
||||||
|
if handle == windows.InvalidHandle {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The
|
||||||
|
// struct must meet the Win32 sockaddr requirements specified here:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
|
||||||
|
//
|
||||||
|
// Specifically, the struct size must be least larger than an int16 (unsigned short)
|
||||||
|
// for the address family.
|
||||||
|
type RawSockaddr interface {
|
||||||
|
// Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing
|
||||||
|
// for the RawSockaddr's data to be overwritten by syscalls (if necessary).
|
||||||
|
//
|
||||||
|
// It is the callers responsibility to validate that the values are valid; invalid
|
||||||
|
// pointers or size can cause a panic.
|
||||||
|
Sockaddr() (unsafe.Pointer, int32, error)
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go
|
||||||
|
|
||||||
|
//sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname
|
||||||
|
//sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername
|
||||||
|
//sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||||
|
|
||||||
|
const socketError = uintptr(^uint32(0))
|
||||||
|
|
||||||
|
var (
|
||||||
|
// todo(helsaawy): create custom error types to store the desired vs actual size and addr family?
|
||||||
|
|
||||||
|
ErrBufferSize = errors.New("buffer size")
|
||||||
|
ErrAddrFamily = errors.New("address family")
|
||||||
|
ErrInvalidPointer = errors.New("invalid pointer")
|
||||||
|
ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed)
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error)
|
||||||
|
|
||||||
|
// GetSockName writes the local address of socket s to the [RawSockaddr] rsa.
|
||||||
|
// If rsa is not large enough, the [windows.WSAEFAULT] is returned.
|
||||||
|
func GetSockName(s windows.Handle, rsa RawSockaddr) error {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// although getsockname returns WSAEFAULT if the buffer is too small, it does not set
|
||||||
|
// &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy
|
||||||
|
return getsockname(s, ptr, &l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerName returns the remote address the socket is connected to.
|
||||||
|
//
|
||||||
|
// See [GetSockName] for more information.
|
||||||
|
func GetPeerName(s windows.Handle, rsa RawSockaddr) error {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getpeername(s, ptr, &l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind(s windows.Handle, rsa RawSockaddr) (err error) {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bind(s, ptr, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the
|
||||||
|
// their sockaddr interface, so they cannot be used with HvsockAddr
|
||||||
|
// Replicate functionality here from
|
||||||
|
// https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go
|
||||||
|
|
||||||
|
// The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at
|
||||||
|
// runtime via a WSAIoctl call:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks
|
||||||
|
|
||||||
|
type runtimeFunc struct {
|
||||||
|
id guid.GUID
|
||||||
|
once sync.Once
|
||||||
|
addr uintptr
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *runtimeFunc) Load() error {
|
||||||
|
f.once.Do(func() {
|
||||||
|
var s windows.Handle
|
||||||
|
s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP)
|
||||||
|
if f.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(s) //nolint:errcheck
|
||||||
|
|
||||||
|
var n uint32
|
||||||
|
f.err = windows.WSAIoctl(s,
|
||||||
|
windows.SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||||
|
(*byte)(unsafe.Pointer(&f.id)),
|
||||||
|
uint32(unsafe.Sizeof(f.id)),
|
||||||
|
(*byte)(unsafe.Pointer(&f.addr)),
|
||||||
|
uint32(unsafe.Sizeof(f.addr)),
|
||||||
|
&n,
|
||||||
|
nil, // overlapped
|
||||||
|
0, // completionRoutine
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// todo: add `AcceptEx` and `GetAcceptExSockaddrs`
|
||||||
|
WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS
|
||||||
|
Data1: 0x25a207b9,
|
||||||
|
Data2: 0xddf3,
|
||||||
|
Data3: 0x4660,
|
||||||
|
Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
|
||||||
|
}
|
||||||
|
|
||||||
|
connectExFunc = runtimeFunc{id: WSAID_CONNECTEX}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectEx(
|
||||||
|
fd windows.Handle,
|
||||||
|
rsa RawSockaddr,
|
||||||
|
sendBuf *byte,
|
||||||
|
sendDataLen uint32,
|
||||||
|
bytesSent *uint32,
|
||||||
|
overlapped *windows.Overlapped,
|
||||||
|
) error {
|
||||||
|
if err := connectExFunc.Load(); err != nil {
|
||||||
|
return fmt.Errorf("failed to load ConnectEx function pointer: %w", err)
|
||||||
|
}
|
||||||
|
ptr, n, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOOL LpfnConnectex(
|
||||||
|
// [in] SOCKET s,
|
||||||
|
// [in] const sockaddr *name,
|
||||||
|
// [in] int namelen,
|
||||||
|
// [in, optional] PVOID lpSendBuffer,
|
||||||
|
// [in] DWORD dwSendDataLength,
|
||||||
|
// [out] LPDWORD lpdwBytesSent,
|
||||||
|
// [in] LPOVERLAPPED lpOverlapped
|
||||||
|
// )
|
||||||
|
|
||||||
|
func connectEx(
|
||||||
|
s windows.Handle,
|
||||||
|
name unsafe.Pointer,
|
||||||
|
namelen int32,
|
||||||
|
sendBuf *byte,
|
||||||
|
sendDataLen uint32,
|
||||||
|
bytesSent *uint32,
|
||||||
|
overlapped *windows.Overlapped,
|
||||||
|
) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(connectExFunc.addr,
|
||||||
|
uintptr(s),
|
||||||
|
uintptr(name),
|
||||||
|
uintptr(namelen),
|
||||||
|
uintptr(unsafe.Pointer(sendBuf)),
|
||||||
|
uintptr(sendDataLen),
|
||||||
|
uintptr(unsafe.Pointer(bytesSent)),
|
||||||
|
uintptr(unsafe.Pointer(overlapped)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
69
vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go
generated
vendored
Normal file
69
vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
|
||||||
|
procbind = modws2_32.NewProc("bind")
|
||||||
|
procgetpeername = modws2_32.NewProc("getpeername")
|
||||||
|
procgetsockname = modws2_32.NewProc("getsockname")
|
||||||
|
)
|
||||||
|
|
||||||
|
func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
132
vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal file
132
vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package stringbuffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: worth exporting and using in mkwinsyscall?
|
||||||
|
|
||||||
|
// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
|
||||||
|
// large path strings:
|
||||||
|
// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310.
|
||||||
|
const MinWStringCap = 310
|
||||||
|
|
||||||
|
// use *[]uint16 since []uint16 creates an extra allocation where the slice header
|
||||||
|
// is copied to heap and then referenced via pointer in the interface header that sync.Pool
|
||||||
|
// stores.
|
||||||
|
var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]uint16, MinWStringCap)
|
||||||
|
return &b
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) }
|
||||||
|
|
||||||
|
// freeBuffer copies the slice header data, and puts a pointer to that in the pool.
|
||||||
|
// This avoids taking a pointer to the slice header in WString, which can be set to nil.
|
||||||
|
func freeBuffer(b []uint16) { pathPool.Put(&b) }
|
||||||
|
|
||||||
|
// WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings
|
||||||
|
// for interacting with Win32 APIs.
|
||||||
|
// Sizes are specified as uint32 and not int.
|
||||||
|
//
|
||||||
|
// It is not thread safe.
|
||||||
|
type WString struct {
|
||||||
|
// type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future.
|
||||||
|
|
||||||
|
// raw buffer
|
||||||
|
b []uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWString returns a [WString] allocated from a shared pool with an
|
||||||
|
// initial capacity of at least [MinWStringCap].
|
||||||
|
// Since the buffer may have been previously used, its contents are not guaranteed to be empty.
|
||||||
|
//
|
||||||
|
// The buffer should be freed via [WString.Free]
|
||||||
|
func NewWString() *WString {
|
||||||
|
return &WString{
|
||||||
|
b: newBuffer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WString) Free() {
|
||||||
|
if b.empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
freeBuffer(b.b)
|
||||||
|
b.b = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeTo grows the buffer to at least c and returns the new capacity, freeing the
|
||||||
|
// previous buffer back into pool.
|
||||||
|
func (b *WString) ResizeTo(c uint32) uint32 {
|
||||||
|
// already sufficient (or n is 0)
|
||||||
|
if c <= b.Cap() {
|
||||||
|
return b.Cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c <= MinWStringCap {
|
||||||
|
c = MinWStringCap
|
||||||
|
}
|
||||||
|
// allocate at-least double buffer size, as is done in [bytes.Buffer] and other places
|
||||||
|
if c <= 2*b.Cap() {
|
||||||
|
c = 2 * b.Cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
b2 := make([]uint16, c)
|
||||||
|
if !b.empty() {
|
||||||
|
copy(b2, b.b)
|
||||||
|
freeBuffer(b.b)
|
||||||
|
}
|
||||||
|
b.b = b2
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer returns the underlying []uint16 buffer.
|
||||||
|
func (b *WString) Buffer() []uint16 {
|
||||||
|
if b.empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b.b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer returns a pointer to the first uint16 in the buffer.
|
||||||
|
// If the [WString.Free] has already been called, the pointer will be nil.
|
||||||
|
func (b *WString) Pointer() *uint16 {
|
||||||
|
if b.empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &b.b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer.
|
||||||
|
//
|
||||||
|
// It assumes that the data is null-terminated.
|
||||||
|
func (b *WString) String() string {
|
||||||
|
// Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows"
|
||||||
|
// and would make this code Windows-only, which makes no sense.
|
||||||
|
// So copy UTF16ToString code into here.
|
||||||
|
// If other windows-specific code is added, switch to [windows.UTF16ToString]
|
||||||
|
|
||||||
|
s := b.b
|
||||||
|
for i, v := range s {
|
||||||
|
if v == 0 {
|
||||||
|
s = s[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap returns the underlying buffer capacity.
|
||||||
|
func (b *WString) Cap() uint32 {
|
||||||
|
if b.empty() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return b.cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WString) cap() uint32 { return uint32(cap(b.b)) }
|
||||||
|
func (b *WString) empty() bool { return b == nil || b.cap() == 0 }
|
|
@ -0,0 +1,586 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe
|
||||||
|
//sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
//sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile
|
||||||
|
//sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
|
||||||
|
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U
|
||||||
|
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl
|
||||||
|
|
||||||
|
type PipeConn interface {
|
||||||
|
net.Conn
|
||||||
|
Disconnect() error
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// type aliases for mkwinsyscall code
|
||||||
|
type (
|
||||||
|
ntAccessMask = fs.AccessMask
|
||||||
|
ntFileShareMode = fs.FileShareMode
|
||||||
|
ntFileCreationDisposition = fs.NTFileCreationDisposition
|
||||||
|
ntFileOptions = fs.NTCreateOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
type ioStatusBlock struct {
|
||||||
|
Status, Information uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedef struct _OBJECT_ATTRIBUTES {
|
||||||
|
// ULONG Length;
|
||||||
|
// HANDLE RootDirectory;
|
||||||
|
// PUNICODE_STRING ObjectName;
|
||||||
|
// ULONG Attributes;
|
||||||
|
// PVOID SecurityDescriptor;
|
||||||
|
// PVOID SecurityQualityOfService;
|
||||||
|
// } OBJECT_ATTRIBUTES;
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes
|
||||||
|
type objectAttributes struct {
|
||||||
|
Length uintptr
|
||||||
|
RootDirectory uintptr
|
||||||
|
ObjectName *unicodeString
|
||||||
|
Attributes uintptr
|
||||||
|
SecurityDescriptor *securityDescriptor
|
||||||
|
SecurityQoS uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type unicodeString struct {
|
||||||
|
Length uint16
|
||||||
|
MaximumLength uint16
|
||||||
|
Buffer uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedef struct _SECURITY_DESCRIPTOR {
|
||||||
|
// BYTE Revision;
|
||||||
|
// BYTE Sbz1;
|
||||||
|
// SECURITY_DESCRIPTOR_CONTROL Control;
|
||||||
|
// PSID Owner;
|
||||||
|
// PSID Group;
|
||||||
|
// PACL Sacl;
|
||||||
|
// PACL Dacl;
|
||||||
|
// } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor
|
||||||
|
type securityDescriptor struct {
|
||||||
|
Revision byte
|
||||||
|
Sbz1 byte
|
||||||
|
Control uint16
|
||||||
|
Owner uintptr
|
||||||
|
Group uintptr
|
||||||
|
Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl
|
||||||
|
Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl
|
||||||
|
}
|
||||||
|
|
||||||
|
type ntStatus int32
|
||||||
|
|
||||||
|
func (status ntStatus) Err() error {
|
||||||
|
if status >= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rtlNtStatusToDosError(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
ErrPipeListenerClosed = net.ErrClosed
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PipeConn = (*win32Pipe)(nil)
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
if err := f.SetReadDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) Disconnect() error {
|
||||||
|
return disconnectNamedPipe(f.win32File.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
err := f.win32File.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF { //nolint:errorlint
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
} else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
|
||||||
|
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||||
|
// and the message still has more bytes. Treat this as a success, since
|
||||||
|
// this package presents all named pipes as byte streams.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||||
|
func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return windows.Handle(0), ctx.Err()
|
||||||
|
default:
|
||||||
|
h, err := fs.CreateFile(*path,
|
||||||
|
access,
|
||||||
|
0, // mode
|
||||||
|
nil, // security attributes
|
||||||
|
fs.OPEN_EXISTING,
|
||||||
|
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel),
|
||||||
|
0, // template file handle
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno
|
||||||
|
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
||||||
|
}
|
||||||
|
// Wait 10 msec and try again. This is a rather simplistic
|
||||||
|
// view, as we always try each 10 milliseconds.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then we use
|
||||||
|
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
} else {
|
||||||
|
absTimeout = time.Now().Add(2 * time.Second)
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := DialPipeContext(ctx, path)
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
||||||
|
return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeImpLevel is an enumeration of impersonation levels that may be set
|
||||||
|
// when calling DialPipeAccessImpersonation.
|
||||||
|
type PipeImpLevel uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS)
|
||||||
|
PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION)
|
||||||
|
PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION)
|
||||||
|
PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
||||||
|
return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with
|
||||||
|
// `access` at `impLevel` until `ctx` cancellation or timeout. The other
|
||||||
|
// DialPipe* implementations use PipeImpLevelAnonymous.
|
||||||
|
func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
var h windows.Handle
|
||||||
|
h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&windows.PIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle windows.Handle
|
||||||
|
path string
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) {
|
||||||
|
path16, err := windows.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oa objectAttributes
|
||||||
|
oa.Length = unsafe.Sizeof(oa)
|
||||||
|
|
||||||
|
var ntPath unicodeString
|
||||||
|
if err := rtlDosPathNameToNtPathName(&path16[0],
|
||||||
|
&ntPath,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
).Err(); err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck
|
||||||
|
oa.ObjectName = &ntPath
|
||||||
|
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
|
||||||
|
|
||||||
|
// The security descriptor is only needed for the first pipe.
|
||||||
|
if first {
|
||||||
|
if sd != nil {
|
||||||
|
//todo: does `sdb` need to be allocated on the heap, or can go allocate it?
|
||||||
|
l := uint32(len(sd))
|
||||||
|
sdb, err := windows.LocalAlloc(0, l)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err)
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck
|
||||||
|
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
|
||||||
|
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
|
||||||
|
} else {
|
||||||
|
// Construct the default named pipe security descriptor.
|
||||||
|
var dacl uintptr
|
||||||
|
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
|
||||||
|
return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck
|
||||||
|
|
||||||
|
sdb := &securityDescriptor{
|
||||||
|
Revision: 1,
|
||||||
|
Control: windows.SE_DACL_PRESENT,
|
||||||
|
Dacl: dacl,
|
||||||
|
}
|
||||||
|
oa.SecurityDescriptor = sdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||||
|
if c.MessageMode {
|
||||||
|
typ |= windows.FILE_PIPE_MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := fs.FILE_OPEN
|
||||||
|
access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE
|
||||||
|
if first {
|
||||||
|
disposition = fs.FILE_CREATE
|
||||||
|
// By not asking for read or write access, the named pipe file system
|
||||||
|
// will put this pipe into an initially disconnected state, blocking
|
||||||
|
// client connections until the next call with first == false.
|
||||||
|
access = fs.SYNCHRONIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := int64(-50 * 10000) // 50ms
|
||||||
|
|
||||||
|
var (
|
||||||
|
h windows.Handle
|
||||||
|
iosb ioStatusBlock
|
||||||
|
)
|
||||||
|
err = ntCreateNamedPipeFile(&h,
|
||||||
|
access,
|
||||||
|
&oa,
|
||||||
|
&iosb,
|
||||||
|
fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE,
|
||||||
|
disposition,
|
||||||
|
0,
|
||||||
|
typ,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0xffffffff,
|
||||||
|
uint32(c.InputBufferSize),
|
||||||
|
uint32(c.OutputBufferSize),
|
||||||
|
&timeout).Err()
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(ntPath)
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func(p *win32File) {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}(p)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
var (
|
||||||
|
p *win32File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
p, err = l.makeConnectedServerPipe()
|
||||||
|
// If the connection was immediately closed by the client, try
|
||||||
|
// again.
|
||||||
|
if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
|
||||||
|
}
|
||||||
|
}
|
||||||
|
windows.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size of the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size of the output buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIO(c, nil, 0, err)
|
||||||
|
if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||||
|
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||||
|
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||||
|
// and the Windows (mixed-endian) encoding. See here for details:
|
||||||
|
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1" //nolint:gosec // not used for secure application
|
||||||
|
"encoding"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
|
||||||
|
|
||||||
|
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||||
|
// how the entirety of the rest of the GUID is interpreted.
|
||||||
|
type Variant uint8
|
||||||
|
|
||||||
|
// The variants specified by RFC 4122 section 4.1.1.
|
||||||
|
const (
|
||||||
|
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||||
|
// the variant encodings specified in RFC 4122.
|
||||||
|
VariantUnknown Variant = iota
|
||||||
|
VariantNCS
|
||||||
|
VariantRFC4122 // RFC 4122
|
||||||
|
VariantMicrosoft
|
||||||
|
VariantFuture
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||||
|
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||||
|
// hash of an input string.
|
||||||
|
type Version uint8
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (encoding.TextMarshaler)(GUID{})
|
||||||
|
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
||||||
|
|
||||||
|
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||||
|
func NewV4() (GUID, error) {
|
||||||
|
var b [16]byte
|
||||||
|
if _, err := rand.Read(b[:]); err != nil {
|
||||||
|
return GUID{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := FromArray(b)
|
||||||
|
g.setVersion(4) // Version 4 means randomly generated.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||||
|
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||||
|
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||||
|
//
|
||||||
|
// Some implementations, such as those found on Windows, treat the name as a
|
||||||
|
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||||
|
// encoded as such before being passed to this function.
|
||||||
|
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
||||||
|
b := sha1.New() //nolint:gosec // not used for secure application
|
||||||
|
namespaceBytes := namespace.ToArray()
|
||||||
|
b.Write(namespaceBytes[:])
|
||||||
|
b.Write(name)
|
||||||
|
|
||||||
|
a := [16]byte{}
|
||||||
|
copy(a[:], b.Sum(nil))
|
||||||
|
|
||||||
|
g := FromArray(a)
|
||||||
|
g.setVersion(5) // Version 5 means generated from a string.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
||||||
|
var g GUID
|
||||||
|
g.Data1 = order.Uint32(b[0:4])
|
||||||
|
g.Data2 = order.Uint16(b[4:6])
|
||||||
|
g.Data3 = order.Uint16(b[6:8])
|
||||||
|
copy(g.Data4[:], b[8:16])
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
||||||
|
b := [16]byte{}
|
||||||
|
order.PutUint32(b[0:4], g.Data1)
|
||||||
|
order.PutUint16(b[4:6], g.Data2)
|
||||||
|
order.PutUint16(b[6:8], g.Data3)
|
||||||
|
copy(b[8:16], g.Data4[:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||||
|
func FromArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToArray() [16]byte {
|
||||||
|
return g.toArray(binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||||
|
func FromWindowsArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToWindowsArray() [16]byte {
|
||||||
|
return g.toArray(binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%08x-%04x-%04x-%04x-%012x",
|
||||||
|
g.Data1,
|
||||||
|
g.Data2,
|
||||||
|
g.Data3,
|
||||||
|
g.Data4[:2],
|
||||||
|
g.Data4[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||||
|
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||||
|
// format.
|
||||||
|
func FromString(s string) (GUID, error) {
|
||||||
|
if len(s) != 36 {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var g GUID
|
||||||
|
|
||||||
|
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data1 = uint32(data1)
|
||||||
|
|
||||||
|
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data2 = uint16(data2)
|
||||||
|
|
||||||
|
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data3 = uint16(data3)
|
||||||
|
|
||||||
|
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||||
|
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data4[i] = uint8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVariant(v Variant) {
|
||||||
|
d := g.Data4[0]
|
||||||
|
switch v {
|
||||||
|
case VariantNCS:
|
||||||
|
d = (d & 0x7f)
|
||||||
|
case VariantRFC4122:
|
||||||
|
d = (d & 0x3f) | 0x80
|
||||||
|
case VariantMicrosoft:
|
||||||
|
d = (d & 0x1f) | 0xc0
|
||||||
|
case VariantFuture:
|
||||||
|
d = (d & 0x0f) | 0xe0
|
||||||
|
case VariantUnknown:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid variant: %d", v))
|
||||||
|
}
|
||||||
|
g.Data4[0] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||||
|
func (g GUID) Variant() Variant {
|
||||||
|
b := g.Data4[0]
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
return VariantNCS
|
||||||
|
} else if b&0xc0 == 0x80 {
|
||||||
|
return VariantRFC4122
|
||||||
|
} else if b&0xe0 == 0xc0 {
|
||||||
|
return VariantMicrosoft
|
||||||
|
} else if b&0xe0 == 0xe0 {
|
||||||
|
return VariantFuture
|
||||||
|
}
|
||||||
|
return VariantUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVersion(v Version) {
|
||||||
|
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the GUID version, as defined in RFC 4122.
|
||||||
|
func (g GUID) Version() Version {
|
||||||
|
return Version((g.Data3 & 0xF000) >> 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText returns the textual representation of the GUID.
|
||||||
|
func (g GUID) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(g.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||||
|
// into this GUID.
|
||||||
|
func (g *GUID) UnmarshalText(text []byte) error {
|
||||||
|
g2, err := FromString(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*g = g2
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type as that is only available to builds
|
||||||
|
// targeted at `windows`. The representation matches that used by native Windows
|
||||||
|
// code.
|
||||||
|
type GUID struct {
|
||||||
|
Data1 uint32
|
||||||
|
Data2 uint16
|
||||||
|
Data3 uint16
|
||||||
|
Data4 [8]byte
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type so that stringification and
|
||||||
|
// marshaling can be supported. The representation matches that used by native
|
||||||
|
// Windows code.
|
||||||
|
type GUID windows.GUID
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[VariantUnknown-0]
|
||||||
|
_ = x[VariantNCS-1]
|
||||||
|
_ = x[VariantRFC4122-2]
|
||||||
|
_ = x[VariantMicrosoft-3]
|
||||||
|
_ = x[VariantFuture-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture"
|
||||||
|
|
||||||
|
var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33}
|
||||||
|
|
||||||
|
func (i Variant) String() string {
|
||||||
|
if i >= Variant(len(_Variant_index)-1) {
|
||||||
|
return "Variant(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Variant_name[_Variant_index[i]:_Variant_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h windows.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
//revive:disable-next-line:var-naming ALL_CAPS
|
||||||
|
SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
|
||||||
|
|
||||||
|
//revive:disable-next-line:var-naming ALL_CAPS
|
||||||
|
ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
SeSecurityPrivilege = "SeSecurityPrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privNames = make(map[string]uint64)
|
||||||
|
privNameMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivilegeError represents an error enabling privileges.
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := "Could not enable privilege "
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivilege enables a single privilege for a function call.
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivileges enables privileges for a function call.
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPrivileges(names []string) ([]uint64, error) {
|
||||||
|
privileges := make([]uint64, 0, len(names))
|
||||||
|
privNameMutex.Lock()
|
||||||
|
defer privNameMutex.Unlock()
|
||||||
|
for _, name := range names {
|
||||||
|
p, ok := privNames[name]
|
||||||
|
if !ok {
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privNames[name] = p
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableProcessPrivileges enables privileges globally for the process.
|
||||||
|
func EnableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableProcessPrivileges disables privileges globally for the process.
|
||||||
|
func DisableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := windows.CurrentProcess()
|
||||||
|
var token windows.Token
|
||||||
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer token.Close()
|
||||||
|
return adjustPrivileges(token, privileges, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, action)
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langID uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (windows.Token, error) {
|
||||||
|
err := impersonateSelf(windows.SecurityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token windows.Token
|
||||||
|
err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h windows.Token) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
return DecodeReparsePointData(tag, b[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = `\??\` + rp.Target[4:]
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch {
|
||||||
|
case errors.Is(e.Err, windows.ERROR_INVALID_SID):
|
||||||
|
s = "the security ID structure is invalid"
|
||||||
|
case errors.Is(e.Err, windows.ERROR_NONE_MAPPED):
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
//
|
||||||
|
//revive:disable-next-line:var-naming SID, not Sid
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
_, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupNameBySid looks up the name of an account by SID
|
||||||
|
//
|
||||||
|
//revive:disable-next-line:var-naming SID, not Sid
|
||||||
|
func LookupNameBySid(sid string) (name string, err error) {
|
||||||
|
if sid == "" {
|
||||||
|
return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
sidBuffer, err := windows.UTF16PtrFromString(sid)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidPtr *byte
|
||||||
|
if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck
|
||||||
|
|
||||||
|
var nameSize, refDomainSize, sidNameUse uint32
|
||||||
|
err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameBuffer := make([]uint16, nameSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
name = windows.UTF16ToString(nameBuffer)
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
sd, err := windows.SecurityDescriptorFromString(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{Sddl: sddl, Err: err}
|
||||||
|
}
|
||||||
|
b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length())
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l {
|
||||||
|
return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE)
|
||||||
|
}
|
||||||
|
s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0]))
|
||||||
|
return s.String(), nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go
|
|
@ -0,0 +1,378 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
||||||
|
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
||||||
|
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
||||||
|
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
||||||
|
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||||
|
)
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSidToSid(str *uint16, sid **byte) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr())
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount))
|
||||||
|
newport = windows.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)))
|
||||||
|
handle = windows.Handle(r0)
|
||||||
|
if handle == windows.InvalidHandle {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnectNamedPipe(pipe windows.Handle) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h windows.Handle) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr())
|
||||||
|
h = windows.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl)))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlNtStatusToDosError(status ntStatus) (winerr error) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status))
|
||||||
|
if r0 != 0 {
|
||||||
|
winerr = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if wait {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright (c) 2012, Neal van Veen (nealvanveen@gmail.com)
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The views and conclusions contained in the software and documentation are those
|
||||||
|
of the authors and should not be interpreted as representing official policies,
|
||||||
|
either expressed or implied, of the FreeBSD Project.
|
|
@ -0,0 +1,5 @@
|
||||||
|
Gotty is a library written in Go that determines and reads termcap database
|
||||||
|
files to produce an interface for interacting with the capabilities of a
|
||||||
|
terminal.
|
||||||
|
See the godoc documentation or the source code for more information about
|
||||||
|
function usage.
|
|
@ -0,0 +1,3 @@
|
||||||
|
gotty.go:// TODO add more concurrency to name lookup, look for more opportunities.
|
||||||
|
all:// TODO add more documentation, with function usage in a doc.go file.
|
||||||
|
all:// TODO add more testing/benchmarking with go test.
|
|
@ -0,0 +1,514 @@
|
||||||
|
// Copyright 2012 Neal van Veen. All rights reserved.
|
||||||
|
// Usage of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package gotty
|
||||||
|
|
||||||
|
// Boolean capabilities
|
||||||
|
var BoolAttr = [...]string{
|
||||||
|
"auto_left_margin", "bw",
|
||||||
|
"auto_right_margin", "am",
|
||||||
|
"no_esc_ctlc", "xsb",
|
||||||
|
"ceol_standout_glitch", "xhp",
|
||||||
|
"eat_newline_glitch", "xenl",
|
||||||
|
"erase_overstrike", "eo",
|
||||||
|
"generic_type", "gn",
|
||||||
|
"hard_copy", "hc",
|
||||||
|
"has_meta_key", "km",
|
||||||
|
"has_status_line", "hs",
|
||||||
|
"insert_null_glitch", "in",
|
||||||
|
"memory_above", "da",
|
||||||
|
"memory_below", "db",
|
||||||
|
"move_insert_mode", "mir",
|
||||||
|
"move_standout_mode", "msgr",
|
||||||
|
"over_strike", "os",
|
||||||
|
"status_line_esc_ok", "eslok",
|
||||||
|
"dest_tabs_magic_smso", "xt",
|
||||||
|
"tilde_glitch", "hz",
|
||||||
|
"transparent_underline", "ul",
|
||||||
|
"xon_xoff", "nxon",
|
||||||
|
"needs_xon_xoff", "nxon",
|
||||||
|
"prtr_silent", "mc5i",
|
||||||
|
"hard_cursor", "chts",
|
||||||
|
"non_rev_rmcup", "nrrmc",
|
||||||
|
"no_pad_char", "npc",
|
||||||
|
"non_dest_scroll_region", "ndscr",
|
||||||
|
"can_change", "ccc",
|
||||||
|
"back_color_erase", "bce",
|
||||||
|
"hue_lightness_saturation", "hls",
|
||||||
|
"col_addr_glitch", "xhpa",
|
||||||
|
"cr_cancels_micro_mode", "crxm",
|
||||||
|
"has_print_wheel", "daisy",
|
||||||
|
"row_addr_glitch", "xvpa",
|
||||||
|
"semi_auto_right_margin", "sam",
|
||||||
|
"cpi_changes_res", "cpix",
|
||||||
|
"lpi_changes_res", "lpix",
|
||||||
|
"backspaces_with_bs", "",
|
||||||
|
"crt_no_scrolling", "",
|
||||||
|
"no_correctly_working_cr", "",
|
||||||
|
"gnu_has_meta_key", "",
|
||||||
|
"linefeed_is_newline", "",
|
||||||
|
"has_hardware_tabs", "",
|
||||||
|
"return_does_clr_eol", "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numerical capabilities
|
||||||
|
var NumAttr = [...]string{
|
||||||
|
"columns", "cols",
|
||||||
|
"init_tabs", "it",
|
||||||
|
"lines", "lines",
|
||||||
|
"lines_of_memory", "lm",
|
||||||
|
"magic_cookie_glitch", "xmc",
|
||||||
|
"padding_baud_rate", "pb",
|
||||||
|
"virtual_terminal", "vt",
|
||||||
|
"width_status_line", "wsl",
|
||||||
|
"num_labels", "nlab",
|
||||||
|
"label_height", "lh",
|
||||||
|
"label_width", "lw",
|
||||||
|
"max_attributes", "ma",
|
||||||
|
"maximum_windows", "wnum",
|
||||||
|
"max_colors", "colors",
|
||||||
|
"max_pairs", "pairs",
|
||||||
|
"no_color_video", "ncv",
|
||||||
|
"buffer_capacity", "bufsz",
|
||||||
|
"dot_vert_spacing", "spinv",
|
||||||
|
"dot_horz_spacing", "spinh",
|
||||||
|
"max_micro_address", "maddr",
|
||||||
|
"max_micro_jump", "mjump",
|
||||||
|
"micro_col_size", "mcs",
|
||||||
|
"micro_line_size", "mls",
|
||||||
|
"number_of_pins", "npins",
|
||||||
|
"output_res_char", "orc",
|
||||||
|
"output_res_line", "orl",
|
||||||
|
"output_res_horz_inch", "orhi",
|
||||||
|
"output_res_vert_inch", "orvi",
|
||||||
|
"print_rate", "cps",
|
||||||
|
"wide_char_size", "widcs",
|
||||||
|
"buttons", "btns",
|
||||||
|
"bit_image_entwining", "bitwin",
|
||||||
|
"bit_image_type", "bitype",
|
||||||
|
"magic_cookie_glitch_ul", "",
|
||||||
|
"carriage_return_delay", "",
|
||||||
|
"new_line_delay", "",
|
||||||
|
"backspace_delay", "",
|
||||||
|
"horizontal_tab_delay", "",
|
||||||
|
"number_of_function_keys", "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String capabilities
|
||||||
|
var StrAttr = [...]string{
|
||||||
|
"back_tab", "cbt",
|
||||||
|
"bell", "bel",
|
||||||
|
"carriage_return", "cr",
|
||||||
|
"change_scroll_region", "csr",
|
||||||
|
"clear_all_tabs", "tbc",
|
||||||
|
"clear_screen", "clear",
|
||||||
|
"clr_eol", "el",
|
||||||
|
"clr_eos", "ed",
|
||||||
|
"column_address", "hpa",
|
||||||
|
"command_character", "cmdch",
|
||||||
|
"cursor_address", "cup",
|
||||||
|
"cursor_down", "cud1",
|
||||||
|
"cursor_home", "home",
|
||||||
|
"cursor_invisible", "civis",
|
||||||
|
"cursor_left", "cub1",
|
||||||
|
"cursor_mem_address", "mrcup",
|
||||||
|
"cursor_normal", "cnorm",
|
||||||
|
"cursor_right", "cuf1",
|
||||||
|
"cursor_to_ll", "ll",
|
||||||
|
"cursor_up", "cuu1",
|
||||||
|
"cursor_visible", "cvvis",
|
||||||
|
"delete_character", "dch1",
|
||||||
|
"delete_line", "dl1",
|
||||||
|
"dis_status_line", "dsl",
|
||||||
|
"down_half_line", "hd",
|
||||||
|
"enter_alt_charset_mode", "smacs",
|
||||||
|
"enter_blink_mode", "blink",
|
||||||
|
"enter_bold_mode", "bold",
|
||||||
|
"enter_ca_mode", "smcup",
|
||||||
|
"enter_delete_mode", "smdc",
|
||||||
|
"enter_dim_mode", "dim",
|
||||||
|
"enter_insert_mode", "smir",
|
||||||
|
"enter_secure_mode", "invis",
|
||||||
|
"enter_protected_mode", "prot",
|
||||||
|
"enter_reverse_mode", "rev",
|
||||||
|
"enter_standout_mode", "smso",
|
||||||
|
"enter_underline_mode", "smul",
|
||||||
|
"erase_chars", "ech",
|
||||||
|
"exit_alt_charset_mode", "rmacs",
|
||||||
|
"exit_attribute_mode", "sgr0",
|
||||||
|
"exit_ca_mode", "rmcup",
|
||||||
|
"exit_delete_mode", "rmdc",
|
||||||
|
"exit_insert_mode", "rmir",
|
||||||
|
"exit_standout_mode", "rmso",
|
||||||
|
"exit_underline_mode", "rmul",
|
||||||
|
"flash_screen", "flash",
|
||||||
|
"form_feed", "ff",
|
||||||
|
"from_status_line", "fsl",
|
||||||
|
"init_1string", "is1",
|
||||||
|
"init_2string", "is2",
|
||||||
|
"init_3string", "is3",
|
||||||
|
"init_file", "if",
|
||||||
|
"insert_character", "ich1",
|
||||||
|
"insert_line", "il1",
|
||||||
|
"insert_padding", "ip",
|
||||||
|
"key_backspace", "kbs",
|
||||||
|
"key_catab", "ktbc",
|
||||||
|
"key_clear", "kclr",
|
||||||
|
"key_ctab", "kctab",
|
||||||
|
"key_dc", "kdch1",
|
||||||
|
"key_dl", "kdl1",
|
||||||
|
"key_down", "kcud1",
|
||||||
|
"key_eic", "krmir",
|
||||||
|
"key_eol", "kel",
|
||||||
|
"key_eos", "ked",
|
||||||
|
"key_f0", "kf0",
|
||||||
|
"key_f1", "kf1",
|
||||||
|
"key_f10", "kf10",
|
||||||
|
"key_f2", "kf2",
|
||||||
|
"key_f3", "kf3",
|
||||||
|
"key_f4", "kf4",
|
||||||
|
"key_f5", "kf5",
|
||||||
|
"key_f6", "kf6",
|
||||||
|
"key_f7", "kf7",
|
||||||
|
"key_f8", "kf8",
|
||||||
|
"key_f9", "kf9",
|
||||||
|
"key_home", "khome",
|
||||||
|
"key_ic", "kich1",
|
||||||
|
"key_il", "kil1",
|
||||||
|
"key_left", "kcub1",
|
||||||
|
"key_ll", "kll",
|
||||||
|
"key_npage", "knp",
|
||||||
|
"key_ppage", "kpp",
|
||||||
|
"key_right", "kcuf1",
|
||||||
|
"key_sf", "kind",
|
||||||
|
"key_sr", "kri",
|
||||||
|
"key_stab", "khts",
|
||||||
|
"key_up", "kcuu1",
|
||||||
|
"keypad_local", "rmkx",
|
||||||
|
"keypad_xmit", "smkx",
|
||||||
|
"lab_f0", "lf0",
|
||||||
|
"lab_f1", "lf1",
|
||||||
|
"lab_f10", "lf10",
|
||||||
|
"lab_f2", "lf2",
|
||||||
|
"lab_f3", "lf3",
|
||||||
|
"lab_f4", "lf4",
|
||||||
|
"lab_f5", "lf5",
|
||||||
|
"lab_f6", "lf6",
|
||||||
|
"lab_f7", "lf7",
|
||||||
|
"lab_f8", "lf8",
|
||||||
|
"lab_f9", "lf9",
|
||||||
|
"meta_off", "rmm",
|
||||||
|
"meta_on", "smm",
|
||||||
|
"newline", "_glitch",
|
||||||
|
"pad_char", "npc",
|
||||||
|
"parm_dch", "dch",
|
||||||
|
"parm_delete_line", "dl",
|
||||||
|
"parm_down_cursor", "cud",
|
||||||
|
"parm_ich", "ich",
|
||||||
|
"parm_index", "indn",
|
||||||
|
"parm_insert_line", "il",
|
||||||
|
"parm_left_cursor", "cub",
|
||||||
|
"parm_right_cursor", "cuf",
|
||||||
|
"parm_rindex", "rin",
|
||||||
|
"parm_up_cursor", "cuu",
|
||||||
|
"pkey_key", "pfkey",
|
||||||
|
"pkey_local", "pfloc",
|
||||||
|
"pkey_xmit", "pfx",
|
||||||
|
"print_screen", "mc0",
|
||||||
|
"prtr_off", "mc4",
|
||||||
|
"prtr_on", "mc5",
|
||||||
|
"repeat_char", "rep",
|
||||||
|
"reset_1string", "rs1",
|
||||||
|
"reset_2string", "rs2",
|
||||||
|
"reset_3string", "rs3",
|
||||||
|
"reset_file", "rf",
|
||||||
|
"restore_cursor", "rc",
|
||||||
|
"row_address", "mvpa",
|
||||||
|
"save_cursor", "row_address",
|
||||||
|
"scroll_forward", "ind",
|
||||||
|
"scroll_reverse", "ri",
|
||||||
|
"set_attributes", "sgr",
|
||||||
|
"set_tab", "hts",
|
||||||
|
"set_window", "wind",
|
||||||
|
"tab", "s_magic_smso",
|
||||||
|
"to_status_line", "tsl",
|
||||||
|
"underline_char", "uc",
|
||||||
|
"up_half_line", "hu",
|
||||||
|
"init_prog", "iprog",
|
||||||
|
"key_a1", "ka1",
|
||||||
|
"key_a3", "ka3",
|
||||||
|
"key_b2", "kb2",
|
||||||
|
"key_c1", "kc1",
|
||||||
|
"key_c3", "kc3",
|
||||||
|
"prtr_non", "mc5p",
|
||||||
|
"char_padding", "rmp",
|
||||||
|
"acs_chars", "acsc",
|
||||||
|
"plab_norm", "pln",
|
||||||
|
"key_btab", "kcbt",
|
||||||
|
"enter_xon_mode", "smxon",
|
||||||
|
"exit_xon_mode", "rmxon",
|
||||||
|
"enter_am_mode", "smam",
|
||||||
|
"exit_am_mode", "rmam",
|
||||||
|
"xon_character", "xonc",
|
||||||
|
"xoff_character", "xoffc",
|
||||||
|
"ena_acs", "enacs",
|
||||||
|
"label_on", "smln",
|
||||||
|
"label_off", "rmln",
|
||||||
|
"key_beg", "kbeg",
|
||||||
|
"key_cancel", "kcan",
|
||||||
|
"key_close", "kclo",
|
||||||
|
"key_command", "kcmd",
|
||||||
|
"key_copy", "kcpy",
|
||||||
|
"key_create", "kcrt",
|
||||||
|
"key_end", "kend",
|
||||||
|
"key_enter", "kent",
|
||||||
|
"key_exit", "kext",
|
||||||
|
"key_find", "kfnd",
|
||||||
|
"key_help", "khlp",
|
||||||
|
"key_mark", "kmrk",
|
||||||
|
"key_message", "kmsg",
|
||||||
|
"key_move", "kmov",
|
||||||
|
"key_next", "knxt",
|
||||||
|
"key_open", "kopn",
|
||||||
|
"key_options", "kopt",
|
||||||
|
"key_previous", "kprv",
|
||||||
|
"key_print", "kprt",
|
||||||
|
"key_redo", "krdo",
|
||||||
|
"key_reference", "kref",
|
||||||
|
"key_refresh", "krfr",
|
||||||
|
"key_replace", "krpl",
|
||||||
|
"key_restart", "krst",
|
||||||
|
"key_resume", "kres",
|
||||||
|
"key_save", "ksav",
|
||||||
|
"key_suspend", "kspd",
|
||||||
|
"key_undo", "kund",
|
||||||
|
"key_sbeg", "kBEG",
|
||||||
|
"key_scancel", "kCAN",
|
||||||
|
"key_scommand", "kCMD",
|
||||||
|
"key_scopy", "kCPY",
|
||||||
|
"key_screate", "kCRT",
|
||||||
|
"key_sdc", "kDC",
|
||||||
|
"key_sdl", "kDL",
|
||||||
|
"key_select", "kslt",
|
||||||
|
"key_send", "kEND",
|
||||||
|
"key_seol", "kEOL",
|
||||||
|
"key_sexit", "kEXT",
|
||||||
|
"key_sfind", "kFND",
|
||||||
|
"key_shelp", "kHLP",
|
||||||
|
"key_shome", "kHOM",
|
||||||
|
"key_sic", "kIC",
|
||||||
|
"key_sleft", "kLFT",
|
||||||
|
"key_smessage", "kMSG",
|
||||||
|
"key_smove", "kMOV",
|
||||||
|
"key_snext", "kNXT",
|
||||||
|
"key_soptions", "kOPT",
|
||||||
|
"key_sprevious", "kPRV",
|
||||||
|
"key_sprint", "kPRT",
|
||||||
|
"key_sredo", "kRDO",
|
||||||
|
"key_sreplace", "kRPL",
|
||||||
|
"key_sright", "kRIT",
|
||||||
|
"key_srsume", "kRES",
|
||||||
|
"key_ssave", "kSAV",
|
||||||
|
"key_ssuspend", "kSPD",
|
||||||
|
"key_sundo", "kUND",
|
||||||
|
"req_for_input", "rfi",
|
||||||
|
"key_f11", "kf11",
|
||||||
|
"key_f12", "kf12",
|
||||||
|
"key_f13", "kf13",
|
||||||
|
"key_f14", "kf14",
|
||||||
|
"key_f15", "kf15",
|
||||||
|
"key_f16", "kf16",
|
||||||
|
"key_f17", "kf17",
|
||||||
|
"key_f18", "kf18",
|
||||||
|
"key_f19", "kf19",
|
||||||
|
"key_f20", "kf20",
|
||||||
|
"key_f21", "kf21",
|
||||||
|
"key_f22", "kf22",
|
||||||
|
"key_f23", "kf23",
|
||||||
|
"key_f24", "kf24",
|
||||||
|
"key_f25", "kf25",
|
||||||
|
"key_f26", "kf26",
|
||||||
|
"key_f27", "kf27",
|
||||||
|
"key_f28", "kf28",
|
||||||
|
"key_f29", "kf29",
|
||||||
|
"key_f30", "kf30",
|
||||||
|
"key_f31", "kf31",
|
||||||
|
"key_f32", "kf32",
|
||||||
|
"key_f33", "kf33",
|
||||||
|
"key_f34", "kf34",
|
||||||
|
"key_f35", "kf35",
|
||||||
|
"key_f36", "kf36",
|
||||||
|
"key_f37", "kf37",
|
||||||
|
"key_f38", "kf38",
|
||||||
|
"key_f39", "kf39",
|
||||||
|
"key_f40", "kf40",
|
||||||
|
"key_f41", "kf41",
|
||||||
|
"key_f42", "kf42",
|
||||||
|
"key_f43", "kf43",
|
||||||
|
"key_f44", "kf44",
|
||||||
|
"key_f45", "kf45",
|
||||||
|
"key_f46", "kf46",
|
||||||
|
"key_f47", "kf47",
|
||||||
|
"key_f48", "kf48",
|
||||||
|
"key_f49", "kf49",
|
||||||
|
"key_f50", "kf50",
|
||||||
|
"key_f51", "kf51",
|
||||||
|
"key_f52", "kf52",
|
||||||
|
"key_f53", "kf53",
|
||||||
|
"key_f54", "kf54",
|
||||||
|
"key_f55", "kf55",
|
||||||
|
"key_f56", "kf56",
|
||||||
|
"key_f57", "kf57",
|
||||||
|
"key_f58", "kf58",
|
||||||
|
"key_f59", "kf59",
|
||||||
|
"key_f60", "kf60",
|
||||||
|
"key_f61", "kf61",
|
||||||
|
"key_f62", "kf62",
|
||||||
|
"key_f63", "kf63",
|
||||||
|
"clr_bol", "el1",
|
||||||
|
"clear_margins", "mgc",
|
||||||
|
"set_left_margin", "smgl",
|
||||||
|
"set_right_margin", "smgr",
|
||||||
|
"label_format", "fln",
|
||||||
|
"set_clock", "sclk",
|
||||||
|
"display_clock", "dclk",
|
||||||
|
"remove_clock", "rmclk",
|
||||||
|
"create_window", "cwin",
|
||||||
|
"goto_window", "wingo",
|
||||||
|
"hangup", "hup",
|
||||||
|
"dial_phone", "dial",
|
||||||
|
"quick_dial", "qdial",
|
||||||
|
"tone", "tone",
|
||||||
|
"pulse", "pulse",
|
||||||
|
"flash_hook", "hook",
|
||||||
|
"fixed_pause", "pause",
|
||||||
|
"wait_tone", "wait",
|
||||||
|
"user0", "u0",
|
||||||
|
"user1", "u1",
|
||||||
|
"user2", "u2",
|
||||||
|
"user3", "u3",
|
||||||
|
"user4", "u4",
|
||||||
|
"user5", "u5",
|
||||||
|
"user6", "u6",
|
||||||
|
"user7", "u7",
|
||||||
|
"user8", "u8",
|
||||||
|
"user9", "u9",
|
||||||
|
"orig_pair", "op",
|
||||||
|
"orig_colors", "oc",
|
||||||
|
"initialize_color", "initc",
|
||||||
|
"initialize_pair", "initp",
|
||||||
|
"set_color_pair", "scp",
|
||||||
|
"set_foreground", "setf",
|
||||||
|
"set_background", "setb",
|
||||||
|
"change_char_pitch", "cpi",
|
||||||
|
"change_line_pitch", "lpi",
|
||||||
|
"change_res_horz", "chr",
|
||||||
|
"change_res_vert", "cvr",
|
||||||
|
"define_char", "defc",
|
||||||
|
"enter_doublewide_mode", "swidm",
|
||||||
|
"enter_draft_quality", "sdrfq",
|
||||||
|
"enter_italics_mode", "sitm",
|
||||||
|
"enter_leftward_mode", "slm",
|
||||||
|
"enter_micro_mode", "smicm",
|
||||||
|
"enter_near_letter_quality", "snlq",
|
||||||
|
"enter_normal_quality", "snrmq",
|
||||||
|
"enter_shadow_mode", "sshm",
|
||||||
|
"enter_subscript_mode", "ssubm",
|
||||||
|
"enter_superscript_mode", "ssupm",
|
||||||
|
"enter_upward_mode", "sum",
|
||||||
|
"exit_doublewide_mode", "rwidm",
|
||||||
|
"exit_italics_mode", "ritm",
|
||||||
|
"exit_leftward_mode", "rlm",
|
||||||
|
"exit_micro_mode", "rmicm",
|
||||||
|
"exit_shadow_mode", "rshm",
|
||||||
|
"exit_subscript_mode", "rsubm",
|
||||||
|
"exit_superscript_mode", "rsupm",
|
||||||
|
"exit_upward_mode", "rum",
|
||||||
|
"micro_column_address", "mhpa",
|
||||||
|
"micro_down", "mcud1",
|
||||||
|
"micro_left", "mcub1",
|
||||||
|
"micro_right", "mcuf1",
|
||||||
|
"micro_row_address", "mvpa",
|
||||||
|
"micro_up", "mcuu1",
|
||||||
|
"order_of_pins", "porder",
|
||||||
|
"parm_down_micro", "mcud",
|
||||||
|
"parm_left_micro", "mcub",
|
||||||
|
"parm_right_micro", "mcuf",
|
||||||
|
"parm_up_micro", "mcuu",
|
||||||
|
"select_char_set", "scs",
|
||||||
|
"set_bottom_margin", "smgb",
|
||||||
|
"set_bottom_margin_parm", "smgbp",
|
||||||
|
"set_left_margin_parm", "smglp",
|
||||||
|
"set_right_margin_parm", "smgrp",
|
||||||
|
"set_top_margin", "smgt",
|
||||||
|
"set_top_margin_parm", "smgtp",
|
||||||
|
"start_bit_image", "sbim",
|
||||||
|
"start_char_set_def", "scsd",
|
||||||
|
"stop_bit_image", "rbim",
|
||||||
|
"stop_char_set_def", "rcsd",
|
||||||
|
"subscript_characters", "subcs",
|
||||||
|
"superscript_characters", "supcs",
|
||||||
|
"these_cause_cr", "docr",
|
||||||
|
"zero_motion", "zerom",
|
||||||
|
"char_set_names", "csnm",
|
||||||
|
"key_mouse", "kmous",
|
||||||
|
"mouse_info", "minfo",
|
||||||
|
"req_mouse_pos", "reqmp",
|
||||||
|
"get_mouse", "getm",
|
||||||
|
"set_a_foreground", "setaf",
|
||||||
|
"set_a_background", "setab",
|
||||||
|
"pkey_plab", "pfxl",
|
||||||
|
"device_type", "devt",
|
||||||
|
"code_set_init", "csin",
|
||||||
|
"set0_des_seq", "s0ds",
|
||||||
|
"set1_des_seq", "s1ds",
|
||||||
|
"set2_des_seq", "s2ds",
|
||||||
|
"set3_des_seq", "s3ds",
|
||||||
|
"set_lr_margin", "smglr",
|
||||||
|
"set_tb_margin", "smgtb",
|
||||||
|
"bit_image_repeat", "birep",
|
||||||
|
"bit_image_newline", "binel",
|
||||||
|
"bit_image_carriage_return", "bicr",
|
||||||
|
"color_names", "colornm",
|
||||||
|
"define_bit_image_region", "defbi",
|
||||||
|
"end_bit_image_region", "endbi",
|
||||||
|
"set_color_band", "setcolor",
|
||||||
|
"set_page_length", "slines",
|
||||||
|
"display_pc_char", "dispc",
|
||||||
|
"enter_pc_charset_mode", "smpch",
|
||||||
|
"exit_pc_charset_mode", "rmpch",
|
||||||
|
"enter_scancode_mode", "smsc",
|
||||||
|
"exit_scancode_mode", "rmsc",
|
||||||
|
"pc_term_options", "pctrm",
|
||||||
|
"scancode_escape", "scesc",
|
||||||
|
"alt_scancode_esc", "scesa",
|
||||||
|
"enter_horizontal_hl_mode", "ehhlm",
|
||||||
|
"enter_left_hl_mode", "elhlm",
|
||||||
|
"enter_low_hl_mode", "elohlm",
|
||||||
|
"enter_right_hl_mode", "erhlm",
|
||||||
|
"enter_top_hl_mode", "ethlm",
|
||||||
|
"enter_vertical_hl_mode", "evhlm",
|
||||||
|
"set_a_attributes", "sgr1",
|
||||||
|
"set_pglen_inch", "slength",
|
||||||
|
"termcap_init2", "",
|
||||||
|
"termcap_reset", "",
|
||||||
|
"linefeed_if_not_lf", "",
|
||||||
|
"backspace_if_not_bs", "",
|
||||||
|
"other_non_function_keys", "",
|
||||||
|
"arrow_key_map", "",
|
||||||
|
"acs_ulcorner", "",
|
||||||
|
"acs_llcorner", "",
|
||||||
|
"acs_urcorner", "",
|
||||||
|
"acs_lrcorner", "",
|
||||||
|
"acs_ltee", "",
|
||||||
|
"acs_rtee", "",
|
||||||
|
"acs_btee", "",
|
||||||
|
"acs_ttee", "",
|
||||||
|
"acs_hline", "",
|
||||||
|
"acs_vline", "",
|
||||||
|
"acs_plus", "",
|
||||||
|
"memory_lock", "",
|
||||||
|
"memory_unlock", "",
|
||||||
|
"box_chars_1", "",
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue