diff --git a/cmd/ursrv/main.go b/cmd/ursrv/main.go
index b9661f222..4871ace2e 100644
--- a/cmd/ursrv/main.go
+++ b/cmd/ursrv/main.go
@@ -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)
diff --git a/go.mod b/go.mod
index d946e4751..758602a77 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 20d7508ae..c911bf8c6 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/gui/default/assets/css/overrides.css b/gui/default/assets/css/overrides.css
index 81679e0ec..a5a652b99 100644
--- a/gui/default/assets/css/overrides.css
+++ b/gui/default/assets/css/overrides.css
@@ -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;
+}
diff --git a/gui/default/assets/lang/lang-en.json b/gui/default/assets/lang/lang-en.json
index 5ce2b5a96..4d58f890b 100644
--- a/gui/default/assets/lang/lang-en.json
+++ b/gui/default/assets/lang/lang-en.json
@@ -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",
diff --git a/gui/default/untrusted/index.html b/gui/default/untrusted/index.html
new file mode 100644
index 000000000..de9b14560
--- /dev/null
+++ b/gui/default/untrusted/index.html
@@ -0,0 +1,950 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Syncthing admin interface requires JavaScript. Please enable JavaScript in your web browser and try again.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{thisDeviceName()}}
+
+
+
+
+ Upgrade To {%version%}
+
+
+
+
+
+ Upgrade To {%version%}
+
+
+
+
+
+
+ Help
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Syncthing admin interface is configured to allow remote access without a password.
+ This can easily give hackers access to read and change any files on your computer.
+ Please set a GUI Authentication User and Password in the Settings dialog.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Restart Needed
+
+
+
+
The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New Device
+ {{ pendingDevice.time | date:"yyyy-MM-dd HH:mm:ss" }}
+
+
+
+
+
+ Device "{%name%}" ({%device%} at {%address%}) wants to connect. Add new device?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New Folder
+ Share Folder
+ {{ pendingFolder.time | date:"yyyy-MM-dd HH:mm:ss" }}
+
+
+
+
+
+ {%device%} wants to share folder "{%folder%}".
+
+
+ {%device%} wants to share folder "{%folderlabel%}" ({%folder%}).
+
+ Share this folder?
+ Add new folder?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filesystem Watcher Errors
+
+
+
+
+ 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. Support
+
+
+
+ {{folderLabel(id)}} {{err}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Folders
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Paused
+ Unknown
+ Unshared
+ Waiting to Scan
+ Cleaning Versions
+ Waiting to Clean
+ Stopped
+
+ Scanning
+
+ ({{scanPercentage(folder.id) | percent}})
+
+
+
+ Up to Date
+ Local Additions
+
+ Waiting to Sync
+
+
+
+ Preparing to Sync
+
+
+
+ Syncing
+ ({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)
+
+ Out of Sync
+ Failed Items
+ {{'Unexpected Items' | translate}}
+
+
+ {{folder.label.length != 0 ? folder.label : folder.id}}
+
+
+
+
+
+
+
+
+ Pause All
+
+
+ Resume All
+
+
+ Rescan All
+
+
+ Add Folder
+
+
+
+
+
+
+
+
+
+
+
+
This Device
+
+
+
+
+ {{deviceName(deviceCfg)}}
+
+
+
+
+
+
+
Remote Devices
+
+
+
+
+
+
+
+ Up to Date
+ Connected (Unused)
+
+ Syncing ({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)
+
+ Paused
+ Paused (Unused)
+ Disconnected
+ Disconnected (Unused)
+
+ {{deviceName(deviceCfg)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gui/default/untrusted/syncthing/core/editShareTemplate.html b/gui/default/untrusted/syncthing/core/editShareTemplate.html
new file mode 100644
index 000000000..2fed682fc
--- /dev/null
+++ b/gui/default/untrusted/syncthing/core/editShareTemplate.html
@@ -0,0 +1,35 @@
+
+
+
+ {{label}}
+
+
+
diff --git a/gui/default/untrusted/syncthing/core/syncthingController.js b/gui/default/untrusted/syncthing/core/syncthingController.js
new file mode 100755
index 000000000..8ab91d0b9
--- /dev/null
+++ b/gui/default/untrusted/syncthing/core/syncthingController.js
@@ -0,0 +1,2585 @@
+angular.module('syncthing.core')
+ .config(function ($locationProvider) {
+ $locationProvider.html5Mode({ enabled: true, requireBase: false }).hashPrefix('!');
+ })
+ .controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $compile, $timeout, $rootScope, $translate) {
+ 'use strict';
+
+ // private/helper definitions
+
+ var prevDate = 0;
+ var navigatingAway = false;
+ var online = false;
+ var restarting = false;
+
+ function initController() {
+ LocaleService.autoConfigLocale();
+ setInterval($scope.refresh, 10000);
+ Events.start();
+ }
+
+ // public/scope definitions
+
+ $scope.completion = {};
+ $scope.config = {};
+ $scope.configInSync = true;
+ $scope.connections = {};
+ $scope.errors = [];
+ $scope.model = {};
+ $scope.myID = '';
+ $scope.devices = {};
+ $scope.discoveryCache = {};
+ $scope.protocolChanged = false;
+ $scope.reportData = {};
+ $scope.reportDataPreview = '';
+ $scope.reportPreview = false;
+ $scope.folders = {};
+ $scope.seenError = '';
+ $scope.upgradeInfo = null;
+ $scope.deviceStats = {};
+ $scope.folderStats = {};
+ $scope.progress = {};
+ $scope.version = {};
+ $scope.needed = {}
+ $scope.neededFolder = '';
+ $scope.failed = {};
+ $scope.localChanged = {};
+ $scope.scanProgress = {};
+ $scope.themes = [];
+ $scope.globalChangeEvents = {};
+ $scope.metricRates = false;
+ $scope.folderPathErrors = {};
+ $scope.currentFolder = {};
+ $scope.ignores = {
+ text: '',
+ error: null,
+ disabled: false,
+ };
+ resetRemoteNeed();
+
+ try {
+ $scope.metricRates = (window.localStorage["metricRates"] == "true");
+ } catch (exception) { }
+
+ $scope.folderDefaults = {
+ devices: [],
+ type: "sendreceive",
+ rescanIntervalS: 3600,
+ fsWatcherDelayS: 10,
+ fsWatcherEnabled: true,
+ minDiskFree: { value: 1, unit: "%" },
+ maxConflicts: 10,
+ fsync: true,
+ order: "random",
+ fileVersioningSelector: "none",
+ trashcanClean: 0,
+ versioningCleanupIntervalS: 3600,
+ simpleKeep: 5,
+ staggeredMaxAge: 365,
+ staggeredCleanInterval: 3600,
+ staggeredVersionsPath: "",
+ externalCommand: "",
+ autoNormalize: true,
+ path: "",
+ };
+
+ $scope.localStateTotal = {
+ bytes: 0,
+ directories: 0,
+ files: 0
+ };
+
+ $(window).bind('beforeunload', function () {
+ navigatingAway = true;
+ });
+
+ $scope.$on("$locationChangeSuccess", function () {
+ LocaleService.useLocale($location.search().lang);
+ });
+
+ $scope.needActions = {
+ 'rm': 'Del',
+ 'rmdir': 'Del (dir)',
+ 'sync': 'Sync',
+ 'touch': 'Update'
+ };
+ $scope.needIcons = {
+ 'rm': 'far fa-fw fa-trash-alt',
+ 'rmdir': 'far fa-fw fa-trash-alt',
+ 'sync': 'far fa-fw fa-arrow-alt-circle-down',
+ 'touch': 'fas fa-fw fa-asterisk'
+ };
+
+ $scope.$on(Events.ONLINE, function () {
+ if (online && !restarting) {
+ return;
+ }
+
+ console.log('UIOnline');
+
+ refreshSystem();
+ refreshDiscoveryCache();
+ refreshConfig();
+ refreshConnectionStats();
+ refreshDeviceStats();
+ refreshFolderStats();
+ refreshGlobalChanges();
+ refreshThemes();
+
+ $http.get(urlbase + '/system/version').success(function (data) {
+ console.log("version", data);
+ if ($scope.version.version && $scope.version.version !== data.version) {
+ // We already have a version response, but it differs from
+ // the new one. Reload the full GUI in case it's changed.
+ document.location.reload(true);
+ }
+
+ $scope.version = data;
+ }).error($scope.emitHTTPError);
+
+ $http.get(urlbase + '/svc/report').success(function (data) {
+ $scope.reportData = data;
+ if ($scope.system && $scope.config.options.urAccepted > -1 && $scope.config.options.urSeen < $scope.system.urVersionMax && $scope.config.options.urAccepted < $scope.system.urVersionMax) {
+ // Usage reporting format has changed, prompt the user to re-accept.
+ $('#ur').modal();
+ }
+ }).error($scope.emitHTTPError);
+
+ $http.get(urlbase + '/system/upgrade').success(function (data) {
+ $scope.upgradeInfo = data;
+ }).error(function () {
+ $scope.upgradeInfo = null;
+ });
+
+ online = true;
+ restarting = false;
+ $('#networkError').modal('hide');
+ $('#restarting').modal('hide');
+ $('#shutdown').modal('hide');
+ });
+
+ $scope.$on(Events.OFFLINE, function () {
+ if (navigatingAway || !online) {
+ return;
+ }
+
+ console.log('UIOffline');
+ online = false;
+ if (!restarting) {
+ $('#networkError').modal();
+ }
+ });
+
+ $scope.$on('HTTPError', function (event, arg) {
+ // Emitted when a HTTP call fails. We use the status code to try
+ // to figure out what's wrong.
+
+ if (navigatingAway || !online) {
+ return;
+ }
+
+ console.log('HTTPError', arg);
+ online = false;
+ if (!restarting) {
+ if (arg.status === 0) {
+ // A network error, not an HTTP error
+ $scope.$emit(Events.OFFLINE);
+ } else if (arg.status >= 400 && arg.status <= 599) {
+ // A genuine HTTP error
+ $('#networkError').modal('hide');
+ $('#restarting').modal('hide');
+ $('#shutdown').modal('hide');
+ $('#httpError').modal();
+ }
+ }
+ });
+
+ $scope.$on(Events.STATE_CHANGED, function (event, arg) {
+ var data = arg.data;
+ if ($scope.model[data.folder]) {
+ $scope.model[data.folder].state = data.to;
+ $scope.model[data.folder].error = data.error;
+
+ // If a folder has started scanning, then any scan progress is
+ // also obsolete.
+ if (data.to === 'scanning') {
+ delete $scope.scanProgress[data.folder];
+ }
+
+ // If a folder finished scanning, then refresh folder stats
+ // to update last scan time.
+ if (data.from === 'scanning' && data.to === 'idle') {
+ refreshFolderStats();
+ }
+ }
+ });
+
+ $scope.$on(Events.LOCAL_INDEX_UPDATED, function (event, arg) {
+ refreshFolderStats();
+ refreshGlobalChanges();
+ });
+
+ $scope.$on(Events.DEVICE_DISCONNECTED, function (event, arg) {
+ $scope.connections[arg.data.id].connected = false;
+ refreshDeviceStats();
+ });
+
+ $scope.$on(Events.DEVICE_CONNECTED, function (event, arg) {
+ if (!$scope.connections[arg.data.id]) {
+ $scope.connections[arg.data.id] = {
+ inbps: 0,
+ outbps: 0,
+ inBytesTotal: 0,
+ outBytesTotal: 0,
+ type: arg.data.type,
+ address: arg.data.addr
+ };
+ $scope.completion[arg.data.id] = {
+ _total: 100,
+ _needBytes: 0,
+ _needItems: 0
+ };
+ }
+ });
+
+ $scope.$on('ConfigLoaded', function () {
+ if ($scope.config.options.urAccepted === 0) {
+ // If usage reporting has been neither accepted nor declined,
+ // we want to ask the user to make a choice. But we don't want
+ // to bug them during initial setup, so we set a cookie with
+ // the time of the first visit. When that cookie is present
+ // and the time is more than four hours ago, we ask the
+ // question.
+
+ var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1");
+ if (!firstVisit) {
+ document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30 * 24 * 3600;
+ } else {
+ if (+firstVisit < Date.now() - 4 * 3600 * 1000) {
+ $('#ur').modal();
+ }
+ }
+ }
+ });
+
+ $scope.$on(Events.CONFIG_SAVED, function (event, arg) {
+ updateLocalConfig(arg.data);
+
+ $http.get(urlbase + '/config/insync').success(function (data) {
+ $scope.configInSync = data.configInSync;
+ }).error($scope.emitHTTPError);
+ });
+
+ $scope.$on(Events.DOWNLOAD_PROGRESS, function (event, arg) {
+ var stats = arg.data;
+ var progress = {};
+ for (var folder in stats) {
+ progress[folder] = {};
+ for (var file in stats[folder]) {
+ var s = stats[folder][file];
+ var reused = 100 * s.reused / s.total;
+ var copiedFromOrigin = 100 * s.copiedFromOrigin / s.total;
+ var copiedFromElsewhere = 100 * s.copiedFromElsewhere / s.total;
+ var pulled = 100 * s.pulled / s.total;
+ var pulling = 100 * s.pulling / s.total;
+ // We try to round up pulling to at least a percent so that it would be at least a bit visible.
+ if (pulling < 1 && pulled + copiedFromElsewhere + copiedFromOrigin + reused <= 99) {
+ pulling = 1;
+ }
+ progress[folder][file] = {
+ reused: reused,
+ copiedFromOrigin: copiedFromOrigin,
+ copiedFromElsewhere: copiedFromElsewhere,
+ pulled: pulled,
+ pulling: pulling,
+ bytesTotal: s.bytesTotal,
+ bytesDone: s.bytesDone,
+ };
+ }
+ }
+ for (var folder in $scope.progress) {
+ if (!(folder in progress)) {
+ if ($scope.neededFolder === folder) {
+ $scope.refreshNeed($scope.needed.page, $scope.needed.perpage);
+ }
+ } else if ($scope.neededFolder === folder) {
+ for (file in $scope.progress[folder]) {
+ if (!(file in progress[folder])) {
+ $scope.refreshNeed($scope.needed.page, $scope.needed.perpage);
+ break;
+ }
+ }
+ }
+ }
+ $scope.progress = progress;
+ console.log("DownloadProgress", $scope.progress);
+ });
+
+ $scope.$on(Events.FOLDER_SUMMARY, function (event, arg) {
+ var data = arg.data;
+ $scope.model[data.folder] = data.summary;
+ recalcLocalStateTotal();
+ });
+
+ $scope.$on(Events.FOLDER_COMPLETION, function (event, arg) {
+ var data = arg.data;
+ if (!$scope.completion[data.device]) {
+ $scope.completion[data.device] = {};
+ }
+ $scope.completion[data.device][data.folder] = data;
+ recalcCompletion(data.device);
+ });
+
+ $scope.$on(Events.FOLDER_ERRORS, function (event, arg) {
+ $scope.model[arg.data.folder].errors = arg.data.errors.length;
+ });
+
+ $scope.$on(Events.FOLDER_SCAN_PROGRESS, function (event, arg) {
+ var data = arg.data;
+ $scope.scanProgress[data.folder] = {
+ current: data.current,
+ total: data.total,
+ rate: data.rate
+ };
+ console.log("FolderScanProgress", data);
+ });
+
+ $scope.emitHTTPError = function (data, status, headers, config) {
+ $scope.$emit('HTTPError', { data: data, status: status, headers: headers, config: config });
+ };
+
+ var debouncedFuncs = {};
+
+ function refreshFolder(folder) {
+ if ($scope.folders[folder].paused) {
+ return;
+ }
+ var key = "refreshFolder" + folder;
+ if (!debouncedFuncs[key]) {
+ debouncedFuncs[key] = debounce(function () {
+ $http.get(urlbase + '/db/status?folder=' + encodeURIComponent(folder)).success(function (data) {
+ $scope.model[folder] = data;
+ recalcLocalStateTotal();
+ console.log("refreshFolder", folder, data);
+ }).error($scope.emitHTTPError);
+ }, 1000);
+ }
+ debouncedFuncs[key]();
+ }
+
+ function updateLocalConfig(config) {
+ var hasConfig = !isEmptyObject($scope.config);
+
+ $scope.config = config;
+ $scope.config.options._listenAddressesStr = $scope.config.options.listenAddresses.join(', ');
+ $scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', ');
+ $scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted;
+
+ $scope.devices = deviceMap($scope.config.devices);
+ for (var id in $scope.devices) {
+ $scope.completion[id] = {
+ _total: 100,
+ _needBytes: 0,
+ _needItems: 0
+ };
+ };
+ $scope.folders = folderMap($scope.config.folders);
+ Object.keys($scope.folders).forEach(function (folder) {
+ refreshFolder(folder);
+ $scope.folders[folder].devices.forEach(function (deviceCfg) {
+ refreshCompletion(deviceCfg.deviceID, folder);
+ });
+ });
+
+ refreshNoAuthWarning();
+ setDefaultTheme();
+
+ if (!hasConfig) {
+ $scope.$emit('ConfigLoaded');
+ }
+ }
+
+ function refreshSystem() {
+ $http.get(urlbase + '/system/status').success(function (data) {
+ $scope.myID = data.myID;
+ $scope.system = data;
+
+ if ($scope.reportDataPreviewVersion === '') {
+ $scope.reportDataPreviewVersion = $scope.system.urVersionMax;
+ }
+
+ var listenersFailed = [];
+ for (var address in data.connectionServiceStatus) {
+ if (data.connectionServiceStatus[address].error) {
+ listenersFailed.push(address + ": " + data.connectionServiceStatus[address].error);
+ }
+ }
+ $scope.listenersFailed = listenersFailed;
+ $scope.listenersTotal = $scope.sizeOf(data.connectionServiceStatus);
+
+ $scope.discoveryTotal = data.discoveryMethods;
+ var discoveryFailed = [];
+ for (var disco in data.discoveryErrors) {
+ if (data.discoveryErrors[disco]) {
+ discoveryFailed.push(disco + ": " + data.discoveryErrors[disco]);
+ }
+ }
+ $scope.discoveryFailed = discoveryFailed;
+
+ refreshNoAuthWarning();
+
+ console.log("refreshSystem", data);
+ }).error($scope.emitHTTPError);
+ }
+
+ function refreshNoAuthWarning() {
+ if (!$scope.system || !$scope.config || !$scope.config.gui) {
+ // We need all to be able to determine the state.
+ return
+ }
+
+ // If we're not listening on localhost, and there is no
+ // authentication configured, and the magic setting to silence the
+ // warning isn't set, then yell at the user.
+ var addr = $scope.system.guiAddressUsed;
+ var guiCfg = $scope.config.gui;
+ $scope.openNoAuth = addr.substr(0, 4) !== "127."
+ && addr.substr(0, 6) !== "[::1]:"
+ && addr.substr(0, 1) !== "/"
+ && (!guiCfg.user || !guiCfg.password)
+ && guiCfg.authMode !== 'ldap'
+ && !guiCfg.insecureAdminAccess;
+
+ if (guiCfg.user && guiCfg.password) {
+ $scope.dismissNotification('authenticationUserAndPassword');
+ }
+ }
+
+
+ function refreshDiscoveryCache() {
+ $http.get(urlbase + '/system/discovery').success(function (data) {
+ for (var device in data) {
+ for (var i = 0; i < data[device].addresses.length; i++) {
+ // Relay addresses are URLs with
+ // .../?foo=barlongstuff that we strip away here. We
+ // remove the final slash as well for symmetry with
+ // tcp://192.0.2.42:1234 type addresses.
+ data[device].addresses[i] = data[device].addresses[i].replace(/\/\?.*/, '');
+ }
+ }
+ $scope.discoveryCache = data;
+ console.log("refreshDiscoveryCache", data);
+ }).error($scope.emitHTTPError);
+ }
+
+ function recalcLocalStateTotal() {
+ $scope.localStateTotal = {
+ bytes: 0,
+ directories: 0,
+ files: 0
+ };
+
+ for (var f in $scope.model) {
+ $scope.localStateTotal.bytes += $scope.model[f].localBytes;
+ $scope.localStateTotal.files += $scope.model[f].localFiles;
+ $scope.localStateTotal.directories += $scope.model[f].localDirectories;
+ }
+ }
+
+ function recalcCompletion(device) {
+ var total = 0, needed = 0, deletes = 0, items = 0;
+ for (var folder in $scope.completion[device]) {
+ if (folder === "_total" || folder === '_needBytes' || folder === '_needItems') {
+ continue;
+ }
+ total += $scope.completion[device][folder].globalBytes;
+ needed += $scope.completion[device][folder].needBytes;
+ items += $scope.completion[device][folder].needItems;
+ deletes += $scope.completion[device][folder].needDeletes;
+ }
+ if (total == 0) {
+ $scope.completion[device]._total = 100;
+ $scope.completion[device]._needBytes = 0;
+ $scope.completion[device]._needItems = 0;
+ } else {
+ $scope.completion[device]._total = Math.floor(100 * (1 - needed / total));
+ $scope.completion[device]._needBytes = needed;
+ $scope.completion[device]._needItems = items + deletes;
+ }
+
+ if (needed == 0 && deletes > 0) {
+ // We don't need any data, but we have deletes that we need
+ // to do. Drop down the completion percentage to indicate
+ // that we have stuff to do.
+ $scope.completion[device]._total = 95;
+ }
+
+ console.log("recalcCompletion", device, $scope.completion[device]);
+ }
+
+ function refreshCompletion(device, folder) {
+ if (device === $scope.myID) {
+ return;
+ }
+
+ $http.get(urlbase + '/db/completion?device=' + device + '&folder=' + encodeURIComponent(folder)).success(function (data) {
+ if (!$scope.completion[device]) {
+ $scope.completion[device] = {};
+ }
+ $scope.completion[device][folder] = data;
+ recalcCompletion(device);
+ }).error($scope.emitHTTPError);
+ }
+
+ function refreshConnectionStats() {
+ $http.get(urlbase + '/system/connections').success(function (data) {
+ var now = Date.now(),
+ td = (now - prevDate) / 1000,
+ id;
+
+ prevDate = now;
+
+ try {
+ data.total.inbps = Math.max(0, (data.total.inBytesTotal - $scope.connectionsTotal.inBytesTotal) / td);
+ data.total.outbps = Math.max(0, (data.total.outBytesTotal - $scope.connectionsTotal.outBytesTotal) / td);
+ } catch (e) {
+ data.total.inbps = 0;
+ data.total.outbps = 0;
+ }
+ $scope.connectionsTotal = data.total;
+
+ data = data.connections;
+ for (id in data) {
+ if (!data.hasOwnProperty(id)) {
+ continue;
+ }
+ try {
+ data[id].inbps = Math.max(0, (data[id].inBytesTotal - $scope.connections[id].inBytesTotal) / td);
+ data[id].outbps = Math.max(0, (data[id].outBytesTotal - $scope.connections[id].outBytesTotal) / td);
+ } catch (e) {
+ data[id].inbps = 0;
+ data[id].outbps = 0;
+ }
+ }
+ $scope.connections = data;
+ console.log("refreshConnections", data);
+ }).error($scope.emitHTTPError);
+ }
+
+ function refreshErrors() {
+ $http.get(urlbase + '/system/error').success(function (data) {
+ $scope.errors = data.errors;
+ console.log("refreshErrors", data);
+ }).error($scope.emitHTTPError);
+ }
+
+ function refreshConfig() {
+ $http.get(urlbase + '/config').success(function (data) {
+ updateLocalConfig(data);
+ console.log("refreshConfig", data);
+ }).error($scope.emitHTTPError);
+
+ $http.get(urlbase + '/config/insync').success(function (data) {
+ $scope.configInSync = data.configInSync;
+ }).error($scope.emitHTTPError);
+ }
+
+ $scope.refreshNeed = function (page, perpage) {
+ if (!$scope.neededFolder) {
+ return;
+ }
+ var url = urlbase + "/db/need?folder=" + encodeURIComponent($scope.neededFolder);
+ url += "&page=" + page;
+ url += "&perpage=" + perpage;
+ $http.get(url).success(function (data) {
+ console.log("refreshNeed", $scope.neededFolder, data);
+ parseNeeded(data);
+ }).error($scope.emitHTTPError);
+ }
+
+ function needAction(file) {
+ var fDelete = 4096;
+ var fDirectory = 16384;
+
+ if ((file.flags & (fDelete + fDirectory)) === fDelete + fDirectory) {
+ return 'rmdir';
+ } else if ((file.flags & fDelete) === fDelete) {
+ return 'rm';
+ } else if ((file.flags & fDirectory) === fDirectory) {
+ return 'touch';
+ } else {
+ return 'sync';
+ }
+ }
+
+ function parseNeeded(data) {
+ $scope.needed = data;
+ var merged = [];
+ data.progress.forEach(function (item) {
+ item.type = "progress";
+ item.action = needAction(item);
+ merged.push(item);
+ });
+ data.queued.forEach(function (item) {
+ item.type = "queued";
+ item.action = needAction(item);
+ merged.push(item);
+ });
+ data.rest.forEach(function (item) {
+ item.type = "rest";
+ item.action = needAction(item);
+ merged.push(item);
+ });
+ $scope.needed.items = merged;
+ }
+
+ function pathJoin(base, name) {
+ base = expandTilde(base);
+ if (base[base.length - 1] !== $scope.system.pathSeparator) {
+ return base + $scope.system.pathSeparator + name;
+ }
+ return base + name;
+ }
+
+ function expandTilde(path) {
+ if (path && path.trim().charAt(0) === '~') {
+ return $scope.system.tilde + path.trim().substring(1);
+ }
+ return path;
+ }
+
+ function shouldSetDefaultFolderPath() {
+ return $scope.config.options && $scope.config.options.defaultFolderPath && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine
+ }
+
+ function resetRemoteNeed() {
+ $scope.remoteNeed = {};
+ $scope.remoteNeedFolders = [];
+ $scope.remoteNeedDevice = undefined;
+ }
+
+
+ function setDefaultTheme() {
+ if (!document.getElementById("fallback-theme-css")) {
+
+ // check if no support for prefers-color-scheme
+ var colorSchemeNotSupported = typeof window.matchMedia === "undefined" || window.matchMedia('(prefers-color-scheme: dark)').media === 'not all';
+
+ if ($scope.config.gui.theme === "default" && colorSchemeNotSupported) {
+ document.documentElement.style.display = 'none';
+ document.head.insertAdjacentHTML(
+ 'beforeend',
+ ' '
+ );
+ }
+ }
+ }
+
+ function saveIgnores(ignores, cb) {
+ $http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
+ ignore: ignores
+ }).success(function () {
+ if (cb) {
+ cb();
+ }
+ });
+ };
+
+ function initShareEditing(editing) {
+ $scope.currentSharing = {};
+ $scope.currentSharing.editing = editing;
+ $scope.currentSharing.shared = [];
+ $scope.currentSharing.unrelated = [];
+ $scope.currentSharing.selected = {};
+ $scope.currentSharing.encryptionPasswords = {};
+ };
+
+ $scope.refreshFailed = function (page, perpage) {
+ if (!$scope.failed || !$scope.failed.folder) {
+ return;
+ }
+ var url = urlbase + '/folder/errors?folder=' + encodeURIComponent($scope.failed.folder);
+ url += "&page=" + page + "&perpage=" + perpage;
+ $http.get(url).success(function (data) {
+ $scope.failed = data;
+ }).error($scope.emitHTTPError);
+ };
+
+ $scope.refreshRemoteNeed = function (folder, page, perpage) {
+ if (!$scope.remoteNeedDevice) {
+ return;
+ }
+ var url = urlbase + '/db/remoteneed?device=' + $scope.remoteNeedDevice.deviceID;
+ url += '&folder=' + encodeURIComponent(folder);
+ url += "&page=" + page + "&perpage=" + perpage;
+ $http.get(url).success(function (data) {
+ $scope.remoteNeed[folder] = data;
+ }).error(function (err) {
+ $scope.remoteNeed[folder] = undefined;
+ $scope.emitHTTPError(err);
+ });
+ };
+
+ $scope.refreshLocalChanged = function (page, perpage) {
+ if (!$scope.localChangedFolder) {
+ return;
+ }
+ var url = urlbase + '/db/localchanged?folder=';
+ url += encodeURIComponent($scope.localChangedFolder);
+ url += "&page=" + page + "&perpage=" + perpage;
+ $http.get(url).success(function (data) {
+ $scope.localChanged = data;
+ }).error($scope.emitHTTPError);
+ };
+
+ var refreshDeviceStats = debounce(function () {
+ $http.get(urlbase + "/stats/device").success(function (data) {
+ $scope.deviceStats = data;
+ for (var device in $scope.deviceStats) {
+ $scope.deviceStats[device].lastSeen = new Date($scope.deviceStats[device].lastSeen);
+ $scope.deviceStats[device].lastSeenDays = (new Date() - $scope.deviceStats[device].lastSeen) / 1000 / 86400;
+ }
+ console.log("refreshDeviceStats", data);
+ }).error($scope.emitHTTPError);
+ }, 2500);
+
+ var refreshFolderStats = debounce(function () {
+ $http.get(urlbase + "/stats/folder").success(function (data) {
+ $scope.folderStats = data;
+ for (var folder in $scope.folderStats) {
+ if ($scope.folderStats[folder].lastFile) {
+ $scope.folderStats[folder].lastFile.at = new Date($scope.folderStats[folder].lastFile.at);
+ }
+
+ $scope.folderStats[folder].lastScan = new Date($scope.folderStats[folder].lastScan);
+ $scope.folderStats[folder].lastScanDays = (new Date() - $scope.folderStats[folder].lastScan) / 1000 / 86400;
+ }
+ console.log("refreshfolderStats", data);
+ }).error($scope.emitHTTPError);
+ }, 2500);
+
+ var refreshThemes = debounce(function () {
+ $http.get("themes.json").success(function (data) { // no urlbase here as this is served by the asset handler
+ $scope.themes = data.themes;
+ }).error($scope.emitHTTPError);
+ }, 2500);
+
+ var refreshGlobalChanges = debounce(function () {
+ $http.get(urlbase + "/events/disk?limit=25").success(function (data) {
+ if (!data) {
+ // For reasons unknown this is called with data being the empty
+ // string on shutdown, causing an error on .reverse().
+ return;
+ }
+ data = data.reverse();
+ $scope.globalChangeEvents = data;
+ console.log("refreshGlobalChanges", data);
+ }).error($scope.emitHTTPError);
+ }, 2500);
+
+ $scope.refresh = function () {
+ refreshSystem();
+ refreshDiscoveryCache();
+ refreshConnectionStats();
+ refreshErrors();
+ };
+
+ $scope.folderStatus = function (folderCfg) {
+ if (folderCfg.paused) {
+ return 'paused';
+ }
+
+ var folderInfo = $scope.model[folderCfg.id];
+
+ // after restart syncthing process state may be empty
+ if (typeof folderInfo === 'undefined' || !folderInfo.state) {
+ return 'unknown';
+ }
+
+ var state = '' + folderInfo.state;
+ if (state === 'error') {
+ return 'stopped'; // legacy, the state is called "stopped" in the GUI
+ }
+
+ if (state !== 'idle') {
+ return state;
+ }
+
+ if (folderInfo.needTotalItems > 0) {
+ return 'outofsync';
+ }
+ if ($scope.hasFailedFiles(folderCfg.id)) {
+ return 'faileditems';
+ }
+ if (folderInfo.receiveOnlyTotalItems) {
+ switch (folderCfg.type) {
+ case 'receiveonly':
+ return 'localadditions';
+ case 'receiveencrypted':
+ return 'localunencrypted';
+ }
+ }
+ if (folderCfg.devices.length <= 1) {
+ return 'unshared';
+ }
+
+ return state;
+ };
+
+ $scope.folderClass = function (folderCfg) {
+ var status = $scope.folderStatus(folderCfg);
+
+ if (status === 'idle' || status === 'localadditions') {
+ return 'success';
+ }
+ if (status == 'paused') {
+ return 'default';
+ }
+ if (status === 'syncing' || status === 'sync-preparing' || status === 'scanning' || status === 'cleaning') {
+ return 'primary';
+ }
+ if (status === 'unknown') {
+ return 'info';
+ }
+ if (status === 'stopped' || status === 'outofsync' || status === 'error' || status === 'faileditems' || status === 'localunencrypted') {
+ return 'danger';
+ }
+ if (status === 'unshared' || status === 'scan-waiting' || status === 'sync-waiting' || status === 'clean-waiting') {
+ return 'warning';
+ }
+
+ return 'info';
+ };
+
+ $scope.syncPercentage = function (folder) {
+ if (typeof $scope.model[folder] === 'undefined') {
+ return 100;
+ }
+ if ($scope.model[folder].needTotalItems === 0) {
+ return 100;
+ }
+ if (($scope.model[folder].needBytes == 0 && $scope.model[folder].needDeletes > 0) || $scope.model[folder].globalBytes == 0) {
+ // We don't need any data, but we have deletes that we need
+ // to do. Drop down the completion percentage to indicate
+ // that we have stuff to do.
+ // Do the same thing in case we only have zero byte files to sync.
+ return 95;
+ }
+ var pct = 100 * $scope.model[folder].inSyncBytes / $scope.model[folder].globalBytes;
+ return Math.floor(pct);
+ };
+
+ $scope.scanPercentage = function (folder) {
+ if (!$scope.scanProgress[folder]) {
+ return undefined;
+ }
+ var pct = 100 * $scope.scanProgress[folder].current / $scope.scanProgress[folder].total;
+ return Math.floor(pct);
+ };
+
+ $scope.scanRate = function (folder) {
+ if (!$scope.scanProgress[folder]) {
+ return 0;
+ }
+ return $scope.scanProgress[folder].rate;
+ };
+
+ $scope.scanRemaining = function (folder) {
+ // Formats the remaining scan time as a string. Includes days and
+ // hours only when relevant, resulting in time stamps like:
+ // 00m 40s
+ // 32m 40s
+ // 2h 32m
+ // 4d 2h
+ // In case remaining scan time appears to be >31d, omit the
+ // details, i.e.:
+ // > 1 month
+
+ if (!$scope.scanProgress[folder]) {
+ return "";
+ }
+ // Calculate remaining bytes and seconds based on our current
+ // rate.
+
+ var remainingBytes = $scope.scanProgress[folder].total - $scope.scanProgress[folder].current;
+ var seconds = remainingBytes / $scope.scanProgress[folder].rate;
+ // Round up to closest ten seconds to avoid flapping too much to
+ // and fro.
+
+ seconds = Math.ceil(seconds / 10) * 10;
+
+ // Separate out the number of days.
+ var days = 0;
+ var res = [];
+ if (seconds >= 86400) {
+ days = Math.floor(seconds / 86400);
+ if (days > 31) {
+ return '> 1 month';
+ }
+ res.push('' + days + 'd')
+ seconds = seconds % 86400;
+ }
+
+ // Separate out the number of hours.
+ var hours = 0;
+ if (seconds > 3600) {
+ hours = Math.floor(seconds / 3600);
+ res.push('' + hours + 'h')
+ seconds = seconds % 3600;
+ }
+
+ var d = new Date(1970, 0, 1).setSeconds(seconds);
+
+ if (days === 0) {
+ // Format minutes only if we're within a day of completion.
+ var f = $filter('date')(d, "m'm'");
+ res.push(f);
+ }
+
+ if (days === 0 && hours === 0) {
+ // Format seconds only when we're within an hour of completion.
+ var f = $filter('date')(d, "ss's'");
+ res.push(f);
+ }
+
+ return res.join(' ');
+ };
+
+ $scope.deviceStatus = function (deviceCfg) {
+ var status = '';
+
+ if ($scope.deviceFolders(deviceCfg).length === 0) {
+ status = 'unused-';
+ }
+
+ if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
+ return 'unknown';
+ }
+
+ if (deviceCfg.paused) {
+ return status + 'paused';
+ }
+
+ if ($scope.connections[deviceCfg.deviceID].connected) {
+ if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
+ return status + 'insync';
+ } else {
+ return 'syncing';
+ }
+ }
+
+ // Disconnected
+ return status + 'disconnected';
+ };
+
+ $scope.deviceClass = function (deviceCfg) {
+ if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
+ return 'info';
+ }
+
+ if (deviceCfg.paused) {
+ return 'default';
+ }
+
+ if ($scope.connections[deviceCfg.deviceID].connected) {
+ if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
+ return 'success';
+ } else {
+ return 'primary';
+ }
+ }
+
+ // Disconnected
+ return 'info';
+ };
+
+ $scope.syncthingStatus = function () {
+ var syncCount = 0;
+ var notifyCount = 0;
+ var pauseCount = 0;
+
+ // loop through all folders
+ var folderListCache = $scope.folderList();
+ for (var i = 0; i < folderListCache.length; i++) {
+ var status = $scope.folderStatus(folderListCache[i]);
+ switch (status) {
+ case 'sync-preparing':
+ case 'syncing':
+ syncCount++;
+ break;
+ case 'stopped':
+ case 'unknown':
+ case 'outofsync':
+ case 'error':
+ notifyCount++;
+ break;
+ }
+ }
+
+ // loop through all devices
+ var deviceCount = 0;
+ var pendingFolders = 0;
+ for (var id in $scope.devices) {
+ var status = $scope.deviceStatus({
+ deviceID: id
+ });
+ switch (status) {
+ case 'unknown':
+ notifyCount++;
+ break;
+ case 'paused':
+ pauseCount++;
+ break;
+ case 'unused':
+ deviceCount--;
+ break;
+ }
+ pendingFolders += $scope.devices[id].pendingFolders.length;
+ deviceCount++;
+ }
+
+ // enumerate notifications
+ if ($scope.openNoAuth || !$scope.configInSync || $scope.errorList().length > 0 || !online || (
+ !isEmptyObject($scope.config) && ($scope.config.pendingDevices.length > 0 || pendingFolders > 0)
+ )) {
+ notifyCount++;
+ }
+
+ // at least one folder is syncing
+ if (syncCount > 0) {
+ return 'sync';
+ }
+
+ // a device is unknown or a folder is stopped/unknown/outofsync/error or some other notification is open or gui offline
+ if (notifyCount > 0) {
+ return 'notify';
+ }
+
+ // all used devices are paused except (this) one
+ if (pauseCount === deviceCount - 1) {
+ return 'pause';
+ }
+
+ return 'default';
+ };
+
+ $scope.deviceAddr = function (deviceCfg) {
+ var conn = $scope.connections[deviceCfg.deviceID];
+ if (conn && conn.connected) {
+ return conn.address;
+ }
+ return '?';
+ };
+
+ $scope.friendlyNameFromShort = function (shortID) {
+ var matches = Object.keys($scope.devices).filter(function (id) {
+ return id.substr(0, 7) === shortID;
+ });
+ if (matches.length !== 1) {
+ return shortID;
+ }
+ return matches[0].name;
+ };
+
+ $scope.friendlyNameFromID = function (deviceID) {
+ var match = $scope.devices[deviceID];
+ if (match) {
+ return $scope.deviceName(match);
+ }
+ return deviceID.substr(0, 6);
+ };
+
+ $scope.deviceName = function (deviceCfg) {
+ if (typeof deviceCfg === 'undefined' || typeof deviceCfg.deviceID === 'undefined') {
+ return "";
+ }
+ if (deviceCfg.name) {
+ return deviceCfg.name;
+ }
+ return deviceCfg.deviceID.substr(0, 6);
+ };
+
+ $scope.thisDeviceName = function () {
+ var device = $scope.thisDevice();
+ if (typeof device === 'undefined') {
+ return "(unknown device)";
+ }
+ if (device.name) {
+ return device.name;
+ }
+ return device.deviceID.substr(0, 6);
+ };
+
+ $scope.setDevicePause = function (device, pause) {
+ $scope.devices[device].paused = pause;
+ $scope.config.devices = $scope.deviceList();
+ $scope.saveConfig();
+ };
+
+ $scope.setFolderPause = function (folder, pause) {
+ var cfg = $scope.folders[folder];
+ if (cfg) {
+ cfg.paused = pause;
+ $scope.config.folders = folderList($scope.folders);
+ $scope.saveConfig();
+ }
+ };
+
+ $scope.showDiscoveryFailures = function () {
+ $('#discovery-failures').modal();
+ };
+
+ $scope.logging = {
+ facilities: {},
+ refreshFacilities: function () {
+ $http.get(urlbase + '/system/debug').success(function (data) {
+ var facilities = {};
+ data.enabled = data.enabled || [];
+ $.each(data.facilities, function (key, value) {
+ facilities[key] = {
+ description: value,
+ enabled: data.enabled.indexOf(key) > -1
+ }
+ })
+ $scope.logging.facilities = facilities;
+ }).error($scope.emitHTTPError);
+ },
+ show: function () {
+ $scope.logging.refreshFacilities();
+ $scope.logging.timer = $timeout($scope.logging.fetch);
+ var textArea = $('#logViewerText');
+ textArea.on("scroll", $scope.logging.onScroll);
+ $('#logViewer').modal().one('shown.bs.modal', function () {
+ // Scroll to bottom.
+ textArea.scrollTop(textArea[0].scrollHeight);
+ }).one('hidden.bs.modal', function () {
+ $timeout.cancel($scope.logging.timer);
+ textArea.off("scroll", $scope.logging.onScroll);
+ $scope.logging.timer = null;
+ $scope.logging.entries = [];
+ });
+ },
+ onFacilityChange: function (facility) {
+ var enabled = $scope.logging.facilities[facility].enabled;
+ // Disable checkboxes while we're in flight.
+ $.each($scope.logging.facilities, function (key) {
+ $scope.logging.facilities[key].enabled = null;
+ })
+ $http.post(urlbase + '/system/debug?' + (enabled ? 'enable=' : 'disable=') + facility)
+ .success($scope.logging.refreshFacilities)
+ .error($scope.emitHTTPError);
+ },
+ onScroll: function () {
+ var textArea = $('#logViewerText');
+ var scrollTop = textArea.prop('scrollTop');
+ var scrollHeight = textArea.prop('scrollHeight');
+ $scope.logging.paused = scrollHeight > (scrollTop + textArea.outerHeight());
+ // Browser events do not cause redraw, trigger manually.
+ $scope.$apply();
+ },
+ timer: null,
+ entries: [],
+ paused: false,
+ content: function () {
+ var content = "";
+ $.each($scope.logging.entries, function (idx, entry) {
+ content += entry.when.split('.')[0].replace('T', ' ') + ' ' + entry.message + "\n";
+ });
+ return content;
+ },
+ fetch: function () {
+ var textArea = $('#logViewerText');
+ if ($scope.logging.paused) {
+ if (!$scope.logging.timer) return;
+ $scope.logging.timer = $timeout($scope.logging.fetch, 500);
+ return;
+ }
+
+ var last = null;
+ if ($scope.logging.entries.length > 0) {
+ last = $scope.logging.entries[$scope.logging.entries.length - 1].when;
+ }
+
+ $http.get(urlbase + '/system/log' + (last ? '?since=' + encodeURIComponent(last) : '')).success(function (data) {
+ if (!$scope.logging.timer) return;
+ $scope.logging.timer = $timeout($scope.logging.fetch, 2000);
+ if (!$scope.logging.paused) {
+ if (data.messages) {
+ $scope.logging.entries.push.apply($scope.logging.entries, data.messages);
+ // Wait for the text area to be redrawn, adding new lines, and then scroll to bottom.
+ $timeout(function () {
+ textArea.scrollTop(textArea[0].scrollHeight);
+ });
+ }
+ }
+ });
+ }
+ };
+
+ $scope.discardChangedSettings = function () {
+ $("#discard-changes-confirmation").modal("hide");
+ $("#settings").off("hide.bs.modal").modal("hide");
+ };
+
+ $scope.showSettings = function () {
+ // Make a working copy
+ $scope.tmpOptions = angular.copy($scope.config.options);
+ $scope.tmpOptions.deviceName = $scope.thisDevice().name;
+ $scope.tmpOptions.upgrades = "none";
+ if ($scope.tmpOptions.autoUpgradeIntervalH > 0) {
+ $scope.tmpOptions.upgrades = "stable";
+ }
+ if ($scope.tmpOptions.upgradeToPreReleases) {
+ $scope.tmpOptions.upgrades = "candidate";
+ }
+ $scope.tmpGUI = angular.copy($scope.config.gui);
+ $scope.tmpRemoteIgnoredDevices = angular.copy($scope.config.remoteIgnoredDevices);
+ $scope.tmpDevices = angular.copy($scope.config.devices);
+ $('#settings').modal("show");
+ $("#settings a[href='#settings-general']").tab("show");
+ $("#settings").on('hide.bs.modal', function (event) {
+ if ($scope.settingsModified()) {
+ event.preventDefault();
+ $("#discard-changes-confirmation").modal("show");
+ } else {
+ $("#settings").off("hide.bs.modal");
+ }
+ });
+ };
+
+ $scope.saveConfig = function (callback) {
+ var cfg = JSON.stringify($scope.config);
+ var opts = {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ };
+ $http.put(urlbase + '/config', cfg, opts).success(function () {
+ refreshConfig();
+
+ if (callback) {
+ callback();
+ }
+ }).error(function (data, status, headers, config) {
+ refreshConfig();
+ $scope.emitHTTPError(data, status, headers, config);
+ });
+ };
+
+ $scope.urVersions = function () {
+ var result = [];
+ if ($scope.system) {
+ for (var i = $scope.system.urVersionMax; i >= 2; i--) {
+ result.push("" + i);
+ }
+ }
+ return result;
+ };
+
+ $scope.settingsModified = function () {
+ // Options has artificial properties injected into the temp config.
+ // Need to recompute them before we can check equality
+ var options = angular.copy($scope.config.options);
+ options.deviceName = $scope.thisDevice().name;
+ options.upgrades = "none";
+ if (options.autoUpgradeIntervalH > 0) {
+ options.upgrades = "stable";
+ }
+ if (options.upgradeToPreReleases) {
+ options.upgrades = "candidate";
+ }
+ var optionsEqual = angular.equals(options, $scope.tmpOptions);
+ var guiEquals = angular.equals($scope.config.gui, $scope.tmpGUI);
+ var ignoredDevicesEquals = angular.equals($scope.config.remoteIgnoredDevices, $scope.tmpRemoteIgnoredDevices);
+ var ignoredFoldersEquals = angular.equals($scope.config.devices, $scope.tmpDevices);
+ console.log("settings equals - options: " + optionsEqual + " gui: " + guiEquals + " ignDev: " + ignoredDevicesEquals + " ignFol: " + ignoredFoldersEquals);
+ return !optionsEqual || !guiEquals || !ignoredDevicesEquals || !ignoredFoldersEquals;
+ };
+
+ $scope.saveSettings = function () {
+ // Make sure something changed
+ if ($scope.settingsModified()) {
+ var themeChanged = $scope.config.gui.theme !== $scope.tmpGUI.theme;
+ // Angular has issues with selects with numeric values, so we handle strings here.
+ $scope.tmpOptions.urAccepted = parseInt($scope.tmpOptions._urAcceptedStr);
+ // Check if auto-upgrade has been enabled or disabled. This
+ // also has an effect on usage reporting, so do the check
+ // for that later.
+ if ($scope.tmpOptions.upgrades == "candidate") {
+ $scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
+ $scope.tmpOptions.upgradeToPreReleases = true;
+ $scope.tmpOptions.urAccepted = $scope.system.urVersionMax;
+ $scope.tmpOptions.urSeen = $scope.system.urVersionMax;
+ } else if ($scope.tmpOptions.upgrades == "stable") {
+ $scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
+ $scope.tmpOptions.upgradeToPreReleases = false;
+ } else {
+ $scope.tmpOptions.autoUpgradeIntervalH = 0;
+ $scope.tmpOptions.upgradeToPreReleases = false;
+ }
+
+ // Check if protocol will need to be changed on restart
+ if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) {
+ $scope.protocolChanged = true;
+ }
+
+ // Parse strings to arrays before copying over
+ ['listenAddresses', 'globalAnnounceServers'].forEach(function (key) {
+ $scope.tmpOptions[key] = $scope.tmpOptions["_" + key + "Str"].split(/[ ,]+/).map(function (x) {
+ return x.trim();
+ });
+ });
+
+ // Apply new settings locally
+ $scope.thisDeviceIn($scope.tmpDevices).name = $scope.tmpOptions.deviceName;
+ $scope.config.options = angular.copy($scope.tmpOptions);
+ $scope.config.gui = angular.copy($scope.tmpGUI);
+ $scope.config.remoteIgnoredDevices = angular.copy($scope.tmpRemoteIgnoredDevices);
+ $scope.config.devices = angular.copy($scope.tmpDevices);
+ // $scope.devices is updated by updateLocalConfig based on
+ // the config changed event, but settingsModified will look
+ // at it before that and conclude that the settings are
+ // modified (even though we just saved) unless we update
+ // here as well...
+ $scope.devices = deviceMap($scope.config.devices);
+
+ $scope.saveConfig(function () {
+ if (themeChanged) {
+ document.location.reload(true);
+ }
+ });
+ }
+
+ $("#settings").off("hide.bs.modal").modal("hide");
+ };
+
+ $scope.saveAdvanced = function () {
+ $scope.config = $scope.advancedConfig;
+ $scope.saveConfig();
+ $('#advanced').modal("hide");
+ };
+
+ $scope.restart = function () {
+ restarting = true;
+ $('#restarting').modal();
+ $http.post(urlbase + '/system/restart');
+ $scope.configInSync = true;
+
+ // Switch webpage protocol if needed
+ if ($scope.protocolChanged) {
+ var protocol = 'http';
+
+ if ($scope.config.gui.useTLS) {
+ protocol = 'https';
+ }
+
+ setTimeout(function () {
+ window.location.protocol = protocol;
+ }, 2500);
+
+ $scope.protocolChanged = false;
+ }
+ };
+
+ $scope.upgrade = function () {
+ restarting = true;
+ $('#upgrade').modal('hide');
+ $('#majorUpgrade').modal('hide');
+ $('#upgrading').modal();
+ $http.post(urlbase + '/system/upgrade').success(function () {
+ $('#restarting').modal();
+ $('#upgrading').modal('hide');
+ }).error(function () {
+ $('#upgrading').modal('hide');
+ });
+ };
+
+ $scope.shutdown = function () {
+ restarting = true;
+ $http.post(urlbase + '/system/shutdown').success(function () {
+ $('#shutdown').modal();
+ }).error($scope.emitHTTPError);
+ $scope.configInSync = true;
+ };
+
+ $scope.editDevice = function (deviceCfg) {
+ $scope.currentDevice = $.extend({}, deviceCfg);
+ $scope.editingExisting = true;
+ $scope.willBeReintroducedBy = undefined;
+ if (deviceCfg.introducedBy) {
+ var introducerDevice = $scope.devices[deviceCfg.introducedBy];
+ if (introducerDevice && introducerDevice.introducer) {
+ $scope.willBeReintroducedBy = $scope.deviceName(introducerDevice);
+ }
+ }
+ $scope.currentDevice._addressesStr = deviceCfg.addresses.join(', ');
+
+ initShareEditing('device');
+ for (var folderID in $scope.folders) {
+ var found = false;
+ for (var i = 0; i < $scope.folders[folderID].devices.length; i++) {
+ if ($scope.folders[folderID].devices[i].deviceID === deviceCfg.deviceID) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ $scope.currentSharing.encryptionPasswords[folderID] = $scope.folders[folderID].devices[i].encryptionPassword;
+ $scope.currentSharing.shared.push($scope.folders[folderID]);
+ } else {
+ $scope.currentSharing.unrelated.push($scope.folders[folderID]);
+ }
+ $scope.currentSharing.selected[folderID] = found;
+ }
+
+ $scope.deviceEditor.$setPristine();
+ $('#editDevice').modal();
+ };
+
+ $scope.selectAllSharedFolders = function (state) {
+ var devices = $scope.currentSharing.shared;
+ for (var i = 0; i < devices.length; i++) {
+ $scope.currentSharing.selected[devices[i].deviceID] = !!state;
+ }
+ };
+
+ $scope.selectAllUnrelatedFolders = function (state) {
+ var devices = $scope.currentSharing.unrelated;
+ for (var i = 0; i < devices.length; i++) {
+ $scope.currentSharing.selected[devices[i].deviceID] = !!state;
+ }
+ };
+
+ $scope.addDevice = function (deviceID, name) {
+ return $http.get(urlbase + '/system/discovery')
+ .success(function (registry) {
+ $scope.discovery = [];
+ for (var id in registry) {
+ if ($scope.discovery.length === 5) {
+ break;
+ }
+ if (id in $scope.devices) {
+ continue
+ }
+ $scope.discovery.push(id);
+ }
+ })
+ .then(function () {
+ $scope.currentDevice = {
+ name: name,
+ deviceID: deviceID,
+ _addressesStr: 'dynamic',
+ compression: 'metadata',
+ introducer: false,
+ pendingFolders: [],
+ ignoredFolders: []
+ };
+ $scope.editingExisting = false;
+ $scope.deviceEditor.$setPristine();
+ $('#editDevice').modal();
+ });
+ };
+
+ $scope.deleteDevice = function () {
+ $('#editDevice').modal('hide');
+ if (!$scope.editingExisting) {
+ return;
+ }
+
+ var id = $scope.currentDevice.deviceID
+ delete $scope.devices[id];
+ $scope.config.devices = $scope.deviceList();
+
+ for (var id in $scope.folders) {
+ $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
+ return n.deviceID !== $scope.currentDevice.deviceID;
+ });
+ }
+
+ $scope.saveConfig();
+ };
+
+ $scope.saveDevice = function () {
+ $('#editDevice').modal('hide');
+ $scope.saveDeviceConfig($scope.currentDevice);
+ };
+
+ $scope.saveDeviceConfig = function (deviceCfg) {
+ deviceCfg.addresses = deviceCfg._addressesStr.split(',').map(function (x) {
+ return x.trim();
+ });
+
+ $scope.devices[deviceCfg.deviceID] = deviceCfg;
+ $scope.config.devices = deviceList($scope.devices);
+
+ $scope.currentSharing.shared.forEach(function (folder) {
+ var id = folder.id;
+ if ($scope.currentSharing.selected[id] !== true) {
+ // Remove device from folder
+ $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
+ return n.deviceID !== deviceCfg.deviceID;
+ });
+ return;
+ }
+ // Update encryption pw
+ for (i = 0; i < $scope.folders[id].devices.length; i++) {
+ if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) {
+ $scope.folders[id].devices[i].encryptionPassword = $scope.currentSharing.encryptionPasswords[id];
+ break;
+ }
+ }
+ });
+ $scope.currentSharing.unrelated.forEach(function (folder) {
+ if ($scope.currentSharing.selected[folder.id] === true) {
+ $scope.folders[folder.id].devices.push({
+ deviceID: deviceCfg.deviceID,
+ encryptionPassword: $scope.currentSharing.encryptionPasswords[folder.id],
+ });
+ }
+ });
+
+ delete $scope.currentSharing;
+
+ $scope.config.folders = folderList($scope.folders);
+
+ $scope.saveConfig();
+ };
+
+ $scope.ignoreDevice = function (pendingDevice) {
+ pendingDevice = angular.copy(pendingDevice);
+ // Bump time
+ pendingDevice.time = (new Date()).toISOString();
+ $scope.config.remoteIgnoredDevices.push(pendingDevice);
+ $scope.saveConfig();
+ };
+
+ $scope.unignoreDeviceFromTemporaryConfig = function (ignoredDevice) {
+ $scope.tmpRemoteIgnoredDevices = $scope.tmpRemoteIgnoredDevices.filter(function (existingIgnoredDevice) {
+ return ignoredDevice.deviceID !== existingIgnoredDevice.deviceID;
+ });
+ };
+
+ $scope.ignoredFoldersCountTmpConfig = function () {
+ var count = 0;
+ ($scope.tmpDevices || []).forEach(function (deviceCfg) {
+ count += deviceCfg.ignoredFolders.length;
+ });
+ return count;
+ };
+
+ $scope.unignoreFolderFromTemporaryConfig = function (device, ignoredFolderID) {
+ for (var i = 0; i < $scope.tmpDevices.length; i++) {
+ if ($scope.tmpDevices[i].deviceID == device) {
+ $scope.tmpDevices[i].ignoredFolders = $scope.tmpDevices[i].ignoredFolders.filter(function (existingIgnoredFolder) {
+ return existingIgnoredFolder.id !== ignoredFolderID;
+ });
+ return;
+ }
+ }
+ };
+
+ $scope.otherDevices = function () {
+ return $scope.deviceList().filter(function (n) {
+ return n.deviceID !== $scope.myID;
+ });
+ };
+
+ $scope.thisDevice = function () {
+ return $scope.devices[$scope.myID];
+ };
+
+ $scope.thisDeviceIn = function (l) {
+ for (var i = 0; i < l.length; i++) {
+ var n = l[i];
+ if (n.deviceID === $scope.myID) {
+ return n;
+ }
+ }
+ };
+
+ $scope.allDevices = function () {
+ var devices = $scope.otherDevices();
+ devices.push($scope.thisDevice());
+ return devices;
+ };
+
+ $scope.setAllDevicesPause = function (pause) {
+ for (var id in $scope.devices) {
+ $scope.devices[id].paused = pause;
+ };
+ $scope.config.devices = deviceList($scope.devices);
+ $scope.saveConfig();
+ }
+
+ $scope.isAtleastOneDevicePausedStateSetTo = function (pause) {
+ for (var id in $scope.devices) {
+ if ($scope.devices[id].paused == pause) {
+ return true;
+ }
+ }
+
+ return false
+ }
+
+ $scope.errorList = function () {
+ if (!$scope.errors) {
+ return [];
+ }
+ return $scope.errors.filter(function (e) {
+ return e.when > $scope.seenError;
+ });
+ };
+
+ $scope.clearErrors = function () {
+ $scope.seenError = $scope.errors[$scope.errors.length - 1].when;
+ $http.post(urlbase + '/system/error/clear');
+ };
+
+ $scope.fsWatcherErrorMap = function () {
+ var errs = {}
+ $.each($scope.folders, function (id, cfg) {
+ if (cfg.fsWatcherEnabled && $scope.model[cfg.id] && $scope.model[id].watchError && !cfg.paused && $scope.folderStatus(cfg) !== 'stopped') {
+ errs[id] = $scope.model[id].watchError;
+ }
+ });
+ return errs;
+ };
+
+ $scope.friendlyDevices = function (str) {
+ for (var id in $scope.devices) {
+ str = str.replace(id, $scope.deviceName($scope.devices[id]));
+ }
+ return str;
+ };
+
+ $scope.folderList = function () {
+ return folderList($scope.folders);
+ };
+
+ $scope.deviceList = function () {
+ return deviceList($scope.devices);
+ };
+
+ $scope.directoryList = [];
+
+ $scope.$watch('currentFolder.path', function (newvalue) {
+ if (!newvalue) {
+ return;
+ }
+ $scope.currentFolder.path = expandTilde(newvalue);
+ $http.get(urlbase + '/system/browse', {
+ params: { current: newvalue }
+ }).success(function (data) {
+ $scope.directoryList = data;
+ }).error($scope.emitHTTPError);
+ });
+
+ $scope.$watch('currentFolder.label', function (newvalue) {
+ if (!newvalue || !shouldSetDefaultFolderPath()) {
+ return;
+ }
+ $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+ });
+
+ $scope.$watch('currentFolder.id', function (newvalue) {
+ if (!newvalue || !shouldSetDefaultFolderPath() || $scope.currentFolder.label) {
+ return;
+ }
+ $scope.currentFolder.path = pathJoin($scope.config.options.defaultFolderPath, newvalue);
+ });
+
+ $scope.fsWatcherToggled = function () {
+ if ($scope.currentFolder.fsWatcherEnabled) {
+ $scope.currentFolder.rescanIntervalS = 3600;
+ } else {
+ $scope.currentFolder.rescanIntervalS = 60;
+ }
+ };
+
+ $scope.loadFormIntoScope = function (form) {
+ console.log('loadFormIntoScope', form.$name);
+ switch (form.$name) {
+ case 'deviceEditor':
+ $scope.deviceEditor = form;
+ break;
+ case 'folderEditor':
+ $scope.folderEditor = form;
+ break;
+ }
+ };
+
+ $scope.globalChanges = function () {
+ $('#globalChanges').modal();
+ };
+
+ $scope.editFolderModal = function () {
+ $scope.folderPathErrors = {};
+ $scope.folderEditor.$setPristine();
+ $('#editFolder').modal().one('shown.bs.tab', function (e) {
+ if (e.target.attributes.href.value === "#folder-ignores") {
+ $('#folder-ignores textarea').focus();
+ }
+ }).one('hidden.bs.modal', function () {
+ $('.nav-tabs a[href="#folder-general"]').tab('show');
+ window.location.hash = "";
+ });
+ };
+
+ $scope.editFolder = function (folderCfg) {
+ $scope.editingExisting = true;
+ $scope.currentFolder = angular.copy(folderCfg);
+ if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
+ $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
+ }
+ // Cache complete device objects indexed by ID for lookups
+ initShareEditing('folder');
+ $scope.currentFolder.devices.forEach(function (n) {
+ if (n.deviceID !== $scope.myID) {
+ $scope.currentSharing.shared.push($scope.devices[n.deviceID]);
+ }
+ if (n.encryptionPassword !== '') {
+ $scope.currentSharing.encryptionPasswords[n.deviceID] = n.encryptionPassword;
+ }
+ $scope.currentSharing.selected[n.deviceID] = true;
+ });
+ $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
+ return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
+ });
+ if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
+ $scope.currentFolder.trashcanFileVersioning = true;
+ $scope.currentFolder.fileVersioningSelector = "trashcan";
+ $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
+ $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
+ } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
+ $scope.currentFolder.simpleFileVersioning = true;
+ $scope.currentFolder.fileVersioningSelector = "simple";
+ $scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
+ $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
+ $scope.currentFolder.trashcanClean = +$scope.currentFolder.versioning.params.cleanoutDays;
+ } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
+ $scope.currentFolder.staggeredFileVersioning = true;
+ $scope.currentFolder.fileVersioningSelector = "staggered";
+ $scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
+ $scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
+ $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
+ $scope.currentFolder.versioningCleanupIntervalS = +$scope.currentFolder.versioning.cleanupIntervalS;
+ } else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "external") {
+ $scope.currentFolder.externalFileVersioning = true;
+ $scope.currentFolder.fileVersioningSelector = "external";
+ $scope.currentFolder.externalCommand = $scope.currentFolder.versioning.params.command;
+ } else {
+ $scope.currentFolder.fileVersioningSelector = "none";
+ }
+ $scope.currentFolder.trashcanClean = $scope.currentFolder.trashcanClean || 0; // weeds out nulls and undefineds
+ $scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
+ $scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
+ $scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.staggeredVersionsPath || "";
+ $scope.currentFolder.versioningCleanupIntervalS = $scope.currentFolder.versioningCleanupIntervalS || 3600;
+
+ // staggeredMaxAge can validly be zero, which we should not replace
+ // with the default value of 365. So only set the default if it's
+ // actually undefined.
+ if (typeof $scope.currentFolder.staggeredMaxAge === 'undefined') {
+ $scope.currentFolder.staggeredMaxAge = 365;
+ }
+ $scope.currentFolder.externalCommand = $scope.currentFolder.externalCommand || "";
+
+ $scope.ignores.text = 'Loading...';
+ $scope.ignores.error = null;
+ $scope.ignores.disabled = true;
+ $http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
+ .success(function (data) {
+ $scope.currentFolder.ignores = data.ignore || [];
+ $scope.ignores.text = $scope.currentFolder.ignores.join('\n');
+ $scope.ignores.error = data.error;
+ $scope.ignores.disabled = false;
+ })
+ .error(function (err) {
+ $scope.ignores.text = $translate.instant("Failed to load ignore patterns.");
+ $scope.emitHTTPError(err);
+ });
+
+ $scope.editFolderModal();
+ };
+
+ $scope.selectAllSharedDevices = function (state) {
+ var devices = $scope.currentSharing.shared;
+ for (var i = 0; i < devices.length; i++) {
+ $scope.currentSharing.selected[devices[i].deviceID] = !!state;
+ }
+ };
+
+ $scope.selectAllUnrelatedDevices = function (state) {
+ var devices = $scope.currentSharing.unrelated;
+ for (var i = 0; i < devices.length; i++) {
+ $scope.currentSharing.selected[devices[i].deviceID] = !!state;
+ }
+ };
+
+ $scope.addFolder = function () {
+ $http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
+ $scope.editingExisting = false;
+ $scope.currentFolder = angular.copy($scope.folderDefaults);
+ initShareEditing('folder');
+ $scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
+ $scope.currentSharing.unrelated = $scope.otherDevices();
+ $scope.ignores.text = '';
+ $scope.ignores.error = null;
+ $scope.ignores.disabled = false;
+ $scope.editFolderModal();
+ });
+ };
+
+ $scope.addFolderAndShare = function (folder, folderLabel, device) {
+ $scope.editingExisting = false;
+ $scope.currentFolder = angular.copy($scope.folderDefaults);
+ $scope.currentFolder.id = folder;
+ $scope.currentFolder.label = folderLabel;
+ $scope.currentFolder.viewFlags = {
+ importFromOtherDevice: true
+ };
+ initShareEditing('folder');
+ $scope.currentSharing.selected[device] = true;
+ $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) {
+ return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID]
+ });
+ $scope.ignores.text = '';
+ $scope.ignores.error = null;
+ $scope.ignores.disabled = false;
+ $scope.editFolderModal();
+ };
+
+ $scope.shareFolderWithDevice = function (folder, device) {
+ $scope.folders[folder].devices.push({
+ deviceID: device
+ });
+ $scope.config.folders = folderList($scope.folders);
+ $scope.saveConfig();
+ };
+
+ $scope.saveFolder = function () {
+ $('#editFolder').modal('hide');
+ var folderCfg = angular.copy($scope.currentFolder);
+ $scope.currentSharing.selected[$scope.myID] = true;
+ var newDevices = [];
+ folderCfg.devices.forEach(function (dev) {
+ if ($scope.currentSharing.selected[dev.deviceID] === true) {
+ dev.encryptionPassword = $scope.currentSharing.encryptionPasswords[dev.deviceID];
+ newDevices.push(dev);
+ delete $scope.currentSharing.selected[dev.deviceID];
+ };
+ });
+ for (var deviceID in $scope.currentSharing.selected) {
+ if ($scope.currentSharing.selected[deviceID] === true) {
+ newDevices.push({
+ deviceID: deviceID,
+ encryptionPassword: $scope.currentSharing.encryptionPasswords[deviceID],
+ });
+ }
+ }
+ folderCfg.devices = newDevices;
+ delete $scope.currentSharing;
+
+ if (folderCfg.fileVersioningSelector === "trashcan") {
+ folderCfg.versioning = {
+ 'type': 'trashcan',
+ 'params': {
+ 'cleanoutDays': '' + folderCfg.trashcanClean
+ },
+ 'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+ };
+ delete folderCfg.trashcanFileVersioning;
+ delete folderCfg.trashcanClean;
+ } else if (folderCfg.fileVersioningSelector === "simple") {
+ folderCfg.versioning = {
+ 'type': 'simple',
+ 'params': {
+ 'keep': '' + folderCfg.simpleKeep,
+ 'cleanoutDays': '' + folderCfg.trashcanClean
+ },
+ 'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+ };
+ delete folderCfg.simpleFileVersioning;
+ delete folderCfg.simpleKeep;
+ } else if (folderCfg.fileVersioningSelector === "staggered") {
+ folderCfg.versioning = {
+ 'type': 'staggered',
+ 'params': {
+ 'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
+ 'cleanInterval': '' + folderCfg.staggeredCleanInterval,
+ 'versionsPath': '' + folderCfg.staggeredVersionsPath
+ },
+ 'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+ };
+ delete folderCfg.staggeredFileVersioning;
+ delete folderCfg.staggeredMaxAge;
+ delete folderCfg.staggeredCleanInterval;
+ delete folderCfg.staggeredVersionsPath;
+ } else if (folderCfg.fileVersioningSelector === "external") {
+ folderCfg.versioning = {
+ 'type': 'external',
+ 'params': {
+ 'command': '' + folderCfg.externalCommand
+ },
+ 'cleanupIntervalS': folderCfg.versioningCleanupIntervalS
+ };
+ delete folderCfg.externalFileVersioning;
+ delete folderCfg.externalCommand;
+ } else {
+ delete folderCfg.versioning;
+ }
+
+ var ignoresLoaded = !$scope.ignores.disabled;
+ var ignores = $scope.ignores.text.split('\n');
+ // Split always returns a minimum 1-length array even for no patterns
+ if (ignores.length === 1 && ignores[0] === "") {
+ ignores = [];
+ }
+ if (!$scope.editingExisting && ignores.length) {
+ folderCfg.paused = true;
+ };
+
+ $scope.folders[folderCfg.id] = folderCfg;
+ $scope.config.folders = folderList($scope.folders);
+
+ if (ignoresLoaded && $scope.editingExisting && ignores !== folderCfg.ignores) {
+ saveIgnores(ignores);
+ };
+
+ $scope.saveConfig(function () {
+ if (!$scope.editingExisting && ignores.length) {
+ saveIgnores(ignores, function () {
+ $scope.setFolderPause(folderCfg.id, false);
+ });
+ }
+ });
+ };
+
+ $scope.ignoreFolder = function (device, pendingFolder) {
+ pendingFolder = angular.copy(pendingFolder);
+ // Bump time
+ pendingFolder.time = (new Date()).toISOString();
+
+ if (id in $scope.devices) {
+ $scope.devices[id].ignoredFolders.push(pendingFolder);
+ $scope.saveConfig();
+ }
+ };
+
+ $scope.sharesFolder = function (folderCfg) {
+ var names = [];
+ folderCfg.devices.forEach(function (device) {
+ if (device.deviceID !== $scope.myID) {
+ names.push($scope.deviceName($scope.devices[device.deviceID]));
+ }
+ });
+ names.sort();
+ return names.join(", ");
+ };
+
+ $scope.deviceFolders = function (deviceCfg) {
+ var folders = [];
+ $scope.folderList().forEach(function (folder) {
+ for (var i = 0; i < folder.devices.length; i++) {
+ if (folder.devices[i].deviceID === deviceCfg.deviceID) {
+ folders.push(folder.id);
+ break;
+ }
+ }
+ });
+ return folders;
+ };
+
+ $scope.folderLabel = function (folderID) {
+ if (!$scope.folders[folderID]) {
+ return folderID;
+ }
+ var label = $scope.folders[folderID].label;
+ return label && label.length > 0 ? label : folderID;
+ };
+
+ $scope.deleteFolder = function (id) {
+ $('#editFolder').modal('hide');
+ if (!$scope.editingExisting) {
+ return;
+ }
+
+ delete $scope.folders[id];
+ delete $scope.model[id];
+ $scope.config.folders = folderList($scope.folders);
+ recalcLocalStateTotal();
+
+ $scope.saveConfig();
+ };
+
+ function resetRestoreVersions() {
+ $scope.restoreVersions = {
+ folder: null,
+ selections: {},
+ versions: null,
+ tree: null,
+ errors: null,
+ filters: {},
+ massAction: function (name, action) {
+ $.each($scope.restoreVersions.versions, function (key) {
+ if (key.indexOf(name + '/') == 0 && (!$scope.restoreVersions.filters.text || key.indexOf($scope.restoreVersions.filters.text) > -1)) {
+ if (action == 'unset') {
+ delete $scope.restoreVersions.selections[key];
+ return;
+ }
+
+ var availableVersions = [];
+ $.each($scope.restoreVersions.filterVersions($scope.restoreVersions.versions[key]), function (idx, version) {
+ availableVersions.push(version.versionTime);
+ })
+
+ if (availableVersions.length) {
+ availableVersions.sort(function (a, b) { return a - b; });
+ if (action == 'latest') {
+ $scope.restoreVersions.selections[key] = availableVersions.pop();
+ } else if (action == 'oldest') {
+ $scope.restoreVersions.selections[key] = availableVersions.shift();
+ }
+ }
+ }
+ });
+ },
+ filterVersions: function (versions) {
+ var filteredVersions = [];
+ $.each(versions, function (idx, version) {
+ if (moment(version.versionTime).isBetween($scope.restoreVersions.filters['start'], $scope.restoreVersions.filters['end'], null, '[]')) {
+ filteredVersions.push(version);
+ }
+ });
+ return filteredVersions;
+ },
+ selectionCount: function () {
+ var count = 0;
+ $.each($scope.restoreVersions.selections, function (key, value) {
+ if (value) {
+ count++;
+ }
+ });
+ return count;
+ },
+
+ restore: function () {
+ $scope.restoreVersions.tree.clear();
+ $scope.restoreVersions.tree = null;
+ $scope.restoreVersions.versions = null;
+ var selections = {};
+ $.each($scope.restoreVersions.selections, function (key, value) {
+ if (value) {
+ selections[key] = value;
+ }
+ });
+ $scope.restoreVersions.selections = {};
+
+ $http.post(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder), selections).success(function (data) {
+ if (Object.keys(data).length == 0) {
+ $('#restoreVersions').modal('hide');
+ } else {
+ $scope.restoreVersions.errors = data;
+ }
+ });
+ },
+ show: function (folder) {
+ $scope.restoreVersions.folder = folder;
+
+ var closed = false;
+ var modalShown = $q.defer();
+ $('#restoreVersions').modal().one('hidden.bs.modal', function () {
+ closed = true;
+ resetRestoreVersions();
+ }).one('shown.bs.modal', function () {
+ modalShown.resolve();
+ });
+
+ var dataReceived = $http.get(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder))
+ .success(function (data) {
+ $.each(data, function (key, values) {
+ $.each(values, function (idx, value) {
+ value.modTime = new Date(value.modTime);
+ value.versionTime = new Date(value.versionTime);
+ });
+ values.sort(function (a, b) {
+ return b.versionTime - a.versionTime;
+ });
+ });
+ if (closed) return;
+ $scope.restoreVersions.versions = data;
+ });
+
+ $q.all([dataReceived, modalShown.promise]).then(function () {
+ $timeout(function () {
+ if (closed) {
+ resetRestoreVersions();
+ return;
+ }
+
+ $scope.restoreVersions.tree = $("#restoreTree").fancytree({
+ extensions: ["table", "filter"],
+ quicksearch: true,
+ filter: {
+ autoApply: true,
+ counter: true,
+ hideExpandedCounter: true,
+ hideExpanders: true,
+ highlight: true,
+ leavesOnly: false,
+ nodata: true,
+ mode: "hide"
+ },
+ table: {
+ indentation: 20,
+ nodeColumnIdx: 0,
+ },
+ debugLevel: 2,
+ source: buildTree($scope.restoreVersions.versions),
+ renderColumns: function (event, data) {
+ var node = data.node,
+ $tdList = $(node.tr).find(">td"),
+ template;
+ if (node.folder) {
+ template = '
';
+ } else {
+ template = '
';
+ }
+
+ var scope = $rootScope.$new(true);
+ scope.key = node.key;
+ scope.restoreVersions = $scope.restoreVersions;
+
+ $tdList.eq(1).html(
+ $compile(template)(scope)
+ );
+
+ // Force angular to redraw.
+ $timeout(function () {
+ $scope.$apply();
+ });
+ }
+ }).fancytree("getTree");
+
+ var minDate = moment(),
+ maxDate = moment(0, 'X'),
+ date;
+
+ // Find version window.
+ $.each($scope.restoreVersions.versions, function (key) {
+ $.each($scope.restoreVersions.versions[key], function (idx, version) {
+ date = moment(version.versionTime);
+ if (date.isBefore(minDate)) {
+ minDate = date;
+ }
+ if (date.isAfter(maxDate)) {
+ maxDate = date;
+ }
+ });
+ });
+
+ $scope.restoreVersions.filters['start'] = minDate;
+ $scope.restoreVersions.filters['end'] = maxDate;
+
+ var ranges = {
+ 'All time': [minDate, maxDate],
+ 'Today': [moment(), moment()],
+ 'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
+ 'Last 7 Days': [moment().subtract(6, 'days'), moment()],
+ 'Last 30 Days': [moment().subtract(29, 'days'), moment()],
+ 'This Month': [moment().startOf('month'), moment().endOf('month')],
+ 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
+ };
+
+ // Filter out invalid ranges.
+ $.each(ranges, function (key, range) {
+ if (!range[0].isBetween(minDate, maxDate, null, '[]') && !range[1].isBetween(minDate, maxDate, null, '[]')) {
+ delete ranges[key];
+ }
+ });
+
+ $("#restoreVersionDateRange").daterangepicker({
+ timePicker: true,
+ timePicker24Hour: true,
+ timePickerSeconds: true,
+ autoUpdateInput: true,
+ opens: "left",
+ drops: "up",
+ startDate: minDate,
+ endDate: maxDate,
+ minDate: minDate,
+ maxDate: maxDate,
+ ranges: ranges,
+ locale: {
+ format: 'YYYY/MM/DD HH:mm:ss',
+ }
+ }).on('apply.daterangepicker', function (ev, picker) {
+ $scope.restoreVersions.filters['start'] = picker.startDate;
+ $scope.restoreVersions.filters['end'] = picker.endDate;
+ // Events for this UI element are not managed by angular.
+ // Force angular to wake up.
+ $timeout(function () {
+ $scope.$apply();
+ });
+ });
+ });
+ });
+ }
+ };
+ }
+ resetRestoreVersions();
+
+ $scope.$watchCollection('restoreVersions.filters', function () {
+ if (!$scope.restoreVersions.tree) return;
+
+ $scope.restoreVersions.tree.filterNodes(function (node) {
+ if (node.folder) return false;
+ if ($scope.restoreVersions.filters.text && node.key.indexOf($scope.restoreVersions.filters.text) < 0) {
+ return false;
+ }
+ if ($scope.restoreVersions.filterVersions(node.data.versions).length == 0) {
+ return false;
+ }
+ return true;
+ });
+ });
+
+ $scope.setAPIKey = function (cfg) {
+ $http.get(urlbase + '/svc/random/string?length=32').success(function (data) {
+ cfg.apiKey = data.random;
+ });
+ };
+
+ $scope.acceptUR = function () {
+ $scope.config.options.urAccepted = $scope.system.urVersionMax;
+ $scope.config.options.urSeen = $scope.system.urVersionMax;
+ $scope.saveConfig();
+ $('#ur').modal('hide');
+ };
+
+ $scope.declineUR = function () {
+ if ($scope.config.options.urAccepted === 0) {
+ $scope.config.options.urAccepted = -1;
+ }
+ $scope.config.options.urSeen = $scope.system.urVersionMax;
+ $scope.saveConfig();
+ $('#ur').modal('hide');
+ };
+
+ $scope.showNeed = function (folder) {
+ $scope.neededFolder = folder;
+ $scope.refreshNeed(1, 10);
+ $('#needed').modal().one('hidden.bs.modal', function () {
+ $scope.needed = undefined;
+ $scope.neededFolder = '';
+ });
+ };
+
+ $scope.showRemoteNeed = function (device) {
+ resetRemoteNeed();
+ $scope.remoteNeedDevice = device;
+ $scope.deviceFolders(device).forEach(function (folder) {
+ var comp = $scope.completion[device.deviceID][folder];
+ if (comp !== undefined && comp.needItems + comp.needDeletes === 0) {
+ return;
+ }
+ $scope.remoteNeedFolders.push(folder);
+ $scope.refreshRemoteNeed(folder, 1, 10);
+ });
+ $('#remoteNeed').modal().one('hidden.bs.modal', function () {
+ resetRemoteNeed();
+ });
+ };
+
+ $scope.showFailed = function (folder) {
+ $scope.failed.folder = folder;
+ $scope.failed = $scope.refreshFailed(1, 10);
+ $('#failed').modal().one('hidden.bs.modal', function () {
+ $scope.failed = {};
+ });
+ };
+
+ $scope.hasFailedFiles = function (folder) {
+ if (!$scope.model[folder]) {
+ return false;
+ }
+ return $scope.model[folder].errors !== 0;
+ };
+
+ $scope.override = function (folder) {
+ $http.post(urlbase + "/db/override?folder=" + encodeURIComponent(folder));
+ };
+
+ $scope.showLocalChanged = function (folder, folderType) {
+ $scope.localChangedFolder = folder;
+ $scope.localChangedType = folderType;
+ $scope.localChanged = $scope.refreshLocalChanged(1, 10);
+ $('#localChanged').modal().one('hidden.bs.modal', function () {
+ $scope.localChanged = {};
+ $scope.localChangedFolder = undefined;
+ $scope.localChangedType = undefined;
+ });
+ };
+
+ $scope.hasReceiveOnlyChanged = function (folderCfg) {
+ if (!folderCfg || folderCfg.type !== "receiveonly") {
+ return false;
+ }
+ var counts = $scope.model[folderCfg.id];
+ return counts && counts.receiveOnlyTotalItems > 0;
+ };
+
+ $scope.hasReceiveEncryptedItems = function (folderCfg) {
+ if (!folderCfg || folderCfg.type !== "receiveencrypted") {
+ return false;
+ }
+ return $scope.receiveEncryptedItemsCount(folderCfg) > 0;
+ };
+
+ $scope.receiveEncryptedItemsCount = function (folderCfg) {
+ var counts = $scope.model[folderCfg.id];
+ if (!counts) {
+ return 0;
+ }
+ return counts.receiveOnlyTotalItems - counts.receiveOnlyChangedDeletes;
+ }
+
+ $scope.revert = function (folder) {
+ $http.post(urlbase + "/db/revert?folder=" + encodeURIComponent(folder));
+ };
+
+ $scope.deleteEncryptionModal = function (folderID) {
+ $scope.revertEncryptionFolder = folderID;
+ $('#delete-encryption-confirmation').modal('show').one('hidden.bs.modal', function () {
+ $scope.revertEncryptionFolder = undefined;
+ });
+ };
+
+ $scope.advanced = function () {
+ $scope.advancedConfig = angular.copy($scope.config);
+ $('#advanced').modal('show');
+ };
+
+ $scope.showReportPreview = function () {
+ $scope.reportPreview = true;
+ };
+
+ $scope.refreshReportDataPreview = function (ver, diff) {
+ $scope.reportDataPreview = '';
+ if (!ver) {
+ return;
+ }
+ var version = parseInt(ver);
+ if (diff && version > 2) {
+ $q.all([
+ $http.get(urlbase + '/svc/report?version=' + version),
+ $http.get(urlbase + '/svc/report?version=' + (version - 1)),
+ ]).then(function (responses) {
+ var newReport = responses[0].data;
+ var oldReport = responses[1].data;
+ angular.forEach(oldReport, function (_, key) {
+ delete newReport[key];
+ });
+ $scope.reportDataPreview = newReport;
+ });
+ } else {
+ $http.get(urlbase + '/svc/report?version=' + version).success(function (data) {
+ $scope.reportDataPreview = data;
+ }).error($scope.emitHTTPError);
+ }
+ };
+
+ $scope.rescanAllFolders = function () {
+ $http.post(urlbase + "/db/scan");
+ };
+
+ $scope.rescanFolder = function (folder) {
+ $http.post(urlbase + "/db/scan?folder=" + encodeURIComponent(folder));
+ };
+
+ $scope.setAllFoldersPause = function (pause) {
+ var folderListCache = $scope.folderList();
+
+ for (var i = 0; i < folderListCache.length; i++) {
+ folderListCache[i].paused = pause;
+ }
+
+ $scope.config.folders = folderList(folderListCache);
+ $scope.saveConfig();
+ };
+
+ $scope.isAtleastOneFolderPausedStateSetTo = function (pause) {
+ var folderListCache = $scope.folderList();
+
+ for (var i = 0; i < folderListCache.length; i++) {
+ if (folderListCache[i].paused == pause) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ $scope.activateAllFsWatchers = function () {
+ var folders = $scope.folderList();
+
+ $.each(folders, function (i) {
+ if (folders[i].fsWatcherEnabled) {
+ return;
+ }
+ folders[i].fsWatcherEnabled = true;
+ if (folders[i].rescanIntervalS === 0) {
+ return;
+ }
+ // Delay full scans, but scan at least once per day
+ folders[i].rescanIntervalS *= 60;
+ if (folders[i].rescanIntervalS > 86400) {
+ folders[i].rescanIntervalS = 86400;
+ }
+ });
+
+ $scope.config.folders = folders;
+ $scope.saveConfig();
+ };
+
+ $scope.bumpFile = function (folder, file) {
+ var url = urlbase + "/db/prio?folder=" + encodeURIComponent(folder) + "&file=" + encodeURIComponent(file);
+ // In order to get the right view of data in the response.
+ url += "&page=" + $scope.needed.page;
+ url += "&perpage=" + $scope.needed.perpage;
+ $http.post(url).success(function (data) {
+ if ($scope.neededFolder === folder) {
+ console.log("bumpFile", folder, data);
+ parseNeeded(data);
+ }
+ }).error($scope.emitHTTPError);
+ };
+
+ $scope.versionString = function () {
+ if (!$scope.version.version) {
+ return '';
+ }
+
+ var os = {
+ 'darwin': 'macOS',
+ 'dragonfly': 'DragonFly BSD',
+ 'freebsd': 'FreeBSD',
+ 'openbsd': 'OpenBSD',
+ 'netbsd': 'NetBSD',
+ 'linux': 'Linux',
+ 'windows': 'Windows',
+ 'solaris': 'Solaris'
+ }[$scope.version.os] || $scope.version.os;
+
+ var arch = {
+ '386': '32 bit',
+ 'amd64': '64 bit',
+ 'arm': 'ARM',
+ 'arm64': 'AArch64',
+ 'ppc64': 'PowerPC',
+ 'ppc64le': 'PowerPC (LE)'
+ }[$scope.version.arch] || $scope.version.arch;
+
+ return $scope.version.version + ', ' + os + ' (' + arch + ')';
+ };
+
+ $scope.inputTypeFor = function (key, value) {
+ if (key.substr(0, 1) === '_') {
+ return 'skip';
+ }
+ if (value === null) {
+ return 'null';
+ }
+ if (typeof value === 'number') {
+ return 'number';
+ }
+ if (typeof value === 'boolean') {
+ return 'checkbox';
+ }
+ if (value instanceof Array) {
+ return 'list';
+ }
+ if (typeof value === 'object') {
+ return 'skip';
+ }
+ return 'text';
+ };
+
+ $scope.themeName = function (theme) {
+ return theme.replace('-', ' ').replace(/(?:^|\s)\S/g, function (a) {
+ return a.toUpperCase();
+ });
+ };
+
+ $scope.modalLoaded = function () {
+ // once all modal elements have been processed
+ if ($('modal').length === 0) {
+ // pseudo main. called on all definitions assigned
+ initController();
+ }
+ };
+
+ $scope.toggleUnits = function () {
+ $scope.metricRates = !$scope.metricRates;
+ try {
+ window.localStorage["metricRates"] = $scope.metricRates;
+ } catch (exception) { }
+ };
+
+ $scope.sizeOf = function (dict) {
+ if (dict === undefined) {
+ return 0;
+ }
+ return Object.keys(dict).length;
+ };
+
+ $scope.dismissNotification = function (id) {
+ var idx = $scope.config.options.unackedNotificationIDs.indexOf(id);
+ if (idx > -1) {
+ $scope.config.options.unackedNotificationIDs.splice(idx, 1);
+ $scope.saveConfig();
+ }
+ };
+
+ $scope.abbreviatedError = function (addr) {
+ var status = $scope.system.lastDialStatus[addr];
+ if (!status || !status.error) {
+ return null;
+ }
+ var time = $filter('date')(status.when, "HH:mm:ss")
+ var err = status.error.replace(/.+: /, '');
+ return err + " (" + time + ")";
+ }
+
+ $scope.setCrashReportingEnabled = function (enabled) {
+ $scope.config.options.crashReportingEnabled = enabled;
+ $scope.saveConfig();
+ };
+
+ $scope.isUnixAddress = function (address) {
+ return address != null &&
+ (address.indexOf('/') == 0 ||
+ address.indexOf('unix://') == 0 ||
+ address.indexOf('unixs://') == 0);
+ }
+
+ })
+ .directive('shareTemplate', function () {
+ return {
+ templateUrl: 'syncthing/core/editShareTemplate.html',
+ scope: {
+ selected: '=',
+ encryptionPasswords: '=',
+ id: '@',
+ label: '@',
+ folderType: '@',
+ },
+ link: function(scope, elem, attrs) {
+ scope.untrusted = attrs.untrusted === 'true';
+ var plain = false;
+ scope.togglePasswordVisibility = function() {
+ scope.plain = !scope.plain;
+ };
+ },
+ }
+ });
diff --git a/gui/default/untrusted/syncthing/device/editDeviceModalView.html b/gui/default/untrusted/syncthing/device/editDeviceModalView.html
new file mode 100644
index 000000000..becac15a0
--- /dev/null
+++ b/gui/default/untrusted/syncthing/device/editDeviceModalView.html
@@ -0,0 +1,166 @@
+
+
+
+
diff --git a/gui/default/untrusted/syncthing/folder/deleteEncryptionFolderDialogView.html b/gui/default/untrusted/syncthing/folder/deleteEncryptionFolderDialogView.html
new file mode 100644
index 000000000..4697ff7a1
--- /dev/null
+++ b/gui/default/untrusted/syncthing/folder/deleteEncryptionFolderDialogView.html
@@ -0,0 +1,19 @@
+
+
+
+ Unexpected items have been found in this folder.
+ You should never add or change anything locally in a "{%receiveEncrypted%}" folder.
+
+
+ Are you sure you want to permanently delete all these files?
+
+
+
+
diff --git a/gui/default/untrusted/syncthing/folder/editFolderModalView.html b/gui/default/untrusted/syncthing/folder/editFolderModalView.html
new file mode 100644
index 000000000..6bde3c58f
--- /dev/null
+++ b/gui/default/untrusted/syncthing/folder/editFolderModalView.html
@@ -0,0 +1,278 @@
+
+
+
+
diff --git a/gui/default/untrusted/syncthing/transfer/localChangedFilesModalView.html b/gui/default/untrusted/syncthing/transfer/localChangedFilesModalView.html
new file mode 100644
index 000000000..2ca483163
--- /dev/null
+++ b/gui/default/untrusted/syncthing/transfer/localChangedFilesModalView.html
@@ -0,0 +1,35 @@
+
+
+
+ The following items were changed locally.
+
+
+ The following unexpected items were found.
+ You should never add or change anything locally in a "{%receiveEncrypted%}" folder.
+
+
+
+
+ Path
+ Size
+
+
+
+ {{file.name}}
+ {{file.size | binary}}B
+
+
+
+
+
+
+
+
diff --git a/lib/api/api.go b/lib/api/api.go
index 97a604802..e857344e7 100644
--- a/lib/api/api.go
+++ b/lib/api/api.go
@@ -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
}
diff --git a/lib/api/api_statics.go b/lib/api/api_statics.go
index ce8fe2c62..27684e79c 100644
--- a/lib/api/api_statics.go
+++ b/lib/api/api_statics.go
@@ -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)
- return
- }
+ 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 {
- // 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 {
- mtype := assets.MimeTypeForFile(file)
- if len(mtype) != 0 {
- w.Header().Set("Content-Type", mtype)
- }
- http.ServeFile(w, r, p)
- return
- }
- }
-
- // Check for a compiled in default asset.
- as, ok = s.assets[config.DefaultTheme+"/"+file]
- if !ok {
- http.NotFound(w, r)
- return
- }
+ 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 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 true
+}
+
+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 {
+ 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)
}
diff --git a/lib/api/mocked_config_test.go b/lib/api/mocked_config_test.go
index 9ff426ae8..2e23b86f1 100644
--- a/lib/api/mocked_config_test.go
+++ b/lib/api/mocked_config_test.go
@@ -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
}
diff --git a/lib/api/mocked_model_test.go b/lib/api/mocked_model_test.go
index 735e0049f..e9cf92001 100644
--- a/lib/api/mocked_model_test.go
+++ b/lib/api/mocked_model_test.go
@@ -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
}
diff --git a/lib/config/config.go b/lib/config/config.go
index dd7a26175..e6efd2f79 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -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) {
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index 3d73912e4..fd667ab31 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -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")
diff --git a/lib/config/deviceconfiguration.pb.go b/lib/config/deviceconfiguration.pb.go
index 6203e9447..ae6c1b546 100644
--- a/lib/config/deviceconfiguration.pb.go
+++ b/lib/config/deviceconfiguration.pb.go
@@ -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:])
diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go
index d92dd67f8..25f840a7e 100644
--- a/lib/config/folderconfiguration.go
+++ b/lib/config/folderconfiguration.go
@@ -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 {
diff --git a/lib/config/folderconfiguration.pb.go b/lib/config/folderconfiguration.pb.go
index ce0716ed0..72cd43d0a 100644
--- a/lib/config/folderconfiguration.pb.go
+++ b/lib/config/folderconfiguration.pb.go
@@ -27,8 +27,9 @@ var _ = math.Inf
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"`
+ 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:])
diff --git a/lib/config/foldertype.go b/lib/config/foldertype.go
index f82e7fba9..4f58f85b8 100644
--- a/lib/config/foldertype.go
+++ b/lib/config/foldertype.go
@@ -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
}
diff --git a/lib/config/foldertype.pb.go b/lib/config/foldertype.pb.go
index dedd2b12c..e65848464 100644
--- a/lib/config/foldertype.pb.go
+++ b/lib/config/foldertype.pb.go
@@ -24,21 +24,24 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type FolderType int32
const (
- FolderTypeSendReceive FolderType = 0
- FolderTypeSendOnly FolderType = 1
- FolderTypeReceiveOnly FolderType = 2
+ 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_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,
}
diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go
index 7f684644a..9aaf9f13b 100644
--- a/lib/config/optionsconfiguration.go
+++ b/lib/config/optionsconfiguration.go
@@ -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
+}
diff --git a/lib/config/optionsconfiguration.pb.go b/lib/config/optionsconfiguration.pb.go
index b4aef27bd..9224c6aec 100644
--- a/lib/config/optionsconfiguration.pb.go
+++ b/lib/config/optionsconfiguration.pb.go
@@ -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)
diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml
index 555f813d4..ffaaca366 100644
--- a/lib/config/testdata/overridenvalues.xml
+++ b/lib/config/testdata/overridenvalues.xml
@@ -45,5 +45,6 @@
foo
asdfasdf
false
+ feature
diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go
index 8d0620f25..87782ffa5 100644
--- a/lib/config/wrapper.go
+++ b/lib/config/wrapper.go
@@ -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()
diff --git a/lib/connections/service.go b/lib/connections/service.go
index 11a5d43bf..48c495c4d 100644
--- a/lib/connections/service.go
+++ b/lib/connections/service.go
@@ -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)
diff --git a/lib/db/structs.pb.go b/lib/db/structs.pb.go
index 2b34e673b..40e9f8a27 100644
--- a/lib/db/structs.pb.go
+++ b/lib/db/structs.pb.go
@@ -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)
diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go
index 4022e4df0..809b0eae8 100644
--- a/lib/model/fakeconns_test.go
+++ b/lib/model/fakeconns_test.go
@@ -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 {
diff --git a/lib/model/folder.go b/lib/model/folder.go
index e5219febd..7c9874a1b 100644
--- a/lib/model/folder.go
+++ b/lib/model/folder.go
@@ -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)
}
- }
-
- // Schedule a pull after scanning, but only if we actually detected any
- // changes.
- changes := 0
- defer func() {
- if changes > 0 {
- f.SchedulePull()
+ 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)
+ }
+ }
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)
diff --git a/lib/model/folder_recvenc.go b/lib/model/folder_recvenc.go
new file mode 100644
index 000000000..d26ad4d55
--- /dev/null
+++ b/lib/model/folder_recvenc.go
@@ -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))
+ }
+ }
+}
diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go
index d42aa7fdc..788e47384 100644
--- a/lib/model/folder_sendrecv.go
+++ b/lib/model/folder_sendrecv.go
@@ -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
}
- scanChan <- parent
+ if f.Type != config.FolderTypeReceiveEncrypted {
+ scanChan <- parent
+ }
return true
}
@@ -1248,38 +1254,11 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
continue
}
- 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 f.Type != config.FolderTypeReceiveEncrypted {
+ f.model.progressEmitter.Register(state.sharedPullerState)
}
- 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,25 +1284,28 @@ 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 {
- return true
- }
+ 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
+ }
- err = f.limitedWriteAt(dstFd, buf, block.Offset)
+ err = f.limitedWriteAt(dstFd, buf, block.Offset)
+ if err != nil {
+ state.fail(errors.Wrap(err, "dst write"))
+ }
+ if offset == block.Offset {
+ state.copiedFromOrigin()
+ } else {
+ state.copiedFromOriginShifted()
+ }
+
+ return false
+ })
if err != nil {
- state.fail(errors.Wrap(err, "dst write"))
+ l.Debugln("weak hasher iter", err)
}
- if offset == block.Offset {
- state.copiedFromOrigin()
- } else {
- state.copiedFromOriginShifted()
- }
-
- return false
- })
- if err != nil {
- l.Debugln("weak hasher iter", err)
}
if !found {
@@ -1341,9 +1323,14 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
return false
}
- if err := verifyBuffer(buf, block); err != nil {
- l.Debugln("Finder failed to verify buffer", err)
- return false
+ // 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 {
@@ -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()
}
- f.model.progressEmitter.Deregister(state)
+ if f.Type != config.FolderTypeReceiveEncrypted {
+ f.model.progressEmitter.Deregister(state)
+ }
f.evLogger.Log(events.ItemFinished, map[string]interface{}{
"folder": f.folderID,
diff --git a/lib/model/folder_summary.go b/lib/model/folder_summary.go
index b87e8eb16..7bd3b2f00 100644
--- a/lib/model/folder_summary.go
+++ b/lib/model/folder_summary.go
@@ -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
diff --git a/lib/model/indexsender.go b/lib/model/indexsender.go
index d0ce79bb6..58a1c6954 100644
--- a/lib/model/indexsender.go
+++ b/lib/model/indexsender.go
@@ -23,15 +23,17 @@ import (
type indexSender struct {
suture.Service
- conn protocol.Connection
- folder string
- fset *db.FileSet
- prevSequence int64
- evLogger events.Logger
- connClosed chan struct{}
- token suture.ServiceToken
- pauseChan chan struct{}
- resumeChan chan *db.FileSet
+ conn protocol.Connection
+ folder string
+ folderIsReceiveEncrypted bool
+ dev string
+ fset *db.FileSet
+ prevSequence int64
+ evLogger events.Logger
+ connClosed chan struct{}
+ token suture.ServiceToken
+ pauseChan chan struct{}
+ resumeChan chan *db.FileSet
}
func (s *indexSender) serve(ctx context.Context) {
@@ -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
diff --git a/lib/model/model.go b/lib/model/model.go
index 789d9b7ba..292d1b2b4 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -11,6 +11,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "io"
"net"
"path/filepath"
"reflect"
@@ -131,15 +132,17 @@ type model struct {
folderIOLimiter *byteSemaphore
// fields protected by fmut
- fmut sync.RWMutex
- folderCfgs map[string]config.FolderConfiguration // folder -> cfg
- folderFiles map[string]*db.FileSet // folder -> files
- deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
- folderIgnores map[string]*ignore.Matcher // folder -> matcher object
- folderRunners map[string]service // folder -> puller or scanner
- 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)
+ fmut sync.RWMutex
+ folderCfgs map[string]config.FolderConfiguration // folder -> cfg
+ folderFiles map[string]*db.FileSet // folder -> files
+ deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
+ folderIgnores map[string]*ignore.Matcher // folder -> matcher object
+ folderRunners map[string]service // folder -> puller or scanner
+ 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
@@ -171,11 +174,18 @@ var (
errNetworkNotAllowed = errors.New("network not allowed")
errNoVersioner = errors.New("folder has no versioner")
// errors about why a connection is closed
- errIgnoredFolderRemoved = errors.New("folder no longer ignored")
- errReplacingConnection = errors.New("replacing connection")
- errStopped = errors.New("Syncthing is being stopped")
- errMissingRemoteInClusterConfig = errors.New("remote device missing in cluster config")
- errMissingLocalInClusterConfig = errors.New("local device missing in cluster config")
+ 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")
)
// NewModel creates and starts a new model. The model starts in read-only mode,
@@ -207,14 +217,16 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
folderIOLimiter: newByteSemaphore(cfg.Options().MaxFolderConcurrency()),
// fields protected by fmut
- fmut: sync.NewRWMutex(),
- folderCfgs: make(map[string]config.FolderConfiguration),
- folderFiles: make(map[string]*db.FileSet),
- deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
- folderIgnores: make(map[string]*ignore.Matcher),
- folderRunners: make(map[string]service),
- folderRunnerToken: make(map[string]suture.ServiceToken),
- folderVersioners: make(map[string]versioner.Versioner),
+ fmut: sync.NewRWMutex(),
+ folderCfgs: make(map[string]config.FolderConfiguration),
+ folderFiles: make(map[string]*db.FileSet),
+ deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
+ folderIgnores: make(map[string]*ignore.Matcher),
+ 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
- }
- 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()
+ changedHere, tempIndexFolders, paused, err := m.ccHandleFolders(cm.Folders, deviceCfg, ccDeviceInfos, indexSenderRegistry)
+ if err != nil {
+ return err
}
-
- 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,
+ })
+}
diff --git a/lib/model/model_test.go b/lib/model/model_test.go
index 8458d6a20..98097ce10 100644
--- a/lib/model/model_test.go
+++ b/lib/model/model_test.go
@@ -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
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index bdbdc0f77..fcb5b32a7 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -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")
}
diff --git a/lib/model/sharedpullerstate.go b/lib/model/sharedpullerstate.go
index 30ebe2eca..95d737db0 100644
--- a/lib/model/sharedpullerstate.go
+++ b/lib/model/sharedpullerstate.go
@@ -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()
diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go
index 7980866f5..3cd6eede5 100644
--- a/lib/model/testutils_test.go
+++ b/lib/model/testutils_test.go
@@ -49,6 +49,7 @@ func init() {
defaultCfg = defaultCfgWrapper.RawCopy()
defaultAutoAcceptCfg = config.Configuration{
+ Version: config.CurrentVersion,
Devices: []config.DeviceConfiguration{
{
DeviceID: myID, // self
diff --git a/lib/protocol/benchmark_test.go b/lib/protocol/benchmark_test.go
index bfa9b0830..19b2a9581 100644
--- a/lib/protocol/benchmark_test.go
+++ b/lib/protocol/benchmark_test.go
@@ -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.
diff --git a/lib/protocol/bep.pb.go b/lib/protocol/bep.pb.go
index 8cc5a4699..4e979af17 100644
--- a/lib/protocol/bep.pb.go
+++ b/lib/protocol/bep.pb.go
@@ -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:])
diff --git a/lib/protocol/common_test.go b/lib/protocol/common_test.go
index a8b10555d..29a5b1d29 100644
--- a/lib/protocol/common_test.go
+++ b/lib/protocol/common_test.go
@@ -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
diff --git a/lib/protocol/encryption.go b/lib/protocol/encryption.go
new file mode 100644
index 000000000..ca5e4fce3
--- /dev/null
+++ b/lib/protocol/encryption.go
@@ -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
+}
diff --git a/lib/protocol/encryption_test.go b/lib/protocol/encryption_test.go
new file mode 100644
index 000000000..5da7739b0
--- /dev/null
+++ b/lib/protocol/encryption_test.go
@@ -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")
+ }
+}
diff --git a/lib/protocol/nativemodel_darwin.go b/lib/protocol/nativemodel_darwin.go
index 17a50aced..68b6ba985 100644
--- a/lib/protocol/nativemodel_darwin.go
+++ b/lib/protocol/nativemodel_darwin.go
@@ -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)
}
diff --git a/lib/protocol/nativemodel_windows.go b/lib/protocol/nativemodel_windows.go
index eb098b0f7..00eab9b3f 100644
--- a/lib/protocol/nativemodel_windows.go
+++ b/lib/protocol/nativemodel_windows.go
@@ -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 {
diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go
index d53dbf071..1ed484ecc 100644
--- a/lib/protocol/protocol.go
+++ b/lib/protocol/protocol.go
@@ -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,
diff --git a/lib/protocol/protocol_test.go b/lib/protocol/protocol_test.go
index bac610ac0..398ac8987 100644
--- a/lib/protocol/protocol_test.go
+++ b/lib/protocol/protocol_test.go
@@ -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
+ }
}
}
diff --git a/lib/protocol/wireformat.go b/lib/protocol/wireformat.go
index e32d0b0d0..cca53cab1 100644
--- a/lib/protocol/wireformat.go
+++ b/lib/protocol/wireformat.go
@@ -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)
}
diff --git a/lib/scanner/walk.go b/lib/scanner/walk.go
index b5d94410a..5bd2826da 100644
--- a/lib/scanner/walk.go
+++ b/lib/scanner/walk.go
@@ -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 := ""
diff --git a/lib/ur/contract/contract.go b/lib/ur/contract/contract.go
index bdc9dac16..28387f27c 100644
--- a/lib/ur/contract/contract.go
+++ b/lib/ur/contract/contract.go
@@ -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"`
diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go
index 6b3d5f490..5f4de3216 100644
--- a/lib/ur/usage_report.go
+++ b/lib/ur/usage_report.go
@@ -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 {
diff --git a/proto/lib/config/deviceconfiguration.proto b/proto/lib/config/deviceconfiguration.proto
index 3494a413d..319ab81eb 100644
--- a/proto/lib/config/deviceconfiguration.proto
+++ b/proto/lib/config/deviceconfiguration.proto
@@ -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;
}
diff --git a/proto/lib/config/folderconfiguration.proto b/proto/lib/config/folderconfiguration.proto
index 0b7093060..afc79e34b 100644
--- a/proto/lib/config/folderconfiguration.proto
+++ b/proto/lib/config/folderconfiguration.proto
@@ -14,8 +14,9 @@ import "lib/fs/copyrangemethod.proto";
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];
+ 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 {
diff --git a/proto/lib/config/foldertype.proto b/proto/lib/config/foldertype.proto
index 69ff2a432..18f19b020 100644
--- a/proto/lib/config/foldertype.proto
+++ b/proto/lib/config/foldertype.proto
@@ -7,7 +7,8 @@ import "repos/protobuf/gogoproto/gogo.proto";
enum FolderType {
option (gogoproto.goproto_enum_stringer) = false;
- FOLDER_TYPE_SEND_RECEIVE = 0;
- FOLDER_TYPE_SEND_ONLY = 1;
- FOLDER_TYPE_RECEIVE_ONLY = 2;
+ FOLDER_TYPE_SEND_RECEIVE = 0;
+ FOLDER_TYPE_SEND_ONLY = 1;
+ FOLDER_TYPE_RECEIVE_ONLY = 2;
+ FOLDER_TYPE_RECEIVE_ENCRYPTED = 3;
}
diff --git a/proto/lib/config/optionsconfiguration.proto b/proto/lib/config/optionsconfiguration.proto
index 8c443ce62..9ad017bff 100644
--- a/proto/lib/config/optionsconfiguration.proto
+++ b/proto/lib/config/optionsconfiguration.proto
@@ -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
diff --git a/proto/lib/db/structs.proto b/proto/lib/db/structs.proto
index c197d20c9..10e6fef79 100644
--- a/proto/lib/db/structs.proto
+++ b/proto/lib/db/structs.proto
@@ -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;
diff --git a/proto/lib/protocol/bep.proto b/proto/lib/protocol/bep.proto
index d470c7d7c..b12c94914 100644
--- a/proto/lib/protocol/bep.proto
+++ b/proto/lib/protocol/bep.proto
@@ -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