all: Add untrusted folders behind feature flag (ref #62) (#7055)

This commit is contained in:
Simon Frei 2020-11-09 15:33:32 +01:00 committed by GitHub
parent 4db5ea5893
commit 31559e908b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 6952 additions and 1111 deletions

View File

@ -754,6 +754,8 @@ func getReport(db *sql.DB) map[string]interface{} {
add(featureGroups["Folder"]["v3"], "Pull Order", prettyCase(key), value)
}
inc(features["Device"]["v3"], "Untrusted", rep.DeviceUsesV3.Untrusted)
totals["GUI"] += rep.GUIStats.Enabled
inc(features["GUI"]["v3"], "Auth Enabled", rep.GUIStats.UseAuth)

1
go.mod
View File

@ -30,6 +30,7 @@ require (
github.com/maruel/panicparse v1.5.1
github.com/mattn/go-isatty v0.0.12
github.com/minio/sha256-simd v0.1.1
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/oschwald/geoip2-golang v1.4.0
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1

54
go.sum
View File

@ -26,10 +26,8 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@ -43,16 +41,13 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e h1:2augTYh6E+XoNrrivZJBadpThP/dsvYKj0nzqfQ8tM4=
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE=
@ -115,11 +110,9 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -152,33 +145,24 @@ github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
@ -188,7 +172,6 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
@ -235,7 +218,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@ -254,7 +236,6 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@ -290,7 +271,6 @@ github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1j
github.com/maruel/panicparse v1.5.1 h1:hUPcXI7ubtEqj/k+P34KsHQqb86zuVk7zBfkP6tBBPc=
github.com/maruel/panicparse v1.5.1/go.mod h1:aOutY/MUjdj80R0AEVI9qE2zHqig+67t2ffUDDiLzAM=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -299,12 +279,13 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+UfmdEAZGJ8IiKld1O0dbGotEnkMolG5hfMSY=
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75/go.mod h1:pBbZyGwC5i16IBkjVKoy/sznA8jPD/K9iedwe1ESE6w=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -366,7 +347,6 @@ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCr
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -382,21 +362,16 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
@ -404,7 +379,6 @@ github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -452,7 +426,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -477,11 +450,8 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -496,7 +466,6 @@ github.com/thejerf/suture v4.0.0+incompatible h1:luAwgEo87y1X30wEYa64N4SKMrsAm9q
github.com/thejerf/suture v4.0.0+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
@ -526,16 +495,13 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -569,9 +535,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -581,7 +545,6 @@ golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -598,7 +561,6 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -611,14 +573,11 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -628,11 +587,9 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -659,7 +616,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -695,11 +651,9 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -707,7 +661,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@ -716,11 +669,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@ -421,3 +421,11 @@ ul.three-columns li, ul.two-columns li {
margin-bottom: 1rem;
}
}
.form-horizontal .form-group {
margin-bottom: 5px;
}
.form-horizontal {
margin-bottom: 10px;
}

View File

@ -18,12 +18,14 @@
"Advanced": "Advanced",
"Advanced Configuration": "Advanced Configuration",
"All Data": "All Data",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Allowed Networks": "Allowed Networks",
"Alphabetic": "Alphabetic",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Are you sure you want to permanently delete all these files?": "Are you sure you want to permanently delete all these files?",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
@ -60,6 +62,7 @@
"Danger!": "Danger!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Delete Unexpected Items": "Delete Unexpected Items",
"Deleted": "Deleted",
"Deselect All": "Deselect All",
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
@ -122,6 +125,7 @@
"Folder Label": "Folder Label",
"Folder Path": "Folder Path",
"Folder Type": "Folder Type",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",
"Folders": "Folders",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"Full Rescan Interval (s)": "Full Rescan Interval (s)",
@ -139,6 +143,7 @@
"Help": "Help",
"Home page": "Home page",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
"If untrusted, enter encryption password": "If untrusted, enter encryption password",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
@ -187,6 +192,7 @@
"No File Versioning": "No File Versioning",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No upgrades": "No upgrades",
"Not shared": "Not shared",
"Notice": "Notice",
"OK": "OK",
"Off": "Off",
@ -220,7 +226,9 @@
"Preview Usage Report": "Preview Usage Report",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"Random": "Random",
"Receive Encrypted": "Receive Encrypted",
"Receive Only": "Receive Only",
"Received data is already encrypted": "Received data is already encrypted",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
@ -249,6 +257,7 @@
"Select All": "Select All",
"Select a version": "Select a version",
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
"Select additional folders to share with this device.": "Select additional folders to share with this device.",
"Select latest version": "Select latest version",
"Select oldest version": "Select oldest version",
"Select the folders to share with this device.": "Select the folders to share with this device.",
@ -259,6 +268,7 @@
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share this folder?": "Share this folder?",
"Shared Folders": "Shared Folders",
"Shared With": "Shared With",
"Sharing": "Sharing",
"Show ID": "Show ID",
@ -281,6 +291,7 @@
"Start Browser": "Start Browser",
"Statistics": "Statistics",
"Stopped": "Stopped",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{{receiveEncrypted}}\" too.",
"Support": "Support",
"Support Bundle": "Support Bundle",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
@ -310,6 +321,7 @@
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.",
"The following items could not be synchronized.": "The following items could not be synchronized.",
"The following items were changed locally.": "The following items were changed locally.",
"The following unexpected items were found.": "The following unexpected items were found.",
"The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.",
"The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.",
@ -335,10 +347,14 @@
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unexpected Items": "Unexpected Items",
"Unexpected items have been found in this folder.": "Unexpected items have been found in this folder.",
"Unignore": "Unignore",
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unshared Devices": "Unshared Devices",
"Unshared Folders": "Unshared Folders",
"Untrusted": "Untrusted",
"Up to Date": "Up to Date",
"Updated": "Updated",
"Upgrade": "Upgrade",
@ -374,6 +390,7 @@
"You have no ignored folders.": "You have no ignored folders.",
"You have unsaved changes. Do you really want to discard them?": "You have unsaved changes. Do you really want to discard them?",
"You must keep at least one version.": "You must keep at least one version.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
"days": "days",
"directories": "directories",
"files": "files",

View File

@ -0,0 +1,950 @@
<!DOCTYPE html>
<!--
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<html lang="en" ng-app="syncthing" ng-controller="SyncthingController">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content=""/>
<meta name="author" content=""/>
<link rel="shortcut icon" href="assets/img/favicon-{{syncthingStatus()}}.png"/>
<link rel="mask-icon" href="assets/img/safari-pinned-tab.svg" color="#0882c8"/>
<title ng-bind="thisDeviceName() + ' | Syncthing'"></title>
<link href="vendor/bootstrap/css/bootstrap.css" rel="stylesheet"/>
<link href="vendor/daterangepicker/daterangepicker.css" rel="stylesheet"/>
<link href="assets/font/raleway.css" rel="stylesheet"/>
<link href="vendor/fork-awesome/css/fork-awesome.css" rel="stylesheet"/>
<link href="vendor/fork-awesome/css/v5-compat.css" rel="stylesheet"/>
<link href="assets/css/overrides.css" rel="stylesheet"/>
<link href="assets/css/theme.css" rel="stylesheet"/>
<link href="vendor/fancytree/css/ui.fancytree.css" rel="stylesheet"/>
</head>
<body>
<noscript>
<nav class="navbar navbar-top navbar-default" role="navigation">
<div class="container">
<span class="navbar-brand" aria-hidden="true">
<img class="logo hidden-xs" src="assets/img/logo-horizontal.svg" height="32" width="117" alt=""/>
<img class="logo hidden visible-xs" src="assets/img/favicon-default.png" height="32" alt=""/>
</span>
</div>
</nav>
<div class="container content">
<div class="row">
<div class="col-md-12">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-exclamation-circle"></span>
</div>
Warning!
</h3>
</div>
<div class="panel-body">
<p>
The Syncthing admin interface requires JavaScript. Please enable JavaScript in your web browser and try again.
</p>
</div>
</div>
</div>
</div>
</div>
</noscript>
<div class="ng-cloak">
<script type="text/javascript" src="syncthing/development/logbar.js"></script>
<div ng-if="version.isBeta" ng-include="'syncthing/development/logbar.html'"></div>
<!-- Top bar -->
<nav class="navbar navbar-top navbar-default" role="navigation">
<div class="container">
<span class="navbar-brand" aria-hidden="true">
<img class="logo hidden-xs" src="assets/img/logo-horizontal.svg" height="32" width="117" alt=""/>
<img class="logo hidden visible-xs" src="assets/img/favicon-default.png" height="32" alt=""/>
</span>
<p class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<ul class="nav navbar-nav navbar-right">
<li ng-if="upgradeInfo && upgradeInfo.newer" class="upgrade-newer">
<button type="button" class="btn navbar-btn btn-primary btn-sm" data-toggle="modal" data-target="#upgrade">
<span class="fas fa-arrow-circle-up"></span>
<span class="hidden-xs" translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
</li>
<li ng-if="upgradeInfo && upgradeInfo.majorNewer" class="upgrade-newer-major">
<button type="button" class="btn navbar-btn btn-danger btn-sm" data-toggle="modal" data-target="#majorUpgrade">
<span class="fas fa-arrow-circle-up"></span>
<span class="hidden-xs" translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
</li>
<li class="dropdown" language-select></li>
<li>
<a class="navbar-link" href="https://docs.syncthing.net/intro/gui.html" target="_blank">
<span class="fas fa-question-circle"></span>
<span class="hidden-xs" translate>Help</span>
</a>
</li>
<li class="dropdown action-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="fas fa-cog"></span>
<span class="hidden-xs" translate>Actions</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="" ng-click="showSettings()"><span class="fas fa-fw fa-cog"></span>&nbsp;<span translate>Settings</span></a></li>
<li><a href="" data-toggle="modal" data-target="#idqr" ng-click="currentDevice=thisDevice()"><span class="fas fa-fw fa-qrcode"></span>&nbsp;<span translate>Show ID</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="shutdown()"><span class="fas fa-fw fa-power-off"></span>&nbsp;<span translate>Shutdown</span></a></li>
<li><a href="" ng-click="restart()"><span class="fas fa-fw fa-refresh"></span>&nbsp;<span translate>Restart</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li class="visible-xs">
<a href="https://docs.syncthing.net/intro/gui.html" target="_blank">
<span class="fas fa-fw fa-question-circle"></span>&nbsp;<span translate>Help</span>
</a>
</li>
<li><a href="" data-toggle="modal" data-target="#about"><span class="far fa-fw fa-heart"></span>&nbsp;<span translate>About</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="advanced()"><span class="fas fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li><a href="" ng-click="logging.show()"><span class="far fa-fw fa-file-alt"></span>&nbsp;<span translate>Logs</span></a></li>
<li class="divider" aria-hidden="true" ng-if="config.gui.debugging"></li>
<li><a href="/rest/debug/support" target="_blank" ng-if="config.gui.debugging"><span class="fa fa-user-md"></span>&nbsp;<span translate>Support Bundle</span></a></li>
</ul>
</li>
</ul>
</div>
</nav>
<div class="container content">
<!-- Panel: Open, no auth -->
<div ng-if="openNoAuth" class="row">
<div class="col-md-12">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-exclamation-circle"></span>
</div>
<span translate>Danger!</span>
</h3>
</div>
<div class="panel-body">
<p>
<span translate>The Syncthing admin interface is configured to allow remote access without a password.</span>
<b><span translate>This can easily give hackers access to read and change any files on your computer.</span></b>
<span translate>Please set a GUI Authentication User and Password in the Settings dialog.</span>
</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="showSettings()">
<span class="fas fa-cog"></span>&nbsp;<span translate>Settings</span>
</button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<!-- Panel: Restart Needed -->
<div ng-if="!configInSync" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-exclamation-circle"></span>
</div>
<span translate>Restart Needed</span>
</h3>
</div>
<div class="panel-body">
<p translate>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="restart()">
<span class="fas fa-refresh"></span>&nbsp;<span translate>Restart</span>
</button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div ng-if="config">
<!-- Panel: Notifications -->
<div ng-if="config.options && config.options.unackedNotificationIDs" ng-include="'syncthing/core/notifications.html'"></div>
<!-- Panel: New Device -->
<div ng-repeat="pendingDevice in config.pendingDevices" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon class="panel-icon" data-value="device"></identicon>
<span translate>New Device</span>
<span class="pull-right">{{ pendingDevice.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<span translate translate-value-device="{{ pendingDevice.deviceID }}" translate-value-address="{{ pendingDevice.address }}" translate-value-name="{{ pendingDevice.name }}">
Device "{%name%}" ({%device%} at {%address%}) wants to connect. Add new device?
</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(pendingDevice.deviceID, pendingDevice.name)">
<span class="fas fa-plus"></span>&nbsp;<span translate>Add Device</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreDevice(pendingDevice)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: New Folder -->
<div ng-repeat="device in config.devices">
<div ng-repeat="pendingFolder in device.pendingFolders" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-folder"></span>
</div>
<span translate ng-if="!folders[pendingFolder.id]">New Folder</span>
<span translate ng-if="folders[pendingFolder.id]">Share Folder</span>
<span class="pull-right">{{ pendingFolder.time | date:"yyyy-MM-dd HH:mm:ss" }}</span>
</h3>
</div>
<div class="panel-body">
<p>
<span ng-if="pendingFolder.label.length == 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span ng-if="pendingFolder.label.length != 0" translate translate-value-device="{{ deviceName(devices[device.deviceID]) }}" translate-value-folder="{{ pendingFolder.id }}" translate-value-folderlabel="{{ pendingFolder.label }}">
{%device%} wants to share folder "{%folderlabel%}" ({%folder%}).
</span>
<span translate ng-if="folders[pendingFolder.id]">Share this folder?</span>
<span translate ng-if="!folders[pendingFolder.id]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(pendingFolder.id, pendingFolder.label, device.deviceID)" ng-if="!folders[pendingFolder.id]">
<span class="fas fa-check"></span>&nbsp;<span translate>Add</span>
</button>
<button type="button" class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(pendingFolder.id, device.deviceID)" ng-if="folders[pendingFolder.id]">
<span class="fas fa-check"></span>&nbsp;<span translate>Share</span>
</button>
<button type="button" class="btn btn-sm btn-danger" ng-click="ignoreFolder(device.deviceID, pendingFolder)">
<span class="fas fa-times"></span>&nbsp;<span translate>Ignore</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: Notice -->
<div ng-if="errorList().length > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-exclamation-circle"></span>
</div>
<span translate>Notice</span>
</h3>
</div>
<div class="panel-body">
<p ng-repeat="err in errorList()">
<small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small>
<span ng-bind-html="friendlyDevices(err.message) | linky: '_blank'"></span>
</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
<span class="fas fa-check"></span>&nbsp;<span translate>OK</span>
</button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<!-- Panel: FS watcher errors -->
<div ng-if="sizeOf(fsWatcherErrorMap()) > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<div class="panel-icon">
<span class="fas fa-exclamation-circle"></span>
</div>
<span translate>Filesystem Watcher Errors</span>
</h3>
</div>
<div class="panel-body">
<p>
<span translate>For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.</span>&emsp;<a href="https://forum.syncthing.net" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Support</span></a>
</p>
<table>
<tr ng-repeat="(id, err) in fsWatcherErrorMap()">
<td>{{folderLabel(id)}}</td><td>{{err}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<!-- First regular row -->
<div class="row">
<!-- Folder list (top left) -->
<div class="col-md-6" aria-labelledby="folder_list" role="region" >
<h3 id="folder_list" translate>Folders</h3>
<div class="panel-group" id="folders">
<div class="panel panel-default" ng-repeat="folder in folderList()">
<button class="btn panel-heading" data-toggle="collapse" data-parent="#folders" data-target="#folder-{{$index}}" aria-expanded="false">
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id) | percent}}"></div>
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id) | percent}}"></div>
<h4 class="panel-title">
<div class="panel-icon hidden-xs">
<span ng-if="folder.type == 'sendreceive'" class="fas fa-fw fa-folder"></span>
<span ng-if="folder.type == 'sendonly'" class="fas fa-fw fa-upload"></span>
<span ng-if="folder.type == 'receiveonly'" class="fas fa-fw fa-download"></span>
<span ng-if="folder.type == 'receiveencrypted'" class="fas fa-fw fa-lock"></span>
</div>
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs" aria-label="{{'Paused' | translate}}"><i class="fas fa-fw fa-pause"></i></span></span>
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs" aria-label="{{'Unknown' | translate}}"><i class="fas fa-fw fa-question-circle"></i></span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs" aria-label="{{'Unshared' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="scan-waiting"><span class="hidden-xs" translate>Waiting to Scan</span><span class="visible-xs" aria-label="{{'Waiting to Scan' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span></span>
<span ng-switch-when="cleaning"><span class="hidden-xs" translate>Cleaning Versions</span><span class="visible-xs" aria-label="{{'Cleaning Versions' | translate}}"><i class="fas fa-fw fa-recycle"></i></span></span>
<span ng-switch-when="clean-waiting"><span class="hidden-xs" translate>Waiting to Clean</span><span class="visible-xs" aria-label="{{'Waiting to Clean' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span></span>
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs" aria-label="{{'Stopped' | translate}}"><i class="fas fa-fw fa-stop"></i></span></span>
<span ng-switch-when="scanning">
<span class="hidden-xs" translate>Scanning</span>
<span class="hidden-xs" ng-if="scanPercentage(folder.id) != undefined">
({{scanPercentage(folder.id) | percent}})
</span>
<span class="visible-xs" aria-label="{{'Scanning' | translate}}"><i class="fas fa-fw fa-search"></i></span>
</span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="localadditions"><span class="hidden-xs" translate>Local Additions</span><span class="visible-xs" aria-label="{{'Local Additions' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="sync-waiting">
<span class="hidden-xs" translate>Waiting to Sync</span>
<span class="visible-xs" aria-label="{{'Waiting to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="sync-preparing">
<span class="hidden-xs" translate>Preparing to Sync</span>
<span class="visible-xs" aria-label="{{'Preparing to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
<span>({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
</span>
<span ng-switch-when="outofsync"><span class="hidden-xs" translate>Out of Sync</span><span class="visible-xs" aria-label="{{'Out of Sync' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
<span ng-switch-when="faileditems"><span class="hidden-xs" translate>Failed Items</span><span class="visible-xs" aria-label="{{'Failed Items' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
<span ng-switch-when="localunencrypted"><span class="hidden-xs">{{'Unexpected Items' | translate}}</span><span class="visible-xs" aria-label="{{'Unexpected Items' | translate}}"><i class="fas fa-fw fa-exclamation-circle"></i></span></span>
</div>
<div class="panel-title-text">
<span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span>
</div>
</h4>
</button>
<div id="folder-{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table table-condensed table-striped table-auto">
<tbody>
<tr ng-show="folder.label != undefined && folder.label.length > 0">
<th><span class="fas fa-fw fa-info-circle"></span>&nbsp;<span translate>Folder ID</span></th>
<td class="text-right no-overflow-ellipse">{{folder.id}}</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-folder-open"></span>&nbsp;<span translate>Folder Path</span></th>
<td class="text-right">
<span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
</td>
</tr>
<tr ng-if="!folder.paused && (model[folder.id].invalid || model[folder.id].error)">
<th><span class="fas fa-fw fa-exclamation-triangle"></span>&nbsp;<span translate>Error</span></th>
<td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
</tr>
<tr ng-if="!folder.paused">
<th><span class="fas fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
<span class="far fa-copy"></span>&nbsp;{{model[folder.id].globalFiles | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-folder"></span>&nbsp;{{model[folder.id].globalDirectories | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-hdd"></span>&nbsp;~{{model[folder.id].globalBytes | binary}}B
</span>
</td>
</tr>
<tr ng-if="!folder.paused">
<th><span class="fas fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
<span class="far fa-copy"></span>&nbsp;{{model[folder.id].localFiles | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-folder"></span>&nbsp;{{model[folder.id].localDirectories | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-hdd"></span>&nbsp;~{{model[folder.id].localBytes | binary}}B<!-- get rid of the annoying trailing whitespace
--><span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
</span>
</td>
</tr>
<tr ng-if="model[folder.id].needTotalItems > 0">
<th><span class="fas fa-fw fa-cloud-download-alt"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">
<th><span class="fas fa-fw fa-hourglass-half"></span>&nbsp;<span translate>Scan Time Remaining</span></th>
<td class="text-right">
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
</td>
</tr>
<tr ng-if="hasFailedFiles(folder.id)">
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Failed Items</span></th>
<!-- Show the number of failed items as a link to bring up the list. -->
<td class="text-right">
<a href="" ng-click="showFailed(folder.id)">{{model[folder.id].pullErrors | alwaysNumber | localeNumber}}&nbsp;<span translate>items</span></a>
</td>
</tr>
<tr ng-if="hasReceiveOnlyChanged(folder)">
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Locally Changed Items</span></th>
<td class="text-right">
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="hasReceiveEncryptedItems(folder)">
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Locally Changed Items</span></th>
<td class="text-right">
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{receiveEncryptedItemsCount(folder) | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folder.type != 'sendreceive'">
<th><span class="fas fa-fw fa-folder"></span>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">
<span ng-if="folder.type == 'sendonly'" translate>Send Only</span>
<span ng-if="folder.type == 'receiveonly'" translate>Receive Only</span>
<span ng-if="folder.type == 'receiveencrypted'" translate>Receive Encrypted</span>
</td>
</tr>
<tr ng-if="folder.ignorePerms">
<th><span class="far fa-fw fa-minus-square"></span>&nbsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-refresh"></span>&nbsp;<span translate>Rescans</span></th>
<td class="text-right">
<div ng-if="folder.rescanIntervalS > 0">
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Periodic scanning at given interval and disabled watching for changes' | translate}}">
<span class="far fa-clock"></span>&nbsp;{{folder.rescanIntervalS | duration}}&ensp;
<span class="fas fa-eye-slash"></span>&nbsp;<span translate>Disabled</span>
</span>
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Periodic scanning at given interval and enabled watching for changes' | translate}}">
<span class="far fa-clock"></span>&nbsp;{{folder.rescanIntervalS | duration}}&ensp;
<span class="fas fa-eye"></span>&nbsp;<span translate>Enabled</span>
</span>
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
<span class="far fa-clock"></span>&nbsp;{{folder.rescanIntervalS | duration}}&ensp;
<span class="fas fa-eye-slash"></span>&nbsp;<span translate>Failed to setup, retrying</span>
</span>
</div>
<div ng-if="folder.rescanIntervalS <= 0">
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Disabled periodic scanning and disabled watching for changes' | translate}}">
<span class="far fa-clock"></span>&nbsp;<span translate>Disabled</span>&ensp;
<span class="fas fa-eye-slash"></span>&nbsp;<span translate>Disabled</span>
</span>
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Disabled periodic scanning and enabled watching for changes' | translate}}">
<span class="far fa-clock"></span>&nbsp;<span translate>Disabled</span>&ensp;
<span class="fas fa-eye"></span>&nbsp;<span translate>Enabled</span>
</span>
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
<span class="far fa-clock"></span>&nbsp;<span translate>Disabled</span>&ensp;
<span class="fas fa-eye-slash"></span>&nbsp;<span translate>Failed to setup, retrying</span>
</span>
</div>
</td>
</tr>
<tr ng-if="folder.order != 'random' && folder.type != 'sendonly'">
<th><span class="fas fa-fw fa-sort"></span>&nbsp;<span translate>File Pull Order</span></th>
<td class="text-right" ng-switch="folder.order">
<span ng-switch-when="random" translate>Random</span>
<span ng-switch-when="alphabetic" translate>Alphabetic</span>
<span ng-switch-when="smallestFirst" translate>Smallest First</span>
<span ng-switch-when="largestFirst" translate>Largest First</span>
<span ng-switch-when="oldestFirst" translate>Oldest First</span>
<span ng-switch-when="newestFirst" translate>Newest First</span>
</td>
</tr>
<tr ng-if="folder.versioning.type">
<th><span class="far fa-fw fa-copy"></span>&nbsp;<span translate>File Versioning</span></th>
<td class="text-right" ng-switch="folder.versioning.type">
<span ng-switch-when="trashcan" translate>Trash Can File Versioning</span>
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
<span ng-switch-when="simple" translate>Simple File Versioning</span>
<span ng-switch-when="external" translate>External File Versioning</span>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-share-alt"></span>&nbsp;<span translate>Shared With</span></th>
<td class="text-right" ng-attr-title="{{sharesFolder(folder)}}">{{sharesFolder(folder)}}</td>
</tr>
<tr ng-if="folderStats[folder.id].lastScan">
<th><span class="far fa-fw fa-clock"></span>&nbsp;<span translate>Last Scan</span></th>
<td translate ng-if="folderStats[folder.id].lastScanDays >= 365" class="text-right">Never</td>
<td ng-if="folderStats[folder.id].lastScanDays < 365" class="text-right">
<span>{{folderStats[folder.id].lastScan | date:'yyyy-MM-dd HH:mm:ss'}}</span>
</td>
</tr>
<tr ng-if="folder.type != 'sendonly' && folder.type != 'receiveencrypted' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
<th><span class="fas fa-fw fa-exchange-alt"></span>&nbsp;<span translate>Latest Change</span></th>
<td class="text-right">
<span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
<span translate ng-if="!folderStats[folder.id].lastFile.deleted">Updated</span>
<span translate ng-if="folderStats[folder.id].lastFile.deleted">Deleted</span>
{{folderStats[folder.id].lastFile.filename | basename}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type == 'sendonly'">
<span class="fas fa-arrow-circle-up"></span>&nbsp;<span translate>Override Changes</span>
</button>
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revert(folder.id)" ng-if="hasReceiveOnlyChanged(folder)">
<span class="fa fa-arrow-circle-down"></span>&nbsp;<span translate>Revert Local Changes</span>
</button>
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="deleteEncryptionModal(folder.id)" ng-if="hasReceiveEncryptedItems(folder)">
<span class="fa fa-arrow-lock"></span>&nbsp;<span translate>Delete Unexpected Items</span>
</button>
<span class="pull-right">
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause</span>
</button>
<button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
<span class="fas fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
<button type="button" class="btn btn-default btn-sm" ng-click="restoreVersions.show(folder.id)" ng-if="folder.versioning.type">
<span class="fas fa-undo"></span>&nbsp;<span translate>Versions</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
<span class="fas fa-refresh"></span>&nbsp;<span translate>Rescan</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="editFolder(folder)">
<span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
</button>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<span class="pull-right">
<button type="button" class="btn btn-sm btn-default" ng-click="setAllFoldersPause(true)" ng-if="isAtleastOneFolderPausedStateSetTo(false)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause All</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="setAllFoldersPause(false)" ng-if="isAtleastOneFolderPausedStateSetTo(true)">
<span class="fas fa-play"></span>&nbsp;<span translate>Resume All</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="rescanAllFolders()">
<span class="fas fa-refresh"></span>&nbsp;<span translate>Rescan All</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="addFolder()">
<span class="fas fa-plus"></span>&nbsp;<span translate>Add Folder</span>
</button>
</span>
<div class="clearfix"></div>
<hr class="visible-sm"/>
</div>
<!-- Device list (top right) -->
<!-- This device -->
<div class="col-md-6" aria-label="{{'Devices' | translate}}" role="region">
<h3 translate>This Device</h3>
<div class="panel panel-default" ng-repeat="deviceCfg in [thisDevice()]">
<button class="btn panel-heading" data-toggle="collapse" data-target="#device-this" aria-expanded="true">
<h4 class="panel-title">
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
<div class="panel-title-text">{{deviceName(deviceCfg)}}</div>
</h4>
</button>
<div id="device-this" class="panel-collapse collapse in">
<div class="panel-body">
<table class="table table-condensed table-striped table-auto">
<tbody>
<tr>
<th><span class="fas fa-fw fa-cloud-download-alt"></span>&nbsp;<span translate>Download Rate</span></th>
<td class="text-right">
<a href="#" class="toggler" ng-click="toggleUnits()">
<span ng-if="!metricRates">{{connectionsTotal.inbps | binary}}B/s</span>
<span ng-if="metricRates">{{connectionsTotal.inbps*8 | metric}}bps</span>
({{connectionsTotal.inBytesTotal | binary}}B)
<small ng-if="config.options.maxRecvKbps > 0"><br/>
<i class="text-muted"><span translate>Limit</span>:
<span ng-if="!metricRates">{{config.options.maxRecvKbps*1024 | binary}}B/s</span>
<span ng-if="metricRates">{{config.options.maxRecvKbps*1024*8 | metric}}bps</span>
</i>
</small>
</a>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-cloud-upload-alt"></span>&nbsp;<span translate>Upload Rate</span></th>
<td class="text-right">
<a href="#" class="toggler" ng-click="toggleUnits()">
<span ng-if="!metricRates">{{connectionsTotal.outbps | binary}}B/s</span>
<span ng-if="metricRates">{{connectionsTotal.outbps*8 | metric}}bps</span>
({{connectionsTotal.outBytesTotal | binary}}B)
<small ng-if="config.options.maxSendKbps > 0"><br/>
<i class="text-muted"><span translate>Limit</span>:
<span ng-if="!metricRates">{{config.options.maxSendKbps*1024 | binary}}B/s</span>
<span ng-if="metricRates">{{config.options.maxSendKbps*1024*8 | metric}}bps</span>
</i>
</small>
</a>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-home"></span>&nbsp;<span translate>Local State (Total)</span></th>
<td class="text-right">
<span tooltip data-original-title="{{localStateTotal.files | alwaysNumber | localeNumber}} {{'files' | translate}}, {{ localStateTotal.directories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{ localStateTotal.bytes | binary}}B">
<span class="far fa-copy"></span>&nbsp;{{localStateTotal.files | alwaysNumber | localeNumber}}&ensp;
<span class="far fa-folder"></span>&nbsp;{{localStateTotal.directories| alwaysNumber | localeNumber}}&ensp;
<span class="far fa-hdd"></span>&nbsp;~{{localStateTotal.bytes | binary}}B
</span>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-sitemap"></span>&nbsp;<span translate>Listeners</span></th>
<td class="text-right">
<span ng-if="listenersFailed.length == 0" class="data text-success">
<span>{{listenersTotal}}/{{listenersTotal}}</span>
</span>
<span ng-if="listenersFailed.length != 0" class="data" ng-class="{'text-danger': listenersFailed.length == listenersTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-html="true" data-content="{{listenersFailed.join('<br>\n')}}">
{{listenersTotal-listenersFailed.length}}/{{listenersTotal}}
</span>
</span>
</td>
</tr>
<tr ng-if="system.discoveryEnabled">
<th><span class="fas fa-fw fa-map-signs"></span>&nbsp;<span translate>Discovery</span></th>
<td class="text-right">
<span ng-if="discoveryFailed.length == 0" class="data text-success">
<span>{{discoveryTotal}}/{{discoveryTotal}}</span>
</span>
<span ng-if="discoveryFailed.length != 0" class="data" ng-class="{'text-danger': discoveryFailed.length == discoveryTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-content="{{'Click to see discovery failures' | translate}}.">
<a href="" style="color:inherit" ng-click="showDiscoveryFailures()">{{discoveryTotal-discoveryFailed.length}}/{{discoveryTotal}}</a>
</span>
</span>
</td>
</tr>
<tr>
<th><span class="far fa-fw fa-clock"></span>&nbsp;<span translate>Uptime</span></th>
<td class="text-right">{{system.uptime | duration:"m"}}</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-tag"></span>&nbsp;<span translate>Version</span></th>
<td class="text-right">
<span tooltip data-original-title="{{versionString()}}">{{versionString()}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Remote devices -->
<h3 translate>Remote Devices</h3>
<div class="panel-group" id="devices">
<div class="panel panel-default" ng-repeat="deviceCfg in otherDevices()">
<button class="btn panel-heading" data-toggle="collapse" data-parent="#devices" data-target="#device-{{$index}}" aria-expanded="false">
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | percent}}"></div>
<h4 class="panel-title">
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="unused-insync"><span class="hidden-xs" translate>Connected (Unused)</span><span class="visible-xs" aria-label="{{'Connected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)
</span>
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs" aria-label="{{'Paused' | translate}}"><i class="fas fa-fw fa-pause"></i></span></span>
<span ng-switch-when="unused-paused"><span class="hidden-xs" translate>Paused (Unused)</span><span class="visible-xs" aria-label="{{'Paused (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs" aria-label="{{'Disconnected' | translate}}"><i class="fas fa-fw fa-power-off"></i></span></span>
<span ng-switch-when="unused-disconnected"><span class="hidden-xs" translate>Disconnected (Unused)</span><span class="visible-xs" aria-label="{{'Disconnected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
</span>
<span>{{deviceName(deviceCfg)}}</span>
</h4>
</button>
<div id="device-{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table table-condensed table-striped table-auto">
<tbody>
<tr ng-if="connections[deviceCfg.deviceID].connected">
<th><span class="fas fa-fw fa-cloud-download-alt"></span>&nbsp;<span translate>Download Rate</span></th>
<td class="text-right">
<a href="#" class="toggler" ng-click="toggleUnits()">
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].inbps | binary}}B/s</span>
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].inbps*8 | metric}}bps</span>
({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)
<small ng-if="deviceCfg.maxRecvKbps > 0"><br/>
<i class="text-muted"><span translate>Limit</span>:
<span ng-if="!metricRates">{{deviceCfg.maxRecvKbps*1024 | binary}}B/s</span>
<span ng-if="metricRates">{{deviceCfg.maxRecvKbps*1024*8 | metric}}bps</span>
</i>
</small>
</a>
</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].connected">
<th><span class="fas fa-fw fa-cloud-upload-alt"></span>&nbsp;<span translate>Upload Rate</span></th>
<td class="text-right">
<a href="#" class="toggler" ng-click="toggleUnits()">
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].outbps | binary}}B/s</span>
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].outbps*8 | metric}}bps</span>
({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)
<small ng-if="deviceCfg.maxSendKbps > 0"><br/>
<i class="text-muted"><span translate>Limit</span>:
<span ng-if="!metricRates">{{deviceCfg.maxSendKbps*1024 | binary}}B/s</span>
<span ng-if="metricRates">{{deviceCfg.maxSendKbps*1024*8 | metric}}bps</span>
</i>
</small>
</a>
</td>
</tr>
<tr ng-if="deviceStatus(deviceCfg) == 'syncing'">
<th><span class="fas fa-fw fa-exchange-alt"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showRemoteNeed(deviceCfg)">{{completion[deviceCfg.deviceID]._needItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{completion[deviceCfg.deviceID]._needBytes | binary}}B</a>
</td>
</tr>
<tr>
<th><span class="fas fa-fw fa-link"></span>&nbsp<span translate>Address</span></th>
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }} {{ connections[deviceCfg.deviceID].crypto }}">
{{deviceAddr(deviceCfg)}}
</span>
</td>
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
<span ng-repeat="addr in deviceCfg.addresses">
<span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br>
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
</span>
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses">
<span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br>
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
</span>
</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].connected && connections[deviceCfg.deviceID].type.indexOf('Relay') > -1" tooltip data-original-title="Connections via relays might be rate limited by the relay">
<th><span class="fas fa-fw fa-exclamation-triangle text-danger"></span>&nbsp;<span translate>Connection Type</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].type}}</td>
</tr>
<tr ng-if="deviceCfg.allowedNetworks.length > 0">
<th><span class="fas fa-fw fa-filter"></span>&nbsp;<span translate>Allowed Networks</span></th>
<td class="text-right">
<span>{{deviceCfg.allowedNetworks.join(", ")}}</span>
</td>
</tr>
<tr ng-if="deviceCfg.compression != 'metadata'">
<th><span class="fas fa-fw fa-compress"></span>&nbsp;<span translate>Compression</span></th>
<td class="text-right">
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
</td>
</tr>
<tr ng-if="deviceCfg.introducer">
<th><span class="far fa-fw fa-thumbs-up"></span>&nbsp;<span translate>Introducer</span></th>
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="deviceCfg.introducedBy">
<th><span class="far fa-fw fa-handshake-o"></span>&nbsp;<span translate>Introduced By</span></th>
<td class="text-right">{{ deviceName(devices[deviceCfg.introducedBy]) || deviceCfg.introducedBy.substring(0, 5) }}</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
<th><span class="fas fa-fw fa-tag"></span>&nbsp;<span translate>Version</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
</tr>
<tr ng-if="!connections[deviceCfg.deviceID].connected">
<th><span class="fas fa-fw fa-eye"></span>&nbsp;<span translate>Last seen</span></th>
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}</td>
</tr>
<tr ng-if="deviceFolders(deviceCfg).length > 0">
<th><span class="fas fa-fw fa-folder"></span>&nbsp;<span translate>Folders</span></th>
<td class="text-right" ng-attr-title="{{deviceFolders(deviceCfg).map(folderLabel).join(', ')}}">{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}</td>
</tr>
</tbody>
</table>
</div>
<div class="panel-footer">
<span class="pull-right">
<button ng-if="!deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, true)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause</span>
</button>
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
<span class="fas fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="editDevice(deviceCfg)">
<span class="fas fa-pencil-alt"></span>&nbsp;<span translate>Edit</span>
</button>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="form-group">
<span class="pull-right">
<button type="button" class="btn btn-sm btn-default" ng-click="setAllDevicesPause(true)" ng-if="isAtleastOneDevicePausedStateSetTo(false)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause All</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="setAllDevicesPause(false)" ng-if="isAtleastOneDevicePausedStateSetTo(true)">
<span class="fas fa-play"></span>&nbsp;<span translate>Resume All</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="globalChanges()">
<span class="fas fa-fw fa-info-circle"></span>&nbsp;<span translate>Recent Changes</span>
</button>
<button type="button" class="btn btn-sm btn-default" ng-click="addDevice()">
<span class="fas fa-plus"></span>&nbsp;<span translate>Add Remote Device</span>
</button>
</span>
<div class="clearfix"></div>
</div>
</div>
</div> <!-- /row -->
</div> <!-- /container -->
</div> <!-- /ng-cloak -->
<!-- Bottom bar -->
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
<ul class="nav navbar-nav">
<li><a class="navbar-link" href="https://syncthing.net/" target="_blank"><span class="fas fa-home"></span>&nbsp;<span translate>Home page</span></a></li>
<li><a class="navbar-link" href="https://docs.syncthing.net/" target="_blank"><span class="fas fa-book"></span>&nbsp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://forum.syncthing.net" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://data.syncthing.net/" target="_blank"><span class="fas fa-bar-chart"></span>&nbsp;<span translate>Statistics</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="far fa-file-alt"></span>&nbsp;<span translate>Changelog</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues" target="_blank"><span class="fas fa-bug"></span>&nbsp;<span translate>Bugs</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing" target="_blank"><span class="fas fa-wrench"></span>&nbsp;<span translate>Source Code</span></a></li>
<li><a class="navbar-link" href="https://twitter.com/syncthing" target="_blank"><span class="fab fa-twitter"></span>&nbsp;Twitter</a></li>
</ul>
</div>
</nav>
<ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/httpErrorDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/restartingDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/upgradingDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/shutdownDialogView.html'"></ng-include>
<ng-include src="'syncthing/device/idqrModalView.html'"></ng-include>
<ng-include src="'syncthing/device/editDeviceModalView.html'"></ng-include>
<ng-include src="'syncthing/device/globalChangesModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/editFolderModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/restoreVersionsModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/restoreVersionsConfirmation.html'"></ng-include>
<ng-include src="'syncthing/settings/settingsModalView.html'"></ng-include>
<ng-include src="'syncthing/settings/advancedSettingsModalView.html'"></ng-include>
<ng-include src="'syncthing/settings/discardChangesConfirmation.html'"></ng-include>
<ng-include src="'syncthing/usagereport/usageReportModalView.html'"></ng-include>
<ng-include src="'syncthing/usagereport/usageReportPreviewModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/neededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/localChangedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/core/upgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>
<ng-include src="'syncthing/core/discoveryFailuresModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/removeFolderDialogView.html'"></ng-include>
<ng-include src="'syncthing/folder/deleteEncryptionFolderDialogView.html'"></ng-include>
<ng-include src="'syncthing/device/removeDeviceDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/logViewerModalView.html'"></ng-include>
<!-- vendor scripts -->
<script type="text/javascript" src="vendor/jquery/jquery-2.2.2.js"></script>
<script type="text/javascript" src="vendor/angular/angular.js"></script>
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script>
<script type="text/javascript" src="vendor/angular/angular-translate.js"></script>
<script type="text/javascript" src="vendor/angular/angular-translate-loader-static-files.js"></script>
<script type="text/javascript" src="vendor/angular/angular-dirPagination.js"></script>
<script type="text/javascript" src="vendor/moment/moment.js"></script>
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript" src="vendor/daterangepicker/daterangepicker.js"></script>
<script type="text/javascript" src="vendor/fancytree/jquery.fancytree-all-deps.js"></script>
<!-- / vendor scripts -->
<!-- gui application code -->
<script type="text/javascript" src="syncthing/core/module.js"></script>
<script type="text/javascript" src="syncthing/core/alwaysNumberFilter.js"></script>
<script type="text/javascript" src="syncthing/core/basenameFilter.js"></script>
<script type="text/javascript" src="syncthing/core/binaryFilter.js"></script>
<script type="text/javascript" src="syncthing/core/localeNumberFilter.js"></script>
<script type="text/javascript" src="syncthing/core/percentFilter.js"></script>
<script type="text/javascript" src="syncthing/core/durationFilter.js"></script>
<script type="text/javascript" src="syncthing/core/eventService.js"></script>
<script type="text/javascript" src="syncthing/core/identiconDirective.js"></script>
<script type="text/javascript" src="syncthing/core/languageSelectDirective.js"></script>
<script type="text/javascript" src="syncthing/core/lastErrorComponentFilter.js"></script>
<script type="text/javascript" src="syncthing/core/localeService.js"></script>
<script type="text/javascript" src="syncthing/core/modalDirective.js"></script>
<script type="text/javascript" src="syncthing/core/metricFilter.js"></script>
<script type="text/javascript" src="syncthing/core/notificationDirective.js"></script>
<script type="text/javascript" src="syncthing/core/pathIsSubDirDirective.js"></script>
<script type="text/javascript" src="syncthing/core/popoverDirective.js"></script>
<script type="text/javascript" src="syncthing/core/selectOnClickDirective.js"></script>
<script type="text/javascript" src="syncthing/core/syncthingController.js"></script>
<script type="text/javascript" src="syncthing/core/tooltipDirective.js"></script>
<script type="text/javascript" src="syncthing/core/uncamelFilter.js"></script>
<script type="text/javascript" src="syncthing/core/uniqueFolderDirective.js"></script>
<script type="text/javascript" src="syncthing/core/validDeviceidDirective.js"></script>
<script type="text/javascript" src="assets/lang/valid-langs.js"></script>
<script type="text/javascript" src="assets/lang/prettyprint.js"></script>
<script type="text/javascript" src="meta.js"></script>
<script type="text/javascript" src="syncthing/app.js"></script>
<!-- / gui application code -->
</body>
</html>

View File

@ -0,0 +1,35 @@
<div class="col-md-6 checkbox">
<label for="sharedwith-{{id}}">
<input id="sharedwith-{{id}}" ng-model="selected[id]" type="checkbox" />
<span tooltip data-original-title="{{id}}">{{label}}</span>
</label>
</div>
<div class="col-md-6">
<div class="input-group">
<span class="input-group-addon" ng-switch="folderType !== 'receiveencrypted' && !encryptionPasswords[id]">
<span ng-switch-when='true' class="fas fa-fw fa-unlock" />
<span ng-switch-default class="fas fa-fw fa-lock" />
</span>
<span ng-switch="folderType === 'receiveencrypted'">
<span ng-switch-when='true'>
<input class="form-control input-sm" type="password" placeholder="{{'Received data is already encrypted' | translate}}" disabled />
</span>
<span ng-switch-default ng-switch="selected[id]">
<span ng-switch-when='true' ng-class="{'has-error': untrusted && !encryptionPasswords[id]}">
<input class="form-control input-sm" type="{{plain ? 'text' : 'password'}}" ng-model="encryptionPasswords[id]" ng-required="untrusted" placeholder="{{'If untrusted, enter encryption password' | translate}}" />
</span>
<span ng-switch-default>
<input class="form-control input-sm" type="password" placeholder="{{'Not shared' | translate}}" disabled />
</span>
</span>
</span>
<span ng-switch="selected[id] && folderType !== 'receiveencrypted'" class="input-group-addon">
<span ng-switch-when='true'>
<span class="button fas fa-fw fa-eye" ng-click="togglePasswordVisibility()" />
</span>
<span ng-switch-default>
<span class="button fas fa-fw fa-eye" disabled />
</span>
</span>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,166 @@
<modal id="editDevice" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-desktop'}}" heading="{{editingExisting ? 'Edit Device' : 'Add Device' | translate}} {{currentDevice.name}}" large="yes" closeable="yes">
<div class="modal-body">
<form role="form" name="deviceEditor">
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
<li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
<li><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
<li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
</ul>
<div class="tab-content">
<div id="device-general" class="tab-pane in active">
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
<label translate for="deviceID">Device ID</label>
<div ng-if="!editingExisting">
<input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
<datalist id="discovery-list">
<option ng-repeat="id in discovery" value="{{id}}" />
</datalist>
<p class="help-block" ng-if="discovery && discovery.length !== 0">
<span translate>You can also select one of these nearby devices:</span>
<ul>
<li ng-repeat="id in discovery"><a href="#" ng-click="currentDevice.deviceID = id">{{id}}</a></li>
</ul>
</p>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">The device ID to enter here can be found in the "Actions > Show ID" dialog on the other device. Spaces and dashes are optional (ignored).</span>
<span translate ng-show="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">When adding a new device, keep in mind that this device must be added on the other side too.</span>
<span translate ng-if="deviceEditor.deviceID.$error.required && deviceEditor.deviceID.$dirty">The device ID cannot be blank.</span>
<span translate ng-if="deviceEditor.deviceID.$error.validDeviceid && deviceEditor.deviceID.$dirty">The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.</span>
<span translate ng-if="deviceEditor.deviceID.$error.unique && deviceEditor.deviceID.$dirty">A device with that ID is already added.</span>
</p>
</div>
<div ng-if="editingExisting" class="well well-sm text-monospace" select-on-click>{{currentDevice.deviceID}}</div>
</div>
<div class="form-group">
<label translate for="name">Device Name</label>
<input id="name" class="form-control" type="text" ng-model="currentDevice.name" />
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
</div>
</div>
<div id="device-sharing" class="tab-pane">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.introducer">
<span translate>Introducer</span>
<p translate class="help-block">Add devices from the introducer to our device list, for mutually shared folders.</p>
</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.autoAcceptFolders">
<span translate>Auto Accept</span>
<p translate class="help-block">Automatically create or share folders that this device advertises at the default path.</p>
</label>
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="form-horizontal" ng-if="currentSharing.shared.length">
<label translate for="folders">Shared Folders</label>
<p class="help-block">
<span translate>Select the folders to share with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedFolders(false)" translate>Deselect All</a></small>
</p>
<div class="form-group" ng-repeat="folder in currentSharing.shared">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="{{device.untrusted}}" />
</div>
</div>
<div class="form-horizontal" ng-if="currentSharing.unrelated.length">
<label translate>Unshared Folders</label>
<p class="help-block">
<span translate>Select additional folders to share with this device.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedFolders(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedFolders(false)" translate>Deselect All</a></small>
</p>
<div class="form-group" ng-repeat="folder in currentSharing.unrelated">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{folder.id}}" label="{{folderLabel(folder.id)}}" folder-type="{{folder.type}}" untrusted="{{device.untrusted}}" />
</div>
</div>
</div>
</div>
<div id="device-advanced" class="tab-pane">
<div class="row form-group">
<div class="col-md-6">
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice._addressesStr"></input>
<p translate class="help-block">Enter comma separated ("tcp://ip:port", "tcp://host:port") addresses or "dynamic" to perform automatic discovery of the address.</p>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
</select>
</div>
</div>
</div>
<div class="row form-group">
<div class="col-md-12">
<label translate>Device rate limits</label>
<div class="row">
<div class="col-md-6" ng-class="{'has-error': deviceEditor.maxRecvKbps.$invalid && deviceEditor.maxRecvKbps.$dirty}">
<div class="row">
<span class="col-md-8" translate>Incoming Rate Limit (KiB/s)</span>
<div class="col-md-4">
<input name="maxRecvKbps" id="maxRecvKbps" class="form-control" type="number" pattern="\d+" ng-model="currentDevice.maxRecvKbps" min="0" />
</div>
</div>
<p class="help-block" ng-if="!deviceEditor.maxRecvKbps.$valid && deviceEditor.maxRecvKbps.$dirty" translate>The rate limit must be a non-negative number (0: no limit)</p>
</div>
<div class="col-md-6" ng-class="{'has-error': deviceEditor.maxSendKbps.$invalid && deviceEditor.maxSendKbps.$dirty}">
<div class="row">
<span class="col-md-8" translate>Outgoing Rate Limit (KiB/s)</span>
<div class="col-md-4">
<input name="maxSendKbps" id="maxSendKbps" class="form-control" type="number" pattern="\d+" ng-model="currentDevice.maxSendKbps" min="0" />
</div>
</div>
<p class="help-block" ng-if="!deviceEditor.maxSendKbps.$valid && deviceEditor.maxSendKbps.$dirty" translate>The rate limit must be a non-negative number (0: no limit)</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<input type="checkbox" id="untrusted" ng-model="currentDevice.untrusted" />
<label for="untrusted" translate>Untrusted</label>
<p translate class="help-block">All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.</p>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" ng-click="saveDevice()" ng-disabled="deviceEditor.$invalid">
<span class="fas fa-check"></span>&nbsp;<span translate>Save</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#idqr" ng-if="editingExisting || deviceEditor.deviceID.$valid">
<span class="fas fa-qrcode"></span>&nbsp;<span translate>Show QR</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
</button>
<div ng-if="editingExisting" class="pull-left">
<button type="button" class="btn btn-warning btn-sm disabled" ng-if="willBeReintroducedBy" tooltip data-original-title="This device will be reintroduced by {{ willBeReintroducedBy }}">
<span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation" ng-if="!willBeReintroducedBy">
<span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
</div>
</div>
</modal>

View File

@ -0,0 +1,19 @@
<modal id="delete-encryption-confirmation" status="danger" icon="fas fa-question-circle" heading="{{'Delete Unexpected Items' | translate}}" large="no" closeable="yes">
<div class="modal-body">
<p>
<span translate>Unexpected items have been found in this folder.</span></br>
<span translate translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">You should never add or change anything locally in a "{%receiveEncrypted%}" folder.</span>
</p>
<p translate>
Are you sure you want to permanently delete all these files?
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger pull-left btn-sm" data-dismiss="modal" ng-click="revert(revertEncryptionFolder)">
<span class="fas fa-check"></span>&nbsp;<span translate>Yes</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>No</span>
</button>
</div>
</modal>

View File

@ -0,0 +1,278 @@
<modal id="editFolder" status="default" icon="{{editingExisting ? 'fas fa-pencil-alt' : 'fas fa-folder'}}" heading="{{editingExisting ? 'Edit Folder' : 'Add Folder' | translate}} ({{folderLabel(currentFolder.id)}})" large="yes" closeable="yes">
<div class="modal-body">
<form role="form" name="folderEditor">
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
<li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
<li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
<li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
<li><a data-toggle="tab" href="#folder-ignores"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
<li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
</ul>
<div class="tab-content">
<div id="folder-general" class="tab-pane in active">
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty}">
<label for="folderLabel"><span translate>Folder Label</span></label>
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
<p class="help-block">
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Required identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
<span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
<label translate for="folderPath">Folder Path</label>
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required="" aria-required="true" path-is-sub-dir />
<datalist id="directory-list">
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
</datalist>
<p class="help-block">
<span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty">The folder path cannot be blank.</span>
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isParent && folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
</p>
</div>
</div>
<div id="folder-sharing" class="tab-pane">
<div class="form-horizontal" ng-if="currentSharing.shared.length">
<label translate>Currently Shared With Devices</label>
<p class="help-block">
<span translate>Deselect devices to stop sharing this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedDevices(false)" translate>Deselect All</a></small>
</p>
<div class="form-group" ng-repeat="device in currentSharing.shared">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="{{device.untrusted}}" />
</div>
</div>
<div class="form-horizontal" ng-if="currentSharing.unrelated.length || otherDevices().length <= 0">
<label translate>Unshared Devices</label>
<p class="help-block" ng-if="otherDevices().length > 0">
<span translate>Select additional devices to share this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedDevices(false)" translate>Deselect All</a></small>
</p>
<p class="help-block" ng-if="otherDevices().length <= 0">
<span translate>There are no devices to share this folder with.</span>
</p>
<div class="form-group" ng-repeat="device in currentSharing.unrelated" ng-init="id = device.deviceID; folder = currentFolder">
<share-template selected="currentSharing.selected" encryption-passwords="currentSharing.encryptionPasswords" id="{{device.deviceID}}" label="{{deviceName(device)}}" folder-type="{{currentFolder.type}}" untrusted="{{device.untrusted}}" />
</div>
</div>
</div>
<div id="folder-versioning" class="tab-pane">
<div class="form-group">
<label translate>File Versioning</label>&emsp;<a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
<option value="none" translate>No File Versioning</option>
<option value="trashcan" translate>Trash Can File Versioning</option>
<option value="simple" translate>Simple File Versioning</option>
<option value="staggered" translate>Staggered File Versioning</option>
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='trashcan' || currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
<input name="trashcanClean" id="trashcanClean" class="form-control text-right" type="number" ng-model="currentFolder.trashcanClean" required="" aria-required="true" min="0" />
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required="" aria-required="true" min="1" />
<p class="help-block">
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required="" aria-required="true" min="0" />
<p class="help-block">
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath" />
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required="" aria-required="true" />
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">See external versioning help for supported templated command line parameters.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector != 'none'" ng-class="{'has-error': folderEditor.versioningCleanupIntervalS.$invalid && folderEditor.versioningCleanupIntervalS.$dirty}">
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
<div class="input-group">
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder.versioningCleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<div class="input-group-addon" translate>seconds</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$valid || folderEditor.versioningCleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.required && folderEditor.versioningCleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor.versioningCleanupIntervalS.$error.min && folderEditor.versioningCleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
</p>
</div>
</div>
<div id="folder-ignores" class="tab-pane">
<p translate>Enter ignore patterns, one per line.</p>
<div ng-class="{'has-error': ignores.error != null}">
<textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
<p class="help-block" ng-if="ignores.error">
{{ignores.error}}
</p>
</div>
<hr />
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
<dl class="dl-horizontal dl-narrow small">
<dt><code>(?d)</code></dt>
<dd><b><span translate>Prefix indicating that the file can be deleted if preventing directory removal</span></b></dd>
<dt><code>(?i)</code></dt>
<dd><span translate>Prefix indicating that the pattern should be matched without case sensitivity</span></dd>
<dt><code>!</code></dt>
<dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
<dt><code>*</code></dt>
<dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
<dt><code>**</code></dt>
<dd><span translate>Multi level wildcard (matches multiple directory levels)</span></dd>
<dt><code>//</code></dt>
<dd><span translate>Comment, when used at the start of a line</span></dd>
</dl>
<hr />
<span translate ng-show="editingExisting" translate-value-path="{{currentFolder.path}}{{system.pathSeparator}}.stignore">Editing {%path%}.</span>
<span translate ng-show="!editingExisting" translate-value-path="{{currentFolder.path}}{{system.pathSeparator}}.stignore">Creating ignore patterns, overwriting an existing file at {%path%}.</span>
</div>
<div id="folder-advanced" class="tab-pane">
<div class="row form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
<div class="col-md-12">
<label translate>Scanning</label>
&nbsp;<a href="https://docs.syncthing.net/users/syncing.html#scanning" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a></br>
<div class="row">
<div class="col-md-6">
<label>
<input type="checkbox" ng-model="currentFolder.fsWatcherEnabled" ng-change="fsWatcherToggled()" tooltip data-original-title="{{'Use notifications from the filesystem to detect changed items.' | translate }}">&nbsp;<span translate>Watch for Changes</span>
</label>
<p translate class="help-block">Watching for changes discovers most changes without periodic scanning.</p>
</div>
<div class="col-md-6">
<label for="rescanIntervalS" translate>Full Rescan Interval (s)</label>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.rescanIntervalS" required="" aria-required="true" min="0" />
<p class="help-block" ng-if="!folderEditor.rescanIntervalS.$valid && folderEditor.rescanIntervalS.$dirty" translate>
The rescan interval must be a non-negative number of seconds.
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group">
<label translate>Folder Type</label>
&nbsp;<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type === 'receiveencrypted'">
<option value="sendreceive" translate>Send &amp; Receive</option>
<option value="sendonly" translate>Send Only</option>
<option value="receiveonly" translate>Receive Only</option>
<option value="receiveencrypted" ng-disabled="editingExisting" translate>Receive Encrypted</option>
</select>
<p ng-if="currentFolder.type == 'sendonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
<p ng-if="currentFolder.type == 'receiveonly'" translate class="help-block">Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.</p>
<p ng-if="currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type "{%receiveEncrypted%}" too.</p>
<p ng-if="editingExisting" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
</div>
<div class="col-md-6 form-group">
<label translate>File Pull Order</label>
<select class="form-control" ng-model="currentFolder.order" ng-if="currentFolder.type != 'sendonly'">
<option value="random" translate>Random</option>
<option value="alphabetic" translate>Alphabetic</option>
<option value="smallestFirst" translate>Smallest First</option>
<option value="largestFirst" translate>Largest First</option>
<option value="oldestFirst" translate>Oldest First</option>
<option value="newestFirst" translate>Newest First</option>
</select>
<select class="form-control" ng-if="currentFolder.type == 'sendonly'" disabled>
<option value="disabled" translate>Disabled</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-6 form-group" ng-class="{'has-error': folderEditor.minDiskFree.$invalid && folderEditor.minDiskFree.$dirty}">
<label for="minDiskFree" translate>Minimum Free Disk Space</label><br />
<div class="row">
<div class="col-xs-9">
<input name="minDiskFree" id="minDiskFree" class="form-control" type="number" ng-model="currentFolder.minDiskFree.value" required="" aria-required="true" min="0" step="0.01" />
</div>
<div class="col-xs-3">
<select class="form-control" ng-model="currentFolder.minDiskFree.unit">
<option value="%">%</option>
<option value="kB">kB</option>
<option value="MB">MB</option>
<option value="GB">GB</option>
<option value="TB">TB</option>
</select>
</div>
</div>
<p class="help-block" ng-show="folderEditor.minDiskFree.$invalid" translate>
Enter a non-negative number (e.g., "2.35") and select a unit. Percentages are as part of the total disk size.
</p>
</div>
<div class="col-md-6 form-group">
<label>
<input type="checkbox" ng-model="currentFolder.ignorePerms" /> <span translate>Ignore Permissions</span>
</label>
<p translate class="help-block">
Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).
</p>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" ng-click="saveFolder()" ng-disabled="folderEditor.$invalid">
<span class="fas fa-check"></span>&nbsp;<span translate>Save</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
</button>
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting">
<span class="fas fa-minus-circle"></span>&nbsp;<span translate>Remove</span>
</button>
</div>
</modal>

View File

@ -0,0 +1,35 @@
<modal id="localChanged" status="{{localChangedType === 'receiveencrypted' ? 'warning' : 'info'}}" icon="fas fa-exclamation-circle" heading="{{localChangedHeading(localChangedType)}}" large="yes" closeable="yes">
<div class="modal-body" ng-switch="localChangedType">
<p ng-switch-when="receiveonly" translate>
The following items were changed locally.
</p>
<p ng-switch-when="receiveencrypted">
<span translate>The following unexpected items were found.</span>
<span translate translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">You should never add or change anything locally in a "{%receiveEncrypted%}" folder.</span>
</p>
<table class="table table-striped">
<thead>
<tr>
<th translate>Path</th>
<th translate>Size</th>
</tr>
</thead>
<tr dir-paginate="file in localChanged.files | itemsPerPage: localChanged.perpage" current-page="localChanged.page" total-items="model[localChangedFolder].receiveOnlyTotalItems" pagination-id="localChanged">
<td class="file-path">{{file.name}}</td>
<td><span ng-hide="file.type == 'DIRECTORY'">{{file.size | binary}}B</span></td>
</tr>
</table>
<dir-pagination-controls on-page-change="refreshLocalChanged(newPageNumber, localChanged.perpage)" pagination-id="localChanged"></dir-pagination-controls>
<ul class="pagination pull-right">
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: localChanged.perpage == option }">
<a href="#" ng-click="refreshLocalChanged(localChanged.page, option)">{{option}}</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>

View File

@ -65,6 +65,7 @@ const (
EventSubBufferSize = 1000
defaultEventTimeout = time.Minute
httpsCertLifetimeDays = 820
featureFlagUntrusted = "untrusted"
)
type service struct {
@ -110,7 +111,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
s := &service{
id: id,
cfg: cfg,
statics: newStaticsServer(cfg.GUI().Theme, assetDir),
statics: newStaticsServer(cfg.GUI().Theme, assetDir, cfg.Options().FeatureFlag(featureFlagUntrusted)),
model: m,
eventSubs: map[events.EventType]events.BufferedSubscription{
DefaultEventMask: defaultSub,
@ -449,7 +450,12 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
// No action required when this changes, so mask the fact that it changed at all.
from.GUI.Debugging = to.GUI.Debugging
if untrusted := to.Options.FeatureFlag(featureFlagUntrusted); untrusted != from.Options.FeatureFlag(featureFlagUntrusted) {
s.statics.setUntrusted(untrusted)
}
if to.GUI == from.GUI {
// No GUI changes, we're done here.
return true
}

View File

@ -30,15 +30,17 @@ type staticsServer struct {
mut sync.RWMutex
theme string
lastThemeChange time.Time
untrusted bool
}
func newStaticsServer(theme, assetDir string) *staticsServer {
func newStaticsServer(theme, assetDir string, untrusted bool) *staticsServer {
s := &staticsServer{
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
lastThemeChange: time.Now().UTC(),
untrusted: untrusted,
}
seen := make(map[string]struct{})
@ -60,6 +62,10 @@ func newStaticsServer(theme, assetDir string) *staticsServer {
}
}
if untrusted {
l.Infoln(`Feature flag "untrusted":`, untrusted)
}
return s
}
@ -88,6 +94,7 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
s.mut.RLock()
theme := s.theme
modificationTime := s.lastThemeChange
untrusted := s.untrusted
s.mut.RUnlock()
// If path starts with special prefix, get theme and file from path
@ -105,44 +112,68 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
}
// Check for an override for the current theme.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, theme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
mtype := assets.MimeTypeForFile(file)
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
http.ServeFile(w, r, p)
if untrusted && s.serveFromAssetDir(file, theme+"/untrusted", w, r) {
l.Debugln("serving", file, "from override untrusted")
return
}
if s.serveFromAssetDir(file, theme, w, r) {
return
}
// Check for a compiled in asset for the current theme.
as, ok := s.assets[theme+"/"+file]
if !ok {
if untrusted && s.serveFromAssets(file, theme+"/untrusted", modificationTime, w, r) {
l.Debugln("serving", file, "from compiled untrusted")
return
}
if s.serveFromAssets(file, theme, modificationTime, w, r) {
return
}
// Check for an overridden default asset.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, config.DefaultTheme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
if untrusted && s.serveFromAssetDir(file, config.DefaultTheme+"/untrusted", w, r) {
l.Debugln("serving", file, "from override untrusted")
return
}
if s.serveFromAssetDir(file, config.DefaultTheme, w, r) {
return
}
// Check for a compiled in default asset.
if untrusted && s.serveFromAssets(file, config.DefaultTheme+"/untrusted", modificationTime, w, r) {
l.Debugln("serving", file, "from compiled untrusted")
return
}
if s.serveFromAssets(file, config.DefaultTheme, modificationTime, w, r) {
return
}
http.NotFound(w, r)
}
func (s *staticsServer) serveFromAssetDir(file, theme string, w http.ResponseWriter, r *http.Request) bool {
if s.assetDir == "" {
return false
}
p := filepath.Join(s.assetDir, theme, filepath.FromSlash(file))
if _, err := os.Stat(p); err != nil {
return false
}
mtype := assets.MimeTypeForFile(file)
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
http.ServeFile(w, r, p)
return
}
return true
}
// Check for a compiled in default asset.
as, ok = s.assets[config.DefaultTheme+"/"+file]
func (s *staticsServer) serveFromAssets(file, theme string, modificationTime time.Time, w http.ResponseWriter, r *http.Request) bool {
as, ok := s.assets[theme+"/"+file]
if !ok {
http.NotFound(w, r)
return
return false
}
}
as.Modified = modificationTime
assets.Serve(w, r, as)
return true
}
func (s *staticsServer) serveThemes(w http.ResponseWriter, r *http.Request) {
@ -158,6 +189,13 @@ func (s *staticsServer) setTheme(theme string) {
s.mut.Unlock()
}
func (s *staticsServer) setUntrusted(enabled bool) {
s.mut.Lock()
l.Infoln(`Feature flag "untrusted":`, enabled)
s.untrusted = enabled
s.mut.Unlock()
}
func (s *staticsServer) String() string {
return fmt.Sprintf("staticsServer@%p", s)
}

View File

@ -114,6 +114,10 @@ func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) {
return noopWaiter{}, nil
}
func (c *mockedConfig) FolderPasswords(device protocol.DeviceID) map[string]string {
return nil
}
func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) {
return config.DeviceConfiguration{}, false
}

View File

@ -135,7 +135,7 @@ func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, fil
return nil
}
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
return nil, nil
}

View File

@ -419,6 +419,9 @@ nextPendingDevice:
}
}
}
if cfg.Options.FeatureFlags == nil {
cfg.Options.FeatureFlags = []string{}
}
return nil
}
@ -432,6 +435,22 @@ func (cfg *Configuration) DeviceMap() map[protocol.DeviceID]DeviceConfiguration
return m
}
// FolderPasswords returns the folder passwords set for this device, for
// folders that have an encryption password set.
func (cfg Configuration) FolderPasswords(device protocol.DeviceID) map[string]string {
res := make(map[string]string, len(cfg.Folders))
nextFolder:
for _, folder := range cfg.Folders {
for _, dev := range folder.Devices {
if dev.DeviceID == device && dev.EncryptionPassword != "" {
res[folder.ID] = dev.EncryptionPassword
continue nextFolder
}
}
}
return res
}
func ensureDevicePresent(devices []FolderDeviceConfiguration, myID protocol.DeviceID) []FolderDeviceConfiguration {
for _, device := range devices {
if device.DeviceID.Equals(myID) {

View File

@ -77,6 +77,7 @@ func TestDefaultValues(t *testing.T) {
StunKeepaliveMinS: 20,
RawStunServers: []string{"default"},
AnnounceLANAddresses: true,
FeatureFlags: []string{},
}
cfg := New(device1)
@ -225,6 +226,7 @@ func TestOverriddenValues(t *testing.T) {
StunKeepaliveStartS: 9000,
StunKeepaliveMinS: 900,
RawStunServers: []string{"foo"},
FeatureFlags: []string{"feature"},
}
os.Unsetenv("STNOUPGRADE")

View File

@ -42,6 +42,7 @@ type DeviceConfiguration struct {
IgnoredFolders []ObservedFolder `protobuf:"bytes,14,rep,name=ignored_folders,json=ignoredFolders,proto3" json:"ignoredFolders" xml:"ignoredFolder"`
PendingFolders []ObservedFolder `protobuf:"bytes,15,rep,name=pending_folders,json=pendingFolders,proto3" json:"pendingFolders" xml:"pendingFolder"`
MaxRequestKiB int `protobuf:"varint,16,opt,name=max_request_kib,json=maxRequestKib,proto3,casttype=int" json:"maxRequestKiB" xml:"maxRequestKiB"`
Untrusted bool `protobuf:"varint,17,opt,name=untrusted,proto3" json:"untrusted" xml:"untrusted"`
}
func (m *DeviceConfiguration) Reset() { *m = DeviceConfiguration{} }
@ -86,64 +87,66 @@ func init() {
}
var fileDescriptor_744b782bd13071dd = []byte{
// 902 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x31, 0x6f, 0xdb, 0x46,
0x18, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x96, 0x65, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x60, 0x35,
0x28, 0x68, 0x2a, 0x17, 0x6e, 0x27, 0xa3, 0x1d, 0xca, 0x04, 0x45, 0x03, 0xa3, 0x49, 0x7b, 0xdd,
0xbc, 0xb0, 0x24, 0xef, 0xac, 0x1c, 0x2c, 0xf2, 0x58, 0xf2, 0xa4, 0x48, 0x40, 0x87, 0x8e, 0x1d,
0x3a, 0x14, 0x59, 0xbb, 0x14, 0x1d, 0x3a, 0xf4, 0x97, 0x18, 0xe8, 0x60, 0x8d, 0x45, 0x87, 0x03,
0x62, 0x6f, 0x1c, 0x39, 0x66, 0x2a, 0x78, 0x47, 0x51, 0x24, 0x1d, 0x17, 0x06, 0xba, 0xdd, 0xbd,
0xf7, 0xee, 0xbd, 0xe3, 0xd3, 0x7d, 0x10, 0xe8, 0x8d, 0xa8, 0x7b, 0xe0, 0xb1, 0xe0, 0x94, 0x0e,
0x0f, 0x30, 0x99, 0x50, 0x8f, 0xa8, 0xcd, 0x38, 0x72, 0x38, 0x65, 0xc1, 0x20, 0x8c, 0x18, 0x67,
0xfa, 0xaa, 0x02, 0xf7, 0xf7, 0x32, 0xb5, 0x84, 0x3c, 0x36, 0x3a, 0x70, 0x49, 0xa8, 0xf8, 0xfd,
0x07, 0x25, 0x17, 0xe6, 0xc6, 0x24, 0x9a, 0x10, 0x9c, 0x53, 0x4d, 0x32, 0xe5, 0x6a, 0x69, 0xfe,
0xb5, 0x05, 0x76, 0x9f, 0xca, 0x8c, 0x27, 0xe5, 0x0c, 0xfd, 0x4f, 0x0d, 0x34, 0x55, 0xb6, 0x4d,
0xb1, 0xa1, 0x75, 0xb5, 0xfe, 0xa6, 0xf5, 0xb3, 0x76, 0x2e, 0x60, 0xe3, 0x1f, 0x01, 0x3f, 0x19,
0x52, 0xfe, 0x72, 0xec, 0x0e, 0x3c, 0xe6, 0x1f, 0xc4, 0xb3, 0xc0, 0xe3, 0x2f, 0x69, 0x30, 0x2c,
0xad, 0xca, 0x37, 0x1a, 0x28, 0xf7, 0x67, 0x4f, 0x2f, 0x05, 0x5c, 0x5f, 0xac, 0x13, 0x01, 0xd7,
0x71, 0xbe, 0x4e, 0x05, 0x6c, 0x4d, 0xfd, 0xd1, 0x91, 0x49, 0xf1, 0x63, 0x87, 0xf3, 0xc8, 0x4c,
0x2e, 0x7a, 0x6b, 0xf9, 0x3a, 0xbd, 0xe8, 0x15, 0xba, 0x9f, 0xe6, 0x3d, 0xed, 0xf5, 0xbc, 0x57,
0x78, 0xa0, 0x05, 0x83, 0xf5, 0xaf, 0xc1, 0x9d, 0xc0, 0xf1, 0x89, 0xf1, 0x5e, 0x57, 0xeb, 0x37,
0xad, 0x4f, 0x13, 0x01, 0xe5, 0x3e, 0x15, 0xf0, 0x81, 0x74, 0xce, 0x36, 0xd2, 0xef, 0x31, 0xf3,
0x29, 0x27, 0x7e, 0xc8, 0x67, 0x59, 0xca, 0xee, 0x3b, 0x70, 0x24, 0x4f, 0xea, 0x53, 0xd0, 0x74,
0x30, 0x8e, 0x48, 0x1c, 0x93, 0xd8, 0x58, 0xe9, 0xae, 0xf4, 0x9b, 0xd6, 0x49, 0x22, 0xe0, 0x12,
0x4c, 0x05, 0x7c, 0x24, 0xbd, 0x73, 0xa4, 0xe4, 0xdc, 0xc5, 0xe4, 0xd4, 0x19, 0x8f, 0xf8, 0x91,
0x89, 0x67, 0x81, 0xe3, 0x53, 0x2f, 0xcb, 0xda, 0xb9, 0xa6, 0x7b, 0x7b, 0xd1, 0x5b, 0xcb, 0x05,
0x68, 0xe9, 0xab, 0x4f, 0xc0, 0x86, 0xc7, 0xfc, 0x30, 0xdb, 0x51, 0x16, 0x18, 0x77, 0xba, 0x5a,
0x7f, 0xeb, 0xf0, 0xde, 0xa0, 0xa8, 0xf3, 0xc9, 0x92, 0xb4, 0x3e, 0x4b, 0x04, 0x2c, 0xab, 0x53,
0x01, 0xf7, 0xe4, 0xa5, 0x4a, 0x58, 0xd1, 0xe9, 0x76, 0x1d, 0x44, 0xe5, 0xa3, 0x3a, 0x01, 0x4d,
0x8f, 0x44, 0xdc, 0x96, 0x45, 0xde, 0x95, 0x45, 0x7e, 0x99, 0xfd, 0x4c, 0x19, 0xf8, 0x5c, 0x95,
0xf9, 0x50, 0x79, 0xe7, 0xc0, 0x3b, 0x0a, 0xbd, 0x7f, 0x03, 0x87, 0x0a, 0x17, 0xfd, 0x04, 0x00,
0x1a, 0xf0, 0x88, 0xe1, 0xb1, 0x47, 0x22, 0x63, 0xb5, 0xab, 0xf5, 0xd7, 0xad, 0xa3, 0x44, 0xc0,
0x12, 0x9a, 0x0a, 0x78, 0x4f, 0x3d, 0x88, 0x02, 0x2a, 0x3e, 0xa2, 0x5d, 0xc3, 0x50, 0xe9, 0x9c,
0xfe, 0xbb, 0x06, 0xf6, 0xe3, 0x33, 0x1a, 0xda, 0x0b, 0x2c, 0x7b, 0xc9, 0x76, 0x44, 0x7c, 0x36,
0x71, 0x46, 0xb1, 0xb1, 0x26, 0xc3, 0x70, 0x22, 0xa0, 0x91, 0xa9, 0x9e, 0x95, 0x44, 0x28, 0xd7,
0xa4, 0x02, 0xbe, 0x2f, 0xa3, 0x6f, 0x12, 0x14, 0x17, 0x79, 0xf8, 0x9f, 0x0a, 0x74, 0x63, 0x82,
0xfe, 0x87, 0x06, 0x5a, 0xc5, 0x9d, 0xb1, 0xed, 0xce, 0x8c, 0x75, 0x39, 0x5c, 0x3f, 0xfe, 0xaf,
0xe1, 0x4a, 0x04, 0xdc, 0x5c, 0xba, 0x5a, 0xb3, 0x54, 0xc0, 0xfb, 0xd5, 0x0e, 0xb1, 0x35, 0x2b,
0x2e, 0xbf, 0x73, 0x0d, 0xcd, 0x86, 0x0b, 0x55, 0x1c, 0xf4, 0x43, 0xb0, 0x1a, 0x3a, 0xe3, 0x98,
0x60, 0xa3, 0x29, 0x8b, 0xdb, 0x4f, 0x04, 0xcc, 0x91, 0x54, 0xc0, 0x4d, 0xe9, 0xae, 0xb6, 0x26,
0xca, 0x71, 0xfd, 0x07, 0xb0, 0xed, 0x8c, 0x46, 0xec, 0x15, 0xc1, 0x76, 0x40, 0xf8, 0x2b, 0x16,
0x9d, 0xc5, 0x06, 0x90, 0xd3, 0xf3, 0x4d, 0x22, 0x60, 0x3b, 0xe7, 0x9e, 0xe7, 0x54, 0x2a, 0x60,
0x47, 0xcd, 0x50, 0x05, 0xaf, 0xbe, 0x29, 0xe3, 0x26, 0x12, 0xd5, 0xed, 0xf4, 0xef, 0xc0, 0xae,
0x33, 0xe6, 0xcc, 0x76, 0x3c, 0x8f, 0x84, 0xdc, 0x3e, 0x65, 0x23, 0x4c, 0xa2, 0xd8, 0xd8, 0x90,
0xd7, 0xff, 0x28, 0x11, 0x70, 0x27, 0xa3, 0x3f, 0x97, 0xec, 0x17, 0x8a, 0x2c, 0x7a, 0xba, 0xc6,
0x98, 0xe8, 0xba, 0x5a, 0x7f, 0x01, 0x5a, 0xbe, 0x33, 0xb5, 0x63, 0x12, 0x60, 0xfb, 0xcc, 0x0d,
0x63, 0x63, 0xb3, 0xab, 0xf5, 0xef, 0x5a, 0x1f, 0x64, 0x73, 0xe8, 0x3b, 0xd3, 0x6f, 0x49, 0x80,
0x8f, 0xdd, 0x30, 0x73, 0xdd, 0x91, 0xae, 0x25, 0xcc, 0x7c, 0x2b, 0xe0, 0x0a, 0x0d, 0x38, 0x2a,
0x0b, 0x17, 0x86, 0x11, 0xf1, 0x26, 0xca, 0xb0, 0x55, 0x31, 0x44, 0xc4, 0x9b, 0xd4, 0x0d, 0x17,
0x58, 0xc5, 0x70, 0x01, 0xea, 0x01, 0x68, 0xd3, 0x61, 0xc0, 0x22, 0x82, 0x8b, 0xef, 0xdf, 0xea,
0xae, 0xf4, 0x37, 0x0e, 0xf7, 0x06, 0xea, 0xbf, 0x60, 0xf0, 0x22, 0xff, 0x2f, 0x50, 0xdf, 0x64,
0x7d, 0x98, 0x3d, 0xbb, 0x44, 0xc0, 0xad, 0xfc, 0xd8, 0xb2, 0x98, 0x5d, 0xf5, 0x80, 0xca, 0xb0,
0x89, 0x6a, 0xb2, 0x2c, 0x2f, 0x24, 0x01, 0xa6, 0xc1, 0xb0, 0xc8, 0x6b, 0xdf, 0x2e, 0x2f, 0x3f,
0x56, 0xcf, 0xab, 0xc0, 0x26, 0xaa, 0xc9, 0xf4, 0x5f, 0x35, 0xd0, 0x56, 0x8d, 0x7d, 0x3f, 0x26,
0x31, 0xb7, 0xcf, 0xa8, 0x6b, 0x6c, 0xcb, 0xce, 0xe2, 0x4b, 0x01, 0x5b, 0x5f, 0x65, 0x55, 0x48,
0xe6, 0x98, 0x5a, 0x89, 0x80, 0x2d, 0xbf, 0x0c, 0x14, 0x21, 0x15, 0x74, 0x51, 0x64, 0x72, 0xd1,
0xab, 0xc9, 0xeb, 0xc0, 0xeb, 0x79, 0xaf, 0x9a, 0x80, 0x2a, 0xbc, 0x6b, 0x1d, 0x9f, 0xbf, 0xe9,
0x34, 0xe6, 0x6f, 0x3a, 0x8d, 0xf3, 0xcb, 0x8e, 0x36, 0xbf, 0xec, 0x68, 0xbf, 0x5c, 0x75, 0x1a,
0xbf, 0x5d, 0x75, 0xb4, 0xf9, 0x55, 0xa7, 0xf1, 0xf7, 0x55, 0xa7, 0x71, 0xf2, 0xe8, 0x16, 0xd3,
0xad, 0x8a, 0x73, 0x57, 0xe5, 0x94, 0x7f, 0xfc, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xda, 0x35,
0x78, 0xb9, 0x0f, 0x08, 0x00, 0x00,
// 930 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x3d, 0x6f, 0xdb, 0x46,
0x00, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x96, 0x65, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x8e, 0x60, 0x35,
0x28, 0x68, 0x2a, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0x26, 0x28, 0x1a, 0x18, 0x4d, 0xda, 0xeb,
0xe6, 0x85, 0x25, 0x79, 0x67, 0xe5, 0x60, 0xf1, 0xa3, 0xe4, 0x51, 0x91, 0x80, 0x0e, 0x1d, 0x0b,
0xb4, 0x43, 0x91, 0xb5, 0x4b, 0xd1, 0xa1, 0x43, 0x7f, 0x89, 0x37, 0x6b, 0x2c, 0x3a, 0x1c, 0x10,
0x7b, 0xe3, 0xc8, 0x31, 0x53, 0xc1, 0x3b, 0x8a, 0x22, 0xe9, 0xb8, 0x08, 0x90, 0xed, 0xee, 0xbd,
0x77, 0xef, 0x1d, 0x9f, 0xee, 0x74, 0xa0, 0x3f, 0xa6, 0xce, 0x81, 0x1b, 0xf8, 0xa7, 0x74, 0x74,
0x80, 0xc9, 0x84, 0xba, 0x44, 0x4e, 0x92, 0xc8, 0x66, 0x34, 0xf0, 0x87, 0x61, 0x14, 0xb0, 0x40,
0x5d, 0x95, 0xe0, 0xfe, 0x5e, 0xae, 0x16, 0x90, 0x1b, 0x8c, 0x0f, 0x1c, 0x12, 0x4a, 0x7e, 0xff,
0x5e, 0xc5, 0x25, 0x70, 0x62, 0x12, 0x4d, 0x08, 0x2e, 0xa8, 0x36, 0x99, 0x32, 0x39, 0x34, 0x7e,
0xe9, 0x82, 0xdd, 0xc7, 0x22, 0xe3, 0x51, 0x35, 0x43, 0xfd, 0x5b, 0x01, 0x6d, 0x99, 0x6d, 0x51,
0xac, 0x29, 0xba, 0x32, 0xd8, 0x34, 0x7f, 0x55, 0xce, 0x39, 0x6c, 0xfd, 0xcb, 0xe1, 0x27, 0x23,
0xca, 0x9e, 0x27, 0xce, 0xd0, 0x0d, 0xbc, 0x83, 0x78, 0xe6, 0xbb, 0xec, 0x39, 0xf5, 0x47, 0x95,
0x51, 0x75, 0x47, 0x43, 0xe9, 0xfe, 0xe4, 0xf1, 0x25, 0x87, 0xeb, 0x8b, 0x71, 0xca, 0xe1, 0x3a,
0x2e, 0xc6, 0x19, 0x87, 0x9d, 0xa9, 0x37, 0x3e, 0x32, 0x28, 0x7e, 0x68, 0x33, 0x16, 0x19, 0xe9,
0x45, 0x7f, 0xad, 0x18, 0x67, 0x17, 0xfd, 0x52, 0xf7, 0xf3, 0xbc, 0xaf, 0xbc, 0x9c, 0xf7, 0x4b,
0x0f, 0xb4, 0x60, 0xb0, 0xfa, 0x0d, 0xb8, 0xe5, 0xdb, 0x1e, 0xd1, 0xde, 0xd3, 0x95, 0x41, 0xdb,
0xfc, 0x34, 0xe5, 0x50, 0xcc, 0x33, 0x0e, 0xef, 0x09, 0xe7, 0x7c, 0x22, 0xfc, 0x1e, 0x06, 0x1e,
0x65, 0xc4, 0x0b, 0xd9, 0x2c, 0x4f, 0xd9, 0x7d, 0x03, 0x8e, 0xc4, 0x4a, 0x75, 0x0a, 0xda, 0x36,
0xc6, 0x11, 0x89, 0x63, 0x12, 0x6b, 0x2b, 0xfa, 0xca, 0xa0, 0x6d, 0x9e, 0xa4, 0x1c, 0x2e, 0xc1,
0x8c, 0xc3, 0x07, 0xc2, 0xbb, 0x40, 0x2a, 0xce, 0x3a, 0x26, 0xa7, 0x76, 0x32, 0x66, 0x47, 0x06,
0x9e, 0xf9, 0xb6, 0x47, 0xdd, 0x3c, 0x6b, 0xe7, 0x9a, 0xee, 0xf5, 0x45, 0x7f, 0xad, 0x10, 0xa0,
0xa5, 0xaf, 0x3a, 0x01, 0x1b, 0x6e, 0xe0, 0x85, 0xf9, 0x8c, 0x06, 0xbe, 0x76, 0x4b, 0x57, 0x06,
0x5b, 0x87, 0x77, 0x86, 0x65, 0x9d, 0x8f, 0x96, 0xa4, 0xf9, 0x59, 0xca, 0x61, 0x55, 0x9d, 0x71,
0xb8, 0x27, 0x36, 0x55, 0xc1, 0xca, 0x4e, 0xb7, 0x9b, 0x20, 0xaa, 0x2e, 0x55, 0x09, 0x68, 0xbb,
0x24, 0x62, 0x96, 0x28, 0xf2, 0xb6, 0x28, 0xf2, 0xab, 0xfc, 0x67, 0xca, 0xc1, 0xa7, 0xb2, 0xcc,
0xfb, 0xd2, 0xbb, 0x00, 0xde, 0x50, 0xe8, 0xdd, 0x1b, 0x38, 0x54, 0xba, 0xa8, 0x27, 0x00, 0x50,
0x9f, 0x45, 0x01, 0x4e, 0x5c, 0x12, 0x69, 0xab, 0xba, 0x32, 0x58, 0x37, 0x8f, 0x52, 0x0e, 0x2b,
0x68, 0xc6, 0xe1, 0x1d, 0x79, 0x20, 0x4a, 0xa8, 0xfc, 0x88, 0x6e, 0x03, 0x43, 0x95, 0x75, 0xea,
0x9f, 0x0a, 0xd8, 0x8f, 0xcf, 0x68, 0x68, 0x2d, 0xb0, 0xfc, 0x24, 0x5b, 0x11, 0xf1, 0x82, 0x89,
0x3d, 0x8e, 0xb5, 0x35, 0x11, 0x86, 0x53, 0x0e, 0xb5, 0x5c, 0xf5, 0xa4, 0x22, 0x42, 0x85, 0x26,
0xe3, 0xf0, 0x7d, 0x11, 0x7d, 0x93, 0xa0, 0xdc, 0xc8, 0xfd, 0xff, 0x55, 0xa0, 0x1b, 0x13, 0xd4,
0xbf, 0x14, 0xd0, 0x29, 0xf7, 0x8c, 0x2d, 0x67, 0xa6, 0xad, 0x8b, 0xcb, 0xf5, 0xd3, 0x3b, 0x5d,
0xae, 0x94, 0xc3, 0xcd, 0xa5, 0xab, 0x39, 0xcb, 0x38, 0xbc, 0x5b, 0xef, 0x10, 0x9b, 0xb3, 0x72,
0xf3, 0x3b, 0xd7, 0xd0, 0xfc, 0x72, 0xa1, 0x9a, 0x83, 0x7a, 0x08, 0x56, 0x43, 0x3b, 0x89, 0x09,
0xd6, 0xda, 0xa2, 0xb8, 0xfd, 0x94, 0xc3, 0x02, 0xc9, 0x38, 0xdc, 0x14, 0xee, 0x72, 0x6a, 0xa0,
0x02, 0x57, 0x7f, 0x04, 0xdb, 0xf6, 0x78, 0x1c, 0xbc, 0x20, 0xd8, 0xf2, 0x09, 0x7b, 0x11, 0x44,
0x67, 0xb1, 0x06, 0xc4, 0xed, 0xf9, 0x36, 0xe5, 0xb0, 0x5b, 0x70, 0x4f, 0x0b, 0x2a, 0xe3, 0xb0,
0x27, 0xef, 0x50, 0x0d, 0xaf, 0x9f, 0x29, 0xed, 0x26, 0x12, 0x35, 0xed, 0xd4, 0xef, 0xc1, 0xae,
0x9d, 0xb0, 0xc0, 0xb2, 0x5d, 0x97, 0x84, 0xcc, 0x3a, 0x0d, 0xc6, 0x98, 0x44, 0xb1, 0xb6, 0x21,
0xb6, 0xff, 0x51, 0xca, 0xe1, 0x4e, 0x4e, 0x7f, 0x21, 0xd8, 0x2f, 0x25, 0x59, 0xf6, 0x74, 0x8d,
0x31, 0xd0, 0x75, 0xb5, 0xfa, 0x0c, 0x74, 0x3c, 0x7b, 0x6a, 0xc5, 0xc4, 0xc7, 0xd6, 0x99, 0x13,
0xc6, 0xda, 0xa6, 0xae, 0x0c, 0x6e, 0x9b, 0x1f, 0xe4, 0xf7, 0xd0, 0xb3, 0xa7, 0xdf, 0x11, 0x1f,
0x1f, 0x3b, 0x61, 0xee, 0xba, 0x23, 0x5c, 0x2b, 0x98, 0xf1, 0x9a, 0xc3, 0x15, 0xea, 0x33, 0x54,
0x15, 0x2e, 0x0c, 0x23, 0xe2, 0x4e, 0xa4, 0x61, 0xa7, 0x66, 0x88, 0x88, 0x3b, 0x69, 0x1a, 0x2e,
0xb0, 0x9a, 0xe1, 0x02, 0x54, 0x7d, 0xd0, 0xa5, 0x23, 0x3f, 0x88, 0x08, 0x2e, 0xbf, 0x7f, 0x4b,
0x5f, 0x19, 0x6c, 0x1c, 0xee, 0x0d, 0xe5, 0x5b, 0x30, 0x7c, 0x56, 0xbc, 0x05, 0xf2, 0x9b, 0xcc,
0x0f, 0xf3, 0x63, 0x97, 0x72, 0xb8, 0x55, 0x2c, 0x5b, 0x16, 0xb3, 0x2b, 0x0f, 0x50, 0x15, 0x36,
0x50, 0x43, 0x96, 0xe7, 0x85, 0xc4, 0xc7, 0xd4, 0x1f, 0x95, 0x79, 0xdd, 0xb7, 0xcb, 0x2b, 0x96,
0x35, 0xf3, 0x6a, 0xb0, 0x81, 0x1a, 0x32, 0xf5, 0x77, 0x05, 0x74, 0x65, 0x63, 0x3f, 0x24, 0x24,
0x66, 0xd6, 0x19, 0x75, 0xb4, 0x6d, 0xd1, 0x59, 0x7c, 0xc9, 0x61, 0xe7, 0xeb, 0xbc, 0x0a, 0xc1,
0x1c, 0x53, 0x33, 0xe5, 0xb0, 0xe3, 0x55, 0x81, 0x32, 0xa4, 0x86, 0x2e, 0x8a, 0x4c, 0x2f, 0xfa,
0x0d, 0x79, 0x13, 0x78, 0x39, 0xef, 0xd7, 0x13, 0x50, 0x8d, 0x77, 0xd4, 0xcf, 0x41, 0x3b, 0xf1,
0x59, 0x94, 0xc4, 0x8c, 0x60, 0x6d, 0x47, 0x9c, 0x3b, 0x3d, 0x7f, 0x36, 0x4a, 0x30, 0xe3, 0xb0,
0x2b, 0x76, 0x50, 0x22, 0x06, 0x5a, 0xb2, 0xe6, 0xf1, 0xf9, 0xab, 0x5e, 0x6b, 0xfe, 0xaa, 0xd7,
0x3a, 0xbf, 0xec, 0x29, 0xf3, 0xcb, 0x9e, 0xf2, 0xdb, 0x55, 0xaf, 0xf5, 0xc7, 0x55, 0x4f, 0x99,
0x5f, 0xf5, 0x5a, 0xff, 0x5c, 0xf5, 0x5a, 0x27, 0x0f, 0xde, 0xe2, 0xdf, 0x41, 0x16, 0xef, 0xac,
0x8a, 0x7f, 0x89, 0x8f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x73, 0xb5, 0x15, 0x0d, 0x4f, 0x08,
0x00, 0x00,
}
func (m *DeviceConfiguration) Marshal() (dAtA []byte, err error) {
@ -166,6 +169,18 @@ func (m *DeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.Untrusted {
i--
if m.Untrusted {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x88
}
if m.MaxRequestKiB != 0 {
i = encodeVarintDeviceconfiguration(dAtA, i, uint64(m.MaxRequestKiB))
i--
@ -388,6 +403,9 @@ func (m *DeviceConfiguration) ProtoSize() (n int) {
if m.MaxRequestKiB != 0 {
n += 2 + sovDeviceconfiguration(uint64(m.MaxRequestKiB))
}
if m.Untrusted {
n += 3
}
return n
}
@ -844,6 +862,26 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error {
break
}
}
case 17:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Untrusted", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDeviceconfiguration
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Untrusted = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipDeviceconfiguration(dAtA[iNdEx:])

View File

@ -236,13 +236,18 @@ func (f FolderConfiguration) RequiresRestartOnly() FolderConfiguration {
return copy
}
func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
func (f *FolderConfiguration) Device(device protocol.DeviceID) (FolderDeviceConfiguration, bool) {
for _, dev := range f.Devices {
if dev.DeviceID == device {
return true
return dev, true
}
}
return false
return FolderDeviceConfiguration{}, false
}
func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool {
_, ok := f.Device(device)
return ok
}
func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error {

View File

@ -29,6 +29,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type FolderDeviceConfiguration struct {
DeviceID github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"deviceID" xml:"id,attr"`
IntroducedBy github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,2,opt,name=introduced_by,json=introducedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"introducedBy" xml:"introducedBy,attr"`
EncryptionPassword string `protobuf:"bytes,3,opt,name=encryption_password,json=encryptionPassword,proto3" json:"encryptionPassword" xml:"encryptionPassword"`
}
func (m *FolderDeviceConfiguration) Reset() { *m = FolderDeviceConfiguration{} }
@ -148,131 +149,134 @@ func init() {
}
var fileDescriptor_44a9785876ed3afa = []byte{
// 1983 bytes of a gzipped FileDescriptorProto
// 2018 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0xcd, 0x6f, 0x1c, 0x49,
0x15, 0x77, 0x3b, 0x5f, 0x76, 0xf9, 0xbb, 0x12, 0x27, 0x15, 0x67, 0xd7, 0xe5, 0x6d, 0x86, 0xc5,
0x15, 0x77, 0x3b, 0x5f, 0x76, 0xf9, 0xbb, 0x1c, 0x27, 0x15, 0x67, 0xd7, 0xe5, 0x6d, 0x86, 0xc5,
0xbb, 0xda, 0x38, 0x89, 0x17, 0x71, 0x88, 0x76, 0x17, 0x76, 0xe2, 0xb5, 0x08, 0x21, 0x9b, 0x51,
0x3b, 0x10, 0x11, 0x90, 0x9a, 0x9e, 0xee, 0x9a, 0x99, 0x5a, 0xf7, 0x17, 0x55, 0x3d, 0xb1, 0x27,
0xa7, 0x70, 0x41, 0x20, 0xf6, 0x80, 0xcc, 0x81, 0x2b, 0x12, 0x08, 0xc1, 0xfe, 0x03, 0x48, 0xfc,
0x05, 0xb9, 0x20, 0xcf, 0x11, 0x71, 0x28, 0x69, 0x9d, 0xdb, 0xdc, 0xe8, 0x63, 0x4e, 0xa8, 0xaa,
0xba, 0x7b, 0xba, 0x7b, 0x66, 0x11, 0xd2, 0xde, 0xa6, 0x7e, 0xbf, 0x5f, 0xbd, 0xf7, 0xfa, 0xf5,
0x7b, 0xaf, 0xaa, 0x07, 0x34, 0x7c, 0xda, 0xbe, 0xe5, 0x46, 0x61, 0x87, 0x76, 0x6f, 0x75, 0x22,
0xdf, 0x23, 0x4c, 0x2f, 0xfa, 0xcc, 0x49, 0x68, 0x14, 0xee, 0xc4, 0x2c, 0x4a, 0x22, 0x78, 0x51,
0x83, 0x1b, 0x37, 0x26, 0xd4, 0xc9, 0x20, 0x26, 0x5a, 0xb4, 0xb1, 0x5e, 0x22, 0x39, 0x7d, 0x9e,
0xc3, 0x1b, 0x25, 0x38, 0xee, 0xfb, 0x7e, 0xc4, 0x3c, 0xc2, 0x32, 0x6e, 0xbb, 0xc4, 0x3d, 0x23,
0x8c, 0xd3, 0x28, 0xa4, 0x61, 0x77, 0x4a, 0x04, 0x1b, 0xb8, 0xa4, 0x6c, 0xfb, 0x91, 0x7b, 0x58,
0x37, 0x05, 0xa5, 0xa0, 0xc3, 0x6f, 0xc9, 0x80, 0x78, 0x86, 0xbd, 0x91, 0x61, 0x6e, 0x14, 0x0f,
0x98, 0x13, 0x76, 0x49, 0x40, 0x92, 0x5e, 0xe4, 0x65, 0xec, 0x3c, 0x39, 0x4e, 0xf4, 0x4f, 0xf3,
0x3f, 0xb3, 0xe0, 0xfa, 0xbe, 0x7a, 0x9e, 0x3d, 0xf2, 0x8c, 0xba, 0xe4, 0x5e, 0x39, 0x02, 0xf8,
0x85, 0x01, 0xe6, 0x3d, 0x85, 0xdb, 0xd4, 0x43, 0xc6, 0x96, 0xb1, 0xbd, 0xd8, 0xfc, 0xdc, 0x78,
0x29, 0xf0, 0xcc, 0xbf, 0x05, 0xfe, 0x76, 0x97, 0x26, 0xbd, 0x7e, 0x7b, 0xc7, 0x8d, 0x82, 0x5b,
0x7c, 0x10, 0xba, 0x49, 0x8f, 0x86, 0xdd, 0xd2, 0x2f, 0x19, 0x82, 0x72, 0xe2, 0x46, 0xfe, 0x8e,
0xb6, 0x7e, 0x7f, 0xef, 0x4c, 0xe0, 0xb9, 0xfc, 0xf7, 0x48, 0xe0, 0x39, 0x2f, 0xfb, 0x9d, 0x0a,
0xbc, 0x74, 0x1c, 0xf8, 0x77, 0x4d, 0xea, 0xbd, 0xe7, 0x24, 0x09, 0x33, 0x47, 0xa7, 0x8d, 0x4b,
0xd9, 0xef, 0xf4, 0xb4, 0x51, 0xe8, 0x7e, 0x3d, 0x6c, 0x18, 0x27, 0xc3, 0x46, 0x61, 0xc3, 0xca,
0x19, 0x0f, 0xfe, 0xc5, 0x00, 0x4b, 0x34, 0x4c, 0x58, 0xe4, 0xf5, 0x5d, 0xe2, 0xd9, 0xed, 0x01,
0x9a, 0x55, 0x01, 0xbf, 0xf8, 0x5a, 0x01, 0x8f, 0x04, 0x5e, 0x1c, 0x5b, 0x6d, 0x0e, 0x52, 0x81,
0xaf, 0xe9, 0x40, 0x4b, 0x60, 0x11, 0xf2, 0xda, 0x04, 0x2a, 0x03, 0xb6, 0x2a, 0x16, 0xcc, 0x57,
0x18, 0x5c, 0xd6, 0x39, 0xaf, 0x66, 0xfb, 0x23, 0x30, 0x9b, 0x65, 0x79, 0xbe, 0xb9, 0x73, 0x26,
0xf0, 0xac, 0xf2, 0x3e, 0x4b, 0xbd, 0xff, 0x95, 0x9c, 0x93, 0x61, 0x63, 0xf6, 0xfe, 0x9e, 0x35,
0x4b, 0x3d, 0xf8, 0x23, 0x70, 0xc1, 0x77, 0xda, 0xc4, 0x57, 0xcf, 0x3d, 0xdf, 0xfc, 0xee, 0x48,
0x60, 0x0d, 0xa4, 0x02, 0x6f, 0xa9, 0xfd, 0x6a, 0xa5, 0x4d, 0x6c, 0x31, 0xc2, 0x13, 0x87, 0x25,
0x77, 0xcd, 0x8e, 0xe3, 0x73, 0x22, 0x4d, 0x82, 0x31, 0xfd, 0x62, 0xd8, 0x98, 0xb1, 0xf4, 0x66,
0xd8, 0x05, 0x2b, 0x1d, 0xea, 0x13, 0x3e, 0xe0, 0x09, 0x09, 0x6c, 0x59, 0x65, 0xe8, 0xdc, 0x96,
0xb1, 0xbd, 0xbc, 0x0b, 0x77, 0x3a, 0x7c, 0x67, 0xbf, 0xa0, 0x1e, 0x0f, 0x62, 0xd2, 0x7c, 0x77,
0x24, 0xf0, 0x72, 0xa7, 0x82, 0xa5, 0x02, 0x5f, 0x51, 0xde, 0xab, 0xb0, 0x69, 0xd5, 0x74, 0xf0,
0x03, 0x70, 0x3e, 0x76, 0x92, 0x1e, 0x3a, 0xaf, 0xc2, 0xdf, 0x1e, 0x09, 0xac, 0xd6, 0xa9, 0xc0,
0x2b, 0x6a, 0xbf, 0x5c, 0x14, 0xcf, 0x3f, 0x5f, 0xac, 0x2c, 0xa5, 0x82, 0x2d, 0x70, 0x5e, 0xc5,
0x76, 0x21, 0x8b, 0x4d, 0xb7, 0xcc, 0x8e, 0x4e, 0xb4, 0x8a, 0x4d, 0x59, 0x4c, 0x74, 0x44, 0xda,
0xa2, 0x5c, 0x8c, 0x2d, 0x16, 0x2b, 0x4b, 0xa9, 0xe0, 0xcf, 0xc0, 0x25, 0x5d, 0x5c, 0x1c, 0x5d,
0xdc, 0x3a, 0xb7, 0xbd, 0xb0, 0xfb, 0x56, 0xd5, 0xe8, 0x94, 0x8e, 0x69, 0x62, 0x59, 0x6b, 0x23,
0x81, 0xf3, 0x9d, 0xa9, 0xc0, 0x8b, 0xca, 0x95, 0x5e, 0x9b, 0x56, 0x4e, 0xc0, 0xdf, 0x1b, 0x60,
0x8d, 0x11, 0xee, 0x3a, 0xa1, 0x4d, 0xc3, 0x84, 0xb0, 0x67, 0x8e, 0x6f, 0x73, 0x74, 0x69, 0xcb,
0xd8, 0xbe, 0xd0, 0xec, 0x8e, 0x04, 0x5e, 0xd1, 0xe4, 0xfd, 0x8c, 0x3b, 0x48, 0x05, 0x7e, 0x47,
0x59, 0xaa, 0xe1, 0xd9, 0xeb, 0xf4, 0x48, 0xc7, 0xe9, 0xfb, 0xc9, 0x5d, 0xf3, 0xfd, 0xef, 0xdc,
0xbe, 0x6d, 0xbe, 0x16, 0xf8, 0x1c, 0x0d, 0x93, 0xd1, 0x69, 0xe3, 0xca, 0x34, 0xf9, 0xeb, 0xd3,
0xc6, 0x79, 0xa9, 0xb3, 0xea, 0x4e, 0xe0, 0x3f, 0x0c, 0x00, 0x3b, 0xdc, 0x3e, 0x72, 0x12, 0xb7,
0x47, 0x98, 0x4d, 0x42, 0xa7, 0xed, 0x13, 0x0f, 0xcd, 0x6d, 0x19, 0xdb, 0x73, 0xcd, 0xdf, 0x1a,
0x67, 0x02, 0xaf, 0xee, 0x1f, 0x3c, 0xd1, 0xec, 0x27, 0x9a, 0x1c, 0x09, 0xbc, 0xda, 0xe1, 0x55,
0x2c, 0x15, 0xf8, 0x5d, 0xfd, 0xce, 0x6b, 0x44, 0x3d, 0xda, 0x84, 0xf5, 0x55, 0xed, 0xad, 0x4f,
0x15, 0xca, 0x38, 0xa5, 0xe2, 0x64, 0xd8, 0x98, 0x70, 0x6b, 0x4d, 0x38, 0x85, 0x7f, 0xaf, 0x06,
0xef, 0x11, 0xdf, 0x19, 0xd8, 0x1c, 0xcd, 0xab, 0x9c, 0xfe, 0x46, 0x06, 0xbf, 0x52, 0x58, 0xd9,
0x93, 0xe4, 0x81, 0xcc, 0x73, 0x61, 0x46, 0x43, 0xa9, 0xc0, 0xdf, 0xaa, 0x86, 0xae, 0xf1, 0x7a,
0xe4, 0x77, 0x2a, 0x59, 0x9e, 0x26, 0x7e, 0x7d, 0xda, 0x98, 0xbd, 0x73, 0xfb, 0x64, 0xd8, 0xa8,
0x7b, 0xb5, 0xea, 0x3e, 0xe1, 0xcf, 0xc1, 0x22, 0xed, 0x86, 0x11, 0x23, 0x76, 0x4c, 0x58, 0xc0,
0x11, 0x50, 0xf9, 0xfe, 0x70, 0x24, 0xf0, 0x82, 0xc6, 0x5b, 0x12, 0x4e, 0x05, 0xbe, 0xaa, 0xe7,
0xc0, 0x18, 0x2b, 0xca, 0x77, 0xb5, 0x0e, 0x5a, 0xe5, 0xad, 0xf0, 0x97, 0x06, 0x58, 0x76, 0xfa,
0x49, 0x64, 0x87, 0x11, 0x0b, 0x1c, 0x9f, 0x3e, 0x27, 0x68, 0x41, 0x39, 0x79, 0x3a, 0x12, 0x78,
0x49, 0x32, 0x9f, 0xe6, 0x44, 0x91, 0x81, 0x0a, 0xfa, 0x55, 0x6f, 0x0e, 0x4e, 0xaa, 0xf2, 0xd7,
0x66, 0x55, 0xed, 0xc2, 0xa7, 0x60, 0x29, 0xa0, 0xa1, 0xed, 0x51, 0x7e, 0x68, 0x77, 0x18, 0x21,
0x68, 0x71, 0xcb, 0xd8, 0x5e, 0xd8, 0x5d, 0xcc, 0xdb, 0xea, 0x80, 0x3e, 0x27, 0xcd, 0xed, 0xac,
0x83, 0x16, 0x02, 0x1a, 0xee, 0x51, 0x7e, 0xb8, 0xcf, 0x88, 0x8c, 0x68, 0x4d, 0x45, 0x54, 0xc2,
0x4c, 0xab, 0xac, 0x80, 0x5d, 0x00, 0xc6, 0xe7, 0x28, 0x5a, 0x52, 0x86, 0x71, 0x6e, 0xf8, 0xc7,
0x05, 0x53, 0xed, 0xd6, 0xb7, 0x33, 0x5f, 0xa5, 0xad, 0xa9, 0xc0, 0xab, 0xca, 0xd5, 0x18, 0x32,
0xad, 0x12, 0x0f, 0x3f, 0x04, 0x97, 0xdc, 0x28, 0xa6, 0x84, 0x71, 0xb4, 0xac, 0x0a, 0xeb, 0x1b,
0xb2, 0xdd, 0x33, 0xa8, 0x98, 0xd4, 0xd9, 0x3a, 0x2f, 0x11, 0x2b, 0x17, 0xc0, 0x7f, 0x1a, 0xe0,
0xaa, 0x3c, 0xc1, 0x09, 0xb3, 0x03, 0xe7, 0xd8, 0x8e, 0x49, 0xe8, 0xd1, 0xb0, 0x6b, 0x1f, 0xd2,
0x36, 0x5a, 0x51, 0xe6, 0xfe, 0x20, 0xeb, 0xf4, 0x72, 0x4b, 0x49, 0x1e, 0x3a, 0xc7, 0x2d, 0x2d,
0x78, 0x40, 0x9b, 0x23, 0x81, 0x2f, 0xc7, 0x93, 0x70, 0x2a, 0xf0, 0x75, 0x3d, 0x1e, 0x27, 0xb9,
0x52, 0x85, 0x4e, 0xdd, 0x3a, 0x1d, 0x3e, 0x19, 0x36, 0xa6, 0xf9, 0xb7, 0xa6, 0x68, 0xdb, 0x32,
0x1d, 0x3d, 0x87, 0xf7, 0x64, 0x3a, 0x56, 0xc7, 0xe9, 0xc8, 0xa0, 0x22, 0x1d, 0xd9, 0x7a, 0x9c,
0x8e, 0x0c, 0x80, 0x1f, 0x83, 0x0b, 0xea, 0x2e, 0x83, 0xd6, 0xd4, 0xd8, 0x5e, 0xcb, 0xdf, 0x98,
0xf4, 0xff, 0x48, 0x12, 0x4d, 0x24, 0x8f, 0x31, 0xa5, 0x49, 0x05, 0x5e, 0x50, 0xd6, 0xd4, 0xca,
0xb4, 0x34, 0x0a, 0x1f, 0x80, 0xa5, 0xac, 0x77, 0x3c, 0xe2, 0x93, 0x84, 0x20, 0xa8, 0xea, 0xfa,
0x6d, 0x75, 0x72, 0x2b, 0x62, 0x4f, 0xe1, 0xa9, 0xc0, 0xb0, 0xd4, 0x3d, 0x1a, 0x34, 0xad, 0x8a,
0x06, 0x1e, 0x03, 0xa4, 0x46, 0x72, 0xcc, 0xa2, 0x2e, 0x23, 0x9c, 0x97, 0x67, 0xf3, 0x65, 0xf5,
0x7c, 0xf2, 0x58, 0x5d, 0x97, 0x9a, 0x56, 0x26, 0x29, 0x4f, 0xe8, 0x1b, 0xca, 0xc1, 0x54, 0xb6,
0x78, 0xf6, 0xe9, 0x9b, 0xe1, 0x01, 0x58, 0xce, 0xea, 0x22, 0x76, 0xfa, 0x9c, 0xd8, 0x1c, 0x5d,
0x51, 0xfe, 0x6e, 0xca, 0xe7, 0xd0, 0x4c, 0x4b, 0x12, 0x07, 0xc5, 0x73, 0x94, 0xc1, 0xc2, 0x7a,
0x45, 0x0a, 0x09, 0x58, 0x92, 0x55, 0x26, 0x93, 0xea, 0x53, 0x37, 0xe1, 0x68, 0x5d, 0xd9, 0xfc,
0x9e, 0xb4, 0x19, 0x38, 0xc7, 0xf7, 0x72, 0x3c, 0x15, 0x18, 0xeb, 0x06, 0x2b, 0x81, 0xa5, 0x66,
0xbf, 0x79, 0x27, 0x77, 0x20, 0x87, 0xda, 0xcd, 0x3b, 0x56, 0x65, 0x37, 0xf4, 0xc0, 0x15, 0x8f,
0x72, 0x39, 0x84, 0x6d, 0x1e, 0x3b, 0x8c, 0x13, 0x5b, 0x1d, 0xed, 0xe8, 0xaa, 0x7a, 0x13, 0xbb,
0x23, 0x81, 0x61, 0xc6, 0x1f, 0x28, 0x5a, 0x5d, 0x1a, 0x52, 0x81, 0x91, 0x3e, 0x1a, 0x27, 0x28,
0xd3, 0x9a, 0xa2, 0x2f, 0x7b, 0x49, 0x48, 0x10, 0xdb, 0x34, 0xf4, 0xc8, 0x31, 0xe1, 0xe8, 0xda,
0x84, 0x97, 0xc7, 0x24, 0x88, 0xef, 0x6b, 0xb6, 0xee, 0xa5, 0x44, 0x8d, 0xbd, 0x94, 0x40, 0xb8,
0x0b, 0x2e, 0xaa, 0x17, 0xe0, 0x21, 0xa4, 0xec, 0x6e, 0x8c, 0x04, 0xce, 0x90, 0xe2, 0x30, 0xd7,
0x4b, 0xd3, 0xca, 0x70, 0x98, 0x80, 0x6b, 0x47, 0xc4, 0x39, 0xb4, 0x65, 0x55, 0xdb, 0x49, 0x8f,
0x11, 0xde, 0x8b, 0x7c, 0xcf, 0x8e, 0xdd, 0x04, 0x5d, 0x57, 0x09, 0x97, 0x93, 0xfc, 0x8a, 0x94,
0x7c, 0xdf, 0xe1, 0xbd, 0xc7, 0xb9, 0xa0, 0xe5, 0x26, 0xa9, 0xc0, 0x1b, 0xca, 0xe4, 0x34, 0xb2,
0x78, 0xa9, 0x53, 0xb7, 0xc2, 0x7b, 0x60, 0x21, 0x70, 0xd8, 0x21, 0x61, 0x76, 0xe8, 0x04, 0x04,
0x6d, 0xa8, 0x6b, 0x93, 0x29, 0xc7, 0x99, 0x86, 0x3f, 0x75, 0x02, 0x52, 0x8c, 0xb3, 0x31, 0x64,
0x5a, 0x25, 0x1e, 0x0e, 0xc0, 0x86, 0xfc, 0x48, 0xb0, 0xa3, 0xa3, 0x90, 0x30, 0xde, 0xa3, 0xb1,
0xdd, 0x61, 0x51, 0x60, 0xc7, 0x0e, 0x23, 0x61, 0x82, 0x6e, 0xa8, 0x14, 0x7c, 0x30, 0x12, 0xf8,
0x9a, 0x54, 0x3d, 0xca, 0x45, 0xfb, 0x2c, 0x0a, 0x5a, 0x4a, 0x92, 0x0a, 0xfc, 0x66, 0x3e, 0xf1,
0xa6, 0xf1, 0xa6, 0xf5, 0x55, 0x3b, 0xe1, 0xaf, 0x0c, 0xb0, 0x16, 0x44, 0x9e, 0x9d, 0xd0, 0x80,
0xd8, 0x47, 0x34, 0xf4, 0xa2, 0x23, 0x9b, 0xa3, 0x37, 0x54, 0xc2, 0x7e, 0x7a, 0x26, 0xf0, 0x9a,
0xe5, 0x1c, 0x3d, 0x8c, 0xbc, 0xc7, 0x34, 0x20, 0x4f, 0x14, 0x2b, 0x8f, 0xeb, 0xe5, 0xa0, 0x82,
0x14, 0x97, 0xcb, 0x2a, 0x9c, 0x67, 0xee, 0x64, 0xd8, 0x98, 0xb4, 0x62, 0xd5, 0x6c, 0xc0, 0x17,
0x06, 0x58, 0xcf, 0xda, 0xc4, 0xed, 0x33, 0x19, 0x9b, 0x7d, 0xc4, 0x68, 0x42, 0x38, 0x7a, 0x53,
0x05, 0xf3, 0x43, 0x39, 0x7a, 0x75, 0xc1, 0x67, 0xfc, 0x13, 0x45, 0xa7, 0x02, 0x7f, 0xb3, 0xd4,
0x35, 0x15, 0xae, 0xd4, 0x3c, 0xbb, 0xa5, 0xde, 0x31, 0x76, 0xad, 0x69, 0x96, 0xe4, 0x10, 0xcb,
0x6b, 0xbb, 0x23, 0xbf, 0x48, 0xd0, 0xe6, 0x78, 0x88, 0x65, 0xc4, 0xbe, 0xc4, 0x8b, 0xe6, 0x2f,
0x83, 0xa6, 0x55, 0xd1, 0x40, 0x1f, 0xac, 0xaa, 0x2f, 0x45, 0x5b, 0xce, 0x02, 0x5b, 0xcf, 0x57,
0xac, 0xe6, 0xeb, 0xd5, 0x7c, 0xbe, 0x36, 0x25, 0x3f, 0x1e, 0xb2, 0xea, 0xda, 0xde, 0xae, 0x60,
0x45, 0x66, 0xab, 0xb0, 0x69, 0xd5, 0x74, 0xf0, 0x73, 0x03, 0xac, 0xa9, 0x12, 0x52, 0x1f, 0x9a,
0xb6, 0xfe, 0xd2, 0x44, 0x5b, 0xca, 0xdf, 0x65, 0xf9, 0x89, 0x70, 0x2f, 0x8a, 0x07, 0x96, 0xe4,
0x1e, 0x2a, 0xaa, 0xf9, 0x40, 0xde, 0xba, 0xdc, 0x2a, 0x98, 0x0a, 0xbc, 0x5d, 0x94, 0x51, 0x09,
0x2f, 0xa5, 0x91, 0x27, 0x4e, 0xe8, 0x39, 0xcc, 0x33, 0x5f, 0x9f, 0x36, 0xe6, 0xf2, 0x85, 0x55,
0x37, 0x04, 0xff, 0x2c, 0xc3, 0x71, 0xe4, 0x00, 0x25, 0x21, 0xa7, 0x09, 0x7d, 0x26, 0x33, 0x8a,
0xde, 0x52, 0xe9, 0x3c, 0x96, 0x57, 0xc0, 0x7b, 0x0e, 0x27, 0x07, 0x39, 0xb7, 0xaf, 0xae, 0x80,
0x6e, 0x15, 0x4a, 0x05, 0x5e, 0xd7, 0xc1, 0x54, 0x71, 0x79, 0xdd, 0x99, 0xd0, 0x4e, 0x42, 0xf2,
0xc6, 0x57, 0x73, 0x62, 0xd5, 0x34, 0x1c, 0xfe, 0xc9, 0x00, 0xab, 0x9d, 0xc8, 0xf7, 0xa3, 0x23,
0xfb, 0xb3, 0x7e, 0xe8, 0xca, 0xeb, 0x08, 0x47, 0xe6, 0x38, 0xca, 0x1f, 0xe4, 0xe0, 0xc7, 0x7c,
0x8f, 0x32, 0x2e, 0xa3, 0xfc, 0xac, 0x0a, 0x15, 0x51, 0xd6, 0x70, 0x15, 0x65, 0x5d, 0x3b, 0x09,
0xc9, 0x28, 0x6b, 0x4e, 0xac, 0x15, 0x1d, 0x51, 0x01, 0xc3, 0x43, 0x30, 0xcf, 0x88, 0xe3, 0xd9,
0x51, 0xe8, 0x0f, 0xd0, 0x5f, 0xf7, 0x55, 0x78, 0x0f, 0xcf, 0x04, 0x86, 0x7b, 0x24, 0x66, 0xc4,
0x75, 0x12, 0xe2, 0x59, 0xc4, 0xf1, 0x1e, 0x85, 0xfe, 0x60, 0x24, 0xb0, 0x71, 0xb3, 0xf8, 0x3a,
0x66, 0x91, 0xba, 0x09, 0xbe, 0x17, 0x05, 0x54, 0xce, 0xea, 0x64, 0xa0, 0xbe, 0x8e, 0x27, 0x50,
0x64, 0x58, 0x73, 0x2c, 0x33, 0x00, 0x7f, 0x01, 0xd6, 0x2a, 0xd7, 0x43, 0x35, 0x3f, 0xff, 0x26,
0x9d, 0x1a, 0xcd, 0x4f, 0xce, 0x04, 0x46, 0x63, 0xa7, 0x0f, 0xc7, 0x37, 0xbf, 0x96, 0x9b, 0xe4,
0xae, 0x37, 0xeb, 0x77, 0xc4, 0x96, 0x9b, 0x94, 0x22, 0x40, 0x86, 0xb5, 0x5c, 0x25, 0xe1, 0x4f,
0xc0, 0x25, 0x7d, 0x5e, 0x72, 0xf4, 0xc5, 0xbe, 0xea, 0xf5, 0x8f, 0xe4, 0xe0, 0x19, 0x3b, 0xd2,
0xf7, 0x20, 0x5e, 0x7d, 0xb8, 0x6c, 0x4b, 0xc9, 0x74, 0xd6, 0xe0, 0xc8, 0xb0, 0x72, 0x7b, 0xcd,
0x07, 0x2f, 0xbf, 0xdc, 0x9c, 0x19, 0x7e, 0xb9, 0x39, 0xf3, 0xf2, 0x6c, 0xd3, 0x18, 0x9e, 0x6d,
0x1a, 0xbf, 0x7b, 0xb5, 0x39, 0xf3, 0xc7, 0x57, 0x9b, 0xc6, 0xf0, 0xd5, 0xe6, 0xcc, 0xbf, 0x5e,
0x6d, 0xce, 0x3c, 0x7d, 0xe7, 0xff, 0xf8, 0x3f, 0x42, 0xb7, 0x6b, 0xfb, 0xa2, 0xfa, 0x5f, 0xe2,
0xfd, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xde, 0x81, 0xf6, 0x5e, 0xb5, 0x12, 0x00, 0x00,
0x3b, 0x10, 0x11, 0x90, 0x9a, 0x76, 0x77, 0xcd, 0x4c, 0xad, 0xfb, 0x8b, 0xaa, 0x9e, 0xd8, 0x93,
0x53, 0xb8, 0x20, 0x10, 0x7b, 0x40, 0xe6, 0xc0, 0x15, 0x09, 0x84, 0x60, 0xff, 0x01, 0x24, 0xfe,
0x82, 0x5c, 0x90, 0xe7, 0x84, 0x10, 0x87, 0x92, 0xd6, 0xbe, 0xcd, 0xb1, 0x8f, 0x39, 0xa1, 0xaa,
0xea, 0xee, 0xe9, 0xee, 0x99, 0x45, 0x48, 0x7b, 0x9b, 0xfa, 0xfd, 0x7e, 0xf5, 0xde, 0xeb, 0x57,
0xf5, 0x5e, 0xbf, 0x1e, 0xd0, 0xf0, 0xe9, 0xc1, 0x6d, 0x37, 0x0a, 0xdb, 0xb4, 0x73, 0xbb, 0x1d,
0xf9, 0x1e, 0x61, 0x7a, 0xd1, 0x63, 0x4e, 0x42, 0xa3, 0x70, 0x3b, 0x66, 0x51, 0x12, 0xc1, 0xcb,
0x1a, 0x5c, 0xbf, 0x39, 0xa6, 0x4e, 0xfa, 0x31, 0xd1, 0xa2, 0xf5, 0xb5, 0x12, 0xc9, 0xe9, 0x8b,
0x1c, 0x5e, 0x2f, 0xc1, 0x71, 0xcf, 0xf7, 0x23, 0xe6, 0x11, 0x96, 0x71, 0x5b, 0x25, 0xee, 0x39,
0x61, 0x9c, 0x46, 0x21, 0x0d, 0x3b, 0x13, 0x22, 0x58, 0xc7, 0x25, 0xe5, 0x81, 0x1f, 0xb9, 0x87,
0x75, 0x53, 0x50, 0x0a, 0xda, 0xfc, 0xb6, 0x0c, 0x88, 0x67, 0xd8, 0x1b, 0x19, 0xe6, 0x46, 0x71,
0x9f, 0x39, 0x61, 0x87, 0x04, 0x24, 0xe9, 0x46, 0x5e, 0xc6, 0xce, 0x92, 0xe3, 0x44, 0xff, 0x34,
0xff, 0x75, 0x01, 0xdc, 0xd8, 0x53, 0xcf, 0xb3, 0x4b, 0x9e, 0x53, 0x97, 0xdc, 0x2f, 0x47, 0x00,
0xbf, 0x30, 0xc0, 0xac, 0xa7, 0x70, 0x9b, 0x7a, 0xc8, 0xd8, 0x34, 0xb6, 0xe6, 0x9b, 0x9f, 0x1b,
0xaf, 0x04, 0x9e, 0xfa, 0x8f, 0xc0, 0xdf, 0xee, 0xd0, 0xa4, 0xdb, 0x3b, 0xd8, 0x76, 0xa3, 0xe0,
0x36, 0xef, 0x87, 0x6e, 0xd2, 0xa5, 0x61, 0xa7, 0xf4, 0x4b, 0x86, 0xa0, 0x9c, 0xb8, 0x91, 0xbf,
0xad, 0xad, 0x3f, 0xd8, 0x3d, 0x13, 0x78, 0x26, 0xff, 0x3d, 0x14, 0x78, 0xc6, 0xcb, 0x7e, 0xa7,
0x02, 0x2f, 0x1c, 0x07, 0xfe, 0x3d, 0x93, 0x7a, 0xef, 0x39, 0x49, 0xc2, 0xcc, 0xe1, 0x69, 0xe3,
0x4a, 0xf6, 0x3b, 0x3d, 0x6d, 0x14, 0xba, 0x5f, 0x0f, 0x1a, 0xc6, 0xc9, 0xa0, 0x51, 0xd8, 0xb0,
0x72, 0xc6, 0x83, 0x7f, 0x31, 0xc0, 0x02, 0x0d, 0x13, 0x16, 0x79, 0x3d, 0x97, 0x78, 0xf6, 0x41,
0x1f, 0x4d, 0xab, 0x80, 0x5f, 0x7e, 0xad, 0x80, 0x87, 0x02, 0xcf, 0x8f, 0xac, 0x36, 0xfb, 0xa9,
0xc0, 0xd7, 0x75, 0xa0, 0x25, 0xb0, 0x08, 0x79, 0x65, 0x0c, 0x95, 0x01, 0x5b, 0x15, 0x0b, 0xd0,
0x05, 0xab, 0x24, 0x74, 0x59, 0x3f, 0x96, 0x39, 0xb6, 0x63, 0x87, 0xf3, 0xa3, 0x88, 0x79, 0xe8,
0xc2, 0xa6, 0xb1, 0x35, 0xdb, 0xdc, 0x19, 0x0a, 0x0c, 0x47, 0x74, 0x2b, 0x63, 0x53, 0x81, 0x91,
0x72, 0x3b, 0x4e, 0x99, 0xd6, 0x04, 0xbd, 0x79, 0x8e, 0xc1, 0xaa, 0x3e, 0xd8, 0xea, 0x91, 0x7e,
0x04, 0xa6, 0xb3, 0xa3, 0x9c, 0x6d, 0x6e, 0x9f, 0x09, 0x3c, 0xad, 0x1e, 0x71, 0x9a, 0x7a, 0xff,
0xeb, 0x04, 0x4e, 0x06, 0x8d, 0xe9, 0x07, 0xbb, 0xd6, 0x34, 0xf5, 0xe0, 0x8f, 0xc0, 0x25, 0xdf,
0x39, 0x20, 0xbe, 0x4a, 0xee, 0x6c, 0xf3, 0xbb, 0x43, 0x81, 0x35, 0x90, 0x0a, 0xbc, 0xa9, 0xf6,
0xab, 0x95, 0x36, 0xb1, 0xc9, 0x08, 0x4f, 0x1c, 0x96, 0xdc, 0x33, 0xdb, 0x8e, 0xcf, 0x89, 0x34,
0x09, 0x46, 0xf4, 0xcb, 0x41, 0x63, 0xca, 0xd2, 0x9b, 0x61, 0x07, 0x2c, 0xb5, 0xa9, 0x4f, 0x78,
0x9f, 0x27, 0x24, 0xb0, 0xe5, 0x55, 0x56, 0xf9, 0x58, 0xdc, 0x81, 0xdb, 0x6d, 0xbe, 0xbd, 0x57,
0x50, 0x4f, 0xfa, 0x31, 0x69, 0xbe, 0x3b, 0x14, 0x78, 0xb1, 0x5d, 0xc1, 0x52, 0x81, 0xaf, 0x2a,
0xef, 0x55, 0xd8, 0xb4, 0x6a, 0x3a, 0xf8, 0x01, 0xb8, 0x18, 0x3b, 0x49, 0x17, 0x5d, 0x54, 0xe1,
0x6f, 0x0d, 0x05, 0x56, 0xeb, 0x54, 0xe0, 0x25, 0xb5, 0x5f, 0x2e, 0x8a, 0xe7, 0x9f, 0x2d, 0x56,
0x96, 0x52, 0xc1, 0x16, 0xb8, 0xa8, 0x62, 0xbb, 0x94, 0xc5, 0xa6, 0xeb, 0x72, 0x5b, 0x27, 0x5a,
0xc5, 0xa6, 0x2c, 0x26, 0x3a, 0x22, 0x6d, 0x51, 0x2e, 0x46, 0x16, 0x8b, 0x95, 0xa5, 0x54, 0xf0,
0x67, 0xe0, 0x8a, 0xbe, 0xc1, 0x1c, 0x5d, 0xde, 0xbc, 0xb0, 0x35, 0xb7, 0xf3, 0x56, 0xd5, 0xe8,
0x84, 0xb2, 0x6c, 0x62, 0x79, 0xa1, 0x87, 0x02, 0xe7, 0x3b, 0x53, 0x81, 0xe7, 0x95, 0x2b, 0xbd,
0x36, 0xad, 0x9c, 0x80, 0xbf, 0x37, 0xc0, 0x0a, 0x23, 0xdc, 0x75, 0x42, 0x9b, 0x86, 0x09, 0x61,
0xcf, 0x1d, 0xdf, 0xe6, 0xe8, 0xca, 0xa6, 0xb1, 0x75, 0xa9, 0xd9, 0x19, 0x0a, 0xbc, 0xa4, 0xc9,
0x07, 0x19, 0xb7, 0x9f, 0x0a, 0xfc, 0x8e, 0xb2, 0x54, 0xc3, 0xb3, 0xe3, 0xf4, 0x48, 0xdb, 0xe9,
0xf9, 0xc9, 0x3d, 0xf3, 0xfd, 0xef, 0xdc, 0xb9, 0x63, 0xbe, 0x16, 0xf8, 0x02, 0x0d, 0x93, 0xe1,
0x69, 0xe3, 0xea, 0x24, 0xf9, 0xeb, 0xd3, 0xc6, 0x45, 0xa9, 0xb3, 0xea, 0x4e, 0xe0, 0x3f, 0x0c,
0x00, 0xdb, 0xdc, 0x3e, 0x72, 0x12, 0xb7, 0x4b, 0x98, 0x4d, 0x42, 0xe7, 0xc0, 0x27, 0x1e, 0x9a,
0xd9, 0x34, 0xb6, 0x66, 0x9a, 0xbf, 0x35, 0xce, 0x04, 0x5e, 0xde, 0xdb, 0x7f, 0xaa, 0xd9, 0x4f,
0x34, 0x39, 0x14, 0x78, 0xb9, 0xcd, 0xab, 0x58, 0x2a, 0xf0, 0xbb, 0xfa, 0xcc, 0x6b, 0x44, 0x3d,
0xda, 0x84, 0xf5, 0xd4, 0xdd, 0x5b, 0x9b, 0x28, 0x94, 0x71, 0x4a, 0xc5, 0xc9, 0xa0, 0x31, 0xe6,
0xd6, 0x1a, 0x73, 0x0a, 0xff, 0x5e, 0x0d, 0xde, 0x23, 0xbe, 0xd3, 0xb7, 0x39, 0x9a, 0x55, 0x39,
0xfd, 0x8d, 0x0c, 0x7e, 0xa9, 0xb0, 0xb2, 0x2b, 0xc9, 0x7d, 0x99, 0xe7, 0xc2, 0x8c, 0x86, 0x52,
0x81, 0xbf, 0x55, 0x0d, 0x5d, 0xe3, 0xf5, 0xc8, 0xef, 0x56, 0xb2, 0x3c, 0x49, 0xfc, 0xfa, 0xb4,
0x31, 0x7d, 0xf7, 0xce, 0xc9, 0xa0, 0x51, 0xf7, 0x6a, 0xd5, 0x7d, 0xc2, 0x9f, 0x83, 0x79, 0xda,
0x09, 0x23, 0x46, 0xec, 0x98, 0xb0, 0x80, 0x23, 0xa0, 0xf2, 0xfd, 0xe1, 0x50, 0xe0, 0x39, 0x8d,
0xb7, 0x24, 0x9c, 0x0a, 0x7c, 0x4d, 0xf7, 0x81, 0x11, 0x56, 0x5c, 0xdf, 0xe5, 0x3a, 0x68, 0x95,
0xb7, 0xc2, 0x5f, 0x1a, 0x60, 0xd1, 0xe9, 0x25, 0x91, 0x1d, 0x46, 0x2c, 0x70, 0x7c, 0xfa, 0x82,
0xa0, 0x39, 0xe5, 0xe4, 0xd9, 0x50, 0xe0, 0x05, 0xc9, 0x7c, 0x9a, 0x13, 0x45, 0x06, 0x2a, 0xe8,
0x57, 0x9d, 0x1c, 0x1c, 0x57, 0xe5, 0xc7, 0x66, 0x55, 0xed, 0xc2, 0x67, 0x60, 0x21, 0xa0, 0xa1,
0xed, 0x51, 0x7e, 0x68, 0xb7, 0x19, 0x21, 0x68, 0x7e, 0xd3, 0xd8, 0x9a, 0xdb, 0x99, 0xcf, 0xcb,
0x6a, 0x9f, 0xbe, 0x20, 0xcd, 0xad, 0xac, 0x82, 0xe6, 0x02, 0x1a, 0xee, 0x52, 0x7e, 0xb8, 0xc7,
0x88, 0x8c, 0x68, 0x45, 0x45, 0x54, 0xc2, 0x4c, 0xab, 0xac, 0x80, 0x1d, 0x00, 0x46, 0x2f, 0x6b,
0xb4, 0xa0, 0x0c, 0xe3, 0xdc, 0xf0, 0x8f, 0x0b, 0xa6, 0x5a, 0xad, 0x6f, 0x67, 0xbe, 0x4a, 0x5b,
0x53, 0x81, 0x97, 0x95, 0xab, 0x11, 0x64, 0x5a, 0x25, 0x1e, 0x7e, 0x08, 0xae, 0xb8, 0x51, 0x4c,
0x09, 0xe3, 0x68, 0x51, 0x5d, 0xac, 0x6f, 0xc8, 0x72, 0xcf, 0xa0, 0xa2, 0x53, 0x67, 0xeb, 0xfc,
0x8a, 0x58, 0xb9, 0x00, 0xfe, 0xd3, 0x00, 0xd7, 0xe4, 0x98, 0x40, 0x98, 0x1d, 0x38, 0xc7, 0x76,
0x4c, 0x42, 0x8f, 0x86, 0x1d, 0xfb, 0x90, 0x1e, 0xa0, 0x25, 0x65, 0xee, 0x0f, 0xf2, 0x9e, 0xae,
0xb6, 0x94, 0xe4, 0x91, 0x73, 0xdc, 0xd2, 0x82, 0x87, 0xb4, 0x39, 0x14, 0x78, 0x35, 0x1e, 0x87,
0x53, 0x81, 0x6f, 0xe8, 0xf6, 0x38, 0xce, 0x95, 0x6e, 0xe8, 0xc4, 0xad, 0x93, 0xe1, 0x93, 0x41,
0x63, 0x92, 0x7f, 0x6b, 0x82, 0xf6, 0x40, 0xa6, 0xa3, 0xeb, 0xf0, 0xae, 0x4c, 0xc7, 0xf2, 0x28,
0x1d, 0x19, 0x54, 0xa4, 0x23, 0x5b, 0x8f, 0xd2, 0x91, 0x01, 0xf0, 0x63, 0x70, 0x49, 0x0d, 0x4c,
0x68, 0x45, 0xb5, 0xed, 0x95, 0xfc, 0xc4, 0xa4, 0xff, 0xc7, 0x92, 0x68, 0x22, 0xf9, 0x1a, 0x53,
0x9a, 0x54, 0xe0, 0x39, 0x65, 0x4d, 0xad, 0x4c, 0x4b, 0xa3, 0xf0, 0x21, 0x58, 0xc8, 0x6a, 0xc7,
0x23, 0x3e, 0x49, 0x08, 0x82, 0xea, 0x5e, 0xbf, 0xad, 0xc6, 0x03, 0x45, 0xec, 0x2a, 0x3c, 0x15,
0x18, 0x96, 0xaa, 0x47, 0x83, 0xa6, 0x55, 0xd1, 0xc0, 0x63, 0x80, 0x54, 0x4b, 0x8e, 0x59, 0xd4,
0x61, 0x84, 0xf3, 0x72, 0x6f, 0x5e, 0x55, 0xcf, 0x27, 0x5f, 0xab, 0x6b, 0x52, 0xd3, 0xca, 0x24,
0xe5, 0x0e, 0x7d, 0x53, 0x39, 0x98, 0xc8, 0x16, 0xcf, 0x3e, 0x79, 0x33, 0xdc, 0x07, 0x8b, 0xd9,
0xbd, 0x88, 0x9d, 0x1e, 0x27, 0x36, 0x47, 0x57, 0x95, 0xbf, 0x5b, 0xf2, 0x39, 0x34, 0xd3, 0x92,
0xc4, 0x7e, 0xf1, 0x1c, 0x65, 0xb0, 0xb0, 0x5e, 0x91, 0x42, 0x02, 0x16, 0xe4, 0x2d, 0x93, 0x49,
0xf5, 0xa9, 0x9b, 0x70, 0xb4, 0xa6, 0x6c, 0x7e, 0x4f, 0xda, 0x0c, 0x9c, 0xe3, 0xfb, 0x39, 0x9e,
0x0a, 0x8c, 0x75, 0x81, 0x95, 0xc0, 0x52, 0xb1, 0xdf, 0xba, 0x9b, 0x3b, 0x90, 0x4d, 0xed, 0xd6,
0x5d, 0xab, 0xb2, 0x1b, 0x7a, 0xe0, 0xaa, 0x47, 0xb9, 0x6c, 0xc2, 0x36, 0x8f, 0x1d, 0xc6, 0x89,
0xad, 0x5e, 0xed, 0xe8, 0x9a, 0x3a, 0x09, 0x35, 0x37, 0x65, 0xfc, 0xbe, 0xa2, 0xd5, 0xd0, 0x50,
0xcc, 0x4d, 0xe3, 0x94, 0x69, 0x4d, 0xd0, 0x97, 0xbd, 0x24, 0x24, 0x88, 0x6d, 0x1a, 0x7a, 0xe4,
0x98, 0x70, 0x74, 0x7d, 0xcc, 0xcb, 0x13, 0x12, 0xc4, 0x0f, 0x34, 0x5b, 0xf7, 0x52, 0xa2, 0x46,
0x5e, 0x4a, 0x20, 0xdc, 0x01, 0x97, 0xd5, 0x01, 0x78, 0x08, 0x29, 0xbb, 0xeb, 0x43, 0x81, 0x33,
0xa4, 0x78, 0x99, 0xeb, 0xa5, 0x69, 0x65, 0x38, 0x4c, 0xc0, 0xf5, 0x23, 0xe2, 0x1c, 0xda, 0xf2,
0x56, 0xdb, 0x49, 0x97, 0x11, 0xde, 0x8d, 0x7c, 0xcf, 0x8e, 0xdd, 0x04, 0xdd, 0x50, 0x09, 0x97,
0x9d, 0xfc, 0xaa, 0x94, 0x7c, 0xdf, 0xe1, 0xdd, 0x27, 0xb9, 0xa0, 0xe5, 0x26, 0xa9, 0xc0, 0xeb,
0xca, 0xe4, 0x24, 0xb2, 0x38, 0xd4, 0x89, 0x5b, 0xe1, 0x7d, 0x30, 0x17, 0x38, 0xec, 0x90, 0x30,
0x3b, 0x74, 0x02, 0x82, 0xd6, 0xd5, 0xd8, 0x64, 0xca, 0x76, 0xa6, 0xe1, 0x4f, 0x9d, 0x80, 0x14,
0xed, 0x6c, 0x04, 0x99, 0x56, 0x89, 0x87, 0x7d, 0xb0, 0x2e, 0xbf, 0x44, 0xec, 0xe8, 0x28, 0x24,
0x8c, 0x77, 0x69, 0x6c, 0xb7, 0x59, 0x14, 0xd8, 0xb1, 0xc3, 0x48, 0x98, 0xa0, 0x9b, 0x2a, 0x05,
0x1f, 0x0c, 0x05, 0xbe, 0x2e, 0x55, 0x8f, 0x73, 0xd1, 0x1e, 0x8b, 0x82, 0x96, 0x92, 0xa4, 0x02,
0xbf, 0x99, 0x77, 0xbc, 0x49, 0xbc, 0x69, 0x7d, 0xd5, 0x4e, 0xf8, 0x2b, 0x03, 0xac, 0x04, 0x91,
0x67, 0x27, 0x34, 0x20, 0xf6, 0x11, 0x0d, 0xbd, 0xe8, 0xc8, 0xe6, 0xe8, 0x0d, 0x95, 0xb0, 0x9f,
0x9e, 0x09, 0xbc, 0x62, 0x39, 0x47, 0x8f, 0x22, 0xef, 0x09, 0x0d, 0xc8, 0x53, 0xc5, 0xca, 0xd7,
0xf5, 0x62, 0x50, 0x41, 0x8a, 0xe1, 0xb2, 0x0a, 0xe7, 0x99, 0x3b, 0x19, 0x34, 0xc6, 0xad, 0x58,
0x35, 0x1b, 0xf0, 0xa5, 0x01, 0xd6, 0xb2, 0x32, 0x71, 0x7b, 0x4c, 0xc6, 0x66, 0x1f, 0x31, 0x9a,
0x10, 0x8e, 0xde, 0x54, 0xc1, 0xfc, 0x50, 0xb6, 0x5e, 0x7d, 0xe1, 0x33, 0xfe, 0xa9, 0xa2, 0x53,
0x81, 0xbf, 0x59, 0xaa, 0x9a, 0x0a, 0x57, 0x2a, 0x9e, 0x9d, 0x52, 0xed, 0x18, 0x3b, 0xd6, 0x24,
0x4b, 0xb2, 0x89, 0xe5, 0x77, 0xbb, 0x2d, 0x3f, 0x7b, 0xd0, 0xc6, 0xa8, 0x89, 0x65, 0xc4, 0x9e,
0xc4, 0x8b, 0xe2, 0x2f, 0x83, 0xa6, 0x55, 0xd1, 0x40, 0x1f, 0x2c, 0xab, 0xcf, 0x51, 0x5b, 0xf6,
0x02, 0x5b, 0xf7, 0x57, 0xac, 0xfa, 0xeb, 0xb5, 0xbc, 0xbf, 0x36, 0x25, 0x3f, 0x6a, 0xb2, 0x6a,
0x6c, 0x3f, 0xa8, 0x60, 0x45, 0x66, 0xab, 0xb0, 0x69, 0xd5, 0x74, 0xf0, 0x73, 0x03, 0xac, 0xa8,
0x2b, 0xa4, 0xbe, 0x66, 0x6d, 0xfd, 0x39, 0x8b, 0x36, 0x95, 0xbf, 0x55, 0xf9, 0x89, 0x70, 0x3f,
0x8a, 0xfb, 0x96, 0xe4, 0x1e, 0x29, 0xaa, 0xf9, 0x50, 0x4e, 0x5d, 0x6e, 0x15, 0x4c, 0x05, 0xde,
0x2a, 0xae, 0x51, 0x09, 0x2f, 0xa5, 0x91, 0x27, 0x4e, 0xe8, 0x39, 0xcc, 0x33, 0x5f, 0x9f, 0x36,
0x66, 0xf2, 0x85, 0x55, 0x37, 0x04, 0xff, 0x2c, 0xc3, 0x71, 0x64, 0x03, 0x25, 0x21, 0xa7, 0x09,
0x7d, 0x2e, 0x33, 0x8a, 0xde, 0x52, 0xe9, 0x3c, 0x96, 0x23, 0xe0, 0x7d, 0x87, 0x93, 0xfd, 0x9c,
0xdb, 0x53, 0x23, 0xa0, 0x5b, 0x85, 0x52, 0x81, 0xd7, 0x74, 0x30, 0x55, 0x5c, 0x8e, 0x3b, 0x63,
0xda, 0x71, 0x48, 0x4e, 0x7c, 0x35, 0x27, 0x56, 0x4d, 0xc3, 0xe1, 0x9f, 0x0c, 0xb0, 0xdc, 0x8e,
0x7c, 0x3f, 0x3a, 0xb2, 0x3f, 0xeb, 0x85, 0xae, 0x1c, 0x47, 0x38, 0x32, 0x47, 0x51, 0xfe, 0x20,
0x07, 0x3f, 0xe6, 0xbb, 0x94, 0x71, 0x19, 0xe5, 0x67, 0x55, 0xa8, 0x88, 0xb2, 0x86, 0xab, 0x28,
0xeb, 0xda, 0x71, 0x48, 0x46, 0x59, 0x73, 0x62, 0x2d, 0xe9, 0x88, 0x0a, 0x18, 0x1e, 0x82, 0x59,
0x46, 0x1c, 0xcf, 0x8e, 0x42, 0xbf, 0x8f, 0xfe, 0xba, 0xa7, 0xc2, 0x7b, 0x74, 0x26, 0x30, 0xdc,
0x25, 0x31, 0x23, 0xae, 0x93, 0x10, 0xcf, 0x22, 0x8e, 0xf7, 0x38, 0xf4, 0xfb, 0x43, 0x81, 0x8d,
0x5b, 0xc5, 0x27, 0x38, 0x8b, 0xd4, 0x24, 0xf8, 0x5e, 0x14, 0x50, 0xd9, 0xab, 0x93, 0xbe, 0xfa,
0x04, 0x1f, 0x43, 0x91, 0x61, 0xcd, 0xb0, 0xcc, 0x00, 0xfc, 0x05, 0x58, 0xa9, 0x8c, 0x87, 0xaa,
0x7f, 0xfe, 0x4d, 0x3a, 0x35, 0x9a, 0x9f, 0x9c, 0x09, 0x8c, 0x46, 0x4e, 0x1f, 0x8d, 0x26, 0xbf,
0x96, 0x9b, 0xe4, 0xae, 0x37, 0xea, 0x33, 0x62, 0xcb, 0x4d, 0x4a, 0x11, 0x20, 0xc3, 0x5a, 0xac,
0x92, 0xf0, 0x27, 0xe0, 0x8a, 0x7e, 0x5f, 0x72, 0xf4, 0xc5, 0x9e, 0xaa, 0xf5, 0x8f, 0x64, 0xe3,
0x19, 0x39, 0xd2, 0x73, 0x10, 0xaf, 0x3e, 0x5c, 0xb6, 0xa5, 0x64, 0x3a, 0x2b, 0x70, 0x64, 0x58,
0xb9, 0xbd, 0xe6, 0xc3, 0x57, 0x5f, 0x6e, 0x4c, 0x0d, 0xbe, 0xdc, 0x98, 0x7a, 0x75, 0xb6, 0x61,
0x0c, 0xce, 0x36, 0x8c, 0xdf, 0x9d, 0x6f, 0x4c, 0xfd, 0xf1, 0x7c, 0xc3, 0x18, 0x9c, 0x6f, 0x4c,
0xfd, 0xfb, 0x7c, 0x63, 0xea, 0xd9, 0x3b, 0xff, 0xc7, 0x9f, 0x1e, 0xba, 0x5c, 0x0f, 0x2e, 0xab,
0x3f, 0x3f, 0xde, 0xff, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe5, 0x04, 0x99, 0x1a, 0x13,
0x00, 0x00,
}
func (m *FolderDeviceConfiguration) Marshal() (dAtA []byte, err error) {
@ -295,6 +299,13 @@ func (m *FolderDeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, erro
_ = i
var l int
_ = l
if len(m.EncryptionPassword) > 0 {
i -= len(m.EncryptionPassword)
copy(dAtA[i:], m.EncryptionPassword)
i = encodeVarintFolderconfiguration(dAtA, i, uint64(len(m.EncryptionPassword)))
i--
dAtA[i] = 0x1a
}
{
size := m.IntroducedBy.ProtoSize()
i -= size
@ -685,6 +696,10 @@ func (m *FolderDeviceConfiguration) ProtoSize() (n int) {
n += 1 + l + sovFolderconfiguration(uint64(l))
l = m.IntroducedBy.ProtoSize()
n += 1 + l + sovFolderconfiguration(uint64(l))
l = len(m.EncryptionPassword)
if l > 0 {
n += 1 + l + sovFolderconfiguration(uint64(l))
}
return n
}
@ -914,6 +929,38 @@ func (m *FolderDeviceConfiguration) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field EncryptionPassword", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowFolderconfiguration
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthFolderconfiguration
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthFolderconfiguration
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.EncryptionPassword = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipFolderconfiguration(dAtA[iNdEx:])

View File

@ -14,6 +14,8 @@ func (t FolderType) String() string {
return "sendonly"
case FolderTypeReceiveOnly:
return "receiveonly"
case FolderTypeReceiveEncrypted:
return "receiveencrypted"
default:
return "unknown"
}
@ -31,6 +33,8 @@ func (t *FolderType) UnmarshalText(bs []byte) error {
*t = FolderTypeSendOnly
case "receiveonly":
*t = FolderTypeReceiveOnly
case "receiveencrypted":
*t = FolderTypeReceiveEncrypted
default:
*t = FolderTypeSendReceive
}

View File

@ -27,18 +27,21 @@ const (
FolderTypeSendReceive FolderType = 0
FolderTypeSendOnly FolderType = 1
FolderTypeReceiveOnly FolderType = 2
FolderTypeReceiveEncrypted FolderType = 3
)
var FolderType_name = map[int32]string{
0: "FOLDER_TYPE_SEND_RECEIVE",
1: "FOLDER_TYPE_SEND_ONLY",
2: "FOLDER_TYPE_RECEIVE_ONLY",
3: "FOLDER_TYPE_RECEIVE_ENCRYPTED",
}
var FolderType_value = map[string]int32{
"FOLDER_TYPE_SEND_RECEIVE": 0,
"FOLDER_TYPE_SEND_ONLY": 1,
"FOLDER_TYPE_RECEIVE_ONLY": 2,
"FOLDER_TYPE_RECEIVE_ENCRYPTED": 3,
}
func (FolderType) EnumDescriptor() ([]byte, []int) {
@ -52,21 +55,23 @@ func init() {
func init() { proto.RegisterFile("lib/config/foldertype.proto", fileDescriptor_ea6ddb20c0633575) }
var fileDescriptor_ea6ddb20c0633575 = []byte{
// 254 bytes of a gzipped FileDescriptorProto
// 287 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xce, 0xc9, 0x4c, 0xd2,
0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x4f, 0xcb, 0xcf, 0x49, 0x49, 0x2d, 0x2a, 0xa9, 0x2c,
0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0x48, 0x48, 0x29, 0x17, 0xa5, 0x16,
0xe4, 0x17, 0xeb, 0x83, 0x05, 0x93, 0x4a, 0xd3, 0xf4, 0xd3, 0xf3, 0xd3, 0xf3, 0xc1, 0x1c, 0x30,
0x0b, 0xa2, 0x58, 0x6b, 0x3b, 0x23, 0x17, 0x97, 0x1b, 0xd8, 0x84, 0x90, 0xca, 0x82, 0x54, 0x21,
0x0b, 0xa2, 0x58, 0xeb, 0x17, 0x23, 0x17, 0x97, 0x1b, 0xd8, 0x84, 0x90, 0xca, 0x82, 0x54, 0x21,
0x73, 0x2e, 0x09, 0x37, 0x7f, 0x1f, 0x17, 0xd7, 0xa0, 0xf8, 0x90, 0xc8, 0x00, 0xd7, 0xf8, 0x60,
0x57, 0x3f, 0x97, 0xf8, 0x20, 0x57, 0x67, 0x57, 0xcf, 0x30, 0x57, 0x01, 0x06, 0x29, 0xc9, 0xae,
0xb9, 0x0a, 0xa2, 0x08, 0xd5, 0xc1, 0xa9, 0x79, 0x29, 0x41, 0xa9, 0xc9, 0xa9, 0x99, 0x65, 0xa9,
0x42, 0x86, 0x5c, 0xa2, 0x18, 0x1a, 0xfd, 0xfd, 0x7c, 0x22, 0x05, 0x18, 0xa5, 0xc4, 0xba, 0xe6,
0x2a, 0x08, 0xa1, 0xea, 0xf2, 0xcf, 0xcb, 0xa9, 0x44, 0xb7, 0x0b, 0x6a, 0x0d, 0x44, 0x17, 0x13,
0xba, 0x5d, 0x50, 0x7b, 0x40, 0x1a, 0xa5, 0x58, 0x56, 0x2c, 0x91, 0x63, 0x70, 0xf2, 0x3e, 0xf1,
0x50, 0x8e, 0xe1, 0xc2, 0x43, 0x39, 0x86, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x9c,
0xf0, 0x58, 0x8e, 0x61, 0xc1, 0x63, 0x39, 0xc6, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63,
0x88, 0xd2, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x2f, 0xae, 0xcc,
0x4b, 0x2e, 0xc9, 0xc8, 0xcc, 0x4b, 0x47, 0x62, 0x21, 0x02, 0x31, 0x89, 0x0d, 0x1c, 0x1a, 0xc6,
0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x36, 0x1f, 0xe5, 0x1d, 0x59, 0x01, 0x00, 0x00,
0xba, 0x5d, 0x50, 0x7b, 0xc0, 0x1a, 0x1d, 0xb9, 0x64, 0xb1, 0x69, 0x74, 0xf5, 0x73, 0x0e, 0x8a,
0x0c, 0x08, 0x71, 0x75, 0x11, 0x60, 0x96, 0x92, 0xeb, 0x9a, 0xab, 0x20, 0x85, 0xa1, 0xdb, 0x35,
0x2f, 0xb9, 0xa8, 0xb2, 0xa0, 0x24, 0x35, 0x45, 0x8a, 0x65, 0xc5, 0x12, 0x39, 0x06, 0x27, 0xef,
0x13, 0x0f, 0xe5, 0x18, 0x2e, 0x3c, 0x94, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39,
0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x16, 0x3c, 0x96, 0x63, 0xbc, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63,
0x39, 0x86, 0x28, 0xcd, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xe2,
0xca, 0xbc, 0xe4, 0x92, 0x8c, 0xcc, 0xbc, 0x74, 0x24, 0x16, 0x22, 0x1e, 0x92, 0xd8, 0xc0, 0x01,
0x6a, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc9, 0x87, 0xbe, 0x2d, 0x9c, 0x01, 0x00, 0x00,
}

View File

@ -147,3 +147,13 @@ func (opts OptionsConfiguration) MaxConcurrentIncomingRequestKiB() int {
func (opts OptionsConfiguration) AutoUpgradeEnabled() bool {
return opts.AutoUpgradeIntervalH > 0
}
func (opts OptionsConfiguration) FeatureFlag(name string) bool {
for _, flag := range opts.FeatureFlags {
if flag == name {
return true
}
}
return false
}

View File

@ -73,6 +73,7 @@ type OptionsConfiguration struct {
RawMaxCIRequestKiB int `protobuf:"varint,47,opt,name=max_concurrent_incoming_request_kib,json=maxConcurrentIncomingRequestKib,proto3,casttype=int" json:"maxConcurrentIncomingRequestKiB" xml:"maxConcurrentIncomingRequestKiB"`
AnnounceLANAddresses bool `protobuf:"varint,48,opt,name=announce_lan_addresses,json=announceLanAddresses,proto3" json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
SendFullIndexOnUpgrade bool `protobuf:"varint,49,opt,name=send_full_index_on_upgrade,json=sendFullIndexOnUpgrade,proto3" json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
FeatureFlags []string `protobuf:"bytes,50,rep,name=feature_flags,json=featureFlags,proto3" json:"featureFlags" xml:"featureFlag"`
// Legacy deprecated
DeprecatedUPnPEnabled bool `protobuf:"varint,9000,opt,name=upnp_enabled,json=upnpEnabled,proto3" json:"-" xml:"upnpEnabled,omitempty"` // Deprecated: Do not use.
DeprecatedUPnPLeaseM int `protobuf:"varint,9001,opt,name=upnp_lease_m,json=upnpLeaseM,proto3,casttype=int" json:"-" xml:"upnpLeaseMinutes,omitempty"` // Deprecated: Do not use.
@ -125,201 +126,204 @@ func init() {
}
var fileDescriptor_d09882599506ca03 = []byte{
// 3103 bytes of a gzipped FileDescriptorProto
// 3138 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x5a, 0x5b, 0x6c, 0x1d, 0x47,
0x19, 0xce, 0x26, 0x4d, 0xda, 0x6c, 0x1c, 0x27, 0x5e, 0x3b, 0xf6, 0x36, 0x49, 0xbd, 0xee, 0xc9,
0x49, 0xeb, 0xde, 0xe2, 0x4b, 0xda, 0x10, 0x2c, 0x21, 0xf0, 0xa5, 0xa6, 0x26, 0xb6, 0x63, 0x8d,
0x6d, 0x15, 0x15, 0xa1, 0xd5, 0x9c, 0x3d, 0x73, 0xec, 0xc5, 0x7b, 0x66, 0x4f, 0x76, 0x67, 0x7d,
0x29, 0xa8, 0x54, 0x45, 0x5c, 0xde, 0x00, 0x8b, 0x8b, 0x04, 0x08, 0x15, 0x01, 0x12, 0xa5, 0x14,
0x21, 0x21, 0x21, 0xc1, 0x0b, 0x08, 0x09, 0xa9, 0x82, 0x07, 0xfb, 0x11, 0x89, 0xb2, 0xa8, 0x4e,
0x9f, 0xce, 0x03, 0x0f, 0xe7, 0xd1, 0xbc, 0xa0, 0x7f, 0xf6, 0x36, 0xbb, 0x3b, 0xa7, 0xc9, 0xdb,
0xd9, 0xff, 0xfb, 0xe7, 0x9f, 0xef, 0x9f, 0xcb, 0x3f, 0xff, 0x3f, 0x73, 0xd4, 0xeb, 0x8e, 0x5d,
0x1b, 0xb3, 0x5c, 0xda, 0xb0, 0x37, 0xc6, 0xdc, 0x16, 0xb3, 0x5d, 0xea, 0x47, 0x5f, 0x81, 0x87,
0xe1, 0xeb, 0x46, 0xcb, 0x73, 0x99, 0xab, 0x9d, 0x89, 0x84, 0x97, 0x87, 0x04, 0x75, 0x16, 0x50,
0x9b, 0x6e, 0x44, 0x0a, 0x97, 0x2f, 0x09, 0x80, 0x6f, 0xbf, 0x4e, 0x62, 0xf1, 0x59, 0xb2, 0xcb,
0xa2, 0x9f, 0x95, 0x9f, 0xcc, 0xab, 0x03, 0x77, 0xa3, 0x1e, 0x66, 0xc5, 0x1e, 0xb4, 0x9f, 0x2a,
0xea, 0x45, 0xc7, 0xf6, 0x19, 0xa1, 0x26, 0xae, 0xd7, 0x3d, 0xe2, 0xfb, 0xc4, 0xd7, 0x95, 0x91,
0x53, 0xa3, 0x67, 0x67, 0xfc, 0xa3, 0xd0, 0xd0, 0x10, 0xde, 0x59, 0xe4, 0xf0, 0x74, 0x82, 0xb6,
0x43, 0xe3, 0x82, 0x93, 0x17, 0x75, 0x42, 0xe3, 0xfa, 0x6e, 0xd3, 0x99, 0xaa, 0xe4, 0xe4, 0x95,
0x91, 0x3a, 0x69, 0xe0, 0xc0, 0x61, 0x53, 0x95, 0xf8, 0x47, 0xe5, 0xf8, 0xa0, 0xfa, 0x68, 0xfc,
0x7b, 0xff, 0xb0, 0x2a, 0x31, 0x8e, 0x8a, 0xa6, 0xb5, 0xff, 0x2a, 0xaa, 0xbe, 0xe1, 0xb8, 0x35,
0xec, 0x98, 0x75, 0xdb, 0xb7, 0xdc, 0x6d, 0xe2, 0xed, 0x99, 0x3e, 0xf1, 0xb6, 0x89, 0xe7, 0xeb,
0x27, 0x39, 0xd1, 0xdf, 0x2b, 0x47, 0xa1, 0xd1, 0x8f, 0xf0, 0xce, 0x67, 0xb9, 0xde, 0x34, 0xa5,
0xab, 0x11, 0xde, 0x0e, 0x8d, 0x4b, 0x1b, 0x89, 0xcc, 0x0d, 0xa8, 0x45, 0x62, 0xa0, 0x13, 0x1a,
0xcf, 0x73, 0xc2, 0x32, 0x54, 0xc2, 0xbb, 0x7d, 0x50, 0x1d, 0x90, 0xa9, 0x76, 0x0e, 0xaa, 0xf2,
0x0e, 0xf2, 0x8e, 0xca, 0xb8, 0xa1, 0xc1, 0xa8, 0xe1, 0x5c, 0xe2, 0x54, 0x2c, 0xd7, 0x3e, 0x92,
0x39, 0x4c, 0x28, 0xae, 0x39, 0xa4, 0xae, 0x9f, 0x1a, 0x51, 0x46, 0x1f, 0x9b, 0x79, 0x07, 0x1c,
0xbe, 0x98, 0x5a, 0x7c, 0x39, 0x02, 0xcb, 0xde, 0xc6, 0x40, 0x27, 0x34, 0x9e, 0x95, 0x78, 0x1b,
0xa3, 0x82, 0xbb, 0xcc, 0x0b, 0x08, 0xf8, 0xda, 0xc5, 0x4c, 0x37, 0xe0, 0xf8, 0xa0, 0xfa, 0x08,
0x34, 0xdd, 0x3f, 0xac, 0x96, 0x48, 0x95, 0xdc, 0x8c, 0xe5, 0xda, 0x07, 0x8a, 0x3a, 0xe4, 0xb8,
0x96, 0xd4, 0xcb, 0x47, 0xb8, 0x97, 0x3f, 0x07, 0x2f, 0x2f, 0x2c, 0x82, 0x4e, 0xce, 0xc9, 0x01,
0x27, 0x16, 0x15, 0x7c, 0x7c, 0x26, 0x5a, 0x82, 0x12, 0x50, 0xe2, 0xa2, 0xdc, 0x48, 0x17, 0xb9,
0xe0, 0x60, 0x91, 0x0f, 0xba, 0xc4, 0x1b, 0x94, 0xdc, 0xfb, 0x87, 0xa2, 0xf6, 0x47, 0xee, 0xe1,
0xd8, 0x96, 0xd9, 0x72, 0x3d, 0xa6, 0x9f, 0x1e, 0x51, 0x46, 0x4f, 0xcf, 0xfc, 0x08, 0x5c, 0xeb,
0x49, 0x4c, 0xad, 0xb8, 0x1e, 0x6b, 0x87, 0x46, 0x5f, 0xae, 0x6b, 0x10, 0x76, 0x42, 0xe3, 0xe9,
0xb2, 0x53, 0x80, 0x08, 0x1e, 0x4d, 0x4e, 0x8c, 0x4f, 0x7e, 0xa2, 0x72, 0x1c, 0x1a, 0xa7, 0x6c,
0xca, 0xda, 0x07, 0x55, 0x89, 0x19, 0x99, 0xf0, 0xf8, 0xa0, 0x7a, 0x9a, 0x37, 0xdd, 0x3f, 0xac,
0xe6, 0x98, 0xa0, 0xb2, 0xae, 0xf6, 0xb5, 0x93, 0xea, 0x48, 0xc1, 0x9b, 0x66, 0xe0, 0x30, 0xdb,
0xc2, 0x3e, 0x4b, 0xe2, 0x86, 0x7e, 0x66, 0x44, 0x19, 0x3d, 0x3b, 0xf3, 0x47, 0x70, 0xad, 0x37,
0x31, 0xb8, 0x34, 0x0b, 0x3b, 0xb9, 0x1d, 0x1a, 0xfd, 0x39, 0xa3, 0x91, 0xb8, 0x13, 0x1a, 0xb7,
0xca, 0xee, 0x45, 0x98, 0xe0, 0xe0, 0x17, 0x1a, 0x8d, 0x89, 0xc9, 0xa9, 0xa9, 0xdb, 0x37, 0x6f,
0xbf, 0xf8, 0xc5, 0xa9, 0xc8, 0xdb, 0xf6, 0x41, 0x55, 0x6a, 0x50, 0x2e, 0x3e, 0x3e, 0xa8, 0x6a,
0x65, 0x23, 0xfb, 0x87, 0xd5, 0x02, 0x4d, 0xf4, 0x44, 0xbe, 0x71, 0xe2, 0x61, 0x1c, 0x8c, 0xb4,
0xbb, 0xea, 0xf9, 0x26, 0xde, 0x35, 0x7d, 0x42, 0xeb, 0xe6, 0x56, 0xad, 0xe5, 0xeb, 0x8f, 0xf2,
0xc9, 0x7c, 0xae, 0x1d, 0x1a, 0xe7, 0x9a, 0x78, 0x77, 0x95, 0xd0, 0xfa, 0x9d, 0x5a, 0x0b, 0x82,
0x4b, 0x1f, 0x77, 0x4b, 0x90, 0x25, 0xf3, 0x83, 0x44, 0xc5, 0xc4, 0xa0, 0x47, 0xac, 0xed, 0xc8,
0xe0, 0x63, 0x39, 0x83, 0x88, 0x58, 0xdb, 0x45, 0x83, 0x89, 0x2c, 0x67, 0x30, 0x11, 0x6a, 0x7f,
0x50, 0xd4, 0x21, 0x8f, 0x58, 0x2e, 0xa5, 0xc4, 0x82, 0xf0, 0x6e, 0xda, 0x94, 0x11, 0x6f, 0x1b,
0x3b, 0xa6, 0xaf, 0x9f, 0xe5, 0xb6, 0xdf, 0xe0, 0x41, 0x3d, 0x51, 0x59, 0x88, 0xe1, 0x55, 0x88,
0x1d, 0x62, 0xc3, 0x14, 0xe8, 0x84, 0xc6, 0x28, 0xef, 0x5b, 0x8a, 0x0a, 0xb3, 0x74, 0x6b, 0x3c,
0xa1, 0x74, 0x7c, 0x50, 0x3d, 0x79, 0x6b, 0x9c, 0xc7, 0xf7, 0x52, 0x3f, 0x48, 0xde, 0x8b, 0xd6,
0x50, 0x7b, 0x3d, 0xe2, 0xe0, 0x3d, 0x3f, 0x8d, 0x01, 0x2a, 0x8f, 0x01, 0x9f, 0x6e, 0x87, 0xc6,
0xf9, 0x08, 0xc9, 0x36, 0x7a, 0x25, 0x26, 0x24, 0x48, 0x8b, 0x3b, 0x3c, 0xd9, 0xb1, 0x28, 0xdf,
0x58, 0x7b, 0xeb, 0xa4, 0x7a, 0x25, 0xee, 0x28, 0x25, 0x92, 0x0d, 0x52, 0x53, 0x3f, 0xc7, 0x07,
0xe9, 0xaf, 0xb0, 0x86, 0x87, 0x10, 0xe8, 0x95, 0x5c, 0x58, 0x6a, 0x87, 0xc6, 0x90, 0x27, 0x87,
0xd2, 0x40, 0xdb, 0x05, 0x17, 0x58, 0x4e, 0x8c, 0x0b, 0x5b, 0xb6, 0xab, 0xbd, 0xee, 0x10, 0x0c,
0xf2, 0x04, 0x0c, 0x72, 0x37, 0x9a, 0x48, 0x8f, 0xfc, 0x2c, 0x23, 0x5a, 0x4d, 0x3d, 0xef, 0x33,
0xec, 0x31, 0xb3, 0xe6, 0xb9, 0x3b, 0x3e, 0xf1, 0xf4, 0x1e, 0x3e, 0xd6, 0x9f, 0x6a, 0x87, 0x46,
0x0f, 0x07, 0x66, 0x22, 0x79, 0x27, 0x34, 0x9e, 0xe4, 0xee, 0x88, 0xc2, 0xae, 0x23, 0x9d, 0x6b,
0xaa, 0xfd, 0x52, 0x51, 0x2f, 0x51, 0xcc, 0x4c, 0xe6, 0x61, 0x38, 0xd5, 0xb0, 0x93, 0x4e, 0x6c,
0x2f, 0xef, 0xec, 0xde, 0x51, 0x68, 0xa8, 0xcb, 0xd3, 0x6b, 0x59, 0x58, 0x57, 0x29, 0x66, 0xd9,
0x1c, 0x1b, 0xbc, 0xe3, 0x4c, 0x24, 0x09, 0xe1, 0x62, 0x83, 0xdc, 0x97, 0x10, 0xae, 0x85, 0x2e,
0x50, 0x3f, 0xc5, 0x6c, 0x2d, 0xa1, 0x93, 0x2c, 0x88, 0x3f, 0x95, 0x78, 0x3a, 0x04, 0xfb, 0xc4,
0x6c, 0xea, 0x17, 0xf8, 0x52, 0xf8, 0x06, 0x2c, 0x85, 0xb3, 0xcb, 0xd3, 0x6b, 0x8b, 0x20, 0x86,
0xc9, 0xbf, 0x40, 0x31, 0x8b, 0x3e, 0x6c, 0x1a, 0x30, 0x9e, 0xfc, 0x54, 0x12, 0xb2, 0xa2, 0x5c,
0xba, 0x37, 0xda, 0x07, 0xd5, 0x52, 0xfb, 0xb2, 0x28, 0xdd, 0x41, 0x59, 0xc7, 0x48, 0x13, 0xd9,
0x47, 0x32, 0xed, 0xef, 0x8a, 0x3a, 0x94, 0x27, 0xef, 0x11, 0x4a, 0x76, 0xf8, 0x4a, 0xbe, 0xc8,
0xe9, 0xef, 0x03, 0xfd, 0x73, 0xcb, 0xd3, 0x6b, 0x28, 0x02, 0xc0, 0x81, 0x3e, 0x8a, 0x59, 0xf2,
0x99, 0xba, 0x50, 0x4d, 0x5c, 0xc8, 0x23, 0x82, 0x13, 0x37, 0x45, 0x27, 0x24, 0x36, 0x64, 0x42,
0x70, 0xe4, 0x26, 0x38, 0x22, 0x52, 0x40, 0x03, 0xa2, 0x2b, 0x89, 0x54, 0xe2, 0x0c, 0xb3, 0x9b,
0xc4, 0x0d, 0x98, 0xe9, 0xeb, 0x7d, 0x79, 0x67, 0xd6, 0x22, 0x60, 0x35, 0x76, 0x26, 0xf9, 0x84,
0x95, 0x5e, 0xcf, 0x39, 0x93, 0x47, 0xba, 0x6d, 0x3f, 0x89, 0x0d, 0x99, 0x30, 0xdd, 0x72, 0x22,
0x85, 0xbc, 0x33, 0x89, 0x54, 0xfb, 0xb1, 0xa2, 0xea, 0x81, 0x8f, 0x37, 0x88, 0xe9, 0x11, 0x38,
0xf7, 0x6d, 0xba, 0x61, 0x62, 0xcb, 0x22, 0x2d, 0x46, 0xea, 0xba, 0xc6, 0xbd, 0xc1, 0xb0, 0x03,
0xd6, 0xd1, 0x74, 0x2c, 0x85, 0x1d, 0x10, 0x78, 0xc9, 0x57, 0x27, 0x34, 0x2e, 0x72, 0x27, 0x32,
0x91, 0x40, 0x58, 0x54, 0xcc, 0x7d, 0xc1, 0x8a, 0xcf, 0x4c, 0xa2, 0x41, 0x4e, 0x01, 0x25, 0x0c,
0x12, 0xb9, 0xf6, 0x65, 0x75, 0xa0, 0x48, 0xce, 0x27, 0x84, 0xea, 0xfd, 0x9c, 0xd8, 0xc2, 0x51,
0x68, 0x9c, 0x59, 0x47, 0xab, 0x84, 0xd0, 0x76, 0x68, 0x9c, 0x09, 0x3c, 0xf8, 0xd5, 0x09, 0x8d,
0x9e, 0x98, 0x10, 0x7c, 0x0a, 0x64, 0x12, 0x85, 0xf4, 0xd7, 0xfe, 0x61, 0x35, 0x6e, 0x8e, 0xb4,
0x3c, 0x01, 0x90, 0x69, 0xdf, 0x57, 0xd4, 0xc7, 0x8b, 0xbd, 0x07, 0xd4, 0xbe, 0x17, 0x10, 0xd3,
0xae, 0xeb, 0x03, 0x3c, 0x89, 0x78, 0x2d, 0x1a, 0x9b, 0x75, 0x2e, 0x5e, 0x98, 0x8b, 0xc6, 0x26,
0xfe, 0x12, 0xc7, 0x26, 0x51, 0xa8, 0x44, 0x83, 0x92, 0x7c, 0x76, 0xc4, 0xaf, 0x78, 0x50, 0x12,
0xac, 0x38, 0x28, 0x89, 0x96, 0xf6, 0x17, 0x45, 0xed, 0x2f, 0xf1, 0xf2, 0x1c, 0xfd, 0x12, 0x67,
0xf4, 0x6d, 0x58, 0x7b, 0xa7, 0xd7, 0xd1, 0x3a, 0x5a, 0x6c, 0x87, 0xc6, 0xe9, 0xc0, 0x5b, 0x47,
0x8b, 0x9d, 0xd0, 0xb8, 0x9d, 0x10, 0x41, 0x8b, 0xc2, 0xea, 0xda, 0x64, 0xac, 0xe5, 0x4f, 0x8d,
0x8d, 0xd5, 0x31, 0xc3, 0x37, 0xfc, 0x3d, 0x6a, 0xb1, 0x4d, 0x28, 0xd6, 0x28, 0x61, 0x63, 0x94,
0xec, 0x80, 0x14, 0x08, 0xc7, 0x46, 0x92, 0x1f, 0xc7, 0x07, 0xd5, 0x87, 0x68, 0xb8, 0x7f, 0x58,
0x8d, 0x58, 0xa0, 0xbe, 0x82, 0x1f, 0x9e, 0xa3, 0xfd, 0x47, 0x51, 0x8d, 0xa2, 0x0b, 0x2d, 0xd7,
0x87, 0x13, 0xce, 0x27, 0x56, 0xe0, 0x11, 0x67, 0x4f, 0x1f, 0xe4, 0xe1, 0xf7, 0x87, 0xbc, 0x82,
0x58, 0x47, 0x2b, 0xae, 0xcf, 0x16, 0x52, 0xb0, 0x1d, 0x1a, 0x17, 0x03, 0x2f, 0x2f, 0xeb, 0x84,
0xc6, 0x53, 0xb1, 0x93, 0x79, 0x40, 0xf0, 0xb7, 0x81, 0x1d, 0x9f, 0x87, 0xe4, 0x72, 0x6b, 0x89,
0x0c, 0x32, 0x4f, 0xde, 0x02, 0xea, 0x85, 0x22, 0x05, 0x74, 0x35, 0xef, 0x56, 0x1e, 0xd5, 0xfe,
0x2d, 0xf1, 0xd0, 0xa6, 0x36, 0xb3, 0xa1, 0x8e, 0x80, 0xf3, 0xce, 0xf4, 0xf5, 0x21, 0xbe, 0x8a,
0x7f, 0xc0, 0xab, 0x87, 0x75, 0xb4, 0x10, 0xa1, 0x73, 0x00, 0x42, 0xc0, 0xb8, 0x10, 0x78, 0x39,
0x51, 0x1a, 0x2e, 0x0a, 0x72, 0x31, 0x58, 0xdc, 0x1e, 0xcf, 0x05, 0xf0, 0xa2, 0x85, 0xb2, 0x08,
0x4e, 0x20, 0x68, 0x05, 0x05, 0x43, 0x81, 0x02, 0xba, 0x92, 0x77, 0x30, 0x07, 0x6a, 0xae, 0xda,
0xe7, 0x91, 0xe8, 0x70, 0x76, 0xa9, 0xb9, 0x83, 0xb7, 0x48, 0xd0, 0xd2, 0x75, 0x3e, 0x65, 0xb3,
0x40, 0x3e, 0x06, 0xef, 0xd2, 0x57, 0x39, 0x94, 0x92, 0x2f, 0xc8, 0xbb, 0x1e, 0xd2, 0x45, 0x03,
0xda, 0x37, 0x15, 0x75, 0x08, 0x07, 0xcc, 0x35, 0x83, 0xd6, 0x86, 0x87, 0xeb, 0x24, 0x4b, 0x86,
0x36, 0xf5, 0xc7, 0xf9, 0x40, 0xae, 0x40, 0xc9, 0x05, 0x2a, 0xeb, 0x91, 0x46, 0x92, 0x47, 0xbc,
0x92, 0x56, 0x27, 0x32, 0x50, 0x1c, 0xbe, 0x49, 0x31, 0x33, 0x9c, 0x98, 0x44, 0x52, 0x6b, 0x5a,
0x53, 0x1d, 0x4a, 0x38, 0x30, 0xd7, 0x6c, 0x79, 0x30, 0xc5, 0xfc, 0x2c, 0xf6, 0xf5, 0xcb, 0x7c,
0x00, 0x6e, 0x01, 0x91, 0x58, 0x65, 0xcd, 0x5d, 0xf1, 0x08, 0x8a, 0xf1, 0x4e, 0x68, 0x5c, 0x8e,
0xa6, 0x50, 0x02, 0x56, 0x90, 0xb4, 0x8d, 0xb6, 0xad, 0x6a, 0x5b, 0x84, 0xb4, 0x4c, 0x46, 0x9a,
0x2d, 0xd7, 0xc3, 0x9e, 0x4d, 0x7c, 0x73, 0x53, 0xbf, 0xc2, 0x5d, 0x7e, 0x05, 0x36, 0x02, 0xa0,
0x6b, 0x19, 0x08, 0xee, 0x5e, 0xe3, 0xbd, 0x14, 0x01, 0xb1, 0x16, 0x7b, 0x51, 0x74, 0x75, 0xf2,
0x45, 0x54, 0xb2, 0xa2, 0xed, 0xa9, 0xfd, 0x16, 0xb6, 0x36, 0x89, 0x69, 0x6f, 0x50, 0xd7, 0x23,
0x75, 0xb3, 0x61, 0x3b, 0xc4, 0xd7, 0xaf, 0x72, 0x17, 0x17, 0xe0, 0x44, 0xe3, 0xf0, 0x42, 0x84,
0xce, 0x03, 0x98, 0x0e, 0x74, 0x09, 0x29, 0xed, 0xc1, 0x74, 0x6f, 0xa1, 0xb2, 0x19, 0xed, 0xbb,
0x8a, 0x7a, 0xb9, 0xe5, 0xb9, 0x1b, 0x50, 0xcc, 0x98, 0x41, 0xab, 0x8e, 0x19, 0x11, 0x0b, 0x84,
0x27, 0xb8, 0xef, 0x6b, 0x90, 0xdf, 0x26, 0x5a, 0xeb, 0x5c, 0x49, 0x2c, 0x06, 0xa2, 0x22, 0xbb,
0x0b, 0x2e, 0xd0, 0x79, 0x49, 0x18, 0x08, 0xe5, 0x25, 0xd4, 0xcd, 0xa2, 0xf6, 0x96, 0xa2, 0x0e,
0x3a, 0x76, 0xd3, 0x66, 0x66, 0x0d, 0xd3, 0xfa, 0x8e, 0x5d, 0x67, 0x9b, 0xa6, 0x4d, 0x4d, 0x07,
0x53, 0x7d, 0x98, 0x0f, 0xc9, 0x12, 0x2f, 0x1e, 0x41, 0x63, 0x26, 0x51, 0x58, 0xa0, 0x8b, 0x98,
0x66, 0x05, 0x7f, 0x19, 0xfb, 0x98, 0x61, 0x91, 0x99, 0xd2, 0xde, 0x54, 0x54, 0xad, 0x69, 0x53,
0x73, 0xd3, 0x6d, 0x12, 0xb3, 0x6e, 0xfb, 0x5b, 0x66, 0xc3, 0x23, 0x44, 0x37, 0x46, 0x94, 0xd1,
0x73, 0x93, 0x3d, 0x37, 0xa2, 0x9b, 0xb5, 0x1b, 0xab, 0xf6, 0xeb, 0x64, 0xe6, 0xe5, 0xf7, 0x43,
0xe3, 0x04, 0xec, 0xc4, 0xa6, 0x4d, 0x5f, 0x71, 0x9b, 0x64, 0xce, 0xf6, 0xb7, 0xe6, 0x3d, 0x42,
0xd2, 0xd5, 0x51, 0x90, 0x8b, 0xfb, 0x60, 0xe4, 0x3a, 0x10, 0x39, 0x35, 0x31, 0x72, 0x1d, 0x15,
0x9b, 0x6b, 0xf7, 0x15, 0xb5, 0x27, 0x59, 0xef, 0xfc, 0xd8, 0x19, 0xe1, 0xc7, 0xce, 0x9f, 0x79,
0xca, 0x93, 0x2c, 0xda, 0xe8, 0xf0, 0x39, 0xe7, 0x65, 0x9f, 0x9d, 0xd0, 0x98, 0x4b, 0x2a, 0x8e,
0x44, 0x26, 0x39, 0x88, 0xe2, 0x1d, 0xe0, 0x17, 0xce, 0x94, 0x26, 0x61, 0xf8, 0xc6, 0x97, 0x7c,
0x97, 0x42, 0xec, 0xce, 0x99, 0xcd, 0x7f, 0x1e, 0x1f, 0x54, 0x47, 0x1f, 0xd6, 0x14, 0xe4, 0x47,
0x02, 0x5f, 0x94, 0xd9, 0xf1, 0x1c, 0xed, 0x55, 0xb5, 0x0f, 0x3b, 0x3b, 0x50, 0x7d, 0x45, 0xb7,
0x09, 0x94, 0x30, 0x5f, 0x7f, 0x92, 0x5f, 0xe2, 0x41, 0xd1, 0x7b, 0x21, 0x02, 0x79, 0x55, 0xbe,
0x4c, 0x18, 0x2c, 0xfc, 0x81, 0x28, 0xc2, 0xe4, 0xe4, 0x15, 0x54, 0x54, 0xd4, 0xfe, 0xa7, 0xa8,
0xa3, 0xee, 0x36, 0xf1, 0x76, 0x3c, 0x9b, 0x41, 0xe0, 0x68, 0xba, 0x8c, 0x98, 0x75, 0xb2, 0x6d,
0x5b, 0xc4, 0xa4, 0xb8, 0x49, 0x7c, 0x08, 0xa7, 0x71, 0x21, 0xa4, 0x57, 0xb2, 0xeb, 0xa5, 0xa1,
0xbb, 0x49, 0x23, 0xc4, 0xdb, 0xcc, 0x91, 0xed, 0x65, 0x50, 0x6f, 0x87, 0xc6, 0x35, 0xb7, 0x04,
0xd9, 0x16, 0xe1, 0xe8, 0x5d, 0x3a, 0x1b, 0x99, 0xea, 0x84, 0xc6, 0x27, 0x39, 0xc1, 0x87, 0xd0,
0xed, 0xbe, 0x28, 0xa1, 0x8a, 0xeb, 0xc2, 0x03, 0x3d, 0x0c, 0x0b, 0xed, 0xab, 0xea, 0x25, 0x08,
0x63, 0xa6, 0x4d, 0xeb, 0x64, 0xd7, 0x84, 0x95, 0x5c, 0x73, 0x5c, 0x6b, 0xcb, 0xd7, 0xaf, 0xf1,
0x2d, 0x0d, 0x8b, 0x46, 0x03, 0x85, 0x05, 0xc0, 0x97, 0x6c, 0x3a, 0xc3, 0xd1, 0xf4, 0xd6, 0xb6,
0x0c, 0x49, 0x33, 0xe5, 0x28, 0xff, 0x45, 0x12, 0x4b, 0xda, 0xbf, 0x20, 0xdd, 0xa5, 0xd8, 0xda,
0x22, 0x75, 0x93, 0xba, 0xcc, 0x6e, 0xd8, 0x16, 0x8e, 0xee, 0x1f, 0xea, 0xbe, 0x5e, 0xe5, 0xf3,
0xfb, 0x36, 0x0c, 0xf7, 0xe0, 0x7a, 0xa4, 0xb4, 0x2c, 0xe8, 0x2c, 0xcc, 0xc1, 0x68, 0x0f, 0x06,
0x52, 0xa4, 0x13, 0x1a, 0x57, 0xa2, 0xd0, 0x2e, 0x83, 0xf9, 0x5d, 0xa5, 0x14, 0xe9, 0x1c, 0x54,
0xbb, 0x58, 0xdc, 0x3f, 0xac, 0x76, 0x61, 0x81, 0xa4, 0x2d, 0xea, 0xbe, 0x86, 0xd4, 0xf3, 0xcc,
0xc3, 0x8d, 0x86, 0x6d, 0x99, 0x96, 0x83, 0x7d, 0x5f, 0xbf, 0xce, 0x87, 0xf5, 0x05, 0xa8, 0x97,
0x63, 0x60, 0x16, 0xe4, 0x9d, 0xd0, 0xd0, 0xa2, 0x01, 0x15, 0x84, 0xe9, 0x45, 0x4d, 0x4e, 0x55,
0xbb, 0xa7, 0xf6, 0xc7, 0x43, 0x6c, 0x36, 0x5c, 0xa7, 0x4e, 0x3c, 0xb3, 0x85, 0xd9, 0xa6, 0xfe,
0x14, 0xdf, 0xf5, 0xd3, 0x70, 0x0c, 0xc4, 0xf0, 0x3c, 0x47, 0x57, 0x30, 0xdb, 0x4c, 0x43, 0x4c,
0x09, 0x11, 0xa6, 0xeb, 0x0d, 0x58, 0x56, 0xca, 0x1b, 0xa8, 0xdc, 0x5c, 0xdb, 0x52, 0x2f, 0xfa,
0x84, 0x99, 0x8e, 0xbb, 0x63, 0xb6, 0x3c, 0xdb, 0xf5, 0x6c, 0xb6, 0xa7, 0x3f, 0xcd, 0xb7, 0x02,
0xf4, 0xd7, 0xeb, 0x13, 0xb6, 0xe8, 0xee, 0xac, 0xc4, 0x48, 0xda, 0x59, 0x5e, 0xdc, 0x35, 0xb1,
0x28, 0x34, 0xd7, 0xde, 0x51, 0xd4, 0xc1, 0x26, 0xde, 0x4d, 0x9c, 0xb3, 0x5c, 0x6a, 0x05, 0x9e,
0x47, 0xa8, 0xb5, 0xa7, 0x8f, 0xf2, 0xd1, 0xf3, 0xf9, 0x15, 0x0b, 0xde, 0x59, 0xc2, 0xbb, 0x11,
0xc7, 0xd9, 0x4c, 0x05, 0x0e, 0xfa, 0xa6, 0x44, 0x9e, 0x1e, 0xf4, 0x32, 0x30, 0x19, 0x68, 0x7e,
0x27, 0x22, 0xb7, 0x8b, 0xa4, 0x56, 0xb5, 0x0f, 0x14, 0xb5, 0xdf, 0xf2, 0xb0, 0xbf, 0x59, 0xc8,
0xfc, 0x9f, 0xe1, 0x93, 0xf1, 0x2e, 0xcf, 0xfc, 0x67, 0x93, 0xcc, 0xdf, 0x8a, 0x33, 0xff, 0xf9,
0xe8, 0x44, 0x86, 0x66, 0x59, 0x0e, 0x2e, 0x0d, 0xbe, 0x5c, 0xa7, 0x9c, 0xcd, 0x73, 0x31, 0xac,
0xe0, 0xbe, 0x92, 0x11, 0xa8, 0x09, 0xac, 0xb8, 0x26, 0xa8, 0x3e, 0x8c, 0x19, 0xa8, 0x0a, 0x66,
0xa3, 0xaa, 0xa0, 0x60, 0xcc, 0x73, 0xb4, 0x9f, 0x29, 0xea, 0x50, 0xd1, 0xbd, 0xe4, 0x32, 0xe6,
0x59, 0x3e, 0xff, 0xf6, 0x51, 0x68, 0x9c, 0x9d, 0x45, 0xc2, 0x3b, 0x42, 0xde, 0x4a, 0xf1, 0x1d,
0x41, 0x8a, 0x76, 0x5b, 0x1a, 0xfb, 0x87, 0xd5, 0xcc, 0x36, 0x92, 0x5b, 0xd6, 0xbe, 0xae, 0xa8,
0x83, 0x3e, 0x0b, 0xa8, 0x09, 0xf9, 0x12, 0x76, 0xec, 0x6d, 0x62, 0x46, 0x59, 0xb0, 0xaf, 0x3f,
0x97, 0x66, 0xa1, 0xfd, 0xa0, 0x71, 0x27, 0x51, 0x58, 0x05, 0x7c, 0x35, 0xcd, 0x8d, 0x24, 0x58,
0x3e, 0x85, 0x17, 0xc2, 0xd8, 0xa9, 0x89, 0xdb, 0xe3, 0x48, 0x66, 0x0d, 0x2a, 0xe3, 0x02, 0x0d,
0x88, 0xa6, 0xbe, 0xfe, 0x3c, 0x27, 0xf1, 0x39, 0xd8, 0x97, 0xb9, 0x66, 0x4b, 0x36, 0xcd, 0x2a,
0x88, 0x12, 0x22, 0x66, 0x86, 0xb9, 0x30, 0x3a, 0x39, 0x8e, 0xca, 0x76, 0x20, 0x17, 0xef, 0xe1,
0xbd, 0x27, 0xcf, 0x5b, 0x2f, 0xf0, 0xc8, 0x59, 0x3f, 0x0a, 0x8d, 0x5e, 0x84, 0x77, 0x56, 0x59,
0x20, 0x3c, 0x6c, 0x9d, 0xf3, 0xb3, 0xcf, 0xf4, 0x0a, 0x2a, 0x93, 0x3d, 0xf0, 0xf1, 0xad, 0x60,
0x11, 0x89, 0xf6, 0xb4, 0x6d, 0xf5, 0x02, 0x14, 0x9b, 0x35, 0xec, 0x13, 0x33, 0x7a, 0x69, 0xd4,
0x6f, 0x8c, 0x28, 0xa3, 0xbd, 0x93, 0xbd, 0x49, 0x32, 0xb4, 0xc6, 0xa5, 0xfc, 0xce, 0xb0, 0x37,
0x51, 0x8d, 0x64, 0x59, 0x98, 0xca, 0x89, 0x2b, 0x23, 0x71, 0xe9, 0x11, 0x2f, 0x8f, 0x37, 0x0f,
0xab, 0x0a, 0x2a, 0x34, 0xd5, 0xbe, 0x77, 0x52, 0xbd, 0x06, 0x51, 0x23, 0x0d, 0x17, 0x50, 0xba,
0x5a, 0x6e, 0x13, 0x96, 0xac, 0x47, 0xee, 0x05, 0xc4, 0x67, 0xe6, 0x96, 0x5d, 0xd3, 0xc7, 0xf8,
0x74, 0xfc, 0x4d, 0x89, 0x5f, 0x28, 0x97, 0xf0, 0xee, 0xec, 0x02, 0x8a, 0xf0, 0x3b, 0xf6, 0x4c,
0x3b, 0x34, 0x8c, 0x26, 0xde, 0x4d, 0xb7, 0x38, 0x5b, 0x88, 0x6d, 0x64, 0x2a, 0xe9, 0xd9, 0xf7,
0x00, 0x3d, 0xa1, 0xec, 0x7b, 0xa0, 0xc9, 0x07, 0xab, 0xc4, 0x6f, 0x9e, 0x05, 0xba, 0xe8, 0x01,
0xcd, 0x6a, 0xda, 0x47, 0x8a, 0x3a, 0x98, 0x3e, 0xbc, 0x38, 0x58, 0x7c, 0xaa, 0x1d, 0xe7, 0x1b,
0xf8, 0x3d, 0x18, 0x89, 0x81, 0xe4, 0xe1, 0x62, 0x71, 0x7a, 0x59, 0x7c, 0xad, 0x1d, 0xc0, 0x12,
0x79, 0x9a, 0x3e, 0xcb, 0x40, 0xd9, 0x7b, 0x99, 0xd4, 0x48, 0x17, 0xb9, 0xb0, 0xf5, 0xa5, 0xa4,
0x50, 0xd6, 0x0a, 0x0b, 0x4f, 0xbd, 0xdb, 0xea, 0x65, 0xfe, 0xb6, 0xd2, 0x08, 0x1c, 0x27, 0xce,
0x65, 0x5c, 0x9a, 0x14, 0xa6, 0xfa, 0x04, 0xf7, 0x74, 0x0a, 0x72, 0x05, 0xd0, 0x9a, 0x0f, 0x1c,
0x87, 0x67, 0x21, 0x77, 0x69, 0x5c, 0x4a, 0x76, 0x42, 0xe3, 0x6a, 0x7c, 0x64, 0xc9, 0xe0, 0x0a,
0xea, 0xd2, 0x4e, 0xfb, 0x8a, 0xda, 0x13, 0xb4, 0x68, 0x2b, 0x0d, 0x8a, 0xbf, 0x9a, 0xe7, 0x5d,
0x7d, 0xfe, 0x28, 0x34, 0x2e, 0xcd, 0x91, 0x96, 0x47, 0x2c, 0xcc, 0x48, 0x7d, 0x7d, 0x85, 0xae,
0x64, 0x11, 0x52, 0x79, 0x21, 0x4b, 0x4d, 0x5a, 0xb4, 0x15, 0x03, 0xcf, 0xbb, 0x4d, 0x1b, 0xd2,
0x23, 0xb6, 0x57, 0xd9, 0x3f, 0xac, 0xca, 0x1b, 0xeb, 0x0a, 0x3a, 0x27, 0x34, 0xd1, 0x7e, 0xa1,
0xc4, 0xdd, 0x27, 0x17, 0xcf, 0xef, 0xcc, 0xf3, 0xd5, 0xfd, 0x26, 0x9f, 0xd3, 0xbc, 0x89, 0xf4,
0x12, 0x9a, 0x77, 0x3f, 0x92, 0x76, 0x2f, 0x5e, 0x1e, 0x0b, 0x1c, 0xb2, 0xc5, 0x7b, 0xb9, 0xbb,
0x16, 0x4c, 0x92, 0xac, 0x17, 0x5d, 0x41, 0x6a, 0xd6, 0x4a, 0xfb, 0x9d, 0xa2, 0xf6, 0x72, 0x9a,
0xd9, 0x15, 0xf3, 0xaf, 0x23, 0xa2, 0xdf, 0xe2, 0x99, 0x5d, 0xde, 0x84, 0x70, 0xdd, 0xcc, 0xa9,
0x56, 0x52, 0xaa, 0xf9, 0x0b, 0x62, 0x29, 0xd9, 0xab, 0x1f, 0xa7, 0x07, 0xf9, 0x9b, 0xbc, 0x2f,
0x5d, 0x41, 0x3d, 0x62, 0xcb, 0x8c, 0x72, 0x76, 0x91, 0xfc, 0x6e, 0x77, 0xca, 0xc2, 0xa5, 0x72,
0x81, 0x72, 0xfe, 0x1a, 0xb8, 0x3b, 0xe5, 0x6e, 0x7a, 0x65, 0xca, 0x89, 0x66, 0x42, 0x39, 0xbd,
0x37, 0x6e, 0xa8, 0xd1, 0x83, 0x55, 0x7a, 0x04, 0xfc, 0x66, 0x9e, 0x9f, 0x01, 0x9f, 0xc9, 0xf3,
0xe5, 0x6f, 0x3e, 0xd9, 0x59, 0x20, 0x2c, 0x46, 0x2f, 0x43, 0x04, 0xa2, 0xd0, 0x8f, 0x80, 0xf8,
0xbc, 0xec, 0x2e, 0x57, 0xbc, 0x66, 0xcb, 0x62, 0xfa, 0x7b, 0x30, 0x44, 0xca, 0xcc, 0xd2, 0x51,
0x68, 0x5c, 0xcd, 0x7a, 0x5c, 0xca, 0xd7, 0xab, 0x2b, 0x16, 0xcb, 0x8f, 0x53, 0xb3, 0x84, 0xe7,
0xbb, 0xd7, 0xca, 0x0a, 0x70, 0xde, 0x0d, 0x14, 0xa2, 0xbd, 0x6f, 0x61, 0xea, 0xeb, 0xbf, 0x8d,
0x66, 0x69, 0xad, 0x40, 0x41, 0x8c, 0x92, 0xab, 0xa0, 0x58, 0xa0, 0x50, 0xc2, 0xcb, 0x53, 0xc5,
0x99, 0x94, 0xf4, 0x66, 0xee, 0xbc, 0xff, 0xe1, 0xf0, 0x89, 0xc3, 0x0f, 0x87, 0x4f, 0xbc, 0x7f,
0x34, 0xac, 0x1c, 0x1e, 0x0d, 0x2b, 0xdf, 0xb9, 0x3f, 0x7c, 0xe2, 0xed, 0xfb, 0xc3, 0xca, 0xe1,
0xfd, 0xe1, 0x13, 0xff, 0xbc, 0x3f, 0x7c, 0xe2, 0xb5, 0x67, 0x36, 0x6c, 0xb6, 0x19, 0xd4, 0x6e,
0x58, 0x6e, 0x73, 0x2c, 0xcd, 0xc1, 0x84, 0x5f, 0xd9, 0x3f, 0x70, 0x6a, 0x67, 0xf8, 0x5f, 0x6e,
0x6e, 0xfe, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x2e, 0x99, 0xf4, 0x6a, 0xde, 0x23, 0x00, 0x00,
0xf9, 0xcf, 0x26, 0x4d, 0xda, 0x6c, 0x1c, 0x27, 0x5e, 0x3b, 0xf6, 0x36, 0x49, 0xbd, 0xee, 0xc9,
0x49, 0xeb, 0xde, 0xe2, 0x4b, 0xda, 0xfc, 0xf3, 0xb7, 0x84, 0xc0, 0x97, 0x9a, 0xba, 0xb1, 0x1d,
0x6b, 0x6c, 0xab, 0xa8, 0x08, 0xad, 0xe6, 0xec, 0x99, 0x63, 0x2f, 0xde, 0x33, 0x7b, 0xb2, 0x3b,
0xeb, 0x4b, 0x41, 0xa5, 0x2a, 0xe2, 0xf2, 0x06, 0x58, 0x5c, 0x24, 0x90, 0x50, 0x11, 0x20, 0x51,
0x4a, 0x11, 0x12, 0x12, 0x12, 0xbc, 0x80, 0x90, 0x90, 0x2a, 0x78, 0xb0, 0x1f, 0x91, 0x28, 0x8b,
0xea, 0xf4, 0x01, 0x9d, 0x07, 0x1e, 0xce, 0xa3, 0x79, 0x41, 0xdf, 0xec, 0x6d, 0x76, 0x77, 0x4e,
0x93, 0xb7, 0xb3, 0xdf, 0xef, 0x9b, 0x6f, 0x7e, 0xdf, 0x5c, 0xbe, 0xf9, 0xbe, 0x99, 0xa3, 0x5e,
0x77, 0xec, 0xda, 0x98, 0xe5, 0xd2, 0x86, 0xbd, 0x31, 0xe6, 0xb6, 0x98, 0xed, 0x52, 0x3f, 0xfa,
0x0a, 0x3c, 0x0c, 0x5f, 0x37, 0x5a, 0x9e, 0xcb, 0x5c, 0xed, 0x4c, 0x24, 0xbc, 0x3c, 0x24, 0xa8,
0xb3, 0x80, 0xda, 0x74, 0x23, 0x52, 0xb8, 0x7c, 0x49, 0x00, 0x7c, 0xfb, 0x0d, 0x12, 0x8b, 0xcf,
0x92, 0x5d, 0x16, 0xfd, 0xac, 0xfc, 0x7b, 0x5e, 0x1d, 0xb8, 0x1b, 0xf5, 0x30, 0x2b, 0xf6, 0xa0,
0xfd, 0x58, 0x51, 0x2f, 0x3a, 0xb6, 0xcf, 0x08, 0x35, 0x71, 0xbd, 0xee, 0x11, 0xdf, 0x27, 0xbe,
0xae, 0x8c, 0x9c, 0x1a, 0x3d, 0x3b, 0xe3, 0x1f, 0x85, 0x86, 0x86, 0xf0, 0xce, 0x22, 0x87, 0xa7,
0x13, 0xb4, 0x1d, 0x1a, 0x17, 0x9c, 0xbc, 0xa8, 0x13, 0x1a, 0xd7, 0x77, 0x9b, 0xce, 0x54, 0x25,
0x27, 0xaf, 0x8c, 0xd4, 0x49, 0x03, 0x07, 0x0e, 0x9b, 0xaa, 0xc4, 0x3f, 0x2a, 0xc7, 0x07, 0xd5,
0x47, 0xe3, 0xdf, 0xfb, 0x87, 0x55, 0x89, 0x71, 0x54, 0x34, 0xad, 0xfd, 0x47, 0x51, 0xf5, 0x0d,
0xc7, 0xad, 0x61, 0xc7, 0xac, 0xdb, 0xbe, 0xe5, 0x6e, 0x13, 0x6f, 0xcf, 0xf4, 0x89, 0xb7, 0x4d,
0x3c, 0x5f, 0x3f, 0xc9, 0x89, 0xfe, 0x56, 0x39, 0x0a, 0x8d, 0x7e, 0x84, 0x77, 0x3e, 0xcb, 0xf5,
0xa6, 0x29, 0x5d, 0x8d, 0xf0, 0x76, 0x68, 0x5c, 0xda, 0x48, 0x64, 0x6e, 0x40, 0x2d, 0x12, 0x03,
0x9d, 0xd0, 0x78, 0x9e, 0x13, 0x96, 0xa1, 0x12, 0xde, 0xed, 0x83, 0xea, 0x80, 0x4c, 0xb5, 0x73,
0x50, 0x95, 0x77, 0x90, 0x77, 0x54, 0xc6, 0x0d, 0x0d, 0x46, 0x0d, 0xe7, 0x12, 0xa7, 0x62, 0xb9,
0xf6, 0xb1, 0xcc, 0x61, 0x42, 0x71, 0xcd, 0x21, 0x75, 0xfd, 0xd4, 0x88, 0x32, 0xfa, 0xd8, 0xcc,
0xbb, 0xe0, 0xf0, 0xc5, 0xd4, 0xe2, 0xcb, 0x11, 0x58, 0xf6, 0x36, 0x06, 0x3a, 0xa1, 0xf1, 0xac,
0xc4, 0xdb, 0x18, 0x15, 0xdc, 0x65, 0x5e, 0x40, 0xc0, 0xd7, 0x2e, 0x66, 0xba, 0x01, 0xc7, 0x07,
0xd5, 0x47, 0xa0, 0xe9, 0xfe, 0x61, 0xb5, 0x44, 0xaa, 0xe4, 0x66, 0x2c, 0xd7, 0x3e, 0x54, 0xd4,
0x21, 0xc7, 0xb5, 0xa4, 0x5e, 0x3e, 0xc2, 0xbd, 0xfc, 0x29, 0x78, 0x79, 0x61, 0x11, 0x74, 0x72,
0x4e, 0x0e, 0x38, 0xb1, 0xa8, 0xe0, 0xe3, 0x33, 0xd1, 0x12, 0x94, 0x80, 0x12, 0x17, 0xe5, 0x46,
0xba, 0xc8, 0x05, 0x07, 0x8b, 0x7c, 0xd0, 0x25, 0xde, 0xa0, 0xe4, 0xde, 0xdf, 0x14, 0xb5, 0x3f,
0x72, 0x0f, 0xc7, 0xb6, 0xcc, 0x96, 0xeb, 0x31, 0xfd, 0xf4, 0x88, 0x32, 0x7a, 0x7a, 0xe6, 0x87,
0xe0, 0x5a, 0x4f, 0x62, 0x6a, 0xc5, 0xf5, 0x58, 0x3b, 0x34, 0xfa, 0x72, 0x5d, 0x83, 0xb0, 0x13,
0x1a, 0x4f, 0x97, 0x9d, 0x02, 0x44, 0xf0, 0x68, 0x72, 0x62, 0x7c, 0xf2, 0xff, 0x2a, 0xc7, 0xa1,
0x71, 0xca, 0xa6, 0xac, 0x7d, 0x50, 0x95, 0x98, 0x91, 0x09, 0x8f, 0x0f, 0xaa, 0xa7, 0x79, 0xd3,
0xfd, 0xc3, 0x6a, 0x8e, 0x09, 0x2a, 0xeb, 0x6a, 0x5f, 0x3d, 0xa9, 0x8e, 0x14, 0xbc, 0x69, 0x06,
0x0e, 0xb3, 0x2d, 0xec, 0xb3, 0x24, 0x6e, 0xe8, 0x67, 0x46, 0x94, 0xd1, 0xb3, 0x33, 0xbf, 0x07,
0xd7, 0x7a, 0x13, 0x83, 0x4b, 0xb3, 0xb0, 0x93, 0xdb, 0xa1, 0xd1, 0x9f, 0x33, 0x1a, 0x89, 0x3b,
0xa1, 0x71, 0xab, 0xec, 0x5e, 0x84, 0x09, 0x0e, 0x7e, 0xbe, 0xd1, 0x98, 0x98, 0x9c, 0x9a, 0xba,
0x7d, 0xf3, 0xf6, 0x8b, 0x5f, 0x98, 0x8a, 0xbc, 0x6d, 0x1f, 0x54, 0xa5, 0x06, 0xe5, 0xe2, 0xe3,
0x83, 0xaa, 0x56, 0x36, 0xb2, 0x7f, 0x58, 0x2d, 0xd0, 0x44, 0x4f, 0xe4, 0x1b, 0x27, 0x1e, 0xc6,
0xc1, 0x48, 0xbb, 0xab, 0x9e, 0x6f, 0xe2, 0x5d, 0xd3, 0x27, 0xb4, 0x6e, 0x6e, 0xd5, 0x5a, 0xbe,
0xfe, 0x28, 0x9f, 0xcc, 0xe7, 0xda, 0xa1, 0x71, 0xae, 0x89, 0x77, 0x57, 0x09, 0xad, 0xdf, 0xa9,
0xb5, 0x20, 0xb8, 0xf4, 0x71, 0xb7, 0x04, 0x59, 0x32, 0x3f, 0x48, 0x54, 0x4c, 0x0c, 0x7a, 0xc4,
0xda, 0x8e, 0x0c, 0x3e, 0x96, 0x33, 0x88, 0x88, 0xb5, 0x5d, 0x34, 0x98, 0xc8, 0x72, 0x06, 0x13,
0xa1, 0xf6, 0x3b, 0x45, 0x1d, 0xf2, 0x88, 0xe5, 0x52, 0x4a, 0x2c, 0x08, 0xef, 0xa6, 0x4d, 0x19,
0xf1, 0xb6, 0xb1, 0x63, 0xfa, 0xfa, 0x59, 0x6e, 0xfb, 0x4d, 0x1e, 0xd4, 0x13, 0x95, 0x85, 0x18,
0x5e, 0x85, 0xd8, 0x21, 0x36, 0x4c, 0x81, 0x4e, 0x68, 0x8c, 0xf2, 0xbe, 0xa5, 0xa8, 0x30, 0x4b,
0xb7, 0xc6, 0x13, 0x4a, 0xc7, 0x07, 0xd5, 0x93, 0xb7, 0xc6, 0x79, 0x7c, 0x2f, 0xf5, 0x83, 0xe4,
0xbd, 0x68, 0x0d, 0xb5, 0xd7, 0x23, 0x0e, 0xde, 0xf3, 0xd3, 0x18, 0xa0, 0xf2, 0x18, 0xf0, 0xe9,
0x76, 0x68, 0x9c, 0x8f, 0x90, 0x6c, 0xa3, 0x57, 0x62, 0x42, 0x82, 0xb4, 0xb8, 0xc3, 0x93, 0x1d,
0x8b, 0xf2, 0x8d, 0xb5, 0xb7, 0x4f, 0xaa, 0x57, 0xe2, 0x8e, 0x52, 0x22, 0xd9, 0x20, 0x35, 0xf5,
0x73, 0x7c, 0x90, 0xfe, 0x0c, 0x6b, 0x78, 0x08, 0x81, 0x5e, 0xc9, 0x85, 0xa5, 0x76, 0x68, 0x0c,
0x79, 0x72, 0x28, 0x0d, 0xb4, 0x5d, 0x70, 0x81, 0xe5, 0xc4, 0xb8, 0xb0, 0x65, 0xbb, 0xda, 0xeb,
0x0e, 0xc1, 0x20, 0x4f, 0xc0, 0x20, 0x77, 0xa3, 0x89, 0xf4, 0xc8, 0xcf, 0x32, 0xa2, 0xd5, 0xd4,
0xf3, 0x3e, 0xc3, 0x1e, 0x33, 0x6b, 0x9e, 0xbb, 0xe3, 0x13, 0x4f, 0xef, 0xe1, 0x63, 0xfd, 0xa9,
0x76, 0x68, 0xf4, 0x70, 0x60, 0x26, 0x92, 0x77, 0x42, 0xe3, 0x49, 0xee, 0x8e, 0x28, 0xec, 0x3a,
0xd2, 0xb9, 0xa6, 0xda, 0xcf, 0x15, 0xf5, 0x12, 0xc5, 0xcc, 0x64, 0x1e, 0x86, 0x53, 0x0d, 0x3b,
0xe9, 0xc4, 0xf6, 0xf2, 0xce, 0xee, 0x1d, 0x85, 0x86, 0xba, 0x3c, 0xbd, 0x96, 0x85, 0x75, 0x95,
0x62, 0x96, 0xcd, 0xb1, 0xc1, 0x3b, 0xce, 0x44, 0x92, 0x10, 0x2e, 0x36, 0xc8, 0x7d, 0x09, 0xe1,
0x5a, 0xe8, 0x02, 0xf5, 0x53, 0xcc, 0xd6, 0x12, 0x3a, 0xc9, 0x82, 0xf8, 0x43, 0x89, 0xa7, 0x43,
0xb0, 0x4f, 0xcc, 0xa6, 0x7e, 0x81, 0x2f, 0x85, 0xaf, 0xc3, 0x52, 0x38, 0xbb, 0x3c, 0xbd, 0xb6,
0x08, 0x62, 0x98, 0xfc, 0x0b, 0x14, 0xb3, 0xe8, 0xc3, 0xa6, 0x01, 0xe3, 0xc9, 0x4f, 0x25, 0x21,
0x2b, 0xca, 0xa5, 0x7b, 0xa3, 0x7d, 0x50, 0x2d, 0xb5, 0x2f, 0x8b, 0xd2, 0x1d, 0x94, 0x75, 0x8c,
0x34, 0x91, 0x7d, 0x24, 0xd3, 0xfe, 0xaa, 0xa8, 0x43, 0x79, 0xf2, 0x1e, 0xa1, 0x64, 0x87, 0xaf,
0xe4, 0x8b, 0x9c, 0xfe, 0x3e, 0xd0, 0x3f, 0xb7, 0x3c, 0xbd, 0x86, 0x22, 0x00, 0x1c, 0xe8, 0xa3,
0x98, 0x25, 0x9f, 0xa9, 0x0b, 0xd5, 0xc4, 0x85, 0x3c, 0x22, 0x38, 0x71, 0x53, 0x74, 0x42, 0x62,
0x43, 0x26, 0x04, 0x47, 0x6e, 0x82, 0x23, 0x22, 0x05, 0x34, 0x20, 0xba, 0x92, 0x48, 0x25, 0xce,
0x30, 0xbb, 0x49, 0xdc, 0x80, 0x99, 0xbe, 0xde, 0x97, 0x77, 0x66, 0x2d, 0x02, 0x56, 0x63, 0x67,
0x92, 0x4f, 0x58, 0xe9, 0xf5, 0x9c, 0x33, 0x79, 0xa4, 0xdb, 0xf6, 0x93, 0xd8, 0x90, 0x09, 0xd3,
0x2d, 0x27, 0x52, 0xc8, 0x3b, 0x93, 0x48, 0xb5, 0x1f, 0x29, 0xaa, 0x1e, 0xf8, 0x78, 0x83, 0x98,
0x1e, 0x81, 0x73, 0xdf, 0xa6, 0x1b, 0x26, 0xb6, 0x2c, 0xd2, 0x62, 0xa4, 0xae, 0x6b, 0xdc, 0x1b,
0x0c, 0x3b, 0x60, 0x1d, 0x4d, 0xc7, 0x52, 0xd8, 0x01, 0x81, 0x97, 0x7c, 0x75, 0x42, 0xe3, 0x22,
0x77, 0x22, 0x13, 0x09, 0x84, 0x45, 0xc5, 0xdc, 0x17, 0xac, 0xf8, 0xcc, 0x24, 0x1a, 0xe4, 0x14,
0x50, 0xc2, 0x20, 0x91, 0x6b, 0x5f, 0x52, 0x07, 0x8a, 0xe4, 0x7c, 0x42, 0xa8, 0xde, 0xcf, 0x89,
0x2d, 0x1c, 0x85, 0xc6, 0x99, 0x75, 0xb4, 0x4a, 0x08, 0x6d, 0x87, 0xc6, 0x99, 0xc0, 0x83, 0x5f,
0x9d, 0xd0, 0xe8, 0x89, 0x09, 0xc1, 0xa7, 0x40, 0x26, 0x51, 0x48, 0x7f, 0xed, 0x1f, 0x56, 0xe3,
0xe6, 0x48, 0xcb, 0x13, 0x00, 0x99, 0xf6, 0x3d, 0x45, 0x7d, 0xbc, 0xd8, 0x7b, 0x40, 0xed, 0x7b,
0x01, 0x31, 0xed, 0xba, 0x3e, 0xc0, 0x93, 0x88, 0xd7, 0xa3, 0xb1, 0x59, 0xe7, 0xe2, 0x85, 0xb9,
0x68, 0x6c, 0xe2, 0x2f, 0x71, 0x6c, 0x12, 0x85, 0x4a, 0x34, 0x28, 0xc9, 0x67, 0x47, 0xfc, 0x8a,
0x07, 0x25, 0xc1, 0x8a, 0x83, 0x92, 0x68, 0x69, 0x7f, 0x52, 0xd4, 0xfe, 0x12, 0x2f, 0xcf, 0xd1,
0x2f, 0x71, 0x46, 0xdf, 0x82, 0xb5, 0x77, 0x7a, 0x1d, 0xad, 0xa3, 0xc5, 0x76, 0x68, 0x9c, 0x0e,
0xbc, 0x75, 0xb4, 0xd8, 0x09, 0x8d, 0xdb, 0x09, 0x11, 0xb4, 0x28, 0xac, 0xae, 0x4d, 0xc6, 0x5a,
0xfe, 0xd4, 0xd8, 0x58, 0x1d, 0x33, 0x7c, 0xc3, 0xdf, 0xa3, 0x16, 0xdb, 0x84, 0x62, 0x8d, 0x12,
0x36, 0x46, 0xc9, 0x0e, 0x48, 0x81, 0x70, 0x6c, 0x24, 0xf9, 0x71, 0x7c, 0x50, 0x7d, 0x88, 0x86,
0xfb, 0x87, 0xd5, 0x88, 0x05, 0xea, 0x2b, 0xf8, 0xe1, 0x39, 0xda, 0xbf, 0x14, 0xd5, 0x28, 0xba,
0xd0, 0x72, 0x7d, 0x38, 0xe1, 0x7c, 0x62, 0x05, 0x1e, 0x71, 0xf6, 0xf4, 0x41, 0x1e, 0x7e, 0x7f,
0xc0, 0x2b, 0x88, 0x75, 0xb4, 0xe2, 0xfa, 0x6c, 0x21, 0x05, 0xdb, 0xa1, 0x71, 0x31, 0xf0, 0xf2,
0xb2, 0x4e, 0x68, 0x3c, 0x15, 0x3b, 0x99, 0x07, 0x04, 0x7f, 0x1b, 0xd8, 0xf1, 0x79, 0x48, 0x2e,
0xb7, 0x96, 0xc8, 0x20, 0xf3, 0xe4, 0x2d, 0xa0, 0x5e, 0x28, 0x52, 0x40, 0x57, 0xf3, 0x6e, 0xe5,
0x51, 0xed, 0x9f, 0x12, 0x0f, 0x6d, 0x6a, 0x33, 0x1b, 0xea, 0x08, 0x38, 0xef, 0x4c, 0x5f, 0x1f,
0xe2, 0xab, 0xf8, 0xfb, 0xbc, 0x7a, 0x58, 0x47, 0x0b, 0x11, 0x3a, 0x07, 0x20, 0x04, 0x8c, 0x0b,
0x81, 0x97, 0x13, 0xa5, 0xe1, 0xa2, 0x20, 0x17, 0x83, 0xc5, 0xed, 0xf1, 0x5c, 0x00, 0x2f, 0x5a,
0x28, 0x8b, 0xe0, 0x04, 0x82, 0x56, 0x50, 0x30, 0x14, 0x28, 0xa0, 0x2b, 0x79, 0x07, 0x73, 0xa0,
0xe6, 0xaa, 0x7d, 0x1e, 0x89, 0x0e, 0x67, 0x97, 0x9a, 0x3b, 0x78, 0x8b, 0x04, 0x2d, 0x5d, 0xe7,
0x53, 0x36, 0x0b, 0xe4, 0x63, 0xf0, 0x2e, 0x7d, 0x8d, 0x43, 0x29, 0xf9, 0x82, 0xbc, 0xeb, 0x21,
0x5d, 0x34, 0xa0, 0x7d, 0x43, 0x51, 0x87, 0x70, 0xc0, 0x5c, 0x33, 0x68, 0x6d, 0x78, 0xb8, 0x4e,
0xb2, 0x64, 0x68, 0x53, 0x7f, 0x9c, 0x0f, 0xe4, 0x0a, 0x94, 0x5c, 0xa0, 0xb2, 0x1e, 0x69, 0x24,
0x79, 0xc4, 0x2b, 0x69, 0x75, 0x22, 0x03, 0xc5, 0xe1, 0x9b, 0x14, 0x33, 0xc3, 0x89, 0x49, 0x24,
0xb5, 0xa6, 0x35, 0xd5, 0xa1, 0x84, 0x03, 0x73, 0xcd, 0x96, 0x07, 0x53, 0xcc, 0xcf, 0x62, 0x5f,
0xbf, 0xcc, 0x07, 0xe0, 0x16, 0x10, 0x89, 0x55, 0xd6, 0xdc, 0x15, 0x8f, 0xa0, 0x18, 0xef, 0x84,
0xc6, 0xe5, 0x68, 0x0a, 0x25, 0x60, 0x05, 0x49, 0xdb, 0x68, 0xdb, 0xaa, 0xb6, 0x45, 0x48, 0xcb,
0x64, 0xa4, 0xd9, 0x72, 0x3d, 0xec, 0xd9, 0xc4, 0x37, 0x37, 0xf5, 0x2b, 0xdc, 0xe5, 0x57, 0x60,
0x23, 0x00, 0xba, 0x96, 0x81, 0xe0, 0xee, 0x35, 0xde, 0x4b, 0x11, 0x10, 0x6b, 0xb1, 0x17, 0x45,
0x57, 0x27, 0x5f, 0x44, 0x25, 0x2b, 0xda, 0x9e, 0xda, 0x6f, 0x61, 0x6b, 0x93, 0x98, 0xf6, 0x06,
0x75, 0x3d, 0x52, 0x37, 0x1b, 0xb6, 0x43, 0x7c, 0xfd, 0x2a, 0x77, 0x71, 0x01, 0x4e, 0x34, 0x0e,
0x2f, 0x44, 0xe8, 0x3c, 0x80, 0xe9, 0x40, 0x97, 0x90, 0xd2, 0x1e, 0x4c, 0xf7, 0x16, 0x2a, 0x9b,
0xd1, 0xbe, 0xa3, 0xa8, 0x97, 0x5b, 0x9e, 0xbb, 0x01, 0xc5, 0x8c, 0x19, 0xb4, 0xea, 0x98, 0x11,
0xb1, 0x40, 0x78, 0x82, 0xfb, 0xbe, 0x06, 0xf9, 0x6d, 0xa2, 0xb5, 0xce, 0x95, 0xc4, 0x62, 0x20,
0x2a, 0xb2, 0xbb, 0xe0, 0x02, 0x9d, 0x97, 0x84, 0x81, 0x50, 0x5e, 0x42, 0xdd, 0x2c, 0x6a, 0x6f,
0x2b, 0xea, 0xa0, 0x63, 0x37, 0x6d, 0x66, 0xd6, 0x30, 0xad, 0xef, 0xd8, 0x75, 0xb6, 0x69, 0xda,
0xd4, 0x74, 0x30, 0xd5, 0x87, 0xf9, 0x90, 0x2c, 0xf1, 0xe2, 0x11, 0x34, 0x66, 0x12, 0x85, 0x05,
0xba, 0x88, 0x69, 0x56, 0xf0, 0x97, 0xb1, 0x4f, 0x18, 0x16, 0x99, 0x29, 0xed, 0x2d, 0x45, 0xd5,
0x9a, 0x36, 0x35, 0x37, 0xdd, 0x26, 0x31, 0xeb, 0xb6, 0xbf, 0x65, 0x36, 0x3c, 0x42, 0x74, 0x63,
0x44, 0x19, 0x3d, 0x37, 0xd9, 0x73, 0x23, 0xba, 0x59, 0xbb, 0xb1, 0x6a, 0xbf, 0x41, 0x66, 0x5e,
0xfe, 0x20, 0x34, 0x4e, 0xc0, 0x4e, 0x6c, 0xda, 0xf4, 0x15, 0xb7, 0x49, 0xe6, 0x6c, 0x7f, 0x6b,
0xde, 0x23, 0x24, 0x5d, 0x1d, 0x05, 0xb9, 0xb8, 0x0f, 0x46, 0xae, 0x03, 0x91, 0x53, 0x13, 0x23,
0xd7, 0x51, 0xb1, 0xb9, 0x76, 0x5f, 0x51, 0x7b, 0x92, 0xf5, 0xce, 0x8f, 0x9d, 0x11, 0x7e, 0xec,
0xfc, 0x91, 0xa7, 0x3c, 0xc9, 0xa2, 0x8d, 0x0e, 0x9f, 0x73, 0x5e, 0xf6, 0xd9, 0x09, 0x8d, 0xb9,
0xa4, 0xe2, 0x48, 0x64, 0x92, 0x83, 0x28, 0xde, 0x01, 0x7e, 0xe1, 0x4c, 0x69, 0x12, 0x86, 0x6f,
0x7c, 0xd1, 0x77, 0x29, 0xc4, 0xee, 0x9c, 0xd9, 0xfc, 0xe7, 0xf1, 0x41, 0x75, 0xf4, 0x61, 0x4d,
0x41, 0x7e, 0x24, 0xf0, 0x45, 0x99, 0x1d, 0xcf, 0xd1, 0x5e, 0x53, 0xfb, 0xb0, 0xb3, 0x03, 0xd5,
0x57, 0x74, 0x9b, 0x40, 0x09, 0xf3, 0xf5, 0x27, 0xf9, 0x25, 0x1e, 0x14, 0xbd, 0x17, 0x22, 0x90,
0x57, 0xe5, 0xcb, 0x84, 0xc1, 0xc2, 0x1f, 0x88, 0x22, 0x4c, 0x4e, 0x5e, 0x41, 0x45, 0x45, 0xed,
0xbf, 0x8a, 0x3a, 0xea, 0x6e, 0x13, 0x6f, 0xc7, 0xb3, 0x19, 0x04, 0x8e, 0xa6, 0xcb, 0x88, 0x59,
0x27, 0xdb, 0xb6, 0x45, 0x4c, 0x8a, 0x9b, 0xc4, 0x87, 0x70, 0x1a, 0x17, 0x42, 0x7a, 0x25, 0xbb,
0x5e, 0x1a, 0xba, 0x9b, 0x34, 0x42, 0xbc, 0xcd, 0x1c, 0xd9, 0x5e, 0x06, 0xf5, 0x76, 0x68, 0x5c,
0x73, 0x4b, 0x90, 0x6d, 0x11, 0x8e, 0xde, 0xa5, 0xb3, 0x91, 0xa9, 0x4e, 0x68, 0xfc, 0x3f, 0x27,
0xf8, 0x10, 0xba, 0xdd, 0x17, 0x25, 0x54, 0x71, 0x5d, 0x78, 0xa0, 0x87, 0x61, 0xa1, 0x7d, 0x45,
0xbd, 0x04, 0x61, 0xcc, 0xb4, 0x69, 0x9d, 0xec, 0x9a, 0xb0, 0x92, 0x6b, 0x8e, 0x6b, 0x6d, 0xf9,
0xfa, 0x35, 0xbe, 0xa5, 0x61, 0xd1, 0x68, 0xa0, 0xb0, 0x00, 0xf8, 0x92, 0x4d, 0x67, 0x38, 0x9a,
0xde, 0xda, 0x96, 0x21, 0x69, 0xa6, 0x1c, 0xe5, 0xbf, 0x48, 0x62, 0x49, 0xfb, 0x07, 0xa4, 0xbb,
0x14, 0x5b, 0x5b, 0xa4, 0x6e, 0x52, 0x97, 0xd9, 0x0d, 0xdb, 0xc2, 0xd1, 0xfd, 0x43, 0xdd, 0xd7,
0xab, 0x7c, 0x7e, 0xdf, 0x81, 0xe1, 0x1e, 0x5c, 0x8f, 0x94, 0x96, 0x05, 0x9d, 0x85, 0x39, 0x18,
0xed, 0xc1, 0x40, 0x8a, 0x74, 0x42, 0xe3, 0x4a, 0x14, 0xda, 0x65, 0x30, 0xbf, 0xab, 0x94, 0x22,
0x9d, 0x83, 0x6a, 0x17, 0x8b, 0xfb, 0x87, 0xd5, 0x2e, 0x2c, 0x90, 0xb4, 0x45, 0xdd, 0xd7, 0x90,
0x7a, 0x9e, 0x79, 0xb8, 0xd1, 0xb0, 0x2d, 0xd3, 0x72, 0xb0, 0xef, 0xeb, 0xd7, 0xf9, 0xb0, 0xbe,
0x00, 0xf5, 0x72, 0x0c, 0xcc, 0x82, 0xbc, 0x13, 0x1a, 0x5a, 0x34, 0xa0, 0x82, 0x30, 0xbd, 0xa8,
0xc9, 0xa9, 0x6a, 0xf7, 0xd4, 0xfe, 0x78, 0x88, 0xcd, 0x86, 0xeb, 0xd4, 0x89, 0x67, 0xb6, 0x30,
0xdb, 0xd4, 0x9f, 0xe2, 0xbb, 0x7e, 0x1a, 0x8e, 0x81, 0x18, 0x9e, 0xe7, 0xe8, 0x0a, 0x66, 0x9b,
0x69, 0x88, 0x29, 0x21, 0xc2, 0x74, 0xbd, 0x09, 0xcb, 0x4a, 0x79, 0x13, 0x95, 0x9b, 0x6b, 0x5b,
0xea, 0x45, 0x9f, 0x30, 0xd3, 0x71, 0x77, 0xcc, 0x96, 0x67, 0xbb, 0x9e, 0xcd, 0xf6, 0xf4, 0xa7,
0xf9, 0x56, 0x80, 0xfe, 0x7a, 0x7d, 0xc2, 0x16, 0xdd, 0x9d, 0x95, 0x18, 0x49, 0x3b, 0xcb, 0x8b,
0xbb, 0x26, 0x16, 0x85, 0xe6, 0xda, 0xbb, 0x8a, 0x3a, 0xd8, 0xc4, 0xbb, 0x89, 0x73, 0x96, 0x4b,
0xad, 0xc0, 0xf3, 0x08, 0xb5, 0xf6, 0xf4, 0x51, 0x3e, 0x7a, 0x3e, 0xbf, 0x62, 0xc1, 0x3b, 0x4b,
0x78, 0x37, 0xe2, 0x38, 0x9b, 0xa9, 0xc0, 0x41, 0xdf, 0x94, 0xc8, 0xd3, 0x83, 0x5e, 0x06, 0x26,
0x03, 0xcd, 0xef, 0x44, 0xe4, 0x76, 0x91, 0xd4, 0xaa, 0xf6, 0xa1, 0xa2, 0xf6, 0x5b, 0x1e, 0xf6,
0x37, 0x0b, 0x99, 0xff, 0x33, 0x7c, 0x32, 0xde, 0xe3, 0x99, 0xff, 0x6c, 0x92, 0xf9, 0x5b, 0x71,
0xe6, 0x3f, 0x1f, 0x9d, 0xc8, 0xd0, 0x2c, 0xcb, 0xc1, 0xa5, 0xc1, 0x97, 0xeb, 0x94, 0xb3, 0x79,
0x2e, 0x86, 0x15, 0xdc, 0x57, 0x32, 0x02, 0x35, 0x81, 0x15, 0xd7, 0x04, 0xd5, 0x87, 0x31, 0x03,
0x55, 0xc1, 0x6c, 0x54, 0x15, 0x14, 0x8c, 0x79, 0x8e, 0xf6, 0x13, 0x45, 0x1d, 0x2a, 0xba, 0x97,
0x5c, 0xc6, 0x3c, 0xcb, 0xe7, 0xdf, 0x3e, 0x0a, 0x8d, 0xb3, 0xb3, 0x48, 0x78, 0x47, 0xc8, 0x5b,
0x29, 0xbe, 0x23, 0x48, 0xd1, 0x6e, 0x4b, 0x63, 0xff, 0xb0, 0x9a, 0xd9, 0x46, 0x72, 0xcb, 0xda,
0xd7, 0x14, 0x75, 0xd0, 0x67, 0x01, 0x35, 0x21, 0x5f, 0xc2, 0x8e, 0xbd, 0x4d, 0xcc, 0x28, 0x0b,
0xf6, 0xf5, 0xe7, 0xd2, 0x2c, 0xb4, 0x1f, 0x34, 0xee, 0x24, 0x0a, 0xab, 0x80, 0xaf, 0xa6, 0xb9,
0x91, 0x04, 0xcb, 0xa7, 0xf0, 0x42, 0x18, 0x3b, 0x35, 0x71, 0x7b, 0x1c, 0xc9, 0xac, 0x41, 0x65,
0x5c, 0xa0, 0x01, 0xd1, 0xd4, 0xd7, 0x9f, 0xe7, 0x24, 0x5e, 0x85, 0x7d, 0x99, 0x6b, 0xb6, 0x64,
0xd3, 0xac, 0x82, 0x28, 0x21, 0x62, 0x66, 0x98, 0x0b, 0xa3, 0x93, 0xe3, 0xa8, 0x6c, 0x07, 0x72,
0xf1, 0x1e, 0xde, 0x7b, 0xf2, 0xbc, 0xf5, 0x02, 0x8f, 0x9c, 0xf5, 0xa3, 0xd0, 0xe8, 0x45, 0x78,
0x67, 0x95, 0x05, 0xc2, 0xc3, 0xd6, 0x39, 0x3f, 0xfb, 0x4c, 0xaf, 0xa0, 0x32, 0xd9, 0x03, 0x1f,
0xdf, 0x0a, 0x16, 0x91, 0x68, 0x4f, 0xdb, 0x56, 0x2f, 0x40, 0xb1, 0x59, 0xc3, 0x3e, 0x31, 0xa3,
0x97, 0x46, 0xfd, 0xc6, 0x88, 0x32, 0xda, 0x3b, 0xd9, 0x9b, 0x24, 0x43, 0x6b, 0x5c, 0xca, 0xef,
0x0c, 0x7b, 0x13, 0xd5, 0x48, 0x96, 0x85, 0xa9, 0x9c, 0xb8, 0x32, 0x12, 0x97, 0x1e, 0xf1, 0xf2,
0x78, 0xeb, 0xb0, 0xaa, 0xa0, 0x42, 0x53, 0xed, 0xbb, 0x27, 0xd5, 0x6b, 0x10, 0x35, 0xd2, 0x70,
0x01, 0xa5, 0xab, 0xe5, 0x36, 0x61, 0xc9, 0x7a, 0xe4, 0x5e, 0x40, 0x7c, 0x66, 0x6e, 0xd9, 0x35,
0x7d, 0x8c, 0x4f, 0xc7, 0x5f, 0x94, 0xf8, 0x85, 0x72, 0x09, 0xef, 0xce, 0x2e, 0xa0, 0x08, 0xbf,
0x63, 0xcf, 0xb4, 0x43, 0xc3, 0x68, 0xe2, 0xdd, 0x74, 0x8b, 0xb3, 0x85, 0xd8, 0x46, 0xa6, 0x92,
0x9e, 0x7d, 0x0f, 0xd0, 0x13, 0xca, 0xbe, 0x07, 0x9a, 0x7c, 0xb0, 0x4a, 0xfc, 0xe6, 0x59, 0xa0,
0x8b, 0x1e, 0xd0, 0xac, 0xa6, 0x7d, 0xac, 0xa8, 0x83, 0xe9, 0xc3, 0x8b, 0x83, 0xc5, 0xa7, 0xda,
0x71, 0xbe, 0x81, 0xdf, 0x87, 0x91, 0x18, 0x48, 0x1e, 0x2e, 0x16, 0xa7, 0x97, 0xc5, 0xd7, 0xda,
0x01, 0x2c, 0x91, 0xa7, 0xe9, 0xb3, 0x0c, 0x94, 0xbd, 0x97, 0x49, 0x8d, 0x74, 0x91, 0x0b, 0x5b,
0x5f, 0x4a, 0x0a, 0x65, 0xad, 0xb0, 0xf0, 0xd4, 0xbb, 0xad, 0x5e, 0xe6, 0x6f, 0x2b, 0x8d, 0xc0,
0x71, 0xe2, 0x5c, 0xc6, 0xa5, 0x49, 0x61, 0xaa, 0x4f, 0x70, 0x4f, 0xa7, 0x20, 0x57, 0x00, 0xad,
0xf9, 0xc0, 0x71, 0x78, 0x16, 0x72, 0x97, 0xc6, 0xa5, 0x64, 0x27, 0x34, 0xae, 0xc6, 0x47, 0x96,
0x0c, 0xae, 0xa0, 0x2e, 0xed, 0xb4, 0x57, 0xd5, 0xf3, 0x0d, 0x82, 0x59, 0xe0, 0x11, 0xb3, 0xe1,
0xe0, 0x0d, 0x5f, 0x9f, 0xe4, 0xfb, 0xee, 0x3a, 0x9c, 0xef, 0x31, 0x30, 0x0f, 0xf2, 0xf4, 0x1d,
0x46, 0x10, 0x56, 0x50, 0x4e, 0x45, 0xfb, 0xb2, 0xda, 0x13, 0xb4, 0x68, 0x2b, 0x0d, 0xb0, 0xbf,
0x98, 0xe7, 0xb4, 0x3f, 0x77, 0x14, 0x1a, 0x97, 0xe6, 0x48, 0xcb, 0x23, 0x16, 0x66, 0xa4, 0xbe,
0xbe, 0x42, 0x57, 0xb2, 0x68, 0xab, 0xbc, 0x90, 0xa5, 0x39, 0x2d, 0xda, 0x8a, 0x81, 0xe7, 0xdd,
0xa6, 0x0d, 0xa9, 0x16, 0xdb, 0xab, 0xec, 0x1f, 0x56, 0xe5, 0x8d, 0x75, 0x05, 0x9d, 0x13, 0x9a,
0x68, 0x3f, 0x53, 0xe2, 0xee, 0x93, 0x4b, 0xec, 0x77, 0xe7, 0xf9, 0x4e, 0x79, 0x8b, 0xaf, 0x8f,
0xbc, 0x89, 0xf4, 0x42, 0x9b, 0x77, 0x3f, 0x92, 0x76, 0x2f, 0x5e, 0x44, 0x0b, 0x1c, 0xb2, 0x8d,
0x70, 0xb9, 0xbb, 0x16, 0x4c, 0xb8, 0xac, 0x17, 0x5d, 0x41, 0x6a, 0xd6, 0x4a, 0xfb, 0x8d, 0xa2,
0xf6, 0x72, 0x9a, 0xd9, 0x75, 0xf5, 0x2f, 0x23, 0xa2, 0xdf, 0xe4, 0x59, 0x62, 0xde, 0x84, 0x70,
0x75, 0xcd, 0xa9, 0x56, 0x52, 0xaa, 0xf9, 0xcb, 0x66, 0x29, 0xd9, 0xab, 0x9f, 0xa4, 0x07, 0xb9,
0xa0, 0xbc, 0x2f, 0x5d, 0x41, 0x3d, 0x62, 0xcb, 0x8c, 0x72, 0x76, 0x29, 0xfd, 0x5e, 0x77, 0xca,
0xc2, 0x05, 0x75, 0x81, 0x72, 0xfe, 0x4a, 0xb9, 0x3b, 0xe5, 0x6e, 0x7a, 0x65, 0xca, 0x89, 0x66,
0x42, 0x39, 0xbd, 0x83, 0x6e, 0xa8, 0xd1, 0xe3, 0x57, 0x7a, 0x9c, 0xfc, 0x6a, 0x9e, 0xaf, 0xeb,
0xcf, 0xe4, 0xf9, 0xf2, 0xf7, 0xa3, 0xec, 0x5c, 0x11, 0x16, 0xa3, 0x97, 0x21, 0x02, 0x51, 0xe8,
0x47, 0x40, 0x7c, 0x5e, 0xc2, 0x97, 0xab, 0x67, 0xb3, 0x65, 0x31, 0xfd, 0x7d, 0x18, 0x22, 0x65,
0x66, 0xe9, 0x28, 0x34, 0xae, 0x66, 0x3d, 0x2e, 0xe5, 0x6b, 0xdf, 0x15, 0x8b, 0xe5, 0xc7, 0xa9,
0x59, 0xc2, 0xf3, 0xdd, 0x6b, 0x65, 0x05, 0x38, 0x3b, 0x07, 0x0a, 0x27, 0x87, 0x6f, 0x61, 0xea,
0xeb, 0xbf, 0x8e, 0x66, 0x69, 0xad, 0x40, 0x41, 0x8c, 0xb8, 0xab, 0xa0, 0x58, 0xa0, 0x50, 0xc2,
0xcb, 0x53, 0xc5, 0x99, 0x94, 0xf4, 0x66, 0xee, 0x7c, 0xf0, 0xd1, 0xf0, 0x89, 0xc3, 0x8f, 0x86,
0x4f, 0x7c, 0x70, 0x34, 0xac, 0x1c, 0x1e, 0x0d, 0x2b, 0xdf, 0xbe, 0x3f, 0x7c, 0xe2, 0x9d, 0xfb,
0xc3, 0xca, 0xe1, 0xfd, 0xe1, 0x13, 0x7f, 0xbf, 0x3f, 0x7c, 0xe2, 0xf5, 0x67, 0x36, 0x6c, 0xb6,
0x19, 0xd4, 0x6e, 0x58, 0x6e, 0x73, 0x2c, 0xcd, 0xe7, 0x84, 0x5f, 0xd9, 0xbf, 0x79, 0x6a, 0x67,
0xf8, 0xdf, 0x77, 0x6e, 0xfe, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x64, 0xe8, 0xf8, 0x5e, 0x2a, 0x24,
0x00, 0x00,
}
func (m *OptionsConfiguration) Marshal() (dAtA []byte, err error) {
@ -415,6 +419,17 @@ func (m *OptionsConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0xc0
}
if len(m.FeatureFlags) > 0 {
for iNdEx := len(m.FeatureFlags) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.FeatureFlags[iNdEx])
copy(dAtA[i:], m.FeatureFlags[iNdEx])
i = encodeVarintOptionsconfiguration(dAtA, i, uint64(len(m.FeatureFlags[iNdEx])))
i--
dAtA[i] = 0x3
i--
dAtA[i] = 0x92
}
}
if m.SendFullIndexOnUpgrade {
i--
if m.SendFullIndexOnUpgrade {
@ -1019,6 +1034,12 @@ func (m *OptionsConfiguration) ProtoSize() (n int) {
if m.SendFullIndexOnUpgrade {
n += 3
}
if len(m.FeatureFlags) > 0 {
for _, s := range m.FeatureFlags {
l = len(s)
n += 2 + l + sovOptionsconfiguration(uint64(l))
}
}
if m.DeprecatedUPnPEnabled {
n += 4
}
@ -2165,6 +2186,38 @@ func (m *OptionsConfiguration) Unmarshal(dAtA []byte) error {
}
}
m.SendFullIndexOnUpgrade = bool(v != 0)
case 50:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field FeatureFlags", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowOptionsconfiguration
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthOptionsconfiguration
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthOptionsconfiguration
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.FeatureFlags = append(m.FeatureFlags, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 9000:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedUPnPEnabled", wireType)

View File

@ -45,5 +45,6 @@
<stunServer>foo</stunServer>
<unackedNotificationID>asdfasdf</unackedNotificationID>
<announceLANAddresses>false</announceLANAddresses>
<featureFlag>feature</featureFlag>
</options>
</configuration>

View File

@ -75,6 +75,7 @@ type Wrapper interface {
RemoveFolder(id string) (Waiter, error)
SetFolder(fld FolderConfiguration) (Waiter, error)
SetFolders(folders []FolderConfiguration) (Waiter, error)
FolderPasswords(device protocol.DeviceID) map[string]string
Device(id protocol.DeviceID) (DeviceConfiguration, bool)
Devices() map[protocol.DeviceID]DeviceConfiguration
@ -353,6 +354,14 @@ func (w *wrapper) RemoveFolder(id string) (Waiter, error) {
return noopWaiter{}, nil
}
// FolderPasswords returns the folder passwords set for this device, for
// folders that have an encryption password set.
func (w *wrapper) FolderPasswords(device protocol.DeviceID) map[string]string {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.FolderPasswords(device)
}
// Options returns the current options configuration object.
func (w *wrapper) Options() OptionsConfiguration {
w.mut.Lock()

View File

@ -324,7 +324,13 @@ func (s *service) handle(ctx context.Context) {
isLAN := s.isLAN(c.RemoteAddr())
rd, wr := s.limiter.getLimiters(remoteID, c, isLAN)
protoConn := protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
var protoConn protocol.Connection
passwords := s.cfg.FolderPasswords(remoteID)
if len(passwords) > 0 {
protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
} else {
protoConn = protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression)
}
modelConn := completeConn{c, protoConn}
l.Infof("Established secure connection to %s at %s", remoteID, c)

View File

@ -113,6 +113,7 @@ type FileInfoTruncated struct {
// repeated BlockInfo Blocks = 16
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlinkTarget" xml:"symlinkTarget"`
BlocksHash []byte `protobuf:"bytes,18,opt,name=blocks_hash,json=blocksHash,proto3" json:"blocksHash" xml:"blocksHash"`
Encrypted []byte `protobuf:"bytes,19,opt,name=encrypted,proto3" json:"encrypted" xml:"encrypted"`
Type protocol.FileInfoType `protobuf:"varint,2,opt,name=type,proto3,enum=protocol.FileInfoType" json:"type" xml:"type"`
Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions" xml:"permissions"`
ModifiedNs int `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3,casttype=int" json:"modifiedNs" xml:"modifiedNs"`
@ -409,87 +410,88 @@ func init() {
func init() { proto.RegisterFile("lib/db/structs.proto", fileDescriptor_5465d80e8cba02e3) }
var fileDescriptor_5465d80e8cba02e3 = []byte{
// 1267 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x6f, 0x13, 0xc7,
0x17, 0xf7, 0xc6, 0x3f, 0x62, 0x8f, 0x9d, 0x40, 0x96, 0x2f, 0x68, 0xbf, 0xb4, 0xf5, 0xb8, 0x43,
0x90, 0xdc, 0x56, 0x72, 0xa4, 0x20, 0x50, 0x85, 0x54, 0x21, 0x96, 0x08, 0x08, 0xa2, 0x50, 0x4d,
0x10, 0xad, 0x7a, 0xb1, 0xbc, 0xeb, 0x49, 0xb2, 0x62, 0xb3, 0xeb, 0xee, 0x6c, 0x08, 0xe6, 0xd6,
0x4b, 0xa5, 0xde, 0x2a, 0xd4, 0x43, 0x55, 0x55, 0x15, 0xa7, 0xfe, 0x09, 0x55, 0xff, 0x04, 0x8e,
0x39, 0x56, 0x3d, 0xac, 0x84, 0x73, 0x69, 0x7d, 0xf4, 0xb1, 0xa7, 0x6a, 0xde, 0xcc, 0xce, 0x8e,
0x89, 0xa8, 0x80, 0x72, 0xdb, 0xf7, 0x79, 0x9f, 0xf7, 0xbc, 0xfb, 0xde, 0xe7, 0x3d, 0x3f, 0xf4,
0xbf, 0x30, 0xf0, 0xd6, 0x86, 0xde, 0x1a, 0x4f, 0x93, 0x7d, 0x3f, 0xe5, 0xbd, 0x51, 0x12, 0xa7,
0xb1, 0xbd, 0x30, 0xf4, 0xce, 0x9e, 0x4b, 0xd8, 0x28, 0xe6, 0x6b, 0x00, 0x78, 0xfb, 0xdb, 0x6b,
0x3b, 0xf1, 0x4e, 0x0c, 0x06, 0x3c, 0x49, 0xe2, 0xd9, 0x33, 0x22, 0x1c, 0x1e, 0xfd, 0x38, 0x5c,
0xf3, 0xd8, 0x48, 0xe1, 0x0d, 0xf6, 0x28, 0x95, 0x8f, 0xe4, 0xe7, 0x05, 0xd4, 0xbc, 0x1e, 0x84,
0xec, 0x3e, 0x4b, 0x78, 0x10, 0x47, 0xf6, 0x6d, 0xb4, 0xf8, 0x50, 0x3e, 0x3a, 0x56, 0xc7, 0xea,
0x36, 0xd7, 0x4f, 0xf6, 0xf2, 0x04, 0xbd, 0xfb, 0xcc, 0x4f, 0xe3, 0xc4, 0xed, 0x3c, 0xcb, 0x70,
0x69, 0x9a, 0xe1, 0x9c, 0x38, 0xcb, 0xf0, 0xd2, 0xa3, 0xbd, 0xf0, 0x32, 0x51, 0x36, 0xa1, 0xb9,
0xc7, 0xbe, 0x84, 0x16, 0x87, 0x2c, 0x64, 0x29, 0x1b, 0x3a, 0x0b, 0x1d, 0xab, 0x5b, 0x77, 0xdf,
0x15, 0x71, 0x0a, 0xd2, 0x71, 0xca, 0x26, 0x34, 0xf7, 0xd8, 0x17, 0x45, 0xdc, 0xc3, 0xc0, 0x67,
0xdc, 0x29, 0x77, 0xca, 0xdd, 0x96, 0xfb, 0x8e, 0x8c, 0x03, 0x68, 0x96, 0xe1, 0x96, 0x8a, 0x13,
0x36, 0x84, 0x81, 0xc3, 0xa6, 0xe8, 0x44, 0x10, 0x3d, 0x1c, 0x84, 0xc1, 0xb0, 0x9f, 0x87, 0x57,
0x20, 0xfc, 0x83, 0x69, 0x86, 0x97, 0x95, 0x6b, 0x43, 0x67, 0x39, 0x05, 0x59, 0xe6, 0x60, 0x42,
0x5f, 0xa0, 0x91, 0xaf, 0x2d, 0xd4, 0x54, 0xc5, 0xb9, 0x1d, 0xf0, 0xd4, 0x0e, 0x51, 0x5d, 0x7d,
0x1d, 0x77, 0xac, 0x4e, 0xb9, 0xdb, 0x5c, 0x3f, 0xd1, 0x1b, 0x7a, 0x3d, 0xa3, 0x86, 0xee, 0x15,
0x51, 0xa0, 0x49, 0x86, 0x9b, 0x74, 0x70, 0xa0, 0x30, 0x3e, 0xcd, 0xb0, 0x8e, 0x3b, 0x56, 0xb0,
0x27, 0x87, 0xab, 0x26, 0x97, 0x6a, 0xe6, 0xe5, 0xca, 0x0f, 0x4f, 0x71, 0x89, 0xfc, 0x86, 0xd0,
0x8a, 0xf8, 0x81, 0xcd, 0x68, 0x3b, 0xbe, 0x97, 0xec, 0x47, 0xfe, 0x40, 0x14, 0xe9, 0x43, 0x54,
0x89, 0x06, 0x7b, 0x0c, 0xfa, 0xd4, 0x70, 0xcf, 0x4c, 0x33, 0x0c, 0xf6, 0x2c, 0xc3, 0x08, 0xb2,
0x0b, 0x83, 0x50, 0xc0, 0x04, 0x97, 0x07, 0x8f, 0x99, 0x53, 0xee, 0x58, 0xdd, 0xb2, 0xe4, 0x0a,
0x5b, 0x73, 0x85, 0x41, 0x28, 0x60, 0xf6, 0x15, 0x84, 0xf6, 0xe2, 0x61, 0xb0, 0x1d, 0xb0, 0x61,
0x9f, 0x3b, 0x55, 0x88, 0xe8, 0x4c, 0x33, 0xdc, 0xc8, 0xd1, 0xad, 0x59, 0x86, 0x4f, 0x40, 0x98,
0x46, 0x08, 0x2d, 0xbc, 0xf6, 0xaf, 0x16, 0x6a, 0xea, 0x0c, 0xde, 0xd8, 0x69, 0x75, 0xac, 0x6e,
0xc5, 0xfd, 0xde, 0x12, 0x65, 0xf9, 0x23, 0xc3, 0x17, 0x76, 0x82, 0x74, 0x77, 0xdf, 0xeb, 0xf9,
0xf1, 0xde, 0x1a, 0x1f, 0x47, 0x7e, 0xba, 0x1b, 0x44, 0x3b, 0xc6, 0x93, 0x29, 0xda, 0xde, 0xd6,
0x6e, 0x9c, 0xa4, 0x9b, 0x1b, 0xd3, 0x0c, 0xeb, 0x97, 0x72, 0xc7, 0xb3, 0x0c, 0x9f, 0x9c, 0xfb,
0x7d, 0x77, 0x4c, 0x7e, 0x3c, 0x5c, 0x7d, 0x93, 0xc4, 0xd4, 0x48, 0x6b, 0x8a, 0xbf, 0xf1, 0xdf,
0xc5, 0x7f, 0x19, 0xd5, 0x39, 0xfb, 0x6a, 0x9f, 0x45, 0x3e, 0x73, 0x10, 0x54, 0xb1, 0x2d, 0x54,
0x90, 0x63, 0xb3, 0x0c, 0x2f, 0xcb, 0xda, 0x2b, 0x80, 0x50, 0xed, 0xb3, 0xef, 0xa2, 0x65, 0x3e,
0xde, 0x0b, 0x83, 0xe8, 0x41, 0x3f, 0x1d, 0x24, 0x3b, 0x2c, 0x75, 0x56, 0xa0, 0xcb, 0xdd, 0x69,
0x86, 0x97, 0x94, 0xe7, 0x1e, 0x38, 0xb4, 0x8e, 0xe7, 0x50, 0x42, 0xe7, 0x59, 0xf6, 0x35, 0xd4,
0xf4, 0xc2, 0xd8, 0x7f, 0xc0, 0xfb, 0xbb, 0x03, 0xbe, 0xeb, 0xd8, 0x1d, 0xab, 0xdb, 0x72, 0x89,
0x28, 0xab, 0x84, 0x6f, 0x0e, 0xf8, 0xae, 0x2e, 0x6b, 0x01, 0x11, 0x6a, 0xf8, 0x6d, 0x17, 0x55,
0xd2, 0xf1, 0x88, 0xc1, 0x2c, 0x2f, 0xaf, 0x9f, 0x29, 0x8a, 0xa3, 0xc5, 0x39, 0x1e, 0x31, 0xa9,
0x2e, 0xc1, 0xd3, 0xea, 0x12, 0x06, 0xa1, 0x80, 0xd9, 0xd7, 0x51, 0x73, 0xc4, 0x92, 0xbd, 0x80,
0xcb, 0x11, 0xaa, 0x74, 0xac, 0xee, 0x92, 0xbb, 0x3a, 0xcd, 0xb0, 0x09, 0xcf, 0x32, 0xbc, 0x02,
0x91, 0x06, 0x46, 0xa8, 0xc9, 0xb0, 0x6f, 0x19, 0x1a, 0x8b, 0xb8, 0xd3, 0xec, 0x58, 0xdd, 0x2a,
0xcc, 0xb9, 0x6e, 0xe8, 0x1d, 0x7e, 0x4c, 0x27, 0x77, 0x38, 0xf9, 0x3b, 0xc3, 0xe5, 0x20, 0x4a,
0xa9, 0x41, 0xb3, 0xb7, 0x91, 0xfc, 0xca, 0x3e, 0xcc, 0xc8, 0x12, 0xa4, 0xba, 0x31, 0xc9, 0x70,
0x8b, 0x0e, 0x0e, 0x5c, 0xe1, 0xd8, 0x0a, 0x1e, 0x33, 0x31, 0x01, 0x5e, 0x6e, 0xe8, 0x09, 0xd0,
0x48, 0x9e, 0xf8, 0xc9, 0xe1, 0xea, 0x5c, 0x18, 0x2d, 0x82, 0xec, 0x0d, 0xd4, 0x0c, 0x63, 0x7f,
0x10, 0xf6, 0xb7, 0xc3, 0xc1, 0x0e, 0x77, 0xfe, 0x5c, 0x84, 0x8f, 0x87, 0x2e, 0x00, 0x7e, 0x5d,
0xc0, 0xfa, 0xa5, 0x0b, 0x88, 0x50, 0xc3, 0x6f, 0xdf, 0x44, 0x2d, 0x25, 0x31, 0xd9, 0xcb, 0xbf,
0x16, 0xa1, 0x99, 0x50, 0x43, 0xe5, 0x50, 0xdd, 0x5c, 0x31, 0x95, 0x29, 0xdb, 0x69, 0x32, 0xcc,
0xf5, 0x5c, 0x7b, 0x9d, 0xf5, 0x4c, 0xd1, 0xa2, 0xda, 0x92, 0xce, 0x22, 0xc4, 0x7d, 0x3c, 0xc9,
0x30, 0xa2, 0x83, 0x83, 0x4d, 0x89, 0x8a, 0x2c, 0x8a, 0xa0, 0xb3, 0x28, 0x5b, 0xec, 0x3a, 0x83,
0x49, 0x73, 0x9e, 0x50, 0x7c, 0x14, 0xf7, 0x4d, 0x69, 0xd4, 0x21, 0x35, 0x28, 0x3e, 0x8a, 0x3f,
0x9b, 0x13, 0x87, 0x54, 0xfc, 0x1c, 0x4a, 0xe8, 0x3c, 0x4b, 0xad, 0xce, 0xcf, 0x51, 0x03, 0x5a,
0x01, 0xbb, 0xfb, 0x16, 0xaa, 0x49, 0x35, 0xab, 0xcd, 0x7d, 0xaa, 0x50, 0x30, 0x90, 0x84, 0x84,
0xdd, 0xf7, 0xd4, 0x84, 0x2b, 0xea, 0x2c, 0xc3, 0xcd, 0xa2, 0xd3, 0x84, 0x2a, 0x98, 0xfc, 0x62,
0xa1, 0xd3, 0x9b, 0xd1, 0x30, 0x48, 0x98, 0x9f, 0xaa, 0x7a, 0x32, 0x7e, 0x37, 0x0a, 0xc7, 0x6f,
0x67, 0xd4, 0xde, 0x5a, 0x93, 0xc9, 0x4f, 0x15, 0x54, 0xbb, 0x16, 0xef, 0x47, 0x29, 0xb7, 0x2f,
0xa2, 0xea, 0x76, 0x10, 0x32, 0x0e, 0x7f, 0x19, 0x55, 0x17, 0x4f, 0x33, 0x2c, 0x01, 0xfd, 0x91,
0x60, 0xe9, 0x19, 0x91, 0x4e, 0xfb, 0x53, 0xd4, 0x94, 0xdf, 0x19, 0x27, 0x01, 0xe3, 0x30, 0xfd,
0x55, 0xf7, 0x23, 0xf1, 0x26, 0x06, 0xac, 0xdf, 0xc4, 0xc0, 0x74, 0x22, 0x93, 0x68, 0x5f, 0x45,
0x75, 0xb5, 0x9b, 0x38, 0xfc, 0x1f, 0x55, 0xdd, 0xf3, 0xb0, 0x17, 0x15, 0x56, 0xec, 0x45, 0x05,
0xe8, 0x2c, 0x9a, 0x62, 0x7f, 0x52, 0x08, 0xb7, 0x02, 0x19, 0xce, 0xfd, 0x9b, 0x70, 0xf3, 0x78,
0xad, 0xdf, 0x1e, 0xaa, 0x7a, 0xe3, 0x94, 0xe5, 0x7f, 0x6e, 0x8e, 0xa8, 0x03, 0x00, 0x45, 0xb3,
0x85, 0x45, 0xa8, 0x44, 0xe7, 0x36, 0x79, 0xed, 0x35, 0x37, 0xf9, 0x16, 0x6a, 0xc8, 0x5b, 0xa4,
0x1f, 0x0c, 0x61, 0x89, 0xb7, 0xdc, 0x4b, 0x93, 0x0c, 0xd7, 0xe5, 0x7d, 0x01, 0xff, 0x6c, 0x75,
0x49, 0xd8, 0x1c, 0xea, 0x44, 0x39, 0x20, 0xa6, 0x45, 0x33, 0xa9, 0xe6, 0x09, 0x89, 0x99, 0x8b,
0xc4, 0x7e, 0x93, 0x3d, 0xa2, 0x06, 0xe4, 0x1b, 0x0b, 0x35, 0xa4, 0x3c, 0xb6, 0x58, 0x6a, 0x5f,
0x45, 0x35, 0x1f, 0x0c, 0x35, 0x21, 0x48, 0xdc, 0x36, 0xd2, 0x5d, 0x0c, 0x86, 0x64, 0xe8, 0x5a,
0x81, 0x49, 0xa8, 0x82, 0xc5, 0x52, 0xf1, 0x13, 0x36, 0xc8, 0x6f, 0xbe, 0xb2, 0x5c, 0x2a, 0x0a,
0xd2, 0xbd, 0x51, 0x36, 0xa1, 0xb9, 0x87, 0x7c, 0xbb, 0x80, 0x4e, 0x1b, 0x57, 0xd4, 0x06, 0x1b,
0x25, 0x4c, 0x1e, 0x3a, 0x6f, 0xf7, 0x26, 0x5d, 0x47, 0x35, 0x59, 0x47, 0x78, 0xbd, 0x96, 0x7b,
0x56, 0x7c, 0x92, 0x44, 0x8e, 0x5d, 0x96, 0x0a, 0x17, 0xdf, 0x94, 0x2f, 0xbc, 0x72, 0xb1, 0x28,
0x5f, 0xb6, 0xe2, 0x8a, 0xa5, 0x76, 0x69, 0x5e, 0xa7, 0xaf, 0xba, 0x60, 0xc9, 0x01, 0x3a, 0x6d,
0xdc, 0x9c, 0x46, 0x29, 0xbe, 0x38, 0x76, 0x7d, 0xfe, 0xff, 0x85, 0xeb, 0xb3, 0x20, 0xbb, 0xef,
0xab, 0xa2, 0xbc, 0xfc, 0xf0, 0x7c, 0xf1, 0xd2, 0x74, 0x6f, 0x3c, 0x7b, 0xde, 0x2e, 0x1d, 0x3e,
0x6f, 0x97, 0x9e, 0x4d, 0xda, 0xd6, 0xe1, 0xa4, 0x6d, 0x7d, 0x77, 0xd4, 0x2e, 0x3d, 0x3d, 0x6a,
0x5b, 0x87, 0x47, 0xed, 0xd2, 0xef, 0x47, 0xed, 0xd2, 0x97, 0xe7, 0x5f, 0xe1, 0xc8, 0x1a, 0x7a,
0x5e, 0x0d, 0x3a, 0x74, 0xe1, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x62, 0x5e, 0x6e, 0xcc, 0xc2,
0x0c, 0x00, 0x00,
// 1289 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x6f, 0xdc, 0x44,
0x14, 0x5e, 0x67, 0x7f, 0x64, 0x77, 0x76, 0x93, 0x36, 0x2e, 0xad, 0x4c, 0x81, 0x9d, 0x65, 0x9a,
0x4a, 0x0b, 0x48, 0x1b, 0x29, 0x55, 0x2b, 0x54, 0x09, 0xaa, 0xba, 0x51, 0xdb, 0x54, 0xa5, 0x45,
0x93, 0xaa, 0x20, 0x2e, 0xab, 0xb5, 0x77, 0x92, 0x58, 0x75, 0xec, 0xc5, 0xe3, 0x34, 0xdd, 0xde,
0xb8, 0x20, 0x71, 0x43, 0x15, 0x07, 0x84, 0x10, 0xea, 0x89, 0x3f, 0x81, 0xbf, 0xa1, 0xc7, 0x1c,
0x11, 0x07, 0x4b, 0x4d, 0x2e, 0xb0, 0xc7, 0x3d, 0x21, 0x4e, 0x68, 0xde, 0x8c, 0xc7, 0xb3, 0x8d,
0x8a, 0xda, 0x92, 0x9b, 0xdf, 0xf7, 0xbe, 0xf7, 0x6c, 0xbf, 0xf9, 0xde, 0x9b, 0x87, 0xde, 0x0a,
0x03, 0x6f, 0x65, 0xe8, 0xad, 0xf0, 0x34, 0xd9, 0xf5, 0x53, 0xde, 0x1b, 0x25, 0x71, 0x1a, 0xdb,
0x73, 0x43, 0xef, 0xec, 0xb9, 0x84, 0x8d, 0x62, 0xbe, 0x02, 0x80, 0xb7, 0xbb, 0xb9, 0xb2, 0x15,
0x6f, 0xc5, 0x60, 0xc0, 0x93, 0x24, 0x9e, 0x3d, 0x23, 0xc2, 0xe1, 0xd1, 0x8f, 0xc3, 0x15, 0x8f,
0x8d, 0x14, 0xde, 0x60, 0x8f, 0x52, 0xf9, 0x48, 0x7e, 0x99, 0x43, 0xcd, 0xeb, 0x41, 0xc8, 0xee,
0xb3, 0x84, 0x07, 0x71, 0x64, 0xdf, 0x46, 0xf3, 0x0f, 0xe5, 0xa3, 0x63, 0x75, 0xac, 0x6e, 0x73,
0xf5, 0x64, 0x2f, 0x4f, 0xd0, 0xbb, 0xcf, 0xfc, 0x34, 0x4e, 0xdc, 0xce, 0xb3, 0x0c, 0x97, 0x26,
0x19, 0xce, 0x89, 0xd3, 0x0c, 0x2f, 0x3c, 0xda, 0x09, 0x2f, 0x13, 0x65, 0x13, 0x9a, 0x7b, 0xec,
0x4b, 0x68, 0x7e, 0xc8, 0x42, 0x96, 0xb2, 0xa1, 0x33, 0xd7, 0xb1, 0xba, 0x75, 0xf7, 0x5d, 0x11,
0xa7, 0x20, 0x1d, 0xa7, 0x6c, 0x42, 0x73, 0x8f, 0x7d, 0x51, 0xc4, 0x3d, 0x0c, 0x7c, 0xc6, 0x9d,
0x72, 0xa7, 0xdc, 0x6d, 0xb9, 0xef, 0xc8, 0x38, 0x80, 0xa6, 0x19, 0x6e, 0xa9, 0x38, 0x61, 0x43,
0x18, 0x38, 0x6c, 0x8a, 0x4e, 0x04, 0xd1, 0xc3, 0x41, 0x18, 0x0c, 0xfb, 0x79, 0x78, 0x05, 0xc2,
0x3f, 0x98, 0x64, 0x78, 0x51, 0xb9, 0xd6, 0x74, 0x96, 0x53, 0x90, 0x65, 0x06, 0x26, 0xf4, 0x05,
0x1a, 0xf9, 0xc6, 0x42, 0x4d, 0x55, 0x9c, 0xdb, 0x01, 0x4f, 0xed, 0x10, 0xd5, 0xd5, 0xdf, 0x71,
0xc7, 0xea, 0x94, 0xbb, 0xcd, 0xd5, 0x13, 0xbd, 0xa1, 0xd7, 0x33, 0x6a, 0xe8, 0x5e, 0x11, 0x05,
0x3a, 0xc8, 0x70, 0x93, 0x0e, 0xf6, 0x14, 0xc6, 0x27, 0x19, 0xd6, 0x71, 0x47, 0x0a, 0xf6, 0x64,
0x7f, 0xd9, 0xe4, 0x52, 0xcd, 0xbc, 0x5c, 0xf9, 0xf1, 0x29, 0x2e, 0x91, 0xbf, 0x11, 0x5a, 0x12,
0x2f, 0x58, 0x8f, 0x36, 0xe3, 0x7b, 0xc9, 0x6e, 0xe4, 0x0f, 0x44, 0x91, 0x3e, 0x44, 0x95, 0x68,
0xb0, 0xc3, 0xe0, 0x9c, 0x1a, 0xee, 0x99, 0x49, 0x86, 0xc1, 0x9e, 0x66, 0x18, 0x41, 0x76, 0x61,
0x10, 0x0a, 0x98, 0xe0, 0xf2, 0xe0, 0x31, 0x73, 0xca, 0x1d, 0xab, 0x5b, 0x96, 0x5c, 0x61, 0x6b,
0xae, 0x30, 0x08, 0x05, 0xcc, 0xbe, 0x82, 0xd0, 0x4e, 0x3c, 0x0c, 0x36, 0x03, 0x36, 0xec, 0x73,
0xa7, 0x0a, 0x11, 0x9d, 0x49, 0x86, 0x1b, 0x39, 0xba, 0x31, 0xcd, 0xf0, 0x09, 0x08, 0xd3, 0x08,
0xa1, 0x85, 0xd7, 0xfe, 0xcd, 0x42, 0x4d, 0x9d, 0xc1, 0x1b, 0x3b, 0xad, 0x8e, 0xd5, 0xad, 0xb8,
0x3f, 0x58, 0xa2, 0x2c, 0x7f, 0x64, 0xf8, 0xc2, 0x56, 0x90, 0x6e, 0xef, 0x7a, 0x3d, 0x3f, 0xde,
0x59, 0xe1, 0xe3, 0xc8, 0x4f, 0xb7, 0x83, 0x68, 0xcb, 0x78, 0x32, 0x45, 0xdb, 0xdb, 0xd8, 0x8e,
0x93, 0x74, 0x7d, 0x6d, 0x92, 0x61, 0xfd, 0x51, 0xee, 0x78, 0x9a, 0xe1, 0x93, 0x33, 0xef, 0x77,
0xc7, 0xe4, 0xa7, 0xfd, 0xe5, 0x37, 0x49, 0x4c, 0x8d, 0xb4, 0xa6, 0xf8, 0x1b, 0xff, 0x5f, 0xfc,
0x97, 0x51, 0x9d, 0xb3, 0xaf, 0x77, 0x59, 0xe4, 0x33, 0x07, 0x41, 0x15, 0xdb, 0x42, 0x05, 0x39,
0x36, 0xcd, 0xf0, 0xa2, 0xac, 0xbd, 0x02, 0x08, 0xd5, 0x3e, 0xfb, 0x2e, 0x5a, 0xe4, 0xe3, 0x9d,
0x30, 0x88, 0x1e, 0xf4, 0xd3, 0x41, 0xb2, 0xc5, 0x52, 0x67, 0x09, 0x4e, 0xb9, 0x3b, 0xc9, 0xf0,
0x82, 0xf2, 0xdc, 0x03, 0x87, 0xd6, 0xf1, 0x0c, 0x4a, 0xe8, 0x2c, 0xcb, 0xbe, 0x86, 0x9a, 0x5e,
0x18, 0xfb, 0x0f, 0x78, 0x7f, 0x7b, 0xc0, 0xb7, 0x1d, 0xbb, 0x63, 0x75, 0x5b, 0x2e, 0x11, 0x65,
0x95, 0xf0, 0xcd, 0x01, 0xdf, 0xd6, 0x65, 0x2d, 0x20, 0x42, 0x0d, 0xbf, 0xfd, 0x29, 0x6a, 0xb0,
0xc8, 0x4f, 0xc6, 0x23, 0xd1, 0xd0, 0xa7, 0x20, 0x05, 0x08, 0x43, 0x83, 0x5a, 0x18, 0x1a, 0x21,
0xb4, 0xf0, 0xda, 0x2e, 0xaa, 0xa4, 0xe3, 0x11, 0x83, 0x59, 0xb0, 0xb8, 0x7a, 0xa6, 0x28, 0xae,
0x16, 0xf7, 0x78, 0xc4, 0xa4, 0x3a, 0x05, 0x4f, 0xab, 0x53, 0x18, 0x84, 0x02, 0x66, 0x5f, 0x47,
0xcd, 0x11, 0x4b, 0x76, 0x02, 0x2e, 0x5b, 0xb0, 0xd2, 0xb1, 0xba, 0x0b, 0xee, 0xf2, 0x24, 0xc3,
0x26, 0x3c, 0xcd, 0xf0, 0x12, 0x44, 0x1a, 0x18, 0xa1, 0x26, 0xc3, 0xbe, 0x65, 0x68, 0x34, 0xe2,
0x4e, 0xb3, 0x63, 0x75, 0xab, 0x30, 0x27, 0xb4, 0x20, 0xee, 0xf0, 0x23, 0x3a, 0xbb, 0xc3, 0xc9,
0x3f, 0x19, 0x2e, 0x07, 0x51, 0x4a, 0x0d, 0x9a, 0xbd, 0x89, 0x64, 0x95, 0xfa, 0xd0, 0x63, 0x0b,
0x90, 0xea, 0xc6, 0x41, 0x86, 0x5b, 0x74, 0xb0, 0xe7, 0x0a, 0xc7, 0x46, 0xf0, 0x98, 0x89, 0x42,
0x79, 0xb9, 0xa1, 0x0b, 0xa5, 0x91, 0x3c, 0xf1, 0x93, 0xfd, 0xe5, 0x99, 0x30, 0x5a, 0x04, 0xd9,
0x6b, 0xa8, 0x19, 0xc6, 0xfe, 0x20, 0xec, 0x6f, 0x86, 0x83, 0x2d, 0xee, 0xfc, 0x39, 0x0f, 0x3f,
0x0f, 0xa7, 0x08, 0xf8, 0x75, 0x01, 0xeb, 0x8f, 0x2e, 0x20, 0x42, 0x0d, 0xbf, 0x7d, 0x13, 0xb5,
0x94, 0x44, 0xa5, 0x16, 0xfe, 0x9a, 0x87, 0x93, 0x84, 0x1a, 0x2a, 0x87, 0x52, 0xc3, 0x92, 0xa9,
0x6c, 0x29, 0x07, 0x93, 0x61, 0x8e, 0xf7, 0xda, 0xeb, 0x8c, 0x77, 0x8a, 0xe6, 0xd5, 0x94, 0x75,
0xe6, 0x21, 0xee, 0xe3, 0x83, 0x0c, 0x23, 0x3a, 0xd8, 0x5b, 0x97, 0xa8, 0xc8, 0xa2, 0x08, 0x3a,
0x8b, 0xb2, 0xc5, 0xac, 0x34, 0x98, 0x34, 0xe7, 0x89, 0x8e, 0x89, 0xe2, 0xbe, 0x29, 0x8d, 0x3a,
0xa4, 0x86, 0x8e, 0x89, 0xe2, 0xcf, 0x67, 0xc4, 0x21, 0x3b, 0x66, 0x06, 0x25, 0x74, 0x96, 0xa5,
0x46, 0xef, 0x17, 0xa8, 0x01, 0x47, 0x01, 0xb3, 0xff, 0x16, 0xaa, 0xc9, 0x6e, 0x50, 0x93, 0xff,
0x54, 0xa1, 0x60, 0x20, 0x09, 0x09, 0xbb, 0xef, 0xa9, 0x09, 0xa1, 0xa8, 0xd3, 0x0c, 0x37, 0x8b,
0x93, 0x26, 0x54, 0xc1, 0xe4, 0x57, 0x0b, 0x9d, 0x5e, 0x8f, 0x86, 0x41, 0xc2, 0xfc, 0x54, 0xd5,
0x93, 0xf1, 0xbb, 0x51, 0x38, 0x3e, 0x9e, 0x56, 0x3d, 0xb6, 0x43, 0x26, 0x3f, 0x57, 0x50, 0xed,
0x5a, 0xbc, 0x1b, 0xa5, 0xdc, 0xbe, 0x88, 0xaa, 0x9b, 0x41, 0xc8, 0x38, 0x5c, 0x39, 0x55, 0x17,
0x4f, 0x32, 0x2c, 0x01, 0xfd, 0x93, 0x60, 0xe9, 0x1e, 0x91, 0x4e, 0xfb, 0x33, 0xd4, 0x94, 0xff,
0x19, 0x27, 0x01, 0xe3, 0xd0, 0xfd, 0x55, 0xf7, 0x23, 0xf1, 0x25, 0x06, 0xac, 0xbf, 0xc4, 0xc0,
0x74, 0x22, 0x93, 0x68, 0x5f, 0x45, 0x75, 0x35, 0xdb, 0x38, 0xdc, 0x67, 0x55, 0xf7, 0x3c, 0xcc,
0x55, 0x85, 0x15, 0x73, 0x55, 0x01, 0x3a, 0x8b, 0xa6, 0xd8, 0x9f, 0x14, 0xc2, 0xad, 0x40, 0x86,
0x73, 0xff, 0x25, 0xdc, 0x3c, 0x5e, 0xeb, 0xb7, 0x87, 0xaa, 0xde, 0x38, 0x65, 0xf9, 0xe5, 0xe8,
0x88, 0x3a, 0x00, 0x50, 0x1c, 0xb6, 0xb0, 0x08, 0x95, 0xe8, 0xcc, 0x4d, 0x50, 0x7b, 0xcd, 0x9b,
0x60, 0x03, 0x35, 0xe4, 0x2e, 0xd3, 0x0f, 0x86, 0x70, 0x09, 0xb4, 0xdc, 0x4b, 0x07, 0x19, 0xae,
0xcb, 0xfd, 0x04, 0x6e, 0xc6, 0xba, 0x24, 0xac, 0x0f, 0x75, 0xa2, 0x1c, 0x10, 0xdd, 0xa2, 0x99,
0x54, 0xf3, 0x84, 0xc4, 0xcc, 0x41, 0x62, 0xbf, 0xc9, 0x1c, 0x51, 0x0d, 0xf2, 0xad, 0x85, 0x1a,
0x52, 0x1e, 0x1b, 0x2c, 0xb5, 0xaf, 0xa2, 0x9a, 0x0f, 0x86, 0xea, 0x10, 0x24, 0x76, 0x23, 0xe9,
0x2e, 0x1a, 0x43, 0x32, 0x74, 0xad, 0xc0, 0x24, 0x54, 0xc1, 0x62, 0xa8, 0xf8, 0x09, 0x1b, 0xe4,
0x3b, 0x63, 0x59, 0x0e, 0x15, 0x05, 0xe9, 0xb3, 0x51, 0x36, 0xa1, 0xb9, 0x87, 0x7c, 0x37, 0x87,
0x4e, 0x1b, 0x5b, 0xd8, 0x1a, 0x1b, 0x25, 0x4c, 0x2e, 0x4a, 0xc7, 0xbb, 0xd3, 0xae, 0xa2, 0x9a,
0xac, 0x23, 0x7c, 0x5e, 0xcb, 0x3d, 0x2b, 0x7e, 0x49, 0x22, 0x47, 0x36, 0x53, 0x85, 0x8b, 0x7f,
0xca, 0x07, 0x5e, 0xb9, 0x18, 0x94, 0x2f, 0x1b, 0x71, 0xc5, 0x50, 0xbb, 0x34, 0xab, 0xd3, 0x57,
0x1d, 0xb0, 0x64, 0x0f, 0x9d, 0x36, 0x76, 0x56, 0xa3, 0x14, 0x5f, 0x1e, 0xd9, 0x5e, 0xdf, 0x7e,
0x61, 0x7b, 0x2d, 0xc8, 0xee, 0xfb, 0xaa, 0x28, 0x2f, 0x5f, 0x5c, 0x5f, 0xdc, 0x54, 0xdd, 0x1b,
0xcf, 0x9e, 0xb7, 0x4b, 0xfb, 0xcf, 0xdb, 0xa5, 0x67, 0x07, 0x6d, 0x6b, 0xff, 0xa0, 0x6d, 0x7d,
0x7f, 0xd8, 0x2e, 0x3d, 0x3d, 0x6c, 0x5b, 0xfb, 0x87, 0xed, 0xd2, 0xef, 0x87, 0xed, 0xd2, 0x57,
0xe7, 0x5f, 0x61, 0x49, 0x1b, 0x7a, 0x5e, 0x0d, 0x4e, 0xe8, 0xc2, 0xbf, 0x01, 0x00, 0x00, 0xff,
0xff, 0xfc, 0x01, 0x79, 0xc2, 0x02, 0x0d, 0x00, 0x00,
}
func (m *FileVersion) Marshal() (dAtA []byte, err error) {
@ -626,6 +628,15 @@ func (m *FileInfoTruncated) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0xc0
}
if len(m.Encrypted) > 0 {
i -= len(m.Encrypted)
copy(dAtA[i:], m.Encrypted)
i = encodeVarintStructs(dAtA, i, uint64(len(m.Encrypted)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x9a
}
if len(m.BlocksHash) > 0 {
i -= len(m.BlocksHash)
copy(dAtA[i:], m.BlocksHash)
@ -1125,6 +1136,10 @@ func (m *FileInfoTruncated) ProtoSize() (n int) {
if l > 0 {
n += 2 + l + sovStructs(uint64(l))
}
l = len(m.Encrypted)
if l > 0 {
n += 2 + l + sovStructs(uint64(l))
}
if m.LocalFlags != 0 {
n += 2 + sovStructs(uint64(m.LocalFlags))
}
@ -1890,6 +1905,40 @@ func (m *FileInfoTruncated) Unmarshal(dAtA []byte) error {
m.BlocksHash = []byte{}
}
iNdEx = postIndex
case 19:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Encrypted", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthStructs
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthStructs
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Encrypted = append(m.Encrypted[:0], dAtA[iNdEx:postIndex]...)
if m.Encrypted == nil {
m.Encrypted = []byte{}
}
iNdEx = postIndex
case 1000:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field LocalFlags", wireType)

View File

@ -83,7 +83,7 @@ func (f *fakeConnection) IndexUpdate(ctx context.Context, folder string, fs []pr
return nil
}
func (f *fakeConnection) Request(ctx context.Context, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (f *fakeConnection) Request(ctx context.Context, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
f.mut.Lock()
defer f.mut.Unlock()
if f.requestFn != nil {

View File

@ -459,7 +459,8 @@ func (f *folder) scanSubdirs(subDirs []string) error {
scanCtx, scanCancel := context.WithCancel(f.ctx)
defer scanCancel()
mtimefs := f.fset.MtimeFS()
fchan := scanner.Walk(scanCtx, scanner.Config{
scanConfig := scanner.Config{
Folder: f.ID,
Subs: subDirs,
Matcher: f.ignores,
@ -474,7 +475,13 @@ func (f *folder) scanSubdirs(subDirs []string) error {
LocalFlags: f.localFlags,
ModTimeWindow: f.modTimeWindow,
EventLogger: f.evLogger,
})
}
var fchan chan scanner.ScanResult
if f.Type == config.FolderTypeReceiveEncrypted {
fchan = scanner.WalkWithoutHashing(scanCtx, scanConfig)
} else {
fchan = scanner.Walk(scanCtx, scanConfig)
}
batch := newFileInfoBatch(func(fs []protocol.FileInfo) error {
if err := f.getHealthErrorWithoutIgnores(); err != nil {
@ -485,13 +492,19 @@ func (f *folder) scanSubdirs(subDirs []string) error {
return nil
})
// Schedule a pull after scanning, but only if we actually detected any
// changes.
changes := 0
defer func() {
if changes > 0 {
f.SchedulePull()
}
}()
var batchAppend func(protocol.FileInfo, *db.Snapshot)
// Resolve items which are identical with the global state.
if f.localFlags&protocol.FlagLocalReceiveOnly == 0 {
batchAppend = func(fi protocol.FileInfo, _ *db.Snapshot) {
batch.append(fi)
}
} else {
switch f.Type {
case config.FolderTypeReceiveOnly:
batchAppend = func(fi protocol.FileInfo, snap *db.Snapshot) {
switch gf, ok := snap.GetGlobal(fi.Name); {
case !ok:
@ -509,16 +522,28 @@ func (f *folder) scanSubdirs(subDirs []string) error {
}
batch.append(fi)
}
case config.FolderTypeReceiveEncrypted:
batchAppend = func(fi protocol.FileInfo, _ *db.Snapshot) {
// This is a "virtual" parent directory of encrypted files.
// We don't track it, but check if anything still exists
// within and delete it otherwise.
if fi.IsDirectory() && protocol.IsEncryptedParent(fi.Name) {
if names, err := mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
mtimefs.Remove(fi.Name)
}
changes--
return
}
// Any local change must not be sent as index entry to
// remotes and show up as an error in the UI.
fi.LocalFlags = protocol.FlagLocalReceiveOnly
batch.append(fi)
}
default:
batchAppend = func(fi protocol.FileInfo, _ *db.Snapshot) {
batch.append(fi)
}
// Schedule a pull after scanning, but only if we actually detected any
// changes.
changes := 0
defer func() {
if changes > 0 {
f.SchedulePull()
}
}()
f.clearScanErrors(subDirs)
alreadyUsed := make(map[string]struct{})
@ -540,7 +565,9 @@ func (f *folder) scanSubdirs(subDirs []string) error {
batchAppend(res.File, snap)
changes++
if f.localFlags&protocol.FlagLocalReceiveOnly == 0 {
switch f.Type {
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
default:
if nf, ok := f.findRename(snap, mtimefs, res.File, alreadyUsed); ok {
batchAppend(nf, snap)
changes++
@ -648,7 +675,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
l.Debugln("marking file as deleted", nf)
batchAppend(nf, snap)
changes++
case file.IsDeleted() && file.IsReceiveOnlyChanged() && f.localFlags&protocol.FlagLocalReceiveOnly != 0 && len(snap.Availability(file.Name)) == 0:
case file.IsDeleted() && file.IsReceiveOnlyChanged() && f.Type == config.FolderTypeReceiveOnly && len(snap.Availability(file.Name)) == 0:
file.Version = protocol.Vector{}
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
l.Debugln("marking deleted item that doesn't exist anywhere as not receive-only", file)

111
lib/model/folder_recvenc.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"fmt"
"sort"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/versioner"
)
func init() {
folderFactories[config.FolderTypeReceiveEncrypted] = newReceiveEncryptedFolder
}
type receiveEncryptedFolder struct {
*sendReceiveFolder
}
func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service {
return &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, fs, evLogger, ioLimiter).(*sendReceiveFolder)}
}
func (f *receiveEncryptedFolder) Revert() {
f.doInSync(func() error { f.revert(); return nil })
}
func (f *receiveEncryptedFolder) revert() {
l.Infof("Reverting unexpected items in folder %v (receive-encrypted)", f.Description())
f.setState(FolderScanning)
defer f.setState(FolderIdle)
batch := newFileInfoBatch(func(fs []protocol.FileInfo) error {
f.updateLocalsFromScanning(fs)
return nil
})
snap := f.fset.Snapshot()
defer snap.Release()
var iterErr error
var dirs []string
snap.WithHaveTruncated(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
if iterErr = batch.flushIfFull(); iterErr != nil {
return false
}
fit := intf.(db.FileInfoTruncated)
if !fit.IsReceiveOnlyChanged() || intf.IsDeleted() {
return true
}
if fit.IsDirectory() {
dirs = append(dirs, fit.Name)
return true
}
if err := f.inWritableDir(f.fs.Remove, fit.Name); err != nil && !fs.IsNotExist(err) {
f.newScanError(fit.Name, fmt.Errorf("deleting unexpected item: %w", err))
}
fi := fit.ConvertToDeletedFileInfo(f.shortID)
// Set version to zero, such that we pull the global version in case
// this is a valid filename that was erroneously changed locally.
// Should already be zero from scanning, but lets be safe.
fi.Version = protocol.Vector{}
// Purposely not removing FlagLocalReceiveOnly as the deleted
// item should still not be sent in index updates. However being
// deleted, it will not show up as an unexpected file in the UI
// anymore.
batch.append(fi)
return true
})
f.revertHandleDirs(dirs, snap)
if iterErr == nil {
iterErr = batch.flush()
}
if iterErr != nil {
l.Infoln("Failed to delete unexpected items:", iterErr)
}
}
func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapshot) {
if len(dirs) == 0 {
return
}
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
defer close(scanChan)
sort.Sort(sort.Reverse(sort.StringSlice(dirs)))
for _, dir := range dirs {
if err := f.deleteDirOnDisk(dir, snap, scanChan); err != nil {
f.newScanError(dir, fmt.Errorf("deleting unexpected dir: %w", err))
}
}
}

View File

@ -686,16 +686,22 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
// user can then clean up as they like...
// This can also occur if an entire tree structure was deleted, but only
// a leave has been scanned.
//
// And if this is an encrypted folder:
// Encrypted files have made-up filenames with two synthetic parent
// directories which don't have any meaning. Create those if necessary.
if _, err := f.fs.Lstat(parent); !fs.IsNotExist(err) {
l.Debugf("%v parent not missing %v", f, file)
return true
}
l.Debugf("%v resurrecting parent directory of %v", f, file)
l.Debugf("%v creating parent directory of %v", f, file)
if err := f.fs.MkdirAll(parent, 0755); err != nil {
f.newPullError(file, errors.Wrap(err, "resurrecting parent dir"))
f.newPullError(file, errors.Wrap(err, "creating parent dir"))
return false
}
if f.Type != config.FolderTypeReceiveEncrypted {
scanChan <- parent
}
return true
}
@ -1248,38 +1254,11 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
continue
}
if f.Type != config.FolderTypeReceiveEncrypted {
f.model.progressEmitter.Register(state.sharedPullerState)
var file fs.File
var weakHashFinder *weakhash.Finder
blocksPercentChanged := 0
if tot := len(state.file.Blocks); tot > 0 {
blocksPercentChanged = (tot - state.have) * 100 / tot
}
if blocksPercentChanged >= f.WeakHashThresholdPct {
hashesToFind := make([]uint32, 0, len(state.blocks))
for _, block := range state.blocks {
if block.WeakHash != 0 {
hashesToFind = append(hashesToFind, block.WeakHash)
}
}
if len(hashesToFind) > 0 {
file, err = f.fs.Open(state.file.Name)
if err == nil {
weakHashFinder, err = weakhash.NewFinder(f.ctx, file, state.file.BlockSize(), hashesToFind)
if err != nil {
l.Debugln("weak hasher", err)
}
}
} else {
l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name)
}
} else {
l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct)
}
weakHashFinder, file := f.initWeakHashFinder(state)
blocks:
for _, block := range state.blocks {
@ -1305,8 +1284,10 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
buf = protocol.BufferPool.Upgrade(buf, int(block.Size))
found, err := weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool {
if verifyBuffer(buf, block) != nil {
var found bool
if f.Type != config.FolderTypeReceiveEncrypted {
found, err = weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool {
if f.verifyBuffer(buf, block) != nil {
return true
}
@ -1325,6 +1306,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
if err != nil {
l.Debugln("weak hasher iter", err)
}
}
if !found {
found = f.model.finder.Iterate(folders, block.Hash, func(folder, path string, index int32) bool {
@ -1341,10 +1323,15 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
return false
}
if err := verifyBuffer(buf, block); err != nil {
// Hash is not SHA256 as it's an encrypted hash token. In that
// case we can't verify the block integrity so we'll take it on
// trust. (The other side can and will verify.)
if f.Type != config.FolderTypeReceiveEncrypted {
if err := f.verifyBuffer(buf, block); err != nil {
l.Debugln("Finder failed to verify buffer", err)
return false
}
}
if f.CopyRangeMethod != fs.CopyRangeMethodStandard {
err = f.withLimiter(func() error {
@ -1390,7 +1377,49 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
}
}
func verifyBuffer(buf []byte, block protocol.BlockInfo) error {
func (f *sendReceiveFolder) initWeakHashFinder(state copyBlocksState) (*weakhash.Finder, fs.File) {
if f.Type == config.FolderTypeReceiveEncrypted {
l.Debugln("not weak hashing due to folder type", f.Type)
return nil, nil
}
blocksPercentChanged := 0
if tot := len(state.file.Blocks); tot > 0 {
blocksPercentChanged = (tot - state.have) * 100 / tot
}
if blocksPercentChanged < f.WeakHashThresholdPct {
l.Debugf("not weak hashing %s. not enough changed %.02f < %d", state.file.Name, blocksPercentChanged, f.WeakHashThresholdPct)
return nil, nil
}
hashesToFind := make([]uint32, 0, len(state.blocks))
for _, block := range state.blocks {
if block.WeakHash != 0 {
hashesToFind = append(hashesToFind, block.WeakHash)
}
}
if len(hashesToFind) == 0 {
l.Debugf("not weak hashing %s. file did not contain any weak hashes", state.file.Name)
return nil, nil
}
file, err := f.fs.Open(state.file.Name)
if err != nil {
l.Debugln("weak hasher", err)
return nil, nil
}
weakHashFinder, err := weakhash.NewFinder(f.ctx, file, state.file.BlockSize(), hashesToFind)
if err != nil {
l.Debugln("weak hasher", err)
return nil, file
}
return weakHashFinder, file
}
func (f *sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) error {
if len(buf) != int(block.Size) {
return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size)
}
@ -1487,7 +1516,8 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
// leastBusy can select another device when someone else asks.
activity.using(selected)
var buf []byte
buf, lastError = f.model.requestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary)
blockNo := int(state.block.Offset / int64(state.file.BlockSize()))
buf, lastError = f.model.requestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, int(state.block.Size), state.block.Hash, state.block.WeakHash, selected.FromTemporary)
activity.done(selected)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "returned error:", lastError)
@ -1496,7 +1526,13 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
// Verify that the received block matches the desired hash, if not
// try pulling it from another device.
lastError = verifyBuffer(buf, state.block)
// For receive-only folders, the hash is not SHA256 as it's an
// encrypted hash token. In that case we can't verify the block
// integrity so we'll take it on trust. (The other side can and
// will verify.)
if f.Type != config.FolderTypeReceiveEncrypted {
lastError = f.verifyBuffer(buf, state.block)
}
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
continue
@ -1595,7 +1631,9 @@ func (f *sendReceiveFolder) finisherRoutine(snap *db.Snapshot, in <-chan *shared
blockStatsMut.Unlock()
}
if f.Type != config.FolderTypeReceiveEncrypted {
f.model.progressEmitter.Deregister(state)
}
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
"folder": f.folderID,

View File

@ -123,9 +123,9 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
}
res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"], res["needTotalItems"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes, need.TotalItems()
if haveFcfg && fcfg.Type == config.FolderTypeReceiveOnly {
if haveFcfg && (fcfg.Type == config.FolderTypeReceiveOnly || fcfg.Type == config.FolderTypeReceiveEncrypted) {
// Add statistics for things that have changed locally in a receive
// only folder.
// only or receive encrypted folder.
res["receiveOnlyChangedFiles"] = ro.Files
res["receiveOnlyChangedDirectories"] = ro.Directories
res["receiveOnlyChangedSymlinks"] = ro.Symlinks

View File

@ -25,6 +25,8 @@ type indexSender struct {
suture.Service
conn protocol.Connection
folder string
folderIsReceiveEncrypted bool
dev string
fset *db.FileSet
prevSequence int64
evLogger events.Logger
@ -169,6 +171,13 @@ func (s *indexSender) sendIndexTo(ctx context.Context) error {
f = fi.(protocol.FileInfo)
// If this is a folder receiving encrypted files only, we
// mustn't ever send locally changed file infos. Those aren't
// encrypted and thus would be a protocol error at the remote.
if s.folderIsReceiveEncrypted && fi.IsReceiveOnlyChanged() {
return true
}
// Mark the file as invalid if any of the local bad stuff flags are set.
f.RawInvalid = f.IsInvalid()
// If the file is marked LocalReceive (i.e., changed locally on a

View File

@ -11,6 +11,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"path/filepath"
"reflect"
@ -140,6 +141,8 @@ type model struct {
folderRunnerToken map[string]suture.ServiceToken // folder -> token for folder runner
folderRestartMuts syncMutexMap // folder -> restart mutex
folderVersioners map[string]versioner.Versioner // folder -> versioner (may be nil)
folderEncryptionPasswordTokens map[string][]byte // folder -> encryption token (may be missing, and only for encryption type folders)
folderEncryptionFailures map[string]map[protocol.DeviceID]error // folder -> device -> error regarding encryption consistency (may be missing)
// fields protected by pmut
pmut sync.RWMutex
@ -174,6 +177,13 @@ var (
errIgnoredFolderRemoved = errors.New("folder no longer ignored")
errReplacingConnection = errors.New("replacing connection")
errStopped = errors.New("Syncthing is being stopped")
errEncryptionInvConfigLocal = errors.New("can't encrypt data for a device when the folder type is receiveEncrypted")
errEncryptionInvConfigRemote = errors.New("remote has encrypted data and encrypts that data for us - this is impossible")
errEncryptionNotEncryptedLocal = errors.New("folder is announced as encrypted, but not configured thus")
errEncryptionNotEncryptedRemote = errors.New("folder is configured to be encrypted but not announced thus")
errEncryptionNotEncryptedUntrusted = errors.New("device is untrusted, but configured to receive not encrypted data")
errEncryptionPassword = errors.New("different encryption passwords used")
errEncryptionReceivedToken = errors.New("resetting connection to send info on new encrypted folder (new cluster config)")
errMissingRemoteInClusterConfig = errors.New("remote device missing in cluster config")
errMissingLocalInClusterConfig = errors.New("local device missing in cluster config")
)
@ -215,6 +225,8 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
folderRunners: make(map[string]service),
folderRunnerToken: make(map[string]suture.ServiceToken),
folderVersioners: make(map[string]versioner.Versioner),
folderEncryptionPasswordTokens: make(map[string][]byte),
folderEncryptionFailures: make(map[string]map[protocol.DeviceID]error),
// fields protected by pmut
pmut: sync.NewRWMutex(),
@ -339,6 +351,14 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
ffs := fset.MtimeFS()
if cfg.Type == config.FolderTypeReceiveEncrypted {
if encryptionToken, err := readEncryptionToken(cfg); err == nil {
m.folderEncryptionPasswordTokens[folder] = encryptionToken
} else if !fs.IsNotExist(err) {
l.Warnf("Failed to read encryption token: %v", err)
}
}
// These are our metadata files, and they should always be hidden.
_ = ffs.Hide(config.DefaultMarkerName)
_ = ffs.Hide(".stversions")
@ -1026,8 +1046,6 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
// Also, collect a list of folders we do share, and if he's interested in
// temporary indexes, subscribe the connection.
tempIndexFolders := make([]string, 0, len(cm.Folders))
m.pmut.RLock()
indexSenderRegistry, ok := m.indexSenders[deviceID]
m.pmut.RUnlock()
@ -1042,11 +1060,37 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
return errDeviceUnknown
}
// Assemble the device information from the connected device about
// themselves and us for all folders.
ccDeviceInfos := make(map[string]*indexSenderStartInfo, len(cm.Folders))
for _, folder := range cm.Folders {
info := &indexSenderStartInfo{}
for _, dev := range folder.Devices {
if dev.ID == m.id {
info.local = dev
} else if dev.ID == deviceID {
info.remote = dev
}
if info.local.ID != protocol.EmptyDeviceID && info.remote.ID != protocol.EmptyDeviceID {
break
}
}
if info.remote.ID == protocol.EmptyDeviceID {
l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID, folder.Description())
return errMissingRemoteInClusterConfig
}
if info.local.ID == protocol.EmptyDeviceID {
l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID, folder.Description())
return errMissingLocalInClusterConfig
}
ccDeviceInfos[folder.ID] = info
}
// Needs to happen outside of the fmut, as can cause CommitConfiguration
if deviceCfg.AutoAcceptFolders {
changedFolders := make([]config.FolderConfiguration, 0, len(cm.Folders))
for _, folder := range cm.Folders {
if fcfg, fchanged := m.handleAutoAccepts(deviceCfg, folder); fchanged {
if fcfg, fchanged := m.handleAutoAccepts(deviceID, folder, ccDeviceInfos[folder.ID]); fchanged {
changedFolders = append(changedFolders, fcfg)
}
}
@ -1061,91 +1105,16 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
}
paused := make(map[string]struct{}, len(cm.Folders))
seenFolders := make(map[string]struct{}, len(cm.Folders))
for _, folder := range cm.Folders {
seenFolders[folder.ID] = struct{}{}
cfg, ok := m.cfg.Folder(folder.ID)
if !ok || !cfg.SharedWith(deviceID) {
indexSenderRegistry.remove(folder.ID)
if deviceCfg.IgnoredFolder(folder.ID) {
l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID)
continue
changedHere, tempIndexFolders, paused, err := m.ccHandleFolders(cm.Folders, deviceCfg, ccDeviceInfos, indexSenderRegistry)
if err != nil {
return err
}
m.cfg.AddOrUpdatePendingFolder(folder.ID, folder.Label, deviceID)
changed = true
m.evLogger.Log(events.FolderRejected, map[string]string{
"folder": folder.ID,
"folderLabel": folder.Label,
"device": deviceID.String(),
})
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
continue
}
deviceInfos := &indexSenderStartInfo{}
for _, dev := range folder.Devices {
if dev.ID == m.id {
deviceInfos.local = dev
} else if dev.ID == deviceID {
deviceInfos.remote = dev
}
if deviceInfos.local.ID != protocol.EmptyDeviceID && deviceInfos.remote.ID != protocol.EmptyDeviceID {
break
}
}
if deviceInfos.remote.ID == protocol.EmptyDeviceID {
l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID, folder.Description())
return errMissingRemoteInClusterConfig
}
if deviceInfos.local.ID == protocol.EmptyDeviceID {
l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID, folder.Description())
return errMissingLocalInClusterConfig
}
if folder.Paused {
indexSenderRegistry.remove(folder.ID)
paused[cfg.ID] = struct{}{}
continue
}
if cfg.Paused {
indexSenderRegistry.addPaused(cfg, deviceInfos)
continue
}
m.fmut.RLock()
fs, ok := m.folderFiles[folder.ID]
m.fmut.RUnlock()
if !ok {
// Shouldn't happen because !cfg.Paused, but might happen
// if the folder is about to be unpaused, but not yet.
continue
}
if !folder.DisableTempIndexes {
tempIndexFolders = append(tempIndexFolders, folder.ID)
}
indexSenderRegistry.add(cfg, fs, deviceInfos)
// We might already have files that we need to pull so let the
// folder runner know that it should recheck the index data.
m.fmut.RLock()
if runner := m.folderRunners[folder.ID]; runner != nil {
defer runner.SchedulePull()
}
m.fmut.RUnlock()
}
indexSenderRegistry.removeAllExcept(seenFolders)
changed = changed || changedHere
m.pmut.Lock()
m.remotePausedFolders[deviceID] = paused
m.pmut.Unlock()
// This breaks if we send multiple CM messages during the same connection.
if len(tempIndexFolders) > 0 {
m.pmut.RLock()
conn, ok := m.conn[deviceID]
@ -1184,6 +1153,212 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
return nil
}
func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.DeviceConfiguration, ccDeviceInfos map[string]*indexSenderStartInfo, indexSenders *indexSenderRegistry) (bool, []string, map[string]struct{}, error) {
var changed bool
var folderDevice config.FolderDeviceConfiguration
tempIndexFolders := make([]string, 0, len(folders))
paused := make(map[string]struct{}, len(folders))
seenFolders := make(map[string]struct{}, len(folders))
deviceID := deviceCfg.DeviceID
for _, folder := range folders {
seenFolders[folder.ID] = struct{}{}
cfg, ok := m.cfg.Folder(folder.ID)
if ok {
folderDevice, ok = cfg.Device(deviceID)
}
if !ok {
indexSenders.remove(folder.ID)
if deviceCfg.IgnoredFolder(folder.ID) {
l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID)
continue
}
m.cfg.AddOrUpdatePendingFolder(folder.ID, folder.Label, deviceID)
changed = true
m.evLogger.Log(events.FolderRejected, map[string]string{
"folder": folder.ID,
"folderLabel": folder.Label,
"device": deviceID.String(),
})
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
continue
}
if folder.Paused {
indexSenders.remove(folder.ID)
paused[cfg.ID] = struct{}{}
continue
}
if cfg.Paused {
indexSenders.addPaused(cfg, ccDeviceInfos[folder.ID])
continue
}
m.fmut.RLock()
fs, ok := m.folderFiles[folder.ID]
m.fmut.RUnlock()
if !ok {
// Shouldn't happen because !cfg.Paused, but might happen
// if the folder is about to be unpaused, but not yet.
l.Debugln("ccH: no fset", folder.ID)
continue
}
if err := m.ccCheckEncryption(cfg, folderDevice, ccDeviceInfos[folder.ID], deviceCfg.Untrusted); err != nil {
sameError := false
if devs, ok := m.folderEncryptionFailures[folder.ID]; ok {
sameError = devs[deviceID] == err
} else {
m.folderEncryptionFailures[folder.ID] = make(map[protocol.DeviceID]error)
}
m.folderEncryptionFailures[folder.ID][deviceID] = err
msg := fmt.Sprintf("Failure checking encryption consistency with device %v for folder %v: %v", deviceID, cfg.Description(), err)
if sameError || err == errEncryptionReceivedToken {
l.Debugln(msg)
} else {
l.Warnln(msg)
}
return changed, tempIndexFolders, paused, err
}
if devErrs, ok := m.folderEncryptionFailures[folder.ID]; ok {
if len(devErrs) == 1 {
delete(m.folderEncryptionFailures, folder.ID)
} else {
delete(m.folderEncryptionFailures[folder.ID], deviceID)
}
}
// Handle indexes
if !folder.DisableTempIndexes {
tempIndexFolders = append(tempIndexFolders, folder.ID)
}
indexSenders.add(cfg, fs, ccDeviceInfos[folder.ID])
// We might already have files that we need to pull so let the
// folder runner know that it should recheck the index data.
m.fmut.RLock()
if runner := m.folderRunners[folder.ID]; runner != nil {
defer runner.SchedulePull()
}
m.fmut.RUnlock()
}
indexSenders.removeAllExcept(seenFolders)
return changed, tempIndexFolders, paused, nil
}
func (m *model) ccCheckEncryption(fcfg config.FolderConfiguration, folderDevice config.FolderDeviceConfiguration, ccDeviceInfos *indexSenderStartInfo, deviceUntrusted bool) error {
hasTokenRemote := len(ccDeviceInfos.remote.EncryptionPasswordToken) > 0
hasTokenLocal := len(ccDeviceInfos.local.EncryptionPasswordToken) > 0
isEncryptedRemote := folderDevice.EncryptionPassword != ""
isEncryptedLocal := fcfg.Type == config.FolderTypeReceiveEncrypted
if !isEncryptedRemote && !isEncryptedLocal && deviceUntrusted {
return errEncryptionNotEncryptedUntrusted
}
if !(hasTokenRemote || hasTokenLocal || isEncryptedRemote || isEncryptedLocal) {
// Noone cares about encryption here
return nil
}
if isEncryptedRemote && isEncryptedLocal {
// Should never happen, but config racyness and be safe.
return errEncryptionInvConfigLocal
}
if hasTokenRemote && hasTokenLocal {
return errEncryptionInvConfigRemote
}
if !(hasTokenRemote || hasTokenLocal) {
return errEncryptionNotEncryptedRemote
}
if !(isEncryptedRemote || isEncryptedLocal) {
return errEncryptionNotEncryptedLocal
}
if isEncryptedRemote {
passwordToken := protocol.PasswordToken(fcfg.ID, folderDevice.EncryptionPassword)
match := false
if hasTokenLocal {
match = bytes.Equal(passwordToken, ccDeviceInfos.local.EncryptionPasswordToken)
} else {
// hasTokenRemote == true
match = bytes.Equal(passwordToken, ccDeviceInfos.remote.EncryptionPasswordToken)
}
if !match {
return errEncryptionPassword
}
return nil
}
// isEncryptedLocal == true
var ccToken []byte
if hasTokenLocal {
ccToken = ccDeviceInfos.local.EncryptionPasswordToken
} else {
// hasTokenRemote == true
ccToken = ccDeviceInfos.remote.EncryptionPasswordToken
}
m.fmut.RLock()
token, ok := m.folderEncryptionPasswordTokens[fcfg.ID]
m.fmut.RUnlock()
if !ok {
var err error
token, err = readEncryptionToken(fcfg)
if err != nil && !fs.IsNotExist(err) {
return err
}
if err == nil {
m.fmut.Lock()
m.folderEncryptionPasswordTokens[fcfg.ID] = token
m.fmut.Unlock()
} else {
if err := writeEncryptionToken(ccToken, fcfg); err != nil {
return err
}
m.fmut.Lock()
m.folderEncryptionPasswordTokens[fcfg.ID] = ccToken
m.fmut.Unlock()
// We can only announce ourselfs once we have the token,
// thus we need to resend CCs now that we have it.
m.resendClusterConfig(fcfg.DeviceIDs())
return nil
}
}
if !bytes.Equal(token, ccToken) {
return errEncryptionPassword
}
return nil
}
func (m *model) resendClusterConfig(ids []protocol.DeviceID) {
if len(ids) == 0 {
return
}
ccConns := make([]protocol.Connection, 0, len(ids))
m.pmut.RLock()
for _, id := range ids {
if conn, ok := m.conn[id]; ok {
ccConns = append(ccConns, conn)
}
}
m.pmut.RUnlock()
// Generating cluster-configs acquires fmut -> must happen outside of pmut.
for _, conn := range ccConns {
cm := m.generateClusterConfig(conn.ID())
go conn.ClusterConfig(cm)
}
}
// handleIntroductions handles adding devices/folders that are shared by an introducer device
func (m *model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (map[string]config.FolderConfiguration, map[protocol.DeviceID]config.DeviceConfiguration, folderDeviceSet, bool) {
changed := false
@ -1295,7 +1470,7 @@ func (m *model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
// handleAutoAccepts handles adding and sharing folders for devices that have
// AutoAcceptFolders set to true.
func (m *model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder protocol.Folder) (config.FolderConfiguration, bool) {
func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Folder, ccDeviceInfos *indexSenderStartInfo) (config.FolderConfiguration, bool) {
if cfg, ok := m.cfg.Folder(folder.ID); !ok {
defaultPath := m.cfg.Options().DefaultFolderPath
defaultPathFs := fs.NewFilesystem(fs.FilesystemTypeBasic, defaultPath)
@ -1310,25 +1485,40 @@ func (m *model) handleAutoAccepts(deviceCfg config.DeviceConfiguration, folder p
fcfg := config.NewFolderConfiguration(m.id, folder.ID, folder.Label, fs.FilesystemTypeBasic, filepath.Join(defaultPath, path))
fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{
DeviceID: deviceCfg.DeviceID,
DeviceID: deviceID,
})
l.Infof("Auto-accepted %s folder %s at path %s", deviceCfg.DeviceID, folder.Description(), fcfg.Path)
if len(ccDeviceInfos.remote.EncryptionPasswordToken) > 0 || len(ccDeviceInfos.local.EncryptionPasswordToken) > 0 {
fcfg.Type = config.FolderTypeReceiveEncrypted
}
l.Infof("Auto-accepted %s folder %s at path %s", deviceID, folder.Description(), fcfg.Path)
return fcfg, true
}
l.Infof("Failed to auto-accept folder %s from %s due to path conflict", folder.Description(), deviceCfg.DeviceID)
l.Infof("Failed to auto-accept folder %s from %s due to path conflict", folder.Description(), deviceID)
return config.FolderConfiguration{}, false
} else {
for _, device := range cfg.DeviceIDs() {
if device == deviceCfg.DeviceID {
if device == deviceID {
// Already shared nothing todo.
return config.FolderConfiguration{}, false
}
}
if cfg.Type == config.FolderTypeReceiveEncrypted {
if len(ccDeviceInfos.remote.EncryptionPasswordToken) == 0 && len(ccDeviceInfos.local.EncryptionPasswordToken) == 0 {
l.Infof("Failed to auto-accept device %s on existing folder %s as the remote wants to send us unencrypted data, but the folder type is receive-encrypted", folder.Description(), deviceID)
return config.FolderConfiguration{}, false
}
} else {
if len(ccDeviceInfos.remote.EncryptionPasswordToken) > 0 || len(ccDeviceInfos.local.EncryptionPasswordToken) > 0 {
l.Infof("Failed to auto-accept device %s on existing folder %s as the remote wants to send us encrypted data, but the folder type is not receive-encrypted", folder.Description(), deviceID)
return config.FolderConfiguration{}, false
}
}
cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{
DeviceID: deviceCfg.DeviceID,
DeviceID: deviceID,
})
l.Infof("Shared %s with %s due to auto-accept", folder.ID, deviceCfg.DeviceID)
l.Infof("Shared %s with %s due to auto-accept", folder.ID, deviceID)
return cfg, true
}
}
@ -1422,7 +1612,7 @@ func (r *requestResponse) Wait() {
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
func (m *model) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
func (m *model) Request(deviceID protocol.DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
if size < 0 || offset < 0 {
return nil, protocol.ErrInvalid
}
@ -1520,12 +1710,15 @@ func (m *model) Request(deviceID protocol.DeviceID, folder, name string, size in
if err := readOffsetIntoBuf(folderFs, name, offset, res.data); fs.IsNotExist(err) {
l.Debugf("%v REQ(in) file doesn't exist: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
} else if err == io.EOF && len(hash) == 0 {
// Read beyond end of file when we can't verify the hash -- this is
// a padded read for an encrypted file. It's fine.
} else if err != nil {
l.Debugf("%v REQ(in) failed reading file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
return nil, protocol.ErrGeneric
}
if !scanner.Validate(res.data, hash, weakHash) {
if len(hash) > 0 && !scanner.Validate(res.data, hash, weakHash) {
m.recheckFile(deviceID, folder, name, offset, hash, weakHash)
l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
@ -1862,7 +2055,7 @@ func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
}
}
func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
m.pmut.RLock()
nc, ok := m.conn[deviceID]
m.pmut.RUnlock()
@ -1871,9 +2064,9 @@ func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, f
return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID)
}
l.Debugf("%v REQ(out): %s: %q / %q o=%d s=%d h=%x wh=%x ft=%t", m, deviceID, folder, name, offset, size, hash, weakHash, fromTemporary)
l.Debugf("%v REQ(out): %s: %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
return nc.Request(ctx, folder, name, offset, size, hash, weakHash, fromTemporary)
return nc.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
}
func (m *model) ScanFolders() map[string]error {
@ -1974,6 +2167,17 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
continue
}
var encryptionToken []byte
var hasEncryptionToken bool
if folderCfg.Type == config.FolderTypeReceiveEncrypted {
if encryptionToken, hasEncryptionToken = m.folderEncryptionPasswordTokens[folderCfg.ID]; !hasEncryptionToken {
// We haven't gotten a token for us yet and without
// one the other side can't validate us - pretend
// we don't have the folder yet.
continue
}
}
protocolFolder := protocol.Folder{
ID: folderCfg.ID,
Label: folderCfg.Label,
@ -2001,6 +2205,12 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
Introducer: deviceCfg.Introducer,
}
if deviceCfg.DeviceID == m.id && hasEncryptionToken {
protocolDevice.EncryptionPasswordToken = encryptionToken
} else if device.EncryptionPassword != "" {
protocolDevice.EncryptionPasswordToken = protocol.PasswordToken(folderCfg.ID, device.EncryptionPassword)
}
if fs != nil {
if deviceCfg.DeviceID == m.id {
protocolDevice.IndexID = fs.IndexID(protocol.LocalDeviceID)
@ -2375,18 +2585,13 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
go conn.Close(errDeviceRemoved)
}
}
ccConns := make([]protocol.Connection, 0, len(clusterConfigDevices))
for id := range clusterConfigDevices {
if conn, ok := m.conn[id]; ok {
ccConns = append(ccConns, conn)
}
}
m.pmut.RUnlock()
// Generating cluster-configs acquires fmut -> must happen outside of pmut.
for _, conn := range ccConns {
cm := m.generateClusterConfig(conn.ID())
go conn.ClusterConfig(cm)
ids := make([]protocol.DeviceID, 0, len(clusterConfigDevices))
for id := range clusterConfigDevices {
ids = append(ids, id)
}
m.resendClusterConfig(ids)
m.globalRequestLimiter.setCapacity(1024 * to.Options.MaxConcurrentIncomingRequestKiB())
m.folderIOLimiter.setCapacity(to.Options.MaxFolderConcurrency())
@ -2600,3 +2805,38 @@ func addDeviceIDsToMap(m map[protocol.DeviceID]struct{}, s []protocol.DeviceID)
}
return m
}
func encryptionTokenPath(cfg config.FolderConfiguration) string {
return filepath.Join(cfg.MarkerName, "syncthing-encryption_password_token")
}
type storedEncryptionToken struct {
FolderID string
Token []byte
}
func readEncryptionToken(cfg config.FolderConfiguration) ([]byte, error) {
fd, err := cfg.Filesystem().Open(encryptionTokenPath(cfg))
if err != nil {
return nil, err
}
defer fd.Close()
var stored storedEncryptionToken
if err := json.NewDecoder(fd).Decode(&stored); err != nil {
return nil, err
}
return stored.Token, nil
}
func writeEncryptionToken(token []byte, cfg config.FolderConfiguration) error {
tokenName := encryptionTokenPath(cfg)
fd, err := cfg.Filesystem().OpenFile(tokenName, fs.OptReadWrite|fs.OptCreate, 0666)
if err != nil {
return err
}
defer fd.Close()
return json.NewEncoder(fd).Encode(storedEncryptionToken{
FolderID: cfg.ID,
Token: token,
})
}

View File

@ -132,12 +132,35 @@ func newState(cfg config.Configuration) *model {
return m
}
func createClusterConfig(remote protocol.DeviceID, ids ...string) protocol.ClusterConfig {
cc := protocol.ClusterConfig{
Folders: make([]protocol.Folder, len(ids)),
}
for i, id := range ids {
cc.Folders[i] = protocol.Folder{
ID: id,
Label: id,
}
}
return addFolderDevicesToClusterConfig(cc, remote)
}
func addFolderDevicesToClusterConfig(cc protocol.ClusterConfig, remote protocol.DeviceID) protocol.ClusterConfig {
for i := range cc.Folders {
cc.Folders[i].Devices = []protocol.Device{
{ID: myID},
{ID: remote},
}
}
return cc
}
func TestRequest(t *testing.T) {
m := setupModel(defaultCfgWrapper)
defer cleanupModel(m)
// Existing, shared file
res, err := m.Request(device1, "default", "foo", 6, 0, nil, 0, false)
res, err := m.Request(device1, "default", "foo", 0, 6, 0, nil, 0, false)
if err != nil {
t.Fatal(err)
}
@ -147,33 +170,37 @@ func TestRequest(t *testing.T) {
}
// Existing, nonshared file
_, err = m.Request(device2, "default", "foo", 6, 0, nil, 0, false)
_, err = m.Request(device2, "default", "foo", 0, 6, 0, nil, 0, false)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Nonexistent file
_, err = m.Request(device1, "default", "nonexistent", 6, 0, nil, 0, false)
_, err = m.Request(device1, "default", "nonexistent", 0, 6, 0, nil, 0, false)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Shared folder, but disallowed file name
_, err = m.Request(device1, "default", "../walk.go", 6, 0, nil, 0, false)
_, err = m.Request(device1, "default", "../walk.go", 0, 6, 0, nil, 0, false)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Negative offset
_, err = m.Request(device1, "default", "foo", -4, 0, nil, 0, false)
_, err = m.Request(device1, "default", "foo", 0, -4, 0, nil, 0, false)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Larger block than available
_, err = m.Request(device1, "default", "foo", 42, 0, nil, 0, false)
_, err = m.Request(device1, "default", "foo", 0, 42, 0, []byte("hash necessary but not checked"), 0, false)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
t.Error("Unexpected nil error on read past end of file")
}
_, err = m.Request(device1, "default", "foo", 0, 42, 0, nil, 0, false)
if err != nil {
t.Error("Unexpected error when large read should be permitted")
}
}
@ -259,7 +286,7 @@ func BenchmarkRequestOut(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
data, err := m.requestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 32, nil, 0, false)
data, err := m.requestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, 0, false)
if err != nil {
b.Error(err)
}
@ -283,7 +310,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 128<<10, 0, nil, 0, false); err != nil {
if _, err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, 128<<10, 0, nil, 0, false); err != nil {
b.Error(err)
}
}
@ -854,14 +881,7 @@ func TestIssue5063(t *testing.T) {
wg := sync.WaitGroup{}
addAndVerify := func(id string) {
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
@ -904,14 +924,7 @@ func TestAutoAcceptRejected(t *testing.T) {
defer cleanupModel(m)
id := srand.String(8)
defer os.RemoveAll(id)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if cfg, ok := m.cfg.Folder(id); ok && cfg.SharedWith(device1) {
t.Error("unexpected shared", id)
@ -924,14 +937,7 @@ func TestAutoAcceptNewFolder(t *testing.T) {
defer cleanupModel(m)
id := srand.String(8)
defer os.RemoveAll(id)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
@ -942,28 +948,14 @@ func TestAutoAcceptNewFolderFromTwoDevices(t *testing.T) {
defer cleanupModel(m)
id := srand.String(8)
defer os.RemoveAll(id)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id)
}
m.ClusterConfig(device2, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device2, createClusterConfig(device2, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device2) {
t.Error("expected shared", id)
}
@ -976,28 +968,14 @@ func TestAutoAcceptNewFolderFromOnlyOneDevice(t *testing.T) {
id := srand.String(8)
defer os.RemoveAll(id)
defer cleanupModel(m)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id)
}
m.ClusterConfig(device2, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device2, createClusterConfig(device2, id))
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected shared", id)
}
@ -1053,18 +1031,7 @@ func TestAutoAcceptMultipleFolders(t *testing.T) {
defer os.RemoveAll(id2)
m := newState(defaultAutoAcceptCfg)
defer cleanupModel(m)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id1,
Label: id1,
},
{
ID: id2,
Label: id2,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id1, id2))
if fcfg, ok := m.cfg.Folder(id1); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id1)
}
@ -1092,14 +1059,7 @@ func TestAutoAcceptExistingFolder(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device1) {
t.Error("missing folder, or shared", id)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || fcfg.Path != idOther {
t.Error("missing folder, or unshared, or path changed", id)
@ -1125,18 +1085,7 @@ func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id1); !ok || fcfg.SharedWith(device1) {
t.Error("missing folder, or shared", id1)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id1,
Label: id1,
},
{
ID: id2,
Label: id2,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id1, id2))
for i, id := range []string{id1, id2} {
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
@ -1166,14 +1115,7 @@ func TestAutoAcceptAlreadyShared(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("missing folder, or not shared", id)
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("missing folder, or not shared", id)
@ -1212,14 +1154,14 @@ func TestAutoAcceptPrefersLabel(t *testing.T) {
defer os.RemoveAll(id)
defer os.RemoveAll(label)
defer cleanupModel(m)
m.ClusterConfig(device1, protocol.ClusterConfig{
m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: label,
},
},
})
}, device1))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, label) {
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
}
@ -1237,14 +1179,14 @@ func TestAutoAcceptFallsBackToID(t *testing.T) {
defer os.RemoveAll(label)
defer os.RemoveAll(id)
defer cleanupModel(m)
m.ClusterConfig(device1, protocol.ClusterConfig{
m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: label,
},
},
})
}, device1))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || !strings.HasSuffix(fcfg.Path, id) {
t.Error("expected shared, or wrong path", id, label, fcfg.Path)
}
@ -1276,14 +1218,7 @@ func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) {
t.Fatal("folder running?")
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
m.generateClusterConfig(device1)
if fcfg, ok := m.cfg.Folder(id); !ok {
@ -1333,14 +1268,7 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
t.Fatal("folder running?")
}
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
m.ClusterConfig(device1, createClusterConfig(device1, id))
m.generateClusterConfig(device1)
if fcfg, ok := m.cfg.Folder(id); !ok {
@ -1361,6 +1289,113 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
}
}
func TestAutoAcceptEnc(t *testing.T) {
tcfg := defaultAutoAcceptCfg.Copy()
m := newState(tcfg)
defer cleanupModel(m)
id := srand.String(8)
defer os.RemoveAll(id)
token := []byte("token")
basicCC := func() protocol.ClusterConfig {
return protocol.ClusterConfig{
Folders: []protocol.Folder{{
ID: id,
Label: id,
}}}
}
// Earlier tests might cause the connection to get closed, thus ClusterConfig
// would panic.
clusterConfig := func(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
m.AddConnection(&fakeConnection{id: deviceID, model: m}, protocol.Hello{})
m.ClusterConfig(deviceID, cm)
}
clusterConfig(device1, basicCC())
if _, ok := m.cfg.Folder(id); ok {
t.Fatal("unexpected added")
}
cc := basicCC()
cc.Folders[0].Devices = []protocol.Device{{ID: device1}}
clusterConfig(device1, cc)
if _, ok := m.cfg.Folder(id); ok {
t.Fatal("unexpected added")
}
cc = basicCC()
cc.Folders[0].Devices = []protocol.Device{{ID: myID}}
clusterConfig(device1, cc)
if _, ok := m.cfg.Folder(id); ok {
t.Fatal("unexpected added")
}
// New folder, encrypted -> add as enc
cc = createClusterConfig(device1, id)
cc.Folders[0].Devices[1].EncryptionPasswordToken = token
clusterConfig(device1, cc)
if cfg, ok := m.cfg.Folder(id); !ok {
t.Fatal("unexpected unadded")
} else {
if !cfg.SharedWith(device1) {
t.Fatal("unexpected unshared")
}
if cfg.Type != config.FolderTypeReceiveEncrypted {
t.Fatal("Folder not added as receiveEncrypted")
}
}
// New device, unencrypted on encrypted folder -> reject
clusterConfig(device2, createClusterConfig(device2, id))
if cfg, _ := m.cfg.Folder(id); cfg.SharedWith(device2) {
t.Fatal("unexpected shared")
}
// New device, encrypted on encrypted folder -> share
cc = createClusterConfig(device2, id)
cc.Folders[0].Devices[1].EncryptionPasswordToken = token
clusterConfig(device2, cc)
if cfg, _ := m.cfg.Folder(id); !cfg.SharedWith(device2) {
t.Fatal("unexpected unshared")
}
// New folder, no encrypted -> add "normal"
id = srand.String(8)
defer os.RemoveAll(id)
clusterConfig(device1, createClusterConfig(device1, id))
if cfg, ok := m.cfg.Folder(id); !ok {
t.Fatal("unexpected unadded")
} else {
if !cfg.SharedWith(device1) {
t.Fatal("unexpected unshared")
}
if cfg.Type != config.FolderTypeSendReceive {
t.Fatal("Folder not added as send-receive")
}
}
// New device, encrypted on unencrypted folder -> reject
cc = createClusterConfig(device2, id)
cc.Folders[0].Devices[1].EncryptionPasswordToken = token
clusterConfig(device2, cc)
if cfg, _ := m.cfg.Folder(id); cfg.SharedWith(device2) {
t.Fatal("unexpected shared")
}
// New device, unencrypted on unencrypted folder -> share
clusterConfig(device2, createClusterConfig(device2, id))
if cfg, _ := m.cfg.Folder(id); !cfg.SharedWith(device2) {
t.Fatal("unexpected unshared")
}
}
func changeIgnores(t *testing.T, m *model, expected []string) {
arrEqual := func(a, b []string) bool {
if len(a) != len(b) {
@ -3220,14 +3255,14 @@ func TestRequestLimit(t *testing.T) {
file := "tmpfile"
befReq := time.Now()
first, err := m.Request(device1, "default", file, 2000, 0, nil, 0, false)
first, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false)
if err != nil {
t.Fatalf("First request failed: %v", err)
}
reqDur := time.Since(befReq)
returned := make(chan struct{})
go func() {
second, err := m.Request(device1, "default", file, 2000, 0, nil, 0, false)
second, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false)
if err != nil {
t.Errorf("Second request failed: %v", err)
}
@ -3829,7 +3864,10 @@ func TestClusterConfigOnFolderAdd(t *testing.T) {
fcfg := testFolderConfigTmp()
fcfg.ID = "second"
fcfg.Label = "second"
fcfg.Devices = []config.FolderDeviceConfiguration{{device2, protocol.EmptyDeviceID}}
fcfg.Devices = []config.FolderDeviceConfiguration{{
DeviceID: device2,
IntroducedBy: protocol.EmptyDeviceID,
}}
if w, err := cfg.SetFolder(fcfg); err != nil {
t.Fatal(err)
} else {
@ -3841,7 +3879,10 @@ func TestClusterConfigOnFolderAdd(t *testing.T) {
func TestClusterConfigOnFolderShare(t *testing.T) {
testConfigChangeTriggersClusterConfigs(t, true, true, nil, func(cfg config.Wrapper) {
fcfg := cfg.FolderList()[0]
fcfg.Devices = []config.FolderDeviceConfiguration{{device2, protocol.EmptyDeviceID}}
fcfg.Devices = []config.FolderDeviceConfiguration{{
DeviceID: device2,
IntroducedBy: protocol.EmptyDeviceID,
}}
if w, err := cfg.SetFolder(fcfg); err != nil {
t.Fatal(err)
} else {
@ -4161,6 +4202,145 @@ func TestNeedMetaAfterIndexReset(t *testing.T) {
}
}
func TestCcCheckEncryption(t *testing.T) {
w, fcfg := tmpDefaultWrapper()
m := setupModel(w)
m.Stop()
defer cleanupModel(m)
pw := "foo"
token := protocol.PasswordToken(fcfg.ID, pw)
m.folderEncryptionPasswordTokens[fcfg.ID] = token
testCases := []struct {
tokenRemote, tokenLocal []byte
isEncryptedRemote, isEncryptedLocal bool
expectedErr error
}{
{
tokenRemote: token,
tokenLocal: token,
expectedErr: errEncryptionInvConfigRemote,
},
{
isEncryptedRemote: true,
isEncryptedLocal: true,
expectedErr: errEncryptionInvConfigLocal,
},
{
tokenRemote: token,
tokenLocal: nil,
isEncryptedRemote: false,
isEncryptedLocal: false,
expectedErr: errEncryptionNotEncryptedLocal,
},
{
tokenRemote: token,
tokenLocal: nil,
isEncryptedRemote: true,
isEncryptedLocal: false,
expectedErr: nil,
},
{
tokenRemote: token,
tokenLocal: nil,
isEncryptedRemote: false,
isEncryptedLocal: true,
expectedErr: nil,
},
{
tokenRemote: nil,
tokenLocal: token,
isEncryptedRemote: true,
isEncryptedLocal: false,
expectedErr: nil,
},
{
tokenRemote: nil,
tokenLocal: token,
isEncryptedRemote: false,
isEncryptedLocal: true,
expectedErr: nil,
},
{
tokenRemote: nil,
tokenLocal: token,
isEncryptedRemote: false,
isEncryptedLocal: false,
expectedErr: errEncryptionNotEncryptedLocal,
},
{
tokenRemote: nil,
tokenLocal: nil,
isEncryptedRemote: true,
isEncryptedLocal: false,
expectedErr: errEncryptionNotEncryptedRemote,
},
{
tokenRemote: nil,
tokenLocal: nil,
isEncryptedRemote: false,
isEncryptedLocal: true,
expectedErr: errEncryptionNotEncryptedRemote,
},
{
tokenRemote: nil,
tokenLocal: nil,
isEncryptedRemote: false,
isEncryptedLocal: false,
expectedErr: nil,
},
}
for i, tc := range testCases {
tfcfg := fcfg.Copy()
if tc.isEncryptedLocal {
tfcfg.Type = config.FolderTypeReceiveEncrypted
m.folderEncryptionPasswordTokens[fcfg.ID] = token
}
dcfg := config.FolderDeviceConfiguration{DeviceID: device1}
if tc.isEncryptedRemote {
dcfg.EncryptionPassword = pw
}
deviceInfos := &indexSenderStartInfo{
remote: protocol.Device{ID: device1, EncryptionPasswordToken: tc.tokenRemote},
local: protocol.Device{ID: myID, EncryptionPasswordToken: tc.tokenLocal},
}
err := m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, false)
if err != tc.expectedErr {
t.Errorf("Testcase %v: Expected error %v, got %v", i, tc.expectedErr, err)
}
if tc.expectedErr == nil {
err := m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, true)
if tc.isEncryptedRemote || tc.isEncryptedLocal {
if err != nil {
t.Errorf("Testcase %v: Expected no error, got %v", i, err)
}
} else {
if err != errEncryptionNotEncryptedUntrusted {
t.Errorf("Testcase %v: Expected error %v, got %v", i, errEncryptionNotEncryptedUntrusted, err)
}
}
}
if err != nil || (!tc.isEncryptedRemote && !tc.isEncryptedLocal) {
continue
}
if tc.isEncryptedLocal {
m.folderEncryptionPasswordTokens[fcfg.ID] = []byte("notAMatch")
} else {
dcfg.EncryptionPassword = "notAMatch"
}
err = m.ccCheckEncryption(tfcfg, dcfg, deviceInfos, false)
if err != errEncryptionPassword {
t.Errorf("Testcase %v: Expected error %v, got %v", i, errEncryptionPassword, err)
}
}
}
func equalStringsInAnyOrder(a, b []string) bool {
if len(a) != len(b) {
return false

View File

@ -101,7 +101,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
<-done
// Request a file by traversing the symlink
res, err := m.Request(device1, "default", "symlink/requests_test.go", 10, 0, nil, 0, false)
res, err := m.Request(device1, "default", "symlink/requests_test.go", 0, 10, 0, nil, 0, false)
if err == nil || res != nil {
t.Error("Managed to traverse symlink")
}
@ -499,7 +499,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
}
res, err := m.Request(device1, "default", "foo", int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
res, err := m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err != nil {
t.Fatal(err)
}
@ -513,7 +513,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
must(t, writeFile(tfs, "foo", payload, 0777))
_, err = m.Request(device1, "default", "foo", int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
_, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err == nil {
t.Fatalf("expected failure")
}

View File

@ -7,6 +7,7 @@
package model
import (
"encoding/binary"
"time"
"github.com/pkg/errors"
@ -131,7 +132,7 @@ func (s *sharedPullerState) tempFile() (*lockedWriterAt, error) {
return s.writer, nil
}
if err := inWritableDir(s.tempFileInWritableDir, s.fs, s.tempName, s.ignorePerms); err != nil {
if err := s.addWriterLocked(); err != nil {
s.failLocked(err)
return nil, err
}
@ -139,6 +140,10 @@ func (s *sharedPullerState) tempFile() (*lockedWriterAt, error) {
return s.writer, nil
}
func (s *sharedPullerState) addWriterLocked() error {
return inWritableDir(s.tempFileInWritableDir, s.fs, s.tempName, s.ignorePerms)
}
// tempFileInWritableDir should only be called from tempFile.
func (s *sharedPullerState) tempFileInWritableDir(_ string) error {
// The permissions to use for the temporary file should be those of the
@ -184,9 +189,14 @@ func (s *sharedPullerState) tempFileInWritableDir(_ string) error {
// Don't truncate symlink files, as that will mean that the path will
// contain a bunch of nulls.
if s.sparse && !s.file.IsSymlink() {
size := s.file.Size
// Trailer added to encrypted files
if len(s.file.Encrypted) > 0 {
size += int64(s.file.ProtoSize() + 4)
}
// Truncate sets the size of the file. This creates a sparse file or a
// space reservation, depending on the underlying filesystem.
if err := fd.Truncate(s.file.Size); err != nil {
if err := fd.Truncate(size); err != nil {
// The truncate call failed. That can happen in some cases when
// space reservation isn't possible or over some network
// filesystems... This generally doesn't matter.
@ -305,6 +315,13 @@ func (s *sharedPullerState) finalClose() (bool, error) {
return false, nil
}
if len(s.file.Encrypted) > 0 {
if err := s.finalizeEncrypted(); err != nil && s.err == nil {
// This is our error as we weren't errored before.
s.err = err
}
}
if s.writer != nil {
if err := s.writer.SyncClose(s.fsync); err != nil && s.err == nil {
// This is our error as we weren't errored before.
@ -324,6 +341,34 @@ func (s *sharedPullerState) finalClose() (bool, error) {
return true, s.err
}
// finalizeEncrypted adds a trailer to the encrypted file containing the
// serialized FileInfo and the length of that FileInfo. When initializing a
// folder from encrypted data we can extract this FileInfo from the end of
// the file and regain the original metadata.
func (s *sharedPullerState) finalizeEncrypted() error {
size := s.file.ProtoSize()
bs := make([]byte, 4+size)
n, err := s.file.MarshalTo(bs)
if err != nil {
return err
}
binary.BigEndian.PutUint32(bs[n:], uint32(n))
bs = bs[:n+4]
if s.writer == nil {
if err := s.addWriterLocked(); err != nil {
return err
}
}
if _, err := s.writer.WriteAt(bs, s.file.Size); err != nil {
return err
}
s.file.Size += int64(len(bs))
return nil
}
// Progress returns the momentarily progress for the puller
func (s *sharedPullerState) Progress() *pullerProgress {
s.mut.RLock()

View File

@ -49,6 +49,7 @@ func init() {
defaultCfg = defaultCfgWrapper.RawCopy()
defaultAutoAcceptCfg = config.Configuration{
Version: config.CurrentVersion,
Devices: []config.DeviceConfiguration{
{
DeviceID: myID, // self

View File

@ -81,9 +81,9 @@ func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) {
// Use c0 and c1 for each alternating request, so we get as much
// data flowing in both directions.
if i%2 == 0 {
buf, err = c0.Request(context.Background(), "folder", "file", int64(i), 128<<10, nil, 0, false)
buf, err = c0.Request(context.Background(), "folder", "file", i, int64(i), 128<<10, nil, 0, false)
} else {
buf, err = c1.Request(context.Background(), "folder", "file", int64(i), 128<<10, nil, 0, false)
buf, err = c1.Request(context.Background(), "folder", "file", i, int64(i), 128<<10, nil, 0, false)
}
if err != nil {
@ -174,7 +174,7 @@ func (m *fakeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileIn
return nil
}
func (m *fakeModel) Request(deviceID DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
func (m *fakeModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
// We write the offset to the end of the buffer, so the receiver
// can verify that it did in fact get some data back over the
// connection.

View File

@ -378,6 +378,7 @@ type Device struct {
Introducer bool `protobuf:"varint,7,opt,name=introducer,proto3" json:"introducer" xml:"introducer"`
IndexID IndexID `protobuf:"varint,8,opt,name=index_id,json=indexId,proto3,customtype=IndexID" json:"indexId" xml:"indexId"`
SkipIntroductionRemovals bool `protobuf:"varint,9,opt,name=skip_introduction_removals,json=skipIntroductionRemovals,proto3" json:"skipIntroductionRemovals" xml:"skipIntroductionRemovals"`
EncryptionPasswordToken []byte `protobuf:"bytes,10,opt,name=encryption_password_token,json=encryptionPasswordToken,proto3" json:"encryptionPasswordToken" xml:"encryptionPasswordToken"`
}
func (m *Device) Reset() { *m = Device{} }
@ -499,6 +500,7 @@ type FileInfo struct {
Blocks []BlockInfo `protobuf:"bytes,16,rep,name=blocks,proto3" json:"blocks" xml:"block"`
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlinkTarget" xml:"symlinkTarget"`
BlocksHash []byte `protobuf:"bytes,18,opt,name=blocks_hash,json=blocksHash,proto3" json:"blocksHash" xml:"blocksHash"`
Encrypted []byte `protobuf:"bytes,19,opt,name=encrypted,proto3" json:"encrypted" xml:"encrypted"`
Type FileInfoType `protobuf:"varint,2,opt,name=type,proto3,enum=protocol.FileInfoType" json:"type" xml:"type"`
Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions" xml:"permissions"`
ModifiedNs int `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3,casttype=int" json:"modifiedNs" xml:"modifiedNs"`
@ -671,6 +673,7 @@ type Request struct {
Hash []byte `protobuf:"bytes,6,opt,name=hash,proto3" json:"hash" xml:"hash"`
FromTemporary bool `protobuf:"varint,7,opt,name=from_temporary,json=fromTemporary,proto3" json:"fromTemporary" xml:"fromTemporary"`
WeakHash uint32 `protobuf:"varint,8,opt,name=weak_hash,json=weakHash,proto3" json:"weakHash" xml:"weakHash"`
BlockNo int `protobuf:"varint,9,opt,name=block_no,json=blockNo,proto3,casttype=int" json:"blockNo" xml:"blockNo"`
}
func (m *Request) Reset() { *m = Request{} }
@ -926,169 +929,175 @@ func init() {
func init() { proto.RegisterFile("lib/protocol/bep.proto", fileDescriptor_311ef540e10d9705) }
var fileDescriptor_311ef540e10d9705 = []byte{
// 2584 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x59, 0x4d, 0x6c, 0xe3, 0xc6,
0xf5, 0x17, 0xf5, 0x61, 0xcb, 0x63, 0x7b, 0x23, 0xcf, 0x7e, 0x29, 0xda, 0x8d, 0xa8, 0xff, 0xfc,
0x37, 0xad, 0xe3, 0x34, 0xde, 0xc4, 0x49, 0xda, 0x34, 0x49, 0x13, 0xe8, 0x83, 0xb6, 0x95, 0xd8,
0x92, 0x3b, 0xf2, 0x6e, 0xba, 0x8b, 0x16, 0x04, 0x2d, 0x8e, 0x6d, 0x62, 0x29, 0x52, 0x25, 0x65,
0xef, 0x3a, 0xe8, 0xa9, 0xbd, 0x04, 0x3a, 0x14, 0x45, 0x4e, 0x45, 0x51, 0xa1, 0x69, 0x2f, 0xbd,
0x15, 0xe8, 0xa1, 0x97, 0xdc, 0x7a, 0x29, 0x72, 0x5c, 0x04, 0x28, 0x50, 0xf4, 0x40, 0x20, 0xbb,
0x97, 0x56, 0x47, 0x1d, 0x7b, 0x2a, 0xe6, 0x83, 0xe4, 0xd0, 0xb2, 0x03, 0x27, 0x39, 0xf4, 0xc6,
0xf7, 0x7b, 0x1f, 0x1c, 0xce, 0xfb, 0xbd, 0x37, 0x6f, 0x24, 0x70, 0xcd, 0xb6, 0xf6, 0x6e, 0xf7,
0x3d, 0x77, 0xe0, 0x76, 0x5d, 0xfb, 0xf6, 0x1e, 0xe9, 0xaf, 0x32, 0x01, 0xe6, 0x43, 0xac, 0x34,
0x47, 0x1e, 0x0d, 0x38, 0x58, 0xfa, 0x7f, 0x8f, 0xf4, 0x5d, 0x9f, 0x9b, 0xef, 0x1d, 0xed, 0xdf,
0x3e, 0x70, 0x0f, 0x5c, 0x26, 0xb0, 0x27, 0x6e, 0x84, 0x9e, 0x28, 0x20, 0xb7, 0x49, 0x6c, 0xdb,
0x85, 0x75, 0x30, 0x6f, 0x92, 0x63, 0xab, 0x4b, 0x74, 0xc7, 0xe8, 0x91, 0xa2, 0x52, 0x51, 0x96,
0xe7, 0x6a, 0x68, 0x1c, 0xa8, 0x80, 0xc3, 0x2d, 0xa3, 0x47, 0x26, 0x81, 0x5a, 0x78, 0xd4, 0xb3,
0xdf, 0x44, 0x31, 0x84, 0xb0, 0xa4, 0xa7, 0x41, 0xba, 0xb6, 0x45, 0x9c, 0x01, 0x0f, 0x92, 0x8e,
0x83, 0x70, 0x38, 0x11, 0x24, 0x86, 0x10, 0x96, 0xf4, 0xb0, 0x0d, 0x2e, 0x89, 0x20, 0xc7, 0xc4,
0xf3, 0x2d, 0xd7, 0x29, 0x66, 0x58, 0x9c, 0xe5, 0x71, 0xa0, 0x2e, 0x72, 0xcd, 0x5d, 0xae, 0x98,
0x04, 0xea, 0x65, 0x29, 0x94, 0x40, 0x11, 0x4e, 0x5a, 0xa1, 0x3f, 0x2b, 0x60, 0x66, 0x93, 0x18,
0x26, 0xf1, 0x60, 0x15, 0x64, 0x07, 0x27, 0x7d, 0xfe, 0x79, 0x97, 0xd6, 0xae, 0xae, 0x86, 0x1b,
0xb7, 0xba, 0x4d, 0x7c, 0xdf, 0x38, 0x20, 0xbb, 0x27, 0x7d, 0x52, 0xbb, 0x36, 0x0e, 0x54, 0x66,
0x36, 0x09, 0x54, 0xc0, 0xe2, 0x53, 0x01, 0x61, 0x86, 0x41, 0x13, 0xcc, 0x77, 0xdd, 0x5e, 0xdf,
0x23, 0x3e, 0x5b, 0x5b, 0x9a, 0x45, 0xba, 0x39, 0x15, 0xa9, 0x1e, 0xdb, 0xd4, 0x6e, 0x8d, 0x03,
0x55, 0x76, 0x9a, 0x04, 0xea, 0x12, 0x5f, 0x77, 0x8c, 0x21, 0x2c, 0x5b, 0xa0, 0x1f, 0x83, 0xc5,
0xba, 0x7d, 0xe4, 0x0f, 0x88, 0x57, 0x77, 0x9d, 0x7d, 0xeb, 0x00, 0xbe, 0x0f, 0x66, 0xf7, 0x5d,
0xdb, 0x24, 0x9e, 0x5f, 0x54, 0x2a, 0x99, 0xe5, 0xf9, 0xb5, 0x42, 0xfc, 0xca, 0x75, 0xa6, 0xa8,
0xa9, 0x9f, 0x05, 0x6a, 0x6a, 0x1c, 0xa8, 0xa1, 0xe1, 0x24, 0x50, 0x17, 0xd8, 0x6b, 0xb8, 0x8c,
0x70, 0xa8, 0x40, 0x9f, 0x66, 0xc1, 0x0c, 0x77, 0x82, 0xab, 0x20, 0x6d, 0x99, 0x22, 0xdd, 0xe5,
0x27, 0x81, 0x9a, 0x6e, 0x36, 0xc6, 0x81, 0x9a, 0xb6, 0xcc, 0x49, 0xa0, 0xe6, 0x99, 0xb7, 0x65,
0xa2, 0x8f, 0x1f, 0xdf, 0x4a, 0x37, 0x1b, 0x38, 0x6d, 0x99, 0x70, 0x15, 0xe4, 0x6c, 0x63, 0x8f,
0xd8, 0x22, 0xb9, 0xc5, 0x71, 0xa0, 0x72, 0x60, 0x12, 0xa8, 0xf3, 0xcc, 0x9e, 0x49, 0x08, 0x73,
0x14, 0xbe, 0x05, 0xe6, 0x3c, 0x62, 0x98, 0xba, 0xeb, 0xd8, 0x27, 0x2c, 0x91, 0xf9, 0x5a, 0x79,
0x1c, 0xa8, 0x79, 0x0a, 0xb6, 0x1d, 0xfb, 0x64, 0x12, 0xa8, 0x97, 0x98, 0x5b, 0x08, 0x20, 0x1c,
0xe9, 0xa0, 0x0e, 0xa0, 0x75, 0xe0, 0xb8, 0x1e, 0xd1, 0xfb, 0xc4, 0xeb, 0x59, 0x6c, 0x6b, 0xfc,
0x62, 0x96, 0x45, 0x79, 0x79, 0x1c, 0xa8, 0x4b, 0x5c, 0xbb, 0x13, 0x2b, 0x27, 0x81, 0x7a, 0x9d,
0xaf, 0xfa, 0xb4, 0x06, 0xe1, 0x69, 0x6b, 0xf8, 0x3e, 0x58, 0x14, 0x2f, 0x30, 0x89, 0x4d, 0x06,
0xa4, 0x98, 0x63, 0xb1, 0xbf, 0x35, 0x0e, 0xd4, 0x05, 0xae, 0x68, 0x30, 0x7c, 0x12, 0xa8, 0x50,
0x0a, 0xcb, 0x41, 0x84, 0x13, 0x36, 0xd0, 0x04, 0x57, 0x4c, 0xcb, 0x37, 0xf6, 0x6c, 0xa2, 0x0f,
0x48, 0xaf, 0xaf, 0x5b, 0x8e, 0x49, 0x1e, 0x11, 0xbf, 0x38, 0xc3, 0x62, 0xae, 0x8d, 0x03, 0x15,
0x0a, 0xfd, 0x2e, 0xe9, 0xf5, 0x9b, 0x5c, 0x3b, 0x09, 0xd4, 0x22, 0xaf, 0xa9, 0x29, 0x15, 0xc2,
0x67, 0xd8, 0xc3, 0x35, 0x30, 0xd3, 0x37, 0x8e, 0x7c, 0x62, 0x16, 0x67, 0x59, 0xdc, 0xd2, 0x38,
0x50, 0x05, 0x12, 0x25, 0x9c, 0x8b, 0x08, 0x0b, 0x9c, 0x92, 0x87, 0x57, 0xa9, 0x5f, 0x2c, 0x9c,
0x26, 0x4f, 0x83, 0x29, 0x62, 0xf2, 0x08, 0xc3, 0x28, 0x16, 0x97, 0x11, 0x0e, 0x15, 0xe8, 0x6f,
0x39, 0x30, 0xc3, 0x9d, 0x60, 0x2d, 0x22, 0xcf, 0x42, 0x6d, 0x8d, 0x06, 0xf8, 0x67, 0xa0, 0xe6,
0xb9, 0xae, 0xd9, 0x38, 0x8f, 0x4c, 0x1f, 0x3d, 0xbe, 0xa5, 0x48, 0x84, 0x5a, 0x01, 0x59, 0xa9,
0x59, 0xb0, 0xda, 0x73, 0x78, 0x9b, 0xe0, 0xb5, 0xe7, 0xb0, 0x06, 0xc1, 0x30, 0xf8, 0x36, 0x98,
0x33, 0x4c, 0x93, 0xd6, 0x08, 0xf1, 0x8b, 0x99, 0x4a, 0x86, 0x72, 0x76, 0x1c, 0xa8, 0x31, 0x38,
0x09, 0xd4, 0x45, 0xe6, 0x25, 0x10, 0x84, 0x63, 0x1d, 0xfc, 0x49, 0xb2, 0x72, 0xb3, 0xa7, 0x7b,
0xc0, 0x37, 0x2b, 0x59, 0xca, 0xf4, 0x2e, 0xf1, 0x44, 0xeb, 0xcb, 0xf1, 0x82, 0xa2, 0x4c, 0xa7,
0xa0, 0x68, 0x7c, 0x9c, 0xe9, 0x21, 0x80, 0x70, 0xa4, 0x83, 0x1b, 0x60, 0xa1, 0x67, 0x3c, 0xd2,
0x7d, 0xf2, 0xd3, 0x23, 0xe2, 0x74, 0x09, 0xe3, 0x4c, 0x86, 0xaf, 0xa2, 0x67, 0x3c, 0xea, 0x08,
0x38, 0x5a, 0x85, 0x84, 0x21, 0x2c, 0x5b, 0xc0, 0x1a, 0x00, 0x96, 0x33, 0xf0, 0x5c, 0xf3, 0xa8,
0x4b, 0x3c, 0x41, 0x11, 0xd6, 0x81, 0x63, 0x34, 0xea, 0xc0, 0x31, 0x84, 0xb0, 0xa4, 0x87, 0x07,
0x20, 0xcf, 0xb8, 0xab, 0x5b, 0x66, 0x31, 0x5f, 0x51, 0x96, 0xb3, 0xb5, 0x2d, 0x91, 0xdc, 0x59,
0xc6, 0x42, 0x96, 0xdb, 0xf0, 0x91, 0x72, 0x86, 0x59, 0x37, 0xcd, 0x68, 0xf7, 0x85, 0x4c, 0xfb,
0x46, 0x68, 0xf6, 0x9b, 0xf8, 0x11, 0x87, 0xf6, 0xf0, 0x67, 0xa0, 0xe4, 0x3f, 0xb0, 0x68, 0xa5,
0xf0, 0x77, 0x0f, 0x2c, 0xd7, 0xd1, 0x3d, 0xd2, 0x73, 0x8f, 0x0d, 0xdb, 0x2f, 0xce, 0xb1, 0xc5,
0xbf, 0x33, 0x0e, 0xd4, 0x22, 0xb5, 0x6a, 0x4a, 0x46, 0x58, 0xd8, 0x4c, 0x02, 0xb5, 0xcc, 0xde,
0x78, 0x9e, 0x01, 0xc2, 0xe7, 0xfa, 0xa2, 0x9f, 0x2b, 0x20, 0xc7, 0x96, 0x44, 0x6b, 0x8a, 0xb7,
0x46, 0xd1, 0x08, 0x59, 0x4d, 0x71, 0x64, 0xaa, 0x89, 0x0a, 0x1c, 0x6a, 0x20, 0xb7, 0x6f, 0xd9,
0xc4, 0x2f, 0xa6, 0x59, 0x45, 0x41, 0xa9, 0x1d, 0x5b, 0x36, 0x69, 0x3a, 0xfb, 0x6e, 0xed, 0x86,
0xa8, 0x29, 0x6e, 0x18, 0x31, 0x9a, 0x4a, 0x08, 0x73, 0x10, 0x7d, 0xa4, 0x80, 0x79, 0xb6, 0x88,
0x3b, 0x7d, 0xd3, 0x18, 0x90, 0xff, 0xe5, 0x52, 0x7e, 0x0f, 0x40, 0x3e, 0x74, 0x88, 0xca, 0x52,
0xb9, 0x40, 0x59, 0xae, 0x80, 0xac, 0x6f, 0x7d, 0x48, 0x58, 0x7b, 0xcf, 0x70, 0x5b, 0x2a, 0x47,
0xb6, 0x54, 0x40, 0x98, 0x61, 0xf0, 0x5d, 0x00, 0x7a, 0xae, 0x69, 0xed, 0x5b, 0xc4, 0xd4, 0x7d,
0x56, 0x26, 0x99, 0x5a, 0x85, 0xd6, 0x70, 0x88, 0x76, 0x26, 0x81, 0xfa, 0x0c, 0x27, 0x79, 0x88,
0x20, 0x1c, 0x6b, 0x69, 0x15, 0x47, 0x01, 0xf6, 0x4e, 0x8a, 0x0b, 0x8c, 0x9f, 0x6f, 0x87, 0xfc,
0xec, 0x1c, 0xba, 0xde, 0x80, 0x91, 0x32, 0x7a, 0x4d, 0xed, 0x24, 0x22, 0x7c, 0x0c, 0x21, 0xca,
0x47, 0x61, 0x8c, 0x25, 0x53, 0xb8, 0x05, 0x66, 0xc3, 0xb1, 0x83, 0xf2, 0x2f, 0xd1, 0x2a, 0xef,
0x92, 0xee, 0xc0, 0xf5, 0x6a, 0x95, 0xb0, 0x55, 0x1e, 0x47, 0x63, 0x08, 0xa7, 0xfd, 0x71, 0x38,
0x80, 0x84, 0x1a, 0xf8, 0x26, 0xc8, 0x47, 0x25, 0x0d, 0xd8, 0xb7, 0xb2, 0x96, 0xe0, 0xc7, 0xf5,
0xcc, 0x5b, 0x82, 0x1f, 0x15, 0x73, 0xa4, 0x83, 0xef, 0x81, 0x99, 0x3d, 0xdb, 0xed, 0x3e, 0x08,
0x7b, 0xf6, 0xe5, 0x78, 0x21, 0x35, 0x8a, 0xb3, 0xbc, 0x3e, 0x27, 0xd6, 0x22, 0x4c, 0xa3, 0x43,
0x98, 0x89, 0x08, 0x0b, 0x98, 0xce, 0x54, 0xfe, 0x49, 0xcf, 0xb6, 0x9c, 0x07, 0xfa, 0xc0, 0xf0,
0x0e, 0xc8, 0xa0, 0xb8, 0x14, 0xcf, 0x54, 0x42, 0xb3, 0xcb, 0x14, 0xd1, 0x4c, 0x95, 0x40, 0x11,
0x4e, 0x5a, 0xd1, 0x49, 0x8f, 0x87, 0xd6, 0x0f, 0x0d, 0xff, 0xb0, 0x08, 0xd9, 0x11, 0xc0, 0xfa,
0x0c, 0x87, 0x37, 0x0d, 0xff, 0x30, 0xda, 0xf6, 0x18, 0x42, 0x58, 0xd2, 0xc3, 0x9a, 0x98, 0xc6,
0xf8, 0x0c, 0x75, 0x6d, 0x9a, 0xb6, 0x17, 0x18, 0xc7, 0xd6, 0xc1, 0xfc, 0xe9, 0xd9, 0x60, 0x91,
0xf7, 0xcd, 0x7e, 0x62, 0x2a, 0xe0, 0x7d, 0xb3, 0x2f, 0xcf, 0x03, 0xb2, 0x05, 0x7c, 0x4f, 0xa2,
0x95, 0xe3, 0x17, 0xe7, 0x2b, 0xca, 0x72, 0xae, 0xf6, 0x82, 0xcc, 0xa3, 0x96, 0x3f, 0xc5, 0xa3,
0x96, 0x8f, 0xfe, 0x13, 0xa8, 0x19, 0xcb, 0x19, 0x60, 0xc9, 0x0c, 0xee, 0x03, 0xfe, 0x95, 0x3a,
0xab, 0x8a, 0x45, 0x16, 0x6a, 0xe3, 0x49, 0xa0, 0x2e, 0x60, 0xe3, 0x21, 0x4b, 0x5d, 0xc7, 0xfa,
0x90, 0x50, 0xce, 0xef, 0x85, 0x42, 0xc4, 0xf9, 0x08, 0x09, 0x03, 0x7f, 0xfc, 0xf8, 0x56, 0xc2,
0x0d, 0xc7, 0x4e, 0xb0, 0x01, 0xe6, 0x6d, 0xb7, 0x6b, 0xd8, 0xfa, 0xbe, 0x6d, 0x1c, 0xf8, 0xc5,
0x7f, 0xcd, 0xb2, 0x8f, 0x67, 0x59, 0x60, 0xf8, 0x3a, 0x85, 0xa3, 0x45, 0xc7, 0x10, 0xc2, 0x92,
0x1e, 0x6e, 0x82, 0x05, 0x41, 0x57, 0x9e, 0xcb, 0x7f, 0xcf, 0xb2, 0x64, 0xb2, 0x3d, 0x14, 0x0a,
0x91, 0xcd, 0x25, 0x99, 0xe5, 0x3c, 0x9d, 0xb2, 0x05, 0xfc, 0x2e, 0x1d, 0x33, 0xe8, 0x28, 0x64,
0x8a, 0x99, 0xe7, 0x26, 0x1f, 0x28, 0x18, 0x14, 0x55, 0x89, 0x90, 0xd9, 0x44, 0xc1, 0x9e, 0x20,
0x06, 0xb3, 0x96, 0x73, 0x6c, 0xd8, 0x56, 0x38, 0xd3, 0xbc, 0xf1, 0x24, 0x50, 0x01, 0x36, 0x1e,
0x36, 0x39, 0xca, 0x8f, 0x18, 0xf6, 0x28, 0x1d, 0x31, 0x4c, 0xa6, 0x47, 0x8c, 0x64, 0x89, 0x43,
0x3b, 0xca, 0x78, 0xc7, 0x4d, 0x8c, 0x8d, 0x79, 0x16, 0x9a, 0x31, 0xde, 0x71, 0x93, 0x23, 0x23,
0x67, 0x7c, 0x02, 0x45, 0x38, 0x69, 0xf5, 0x66, 0xf6, 0xd7, 0x9f, 0xa8, 0x29, 0xf4, 0x85, 0x02,
0xe6, 0xa2, 0xea, 0xa3, 0x8d, 0x8f, 0x6d, 0x59, 0x86, 0xed, 0x18, 0x23, 0xea, 0x21, 0xdf, 0x2a,
0x4e, 0xd4, 0x43, 0xb6, 0x47, 0x0c, 0xa3, 0x8d, 0xdd, 0xdd, 0xdf, 0xf7, 0xc9, 0x80, 0xb5, 0xd4,
0x0c, 0x6f, 0xec, 0x1c, 0x89, 0x1a, 0x3b, 0x17, 0x11, 0x16, 0x38, 0x7c, 0x45, 0x34, 0xd6, 0x34,
0xa3, 0xd0, 0x73, 0x67, 0x37, 0xd6, 0x90, 0x81, 0xbc, 0xbf, 0xbe, 0x05, 0xe6, 0x1e, 0x12, 0xe3,
0x01, 0x4f, 0x25, 0xaf, 0x06, 0xd6, 0x72, 0x28, 0x28, 0xd2, 0xc8, 0x5b, 0x4e, 0x08, 0x20, 0x1c,
0xe9, 0xc4, 0x37, 0xde, 0x07, 0x33, 0xbc, 0xd3, 0xc1, 0x1d, 0x90, 0xef, 0xba, 0x47, 0xce, 0x20,
0xbe, 0x75, 0x2c, 0xc9, 0xe3, 0x12, 0xd3, 0xd4, 0xfe, 0x4f, 0xb4, 0xa0, 0xc8, 0x34, 0xca, 0x91,
0x00, 0xe8, 0x9c, 0x23, 0x54, 0xe8, 0x17, 0x0a, 0x98, 0x15, 0x8e, 0x70, 0x33, 0x9a, 0x1e, 0xb3,
0xb5, 0x37, 0x4e, 0x35, 0xf0, 0x2f, 0xbf, 0x89, 0xc8, 0xcd, 0x5b, 0x5c, 0x4a, 0x8e, 0x0d, 0xfb,
0x88, 0x6f, 0x54, 0x96, 0x5f, 0x4a, 0x18, 0x10, 0xf5, 0x43, 0x26, 0x21, 0xcc, 0x51, 0xf4, 0xd7,
0x0c, 0x98, 0xc5, 0xb4, 0xcf, 0xfa, 0x03, 0xf8, 0x7a, 0xb4, 0x8a, 0x5c, 0xed, 0xf9, 0xf3, 0x5e,
0x1b, 0x17, 0x63, 0x38, 0xb6, 0xc6, 0xe7, 0x74, 0xfa, 0xc2, 0xe7, 0x74, 0x78, 0xa6, 0x66, 0x2e,
0x70, 0xa6, 0xc6, 0x74, 0xc9, 0x7e, 0x65, 0xba, 0xe4, 0x2e, 0x4e, 0x97, 0x90, 0xc1, 0x33, 0x17,
0x60, 0x70, 0x1b, 0x5c, 0xda, 0xf7, 0xdc, 0x1e, 0xbb, 0xdc, 0xb8, 0x9e, 0xe1, 0x9d, 0x88, 0x6a,
0x65, 0x25, 0x45, 0x35, 0xbb, 0xa1, 0x22, 0x2a, 0xa9, 0x04, 0x8a, 0x70, 0xd2, 0x2a, 0xc9, 0xd5,
0xfc, 0x57, 0xe3, 0x2a, 0xfa, 0x93, 0x02, 0xf2, 0x98, 0xf8, 0x7d, 0xd7, 0xf1, 0xc9, 0xd7, 0x4d,
0xe2, 0x0a, 0xc8, 0x9a, 0xc6, 0xc0, 0x60, 0x29, 0x14, 0x5f, 0x4f, 0xe5, 0xe8, 0xeb, 0xa9, 0x80,
0x30, 0xc3, 0xe0, 0xbb, 0x20, 0xdb, 0x75, 0x4d, 0x9e, 0xbc, 0x4b, 0xf2, 0x61, 0xac, 0x79, 0x9e,
0xeb, 0xd5, 0x5d, 0x53, 0x9c, 0x54, 0xd4, 0x28, 0x0a, 0x40, 0x05, 0x84, 0x19, 0x86, 0xfe, 0xa8,
0x80, 0x42, 0xc3, 0x7d, 0xe8, 0xd8, 0xae, 0x61, 0xee, 0x78, 0xee, 0x01, 0xbd, 0x37, 0x7c, 0xad,
0x71, 0x4f, 0x07, 0xb3, 0x47, 0x6c, 0x58, 0x0c, 0x07, 0xbe, 0x5b, 0xc9, 0x93, 0xf3, 0xf4, 0x4b,
0xf8, 0x64, 0x19, 0xdf, 0xf0, 0x84, 0x73, 0x14, 0x9f, 0xcb, 0x08, 0x87, 0x0a, 0xf4, 0x87, 0x0c,
0x28, 0x9d, 0x1f, 0x08, 0xf6, 0xc0, 0x3c, 0xb7, 0xd4, 0xa5, 0xdf, 0x52, 0x96, 0x2f, 0xb2, 0x06,
0x76, 0x9e, 0xb3, 0xf3, 0xe9, 0x28, 0x92, 0xa3, 0xf3, 0x29, 0x86, 0x10, 0x96, 0xf4, 0x5f, 0xe9,
0x82, 0x28, 0x4d, 0x6f, 0x99, 0x6f, 0x3e, 0xbd, 0x75, 0xc0, 0x22, 0x3f, 0xc7, 0xc3, 0x9b, 0x7c,
0xb6, 0x92, 0x59, 0xce, 0xd5, 0x56, 0xc7, 0x81, 0xba, 0xb0, 0xc7, 0x0f, 0x81, 0xf0, 0x0e, 0xbf,
0x14, 0x9f, 0xde, 0x1c, 0x0c, 0xd9, 0x56, 0x48, 0xe1, 0x84, 0x2d, 0x5c, 0x4f, 0x0c, 0x07, 0xbc,
0x54, 0xbf, 0x7d, 0xc1, 0x61, 0x40, 0x3a, 0xfc, 0xd1, 0x0c, 0xc8, 0xee, 0x58, 0xce, 0x01, 0x7a,
0x0b, 0xe4, 0xea, 0xb6, 0xeb, 0xb3, 0x8e, 0xe1, 0x11, 0xc3, 0x77, 0x1d, 0x99, 0x4a, 0x1c, 0x89,
0x52, 0xcd, 0x45, 0x84, 0x05, 0xbe, 0xf2, 0x69, 0x06, 0xcc, 0x4b, 0x3f, 0x7d, 0xc1, 0x1f, 0x80,
0x1b, 0xdb, 0x5a, 0xa7, 0x53, 0xdd, 0xd0, 0xf4, 0xdd, 0x7b, 0x3b, 0x9a, 0x5e, 0xdf, 0xba, 0xd3,
0xd9, 0xd5, 0xb0, 0x5e, 0x6f, 0xb7, 0xd6, 0x9b, 0x1b, 0x85, 0x54, 0xe9, 0xe6, 0x70, 0x54, 0x29,
0x4a, 0x1e, 0xc9, 0x1f, 0xa9, 0xbe, 0x03, 0x60, 0xc2, 0xbd, 0xd9, 0x6a, 0x68, 0x3f, 0x2a, 0x28,
0xa5, 0x2b, 0xc3, 0x51, 0xa5, 0x20, 0x79, 0xf1, 0x5b, 0xd7, 0xf7, 0xc1, 0xb3, 0xd3, 0xd6, 0xfa,
0x9d, 0x9d, 0x46, 0x75, 0x57, 0x2b, 0xa4, 0x4b, 0xa5, 0xe1, 0xa8, 0x72, 0xed, 0xb4, 0x93, 0xa0,
0xe0, 0xcb, 0xe0, 0x4a, 0xc2, 0x15, 0x6b, 0x3f, 0xbc, 0xa3, 0x75, 0x76, 0x0b, 0x99, 0xd2, 0xb5,
0xe1, 0xa8, 0x02, 0x25, 0xaf, 0xb0, 0xcd, 0xaf, 0x81, 0xab, 0xa7, 0x3c, 0x3a, 0x3b, 0xed, 0x56,
0x47, 0x2b, 0x64, 0x4b, 0xd7, 0x87, 0xa3, 0xca, 0xe5, 0x84, 0x8b, 0xe8, 0x2a, 0x75, 0x50, 0x4e,
0xf8, 0x34, 0xda, 0x1f, 0xb4, 0xb6, 0xda, 0xd5, 0x86, 0xbe, 0x83, 0xdb, 0x1b, 0x58, 0xeb, 0x74,
0x0a, 0xb9, 0x92, 0x3a, 0x1c, 0x55, 0x6e, 0x48, 0xce, 0x53, 0x15, 0xbe, 0x02, 0x96, 0x12, 0x41,
0x76, 0x9a, 0xad, 0x8d, 0xc2, 0x4c, 0xe9, 0xf2, 0x70, 0x54, 0x79, 0x46, 0xf2, 0xa3, 0xb9, 0x9c,
0xda, 0xbf, 0xfa, 0x56, 0xbb, 0xa3, 0x15, 0x66, 0xa7, 0xf6, 0x8f, 0x25, 0x7c, 0xe5, 0x77, 0x0a,
0x80, 0xd3, 0xbf, 0x36, 0xc2, 0x37, 0x40, 0x31, 0x0c, 0x52, 0x6f, 0x6f, 0xef, 0xd0, 0x75, 0x36,
0xdb, 0x2d, 0xbd, 0xd5, 0x6e, 0x69, 0x85, 0x54, 0x62, 0x57, 0x25, 0xaf, 0x96, 0xeb, 0x10, 0xd8,
0x06, 0xd7, 0xcf, 0xf2, 0xdc, 0xba, 0xff, 0x5a, 0x41, 0x29, 0xad, 0x0d, 0x47, 0x95, 0xab, 0xd3,
0x8e, 0x5b, 0xf7, 0x5f, 0xfb, 0xfc, 0x97, 0xcf, 0x9f, 0xad, 0x58, 0xf9, 0xad, 0x02, 0xe6, 0xe5,
0xa5, 0xbd, 0x02, 0xae, 0xc8, 0x81, 0xb7, 0xb5, 0xdd, 0x6a, 0xa3, 0xba, 0x5b, 0x2d, 0xa4, 0x78,
0x0e, 0x24, 0xd3, 0x6d, 0x32, 0x30, 0x58, 0xdb, 0x7d, 0x11, 0x2c, 0x25, 0xbe, 0x42, 0xbb, 0xab,
0xe1, 0x90, 0x51, 0xf2, 0xfa, 0xc9, 0x31, 0xf1, 0xe0, 0x4b, 0x00, 0xca, 0xc6, 0xd5, 0xad, 0x0f,
0xaa, 0xf7, 0x3a, 0x85, 0x74, 0xe9, 0xea, 0x70, 0x54, 0x59, 0x92, 0xac, 0xab, 0xf6, 0x43, 0xe3,
0xc4, 0x5f, 0xf9, 0x4b, 0x1a, 0x2c, 0xc8, 0x57, 0x0d, 0xf8, 0x12, 0xb8, 0xbc, 0xde, 0xdc, 0xa2,
0x4c, 0x5c, 0x6f, 0xf3, 0x0c, 0x50, 0xb1, 0x90, 0xe2, 0xaf, 0x93, 0x4d, 0xe9, 0x33, 0xfc, 0x1e,
0x28, 0x9e, 0x32, 0x6f, 0x34, 0xb1, 0x56, 0xdf, 0x6d, 0xe3, 0x7b, 0x05, 0xa5, 0xf4, 0x2c, 0xdd,
0x30, 0xd9, 0xa7, 0x61, 0x79, 0xac, 0x05, 0x9d, 0xc0, 0x77, 0xc0, 0x8d, 0x53, 0x8e, 0x9d, 0x7b,
0xdb, 0x5b, 0xcd, 0xd6, 0xfb, 0xfc, 0x7d, 0xe9, 0xd2, 0x73, 0xc3, 0x51, 0xe5, 0xba, 0xec, 0xdb,
0xe1, 0xb7, 0x2f, 0x0a, 0xe5, 0x15, 0xb8, 0x09, 0x2a, 0xe7, 0xf8, 0xc7, 0x0b, 0xc8, 0x94, 0xd0,
0x70, 0x54, 0xb9, 0x79, 0x46, 0x90, 0x68, 0x1d, 0x79, 0x05, 0xbe, 0x0a, 0xae, 0x9d, 0x1d, 0x29,
0xac, 0x8b, 0x33, 0xfc, 0x57, 0xfe, 0xae, 0x80, 0xb9, 0xe8, 0xd4, 0xa3, 0x9b, 0xa6, 0x61, 0xdc,
0xa6, 0x4d, 0xa2, 0xa1, 0xe9, 0xad, 0xb6, 0xce, 0xa4, 0x70, 0xd3, 0x22, 0xbb, 0x96, 0xcb, 0x1e,
0x29, 0xc7, 0x25, 0xf3, 0x0d, 0xad, 0xa5, 0xe1, 0x66, 0x3d, 0xcc, 0x68, 0x64, 0xbd, 0x41, 0x1c,
0xe2, 0x59, 0x5d, 0xf8, 0x1a, 0xb8, 0x9e, 0x0c, 0xde, 0xb9, 0x53, 0xdf, 0x0c, 0x77, 0x89, 0x2d,
0x50, 0x7a, 0x41, 0xe7, 0xa8, 0x7b, 0xc8, 0x12, 0xf3, 0x7a, 0xc2, 0xab, 0xd9, 0xba, 0x5b, 0xdd,
0x6a, 0x36, 0xb8, 0x57, 0xa6, 0x54, 0x1c, 0x8e, 0x2a, 0x57, 0x22, 0x2f, 0x71, 0x71, 0xa0, 0x6e,
0x2b, 0x9f, 0x2b, 0xa0, 0xfc, 0xe5, 0x87, 0x17, 0xfc, 0x00, 0xbc, 0xc0, 0xf6, 0x6b, 0xaa, 0x15,
0x88, 0xbe, 0xc5, 0xf7, 0xb0, 0xba, 0xb3, 0xa3, 0xb5, 0x1a, 0x85, 0x54, 0x69, 0x79, 0x38, 0xaa,
0xdc, 0xfa, 0xf2, 0x90, 0xd5, 0x7e, 0x9f, 0x38, 0xe6, 0x05, 0x03, 0xaf, 0xb7, 0xf1, 0x86, 0xb6,
0x5b, 0x50, 0x2e, 0x12, 0x78, 0xdd, 0xa5, 0x37, 0xf5, 0xda, 0xf6, 0x67, 0x5f, 0x94, 0x53, 0x8f,
0xbf, 0x28, 0xa7, 0x3e, 0x7b, 0x52, 0x56, 0x1e, 0x3f, 0x29, 0x2b, 0xbf, 0x7a, 0x5a, 0x4e, 0x7d,
0xf2, 0xb4, 0xac, 0x3c, 0x7e, 0x5a, 0x4e, 0xfd, 0xe3, 0x69, 0x39, 0x75, 0xff, 0xc5, 0x03, 0x6b,
0x70, 0x78, 0xb4, 0xb7, 0xda, 0x75, 0x7b, 0xb7, 0xfd, 0x13, 0xa7, 0x3b, 0x38, 0xb4, 0x9c, 0x03,
0xe9, 0x49, 0xfe, 0xd7, 0x69, 0x6f, 0x86, 0x3d, 0xbd, 0xfa, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff,
0xf3, 0xce, 0x28, 0x00, 0x8c, 0x1a, 0x00, 0x00,
// 2681 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x59, 0x4d, 0x6c, 0x1b, 0xc7,
0xf5, 0xd7, 0x92, 0x94, 0x44, 0x8d, 0x24, 0x87, 0x1a, 0x7f, 0x6d, 0x68, 0x9b, 0xcb, 0xff, 0xc4,
0xf9, 0x57, 0x51, 0x1a, 0x39, 0x51, 0x92, 0x36, 0x4d, 0x52, 0x07, 0xfc, 0x92, 0xc4, 0x44, 0x22,
0xd9, 0x21, 0xed, 0xd4, 0x46, 0x0b, 0x62, 0xc5, 0x1d, 0x51, 0x0b, 0x2f, 0x77, 0xd9, 0x5d, 0x4a,
0xb6, 0x82, 0x5e, 0xda, 0x5e, 0x02, 0x1e, 0x8a, 0x22, 0xa7, 0xa2, 0x28, 0xd1, 0xa0, 0x97, 0xde,
0x0a, 0xf4, 0xd0, 0x4b, 0x4e, 0x3d, 0xe6, 0x68, 0x04, 0x28, 0x50, 0xf4, 0xb0, 0x80, 0xed, 0x4b,
0xcb, 0x23, 0x8f, 0x3d, 0x15, 0xf3, 0xb1, 0xbb, 0xb3, 0xa2, 0x14, 0xc8, 0xc9, 0xa1, 0xb7, 0x7d,
0xbf, 0xf7, 0x7b, 0x6f, 0x86, 0x6f, 0xde, 0x7b, 0xf3, 0x76, 0x09, 0xae, 0x58, 0xe6, 0xde, 0xad,
0xbe, 0xeb, 0x0c, 0x9c, 0x8e, 0x63, 0xdd, 0xda, 0x23, 0xfd, 0x75, 0x26, 0xc0, 0x74, 0x80, 0x65,
0x17, 0xc8, 0xa3, 0x01, 0x07, 0xb3, 0x2f, 0xb9, 0xa4, 0xef, 0x78, 0x9c, 0xbe, 0x77, 0xb8, 0x7f,
0xab, 0xeb, 0x74, 0x1d, 0x26, 0xb0, 0x27, 0x4e, 0x42, 0x4f, 0x15, 0x30, 0xbb, 0x4d, 0x2c, 0xcb,
0x81, 0x25, 0xb0, 0x68, 0x90, 0x23, 0xb3, 0x43, 0xda, 0xb6, 0xde, 0x23, 0xaa, 0x92, 0x57, 0x56,
0x17, 0x8a, 0x68, 0xec, 0x6b, 0x80, 0xc3, 0x35, 0xbd, 0x47, 0x26, 0xbe, 0x96, 0x79, 0xd4, 0xb3,
0xde, 0x45, 0x11, 0x84, 0xb0, 0xa4, 0xa7, 0x4e, 0x3a, 0x96, 0x49, 0xec, 0x01, 0x77, 0x92, 0x88,
0x9c, 0x70, 0x38, 0xe6, 0x24, 0x82, 0x10, 0x96, 0xf4, 0xb0, 0x0e, 0x2e, 0x08, 0x27, 0x47, 0xc4,
0xf5, 0x4c, 0xc7, 0x56, 0x93, 0xcc, 0xcf, 0xea, 0xd8, 0xd7, 0x96, 0xb9, 0xe6, 0x2e, 0x57, 0x4c,
0x7c, 0xed, 0xa2, 0xe4, 0x4a, 0xa0, 0x08, 0xc7, 0x59, 0xe8, 0x2f, 0x0a, 0x98, 0xdb, 0x26, 0xba,
0x41, 0x5c, 0x58, 0x00, 0xa9, 0xc1, 0x71, 0x9f, 0xff, 0xbc, 0x0b, 0x1b, 0x97, 0xd7, 0x83, 0xc0,
0xad, 0xef, 0x12, 0xcf, 0xd3, 0xbb, 0xa4, 0x75, 0xdc, 0x27, 0xc5, 0x2b, 0x63, 0x5f, 0x63, 0xb4,
0x89, 0xaf, 0x01, 0xe6, 0x9f, 0x0a, 0x08, 0x33, 0x0c, 0x1a, 0x60, 0xb1, 0xe3, 0xf4, 0xfa, 0x2e,
0xf1, 0xd8, 0xde, 0x12, 0xcc, 0xd3, 0xf5, 0x29, 0x4f, 0xa5, 0x88, 0x53, 0xbc, 0x39, 0xf6, 0x35,
0xd9, 0x68, 0xe2, 0x6b, 0x2b, 0x7c, 0xdf, 0x11, 0x86, 0xb0, 0xcc, 0x40, 0x3f, 0x01, 0xcb, 0x25,
0xeb, 0xd0, 0x1b, 0x10, 0xb7, 0xe4, 0xd8, 0xfb, 0x66, 0x17, 0x7e, 0x04, 0xe6, 0xf7, 0x1d, 0xcb,
0x20, 0xae, 0xa7, 0x2a, 0xf9, 0xe4, 0xea, 0xe2, 0x46, 0x26, 0x5a, 0x72, 0x93, 0x29, 0x8a, 0xda,
0x97, 0xbe, 0x36, 0x33, 0xf6, 0xb5, 0x80, 0x38, 0xf1, 0xb5, 0x25, 0xb6, 0x0c, 0x97, 0x11, 0x0e,
0x14, 0xe8, 0x8b, 0x14, 0x98, 0xe3, 0x46, 0x70, 0x1d, 0x24, 0x4c, 0x43, 0x1c, 0x77, 0xee, 0xa9,
0xaf, 0x25, 0xaa, 0xe5, 0xb1, 0xaf, 0x25, 0x4c, 0x63, 0xe2, 0x6b, 0x69, 0x66, 0x6d, 0x1a, 0xe8,
0xb3, 0xc7, 0x37, 0x13, 0xd5, 0x32, 0x4e, 0x98, 0x06, 0x5c, 0x07, 0xb3, 0x96, 0xbe, 0x47, 0x2c,
0x71, 0xb8, 0xea, 0xd8, 0xd7, 0x38, 0x30, 0xf1, 0xb5, 0x45, 0xc6, 0x67, 0x12, 0xc2, 0x1c, 0x85,
0xef, 0x81, 0x05, 0x97, 0xe8, 0x46, 0xdb, 0xb1, 0xad, 0x63, 0x76, 0x90, 0xe9, 0x62, 0x6e, 0xec,
0x6b, 0x69, 0x0a, 0xd6, 0x6d, 0xeb, 0x78, 0xe2, 0x6b, 0x17, 0x98, 0x59, 0x00, 0x20, 0x1c, 0xea,
0x60, 0x1b, 0x40, 0xb3, 0x6b, 0x3b, 0x2e, 0x69, 0xf7, 0x89, 0xdb, 0x33, 0x59, 0x68, 0x3c, 0x35,
0xc5, 0xbc, 0xbc, 0x3e, 0xf6, 0xb5, 0x15, 0xae, 0x6d, 0x44, 0xca, 0x89, 0xaf, 0x5d, 0xe5, 0xbb,
0x3e, 0xa9, 0x41, 0x78, 0x9a, 0x0d, 0x3f, 0x02, 0xcb, 0x62, 0x01, 0x83, 0x58, 0x64, 0x40, 0xd4,
0x59, 0xe6, 0xfb, 0xff, 0xc7, 0xbe, 0xb6, 0xc4, 0x15, 0x65, 0x86, 0x4f, 0x7c, 0x0d, 0x4a, 0x6e,
0x39, 0x88, 0x70, 0x8c, 0x03, 0x0d, 0x70, 0xc9, 0x30, 0x3d, 0x7d, 0xcf, 0x22, 0xed, 0x01, 0xe9,
0xf5, 0xdb, 0xa6, 0x6d, 0x90, 0x47, 0xc4, 0x53, 0xe7, 0x98, 0xcf, 0x8d, 0xb1, 0xaf, 0x41, 0xa1,
0x6f, 0x91, 0x5e, 0xbf, 0xca, 0xb5, 0x13, 0x5f, 0x53, 0x79, 0x4d, 0x4d, 0xa9, 0x10, 0x3e, 0x85,
0x0f, 0x37, 0xc0, 0x5c, 0x5f, 0x3f, 0xf4, 0x88, 0xa1, 0xce, 0x33, 0xbf, 0xd9, 0xb1, 0xaf, 0x09,
0x24, 0x3c, 0x70, 0x2e, 0x22, 0x2c, 0x70, 0x9a, 0x3c, 0xbc, 0x4a, 0x3d, 0x35, 0x73, 0x32, 0x79,
0xca, 0x4c, 0x11, 0x25, 0x8f, 0x20, 0x86, 0xbe, 0xb8, 0x8c, 0x70, 0xa0, 0x40, 0x7f, 0x9b, 0x03,
0x73, 0xdc, 0x08, 0x16, 0xc3, 0xe4, 0x59, 0x2a, 0x6e, 0x50, 0x07, 0xff, 0xf4, 0xb5, 0x34, 0xd7,
0x55, 0xcb, 0x67, 0x25, 0xd3, 0xa7, 0x8f, 0x6f, 0x2a, 0x52, 0x42, 0xad, 0x81, 0x94, 0xd4, 0x2c,
0x58, 0xed, 0xd9, 0xbc, 0x4d, 0xf0, 0xda, 0xb3, 0x59, 0x83, 0x60, 0x18, 0x7c, 0x1f, 0x2c, 0xe8,
0x86, 0x41, 0x6b, 0x84, 0x78, 0x6a, 0x32, 0x9f, 0xa4, 0x39, 0x3b, 0xf6, 0xb5, 0x08, 0x9c, 0xf8,
0xda, 0x32, 0xb3, 0x12, 0x08, 0xc2, 0x91, 0x0e, 0xfe, 0x34, 0x5e, 0xb9, 0xa9, 0x93, 0x3d, 0xe0,
0xdb, 0x95, 0x2c, 0xcd, 0xf4, 0x0e, 0x71, 0x45, 0xeb, 0x9b, 0xe5, 0x05, 0x45, 0x33, 0x9d, 0x82,
0xa2, 0xf1, 0xf1, 0x4c, 0x0f, 0x00, 0x84, 0x43, 0x1d, 0xdc, 0x02, 0x4b, 0x3d, 0xfd, 0x51, 0xdb,
0x23, 0x3f, 0x3b, 0x24, 0x76, 0x87, 0xb0, 0x9c, 0x49, 0xf2, 0x5d, 0xf4, 0xf4, 0x47, 0x4d, 0x01,
0x87, 0xbb, 0x90, 0x30, 0x84, 0x65, 0x06, 0x2c, 0x02, 0x60, 0xda, 0x03, 0xd7, 0x31, 0x0e, 0x3b,
0xc4, 0x15, 0x29, 0xc2, 0x3a, 0x70, 0x84, 0x86, 0x1d, 0x38, 0x82, 0x10, 0x96, 0xf4, 0xb0, 0x0b,
0xd2, 0x2c, 0x77, 0xdb, 0xa6, 0xa1, 0xa6, 0xf3, 0xca, 0x6a, 0xaa, 0xb8, 0x23, 0x0e, 0x77, 0x9e,
0x65, 0x21, 0x3b, 0xdb, 0xe0, 0x91, 0xe6, 0x0c, 0x63, 0x57, 0x8d, 0x30, 0xfa, 0x42, 0xa6, 0x7d,
0x23, 0xa0, 0xfd, 0x2e, 0x7a, 0xc4, 0x01, 0x1f, 0xfe, 0x1c, 0x64, 0xbd, 0x07, 0x26, 0xad, 0x14,
0xbe, 0xf6, 0xc0, 0x74, 0xec, 0xb6, 0x4b, 0x7a, 0xce, 0x91, 0x6e, 0x79, 0xea, 0x02, 0xdb, 0xfc,
0xed, 0xb1, 0xaf, 0xa9, 0x94, 0x55, 0x95, 0x48, 0x58, 0x70, 0x26, 0xbe, 0x96, 0x63, 0x2b, 0x9e,
0x45, 0x40, 0xf8, 0x4c, 0x5b, 0xf8, 0x08, 0xbc, 0x48, 0xec, 0x8e, 0x7b, 0xdc, 0x67, 0xcb, 0xf6,
0x75, 0xcf, 0x7b, 0xe8, 0xb8, 0x46, 0x7b, 0xe0, 0x3c, 0x20, 0xb6, 0x0a, 0x58, 0x52, 0xbf, 0x3f,
0xf6, 0xb5, 0xab, 0x11, 0xa9, 0x21, 0x38, 0x2d, 0x4a, 0x99, 0xf8, 0xda, 0x0d, 0xb6, 0xf6, 0x19,
0x7a, 0x84, 0xcf, 0xb2, 0x44, 0xbf, 0x54, 0xc0, 0x2c, 0x0b, 0x06, 0xad, 0x66, 0xde, 0x94, 0x45,
0x0b, 0x66, 0xd5, 0xcc, 0x91, 0xa9, 0xf6, 0x2d, 0x70, 0x58, 0x01, 0xb3, 0xfb, 0xa6, 0x45, 0x3c,
0x35, 0xc1, 0x6a, 0x19, 0x4a, 0x17, 0x81, 0x69, 0x91, 0xaa, 0xbd, 0xef, 0x14, 0xaf, 0x89, 0x6a,
0xe6, 0xc4, 0xb0, 0x96, 0xa8, 0x84, 0x30, 0x07, 0xd1, 0xa7, 0x0a, 0x58, 0x64, 0x9b, 0xb8, 0xd3,
0x37, 0xf4, 0x01, 0xf9, 0x5f, 0x6e, 0xe5, 0x09, 0x00, 0xe9, 0xc0, 0x20, 0x6c, 0x08, 0xca, 0x39,
0x1a, 0xc2, 0x1a, 0x48, 0x79, 0xe6, 0x27, 0x84, 0x5d, 0x2c, 0x49, 0xce, 0xa5, 0x72, 0xc8, 0xa5,
0x02, 0xc2, 0x0c, 0x83, 0x1f, 0x00, 0xd0, 0x73, 0x0c, 0x73, 0xdf, 0x24, 0x46, 0xdb, 0x63, 0x05,
0x9a, 0x2c, 0xe6, 0x69, 0xf7, 0x08, 0xd0, 0xe6, 0xc4, 0xd7, 0x5e, 0xe0, 0xe5, 0x15, 0x20, 0x08,
0x47, 0x5a, 0xda, 0x3f, 0x42, 0x07, 0x7b, 0xc7, 0xea, 0x12, 0xab, 0x8c, 0xf7, 0x83, 0xca, 0x68,
0x1e, 0x38, 0xee, 0x80, 0x95, 0x43, 0xb8, 0x4c, 0xf1, 0x38, 0x2c, 0xb5, 0x08, 0x42, 0xb4, 0x12,
0x04, 0x19, 0x4b, 0x54, 0xb8, 0x03, 0xe6, 0x83, 0x81, 0x87, 0x66, 0x7e, 0xac, 0x49, 0xdf, 0x25,
0x9d, 0x81, 0xe3, 0x16, 0xf3, 0x41, 0x93, 0x3e, 0x0a, 0x07, 0x20, 0x5e, 0x70, 0x47, 0xc1, 0xe8,
0x13, 0x68, 0xe0, 0xbb, 0x20, 0x1d, 0x36, 0x13, 0xc0, 0x7e, 0x2b, 0x6b, 0x46, 0x5e, 0xd4, 0x49,
0x78, 0x33, 0xf2, 0xc2, 0x36, 0x12, 0xea, 0xe0, 0x87, 0x60, 0x6e, 0xcf, 0x72, 0x3a, 0x0f, 0x82,
0xdb, 0xe2, 0x62, 0xb4, 0x91, 0x22, 0xc5, 0xd9, 0xb9, 0xde, 0x10, 0x7b, 0x11, 0xd4, 0xf0, 0xfa,
0x67, 0x22, 0xc2, 0x02, 0xa6, 0xd3, 0x9c, 0x77, 0xdc, 0xb3, 0x4c, 0xfb, 0x41, 0x7b, 0xa0, 0xbb,
0x5d, 0x32, 0x50, 0x57, 0xa2, 0x69, 0x4e, 0x68, 0x5a, 0x4c, 0x11, 0x4e, 0x73, 0x31, 0x14, 0xe1,
0x38, 0x8b, 0xce, 0x98, 0xdc, 0x75, 0xfb, 0x40, 0xf7, 0x0e, 0x54, 0xc8, 0xea, 0x94, 0x75, 0x38,
0x0e, 0x6f, 0xeb, 0xde, 0x41, 0x18, 0xf6, 0x08, 0x42, 0x58, 0xd2, 0xc3, 0xdb, 0x60, 0x41, 0xd4,
0x26, 0x31, 0xd4, 0x8b, 0xcc, 0x05, 0x4b, 0x85, 0x10, 0x0c, 0x53, 0x21, 0x44, 0x10, 0x8e, 0xb4,
0xb0, 0x28, 0xe6, 0x48, 0x3e, 0xfd, 0x5d, 0x99, 0x4e, 0xfb, 0x73, 0x0c, 0x92, 0x9b, 0x60, 0xf1,
0xe4, 0x54, 0xb3, 0xcc, 0x3b, 0x7e, 0x3f, 0x36, 0xcf, 0xf0, 0x8e, 0xdf, 0x97, 0x27, 0x19, 0x99,
0x01, 0x3f, 0x94, 0xd2, 0xd2, 0xf6, 0xd4, 0xc5, 0xbc, 0xb2, 0x3a, 0x5b, 0x7c, 0x45, 0xce, 0xc3,
0x9a, 0x37, 0x95, 0x87, 0x35, 0x0f, 0xfd, 0xc7, 0xd7, 0x92, 0xa6, 0x3d, 0xc0, 0x12, 0x0d, 0xee,
0x03, 0x1e, 0xa5, 0x36, 0xab, 0xaa, 0x65, 0xe6, 0x6a, 0xeb, 0xa9, 0xaf, 0x2d, 0x61, 0xfd, 0x21,
0x3b, 0xfa, 0xa6, 0xf9, 0x09, 0xa1, 0x81, 0xda, 0x0b, 0x84, 0x30, 0x50, 0x21, 0x12, 0x38, 0xfe,
0xec, 0xf1, 0xcd, 0x98, 0x19, 0x8e, 0x8c, 0x60, 0x19, 0x2c, 0x5a, 0x4e, 0x47, 0xb7, 0xda, 0xfb,
0x96, 0xde, 0xf5, 0xd4, 0x7f, 0xcd, 0xb3, 0x1f, 0xcf, 0x4e, 0x91, 0xe1, 0x9b, 0x14, 0x0e, 0x37,
0x1d, 0x41, 0x08, 0x4b, 0x7a, 0xb8, 0x0d, 0x96, 0x44, 0xba, 0xf3, 0x5c, 0xf8, 0xf7, 0x3c, 0x3b,
0x49, 0x16, 0x43, 0xa1, 0x10, 0xd9, 0xb0, 0x22, 0x57, 0x09, 0x4f, 0x07, 0x99, 0x01, 0xbf, 0x47,
0x07, 0x24, 0x3a, 0xc4, 0x19, 0x62, 0x5a, 0xbb, 0xce, 0x47, 0x21, 0x06, 0x85, 0x55, 0x26, 0x64,
0x36, 0x0b, 0xb1, 0x27, 0x88, 0xc1, 0xbc, 0x69, 0x1f, 0xe9, 0x96, 0x19, 0x4c, 0x63, 0xef, 0x3c,
0xf5, 0x35, 0x80, 0xf5, 0x87, 0x55, 0x8e, 0xf2, 0xcb, 0x91, 0x3d, 0x4a, 0x97, 0x23, 0x93, 0xe9,
0xe5, 0x28, 0x31, 0x71, 0xc0, 0xa3, 0x15, 0x63, 0x3b, 0xb1, 0x81, 0x37, 0xcd, 0x5c, 0xb3, 0x8a,
0xb1, 0x9d, 0xf8, 0xb0, 0xcb, 0x2b, 0x26, 0x86, 0x22, 0x1c, 0x67, 0xbd, 0x9b, 0xfa, 0xed, 0xe7,
0xda, 0x0c, 0x7a, 0xa2, 0x80, 0x85, 0xb0, 0x7a, 0x69, 0xe3, 0x64, 0x21, 0x4b, 0xb2, 0x88, 0xb1,
0x44, 0x3d, 0xe0, 0xa1, 0xe2, 0x89, 0x7a, 0xc0, 0x62, 0xc4, 0x30, 0x7a, 0x31, 0x38, 0xfb, 0xfb,
0x1e, 0x19, 0xb0, 0x96, 0x9c, 0xe4, 0x17, 0x03, 0x47, 0xc2, 0x8b, 0x81, 0x8b, 0x08, 0x0b, 0x1c,
0xbe, 0x21, 0x1a, 0x73, 0x82, 0xa5, 0xd0, 0x8d, 0xd3, 0x1b, 0x73, 0x90, 0x81, 0xbc, 0x3f, 0xbf,
0x07, 0x16, 0x1e, 0x12, 0xfd, 0x01, 0x3f, 0x4a, 0x5e, 0x0d, 0xac, 0x65, 0x51, 0x50, 0x1c, 0x23,
0x6f, 0x59, 0x01, 0x80, 0x70, 0xa8, 0x13, 0xbf, 0xf1, 0x3e, 0x98, 0xe3, 0x9d, 0x12, 0x36, 0x40,
0xba, 0xe3, 0x1c, 0xda, 0x83, 0xe8, 0x7d, 0x69, 0x45, 0x1e, 0xf4, 0x98, 0xa6, 0xf8, 0x7f, 0xa2,
0x85, 0x85, 0xd4, 0xf0, 0x8c, 0x04, 0x40, 0x27, 0x34, 0xa1, 0x42, 0xbf, 0x52, 0xc0, 0xbc, 0x30,
0x84, 0xdb, 0xe1, 0xdc, 0x9b, 0x2a, 0xbe, 0x73, 0xe2, 0x02, 0xf8, 0xfa, 0x77, 0x28, 0xb9, 0xf9,
0x8b, 0xd7, 0xa9, 0x23, 0xdd, 0x3a, 0xe4, 0x81, 0x4a, 0xf1, 0xd7, 0x29, 0x06, 0x84, 0xfd, 0x94,
0x49, 0x08, 0x73, 0x14, 0xfd, 0x22, 0x05, 0xe6, 0x31, 0xed, 0xd3, 0xde, 0x00, 0xbe, 0x1d, 0xee,
0x62, 0xb6, 0xf8, 0xf2, 0x59, 0xcb, 0x46, 0xc5, 0x18, 0x0c, 0xdc, 0xd1, 0x3d, 0x9f, 0x38, 0xf7,
0x3d, 0x1f, 0xdc, 0xc9, 0xc9, 0x73, 0xdc, 0xc9, 0x51, 0xba, 0xa4, 0x9e, 0x3b, 0x5d, 0x66, 0xcf,
0x9f, 0x2e, 0x41, 0x06, 0xcf, 0x9d, 0x23, 0x83, 0xeb, 0xe0, 0xc2, 0xbe, 0xeb, 0xf4, 0xd8, 0x6b,
0x99, 0xe3, 0xea, 0xee, 0xb1, 0xa8, 0x56, 0x56, 0x52, 0x54, 0xd3, 0x0a, 0x14, 0x61, 0x49, 0xc5,
0x50, 0x84, 0xe3, 0xac, 0x78, 0xae, 0xa6, 0x9f, 0x2f, 0x57, 0xe1, 0x6d, 0x90, 0xe6, 0x4d, 0xd6,
0x76, 0xd8, 0x4d, 0x3f, 0x5b, 0x7c, 0x89, 0xf6, 0x09, 0x86, 0xd5, 0x9c, 0x30, 0x07, 0x85, 0x1c,
0xfe, 0xec, 0x80, 0x80, 0xfe, 0xac, 0x80, 0x34, 0x26, 0x5e, 0xdf, 0xb1, 0x3d, 0xf2, 0x4d, 0x93,
0x60, 0x0d, 0xa4, 0x0c, 0x7d, 0xa0, 0xb3, 0x14, 0x10, 0xd1, 0xa3, 0x72, 0x18, 0x3d, 0x2a, 0x20,
0xcc, 0x30, 0xf8, 0x01, 0x48, 0x75, 0x1c, 0x83, 0x1f, 0xfe, 0x05, 0x79, 0x18, 0xa8, 0xb8, 0xae,
0xe3, 0x96, 0x1c, 0x43, 0xdc, 0x74, 0x94, 0x14, 0x3a, 0xa0, 0x02, 0xc2, 0x0c, 0x43, 0x7f, 0x52,
0x40, 0xa6, 0xec, 0x3c, 0xb4, 0x2d, 0x47, 0x37, 0x1a, 0xae, 0xd3, 0xa5, 0x6f, 0x4c, 0xdf, 0x68,
0xdc, 0x6c, 0x83, 0xf9, 0x43, 0x36, 0xac, 0x06, 0x03, 0xe7, 0xcd, 0xf8, 0xcd, 0x7b, 0x72, 0x11,
0x3e, 0xd9, 0x46, 0xef, 0xb6, 0xc2, 0x38, 0xf4, 0xcf, 0x65, 0x84, 0x03, 0x05, 0xfa, 0x63, 0x12,
0x64, 0xcf, 0x76, 0x04, 0x7b, 0x60, 0x91, 0x33, 0xdb, 0xd2, 0x57, 0xa4, 0xd5, 0xf3, 0xec, 0x81,
0xcd, 0x03, 0xec, 0x7e, 0x3b, 0x0c, 0xe5, 0xf0, 0x7e, 0x8b, 0x20, 0x84, 0x25, 0xfd, 0x73, 0xbd,
0x1a, 0x4b, 0xd3, 0x63, 0xf2, 0xdb, 0x4f, 0x8f, 0x4d, 0xb0, 0xcc, 0x53, 0x34, 0xf8, 0x86, 0x91,
0xca, 0x27, 0x57, 0x67, 0x8b, 0xeb, 0x63, 0x5f, 0x5b, 0xda, 0xe3, 0x97, 0x48, 0xf0, 0xf5, 0x62,
0x25, 0x4a, 0x56, 0x0e, 0x06, 0xd9, 0x96, 0x99, 0xc1, 0x31, 0x2e, 0xdc, 0x8c, 0x0d, 0x17, 0xbc,
0xd4, 0xbf, 0x73, 0xce, 0x61, 0x42, 0x1a, 0x1e, 0xd0, 0x1c, 0x48, 0x35, 0x4c, 0xbb, 0x8b, 0xde,
0x03, 0xb3, 0x25, 0xcb, 0xf1, 0x58, 0xc7, 0x71, 0x89, 0xee, 0x39, 0xb6, 0x9c, 0x4a, 0x1c, 0x09,
0x8f, 0x9a, 0x8b, 0x08, 0x0b, 0x7c, 0xed, 0x8b, 0x24, 0x58, 0x94, 0x3e, 0xfa, 0xc1, 0x1f, 0x82,
0x6b, 0xbb, 0x95, 0x66, 0xb3, 0xb0, 0x55, 0x69, 0xb7, 0xee, 0x35, 0x2a, 0xed, 0xd2, 0xce, 0x9d,
0x66, 0xab, 0x82, 0xdb, 0xa5, 0x7a, 0x6d, 0xb3, 0xba, 0x95, 0x99, 0xc9, 0x5e, 0x1f, 0x8e, 0xf2,
0xaa, 0x64, 0x11, 0xff, 0x3c, 0xf7, 0x5d, 0x00, 0x63, 0xe6, 0xd5, 0x5a, 0xb9, 0xf2, 0xe3, 0x8c,
0x92, 0xbd, 0x34, 0x1c, 0xe5, 0x33, 0x92, 0x15, 0x7f, 0xeb, 0xfb, 0x01, 0x78, 0x71, 0x9a, 0xdd,
0xbe, 0xd3, 0x28, 0x17, 0x5a, 0x95, 0x4c, 0x22, 0x9b, 0x1d, 0x8e, 0xf2, 0x57, 0x4e, 0x1a, 0x89,
0x14, 0x7c, 0x1d, 0x5c, 0x8a, 0x99, 0xe2, 0xca, 0x8f, 0xee, 0x54, 0x9a, 0xad, 0x4c, 0x32, 0x7b,
0x65, 0x38, 0xca, 0x43, 0xc9, 0x2a, 0xb8, 0x26, 0x36, 0xc0, 0xe5, 0x13, 0x16, 0xcd, 0x46, 0xbd,
0xd6, 0xac, 0x64, 0x52, 0xd9, 0xab, 0xc3, 0x51, 0xfe, 0x62, 0xcc, 0x44, 0x74, 0x95, 0x12, 0xc8,
0xc5, 0x6c, 0xca, 0xf5, 0x8f, 0x6b, 0x3b, 0xf5, 0x42, 0xb9, 0xdd, 0xc0, 0xf5, 0x2d, 0x5c, 0x69,
0x36, 0x33, 0xb3, 0x59, 0x6d, 0x38, 0xca, 0x5f, 0x93, 0x8c, 0xa7, 0x2a, 0x7c, 0x0d, 0xac, 0xc4,
0x9c, 0x34, 0xaa, 0xb5, 0xad, 0xcc, 0x5c, 0xf6, 0xe2, 0x70, 0x94, 0x7f, 0x41, 0xb2, 0xa3, 0x67,
0x39, 0x15, 0xbf, 0xd2, 0x4e, 0xbd, 0x59, 0xc9, 0xcc, 0x4f, 0xc5, 0x8f, 0x1d, 0xf8, 0xda, 0x1f,
0x14, 0x00, 0xa7, 0xbf, 0xb3, 0xc2, 0x77, 0x80, 0x1a, 0x38, 0x29, 0xd5, 0x77, 0x1b, 0x74, 0x9f,
0xd5, 0x7a, 0xad, 0x5d, 0xab, 0xd7, 0x2a, 0x99, 0x99, 0x58, 0x54, 0x25, 0xab, 0x9a, 0x63, 0x13,
0x58, 0x07, 0x57, 0x4f, 0xb3, 0xdc, 0xb9, 0xff, 0x56, 0x46, 0xc9, 0x6e, 0x0c, 0x47, 0xf9, 0xcb,
0xd3, 0x86, 0x3b, 0xf7, 0xdf, 0xfa, 0xea, 0xd7, 0x2f, 0x9f, 0xae, 0x58, 0xfb, 0xbd, 0x02, 0x16,
0xe5, 0xad, 0xbd, 0x01, 0x2e, 0xc9, 0x8e, 0x77, 0x2b, 0xad, 0x42, 0xb9, 0xd0, 0x2a, 0x64, 0x66,
0xf8, 0x19, 0x48, 0xd4, 0x5d, 0x32, 0xd0, 0x59, 0xdb, 0x7d, 0x15, 0xac, 0xc4, 0x7e, 0x45, 0xe5,
0x6e, 0x05, 0x07, 0x19, 0x25, 0xef, 0x9f, 0x1c, 0x11, 0x17, 0xbe, 0x06, 0xa0, 0x4c, 0x2e, 0xec,
0x7c, 0x5c, 0xb8, 0xd7, 0xcc, 0x24, 0xb2, 0x97, 0x87, 0xa3, 0xfc, 0x8a, 0xc4, 0x2e, 0x58, 0x0f,
0xf5, 0x63, 0x6f, 0xed, 0xaf, 0x09, 0xb0, 0x24, 0xbf, 0xaa, 0xc0, 0xd7, 0xc0, 0xc5, 0xcd, 0xea,
0x0e, 0xcd, 0xc4, 0xcd, 0x3a, 0x3f, 0x01, 0x2a, 0x66, 0x66, 0xf8, 0x72, 0x32, 0x95, 0x3e, 0xc3,
0xef, 0x03, 0xf5, 0x04, 0xbd, 0x5c, 0xc5, 0x95, 0x52, 0xab, 0x8e, 0xef, 0x65, 0x94, 0xec, 0x8b,
0x34, 0x60, 0xb2, 0x4d, 0xd9, 0x74, 0x59, 0x0b, 0x3a, 0x86, 0xb7, 0xc1, 0xb5, 0x13, 0x86, 0xcd,
0x7b, 0xbb, 0x3b, 0xd5, 0xda, 0x47, 0x7c, 0xbd, 0x44, 0xf6, 0xc6, 0x70, 0x94, 0xbf, 0x2a, 0xdb,
0x36, 0xf9, 0xdb, 0x1f, 0x85, 0xd2, 0x0a, 0xdc, 0x06, 0xf9, 0x33, 0xec, 0xa3, 0x0d, 0x24, 0xb3,
0x68, 0x38, 0xca, 0x5f, 0x3f, 0xc5, 0x49, 0xb8, 0x8f, 0xb4, 0x02, 0xdf, 0x04, 0x57, 0x4e, 0xf7,
0x14, 0xd4, 0xc5, 0x29, 0xf6, 0x6b, 0x7f, 0x57, 0xc0, 0x42, 0x78, 0xeb, 0xd1, 0xa0, 0x55, 0x30,
0xae, 0xd3, 0x26, 0x51, 0xae, 0xb4, 0x6b, 0xf5, 0x36, 0x93, 0x82, 0xa0, 0x85, 0xbc, 0x9a, 0xc3,
0x1e, 0x69, 0x8e, 0x4b, 0xf4, 0xad, 0x4a, 0xad, 0x82, 0xab, 0xa5, 0xe0, 0x44, 0x43, 0xf6, 0x16,
0xb1, 0x89, 0x6b, 0x76, 0xe0, 0x5b, 0xe0, 0x6a, 0xdc, 0x79, 0xf3, 0x4e, 0x69, 0x3b, 0x88, 0x12,
0xdb, 0xa0, 0xb4, 0x40, 0xf3, 0xb0, 0x73, 0xc0, 0x0e, 0xe6, 0xed, 0x98, 0x55, 0xb5, 0x76, 0xb7,
0xb0, 0x53, 0x2d, 0x73, 0xab, 0x64, 0x56, 0x1d, 0x8e, 0xf2, 0x97, 0x42, 0x2b, 0xf1, 0xe2, 0x41,
0xcd, 0xd6, 0xbe, 0x52, 0x40, 0xee, 0xeb, 0x2f, 0x2f, 0xf8, 0x31, 0x78, 0x85, 0xc5, 0x6b, 0xaa,
0x15, 0x88, 0xbe, 0xc5, 0x63, 0x58, 0x68, 0x34, 0x2a, 0xb5, 0x72, 0x66, 0x26, 0xbb, 0x3a, 0x1c,
0xe5, 0x6f, 0x7e, 0xbd, 0xcb, 0x42, 0xbf, 0x4f, 0x6c, 0xe3, 0x9c, 0x8e, 0x37, 0xeb, 0x78, 0xab,
0xd2, 0xca, 0x28, 0xe7, 0x71, 0xbc, 0xe9, 0xb8, 0x5d, 0x32, 0x28, 0xee, 0x7e, 0xf9, 0x24, 0x37,
0xf3, 0xf8, 0x49, 0x6e, 0xe6, 0xcb, 0xa7, 0x39, 0xe5, 0xf1, 0xd3, 0x9c, 0xf2, 0x9b, 0x67, 0xb9,
0x99, 0xcf, 0x9f, 0xe5, 0x94, 0xc7, 0xcf, 0x72, 0x33, 0xff, 0x78, 0x96, 0x9b, 0xb9, 0xff, 0x6a,
0xd7, 0x1c, 0x1c, 0x1c, 0xee, 0xad, 0x77, 0x9c, 0xde, 0x2d, 0xef, 0xd8, 0xee, 0x0c, 0x0e, 0x4c,
0xbb, 0x2b, 0x3d, 0xc9, 0xff, 0xb7, 0xed, 0xcd, 0xb1, 0xa7, 0x37, 0xff, 0x1b, 0x00, 0x00, 0xff,
0xff, 0xf7, 0xe3, 0xa9, 0xd7, 0x86, 0x1b, 0x00, 0x00,
}
func (m *Hello) Marshal() (dAtA []byte, err error) {
@ -1328,6 +1337,13 @@ func (m *Device) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.EncryptionPasswordToken) > 0 {
i -= len(m.EncryptionPasswordToken)
copy(dAtA[i:], m.EncryptionPasswordToken)
i = encodeVarintBep(dAtA, i, uint64(len(m.EncryptionPasswordToken)))
i--
dAtA[i] = 0x52
}
if m.SkipIntroductionRemovals {
i--
if m.SkipIntroductionRemovals {
@ -1523,6 +1539,15 @@ func (m *FileInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0xc0
}
if len(m.Encrypted) > 0 {
i -= len(m.Encrypted)
copy(dAtA[i:], m.Encrypted)
i = encodeVarintBep(dAtA, i, uint64(len(m.Encrypted)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x9a
}
if len(m.BlocksHash) > 0 {
i -= len(m.BlocksHash)
copy(dAtA[i:], m.BlocksHash)
@ -1782,6 +1807,11 @@ func (m *Request) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.BlockNo != 0 {
i = encodeVarintBep(dAtA, i, uint64(m.BlockNo))
i--
dAtA[i] = 0x48
}
if m.WeakHash != 0 {
i = encodeVarintBep(dAtA, i, uint64(m.WeakHash))
i--
@ -2167,6 +2197,10 @@ func (m *Device) ProtoSize() (n int) {
if m.SkipIntroductionRemovals {
n += 2
}
l = len(m.EncryptionPasswordToken)
if l > 0 {
n += 1 + l + sovBep(uint64(l))
}
return n
}
@ -2267,6 +2301,10 @@ func (m *FileInfo) ProtoSize() (n int) {
if l > 0 {
n += 2 + l + sovBep(uint64(l))
}
l = len(m.Encrypted)
if l > 0 {
n += 2 + l + sovBep(uint64(l))
}
if m.LocalFlags != 0 {
n += 2 + sovBep(uint64(m.LocalFlags))
}
@ -2362,6 +2400,9 @@ func (m *Request) ProtoSize() (n int) {
if m.WeakHash != 0 {
n += 1 + sovBep(uint64(m.WeakHash))
}
if m.BlockNo != 0 {
n += 1 + sovBep(uint64(m.BlockNo))
}
return n
}
@ -3290,6 +3331,40 @@ func (m *Device) Unmarshal(dAtA []byte) error {
}
}
m.SkipIntroductionRemovals = bool(v != 0)
case 10:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field EncryptionPasswordToken", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBep
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthBep
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthBep
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.EncryptionPasswordToken = append(m.EncryptionPasswordToken[:0], dAtA[iNdEx:postIndex]...)
if m.EncryptionPasswordToken == nil {
m.EncryptionPasswordToken = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:])
@ -3958,6 +4033,40 @@ func (m *FileInfo) Unmarshal(dAtA []byte) error {
m.BlocksHash = []byte{}
}
iNdEx = postIndex
case 19:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Encrypted", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBep
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthBep
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthBep
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Encrypted = append(m.Encrypted[:0], dAtA[iNdEx:postIndex]...)
if m.Encrypted == nil {
m.Encrypted = []byte{}
}
iNdEx = postIndex
case 1000:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field LocalFlags", wireType)
@ -4580,6 +4689,25 @@ func (m *Request) Unmarshal(dAtA []byte) error {
break
}
}
case 9:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field BlockNo", wireType)
}
m.BlockNo = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBep
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.BlockNo |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:])

View File

@ -36,7 +36,7 @@ func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileIn
return nil
}
func (t *TestModel) Request(deviceID DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
func (t *TestModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
t.folder = folder
t.name = name
t.offset = offset

547
lib/protocol/encryption.go Normal file
View File

@ -0,0 +1,547 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package protocol
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base32"
"errors"
"io"
"strings"
"time"
"github.com/gogo/protobuf/proto"
"github.com/miscreant/miscreant.go"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/scrypt"
)
const (
nonceSize = 24 // chacha20poly1305.NonceSizeX
tagSize = 16 // chacha20poly1305.Overhead()
keySize = 32 // fits both chacha20poly1305 and AES-SIV
minPaddedSize = 1024 // smallest block we'll allow
blockOverhead = tagSize + nonceSize
maxPathComponent = 200 // characters
encryptedDirExtension = ".syncthing-enc" // for top level dirs
miscreantAlgo = "AES-SIV"
)
// The encryptedModel sits between the encrypted device and the model. It
// receives encrypted metadata and requests from the untrusted device, so it
// must decrypt those and answer requests by encrypting the data.
type encryptedModel struct {
model Model
folderKeys map[string]*[keySize]byte // folder ID -> key
}
func (e encryptedModel) Index(deviceID DeviceID, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
// incoming index data to be decrypted
if err := decryptFileInfos(files, folderKey); err != nil {
return err
}
}
return e.model.Index(deviceID, folder, files)
}
func (e encryptedModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
// incoming index data to be decrypted
if err := decryptFileInfos(files, folderKey); err != nil {
return err
}
}
return e.model.IndexUpdate(deviceID, folder, files)
}
func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
folderKey, ok := e.folderKeys[folder]
if !ok {
return e.model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}
// Figure out the real file name, offset and size from the encrypted /
// tweaked values.
realName, err := decryptName(name, folderKey)
if err != nil {
return nil, err
}
realSize := size - blockOverhead
realOffset := offset - int64(blockNo*blockOverhead)
if size < minPaddedSize {
return nil, errors.New("short request")
}
// Perform that request and grab the data. Explicitly zero out the
// hashes which are meaningless.
resp, err := e.model.Request(deviceID, folder, realName, blockNo, realSize, realOffset, nil, 0, false)
if err != nil {
return nil, err
}
// Encrypt the response. Blocks smaller than minPaddedSize are padded
// with random data.
data := resp.Data()
if len(data) < minPaddedSize {
nd := make([]byte, minPaddedSize)
copy(nd, data)
if _, err := rand.Read(nd[len(data):]); err != nil {
panic("catastrophic randomness failure")
}
data = nd
}
fileKey := FileKey(realName, folderKey)
enc := encryptBytes(data, fileKey)
resp.Close()
return rawResponse{enc}, nil
}
func (e encryptedModel) DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error {
if _, ok := e.folderKeys[folder]; !ok {
return e.model.DownloadProgress(deviceID, folder, updates)
}
// Encrypted devices shouldn't send these - ignore them.
return nil
}
func (e encryptedModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) error {
return e.model.ClusterConfig(deviceID, config)
}
func (e encryptedModel) Closed(conn Connection, err error) {
e.model.Closed(conn, err)
}
// The encryptedConnection sits between the model and the encrypted device. It
// encrypts outgoing metadata and decrypts incoming responses.
type encryptedConnection struct {
conn Connection
folderKeys map[string]*[keySize]byte // folder ID -> key
}
func (e encryptedConnection) Start() {
e.conn.Start()
}
func (e encryptedConnection) ID() DeviceID {
return e.conn.ID()
}
func (e encryptedConnection) Name() string {
return e.conn.Name()
}
func (e encryptedConnection) Index(ctx context.Context, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
encryptFileInfos(files, folderKey)
}
return e.conn.Index(ctx, folder, files)
}
func (e encryptedConnection) IndexUpdate(ctx context.Context, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
encryptFileInfos(files, folderKey)
}
return e.conn.IndexUpdate(ctx, folder, files)
}
func (e encryptedConnection) Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
folderKey, ok := e.folderKeys[folder]
if !ok {
return e.conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
}
// Encrypt / adjust the request parameters.
origSize := size
if size < minPaddedSize {
// Make a request for minPaddedSize data instead of the smaller
// block. We'll chop of the extra data later.
size = minPaddedSize
}
encName := encryptName(name, folderKey)
encOffset := offset + int64(blockNo*blockOverhead)
encSize := size + blockOverhead
// Perform that request, getting back and encrypted block.
bs, err := e.conn.Request(ctx, folder, encName, blockNo, encOffset, encSize, nil, 0, false)
if err != nil {
return nil, err
}
// Return the decrypted block (or an error if it fails decryption)
fileKey := FileKey(name, folderKey)
bs, err = DecryptBytes(bs, fileKey)
if err != nil {
return nil, err
}
return bs[:origSize], nil
}
func (e encryptedConnection) DownloadProgress(ctx context.Context, folder string, updates []FileDownloadProgressUpdate) {
if _, ok := e.folderKeys[folder]; !ok {
e.conn.DownloadProgress(ctx, folder, updates)
}
// No need to send these
}
func (e encryptedConnection) ClusterConfig(config ClusterConfig) {
e.conn.ClusterConfig(config)
}
func (e encryptedConnection) Close(err error) {
e.conn.Close(err)
}
func (e encryptedConnection) Closed() bool {
return e.conn.Closed()
}
func (e encryptedConnection) Statistics() Statistics {
return e.conn.Statistics()
}
func encryptFileInfos(files []FileInfo, folderKey *[keySize]byte) {
for i, fi := range files {
files[i] = encryptFileInfo(fi, folderKey)
}
}
// encryptFileInfo encrypts a FileInfo and wraps it into a new fake FileInfo
// with an encrypted name.
func encryptFileInfo(fi FileInfo, folderKey *[keySize]byte) FileInfo {
fileKey := FileKey(fi.Name, folderKey)
// The entire FileInfo is encrypted with a random nonce, and concatenated
// with that nonce.
bs, err := proto.Marshal(&fi)
if err != nil {
panic("impossible serialization mishap: " + err.Error())
}
encryptedFI := encryptBytes(bs, fileKey)
// The vector is set to something that is higher than any other version sent
// previously, assuming people's clocks are correct. We do this because
// there is no way for the insecure device on the other end to do proper
// conflict resolution, so they will simply accept and keep whatever is the
// latest version they see. The secure devices will decrypt the real
// FileInfo, see the real Version, and act appropriately regardless of what
// this fake version happens to be.
version := Vector{
Counters: []Counter{
{
ID: 1,
Value: uint64(time.Now().UnixNano()),
},
},
}
// Construct the fake block list. Each block will be blockOverhead bytes
// larger than the corresponding real one and have an encrypted hash.
// Very small blocks will be padded upwards to minPaddedSize.
//
// The encrypted hash becomes just a "token" for the data -- it doesn't
// help verifying it, but it lets the encrypted device do block level
// diffs and data reuse properly when it gets a new version of a file.
var offset int64
blocks := make([]BlockInfo, len(fi.Blocks))
for i, b := range fi.Blocks {
if b.Size < minPaddedSize {
b.Size = minPaddedSize
}
size := b.Size + blockOverhead
blocks[i] = BlockInfo{
Offset: offset,
Size: size,
Hash: encryptDeterministic(b.Hash, fileKey),
}
offset += int64(size)
}
// Construct the fake FileInfo. This is mostly just a wrapper around the
// encrypted FileInfo and fake block list. We'll represent symlinks as
// directories, because they need some sort of on disk representation
// but have no data outside of the metadata. Deletion and sequence
// numbering are handled as usual.
typ := FileInfoTypeFile
if fi.Type != FileInfoTypeFile {
typ = FileInfoTypeDirectory
}
enc := FileInfo{
Name: encryptName(fi.Name, folderKey),
Type: typ,
Size: offset, // new total file size
Permissions: 0644,
ModifiedS: 1234567890, // Sat Feb 14 00:31:30 CET 2009
Deleted: fi.Deleted,
Version: version,
Sequence: fi.Sequence,
RawBlockSize: fi.RawBlockSize + blockOverhead,
Blocks: blocks,
Encrypted: encryptedFI,
}
return enc
}
func decryptFileInfos(files []FileInfo, folderKey *[keySize]byte) error {
for i, fi := range files {
decFI, err := DecryptFileInfo(fi, folderKey)
if err != nil {
return err
}
files[i] = decFI
}
return nil
}
// DecryptFileInfo extracts the encrypted portion of a FileInfo, decrypts it
// and returns that.
func DecryptFileInfo(fi FileInfo, folderKey *[keySize]byte) (FileInfo, error) {
realName, err := decryptName(fi.Name, folderKey)
if err != nil {
return FileInfo{}, err
}
fileKey := FileKey(realName, folderKey)
dec, err := DecryptBytes(fi.Encrypted, fileKey)
if err != nil {
return FileInfo{}, err
}
var decFI FileInfo
if err := proto.Unmarshal(dec, &decFI); err != nil {
return FileInfo{}, err
}
return decFI, nil
}
// encryptName encrypts the given string in a deterministic manner (the
// result is always the same for any given string) and encodes it in a
// filesystem-friendly manner.
func encryptName(name string, key *[keySize]byte) string {
enc := encryptDeterministic([]byte(name), key)
b32enc := base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(enc)
return slashify(b32enc)
}
// decryptName decrypts a string from encryptName
func decryptName(name string, key *[keySize]byte) (string, error) {
name = deslashify(name)
bs, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(name)
if err != nil {
return "", err
}
dec, err := decryptDeterministic(bs, key)
if err != nil {
return "", err
}
return string(dec), nil
}
// encryptBytes encrypts bytes with a random nonce
func encryptBytes(data []byte, key *[keySize]byte) []byte {
nonce := randomNonce()
return encrypt(data, nonce, key)
}
// encryptDeterministic encrypts bytes using AES-SIV
func encryptDeterministic(data []byte, key *[keySize]byte) []byte {
aead, err := miscreant.NewAEAD(miscreantAlgo, key[:], 0)
if err != nil {
panic("cipher failure: " + err.Error())
}
return aead.Seal(nil, nil, data, nil)
}
// decryptDeterministic decrypts bytes using AES-SIV
func decryptDeterministic(data []byte, key *[keySize]byte) ([]byte, error) {
aead, err := miscreant.NewAEAD(miscreantAlgo, key[:], 0)
if err != nil {
panic("cipher failure: " + err.Error())
}
return aead.Open(nil, nil, data, nil)
}
func encrypt(data []byte, nonce *[nonceSize]byte, key *[keySize]byte) []byte {
aead, err := chacha20poly1305.NewX(key[:])
if err != nil {
// Can only fail if the key is the wrong length
panic("cipher failure: " + err.Error())
}
if aead.NonceSize() != nonceSize || aead.Overhead() != tagSize {
// We want these values to be constant for our type declarations so
// we don't use the values returned by the GCM, but we verify them
// here.
panic("crypto parameter mismatch")
}
// Data is appended to the nonce
return aead.Seal(nonce[:], nonce[:], data, nil)
}
// DecryptBytes returns the decrypted bytes, or an error if decryption
// failed.
func DecryptBytes(data []byte, key *[keySize]byte) ([]byte, error) {
if len(data) < blockOverhead {
return nil, errors.New("data too short")
}
aead, err := chacha20poly1305.NewX(key[:])
if err != nil {
// Can only fail if the key is the wrong length
panic("cipher failure: " + err.Error())
}
if aead.NonceSize() != nonceSize || aead.Overhead() != tagSize {
// We want these values to be constant for our type declarations so
// we don't use the values returned by the GCM, but we verify them
// here.
panic("crypto parameter mismatch")
}
return aead.Open(nil, data[:nonceSize], data[nonceSize:], nil)
}
// randomNonce is a normal, cryptographically random nonce
func randomNonce() *[nonceSize]byte {
var nonce [nonceSize]byte
if _, err := rand.Read(nonce[:]); err != nil {
panic("catastrophic randomness failure: " + err.Error())
}
return &nonce
}
// keysFromPasswords converts a set of folder ID to password into a set of
// folder ID to encryption key, using our key derivation function.
func keysFromPasswords(passwords map[string]string) map[string]*[keySize]byte {
res := make(map[string]*[keySize]byte, len(passwords))
for folder, password := range passwords {
res[folder] = KeyFromPassword(folder, password)
}
return res
}
func knownBytes(folderID string) []byte {
return []byte("syncthing" + folderID)
}
// KeyFromPassword uses key derivation to generate a stronger key from a
// probably weak password.
func KeyFromPassword(folderID, password string) *[keySize]byte {
bs, err := scrypt.Key([]byte(password), knownBytes(folderID), 32768, 8, 1, keySize)
if err != nil {
panic("key derivation failure: " + err.Error())
}
if len(bs) != keySize {
panic("key derivation failure: wrong number of bytes")
}
var key [keySize]byte
copy(key[:], bs)
return &key
}
func FileKey(filename string, folderKey *[keySize]byte) *[keySize]byte {
kdf := hkdf.New(sha256.New, append(folderKey[:], filename...), []byte("syncthing"), nil)
var fileKey [keySize]byte
n, err := io.ReadFull(kdf, fileKey[:])
if err != nil || n != keySize {
panic("hkdf failure")
}
return &fileKey
}
func PasswordToken(folderID, password string) []byte {
return encryptDeterministic(knownBytes(folderID), KeyFromPassword(folderID, password))
}
// slashify inserts slashes (and file extension) in the string to create an
// appropriate tree. ABCDEFGH... => A.syncthing-enc/BC/DEFGH... We can use
// forward slashes here because we're on the outside of native path formats,
// the slash is the wire format.
func slashify(s string) string {
// We somewhat sloppily assume bytes == characters here, but the only
// file names we should deal with are those that come from our base32
// encoding.
comps := make([]string, 0, len(s)/maxPathComponent+3)
comps = append(comps, s[:1]+encryptedDirExtension)
s = s[1:]
comps = append(comps, s[:2])
s = s[2:]
for len(s) > maxPathComponent {
comps = append(comps, s[:maxPathComponent])
s = s[maxPathComponent:]
}
if len(s) > 0 {
comps = append(comps, s)
}
return strings.Join(comps, "/")
}
// deslashify removes slashes and encrypted file extensions from the string.
// This is the inverse of slashify().
func deslashify(s string) string {
s = strings.ReplaceAll(s, encryptedDirExtension, "")
return strings.ReplaceAll(s, "/", "")
}
type rawResponse struct {
data []byte
}
func (r rawResponse) Data() []byte {
return r.data
}
func (r rawResponse) Close() {}
func (r rawResponse) Wait() {}
// IsEncryptedPath returns true if the path points at encrypted data. This is
// determined by checking for a sentinel string in the path.
func IsEncryptedPath(path string) bool {
pathComponents := strings.Split(path, "/")
if len(pathComponents) != 3 {
return false
}
return isEncryptedParentFromComponents(pathComponents[:2])
}
// IsEncryptedParent returns true if the path points at a parent directory of
// encrypted data, i.e. is not a "real" directory. This is determined by
// checking for a sentinel string in the path.
func IsEncryptedParent(path string) bool {
return isEncryptedParentFromComponents(strings.Split(path, "/"))
}
func isEncryptedParentFromComponents(pathComponents []string) bool {
if l := len(pathComponents); l > 2 {
return false
} else if l == 2 && len(pathComponents[1]) != 2 {
return false
}
return pathComponents[0][1:1+len(encryptedDirExtension)] == encryptedDirExtension
}

View File

@ -0,0 +1,97 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package protocol
import (
"bytes"
"reflect"
"strings"
"testing"
)
func TestEnDecryptName(t *testing.T) {
var key [32]byte
cases := []string{
"",
"foo",
"a longer name/with/slashes and spaces",
}
for _, tc := range cases {
var prev string
for i := 0; i < 5; i++ {
enc := encryptName(tc, &key)
if prev != "" && prev != enc {
t.Error("name should always encrypt the same")
}
prev = enc
if tc != "" && strings.Contains(enc, tc) {
t.Error("shouldn't contain plaintext")
}
dec, err := decryptName(enc, &key)
if err != nil {
t.Error(err)
}
if dec != tc {
t.Error("mismatch after decryption")
}
t.Log(enc)
}
}
}
func TestEnDecryptBytes(t *testing.T) {
var key [32]byte
cases := [][]byte{
{},
{1, 2, 3, 4, 5},
}
for _, tc := range cases {
var prev []byte
for i := 0; i < 5; i++ {
enc := encryptBytes(tc, &key)
if bytes.Equal(enc, prev) {
t.Error("encryption should not repeat")
}
prev = enc
if len(tc) > 0 && bytes.Contains(enc, tc) {
t.Error("shouldn't contain plaintext")
}
dec, err := DecryptBytes(enc, &key)
if err != nil {
t.Error(err)
}
if !bytes.Equal(dec, tc) {
t.Error("mismatch after decryption")
}
}
}
}
func TestEnDecryptFileInfo(t *testing.T) {
var key [32]byte
fi := FileInfo{
Name: "hello",
Size: 45,
Permissions: 0755,
ModifiedS: 8080,
Blocks: []BlockInfo{
{
Size: 45,
Hash: []byte{1, 2, 3},
},
},
}
enc := encryptFileInfo(fi, &key)
dec, err := DecryptFileInfo(enc, &key)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(fi, dec) {
t.Error("mismatch after decryption")
}
}

View File

@ -26,7 +26,7 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
return m.Model.IndexUpdate(deviceID, folder, files)
}
func (m nativeModel) Request(deviceID DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
func (m nativeModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
name = norm.NFD.String(name)
return m.Model.Request(deviceID, folder, name, size, offset, hash, weakHash, fromTemporary)
return m.Model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}

View File

@ -26,14 +26,14 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
return m.Model.IndexUpdate(deviceID, folder, files)
}
func (m nativeModel) Request(deviceID DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
func (m nativeModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
if strings.Contains(name, `\`) {
l.Warnf("Dropping request for %s, contains invalid path separator", name)
return nil, ErrNoSuchFile
}
name = filepath.FromSlash(name)
return m.Model.Request(deviceID, folder, name, size, offset, hash, weakHash, fromTemporary)
return m.Model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}
func fixupFiles(files []FileInfo) []FileInfo {

View File

@ -115,7 +115,7 @@ type Model interface {
// An index update was received from the peer device
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error
// A request was made by the peer device
Request(deviceID DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
// A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfig) error
// The peer device closed the connection
@ -137,7 +137,7 @@ type Connection interface {
Name() string
Index(ctx context.Context, folder string, files []FileInfo) error
IndexUpdate(ctx context.Context, folder string, files []FileInfo) error
Request(ctx context.Context, folder string, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error)
Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error)
ClusterConfig(config ClusterConfig)
DownloadProgress(ctx context.Context, folder string, updates []FileDownloadProgressUpdate)
Statistics() Statistics
@ -204,13 +204,36 @@ const (
var CloseTimeout = 10 * time.Second
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
receiver = nativeModel{receiver}
rc := newRawConnection(deviceID, reader, writer, receiver, name, compress)
return wireFormatConnection{rc}
}
func NewEncryptedConnection(passwords map[string]string, deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
keys := keysFromPasswords(passwords)
// Encryption / decryption is first (outermost) before conversion to
// native path formats.
nm := nativeModel{receiver}
em := encryptedModel{model: nm, folderKeys: keys}
// We do the wire format conversion first (outermost) so that the
// metadata is in wire format when it reaches the encryption step.
rc := newRawConnection(deviceID, reader, writer, em, name, compress)
ec := encryptedConnection{conn: rc, folderKeys: keys}
wc := wireFormatConnection{ec}
return wc
}
func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) *rawConnection {
cr := &countingReader{Reader: reader}
cw := &countingWriter{Writer: writer}
c := rawConnection{
return &rawConnection{
id: deviceID,
name: name,
receiver: nativeModel{receiver},
receiver: receiver,
cr: cr,
cw: cw,
awaiting: make(map[int]chan asyncResult),
@ -222,8 +245,6 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
closed: make(chan struct{}),
compression: compress,
}
return wireFormatConnection{&c}
}
// Start creates the goroutines for sending and receiving of messages. It must
@ -281,7 +302,7 @@ func (c *rawConnection) IndexUpdate(ctx context.Context, folder string, idx []Fi
}
// Request returns the bytes for the specified block after fetching them from the connected peer.
func (c *rawConnection) Request(ctx context.Context, folder string, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (c *rawConnection) Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
c.nextIDMut.Lock()
id := c.nextID
c.nextID++
@ -302,6 +323,7 @@ func (c *rawConnection) Request(ctx context.Context, folder string, name string,
Name: name,
Offset: offset,
Size: size,
BlockNo: blockNo,
Hash: hash,
WeakHash: weakHash,
FromTemporary: fromTemporary,
@ -622,7 +644,7 @@ func checkFilename(name string) error {
}
func (c *rawConnection) handleRequest(req Request) {
res, err := c.receiver.Request(c.id, req.Folder, req.Name, int32(req.Size), req.Offset, req.Hash, req.WeakHash, req.FromTemporary)
res, err := c.receiver.Request(c.id, req.Folder, req.Name, int32(req.BlockNo), int32(req.Size), req.Offset, req.Hash, req.WeakHash, req.FromTemporary)
if err != nil {
c.send(context.Background(), &Response{
ID: req.ID,

View File

@ -80,7 +80,7 @@ func TestClose(t *testing.T) {
c0.Index(ctx, "default", nil)
c0.Index(ctx, "default", nil)
if _, err := c0.Request(ctx, "default", "foo", 0, 0, nil, 0, false); err == nil {
if _, err := c0.Request(ctx, "default", "foo", 0, 0, 0, nil, 0, false); err == nil {
t.Error("Request should return an error")
}
}
@ -279,6 +279,9 @@ func TestMarshalIndexMessage(t *testing.T) {
if len(f.Version.Counters) == 0 {
m1.Files[i].Version.Counters = nil
}
if len(f.Encrypted) == 0 {
m1.Files[i].Encrypted = nil
}
}
return testMarshal(t, "index", &m1, &Index{})
@ -340,6 +343,9 @@ func TestMarshalClusterConfigMessage(t *testing.T) {
if len(m1.Folders[i].Devices[j].Addresses) == 0 {
m1.Folders[i].Devices[j].Addresses = nil
}
if len(m1.Folders[i].Devices[j].EncryptionPasswordToken) == 0 {
m1.Folders[i].Devices[j].EncryptionPasswordToken = nil
}
}
}

View File

@ -35,7 +35,7 @@ func (c wireFormatConnection) IndexUpdate(ctx context.Context, folder string, fs
return c.Connection.IndexUpdate(ctx, folder, myFs)
}
func (c wireFormatConnection) Request(ctx context.Context, folder string, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
func (c wireFormatConnection) Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
name = norm.NFC.String(filepath.ToSlash(name))
return c.Connection.Request(ctx, folder, name, offset, size, hash, weakHash, fromTemporary)
return c.Connection.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
}

View File

@ -73,7 +73,15 @@ type ScanResult struct {
}
func Walk(ctx context.Context, cfg Config) chan ScanResult {
w := walker{cfg}
return newWalker(cfg).walk(ctx)
}
func WalkWithoutHashing(ctx context.Context, cfg Config) chan ScanResult {
return newWalker(cfg).walkWithoutHashing(ctx)
}
func newWalker(cfg Config) *walker {
w := &walker{cfg}
if w.CurrentFiler == nil {
w.CurrentFiler = noCurrentFiler{}
@ -85,7 +93,7 @@ func Walk(ctx context.Context, cfg Config) chan ScanResult {
w.Matcher = ignore.New(w.Filesystem)
}
return w.walk(ctx)
return w
}
var (
@ -108,21 +116,7 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
// A routine which walks the filesystem tree, and sends files which have
// been modified to the counter routine.
go func() {
hashFiles := w.walkAndHashFiles(ctx, toHashChan, finishedChan)
if len(w.Subs) == 0 {
w.Filesystem.Walk(".", hashFiles)
} else {
for _, sub := range w.Subs {
if err := osutil.TraversesSymlink(w.Filesystem, filepath.Dir(sub)); err != nil {
l.Debugf("Skip walking %v as it is below a symlink", sub)
continue
}
w.Filesystem.Walk(sub, hashFiles)
}
}
close(toHashChan)
}()
go w.scan(ctx, toHashChan, finishedChan)
// We're not required to emit scan progress events, just kick off hashers,
// and feed inputs directly from the walker.
@ -203,6 +197,42 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
return finishedChan
}
func (w *walker) walkWithoutHashing(ctx context.Context) chan ScanResult {
l.Debugln("Walk without hashing", w.Subs, w.Matcher)
toHashChan := make(chan protocol.FileInfo)
finishedChan := make(chan ScanResult)
// A routine which walks the filesystem tree, and sends files which have
// been modified to the counter routine.
go w.scan(ctx, toHashChan, finishedChan)
go func() {
for file := range toHashChan {
finishedChan <- ScanResult{File: file}
}
close(finishedChan)
}()
return finishedChan
}
func (w *walker) scan(ctx context.Context, toHashChan chan<- protocol.FileInfo, finishedChan chan<- ScanResult) {
hashFiles := w.walkAndHashFiles(ctx, toHashChan, finishedChan)
if len(w.Subs) == 0 {
w.Filesystem.Walk(".", hashFiles)
} else {
for _, sub := range w.Subs {
if err := osutil.TraversesSymlink(w.Filesystem, filepath.Dir(sub)); err != nil {
l.Debugf("Skip walking %v as it is below a symlink", sub)
continue
}
w.Filesystem.Walk(sub, hashFiles)
}
}
close(toHashChan)
}
func (w *walker) walkAndHashFiles(ctx context.Context, toHashChan chan<- protocol.FileInfo, finishedChan chan<- ScanResult) fs.WalkFunc {
now := time.Now()
ignoredParent := ""

View File

@ -131,6 +131,10 @@ type Report struct {
CaseSensitiveFS int `json:"caseSensitiveFS,omitempty" since:"3"`
} `json:"folderUsesV3,omitempty" since:"3"`
DeviceUsesV3 struct {
Untrusted int `json:"untrusted,omitempty" since:"3"`
} `json:"deviceUsesV3,omitempty" since:"3"`
GUIStats struct {
Enabled int `json:"enabled,omitempty" since:"3"`
UseTLS int `json:"useTLS,omitempty" since:"3"`

View File

@ -275,6 +275,12 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) (
}
sort.Ints(report.FolderUsesV3.FsWatcherDelays)
for _, cfg := range s.cfg.Devices() {
if cfg.Untrusted {
report.DeviceUsesV3.Untrusted++
}
}
guiCfg := s.cfg.GUI()
// Anticipate multiple GUI configs in the future, hence store counts.
if guiCfg.Enabled {

View File

@ -24,4 +24,5 @@ message DeviceConfiguration {
repeated ObservedFolder ignored_folders = 14;
repeated ObservedFolder pending_folders = 15;
int32 max_request_kib = 16 [(ext.goname) = "MaxRequestKiB", (ext.xml) = "maxRequestKiB", (ext.json) = "maxRequestKiB"];
bool untrusted = 17;
}

View File

@ -16,6 +16,7 @@ import "ext.proto";
message FolderDeviceConfiguration {
bytes device_id = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true];
bytes introduced_by = 2 [(ext.xml) = "introducedBy,attr", (ext.device_id) = true];
string encryption_password = 3;
}
message FolderConfiguration {

View File

@ -10,4 +10,5 @@ enum FolderType {
FOLDER_TYPE_SEND_RECEIVE = 0;
FOLDER_TYPE_SEND_ONLY = 1;
FOLDER_TYPE_RECEIVE_ONLY = 2;
FOLDER_TYPE_RECEIVE_ENCRYPTED = 3;
}

View File

@ -56,6 +56,7 @@ message OptionsConfiguration {
int32 max_concurrent_incoming_request_kib = 47 [(ext.goname) = "RawMaxCIRequestKiB", (ext.xml) = "maxConcurrentIncomingRequestKiB", (ext.json) = "maxConcurrentIncomingRequestKiB"];
bool announce_lan_addresses = 48 [(ext.goname)= "AnnounceLANAddresses", (ext.xml) = "announceLANAddresses", (ext.json) = "announceLANAddresses", (ext.default) = "true"];
bool send_full_index_on_upgrade = 49;
repeated string feature_flags = 50;
// Legacy deprecated

View File

@ -30,6 +30,7 @@ message FileInfoTruncated {
// repeated BlockInfo Blocks = 16
string symlink_target = 17;
bytes blocks_hash = 18;
bytes encrypted = 19;
protocol.FileInfoType type = 2;
uint32 permissions = 4;
int32 modified_ns = 11;

View File

@ -66,6 +66,7 @@ message Device {
bool introducer = 7;
uint64 index_id = 8 [(ext.goname) = "IndexID", (ext.gotype) = "IndexID"];
bool skip_introduction_removals = 9;
bytes encryption_password_token = 10;
}
enum Compression {
@ -101,6 +102,7 @@ message FileInfo {
repeated BlockInfo blocks = 16;
string symlink_target = 17;
bytes blocks_hash = 18;
bytes encrypted = 19;
FileInfoType type = 2;
uint32 permissions = 4;
int32 modified_ns = 11;
@ -156,6 +158,7 @@ message Request {
bytes hash = 6;
bool from_temporary = 7;
uint32 weak_hash = 8;
int32 block_no = 9;
}
// Response