Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
66e016c6dc | |||
74b12e35bf | |||
388d8ccd9f | |||
9720da34db | |||
090197227f | |||
48857ae88e | |||
ad77f43b9d | |||
9d96d9bef5 | |||
7101f65a8b | |||
af3b5e9ce1 | |||
7deac5693c | |||
d5f6ee4620 | |||
fb73e6cb9b | |||
a6e64aea67 | |||
b3f21ddbba |
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,20 +1,36 @@
|
||||
## v0.4.3 (Unreleased)
|
||||
## v0.4.3 (July 25, 2019)
|
||||
|
||||
### Notes
|
||||
|
||||
### Features
|
||||
- **Docker users:** The `$PASSWORD` and `$DATADIR` environment variables are not supported anymore since this release. From now on you should mount the password or data dirctories as a volume. For example:
|
||||
```bash
|
||||
$ docker run -it -v $PWD/hostdata:/data \
|
||||
-v $PWD/password:/password \
|
||||
ethersphere/swarm:0.4.3 \
|
||||
--datadir /data \
|
||||
--password /password
|
||||
```
|
||||
|
||||
### Improvements
|
||||
### Bug fixes and improvements
|
||||
|
||||
### Bug fixes
|
||||
* [#1586](https://github.com/ethersphere/swarm/pull/1586): network: structured output for kademlia table
|
||||
* [#1582](https://github.com/ethersphere/swarm/pull/1582): client: add bzz client, update smoke tests
|
||||
* [#1578](https://github.com/ethersphere/swarm/pull/1578): swarm-smoke: fix check max prox hosts for pull/push sync modes
|
||||
* [#1557](https://github.com/ethersphere/swarm/pull/1557): cmd/swarm: allow using a network interface by name for nat purposes
|
||||
* [#1534](https://github.com/ethersphere/swarm/pull/1534): api, network: count chunk deliveries per peer
|
||||
* [#1537](https://github.com/ethersphere/swarm/pull/1537): swarm: fix bzz_info.port when using dynamic port allocation
|
||||
* [#1531](https://github.com/ethersphere/swarm/pull/1531): cmd/swarm: make bzzaccount flag optional and add bzzkeyhex flag
|
||||
* [#1536](https://github.com/ethersphere/swarm/pull/1536): cmd/swarm: use only one function to parse flags
|
||||
* [#1530](https://github.com/ethersphere/swarm/pull/1530): network/bitvector: Multibit set/unset + string rep
|
||||
* [#1555](https://github.com/ethersphere/swarm/pull/1555): PoC: Network simulation framework
|
||||
|
||||
## v0.4.2 (28 June 2019)
|
||||
## v0.4.2 (June 28, 2019)
|
||||
|
||||
### Notes
|
||||
|
||||
This release is not backward compatible with the previous versions of Swarm due to changes to the wire protocol of the Retrieve Request messages. Please update your nodes.
|
||||
|
||||
### Bug fixes and Improvements
|
||||
### Bug fixes and improvements
|
||||
|
||||
* [#1503](https://github.com/ethersphere/swarm/pull/1503): network/simulation: add ExecAdapter capability to swarm simulations
|
||||
* [#1495](https://github.com/ethersphere/swarm/pull/1495): build: enable ubuntu ppa disco (19.04) builds
|
||||
|
@ -10,5 +10,4 @@ FROM alpine:3.9
|
||||
RUN apk --no-cache add ca-certificates && update-ca-certificates
|
||||
COPY --from=builder /swarm/build/bin/swarm /usr/local/bin/
|
||||
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
|
||||
COPY docker/run.sh /run.sh
|
||||
ENTRYPOINT ["/run.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/swarm"]
|
||||
|
@ -10,6 +10,5 @@ FROM alpine:3.9
|
||||
RUN apk --no-cache add ca-certificates
|
||||
COPY --from=builder /swarm/build/bin/* /usr/local/bin/
|
||||
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
|
||||
COPY docker/run.sh /run.sh
|
||||
COPY docker/run-smoke.sh /run-smoke.sh
|
||||
ENTRYPOINT ["/run.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/swarm"]
|
||||
|
79
README.md
79
README.md
@ -9,26 +9,25 @@ Swarm is a distributed storage platform and content distribution service, a nati
|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [Building the source](#building-the-source)
|
||||
- [Running Swarm](#running-swarm)
|
||||
- [Verifying that your local Swarm node is running](#verifying-that-your-local-swarm-node-is-running)
|
||||
- [Ethereum Name Service resolution](#ethereum-name-service-resolution)
|
||||
- [Documentation](#documentation)
|
||||
- [Docker](#docker)
|
||||
- [Docker tags](#docker-tags)
|
||||
- [Environment variables](#environment-variables)
|
||||
- [Swarm command line arguments](#swarm-command-line-arguments)
|
||||
- [Developers Guide](#developers-guide)
|
||||
- [Go Environment](#go-environment)
|
||||
- [Vendored Dependencies](#vendored-dependencies)
|
||||
- [Testing](#testing)
|
||||
- [Profiling Swarm](#profiling-swarm)
|
||||
- [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
|
||||
- [Visualizing metrics](#visualizing-metrics)
|
||||
- [Public Gateways](#public-gateways)
|
||||
- [Swarm Dapps](#swarm-dapps)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
- [Building the source](#Building-the-source)
|
||||
- [Running Swarm](#Running-Swarm)
|
||||
- [Verifying that your local Swarm node is running](#Verifying-that-your-local-Swarm-node-is-running)
|
||||
- [Ethereum Name Service resolution](#Ethereum-Name-Service-resolution)
|
||||
- [Documentation](#Documentation)
|
||||
- [Docker](#Docker)
|
||||
- [Docker tags](#Docker-tags)
|
||||
- [Swarm command line arguments](#Swarm-command-line-arguments)
|
||||
- [Developers Guide](#Developers-Guide)
|
||||
- [Go Environment](#Go-Environment)
|
||||
- [Vendored Dependencies](#Vendored-Dependencies)
|
||||
- [Testing](#Testing)
|
||||
- [Profiling Swarm](#Profiling-Swarm)
|
||||
- [Metrics and Instrumentation in Swarm](#Metrics-and-Instrumentation-in-Swarm)
|
||||
- [Visualizing metrics](#Visualizing-metrics)
|
||||
- [Public Gateways](#Public-Gateways)
|
||||
- [Swarm Dapps](#Swarm-Dapps)
|
||||
- [Contributing](#Contributing)
|
||||
- [License](#License)
|
||||
|
||||
## Building the source
|
||||
|
||||
@ -53,15 +52,11 @@ $ go install github.com/ethersphere/swarm/cmd/swarm
|
||||
|
||||
## Running Swarm
|
||||
|
||||
Going through all the possible command line flags is out of scope here, but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Swarm node.
|
||||
|
||||
To run Swarm you need an Ethereum account. Download and install [Geth](https://geth.ethereum.org) if you don't have it on your system. You can create a new Ethereum account by running the following command:
|
||||
|
||||
```bash
|
||||
$ geth account new
|
||||
$ swarm
|
||||
```
|
||||
|
||||
You will be prompted for a password:
|
||||
If you don't have an account yet, then you will be prompted to create one and secure it with a password:
|
||||
|
||||
```
|
||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||
@ -69,18 +64,12 @@ Passphrase:
|
||||
Repeat passphrase:
|
||||
```
|
||||
|
||||
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
|
||||
|
||||
```
|
||||
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
|
||||
```
|
||||
|
||||
Using this account, connect to Swarm with
|
||||
If you have multiple accounts created, then you'll have to choose one of the accounts by using the `--bzzaccount` flag.
|
||||
|
||||
```bash
|
||||
$ swarm --bzzaccount <your-account-here>
|
||||
|
||||
# in our example
|
||||
# example
|
||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
|
||||
```
|
||||
|
||||
@ -119,11 +108,6 @@ Swarm container images are available at Docker Hub: [ethersphere/swarm](https://
|
||||
* `edge` - latest build from `master`
|
||||
* `v0.x.y` - specific stable release
|
||||
|
||||
### Environment variables
|
||||
|
||||
* `PASSWORD` - *required* - Used to setup a sample Ethereum account in the data directory. If a data directory is mounted with a volume, the first Ethereum account from it is loaded, and Swarm will try to decrypt it non-interactively with `PASSWORD`
|
||||
* `DATADIR` - *optional* - Defaults to `/root/.ethereum`
|
||||
|
||||
### Swarm command line arguments
|
||||
|
||||
All Swarm command line arguments are supported and can be sent as part of the CMD field to the Docker container.
|
||||
@ -133,15 +117,16 @@ All Swarm command line arguments are supported and can be sent as part of the CM
|
||||
Running a Swarm container from the command line
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
$ docker run -it ethersphere/swarm \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
```
|
||||
|
||||
|
||||
Running a Swarm container with custom ENS endpoint
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
$ docker run -it ethersphere/swarm \
|
||||
--ens-api http://1.2.3.4:8545 \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
@ -150,7 +135,7 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
Running a Swarm container with metrics enabled
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
$ docker run -it ethersphere/swarm \
|
||||
--debug \
|
||||
--metrics \
|
||||
--metrics.influxdb.export \
|
||||
@ -165,7 +150,7 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
Running a Swarm container with tracing and pprof server enabled
|
||||
|
||||
```bash
|
||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
$ docker run -it ethersphere/swarm \
|
||||
--debug \
|
||||
--tracing \
|
||||
--tracing.endpoint 127.0.0.1:6831 \
|
||||
@ -175,10 +160,14 @@ $ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
||||
--pprofport 6060
|
||||
```
|
||||
|
||||
Running a Swarm container with custom data directory mounted from a volume
|
||||
Running a Swarm container with a custom data directory mounted from a volume and a password file to unlock the swarm account
|
||||
|
||||
```bash
|
||||
$ docker run -e DATADIR=/data -e PASSWORD=password123 -v /tmp/hostdata:/data -t ethersphere/swarm \
|
||||
$ docker run -it -v $PWD/hostdata:/data \
|
||||
-v $PWD/password:/password \
|
||||
ethersphere/swarm \
|
||||
--datadir /data \
|
||||
--password /password \
|
||||
--debug \
|
||||
--verbosity 4
|
||||
```
|
||||
|
@ -39,19 +39,16 @@ func NewInspector(api *API, hive *network.Hive, netStore *storage.NetStore) *Ins
|
||||
}
|
||||
|
||||
// Hive prints the kademlia table
|
||||
func (inspector *Inspector) Hive() string {
|
||||
return inspector.hive.String()
|
||||
func (i *Inspector) Hive() string {
|
||||
return i.hive.String()
|
||||
}
|
||||
|
||||
func (inspector *Inspector) ListKnown() []string {
|
||||
res := []string{}
|
||||
for _, v := range inspector.hive.Kademlia.ListKnown() {
|
||||
res = append(res, fmt.Sprintf("%v", v))
|
||||
}
|
||||
return res
|
||||
// KademliaInfo returns structured output of the Kademlia state that we can check for equality
|
||||
func (i *Inspector) KademliaInfo() network.KademliaInfo {
|
||||
return i.hive.KademliaInfo()
|
||||
}
|
||||
|
||||
func (inspector *Inspector) IsSyncing() bool {
|
||||
func (i *Inspector) IsPullSyncing() bool {
|
||||
lastReceivedChunksMsg := metrics.GetOrRegisterGauge("network.stream.received_chunks", nil)
|
||||
|
||||
// last received chunks msg time
|
||||
@ -63,13 +60,30 @@ func (inspector *Inspector) IsSyncing() bool {
|
||||
return lrct.After(time.Now().Add(-15 * time.Second))
|
||||
}
|
||||
|
||||
// DeliveriesPerPeer returns the sum of chunks we received from a given peer
|
||||
func (i *Inspector) DeliveriesPerPeer() map[string]int64 {
|
||||
res := map[string]int64{}
|
||||
|
||||
// iterate connection in kademlia
|
||||
i.hive.Kademlia.EachConn(nil, 255, func(p *network.Peer, po int) bool {
|
||||
// get how many chunks we receive for retrieve requests per peer
|
||||
peermetric := fmt.Sprintf("chunk.delivery.%x", p.Over()[:16])
|
||||
|
||||
res[fmt.Sprintf("%x", p.Over()[:16])] = metrics.GetOrRegisterCounter(peermetric, nil).Count()
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Has checks whether each chunk address is present in the underlying datastore,
|
||||
// the bool in the returned structs indicates if the underlying datastore has
|
||||
// the chunk stored with the given address (true), or not (false)
|
||||
func (inspector *Inspector) Has(chunkAddresses []storage.Address) string {
|
||||
func (i *Inspector) Has(chunkAddresses []storage.Address) string {
|
||||
hostChunks := []string{}
|
||||
for _, addr := range chunkAddresses {
|
||||
has, err := inspector.netStore.Has(context.Background(), addr)
|
||||
has, err := i.netStore.Has(context.Background(), addr)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
81
client/bzz.go
Normal file
81
client/bzz.go
Normal file
@ -0,0 +1,81 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
)
|
||||
|
||||
type Bzz struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
// NewBzz is a constructor for a Bzz API
|
||||
func NewBzz(client *rpc.Client) *Bzz {
|
||||
return &Bzz{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// GetChunksBitVector returns a bit vector of presence for a given slice of chunks
|
||||
func (b *Bzz) GetChunksBitVector(addrs []storage.Address) (string, error) {
|
||||
var hostChunks string
|
||||
const trackChunksPageSize = 7500
|
||||
|
||||
for len(addrs) > 0 {
|
||||
var pageChunks string
|
||||
// get current page size, so that we avoid a slice out of bounds on the last page
|
||||
pagesize := trackChunksPageSize
|
||||
if len(addrs) < trackChunksPageSize {
|
||||
pagesize = len(addrs)
|
||||
}
|
||||
|
||||
err := b.client.Call(&pageChunks, "bzz_has", addrs[:pagesize])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hostChunks += pageChunks
|
||||
addrs = addrs[pagesize:]
|
||||
}
|
||||
|
||||
return hostChunks, nil
|
||||
}
|
||||
|
||||
// GetBzzAddr returns the bzzAddr of the node
|
||||
func (b *Bzz) GetBzzAddr() (string, error) {
|
||||
var info swarm.Info
|
||||
|
||||
err := b.client.Call(&info, "bzz_info")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return info.BzzKey[2:], nil
|
||||
}
|
||||
|
||||
// IsPullSyncing is checking if the node is still receiving chunk deliveries due to pull syncing
|
||||
func (b *Bzz) IsPullSyncing() (bool, error) {
|
||||
var isSyncing bool
|
||||
|
||||
err := b.client.Call(&isSyncing, "bzz_isPullSyncing")
|
||||
if err != nil {
|
||||
log.Error("error calling host for isPullSyncing", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return isSyncing, nil
|
||||
}
|
||||
|
||||
// IsPushSynced checks if the given `tag` is done syncing, i.e. we've received receipts for all chunks
|
||||
func (b *Bzz) IsPushSynced(tagname string) (bool, error) {
|
||||
var isSynced bool
|
||||
|
||||
err := b.client.Call(&isSynced, "bzz_isPushSynced", tagname)
|
||||
if err != nil {
|
||||
log.Error("error calling host for isPushSynced", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return isSynced, nil
|
||||
}
|
@ -37,22 +37,23 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
allhosts string
|
||||
hosts []string
|
||||
filesize int
|
||||
syncDelay bool
|
||||
inputSeed int
|
||||
httpPort int
|
||||
wsPort int
|
||||
verbosity int
|
||||
timeout int
|
||||
single bool
|
||||
onlyUpload bool
|
||||
debug bool
|
||||
allhosts string
|
||||
hosts []string
|
||||
filesize int
|
||||
syncDelay bool
|
||||
pushsyncDelay bool
|
||||
syncMode string
|
||||
inputSeed int
|
||||
httpPort int
|
||||
wsPort int
|
||||
verbosity int
|
||||
timeout int
|
||||
single bool
|
||||
onlyUpload bool
|
||||
debug bool
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "smoke-test"
|
||||
app.Usage = ""
|
||||
@ -88,6 +89,17 @@ func main() {
|
||||
Usage: "file size for generated random file in KB",
|
||||
Destination: &filesize,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sync-mode",
|
||||
Value: "pullsync",
|
||||
Usage: "sync mode - pushsync or pullsync or both",
|
||||
Destination: &syncMode,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "pushsync-delay",
|
||||
Usage: "wait for content to be push synced",
|
||||
Destination: &pushsyncDelay,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "sync-delay",
|
||||
Usage: "wait for content to be synced",
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -33,8 +32,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/client"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/testutil"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
@ -116,14 +118,16 @@ func trackChunks(testData []byte, submitMetrics bool) error {
|
||||
return
|
||||
}
|
||||
|
||||
hostChunks, err := getChunksBitVectorFromHost(rpcClient, addrs)
|
||||
bzzClient := client.NewBzz(rpcClient)
|
||||
|
||||
hostChunks, err := bzzClient.GetChunksBitVector(addrs)
|
||||
if err != nil {
|
||||
log.Error("error getting chunks bit vector from host", "err", err, "host", httpHost)
|
||||
hasErr = true
|
||||
return
|
||||
}
|
||||
|
||||
bzzAddr, err := getBzzAddrFromHost(rpcClient)
|
||||
bzzAddr, err := bzzClient.GetBzzAddr()
|
||||
if err != nil {
|
||||
log.Error("error getting bzz addrs from host", "err", err, "host", httpHost)
|
||||
hasErr = true
|
||||
@ -175,46 +179,6 @@ func trackChunks(testData []byte, submitMetrics bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChunksBitVectorFromHost returns a bit vector of presence for a given slice of chunks from a given host
|
||||
func getChunksBitVectorFromHost(client *rpc.Client, addrs []storage.Address) (string, error) {
|
||||
var hostChunks string
|
||||
const trackChunksPageSize = 7500
|
||||
|
||||
for len(addrs) > 0 {
|
||||
var pageChunks string
|
||||
// get current page size, so that we avoid a slice out of bounds on the last page
|
||||
pagesize := trackChunksPageSize
|
||||
if len(addrs) < trackChunksPageSize {
|
||||
pagesize = len(addrs)
|
||||
}
|
||||
|
||||
err := client.Call(&pageChunks, "bzz_has", addrs[:pagesize])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hostChunks += pageChunks
|
||||
addrs = addrs[pagesize:]
|
||||
}
|
||||
|
||||
return hostChunks, nil
|
||||
}
|
||||
|
||||
// getBzzAddrFromHost returns the bzzAddr for a given host
|
||||
func getBzzAddrFromHost(client *rpc.Client) (string, error) {
|
||||
var hive string
|
||||
|
||||
err := client.Call(&hive, "bzz_hive")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// we make an ugly assumption about the output format of the hive.String() method
|
||||
// ideally we should replace this with an API call that returns the bzz addr for a given host,
|
||||
// but this also works for now (provided we don't change the hive.String() method, which we haven't in some time
|
||||
ss := strings.Split(strings.Split(hive, "\n")[3], " ")
|
||||
return ss[len(ss)-1], nil
|
||||
}
|
||||
|
||||
// checkChunksVsMostProxHosts is checking:
|
||||
// 1. whether a chunk has been found at less than 2 hosts. Considering our NN size, this should not happen.
|
||||
// 2. if a chunk is not found at its closest node. This should also not happen.
|
||||
@ -232,7 +196,7 @@ func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[strin
|
||||
for i := range addrs {
|
||||
var foundAt int
|
||||
maxProx := -1
|
||||
var maxProxHost string
|
||||
var maxProxHosts []string
|
||||
for host := range allHostChunks {
|
||||
if allHostChunks[host][i] == '1' {
|
||||
foundAt++
|
||||
@ -247,19 +211,43 @@ func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[strin
|
||||
prox := chunk.Proximity(addrs[i], ba)
|
||||
if prox > maxProx {
|
||||
maxProx = prox
|
||||
maxProxHost = host
|
||||
maxProxHosts = []string{host}
|
||||
} else if prox == maxProx {
|
||||
maxProxHosts = append(maxProxHosts, host)
|
||||
}
|
||||
}
|
||||
|
||||
if allHostChunks[maxProxHost][i] == '0' {
|
||||
log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
} else {
|
||||
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
log.Debug("sync mode", "sync mode", syncMode)
|
||||
|
||||
if syncMode == "pullsync" || syncMode == "both" {
|
||||
for _, maxProxHost := range maxProxHosts {
|
||||
if allHostChunks[maxProxHost][i] == '0' {
|
||||
log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
} else {
|
||||
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
}
|
||||
}
|
||||
|
||||
// if chunk found at less than 2 hosts, which is actually less that the min size of a NN
|
||||
if foundAt < 2 {
|
||||
log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// if chunk found at less than 2 hosts
|
||||
if foundAt < 2 {
|
||||
log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
|
||||
if syncMode == "pushsync" {
|
||||
var found bool
|
||||
for _, maxProxHost := range maxProxHosts {
|
||||
if allHostChunks[maxProxHost][i] == '1' {
|
||||
found = true
|
||||
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
for _, maxProxHost := range maxProxHosts {
|
||||
log.Error("chunk not found at any max prox host", "ref", addrs[i], "hosts", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -284,7 +272,8 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
|
||||
log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
|
||||
|
||||
t1 := time.Now()
|
||||
hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
|
||||
tag := uuid.New()[:8]
|
||||
hash, err := uploadWithTag(randomBytes, httpEndpoint(hosts[0]), tag)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
@ -300,6 +289,11 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
|
||||
|
||||
log.Info("uploaded successfully", "hash", hash, "took", t2, "digest", fmt.Sprintf("%x", fhash))
|
||||
|
||||
// wait to push sync sync
|
||||
if pushsyncDelay {
|
||||
waitToPushSynced(tag)
|
||||
}
|
||||
|
||||
// wait to sync and log chunks before fetch attempt, only if syncDelay is set to true
|
||||
if syncDelay {
|
||||
waitToSync()
|
||||
@ -338,27 +332,31 @@ func uploadAndSync(c *cli.Context, randomBytes []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSyncing(wsHost string) (bool, error) {
|
||||
rpcClient, err := rpc.Dial(wsHost)
|
||||
if rpcClient != nil {
|
||||
defer rpcClient.Close()
|
||||
func waitToPushSynced(tagname string) {
|
||||
for {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
rpcClient, err := rpc.Dial(wsEndpoint(hosts[0]))
|
||||
if rpcClient != nil {
|
||||
defer rpcClient.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("error dialing host", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
bzzClient := client.NewBzz(rpcClient)
|
||||
|
||||
synced, err := bzzClient.IsPushSynced(tagname)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if synced {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("error dialing host", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
var isSyncing bool
|
||||
err = rpcClient.Call(&isSyncing, "bzz_isSyncing")
|
||||
if err != nil {
|
||||
log.Error("error calling host for isSyncing", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Debug("isSyncing result", "host", wsHost, "isSyncing", isSyncing)
|
||||
|
||||
return isSyncing, nil
|
||||
}
|
||||
|
||||
func waitToSync() {
|
||||
@ -370,22 +368,39 @@ func waitToSync() {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
notSynced := uint64(0)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(hosts))
|
||||
|
||||
var g errgroup.Group
|
||||
for i := 0; i < len(hosts); i++ {
|
||||
i := i
|
||||
go func(idx int) {
|
||||
stillSyncing, err := isSyncing(wsEndpoint(hosts[idx]))
|
||||
g.Go(func() error {
|
||||
rpcClient, err := rpc.Dial(wsEndpoint(hosts[i]))
|
||||
if rpcClient != nil {
|
||||
defer rpcClient.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("error dialing host", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if stillSyncing || err != nil {
|
||||
bzzClient := client.NewBzz(rpcClient)
|
||||
|
||||
stillSyncing, err := bzzClient.IsPullSyncing()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stillSyncing {
|
||||
atomic.AddUint64(¬Synced, 1)
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
ns = atomic.LoadUint64(¬Synced)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for all RPC calls to complete.
|
||||
if err := g.Wait(); err == nil {
|
||||
ns = atomic.LoadUint64(¬Synced)
|
||||
}
|
||||
}
|
||||
|
||||
t2 := time.Since(t1)
|
||||
|
@ -38,6 +38,7 @@ import (
|
||||
"github.com/ethersphere/swarm/api/client"
|
||||
"github.com/ethersphere/swarm/spancontext"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"github.com/pborman/uuid"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
@ -193,8 +194,13 @@ func fetch(hash string, endpoint string, original []byte, ruid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
|
||||
// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
|
||||
func upload(data []byte, endpoint string) (string, error) {
|
||||
return uploadWithTag(data, endpoint, uuid.New()[:8])
|
||||
}
|
||||
|
||||
// uploadWithTag an arbitrary byte as a plaintext file to `endpoint` using the api client with a given tag
|
||||
func uploadWithTag(data []byte, endpoint string, tag string) (string, error) {
|
||||
swarm := client.NewClient(endpoint)
|
||||
f := &client.File{
|
||||
ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
|
||||
@ -203,10 +209,10 @@ func upload(data []byte, endpoint string) (string, error) {
|
||||
Mode: 0660,
|
||||
Size: int64(len(data)),
|
||||
},
|
||||
Tag: tag,
|
||||
}
|
||||
|
||||
// upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded.
|
||||
return swarm.Upload(f, "", false)
|
||||
return swarm.TarUpload("", &client.FileUploader{f}, "", false)
|
||||
}
|
||||
|
||||
func digest(r io.Reader) ([]byte, error) {
|
||||
|
@ -106,7 +106,7 @@ func accessNewPass(ctx *cli.Context) {
|
||||
accessKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
password = getPassPhrase("", 0, makePasswordList(ctx))
|
||||
password = getPassPhrase("", false, 0, makePasswordList(ctx))
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
accessKey, ae, err = api.DoPassword(ctx, password, salt)
|
||||
|
@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -24,7 +25,6 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
@ -61,6 +61,7 @@ var (
|
||||
const (
|
||||
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
|
||||
SwarmEnvAccount = "SWARM_ACCOUNT"
|
||||
SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX"
|
||||
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
|
||||
SwarmEnvPort = "SWARM_PORT"
|
||||
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
|
||||
@ -80,6 +81,7 @@ const (
|
||||
SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY"
|
||||
SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY"
|
||||
SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE"
|
||||
SwarmEnvNATInterface = "SWARM_NAT_INTERFACE"
|
||||
SwarmAccessPassword = "SWARM_ACCESS_PASSWORD"
|
||||
SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH"
|
||||
SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API"
|
||||
@ -112,10 +114,8 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//override settings provided by environment variables
|
||||
config = envVarsOverride(config)
|
||||
//override settings provided by command line
|
||||
config = cmdLineOverride(config, ctx)
|
||||
//override settings provided by flags
|
||||
config = flagsOverride(config, ctx)
|
||||
//validate configuration parameters
|
||||
err = validateConfig(config)
|
||||
|
||||
@ -124,9 +124,9 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
||||
|
||||
//finally, after the configuration build phase is finished, initialize
|
||||
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
|
||||
//at this point, all vars should be set in the Config
|
||||
//get the account for the provided swarm account
|
||||
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
||||
var prvkey *ecdsa.PrivateKey
|
||||
config.BzzAccount, prvkey = getOrCreateAccount(ctx, stack)
|
||||
//set the resolved config path (geth --datadir)
|
||||
config.Path = expandPath(stack.InstanceDir())
|
||||
//finally, initialize the configuration
|
||||
@ -170,9 +170,9 @@ func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config
|
||||
return config, err
|
||||
}
|
||||
|
||||
// cmdLineOverride overrides the current config with whatever is provided through the command line
|
||||
// flagsOverride overrides the current config with whatever is provided through flags (cli or env vars)
|
||||
// most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
|
||||
func flagsOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
|
||||
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
@ -275,122 +275,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||
|
||||
}
|
||||
|
||||
// envVarsOverride overrides the current config with whatver is provided in environment variables
|
||||
// most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||
if keyid := os.Getenv(SwarmEnvAccount); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
|
||||
if chbookaddr := os.Getenv(SwarmEnvChequebookAddr); chbookaddr != "" {
|
||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||
}
|
||||
|
||||
if networkid := os.Getenv(SwarmEnvNetworkID); networkid != "" {
|
||||
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvNetworkID, err)
|
||||
}
|
||||
if id != 0 {
|
||||
currentConfig.NetworkID = id
|
||||
}
|
||||
}
|
||||
|
||||
if datadir := os.Getenv(GethEnvDataDir); datadir != "" {
|
||||
currentConfig.Path = expandPath(datadir)
|
||||
}
|
||||
|
||||
bzzport := os.Getenv(SwarmEnvPort)
|
||||
if len(bzzport) > 0 {
|
||||
currentConfig.Port = bzzport
|
||||
}
|
||||
|
||||
if bzzaddr := os.Getenv(SwarmEnvListenAddr); bzzaddr != "" {
|
||||
currentConfig.ListenAddr = bzzaddr
|
||||
}
|
||||
|
||||
if swapenable := os.Getenv(SwarmEnvSwapEnable); swapenable != "" {
|
||||
swap, err := strconv.ParseBool(swapenable)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSwapEnable, err)
|
||||
}
|
||||
currentConfig.SwapEnabled = swap
|
||||
}
|
||||
|
||||
if syncdisable := os.Getenv(SwarmEnvSyncDisable); syncdisable != "" {
|
||||
sync, err := strconv.ParseBool(syncdisable)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncDisable, err)
|
||||
}
|
||||
currentConfig.SyncEnabled = !sync
|
||||
}
|
||||
|
||||
if v := os.Getenv(SwarmEnvDeliverySkipCheck); v != "" {
|
||||
skipCheck, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
currentConfig.DeliverySkipCheck = skipCheck
|
||||
}
|
||||
}
|
||||
|
||||
if v := os.Getenv(SwarmEnvSyncUpdateDelay); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncUpdateDelay, err)
|
||||
}
|
||||
currentConfig.SyncUpdateDelay = d
|
||||
}
|
||||
|
||||
if max := os.Getenv(SwarmEnvMaxStreamPeerServers); max != "" {
|
||||
m, err := strconv.Atoi(max)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvMaxStreamPeerServers, err)
|
||||
}
|
||||
currentConfig.MaxStreamPeerServers = m
|
||||
}
|
||||
|
||||
if lne := os.Getenv(SwarmEnvLightNodeEnable); lne != "" {
|
||||
lightnode, err := strconv.ParseBool(lne)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvLightNodeEnable, err)
|
||||
}
|
||||
currentConfig.LightNodeEnabled = lightnode
|
||||
}
|
||||
|
||||
if swapapi := os.Getenv(SwarmEnvSwapAPI); swapapi != "" {
|
||||
currentConfig.SwapAPI = swapapi
|
||||
}
|
||||
|
||||
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
||||
utils.Fatalf(SwarmErrSwapSetNoAPI)
|
||||
}
|
||||
|
||||
if ensapi := os.Getenv(SwarmEnvENSAPI); ensapi != "" {
|
||||
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
|
||||
}
|
||||
|
||||
if ensaddr := os.Getenv(SwarmEnvENSAddr); ensaddr != "" {
|
||||
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||
}
|
||||
|
||||
if cors := os.Getenv(SwarmEnvCORS); cors != "" {
|
||||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if bm := os.Getenv(SwarmEnvBootnodeMode); bm != "" {
|
||||
bootnodeMode, err := strconv.ParseBool(bm)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvBootnodeMode, err)
|
||||
}
|
||||
currentConfig.BootnodeMode = bootnodeMode
|
||||
}
|
||||
|
||||
if api := os.Getenv(SwarmGlobalstoreAPI); api != "" {
|
||||
currentConfig.GlobalStoreAPI = api
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
// dumpConfig is the dumpconfig command.
|
||||
// writes a default config to STDOUT
|
||||
func dumpConfig(ctx *cli.Context) error {
|
||||
|
@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -28,6 +29,8 @@ import (
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
"github.com/ethersphere/swarm/api"
|
||||
@ -48,6 +51,7 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
|
||||
}
|
||||
|
||||
@ -56,15 +60,161 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestConfigFailsNoBzzAccount(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
func TestBzzKeyFlag(t *testing.T) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal("unable to generate key")
|
||||
}
|
||||
hexKey := hex.EncodeToString(crypto.FromECDSA(key))
|
||||
bzzaccount := crypto.PubkeyToAddress(key.PublicKey).Hex()
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
conf := &node.Config{
|
||||
DataDir: dir,
|
||||
IPCPath: "testbzzkeyflag.ipc",
|
||||
NoUSB: true,
|
||||
}
|
||||
|
||||
swarm := runSwarm(t, flags...)
|
||||
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
|
||||
swarm.ExpectExit()
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
|
||||
fmt.Sprintf("--%s", SwarmBzzKeyHexFlag.Name), hexKey,
|
||||
}
|
||||
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
defer func() {
|
||||
node.Shutdown()
|
||||
}()
|
||||
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.BzzAccount != bzzaccount {
|
||||
t.Fatalf("Expected account to be %s, got %s", bzzaccount, info.BzzAccount)
|
||||
}
|
||||
}
|
||||
func TestEmptyBzzAccountFlagMultipleAccounts(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create two accounts on disk
|
||||
getTestAccount(t, dir)
|
||||
getTestAccount(t, dir)
|
||||
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
}
|
||||
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
|
||||
node.Cmd.ExpectRegexp(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
|
||||
}
|
||||
|
||||
func TestEmptyBzzAccountFlagSingleAccount(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
|
||||
}
|
||||
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
defer func() {
|
||||
node.Shutdown()
|
||||
}()
|
||||
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.BzzAccount != account.Address.Hex() {
|
||||
t.Fatalf("Expected account to be %s, got %s", account.Address.Hex(), info.BzzAccount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyBzzAccountFlagNoAccountWrongPassword(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
}
|
||||
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
|
||||
// Set password
|
||||
node.Cmd.InputLine("goodpassword")
|
||||
// Confirm password
|
||||
node.Cmd.InputLine("wrongpassword")
|
||||
|
||||
node.Cmd.ExpectRegexp("Passphrases do not match")
|
||||
}
|
||||
|
||||
func TestConfigCmdLineOverrides(t *testing.T) {
|
||||
@ -86,6 +236,7 @@ func TestConfigCmdLineOverrides(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
|
||||
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
|
||||
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
|
@ -101,7 +101,7 @@ func download(ctx *cli.Context) {
|
||||
return nil
|
||||
}
|
||||
if passwords := makePasswordList(ctx); passwords != nil {
|
||||
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
|
||||
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), false, 0, passwords)
|
||||
err = dl(password)
|
||||
} else {
|
||||
err = dl("")
|
||||
|
@ -30,6 +30,11 @@ var (
|
||||
Usage: "Swarm account key file",
|
||||
EnvVar: SwarmEnvAccount,
|
||||
}
|
||||
SwarmBzzKeyHexFlag = cli.StringFlag{
|
||||
Name: "bzzkeyhex",
|
||||
Usage: "BzzAccount key in hex (for testing)",
|
||||
EnvVar: SwarmEnvBzzKeyHex,
|
||||
}
|
||||
SwarmListenAddrFlag = cli.StringFlag{
|
||||
Name: "httpaddr",
|
||||
Usage: "Swarm HTTP API listening interface",
|
||||
@ -40,6 +45,11 @@ var (
|
||||
Usage: "Swarm local http api port",
|
||||
EnvVar: SwarmEnvPort,
|
||||
}
|
||||
SwarmNATInterfaceFlag = cli.StringFlag{
|
||||
Name: "natif",
|
||||
Usage: "Announce the IP address of a given network interface (e.g. eth0)",
|
||||
EnvVar: SwarmEnvNATInterface,
|
||||
}
|
||||
SwarmNetworkIdFlag = cli.IntFlag{
|
||||
Name: "bzznetworkid",
|
||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
@ -38,6 +39,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
bzzapi "github.com/ethersphere/swarm/api"
|
||||
@ -170,6 +172,7 @@ func init() {
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.PasswordFileFlag,
|
||||
SwarmNATInterfaceFlag,
|
||||
// bzzd-specific flags
|
||||
CorsStringFlag,
|
||||
EnsAPIFlag,
|
||||
@ -184,6 +187,7 @@ func init() {
|
||||
SwarmListenAddrFlag,
|
||||
SwarmPortFlag,
|
||||
SwarmAccountFlag,
|
||||
SwarmBzzKeyHexFlag,
|
||||
SwarmNetworkIdFlag,
|
||||
ChequebookAddrFlag,
|
||||
// upload flags
|
||||
@ -290,6 +294,9 @@ func bzzd(ctx *cli.Context) error {
|
||||
//disable dynamic dialing from p2p/discovery
|
||||
cfg.P2P.NoDial = true
|
||||
|
||||
//optionally set the NAT IP from a network interface
|
||||
setSwarmNATFromInterface(ctx, &cfg)
|
||||
|
||||
stack, err := node.New(&cfg)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't create node: %v", err)
|
||||
@ -351,21 +358,62 @@ func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||
//an account is mandatory
|
||||
if bzzaccount == "" {
|
||||
utils.Fatalf(SwarmErrNoBZZAccount)
|
||||
// getOrCreateAccount returns the address and associated private key for a bzzaccount
|
||||
// If no account exists, it will create an account for you.
|
||||
func getOrCreateAccount(ctx *cli.Context, stack *node.Node) (string, *ecdsa.PrivateKey) {
|
||||
var bzzaddr string
|
||||
|
||||
// Check if a key was provided
|
||||
if hexkey := ctx.GlobalString(SwarmBzzKeyHexFlag.Name); hexkey != "" {
|
||||
key, err := crypto.HexToECDSA(hexkey)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed using %s: %v", SwarmBzzKeyHexFlag.Name, err)
|
||||
}
|
||||
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
|
||||
log.Info(fmt.Sprintf("Swarm account key loaded from %s", SwarmBzzKeyHexFlag.Name), "address", bzzaddr)
|
||||
return bzzaddr, key
|
||||
}
|
||||
// Try to load the arg as a hex key file.
|
||||
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
||||
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||
return key
|
||||
}
|
||||
// Otherwise try getting it from the keystore.
|
||||
|
||||
am := stack.AccountManager()
|
||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
||||
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
||||
// Check if an address was provided
|
||||
if bzzaddr = ctx.GlobalString(SwarmAccountFlag.Name); bzzaddr != "" {
|
||||
// Try to load the arg as a hex key file.
|
||||
if key, err := crypto.LoadECDSA(bzzaddr); err == nil {
|
||||
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
|
||||
log.Info("Swarm account key loaded", "address", bzzaddr)
|
||||
return bzzaddr, key
|
||||
}
|
||||
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
|
||||
}
|
||||
|
||||
// No address or key were provided
|
||||
accounts := ks.Accounts()
|
||||
|
||||
switch l := len(accounts); l {
|
||||
case 0:
|
||||
// Create an account
|
||||
log.Info("You don't have an account yet. Creating one...")
|
||||
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||
account, err := ks.NewAccount(password)
|
||||
if err != nil {
|
||||
utils.Fatalf("failed creating an account: %v", err)
|
||||
}
|
||||
bzzaddr = account.Address.Hex()
|
||||
case 1:
|
||||
// Use existing account
|
||||
bzzaddr = accounts[0].Address.Hex()
|
||||
default:
|
||||
// Inform user about multiple accounts
|
||||
log.Info(fmt.Sprintf("Multiple (%d) accounts were found in your keystore.", l))
|
||||
for _, a := range accounts {
|
||||
log.Info(fmt.Sprintf("Account: %s", a.Address.Hex()))
|
||||
}
|
||||
utils.Fatalf(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
|
||||
}
|
||||
|
||||
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
|
||||
}
|
||||
|
||||
// getPrivKey returns the private key of the specified bzzaccount
|
||||
@ -387,7 +435,9 @@ func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
|
||||
}
|
||||
defer stack.Close()
|
||||
|
||||
return getAccount(bzzconfig.BzzAccount, ctx, stack)
|
||||
var privkey *ecdsa.PrivateKey
|
||||
bzzconfig.BzzAccount, privkey = getOrCreateAccount(ctx, stack)
|
||||
return privkey
|
||||
}
|
||||
|
||||
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
||||
@ -412,7 +462,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
|
||||
utils.Fatalf("Can't load swarm account key: %v", err)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
|
||||
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), false, i, passwords)
|
||||
key, err := keystore.DecryptKey(keyjson, password)
|
||||
if err == nil {
|
||||
return key.PrivateKey
|
||||
@ -422,18 +472,17 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPassPhrase retrieves the password associated with bzz account, either by fetching
|
||||
// from a list of pre-loaded passwords, or by requesting it interactively from user.
|
||||
func getPassPhrase(prompt string, i int, passwords []string) string {
|
||||
// non-interactive
|
||||
// getPassPhrase retrieves the password associated with a bzzaccount, either fetched
|
||||
// from a list of preloaded passphrases, or requested interactively from the user.
|
||||
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
|
||||
// If a list of passwords was supplied, retrieve from them
|
||||
if len(passwords) > 0 {
|
||||
if i < len(passwords) {
|
||||
return passwords[i]
|
||||
}
|
||||
return passwords[len(passwords)-1]
|
||||
}
|
||||
|
||||
// fallback to interactive mode
|
||||
// Otherwise prompt the user for the password
|
||||
if prompt != "" {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
@ -441,6 +490,15 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
if confirmation {
|
||||
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||
}
|
||||
if password != confirm {
|
||||
utils.Fatalf("Passphrases do not match")
|
||||
}
|
||||
}
|
||||
return password
|
||||
}
|
||||
|
||||
@ -473,3 +531,26 @@ func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setSwarmNATFromInterface(ctx *cli.Context, cfg *node.Config) {
|
||||
ifacename := ctx.GlobalString(SwarmNATInterfaceFlag.Name)
|
||||
|
||||
if ifacename == "" {
|
||||
return
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(ifacename)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't get network interface %s", ifacename)
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil || len(addrs) == 0 {
|
||||
utils.Fatalf("could not get address from interface %s: %v", ifacename, err)
|
||||
}
|
||||
|
||||
ip, _, err := net.ParseCIDR(addrs[0].String())
|
||||
if err != nil {
|
||||
utils.Fatalf("could not parse IP addr from interface %s: %v", ifacename, err)
|
||||
}
|
||||
cfg.P2P.NAT = nat.ExtIP(ip)
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
PASSWORD=${PASSWORD:-}
|
||||
DATADIR=${DATADIR:-/root/.ethereum/}
|
||||
|
||||
if [ "$PASSWORD" == "" ]; then echo "Password must be set, in order to use swarm non-interactively." && exit 1; fi
|
||||
|
||||
echo $PASSWORD > /password
|
||||
|
||||
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
|
||||
if [ ! -f "$KEYFILE" ]; then echo "No keyfile found. Generating..." && geth --datadir $DATADIR --password /password account new; fi
|
||||
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
|
||||
if [ ! -f "$KEYFILE" ]; then echo "Could not find nor generate a BZZ keyfile." && exit 1; else echo "Found keyfile $KEYFILE"; fi
|
||||
|
||||
VERSION=`swarm version`
|
||||
echo "Running Swarm:"
|
||||
echo $VERSION
|
||||
|
||||
export BZZACCOUNT="`echo -n $KEYFILE | tail -c 40`" || true
|
||||
if [ "$BZZACCOUNT" == "" ]; then echo "Could not parse BZZACCOUNT from keyfile." && exit 1; fi
|
||||
|
||||
exec swarm --bzzaccount=$BZZACCOUNT --password /password --datadir $DATADIR $@ 2>&1
|
@ -67,8 +67,8 @@ Wire Protocol Specifications
|
||||
|
||||
| Msg Name | From->To | Params | Example |
|
||||
| -------- | -------- | -------- | ------- |
|
||||
| StreamInfoReq | Client->Server | Streams`[]string` | `SYNC\|6, SYNC\|5` |
|
||||
| StreamInfoRes | Server->Client | Streams`[]StreamDescriptor` <br>Stream`string`<br>Cursor`uint64`<br>Bounded`bool` | `SYNC\|6;CUR=1632;bounded, SYNC\|7;CUR=18433;bounded` |
|
||||
| StreamInfoReq | Client->Server | Streams`[]ID` | `SYNC\|6, SYNC\|5` |
|
||||
| StreamInfoRes | Server->Client | Streams`[]StreamDescriptor` <br>Stream`ID`<br>Cursor`uint64`<br>Bounded`bool` | `SYNC\|6;CUR=1632;bounded, SYNC\|7;CUR=18433;bounded` |
|
||||
| GetRange | Client->Server| Ruid`uint`<br>Stream `string`<br>From`uint`<br>To`*uint`(nullable)<br>Roundtrip`bool` | `Ruid: 21321, Stream: SYNC\|6, From: 1, To: 100`(bounded), Roundtrip: true<br>`Stream: SYNC\|7, From: 109, Roundtrip: true`(unbounded) |
|
||||
| OfferedHashes | Server->Client| Ruid`uint`<br>Hashes `[]byte` | `Ruid: 21321, Hashes: [cbcbbaddda, bcbbbdbbdc, ....]` |
|
||||
| WantedHashes | Client->Server | Ruid`uint`<br>Bitvector`[]byte` | `Ruid: 21321, Bitvector: [0100100100] ` |
|
||||
@ -81,65 +81,120 @@ Notes:
|
||||
* two notions of bounded - on the stream level and on the localstore
|
||||
* if TO is not specified - we assume unbounded stream, and we just send whatever, until at most, we fill up an entire batch.
|
||||
|
||||
### Message struct definitions:
|
||||
### Message and interface definitions:
|
||||
|
||||
|
||||
```go
|
||||
// StreamProvider interface provides a lightweight abstraction that allows an easily-pluggable
|
||||
// stream provider as part of the Stream! protocol specification.
|
||||
type StreamProvider interface {
|
||||
NeedData(ctx context.Context, key []byte) (need bool, wait func(context.Context) error)
|
||||
Get(ctx context.Context, addr chunk.Address) ([]byte, error)
|
||||
Put(ctx context.Context, addr chunk.Address, data []byte) (exists bool, err error)
|
||||
Subscribe(ctx context.Context, key interface{}, from, to uint64) (<-chan chunk.Descriptor, func())
|
||||
Cursor(interface{}) (uint64, error)
|
||||
RunUpdateStreams(p *Peer)
|
||||
StreamName() string
|
||||
ParseKey(string) (interface{}, error)
|
||||
EncodeKey(interface{}) (string, error)
|
||||
StreamBehavior() StreamInitBehavior
|
||||
Boundedness() bool
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type StreamInitBehavior int
|
||||
```
|
||||
|
||||
```go
|
||||
// StreamInfoReq is a request to get information about particular streams
|
||||
type StreamInfoReq struct {
|
||||
Streams []string
|
||||
Streams []ID
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// StreamInfoRes is a response to StreamInfoReq with the corresponding stream descriptors
|
||||
type StreamInfoRes struct {
|
||||
Streams []StreamDescriptor
|
||||
Streams []StreamDescriptor
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// StreamDescriptor describes an arbitrary stream
|
||||
type StreamDescriptor struct {
|
||||
Name string
|
||||
Cursor uint
|
||||
Bounded bool
|
||||
Stream ID
|
||||
Cursor uint64
|
||||
Bounded bool
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// GetRange is a message sent from the downstream peer to the upstream peer asking for chunks
|
||||
// within a particular interval for a certain stream
|
||||
type GetRange struct {
|
||||
Ruid uint
|
||||
Stream string
|
||||
From uint
|
||||
To uint `rlp:nil`
|
||||
BatchSize uint
|
||||
Roundtrip bool
|
||||
Ruid uint
|
||||
Stream ID
|
||||
From uint64
|
||||
To uint64 `rlp:nil`
|
||||
BatchSize uint
|
||||
Roundtrip bool
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// OfferedHashes is a message sent from the upstream peer to the downstream peer allowing the latter
|
||||
// to selectively ask for chunks within a particular requested interval
|
||||
type OfferedHashes struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Hashes []byte
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Hashes []byte
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// WantedHashes is a message sent from the downstream peer to the upstream peer in response
|
||||
// to OfferedHashes in order to selectively ask for a particular chunks within an interval
|
||||
type WantedHashes struct {
|
||||
Ruid uint
|
||||
BitVector []byte
|
||||
Ruid uint
|
||||
BitVector []byte
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// ChunkDelivery delivers a frame of chunks in response to a WantedHashes message
|
||||
type ChunkDelivery struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Chunks [][]byte
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Chunks []DeliveredChunk
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type BatchDone struct {
|
||||
Ruid uint
|
||||
Last uint
|
||||
// DeliveredChunk encapsulates a particular chunk's underlying data within a ChunkDelivery message
|
||||
type DeliveredChunk struct {
|
||||
Addr storage.Address
|
||||
Data []byte
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// StreamState is a message exchanged between two nodes to notify of changes or errors in a stream's state
|
||||
type StreamState struct {
|
||||
Stream string
|
||||
Code uint16
|
||||
Message string
|
||||
Stream ID
|
||||
Code uint16
|
||||
Message string
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Stream defines a unique stream identifier in a textual representation
|
||||
type ID struct {
|
||||
// Name is used for the Stream provider identification
|
||||
Name string
|
||||
// Key is the name of specific data stream within the stream provider. The semantics of this value
|
||||
// is at the discretion of the stream provider implementation
|
||||
Key string
|
||||
}
|
||||
```
|
||||
|
||||
@ -147,14 +202,14 @@ Message exchange examples:
|
||||
======
|
||||
|
||||
Initial handshake - client queries server for stream states<br>
|
||||

|
||||

|
||||
<br>
|
||||
GetRange (bounded) - client requests a bounded range within a stream<br>
|
||||

|
||||

|
||||
<br>
|
||||
GetRange (unbounded) - client requests an unbounded range (specifies only `From` parameter)<br>
|
||||

|
||||

|
||||
<br>
|
||||
GetRange (no roundtrip) - client requests an unbounded or bounded range with no roundtrip configured<br>
|
||||

|
||||

|
||||
|
||||
|
@ -22,15 +22,20 @@ import (
|
||||
|
||||
var errInvalidLength = errors.New("invalid length")
|
||||
|
||||
// BitVector is a convenience object for manipulating and representing bit vectors
|
||||
type BitVector struct {
|
||||
len int
|
||||
b []byte
|
||||
}
|
||||
|
||||
// New creates a new bit vector with the given length
|
||||
func New(l int) (bv *BitVector, err error) {
|
||||
return NewFromBytes(make([]byte, l/8+1), l)
|
||||
}
|
||||
|
||||
// NewFromBytes creates a bit vector from the passed byte slice.
|
||||
//
|
||||
// Leftmost bit in byte slice becomes leftmost bit in bit vector
|
||||
func NewFromBytes(b []byte, l int) (bv *BitVector, err error) {
|
||||
if l <= 0 {
|
||||
return nil, errInvalidLength
|
||||
@ -44,12 +49,14 @@ func NewFromBytes(b []byte, l int) (bv *BitVector, err error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get gets the corresponding bit, counted from left to right
|
||||
func (bv *BitVector) Get(i int) bool {
|
||||
bi := i / 8
|
||||
return bv.b[bi]&(0x1<<uint(i%8)) != 0
|
||||
}
|
||||
|
||||
func (bv *BitVector) Set(i int, v bool) {
|
||||
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
|
||||
func (bv *BitVector) set(i int, v bool) {
|
||||
bi := i / 8
|
||||
cv := bv.Get(i)
|
||||
if cv != v {
|
||||
@ -57,6 +64,61 @@ func (bv *BitVector) Set(i int, v bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
|
||||
func (bv *BitVector) Set(i int) {
|
||||
bv.set(i, true)
|
||||
}
|
||||
|
||||
// Unset UNSETS the corresponding bit, counted from left to right
|
||||
func (bv *BitVector) Unset(i int) {
|
||||
bv.set(i, false)
|
||||
}
|
||||
|
||||
// SetBytes sets all bits in the bitvector that are set in the argument
|
||||
//
|
||||
// The argument must be the same as the bitvector length
|
||||
func (bv *BitVector) SetBytes(bs []byte) error {
|
||||
if len(bs) != bv.len {
|
||||
return errors.New("invalid length")
|
||||
}
|
||||
for i := 0; i < bv.len*8; i++ {
|
||||
bi := i / 8
|
||||
if bs[bi]&(0x01<<uint(i%8)) > 0 {
|
||||
bv.set(i, true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetBytes UNSETS all bits in the bitvector that are set in the argument
|
||||
//
|
||||
// The argument must be the same as the bitvector length
|
||||
func (bv *BitVector) UnsetBytes(bs []byte) error {
|
||||
if len(bs) != bv.len {
|
||||
return errors.New("invalid length")
|
||||
}
|
||||
for i := 0; i < bv.len*8; i++ {
|
||||
bi := i / 8
|
||||
if bs[bi]&(0x01<<uint(i%8)) > 0 {
|
||||
bv.set(i, false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements Stringer interface
|
||||
func (bv *BitVector) String() (s string) {
|
||||
for i := 0; i < bv.len*8; i++ {
|
||||
if bv.Get(i) {
|
||||
s += "1"
|
||||
} else {
|
||||
s += "0"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Bytes retrieves the underlying bytes of the bitvector
|
||||
func (bv *BitVector) Bytes() []byte {
|
||||
return bv.b
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package bitvector
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestBitvectorNew checks that enforcements of argument length works in the constructors
|
||||
func TestBitvectorNew(t *testing.T) {
|
||||
_, err := New(0)
|
||||
if err != errInvalidLength {
|
||||
@ -40,6 +41,7 @@ func TestBitvectorNew(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBitvectorGetSet tests correctness of individual Set and Get commands
|
||||
func TestBitvectorGetSet(t *testing.T) {
|
||||
for _, length := range []int{
|
||||
1,
|
||||
@ -71,7 +73,7 @@ func TestBitvectorGetSet(t *testing.T) {
|
||||
}()
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
bv.Set(i, true)
|
||||
bv.Set(i)
|
||||
for j := 0; j < length; j++ {
|
||||
if j == i {
|
||||
if !bv.Get(j) {
|
||||
@ -84,7 +86,7 @@ func TestBitvectorGetSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
bv.Set(i, false)
|
||||
bv.Unset(i)
|
||||
|
||||
if bv.Get(i) {
|
||||
t.Errorf("element on index %v is not set to false", i)
|
||||
@ -93,6 +95,7 @@ func TestBitvectorGetSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBitvectorNewFromBytesGet tests that bit vector is initialized correctly from underlying byte slice
|
||||
func TestBitvectorNewFromBytesGet(t *testing.T) {
|
||||
bv, err := NewFromBytes([]byte{8}, 8)
|
||||
if err != nil {
|
||||
@ -102,3 +105,36 @@ func TestBitvectorNewFromBytesGet(t *testing.T) {
|
||||
t.Fatalf("element 3 is not set to true: state %08b", bv.b[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestBitVectorString tests that string representation of bit vector is correct
|
||||
func TestBitVectorString(t *testing.T) {
|
||||
b := []byte{0xa5, 0x81}
|
||||
expect := "1010010110000001"
|
||||
bv, err := NewFromBytes(b, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bv.String() != expect {
|
||||
t.Fatalf("bitvector string fail: got %s, expect %s", bv.String(), expect)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBitVectorSetUnsetBytes tests that setting and unsetting by byte slice modifies the bit vector correctly
|
||||
func TestBitVectorSetBytes(t *testing.T) {
|
||||
b := []byte{0xff, 0xff}
|
||||
cb := []byte{0xa5, 0x81}
|
||||
expectUnset := "0101101001111110"
|
||||
expectReset := "1111111111111111"
|
||||
bv, err := NewFromBytes(b, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bv.UnsetBytes(cb)
|
||||
if bv.String() != expectUnset {
|
||||
t.Fatalf("bitvector unset bytes fail: got %s, expect %s", bv.String(), expectUnset)
|
||||
}
|
||||
bv.SetBytes(cb)
|
||||
if bv.String() != expectReset {
|
||||
t.Fatalf("bitvector reset bytes fail: got %s, expect %s", bv.String(), expectReset)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -93,6 +94,14 @@ type Kademlia struct {
|
||||
nDepthSig []chan struct{} // signals when neighbourhood depth nDepth is changed
|
||||
}
|
||||
|
||||
type KademliaInfo struct {
|
||||
Depth int `json:"depth"`
|
||||
TotalConnections int `json:"total_connections"`
|
||||
TotalKnown int `json:"total_known"`
|
||||
Connections [][]string `json:"connections"`
|
||||
Known [][]string `json:"known"`
|
||||
}
|
||||
|
||||
// NewKademlia creates a Kademlia table for base address addr
|
||||
// with parameters as in params
|
||||
// if params is nil, it uses default values
|
||||
@ -422,18 +431,6 @@ func (k *Kademlia) Off(p *Peer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kademlia) ListKnown() []*BzzAddr {
|
||||
res := []*BzzAddr{}
|
||||
|
||||
k.addrs.Each(func(val pot.Val) bool {
|
||||
e := val.(*entry)
|
||||
res = append(res, e.BzzAddr)
|
||||
return true
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// EachConn is an iterator with args (base, po, f) applies f to each live peer
|
||||
// that has proximity order po or less as measured from the base
|
||||
// if base is nil, kademlia base address is used
|
||||
@ -576,6 +573,56 @@ func (k *Kademlia) BaseAddr() []byte {
|
||||
return k.base
|
||||
}
|
||||
|
||||
func (k *Kademlia) KademliaInfo() KademliaInfo {
|
||||
k.lock.RLock()
|
||||
defer k.lock.RUnlock()
|
||||
return k.kademliaInfo()
|
||||
}
|
||||
|
||||
func (k *Kademlia) kademliaInfo() (ki KademliaInfo) {
|
||||
ki.Depth = depthForPot(k.conns, k.NeighbourhoodSize, k.base)
|
||||
ki.TotalConnections = k.conns.Size()
|
||||
ki.TotalKnown = k.addrs.Size()
|
||||
ki.Connections = make([][]string, k.MaxProxDisplay)
|
||||
ki.Known = make([][]string, k.MaxProxDisplay)
|
||||
|
||||
k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
|
||||
if po >= k.MaxProxDisplay {
|
||||
po = k.MaxProxDisplay - 1
|
||||
}
|
||||
|
||||
row := []string{}
|
||||
f(func(val pot.Val) bool {
|
||||
e := val.(*Peer)
|
||||
row = append(row, fmt.Sprintf("%x", e.Address()))
|
||||
return true
|
||||
})
|
||||
sort.Strings(row)
|
||||
ki.Connections[po] = row
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
|
||||
if po >= k.MaxProxDisplay {
|
||||
po = k.MaxProxDisplay - 1
|
||||
}
|
||||
|
||||
row := []string{}
|
||||
f(func(val pot.Val) bool {
|
||||
e := val.(*entry)
|
||||
row = append(row, fmt.Sprintf("%x", e.Address()))
|
||||
return true
|
||||
})
|
||||
sort.Strings(row)
|
||||
ki.Known[po] = row
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// String returns kademlia table + kaddb table displayed with ascii
|
||||
func (k *Kademlia) String() string {
|
||||
k.lock.RLock()
|
||||
|
65
network/newstream/common_test.go
Normal file
65
network/newstream/common_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package newstream
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/storage/localstore"
|
||||
"github.com/ethersphere/swarm/storage/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
loglevel = flag.Int("loglevel", 5, "verbosity of logs")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
}
|
||||
|
||||
func newTestLocalStore(id enode.ID, addr *network.BzzAddr, globalStore mock.GlobalStorer) (localStore *localstore.DB, cleanup func(), err error) {
|
||||
dir, err := ioutil.TempDir("", "swarm-stream-")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cleanup = func() {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
var mockStore *mock.NodeStore
|
||||
if globalStore != nil {
|
||||
mockStore = globalStore.NewNodeStore(common.BytesToAddress(id.Bytes()))
|
||||
}
|
||||
|
||||
localStore, err = localstore.New(dir, addr.Over(), &localstore.Options{
|
||||
MockStore: mockStore,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, err
|
||||
}
|
||||
return localStore, cleanup, nil
|
||||
}
|
100
network/newstream/peer.go
Normal file
100
network/newstream/peer.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package newstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/network/bitvector"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
)
|
||||
|
||||
var ErrEmptyBatch = errors.New("empty batch")
|
||||
|
||||
const (
|
||||
HashSize = 32
|
||||
BatchSize = 16
|
||||
)
|
||||
|
||||
// Peer is the Peer extension for the streaming protocol
|
||||
type Peer struct {
|
||||
*network.BzzPeer
|
||||
mtx sync.Mutex
|
||||
providers map[string]StreamProvider
|
||||
intervalsStore state.Store
|
||||
|
||||
streamCursorsMu sync.Mutex
|
||||
streamCursors map[string]uint64 // key: Stream ID string representation, value: session cursor. Keeps cursors for all streams. when unset - we are not interested in that bin
|
||||
dirtyStreams map[string]bool // key: stream ID, value: whether cursors for a stream should be updated
|
||||
activeBoundedGets map[string]chan struct{}
|
||||
openWants map[uint]*want // maintain open wants on the client side
|
||||
openOffers map[uint]offer // maintain open offers on the server side
|
||||
quit chan struct{} // closed when peer is going offline
|
||||
}
|
||||
|
||||
// NewPeer is the constructor for Peer
|
||||
func NewPeer(peer *network.BzzPeer, i state.Store, providers map[string]StreamProvider) *Peer {
|
||||
p := &Peer{
|
||||
BzzPeer: peer,
|
||||
providers: providers,
|
||||
intervalsStore: i,
|
||||
streamCursors: make(map[string]uint64),
|
||||
dirtyStreams: make(map[string]bool),
|
||||
openWants: make(map[uint]*want),
|
||||
openOffers: make(map[uint]offer),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
return p
|
||||
}
|
||||
func (p *Peer) Left() {
|
||||
close(p.quit)
|
||||
}
|
||||
|
||||
// HandleMsg is the message handler that delegates incoming messages
|
||||
func (p *Peer) HandleMsg(ctx context.Context, msg interface{}) error {
|
||||
switch msg := msg.(type) {
|
||||
default:
|
||||
return fmt.Errorf("unknown message type: %T", msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type offer struct {
|
||||
ruid uint
|
||||
stream ID
|
||||
hashes []byte
|
||||
requested time.Time
|
||||
}
|
||||
|
||||
type want struct {
|
||||
ruid uint
|
||||
from uint64
|
||||
to uint64
|
||||
stream ID
|
||||
hashes map[string]bool
|
||||
bv *bitvector.BitVector
|
||||
requested time.Time
|
||||
remaining uint64
|
||||
chunks chan chunk.Chunk
|
||||
done chan error
|
||||
}
|
167
network/newstream/stream.go
Normal file
167
network/newstream/stream.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package newstream
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/p2p/protocols"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
)
|
||||
|
||||
// SlipStream implements node.Service
|
||||
var _ node.Service = (*SlipStream)(nil)
|
||||
|
||||
var SyncerSpec = &protocols.Spec{
|
||||
Name: "bzz-stream",
|
||||
Version: 8,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
StreamInfoReq{},
|
||||
StreamInfoRes{},
|
||||
GetRange{},
|
||||
OfferedHashes{},
|
||||
ChunkDelivery{},
|
||||
WantedHashes{},
|
||||
},
|
||||
}
|
||||
|
||||
// SlipStream is the base type that handles all client/server operations on a node
|
||||
// it is instantiated once per stream protocol instance, that is, it should have
|
||||
// one instance per node
|
||||
type SlipStream struct {
|
||||
mtx sync.RWMutex
|
||||
intervalsStore state.Store //every protocol would make use of this
|
||||
peers map[enode.ID]*Peer
|
||||
kad *network.Kademlia
|
||||
|
||||
providers map[string]StreamProvider
|
||||
|
||||
spec *protocols.Spec //this protocol's spec
|
||||
balance protocols.Balance //implements protocols.Balance, for accounting
|
||||
prices protocols.Prices //implements protocols.Prices, provides prices to accounting
|
||||
|
||||
quit chan struct{} // terminates registry goroutines
|
||||
}
|
||||
|
||||
func NewSlipStream(intervalsStore state.Store, kad *network.Kademlia, providers ...StreamProvider) *SlipStream {
|
||||
slipStream := &SlipStream{
|
||||
intervalsStore: intervalsStore,
|
||||
kad: kad,
|
||||
peers: make(map[enode.ID]*Peer),
|
||||
providers: make(map[string]StreamProvider),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, p := range providers {
|
||||
slipStream.providers[p.StreamName()] = p
|
||||
}
|
||||
|
||||
slipStream.spec = SyncerSpec
|
||||
|
||||
return slipStream
|
||||
}
|
||||
|
||||
func (s *SlipStream) getPeer(id enode.ID) *Peer {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
p := s.peers[id]
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *SlipStream) addPeer(p *Peer) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.peers[p.ID()] = p
|
||||
}
|
||||
|
||||
func (s *SlipStream) removePeer(p *Peer) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
if _, found := s.peers[p.ID()]; found {
|
||||
log.Error("removing peer", "id", p.ID())
|
||||
delete(s.peers, p.ID())
|
||||
p.Left()
|
||||
} else {
|
||||
log.Warn("peer was marked for removal but not found", "peer", p.ID())
|
||||
}
|
||||
}
|
||||
|
||||
// Run is being dispatched when 2 nodes connect
|
||||
func (s *SlipStream) Run(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
peer := protocols.NewPeer(p, rw, s.spec)
|
||||
bp := network.NewBzzPeer(peer)
|
||||
|
||||
np := network.NewPeer(bp, s.kad)
|
||||
s.kad.On(np)
|
||||
defer s.kad.Off(np)
|
||||
|
||||
sp := NewPeer(bp, s.intervalsStore, s.providers)
|
||||
s.addPeer(sp)
|
||||
defer s.removePeer(sp)
|
||||
return peer.Run(sp.HandleMsg)
|
||||
}
|
||||
|
||||
func (s *SlipStream) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{
|
||||
{
|
||||
Name: "bzz-stream",
|
||||
Version: 1,
|
||||
Length: 10 * 1024 * 1024,
|
||||
Run: s.Run,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SlipStream) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "bzz-stream",
|
||||
Version: "1.0",
|
||||
Service: NewAPI(s),
|
||||
Public: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Additional public methods accessible through API for pss
|
||||
type API struct {
|
||||
*SlipStream
|
||||
}
|
||||
|
||||
func NewAPI(s *SlipStream) *API {
|
||||
return &API{SlipStream: s}
|
||||
}
|
||||
|
||||
func (s *SlipStream) Start(server *p2p.Server) error {
|
||||
log.Info("slip stream starting")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SlipStream) Stop() error {
|
||||
log.Info("slip stream closing")
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
close(s.quit)
|
||||
return nil
|
||||
}
|
174
network/newstream/wire.go
Normal file
174
network/newstream/wire.go
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package newstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
)
|
||||
|
||||
// StreamProvider interface provides a lightweight abstraction that allows an easily-pluggable
|
||||
// stream provider as part of the Stream! protocol specification.
|
||||
// Since Stream! thoroughly defines the concepts of a stream, intervals, clients and servers, the
|
||||
// interface therefore needs only a pluggable provider.
|
||||
// The domain interpretable notions which are at the discretion of the implementing
|
||||
// provider therefore are - sourcing data (get, put, subscribe for constant new data, and need data
|
||||
// which is to decide whether to retrieve data or not), retrieving cursors from the data store, the
|
||||
// implementation of which streams to maintain with a certain peer and providing functionality
|
||||
// to expose, parse and encode values related to the string represntation of the stream
|
||||
type StreamProvider interface {
|
||||
|
||||
// NeedData informs the caller whether a certain chunk needs to be fetched from another peer or not.
|
||||
// Typically this will involve checking whether a certain chunk exists locally.
|
||||
// In case a chunk does not exist locally - a `wait` function returns upon chunk delivery
|
||||
NeedData(ctx context.Context, key []byte) (need bool, wait func(context.Context) error)
|
||||
|
||||
// Get a particular chunk identified by addr from the local storage
|
||||
Get(ctx context.Context, addr chunk.Address) ([]byte, error)
|
||||
|
||||
// Put a certain chunk into the local storage
|
||||
Put(ctx context.Context, addr chunk.Address, data []byte) (exists bool, err error)
|
||||
|
||||
// Subscribe to a data stream from an arbitrary data source
|
||||
Subscribe(ctx context.Context, key interface{}, from, to uint64) (<-chan chunk.Descriptor, func())
|
||||
|
||||
// Cursor returns the last known Cursor for a given Stream Key
|
||||
Cursor(interface{}) (uint64, error)
|
||||
|
||||
// RunUpdateStreams is a provider specific implementation on how to maintain running streams with
|
||||
// an arbitrary Peer. This method should always be run in a separate goroutine
|
||||
RunUpdateStreams(p *Peer)
|
||||
|
||||
// StreamName returns the Name of the Stream (see ID)
|
||||
StreamName() string
|
||||
|
||||
// ParseStream from a standard pipe-separated string and return the Stream Key
|
||||
ParseKey(string) (interface{}, error)
|
||||
|
||||
// EncodeStream from a Stream Key to a Stream pipe-separated string representation
|
||||
EncodeKey(interface{}) (string, error)
|
||||
|
||||
// StreamBehavior defines how the stream behaves upon initialisation
|
||||
StreamBehavior() StreamInitBehavior
|
||||
|
||||
Boundedness() bool
|
||||
}
|
||||
|
||||
// StreamInitBehavior defines the stream behavior upon init
|
||||
type StreamInitBehavior int
|
||||
|
||||
const (
|
||||
// StreamIdle means that there is no initial automatic message exchange
|
||||
// between the nodes when the protocol gets established
|
||||
StreamIdle StreamInitBehavior = iota
|
||||
|
||||
// StreamGetCursors tells the two nodes to automatically fetch stream
|
||||
// cursors from each other
|
||||
StreamGetCursors
|
||||
|
||||
// StreamAutostart automatically starts fetching data from the streams
|
||||
// once the cursors arrive
|
||||
StreamAutostart
|
||||
)
|
||||
|
||||
// StreamInfoReq is a request to get information about particular streams
|
||||
type StreamInfoReq struct {
|
||||
Streams []ID
|
||||
}
|
||||
|
||||
// StreamInfoRes is a response to StreamInfoReq with the corresponding stream descriptors
|
||||
type StreamInfoRes struct {
|
||||
Streams []StreamDescriptor
|
||||
}
|
||||
|
||||
// StreamDescriptor describes an arbitrary stream
|
||||
type StreamDescriptor struct {
|
||||
Stream ID
|
||||
Cursor uint64
|
||||
Bounded bool
|
||||
}
|
||||
|
||||
// GetRange is a message sent from the downstream peer to the upstream peer asking for chunks
|
||||
// within a particular interval for a certain stream
|
||||
type GetRange struct {
|
||||
Ruid uint
|
||||
Stream ID
|
||||
From uint64
|
||||
To uint64 `rlp:nil`
|
||||
BatchSize uint
|
||||
Roundtrip bool
|
||||
}
|
||||
|
||||
// OfferedHashes is a message sent from the upstream peer to the downstream peer allowing the latter
|
||||
// to selectively ask for chunks within a particular requested interval
|
||||
type OfferedHashes struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Hashes []byte
|
||||
}
|
||||
|
||||
// WantedHashes is a message sent from the downstream peer to the upstream peer in response
|
||||
// to OfferedHashes in order to selectively ask for a particular chunks within an interval
|
||||
type WantedHashes struct {
|
||||
Ruid uint
|
||||
BitVector []byte
|
||||
}
|
||||
|
||||
// ChunkDelivery delivers a frame of chunks in response to a WantedHashes message
|
||||
type ChunkDelivery struct {
|
||||
Ruid uint
|
||||
LastIndex uint
|
||||
Chunks []DeliveredChunk
|
||||
}
|
||||
|
||||
// DeliveredChunk encapsulates a particular chunk's underlying data within a ChunkDelivery message
|
||||
type DeliveredChunk struct {
|
||||
Addr storage.Address //chunk address
|
||||
Data []byte //chunk data
|
||||
}
|
||||
|
||||
// StreamState is a message exchanged between two nodes to notify of changes or errors in a stream's state
|
||||
type StreamState struct {
|
||||
Stream ID
|
||||
Code uint16
|
||||
Message string
|
||||
}
|
||||
|
||||
// Stream defines a unique stream identifier in a textual representation
|
||||
type ID struct {
|
||||
// Name is used for the Stream provider identification
|
||||
Name string
|
||||
// Key is the name of specific data stream within the stream provider. The semantics of this value
|
||||
// is at the discretion of the stream provider implementation
|
||||
Key string
|
||||
}
|
||||
|
||||
// NewID returns a new Stream ID for a particular stream Name and Key
|
||||
func NewID(name string, key string) ID {
|
||||
return ID{
|
||||
Name: name,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// String return a stream id based on all Stream fields.
|
||||
func (s ID) String() string {
|
||||
return fmt.Sprintf("%s|%s", s.Name, s.Key)
|
||||
}
|
36
network/retrieval/peer.go
Normal file
36
network/retrieval/peer.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package retrieval
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
)
|
||||
|
||||
// Peer wraps BzzPeer with a contextual logger for this peer
|
||||
type Peer struct {
|
||||
*network.BzzPeer
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewPeer is the constructor for Peer
|
||||
func NewPeer(peer *network.BzzPeer) *Peer {
|
||||
return &Peer{
|
||||
BzzPeer: peer,
|
||||
logger: log.New("peer", peer.ID()),
|
||||
}
|
||||
}
|
424
network/retrieval/retrieve.go
Normal file
424
network/retrieval/retrieve.go
Normal file
@ -0,0 +1,424 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package retrieval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/network/timeouts"
|
||||
"github.com/ethersphere/swarm/p2p/protocols"
|
||||
"github.com/ethersphere/swarm/spancontext"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
olog "github.com/opentracing/opentracing-go/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// Compile time interface check
|
||||
_ node.Service = &Retrieval{}
|
||||
|
||||
// Metrics
|
||||
processReceivedChunksCount = metrics.NewRegisteredCounter("network.retrieve.received_chunks.count", nil)
|
||||
handleRetrieveRequestMsgCount = metrics.NewRegisteredCounter("network.retrieve.handle_retrieve_request_msg.count", nil)
|
||||
retrieveChunkFail = metrics.NewRegisteredCounter("network.retrieve.retrieve_chunks_fail.count", nil)
|
||||
|
||||
lastReceivedRetrieveChunksMsg = metrics.GetOrRegisterGauge("network.retrieve.received_chunks", nil)
|
||||
|
||||
// Protocol spec
|
||||
spec = &protocols.Spec{
|
||||
Name: "bzz-retrieve",
|
||||
Version: 1,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
ChunkDelivery{},
|
||||
RetrieveRequest{},
|
||||
},
|
||||
}
|
||||
|
||||
ErrNoPeerFound = errors.New("no peer found")
|
||||
)
|
||||
|
||||
// Retrieval holds state and handles protocol messages for the `bzz-retrieve` protocol
|
||||
type Retrieval struct {
|
||||
mtx sync.Mutex
|
||||
netStore *storage.NetStore
|
||||
kad *network.Kademlia
|
||||
peers map[enode.ID]*Peer
|
||||
spec *protocols.Spec //this protocol's spec
|
||||
|
||||
quit chan struct{} // termination
|
||||
}
|
||||
|
||||
// NewRetrieval returns a new instance of the retrieval protocol handler
|
||||
func New(kad *network.Kademlia, ns *storage.NetStore) *Retrieval {
|
||||
return &Retrieval{
|
||||
kad: kad,
|
||||
peers: make(map[enode.ID]*Peer),
|
||||
netStore: ns,
|
||||
quit: make(chan struct{}),
|
||||
spec: spec,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retrieval) addPeer(p *Peer) {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
r.peers[p.ID()] = p
|
||||
}
|
||||
|
||||
func (r *Retrieval) removePeer(p *Peer) {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
delete(r.peers, p.ID())
|
||||
}
|
||||
|
||||
func (r *Retrieval) getPeer(id enode.ID) *Peer {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
return r.peers[id]
|
||||
}
|
||||
|
||||
// Run protocol function
|
||||
func (r *Retrieval) Run(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
peer := protocols.NewPeer(p, rw, r.spec)
|
||||
bp := network.NewBzzPeer(peer)
|
||||
sp := NewPeer(bp)
|
||||
r.addPeer(sp)
|
||||
defer r.removePeer(sp)
|
||||
return peer.Run(r.handleMsg(sp))
|
||||
}
|
||||
|
||||
func (r *Retrieval) handleMsg(p *Peer) func(context.Context, interface{}) error {
|
||||
return func(ctx context.Context, msg interface{}) error {
|
||||
switch msg := msg.(type) {
|
||||
case *RetrieveRequest:
|
||||
go r.handleRetrieveRequest(ctx, p, msg)
|
||||
case *ChunkDelivery:
|
||||
go r.handleChunkDelivery(ctx, p, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// getOriginPo returns the originPo if the incoming Request has an Origin
|
||||
// if our node is the first node that requests this chunk, then we don't have an Origin,
|
||||
// and return -1
|
||||
// this is used only for tracing, and can probably be refactor so that we don't have to
|
||||
// iterater over Kademlia
|
||||
func (r *Retrieval) getOriginPo(req *storage.Request) int {
|
||||
log.Trace("retrieval.getOriginPo", "req.Addr", req.Addr)
|
||||
originPo := -1
|
||||
|
||||
r.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
|
||||
id := p.ID()
|
||||
|
||||
// get po between chunk and origin
|
||||
if bytes.Equal(req.Origin.Bytes(), id.Bytes()) {
|
||||
originPo = po
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return originPo
|
||||
}
|
||||
|
||||
// findPeer finds a peer we need to ask for a specific chunk from according to our kademlia
|
||||
func (r *Retrieval) findPeer(ctx context.Context, req *storage.Request) (retPeer *network.Peer, err error) {
|
||||
log.Trace("retrieval.findPeer", "req.Addr", req.Addr)
|
||||
osp, _ := ctx.Value("remote.fetch").(opentracing.Span)
|
||||
|
||||
// originPo - proximity of the node that made the request; -1 if the request originator is our node;
|
||||
// myPo - this node's proximity with the requested chunk
|
||||
// selectedPeerPo - kademlia suggested node's proximity with the requested chunk (computed further below)
|
||||
originPo := r.getOriginPo(req)
|
||||
myPo := chunk.Proximity(req.Addr, r.kad.BaseAddr())
|
||||
selectedPeerPo := -1
|
||||
|
||||
depth := r.kad.NeighbourhoodDepth()
|
||||
|
||||
if osp != nil {
|
||||
osp.LogFields(olog.Int("originPo", originPo))
|
||||
osp.LogFields(olog.Int("depth", depth))
|
||||
osp.LogFields(olog.Int("myPo", myPo))
|
||||
}
|
||||
|
||||
// do not forward requests if origin proximity is bigger than our node's proximity
|
||||
// this means that origin is closer to the chunk
|
||||
if originPo > myPo {
|
||||
return nil, errors.New("not forwarding request, origin node is closer to chunk than this node")
|
||||
}
|
||||
|
||||
r.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool {
|
||||
id := p.ID()
|
||||
|
||||
// skip light nodes
|
||||
if p.LightNode {
|
||||
return true
|
||||
}
|
||||
|
||||
// do not send request back to peer who asked us. maybe merge with SkipPeer at some point
|
||||
if bytes.Equal(req.Origin.Bytes(), id.Bytes()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip peers that we have already tried
|
||||
if req.SkipPeer(id.String()) {
|
||||
log.Trace("findpeer skip peer", "peer", id, "ref", req.Addr.String())
|
||||
return true
|
||||
}
|
||||
|
||||
if myPo < depth { // chunk is NOT within the neighbourhood
|
||||
if po <= myPo { // always choose a peer strictly closer to chunk than us
|
||||
log.Trace("findpeer1a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else {
|
||||
log.Trace("findpeer1b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
}
|
||||
} else { // chunk IS WITHIN neighbourhood
|
||||
if po < depth { // do not select peer outside the neighbourhood. But allows peers further from the chunk than us
|
||||
log.Trace("findpeer2a", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else if po <= originPo { // avoid loop in neighbourhood, so not forward when a request comes from the neighbourhood
|
||||
log.Trace("findpeer2b", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
return false
|
||||
} else {
|
||||
log.Trace("findpeer2c", "originpo", originPo, "mypo", myPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
// if selected peer is not in the depth (2nd condition; if depth <= po, then peer is in nearest neighbourhood)
|
||||
// and they have a lower po than ours, return error
|
||||
if po < myPo && depth > po {
|
||||
log.Trace("findpeer4 skip peer because origin was closer", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
|
||||
err = fmt.Errorf("not asking peers further away from origin; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
|
||||
return false
|
||||
}
|
||||
|
||||
// if chunk falls in our nearest neighbourhood (1st condition), but suggested peer is not in
|
||||
// the nearest neighbourhood (2nd condition), don't forward the request to suggested peer
|
||||
if depth <= myPo && depth > po {
|
||||
log.Trace("findpeer5 skip peer because depth", "originpo", originPo, "po", po, "depth", depth, "peer", id, "ref", req.Addr.String())
|
||||
|
||||
err = fmt.Errorf("not going outside of depth; ref=%s originpo=%v po=%v depth=%v myPo=%v", req.Addr.String(), originPo, po, depth, myPo)
|
||||
return false
|
||||
}
|
||||
|
||||
retPeer = p
|
||||
|
||||
// sp could be nil, if we encountered a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol
|
||||
// if sp is not nil, then we have selected the next peer and we stop iterating
|
||||
// if sp is nil, we continue iterating
|
||||
if retPeer != nil {
|
||||
selectedPeerPo = po
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// continue iterating
|
||||
return true
|
||||
})
|
||||
|
||||
if osp != nil {
|
||||
osp.LogFields(olog.Int("selectedPeerPo", selectedPeerPo))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if retPeer == nil {
|
||||
return nil, ErrNoPeerFound
|
||||
}
|
||||
|
||||
return retPeer, nil
|
||||
}
|
||||
|
||||
// handleRetrieveRequest handles an incoming retrieve request from a certain Peer
|
||||
// if the chunk is found in the localstore it is served immediately, otherwise
|
||||
// it results in a new retrieve request to candidate peers in our kademlia
|
||||
func (r *Retrieval) handleRetrieveRequest(ctx context.Context, p *Peer, msg *RetrieveRequest) {
|
||||
p.logger.Debug("retrieval.handleRetrieveRequest", "ref", msg.Addr)
|
||||
handleRetrieveRequestMsgCount.Inc(1)
|
||||
|
||||
ctx, osp := spancontext.StartSpan(
|
||||
ctx,
|
||||
"handle.retrieve.request")
|
||||
|
||||
osp.LogFields(olog.String("ref", msg.Addr.String()))
|
||||
|
||||
defer osp.Finish()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, timeouts.FetcherGlobalTimeout)
|
||||
defer cancel()
|
||||
|
||||
req := &storage.Request{
|
||||
Addr: msg.Addr,
|
||||
Origin: p.ID(),
|
||||
}
|
||||
chunk, err := r.netStore.Get(ctx, chunk.ModeGetRequest, req)
|
||||
if err != nil {
|
||||
retrieveChunkFail.Inc(1)
|
||||
p.logger.Debug("netstore.Get can not retrieve chunk", "ref", msg.Addr, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
p.logger.Trace("retrieval.handleRetrieveRequest - delivery", "ref", msg.Addr)
|
||||
|
||||
deliveryMsg := &ChunkDelivery{
|
||||
Addr: chunk.Address(),
|
||||
SData: chunk.Data(),
|
||||
}
|
||||
|
||||
err = p.Send(ctx, deliveryMsg)
|
||||
if err != nil {
|
||||
p.logger.Error("retrieval.handleRetrieveRequest - peer delivery failed", "ref", msg.Addr, "err", err)
|
||||
osp.LogFields(olog.Bool("delivered", false))
|
||||
return
|
||||
}
|
||||
osp.LogFields(olog.Bool("delivered", true))
|
||||
}
|
||||
|
||||
// handleChunkDelivery handles a ChunkDelivery message from a certain peer
|
||||
// if the chunk proximity order in relation to our base address is within depth
|
||||
// we treat the chunk as a chunk received in syncing
|
||||
func (r *Retrieval) handleChunkDelivery(ctx context.Context, p *Peer, msg *ChunkDelivery) error {
|
||||
p.logger.Debug("retrieval.handleChunkDelivery", "ref", msg.Addr)
|
||||
var osp opentracing.Span
|
||||
ctx, osp = spancontext.StartSpan(
|
||||
ctx,
|
||||
"handle.chunk.delivery")
|
||||
|
||||
processReceivedChunksCount.Inc(1)
|
||||
|
||||
// record the last time we received a chunk delivery message
|
||||
lastReceivedRetrieveChunksMsg.Update(time.Now().UnixNano())
|
||||
|
||||
// count how many chunks we receive for retrieve requests per peer
|
||||
peermetric := fmt.Sprintf("chunk.delivery.%x", p.BzzAddr.Over()[:16])
|
||||
metrics.GetOrRegisterCounter(peermetric, nil).Inc(1)
|
||||
|
||||
peerPO := chunk.Proximity(p.BzzAddr.Over(), msg.Addr)
|
||||
po := chunk.Proximity(r.kad.BaseAddr(), msg.Addr)
|
||||
depth := r.kad.NeighbourhoodDepth()
|
||||
var mode chunk.ModePut
|
||||
// chunks within the area of responsibility should always sync
|
||||
// https://github.com/ethersphere/go-ethereum/pull/1282#discussion_r269406125
|
||||
if po >= depth || peerPO < po {
|
||||
mode = chunk.ModePutSync
|
||||
} else {
|
||||
// do not sync if peer that is sending us a chunk is closer to the chunk then we are
|
||||
mode = chunk.ModePutRequest
|
||||
}
|
||||
|
||||
p.logger.Trace("handle.chunk.delivery", "ref", msg.Addr)
|
||||
|
||||
go func() {
|
||||
defer osp.Finish()
|
||||
p.logger.Trace("handle.chunk.delivery", "put", msg.Addr)
|
||||
_, err := r.netStore.Put(ctx, mode, storage.NewChunk(msg.Addr, msg.SData))
|
||||
if err != nil {
|
||||
if err == storage.ErrChunkInvalid {
|
||||
p.Drop()
|
||||
}
|
||||
}
|
||||
p.logger.Trace("handle.chunk.delivery", "done put", msg.Addr, "err", err)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestFromPeers sends a chunk retrieve request to the next found peer
|
||||
func (r *Retrieval) RequestFromPeers(ctx context.Context, req *storage.Request, localID enode.ID) (*enode.ID, error) {
|
||||
log.Debug("retrieval.requestFromPeers", "req.Addr", req.Addr)
|
||||
metrics.GetOrRegisterCounter("network.retrieve.request_from_peers", nil).Inc(1)
|
||||
|
||||
const maxFindPeerRetries = 5
|
||||
retries := 0
|
||||
|
||||
FINDPEER:
|
||||
sp, err := r.findPeer(ctx, req)
|
||||
if err != nil {
|
||||
log.Trace(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
protoPeer := r.getPeer(sp.ID())
|
||||
if protoPeer == nil {
|
||||
retries++
|
||||
if retries == maxFindPeerRetries {
|
||||
log.Error("max find peer retries reached", "max retries", maxFindPeerRetries)
|
||||
return nil, ErrNoPeerFound
|
||||
}
|
||||
|
||||
goto FINDPEER
|
||||
}
|
||||
|
||||
ret := RetrieveRequest{
|
||||
Addr: req.Addr,
|
||||
}
|
||||
protoPeer.logger.Trace("sending retrieve request", "ref", ret.Addr, "origin", localID)
|
||||
err = protoPeer.Send(ctx, ret)
|
||||
if err != nil {
|
||||
protoPeer.logger.Error("error sending retrieve request to peer", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spID := protoPeer.ID()
|
||||
return &spID, nil
|
||||
}
|
||||
|
||||
func (r *Retrieval) Start(server *p2p.Server) error {
|
||||
log.Info("starting bzz-retrieve")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Retrieval) Stop() error {
|
||||
log.Info("shutting down bzz-retrieve")
|
||||
close(r.quit)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Retrieval) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{
|
||||
{
|
||||
Name: r.spec.Name,
|
||||
Version: r.spec.Version,
|
||||
Length: r.spec.Length(),
|
||||
Run: r.Run,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Retrieval) APIs() []rpc.API {
|
||||
return nil
|
||||
}
|
398
network/retrieval/retrieve_test.go
Normal file
398
network/retrieval/retrieve_test.go
Normal file
@ -0,0 +1,398 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package retrieval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"github.com/ethersphere/swarm/network/simulation"
|
||||
"github.com/ethersphere/swarm/p2p/protocols"
|
||||
"github.com/ethersphere/swarm/state"
|
||||
"github.com/ethersphere/swarm/storage"
|
||||
"github.com/ethersphere/swarm/storage/localstore"
|
||||
"github.com/ethersphere/swarm/storage/mock"
|
||||
"github.com/ethersphere/swarm/testutil"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
loglevel = flag.Int("loglevel", 5, "verbosity of logs")
|
||||
bucketKeyFileStore = simulation.BucketKey("filestore")
|
||||
bucketKeyNetstore = simulation.BucketKey("netstore")
|
||||
|
||||
hash0 = sha3.Sum256([]byte{0})
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
}
|
||||
|
||||
// TestChunkDelivery brings up two nodes, stores a few chunks on the first node, then tries to retrieve them through the second node
|
||||
func TestChunkDelivery(t *testing.T) {
|
||||
chunkCount := 10
|
||||
filesize := chunkCount * 4096
|
||||
|
||||
sim := simulation.NewBzzInProc(map[string]simulation.ServiceFunc{
|
||||
"bzz-retrieve": newBzzRetrieveWithLocalstore,
|
||||
})
|
||||
defer sim.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_, err := sim.AddNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
|
||||
nodeIDs := sim.UpNodeIDs()
|
||||
log.Debug("uploader node", "enode", nodeIDs[0])
|
||||
|
||||
fs := sim.MustNodeItem(nodeIDs[0], bucketKeyFileStore).(*storage.FileStore)
|
||||
|
||||
//put some data into just the first node
|
||||
data := make([]byte, filesize)
|
||||
if _, err := io.ReadFull(rand.Reader, data); err != nil {
|
||||
t.Fatalf("reading from crypto/rand failed: %v", err.Error())
|
||||
}
|
||||
refs, err := getAllRefs(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace("got all refs", "refs", refs)
|
||||
_, wait, err := fs.Store(context.Background(), bytes.NewReader(data), int64(filesize), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := wait(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := sim.AddNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sim.Net.Connect(id, nodeIDs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeIDs = sim.UpNodeIDs()
|
||||
if len(nodeIDs) != 2 {
|
||||
return fmt.Errorf("wrong number of nodes, expected %d got %d", 2, len(nodeIDs))
|
||||
}
|
||||
|
||||
// allow the two nodes time to set up the protocols otherwise kademlias will be empty when retrieve requests happen
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
log.Debug("fetching through node", "enode", nodeIDs[1])
|
||||
ns := sim.MustNodeItem(nodeIDs[1], bucketKeyNetstore).(*storage.NetStore)
|
||||
ctr := 0
|
||||
for _, ch := range refs {
|
||||
ctr++
|
||||
_, err := ns.Get(context.Background(), chunk.ModeGetRequest, storage.NewRequest(ch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if result.Error != nil {
|
||||
t.Fatal(result.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeliveryForwarding tests that chunk delivery forwarding requests happen. It creates three nodes (fetching, forwarding and uploading)
|
||||
// where po(fetching,forwarding) = 1 and po(forwarding,uploading) = 1, then uploads chunks to the uploading node, afterwards
|
||||
// tries to retrieve the relevant chunks (ones with po = 0 to fetching i.e. no bits in common with fetching and with
|
||||
// po >= 1 with uploading i.e. with 1 bit or more in common with the uploading)
|
||||
func TestDeliveryForwarding(t *testing.T) {
|
||||
chunkCount := 100
|
||||
filesize := chunkCount * 4096
|
||||
sim, uploader, forwarder, fetcher := setupTestDeliveryForwardingSimulation(t)
|
||||
defer sim.Close()
|
||||
|
||||
log.Debug("test delivery forwarding", "uploader", uploader, "forwarder", forwarder, "fetcher", fetcher)
|
||||
|
||||
uploaderNodeStore := sim.MustNodeItem(uploader, bucketKeyFileStore).(*storage.FileStore)
|
||||
fetcherBase := sim.MustNodeItem(fetcher, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
|
||||
uploaderBase := sim.MustNodeItem(fetcher, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
|
||||
ctx := context.Background()
|
||||
_, wait, err := uploaderNodeStore.Store(ctx, testutil.RandomReader(101010, filesize), int64(filesize), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = wait(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
chunks, err := getChunks(uploaderNodeStore.ChunkStore)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for c := range chunks {
|
||||
addr, err := hex.DecodeString(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// try to retrieve all of the chunks which have no bits in common with the
|
||||
// fetcher, but have more than one bit in common with the uploader node
|
||||
if chunk.Proximity(addr, fetcherBase) == 0 && chunk.Proximity(addr, uploaderBase) >= 1 {
|
||||
req := storage.NewRequest(chunk.Address(addr))
|
||||
fetcherNetstore := sim.MustNodeItem(fetcher, bucketKeyNetstore).(*storage.NetStore)
|
||||
_, err := fetcherNetstore.Get(ctx, chunk.ModeGetRequest, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestDeliveryForwardingSimulation(t *testing.T) (sim *simulation.Simulation, uploader, forwarder, fetching enode.ID) {
|
||||
sim = simulation.NewBzzInProc(map[string]simulation.ServiceFunc{
|
||||
"bzz-retrieve": newBzzRetrieveWithLocalstore,
|
||||
})
|
||||
|
||||
fetching, err := sim.AddNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fetcherBase := sim.MustNodeItem(fetching, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
|
||||
|
||||
override := func(o *adapters.NodeConfig) func(*adapters.NodeConfig) {
|
||||
return func(c *adapters.NodeConfig) {
|
||||
*o = *c
|
||||
}
|
||||
}
|
||||
|
||||
// create a node that will be in po 1 from fetcher
|
||||
forwarderConfig := testutil.NodeConfigAtPo(t, fetcherBase, 1)
|
||||
forwarder, err = sim.AddNode(override(forwarderConfig))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = sim.Net.Connect(fetching, forwarder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
forwarderBase := sim.MustNodeItem(forwarder, simulation.BucketKeyKademlia).(*network.Kademlia).BaseAddr()
|
||||
|
||||
// create a node on which the files will be stored at po 1 in relation to the forwarding node
|
||||
uploaderConfig := testutil.NodeConfigAtPo(t, forwarderBase, 1)
|
||||
uploader, err = sim.AddNode(override(uploaderConfig))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = sim.Net.Connect(forwarder, uploader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return sim, uploader, forwarder, fetching
|
||||
}
|
||||
|
||||
// if there is one peer in the Kademlia, RequestFromPeers should return it
|
||||
func TestRequestFromPeers(t *testing.T) {
|
||||
dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
|
||||
|
||||
addr := network.RandomAddr()
|
||||
to := network.NewKademlia(addr.OAddr, network.NewKadParams())
|
||||
protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil)
|
||||
peer := network.NewPeer(&network.BzzPeer{
|
||||
BzzAddr: network.RandomAddr(),
|
||||
LightNode: false,
|
||||
Peer: protocolsPeer,
|
||||
}, to)
|
||||
|
||||
to.On(peer)
|
||||
|
||||
s := New(to, nil)
|
||||
|
||||
req := storage.NewRequest(storage.Address(hash0[:]))
|
||||
id, err := s.findPeer(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if id.ID() != dummyPeerID {
|
||||
t.Fatalf("Expected an id, got %v", id)
|
||||
}
|
||||
}
|
||||
|
||||
// RequestFromPeers should not return light nodes
|
||||
func TestRequestFromPeersWithLightNode(t *testing.T) {
|
||||
dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8")
|
||||
|
||||
addr := network.RandomAddr()
|
||||
to := network.NewKademlia(addr.OAddr, network.NewKadParams())
|
||||
|
||||
protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil)
|
||||
|
||||
// setting up a lightnode
|
||||
peer := network.NewPeer(&network.BzzPeer{
|
||||
BzzAddr: network.RandomAddr(),
|
||||
LightNode: true,
|
||||
Peer: protocolsPeer,
|
||||
}, to)
|
||||
|
||||
to.On(peer)
|
||||
|
||||
r := New(to, nil)
|
||||
req := storage.NewRequest(storage.Address(hash0[:]))
|
||||
|
||||
// making a request which should return with "no peer found"
|
||||
_, err := r.findPeer(context.Background(), req)
|
||||
|
||||
if err != ErrNoPeerFound {
|
||||
t.Fatalf("expected '%v', got %v", ErrNoPeerFound, err)
|
||||
}
|
||||
}
|
||||
|
||||
func newBzzRetrieveWithLocalstore(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
|
||||
n := ctx.Config.Node()
|
||||
addr := network.NewAddr(n)
|
||||
|
||||
localStore, localStoreCleanup, err := newTestLocalStore(n.ID(), addr, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var kad *network.Kademlia
|
||||
if kv, ok := bucket.Load(simulation.BucketKeyKademlia); ok {
|
||||
kad = kv.(*network.Kademlia)
|
||||
} else {
|
||||
kad = network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||
bucket.Store(simulation.BucketKeyKademlia, kad)
|
||||
}
|
||||
|
||||
netStore := storage.NewNetStore(localStore, n.ID())
|
||||
lnetStore := storage.NewLNetStore(netStore)
|
||||
fileStore := storage.NewFileStore(lnetStore, storage.NewFileStoreParams(), chunk.NewTags())
|
||||
|
||||
var store *state.DBStore
|
||||
// Use on-disk DBStore to reduce memory consumption in race tests.
|
||||
dir, err := ioutil.TempDir("", "statestore-")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
store, err = state.NewDBStore(dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := New(kad, netStore)
|
||||
netStore.RemoteGet = r.RequestFromPeers
|
||||
bucket.Store(bucketKeyFileStore, fileStore)
|
||||
bucket.Store(bucketKeyNetstore, netStore)
|
||||
bucket.Store(simulation.BucketKeyKademlia, kad)
|
||||
|
||||
cleanup = func() {
|
||||
localStore.Close()
|
||||
localStoreCleanup()
|
||||
store.Close()
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
return r, cleanup, nil
|
||||
}
|
||||
|
||||
func newTestLocalStore(id enode.ID, addr *network.BzzAddr, globalStore mock.GlobalStorer) (localStore *localstore.DB, cleanup func(), err error) {
|
||||
dir, err := ioutil.TempDir("", "localstore-")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cleanup = func() {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
var mockStore *mock.NodeStore
|
||||
if globalStore != nil {
|
||||
mockStore = globalStore.NewNodeStore(common.BytesToAddress(id.Bytes()))
|
||||
}
|
||||
|
||||
localStore, err = localstore.New(dir, addr.Over(), &localstore.Options{
|
||||
MockStore: mockStore,
|
||||
})
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, err
|
||||
}
|
||||
return localStore, cleanup, nil
|
||||
}
|
||||
|
||||
func getAllRefs(testData []byte) (storage.AddressCollection, error) {
|
||||
datadir, err := ioutil.TempDir("", "chunk-debug")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(datadir)
|
||||
fileStore, cleanup, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
reader := bytes.NewReader(testData)
|
||||
return fileStore.GetAllReferences(context.Background(), reader, false)
|
||||
}
|
||||
|
||||
func getChunks(store chunk.Store) (chunks map[string]struct{}, err error) {
|
||||
chunks = make(map[string]struct{})
|
||||
for po := uint8(0); po <= chunk.MaxPO; po++ {
|
||||
last, err := store.LastPullSubscriptionBinID(uint8(po))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if last == 0 {
|
||||
continue
|
||||
}
|
||||
ch, _ := store.SubscribePull(context.Background(), po, 0, last)
|
||||
for c := range ch {
|
||||
addr := c.Address.Hex()
|
||||
if _, ok := chunks[addr]; ok {
|
||||
return nil, fmt.Errorf("duplicate chunk %s", addr)
|
||||
}
|
||||
chunks[addr] = struct{}{}
|
||||
}
|
||||
}
|
||||
return chunks, nil
|
||||
}
|
30
network/retrieval/wire.go
Normal file
30
network/retrieval/wire.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package retrieval
|
||||
|
||||
import "github.com/ethersphere/swarm/storage"
|
||||
|
||||
// RetrieveRequestMsg is the protocol msg for chunk retrieve requests
|
||||
type RetrieveRequest struct {
|
||||
Addr storage.Address
|
||||
}
|
||||
|
||||
// ChunkDelivery is the protocol msg for delivering a solicited chunk to a peer
|
||||
type ChunkDelivery struct {
|
||||
Addr storage.Address
|
||||
SData []byte // the stored chunk Data (incl size)
|
||||
}
|
@ -16,7 +16,11 @@
|
||||
|
||||
package simulation
|
||||
|
||||
import "github.com/ethereum/go-ethereum/p2p/enode"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// BucketKey is the type that should be used for keys in simulation buckets.
|
||||
type BucketKey string
|
||||
@ -32,6 +36,24 @@ func (s *Simulation) NodeItem(id enode.ID, key interface{}) (value interface{},
|
||||
return s.buckets[id].Load(key)
|
||||
}
|
||||
|
||||
// MustNodeItem returns the item set in ServiceFunc for a particular node or panics in case
|
||||
// the item is not found
|
||||
func (s *Simulation) MustNodeItem(id enode.ID, key interface{}) (value interface{}) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if _, ok := s.buckets[id]; !ok {
|
||||
e := fmt.Errorf("cannot find node id %s in bucket", id.String())
|
||||
panic(e)
|
||||
}
|
||||
if v, ok := s.buckets[id].Load(key); ok {
|
||||
return v
|
||||
} else {
|
||||
e := fmt.Errorf("cannot find key %v on node bucket", key)
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// SetNodeItem sets a new item associated with the node with provided NodeID.
|
||||
// Buckets should be used to avoid managing separate simulation global state.
|
||||
func (s *Simulation) SetNodeItem(id enode.ID, key interface{}, value interface{}) {
|
||||
|
@ -99,6 +99,34 @@ func NewInProc(services map[string]ServiceFunc) (s *Simulation) {
|
||||
return s
|
||||
}
|
||||
|
||||
// NewBzzInProc is the same as NewInProc but injects bzz as a default protocol
|
||||
func NewBzzInProc(services map[string]ServiceFunc) (s *Simulation) {
|
||||
services["bzz"] = func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
hp := network.NewHiveParams()
|
||||
hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
|
||||
hp.Discovery = false
|
||||
var kad *network.Kademlia
|
||||
|
||||
// check if another kademlia already exists and load it if necessary - we dont want two independent copies of it
|
||||
if kv, ok := bucket.Load(BucketKeyKademlia); ok {
|
||||
kad = kv.(*network.Kademlia)
|
||||
} else {
|
||||
kad = network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||
bucket.Store(BucketKeyKademlia, kad)
|
||||
}
|
||||
|
||||
config := &network.BzzConfig{
|
||||
OverlayAddr: addr.Over(),
|
||||
UnderlayAddr: addr.Under(),
|
||||
HiveParams: hp,
|
||||
}
|
||||
return network.NewBzz(config, kad, nil, nil, nil), nil, nil
|
||||
}
|
||||
|
||||
return NewInProc(services)
|
||||
}
|
||||
|
||||
// NewExec does the same as New but lets the caller specify the adapter to use
|
||||
func NewExec(services map[string]ServiceFunc) (s *Simulation, err error) {
|
||||
s = &Simulation{
|
||||
|
@ -131,6 +131,10 @@ func (d *Delivery) handleChunkDeliveryMsg(ctx context.Context, sp *Peer, req int
|
||||
var mode chunk.ModePut
|
||||
switch r := req.(type) {
|
||||
case *ChunkDeliveryMsgRetrieval:
|
||||
// count how many chunks we receive for retrieve requests per peer
|
||||
peermetric := fmt.Sprintf("chunk.delivery.%x", sp.BzzAddr.Over()[:16])
|
||||
metrics.GetOrRegisterCounter(peermetric, nil).Inc(1)
|
||||
|
||||
msg = (*ChunkDeliveryMsg)(r)
|
||||
peerPO := chunk.Proximity(sp.BzzAddr.Over(), msg.Addr)
|
||||
po := chunk.Proximity(d.kad.BaseAddr(), msg.Addr)
|
||||
|
@ -231,7 +231,7 @@ func (p *Peer) handleOfferedHashesMsg(ctx context.Context, req *OfferedHashesMsg
|
||||
ctr++
|
||||
|
||||
// set the bit, so create a request
|
||||
want.Set(i/HashSize, true)
|
||||
want.Set(i / HashSize)
|
||||
log.Trace("need data", "ref", fmt.Sprintf("%x", hash), "request", true)
|
||||
|
||||
// measure how long it takes before we mark chunks for retrieval, and actually send the request
|
||||
|
@ -32,6 +32,7 @@ var testCases []testCase
|
||||
// selects the peers for message forwarding, depending on the message address
|
||||
// and kademlia constellation.
|
||||
func TestForwardBasic(t *testing.T) {
|
||||
t.Skip("Flaky on macOS on local machines")
|
||||
baseAddrBytes := make([]byte, 32)
|
||||
for i := 0; i < len(baseAddrBytes); i++ {
|
||||
baseAddrBytes[i] = 0xFF
|
||||
|
416
simulation/docker.go
Normal file
416
simulation/docker.go
Normal file
@ -0,0 +1,416 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerP2PPort = 30399
|
||||
dockerWebsocketPort = 8546
|
||||
dockerHTTPPort = 8500
|
||||
dockerPProfPort = 6060
|
||||
)
|
||||
|
||||
// DockerAdapter is an adapter that can manage DockerNodes
|
||||
type DockerAdapter struct {
|
||||
client *client.Client
|
||||
image string
|
||||
config DockerAdapterConfig
|
||||
}
|
||||
|
||||
// DockerAdapterConfig is the configuration that can be provided when
|
||||
// initializing a DockerAdapter
|
||||
type DockerAdapterConfig struct {
|
||||
// BuildContext can be used to build a docker image
|
||||
// from a Dockerfile and a context directory
|
||||
BuildContext *DockerBuildContext `json:"build,omitempty"`
|
||||
// DockerImage points to an existing docker image
|
||||
// e.g. ethersphere/swarm:latest
|
||||
DockerImage string `json:"image,omitempty"`
|
||||
// DaemonAddr is the docker daemon address
|
||||
DaemonAddr string `json:"daemonAddr,omitempty"`
|
||||
}
|
||||
|
||||
// DockerBuildContext defines the build context to build
|
||||
// local docker images
|
||||
type DockerBuildContext struct {
|
||||
// Dockefile is the path to the dockerfile
|
||||
Dockerfile string `json:"dockerfile"`
|
||||
// Directory is the directory that will be used
|
||||
// in the context of a docker build
|
||||
Directory string `json:"directory"`
|
||||
// Tag is used to tag the image
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// DockerNode is a node that was started via the DockerAdapter
|
||||
type DockerNode struct {
|
||||
config NodeConfig
|
||||
adapter *DockerAdapter
|
||||
info NodeInfo
|
||||
ipAddr string
|
||||
}
|
||||
|
||||
// DefaultDockerAdapterConfig returns the default configuration
|
||||
// that uses the local docker daemon
|
||||
func DefaultDockerAdapterConfig() DockerAdapterConfig {
|
||||
return DockerAdapterConfig{
|
||||
DaemonAddr: client.DefaultDockerHost,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDockerBuildContext returns the default build context that uses a Dockerfile
|
||||
func DefaultDockerBuildContext() DockerBuildContext {
|
||||
return DockerBuildContext{
|
||||
Dockerfile: "Dockerfile",
|
||||
Directory: ".",
|
||||
}
|
||||
}
|
||||
|
||||
// IsDockerAvailable can be used to check the connectivity to the docker daemon
|
||||
func IsDockerAvailable(daemonAddr string) bool {
|
||||
cli, err := client.NewClientWithOpts(
|
||||
client.WithHost(daemonAddr),
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = cli.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
cli.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// NewDockerAdapter creates a DockerAdapter by receiving a DockerAdapterConfig
|
||||
func NewDockerAdapter(config DockerAdapterConfig) (*DockerAdapter, error) {
|
||||
if config.DockerImage != "" && config.BuildContext != nil {
|
||||
return nil, fmt.Errorf("only one can be defined: BuildContext (%v) or DockerImage(%s)",
|
||||
config.BuildContext, config.DockerImage)
|
||||
}
|
||||
|
||||
if config.DockerImage == "" && config.BuildContext == nil {
|
||||
return nil, errors.New("required: BuildContext or ExecutablePath")
|
||||
}
|
||||
|
||||
// Create docker client
|
||||
cli, err := client.NewClientWithOpts(
|
||||
client.WithHost(config.DaemonAddr),
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create the docker client: %v", err)
|
||||
}
|
||||
|
||||
// Figure out which docker image should be used
|
||||
image := config.DockerImage
|
||||
|
||||
// Build docker image
|
||||
if config.BuildContext != nil {
|
||||
var err error
|
||||
image, err = buildImage(*config.BuildContext, config.DaemonAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not build the docker image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Pull docker image
|
||||
if config.DockerImage != "" {
|
||||
reader, err := cli.ImagePull(context.Background(), config.DockerImage, types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pull image error: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(os.Stdout, reader); err != nil && err != io.EOF {
|
||||
log.Error("Error pulling docker image", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &DockerAdapter{
|
||||
image: image,
|
||||
client: cli,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewNode creates a new node
|
||||
func (a DockerAdapter) NewNode(config NodeConfig) Node {
|
||||
info := NodeInfo{
|
||||
ID: config.ID,
|
||||
}
|
||||
|
||||
node := &DockerNode{
|
||||
config: config,
|
||||
adapter: &a,
|
||||
info: info,
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the adapter
|
||||
func (a DockerAdapter) Snapshot() AdapterSnapshot {
|
||||
return AdapterSnapshot{
|
||||
Type: "docker",
|
||||
Config: a.config,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns the node status
|
||||
func (n *DockerNode) Info() NodeInfo {
|
||||
return n.info
|
||||
}
|
||||
|
||||
// Start starts the node
|
||||
func (n *DockerNode) Start() error {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Error("Stopping node due to errors", "err", err)
|
||||
if err := n.Stop(); err != nil {
|
||||
log.Error("Failed stopping node", "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Define arguments
|
||||
args := []string{}
|
||||
|
||||
// Append user defined arguments
|
||||
args = append(args, n.config.Args...)
|
||||
|
||||
// Append network ports arguments
|
||||
args = append(args, "--pprofport", strconv.Itoa(dockerPProfPort))
|
||||
args = append(args, "--bzzport", strconv.Itoa(dockerHTTPPort))
|
||||
args = append(args, "--ws")
|
||||
// TODO: Can we get the APIs from somewhere instead of hardcoding them here?
|
||||
args = append(args, "--wsapi", "admin,net,debug,bzz,accounting,hive")
|
||||
args = append(args, "--wsport", strconv.Itoa(dockerWebsocketPort))
|
||||
args = append(args, "--wsaddr", "0.0.0.0")
|
||||
args = append(args, "--wsorigins", "*")
|
||||
args = append(args, "--port", strconv.Itoa(dockerP2PPort))
|
||||
args = append(args, "--natif", "eth0")
|
||||
|
||||
// Start the node via a container
|
||||
ctx := context.Background()
|
||||
dockercli := n.adapter.client
|
||||
|
||||
resp, err := dockercli.ContainerCreate(ctx, &container.Config{
|
||||
Image: n.adapter.image,
|
||||
Cmd: args,
|
||||
Env: n.config.Env,
|
||||
}, &container.HostConfig{}, nil, n.containerName())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create container %s: %v", n.containerName(), err)
|
||||
}
|
||||
|
||||
if err := dockercli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to start container %s: %v", n.containerName(), err)
|
||||
}
|
||||
|
||||
// Get container logs
|
||||
if n.config.Stderr != nil {
|
||||
go func() {
|
||||
// Stderr
|
||||
stderr, err := dockercli.ContainerLogs(context.Background(), n.containerName(), types.ContainerLogsOptions{
|
||||
ShowStderr: true,
|
||||
ShowStdout: false,
|
||||
Follow: true,
|
||||
})
|
||||
if err != nil && err != io.EOF {
|
||||
log.Error("Error getting stderr container logs", "err", err)
|
||||
}
|
||||
defer stderr.Close()
|
||||
if _, err := io.Copy(n.config.Stderr, stderr); err != nil && err != io.EOF {
|
||||
log.Error("Error writing stderr container logs", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if n.config.Stdout != nil {
|
||||
go func() {
|
||||
// Stdout
|
||||
stdout, err := dockercli.ContainerLogs(context.Background(), n.containerName(), types.ContainerLogsOptions{
|
||||
ShowStderr: false,
|
||||
ShowStdout: true,
|
||||
Follow: true,
|
||||
})
|
||||
if err != nil && err != io.EOF {
|
||||
log.Error("Error getting stdout container logs", "err", err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
if _, err := io.Copy(n.config.Stdout, stdout); err != nil && err != io.EOF {
|
||||
log.Error("Error writing stdout container logs", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Get container info
|
||||
cinfo := types.ContainerJSON{}
|
||||
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
cinfo, err = dockercli.ContainerInspect(ctx, n.containerName())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get container info: %v", err)
|
||||
}
|
||||
|
||||
// Get the container IP addr
|
||||
n.ipAddr = cinfo.NetworkSettings.IPAddress
|
||||
|
||||
// Wait for the node to start
|
||||
client, err := n.rpcClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var swarminfo swarm.Info
|
||||
err = client.Call(&swarminfo, "bzz_info")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
var p2pinfo p2p.NodeInfo
|
||||
err = client.Call(&p2pinfo, "admin_nodeInfo")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
n.info = NodeInfo{
|
||||
ID: n.config.ID,
|
||||
Enode: strings.Replace(p2pinfo.Enode, "127.0.0.1", n.ipAddr, 1),
|
||||
BzzAddr: swarminfo.BzzKey,
|
||||
RPCListen: fmt.Sprintf("ws://%s:%d", n.ipAddr, dockerWebsocketPort),
|
||||
HTTPListen: fmt.Sprintf("http://%s:%d", n.ipAddr, dockerHTTPPort),
|
||||
PprofListen: fmt.Sprintf("http://%s:%d", n.ipAddr, dockerPProfPort),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the node
|
||||
func (n *DockerNode) Stop() error {
|
||||
cli := n.adapter.client
|
||||
|
||||
var stopTimeout = 30 * time.Second
|
||||
err := cli.ContainerStop(context.Background(), n.containerName(), &stopTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop container %s : %v", n.containerName(), err)
|
||||
}
|
||||
|
||||
err = cli.ContainerRemove(context.Background(), n.containerName(), types.ContainerRemoveOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove container %s : %v", n.containerName(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the node
|
||||
func (n *DockerNode) Snapshot() (NodeSnapshot, error) {
|
||||
snap := NodeSnapshot{
|
||||
Config: n.config,
|
||||
}
|
||||
adapterSnap := n.adapter.Snapshot()
|
||||
snap.Adapter = &adapterSnap
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func (n *DockerNode) containerName() string {
|
||||
return fmt.Sprintf("sim-docker-%s", n.config.ID)
|
||||
}
|
||||
|
||||
func (n *DockerNode) rpcClient() (*rpc.Client, error) {
|
||||
var client *rpc.Client
|
||||
var err error
|
||||
wsAddr := fmt.Sprintf("ws://%s:%d", n.ipAddr, dockerWebsocketPort)
|
||||
for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
client, err = rpc.Dial(wsAddr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// buildImage builds a docker image and returns the image identifier (tag).
|
||||
func buildImage(buildContext DockerBuildContext, deamonAddr string) (string, error) {
|
||||
// Connect to docker daemon
|
||||
c, err := client.NewClientWithOpts(
|
||||
client.WithHost(deamonAddr),
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create docker client: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
// Use directory for build context
|
||||
ctx, err := archive.TarWithOptions(buildContext.Directory, &archive.TarOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Default image tag
|
||||
imageTag := "sim-docker:latest"
|
||||
|
||||
// Use a tag if one is defined
|
||||
if buildContext.Tag != "" {
|
||||
imageTag = buildContext.Tag
|
||||
}
|
||||
|
||||
// Build image
|
||||
opts := types.ImageBuildOptions{
|
||||
SuppressOutput: false,
|
||||
PullParent: true,
|
||||
Tags: []string{imageTag},
|
||||
Dockerfile: buildContext.Dockerfile,
|
||||
}
|
||||
|
||||
buildResp, err := c.ImageBuild(context.Background(), ctx, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("build error: %v", err)
|
||||
}
|
||||
|
||||
// Parse build output
|
||||
d := json.NewDecoder(buildResp.Body)
|
||||
var event *jsonmessage.JSONMessage
|
||||
for {
|
||||
if err := d.Decode(&event); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
log.Info("Docker build", "msg", event.Stream)
|
||||
if event.Error != nil {
|
||||
log.Error("Docker build error", "err", event.Error.Message)
|
||||
return "", fmt.Errorf("failed to build docker image: %v", event.Error)
|
||||
}
|
||||
}
|
||||
return imageTag, nil
|
||||
}
|
53
simulation/docker_test.go
Normal file
53
simulation/docker_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func TestDockerAdapterBuild(t *testing.T) {
|
||||
if !IsDockerAvailable(client.DefaultDockerHost) {
|
||||
t.Skip("could not connect to the docker daemon")
|
||||
}
|
||||
|
||||
// Create a docker client
|
||||
c, err := dockerClient()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create docker client: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
imageTag := "test-docker-adapter-build:latest"
|
||||
|
||||
config := DefaultDockerAdapterConfig()
|
||||
|
||||
// Build based on a Dockerfile
|
||||
config.BuildContext = &DockerBuildContext{
|
||||
Directory: "../",
|
||||
Dockerfile: "Dockerfile",
|
||||
Tag: imageTag,
|
||||
}
|
||||
|
||||
// Create docker adapter: This will build the image
|
||||
_, err = NewDockerAdapter(config)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create docker adapter: %v", err)
|
||||
}
|
||||
|
||||
// Cleanup image
|
||||
_, err = c.ImageRemove(context.Background(), imageTag, types.ImageRemoveOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("could not delete docker image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create docker client
|
||||
func dockerClient() (*client.Client, error) {
|
||||
return client.NewClientWithOpts(
|
||||
client.WithHost(client.DefaultDockerHost),
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
}
|
1
simulation/examples/cluster/cluster.go
Normal file
1
simulation/examples/cluster/cluster.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
132
simulation/examples/cluster/cluster_test.go
Normal file
132
simulation/examples/cluster/cluster_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethersphere/swarm/simulation"
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
var (
|
||||
nodes = flag.Int("nodes", 20, "number of nodes to create")
|
||||
loglevel = flag.Int("loglevel", 3, "verbosity of logs")
|
||||
rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog))))
|
||||
}
|
||||
|
||||
func TestCluster(t *testing.T) {
|
||||
nodeCount := *nodes
|
||||
|
||||
// Test exec adapter
|
||||
t.Run("exec", func(t *testing.T) {
|
||||
execPath := "../../../build/bin/swarm"
|
||||
|
||||
if _, err := os.Stat(execPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Skip("swarm binary not found. build it before running the test")
|
||||
}
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "test-sim-exec")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
adapter, err := simulation.NewExecAdapter(simulation.ExecAdapterConfig{
|
||||
ExecutablePath: execPath,
|
||||
BaseDataDirectory: tmpdir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("could not create exec adapter: %v", err)
|
||||
}
|
||||
startSimulation(t, adapter, nodeCount)
|
||||
})
|
||||
|
||||
// Test docker adapter
|
||||
t.Run("docker", func(t *testing.T) {
|
||||
config := simulation.DefaultDockerAdapterConfig()
|
||||
if !simulation.IsDockerAvailable(config.DaemonAddr) {
|
||||
t.Skip("docker is not available, skipping test")
|
||||
}
|
||||
config.DockerImage = "ethersphere/swarm:edge"
|
||||
adapter, err := simulation.NewDockerAdapter(config)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create docker adapter: %v", err)
|
||||
}
|
||||
startSimulation(t, adapter, nodeCount)
|
||||
})
|
||||
|
||||
// Test kubernetes adapter
|
||||
t.Run("kubernetes", func(t *testing.T) {
|
||||
config := simulation.DefaultKubernetesAdapterConfig()
|
||||
if !simulation.IsKubernetesAvailable(config.KubeConfigPath) {
|
||||
t.Skip("kubernetes is not available, skipping test")
|
||||
}
|
||||
config.Namespace = "simulation-test"
|
||||
config.DockerImage = "ethersphere/swarm:edge"
|
||||
adapter, err := simulation.NewKubernetesAdapter(config)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create kubernetes adapter: %v", err)
|
||||
}
|
||||
startSimulation(t, adapter, nodeCount)
|
||||
})
|
||||
}
|
||||
|
||||
func startSimulation(t *testing.T, adapter simulation.Adapter, count int) {
|
||||
sim := simulation.NewSimulation(adapter)
|
||||
|
||||
defer sim.StopAll()
|
||||
|
||||
// Common args used by all nodes
|
||||
commonArgs := []string{
|
||||
"--bzznetworkid", "599",
|
||||
}
|
||||
|
||||
// Start a cluster with 'count' nodes and a bootnode
|
||||
nodes, err := sim.CreateClusterWithBootnode("test", count, commonArgs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for all nodes to be considered healthy
|
||||
err = sim.WaitForHealthyNetwork()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get healthy network: %v", err)
|
||||
}
|
||||
|
||||
// Check hive output on the first node
|
||||
client, err := sim.RPCClient(nodes[0].Info().ID)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get rpc client: %v", err)
|
||||
}
|
||||
|
||||
var hive string
|
||||
err = client.Call(&hive, "bzz_hive")
|
||||
if err != nil {
|
||||
t.Errorf("could not get hive info: %v", err)
|
||||
}
|
||||
|
||||
snap, err := sim.Snapshot()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(snap)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
|
||||
fmt.Println(hive)
|
||||
}
|
641
simulation/examples/snapshot/docker.json
Normal file
641
simulation/examples/snapshot/docker.json
Normal file
@ -0,0 +1,641 @@
|
||||
{
|
||||
"defaultAdapter": {
|
||||
"type": "docker",
|
||||
"config": {
|
||||
"image": "ethersphere/swarm:edge",
|
||||
"daemonAddr": "unix:///var/run/docker.sock"
|
||||
}
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"config": {
|
||||
"id": "test-1",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"675a1c7dbaa5ea1bbddd46ee1a06e55929175760c5824bb5a6992d0b96f84e9d",
|
||||
"--nodekeyhex",
|
||||
"1de361ad2978679213635f7f11e35dbd55a34979c931734ff9c3760cb5a3e6ad",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-11",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"9c7f8bfdcfcc129baf677585703e6061189f9076e354d27f4b7f663e37fdc92b",
|
||||
"--nodekeyhex",
|
||||
"a8f2e98633c85a0b3a89facafbd35cb5d10c556bdf142daab63fb554a53715ec",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-2",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"f60e0733d0cf9ae1e77bccd3256e709d9e552848d5ae19b1536beeac7157c514",
|
||||
"--nodekeyhex",
|
||||
"51135dc28378604cb7d7113f22114ac68f0dc235332649ac688fb1d13b622e3f",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-15",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"5dfd72d5b92dabde0e58a4e6289be7c1e738c40dac8fafd151e56974f739a8f9",
|
||||
"--nodekeyhex",
|
||||
"70893f8a2c3fe1a24dcfb202a81e33dce03c3ded913d8fda92e6eb51c737e8de",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-10",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"bbd17f109585e6df0a92953985059b791a7be2d01a973743a5e0100b7ab06715",
|
||||
"--nodekeyhex",
|
||||
"8592bf30d3a88dff49bdf57d583e0b235475151a958e6bf285520767ab7e7104",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-18",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"2315b8bc42c2ba56ed4f88ec2a60a4b02bf3f8e94f2faac8dd4d642502c2056a",
|
||||
"--nodekeyhex",
|
||||
"975466b3530c2bf5940219d43525eadf5385df9f2defc1fa16b10d141f272b49",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-7",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"494bceb4a9e564b1da2f00f5b6ff872dbfcfcbee4655e704e85ebfdd86bf29e5",
|
||||
"--nodekeyhex",
|
||||
"c677fdca73661ef5dc3b7773bbc52f7444b5ac9913230b7084a84f1a5f10b2cc",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-12",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"dde10d9d208b0f70951e96941db0d9b245175f43ab5485cf0c8811d12f9b9cdb",
|
||||
"--nodekeyhex",
|
||||
"747ed88c92cdd8bf77aa0b09528038f457657ffb97e460895a7284b796da9dd4",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-9",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"10b0fff107a590a424e5e74abdd119406ed096b7e600cbe04ff67584f9d55c51",
|
||||
"--nodekeyhex",
|
||||
"7ce5b908dfe70f5103948c413debc0fa03f50a6426b51da158438922abf3ea10",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-4",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"3b0f49376815b1952f3c73fe6fd01a93e48d4bfac5f871c4712e092181549d91",
|
||||
"--nodekeyhex",
|
||||
"7bfe1dd5c1bbe83d9e29828e58e8cf9a431584a7d34c7bfb795a027a4adced8a",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-16",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"46ed3a44c6fa46d4a6368b3d915134b185986ac7627a70b970f787c8896d6c6a",
|
||||
"--nodekeyhex",
|
||||
"e48066d163683429380f8b48fea282e25a184c9a50a5dbf24d85186469ca795c",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-19",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"9314b3cc4d2cfa01fd044df334ec840f999f0ebb11f7a8a184786cef17703fc2",
|
||||
"--nodekeyhex",
|
||||
"4775f2c8a3cec55208c14d0a9b7fcdaf2447513142e69e03a3e8e2a759de0b55",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-14",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"c54019ce7e3692db5f7d3531c6c00c96b9b744acf520ebd124194a5789c732d9",
|
||||
"--nodekeyhex",
|
||||
"94b5c4e9fd4496bdc3f1bd673cca9cbb2f4afe354a8d6ebb50d198798f0f4a45",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-13",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"c7f8d404fee2b04242534a3486b1cf031ce6af150a075ba943d50bf1968dd85a",
|
||||
"--nodekeyhex",
|
||||
"24ae9cdc2ceea666fc588586b0e062721744562495e064fccb0ada19481f133d",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-17",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"d48f7cb8ac5be805e700f1575cb644ea5cf05413dbdd9f969c43d1d54a9df29e",
|
||||
"--nodekeyhex",
|
||||
"8f85b38e6c6034d7c3c23d1391aaeae3eb3d680803b199cb716388de5fde3cf5",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-8",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"ac7c28909be33eab4fccf5f39e808dd0a96ae412a3e7fe52f040ebb4d4d4d6f0",
|
||||
"--nodekeyhex",
|
||||
"bbef6fca5bc85f91d6315626fab3fdecdad8abfa3f38b216f98ee92489625a7c",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-5",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"1a16fab4e3cbbc217db7dcb1bddb0ef12dd28c48c8a50bd4bb5550beaa80c7a0",
|
||||
"--nodekeyhex",
|
||||
"19234026cedfdfb10cf682f6491ff4c876411804949ac1b0dd5f1faee66f0bf3",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-3",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"6410c2eadadac52be6a261a0e72060e5488f9165b24cc4c1764ad7a5ef6c0532",
|
||||
"--nodekeyhex",
|
||||
"6a3997bc8a3884797f18c8e4e345470db695d827a923975e5ce4dfb3c5d09d1e",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-0",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"118ecf1dd2ebd700975af6de7dff806a3fcd41bc82987db96c45264694ec4276",
|
||||
"--nodekeyhex",
|
||||
"3422448fa8b3e6e41ea51a84262917d38d54e2c4345ec95d0586d741d31f91b5",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "test-6",
|
||||
"args": [
|
||||
"--bzzkeyhex",
|
||||
"66fa75cc080f0b5115c165004310378e98bb90cd652d4f9d92c71c489995942f",
|
||||
"--nodekeyhex",
|
||||
"e1a6f1bb9ed90cfa534dc8abb5088afa010acd7585837b3a8ea19c31c38da068",
|
||||
"--bzznetworkid",
|
||||
"599"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
|
||||
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3",
|
||||
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
},
|
||||
{
|
||||
"from": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
},
|
||||
{
|
||||
"from": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
|
||||
},
|
||||
{
|
||||
"from": "enode://7f3385e72e489ff1b8f17f65af808348708d80df56d8a797fe9c8369128ecac6c8b2a58537f5d8987ea57a2779b3038b2a2fa3428eda55d5e0d6f4fc2f5c717f",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
|
||||
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
|
||||
},
|
||||
{
|
||||
"from": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
|
||||
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
|
||||
},
|
||||
{
|
||||
"from": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
},
|
||||
{
|
||||
"from": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
|
||||
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
|
||||
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
|
||||
},
|
||||
{
|
||||
"from": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
|
||||
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
|
||||
},
|
||||
{
|
||||
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
|
||||
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa",
|
||||
"to": "enode://6419bc38b529be4c7ce7065783eeac741f20fbe8138f6cb0a12b2928e64cefda8c7c1ab6657e59a2c7d68ab9170e25e7e83a34d4fbd5231727c7f5bde800fc54"
|
||||
},
|
||||
{
|
||||
"from": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://13b19a04feca4ebb50813019ef145d4622c650ae2cdc2cba0b00758b2f4bc2d6629a646d3a49eddae17b8e818fe41c558f1746bb1b713c3acad880c537c87ca2",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://d8cc415f199cba179af255ac6effc72afec5aee0c7754ae0a00ee8f91a6be4affc3aa75a75bba4ab75fb07aa7e2b50eeb0ca785c7066d1d23fd772deeefbd3aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://41a6567457b90fb8e9856575deaaaa90c344dda1897275bff680d56018d4dcdc8330a85aecc9a5d4f69f9b145f047e0d1fcda4d78647534b32167e983129e091"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
},
|
||||
{
|
||||
"from": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://f5c8f1ab0be7d77364cf7da43297abcc19cb8fbf66458ab78236235b362093c34d68bd0cf22c9a900a1b15398515edbb42663dafdaee8d69d91d40e8e58f69c8"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
},
|
||||
{
|
||||
"from": "enode://cfa1547aeb9d5ab143f138d945b3f72878ec204b7cc90f163fb3dce4201ad837478770c615785e597390eb8dbd26536f5b42b2e7620f1a6a9974bc894fb09d6d",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
|
||||
"to": "enode://e76225bd1dc9ee34b2e551f82646f7a6f2f74bbd8a5f5bb048b9057e34696813b2ceddd3c75069b50c506c15bbc9b7153410e73e0637390a6550c29dea3995aa"
|
||||
},
|
||||
{
|
||||
"from": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
|
||||
"to": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a"
|
||||
},
|
||||
{
|
||||
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
|
||||
"to": "enode://9947aaf751f83384eada5a358c679a322e26c79051097eea44164c9d5d6da2c9984b61d865a777b250fabcb1cf31a835045cf132fadb43e067641914c2991265"
|
||||
},
|
||||
{
|
||||
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
|
||||
"to": "enode://f17f5ce80833211c6a93762f2ee55bf97115dd3f7b743b2ab7e9c3d7962defca70f3b5e4a8b96fa46d5a33f4319140cf2ff0ab85939a62d4cbf1b69c65d53946"
|
||||
},
|
||||
{
|
||||
"from": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a",
|
||||
"to": "enode://2db289c17757f863d2d833a4d6641cd1a096dd0d392e356e3c4b6fed73382f9451b1fb23ff25f8c99e76bdb587428358817ab4969d358b24a126fa59c8bd98af"
|
||||
},
|
||||
{
|
||||
"from": "enode://c9902d1a77c0d51adf2b94c6d14f7dd10ad732b18986a221464563758b9b9a04fe159b75cdbf777196b5f90e75f0f04513e018a2f7622e4fcadd45a6b9e2cc9a",
|
||||
"to": "enode://6833ef592ba6ac03d9797fd757467909db84ce832f749c060b29a4d4e0b82cd6865e4bc340cd73462a39677142656e7999eaff7aecc4746703d3350b50c15ebd"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://7eb662ba026270024a625d9df71b5a466f61a303dff47ae61cccc3d18550ecc2731f2ea2d768f121da4e7cd7fc4f9ad30ab278e981a2e9c2a0d7d6ee0430cfe2"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://b270650132ec89eda0e0addac435d1074007818291cc25510d332752f81901d161777e80e138f5b25028660e9af05983f77bc240aa7628a4cf487131910c64b3"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://5b94644c474fef835f68bca9460b79daa0d44101b581f159ca4de594f4e4d4b7f701f97fb5713267bd89b22df351ead20a1587dd604d5c6f1c3ebddf3f7f48c4"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://520b57f167660f2b4004866b848fe1015d8b5d7d98dff0207a7e4925478c937eaa1d0e07994be75d5023d18f6fc7633562d0f69b320835726d3c24645140b715"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://cd29456310d3526660cf3895bceeac648dc02ed5496f4c0e2f7332c4faf1a006d1b89fcf1d3761b3d343dc8cde605b598ddab8c1b7f003ad44d05b2f90c680e2"
|
||||
},
|
||||
{
|
||||
"from": "enode://9b246088fd8224ce44a106068a6aa4ca1e17433443babe41905e3d07d89df4685359de20c6115ff253e3a38d0ed0954c50afe60cdf970930d05037313db17afd",
|
||||
"to": "enode://7c9adbfec6f76c7efb599db2300aded9a35efc616f3f4aeb1a20dc54eb4d238ed48c3b79273eff264578bd2a1fc583afd8254841c246c55dfe7fe851986fa77d"
|
||||
}
|
||||
]
|
||||
}
|
55
simulation/examples/snapshot/docker_test.go
Normal file
55
simulation/examples/snapshot/docker_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethersphere/swarm/simulation"
|
||||
)
|
||||
|
||||
func TestDockerSnapshotFromFile(t *testing.T) {
|
||||
snap, err := simulation.LoadSnapshotFromFile("docker.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !simulation.IsDockerAvailable(snap.DefaultAdapter.Config.(simulation.DockerAdapterConfig).DaemonAddr) {
|
||||
t.Skip("docker is not available, skipping test")
|
||||
}
|
||||
|
||||
sim, err := simulation.NewSimulationFromSnapshot(snap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = sim.StopAll()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
nodes := sim.GetAll()
|
||||
if len(nodes) != len(snap.Nodes) {
|
||||
t.Fatalf("Got %d . Expected %d nodes", len(nodes), len(snap.Nodes))
|
||||
}
|
||||
|
||||
// Check hive output on the first node
|
||||
node, err := sim.Get(simulation.NodeID("test-0"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
client, err := sim.RPCClient(node.Info().ID)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get rpc client: %v", err)
|
||||
}
|
||||
|
||||
var hive string
|
||||
err = client.Call(&hive, "bzz_hive")
|
||||
if err != nil {
|
||||
t.Errorf("could not get hive info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println(hive)
|
||||
}
|
1
simulation/examples/snapshot/snapshot.go
Normal file
1
simulation/examples/snapshot/snapshot.go
Normal file
@ -0,0 +1 @@
|
||||
package snapshot
|
228
simulation/exec.go
Normal file
228
simulation/exec.go
Normal file
@ -0,0 +1,228 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
)
|
||||
|
||||
// ExecAdapter can manage local exec nodes
|
||||
type ExecAdapter struct {
|
||||
config ExecAdapterConfig
|
||||
}
|
||||
|
||||
// ExecAdapterConfig is used to configure an ExecAdapter
|
||||
type ExecAdapterConfig struct {
|
||||
// Path to the executable
|
||||
ExecutablePath string `json:"executable"`
|
||||
// BaseDataDirectory stores all the nodes' data directories
|
||||
BaseDataDirectory string `json:"basedir"`
|
||||
}
|
||||
|
||||
// ExecNode is a node that is executed locally
|
||||
type ExecNode struct {
|
||||
adapter *ExecAdapter
|
||||
config NodeConfig
|
||||
cmd *exec.Cmd
|
||||
info NodeInfo
|
||||
}
|
||||
|
||||
// NewExecAdapter creates an ExecAdapter by receiving a ExecAdapterConfig
|
||||
func NewExecAdapter(config ExecAdapterConfig) (*ExecAdapter, error) {
|
||||
if _, err := os.Stat(config.BaseDataDirectory); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("'%s' directory does not exist", config.BaseDataDirectory)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(config.ExecutablePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("'%s' executable does not exist", config.ExecutablePath)
|
||||
}
|
||||
|
||||
absExec, err := filepath.Abs(config.ExecutablePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.ExecutablePath, err)
|
||||
}
|
||||
config.ExecutablePath = absExec
|
||||
|
||||
absDir, err := filepath.Abs(config.BaseDataDirectory)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.BaseDataDirectory, err)
|
||||
}
|
||||
config.BaseDataDirectory = absDir
|
||||
|
||||
a := &ExecAdapter{
|
||||
config: config,
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// NewNode creates a new node
|
||||
func (a ExecAdapter) NewNode(config NodeConfig) Node {
|
||||
info := NodeInfo{
|
||||
ID: config.ID,
|
||||
}
|
||||
node := &ExecNode{
|
||||
config: config,
|
||||
adapter: &a,
|
||||
info: info,
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the adapter
|
||||
func (a ExecAdapter) Snapshot() AdapterSnapshot {
|
||||
return AdapterSnapshot{
|
||||
Type: "exec",
|
||||
Config: a.config,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns the node info
|
||||
func (n *ExecNode) Info() NodeInfo {
|
||||
return n.info
|
||||
}
|
||||
|
||||
// Start starts the node
|
||||
func (n *ExecNode) Start() error {
|
||||
// Check if command already exists
|
||||
if n.cmd != nil {
|
||||
return fmt.Errorf("node %s is already running", n.config.ID)
|
||||
}
|
||||
|
||||
// Create command line arguments
|
||||
args := []string{filepath.Base(n.adapter.config.ExecutablePath)}
|
||||
|
||||
// Create data directory for this node
|
||||
dir := n.dataDir()
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create node directory: %s", err)
|
||||
}
|
||||
|
||||
// Configure data directory
|
||||
args = append(args, "--datadir", dir)
|
||||
|
||||
// Configure IPC path
|
||||
args = append(args, "--ipcpath", n.ipcPath())
|
||||
|
||||
// Automatically allocate ports
|
||||
args = append(args, "--pprofport", "0")
|
||||
args = append(args, "--bzzport", "0")
|
||||
args = append(args, "--wsport", "0")
|
||||
args = append(args, "--port", "0")
|
||||
|
||||
// Append user defined arguments
|
||||
args = append(args, n.config.Args...)
|
||||
|
||||
// Start command
|
||||
n.cmd = &exec.Cmd{
|
||||
Path: n.adapter.config.ExecutablePath,
|
||||
Args: args,
|
||||
Dir: dir,
|
||||
Env: n.config.Env,
|
||||
Stdout: n.config.Stdout,
|
||||
Stderr: n.config.Stderr,
|
||||
}
|
||||
|
||||
if err := n.cmd.Start(); err != nil {
|
||||
n.cmd = nil
|
||||
return fmt.Errorf("error starting node %s: %s", n.config.ID, err)
|
||||
}
|
||||
|
||||
// Wait for the node to start
|
||||
var client *rpc.Client
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
n.Stop()
|
||||
}
|
||||
}()
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
client, err = rpc.Dial(n.ipcPath())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
return fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var swarminfo swarm.Info
|
||||
err = client.Call(&swarminfo, "bzz_info")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
var p2pinfo p2p.NodeInfo
|
||||
err = client.Call(&p2pinfo, "admin_nodeInfo")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
n.info = NodeInfo{
|
||||
ID: n.config.ID,
|
||||
Enode: p2pinfo.Enode,
|
||||
BzzAddr: swarminfo.BzzKey,
|
||||
RPCListen: n.ipcPath(),
|
||||
HTTPListen: fmt.Sprintf("http://localhost:%s", swarminfo.Port),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the node
|
||||
func (n *ExecNode) Stop() error {
|
||||
if n.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
n.cmd = nil
|
||||
}()
|
||||
// Try to gracefully terminate the process
|
||||
if err := n.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||
return n.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// Wait for the process to terminate or timeout
|
||||
waitErr := make(chan error)
|
||||
go func() {
|
||||
waitErr <- n.cmd.Wait()
|
||||
}()
|
||||
select {
|
||||
case err := <-waitErr:
|
||||
return err
|
||||
case <-time.After(20 * time.Second):
|
||||
return n.cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the node
|
||||
func (n *ExecNode) Snapshot() (NodeSnapshot, error) {
|
||||
snap := NodeSnapshot{
|
||||
Config: n.config,
|
||||
}
|
||||
adapterSnap := n.adapter.Snapshot()
|
||||
snap.Adapter = &adapterSnap
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// ipcPath returns the path to the ipc socket
|
||||
func (n *ExecNode) ipcPath() string {
|
||||
ipcfile := "bzzd.ipc"
|
||||
// On windows we can have to use pipes
|
||||
if runtime.GOOS == "windows" {
|
||||
return `\\.\pipe\` + ipcfile
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", n.dataDir(), ipcfile)
|
||||
}
|
||||
|
||||
// dataDir returns the path to the data directory that the node should use
|
||||
func (n *ExecNode) dataDir() string {
|
||||
return filepath.Join(n.adapter.config.BaseDataDirectory, string(n.config.ID))
|
||||
}
|
94
simulation/exec_test.go
Normal file
94
simulation/exec_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func TestExecAdapter(t *testing.T) {
|
||||
|
||||
execPath := "../build/bin/swarm"
|
||||
|
||||
// Skip test if binary doesn't exist
|
||||
if _, err := os.Stat(execPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Skip("swarm binary not found. build it before running the test")
|
||||
}
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "test-adapter-exec")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
adapter, err := NewExecAdapter(ExecAdapterConfig{
|
||||
ExecutablePath: execPath,
|
||||
BaseDataDirectory: tmpdir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("could not create exec adapter: %v", err)
|
||||
}
|
||||
|
||||
bzzkey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate key: %v", err)
|
||||
}
|
||||
bzzkeyhex := hex.EncodeToString(crypto.FromECDSA(bzzkey))
|
||||
|
||||
nodekey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate key: %v", err)
|
||||
}
|
||||
nodekeyhex := hex.EncodeToString(crypto.FromECDSA(nodekey))
|
||||
|
||||
args := []string{
|
||||
"--bootnodes", "",
|
||||
"--bzzkeyhex", bzzkeyhex,
|
||||
"--nodekeyhex", nodekeyhex,
|
||||
"--bzznetworkid", "499",
|
||||
}
|
||||
nodeconfig := NodeConfig{
|
||||
ID: "node1",
|
||||
Args: args,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
node := adapter.NewNode(nodeconfig)
|
||||
info := node.Info()
|
||||
if info.ID != "node1" {
|
||||
t.Fatal("node id is different")
|
||||
}
|
||||
|
||||
err = node.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("node did not start: %v", err)
|
||||
}
|
||||
|
||||
infoA := node.Info()
|
||||
|
||||
err = node.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("node didn't stop: %v", err)
|
||||
}
|
||||
|
||||
err = node.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("node didn't start again: %v", err)
|
||||
}
|
||||
|
||||
infoB := node.Info()
|
||||
|
||||
if infoA.BzzAddr != infoB.BzzAddr {
|
||||
t.Errorf("bzzaddr should be the same: %s - %s", infoA.Enode, infoB.Enode)
|
||||
}
|
||||
|
||||
err = node.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("node didn't stop: %v", err)
|
||||
}
|
||||
}
|
467
simulation/kubernetes.go
Normal file
467
simulation/kubernetes.go
Normal file
@ -0,0 +1,467 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// KubernetesAdapter can manage nodes on a kubernetes cluster
|
||||
type KubernetesAdapter struct {
|
||||
client *kubernetes.Clientset
|
||||
config KubernetesAdapterConfig
|
||||
image string
|
||||
proxy string
|
||||
}
|
||||
|
||||
// KubernetesAdapterConfig is the configuration provided to a KubernetesAdapter
|
||||
type KubernetesAdapterConfig struct {
|
||||
// KubeConfigPath is the path to your kubernetes configuration path
|
||||
KubeConfigPath string `json:"kubeConfigPath"`
|
||||
// Namespace is the kubernetes namespaces where the pods should be running
|
||||
Namespace string `json:"namespace"`
|
||||
// BuildContext can be used to build a docker image
|
||||
// from a Dockerfile and a context directory
|
||||
BuildContext *KubernetesBuildContext `json:"build,omitempty"`
|
||||
// DockerImage points to an existing docker image
|
||||
// e.g. ethersphere/swarm:latest
|
||||
DockerImage string `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesBuildContext defines the build context to build
|
||||
// local docker images
|
||||
type KubernetesBuildContext struct {
|
||||
// Dockefile is the path to the dockerfile
|
||||
Dockerfile string `json:"dockerfile"`
|
||||
// Directory is the directory that will be used
|
||||
// in the context of a docker build
|
||||
Directory string `json:"dir"`
|
||||
// Tag is used to tag the image
|
||||
Tag string `json:"tag"`
|
||||
// Registry is the image registry where the image will be pushed to
|
||||
Registry string `json:"registry"`
|
||||
// Username is the user used to push the image to the registry
|
||||
Username string `json:"username"`
|
||||
// Password is the password of the user that is used to push the image
|
||||
// to the registry
|
||||
Password string `json:"-"`
|
||||
}
|
||||
|
||||
// ImageTag is the full image tag, including the registry
|
||||
func (bc *KubernetesBuildContext) ImageTag() string {
|
||||
return fmt.Sprintf("%s/%s", bc.Registry, bc.Tag)
|
||||
}
|
||||
|
||||
// DefaultKubernetesAdapterConfig uses the default ~/.kube/config
|
||||
// to discover the kubernetes clusters. It also uses the "default" namespace.
|
||||
func DefaultKubernetesAdapterConfig() KubernetesAdapterConfig {
|
||||
kubeconfig := filepath.Join(homeDir(), ".kube", "config")
|
||||
return KubernetesAdapterConfig{
|
||||
KubeConfigPath: kubeconfig,
|
||||
Namespace: "default",
|
||||
}
|
||||
}
|
||||
|
||||
// IsKubernetesAvailable checks if a kubernetes configuration file exists
|
||||
func IsKubernetesAvailable(kubeConfigPath string) bool {
|
||||
k8scfg, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = kubernetes.NewForConfig(k8scfg)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// NewKubernetesAdapter creates a KubernetesAdpater by receiving a KubernetesAdapterConfig
|
||||
func NewKubernetesAdapter(config KubernetesAdapterConfig) (*KubernetesAdapter, error) {
|
||||
if config.DockerImage != "" && config.BuildContext != nil {
|
||||
return nil, fmt.Errorf("only one can be defined: BuildContext (%v) or DockerImage(%s)",
|
||||
config.BuildContext, config.DockerImage)
|
||||
}
|
||||
|
||||
if config.DockerImage == "" && config.BuildContext == nil {
|
||||
return nil, errors.New("required: Dockerfile or DockerImage")
|
||||
}
|
||||
|
||||
// Define k8s client configuration
|
||||
k8scfg, err := clientcmd.BuildConfigFromFlags("", config.KubeConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start k8s client from config: %v", err)
|
||||
|
||||
}
|
||||
|
||||
// Create the clientset
|
||||
clientset, err := kubernetes.NewForConfig(k8scfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create clientset: %v", err)
|
||||
}
|
||||
|
||||
// Figure out which docker image should be used
|
||||
image := config.DockerImage
|
||||
|
||||
// Build and push container image
|
||||
if config.BuildContext != nil {
|
||||
var err error
|
||||
// Build image
|
||||
image, err = buildImage(DockerBuildContext{
|
||||
Dockerfile: config.BuildContext.Dockerfile,
|
||||
Directory: config.BuildContext.Directory,
|
||||
Tag: config.BuildContext.ImageTag(),
|
||||
}, DefaultDockerAdapterConfig().DaemonAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not build the docker image: %v", err)
|
||||
}
|
||||
|
||||
// Push image
|
||||
dockerClient, err := client.NewClientWithOpts(
|
||||
client.WithHost(client.DefaultDockerHost),
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create the docker client: %v", err)
|
||||
}
|
||||
|
||||
authConfig := types.AuthConfig{
|
||||
Username: config.BuildContext.Username,
|
||||
Password: config.BuildContext.Password,
|
||||
}
|
||||
encodedJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed marshaling the authentication parameters")
|
||||
}
|
||||
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
|
||||
out, err := dockerClient.ImagePush(
|
||||
context.Background(),
|
||||
config.BuildContext.ImageTag(),
|
||||
types.ImagePushOptions{
|
||||
RegistryAuth: authStr,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to push image: %v", err)
|
||||
}
|
||||
defer out.Close()
|
||||
if _, err := io.Copy(os.Stdout, out); err != nil && err != io.EOF {
|
||||
log.Error("Error pushing docker image", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup proxy to access pods
|
||||
server, err := newProxyServer(k8scfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create proxy: %v", err)
|
||||
}
|
||||
|
||||
l, err := server.Listen("127.0.0.1", 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start proxy: %v", err)
|
||||
}
|
||||
go func() {
|
||||
if err := server.ServeOnListener(l); err != nil {
|
||||
log.Error("Kubernetes dapater proxy failed:", "err", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// Return adapter
|
||||
return &KubernetesAdapter{
|
||||
client: clientset,
|
||||
image: image,
|
||||
config: config,
|
||||
proxy: l.Addr().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewNode creates a new node
|
||||
func (a KubernetesAdapter) NewNode(config NodeConfig) Node {
|
||||
info := NodeInfo{
|
||||
ID: config.ID,
|
||||
}
|
||||
node := &KubernetesNode{
|
||||
config: config,
|
||||
adapter: &a,
|
||||
info: info,
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the Adapter
|
||||
func (a KubernetesAdapter) Snapshot() AdapterSnapshot {
|
||||
return AdapterSnapshot{
|
||||
Type: "kubernetes",
|
||||
Config: a.config,
|
||||
}
|
||||
}
|
||||
|
||||
// KubernetesNode is a node that was started via the KubernetesAdapter
|
||||
type KubernetesNode struct {
|
||||
config NodeConfig
|
||||
adapter *KubernetesAdapter
|
||||
info NodeInfo
|
||||
}
|
||||
|
||||
// Info returns the node info
|
||||
func (n *KubernetesNode) Info() NodeInfo {
|
||||
return n.info
|
||||
}
|
||||
|
||||
// Start starts the node
|
||||
func (n *KubernetesNode) Start() error {
|
||||
// Define arguments
|
||||
args := []string{}
|
||||
|
||||
// Append user defined arguments
|
||||
args = append(args, n.config.Args...)
|
||||
|
||||
// Append network ports arguments
|
||||
args = append(args, "--pprofport", strconv.Itoa(dockerPProfPort))
|
||||
args = append(args, "--bzzport", strconv.Itoa(dockerHTTPPort))
|
||||
args = append(args, "--ws")
|
||||
// TODO: Can we get the APIs from somewhere instead of hardcoding them here?
|
||||
args = append(args, "--wsapi", "admin,net,debug,bzz,accounting,hive")
|
||||
args = append(args, "--wsport", strconv.Itoa(dockerWebsocketPort))
|
||||
args = append(args, "--wsaddr", "0.0.0.0")
|
||||
args = append(args, "--wsorigins", "*")
|
||||
args = append(args, "--port", strconv.Itoa(dockerP2PPort))
|
||||
args = append(args, "--nat", "ip:$(POD_IP)")
|
||||
|
||||
// Build environment variables
|
||||
env := []v1.EnvVar{
|
||||
{
|
||||
// POD_IP is useful for setting the NAT config: e.g. `--nat ip:$POD_IP`
|
||||
Name: "POD_IP",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
FieldPath: "status.podIP",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, e := range n.config.Env {
|
||||
var name, value string
|
||||
s := strings.SplitN(e, "=", 1)
|
||||
name = s[0]
|
||||
if len(s) > 1 {
|
||||
value = s[1]
|
||||
}
|
||||
env = append(env, v1.EnvVar{
|
||||
Name: name,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
adapter := n.adapter
|
||||
|
||||
// Create Kubernetes Pod
|
||||
podRequest := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: n.podName(),
|
||||
Labels: map[string]string{
|
||||
"app": "simulation",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: n.podName(),
|
||||
Image: adapter.image,
|
||||
Args: args,
|
||||
Env: env,
|
||||
Resources: v1.ResourceRequirements{
|
||||
Limits: v1.ResourceList{
|
||||
v1.ResourceMemory: resource.MustParse("400Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
pod, err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Create(podRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pod: %v", err)
|
||||
}
|
||||
|
||||
// Wait for pod
|
||||
start := time.Now()
|
||||
for {
|
||||
log.Debug("Waiting for pod", "pod", n.podName())
|
||||
pod, err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Get(n.podName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if pod.Status.Phase == v1.PodRunning {
|
||||
break
|
||||
}
|
||||
if time.Since(start) > 5*time.Minute {
|
||||
return errors.New("timeout waiting for pod")
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Get logs
|
||||
logOpts := &v1.PodLogOptions{
|
||||
Container: n.podName(),
|
||||
Follow: true,
|
||||
Previous: false,
|
||||
}
|
||||
req := adapter.client.CoreV1().Pods(adapter.config.Namespace).GetLogs(n.podName(), logOpts)
|
||||
readCloser, err := req.Stream()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get logs: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer readCloser.Close()
|
||||
if _, err := io.Copy(n.config.Stderr, readCloser); err != nil && err != io.EOF {
|
||||
log.Error("Error writing pod logs", "pod", pod.Name, "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the node to start
|
||||
var client *rpc.Client
|
||||
wsAddr := fmt.Sprintf("ws://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
|
||||
adapter.proxy, adapter.config.Namespace, n.podName(), dockerWebsocketPort)
|
||||
|
||||
for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
client, err = rpc.Dial(wsAddr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
return fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var swarminfo swarm.Info
|
||||
err = client.Call(&swarminfo, "bzz_info")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
var p2pinfo p2p.NodeInfo
|
||||
err = client.Call(&p2pinfo, "admin_nodeInfo")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
n.info = NodeInfo{
|
||||
ID: n.config.ID,
|
||||
Enode: p2pinfo.Enode,
|
||||
BzzAddr: swarminfo.BzzKey,
|
||||
RPCListen: wsAddr,
|
||||
HTTPListen: fmt.Sprintf("http://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
|
||||
adapter.proxy, adapter.config.Namespace, n.podName(), dockerHTTPPort),
|
||||
PprofListen: fmt.Sprintf("http://%s/api/v1/namespaces/%s/pods/%s:%d/proxy",
|
||||
adapter.proxy, adapter.config.Namespace, n.podName(), dockerPProfPort),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the node
|
||||
func (n *KubernetesNode) Stop() error {
|
||||
adapter := n.adapter
|
||||
|
||||
gracePeriod := int64(30)
|
||||
|
||||
deleteOpts := &metav1.DeleteOptions{
|
||||
GracePeriodSeconds: &gracePeriod,
|
||||
}
|
||||
err := adapter.client.CoreV1().Pods(adapter.config.Namespace).Delete(n.podName(), deleteOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete pod: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the node
|
||||
func (n *KubernetesNode) Snapshot() (NodeSnapshot, error) {
|
||||
snap := NodeSnapshot{
|
||||
Config: n.config,
|
||||
}
|
||||
adapterSnap := n.adapter.Snapshot()
|
||||
snap.Adapter = &adapterSnap
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func (n *KubernetesNode) podName() string {
|
||||
return fmt.Sprintf("sim-k8s-%s", n.config.ID)
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if h := os.Getenv("HOME"); h != "" {
|
||||
return h
|
||||
}
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
// proxyServer is a http.Handler which proxies Kubernetes APIs to remote API server.
|
||||
type proxyServer struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
// Listen is a simple wrapper around net.Listen.
|
||||
func (s *proxyServer) Listen(address string, port int) (net.Listener, error) {
|
||||
return net.Listen("tcp", fmt.Sprintf("%s:%d", address, port))
|
||||
}
|
||||
|
||||
// ServeOnListener starts the server using given listener, loops forever.
|
||||
func (s *proxyServer) ServeOnListener(l net.Listener) error {
|
||||
server := http.Server{
|
||||
Handler: s.handler,
|
||||
}
|
||||
return server.Serve(l)
|
||||
}
|
||||
|
||||
func (s *proxyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
s.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// newProxyServer creates a proxy server that can be used to proxy to the kubernetes API
|
||||
func newProxyServer(cfg *rest.Config) (*proxyServer, error) {
|
||||
target, err := url.Parse(cfg.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
transport, err := rest.TransportFor(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy.Transport = transport
|
||||
|
||||
return &proxyServer{
|
||||
handler: proxy,
|
||||
}, nil
|
||||
}
|
604
simulation/simulation.go
Normal file
604
simulation/simulation.go
Normal file
@ -0,0 +1,604 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm/log"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type nodeMap struct {
|
||||
sync.RWMutex
|
||||
internal map[NodeID]Node
|
||||
}
|
||||
|
||||
func newNodeMap() *nodeMap {
|
||||
return &nodeMap{
|
||||
internal: make(map[NodeID]Node),
|
||||
}
|
||||
}
|
||||
|
||||
func (nm *nodeMap) Load(key NodeID) (value Node, ok bool) {
|
||||
nm.RLock()
|
||||
result, ok := nm.internal[key]
|
||||
nm.RUnlock()
|
||||
return result, ok
|
||||
}
|
||||
|
||||
func (nm *nodeMap) LoadAll() []Node {
|
||||
nm.RLock()
|
||||
v := []Node{}
|
||||
for _, node := range nm.internal {
|
||||
v = append(v, node)
|
||||
}
|
||||
nm.RUnlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (nm *nodeMap) Store(key NodeID, value Node) {
|
||||
nm.Lock()
|
||||
nm.internal[key] = value
|
||||
nm.Unlock()
|
||||
}
|
||||
|
||||
// Simulation is used to simulate a network of nodes
|
||||
type Simulation struct {
|
||||
adapter Adapter
|
||||
nodes *nodeMap
|
||||
}
|
||||
|
||||
// NewSimulation creates a new simulation given an adapter
|
||||
func NewSimulation(adapter Adapter) *Simulation {
|
||||
sim := &Simulation{
|
||||
adapter: adapter,
|
||||
nodes: newNodeMap(),
|
||||
}
|
||||
return sim
|
||||
}
|
||||
|
||||
func getAdapterFromSnapshotConfig(snapshot *AdapterSnapshot) (Adapter, error) {
|
||||
if snapshot == nil {
|
||||
return nil, errors.New("snapshot can't be nil")
|
||||
}
|
||||
var adapter Adapter
|
||||
var err error
|
||||
switch t := snapshot.Type; t {
|
||||
case "exec":
|
||||
adapter, err = NewExecAdapter(snapshot.Config.(ExecAdapterConfig))
|
||||
case "docker":
|
||||
adapter, err = NewDockerAdapter(snapshot.Config.(DockerAdapterConfig))
|
||||
case "kubernetes":
|
||||
adapter, err = NewKubernetesAdapter(snapshot.Config.(KubernetesAdapterConfig))
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown adapter type: %s", t)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize %s adapter: %v", snapshot.Type, err)
|
||||
}
|
||||
return adapter, nil
|
||||
}
|
||||
|
||||
// NewSimulationFromSnapshot creates a simulation from a snapshot
|
||||
func NewSimulationFromSnapshot(snapshot *Snapshot) (*Simulation, error) {
|
||||
// Create adapter
|
||||
adapter, err := getAdapterFromSnapshotConfig(snapshot.DefaultAdapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sim := &Simulation{
|
||||
adapter: adapter,
|
||||
nodes: newNodeMap(),
|
||||
}
|
||||
|
||||
// Loop over nodes and add them
|
||||
for _, n := range snapshot.Nodes {
|
||||
if n.Adapter == nil {
|
||||
if err := sim.Init(n.Config); err != nil {
|
||||
return sim, fmt.Errorf("failed to initialize node %v", err)
|
||||
}
|
||||
} else {
|
||||
adapter, err := getAdapterFromSnapshotConfig(n.Adapter)
|
||||
if err != nil {
|
||||
return sim, fmt.Errorf("could not read adapter configureation for node %s: %v", n.Config.ID, err)
|
||||
}
|
||||
if err := sim.InitWithAdapter(n.Config, adapter); err != nil {
|
||||
return sim, fmt.Errorf("failed to initialize node %s: %v", n.Config.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start all nodes
|
||||
err = sim.StartAll()
|
||||
if err != nil {
|
||||
return sim, err
|
||||
}
|
||||
|
||||
// Establish connections
|
||||
m := make(map[string]Node)
|
||||
for _, n := range sim.GetAll() {
|
||||
enode := removeNetworkAddressFromEnode(n.Info().Enode)
|
||||
m[enode] = n
|
||||
}
|
||||
|
||||
for _, con := range snapshot.Connections {
|
||||
from, ok := m[con.From]
|
||||
if !ok {
|
||||
return sim, fmt.Errorf("no node found with enode: %s", con.From)
|
||||
}
|
||||
to, ok := m[con.To]
|
||||
if !ok {
|
||||
return sim, fmt.Errorf("no node found with enode: %s", con.To)
|
||||
}
|
||||
|
||||
client, err := sim.RPCClient(from.Info().ID)
|
||||
if err != nil {
|
||||
return sim, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Call(nil, "admin_addPeer", to.Info().Enode); err != nil {
|
||||
return sim, err
|
||||
}
|
||||
}
|
||||
return sim, nil
|
||||
}
|
||||
|
||||
func (s *AdapterSnapshot) detectConfigurationType() error {
|
||||
adapterconfig, err := json.Marshal(s.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := s.Type; t {
|
||||
case "exec":
|
||||
var config ExecAdapterConfig
|
||||
err := json.Unmarshal(adapterconfig, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Config = config
|
||||
case "docker":
|
||||
var config DockerAdapterConfig
|
||||
err := json.Unmarshal(adapterconfig, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Config = config
|
||||
case "kubernetes":
|
||||
var config KubernetesAdapterConfig
|
||||
err := json.Unmarshal(adapterconfig, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Config = config
|
||||
default:
|
||||
return fmt.Errorf("unknown adapter type: %s", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalSnapshot(data []byte, snapshot *Snapshot) error {
|
||||
err := json.Unmarshal(data, snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// snapshot.Adapter.Config will be of type map[string]interface{}
|
||||
// we have to unmarshal it to the correct adapter configuration struct
|
||||
if err := snapshot.DefaultAdapter.detectConfigurationType(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range snapshot.Nodes {
|
||||
if n.Adapter != nil {
|
||||
if err := n.Adapter.detectConfigurationType(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadSnapshotFromFile loads a snapshot from a given JSON file
|
||||
func LoadSnapshotFromFile(filePath string) (*Snapshot, error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var snapshot Snapshot
|
||||
err = unmarshalSnapshot(bytes, &snapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
// Get returns a node by ID
|
||||
func (s *Simulation) Get(id NodeID) (Node, error) {
|
||||
node, ok := s.nodes.Load(id)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("a node with id %s does not exist", id)
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// GetAll returns all nodes
|
||||
func (s *Simulation) GetAll() []Node {
|
||||
return s.nodes.LoadAll()
|
||||
}
|
||||
|
||||
// DefaultAdapter returns the default adapter that the simulation was initialized with
|
||||
func (s *Simulation) DefaultAdapter() Adapter {
|
||||
return s.adapter
|
||||
}
|
||||
|
||||
// Init initializes a node with the NodeConfig with the default Adapter
|
||||
func (s *Simulation) Init(config NodeConfig) error {
|
||||
return s.InitWithAdapter(config, s.DefaultAdapter())
|
||||
}
|
||||
|
||||
// InitWithAdapter initializes a node with the NodeConfig and the given Adapter
|
||||
func (s *Simulation) InitWithAdapter(config NodeConfig, adapter Adapter) error {
|
||||
if _, ok := s.nodes.Load(config.ID); ok {
|
||||
return fmt.Errorf("a node with id %s already exists", config.ID)
|
||||
}
|
||||
node := adapter.NewNode(config)
|
||||
s.nodes.Store(config.ID, node)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a node by ID
|
||||
func (s *Simulation) Start(id NodeID) error {
|
||||
node, ok := s.nodes.Load(id)
|
||||
if !ok {
|
||||
return fmt.Errorf("a node with id %s does not exist", id)
|
||||
}
|
||||
|
||||
if err := node.Start(); err != nil {
|
||||
return fmt.Errorf("could not start node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops a node by ID
|
||||
func (s *Simulation) Stop(id NodeID) error {
|
||||
node, ok := s.nodes.Load(id)
|
||||
if !ok {
|
||||
return fmt.Errorf("a node with id %s does not exist", id)
|
||||
}
|
||||
|
||||
if err := node.Stop(); err != nil {
|
||||
return fmt.Errorf("could not stop node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartAll starts all nodes
|
||||
func (s *Simulation) StartAll() error {
|
||||
g, _ := errgroup.WithContext(context.Background())
|
||||
for _, node := range s.nodes.LoadAll() {
|
||||
g.Go(node.Start)
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// StopAll stops all nodes
|
||||
func (s *Simulation) StopAll() error {
|
||||
g, _ := errgroup.WithContext(context.Background())
|
||||
for _, node := range s.nodes.LoadAll() {
|
||||
g.Go(node.Stop)
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// RPCClient returns an RPC Client for a given node
|
||||
func (s *Simulation) RPCClient(id NodeID) (*rpc.Client, error) {
|
||||
node, ok := s.nodes.Load(id)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("a node with id %s does not exist", id)
|
||||
}
|
||||
|
||||
info := node.Info()
|
||||
|
||||
var client *rpc.Client
|
||||
var err error
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
client, err = rpc.Dial(info.RPCListen)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("could not establish rpc connection: %v", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// HTTPBaseAddr returns the address for the HTTP API
|
||||
func (s *Simulation) HTTPBaseAddr(id NodeID) (string, error) {
|
||||
node, ok := s.nodes.Load(id)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("a node with id %s does not exist", id)
|
||||
}
|
||||
info := node.Info()
|
||||
return info.HTTPListen, nil
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the simulation
|
||||
func (s *Simulation) Snapshot() (*Snapshot, error) {
|
||||
snap := Snapshot{}
|
||||
|
||||
// Default adapter snapshot
|
||||
asnap := s.DefaultAdapter().Snapshot()
|
||||
snap.DefaultAdapter = &asnap
|
||||
|
||||
// Nodes snapshot
|
||||
nodes := s.GetAll()
|
||||
snap.Nodes = make([]NodeSnapshot, len(nodes))
|
||||
|
||||
snap.Connections = []ConnectionSnapshot{}
|
||||
|
||||
for idx, n := range nodes {
|
||||
ns, err := n.Snapshot()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get nodes snapshot %s: %v", n.Info().ID, err)
|
||||
}
|
||||
|
||||
// Don't need to specify the node's adapter snapshot if it's
|
||||
// the same as the default adapters snapshot
|
||||
if reflect.DeepEqual(asnap, *ns.Adapter) {
|
||||
ns.Adapter = nil
|
||||
}
|
||||
snap.Nodes[idx] = ns
|
||||
|
||||
// Get connections
|
||||
client, err := s.RPCClient(n.Info().ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
var peers []*p2p.PeerInfo
|
||||
err = client.Call(&peers, "admin_peers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range peers {
|
||||
// Only care about outbound connections
|
||||
if !p.Network.Inbound {
|
||||
snap.Connections = append(snap.Connections, ConnectionSnapshot{
|
||||
// we need to remove network addresses from enodes
|
||||
// because they will change between simulations
|
||||
From: removeNetworkAddressFromEnode(n.Info().Enode),
|
||||
To: removeNetworkAddressFromEnode(p.Enode),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &snap, nil
|
||||
}
|
||||
|
||||
// AddBootnode adds and starts a bootnode with the given id and arguments
|
||||
func (s *Simulation) AddBootnode(id NodeID, args []string) (Node, error) {
|
||||
a := []string{
|
||||
"--bootnode-mode",
|
||||
"--bootnodes", "",
|
||||
}
|
||||
a = append(a, args...)
|
||||
return s.AddNode(id, a)
|
||||
}
|
||||
|
||||
// AddNode adds and starts a node with the given id and arguments
|
||||
func (s *Simulation) AddNode(id NodeID, args []string) (Node, error) {
|
||||
bzzkey, err := randomHexKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodekey, err := randomHexKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a := []string{
|
||||
"--bzzkeyhex", bzzkey,
|
||||
"--nodekeyhex", nodekey,
|
||||
}
|
||||
a = append(a, args...)
|
||||
cfg := NodeConfig{
|
||||
ID: id,
|
||||
Args: a,
|
||||
// TODO: Figure out how to handle logs when using AddNode(...)
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
err = s.Init(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.Start(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := s.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// AddNodes adds and starts 'count' nodes with a given ID prefix, arguments.
|
||||
// If the idPrefix is "node" and count is 3 then the following nodes will be
|
||||
// created: node-0, node-1, node-2
|
||||
func (s *Simulation) AddNodes(idPrefix string, count int, args []string) ([]Node, error) {
|
||||
g, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
idFormat := "%s-%d"
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
id := NodeID(fmt.Sprintf(idFormat, idPrefix, i))
|
||||
g.Go(func() error {
|
||||
node, err := s.AddNode(id, args)
|
||||
if err != nil {
|
||||
log.Warn("Failed to add node", "id", id, "err", err.Error())
|
||||
} else {
|
||||
log.Info("Added node", "id", id, "enode", node.Info().Enode)
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
err := g.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes := make([]Node, count)
|
||||
for i := 0; i < count; i++ {
|
||||
id := NodeID(fmt.Sprintf(idFormat, idPrefix, i))
|
||||
nodes[i], err = s.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// CreateClusterWithBootnode adds and starts a bootnode. Afterwards it will add and start 'count' nodes that connect
|
||||
// to the bootnode. All nodes can be provided by custom arguments.
|
||||
// If the idPrefix is "node" and count is 3 then you will have the following nodes created:
|
||||
// node-bootnode, node-0, node-1, node-2.
|
||||
// The bootnode will be the first node on the returned Node slice.
|
||||
func (s *Simulation) CreateClusterWithBootnode(idPrefix string, count int, args []string) ([]Node, error) {
|
||||
bootnode, err := s.AddBootnode(NodeID(fmt.Sprintf("%s-bootnode", idPrefix)), args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeArgs := []string{
|
||||
"--bootnodes", bootnode.Info().Enode,
|
||||
}
|
||||
nodeArgs = append(nodeArgs, args...)
|
||||
|
||||
n, err := s.AddNodes(idPrefix, count, nodeArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes := []Node{bootnode}
|
||||
nodes = append(nodes, n...)
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// WaitForHealthyNetwork will block until all the nodes are considered
|
||||
// to have a healthy kademlia table
|
||||
func (s *Simulation) WaitForHealthyNetwork() error {
|
||||
nodes := s.GetAll()
|
||||
|
||||
// Generate RPC clients
|
||||
var clients struct {
|
||||
RPC []*rpc.Client
|
||||
mu sync.Mutex
|
||||
}
|
||||
clients.RPC = make([]*rpc.Client, len(nodes))
|
||||
|
||||
g, _ := errgroup.WithContext(context.Background())
|
||||
|
||||
for idx, node := range nodes {
|
||||
node := node
|
||||
idx := idx
|
||||
g.Go(func() error {
|
||||
id := node.Info().ID
|
||||
client, err := s.RPCClient(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clients.mu.Lock()
|
||||
clients.RPC[idx] = client
|
||||
clients.mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range clients.RPC {
|
||||
defer c.Close()
|
||||
}
|
||||
|
||||
// Generate addresses for PotMap
|
||||
addrs := [][]byte{}
|
||||
for _, node := range nodes {
|
||||
byteaddr, err := hexutil.Decode(node.Info().BzzAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addrs = append(addrs, byteaddr)
|
||||
}
|
||||
|
||||
ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
|
||||
|
||||
log.Info("Waiting for healthy kademlia...")
|
||||
|
||||
// Check for healthInfo on all nodes
|
||||
for {
|
||||
g, _ = errgroup.WithContext(context.Background())
|
||||
for i := 0; i < len(nodes)-1; i++ {
|
||||
i := i
|
||||
g.Go(func() error {
|
||||
log.Debug("Checking hive_getHealthInfo", "node", nodes[i].Info().ID)
|
||||
healthy := &network.Health{}
|
||||
if err := clients.RPC[i].Call(&healthy, "hive_getHealthInfo", ppmap[nodes[i].Info().BzzAddr[2:]]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !healthy.Healthy() {
|
||||
return fmt.Errorf("node %s is not healthy", nodes[i].Info().ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
err := g.Wait()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
log.Info("Not healthy yet...", "msg", err.Error())
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
log.Info("Healthy kademlia on all nodes")
|
||||
return nil
|
||||
}
|
||||
|
||||
func randomHexKey() (string, error) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
keyhex := hex.EncodeToString(crypto.FromECDSA(key))
|
||||
return keyhex, nil
|
||||
}
|
||||
|
||||
func removeNetworkAddressFromEnode(enode string) string {
|
||||
if idx := strings.Index(enode, "@"); idx != -1 {
|
||||
return enode[:idx]
|
||||
}
|
||||
return enode
|
||||
}
|
81
simulation/types.go
Normal file
81
simulation/types.go
Normal file
@ -0,0 +1,81 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Node is a node within a simulation
|
||||
type Node interface {
|
||||
Info() NodeInfo
|
||||
// Start starts the node
|
||||
Start() error
|
||||
// Stop stops the node
|
||||
Stop() error
|
||||
// Snapshot returns a snapshot of the node
|
||||
Snapshot() (NodeSnapshot, error)
|
||||
}
|
||||
|
||||
// Adapter can handle Node creation
|
||||
type Adapter interface {
|
||||
// NewNode creates a new node based on the NodeConfig
|
||||
NewNode(config NodeConfig) Node
|
||||
// Snapshot returns a snapshot of the adapter
|
||||
Snapshot() AdapterSnapshot
|
||||
}
|
||||
|
||||
// NodeID is the node identifier within a simulation. This can be an arbitrary string.
|
||||
type NodeID string
|
||||
|
||||
// NodeConfig is the configuration of a specific node
|
||||
type NodeConfig struct {
|
||||
// Arbitrary string used to identify a node
|
||||
ID NodeID `json:"id"`
|
||||
// Command line arguments
|
||||
Args []string `json:"args"`
|
||||
// Environment variables
|
||||
Env []string `json:"env,omitempty"`
|
||||
// Stdout and Stderr specify the nodes' standard output and error
|
||||
Stdout io.Writer `json:"-"`
|
||||
Stderr io.Writer `json:"-"`
|
||||
}
|
||||
|
||||
// NodeInfo contains the nodes information and connections strings
|
||||
type NodeInfo struct {
|
||||
ID NodeID
|
||||
Enode string
|
||||
BzzAddr string
|
||||
|
||||
RPCListen string // RPC listener address. Should be a valid ipc or websocket path
|
||||
HTTPListen string // HTTP listener address: e.g. http://localhost:8500
|
||||
PprofListen string // PProf listener address: e.g http://localhost:6060
|
||||
}
|
||||
|
||||
// Snapshot is a snapshot of a simulation. It contains snapshots of:
|
||||
// - the default adapter that the simulation was initialized with
|
||||
// - the list of nodes that were created within the simulation
|
||||
// - the list of connections between nodes
|
||||
type Snapshot struct {
|
||||
DefaultAdapter *AdapterSnapshot `json:"defaultAdapter"`
|
||||
Nodes []NodeSnapshot `json:"nodes"`
|
||||
Connections []ConnectionSnapshot `json:"connections"`
|
||||
}
|
||||
|
||||
// NodeSnapshot is a snapshot of the node, it contains the node configuration and an adapter snapshot
|
||||
type NodeSnapshot struct {
|
||||
Config NodeConfig `json:"config"`
|
||||
Adapter *AdapterSnapshot `json:"adapter,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionSnapshot is a snapshot of a connection between peers
|
||||
type ConnectionSnapshot struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
// AdapterSnapshot is a snapshot of the configuration of an adapter
|
||||
// - The type can be an arbitrary strings, e.g. "exec", "docker", etc.
|
||||
// - The config will depend on the type, as every adapter has different configuration options
|
||||
type AdapterSnapshot struct {
|
||||
Type string `json:"type"`
|
||||
Config interface{} `json:"config"`
|
||||
}
|
18
swarm.go
18
swarm.go
@ -25,7 +25,9 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@ -383,12 +385,21 @@ func (s *Swarm) Start(srv *p2p.Server) error {
|
||||
server := httpapi.NewServer(s.api, s.config.Cors)
|
||||
|
||||
if s.config.Cors != "" {
|
||||
log.Debug("Swarm HTTP proxy CORS headers", "allowedOrigins", s.config.Cors)
|
||||
log.Info("Swarm HTTP proxy CORS headers", "allowedOrigins", s.config.Cors)
|
||||
}
|
||||
|
||||
log.Debug("Starting Swarm HTTP proxy", "port", s.config.Port)
|
||||
go func() {
|
||||
err := server.ListenAndServe(addr)
|
||||
// We need to use net.Listen because the addr could be on port '0',
|
||||
// which means that the OS will allocate a port for us
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Error("Could not open a port for Swarm HTTP proxy", "err", err.Error())
|
||||
return
|
||||
}
|
||||
s.config.Port = strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)
|
||||
log.Info("Starting Swarm HTTP proxy", "port", s.config.Port)
|
||||
|
||||
err = http.Serve(listener, server)
|
||||
if err != nil {
|
||||
log.Error("Could not start Swarm HTTP proxy", "err", err.Error())
|
||||
}
|
||||
@ -481,7 +492,6 @@ func (s *Swarm) Protocols() (protos []p2p.Protocol) {
|
||||
// implements node.Service
|
||||
// APIs returns the RPC API descriptors the Swarm implementation offers
|
||||
func (s *Swarm) APIs() []rpc.API {
|
||||
|
||||
apis := []rpc.API{
|
||||
// public APIs
|
||||
{
|
||||
|
58
testutil/node.go
Normal file
58
testutil/node.go
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2019 The Swarm Authors
|
||||
// This file is part of the Swarm library.
|
||||
//
|
||||
// The Swarm library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The Swarm library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the Swarm library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
"github.com/ethersphere/swarm/chunk"
|
||||
"github.com/ethersphere/swarm/network"
|
||||
)
|
||||
|
||||
// NodeConfigAtPo brute forces a node config to create a node that has an overlay address at the provided po in relation to the given baseaddr
|
||||
func NodeConfigAtPo(t *testing.T, baseaddr []byte, po int) *adapters.NodeConfig {
|
||||
foundPo := -1
|
||||
var conf *adapters.NodeConfig
|
||||
for foundPo != po {
|
||||
conf = adapters.RandomNodeConfig()
|
||||
ip := net.IPv4(127, 0, 0, 1)
|
||||
enrIP := enr.IP(ip)
|
||||
conf.Record.Set(&enrIP)
|
||||
enrTCPPort := enr.TCP(conf.Port)
|
||||
conf.Record.Set(&enrTCPPort)
|
||||
enrUDPPort := enr.UDP(0)
|
||||
conf.Record.Set(&enrUDPPort)
|
||||
|
||||
err := enode.SignV4(&conf.Record, conf.PrivateKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate ENR: %v", err)
|
||||
}
|
||||
nod, err := enode.New(enode.V4ID{}, &conf.Record)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create enode: %v", err)
|
||||
}
|
||||
|
||||
n := network.NewAddr(nod)
|
||||
foundPo = chunk.Proximity(baseaddr, n.Over())
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
@ -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.
|
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
@ -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.
|
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
@ -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
|
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
type ansiContext struct {
|
||||
currentChar byte
|
||||
paramBuffer []byte
|
||||
interBuffer []byte
|
||||
}
|
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
@ -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
|
||||
}
|
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
@ -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
|
||||
}
|
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
@ -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
|
||||
}
|
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
@ -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
|
||||
}
|
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
@ -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
|
||||
}
|
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
@ -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
|
||||
}
|
182
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
182
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
)
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
327
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
327
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
@ -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{}) {}
|
100
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
100
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
101
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
101
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
@ -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
|
||||
}
|
84
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
84
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
@ -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
|
||||
}
|
118
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
118
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
@ -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
|
||||
}
|
9
vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
generated
vendored
Normal file
9
vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
743
vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
Normal file
743
vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
Normal file
@ -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
|
||||
}
|
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
@ -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.
|
||||
|
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# go-winio
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||
for another named pipe implementation.
|
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||
//sys backupWrite(h syscall.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
|
||||
BackupReparseData
|
||||
BackupSparseBlock
|
||||
BackupTxfsData
|
||||
)
|
||||
|
||||
const (
|
||||
StreamSparseAttributes = uint32(8)
|
||||
)
|
||||
|
||||
const (
|
||||
WRITE_DAC = 0x40000
|
||||
WRITE_OWNER = 0x80000
|
||||
ACCESS_SYSTEM_SECURITY = 0x1000000
|
||||
)
|
||||
|
||||
// BackupHeader represents a backup stream of a file.
|
||||
type BackupHeader struct {
|
||||
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 {
|
||||
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(ioutil.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 = syscall.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(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{"BackupRead", r.f.Name(), 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(syscall.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(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{"BackupWrite", w.f.Name(), 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(syscall.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) {
|
||||
winPath, err := syscall.UTF16FromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.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
|
||||
}
|
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
@ -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
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
323
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
323
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
type atomicBool int32
|
||||
|
||||
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
|
||||
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
|
||||
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
|
||||
func (b *atomicBool) swap(new bool) bool {
|
||||
var newInt int32
|
||||
if new {
|
||||
newInt = 1
|
||||
}
|
||||
return atomic.SwapInt32((*int32)(b), newInt) == 1
|
||||
}
|
||||
|
||||
const (
|
||||
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
|
||||
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFileClosed = errors.New("file has already been closed")
|
||||
ErrTimeout = &timeoutError{}
|
||||
)
|
||||
|
||||
type timeoutError struct{}
|
||||
|
||||
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||
func (e *timeoutError) Timeout() bool { return true }
|
||||
func (e *timeoutError) Temporary() bool { return true }
|
||||
|
||||
type timeoutChan chan struct{}
|
||||
|
||||
var ioInitOnce sync.Once
|
||||
var ioCompletionPort syscall.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 syscall.Overlapped
|
||||
ch chan ioResult
|
||||
}
|
||||
|
||||
func initIo() {
|
||||
h, err := createIoCompletionPort(syscall.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 syscall.Handle
|
||||
wg sync.WaitGroup
|
||||
wgLock sync.RWMutex
|
||||
closing atomicBool
|
||||
socket bool
|
||||
readDeadline deadlineHandler
|
||||
writeDeadline deadlineHandler
|
||||
}
|
||||
|
||||
type deadlineHandler struct {
|
||||
setLock sync.Mutex
|
||||
channel timeoutChan
|
||||
channelLock sync.RWMutex
|
||||
timer *time.Timer
|
||||
timedout atomicBool
|
||||
}
|
||||
|
||||
// makeWin32File makes a new win32File from an existing file handle
|
||||
func makeWin32File(h syscall.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, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.readDeadline.channel = make(timeoutChan)
|
||||
f.writeDeadline.channel = make(timeoutChan)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func MakeOpenFile(h syscall.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
|
||||
syscall.Close(f.handle)
|
||||
f.handle = 0
|
||||
} else {
|
||||
f.wgLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes a win32File.
|
||||
func (f *win32File) Close() error {
|
||||
f.closeHandle()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.isSet() {
|
||||
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 syscall.Handle) {
|
||||
for {
|
||||
var bytes uint32
|
||||
var key uintptr
|
||||
var op *ioOperation
|
||||
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||
if op == nil {
|
||||
panic(err)
|
||||
}
|
||||
op.ch <- ioResult{bytes, err}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 != syscall.ERROR_IO_PENDING {
|
||||
return int(bytes), err
|
||||
}
|
||||
|
||||
if f.closing.isSet() {
|
||||
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 == syscall.ERROR_OPERATION_ABORTED {
|
||||
if f.closing.isSet() {
|
||||
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 == syscall.ERROR_OPERATION_ABORTED {
|
||||
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.
|
||||
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.isSet() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.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 == syscall.ERROR_BROKEN_PIPE {
|
||||
return 0, io.EOF
|
||||
} else {
|
||||
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.isSet() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.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 syscall.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.setFalse()
|
||||
|
||||
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.setTrue()
|
||||
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
|
||||
}
|
61
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
61
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||
|
||||
const (
|
||||
fileBasicInfo = 0
|
||||
fileIDInfo = 0x12
|
||||
)
|
||||
|
||||
// FileBasicInfo contains file access time and file attributes information.
|
||||
type FileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||
FileAttributes uint32
|
||||
pad uint32 // padding
|
||||
}
|
||||
|
||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
bi := &FileBasicInfo{}
|
||||
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 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)
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
// SetFileBasicInfo sets times and attributes for a file.
|
||||
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return 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 := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 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
|
||||
}
|
9
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
Normal file
9
vendor/github.com/Microsoft/go-winio/go.mod
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module github.com/Microsoft/go-winio
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b
|
||||
)
|
16
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
Normal file
16
vendor/github.com/Microsoft/go-winio/go.sum
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
305
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
305
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
@ -0,0 +1,305 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
)
|
||||
|
||||
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||
|
||||
const (
|
||||
afHvSock = 34 // AF_HYPERV
|
||||
|
||||
socketError = ^uintptr(0)
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Network returns the address's network name, "hvsock".
|
||||
func (addr *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, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
||||
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
|
||||
}
|
||||
|
||||
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||
type HvsockListener struct {
|
||||
sock *win32File
|
||||
addr HvsockAddr
|
||||
}
|
||||
|
||||
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||
type HvsockConn struct {
|
||||
sock *win32File
|
||||
local, remote HvsockAddr
|
||||
}
|
||||
|
||||
func newHvSocket() (*win32File, error) {
|
||||
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("socket", err)
|
||||
}
|
||||
f, err := makeWin32File(fd)
|
||||
if err != nil {
|
||||
syscall.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}
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", err)
|
||||
}
|
||||
sa := addr.raw()
|
||||
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||
}
|
||||
err = syscall.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.
|
||||
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||
var addrbuf [addrlen * 2]byte
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
|
||||
_, err = l.sock.asyncIo(c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
}
|
||||
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||
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()
|
||||
}
|
||||
|
||||
/* Need to finish ConnectEx handling
|
||||
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if sock != nil {
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
c, err := sock.prepareIo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sock.wg.Done()
|
||||
var bytes uint32
|
||||
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
|
||||
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
remote: *addr,
|
||||
}
|
||||
sock = nil
|
||||
return conn, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||
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 := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var flags, bytes uint32
|
||||
err = syscall.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 {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsarecv", err)
|
||||
}
|
||||
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 := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var bytes uint32
|
||||
err = syscall.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 {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsasend", err)
|
||||
}
|
||||
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) shutdown(how int) error {
|
||||
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("shutdown", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseRead shuts down the read end of the socket.
|
||||
func (conn *HvsockConn) CloseRead() error {
|
||||
err := conn.shutdown(syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return conn.opErr("close", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||
// no more data will be written.
|
||||
func (conn *HvsockConn) CloseWrite() error {
|
||||
err := conn.shutdown(syscall.SHUT_WR)
|
||||
if err != nil {
|
||||
return conn.opErr("close", 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 {
|
||||
conn.SetReadDeadline(t)
|
||||
conn.SetWriteDeadline(t)
|
||||
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)
|
||||
}
|
510
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
510
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
||||
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, 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 ioStatusBlock struct {
|
||||
Status, Information uintptr
|
||||
}
|
||||
|
||||
type objectAttributes struct {
|
||||
Length uintptr
|
||||
RootDirectory uintptr
|
||||
ObjectName *unicodeString
|
||||
Attributes uintptr
|
||||
SecurityDescriptor *securityDescriptor
|
||||
SecurityQoS uintptr
|
||||
}
|
||||
|
||||
type unicodeString struct {
|
||||
Length uint16
|
||||
MaximumLength uint16
|
||||
Buffer uintptr
|
||||
}
|
||||
|
||||
type securityDescriptor struct {
|
||||
Revision byte
|
||||
Sbz1 byte
|
||||
Control uint16
|
||||
Owner uintptr
|
||||
Group uintptr
|
||||
Sacl uintptr
|
||||
Dacl uintptr
|
||||
}
|
||||
|
||||
type ntstatus int32
|
||||
|
||||
func (status ntstatus) Err() error {
|
||||
if status >= 0 {
|
||||
return nil
|
||||
}
|
||||
return rtlNtStatusToDosError(status)
|
||||
}
|
||||
|
||||
const (
|
||||
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||
cERROR_NO_DATA = syscall.Errno(232)
|
||||
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||
|
||||
cSECURITY_SQOS_PRESENT = 0x100000
|
||||
cSECURITY_ANONYMOUS = 0
|
||||
|
||||
cPIPE_TYPE_MESSAGE = 4
|
||||
|
||||
cPIPE_READMODE_MESSAGE = 2
|
||||
|
||||
cFILE_OPEN = 1
|
||||
cFILE_CREATE = 2
|
||||
|
||||
cFILE_PIPE_MESSAGE_TYPE = 1
|
||||
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
|
||||
|
||||
cSE_DACL_PRESENT = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||
// This error should match net.errClosing since docker takes a dependency on its text.
|
||||
ErrPipeListenerClosed = errors.New("use of closed network connection")
|
||||
|
||||
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||
)
|
||||
|
||||
type win32Pipe struct {
|
||||
*win32File
|
||||
path string
|
||||
}
|
||||
|
||||
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 {
|
||||
f.SetReadDeadline(t)
|
||||
f.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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 == syscall.ERROR_MORE_DATA {
|
||||
// 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 (s 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) (syscall.Handle, error) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return syscall.Handle(0), ctx.Err()
|
||||
default:
|
||||
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
if err != cERROR_PIPE_BUSY {
|
||||
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(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(time.Second * 2)
|
||||
}
|
||||
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||
conn, err := DialPipeContext(ctx, path)
|
||||
if 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) {
|
||||
var err error
|
||||
var h syscall.Handle
|
||||
h, err = tryDialPipe(ctx, &path)
|
||||
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 {
|
||||
syscall.Close(h)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the pipe is in message mode, return a message byte pipe, which
|
||||
// supports CloseWrite().
|
||||
if flags&cPIPE_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 syscall.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) (syscall.Handle, error) {
|
||||
path16, err := syscall.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 localFree(ntPath.Buffer)
|
||||
oa.ObjectName = &ntPath
|
||||
|
||||
// The security descriptor is only needed for the first pipe.
|
||||
if first {
|
||||
if sd != nil {
|
||||
len := uint32(len(sd))
|
||||
sdb := localAlloc(0, len)
|
||||
defer localFree(sdb)
|
||||
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: %s", err)
|
||||
}
|
||||
defer localFree(dacl)
|
||||
|
||||
sdb := &securityDescriptor{
|
||||
Revision: 1,
|
||||
Control: cSE_DACL_PRESENT,
|
||||
Dacl: dacl,
|
||||
}
|
||||
oa.SecurityDescriptor = sdb
|
||||
}
|
||||
}
|
||||
|
||||
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||
if c.MessageMode {
|
||||
typ |= cFILE_PIPE_MESSAGE_TYPE
|
||||
}
|
||||
|
||||
disposition := uint32(cFILE_OPEN)
|
||||
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
|
||||
if first {
|
||||
disposition = cFILE_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 = syscall.SYNCHRONIZE
|
||||
}
|
||||
|
||||
timeout := int64(-50 * 10000) // 50ms
|
||||
|
||||
var (
|
||||
h syscall.Handle
|
||||
iosb ioStatusBlock
|
||||
)
|
||||
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.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 {
|
||||
syscall.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 {
|
||||
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 != cERROR_NO_DATA {
|
||||
break
|
||||
}
|
||||
}
|
||||
responseCh <- acceptResponse{p, err}
|
||||
closed = err == ErrPipeListenerClosed
|
||||
}
|
||||
}
|
||||
syscall.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 the input buffer, in bytes.
|
||||
InputBufferSize int32
|
||||
|
||||
// OutputBufferSize specifies the size the input 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 != cERROR_PIPE_CONNECTED {
|
||||
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)
|
||||
}
|
187
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
187
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
// 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"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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
|
||||
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
|
||||
|
||||
var _ = (encoding.TextMarshaler)(GUID{})
|
||||
var _ = (encoding.TextUnmarshaler)(&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 so that stringification and
|
||||
// marshaling can be supported. The representation matches that used by native
|
||||
// Windows code.
|
||||
type GUID windows.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
|
||||
}
|
||||
|
||||
b[6] = (b[6] & 0x0f) | 0x40 // Version 4 (randomly generated)
|
||||
b[8] = (b[8] & 0x3f) | 0x80 // RFC4122 variant
|
||||
|
||||
return FromArray(b), 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"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 syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||
//sys getCurrentThread() (h syscall.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 (
|
||||
SE_PRIVILEGE_ENABLED = 2
|
||||
|
||||
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||
|
||||
SeBackupPrivilege = "SeBackupPrivilege"
|
||||
SeRestorePrivilege = "SeRestorePrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
securityAnonymous = iota
|
||||
securityIdentification
|
||||
securityImpersonation
|
||||
securityDelegation
|
||||
)
|
||||
|
||||
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 := ""
|
||||
if len(e.privileges) > 1 {
|
||||
s = "Could not enable privileges "
|
||||
} else {
|
||||
s = "Could not enable privilege "
|
||||
}
|
||||
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) {
|
||||
var privileges []uint64
|
||||
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.GetCurrentProcess()
|
||||
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 {
|
||||
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(securityImpersonation)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var token windows.Token
|
||||
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.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()
|
||||
}
|
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
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()
|
||||
}
|
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
|
||||
//sys localFree(mem uintptr) = LocalFree
|
||||
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
|
||||
|
||||
const (
|
||||
cERROR_NONE_MAPPED = syscall.Errno(1332)
|
||||
)
|
||||
|
||||
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 e.Err {
|
||||
case cERROR_NONE_MAPPED:
|
||||
s = "not found"
|
||||
default:
|
||||
s = e.Err.Error()
|
||||
}
|
||||
return "lookup account " + e.Name + ": " + s
|
||||
}
|
||||
|
||||
type SddlConversionError struct {
|
||||
Sddl string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *SddlConversionError) Error() string {
|
||||
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// LookupSidByName looks up the SID of an account by name
|
||||
func LookupSidByName(name string) (sid string, err error) {
|
||||
if name == "" {
|
||||
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
|
||||
}
|
||||
|
||||
var sidSize, sidNameUse, refDomainSize uint32
|
||||
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||
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 = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||
localFree(uintptr(unsafe.Pointer(strBuffer)))
|
||||
return sid, nil
|
||||
}
|
||||
|
||||
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||
var sdBuffer uintptr
|
||||
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
|
||||
if err != nil {
|
||||
return nil, &SddlConversionError{sddl, err}
|
||||
}
|
||||
defer localFree(sdBuffer)
|
||||
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
|
||||
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||
var sddl *uint16
|
||||
// The returned string length seems to including an aribtrary number of terminating NULs.
|
||||
// Don't use it.
|
||||
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer localFree(uintptr(unsafe.Pointer(sddl)))
|
||||
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
|
||||
}
|
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package winio
|
||||
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
562
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
562
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,562 @@
|
||||
// Code generated by 'go generate'; 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)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
|
||||
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
|
||||
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
||||
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
||||
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
||||
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
||||
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
|
||||
procLocalFree = modkernel32.NewProc("LocalFree")
|
||||
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
|
||||
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||
procbind = modws2_32.NewProc("bind")
|
||||
)
|
||||
|
||||
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||
newport = syscall.Handle(r0)
|
||||
if newport == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||
var _p0 uint32
|
||||
if wait {
|
||||
_p0 = 1
|
||||
} else {
|
||||
_p0 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.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 *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
|
||||
handle = syscall.Handle(r0)
|
||||
if handle == syscall.InvalidHandle {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.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 uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
|
||||
handle = syscall.Handle(r0)
|
||||
if handle == syscall.InvalidHandle {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
|
||||
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
|
||||
ptr = uintptr(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
|
||||
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, 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)), 0)
|
||||
status = ntstatus(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
|
||||
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
|
||||
if r0 != 0 {
|
||||
winerr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
|
||||
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
|
||||
status = ntstatus(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
|
||||
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
|
||||
status = ntstatus(r0)
|
||||
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.Syscall9(procLookupAccountNameW.Addr(), 7, 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)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||
var _p0 *uint16
|
||||
_p0, err = syscall.UTF16PtrFromString(str)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
|
||||
}
|
||||
|
||||
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func localFree(mem uintptr) {
|
||||
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
|
||||
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
|
||||
len = uint32(r0)
|
||||
return
|
||||
}
|
||||
|
||||
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
_p0 = 0
|
||||
}
|
||||
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||
success = r0 != 0
|
||||
if true {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func impersonateSelf(level uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func revertToSelf() (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||
var _p0 uint32
|
||||
if openAsSelf {
|
||||
_p0 = 1
|
||||
} else {
|
||||
_p0 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getCurrentThread() (h syscall.Handle) {
|
||||
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
|
||||
h = syscall.Handle(r0)
|
||||
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.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
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.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
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.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func backupRead(h syscall.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
|
||||
} else {
|
||||
_p1 = 0
|
||||
}
|
||||
var _p2 uint32
|
||||
if processSecurity {
|
||||
_p2 = 1
|
||||
} else {
|
||||
_p2 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func backupWrite(h syscall.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
|
||||
} else {
|
||||
_p1 = 0
|
||||
}
|
||||
var _p2 uint32
|
||||
if processSecurity {
|
||||
_p2 = 1
|
||||
} else {
|
||||
_p2 = 0
|
||||
}
|
||||
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
|
||||
if r1 == socketError {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
21
vendor/github.com/Microsoft/hcsshim/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Microsoft/hcsshim/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
57
vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
57
vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package osversion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// OSVersion is a wrapper for Windows version information
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||
type OSVersion struct {
|
||||
Version uint32
|
||||
MajorVersion uint8
|
||||
MinorVersion uint8
|
||||
Build uint16
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
|
||||
type osVersionInfoEx struct {
|
||||
OSVersionInfoSize uint32
|
||||
MajorVersion uint32
|
||||
MinorVersion uint32
|
||||
BuildNumber uint32
|
||||
PlatformID uint32
|
||||
CSDVersion [128]uint16
|
||||
ServicePackMajor uint16
|
||||
ServicePackMinor uint16
|
||||
SuiteMask uint16
|
||||
ProductType byte
|
||||
Reserve byte
|
||||
}
|
||||
|
||||
// Get gets the operating system version on Windows.
|
||||
// The calling application must be manifested to get the correct version information.
|
||||
func Get() OSVersion {
|
||||
var err error
|
||||
osv := OSVersion{}
|
||||
osv.Version, err = windows.GetVersion()
|
||||
if err != nil {
|
||||
// GetVersion never fails.
|
||||
panic(err)
|
||||
}
|
||||
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||
osv.Build = uint16(osv.Version >> 16)
|
||||
return osv
|
||||
}
|
||||
|
||||
// Build gets the build-number on Windows
|
||||
// The calling application must be manifested to get the correct version information.
|
||||
func Build() uint16 {
|
||||
return Get().Build
|
||||
}
|
||||
|
||||
func (osv OSVersion) ToString() string {
|
||||
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||
}
|
23
vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
23
vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package osversion
|
||||
|
||||
const (
|
||||
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
|
||||
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
|
||||
RS1 = 14393
|
||||
|
||||
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
|
||||
// corresponds to Windows 10 (Creators Update).
|
||||
RS2 = 15063
|
||||
|
||||
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
|
||||
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
|
||||
RS3 = 16299
|
||||
|
||||
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
|
||||
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
|
||||
RS4 = 17134
|
||||
|
||||
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
|
||||
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
|
||||
RS5 = 17763
|
||||
)
|
191
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
191
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright The containerd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
172
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
172
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var bufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
buffer := make([]byte, 32*1024)
|
||||
return &buffer
|
||||
},
|
||||
}
|
||||
|
||||
// XAttrErrorHandlers transform a non-nil xattr error.
|
||||
// Return nil to ignore an error.
|
||||
// xattrKey can be empty for listxattr operation.
|
||||
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||
|
||||
type copyDirOpts struct {
|
||||
xeh XAttrErrorHandler
|
||||
}
|
||||
|
||||
type CopyDirOpt func(*copyDirOpts) error
|
||||
|
||||
// WithXAttrErrorHandler allows specifying XAttrErrorHandler
|
||||
// If nil XAttrErrorHandler is specified (default), CopyDir stops
|
||||
// on a non-nil xattr error.
|
||||
func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
|
||||
return func(o *copyDirOpts) error {
|
||||
o.xeh = xeh
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllowXAttrErrors allows ignoring xattr errors.
|
||||
func WithAllowXAttrErrors() CopyDirOpt {
|
||||
xeh := func(dst, src, xattrKey string, err error) error {
|
||||
return nil
|
||||
}
|
||||
return WithXAttrErrorHandler(xeh)
|
||||
}
|
||||
|
||||
// CopyDir copies the directory from src to dst.
|
||||
// Most efficient copy of files is attempted.
|
||||
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
|
||||
var o copyDirOpts
|
||||
for _, opt := range opts {
|
||||
if err := opt(&o); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
inodes := map[uint64]string{}
|
||||
return copyDirectory(dst, src, inodes, &o)
|
||||
}
|
||||
|
||||
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
|
||||
stat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to stat %s", src)
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
return errors.Errorf("source is not directory")
|
||||
}
|
||||
|
||||
if st, err := os.Stat(dst); err != nil {
|
||||
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||
}
|
||||
} else if !st.IsDir() {
|
||||
return errors.Errorf("cannot copy to non-directory: %s", dst)
|
||||
} else {
|
||||
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod on %s", dst)
|
||||
}
|
||||
}
|
||||
|
||||
fis, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", src)
|
||||
}
|
||||
|
||||
if err := copyFileInfo(stat, dst); err != nil {
|
||||
return errors.Wrapf(err, "failed to copy file info for %s", dst)
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
source := filepath.Join(src, fi.Name())
|
||||
target := filepath.Join(dst, fi.Name())
|
||||
|
||||
switch {
|
||||
case fi.IsDir():
|
||||
if err := copyDirectory(target, source, inodes, o); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
case (fi.Mode() & os.ModeType) == 0:
|
||||
link, err := getLinkSource(target, fi, inodes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get hardlink")
|
||||
}
|
||||
if link != "" {
|
||||
if err := os.Link(link, target); err != nil {
|
||||
return errors.Wrap(err, "failed to create hard link")
|
||||
}
|
||||
} else if err := CopyFile(target, source); err != nil {
|
||||
return errors.Wrap(err, "failed to copy files")
|
||||
}
|
||||
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
||||
link, err := os.Readlink(source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read link: %s", source)
|
||||
}
|
||||
if err := os.Symlink(link, target); err != nil {
|
||||
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
||||
}
|
||||
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
||||
if err := copyDevice(target, fi); err != nil {
|
||||
return errors.Wrapf(err, "failed to create device")
|
||||
}
|
||||
default:
|
||||
// TODO: Support pipes and sockets
|
||||
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
||||
}
|
||||
if err := copyFileInfo(fi, target); err != nil {
|
||||
return errors.Wrap(err, "failed to copy file info")
|
||||
}
|
||||
|
||||
if err := copyXAttrs(target, source, o.xeh); err != nil {
|
||||
return errors.Wrap(err, "failed to copy xattrs")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile copies the source file to the target.
|
||||
// The most efficient means of copying is used for the platform.
|
||||
func CopyFile(target, source string) error {
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open source %s", source)
|
||||
}
|
||||
defer src.Close()
|
||||
tgt, err := os.Create(target)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open target %s", target)
|
||||
}
|
||||
defer tgt.Close()
|
||||
|
||||
return copyFileContent(tgt, src)
|
||||
}
|
144
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
144
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Normally if uid/gid are the same this would be a no-op, but some
|
||||
// filesystems may still return EPERM... for instance NFS does this.
|
||||
// In such a case, this is not an error.
|
||||
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
|
||||
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||
return errors.Wrapf(err, "failed to utime %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxSSizeT = int64(^uint(0) >> 1)
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
st, err := src.Stat()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to stat source")
|
||||
}
|
||||
|
||||
size := st.Size()
|
||||
first := true
|
||||
srcFd := int(src.Fd())
|
||||
dstFd := int(dst.Fd())
|
||||
|
||||
for size > 0 {
|
||||
// Ensure that we are never trying to copy more than SSIZE_MAX at a
|
||||
// time and at the same time avoids overflows when the file is larger
|
||||
// than 4GB on 32-bit systems.
|
||||
var copySize int
|
||||
if size > maxSSizeT {
|
||||
copySize = int(maxSSizeT)
|
||||
} else {
|
||||
copySize = int(size)
|
||||
}
|
||||
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0)
|
||||
if err != nil {
|
||||
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
|
||||
return errors.Wrap(err, "copy file range failed")
|
||||
}
|
||||
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err = io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return errors.Wrap(err, "userspace copy failed")
|
||||
}
|
||||
|
||||
first = false
|
||||
size -= int64(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
if xeh != nil {
|
||||
e = xeh(dst, src, "", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("unsupported stat type")
|
||||
}
|
||||
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||
}
|
112
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
112
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
// +build solaris darwin freebsd
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// Normally if uid/gid are the same this would be a no-op, but some
|
||||
// filesystems may still return EPERM... for instance NFS does this.
|
||||
// In such a case, this is not an error.
|
||||
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
|
||||
if err := syscall.UtimesNano(name, timespec); err != nil {
|
||||
return errors.Wrapf(err, "failed to utime %s", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
xattrKeys, err := sysx.LListxattr(src)
|
||||
if err != nil {
|
||||
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||
if xeh != nil {
|
||||
e = xeh(dst, src, "", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
for _, xattr := range xattrKeys {
|
||||
data, err := sysx.LGetxattr(src, xattr)
|
||||
if err != nil {
|
||||
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||
if xeh != nil {
|
||||
if e = xeh(dst, src, xattr, e); e == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("unsupported stat type")
|
||||
}
|
||||
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||
}
|
49
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
49
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
|
||||
// TODO: copy windows specific metadata
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
return errors.New("device copy not supported")
|
||||
}
|
326
vendor/github.com/containerd/continuity/fs/diff.go
generated
vendored
Normal file
326
vendor/github.com/containerd/continuity/fs/diff.go
generated
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ChangeKind is the type of modification that
|
||||
// a change is making.
|
||||
type ChangeKind int
|
||||
|
||||
const (
|
||||
// ChangeKindUnmodified represents an unmodified
|
||||
// file
|
||||
ChangeKindUnmodified = iota
|
||||
|
||||
// ChangeKindAdd represents an addition of
|
||||
// a file
|
||||
ChangeKindAdd
|
||||
|
||||
// ChangeKindModify represents a change to
|
||||
// an existing file
|
||||
ChangeKindModify
|
||||
|
||||
// ChangeKindDelete represents a delete of
|
||||
// a file
|
||||
ChangeKindDelete
|
||||
)
|
||||
|
||||
func (k ChangeKind) String() string {
|
||||
switch k {
|
||||
case ChangeKindUnmodified:
|
||||
return "unmodified"
|
||||
case ChangeKindAdd:
|
||||
return "add"
|
||||
case ChangeKindModify:
|
||||
return "modify"
|
||||
case ChangeKindDelete:
|
||||
return "delete"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Change represents single change between a diff and its parent.
|
||||
type Change struct {
|
||||
Kind ChangeKind
|
||||
Path string
|
||||
}
|
||||
|
||||
// ChangeFunc is the type of function called for each change
|
||||
// computed during a directory changes calculation.
|
||||
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
|
||||
|
||||
// Changes computes changes between two directories calling the
|
||||
// given change function for each computed change. The first
|
||||
// directory is intended to the base directory and second
|
||||
// directory the changed directory.
|
||||
//
|
||||
// The change callback is called by the order of path names and
|
||||
// should be appliable in that order.
|
||||
// Due to this apply ordering, the following is true
|
||||
// - Removed directory trees only create a single change for the root
|
||||
// directory removed. Remaining changes are implied.
|
||||
// - A directory which is modified to become a file will not have
|
||||
// delete entries for sub-path items, their removal is implied
|
||||
// by the removal of the parent directory.
|
||||
//
|
||||
// Opaque directories will not be treated specially and each file
|
||||
// removed from the base directory will show up as a removal.
|
||||
//
|
||||
// File content comparisons will be done on files which have timestamps
|
||||
// which may have been truncated. If either of the files being compared
|
||||
// has a zero value nanosecond value, each byte will be compared for
|
||||
// differences. If 2 files have the same seconds value but different
|
||||
// nanosecond values where one of those values is zero, the files will
|
||||
// be considered unchanged if the content is the same. This behavior
|
||||
// is to account for timestamp truncation during archiving.
|
||||
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
|
||||
if a == "" {
|
||||
logrus.Debugf("Using single walk diff for %s", b)
|
||||
return addDirChanges(ctx, changeFn, b)
|
||||
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
|
||||
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
|
||||
return diffDirChanges(ctx, changeFn, a, diffOptions)
|
||||
}
|
||||
|
||||
logrus.Debugf("Using double walk diff for %s from %s", b, a)
|
||||
return doubleWalkDiff(ctx, changeFn, a, b)
|
||||
}
|
||||
|
||||
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
|
||||
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return changeFn(ChangeKindAdd, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// diffDirOptions is used when the diff can be directly calculated from
|
||||
// a diff directory to its base, without walking both trees.
|
||||
type diffDirOptions struct {
|
||||
diffDir string
|
||||
skipChange func(string) (bool, error)
|
||||
deleteChange func(string, string, os.FileInfo) (string, error)
|
||||
}
|
||||
|
||||
// diffDirChanges walks the diff directory and compares changes against the base.
|
||||
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
|
||||
changedDirs := make(map[string]struct{})
|
||||
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(o.diffDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: handle opaqueness, start new double walker at this
|
||||
// location to get deletes, and skip tree in single walker
|
||||
|
||||
if o.skipChange != nil {
|
||||
if skip, err := o.skipChange(path); skip {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var kind ChangeKind
|
||||
|
||||
deletedFile, err := o.deleteChange(o.diffDir, path, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
if deletedFile != "" {
|
||||
path = deletedFile
|
||||
kind = ChangeKindDelete
|
||||
f = nil
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
kind = ChangeKindAdd
|
||||
|
||||
// ...Unless it already existed in a base, in which case, it's a modification
|
||||
stat, err := os.Stat(filepath.Join(base, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the base, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
kind = ChangeKindModify
|
||||
}
|
||||
}
|
||||
|
||||
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
|
||||
// This block is here to ensure the change is recorded even if the
|
||||
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
|
||||
// Check https://github.com/docker/docker/pull/13590 for details.
|
||||
if f.IsDir() {
|
||||
changedDirs[path] = struct{}{}
|
||||
}
|
||||
if kind == ChangeKindAdd || kind == ChangeKindDelete {
|
||||
parent := filepath.Dir(path)
|
||||
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
||||
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
|
||||
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
|
||||
return err
|
||||
}
|
||||
changedDirs[parent] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return changeFn(kind, path, f, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// doubleWalkDiff walks both directories to create a diff
|
||||
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
var (
|
||||
c1 = make(chan *currentPath)
|
||||
c2 = make(chan *currentPath)
|
||||
|
||||
f1, f2 *currentPath
|
||||
rmdir string
|
||||
)
|
||||
g.Go(func() error {
|
||||
defer close(c1)
|
||||
return pathWalk(ctx, a, c1)
|
||||
})
|
||||
g.Go(func() error {
|
||||
defer close(c2)
|
||||
return pathWalk(ctx, b, c2)
|
||||
})
|
||||
g.Go(func() error {
|
||||
for c1 != nil || c2 != nil {
|
||||
if f1 == nil && c1 != nil {
|
||||
f1, err = nextPath(ctx, c1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f1 == nil {
|
||||
c1 = nil
|
||||
}
|
||||
}
|
||||
|
||||
if f2 == nil && c2 != nil {
|
||||
f2, err = nextPath(ctx, c2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f2 == nil {
|
||||
c2 = nil
|
||||
}
|
||||
}
|
||||
if f1 == nil && f2 == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var f os.FileInfo
|
||||
k, p := pathChange(f1, f2)
|
||||
switch k {
|
||||
case ChangeKindAdd:
|
||||
if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f = f2.f
|
||||
f2 = nil
|
||||
case ChangeKindDelete:
|
||||
// Check if this file is already removed by being
|
||||
// under of a removed directory
|
||||
if rmdir != "" && strings.HasPrefix(f1.path, rmdir) {
|
||||
f1 = nil
|
||||
continue
|
||||
} else if f1.f.IsDir() {
|
||||
rmdir = f1.path + string(os.PathSeparator)
|
||||
} else if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f1 = nil
|
||||
case ChangeKindModify:
|
||||
same, err := sameFile(f1, f2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f1.f.IsDir() && !f2.f.IsDir() {
|
||||
rmdir = f1.path + string(os.PathSeparator)
|
||||
} else if rmdir != "" {
|
||||
rmdir = ""
|
||||
}
|
||||
f = f2.f
|
||||
f1 = nil
|
||||
f2 = nil
|
||||
if same {
|
||||
if !isLinked(f) {
|
||||
continue
|
||||
}
|
||||
k = ChangeKindUnmodified
|
||||
}
|
||||
}
|
||||
if err := changeFn(k, p, f, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
74
vendor/github.com/containerd/continuity/fs/diff_unix.go
generated
vendored
Normal file
74
vendor/github.com/containerd/continuity/fs/diff_unix.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// detectDirDiff returns diff dir options if a directory could
|
||||
// be found in the mount info for upper which is the direct
|
||||
// diff with the provided lower directory
|
||||
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||
// TODO: get mount options for upper
|
||||
// TODO: detect AUFS
|
||||
// TODO: detect overlay
|
||||
return nil
|
||||
}
|
||||
|
||||
// compareSysStat returns whether the stats are equivalent,
|
||||
// whether the files are considered the same file, and
|
||||
// an error
|
||||
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||
ls1, ok := s1.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
ls2, ok := s2.(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
|
||||
}
|
||||
|
||||
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||
c1, err := sysx.LGetxattr(p1, "security.capability")
|
||||
if err != nil && err != sysx.ENODATA {
|
||||
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
|
||||
}
|
||||
c2, err := sysx.LGetxattr(p2, "security.capability")
|
||||
if err != nil && err != sysx.ENODATA {
|
||||
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
|
||||
}
|
||||
return bytes.Equal(c1, c2), nil
|
||||
}
|
||||
|
||||
func isLinked(f os.FileInfo) bool {
|
||||
s, ok := f.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !f.IsDir() && s.Nlink > 1
|
||||
}
|
48
vendor/github.com/containerd/continuity/fs/diff_windows.go
generated
vendored
Normal file
48
vendor/github.com/containerd/continuity/fs/diff_windows.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||
f1, ok := s1.(windows.Win32FileAttributeData)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
f2, ok := s2.(windows.Win32FileAttributeData)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
return f1.FileAttributes == f2.FileAttributes, nil
|
||||
}
|
||||
|
||||
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||
// TODO: Use windows equivalent
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isLinked(os.FileInfo) bool {
|
||||
return false
|
||||
}
|
103
vendor/github.com/containerd/continuity/fs/dtype_linux.go
generated
vendored
Normal file
103
vendor/github.com/containerd/continuity/fs/dtype_linux.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func locateDummyIfEmpty(path string) (string, error) {
|
||||
children, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(children) != 0 {
|
||||
return "", nil
|
||||
}
|
||||
dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := dummyFile.Name()
|
||||
err = dummyFile.Close()
|
||||
return name, err
|
||||
}
|
||||
|
||||
// SupportsDType returns whether the filesystem mounted on path supports d_type
|
||||
func SupportsDType(path string) (bool, error) {
|
||||
// locate dummy so that we have at least one dirent
|
||||
dummy, err := locateDummyIfEmpty(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if dummy != "" {
|
||||
defer os.Remove(dummy)
|
||||
}
|
||||
|
||||
visited := 0
|
||||
supportsDType := true
|
||||
fn := func(ent *syscall.Dirent) bool {
|
||||
visited++
|
||||
if ent.Type == syscall.DT_UNKNOWN {
|
||||
supportsDType = false
|
||||
// stop iteration
|
||||
return true
|
||||
}
|
||||
// continue iteration
|
||||
return false
|
||||
}
|
||||
if err = iterateReadDir(path, fn); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if visited == 0 {
|
||||
return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
|
||||
}
|
||||
return supportsDType, nil
|
||||
}
|
||||
|
||||
func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
fd := int(d.Fd())
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
nbytes, err := syscall.ReadDirent(fd, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nbytes == 0 {
|
||||
break
|
||||
}
|
||||
for off := 0; off < nbytes; {
|
||||
ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
|
||||
if stop := fn(ent); stop {
|
||||
return nil
|
||||
}
|
||||
off += int(ent.Reclen)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
38
vendor/github.com/containerd/continuity/fs/du.go
generated
vendored
Normal file
38
vendor/github.com/containerd/continuity/fs/du.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import "context"
|
||||
|
||||
// Usage of disk information
|
||||
type Usage struct {
|
||||
Inodes int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
// DiskUsage counts the number of inodes and disk usage for the resources under
|
||||
// path.
|
||||
func DiskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||
return diskUsage(ctx, roots...)
|
||||
}
|
||||
|
||||
// DiffUsage counts the numbers of inodes and disk usage in the
|
||||
// diff between the 2 directories. The first path is intended
|
||||
// as the base directory and the second as the changed directory.
|
||||
func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||
return diffUsage(ctx, a, b)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user