Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24e691925c |
@@ -1,20 +0,0 @@
|
|||||||
comment: false
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
range: "40...100"
|
|
||||||
precision: 1
|
|
||||||
status:
|
|
||||||
patch:
|
|
||||||
default:
|
|
||||||
informational: true
|
|
||||||
project:
|
|
||||||
default:
|
|
||||||
informational: true
|
|
||||||
|
|
||||||
github_checks:
|
|
||||||
annotations: false
|
|
||||||
|
|
||||||
ignore:
|
|
||||||
- "**.pb.go"
|
|
||||||
- "**_mocked.go"
|
|
||||||
- "**/mocks/*"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
version = 1
|
|
||||||
|
|
||||||
exclude_patterns = ["**/*.pb.go"]
|
|
||||||
test_patterns = ["**/*_test.go"]
|
|
||||||
|
|
||||||
[[analyzers]]
|
|
||||||
name = "go"
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[analyzers.meta]
|
|
||||||
import_paths = ["github.com/syncthing/syncthing"]
|
|
||||||
build_tags = ["noassets"]
|
|
||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
# Except the dependencies, which we leave alone
|
# Except the dependencies, which we leave alone
|
||||||
vendor/** -text=auto
|
Godeps/** -text=auto
|
||||||
|
|
||||||
# Diffs on these files are meaningless
|
# Diffs on these files are meaningless
|
||||||
|
gui.files.go -diff
|
||||||
*.svg -diff
|
*.svg -diff
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
/AUTHORS @calmh
|
|
||||||
/*.md @calmh
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
github: syncthing
|
|
||||||
custom: "https://syncthing.net/donations/"
|
|
||||||
|
|
||||||
# patreon: # Replace with a single Patreon username
|
|
||||||
# open_collective: # Replace with a single Open Collective username
|
|
||||||
# ko_fi: # Replace with a single Ko-fi username
|
|
||||||
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
# liberapay: # Replace with a single Liberapay username
|
|
||||||
# issuehunt: # Replace with a single IssueHunt username
|
|
||||||
# otechie: # Replace with a single Otechie username
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: If you're just not sure how to do something, see "ask a question".
|
|
||||||
labels: enhancement, needs-triage
|
|
||||||
---
|
|
||||||
|
|
||||||
### Include required information
|
|
||||||
|
|
||||||
Please be sure to include at least:
|
|
||||||
|
|
||||||
- what problem your new feature would solve
|
|
||||||
- how or why you think it is generally useful (i.e., not just for you)
|
|
||||||
- what alternatives or workarounds you considered
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: If you're actually looking for support, see "ask a question".
|
|
||||||
labels: bug, needs-triage
|
|
||||||
---
|
|
||||||
|
|
||||||
### Does your log mention database corruption?
|
|
||||||
|
|
||||||
If your Syncthing log reports panics because of database corruption it is
|
|
||||||
most likely a fault with your system's storage or memory. Affected log
|
|
||||||
entries will contain lines starting with `panic: leveldb`. You will need to
|
|
||||||
delete the index database to clear this, by running `syncthing
|
|
||||||
-reset-database`.
|
|
||||||
|
|
||||||
### Include required information
|
|
||||||
|
|
||||||
Please be sure to include at least:
|
|
||||||
|
|
||||||
- which version of Syncthing and what operating system you are using
|
|
||||||
- browser and version, if applicable
|
|
||||||
- what happened,
|
|
||||||
- what you expected to happen instead, and
|
|
||||||
- any steps to reproduce the problem.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: I need help / I have a question
|
|
||||||
url: https://forum.syncthing.net/
|
|
||||||
about: Ask questions, get support, and discuss with other community members.
|
|
||||||
- name: Android issues
|
|
||||||
url: https://github.com/syncthing/syncthing-android/issues/
|
|
||||||
about: The Android app has its own issue tracker.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
### Purpose
|
|
||||||
|
|
||||||
Describe the purpose of this change. If there is an existing issue that is
|
|
||||||
resolved by this pull request, ensure that the commit subject is on the form
|
|
||||||
`Some short description (fixes #1234)` where 1234 is the issue number.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
Describe what testing has been done, and how the reviewer can test the change
|
|
||||||
if new tests are not included.
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
If this is a GUI change, include screenshots of the change. If not, please
|
|
||||||
feel free to just delete this section.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
If this is a user visible change (including API and protocol changes), add a link here
|
|
||||||
to the corresponding pull request on https://github.com/syncthing/docs or describe
|
|
||||||
the documentation changes necessary.
|
|
||||||
|
|
||||||
## Authorship
|
|
||||||
|
|
||||||
Your name and email will be added automatically to the AUTHORS file
|
|
||||||
based on the commit metadata.
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
If you believe that you've found a Syncthing-related security vulnerability,
|
|
||||||
please report it by sending email to the address security@syncthing.net. The
|
|
||||||
[PGP key for security@syncthing.net
|
|
||||||
(B683AD7B76CAB013)](https://syncthing.net/security-key.txt) can be used to
|
|
||||||
send encrypted mail or to verify responses received from that address.
|
|
||||||
|
|
||||||
You can read more about Syncthing security at
|
|
||||||
https://syncthing.net/security/.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
name: Build Infrastructure Images
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- infrastructure
|
|
||||||
|
|
||||||
env:
|
|
||||||
GO_VERSION: "^1.21.0"
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
BUILD_USER: docker
|
|
||||||
BUILD_HOST: github.syncthing.net
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
docker-syncthing:
|
|
||||||
name: Build and push Docker images
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: docker
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
pkg:
|
|
||||||
- stcrashreceiver
|
|
||||||
- strelaypoolsrv
|
|
||||||
- stupgrades
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build binaries
|
|
||||||
run: |
|
|
||||||
for arch in arm64 amd64; do
|
|
||||||
go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
|
|
||||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile.${{ matrix.pkg }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: syncthing/${{ matrix.pkg }}:latest,syncthing/${{ matrix.pkg }}:${{ github.sha }}
|
|
||||||
@@ -1,789 +0,0 @@
|
|||||||
name: Build Syncthing
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
schedule:
|
|
||||||
# Run nightly build at 05:00 UTC
|
|
||||||
- cron: '00 05 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
# The go version to use for builds. We set check-latest to true when
|
|
||||||
# installing, so we get the latest patch version that matches the
|
|
||||||
# expression.
|
|
||||||
GO_VERSION: "~1.21.0"
|
|
||||||
|
|
||||||
# Optimize compatibility on the slow archictures.
|
|
||||||
GO386: softfloat
|
|
||||||
GOARM: "5"
|
|
||||||
GOMIPS: softfloat
|
|
||||||
|
|
||||||
# Avoid hilarious amounts of obscuring log output when running tests.
|
|
||||||
LOGGER_DISCARD: "1"
|
|
||||||
|
|
||||||
# Our build metadata
|
|
||||||
BUILD_USER: builder
|
|
||||||
BUILD_HOST: github.syncthing.net
|
|
||||||
|
|
||||||
# A note on actions and third party code... The actions under actions/ (like
|
|
||||||
# `uses: actions/checkout`) are maintained by GitHub, and we need to trust
|
|
||||||
# GitHub to maintain their code and infrastructure or we're in deep shit in
|
|
||||||
# general. The same doesn't necessarily apply to other actions authors, so
|
|
||||||
# some care needs to be taken when adding steps, especially in the paths
|
|
||||||
# that lead up to code being packaged and signed.
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
#
|
|
||||||
# Tests for all platforms. Runs a matrix build on Windows, Linux and Mac,
|
|
||||||
# with the list of expected supported Go versions (current, previous).
|
|
||||||
#
|
|
||||||
|
|
||||||
build-test:
|
|
||||||
name: Build and test
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
runner: ["windows-latest", "ubuntu-latest", "macos-latest"]
|
|
||||||
# The oldest version in this list should match what we have in our go.mod.
|
|
||||||
# Variables don't seem to be supported here, or we could have done something nice.
|
|
||||||
go: ["1.20", "1.21"]
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
steps:
|
|
||||||
- name: Set git to use LF
|
|
||||||
if: matrix.runner == 'windows-latest'
|
|
||||||
# Without this, the Windows checkout will happen with CRLF line
|
|
||||||
# endings, which is fine for the source code but messes up tests
|
|
||||||
# that depend on data on disk being as expected. Ideally, those
|
|
||||||
# tests should be fixed, but not today.
|
|
||||||
run: |
|
|
||||||
git config --global core.autocrlf false
|
|
||||||
git config --global core.eol lf
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go }}
|
|
||||||
cache: true
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
go run build.go
|
|
||||||
|
|
||||||
- name: Install go-test-json-to-loki
|
|
||||||
run: |
|
|
||||||
go install calmh.dev/go-test-json-to-loki@latest
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
go run build.go test | go-test-json-to-loki
|
|
||||||
env:
|
|
||||||
GOFLAGS: "-json"
|
|
||||||
LOKI_URL: ${{ secrets.LOKI_URL }}
|
|
||||||
LOKI_USER: ${{ secrets.LOKI_USER }}
|
|
||||||
LOKI_PASSWORD: ${{ secrets.LOKI_PASSWORD }}
|
|
||||||
LOKI_LABELS: "go=${{ matrix.go }},runner=${{ matrix.runner }},repo=${{ github.repository }},ref=${{ github.ref }}"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Meta checks for formatting, copyright, etc
|
|
||||||
#
|
|
||||||
|
|
||||||
correctness:
|
|
||||||
name: Check correctness
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Check correctness
|
|
||||||
run: |
|
|
||||||
go test -v ./meta
|
|
||||||
|
|
||||||
#
|
|
||||||
# The basic checks job is a virtual one that depends on the matrix tests,
|
|
||||||
# the correctness checks, and various builds that we always do. This makes
|
|
||||||
# it easy to have the PR process have a single test as a gatekeeper for
|
|
||||||
# merging, instead of having to add all the matrix tests and update them
|
|
||||||
# each time the version changes. (The top level test is not available for
|
|
||||||
# choosing there, only the matrix "children".)
|
|
||||||
#
|
|
||||||
|
|
||||||
basics:
|
|
||||||
name: Basic checks passed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- build-test
|
|
||||||
- correctness
|
|
||||||
- package-linux
|
|
||||||
- package-cross
|
|
||||||
- package-source
|
|
||||||
- package-debian
|
|
||||||
- govulncheck
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
#
|
|
||||||
# Windows
|
|
||||||
#
|
|
||||||
|
|
||||||
package-windows:
|
|
||||||
name: Package for Windows
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
|
||||||
environment: signing
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Set git to use LF
|
|
||||||
# Without this, the checkout will happen with CRLF line endings,
|
|
||||||
# which is fine for the source code but messes up tests that depend
|
|
||||||
# on data on disk being as expected. Ideally, those tests should be
|
|
||||||
# fixed, but not today.
|
|
||||||
run: |
|
|
||||||
git config --global core.autocrlf false
|
|
||||||
git config --global core.eol lf
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~\AppData\Local\go-build
|
|
||||||
~\go\pkg\mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.4.0
|
|
||||||
|
|
||||||
- name: Create packages
|
|
||||||
run: |
|
|
||||||
go run build.go -goarch amd64 zip
|
|
||||||
go run build.go -goarch arm zip
|
|
||||||
go run build.go -goarch arm64 zip
|
|
||||||
go run build.go -goarch 386 zip
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
CODESIGN_SIGNTOOL: ${{ secrets.CODESIGN_SIGNTOOL }}
|
|
||||||
CODESIGN_CERTIFICATE_BASE64: ${{ secrets.CODESIGN_CERTIFICATE_BASE64 }}
|
|
||||||
CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODESIGN_CERTIFICATE_PASSWORD }}
|
|
||||||
CODESIGN_TIMESTAMP_SERVER: ${{ secrets.CODESIGN_TIMESTAMP_SERVER }}
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-windows
|
|
||||||
path: syncthing-windows-*.zip
|
|
||||||
|
|
||||||
#
|
|
||||||
# Linux
|
|
||||||
#
|
|
||||||
|
|
||||||
package-linux:
|
|
||||||
name: Package for Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Create packages
|
|
||||||
run: |
|
|
||||||
archs=$(go tool dist list | grep linux | sed 's#linux/##')
|
|
||||||
for goarch in $archs ; do
|
|
||||||
go run build.go -goarch "$goarch" tar
|
|
||||||
done
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-linux
|
|
||||||
path: syncthing-linux-*.tar.gz
|
|
||||||
|
|
||||||
#
|
|
||||||
# macOS
|
|
||||||
#
|
|
||||||
|
|
||||||
package-macos:
|
|
||||||
name: Package for macOS
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
|
||||||
environment: signing
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-package-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Import signing certificate
|
|
||||||
run: |
|
|
||||||
# Set up a run-specific keychain, making it available for the
|
|
||||||
# `codesign` tool.
|
|
||||||
umask 066
|
|
||||||
KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain
|
|
||||||
KEYCHAIN_PASSWORD=$(uuidgen)
|
|
||||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
||||||
security default-keychain -s "$KEYCHAIN_PATH"
|
|
||||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
||||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
||||||
|
|
||||||
# Import the certificate
|
|
||||||
CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
|
|
||||||
echo "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 -d -o "$CERTIFICATE_PATH"
|
|
||||||
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$DEVELOPER_ID_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
|
|
||||||
security set-key-partition-list -S apple-tool:,apple: -s -k actions "$KEYCHAIN_PATH"
|
|
||||||
|
|
||||||
# Set the codesign identity for following steps
|
|
||||||
echo "CODESIGN_IDENTITY=$CODESIGN_IDENTITY" >> $GITHUB_ENV
|
|
||||||
env:
|
|
||||||
DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}
|
|
||||||
DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
|
||||||
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
|
|
||||||
|
|
||||||
- name: Create package (amd64)
|
|
||||||
run: |
|
|
||||||
go run build.go -goarch amd64 zip
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
|
|
||||||
- name: Create package (arm64 cross)
|
|
||||||
run: |
|
|
||||||
cat <<EOT > xgo.sh
|
|
||||||
#!/bin/bash
|
|
||||||
CGO_ENABLED=1 \
|
|
||||||
CGO_CFLAGS="-target arm64-apple-macos10.15" \
|
|
||||||
CGO_LDFLAGS="-target arm64-apple-macos10.15" \
|
|
||||||
go "\$@"
|
|
||||||
EOT
|
|
||||||
chmod 755 xgo.sh
|
|
||||||
go run build.go -gocmd ./xgo.sh -goarch arm64 zip
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
|
|
||||||
- name: Create package (universal)
|
|
||||||
run: |
|
|
||||||
rm -rf _tmp
|
|
||||||
mkdir _tmp
|
|
||||||
pushd _tmp
|
|
||||||
|
|
||||||
unzip ../syncthing-macos-amd64-*.zip
|
|
||||||
unzip ../syncthing-macos-arm64-*.zip
|
|
||||||
lipo -create syncthing-macos-amd64-*/syncthing syncthing-macos-arm64-*/syncthing -o syncthing
|
|
||||||
|
|
||||||
amd64=(syncthing-macos-amd64-*)
|
|
||||||
universal="${amd64/amd64/universal}"
|
|
||||||
mv "$amd64" "$universal"
|
|
||||||
mv syncthing "$universal"
|
|
||||||
zip -r "../$universal.zip" "$universal"
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-macos
|
|
||||||
path: syncthing-*.zip
|
|
||||||
|
|
||||||
notarize-macos:
|
|
||||||
name: Notarize for macOS
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
|
||||||
environment: signing
|
|
||||||
needs:
|
|
||||||
- package-macos
|
|
||||||
- basics
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-macos
|
|
||||||
|
|
||||||
- name: Notarize binaries
|
|
||||||
run: |
|
|
||||||
APPSTORECONNECT_API_KEY_PATH="$RUNNER_TEMP/apikey.p8"
|
|
||||||
echo "$APPSTORECONNECT_API_KEY" | base64 -d -o "$APPSTORECONNECT_API_KEY_PATH"
|
|
||||||
for file in syncthing-macos-*.zip ; do
|
|
||||||
xcrun notarytool submit \
|
|
||||||
-k "$APPSTORECONNECT_API_KEY_PATH" \
|
|
||||||
-d "$APPSTORECONNECT_API_KEY_ID" \
|
|
||||||
-i "$APPSTORECONNECT_API_KEY_ISSUER" \
|
|
||||||
$file
|
|
||||||
done
|
|
||||||
env:
|
|
||||||
APPSTORECONNECT_API_KEY: ${{ secrets.APPSTORECONNECT_API_KEY }}
|
|
||||||
APPSTORECONNECT_API_KEY_ID: ${{ secrets.APPSTORECONNECT_API_KEY_ID }}
|
|
||||||
APPSTORECONNECT_API_KEY_ISSUER: ${{ secrets.APPSTORECONNECT_API_KEY_ISSUER }}
|
|
||||||
|
|
||||||
#
|
|
||||||
# Cross compile other unixes
|
|
||||||
#
|
|
||||||
|
|
||||||
package-cross:
|
|
||||||
name: Package cross compiled
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-cross-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Create packages
|
|
||||||
run: |
|
|
||||||
platforms=$(go tool dist list \
|
|
||||||
| grep -v aix/ppc64 \
|
|
||||||
| grep -v android/ \
|
|
||||||
| grep -v darwin/ \
|
|
||||||
| grep -v ios/ \
|
|
||||||
| grep -v js/ \
|
|
||||||
| grep -v linux/ \
|
|
||||||
| grep -v nacl/ \
|
|
||||||
| grep -v plan9/ \
|
|
||||||
| grep -v windows/ \
|
|
||||||
| grep -v /wasm \
|
|
||||||
)
|
|
||||||
|
|
||||||
for plat in $platforms; do
|
|
||||||
goos="${plat%/*}"
|
|
||||||
goarch="${plat#*/}"
|
|
||||||
if ! go run build.go -goos "$goos" -goarch "$goarch" tar ; then
|
|
||||||
echo "*** $plat failed ***"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-other
|
|
||||||
path: syncthing-*.tar.gz
|
|
||||||
|
|
||||||
#
|
|
||||||
# Source
|
|
||||||
#
|
|
||||||
|
|
||||||
package-source:
|
|
||||||
name: Package source code
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: Package source
|
|
||||||
run: |
|
|
||||||
version=$(go run build.go version)
|
|
||||||
echo "$version" > RELEASE
|
|
||||||
|
|
||||||
go mod vendor
|
|
||||||
go run build.go assets
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
tar c -z -f "syncthing-source-$version.tar.gz" \
|
|
||||||
--exclude .git \
|
|
||||||
syncthing
|
|
||||||
|
|
||||||
mv "syncthing-source-$version.tar.gz" syncthing
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-source
|
|
||||||
path: syncthing-source-*.tar.gz
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sign binaries for auto upgrade, generate ASC signature files
|
|
||||||
#
|
|
||||||
|
|
||||||
sign-for-upgrade:
|
|
||||||
name: Sign for upgrade
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/heads/release-'))
|
|
||||||
environment: signing
|
|
||||||
needs:
|
|
||||||
- basics
|
|
||||||
- package-windows
|
|
||||||
- package-linux
|
|
||||||
- package-macos
|
|
||||||
- package-cross
|
|
||||||
- package-source
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: syncthing/release-tools
|
|
||||||
path: tools
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
|
|
||||||
- name: Install signing tool
|
|
||||||
run: |
|
|
||||||
go install ./cmd/stsigtool
|
|
||||||
|
|
||||||
- name: Sign archives
|
|
||||||
run: |
|
|
||||||
export PRIVATE_KEY="$RUNNER_TEMP/privkey.pem"
|
|
||||||
export PATH="$PATH:$(go env GOPATH)/bin"
|
|
||||||
echo "$STSIGTOOL_PRIVATE_KEY" | base64 -d > "$PRIVATE_KEY"
|
|
||||||
mkdir packages
|
|
||||||
mv packages-*/* packages
|
|
||||||
pushd packages
|
|
||||||
"$GITHUB_WORKSPACE/tools/sign-only"
|
|
||||||
rm -f "$PRIVATE_KEY"
|
|
||||||
env:
|
|
||||||
STSIGTOOL_PRIVATE_KEY: ${{ secrets.STSIGTOOL_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Create and sign .asc files
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt -y install gnupg
|
|
||||||
|
|
||||||
export SIGNING_KEY="$RUNNER_TEMP/gpg-secret.asc"
|
|
||||||
echo "$GNUPG_SIGNING_KEY_BASE64" | base64 -d > "$SIGNING_KEY"
|
|
||||||
gpg --import < "$SIGNING_KEY"
|
|
||||||
|
|
||||||
pushd packages
|
|
||||||
files=(*.tar.gz *.zip)
|
|
||||||
sha1sum "${files[@]}" | gpg --clearsign > sha1sum.txt.asc
|
|
||||||
sha256sum "${files[@]}" | gpg --clearsign > sha256sum.txt.asc
|
|
||||||
gpg --sign --armour --detach syncthing-source-*.tar.gz
|
|
||||||
popd
|
|
||||||
rm -f "$SIGNING_KEY" .gnupg
|
|
||||||
env:
|
|
||||||
GNUPG_SIGNING_KEY_BASE64: ${{ secrets.GNUPG_SIGNING_KEY_BASE64 }}
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-signed
|
|
||||||
path: packages/*
|
|
||||||
|
|
||||||
#
|
|
||||||
# Debian
|
|
||||||
#
|
|
||||||
|
|
||||||
package-debian:
|
|
||||||
name: Package for Debian
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.0'
|
|
||||||
|
|
||||||
- name: Install fpm
|
|
||||||
run: |
|
|
||||||
gem install fpm
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-debian-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Package for Debian
|
|
||||||
run: |
|
|
||||||
for arch in amd64 i386 armhf armel arm64 ; do
|
|
||||||
go run build.go -no-upgrade -installsuffix=no-upgrade -goarch "$arch" deb
|
|
||||||
done
|
|
||||||
env:
|
|
||||||
BUILD_USER: debian
|
|
||||||
|
|
||||||
- name: Archive artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: debian-packages
|
|
||||||
path: "*.deb"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Nightlies
|
|
||||||
#
|
|
||||||
|
|
||||||
publish-nightly:
|
|
||||||
name: Publish nightly build
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release-nightly')
|
|
||||||
environment: signing
|
|
||||||
needs:
|
|
||||||
- sign-for-upgrade
|
|
||||||
- notarize-macos
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: syncthing/release-tools
|
|
||||||
path: tools
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-signed
|
|
||||||
path: packages
|
|
||||||
|
|
||||||
- name: Create release json
|
|
||||||
run: |
|
|
||||||
cd packages
|
|
||||||
"$GITHUB_WORKSPACE/tools/generate-release-json" "$BASE_URL" > nightly.json
|
|
||||||
env:
|
|
||||||
BASE_URL: https://syncthing.ams3.digitaloceanspaces.com/nightly/
|
|
||||||
|
|
||||||
- name: Push artifacts
|
|
||||||
uses: docker://docker.io/rclone/rclone:latest
|
|
||||||
env:
|
|
||||||
RCLONE_CONFIG_SPACES_TYPE: s3
|
|
||||||
RCLONE_CONFIG_SPACES_PROVIDER: DigitalOcean
|
|
||||||
RCLONE_CONFIG_SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_KEY }}
|
|
||||||
RCLONE_CONFIG_SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET }}
|
|
||||||
RCLONE_CONFIG_SPACES_ENDPOINT: ams3.digitaloceanspaces.com
|
|
||||||
RCLONE_CONFIG_SPACES_ACL: public-read
|
|
||||||
with:
|
|
||||||
args: sync packages spaces:syncthing/nightly
|
|
||||||
|
|
||||||
#
|
|
||||||
# Push release artifacts to Spaces
|
|
||||||
#
|
|
||||||
|
|
||||||
publish-release-files:
|
|
||||||
name: Publish release files
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/release'
|
|
||||||
environment: signing
|
|
||||||
needs:
|
|
||||||
- sign-for-upgrade
|
|
||||||
- package-debian
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download signed packages
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: packages-signed
|
|
||||||
path: packages
|
|
||||||
|
|
||||||
- name: Download debian packages
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: debian-packages
|
|
||||||
path: packages
|
|
||||||
|
|
||||||
- name: Set version
|
|
||||||
run: |
|
|
||||||
version=$(go run build.go version)
|
|
||||||
echo "VERSION=$version" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Push to Spaces (${{ env.VERSION }})
|
|
||||||
uses: docker://docker.io/rclone/rclone:latest
|
|
||||||
env:
|
|
||||||
RCLONE_CONFIG_SPACES_TYPE: s3
|
|
||||||
RCLONE_CONFIG_SPACES_PROVIDER: DigitalOcean
|
|
||||||
RCLONE_CONFIG_SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_KEY }}
|
|
||||||
RCLONE_CONFIG_SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET }}
|
|
||||||
RCLONE_CONFIG_SPACES_ENDPOINT: ams3.digitaloceanspaces.com
|
|
||||||
RCLONE_CONFIG_SPACES_ACL: public-read
|
|
||||||
with:
|
|
||||||
args: sync packages spaces:syncthing/release/${{ env.VERSION }}
|
|
||||||
|
|
||||||
- name: Push to Spaces (latest)
|
|
||||||
uses: docker://docker.io/rclone/rclone:latest
|
|
||||||
env:
|
|
||||||
RCLONE_CONFIG_SPACES_TYPE: s3
|
|
||||||
RCLONE_CONFIG_SPACES_PROVIDER: DigitalOcean
|
|
||||||
RCLONE_CONFIG_SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_KEY }}
|
|
||||||
RCLONE_CONFIG_SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET }}
|
|
||||||
RCLONE_CONFIG_SPACES_ENDPOINT: ams3.digitaloceanspaces.com
|
|
||||||
RCLONE_CONFIG_SPACES_ACL: public-read
|
|
||||||
with:
|
|
||||||
args: sync spaces:syncthing/release/${{ env.VERSION }} spaces:syncthing/release/latest
|
|
||||||
|
|
||||||
#
|
|
||||||
# Build and push to Docker Hub
|
|
||||||
#
|
|
||||||
|
|
||||||
docker-syncthing:
|
|
||||||
name: Build and push Docker images
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-'))
|
|
||||||
environment: docker
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
pkg:
|
|
||||||
- syncthing
|
|
||||||
- strelaysrv
|
|
||||||
- stdiscosrv
|
|
||||||
include:
|
|
||||||
- pkg: syncthing
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
image: syncthing/syncthing
|
|
||||||
- pkg: strelaysrv
|
|
||||||
dockerfile: Dockerfile.strelaysrv
|
|
||||||
image: syncthing/relaysrv
|
|
||||||
- pkg: stdiscosrv
|
|
||||||
dockerfile: Dockerfile.stdiscosrv
|
|
||||||
image: syncthing/discosrv
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
|
|
||||||
|
|
||||||
- name: Build binaries
|
|
||||||
run: |
|
|
||||||
for arch in amd64 arm64 arm; do
|
|
||||||
go run build.go -goos linux -goarch "$arch" -no-upgrade build ${{ matrix.pkg }}
|
|
||||||
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
|
|
||||||
done
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
BUILD_USER: docker
|
|
||||||
|
|
||||||
- name: Check if we will be able to push images
|
|
||||||
run: |
|
|
||||||
if [[ "${{ secrets.DOCKERHUB_TOKEN }}" != "" ]]; then
|
|
||||||
echo "DOCKER_PUSH=true" >> $GITHUB_ENV;
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
if: env.DOCKER_PUSH == 'true'
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Set version tags
|
|
||||||
run: |
|
|
||||||
version=$(go run build.go version)
|
|
||||||
version=${version#v}
|
|
||||||
if [[ $version == @([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]).@([0-9]|[0-9][0-9]) ]] ; then
|
|
||||||
echo Release version, pushing to :latest and version tags
|
|
||||||
major=${version%.*.*}
|
|
||||||
minor=${version%.*}
|
|
||||||
tags=${{ matrix.image }}:$version,${{ matrix.image }}:$major,${{ matrix.image }}:$minor,${{ matrix.image }}:latest
|
|
||||||
elif [[ $version == *-rc.@([0-9]|[0-9][0-9]) ]] ; then
|
|
||||||
echo Release candidate, pushing to :rc
|
|
||||||
tags=${{ matrix.image }}:rc
|
|
||||||
else
|
|
||||||
echo Development version, pushing to :edge
|
|
||||||
tags=${{ matrix.image }}:edge
|
|
||||||
fi
|
|
||||||
echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ${{ matrix.dockerfile }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/7
|
|
||||||
push: ${{ env.DOCKER_PUSH == 'true' }}
|
|
||||||
tags: ${{ env.DOCKER_TAGS }}
|
|
||||||
|
|
||||||
#
|
|
||||||
# Check for known vulnerabilities in Go dependencies
|
|
||||||
#
|
|
||||||
|
|
||||||
govulncheck:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Run govulncheck
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
cache: false
|
|
||||||
check-latest: true
|
|
||||||
|
|
||||||
- name: run govulncheck
|
|
||||||
run: |
|
|
||||||
go run build.go assets
|
|
||||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
||||||
govulncheck ./...
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
name: Trigger nightly build & release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
# Run nightly build at 01:00 UTC
|
|
||||||
- cron: '00 01 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
trigger-nightly:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Push to release-nightly to trigger build
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- run: |
|
|
||||||
git push origin main:release-nightly
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
name: Update translations and documentation
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '42 3 * * 1'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
update_transifex_docs:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Update translations and documentation
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: ^1.19.6
|
|
||||||
- run: |
|
|
||||||
set -euo pipefail
|
|
||||||
git config --global user.name 'Syncthing Release Automation'
|
|
||||||
git config --global user.email 'release@syncthing.net'
|
|
||||||
bash build.sh translate
|
|
||||||
bash build.sh prerelease
|
|
||||||
git push
|
|
||||||
env:
|
|
||||||
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
|
|
||||||
+4
-11
@@ -1,22 +1,15 @@
|
|||||||
/syncthing
|
./syncthing
|
||||||
/stdiscosrv
|
|
||||||
syncthing.exe
|
syncthing.exe
|
||||||
stdiscosrv.exe
|
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
*.asc
|
*.asc
|
||||||
*.deb
|
|
||||||
.jshintrc
|
.jshintrc
|
||||||
coverage.out
|
coverage.out
|
||||||
files/pidx
|
files/pidx
|
||||||
bin
|
bin
|
||||||
perfstats*.csv
|
perfstats*.csv
|
||||||
coverage.xml
|
coverage.xml
|
||||||
syncthing.sig
|
!gui/scripts/syncthing
|
||||||
|
syncthing.md5
|
||||||
|
syncthing.exe.md5
|
||||||
RELEASE
|
RELEASE
|
||||||
deb
|
|
||||||
*.bz2
|
|
||||||
/repos
|
|
||||||
/proto/scripts/protoc-gen-gosyncthing
|
|
||||||
/gui/next-gen-gui
|
|
||||||
.idea
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
linters-settings:
|
|
||||||
maligned:
|
|
||||||
suggest-new: true
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
- goimports
|
|
||||||
- depguard
|
|
||||||
- lll
|
|
||||||
- gochecknoinits
|
|
||||||
- gochecknoglobals
|
|
||||||
- gofmt
|
|
||||||
- scopelint
|
|
||||||
- gocyclo
|
|
||||||
- funlen
|
|
||||||
- wsl
|
|
||||||
- gocognit
|
|
||||||
- godox
|
|
||||||
|
|
||||||
service:
|
|
||||||
golangci-lint-version: 1.21.x
|
|
||||||
prepare:
|
|
||||||
- rm -f go.sum # 1.12 -> 1.13 issues with QUIC-go
|
|
||||||
- GO111MODULE=on go mod vendor
|
|
||||||
- go run build.go assets
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
line_ending: lf
|
|
||||||
formatter:
|
|
||||||
type: basic
|
|
||||||
retain_line_breaks: true
|
|
||||||
@@ -1,323 +1,56 @@
|
|||||||
# This is the official list of Syncthing authors for copyright purposes.
|
# This is the official list of Syncthing authors for copyright purposes.
|
||||||
#
|
|
||||||
# THIS FILE IS MOSTLY AUTO GENERATED. IF YOU'VE MADE A COMMIT TO THE
|
|
||||||
# REPOSITORY YOU WILL BE ADDED HERE AUTOMATICALLY WITHOUT THE NEED FOR
|
|
||||||
# ANY MANUAL ACTION.
|
|
||||||
#
|
|
||||||
# That said, you are welcome to correct your name or add a nickname / GitHub
|
|
||||||
# user name as appropriate. The format is:
|
|
||||||
#
|
|
||||||
# Name Name Name (nickname) <email1@example.com> <email2@example.com>
|
|
||||||
#
|
|
||||||
# The in-GUI authors list is periodically automatically updated from the
|
|
||||||
# contents of this file.
|
|
||||||
#
|
|
||||||
|
|
||||||
Aaron Bieber (qbit) <qbit@deftly.net>
|
Aaron Bieber <qbit@deftly.net>
|
||||||
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
|
Alexander Graf <register-github@alex-graf.de>
|
||||||
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
|
Andrew Dunham <andrew@du.nham.ca>
|
||||||
Alan Pope <alan@popey.com>
|
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||||
Alberto Donato <albertodonato@users.noreply.github.com>
|
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||||
Aleksey Vasenev <margtu-fivt@ya.ru>
|
Ben Curthoys <ben@bencurthoys.com>
|
||||||
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
|
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||||
Alex Lindeman <139387+aelindeman@users.noreply.github.com>
|
Ben Sidhom <bsidhom@gmail.com>
|
||||||
Alex Xu <alex.hello71@gmail.com>
|
Brandon Philips <brandon@ifup.org>
|
||||||
Alexander Graf (alex2108) <register-github@alex-graf.de>
|
Brendan Long <self@brendanlong.com>
|
||||||
Alexander Seiler <seileralex@gmail.com>
|
Caleb Callaway <enlightened.despot@gmail.com>
|
||||||
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
|
Carsten Hagemann <moter8@gmail.com>
|
||||||
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
|
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
||||||
Aman Gupta <aman@tmm1.net>
|
Chris Joel <chris@scriptolo.gy>
|
||||||
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
|
Colin Kennedy <moshen.colin@gmail.com>
|
||||||
Andreas Sommer <andreas.sommer87@googlemail.com>
|
Daniel Martí <mvdan@mvdan.cc>
|
||||||
andresvia <andres.via@gmail.com>
|
Dennis Wilson <dw@risu.io>
|
||||||
Andrew Dunham (andrew-d) <andrew@du.nham.ca>
|
Dominik Heidler <dominik@heidler.eu>
|
||||||
Andrew Meyer <andrewm.bpi@gmail.com>
|
Elias Jarlebring <jarlebring@gmail.com>
|
||||||
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
|
Emil Hessman <emil@hessman.se>
|
||||||
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
|
Federico Castagnini <federico.castagnini@gmail.com>
|
||||||
André Colomb (acolomb) <src@andre.colomb.de> <github.com@andre.colomb.de>
|
Felix Ableitner <me@nutomic.com>
|
||||||
andyleap <andyleap@gmail.com>
|
Felix Unterpaintner <bigbear2nd@gmail.com>
|
||||||
Anjan Momi <anjan@momi.ca>
|
Francois-Xavier Gsell <fxgsell@gmail.com>
|
||||||
Anthony Goeckner <agoeckner@users.noreply.github.com>
|
Gilli Sigurdsson <gilli@vx.is>
|
||||||
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
|
Jakob Borg <jakob@nym.se>
|
||||||
Antony Male (canton7) <antony.male@gmail.com>
|
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||||
Anur <anurnomeru@163.com>
|
Jaroslav Malec <dzardacz@gmail.com>
|
||||||
Aranjedeath <Aranjedeath@users.noreply.github.com>
|
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||||
Arkadiusz Tymiński <gevleeog@gmail.com>
|
Jochen Voss <voss@seehuhn.de>
|
||||||
Aroun <login@b-vo.fr>
|
Johan Vromans <jvromans@squirrel.nl>
|
||||||
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
Karol Różycki <rozycki.karol@gmail.com>
|
||||||
Artur Zubilewicz <AkaZecik@users.noreply.github.com>
|
Ken'ichi Kamada <kamada@nanohz.org>
|
||||||
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com> <github@audrius.rocks>
|
Lode Hoste <zillode@zillode.be>
|
||||||
Aurélien Rainone <476650+arl@users.noreply.github.com>
|
Lord Landon Agahnim <lordlandon@gmail.com>
|
||||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
|
||||||
Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
|
Marc Pujol <kilburn@la3.org>
|
||||||
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||||
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
Michael Tilli <pyfisch@gmail.com>
|
||||||
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
|
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||||
Benedikt Heine (bebehei) <bebe@bebehei.de>
|
Peter Hoeg <peter@speartail.com>
|
||||||
Benedikt Morbach <benedikt.morbach@googlemail.com>
|
Philippe Schommers <philippe@schommers.be>
|
||||||
Benjamin Nater <17193640+bn4t@users.noreply.github.com>
|
Phill Luby <phill.luby@newredo.com>
|
||||||
Benno Fünfstück <benno.fuenfstueck@gmail.com>
|
Piotr Bejda <piotrb10@gmail.com>
|
||||||
Benny Ng (tpng) <benny.tpng@gmail.com>
|
Ryan Sullivan <kayoticsully@gmail.com>
|
||||||
boomsquared <54829195+boomsquared@users.noreply.github.com>
|
Sergey Mishin <ralder@yandex.ru>
|
||||||
Boqin Qin <bobbqqin@bupt.edu.cn>
|
Stefan Tatschner <stefan@sevenbyte.org>
|
||||||
Boris Rybalkin <ribalkin@gmail.com>
|
Tim Abell <tim@timwise.co.uk>
|
||||||
Brandon Philips (philips) <brandon@ifup.org>
|
Tobias Nygren <tnn@nygren.pp.se>
|
||||||
Brendan Long (brendanlong) <self@brendanlong.com>
|
Tomas Cerveny <kozec@kozec.com>
|
||||||
Brian R. Becker (brbecker) <brbecker@gmail.com>
|
Tully Robinson <tully@tojr.org>
|
||||||
bt90 <btom1990@googlemail.com>
|
Veeti Paananen <veeti.paananen@rojekti.fi>
|
||||||
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
|
Vil Brekin <vilbrekin@gmail.com>
|
||||||
Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
|
||||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
|
||||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
|
||||||
chenrui <rui@meetup.com>
|
|
||||||
Chih-Hsuan Yen <yan12125@gmail.com> <1937689+yan12125@users.noreply.github.com>
|
|
||||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
|
||||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
|
||||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
|
||||||
Chris Tonkinson <chris@masterbran.ch>
|
|
||||||
Christian Kujau <ckujau@users.noreply.github.com>
|
|
||||||
Christian Prescott <me@christianprescott.com>
|
|
||||||
chucic <chucic@seznam.cz>
|
|
||||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
|
||||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
|
||||||
cui fliter <imcusg@gmail.com>
|
|
||||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
|
||||||
Dale Visser <dale.visser@live.com>
|
|
||||||
Dan <benda.daniel@gmail.com>
|
|
||||||
Daniel Barczyk <46358936+DanielBarczyk@users.noreply.github.com>
|
|
||||||
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
|
||||||
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
|
|
||||||
Daniel Martí (mvdan) <mvdan@mvdan.cc>
|
|
||||||
Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
|
|
||||||
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
|
|
||||||
deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
|
|
||||||
Denis A. (dva) <denisva@gmail.com>
|
|
||||||
Dennis Wilson (snnd) <dw@risu.io>
|
|
||||||
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
|
||||||
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
|
|
||||||
derekriemer <derek.riemer@colorado.edu>
|
|
||||||
desbma <desbma@users.noreply.github.com>
|
|
||||||
Devon G. Redekopp <devon@redekopp.com>
|
|
||||||
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
|
||||||
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
|
|
||||||
Domenic Horner <domenic@tgxn.net>
|
|
||||||
Dominik Heidler (asdil12) <dominik@heidler.eu>
|
|
||||||
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
|
|
||||||
Elliot Huffman <thelich2@gmail.com>
|
|
||||||
Emil Hessman (ceh) <emil@hessman.se>
|
|
||||||
Emil Lundberg <emil@emlun.se>
|
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
|
||||||
entity0xfe <109791748+entity0xfe@users.noreply.github.com> <entity0xfe@my.domain>
|
|
||||||
Eric Lesiuta <elesiuta@gmail.com>
|
|
||||||
Eric P <eric@kastelo.net>
|
|
||||||
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
|
|
||||||
Evan Spensley <94762716+0evan@users.noreply.github.com>
|
|
||||||
Evgeny Kuznetsov <evgeny@kuznetsov.md>
|
|
||||||
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
|
|
||||||
Felix <53702818+f-eliks@users.noreply.github.com>
|
|
||||||
Felix Ableitner (Nutomic) <me@nutomic.com>
|
|
||||||
Felix Lampe <mail@flampe.de>
|
|
||||||
Felix Unterpaintner (bigbear2nd) <bigbear2nd@gmail.com>
|
|
||||||
Francois-Xavier Gsell (zukoo) <fxgsell@gmail.com>
|
|
||||||
Frank Isemann (fti7) <frank@isemann.name>
|
|
||||||
Gahl Saraf <saraf.gahl@gmail.com> <gahl@raftt.io>
|
|
||||||
georgespatton <georgespatton@users.noreply.github.com>
|
|
||||||
ghjklw <malo@jaffre.info>
|
|
||||||
Gilli Sigurdsson (gillisig) <gilli@vx.is>
|
|
||||||
Gleb Sinyavskiy <zhulik.gleb@gmail.com>
|
|
||||||
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
|
|
||||||
greatroar <61184462+greatroar@users.noreply.github.com>
|
|
||||||
Greg <gco@jazzhaiku.com>
|
|
||||||
guangwu <guoguangwu@magic-shield.com>
|
|
||||||
Han Boetes <han@boetes.org>
|
|
||||||
HansK-p <42314815+HansK-p@users.noreply.github.com>
|
|
||||||
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
|
|
||||||
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
|
|
||||||
Hugo Locurcio <hugo.locurcio@hugo.pro>
|
|
||||||
Iain Barnett <iainspeed@gmail.com>
|
|
||||||
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
|
|
||||||
ignacy123 <ignacy.buczek@onet.pl>
|
|
||||||
Ikko Ashimine <eltociear@gmail.com>
|
|
||||||
Ilya Brin <464157+ilyabrin@users.noreply.github.com>
|
|
||||||
Iskander Sharipov (Alex) <quasilyte@gmail.com>
|
|
||||||
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
|
|
||||||
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
|
|
||||||
Jack Croft <jccroft1@users.noreply.github.com>
|
|
||||||
Jacob <jyundt@gmail.com>
|
|
||||||
Jake Peterson (acogdev) <jake@acogdev.com>
|
|
||||||
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net>
|
|
||||||
James O'Beirne <wild-github@au92.org>
|
|
||||||
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
|
||||||
janost <janost@tuta.io>
|
|
||||||
Jaroslav Lichtblau <svetlemodry@users.noreply.github.com>
|
|
||||||
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
|
|
||||||
jaseg <githubaccount@jaseg.net>
|
|
||||||
Jauder Ho <jauderho@users.noreply.github.com>
|
|
||||||
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
|
|
||||||
Jaya Kumar <jaya.kumar@ict.nl>
|
|
||||||
Jeffery To <jeffery.to@gmail.com>
|
|
||||||
jelle van der Waa <jelle@vdwaa.nl>
|
|
||||||
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
|
|
||||||
Jerry Jacobs (xor-gate) <jerry.jacobs@xor-gate.org> <xor-gate@users.noreply.github.com>
|
|
||||||
Jesse Lucas <jesse@jesselucas.com>
|
|
||||||
Jochen Voss (seehuhn) <voss@seehuhn.de>
|
|
||||||
Johan Andersson <j@i19.se>
|
|
||||||
Johan Vromans (sciurius) <jvromans@squirrel.nl>
|
|
||||||
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
|
||||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
|
||||||
Jonathan <artback@protonmail.com> <jonagn@gmail.com>
|
|
||||||
Jonathan Cross <jcross@gmail.com>
|
|
||||||
Jonta <359397+Jonta@users.noreply.github.com>
|
|
||||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
|
||||||
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
|
||||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
|
||||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
|
||||||
K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
|
|
||||||
Kalle Laine <pahakalle@protonmail.com>
|
|
||||||
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
|
|
||||||
Kebin Liu <lkebin@gmail.com>
|
|
||||||
Keith Harrison <keithh@protonmail.com>
|
|
||||||
Keith Turner <kturner@apache.org>
|
|
||||||
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
|
|
||||||
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
|
|
||||||
Kevin Allen (ironmig) <kma1660@gmail.com>
|
|
||||||
Kevin Bushiri (keevBush) <keevbush@gmail.com> <36192217+keevBush@users.noreply.github.com>
|
|
||||||
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
|
|
||||||
klemens <ka7@github.com>
|
|
||||||
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
|
|
||||||
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
|
|
||||||
Lars Lehtonen <lars.lehtonen@gmail.com>
|
|
||||||
Laurent Arnoud <laurent@spkdev.net>
|
|
||||||
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
|
|
||||||
Leo Arias (elopio) <yo@elopio.net>
|
|
||||||
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
|
||||||
Lode Hoste (Zillode) <zillode@zillode.be>
|
|
||||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
|
||||||
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
|
||||||
Lukas Lihotzki <lukas@lihotzki.de>
|
|
||||||
luzpaz <luzpaz@users.noreply.github.com>
|
|
||||||
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
|
|
||||||
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
|
|
||||||
Marc Pujol (kilburn) <kilburn@la3.org>
|
|
||||||
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
|
|
||||||
marco-m <marco.molteni@laposte.net>
|
|
||||||
Marcus Legendre <marcus.legendre@gmail.com>
|
|
||||||
Mario Majila <mariustshipichik@gmail.com>
|
|
||||||
Mark Pulford (mpx) <mark@kyne.com.au>
|
|
||||||
Martchus <martchus@gmx.net>
|
|
||||||
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
|
|
||||||
Mateusz Ż <thedead4fun@live.com>
|
|
||||||
Matic Potočnik <hairyfotr@gmail.com>
|
|
||||||
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
|
|
||||||
Matt Robenolt <matt@ydekproductions.com>
|
|
||||||
Matteo Ruina <matteo.ruina@gmail.com>
|
|
||||||
Maurizio Tomasi <ziotom78@gmail.com>
|
|
||||||
Max <github@germancoding.com>
|
|
||||||
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
|
|
||||||
MaximAL <almaximal@ya.ru>
|
|
||||||
Maxime Thirouin <m@moox.io>
|
|
||||||
mclang <1721600+mclang@users.noreply.github.com>
|
|
||||||
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
|
||||||
Michael Ploujnikov (plouj) <ploujj@gmail.com>
|
|
||||||
Michael Rienstra <mrienstra@gmail.com>
|
|
||||||
Michael Tilli (pyfisch) <pyfisch@gmail.com>
|
|
||||||
MichaIng <micha@dietpi.com>
|
|
||||||
Migelo <miha@filetki.si>
|
|
||||||
Mike Boone <mike@boonedocks.net>
|
|
||||||
MikeLund <MikeLund@users.noreply.github.com>
|
|
||||||
MikolajTwarog <43782609+MikolajTwarog@users.noreply.github.com>
|
|
||||||
Mingxuan Lin <gdlmx@users.noreply.github.com>
|
|
||||||
mv1005 <49659413+mv1005@users.noreply.github.com>
|
|
||||||
Nate Morrison (nrm21) <natemorrison@gmail.com>
|
|
||||||
Naveen <172697+naveensrinivasan@users.noreply.github.com>
|
|
||||||
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
|
|
||||||
Nick Busey <NickBusey@users.noreply.github.com>
|
|
||||||
Nico Stapelbroek <3368018+nstapelbroek@users.noreply.github.com>
|
|
||||||
Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
|
|
||||||
Nicolas Perraut <n.perraut@gmail.com>
|
|
||||||
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
|
|
||||||
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
|
|
||||||
NinoM4ster <ninom4ster@gmail.com>
|
|
||||||
Nitroretro <43112364+Nitroretro@users.noreply.github.com>
|
|
||||||
NoLooseEnds <jon.koslung@gmail.com>
|
|
||||||
Oliver Freyermuth <o.freyermuth@googlemail.com>
|
|
||||||
otbutz <tbutz@optitool.de>
|
|
||||||
Otiel <Otiel@users.noreply.github.com>
|
|
||||||
overkill <22098433+0verk1ll@users.noreply.github.com>
|
|
||||||
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
|
|
||||||
Pablo <pbaeyens31+github@gmail.com>
|
|
||||||
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
|
|
||||||
Paul Brit <paulbrit44@gmail.com>
|
|
||||||
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
|
|
||||||
Paweł Rozlach <vespian@users.noreply.github.com>
|
|
||||||
perewa <cavalcante.ten@gmail.com>
|
|
||||||
Peter Badida <KeyWeeUsr@users.noreply.github.com>
|
|
||||||
Peter Dave Hello <hsu@peterdavehello.org>
|
|
||||||
Peter Hoeg (peterhoeg) <peter@speartail.com>
|
|
||||||
Peter Marquardt (wwwutz) <wwwutz@gmail.com> <wwwutz@googlemail.com>
|
|
||||||
Phani Rithvij <phanirithvij2000@gmail.com>
|
|
||||||
Phil Davis <phil.davis@inf.org>
|
|
||||||
Philippe Schommers (filoozoom) <philippe@schommers.be>
|
|
||||||
Phill Luby (pluby) <phill.luby@newredo.com>
|
|
||||||
Pier Paolo Ramon <ramonpierre@gmail.com>
|
|
||||||
Piotr Bejda (piobpl) <piotrb10@gmail.com>
|
|
||||||
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
|
|
||||||
Quentin Hibon <qh.public@yahoo.com>
|
|
||||||
Rahmi Pruitt <rjpruitt16@gmail.com>
|
|
||||||
red_led <red-led@users.noreply.github.com>
|
|
||||||
Richard Hartmann <RichiH@users.noreply.github.com>
|
|
||||||
Robert Carosi (nov1n) <robert@carosi.nl>
|
|
||||||
Roberto Santalla <roobre@users.noreply.github.com>
|
|
||||||
Robin Schoonover <robin@cornhooves.org>
|
|
||||||
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
|
|
||||||
Ross Smith II (rasa) <ross@smithii.com>
|
|
||||||
rubenbe <github-com-00ff86@vandamme.email>
|
|
||||||
Ruslan Yevdokymov <38809160+ruslanye@users.noreply.github.com>
|
|
||||||
Ryan Qian <i@bitbili.net>
|
|
||||||
Ryan Sullivan (KayoticSully) <kayoticsully@gmail.com>
|
|
||||||
Sacheendra Talluri (sacheendra) <sacheendra.t@gmail.com>
|
|
||||||
Scott Klupfel (kluppy) <kluppy@going2blue.com>
|
|
||||||
sec65 <106604020+sec65@users.noreply.github.com>
|
|
||||||
Sergey Mishin (ralder) <ralder@yandex.ru>
|
|
||||||
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
|
|
||||||
Simon Frei (imsodin) <freisim93@gmail.com>
|
|
||||||
Simon Mwepu <simonmwepu@gmail.com>
|
|
||||||
Sly_tom_cat <slytomcat@mail.ru>
|
|
||||||
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
|
|
||||||
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
|
|
||||||
Steven Eckhoff <steven.eckhoff.opensource@gmail.com>
|
|
||||||
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
|
|
||||||
Syncthing Automation <automation@syncthing.net>
|
|
||||||
Syncthing Release Automation <release@syncthing.net>
|
|
||||||
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
|
|
||||||
Thomas Hipp <thomashipp@gmail.com>
|
|
||||||
Tim Abell (timabell) <tim@timwise.co.uk>
|
|
||||||
Tim Howes (timhowes) <timhowes@berkeley.edu>
|
|
||||||
Tobias Klauser <tobias.klauser@gmail.com>
|
|
||||||
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
|
|
||||||
Tobias Tom (tobiastom) <t.tom@succont.de>
|
|
||||||
Tom Jakubowski <tom@crystae.net>
|
|
||||||
Tomasz Wilczyński <5626656+tomasz1986@users.noreply.github.com> <twilczynski@naver.com>
|
|
||||||
Tommy Thorn <tommy-github-email@thorn.ws>
|
|
||||||
Tully Robinson (tojrobinson) <tully@tojr.org>
|
|
||||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
|
||||||
Tyler Kropp <kropptyler@gmail.com>
|
|
||||||
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
|
||||||
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
|
||||||
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
|
||||||
Vik <63919734+ViktorOn@users.noreply.github.com>
|
|
||||||
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
|
||||||
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
|
||||||
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
|
||||||
wangguoliang <liangcszzu@163.com>
|
|
||||||
Will Rouesnel <wrouesnel@wrouesnel.com>
|
|
||||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
|
||||||
wouter bolsterlee <wouter@bolsterl.ee>
|
|
||||||
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
|
|
||||||
xarx00 <xarx00@users.noreply.github.com>
|
|
||||||
Xavier O. (damajor) <damajor@gmail.com>
|
|
||||||
xjtdy888 (xjtdy888) <xjtdy888@163.com>
|
|
||||||
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
|
|
||||||
佛跳墙 <daoquan@qq.com>
|
|
||||||
落心 <luoxin.ttt@gmail.com>
|
|
||||||
|
|||||||
+74
-55
@@ -1,73 +1,92 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
## Conduct
|
||||||
|
|
||||||
## Our Pledge
|
* We are committed to providing a friendly, safe and welcoming
|
||||||
|
environment for all, regardless of gender, sexual orientation,
|
||||||
|
disability, ethnicity, religion, or similar personal characteristic.
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
* On IRC, please avoid using overtly sexual nicknames or other nicknames
|
||||||
contributors and maintainers pledge to making participation in our project and
|
that might detract from a friendly, safe and welcoming environment for
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
all.
|
||||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
||||||
education, socio-economic status, nationality, personal appearance, race,
|
|
||||||
religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
* Please be kind and courteous. There's no need to be mean or rude.
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment
|
* Respect that people have differences of opinion and that every design
|
||||||
include:
|
or implementation choice carries a trade-off and numerous costs. There
|
||||||
|
is seldom a right answer.
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
* Please keep unstructured critique to a minimum. If you have solid
|
||||||
* Being respectful of differing viewpoints and experiences
|
ideas you want to experiment with, make a fork and see how it works.
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
* We will exclude you from interaction if you insult, demean or harass
|
||||||
|
anyone. That is not welcome behaviour. We interpret the term
|
||||||
|
"harassment" as including the definition in the <a
|
||||||
|
href="http://citizencodeofconduct.org/">Citizen Code of Conduct</a>;
|
||||||
|
if you have any lack of clarity about what might be included in that
|
||||||
|
concept, please read their definition. In particular, we don't
|
||||||
|
tolerate behavior that excludes people in socially marginalized
|
||||||
|
groups.
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
* Private harassment is also unacceptable. No matter who you are, if you
|
||||||
advances
|
feel you have been or are being harassed or made uncomfortable by a
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
community member, please contact one of the channel ops or any of the
|
||||||
* Public or private harassment
|
Syncthing core team immediately. Whether you're a regular contributor
|
||||||
* Publishing others' private information, such as a physical or electronic
|
or a newcomer, we care about making this community a safe place for
|
||||||
address, without explicit permission
|
you and we've got your back.
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
* Likewise any spamming, trolling, flaming, baiting or other
|
||||||
|
attention-stealing behaviour is not welcome.
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable
|
## Moderation
|
||||||
behavior and are expected to take appropriate and fair corrective action in
|
|
||||||
response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
These are the policies for upholding our community's standards of
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
conduct in our communication channels, most notably in Syncthing-related
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
IRC channels and on the web forum.
|
||||||
permanently any contributor for other behaviors that they deem inappropriate,
|
|
||||||
threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
1. Remarks that violate the Syncthing standards of conduct, including
|
||||||
|
hateful, hurtful, oppressive, or exclusionary remarks, are not
|
||||||
|
allowed. (Cursing is allowed, but never targeting another user, and
|
||||||
|
never in a hateful manner.)
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces
|
2. Remarks that moderators find inappropriate, whether listed in the
|
||||||
when an individual is representing the project or its community. Examples of
|
code of conduct or not, are also not allowed.
|
||||||
representing a project or community include using an official project e-mail
|
|
||||||
address, posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event. Representation of a project may be
|
|
||||||
further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
3. Moderators will first respond to such remarks with a warning.
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
4. If the warning is unheeded, the user will be "kicked," i.e., kicked
|
||||||
reported by contacting the project team at security@syncthing.net. All
|
out of the communication channel to cool off.
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
||||||
Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
5. If the user comes back and continues to make trouble, they will be
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
banned, i.e., indefinitely excluded.
|
||||||
members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
6. Moderators may choose at their discretion to un-ban the user if it
|
||||||
|
was a first offense and they offer the offended party a genuine
|
||||||
|
apology.
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
7. If a moderator bans someone and you think it was unjustified, please
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
take it up with that moderator, or with a different moderator, **in
|
||||||
|
private**. Complaints about bans in-channel are not allowed.
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
8. Moderators are held to a higher standard than other community
|
||||||
|
members. If a moderator creates an inappropriate situation, they
|
||||||
|
should expect less leeway than others.
|
||||||
|
|
||||||
|
In the Syncthing community we strive to go the extra step to look out
|
||||||
|
for each other. Don't just aim to be technically unimpeachable, try to
|
||||||
|
be your best self. In particular, avoid flirting with offensive or
|
||||||
|
sensitive issues, particularly if they're off-topic; this all too
|
||||||
|
often leads to unnecessary fights, hurt feelings, and damaged trust;
|
||||||
|
worse, it can drive people away from the community entirely.
|
||||||
|
|
||||||
|
And if someone takes issue with something you said or did, resist the
|
||||||
|
urge to be defensive. Just stop doing what it was they complained about
|
||||||
|
and apologize. Even if you feel you were misinterpreted or unfairly
|
||||||
|
accused, chances are good there was something you could've communicated
|
||||||
|
better — remember that it's your responsibility to make your fellow
|
||||||
|
community members comfortable. Everyone wants to get along and we are
|
||||||
|
all here first and foremost because we want to talk about cool
|
||||||
|
technology. You will find that people will be eager to assume good
|
||||||
|
intent and forgive as long as you earn their trust.
|
||||||
|
|
||||||
|
*Adapted from the [Rust Code of Conduct](https://github.com/rust-lang/rust/wiki/Note-development-policy#conduct)*
|
||||||
|
|
||||||
|
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling)*
|
||||||
|
|||||||
+100
-28
@@ -1,6 +1,6 @@
|
|||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
|
|
||||||
Please file bugs in the [GitHub Issue
|
Please file bugs in the [Github Issue
|
||||||
Tracker](https://github.com/syncthing/syncthing/issues). Include at
|
Tracker](https://github.com/syncthing/syncthing/issues). Include at
|
||||||
least the following:
|
least the following:
|
||||||
|
|
||||||
@@ -24,45 +24,117 @@ too much information will never get you yelled at. :)
|
|||||||
## Contributing Translations
|
## Contributing Translations
|
||||||
|
|
||||||
All translations are done via
|
All translations are done via
|
||||||
[Weblate](https://hosted.weblate.org/projects/syncthing/). If you wish
|
[Transifex](https://www.transifex.com/projects/p/syncthing/). If you
|
||||||
to contribute to a translation, just head over there and sign up.
|
wish to contribute to a translation, just head over there and sign up.
|
||||||
Before every release, the language resources are updated from the
|
Before every release, the language resources are updated from the
|
||||||
latest info on Weblate.
|
latest info on Transifex.
|
||||||
|
|
||||||
Note that the previously used service at
|
|
||||||
[Transifex](https://www.transifex.com/projects/p/syncthing/) is being
|
|
||||||
retired and we kindly ask you to sign up on Weblate for continued
|
|
||||||
involvement.
|
|
||||||
|
|
||||||
## Contributing Code
|
## Contributing Code
|
||||||
|
|
||||||
Every contribution is welcome. If you want to contribute but are unsure
|
Every contribution is welcome. If you want to contribute but are unsure
|
||||||
where to start, any open issues are fair game! See the [Contribution
|
where to start, any open issues are fair game! Be prepared for a
|
||||||
Guidelines](https://docs.syncthing.net/dev/contributing.html) for the full
|
[certain amount of review](https://github.com/syncthing/syncthing/wiki/FAQ#why-are-you-being-so-hard-on-my-pull-request);
|
||||||
story on committing code.
|
it's all in the name of quality. :) Following the points below will make this
|
||||||
|
a smoother process.
|
||||||
|
|
||||||
## Contributing Documentation
|
Individuals making significant and valuable contributions are given
|
||||||
|
commit-access to the project. If you make a significant contribution and
|
||||||
|
are not considered for commit-access, please contact any of the
|
||||||
|
Syncthing core team members.
|
||||||
|
|
||||||
Updates to the [documentation site](https://docs.syncthing.net/) can be
|
All nontrivial contributions should go through the pull request
|
||||||
made as pull requests on the [documentation
|
mechanism for internal review. Determining what is "nontrivial" is left
|
||||||
repository](https://github.com/syncthing/docs).
|
at the discretion of the contributor.
|
||||||
|
|
||||||
|
### Authorship
|
||||||
|
|
||||||
|
All code authors are listed in the AUTHORS file. Commits must be made
|
||||||
|
with the same name and email as listed in the AUTHORS file. To
|
||||||
|
accomplish this, ensure that your git configuration is set correctly
|
||||||
|
prior to making your first commit;
|
||||||
|
|
||||||
|
$ git config --global user.name "Jane Doe"
|
||||||
|
$ git config --global user.email janedoe@example.com
|
||||||
|
|
||||||
|
You must be reachable on the given email address. If you do not wish to
|
||||||
|
use your real name for whatever reason, using a nickname or pseudonym is
|
||||||
|
perfectly acceptable.
|
||||||
|
|
||||||
|
### Core Team
|
||||||
|
|
||||||
|
The Syncthing core team currently consists of the following members;
|
||||||
|
|
||||||
|
- Jakob Borg (@calmh)
|
||||||
|
- Audrius Butkevicius (@AudriusButkevicius)
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
|
||||||
|
as much as makes sense.
|
||||||
|
|
||||||
|
- All text files use Unix line endings.
|
||||||
|
|
||||||
|
- Each commit should be `go fmt` clean.
|
||||||
|
|
||||||
|
- The commit message subject should be a single short sentence
|
||||||
|
describing the change, starting with a capital letter.
|
||||||
|
|
||||||
|
- Commits that resolve an existing issue must include the issue number
|
||||||
|
as `(fixes #123)` at the end of the commit message subject.
|
||||||
|
|
||||||
|
- Imports are grouped per `goimports` standard; that is, standard
|
||||||
|
library first, then third party libraries after a blank line.
|
||||||
|
|
||||||
|
- A contribution solving a single issue or introducing a single new
|
||||||
|
feature should probably be a single commit based on the current
|
||||||
|
`master` branch. You may be asked to "rebase" or "squash" your pull
|
||||||
|
request to make sure this is the case, especially if there have been
|
||||||
|
amendments during review.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
All contributions are made available under the same license as the already
|
All contributions are made under the same MPLv2 license as the rest of
|
||||||
existing material being contributed to. For most of the project and unless
|
the project, except documentation, user interface text and translation
|
||||||
otherwise stated this means MPLv2, but there are exceptions:
|
strings which are licensed under the Creative Commons Attribution 4.0
|
||||||
|
International License. You retain the copyright to code you have
|
||||||
|
written.
|
||||||
|
|
||||||
- Certain commands (under cmd/...) may have a separate license, indicated by
|
When accepting your first contribution, the maintainer of the project
|
||||||
the presence of a LICENSE file in the corresponding directory.
|
will ensure that you are added to the AUTHORS file. You are welcome to
|
||||||
|
add yourself as a separate commit in your first pull request.
|
||||||
|
|
||||||
- The documentation (man/...) is licensed under the Creative Commons
|
## Building
|
||||||
Attribution 4.0 International License.
|
|
||||||
|
|
||||||
- Projects under vendor/... are copyright by and licensed from their
|
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
|
||||||
respective original authors. Contributions should be made to the original
|
on how to get started with a build environment.
|
||||||
project, not here.
|
|
||||||
|
|
||||||
Regardless of the license in effect, you retain the copyright to your
|
## Branches
|
||||||
contribution.
|
|
||||||
|
|
||||||
|
- `master` is the main branch containing good code that will end up in
|
||||||
|
the next release. You should base your work on it. It won't ever be
|
||||||
|
rebased or force-pushed to.
|
||||||
|
|
||||||
|
- `vx.y` branches exist to make patch releases on otherwise obsolete
|
||||||
|
minor releases. Should only contain fixes cherry picked from master.
|
||||||
|
Don't base any work on them.
|
||||||
|
|
||||||
|
- Other branches are probably topic branches and may be subject to
|
||||||
|
rebasing. Don't base any work on them unless you specifically know
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
All releases are tagged semver style as `vx.y.z`. Release tags are
|
||||||
|
signed by GPG key BCE524C7.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Yes please!
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Over here!](https://github.com/syncthing/syncthing/wiki)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MPLv2
|
||||||
|
|||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
ARG GOVERSION=latest
|
|
||||||
|
|
||||||
#
|
|
||||||
# Maybe build Syncthing. This is a bit ugly as we can't make an entire
|
|
||||||
# section of the Dockerfile conditional, so we end up always pulling the
|
|
||||||
# golang image as builder. Then we check if the executable we need already
|
|
||||||
# exists (pre-built) otherwise we build it.
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
ARG BUILD_USER
|
|
||||||
ARG BUILD_HOST
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
RUN if [ ! -f syncthing-linux-$TARGETARCH ] ; then \
|
|
||||||
go run build.go -no-upgrade build syncthing ; \
|
|
||||||
mv syncthing syncthing-linux-$TARGETARCH ; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# The rest of the Dockerfile uses the binary from the builder, prebuilt or
|
|
||||||
# not.
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 8384 22000/tcp 22000/udp 21027/udp
|
|
||||||
|
|
||||||
VOLUME ["/var/syncthing"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates curl libcap su-exec tzdata
|
|
||||||
|
|
||||||
COPY --from=builder /src/syncthing-linux-$TARGETARCH /bin/syncthing
|
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=10s \
|
|
||||||
CMD curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
|
|
||||||
|
|
||||||
ENV STGUIADDRESS=0.0.0.0:8384
|
|
||||||
ENV STHOMEDIR=/var/syncthing/config
|
|
||||||
RUN chmod 755 /bin/entrypoint.sh
|
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing"]
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION
|
|
||||||
|
|
||||||
# FPM to build Debian packages
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
locales rubygems ruby-dev build-essential git \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& gem install fpm
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
COPY stcrashreceiver-linux-${TARGETARCH} /bin/stcrashreceiver
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/stcrashreceiver" ]
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
ARG BUILD_USER
|
|
||||||
ARG BUILD_HOST
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
RUN if [ ! -f stdiscosrv-linux-$TARGETARCH ] ; then \
|
|
||||||
go run build.go -no-upgrade build stdiscosrv ; \
|
|
||||||
mv stdiscosrv stdiscosrv-linux-$TARGETARCH ; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 19200 8443
|
|
||||||
|
|
||||||
VOLUME ["/var/stdiscosrv"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates su-exec
|
|
||||||
|
|
||||||
COPY --from=builder /src/stdiscosrv-linux-$TARGETARCH /bin/stdiscosrv
|
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=10s \
|
|
||||||
CMD nc -z localhost 8443 || exit 1
|
|
||||||
|
|
||||||
WORKDIR /var/stdiscosrv
|
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/stdiscosrv"]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates su-exec curl
|
|
||||||
ENV PUID=1000 PGID=1000 MAXMIND_KEY=
|
|
||||||
|
|
||||||
RUN mkdir /var/strelaypoolsrv && chown 1000 /var/strelaypoolsrv
|
|
||||||
USER 1000
|
|
||||||
|
|
||||||
COPY strelaypoolsrv-linux-${TARGETARCH} /bin/strelaypoolsrv
|
|
||||||
COPY script/strelaypoolsrv-entrypoint.sh /bin/entrypoint.sh
|
|
||||||
|
|
||||||
WORKDIR /var/strelaypoolsrv
|
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaypoolsrv", "-listen", ":8080"]
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
ARG GOVERSION=latest
|
|
||||||
FROM golang:$GOVERSION AS builder
|
|
||||||
ARG BUILD_USER
|
|
||||||
ARG BUILD_HOST
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
RUN if [ ! -f strelaysrv-linux-$TARGETARCH ] ; then \
|
|
||||||
go run build.go -no-upgrade build strelaysrv ; \
|
|
||||||
mv strelaysrv strelaysrv-linux-$TARGETARCH ; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 22067 22070
|
|
||||||
|
|
||||||
VOLUME ["/var/strelaysrv"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates su-exec
|
|
||||||
|
|
||||||
COPY --from=builder /src/strelaysrv-linux-$TARGETARCH /bin/strelaysrv
|
|
||||||
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
|
|
||||||
|
|
||||||
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=10s \
|
|
||||||
CMD nc -z localhost 22067 || exit 1
|
|
||||||
|
|
||||||
WORKDIR /var/strelaysrv
|
|
||||||
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaysrv"]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
COPY stupgrades-linux-${TARGETARCH} /bin/stupgrades
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/stupgrades" ]
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# The Syncthing Goals
|
|
||||||
|
|
||||||
Syncthing is a **continuous file synchronization program**. It synchronizes
|
|
||||||
files between two or more computers. We strive to fulfill the goals below.
|
|
||||||
The goals are listed in order of importance, the most important one being
|
|
||||||
the first.
|
|
||||||
|
|
||||||
> "Syncing files" here is precise. It means we specifically exclude things
|
|
||||||
> that are not files - calendar items, instant messages, and so on. If those
|
|
||||||
> are in fact stored as files on disk, they can of course be synced as
|
|
||||||
> files.
|
|
||||||
|
|
||||||
Syncthing should be:
|
|
||||||
|
|
||||||
### 1. Safe From Data Loss
|
|
||||||
|
|
||||||
Protecting the user's data is paramount. We take every reasonable precaution
|
|
||||||
to avoid corrupting the user's files.
|
|
||||||
|
|
||||||
> This is the overriding goal, without which synchronizing files becomes
|
|
||||||
> pointless. This means that we do not make unsafe trade offs for the sake
|
|
||||||
> of performance or, in some cases, even usability.
|
|
||||||
|
|
||||||
### 2. Secure Against Attackers
|
|
||||||
|
|
||||||
Again, protecting the user's data is paramount. Regardless of our other
|
|
||||||
goals we must never allow the user's data to be susceptible to eavesdropping
|
|
||||||
or modification by unauthorized parties.
|
|
||||||
|
|
||||||
> This should be understood in context. It is not necessarily reasonable to
|
|
||||||
> expect Syncthing to be resistant against well equipped state level
|
|
||||||
> attackers. We will however do our best. Note also that this is different
|
|
||||||
> from anonymity which is not, currently, a goal.
|
|
||||||
|
|
||||||
### 3. Easy to Use
|
|
||||||
|
|
||||||
Syncthing should be approachable, understandable and inclusive.
|
|
||||||
|
|
||||||
> Complex concepts and maths form the base of Syncthing's functionality.
|
|
||||||
> This should nonetheless be abstracted or hidden to a degree where
|
|
||||||
> Syncthing is usable by the general public.
|
|
||||||
|
|
||||||
### 4. Automatic
|
|
||||||
|
|
||||||
User interaction should be required only when absolutely necessary.
|
|
||||||
|
|
||||||
> Specifically this means that changes to files are picked up without
|
|
||||||
> prompting, conflicts are resolved without prompting and connections are
|
|
||||||
> maintained without prompting. We only prompt the user when it is required
|
|
||||||
> to fulfill one of the (overriding) Secure, Safe or Easy goals.
|
|
||||||
|
|
||||||
### 5. Universally Available
|
|
||||||
|
|
||||||
Syncthing should run on every common computer. We are mindful that the
|
|
||||||
latest technology is not always available to any given individual.
|
|
||||||
|
|
||||||
> Computers include desktops, laptops, servers, virtual machines, small
|
|
||||||
> general purpose computers such as Raspberry Pis and, *where possible*,
|
|
||||||
> tablets and phones. NAS appliances, toasters, cars, firearms, thermostats
|
|
||||||
> and so on may include computing capabilities but it is not our goal for
|
|
||||||
> Syncthing to run smoothly on these devices.
|
|
||||||
|
|
||||||
### 6. For Individuals
|
|
||||||
|
|
||||||
Syncthing is primarily about empowering the individual user with safe,
|
|
||||||
secure and easy to use file synchronization.
|
|
||||||
|
|
||||||
> We acknowledge that it's also useful in an enterprise setting and include
|
|
||||||
> functionality to support that. If this is in conflict with the
|
|
||||||
> requirements of the individual, those will however take priority.
|
|
||||||
|
|
||||||
### 7. Everything Else
|
|
||||||
|
|
||||||
There are many things we care about that don't make it on to the list. It is
|
|
||||||
fine to optimize for these values as well, as long as they are not in
|
|
||||||
conflict with the stated goals above.
|
|
||||||
|
|
||||||
> For example, performance is a thing we care about. We just don't care more
|
|
||||||
> about it than safety, security, etc. Maintainability of the code base and
|
|
||||||
> providing entertainment value for the maintainers are also things that
|
|
||||||
> matter. It is understood that there are aspects of Syncthing that are
|
|
||||||
> suboptimal or even in opposition with the goals above. However, we
|
|
||||||
> continuously strive to align Syncthing more and more with these goals.
|
|
||||||
Generated
+77
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/syncthing/syncthing",
|
||||||
|
"GoVersion": "go1.4",
|
||||||
|
"Packages": [
|
||||||
|
"./cmd/..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/bkaradzic/go-lz4",
|
||||||
|
"Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/calmh/logger",
|
||||||
|
"Rev": "4d4e2801954c5581e4c2a80a3d3beb3b3645fd04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/calmh/luhn",
|
||||||
|
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/calmh/xdr",
|
||||||
|
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/juju/ratelimit",
|
||||||
|
"Rev": "c5abe513796336ee2869745bff0638508450e9c5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/kardianos/osext",
|
||||||
|
"Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syncthing/protocol",
|
||||||
|
"Rev": "e7db2648034fb71b051902a02bc25d4468ed492e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||||
|
"Rev": "87e4e645d80ae9c537e8f2dee52b28036a5dd75e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syndtr/gosnappy/snappy",
|
||||||
|
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/thejerf/suture",
|
||||||
|
"Rev": "ff19fb384c3fe30f42717967eaa69da91e5f317c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/vitrun/qart/coding",
|
||||||
|
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/vitrun/qart/gf256",
|
||||||
|
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/vitrun/qart/qr",
|
||||||
|
"Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/bcrypt",
|
||||||
|
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||||
|
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/text/transform",
|
||||||
|
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/text/unicode/norm",
|
||||||
|
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
||||||
Generated
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/lz4-example/lz4-example
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- tip
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||||
|
Copyright 2013 Damian Gryski. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
go-lz4
|
||||||
|
======
|
||||||
|
|
||||||
|
go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code
|
||||||
|
is located at:
|
||||||
|
|
||||||
|
https://code.google.com/p/lz4/
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
[](http://travis-ci.org/bkaradzic/go-lz4)
|
||||||
|
[](https://godoc.org/github.com/bkaradzic/go-lz4)
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
go get github.com/bkaradzic/go-lz4
|
||||||
|
|
||||||
|
import "github.com/bkaradzic/go-lz4"
|
||||||
|
|
||||||
|
The package name is `lz4`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
* go-lz4 saves a uint32 with the original uncompressed length at the beginning
|
||||||
|
of the encoded buffer. They may get in the way of interoperability with
|
||||||
|
other implementations.
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
Damian Gryski ([@dgryski](https://github.com/dgryski))
|
||||||
|
Dustin Sallings ([@dustin](https://github.com/dustin))
|
||||||
|
|
||||||
|
Contact
|
||||||
|
-------
|
||||||
|
|
||||||
|
[@bkaradzic](https://twitter.com/bkaradzic)
|
||||||
|
http://www.stuckingeometry.com
|
||||||
|
|
||||||
|
Project page
|
||||||
|
https://github.com/bkaradzic/go-lz4
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||||
|
Copyright 2013 Damian Gryski. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/bkaradzic/go-lz4"
|
||||||
|
|
||||||
|
// lz4's API matches snappy's, so we can easily see how it performs
|
||||||
|
// lz4 "code.google.com/p/snappy-go/snappy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var input = `
|
||||||
|
ADVENTURE I. A SCANDAL IN BOHEMIA
|
||||||
|
|
||||||
|
I.
|
||||||
|
|
||||||
|
To Sherlock Holmes she is always THE woman. I have seldom heard
|
||||||
|
him mention her under any other name. In his eyes she eclipses
|
||||||
|
and predominates the whole of her sex. It was not that he felt
|
||||||
|
any emotion akin to love for Irene Adler. All emotions, and that
|
||||||
|
one particularly, were abhorrent to his cold, precise but
|
||||||
|
admirably balanced mind. He was, I take it, the most perfect
|
||||||
|
reasoning and observing machine that the world has seen, but as a
|
||||||
|
lover he would have placed himself in a false position. He never
|
||||||
|
spoke of the softer passions, save with a gibe and a sneer. They
|
||||||
|
were admirable things for the observer--excellent for drawing the
|
||||||
|
veil from men's motives and actions. But for the trained reasoner
|
||||||
|
to admit such intrusions into his own delicate and finely
|
||||||
|
adjusted temperament was to introduce a distracting factor which
|
||||||
|
might throw a doubt upon all his mental results. Grit in a
|
||||||
|
sensitive instrument, or a crack in one of his own high-power
|
||||||
|
lenses, would not be more disturbing than a strong emotion in a
|
||||||
|
nature such as his. And yet there was but one woman to him, and
|
||||||
|
that woman was the late Irene Adler, of dubious and questionable
|
||||||
|
memory.
|
||||||
|
|
||||||
|
I had seen little of Holmes lately. My marriage had drifted us
|
||||||
|
away from each other. My own complete happiness, and the
|
||||||
|
home-centred interests which rise up around the man who first
|
||||||
|
finds himself master of his own establishment, were sufficient to
|
||||||
|
absorb all my attention, while Holmes, who loathed every form of
|
||||||
|
society with his whole Bohemian soul, remained in our lodgings in
|
||||||
|
Baker Street, buried among his old books, and alternating from
|
||||||
|
week to week between cocaine and ambition, the drowsiness of the
|
||||||
|
drug, and the fierce energy of his own keen nature. He was still,
|
||||||
|
as ever, deeply attracted by the study of crime, and occupied his
|
||||||
|
immense faculties and extraordinary powers of observation in
|
||||||
|
following out those clues, and clearing up those mysteries which
|
||||||
|
had been abandoned as hopeless by the official police. From time
|
||||||
|
to time I heard some vague account of his doings: of his summons
|
||||||
|
to Odessa in the case of the Trepoff murder, of his clearing up
|
||||||
|
of the singular tragedy of the Atkinson brothers at Trincomalee,
|
||||||
|
and finally of the mission which he had accomplished so
|
||||||
|
delicately and successfully for the reigning family of Holland.
|
||||||
|
Beyond these signs of his activity, however, which I merely
|
||||||
|
shared with all the readers of the daily press, I knew little of
|
||||||
|
my former friend and companion.
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
compressed, _ := lz4.Encode(nil, []byte(input))
|
||||||
|
|
||||||
|
modified := make([]byte, len(compressed))
|
||||||
|
|
||||||
|
for {
|
||||||
|
copy(modified, compressed)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
modified[rand.Intn(len(compressed)-4)+4] = byte(rand.Intn(256))
|
||||||
|
}
|
||||||
|
lz4.Decode(nil, modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Branimir Karadzic. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
lz4 "github.com/bkaradzic/go-lz4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
decompress = flag.Bool("d", false, "decompress")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
var optCPUProfile = flag.String("cpuprofile", "", "profile")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *optCPUProfile != "" {
|
||||||
|
f, err := os.Create(*optCPUProfile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
pprof.StartCPUProfile(f)
|
||||||
|
defer pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Print("Usage: lz4 [-d] <input> <output>\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := os.OpenFile(args[0], os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open input file %s\n", args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
if *decompress {
|
||||||
|
data, _ = ioutil.ReadAll(input)
|
||||||
|
data, err = lz4.Decode(nil, data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to decode:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data, _ = ioutil.ReadAll(input)
|
||||||
|
data, err = lz4.Encode(nil, data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to encode:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(args[1], data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open output file %s\n", args[1])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package lz4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testfile, _ = ioutil.ReadFile("testdata/pg1661.txt")
|
||||||
|
|
||||||
|
func roundtrip(t *testing.T, input []byte) {
|
||||||
|
|
||||||
|
dst, err := Encode(nil, input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("got error during compression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := Decode(nil, dst)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("got error during decompress: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(output, input) {
|
||||||
|
t.Errorf("roundtrip failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
roundtrip(t, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLengths(t *testing.T) {
|
||||||
|
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
roundtrip(t, testfile[:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1024; i < 4096; i += 23 {
|
||||||
|
roundtrip(t, testfile[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWords(t *testing.T) {
|
||||||
|
roundtrip(t, testfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLZ4Encode(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Encode(nil, testfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLZ4Decode(b *testing.B) {
|
||||||
|
|
||||||
|
var compressed, _ = Encode(nil, testfile)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Decode(nil, compressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lz4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCorrupt indicates the input was corrupt
|
||||||
|
ErrCorrupt = errors.New("corrupt input")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mlBits = 4
|
||||||
|
mlMask = (1 << mlBits) - 1
|
||||||
|
runBits = 8 - mlBits
|
||||||
|
runMask = (1 << runBits) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
src []byte
|
||||||
|
dst []byte
|
||||||
|
spos uint32
|
||||||
|
dpos uint32
|
||||||
|
ref uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) readByte() (uint8, error) {
|
||||||
|
if int(d.spos) == len(d.src) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
b := d.src[d.spos]
|
||||||
|
d.spos++
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) getLen() (uint32, error) {
|
||||||
|
|
||||||
|
length := uint32(0)
|
||||||
|
ln, err := d.readByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrCorrupt
|
||||||
|
}
|
||||||
|
for ln == 255 {
|
||||||
|
length += 255
|
||||||
|
ln, err = d.readByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrCorrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length += uint32(ln)
|
||||||
|
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) cp(length, decr uint32) {
|
||||||
|
|
||||||
|
if int(d.ref+length) < int(d.dpos) {
|
||||||
|
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
|
||||||
|
} else {
|
||||||
|
for ii := uint32(0); ii < length; ii++ {
|
||||||
|
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.dpos += length
|
||||||
|
d.ref += length - decr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) finish(err error) error {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode returns the decoded form of src. The returned slice may be a
|
||||||
|
// subslice of dst if it was large enough to hold the entire decoded block.
|
||||||
|
func Decode(dst, src []byte) ([]byte, error) {
|
||||||
|
|
||||||
|
if len(src) < 4 {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
uncompressedLen := binary.LittleEndian.Uint32(src)
|
||||||
|
|
||||||
|
if uncompressedLen == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if uncompressedLen > MaxInputSize {
|
||||||
|
return nil, ErrTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst == nil || len(dst) < int(uncompressedLen) {
|
||||||
|
dst = make([]byte, uncompressedLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
|
||||||
|
|
||||||
|
decr := []uint32{0, 3, 2, 3}
|
||||||
|
|
||||||
|
for {
|
||||||
|
code, err := d.readByte()
|
||||||
|
if err != nil {
|
||||||
|
return d.dst, d.finish(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
length := uint32(code >> mlBits)
|
||||||
|
if length == runMask {
|
||||||
|
ln, err := d.getLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
length += ln
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(d.spos+length) > len(d.src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
for ii := uint32(0); ii < length; ii++ {
|
||||||
|
d.dst[d.dpos+ii] = d.src[d.spos+ii]
|
||||||
|
}
|
||||||
|
|
||||||
|
d.spos += length
|
||||||
|
d.dpos += length
|
||||||
|
|
||||||
|
if int(d.spos) == len(d.src) {
|
||||||
|
return d.dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(d.spos+2) >= len(d.src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
|
||||||
|
|
||||||
|
if back > d.dpos {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
d.spos += 2
|
||||||
|
d.ref = d.dpos - back
|
||||||
|
|
||||||
|
length = uint32(code & mlMask)
|
||||||
|
if length == mlMask {
|
||||||
|
ln, err := d.getLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
length += ln
|
||||||
|
}
|
||||||
|
|
||||||
|
literal := d.dpos - d.ref
|
||||||
|
if literal < 4 {
|
||||||
|
d.cp(4, decr[literal])
|
||||||
|
} else {
|
||||||
|
length += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.dpos+length > uncompressedLen {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
d.cp(length, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
+13052
File diff suppressed because it is too large
Load Diff
+188
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||||
|
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lz4
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
minMatch = 4
|
||||||
|
hashLog = 17
|
||||||
|
hashTableSize = 1 << hashLog
|
||||||
|
hashShift = (minMatch * 8) - hashLog
|
||||||
|
incompressible uint32 = 128
|
||||||
|
uninitHash = 0x88888888
|
||||||
|
|
||||||
|
// MaxInputSize is the largest buffer than can be compressed in a single block
|
||||||
|
MaxInputSize = 0x7E000000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrTooLarge indicates the input buffer was too large
|
||||||
|
ErrTooLarge = errors.New("input too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
type encoder struct {
|
||||||
|
src []byte
|
||||||
|
dst []byte
|
||||||
|
hashTable []uint32
|
||||||
|
pos uint32
|
||||||
|
anchor uint32
|
||||||
|
dpos uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
|
||||||
|
func CompressBound(isize int) int {
|
||||||
|
if isize > MaxInputSize {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return isize + ((isize) / 255) + 16 + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
|
||||||
|
|
||||||
|
ln := length
|
||||||
|
|
||||||
|
var code byte
|
||||||
|
if ln > runMask-1 {
|
||||||
|
code = runMask
|
||||||
|
} else {
|
||||||
|
code = byte(ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mlLen > mlMask-1 {
|
||||||
|
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
|
||||||
|
} else {
|
||||||
|
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
|
||||||
|
}
|
||||||
|
e.dpos++
|
||||||
|
|
||||||
|
if code == runMask {
|
||||||
|
ln -= runMask
|
||||||
|
for ; ln > 254; ln -= 255 {
|
||||||
|
e.dst[e.dpos] = 255
|
||||||
|
e.dpos++
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dst[e.dpos] = byte(ln)
|
||||||
|
e.dpos++
|
||||||
|
}
|
||||||
|
|
||||||
|
for ii := uint32(0); ii < length; ii++ {
|
||||||
|
e.dst[e.dpos+ii] = e.src[pos+ii]
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dpos += length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns the encoded form of src. The returned array may be a
|
||||||
|
// sub-slice of dst if it was large enough to hold the entire output.
|
||||||
|
func Encode(dst, src []byte) ([]byte, error) {
|
||||||
|
|
||||||
|
if len(src) >= MaxInputSize {
|
||||||
|
return nil, ErrTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := CompressBound(len(src)); len(dst) < n {
|
||||||
|
dst = make([]byte, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
|
||||||
|
e.dpos = 4
|
||||||
|
|
||||||
|
var (
|
||||||
|
step uint32 = 1
|
||||||
|
limit = incompressible
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if int(e.pos)+12 >= len(e.src) {
|
||||||
|
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
|
||||||
|
return e.dst[:e.dpos], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
|
||||||
|
|
||||||
|
hash := (sequence * 2654435761) >> hashShift
|
||||||
|
ref := e.hashTable[hash] + uninitHash
|
||||||
|
e.hashTable[hash] = e.pos - uninitHash
|
||||||
|
|
||||||
|
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
|
||||||
|
if e.pos-e.anchor > limit {
|
||||||
|
limit <<= 1
|
||||||
|
step += 1 + (step >> 2)
|
||||||
|
}
|
||||||
|
e.pos += step
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if step > 1 {
|
||||||
|
e.hashTable[hash] = ref - uninitHash
|
||||||
|
e.pos -= step - 1
|
||||||
|
step = 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
limit = incompressible
|
||||||
|
|
||||||
|
ln := e.pos - e.anchor
|
||||||
|
back := e.pos - ref
|
||||||
|
|
||||||
|
anchor := e.anchor
|
||||||
|
|
||||||
|
e.pos += minMatch
|
||||||
|
ref += minMatch
|
||||||
|
e.anchor = e.pos
|
||||||
|
|
||||||
|
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
|
||||||
|
e.pos++
|
||||||
|
ref++
|
||||||
|
}
|
||||||
|
|
||||||
|
mlLen := e.pos - e.anchor
|
||||||
|
|
||||||
|
e.writeLiterals(ln, mlLen, anchor)
|
||||||
|
e.dst[e.dpos] = uint8(back)
|
||||||
|
e.dst[e.dpos+1] = uint8(back >> 8)
|
||||||
|
e.dpos += 2
|
||||||
|
|
||||||
|
if mlLen > mlMask-1 {
|
||||||
|
mlLen -= mlMask
|
||||||
|
for mlLen > 254 {
|
||||||
|
mlLen -= 255
|
||||||
|
|
||||||
|
e.dst[e.dpos] = 255
|
||||||
|
e.dpos++
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dst[e.dpos] = byte(mlLen)
|
||||||
|
e.dpos++
|
||||||
|
}
|
||||||
|
|
||||||
|
e.anchor = e.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
logger
|
||||||
|
======
|
||||||
|
|
||||||
|
A small wrapper around `log` to provide log levels.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
http://godoc.org/github.com/calmh/logger
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package logger implements a standardized logger with callback functionality
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LevelDebug LogLevel = iota
|
||||||
|
LevelVerbose
|
||||||
|
LevelInfo
|
||||||
|
LevelOK
|
||||||
|
LevelWarn
|
||||||
|
LevelFatal
|
||||||
|
NumLevels
|
||||||
|
)
|
||||||
|
|
||||||
|
// A MessageHandler is called with the log level and message text.
|
||||||
|
type MessageHandler func(l LogLevel, msg string)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
logger *log.Logger
|
||||||
|
handlers [NumLevels][]MessageHandler
|
||||||
|
mut sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default logger logs to standard output with a time prefix.
|
||||||
|
var DefaultLogger = New()
|
||||||
|
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
logger: log.New(os.Stdout, "", log.Ltime),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHandler registers a new MessageHandler to receive messages with the
|
||||||
|
// specified log level or above.
|
||||||
|
func (l *Logger) AddHandler(level LogLevel, h MessageHandler) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
l.handlers[level] = append(l.handlers[level], h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See log.SetFlags
|
||||||
|
func (l *Logger) SetFlags(flag int) {
|
||||||
|
l.logger.SetFlags(flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See log.SetPrefix
|
||||||
|
func (l *Logger) SetPrefix(prefix string) {
|
||||||
|
l.logger.SetPrefix(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) callHandlers(level LogLevel, s string) {
|
||||||
|
for _, h := range l.handlers[level] {
|
||||||
|
h(level, strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln logs a line with a DEBUG prefix.
|
||||||
|
func (l *Logger) Debugln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "DEBUG: "+s)
|
||||||
|
l.callHandlers(LevelDebug, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a formatted line with a DEBUG prefix.
|
||||||
|
func (l *Logger) Debugf(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "DEBUG: "+s)
|
||||||
|
l.callHandlers(LevelDebug, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a line with a VERBOSE prefix.
|
||||||
|
func (l *Logger) Verboseln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "VERBOSE: "+s)
|
||||||
|
l.callHandlers(LevelVerbose, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted line with a VERBOSE prefix.
|
||||||
|
func (l *Logger) Verbosef(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "VERBOSE: "+s)
|
||||||
|
l.callHandlers(LevelVerbose, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a line with an INFO prefix.
|
||||||
|
func (l *Logger) Infoln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "INFO: "+s)
|
||||||
|
l.callHandlers(LevelInfo, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted line with an INFO prefix.
|
||||||
|
func (l *Logger) Infof(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "INFO: "+s)
|
||||||
|
l.callHandlers(LevelInfo, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okln logs a line with an OK prefix.
|
||||||
|
func (l *Logger) Okln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "OK: "+s)
|
||||||
|
l.callHandlers(LevelOK, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okf logs a formatted line with an OK prefix.
|
||||||
|
func (l *Logger) Okf(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "OK: "+s)
|
||||||
|
l.callHandlers(LevelOK, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnln logs a formatted line with a WARNING prefix.
|
||||||
|
func (l *Logger) Warnln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "WARNING: "+s)
|
||||||
|
l.callHandlers(LevelWarn, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a formatted line with a WARNING prefix.
|
||||||
|
func (l *Logger) Warnf(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "WARNING: "+s)
|
||||||
|
l.callHandlers(LevelWarn, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs a line with a FATAL prefix and exits the process with exit
|
||||||
|
// code 1.
|
||||||
|
func (l *Logger) Fatalln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "FATAL: "+s)
|
||||||
|
l.callHandlers(LevelFatal, s)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a formatted line with a FATAL prefix and exits the process with
|
||||||
|
// exit code 1.
|
||||||
|
func (l *Logger) Fatalf(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "FATAL: "+s)
|
||||||
|
l.callHandlers(LevelFatal, s)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPI(t *testing.T) {
|
||||||
|
l := New()
|
||||||
|
l.SetFlags(0)
|
||||||
|
l.SetPrefix("testing")
|
||||||
|
|
||||||
|
debug := 0
|
||||||
|
l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
|
||||||
|
info := 0
|
||||||
|
l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
|
||||||
|
warn := 0
|
||||||
|
l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
|
||||||
|
ok := 0
|
||||||
|
l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
|
||||||
|
|
||||||
|
l.Debugf("test %d", 0)
|
||||||
|
l.Debugln("test", 0)
|
||||||
|
l.Infof("test %d", 1)
|
||||||
|
l.Infoln("test", 1)
|
||||||
|
l.Warnf("test %d", 2)
|
||||||
|
l.Warnln("test", 2)
|
||||||
|
l.Okf("test %d", 3)
|
||||||
|
l.Okln("test", 3)
|
||||||
|
|
||||||
|
if debug != 2 {
|
||||||
|
t.Errorf("Debug handler called %d != 2 times", debug)
|
||||||
|
}
|
||||||
|
if info != 2 {
|
||||||
|
t.Errorf("Info handler called %d != 2 times", info)
|
||||||
|
}
|
||||||
|
if warn != 2 {
|
||||||
|
t.Errorf("Warn handler called %d != 2 times", warn)
|
||||||
|
}
|
||||||
|
if ok != 2 {
|
||||||
|
t.Errorf("Ok handler called %d != 2 times", ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
|
||||||
|
return func(l LogLevel, msg string) {
|
||||||
|
*counter++
|
||||||
|
if l != expectl {
|
||||||
|
t.Errorf("Incorrect message level %d != %d", l, expectl)
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(msg, expectmsg) {
|
||||||
|
t.Errorf("%q does not end with %q", msg, expectmsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014 Jakob Borg
|
||||||
|
|
||||||
|
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.
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg
|
||||||
|
|
||||||
|
// Package luhn generates and validates Luhn mod N check digits.
|
||||||
|
package luhn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An alphabet is a string of N characters, representing the digits of a given
|
||||||
|
// base N.
|
||||||
|
type Alphabet string
|
||||||
|
|
||||||
|
var (
|
||||||
|
Base32 Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate returns a check digit for the string s, which should be composed
|
||||||
|
// of characters from the Alphabet a.
|
||||||
|
func (a Alphabet) Generate(s string) (rune, error) {
|
||||||
|
if err := a.check(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
factor := 1
|
||||||
|
sum := 0
|
||||||
|
n := len(a)
|
||||||
|
|
||||||
|
for i := range s {
|
||||||
|
codepoint := strings.IndexByte(string(a), s[i])
|
||||||
|
if codepoint == -1 {
|
||||||
|
return 0, fmt.Errorf("Digit %q not valid in alphabet %q", s[i], a)
|
||||||
|
}
|
||||||
|
addend := factor * codepoint
|
||||||
|
if factor == 2 {
|
||||||
|
factor = 1
|
||||||
|
} else {
|
||||||
|
factor = 2
|
||||||
|
}
|
||||||
|
addend = (addend / n) + (addend % n)
|
||||||
|
sum += addend
|
||||||
|
}
|
||||||
|
remainder := sum % n
|
||||||
|
checkCodepoint := (n - remainder) % n
|
||||||
|
return rune(a[checkCodepoint]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns true if the last character of the string s is correct, for
|
||||||
|
// a string s composed of characters in the alphabet a.
|
||||||
|
func (a Alphabet) Validate(s string) bool {
|
||||||
|
t := s[:len(s)-1]
|
||||||
|
c, err := a.Generate(t)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return rune(s[len(s)-1]) == c
|
||||||
|
}
|
||||||
|
|
||||||
|
// check returns an error if the given alphabet does not consist of unique characters
|
||||||
|
func (a Alphabet) check() error {
|
||||||
|
cm := make(map[byte]bool, len(a))
|
||||||
|
for i := range a {
|
||||||
|
if cm[a[i]] {
|
||||||
|
return fmt.Errorf("Digit %q non-unique in alphabet %q", a[i], a)
|
||||||
|
}
|
||||||
|
cm[a[i]] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg
|
||||||
|
|
||||||
|
package luhn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/calmh/luhn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
// Base 6 Luhn
|
||||||
|
a := luhn.Alphabet("abcdef")
|
||||||
|
c, err := a.Generate("abcdef")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != 'e' {
|
||||||
|
t.Errorf("Incorrect check digit %c != e", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base 10 Luhn
|
||||||
|
a = luhn.Alphabet("0123456789")
|
||||||
|
c, err = a.Generate("7992739871")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != '3' {
|
||||||
|
t.Errorf("Incorrect check digit %c != 3", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidString(t *testing.T) {
|
||||||
|
a := luhn.Alphabet("ABC")
|
||||||
|
_, err := a.Generate("7992739871")
|
||||||
|
t.Log(err)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Unexpected nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadAlphabet(t *testing.T) {
|
||||||
|
a := luhn.Alphabet("01234566789")
|
||||||
|
_, err := a.Generate("7992739871")
|
||||||
|
t.Log(err)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Unexpected nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
a := luhn.Alphabet("abcdef")
|
||||||
|
if !a.Validate("abcdefe") {
|
||||||
|
t.Errorf("Incorrect validation response for abcdefe")
|
||||||
|
}
|
||||||
|
if a.Validate("abcdefd") {
|
||||||
|
t.Errorf("Incorrect validation response for abcdefd")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
coverage.out
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
|
- go get code.google.com/p/go.tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./generate.sh
|
||||||
|
- go test -coverprofile=coverage.out
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- goveralls -coverprofile=coverage.out -service=travis-ci -package=calmh/xdr -repotoken="$COVERALLS_TOKEN"
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
secure: SmgnrGfp2zLrA44ChRMpjPeujubt9veZ8Fx/OseMWECmacyV5N/TuDhzIbwo6QwV4xB0sBacoPzvxQbJRVjNKsPiSu72UbcQmQ7flN4Tf7nW09tSh1iW8NgrpBCq/3UYLoBu2iPBEBKm93IK0aGNAKs6oEkB0fU27iTVBwiTXOY=
|
||||||
Generated
Vendored
+10
-10
@@ -1,19 +1,19 @@
|
|||||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
Copyright (C) 2014 Jakob Borg.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to
|
||||||
in the Software without restriction, including without limitation the rights
|
deal in the Software without restriction, including without limitation the
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
- The above copyright notice and this permission notice shall be included in
|
||||||
all copies or substantial portions of the Software.
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
xdr
|
||||||
|
===
|
||||||
|
|
||||||
|
[](https://travis-ci.org/calmh/xdr)
|
||||||
|
[](https://coveralls.io/r/calmh/xdr?branch=master)
|
||||||
|
[](http://godoc.org/github.com/calmh/xdr)
|
||||||
|
[](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
This is an XDR encoding/decoding library. It uses code generation and
|
||||||
|
not reflection. It supports the IPDR bastardized XDR format when built
|
||||||
|
with `-tags ipdr`.
|
||||||
|
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XDRBenchStruct struct {
|
||||||
|
I1 uint64
|
||||||
|
I2 uint32
|
||||||
|
I3 uint16
|
||||||
|
I4 uint8
|
||||||
|
Bs0 []byte // max:128
|
||||||
|
Bs1 []byte
|
||||||
|
S0 string // max:128
|
||||||
|
S1 string
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte // no to be optimized away
|
||||||
|
var s = XDRBenchStruct{
|
||||||
|
I1: 42,
|
||||||
|
I2: 43,
|
||||||
|
I3: 44,
|
||||||
|
I4: 45,
|
||||||
|
Bs0: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18},
|
||||||
|
Bs1: []byte{11, 12, 13, 14, 15, 16, 17, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||||
|
S0: "Hello World! String one.",
|
||||||
|
S1: "Hello World! String two.",
|
||||||
|
}
|
||||||
|
var e []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
e, _ = s.MarshalXDR()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisMarshal(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
res, _ = s.MarshalXDR()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisUnmarshal(b *testing.B) {
|
||||||
|
var t XDRBenchStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := t.UnmarshalXDR(e)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisEncode(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := s.EncodeXDR(ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisEncoder(b *testing.B) {
|
||||||
|
w := xdr.NewWriter(ioutil.Discard)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := s.EncodeXDRInto(w)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type repeatReader struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repeatReader) Read(bs []byte) (n int, err error) {
|
||||||
|
if len(bs) > len(r.data) {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
n = copy(bs, r.data)
|
||||||
|
r.data = r.data[n:]
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repeatReader) Reset(bs []byte) {
|
||||||
|
r.data = bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisDecode(b *testing.B) {
|
||||||
|
rr := &repeatReader{e}
|
||||||
|
var t XDRBenchStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := t.DecodeXDR(rr)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
rr.Reset(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkThisDecoder(b *testing.B) {
|
||||||
|
rr := &repeatReader{e}
|
||||||
|
r := xdr.NewReader(rr)
|
||||||
|
var t XDRBenchStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := t.DecodeXDRFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
rr.Reset(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
// ************************************************************
|
||||||
|
// This file is automatically generated by genxdr. Do not edit.
|
||||||
|
// ************************************************************
|
||||||
|
|
||||||
|
package xdr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
XDRBenchStruct Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| |
|
||||||
|
+ I1 (64 bits) +
|
||||||
|
| |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| I2 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| 0x0000 | I3 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ uint8 Structure \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Bs0 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Bs0 (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Bs1 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Bs1 (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of S0 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ S0 (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of S1 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ S1 (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct XDRBenchStruct {
|
||||||
|
unsigned hyper I1;
|
||||||
|
unsigned int I2;
|
||||||
|
unsigned int I3;
|
||||||
|
uint8 I4;
|
||||||
|
opaque Bs0<128>;
|
||||||
|
opaque Bs1<>;
|
||||||
|
string S0<128>;
|
||||||
|
string S1<>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o XDRBenchStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o XDRBenchStruct) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o XDRBenchStruct) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o XDRBenchStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o XDRBenchStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
xw.WriteUint64(o.I1)
|
||||||
|
xw.WriteUint32(o.I2)
|
||||||
|
xw.WriteUint16(o.I3)
|
||||||
|
xw.WriteUint8(o.I4)
|
||||||
|
if l := len(o.Bs0); l > 128 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Bs0", l, 128)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.Bs0)
|
||||||
|
xw.WriteBytes(o.Bs1)
|
||||||
|
if l := len(o.S0); l > 128 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("S0", l, 128)
|
||||||
|
}
|
||||||
|
xw.WriteString(o.S0)
|
||||||
|
xw.WriteString(o.S1)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *XDRBenchStruct) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *XDRBenchStruct) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *XDRBenchStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.I1 = xr.ReadUint64()
|
||||||
|
o.I2 = xr.ReadUint32()
|
||||||
|
o.I3 = xr.ReadUint16()
|
||||||
|
o.I4 = xr.ReadUint8()
|
||||||
|
o.Bs0 = xr.ReadBytesMax(128)
|
||||||
|
o.Bs1 = xr.ReadBytes()
|
||||||
|
o.S0 = xr.ReadStringMax(128)
|
||||||
|
o.S1 = xr.ReadString()
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
repeatReader Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of data |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ data (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct repeatReader {
|
||||||
|
opaque data<>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o repeatReader) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o repeatReader) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o repeatReader) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o repeatReader) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o repeatReader) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
xw.WriteBytes(o.data)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *repeatReader) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *repeatReader) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *repeatReader) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.data = xr.ReadBytes()
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
+467
@@ -0,0 +1,467 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldInfo struct {
|
||||||
|
Name string
|
||||||
|
IsBasic bool // handled by one the native Read/WriteUint64 etc functions
|
||||||
|
IsSlice bool // field is a slice of FieldType
|
||||||
|
FieldType string // original type of field, i.e. "int"
|
||||||
|
Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64
|
||||||
|
Convert string // what to convert to when encoding, i.e. "uint64"
|
||||||
|
Max int // max size for slices and strings
|
||||||
|
}
|
||||||
|
|
||||||
|
type structInfo struct {
|
||||||
|
Name string
|
||||||
|
Fields []fieldInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerTpl = template.Must(template.New("header").Parse(`// ************************************************************
|
||||||
|
// This file is automatically generated by genxdr. Do not edit.
|
||||||
|
// ************************************************************
|
||||||
|
|
||||||
|
package {{.Package}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
`))
|
||||||
|
|
||||||
|
var encodeTpl = template.Must(template.New("encoder").Parse(`
|
||||||
|
func (o {{.TypeName}}) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o {{.TypeName}}) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o {{.TypeName}}) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o {{.TypeName}}) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
{{range $fieldInfo := .Fields}}
|
||||||
|
{{if not $fieldInfo.IsSlice}}
|
||||||
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
|
xw.Write{{$fieldInfo.Encoder}}({{$fieldInfo.Convert}}(o.{{$fieldInfo.Name}}))
|
||||||
|
{{else if $fieldInfo.IsBasic}}
|
||||||
|
{{if ge $fieldInfo.Max 1}}
|
||||||
|
if l := len(o.{{$fieldInfo.Name}}); l > {{$fieldInfo.Max}} {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", l, {{$fieldInfo.Max}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}})
|
||||||
|
{{else}}
|
||||||
|
_, err := o.{{$fieldInfo.Name}}.EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
{{if ge $fieldInfo.Max 1}}
|
||||||
|
if l := len(o.{{$fieldInfo.Name}}); l > {{$fieldInfo.Max}} {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", l, {{$fieldInfo.Max}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
xw.WriteUint32(uint32(len(o.{{$fieldInfo.Name}})))
|
||||||
|
for i := range o.{{$fieldInfo.Name}} {
|
||||||
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
|
xw.Write{{$fieldInfo.Encoder}}({{$fieldInfo.Convert}}(o.{{$fieldInfo.Name}}[i]))
|
||||||
|
{{else if $fieldInfo.IsBasic}}
|
||||||
|
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}}[i])
|
||||||
|
{{else}}
|
||||||
|
_, err := o.{{$fieldInfo.Name}}[i].EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o *{{.TypeName}}) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o *{{.TypeName}}) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}//+n
|
||||||
|
|
||||||
|
func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
{{range $fieldInfo := .Fields}}
|
||||||
|
{{if not $fieldInfo.IsSlice}}
|
||||||
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
|
o.{{$fieldInfo.Name}} = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}())
|
||||||
|
{{else if $fieldInfo.IsBasic}}
|
||||||
|
{{if ge $fieldInfo.Max 1}}
|
||||||
|
o.{{$fieldInfo.Name}} = xr.Read{{$fieldInfo.Encoder}}Max({{$fieldInfo.Max}})
|
||||||
|
{{else}}
|
||||||
|
o.{{$fieldInfo.Name}} = xr.Read{{$fieldInfo.Encoder}}()
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
(&o.{{$fieldInfo.Name}}).DecodeXDRFrom(xr)
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
|
||||||
|
if _{{$fieldInfo.Name}}Size < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||||
|
}
|
||||||
|
{{if ge $fieldInfo.Max 1}}
|
||||||
|
if _{{$fieldInfo.Name}}Size > {{$fieldInfo.Max}} {
|
||||||
|
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
o.{{$fieldInfo.Name}} = make([]{{$fieldInfo.FieldType}}, _{{$fieldInfo.Name}}Size)
|
||||||
|
for i := range o.{{$fieldInfo.Name}} {
|
||||||
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
|
o.{{$fieldInfo.Name}}[i] = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}())
|
||||||
|
{{else if $fieldInfo.IsBasic}}
|
||||||
|
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
|
||||||
|
{{else}}
|
||||||
|
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
return xr.Error()
|
||||||
|
}`))
|
||||||
|
|
||||||
|
var maxRe = regexp.MustCompile(`\Wmax:(\d+)`)
|
||||||
|
|
||||||
|
type typeSet struct {
|
||||||
|
Type string
|
||||||
|
Encoder string
|
||||||
|
}
|
||||||
|
|
||||||
|
var xdrEncoders = map[string]typeSet{
|
||||||
|
"int8": typeSet{"uint8", "Uint8"},
|
||||||
|
"uint8": typeSet{"", "Uint8"},
|
||||||
|
"int16": typeSet{"uint16", "Uint16"},
|
||||||
|
"uint16": typeSet{"", "Uint16"},
|
||||||
|
"int32": typeSet{"uint32", "Uint32"},
|
||||||
|
"uint32": typeSet{"", "Uint32"},
|
||||||
|
"int64": typeSet{"uint64", "Uint64"},
|
||||||
|
"uint64": typeSet{"", "Uint64"},
|
||||||
|
"int": typeSet{"uint64", "Uint64"},
|
||||||
|
"string": typeSet{"", "String"},
|
||||||
|
"[]byte": typeSet{"", "Bytes"},
|
||||||
|
"bool": typeSet{"", "Bool"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStruct(t *ast.StructType) []fieldInfo {
|
||||||
|
var fs []fieldInfo
|
||||||
|
|
||||||
|
for _, sf := range t.Fields.List {
|
||||||
|
if len(sf.Names) == 0 {
|
||||||
|
// We don't handle anonymous fields
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := sf.Names[0].Name
|
||||||
|
var max = 0
|
||||||
|
if sf.Comment != nil {
|
||||||
|
c := sf.Comment.List[0].Text
|
||||||
|
if m := maxRe.FindStringSubmatch(c); m != nil {
|
||||||
|
max, _ = strconv.Atoi(m[1])
|
||||||
|
}
|
||||||
|
if strings.Contains(c, "noencode") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var f fieldInfo
|
||||||
|
switch ft := sf.Type.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
tn := ft.Name
|
||||||
|
if enc, ok := xdrEncoders[tn]; ok {
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
IsBasic: true,
|
||||||
|
FieldType: tn,
|
||||||
|
Encoder: enc.Encoder,
|
||||||
|
Convert: enc.Type,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
IsBasic: false,
|
||||||
|
FieldType: tn,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.ArrayType:
|
||||||
|
if ft.Len != nil {
|
||||||
|
// We don't handle arrays
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tn := ft.Elt.(*ast.Ident).Name
|
||||||
|
if enc, ok := xdrEncoders["[]"+tn]; ok {
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
IsBasic: true,
|
||||||
|
FieldType: tn,
|
||||||
|
Encoder: enc.Encoder,
|
||||||
|
Convert: enc.Type,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
} else if enc, ok := xdrEncoders[tn]; ok {
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
IsBasic: true,
|
||||||
|
IsSlice: true,
|
||||||
|
FieldType: tn,
|
||||||
|
Encoder: enc.Encoder,
|
||||||
|
Convert: enc.Type,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
IsSlice: true,
|
||||||
|
FieldType: tn,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
FieldType: ft.Sel.Name,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs = append(fs, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCode(output io.Writer, s structInfo) {
|
||||||
|
name := s.Name
|
||||||
|
fs := s.Fields
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := encodeTpl.Execute(&buf, map[string]interface{}{"TypeName": name, "Fields": fs})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := regexp.MustCompile(`(\s*\n)+`).ReplaceAll(buf.Bytes(), []byte("\n"))
|
||||||
|
bs = bytes.Replace(bs, []byte("//+n"), []byte("\n"), -1)
|
||||||
|
|
||||||
|
bs, err = format.Source(bs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(output, string(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func uncamelize(s string) string {
|
||||||
|
return regexp.MustCompile("[a-z][A-Z]").ReplaceAllStringFunc(s, func(camel string) string {
|
||||||
|
return camel[:1] + " " + camel[1:]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDiagram(output io.Writer, s structInfo) {
|
||||||
|
sn := s.Name
|
||||||
|
fs := s.Fields
|
||||||
|
|
||||||
|
fmt.Fprintln(output, sn+" Structure:")
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
fmt.Fprintln(output, " 0 1 2 3")
|
||||||
|
fmt.Fprintln(output, " 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1")
|
||||||
|
line := "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
|
||||||
|
for _, f := range fs {
|
||||||
|
tn := f.FieldType
|
||||||
|
name := uncamelize(f.Name)
|
||||||
|
|
||||||
|
if f.IsSlice {
|
||||||
|
fmt.Fprintf(output, "| %s |\n", center("Number of "+name, 61))
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
}
|
||||||
|
switch tn {
|
||||||
|
case "bool":
|
||||||
|
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
case "int16", "uint16":
|
||||||
|
fmt.Fprintf(output, "| %s | %s |\n", center("0x0000", 29), center(name, 29))
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
case "int32", "uint32":
|
||||||
|
fmt.Fprintf(output, "| %s |\n", center(name, 61))
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
case "int64", "uint64":
|
||||||
|
fmt.Fprintf(output, "| %-61s |\n", "")
|
||||||
|
fmt.Fprintf(output, "+ %s +\n", center(name+" (64 bits)", 61))
|
||||||
|
fmt.Fprintf(output, "| %-61s |\n", "")
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
case "string", "byte": // XXX We assume slice of byte!
|
||||||
|
fmt.Fprintf(output, "| %s |\n", center("Length of "+name, 61))
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
fmt.Fprintf(output, "/ %61s /\n", "")
|
||||||
|
fmt.Fprintf(output, "\\ %s \\\n", center(name+" (variable length)", 61))
|
||||||
|
fmt.Fprintf(output, "/ %61s /\n", "")
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
default:
|
||||||
|
if f.IsSlice {
|
||||||
|
tn = "Zero or more " + tn + " Structures"
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
|
} else {
|
||||||
|
tn = tn + " Structure"
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(output, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateXdr(output io.Writer, s structInfo) {
|
||||||
|
sn := s.Name
|
||||||
|
fs := s.Fields
|
||||||
|
|
||||||
|
fmt.Fprintf(output, "struct %s {\n", sn)
|
||||||
|
|
||||||
|
for _, f := range fs {
|
||||||
|
tn := f.FieldType
|
||||||
|
fn := f.Name
|
||||||
|
suf := ""
|
||||||
|
l := ""
|
||||||
|
if f.Max > 0 {
|
||||||
|
l = strconv.Itoa(f.Max)
|
||||||
|
}
|
||||||
|
if f.IsSlice {
|
||||||
|
suf = "<" + l + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tn {
|
||||||
|
case "int16", "int32":
|
||||||
|
fmt.Fprintf(output, "\tint %s%s;\n", fn, suf)
|
||||||
|
case "uint16", "uint32":
|
||||||
|
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
|
||||||
|
case "int64":
|
||||||
|
fmt.Fprintf(output, "\thyper %s%s;\n", fn, suf)
|
||||||
|
case "uint64":
|
||||||
|
fmt.Fprintf(output, "\tunsigned hyper %s%s;\n", fn, suf)
|
||||||
|
case "string":
|
||||||
|
fmt.Fprintf(output, "\tstring %s<%s>;\n", fn, l)
|
||||||
|
case "byte":
|
||||||
|
fmt.Fprintf(output, "\topaque %s<%s>;\n", fn, l)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(output, "\t%s %s%s;\n", tn, fn, suf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintln(output, "}")
|
||||||
|
fmt.Fprintln(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func center(s string, w int) string {
|
||||||
|
w -= len(s)
|
||||||
|
l := w / 2
|
||||||
|
r := l
|
||||||
|
if l+r < w {
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
return strings.Repeat(" ", l) + s + strings.Repeat(" ", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspector(structs *[]structInfo) func(ast.Node) bool {
|
||||||
|
return func(n ast.Node) bool {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
switch t := n.Type.(type) {
|
||||||
|
case *ast.StructType:
|
||||||
|
name := n.Name.Name
|
||||||
|
fs := handleStruct(t)
|
||||||
|
*structs = append(*structs, structInfo{name, fs})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
outputFile := flag.String("o", "", "Output file, blank for stdout")
|
||||||
|
flag.Parse()
|
||||||
|
fname := flag.Arg(0)
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var structs []structInfo
|
||||||
|
i := inspector(&structs)
|
||||||
|
ast.Inspect(f, i)
|
||||||
|
|
||||||
|
var output io.Writer = os.Stdout
|
||||||
|
if *outputFile != "" {
|
||||||
|
fd, err := os.Create(*outputFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
output = fd
|
||||||
|
}
|
||||||
|
|
||||||
|
headerTpl.Execute(output, map[string]string{"Package": f.Name.Name})
|
||||||
|
for _, s := range structs {
|
||||||
|
fmt.Fprintf(output, "\n/*\n\n")
|
||||||
|
generateDiagram(output, s)
|
||||||
|
generateXdr(output, s)
|
||||||
|
fmt.Fprintf(output, "*/\n")
|
||||||
|
generateCode(output, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = len(os.Getenv("XDRTRACE")) > 0
|
||||||
|
dl = log.New(os.Stdout, "xdr: ", log.Lshortfile|log.Ltime|log.Lmicroseconds)
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxDebugBytes = 32
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package xdr implements an XDR (RFC 4506) encoder/decoder.
|
||||||
|
package xdr
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contains all supported types
|
||||||
|
type TestStruct struct {
|
||||||
|
I int
|
||||||
|
I8 int8
|
||||||
|
UI8 uint8
|
||||||
|
I16 int16
|
||||||
|
UI16 uint16
|
||||||
|
I32 int32
|
||||||
|
UI32 uint32
|
||||||
|
I64 int64
|
||||||
|
UI64 uint64
|
||||||
|
BS []byte // max:1024
|
||||||
|
S string // max:1024
|
||||||
|
C Opaque
|
||||||
|
SS []string // max:1024
|
||||||
|
}
|
||||||
|
|
||||||
|
type Opaque [32]byte
|
||||||
|
|
||||||
|
func (u *Opaque) EncodeXDRInto(w *xdr.Writer) (int, error) {
|
||||||
|
return w.WriteRaw(u[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Opaque) DecodeXDRFrom(r *xdr.Reader) (int, error) {
|
||||||
|
return r.ReadRaw(u[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Opaque) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||||
|
var u Opaque
|
||||||
|
for i := range u[:] {
|
||||||
|
u[i] = byte(rand.Int())
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncDec(t *testing.T) {
|
||||||
|
fn := func(t0 TestStruct) bool {
|
||||||
|
bs, err := t0.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var t1 TestStruct
|
||||||
|
err = t1.UnmarshalXDR(bs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not comparing with DeepEqual since we'll unmarshal nil slices as empty
|
||||||
|
if t0.I != t1.I ||
|
||||||
|
t0.I16 != t1.I16 || t0.UI16 != t1.UI16 ||
|
||||||
|
t0.I32 != t1.I32 || t0.UI32 != t1.UI32 ||
|
||||||
|
t0.I64 != t1.I64 || t0.UI64 != t1.UI64 ||
|
||||||
|
bytes.Compare(t0.BS, t1.BS) != 0 ||
|
||||||
|
t0.S != t1.S || t0.C != t1.C {
|
||||||
|
t.Logf("%#v", t0)
|
||||||
|
t.Logf("%#v", t1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err := quick.Check(fn, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
// ************************************************************
|
||||||
|
// This file is automatically generated by genxdr. Do not edit.
|
||||||
|
// ************************************************************
|
||||||
|
|
||||||
|
package xdr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
TestStruct Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ int Structure \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ int8 Structure \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ uint8 Structure \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| 0x0000 | I16 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| 0x0000 | UI16 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| I32 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| UI32 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| |
|
||||||
|
+ I64 (64 bits) +
|
||||||
|
| |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| |
|
||||||
|
+ UI64 (64 bits) +
|
||||||
|
| |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of BS |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ BS (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of S |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ S (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Opaque Structure \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Number of SS |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of SS |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ SS (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct TestStruct {
|
||||||
|
int I;
|
||||||
|
int8 I8;
|
||||||
|
uint8 UI8;
|
||||||
|
int I16;
|
||||||
|
unsigned int UI16;
|
||||||
|
int I32;
|
||||||
|
unsigned int UI32;
|
||||||
|
hyper I64;
|
||||||
|
unsigned hyper UI64;
|
||||||
|
opaque BS<1024>;
|
||||||
|
string S<1024>;
|
||||||
|
Opaque C;
|
||||||
|
string SS<1024>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o TestStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TestStruct) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TestStruct) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TestStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TestStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
xw.WriteUint64(uint64(o.I))
|
||||||
|
xw.WriteUint8(uint8(o.I8))
|
||||||
|
xw.WriteUint8(o.UI8)
|
||||||
|
xw.WriteUint16(uint16(o.I16))
|
||||||
|
xw.WriteUint16(o.UI16)
|
||||||
|
xw.WriteUint32(uint32(o.I32))
|
||||||
|
xw.WriteUint32(o.UI32)
|
||||||
|
xw.WriteUint64(uint64(o.I64))
|
||||||
|
xw.WriteUint64(o.UI64)
|
||||||
|
if l := len(o.BS); l > 1024 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("BS", l, 1024)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.BS)
|
||||||
|
if l := len(o.S); l > 1024 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("S", l, 1024)
|
||||||
|
}
|
||||||
|
xw.WriteString(o.S)
|
||||||
|
_, err := o.C.EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
if l := len(o.SS); l > 1024 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("SS", l, 1024)
|
||||||
|
}
|
||||||
|
xw.WriteUint32(uint32(len(o.SS)))
|
||||||
|
for i := range o.SS {
|
||||||
|
xw.WriteString(o.SS[i])
|
||||||
|
}
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TestStruct) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TestStruct) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TestStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.I = int(xr.ReadUint64())
|
||||||
|
o.I8 = int8(xr.ReadUint8())
|
||||||
|
o.UI8 = xr.ReadUint8()
|
||||||
|
o.I16 = int16(xr.ReadUint16())
|
||||||
|
o.UI16 = xr.ReadUint16()
|
||||||
|
o.I32 = int32(xr.ReadUint32())
|
||||||
|
o.UI32 = xr.ReadUint32()
|
||||||
|
o.I64 = int64(xr.ReadUint64())
|
||||||
|
o.UI64 = xr.ReadUint64()
|
||||||
|
o.BS = xr.ReadBytesMax(1024)
|
||||||
|
o.S = xr.ReadStringMax(1024)
|
||||||
|
(&o.C).DecodeXDRFrom(xr)
|
||||||
|
_SSSize := int(xr.ReadUint32())
|
||||||
|
if _SSSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||||
|
}
|
||||||
|
if _SSSize > 1024 {
|
||||||
|
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||||
|
}
|
||||||
|
o.SS = make([]string, _SSSize)
|
||||||
|
for i := range o.SS {
|
||||||
|
o.SS[i] = xr.ReadString()
|
||||||
|
}
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
go run cmd/genxdr/main.go -- bench_test.go > bench_xdr_test.go
|
||||||
|
go run cmd/genxdr/main.go -- encdec_test.go > encdec_xdr_test.go
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
func pad(l int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
func pad(l int) int {
|
||||||
|
d := l % 4
|
||||||
|
if d == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 4 - d
|
||||||
|
}
|
||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
b [8]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadRaw(bs []byte) (int, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, r.err = io.ReadFull(r.r, bs)
|
||||||
|
return n, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadString() string {
|
||||||
|
return r.ReadStringMax(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadStringMax(max int) string {
|
||||||
|
buf := r.ReadBytesMaxInto(max, nil)
|
||||||
|
bh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh := reflect.StringHeader{
|
||||||
|
Data: bh.Data,
|
||||||
|
Len: bh.Len,
|
||||||
|
}
|
||||||
|
return *((*string)(unsafe.Pointer(&sh)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBytes() []byte {
|
||||||
|
return r.ReadBytesInto(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBytesMax(max int) []byte {
|
||||||
|
return r.ReadBytesMaxInto(max, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBytesInto(dst []byte) []byte {
|
||||||
|
return r.ReadBytesMaxInto(0, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l := int(r.ReadUint32())
|
||||||
|
if r.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if l < 0 || max > 0 && l > max {
|
||||||
|
// l may be negative on 32 bit builds
|
||||||
|
r.err = ElementSizeExceeded("bytes field", l, max)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullLen := l + pad(l); fullLen > len(dst) {
|
||||||
|
dst = make([]byte, fullLen)
|
||||||
|
} else {
|
||||||
|
dst = dst[:fullLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, r.err = io.ReadFull(r.r, dst)
|
||||||
|
if r.err != nil {
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd bytes (%d): %v", len(dst), r.err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
if n > maxDebugBytes {
|
||||||
|
dl.Printf("rd bytes (%d): %x...", len(dst), dst[:maxDebugBytes])
|
||||||
|
} else {
|
||||||
|
dl.Printf("rd bytes (%d): %x", len(dst), dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst[:l]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBool() bool {
|
||||||
|
return r.ReadUint8() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint32() uint32 {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, r.err = io.ReadFull(r.r, r.b[:4])
|
||||||
|
if r.err != nil {
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint32: %v", r.err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
v := uint32(r.b[3]) | uint32(r.b[2])<<8 | uint32(r.b[1])<<16 | uint32(r.b[0])<<24
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint32=%d (0x%08x)", v, v)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint64() uint64 {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, r.err = io.ReadFull(r.r, r.b[:8])
|
||||||
|
if r.err != nil {
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint64: %v", r.err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
v := uint64(r.b[7]) | uint64(r.b[6])<<8 | uint64(r.b[5])<<16 | uint64(r.b[4])<<24 |
|
||||||
|
uint64(r.b[3])<<32 | uint64(r.b[2])<<40 | uint64(r.b[1])<<48 | uint64(r.b[0])<<56
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint64=%d (0x%016x)", v, v)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
type XDRError struct {
|
||||||
|
op string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e XDRError) Error() string {
|
||||||
|
return "xdr " + e.op + ": " + e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e XDRError) IsEOF() bool {
|
||||||
|
return e.err == io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Error() error {
|
||||||
|
if r.err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return XDRError{"read", r.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ElementSizeExceeded(field string, size, limit int) error {
|
||||||
|
return fmt.Errorf("%s exceeds size limit; %d > %d", field, size, limit)
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint8() uint8 {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, r.err = io.ReadFull(r.r, r.b[:1])
|
||||||
|
if r.err != nil {
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint8: %v", r.err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint8=%d (0x%02x)", r.b[0], r.b[0])
|
||||||
|
}
|
||||||
|
return r.b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint16() uint16 {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, r.err = io.ReadFull(r.r, r.b[:2])
|
||||||
|
if r.err != nil {
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint16: %v", r.err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
v := uint16(r.b[1]) | uint16(r.b[0])<<8
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("rd uint16=%d (0x%04x)", v, v)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||||
|
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint8() uint8 {
|
||||||
|
return uint8(r.ReadUint32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint16() uint16 {
|
||||||
|
return uint16(r.ReadUint32())
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build refl
|
||||||
|
|
||||||
|
package xdr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
refl "github.com/davecgh/go-xdr/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompareMarshals(t *testing.T) {
|
||||||
|
e0 := s.MarshalXDR()
|
||||||
|
e1, err := refl.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if bytes.Compare(e0, e1) != 0 {
|
||||||
|
t.Fatalf("Encoding mismatch;\n\t%x (this)\n\t%x (refl)", e0, e1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReflMarshal(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
res, err = refl.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReflUnmarshal(b *testing.B) {
|
||||||
|
var t XDRBenchStruct
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := refl.Unmarshal(e, &t)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var padBytes = []byte{0, 0, 0}
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
tot int
|
||||||
|
err error
|
||||||
|
b [8]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppendWriter []byte
|
||||||
|
|
||||||
|
func (w *AppendWriter) Write(bs []byte) (int, error) {
|
||||||
|
*w = append(*w, bs...)
|
||||||
|
return len(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteRaw(bs []byte) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, w.err = w.w.Write(bs)
|
||||||
|
return n, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteString(s string) (int, error) {
|
||||||
|
sh := *((*reflect.StringHeader)(unsafe.Pointer(&s)))
|
||||||
|
bh := reflect.SliceHeader{
|
||||||
|
Data: sh.Data,
|
||||||
|
Len: sh.Len,
|
||||||
|
Cap: sh.Len,
|
||||||
|
}
|
||||||
|
return w.WriteBytes(*(*[]byte)(unsafe.Pointer(&bh)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteBytes(bs []byte) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteUint32(uint32(len(bs)))
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
if len(bs) > maxDebugBytes {
|
||||||
|
dl.Printf("wr bytes (%d): %x...", len(bs), bs[:maxDebugBytes])
|
||||||
|
} else {
|
||||||
|
dl.Printf("wr bytes (%d): %x", len(bs), bs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var l, n int
|
||||||
|
n, w.err = w.w.Write(bs)
|
||||||
|
l += n
|
||||||
|
|
||||||
|
if p := pad(len(bs)); w.err == nil && p > 0 {
|
||||||
|
n, w.err = w.w.Write(padBytes[:p])
|
||||||
|
l += n
|
||||||
|
}
|
||||||
|
|
||||||
|
w.tot += l
|
||||||
|
return l, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteBool(v bool) (int, error) {
|
||||||
|
if v {
|
||||||
|
return w.WriteUint8(1)
|
||||||
|
} else {
|
||||||
|
return w.WriteUint8(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint32(v uint32) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("wr uint32=%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.b[0] = byte(v >> 24)
|
||||||
|
w.b[1] = byte(v >> 16)
|
||||||
|
w.b[2] = byte(v >> 8)
|
||||||
|
w.b[3] = byte(v)
|
||||||
|
|
||||||
|
var l int
|
||||||
|
l, w.err = w.w.Write(w.b[:4])
|
||||||
|
w.tot += l
|
||||||
|
return l, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint64(v uint64) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("wr uint64=%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.b[0] = byte(v >> 56)
|
||||||
|
w.b[1] = byte(v >> 48)
|
||||||
|
w.b[2] = byte(v >> 40)
|
||||||
|
w.b[3] = byte(v >> 32)
|
||||||
|
w.b[4] = byte(v >> 24)
|
||||||
|
w.b[5] = byte(v >> 16)
|
||||||
|
w.b[6] = byte(v >> 8)
|
||||||
|
w.b[7] = byte(v)
|
||||||
|
|
||||||
|
var l int
|
||||||
|
l, w.err = w.w.Write(w.b[:8])
|
||||||
|
w.tot += l
|
||||||
|
return l, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Tot() int {
|
||||||
|
return w.tot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Error() error {
|
||||||
|
if w.err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return XDRError{"write", w.err}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint8(v uint8) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("wr uint8=%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.b[0] = byte(v)
|
||||||
|
|
||||||
|
var l int
|
||||||
|
l, w.err = w.w.Write(w.b[:1])
|
||||||
|
w.tot += l
|
||||||
|
return l, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint16(v uint16) (int, error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
dl.Printf("wr uint8=%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.b[0] = byte(v >> 8)
|
||||||
|
w.b[1] = byte(v)
|
||||||
|
|
||||||
|
var l int
|
||||||
|
l, w.err = w.w.Write(w.b[:2])
|
||||||
|
w.tot += l
|
||||||
|
return l, w.err
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !ipdr
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint8(v uint8) (int, error) {
|
||||||
|
return w.WriteUint32(uint32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteUint16(v uint16) (int, error) {
|
||||||
|
return w.WriteUint32(uint32(v))
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
||||||
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xdr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBytesNil(t *testing.T) {
|
||||||
|
fn := func(bs []byte) bool {
|
||||||
|
var b = new(bytes.Buffer)
|
||||||
|
var w = NewWriter(b)
|
||||||
|
var r = NewReader(b)
|
||||||
|
w.WriteBytes(bs)
|
||||||
|
w.WriteBytes(bs)
|
||||||
|
r.ReadBytes()
|
||||||
|
res := r.ReadBytes()
|
||||||
|
return bytes.Compare(bs, res) == 0
|
||||||
|
}
|
||||||
|
if err := quick.Check(fn, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBytesGiven(t *testing.T) {
|
||||||
|
fn := func(bs []byte) bool {
|
||||||
|
var b = new(bytes.Buffer)
|
||||||
|
var w = NewWriter(b)
|
||||||
|
var r = NewReader(b)
|
||||||
|
w.WriteBytes(bs)
|
||||||
|
w.WriteBytes(bs)
|
||||||
|
res := make([]byte, 12)
|
||||||
|
res = r.ReadBytesInto(res)
|
||||||
|
res = r.ReadBytesInto(res)
|
||||||
|
return bytes.Compare(bs, res) == 0
|
||||||
|
}
|
||||||
|
if err := quick.Check(fn, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadBytesMaxInto(t *testing.T) {
|
||||||
|
var max = 64
|
||||||
|
for tot := 32; tot < 128; tot++ {
|
||||||
|
for diff := -32; diff <= 32; diff++ {
|
||||||
|
var b = new(bytes.Buffer)
|
||||||
|
var r = NewReader(b)
|
||||||
|
var w = NewWriter(b)
|
||||||
|
|
||||||
|
var toWrite = make([]byte, tot)
|
||||||
|
w.WriteBytes(toWrite)
|
||||||
|
|
||||||
|
var buf = make([]byte, tot+diff)
|
||||||
|
var bs = r.ReadBytesMaxInto(max, buf)
|
||||||
|
|
||||||
|
if tot <= max {
|
||||||
|
if read := len(bs); read != tot {
|
||||||
|
t.Errorf("Incorrect read bytes, wrote=%d, buf=%d, max=%d, read=%d", tot, tot+diff, max, read)
|
||||||
|
}
|
||||||
|
} else if !strings.Contains(r.err.Error(), "exceeds size") {
|
||||||
|
t.Errorf("Unexpected non-ErrElementSizeExceeded error for wrote=%d, max=%d: %v", tot, max, r.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadStringMax(t *testing.T) {
|
||||||
|
for tot := 42; tot < 72; tot++ {
|
||||||
|
for max := 0; max < 128; max++ {
|
||||||
|
var b = new(bytes.Buffer)
|
||||||
|
var r = NewReader(b)
|
||||||
|
var w = NewWriter(b)
|
||||||
|
|
||||||
|
var toWrite = make([]byte, tot)
|
||||||
|
w.WriteBytes(toWrite)
|
||||||
|
|
||||||
|
var str = r.ReadStringMax(max)
|
||||||
|
var read = len(str)
|
||||||
|
|
||||||
|
if max == 0 || tot <= max {
|
||||||
|
if read != tot {
|
||||||
|
t.Errorf("Incorrect read bytes, wrote=%d, max=%d, read=%d", tot, max, read)
|
||||||
|
}
|
||||||
|
} else if !strings.Contains(r.err.Error(), "exceeds size") {
|
||||||
|
t.Errorf("Unexpected non-ErrElementSizeExceeded error for wrote=%d, max=%d, read=%d: %v", tot, max, read, r.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
This package contains an efficient token-bucket-based rate limiter.
|
||||||
|
Copyright (C) 2015 Canonical Ltd.
|
||||||
|
|
||||||
|
This software is licensed under the LGPLv3, included below.
|
||||||
|
|
||||||
|
As a special exception to the GNU Lesser General Public License version 3
|
||||||
|
("LGPL3"), the copyright holders of this Library give you permission to
|
||||||
|
convey to a third party a Combined Work that links statically or dynamically
|
||||||
|
to this Library without providing any Minimal Corresponding Source or
|
||||||
|
Minimal Application Code as set out in 4d or providing the installation
|
||||||
|
information set out in section 4e, provided that you comply with the other
|
||||||
|
provisions of LGPL3 and provided that you meet, for the Application the
|
||||||
|
terms and conditions of the license(s) which apply to the Application.
|
||||||
|
|
||||||
|
Except as stated in this special exception, the provisions of LGPL3 will
|
||||||
|
continue to comply in full to this Library. If you modify this Library, you
|
||||||
|
may apply this exception to your version of this Library, but you are not
|
||||||
|
obliged to do so. If you do not wish to do so, delete this exception
|
||||||
|
statement from your version. This exception does not (and cannot) modify any
|
||||||
|
license terms which apply to the Application, with which you must still
|
||||||
|
comply.
|
||||||
|
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
# ratelimit
|
||||||
|
--
|
||||||
|
import "github.com/juju/ratelimit"
|
||||||
|
|
||||||
|
The ratelimit package provides an efficient token bucket implementation. See
|
||||||
|
http://en.wikipedia.org/wiki/Token_bucket.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
#### func Reader
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Reader(r io.Reader, bucket *Bucket) io.Reader
|
||||||
|
```
|
||||||
|
Reader returns a reader that is rate limited by the given token bucket. Each
|
||||||
|
token in the bucket represents one byte.
|
||||||
|
|
||||||
|
#### func Writer
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Writer(w io.Writer, bucket *Bucket) io.Writer
|
||||||
|
```
|
||||||
|
Writer returns a reader that is rate limited by the given token bucket. Each
|
||||||
|
token in the bucket represents one byte.
|
||||||
|
|
||||||
|
#### type Bucket
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Bucket struct {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bucket represents a token bucket that fills at a predetermined rate. Methods on
|
||||||
|
Bucket may be called concurrently.
|
||||||
|
|
||||||
|
#### func NewBucket
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucket returns a new token bucket that fills at the rate of one token every
|
||||||
|
fillInterval, up to the given maximum capacity. Both arguments must be positive.
|
||||||
|
The bucket is initially full.
|
||||||
|
|
||||||
|
#### func NewBucketWithQuantum
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucketWithQuantum is similar to NewBucket, but allows the specification of
|
||||||
|
the quantum size - quantum tokens are added every fillInterval.
|
||||||
|
|
||||||
|
#### func NewBucketWithRate
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBucketWithRate(rate float64, capacity int64) *Bucket
|
||||||
|
```
|
||||||
|
NewBucketWithRate returns a token bucket that fills the bucket at the rate of
|
||||||
|
rate tokens per second up to the given maximum capacity. Because of limited
|
||||||
|
clock resolution, at high rates, the actual rate may be up to 1% different from
|
||||||
|
the specified rate.
|
||||||
|
|
||||||
|
#### func (*Bucket) Rate
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Rate() float64
|
||||||
|
```
|
||||||
|
Rate returns the fill rate of the bucket, in tokens per second.
|
||||||
|
|
||||||
|
#### func (*Bucket) Take
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Take(count int64) time.Duration
|
||||||
|
```
|
||||||
|
Take takes count tokens from the bucket without blocking. It returns the time
|
||||||
|
that the caller should wait until the tokens are actually available.
|
||||||
|
|
||||||
|
Note that if the request is irrevocable - there is no way to return tokens to
|
||||||
|
the bucket once this method commits us to taking them.
|
||||||
|
|
||||||
|
#### func (*Bucket) TakeAvailable
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) TakeAvailable(count int64) int64
|
||||||
|
```
|
||||||
|
TakeAvailable takes up to count immediately available tokens from the bucket. It
|
||||||
|
returns the number of tokens removed, or zero if there are no available tokens.
|
||||||
|
It does not block.
|
||||||
|
|
||||||
|
#### func (*Bucket) TakeMaxDuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)
|
||||||
|
```
|
||||||
|
TakeMaxDuration is like Take, except that it will only take tokens from the
|
||||||
|
bucket if the wait time for the tokens is no greater than maxWait.
|
||||||
|
|
||||||
|
If it would take longer than maxWait for the tokens to become available, it does
|
||||||
|
nothing and reports false, otherwise it returns the time that the caller should
|
||||||
|
wait until the tokens are actually available, and reports true.
|
||||||
|
|
||||||
|
#### func (*Bucket) Wait
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) Wait(count int64)
|
||||||
|
```
|
||||||
|
Wait takes count tokens from the bucket, waiting until they are available.
|
||||||
|
|
||||||
|
#### func (*Bucket) WaitMaxDuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool
|
||||||
|
```
|
||||||
|
WaitMaxDuration is like Wait except that it will only take tokens from the
|
||||||
|
bucket if it needs to wait for no greater than maxWait. It reports whether any
|
||||||
|
tokens have been removed from the bucket If no tokens have been removed, it
|
||||||
|
returns immediately.
|
||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
|
// See LICENCE file for details.
|
||||||
|
|
||||||
|
// The ratelimit package provides an efficient token bucket implementation.
|
||||||
|
// See http://en.wikipedia.org/wiki/Token_bucket.
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bucket represents a token bucket that fills at a predetermined rate.
|
||||||
|
// Methods on Bucket may be called concurrently.
|
||||||
|
type Bucket struct {
|
||||||
|
startTime time.Time
|
||||||
|
capacity int64
|
||||||
|
quantum int64
|
||||||
|
fillInterval time.Duration
|
||||||
|
|
||||||
|
// The mutex guards the fields following it.
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// avail holds the number of available tokens
|
||||||
|
// in the bucket, as of availTick ticks from startTime.
|
||||||
|
// It will be negative when there are consumers
|
||||||
|
// waiting for tokens.
|
||||||
|
avail int64
|
||||||
|
availTick int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucket returns a new token bucket that fills at the
|
||||||
|
// rate of one token every fillInterval, up to the given
|
||||||
|
// maximum capacity. Both arguments must be
|
||||||
|
// positive. The bucket is initially full.
|
||||||
|
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket {
|
||||||
|
return NewBucketWithQuantum(fillInterval, capacity, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rateMargin specifes the allowed variance of actual
|
||||||
|
// rate from specified rate. 1% seems reasonable.
|
||||||
|
const rateMargin = 0.01
|
||||||
|
|
||||||
|
// NewBucketWithRate returns a token bucket that fills the bucket
|
||||||
|
// at the rate of rate tokens per second up to the given
|
||||||
|
// maximum capacity. Because of limited clock resolution,
|
||||||
|
// at high rates, the actual rate may be up to 1% different from the
|
||||||
|
// specified rate.
|
||||||
|
func NewBucketWithRate(rate float64, capacity int64) *Bucket {
|
||||||
|
for quantum := int64(1); quantum < 1<<50; quantum = nextQuantum(quantum) {
|
||||||
|
fillInterval := time.Duration(1e9 * float64(quantum) / rate)
|
||||||
|
if fillInterval <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
|
||||||
|
if diff := abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
||||||
|
return tb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("cannot find suitable quantum for " + strconv.FormatFloat(rate, 'g', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextQuantum returns the next quantum to try after q.
|
||||||
|
// We grow the quantum exponentially, but slowly, so we
|
||||||
|
// get a good fit in the lower numbers.
|
||||||
|
func nextQuantum(q int64) int64 {
|
||||||
|
q1 := q * 11 / 10
|
||||||
|
if q1 == q {
|
||||||
|
q1++
|
||||||
|
}
|
||||||
|
return q1
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBucketWithQuantum is similar to NewBucket, but allows
|
||||||
|
// the specification of the quantum size - quantum tokens
|
||||||
|
// are added every fillInterval.
|
||||||
|
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket {
|
||||||
|
if fillInterval <= 0 {
|
||||||
|
panic("token bucket fill interval is not > 0")
|
||||||
|
}
|
||||||
|
if capacity <= 0 {
|
||||||
|
panic("token bucket capacity is not > 0")
|
||||||
|
}
|
||||||
|
if quantum <= 0 {
|
||||||
|
panic("token bucket quantum is not > 0")
|
||||||
|
}
|
||||||
|
return &Bucket{
|
||||||
|
startTime: time.Now(),
|
||||||
|
capacity: capacity,
|
||||||
|
quantum: quantum,
|
||||||
|
avail: capacity,
|
||||||
|
fillInterval: fillInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait takes count tokens from the bucket, waiting until they are
|
||||||
|
// available.
|
||||||
|
func (tb *Bucket) Wait(count int64) {
|
||||||
|
if d := tb.Take(count); d > 0 {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitMaxDuration is like Wait except that it will
|
||||||
|
// only take tokens from the bucket if it needs to wait
|
||||||
|
// for no greater than maxWait. It reports whether
|
||||||
|
// any tokens have been removed from the bucket
|
||||||
|
// If no tokens have been removed, it returns immediately.
|
||||||
|
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {
|
||||||
|
d, ok := tb.TakeMaxDuration(count, maxWait)
|
||||||
|
if d > 0 {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
const infinityDuration time.Duration = 0x7fffffffffffffff
|
||||||
|
|
||||||
|
// Take takes count tokens from the bucket without blocking. It returns
|
||||||
|
// the time that the caller should wait until the tokens are actually
|
||||||
|
// available.
|
||||||
|
//
|
||||||
|
// Note that if the request is irrevocable - there is no way to return
|
||||||
|
// tokens to the bucket once this method commits us to taking them.
|
||||||
|
func (tb *Bucket) Take(count int64) time.Duration {
|
||||||
|
d, _ := tb.take(time.Now(), count, infinityDuration)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeMaxDuration is like Take, except that
|
||||||
|
// it will only take tokens from the bucket if the wait
|
||||||
|
// time for the tokens is no greater than maxWait.
|
||||||
|
//
|
||||||
|
// If it would take longer than maxWait for the tokens
|
||||||
|
// to become available, it does nothing and reports false,
|
||||||
|
// otherwise it returns the time that the caller should
|
||||||
|
// wait until the tokens are actually available, and reports
|
||||||
|
// true.
|
||||||
|
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool) {
|
||||||
|
return tb.take(time.Now(), count, maxWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TakeAvailable takes up to count immediately available tokens from the
|
||||||
|
// bucket. It returns the number of tokens removed, or zero if there are
|
||||||
|
// no available tokens. It does not block.
|
||||||
|
func (tb *Bucket) TakeAvailable(count int64) int64 {
|
||||||
|
return tb.takeAvailable(time.Now(), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeAvailable is the internal version of TakeAvailable - it takes the
|
||||||
|
// current time as an argument to enable easy testing.
|
||||||
|
func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
|
||||||
|
if count <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
tb.mu.Lock()
|
||||||
|
defer tb.mu.Unlock()
|
||||||
|
|
||||||
|
tb.adjust(now)
|
||||||
|
if tb.avail <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if count > tb.avail {
|
||||||
|
count = tb.avail
|
||||||
|
}
|
||||||
|
tb.avail -= count
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate returns the fill rate of the bucket, in tokens per second.
|
||||||
|
func (tb *Bucket) Rate() float64 {
|
||||||
|
return 1e9 * float64(tb.quantum) / float64(tb.fillInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// take is the internal version of Take - it takes the current time as
|
||||||
|
// an argument to enable easy testing.
|
||||||
|
func (tb *Bucket) take(now time.Time, count int64, maxWait time.Duration) (time.Duration, bool) {
|
||||||
|
if count <= 0 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
tb.mu.Lock()
|
||||||
|
defer tb.mu.Unlock()
|
||||||
|
|
||||||
|
currentTick := tb.adjust(now)
|
||||||
|
avail := tb.avail - count
|
||||||
|
if avail >= 0 {
|
||||||
|
tb.avail = avail
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
// Round up the missing tokens to the nearest multiple
|
||||||
|
// of quantum - the tokens won't be available until
|
||||||
|
// that tick.
|
||||||
|
endTick := currentTick + (-avail+tb.quantum-1)/tb.quantum
|
||||||
|
endTime := tb.startTime.Add(time.Duration(endTick) * tb.fillInterval)
|
||||||
|
waitTime := endTime.Sub(now)
|
||||||
|
if waitTime > maxWait {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
tb.avail = avail
|
||||||
|
return waitTime, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust adjusts the current bucket capacity based on the current time.
|
||||||
|
// It returns the current tick.
|
||||||
|
func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
|
||||||
|
currentTick = int64(now.Sub(tb.startTime) / tb.fillInterval)
|
||||||
|
|
||||||
|
if tb.avail >= tb.capacity {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tb.avail += (currentTick - tb.availTick) * tb.quantum
|
||||||
|
if tb.avail > tb.capacity {
|
||||||
|
tb.avail = tb.capacity
|
||||||
|
}
|
||||||
|
tb.availTick = currentTick
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(f float64) float64 {
|
||||||
|
if f < 0 {
|
||||||
|
return -f
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
+328
@@ -0,0 +1,328 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
|
// See LICENCE file for details.
|
||||||
|
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
gc "launchpad.net/gocheck"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPackage(t *testing.T) {
|
||||||
|
gc.TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type rateLimitSuite struct{}
|
||||||
|
|
||||||
|
var _ = gc.Suite(rateLimitSuite{})
|
||||||
|
|
||||||
|
type takeReq struct {
|
||||||
|
time time.Duration
|
||||||
|
count int64
|
||||||
|
expectWait time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var takeTests = []struct {
|
||||||
|
about string
|
||||||
|
fillInterval time.Duration
|
||||||
|
capacity int64
|
||||||
|
reqs []takeReq
|
||||||
|
}{{
|
||||||
|
about: "serial requests",
|
||||||
|
fillInterval: 250 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 0,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 250 * time.Millisecond,
|
||||||
|
}, {
|
||||||
|
time: 250 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 250 * time.Millisecond,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "concurrent requests",
|
||||||
|
fillInterval: 250 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 2,
|
||||||
|
expectWait: 500 * time.Millisecond,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 2,
|
||||||
|
expectWait: 1000 * time.Millisecond,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 1250 * time.Millisecond,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "more than capacity",
|
||||||
|
fillInterval: 1 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 20 * time.Millisecond,
|
||||||
|
count: 15,
|
||||||
|
expectWait: 5 * time.Millisecond,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "sub-quantum time",
|
||||||
|
fillInterval: 10 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 7 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 3 * time.Millisecond,
|
||||||
|
}, {
|
||||||
|
time: 8 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 12 * time.Millisecond,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "within capacity",
|
||||||
|
fillInterval: 10 * time.Millisecond,
|
||||||
|
capacity: 5,
|
||||||
|
reqs: []takeReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 5,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 60 * time.Millisecond,
|
||||||
|
count: 5,
|
||||||
|
expectWait: 0,
|
||||||
|
}, {
|
||||||
|
time: 60 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expectWait: 10 * time.Millisecond,
|
||||||
|
}, {
|
||||||
|
time: 80 * time.Millisecond,
|
||||||
|
count: 2,
|
||||||
|
expectWait: 10 * time.Millisecond,
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestTake(c *gc.C) {
|
||||||
|
for i, test := range takeTests {
|
||||||
|
tb := NewBucket(test.fillInterval, test.capacity)
|
||||||
|
for j, req := range test.reqs {
|
||||||
|
d, ok := tb.take(tb.startTime.Add(req.time), req.count, infinityDuration)
|
||||||
|
c.Assert(ok, gc.Equals, true)
|
||||||
|
if d != req.expectWait {
|
||||||
|
c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expectWait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestTakeMaxDuration(c *gc.C) {
|
||||||
|
for i, test := range takeTests {
|
||||||
|
tb := NewBucket(test.fillInterval, test.capacity)
|
||||||
|
for j, req := range test.reqs {
|
||||||
|
if req.expectWait > 0 {
|
||||||
|
d, ok := tb.take(tb.startTime.Add(req.time), req.count, req.expectWait-1)
|
||||||
|
c.Assert(ok, gc.Equals, false)
|
||||||
|
c.Assert(d, gc.Equals, time.Duration(0))
|
||||||
|
}
|
||||||
|
d, ok := tb.take(tb.startTime.Add(req.time), req.count, req.expectWait)
|
||||||
|
c.Assert(ok, gc.Equals, true)
|
||||||
|
if d != req.expectWait {
|
||||||
|
c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expectWait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type takeAvailableReq struct {
|
||||||
|
time time.Duration
|
||||||
|
count int64
|
||||||
|
expect int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var takeAvailableTests = []struct {
|
||||||
|
about string
|
||||||
|
fillInterval time.Duration
|
||||||
|
capacity int64
|
||||||
|
reqs []takeAvailableReq
|
||||||
|
}{{
|
||||||
|
about: "serial requests",
|
||||||
|
fillInterval: 250 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeAvailableReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 0,
|
||||||
|
expect: 0,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expect: 10,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 1,
|
||||||
|
expect: 0,
|
||||||
|
}, {
|
||||||
|
time: 250 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expect: 1,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "concurrent requests",
|
||||||
|
fillInterval: 250 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeAvailableReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 5,
|
||||||
|
expect: 5,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 2,
|
||||||
|
expect: 2,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 5,
|
||||||
|
expect: 3,
|
||||||
|
}, {
|
||||||
|
time: 0,
|
||||||
|
count: 1,
|
||||||
|
expect: 0,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "more than capacity",
|
||||||
|
fillInterval: 1 * time.Millisecond,
|
||||||
|
capacity: 10,
|
||||||
|
reqs: []takeAvailableReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 10,
|
||||||
|
expect: 10,
|
||||||
|
}, {
|
||||||
|
time: 20 * time.Millisecond,
|
||||||
|
count: 15,
|
||||||
|
expect: 10,
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
about: "within capacity",
|
||||||
|
fillInterval: 10 * time.Millisecond,
|
||||||
|
capacity: 5,
|
||||||
|
reqs: []takeAvailableReq{{
|
||||||
|
time: 0,
|
||||||
|
count: 5,
|
||||||
|
expect: 5,
|
||||||
|
}, {
|
||||||
|
time: 60 * time.Millisecond,
|
||||||
|
count: 5,
|
||||||
|
expect: 5,
|
||||||
|
}, {
|
||||||
|
time: 70 * time.Millisecond,
|
||||||
|
count: 1,
|
||||||
|
expect: 1,
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestTakeAvailable(c *gc.C) {
|
||||||
|
for i, test := range takeAvailableTests {
|
||||||
|
tb := NewBucket(test.fillInterval, test.capacity)
|
||||||
|
for j, req := range test.reqs {
|
||||||
|
d := tb.takeAvailable(tb.startTime.Add(req.time), req.count)
|
||||||
|
if d != req.expect {
|
||||||
|
c.Fatalf("test %d.%d, %s, got %v want %v", i, j, test.about, d, req.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestPanics(c *gc.C) {
|
||||||
|
c.Assert(func() { NewBucket(0, 1) }, gc.PanicMatches, "token bucket fill interval is not > 0")
|
||||||
|
c.Assert(func() { NewBucket(-2, 1) }, gc.PanicMatches, "token bucket fill interval is not > 0")
|
||||||
|
c.Assert(func() { NewBucket(1, 0) }, gc.PanicMatches, "token bucket capacity is not > 0")
|
||||||
|
c.Assert(func() { NewBucket(1, -2) }, gc.PanicMatches, "token bucket capacity is not > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCloseTo(x, y, tolerance float64) bool {
|
||||||
|
return abs(x-y)/y < tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestRate(c *gc.C) {
|
||||||
|
tb := NewBucket(1, 1)
|
||||||
|
if !isCloseTo(tb.Rate(), 1e9, 0.00001) {
|
||||||
|
c.Fatalf("got %v want 1e9", tb.Rate())
|
||||||
|
}
|
||||||
|
tb = NewBucket(2*time.Second, 1)
|
||||||
|
if !isCloseTo(tb.Rate(), 0.5, 0.00001) {
|
||||||
|
c.Fatalf("got %v want 0.5", tb.Rate())
|
||||||
|
}
|
||||||
|
tb = NewBucketWithQuantum(100*time.Millisecond, 1, 5)
|
||||||
|
if !isCloseTo(tb.Rate(), 50, 0.00001) {
|
||||||
|
c.Fatalf("got %v want 50", tb.Rate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRate(c *gc.C, rate float64) {
|
||||||
|
tb := NewBucketWithRate(rate, 1<<62)
|
||||||
|
if !isCloseTo(tb.Rate(), rate, rateMargin) {
|
||||||
|
c.Fatalf("got %g want %v", tb.Rate(), rate)
|
||||||
|
}
|
||||||
|
d, ok := tb.take(tb.startTime, 1<<62, infinityDuration)
|
||||||
|
c.Assert(ok, gc.Equals, true)
|
||||||
|
c.Assert(d, gc.Equals, time.Duration(0))
|
||||||
|
|
||||||
|
// Check that the actual rate is as expected by
|
||||||
|
// asking for a not-quite multiple of the bucket's
|
||||||
|
// quantum and checking that the wait time
|
||||||
|
// correct.
|
||||||
|
d, ok = tb.take(tb.startTime, tb.quantum*2-tb.quantum/2, infinityDuration)
|
||||||
|
c.Assert(ok, gc.Equals, true)
|
||||||
|
expectTime := 1e9 * float64(tb.quantum) * 2 / rate
|
||||||
|
if !isCloseTo(float64(d), expectTime, rateMargin) {
|
||||||
|
c.Fatalf("rate %g: got %g want %v", rate, float64(d), expectTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rateLimitSuite) TestNewWithRate(c *gc.C) {
|
||||||
|
for rate := float64(1); rate < 1e6; rate += 7 {
|
||||||
|
checkRate(c, rate)
|
||||||
|
}
|
||||||
|
for _, rate := range []float64{
|
||||||
|
1024 * 1024 * 1024,
|
||||||
|
1e-5,
|
||||||
|
0.9e-5,
|
||||||
|
0.5,
|
||||||
|
0.9,
|
||||||
|
0.9e8,
|
||||||
|
3e12,
|
||||||
|
4e18,
|
||||||
|
} {
|
||||||
|
checkRate(c, rate)
|
||||||
|
checkRate(c, rate/3)
|
||||||
|
checkRate(c, rate*1.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWait(b *testing.B) {
|
||||||
|
tb := NewBucket(1, 16*1024)
|
||||||
|
for i := b.N - 1; i >= 0; i-- {
|
||||||
|
tb.Wait(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
|
// See LICENCE file for details.
|
||||||
|
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type reader struct {
|
||||||
|
r io.Reader
|
||||||
|
bucket *Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader returns a reader that is rate limited by
|
||||||
|
// the given token bucket. Each token in the bucket
|
||||||
|
// represents one byte.
|
||||||
|
func Reader(r io.Reader, bucket *Bucket) io.Reader {
|
||||||
|
return &reader{
|
||||||
|
r: r,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reader) Read(buf []byte) (int, error) {
|
||||||
|
n, err := r.r.Read(buf)
|
||||||
|
if n <= 0 {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
r.bucket.Wait(int64(n))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type writer struct {
|
||||||
|
w io.Writer
|
||||||
|
bucket *Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer returns a reader that is rate limited by
|
||||||
|
// the given token bucket. Each token in the bucket
|
||||||
|
// represents one byte.
|
||||||
|
func Writer(w io.Writer, bucket *Bucket) io.Writer {
|
||||||
|
return &writer{
|
||||||
|
w: w,
|
||||||
|
bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Write(buf []byte) (int, error) {
|
||||||
|
w.bucket.Wait(int64(len(buf)))
|
||||||
|
return w.w.Write(buf)
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
### Extensions to the "os" package.
|
||||||
|
|
||||||
|
## Find the current Executable and ExecutableFolder.
|
||||||
|
|
||||||
|
There is sometimes utility in finding the current executable file
|
||||||
|
that is running. This can be used for upgrading the current executable
|
||||||
|
or finding resources located relative to the executable file.
|
||||||
|
|
||||||
|
Multi-platform and supports:
|
||||||
|
* Linux
|
||||||
|
* OS X
|
||||||
|
* Windows
|
||||||
|
* Plan 9
|
||||||
|
* BSDs.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Extensions to the standard "os" package.
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
|
// Executable returns an absolute path that can be used to
|
||||||
|
// re-invoke the current program.
|
||||||
|
// It may not be valid after the current program exits.
|
||||||
|
func Executable() (string, error) {
|
||||||
|
p, err := executable()
|
||||||
|
return filepath.Clean(p), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns same path as Executable, returns just the folder
|
||||||
|
// path. Excludes the executable name.
|
||||||
|
func ExecutableFolder() (string, error) {
|
||||||
|
p, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
folder, _ := filepath.Split(p)
|
||||||
|
return folder, nil
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return syscall.Fd2path(int(f.Fd()))
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux netbsd openbsd solaris dragonfly
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
const deletedSuffix = " (deleted)"
|
||||||
|
execpath, err := os.Readlink("/proc/self/exe")
|
||||||
|
if err != nil {
|
||||||
|
return execpath, err
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(execpath, deletedSuffix), nil
|
||||||
|
case "netbsd":
|
||||||
|
return os.Readlink("/proc/curproc/exe")
|
||||||
|
case "openbsd", "dragonfly":
|
||||||
|
return os.Readlink("/proc/curproc/file")
|
||||||
|
case "solaris":
|
||||||
|
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
||||||
|
}
|
||||||
|
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initCwd, initCwdErr = os.Getwd()
|
||||||
|
|
||||||
|
func executable() (string, error) {
|
||||||
|
var mib [4]int32
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "freebsd":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
||||||
|
case "darwin":
|
||||||
|
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := uintptr(0)
|
||||||
|
// Get length.
|
||||||
|
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
||||||
|
if errNum != 0 {
|
||||||
|
return "", errNum
|
||||||
|
}
|
||||||
|
if n == 0 { // This shouldn't happen.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
for i, v := range buf {
|
||||||
|
if v == 0 {
|
||||||
|
buf = buf[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
execPath := string(buf)
|
||||||
|
// execPath will not be empty due to above checks.
|
||||||
|
// Try to get the absolute path if the execPath is not rooted.
|
||||||
|
if execPath[0] != '/' {
|
||||||
|
execPath, err = getAbs(execPath)
|
||||||
|
if err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
|
||||||
|
// actual executable.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
|
||||||
|
return execPath, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return execPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAbs(execPath string) (string, error) {
|
||||||
|
if initCwdErr != nil {
|
||||||
|
return execPath, initCwdErr
|
||||||
|
}
|
||||||
|
// The execPath may begin with a "../" or a "./" so clean it first.
|
||||||
|
// Join the two paths, trailing and starting slashes undetermined, so use
|
||||||
|
// the generic Join function.
|
||||||
|
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
|
||||||
|
}
|
||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin linux freebsd netbsd windows
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
||||||
|
|
||||||
|
executableEnvValueMatch = "match"
|
||||||
|
executableEnvValueDelete = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExecutableMatch(t *testing.T) {
|
||||||
|
ep, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Executable failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullpath to be of the form "dir/prog".
|
||||||
|
dir := filepath.Dir(filepath.Dir(ep))
|
||||||
|
fullpath, err := filepath.Rel(dir, ep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("filepath.Rel: %v", err)
|
||||||
|
}
|
||||||
|
// Make child start with a relative program path.
|
||||||
|
// Alter argv[0] for child to verify getting real path without argv[0].
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Dir: dir,
|
||||||
|
Path: fullpath,
|
||||||
|
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
||||||
|
}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) failed: %v", err)
|
||||||
|
}
|
||||||
|
outs := string(out)
|
||||||
|
if !filepath.IsAbs(outs) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||||
|
}
|
||||||
|
if !sameFile(outs, ep) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecutableDelete(t *testing.T) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
fpath, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Executable failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
stderrBuff := &bytes.Buffer{}
|
||||||
|
stdoutBuff := &bytes.Buffer{}
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: fpath,
|
||||||
|
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
||||||
|
Stdin: r,
|
||||||
|
Stderr: stderrBuff,
|
||||||
|
Stdout: stdoutBuff,
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) start failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPath := fpath + "_copy"
|
||||||
|
_ = os.Remove(tempPath)
|
||||||
|
|
||||||
|
err = copyFile(tempPath, fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("copy file failed: %v", err)
|
||||||
|
}
|
||||||
|
err = os.Remove(fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("remove running test file failed: %v", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(tempPath, fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rename copy to previous name failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte{0})
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec wait failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
childPath := stderrBuff.String()
|
||||||
|
if !filepath.IsAbs(childPath) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
||||||
|
}
|
||||||
|
if !sameFile(childPath, fpath) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(fn1, fn2 string) bool {
|
||||||
|
fi1, err := os.Stat(fn1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fi2, err := os.Stat(fn2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return os.SameFile(fi1, fi2)
|
||||||
|
}
|
||||||
|
func copyFile(dest, src string) error {
|
||||||
|
df, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer df.Close()
|
||||||
|
|
||||||
|
sf, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sf.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(df, sf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
env := os.Getenv(executableEnvVar)
|
||||||
|
switch env {
|
||||||
|
case "":
|
||||||
|
os.Exit(m.Run())
|
||||||
|
case executableEnvValueMatch:
|
||||||
|
// First chdir to another path.
|
||||||
|
dir := "/"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
dir = filepath.VolumeName(".")
|
||||||
|
}
|
||||||
|
os.Chdir(dir)
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
case executableEnvValueDelete:
|
||||||
|
bb := make([]byte, 1)
|
||||||
|
var err error
|
||||||
|
n, err := os.Stdin.Read(bb)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleFileName() with hModule = NULL
|
||||||
|
func executable() (exePath string, err error) {
|
||||||
|
return getModuleFileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getModuleFileName() (string, error) {
|
||||||
|
var n uint32
|
||||||
|
b := make([]uint16, syscall.MAX_PATH)
|
||||||
|
size := uint32(len(b))
|
||||||
|
|
||||||
|
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
||||||
|
n = uint32(r0)
|
||||||
|
if n == 0 {
|
||||||
|
return "", e1
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(b[0:n])), nil
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
# This is the official list of Protocol Authors for copyright purposes.
|
||||||
|
|
||||||
|
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||||
|
Jakob Borg <jakob@nym.se>
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
Please file bugs in the [Github Issue
|
||||||
|
Tracker](https://github.com/syncthing/protocol/issues).
|
||||||
|
|
||||||
|
## Contributing Code
|
||||||
|
|
||||||
|
Every contribution is welcome. Following the points below will make this
|
||||||
|
a smoother process.
|
||||||
|
|
||||||
|
Individuals making significant and valuable contributions are given
|
||||||
|
commit-access to the project. If you make a significant contribution and
|
||||||
|
are not considered for commit-access, please contact any of the
|
||||||
|
Syncthing core team members.
|
||||||
|
|
||||||
|
All nontrivial contributions should go through the pull request
|
||||||
|
mechanism for internal review. Determining what is "nontrivial" is left
|
||||||
|
at the discretion of the contributor.
|
||||||
|
|
||||||
|
### Authorship
|
||||||
|
|
||||||
|
All code authors are listed in the AUTHORS file. Commits must be made
|
||||||
|
with the same name and email as listed in the AUTHORS file. To
|
||||||
|
accomplish this, ensure that your git configuration is set correctly
|
||||||
|
prior to making your first commit;
|
||||||
|
|
||||||
|
$ git config --global user.name "Jane Doe"
|
||||||
|
$ git config --global user.email janedoe@example.com
|
||||||
|
|
||||||
|
You must be reachable on the given email address. If you do not wish to
|
||||||
|
use your real name for whatever reason, using a nickname or pseudonym is
|
||||||
|
perfectly acceptable.
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
|
||||||
|
as much as makes sense.
|
||||||
|
|
||||||
|
- All text files use Unix line endings.
|
||||||
|
|
||||||
|
- Each commit should be `go fmt` clean.
|
||||||
|
|
||||||
|
- The commit message subject should be a single short sentence
|
||||||
|
describing the change, starting with a capital letter.
|
||||||
|
|
||||||
|
- Commits that resolve an existing issue must include the issue number
|
||||||
|
as `(fixes #123)` at the end of the commit message subject.
|
||||||
|
|
||||||
|
- Imports are grouped per `goimports` standard; that is, standard
|
||||||
|
library first, then third party libraries after a blank line.
|
||||||
|
|
||||||
|
- A contribution solving a single issue or introducing a single new
|
||||||
|
feature should probably be a single commit based on the current
|
||||||
|
`master` branch. You may be asked to "rebase" or "squash" your pull
|
||||||
|
request to make sure this is the case, especially if there have been
|
||||||
|
amendments during review.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
All contributions are made under the same MIT license as the rest of the
|
||||||
|
project, except documentation, user interface text and translation
|
||||||
|
strings which are licensed under the Creative Commons Attribution 4.0
|
||||||
|
International License. You retain the copyright to code you have
|
||||||
|
written.
|
||||||
|
|
||||||
|
When accepting your first contribution, the maintainer of the project
|
||||||
|
will ensure that you are added to the AUTHORS file. You are welcome to
|
||||||
|
add yourself as a separate commit in your first pull request.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Yes please!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
The BEPv1 Protocol
|
||||||
|
==================
|
||||||
|
|
||||||
|
[](http://build.syncthing.net/job/protocol/lastBuild/)
|
||||||
|
[](http://godoc.org/github.com/syncthing/protocol)
|
||||||
|
[](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
This is the protocol implementation used by Syncthing.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
MIT
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestModel struct {
|
||||||
|
data []byte
|
||||||
|
folder string
|
||||||
|
name string
|
||||||
|
offset int64
|
||||||
|
size int
|
||||||
|
hash []byte
|
||||||
|
flags uint32
|
||||||
|
options []Option
|
||||||
|
closedCh chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestModel() *TestModel {
|
||||||
|
return &TestModel{
|
||||||
|
closedCh: make(chan bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
|
||||||
|
t.folder = folder
|
||||||
|
t.name = name
|
||||||
|
t.offset = offset
|
||||||
|
t.size = size
|
||||||
|
t.hash = hash
|
||||||
|
t.flags = flags
|
||||||
|
t.options = options
|
||||||
|
return t.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) Close(deviceID DeviceID, err error) {
|
||||||
|
close(t.closedCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestModel) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-t.closedCh:
|
||||||
|
return true
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
return false // Timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrPipe struct {
|
||||||
|
io.PipeWriter
|
||||||
|
written int
|
||||||
|
max int
|
||||||
|
err error
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrPipe) Write(data []byte) (int, error) {
|
||||||
|
if e.closed {
|
||||||
|
return 0, e.err
|
||||||
|
}
|
||||||
|
if e.written+len(data) > e.max {
|
||||||
|
n, _ := e.PipeWriter.Write(data[:e.max-e.written])
|
||||||
|
e.PipeWriter.CloseWithError(e.err)
|
||||||
|
e.closed = true
|
||||||
|
return n, e.err
|
||||||
|
}
|
||||||
|
return e.PipeWriter.Write(data)
|
||||||
|
}
|
||||||
Generated
Vendored
+22
-8
@@ -4,25 +4,39 @@ package protocol
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
type Compression int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
CompressMetadata Compression = iota // zero value is the default, default should be "metadata"
|
||||||
|
CompressNever
|
||||||
|
CompressAlways
|
||||||
|
|
||||||
compressionThreshold = 128 // don't bother compressing messages smaller than this many bytes
|
compressionThreshold = 128 // don't bother compressing messages smaller than this many bytes
|
||||||
)
|
)
|
||||||
|
|
||||||
var compressionMarshal = map[Compression]string{
|
var compressionMarshal = map[Compression]string{
|
||||||
CompressionNever: "never",
|
CompressNever: "never",
|
||||||
CompressionMetadata: "metadata",
|
CompressMetadata: "metadata",
|
||||||
CompressionAlways: "always",
|
CompressAlways: "always",
|
||||||
}
|
}
|
||||||
|
|
||||||
var compressionUnmarshal = map[string]Compression{
|
var compressionUnmarshal = map[string]Compression{
|
||||||
// Legacy
|
// Legacy
|
||||||
"false": CompressionNever,
|
"false": CompressNever,
|
||||||
"true": CompressionMetadata,
|
"true": CompressMetadata,
|
||||||
|
|
||||||
// Current
|
// Current
|
||||||
"never": CompressionNever,
|
"never": CompressNever,
|
||||||
"metadata": CompressionMetadata,
|
"metadata": CompressMetadata,
|
||||||
"always": CompressionAlways,
|
"always": CompressAlways,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Compression) String() string {
|
||||||
|
s, ok := compressionMarshal[c]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("unknown:%d", c)
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Compression) GoString() string {
|
func (c Compression) GoString() string {
|
||||||
Generated
Vendored
+9
-9
@@ -9,21 +9,21 @@ func TestCompressionMarshal(t *testing.T) {
|
|||||||
s string
|
s string
|
||||||
c Compression
|
c Compression
|
||||||
}{
|
}{
|
||||||
{"true", CompressionMetadata},
|
{"true", CompressMetadata},
|
||||||
{"false", CompressionNever},
|
{"false", CompressNever},
|
||||||
{"never", CompressionNever},
|
{"never", CompressNever},
|
||||||
{"metadata", CompressionMetadata},
|
{"metadata", CompressMetadata},
|
||||||
{"always", CompressionAlways},
|
{"always", CompressAlways},
|
||||||
{"whatever", CompressionMetadata},
|
{"whatever", CompressMetadata},
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestcases := []struct {
|
mTestcases := []struct {
|
||||||
s string
|
s string
|
||||||
c Compression
|
c Compression
|
||||||
}{
|
}{
|
||||||
{"never", CompressionNever},
|
{"never", CompressNever},
|
||||||
{"metadata", CompressionMetadata},
|
{"metadata", CompressMetadata},
|
||||||
{"always", CompressionAlways},
|
{"always", CompressAlways},
|
||||||
}
|
}
|
||||||
|
|
||||||
var c Compression
|
var c Compression
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type countingReader struct {
|
||||||
|
io.Reader
|
||||||
|
tot int64 // bytes
|
||||||
|
last int64 // unix nanos
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
totalIncoming int64
|
||||||
|
totalOutgoing int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *countingReader) Read(bs []byte) (int, error) {
|
||||||
|
n, err := c.Reader.Read(bs)
|
||||||
|
atomic.AddInt64(&c.tot, int64(n))
|
||||||
|
atomic.AddInt64(&totalIncoming, int64(n))
|
||||||
|
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingReader) Tot() int64 {
|
||||||
|
return atomic.LoadInt64(&c.tot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingReader) Last() time.Time {
|
||||||
|
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||||
|
}
|
||||||
|
|
||||||
|
type countingWriter struct {
|
||||||
|
io.Writer
|
||||||
|
tot int64 // bytes
|
||||||
|
last int64 // unix nanos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Write(bs []byte) (int, error) {
|
||||||
|
n, err := c.Writer.Write(bs)
|
||||||
|
atomic.AddInt64(&c.tot, int64(n))
|
||||||
|
atomic.AddInt64(&totalOutgoing, int64(n))
|
||||||
|
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Tot() int64 {
|
||||||
|
return atomic.LoadInt64(&c.tot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Last() time.Time {
|
||||||
|
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TotalInOut() (int64, int64) {
|
||||||
|
return atomic.LoadInt64(&totalIncoming), atomic.LoadInt64(&totalOutgoing)
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
|
||||||
|
l = logger.DefaultLogger
|
||||||
|
)
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base32"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/luhn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceID [32]byte
|
||||||
|
|
||||||
|
var LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
|
|
||||||
|
// NewDeviceID generates a new device ID from the raw bytes of a certificate
|
||||||
|
func NewDeviceID(rawCert []byte) DeviceID {
|
||||||
|
var n DeviceID
|
||||||
|
hf := sha256.New()
|
||||||
|
hf.Write(rawCert)
|
||||||
|
hf.Sum(n[:0])
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeviceIDFromString(s string) (DeviceID, error) {
|
||||||
|
var n DeviceID
|
||||||
|
err := n.UnmarshalText([]byte(s))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeviceIDFromBytes(bs []byte) DeviceID {
|
||||||
|
var n DeviceID
|
||||||
|
if len(bs) != len(n) {
|
||||||
|
panic("incorrect length of byte slice representing device ID")
|
||||||
|
}
|
||||||
|
copy(n[:], bs)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the canonical string representation of the device ID
|
||||||
|
func (n DeviceID) String() string {
|
||||||
|
id := base32.StdEncoding.EncodeToString(n[:])
|
||||||
|
id = strings.Trim(id, "=")
|
||||||
|
id, err := luhnify(id)
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id = chunkify(id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeviceID) GoString() string {
|
||||||
|
return n.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeviceID) Compare(other DeviceID) int {
|
||||||
|
return bytes.Compare(n[:], other[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeviceID) Equals(other DeviceID) bool {
|
||||||
|
return bytes.Compare(n[:], other[:]) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short returns an integer representing bits 0-63 of the device ID.
|
||||||
|
func (n DeviceID) Short() uint64 {
|
||||||
|
return binary.BigEndian.Uint64(n[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *DeviceID) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(n.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *DeviceID) UnmarshalText(bs []byte) error {
|
||||||
|
id := string(bs)
|
||||||
|
id = strings.Trim(id, "=")
|
||||||
|
id = strings.ToUpper(id)
|
||||||
|
id = untypeoify(id)
|
||||||
|
id = unchunkify(id)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch len(id) {
|
||||||
|
case 56:
|
||||||
|
// New style, with check digits
|
||||||
|
id, err = unluhnify(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 52:
|
||||||
|
// Old style, no check digits
|
||||||
|
dec, err := base32.StdEncoding.DecodeString(id + "====")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(n[:], dec)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("device ID invalid: incorrect length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func luhnify(s string) (string, error) {
|
||||||
|
if len(s) != 52 {
|
||||||
|
panic("unsupported string length")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := s[i*13 : (i+1)*13]
|
||||||
|
l, err := luhn.Base32.Generate(p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
res = append(res, fmt.Sprintf("%s%c", p, l))
|
||||||
|
}
|
||||||
|
return res[0] + res[1] + res[2] + res[3], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unluhnify(s string) (string, error) {
|
||||||
|
if len(s) != 56 {
|
||||||
|
return "", fmt.Errorf("unsupported string length %d", len(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := s[i*14 : (i+1)*14-1]
|
||||||
|
l, err := luhn.Base32.Generate(p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
|
||||||
|
return "", errors.New("check digit incorrect")
|
||||||
|
}
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
return res[0] + res[1] + res[2] + res[3], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func chunkify(s string) string {
|
||||||
|
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
||||||
|
s = strings.Trim(s, "-")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func unchunkify(s string) string {
|
||||||
|
s = strings.Replace(s, "-", "", -1)
|
||||||
|
s = strings.Replace(s, " ", "", -1)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func untypeoify(s string) string {
|
||||||
|
s = strings.Replace(s, "0", "O", -1)
|
||||||
|
s = strings.Replace(s, "1", "I", -1)
|
||||||
|
s = strings.Replace(s, "8", "B", -1)
|
||||||
|
return s
|
||||||
|
}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var formatted = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"
|
||||||
|
var formatCases = []string{
|
||||||
|
"P56IOI-7MZJNU-2IQGDR-EYDM2M-GTMGL3-BXNPQ6-W5BTBB-Z4TJXZ-WICQ",
|
||||||
|
"P56IOI-7MZJNU2Y-IQGDR-EYDM2M-GTI-MGL3-BXNPQ6-W5BM-TBB-Z4TJXZ-WICQ2",
|
||||||
|
"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ",
|
||||||
|
"P56IOI7 MZJNU2Y IQGDREY DM2MGTI MGL3BXN PQ6W5BM TBBZ4TJ XZWICQ2",
|
||||||
|
"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ",
|
||||||
|
"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq",
|
||||||
|
"P56IOI7MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMTBBZ4TJXZWICQ2",
|
||||||
|
"P561017MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMT88Z4TJXZWICQ2",
|
||||||
|
"p56ioi7mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmtbbz4tjxzwicq2",
|
||||||
|
"p561017mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmt88z4tjxzwicq2",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDeviceID(t *testing.T) {
|
||||||
|
for i, tc := range formatCases {
|
||||||
|
var id DeviceID
|
||||||
|
err := id.UnmarshalText([]byte(tc))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d UnmarshalText(%q); %v", i, tc, err)
|
||||||
|
} else if f := id.String(); f != formatted {
|
||||||
|
t.Errorf("#%d FormatDeviceID(%q)\n\t%q !=\n\t%q", i, tc, f, formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validateCases = []struct {
|
||||||
|
s string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2", true},
|
||||||
|
{"P56IOI7-MZJNU2-IQGDREY-DM2MGT-MGL3BXN-PQ6W5B-TBBZ4TJ-XZWICQ", true},
|
||||||
|
{"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ", true},
|
||||||
|
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ", true},
|
||||||
|
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQCCCC", false},
|
||||||
|
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq", true},
|
||||||
|
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicqCCCC", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateDeviceID(t *testing.T) {
|
||||||
|
for _, tc := range validateCases {
|
||||||
|
var id DeviceID
|
||||||
|
err := id.UnmarshalText([]byte(tc.s))
|
||||||
|
if (err == nil && !tc.ok) || (err != nil && tc.ok) {
|
||||||
|
t.Errorf("ValidateDeviceID(%q); %v != %v", tc.s, err, tc.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshallingDeviceID(t *testing.T) {
|
||||||
|
n0 := DeviceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 10, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
|
||||||
|
n1 := DeviceID{}
|
||||||
|
n2 := DeviceID{}
|
||||||
|
|
||||||
|
bs, _ := n0.MarshalText()
|
||||||
|
n1.UnmarshalText(bs)
|
||||||
|
bs, _ = n1.MarshalText()
|
||||||
|
n2.UnmarshalText(bs)
|
||||||
|
|
||||||
|
if n2.String() != n0.String() {
|
||||||
|
t.Errorf("String marshalling error; %q != %q", n2.String(), n0.String())
|
||||||
|
}
|
||||||
|
if !n2.Equals(n0) {
|
||||||
|
t.Error("Equals error")
|
||||||
|
}
|
||||||
|
if n2.Compare(n0) != 0 {
|
||||||
|
t.Error("Compare error")
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ecNoError int32 = iota
|
||||||
|
ecGeneric
|
||||||
|
ecNoSuchFile
|
||||||
|
ecInvalid
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoError error = nil
|
||||||
|
ErrGeneric = errors.New("generic error")
|
||||||
|
ErrNoSuchFile = errors.New("no such file")
|
||||||
|
ErrInvalid = errors.New("file is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
var lookupError = map[int32]error{
|
||||||
|
ecNoError: ErrNoError,
|
||||||
|
ecGeneric: ErrGeneric,
|
||||||
|
ecNoSuchFile: ErrNoSuchFile,
|
||||||
|
ecInvalid: ErrInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
var lookupCode = map[error]int32{
|
||||||
|
ErrNoError: ecNoError,
|
||||||
|
ErrGeneric: ecGeneric,
|
||||||
|
ErrNoSuchFile: ecNoSuchFile,
|
||||||
|
ErrInvalid: ecInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
func codeToError(errcode int32) error {
|
||||||
|
err, ok := lookupError[errcode]
|
||||||
|
if !ok {
|
||||||
|
return ErrGeneric
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorToCode(err error) int32 {
|
||||||
|
code, ok := lookupCode[err]
|
||||||
|
if !ok {
|
||||||
|
return ecGeneric
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "github.com/calmh/xdr"
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
version int
|
||||||
|
msgID int
|
||||||
|
msgType int
|
||||||
|
compression bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h header) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||||
|
u := encodeHeader(h)
|
||||||
|
return xw.WriteUint32(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) decodeXDR(xr *xdr.Reader) error {
|
||||||
|
u := xr.ReadUint32()
|
||||||
|
*h = decodeHeader(u)
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHeader(h header) uint32 {
|
||||||
|
var isComp uint32
|
||||||
|
if h.compression {
|
||||||
|
isComp = 1 << 0 // the zeroth bit is the compression bit
|
||||||
|
}
|
||||||
|
return uint32(h.version&0xf)<<28 +
|
||||||
|
uint32(h.msgID&0xfff)<<16 +
|
||||||
|
uint32(h.msgType&0xff)<<8 +
|
||||||
|
isComp
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHeader(u uint32) header {
|
||||||
|
return header{
|
||||||
|
version: int(u>>28) & 0xf,
|
||||||
|
msgID: int(u>>16) & 0xfff,
|
||||||
|
msgType: int(u>>8) & 0xff,
|
||||||
|
compression: u&1 == 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
//go:generate -command genxdr go run ../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
|
||||||
|
//go:generate genxdr -o message_xdr.go message.go
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type IndexMessage struct {
|
||||||
|
Folder string
|
||||||
|
Files []FileInfo
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Name string // max:8192
|
||||||
|
Flags uint32
|
||||||
|
Modified int64
|
||||||
|
Version Vector
|
||||||
|
LocalVersion int64
|
||||||
|
Blocks []BlockInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) String() string {
|
||||||
|
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%v, Size:%d, Blocks:%v}",
|
||||||
|
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.Blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) Size() (bytes int64) {
|
||||||
|
if f.IsDeleted() || f.IsDirectory() {
|
||||||
|
return 128
|
||||||
|
}
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
bytes += int64(b.Size)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsDeleted() bool {
|
||||||
|
return f.Flags&FlagDeleted != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsInvalid() bool {
|
||||||
|
return f.Flags&FlagInvalid != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsDirectory() bool {
|
||||||
|
return f.Flags&FlagDirectory != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsSymlink() bool {
|
||||||
|
return f.Flags&FlagSymlink != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) HasPermissionBits() bool {
|
||||||
|
return f.Flags&FlagNoPermBits == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockInfo struct {
|
||||||
|
Offset int64 // noencode (cache only)
|
||||||
|
Size int32
|
||||||
|
Hash []byte // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BlockInfo) String() string {
|
||||||
|
return fmt.Sprintf("Block{%d/%d/%x}", b.Offset, b.Size, b.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestMessage struct {
|
||||||
|
Folder string // max:64
|
||||||
|
Name string // max:8192
|
||||||
|
Offset int64
|
||||||
|
Size int32
|
||||||
|
Hash []byte // max:64
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseMessage struct {
|
||||||
|
Data []byte
|
||||||
|
Code int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterConfigMessage struct {
|
||||||
|
ClientName string // max:64
|
||||||
|
ClientVersion string // max:64
|
||||||
|
Folders []Folder
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClusterConfigMessage) GetOption(key string) string {
|
||||||
|
for _, option := range o.Options {
|
||||||
|
if option.Key == key {
|
||||||
|
return option.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Folder struct {
|
||||||
|
ID string // max:64
|
||||||
|
Devices []Device
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
ID []byte // max:32
|
||||||
|
MaxLocalVersion int64
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Key string // max:64
|
||||||
|
Value string // max:1024
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseMessage struct {
|
||||||
|
Reason string // max:1024
|
||||||
|
Code int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyMessage struct{}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user