aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Borg <jakob@kastelo.net>2023-09-06 12:52:01 +0200
committerGitHub <noreply@github.com>2023-09-06 12:52:01 +0200
commitc6334e61aa56575db6b274d45d55f7ada4dcd0fe (patch)
tree1267b2b1e67c09d80843b45b88049892dbedef7f
parent38bbdebffa74df9e5937749a3780d39b24adc331 (diff)
downloadsyncthing-c6334e61aa56575db6b274d45d55f7ada4dcd0fe.tar.gz
syncthing-c6334e61aa56575db6b274d45d55f7ada4dcd0fe.zip
all: Support multiple device connections (fixes #141) (#8918)
This adds the ability to have multiple concurrent connections to a single device. This is primarily useful when the network has multiple physical links for aggregated bandwidth. A single connection will never see a higher rate than a single link can give, but multiple connections are load-balanced over multiple links. It is also incidentally useful for older multi-core CPUs, where bandwidth could be limited by the TLS performance of a single CPU core -- using multiple connections achieves concurrency in the required crypto calculations... Co-authored-by: Simon Frei <freisim93@gmail.com> Co-authored-by: tomasz1986 <twilczynski@naver.com> Co-authored-by: bt90 <btom1990@googlemail.com>
-rw-r--r--cmd/stdiscosrv/database.go10
-rw-r--r--cmd/stdiscosrv/database_test.go2
-rw-r--r--gui/default/index.html7
-rw-r--r--gui/default/syncthing/device/editDeviceModalView.html20
-rw-r--r--lib/api/api.go10
-rw-r--r--lib/config/config.go8
-rw-r--r--lib/config/deviceconfiguration.go13
-rw-r--r--lib/config/deviceconfiguration.pb.go166
-rw-r--r--lib/config/wrapper.go5
-rw-r--r--lib/connections/quic_dial.go4
-rw-r--r--lib/connections/quic_listen.go4
-rw-r--r--lib/connections/registry/registry.go5
-rw-r--r--lib/connections/service.go268
-rw-r--r--lib/connections/structs.go19
-rw-r--r--lib/connections/tcp_dial.go4
-rw-r--r--lib/model/fakeconns_test.go1
-rw-r--r--lib/model/folder_recvonly_test.go2
-rw-r--r--lib/model/folder_sendrecv.go2
-rw-r--r--lib/model/mocks/model.go240
-rw-r--r--lib/model/model.go569
-rw-r--r--lib/model/model_test.go26
-rw-r--r--lib/model/requests_test.go4
-rw-r--r--lib/model/testutils_test.go8
-rw-r--r--lib/protocol/bep.pb.go502
-rw-r--r--lib/protocol/encryption.go4
-rw-r--r--lib/protocol/hello.go15
-rw-r--r--lib/protocol/hello_test.go6
-rw-r--r--lib/protocol/mocked_connection_info_test.go65
-rw-r--r--lib/protocol/mocks/connection.go65
-rw-r--r--lib/protocol/mocks/connection_info.go65
-rw-r--r--lib/protocol/nativemodel_darwin.go10
-rw-r--r--lib/protocol/nativemodel_unix.go2
-rw-r--r--lib/protocol/nativemodel_windows.go10
-rw-r--r--lib/protocol/protocol.go26
-rw-r--r--lib/sliceutil/sliceutil.go16
-rw-r--r--lib/sliceutil/sliceutil_test.go28
-rw-r--r--lib/syncthing/syncthing.go2
-rw-r--r--proto/lib/config/deviceconfiguration.proto3
-rw-r--r--proto/lib/protocol/bep.proto11
-rw-r--r--test/h1/config.xml157
-rw-r--r--test/h2/config.xml183
41 files changed, 1637 insertions, 930 deletions
diff --git a/cmd/stdiscosrv/database.go b/cmd/stdiscosrv/database.go
index ce151da3c..92ed45db6 100644
--- a/cmd/stdiscosrv/database.go
+++ b/cmd/stdiscosrv/database.go
@@ -15,6 +15,7 @@ import (
"sort"
"time"
+ "github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
@@ -352,14 +353,7 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
i := 0
for i < len(addrs) {
if addrs[i].Expires < now {
- // This item is expired. Replace it with the last in the list
- // (noop if we are at the last item).
- addrs[i] = addrs[len(addrs)-1]
- // Wipe the last item of the list to release references to
- // strings and stuff.
- addrs[len(addrs)-1] = DatabaseAddress{}
- // Shorten the slice.
- addrs = addrs[:len(addrs)-1]
+ addrs = sliceutil.RemoveAndZero(addrs, i)
continue
}
i++
diff --git a/cmd/stdiscosrv/database_test.go b/cmd/stdiscosrv/database_test.go
index 2596b6b17..14b15059d 100644
--- a/cmd/stdiscosrv/database_test.go
+++ b/cmd/stdiscosrv/database_test.go
@@ -185,7 +185,7 @@ func TestFilter(t *testing.T) {
},
{
a: []DatabaseAddress{{Address: "a", Expires: 5}, {Address: "b", Expires: 15}, {Address: "c", Expires: 5}, {Address: "d", Expires: 15}, {Address: "e", Expires: 5}},
- b: []DatabaseAddress{{Address: "d", Expires: 15}, {Address: "b", Expires: 15}}, // gets reordered
+ b: []DatabaseAddress{{Address: "b", Expires: 15}, {Address: "d", Expires: 15}},
},
}
diff --git a/gui/default/index.html b/gui/default/index.html
index e9691a02b..5b151cb00 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -871,6 +871,13 @@
</span>
</td>
</tr>
+ <tr ng-if="connections[deviceCfg.deviceID].connected">
+ <th><span class="fas fa-fw fa-random"></span>&nbsp;<span translate>Number of Connections</span></th>
+ <td class="text-right">
+ <span ng-if="connections[deviceCfg.deviceID].secondary.length">1 + {{connections[deviceCfg.deviceID].secondary.length | alwaysNumber}}</span>
+ <span ng-if="!connections[deviceCfg.deviceID].secondary.length">1</span>
+ </td>
+ </tr>
<tr ng-if="deviceCfg.allowedNetworks.length > 0">
<th><span class="fas fa-fw fa-filter"></span>&nbsp;<span translate>Allowed Networks</span></th>
<td class="text-right">
diff --git a/gui/default/syncthing/device/editDeviceModalView.html b/gui/default/syncthing/device/editDeviceModalView.html
index 39d037e04..160cb0f21 100644
--- a/gui/default/syncthing/device/editDeviceModalView.html
+++ b/gui/default/syncthing/device/editDeviceModalView.html
@@ -137,11 +137,22 @@
</div>
</div>
</div>
- <div class="row form-group">
- <div class="col-md-12">
+ <div class="row">
+ <div class="col-md-6" ng-class="{'has-error': deviceEditor.numConnections.$invalid && deviceEditor.numConnections.$dirty}">
+ <label translate>Connection Management</label>
+ <div class="row">
+ <span class="col-md-8" translate>Number of Connections</span>
+ <div class="col-md-4">
+ <input name="numConnections" id="numConnections" class="form-control" type="number" pattern="\d+" ng-model="currentDevice.numConnections" min="0" />
+ </div>
+ </div>
+ <p class="help-block" ng-if="!deviceEditor.numConnections.$valid && deviceEditor.numConnections.$dirty" translate>The number of connections must be a non-negative number.</p>
+ <p class="help-block" ng-if="deviceEditor.numConnections.$valid || deviceEditor.numConnections.$pristine" translate>When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.</p>
+ </div>
+ <div class="col-md-6 form-group">
<label translate>Device rate limits</label>
<div class="row">
- <div class="col-md-6" ng-class="{'has-error': deviceEditor.maxRecvKbps.$invalid && deviceEditor.maxRecvKbps.$dirty}">
+ <div class="col-md-12" ng-class="{'has-error': deviceEditor.maxRecvKbps.$invalid && deviceEditor.maxRecvKbps.$dirty}">
<div class="row">
<span class="col-md-8" translate>Incoming Rate Limit (KiB/s)</span>
<div class="col-md-4">
@@ -150,7 +161,7 @@
</div>
<p class="help-block" ng-if="!deviceEditor.maxRecvKbps.$valid && deviceEditor.maxRecvKbps.$dirty" translate>The rate limit must be a non-negative number (0: no limit)</p>
</div>
- <div class="col-md-6" ng-class="{'has-error': deviceEditor.maxSendKbps.$invalid && deviceEditor.maxSendKbps.$dirty}">
+ <div class="col-md-12" ng-class="{'has-error': deviceEditor.maxSendKbps.$invalid && deviceEditor.maxSendKbps.$dirty}">
<div class="row">
<span class="col-md-8" translate>Outgoing Rate Limit (KiB/s)</span>
<div class="col-md-4">
@@ -158,6 +169,7 @@
</div>
</div>
<p class="help-block" ng-if="!deviceEditor.maxSendKbps.$valid && deviceEditor.maxSendKbps.$dirty" translate>The rate limit must be a non-negative number (0: no limit)</p>
+ <p class="help-block" ng-if="(deviceEditor.maxSendKbps.$valid || deviceEditor.maxSendKbps.$pristine) && (deviceEditor.maxRecvKbps.$valid || deviceEditor.maxRecvKbps.$pristine)">The rate limit is applied to the accumulated traffic of all connections to this device.</p>
</div>
</div>
</div>
diff --git a/lib/api/api.go b/lib/api/api.go
index 363c6a162..67ebf0fce 100644
--- a/lib/api/api.go
+++ b/lib/api/api.go
@@ -1230,6 +1230,14 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
promhttp.Handler().ServeHTTP(wr, &http.Request{Method: http.MethodGet})
files = append(files, fileEntry{name: "metrics.txt", data: buf.Bytes()})
+ // Connection data as JSON
+ connStats := s.model.ConnectionStats()
+ if connStatsJSON, err := json.MarshalIndent(connStats, "", " "); err != nil {
+ l.Warnln("Support bundle: failed to serialize connection-stats.json.txt", err)
+ } else {
+ files = append(files, fileEntry{name: "connection-stats.json.txt", data: connStatsJSON})
+ }
+
// Heap and CPU Proofs as a pprof extension
var heapBuffer, cpuBuffer bytes.Buffer
filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, build.Version, time.Now().Format("150405")) // hhmmss
@@ -1607,7 +1615,7 @@ func (s *service) getPeerCompletion(w http.ResponseWriter, _ *http.Request) {
for _, folder := range s.cfg.Folders() {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
- if _, ok := s.model.Connection(device); ok {
+ if s.model.ConnectedTo(device) {
comp, err := s.model.Completion(device, folder.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/lib/config/config.go b/lib/config/config.go
index 4f02085fc..62f2b278b 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -25,6 +25,7 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/netutil"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syncthing/syncthing/lib/structutil"
)
@@ -564,8 +565,7 @@ func ensureNoUntrustedTrustingSharing(f *FolderConfiguration, devices []FolderDe
}
if devCfg := existingDevices[dev.DeviceID]; devCfg.Untrusted {
l.Warnf("Folder %s (%s) is shared in trusted mode with untrusted device %s (%s); unsharing.", f.ID, f.Label, dev.DeviceID.Short(), devCfg.Name)
- copy(devices[i:], devices[i+1:])
- devices = devices[:len(devices)-1]
+ devices = sliceutil.RemoveAndZero(devices, i)
i--
}
}
@@ -601,9 +601,7 @@ func filterURLSchemePrefix(addrs []string, prefix string) []string {
continue
}
if strings.HasPrefix(uri.Scheme, prefix) {
- // Remove this entry
- copy(addrs[i:], addrs[i+1:])
- addrs = addrs[:len(addrs)-1]
+ addrs = sliceutil.RemoveAndZero(addrs, i)
i--
}
}
diff --git a/lib/config/deviceconfiguration.go b/lib/config/deviceconfiguration.go
index 54a30affc..8635bcc39 100644
--- a/lib/config/deviceconfiguration.go
+++ b/lib/config/deviceconfiguration.go
@@ -11,6 +11,8 @@ import (
"sort"
)
+const defaultNumConnections = 1 // number of connections to use by default; may change in the future.
+
func (cfg DeviceConfiguration) Copy() DeviceConfiguration {
c := cfg
c.Addresses = make([]string, len(cfg.Addresses))
@@ -49,6 +51,17 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
}
}
+func (cfg *DeviceConfiguration) NumConnections() int {
+ switch {
+ case cfg.RawNumConnections == 0:
+ return defaultNumConnections
+ case cfg.RawNumConnections < 0:
+ return 1
+ default:
+ return cfg.RawNumConnections
+ }
+}
+
func (cfg *DeviceConfiguration) IgnoredFolder(folder string) bool {
for _, ignoredFolder := range cfg.IgnoredFolders {
if ignoredFolder.ID == folder {
diff --git a/lib/config/deviceconfiguration.pb.go b/lib/config/deviceconfiguration.pb.go
index e07bb1eb0..1f5d2a1a2 100644
--- a/lib/config/deviceconfiguration.pb.go
+++ b/lib/config/deviceconfiguration.pb.go
@@ -28,7 +28,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type DeviceConfiguration 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" nodefault:"true"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name" xml:"name,attr,omitempty"`
- Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses" xml:"address,omitempty" default:"dynamic"`
+ Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses" xml:"address,omitempty"`
Compression protocol.Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression" xml:"compression,attr"`
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"certName" xml:"certName,attr,omitempty"`
Introducer bool `protobuf:"varint,6,opt,name=introducer,proto3" json:"introducer" xml:"introducer,attr"`
@@ -44,6 +44,7 @@ type DeviceConfiguration struct {
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"`
RemoteGUIPort int `protobuf:"varint,18,opt,name=remote_gui_port,json=remoteGuiPort,proto3,casttype=int" json:"remoteGUIPort" xml:"remoteGUIPort"`
+ RawNumConnections int `protobuf:"varint,19,opt,name=num_connections,json=numConnections,proto3,casttype=int" json:"numConnections" xml:"numConnections"`
}
func (m *DeviceConfiguration) Reset() { *m = DeviceConfiguration{} }
@@ -88,72 +89,74 @@ func init() {
}
var fileDescriptor_744b782bd13071dd = []byte{
- // 1026 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xbf, 0x6f, 0xdb, 0x46,
- 0x18, 0x15, 0xeb, 0xc4, 0xb6, 0xce, 0x3f, 0x64, 0xd3, 0x88, 0xc3, 0x18, 0x88, 0x4e, 0x50, 0x35,
- 0x28, 0x68, 0x22, 0x17, 0x6e, 0x27, 0xa3, 0x2d, 0x50, 0xc6, 0x68, 0x63, 0x18, 0x4d, 0x5c, 0x16,
- 0x5d, 0xbc, 0xb0, 0x24, 0xef, 0xac, 0x1c, 0x2c, 0xf2, 0x58, 0xf2, 0xa8, 0x58, 0x40, 0xff, 0x80,
- 0x76, 0x2b, 0x02, 0x74, 0xea, 0x92, 0xf6, 0xdf, 0xe8, 0xd0, 0xd5, 0x9b, 0x35, 0x16, 0x1d, 0x0e,
- 0x88, 0xbd, 0x71, 0x29, 0xc0, 0x31, 0x53, 0x71, 0x77, 0x14, 0x45, 0xca, 0x51, 0x50, 0xa0, 0x1b,
- 0xef, 0xbd, 0x77, 0xef, 0xdd, 0xf7, 0xe9, 0xbb, 0x13, 0xe8, 0x0c, 0x88, 0xbb, 0xeb, 0xd1, 0xe0,
- 0x94, 0xf4, 0x77, 0x11, 0x1e, 0x12, 0x0f, 0xab, 0x45, 0x12, 0x39, 0x8c, 0xd0, 0xa0, 0x17, 0x46,
- 0x94, 0x51, 0x7d, 0x51, 0x81, 0x3b, 0xdb, 0x42, 0x2d, 0x21, 0x8f, 0x0e, 0x76, 0x5d, 0x1c, 0x2a,
- 0x7e, 0xe7, 0x5e, 0xc9, 0x85, 0xba, 0x31, 0x8e, 0x86, 0x18, 0xe5, 0x54, 0x1d, 0x9f, 0x33, 0xf5,
- 0xd9, 0xfe, 0x67, 0x03, 0x6c, 0x1d, 0xc8, 0x8c, 0xc7, 0xe5, 0x0c, 0xfd, 0x4f, 0x0d, 0xd4, 0x55,
- 0xb6, 0x4d, 0x90, 0xa1, 0xb5, 0xb4, 0xee, 0xaa, 0xf9, 0x9b, 0x76, 0xc1, 0x61, 0xed, 0x6f, 0x0e,
- 0x3f, 0xee, 0x13, 0xf6, 0x3c, 0x71, 0x7b, 0x1e, 0xf5, 0x77, 0xe3, 0x51, 0xe0, 0xb1, 0xe7, 0x24,
- 0xe8, 0x97, 0xbe, 0xca, 0x27, 0xea, 0x29, 0xf7, 0xc3, 0x83, 0x2b, 0x0e, 0x97, 0x27, 0xdf, 0x29,
- 0x87, 0xcb, 0x28, 0xff, 0xce, 0x38, 0x6c, 0x9e, 0xfb, 0x83, 0xfd, 0x36, 0x41, 0x0f, 0x1d, 0xc6,
- 0xa2, 0x76, 0x2b, 0xa0, 0x08, 0x9f, 0x3a, 0xc9, 0x80, 0xed, 0xb7, 0x59, 0x94, 0xe0, 0x76, 0x7a,
- 0xd9, 0x59, 0xca, 0xc9, 0xec, 0xb2, 0x53, 0x6c, 0xfc, 0x71, 0xdc, 0xd1, 0x5e, 0x8e, 0x3b, 0x85,
- 0xe9, 0xab, 0x71, 0x47, 0xb3, 0x26, 0x2c, 0xd2, 0x8f, 0xc1, 0xad, 0xc0, 0xf1, 0xb1, 0xf1, 0x5e,
- 0x4b, 0xeb, 0xd6, 0xcd, 0x4f, 0x52, 0x0e, 0xe5, 0x3a, 0xe3, 0xf0, 0x9e, 0x8c, 0x13, 0x0b, 0xe9,
- 0xf9, 0x90, 0xfa, 0x84, 0x61, 0x3f, 0x64, 0x23, 0x91, 0xb4, 0xf5, 0x16, 0xdc, 0x92, 0x3b, 0xf5,
- 0x73, 0x50, 0x77, 0x10, 0x8a, 0x70, 0x1c, 0xe3, 0xd8, 0x58, 0x68, 0x2d, 0x74, 0xeb, 0xe6, 0x49,
- 0xca, 0xe1, 0x14, 0xcc, 0x38, 0x7c, 0x20, 0xbd, 0x73, 0xa4, 0xe4, 0xdc, 0x2a, 0x4a, 0x42, 0xa3,
- 0xc0, 0xf1, 0x89, 0x27, 0xb2, 0x36, 0x6f, 0xe8, 0xde, 0x5c, 0x76, 0x96, 0x72, 0x81, 0x35, 0xf5,
- 0xd5, 0x87, 0x60, 0xc5, 0xa3, 0x7e, 0x28, 0x56, 0x84, 0x06, 0xc6, 0xad, 0x96, 0xd6, 0x5d, 0xdf,
- 0xbb, 0xd3, 0x2b, 0x7a, 0xfc, 0x78, 0x4a, 0x9a, 0x9f, 0xa6, 0x1c, 0x96, 0xd5, 0x19, 0x87, 0xdb,
- 0xf2, 0x50, 0x25, 0x4c, 0x35, 0x3a, 0xbd, 0xec, 0x6c, 0xcc, 0x82, 0x56, 0x79, 0xab, 0x8e, 0x41,
- 0xdd, 0xc3, 0x11, 0xb3, 0x65, 0x23, 0x6f, 0xcb, 0x46, 0x3e, 0x11, 0xbf, 0x9d, 0x00, 0x9f, 0xaa,
- 0x66, 0xde, 0x57, 0xde, 0x39, 0xf0, 0x96, 0x86, 0xde, 0x9d, 0xc3, 0x59, 0x85, 0x8b, 0x7e, 0x02,
- 0x00, 0x09, 0x58, 0x44, 0x51, 0xe2, 0xe1, 0xc8, 0x58, 0x6c, 0x69, 0xdd, 0x65, 0x73, 0x3f, 0xe5,
- 0xb0, 0x84, 0x66, 0x1c, 0xde, 0x51, 0x53, 0x52, 0x40, 0x45, 0x11, 0x8d, 0x19, 0xcc, 0x2a, 0xed,
- 0xd3, 0x7f, 0xd7, 0xc0, 0x4e, 0x7c, 0x46, 0x42, 0x7b, 0x82, 0x89, 0xf1, 0xb6, 0x23, 0xec, 0xd3,
- 0xa1, 0x33, 0x88, 0x8d, 0x25, 0x19, 0x86, 0x52, 0x0e, 0x0d, 0xa1, 0x3a, 0x2c, 0x89, 0xac, 0x5c,
- 0x93, 0x71, 0xf8, 0xbe, 0x8c, 0x9e, 0x27, 0x28, 0x0e, 0x72, 0xff, 0x9d, 0x0a, 0x6b, 0x6e, 0x82,
- 0xfe, 0x87, 0x06, 0xd6, 0x8a, 0x33, 0x23, 0xdb, 0x1d, 0x19, 0xcb, 0xf2, 0xc6, 0xfd, 0xf2, 0xbf,
- 0x6e, 0x5c, 0xca, 0xe1, 0xea, 0xd4, 0xd5, 0x1c, 0x65, 0x1c, 0x76, 0xab, 0x3d, 0x44, 0xe6, 0x68,
- 0xfe, 0x9d, 0xdb, 0xbc, 0x21, 0x13, 0x37, 0x4e, 0xde, 0xb2, 0x8a, 0xad, 0xbe, 0x07, 0x16, 0x43,
- 0x27, 0x89, 0x31, 0x32, 0xea, 0xb2, 0x9b, 0x3b, 0x29, 0x87, 0x39, 0x92, 0x71, 0xb8, 0x2a, 0x23,
- 0xd5, 0xb2, 0x6d, 0xe5, 0xb8, 0xfe, 0x03, 0xd8, 0x70, 0x06, 0x03, 0xfa, 0x02, 0x23, 0x3b, 0xc0,
- 0xec, 0x05, 0x8d, 0xce, 0x62, 0x03, 0xc8, 0x2b, 0xf5, 0x75, 0xca, 0x61, 0x23, 0xe7, 0x9e, 0xe6,
- 0x54, 0xf1, 0x46, 0x54, 0xf1, 0xea, 0xa0, 0x19, 0xf3, 0x48, 0x6b, 0xd6, 0x4e, 0xff, 0x0e, 0x6c,
- 0x39, 0x09, 0xa3, 0xb6, 0xe3, 0x79, 0x38, 0x64, 0xf6, 0x29, 0x1d, 0x20, 0x1c, 0xc5, 0xc6, 0x8a,
- 0x3c, 0xfe, 0x87, 0x29, 0x87, 0x9b, 0x82, 0xfe, 0x5c, 0xb2, 0x5f, 0x28, 0x32, 0xe3, 0xf0, 0xae,
- 0x3a, 0xc2, 0x2c, 0xd3, 0xb6, 0x6e, 0xaa, 0xf5, 0x67, 0x60, 0xcd, 0x77, 0xce, 0xed, 0x18, 0x07,
- 0xc8, 0x3e, 0x73, 0xc3, 0xd8, 0x58, 0x6d, 0x69, 0xdd, 0xdb, 0xe6, 0x07, 0xe2, 0x72, 0xfa, 0xce,
- 0xf9, 0x37, 0x38, 0x40, 0x47, 0x6e, 0x28, 0x5c, 0x37, 0xa5, 0x6b, 0x09, 0x6b, 0xbf, 0xe1, 0x70,
- 0x81, 0x04, 0xcc, 0x2a, 0x0b, 0x27, 0x86, 0x11, 0xf6, 0x86, 0xca, 0x70, 0xad, 0x62, 0x68, 0x61,
- 0x6f, 0x38, 0x6b, 0x38, 0xc1, 0x2a, 0x86, 0x13, 0x50, 0x0f, 0x40, 0x83, 0xf4, 0x03, 0x1a, 0x61,
- 0x54, 0xd4, 0xbf, 0xde, 0x5a, 0xe8, 0xae, 0xec, 0x6d, 0xf7, 0xd4, 0xbf, 0x46, 0xef, 0x59, 0xfe,
- 0xaf, 0xa1, 0x6a, 0x32, 0x1f, 0x89, 0x59, 0x4c, 0x39, 0x5c, 0xcf, 0xb7, 0x4d, 0x1b, 0xb3, 0xa5,
- 0xa6, 0xaa, 0x0c, 0xb7, 0xad, 0x19, 0x99, 0xfe, 0x93, 0x06, 0x1a, 0x21, 0x0e, 0x10, 0x09, 0xfa,
- 0x45, 0x60, 0xe3, 0x9d, 0x81, 0x4f, 0x44, 0xe0, 0x15, 0x87, 0xc6, 0x01, 0x0e, 0x23, 0xec, 0x39,
- 0x0c, 0xa3, 0x63, 0x65, 0x90, 0x7b, 0xa6, 0x1c, 0x6a, 0x8f, 0x8a, 0x37, 0x28, 0x2c, 0x73, 0xa5,
- 0xd1, 0x30, 0x34, 0x6b, 0xbd, 0xc2, 0xc5, 0xfa, 0xaf, 0x1a, 0x68, 0xa8, 0x6e, 0x7e, 0x9f, 0xe0,
- 0x98, 0xd9, 0x67, 0xc4, 0x35, 0x36, 0x64, 0x3f, 0xe3, 0x2b, 0x0e, 0xd7, 0xbe, 0x12, 0x6d, 0x92,
- 0xcc, 0x11, 0x31, 0x53, 0x0e, 0xd7, 0xfc, 0x32, 0x50, 0x14, 0x5c, 0x41, 0x27, 0x4d, 0x4e, 0x2f,
- 0x3b, 0x33, 0xf2, 0x59, 0xe0, 0xe5, 0xb8, 0x53, 0x4d, 0xb0, 0x2a, 0xbc, 0xab, 0x7f, 0x06, 0xea,
- 0x49, 0xc0, 0xa2, 0x24, 0x66, 0x18, 0x19, 0x9b, 0x72, 0x26, 0x5b, 0xe2, 0x7f, 0xa6, 0x00, 0x33,
- 0x0e, 0x1b, 0xf2, 0x04, 0x05, 0xd2, 0xb6, 0xa6, 0xac, 0xac, 0x4e, 0x3c, 0x70, 0x0c, 0xdb, 0xfd,
- 0x84, 0xd8, 0x21, 0x8d, 0x98, 0xa1, 0x4f, 0xab, 0xb3, 0x24, 0xf5, 0xe5, 0xb7, 0x87, 0xc7, 0x34,
- 0x62, 0xa2, 0xba, 0xa8, 0x0c, 0x14, 0xd5, 0x55, 0xd0, 0x72, 0x75, 0x55, 0xf9, 0x2c, 0x20, 0xaa,
- 0xab, 0x24, 0x58, 0x13, 0x3e, 0x21, 0x62, 0x69, 0x1e, 0x5d, 0xbc, 0x6e, 0xd6, 0xc6, 0xaf, 0x9b,
- 0xb5, 0x8b, 0xab, 0xa6, 0x36, 0xbe, 0x6a, 0x6a, 0x3f, 0x5f, 0x37, 0x6b, 0xaf, 0xae, 0x9b, 0xda,
- 0xf8, 0xba, 0x59, 0xfb, 0xeb, 0xba, 0x59, 0x3b, 0x79, 0xf0, 0x1f, 0x1e, 0x3b, 0x35, 0x31, 0xee,
- 0xa2, 0x7c, 0xf4, 0x3e, 0xfa, 0x37, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x4a, 0x4f, 0x60, 0x33, 0x09,
- 0x00, 0x00,
+ // 1057 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x41, 0x6f, 0xe3, 0x44,
+ 0x14, 0x8e, 0xe9, 0x6e, 0xb7, 0x99, 0x6d, 0x9b, 0xc6, 0x65, 0xbb, 0xde, 0x4a, 0x9b, 0x89, 0x42,
+ 0x0e, 0x41, 0xec, 0xa6, 0xa8, 0x70, 0xaa, 0x00, 0x89, 0xb4, 0x82, 0xad, 0x2a, 0xba, 0x65, 0x10,
+ 0x97, 0xdd, 0x83, 0x71, 0x3c, 0xd3, 0xac, 0xd5, 0x78, 0xc6, 0xd8, 0xe3, 0xb4, 0x95, 0x38, 0x72,
+ 0x80, 0x1b, 0xaa, 0xc4, 0x89, 0xcb, 0xc2, 0xdf, 0xe0, 0xc0, 0xb5, 0xb7, 0xe6, 0x08, 0x1c, 0x46,
+ 0xda, 0xf4, 0xe6, 0xa3, 0x8f, 0x9c, 0xd0, 0x8c, 0x1d, 0xc7, 0x76, 0x37, 0x2b, 0x24, 0x6e, 0x9e,
+ 0xef, 0x7b, 0xf3, 0x7d, 0xf3, 0x9e, 0xdf, 0x9b, 0x01, 0xed, 0xa1, 0xd3, 0xdf, 0xb2, 0x19, 0x3d,
+ 0x76, 0x06, 0x5b, 0x98, 0x8c, 0x1c, 0x9b, 0x24, 0x8b, 0xd0, 0xb7, 0xb8, 0xc3, 0x68, 0xd7, 0xf3,
+ 0x19, 0x67, 0xfa, 0x62, 0x02, 0x6e, 0x6e, 0xc8, 0x68, 0x05, 0xd9, 0x6c, 0xb8, 0xd5, 0x27, 0x5e,
+ 0xc2, 0x6f, 0x3e, 0xc8, 0xa9, 0xb0, 0x7e, 0x40, 0xfc, 0x11, 0xc1, 0x29, 0x55, 0x25, 0x67, 0x3c,
+ 0xf9, 0x6c, 0xfd, 0x55, 0x07, 0xeb, 0x7b, 0xca, 0x63, 0x37, 0xef, 0xa1, 0xff, 0xa1, 0x81, 0x6a,
+ 0xe2, 0x6d, 0x3a, 0xd8, 0xd0, 0x9a, 0x5a, 0x67, 0xb9, 0xf7, 0xab, 0x76, 0x29, 0x60, 0xe5, 0x6f,
+ 0x01, 0x3f, 0x1c, 0x38, 0xfc, 0x45, 0xd8, 0xef, 0xda, 0xcc, 0xdd, 0x0a, 0xce, 0xa9, 0xcd, 0x5f,
+ 0x38, 0x74, 0x90, 0xfb, 0xca, 0x9f, 0xa8, 0x9b, 0xa8, 0xef, 0xef, 0x4d, 0x04, 0x5c, 0x9a, 0x7e,
+ 0x47, 0x02, 0x2e, 0xe1, 0xf4, 0x3b, 0x16, 0xb0, 0x71, 0xe6, 0x0e, 0x77, 0x5a, 0x0e, 0x7e, 0x64,
+ 0x71, 0xee, 0xb7, 0x9a, 0x94, 0x61, 0x72, 0x6c, 0x85, 0x43, 0xbe, 0xd3, 0xe2, 0x7e, 0x48, 0x5a,
+ 0xd1, 0x55, 0xfb, 0x4e, 0x4a, 0xc6, 0x57, 0xed, 0x6c, 0xe3, 0x0f, 0xe3, 0xb6, 0x76, 0x31, 0x6e,
+ 0x67, 0xa2, 0x2f, 0xc7, 0x6d, 0x0d, 0x4d, 0x59, 0xac, 0x1f, 0x81, 0x5b, 0xd4, 0x72, 0x89, 0xf1,
+ 0x56, 0x53, 0xeb, 0x54, 0x7b, 0x1f, 0x45, 0x02, 0xaa, 0x75, 0x2c, 0xe0, 0x03, 0x65, 0x27, 0x17,
+ 0x4a, 0xf3, 0x11, 0x73, 0x1d, 0x4e, 0x5c, 0x8f, 0x9f, 0x4b, 0xa7, 0xf5, 0xd7, 0xe0, 0x48, 0xed,
+ 0xd4, 0x9f, 0x83, 0xaa, 0x85, 0xb1, 0x4f, 0x82, 0x80, 0x04, 0xc6, 0x42, 0x73, 0xa1, 0x53, 0xed,
+ 0x7d, 0x1c, 0x09, 0x38, 0x03, 0x63, 0x01, 0xef, 0x2b, 0xed, 0x14, 0x29, 0x2a, 0xd7, 0x6f, 0xa0,
+ 0x68, 0xb6, 0x55, 0x1f, 0x81, 0xbb, 0x36, 0x73, 0x3d, 0xb9, 0x72, 0x18, 0x35, 0x6e, 0x35, 0xb5,
+ 0xce, 0xea, 0xf6, 0xbd, 0x6e, 0x56, 0xc6, 0xdd, 0x19, 0xa9, 0x5c, 0xf3, 0xd1, 0xb1, 0x80, 0x1b,
+ 0xca, 0x37, 0x87, 0x25, 0xb5, 0x8c, 0xae, 0xda, 0x6b, 0x65, 0x10, 0xe5, 0xb7, 0xea, 0x04, 0x54,
+ 0x6d, 0xe2, 0x73, 0x53, 0xd5, 0xea, 0xb6, 0xaa, 0xd5, 0x13, 0xf9, 0x7b, 0x24, 0x78, 0x98, 0xd4,
+ 0xeb, 0x61, 0xa2, 0x9d, 0x02, 0xaf, 0xa9, 0xd9, 0xfd, 0x39, 0x1c, 0xca, 0x54, 0xf4, 0x67, 0x00,
+ 0x38, 0x94, 0xfb, 0x0c, 0x87, 0x36, 0xf1, 0x8d, 0xc5, 0xa6, 0xd6, 0x59, 0xea, 0xed, 0x44, 0x02,
+ 0xe6, 0xd0, 0x58, 0xc0, 0x7b, 0x49, 0x23, 0x64, 0x50, 0x96, 0x44, 0xad, 0x84, 0xa1, 0xdc, 0x3e,
+ 0xfd, 0x37, 0x0d, 0x6c, 0x06, 0x27, 0x8e, 0x67, 0x4e, 0x31, 0xd9, 0xc1, 0xa6, 0x4f, 0x5c, 0x36,
+ 0xb2, 0x86, 0x81, 0x71, 0x47, 0x99, 0xe1, 0x48, 0x40, 0x43, 0x46, 0xed, 0xe7, 0x82, 0x50, 0x1a,
+ 0x13, 0x0b, 0xf8, 0x8e, 0xb2, 0x9e, 0x17, 0x90, 0x1d, 0xe4, 0xe1, 0x1b, 0x23, 0xd0, 0x5c, 0x07,
+ 0xfd, 0x77, 0x0d, 0xac, 0x64, 0x67, 0xc6, 0x66, 0xff, 0xdc, 0x58, 0x52, 0x43, 0xf5, 0xf3, 0xff,
+ 0x1a, 0xaa, 0x48, 0xc0, 0xe5, 0x99, 0x6a, 0xef, 0x3c, 0x16, 0xb0, 0x53, 0xac, 0x21, 0xee, 0x9d,
+ 0xcf, 0x1f, 0xab, 0xfa, 0x8d, 0x30, 0x39, 0x54, 0x6a, 0x90, 0x0a, 0xb2, 0xfa, 0x36, 0x58, 0xf4,
+ 0xac, 0x30, 0x20, 0xd8, 0xa8, 0xaa, 0x6a, 0x6e, 0x46, 0x02, 0xa6, 0x48, 0x2c, 0xe0, 0xb2, 0xb2,
+ 0x4c, 0x96, 0x2d, 0x94, 0xe2, 0xfa, 0x77, 0x60, 0xcd, 0x1a, 0x0e, 0xd9, 0x29, 0xc1, 0x26, 0x25,
+ 0xfc, 0x94, 0xf9, 0x27, 0x81, 0x01, 0xd4, 0xd4, 0x7c, 0x19, 0x09, 0x58, 0x4b, 0xb9, 0xc3, 0x94,
+ 0xca, 0xae, 0x81, 0x22, 0x5e, 0x6c, 0x34, 0x63, 0x1e, 0x89, 0xca, 0x72, 0xfa, 0x37, 0x60, 0xdd,
+ 0x0a, 0x39, 0x33, 0x2d, 0xdb, 0x26, 0x1e, 0x37, 0x8f, 0xd9, 0x10, 0x13, 0x3f, 0x30, 0xee, 0xaa,
+ 0xe3, 0xbf, 0x1f, 0x09, 0x58, 0x97, 0xf4, 0xa7, 0x8a, 0xfd, 0x2c, 0x21, 0x67, 0xe3, 0x5b, 0x66,
+ 0x5a, 0xe8, 0x66, 0xb4, 0xfe, 0x14, 0xac, 0xb8, 0xd6, 0x99, 0x19, 0x10, 0x8a, 0xcd, 0x93, 0xbe,
+ 0x17, 0x18, 0xcb, 0x4d, 0xad, 0x73, 0xbb, 0xf7, 0x9e, 0x1c, 0x4e, 0xd7, 0x3a, 0xfb, 0x8a, 0x50,
+ 0x7c, 0xd0, 0xf7, 0xa4, 0x6a, 0x5d, 0xa9, 0xe6, 0xb0, 0xd6, 0x3f, 0x02, 0x2e, 0x38, 0x94, 0xa3,
+ 0x7c, 0xe0, 0x54, 0xd0, 0x27, 0xf6, 0x28, 0x11, 0x5c, 0x29, 0x08, 0x22, 0x62, 0x8f, 0xca, 0x82,
+ 0x53, 0xac, 0x20, 0x38, 0x05, 0x75, 0x0a, 0x6a, 0xce, 0x80, 0x32, 0x9f, 0xe0, 0x2c, 0xff, 0xd5,
+ 0xe6, 0x42, 0xe7, 0xee, 0xf6, 0x46, 0x37, 0x79, 0x18, 0xba, 0x4f, 0xd3, 0x87, 0x21, 0xc9, 0xa9,
+ 0xf7, 0x58, 0xf6, 0x62, 0x24, 0xe0, 0x6a, 0xba, 0x6d, 0x56, 0x98, 0xf5, 0xa4, 0xab, 0xf2, 0x70,
+ 0x0b, 0x95, 0xc2, 0xf4, 0x1f, 0x35, 0x50, 0xf3, 0x08, 0xc5, 0x0e, 0x1d, 0x64, 0x86, 0xb5, 0x37,
+ 0x1a, 0x3e, 0x91, 0x86, 0x13, 0x01, 0x8d, 0x3d, 0xe2, 0xf9, 0xc4, 0xb6, 0x38, 0xc1, 0x47, 0x89,
+ 0x40, 0xaa, 0x19, 0x09, 0xa8, 0x3d, 0xce, 0xee, 0x20, 0x2f, 0xcf, 0xe5, 0x5a, 0xc3, 0xd0, 0xd0,
+ 0x6a, 0x81, 0x0b, 0xf4, 0x5f, 0x34, 0x50, 0x4b, 0xaa, 0xf9, 0x6d, 0x48, 0x02, 0x6e, 0x9e, 0x38,
+ 0x7d, 0x63, 0x4d, 0xd5, 0x33, 0x98, 0x08, 0xb8, 0xf2, 0x85, 0x2c, 0x93, 0x62, 0x0e, 0x9c, 0x5e,
+ 0x24, 0xe0, 0x8a, 0x9b, 0x07, 0xb2, 0x84, 0x0b, 0xe8, 0xb4, 0xc8, 0xd1, 0x55, 0xbb, 0x14, 0x5e,
+ 0x06, 0x2e, 0xc6, 0xed, 0xa2, 0x03, 0x2a, 0xf0, 0x7d, 0xfd, 0x13, 0x50, 0x0d, 0x29, 0xf7, 0xc3,
+ 0x80, 0x13, 0x6c, 0xd4, 0x55, 0x4f, 0x36, 0xe5, 0x53, 0x92, 0x81, 0xb1, 0x80, 0x35, 0x75, 0x82,
+ 0x0c, 0x69, 0xa1, 0x19, 0xab, 0xb2, 0x93, 0x17, 0x1c, 0x27, 0xe6, 0x20, 0x74, 0x4c, 0x8f, 0xf9,
+ 0xdc, 0xd0, 0x67, 0xd9, 0x21, 0x45, 0x7d, 0xfe, 0xf5, 0xfe, 0x11, 0xf3, 0xb9, 0xcc, 0xce, 0xcf,
+ 0x03, 0x59, 0x76, 0x05, 0x34, 0x9f, 0x5d, 0x31, 0xbc, 0x0c, 0xc8, 0xec, 0x0a, 0x0e, 0x68, 0xca,
+ 0x87, 0x8e, 0x5c, 0xea, 0xdf, 0x6b, 0xa0, 0x46, 0x43, 0xd7, 0xb4, 0x19, 0xa5, 0x44, 0x5d, 0x83,
+ 0x81, 0xb1, 0xae, 0x4e, 0xf7, 0x7c, 0x22, 0x60, 0x1d, 0x59, 0xa7, 0x87, 0xa1, 0xbb, 0x3b, 0x23,
+ 0x65, 0xc7, 0xd1, 0x02, 0x12, 0x0b, 0xf8, 0x76, 0xf2, 0x4a, 0x17, 0xe0, 0xe9, 0x19, 0x2f, 0xc6,
+ 0xed, 0x9b, 0x2a, 0xa8, 0xa4, 0xd1, 0x3b, 0xb8, 0x7c, 0xd5, 0xa8, 0x8c, 0x5f, 0x35, 0x2a, 0x97,
+ 0x93, 0x86, 0x36, 0x9e, 0x34, 0xb4, 0x9f, 0xae, 0x1b, 0x95, 0x97, 0xd7, 0x0d, 0x6d, 0x7c, 0xdd,
+ 0xa8, 0xfc, 0x79, 0xdd, 0xa8, 0x3c, 0x7b, 0xf7, 0x3f, 0xdc, 0xb9, 0x49, 0xe3, 0xf6, 0x17, 0xd5,
+ 0xdd, 0xfb, 0xc1, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x75, 0x19, 0xf5, 0x92, 0x9d, 0x09, 0x00,
+ 0x00,
}
func (m *DeviceConfiguration) Marshal() (dAtA []byte, err error) {
@@ -176,6 +179,13 @@ func (m *DeviceConfiguration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
+ if m.RawNumConnections != 0 {
+ i = encodeVarintDeviceconfiguration(dAtA, i, uint64(m.RawNumConnections))
+ i--
+ dAtA[i] = 0x1
+ i--
+ dAtA[i] = 0x98
+ }
if m.RemoteGUIPort != 0 {
i = encodeVarintDeviceconfiguration(dAtA, i, uint64(m.RemoteGUIPort))
i--
@@ -423,6 +433,9 @@ func (m *DeviceConfiguration) ProtoSize() (n int) {
if m.RemoteGUIPort != 0 {
n += 2 + sovDeviceconfiguration(uint64(m.RemoteGUIPort))
}
+ if m.RawNumConnections != 0 {
+ n += 2 + sovDeviceconfiguration(uint64(m.RawNumConnections))
+ }
return n
}
@@ -918,6 +931,25 @@ func (m *DeviceConfiguration) Unmarshal(dAtA []byte) error {
break
}
}
+ case 19:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field RawNumConnections", wireType)
+ }
+ m.RawNumConnections = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowDeviceconfiguration
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.RawNumConnections |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
default:
iNdEx = preIndex
skippy, err := skipDeviceconfiguration(dAtA[iNdEx:])
diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go
index 20002ac55..dcb3a9dea 100644
--- a/lib/config/wrapper.go
+++ b/lib/config/wrapper.go
@@ -20,6 +20,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture/v4"
)
@@ -198,9 +199,7 @@ func (w *wrapper) Unsubscribe(c Committer) {
w.mut.Lock()
for i := range w.subs {
if w.subs[i] == c {
- copy(w.subs[i:], w.subs[i+1:])
- w.subs[len(w.subs)-1] = nil
- w.subs = w.subs[:len(w.subs)-1]
+ w.subs = sliceutil.RemoveAndZero(w.subs, i)
break
}
}
diff --git a/lib/connections/quic_dial.go b/lib/connections/quic_dial.go
index 7466f8ca3..2450fe9f7 100644
--- a/lib/connections/quic_dial.go
+++ b/lib/connections/quic_dial.go
@@ -91,6 +91,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
if isLocal {
priority = d.lanPriority
}
+
return newInternalConn(&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, isLocal, priority), nil
}
@@ -108,9 +109,10 @@ func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Confi
commonDialer: commonDialer{
reconnectInterval: time.Duration(quicInterval) * time.Second,
tlsCfg: tlsCfg,
+ lanChecker: lanChecker,
lanPriority: opts.ConnectionPriorityQUICLAN,
wanPriority: opts.ConnectionPriorityQUICWAN,
- lanChecker: lanChecker,
+ allowsMultiConns: true,
},
registry: registry,
}
diff --git a/lib/connections/quic_listen.go b/lib/connections/quic_listen.go
index 930376777..f5baf6af2 100644
--- a/lib/connections/quic_listen.go
+++ b/lib/connections/quic_listen.go
@@ -105,7 +105,9 @@ func (t *quicListener) serve(ctx context.Context) error {
defer quicTransport.Close()
svc := stun.New(t.cfg, t, &transportPacketConn{tran: quicTransport}, tracer)
- go svc.Serve(ctx)
+ stunCtx, cancel := context.WithCancel(ctx)
+ defer cancel()
+ go svc.Serve(stunCtx)
t.registry.Register(t.uri.Scheme, quicTransport)
defer t.registry.Unregister(t.uri.Scheme, quicTransport)
diff --git a/lib/connections/registry/registry.go b/lib/connections/registry/registry.go
index a262f2be8..e86fcb5b7 100644
--- a/lib/connections/registry/registry.go
+++ b/lib/connections/registry/registry.go
@@ -12,6 +12,7 @@ package registry
import (
"strings"
+ "github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -41,9 +42,7 @@ func (r *Registry) Unregister(scheme string, item interface{}) {
candidates := r.available[scheme]
for i, existingItem := range candidates {
if existingItem == item {
- candidates[i] = candidates[len(candidates)-1]
- candidates[len(candidates)-1] = nil
- r.available[scheme] = candidates[:len(candidates)-1]
+ r.available[scheme] = sliceutil.RemoveAndZero(candidates, i)
break
}
}
diff --git a/lib/connections/service.go b/lib/connections/service.go
index 484fb94cc..23968fc80 100644
--- a/lib/connections/service.go
+++ b/lib/connections/service.go
@@ -11,10 +11,14 @@ package connections
import (
"context"
+ "crypto/rand"
"crypto/tls"
"crypto/x509"
+ "encoding/base32"
+ "encoding/binary"
"errors"
"fmt"
+ "io"
"math"
"net"
"net/url"
@@ -23,8 +27,10 @@ import (
stdsync "sync"
"time"
+ "golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
+ "github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections/registry"
"github.com/syncthing/syncthing/lib/discover"
@@ -33,6 +39,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/semaphore"
+ "github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syncthing/syncthing/lib/stringutil"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
@@ -66,12 +73,14 @@ var (
errDeviceIgnored = errors.New("device is ignored")
errConnLimitReached = errors.New("connection limit reached")
errDevicePaused = errors.New("device is paused")
+
+ // A connection is being closed to make space for better ones
+ errReplacingConnection = errors.New("replacing connection")
)
const (
perDeviceWarningIntv = 15 * time.Minute
tlsHandshakeTimeout = 10 * time.Second
- minConnectionReplaceAge = 10 * time.Second
minConnectionLoopSleep = 5 * time.Second
stdConnectionLoopSleep = time.Minute
worstDialerPriority = math.MaxInt32
@@ -79,6 +88,7 @@ const (
shortLivedConnectionThreshold = 5 * time.Second
dialMaxParallel = 64
dialMaxParallelPerDevice = 8
+ maxNumConnections = 128 // the maximum number of connections we maintain to any given device
)
// From go/src/crypto/tls/cipher_suites.go
@@ -150,6 +160,7 @@ type connWithHello struct {
type service struct {
*suture.Supervisor
connectionStatusHandler
+ deviceConnectionTracker
cfg config.Wrapper
myID protocol.DeviceID
@@ -281,21 +292,43 @@ func (s *service) handleConns(ctx context.Context) error {
_ = c.SetDeadline(time.Now().Add(20 * time.Second))
go func() {
- hello, err := protocol.ExchangeHello(c, s.model.GetHello(remoteID))
+ // Exchange Hello messages with the peer.
+ outgoing := s.helloForDevice(remoteID)
+ incoming, err := protocol.ExchangeHello(c, outgoing)
+ // The timestamps are used to create the connection ID.
+ c.connectionID = newConnectionID(outgoing.Timestamp, incoming.Timestamp)
+
select {
- case s.hellos <- &connWithHello{c, hello, err, remoteID, remoteCert}:
+ case s.hellos <- &connWithHello{c, incoming, err, remoteID, remoteCert}:
case <-ctx.Done():
}
}()
}
}
+func (s *service) helloForDevice(remoteID protocol.DeviceID) protocol.Hello {
+ hello := protocol.Hello{
+ ClientName: "syncthing",
+ ClientVersion: build.Version,
+ Timestamp: time.Now().UnixNano(),
+ }
+ if cfg, ok := s.cfg.Device(remoteID); ok {
+ hello.NumConnections = cfg.NumConnections()
+ // Set our name (from the config of our device ID) only if we
+ // already know about the other side device ID.
+ if myCfg, ok := s.cfg.Device(s.myID); ok {
+ hello.DeviceName = myCfg.Name
+ }
+ }
+ return hello
+}
+
func (s *service) connectionCheckEarly(remoteID protocol.DeviceID, c internalConn) error {
if s.cfg.IgnoredDevice(remoteID) {
return errDeviceIgnored
}
- if max := s.cfg.Options().ConnectionLimitMax; max > 0 && s.model.NumConnections() >= max {
+ if max := s.cfg.Options().ConnectionLimitMax; max > 0 && s.numConnectedDevices() >= max {
// We're not allowed to accept any more connections.
return errConnLimitReached
}
@@ -315,31 +348,26 @@ func (s *service) connectionCheckEarly(remoteID protocol.DeviceID, c internalCon
return errNetworkNotAllowed
}
- // Lower priority is better, just like nice etc.
- if ct, ok := s.model.Connection(remoteID); ok {
- if ct.Priority() > c.priority || time.Since(ct.Statistics().StartedAt) > minConnectionReplaceAge {
- l.Debugf("Switching connections %s (existing: %s new: %s)", remoteID, ct, c)
- } else {
- // We should not already be connected to the other party. TODO: This
- // could use some better handling. If the old connection is dead but
- // hasn't timed out yet we may want to drop *that* connection and keep
- // this one. But in case we are two devices connecting to each other
- // in parallel we don't want to do that or we end up with no
- // connections still established...
- return errDeviceAlreadyConnected
- }
+ currentConns := s.numConnectionsForDevice(cfg.DeviceID)
+ desiredConns := s.desiredConnectionsToDevice(cfg.DeviceID)
+ worstPrio := s.worstConnectionPriority(remoteID)
+ ourUpgradeThreshold := c.priority + s.cfg.Options().ConnectionPriorityUpgradeThreshold
+ if currentConns >= desiredConns && ourUpgradeThreshold >= worstPrio {
+ l.Debugf("Not accepting connection to %s at %s: already have %d connections, desire %d", remoteID, c, currentConns, desiredConns)
+ return errDeviceAlreadyConnected
}
return nil
}
func (s *service) handleHellos(ctx context.Context) error {
- var c internalConn
- var hello protocol.Hello
- var err error
- var remoteID protocol.DeviceID
- var remoteCert *x509.Certificate
for {
+ var c internalConn
+ var hello protocol.Hello
+ var err error
+ var remoteID protocol.DeviceID
+ var remoteCert *x509.Certificate
+
select {
case <-ctx.Done():
return ctx.Err()
@@ -416,15 +444,17 @@ func (s *service) handleHellos(ctx context.Context) error {
rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal())
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID), s.keyGen)
+ s.accountAddedConnection(protoConn, hello, s.cfg.Options().ConnectionPriorityUpgradeThreshold)
go func() {
<-protoConn.Closed()
+ s.accountRemovedConnection(protoConn)
s.dialNowDevicesMut.Lock()
s.dialNowDevices[remoteID] = struct{}{}
s.scheduleDialNow()
s.dialNowDevicesMut.Unlock()
}()
- l.Infof("Established secure connection to %s at %s", remoteID, c)
+ l.Infof("Established secure connection to %s at %s", remoteID.Short(), c)
s.model.AddConnection(protoConn, hello)
continue
@@ -518,7 +548,7 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
allowAdditional := 0 // no limit
connectionLimit := cfg.Options.LowestConnectionLimit()
if connectionLimit > 0 {
- current := s.model.NumConnections()
+ current := s.numConnectedDevices()
allowAdditional = connectionLimit - current
if allowAdditional <= 0 {
l.Debugf("Skipping dial because we've reached the connection limit, current %d >= limit %d", current, connectionLimit)
@@ -545,19 +575,20 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
// See if we are already connected and, if so, what our cutoff is
// for dialer priority.
priorityCutoff := worstDialerPriority
- connection, connected := s.model.Connection(deviceCfg.DeviceID)
- if connected {
+ if currentConns := s.numConnectionsForDevice(deviceCfg.DeviceID); currentConns > 0 {
// Set the priority cutoff to the current connection's priority,
// so that we don't attempt any dialers with worse priority.
- priorityCutoff = connection.Priority()
+ priorityCutoff = s.worstConnectionPriority(deviceCfg.DeviceID)
// Reduce the priority cutoff by the upgrade threshold, so that
// we don't attempt dialers that aren't considered a worthy upgrade.
priorityCutoff -= cfg.Options.ConnectionPriorityUpgradeThreshold
- if bestDialerPriority >= priorityCutoff {
+ if bestDialerPriority >= priorityCutoff && currentConns >= s.desiredConnectionsToDevice(deviceCfg.DeviceID) {
// Our best dialer is not any better than what we already
- // have, so nothing to do here.
+ // have, and we already have the desired number of
+ // connections to this device,so nothing to do here.
+ l.Debugf("Skipping dial to %s because we already have %d connections and our best dialer is not better than %d", deviceCfg.DeviceID.Short(), currentConns, priorityCutoff)
continue
}
}
@@ -625,14 +656,14 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
deviceID := deviceCfg.DeviceID
addrs := s.resolveDeviceAddrs(ctx, deviceCfg)
- l.Debugln("Resolved device", deviceID, "addresses:", addrs)
+ l.Debugln("Resolved device", deviceID.Short(), "addresses:", addrs)
dialTargets := make([]dialTarget, 0, len(addrs))
for _, addr := range addrs {
// Use both device and address, as you might have two devices connected
// to the same relay
if !initial && nextDialAt.get(deviceID, addr).After(now) {
- l.Debugf("Not dialing %s via %v as it's not time yet", deviceID, addr)
+ l.Debugf("Not dialing %s via %v as it's not time yet", deviceID.Short(), addr)
continue
}
@@ -669,8 +700,17 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg, s.registry, s.lanChecker)
priority := dialer.Priority(uri.Host)
- if priority >= priorityCutoff {
- l.Debugf("Not dialing using %s as priority is not better than current connection (%d >= %d)", dialerFactory, priority, priorityCutoff)
+ currentConns := s.numConnectionsForDevice(deviceCfg.DeviceID)
+ if priority > priorityCutoff {
+ l.Debugf("Not dialing %s at %s using %s as priority is worse than current connection (%d > %d)", deviceID.Short(), addr, dialerFactory, priority, priorityCutoff)
+ continue
+ }
+ if currentConns > 0 && !dialer.AllowsMultiConns() {
+ l.Debugf("Not dialing %s at %s using %s as it does not allow multiple connections and we already have a connection", deviceID.Short(), addr, dialerFactory)
+ continue
+ }
+ if currentConns >= s.desiredConnectionsToDevice(deviceCfg.DeviceID) && priority == priorityCutoff {
+ l.Debugf("Not dialing %s at %s using %s as priority is equal and we already have %d/%d connections", deviceID.Short(), addr, dialerFactory, currentConns, deviceCfg.NumConnections)
continue
}
@@ -1272,3 +1312,165 @@ func (r nextDialRegistry) sleepDurationAndCleanup(now time.Time) time.Duration {
}
return sleep
}
+
+func (s *service) desiredConnectionsToDevice(deviceID protocol.DeviceID) int {
+ cfg, ok := s.cfg.Device(deviceID)
+ if !ok {
+ // We want no connections to an unknown device.
+ return 0
+ }
+
+ otherSide := s.wantConnectionsForDevice(deviceID)
+ thisSide := cfg.NumConnections()
+ switch {
+ case otherSide <= 0:
+ // The other side doesn't support multiple connections, or we
+ // haven't yet connected to them so we don't know what they support
+ // or not. Use a single connection until we know better.
+ return 1
+
+ case otherSide == 1:
+ // The other side supports multiple connections, but only wants
+ // one. We should honour that.
+ return 1
+
+ case thisSide == 1:
+ // We want only one connection, so we should honour that.
+ return 1
+
+ // Finally, we allow negotiation and use the higher of the two values,
+ // while keeping at or below the max allowed value.
+ default:
+ return min(max(thisSide, otherSide), maxNumConnections)
+ }
+}
+
+// The deviceConnectionTracker keeps track of how many devices we are
+// connected to and how many connections we have to each device. It also
+// tracks how many connections they are willing to use.
+type deviceConnectionTracker struct {
+ connectionsMut stdsync.Mutex
+ connections map[protocol.DeviceID][]protocol.Connection // current connections
+ wantConnections map[protocol.DeviceID]int // number of connections they want
+}
+
+func (c *deviceConnectionTracker) accountAddedConnection(conn protocol.Connection, h protocol.Hello, upgradeThreshold int) {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ // Lazily initialize the maps
+ if c.connections == nil {
+ c.connections = make(map[protocol.DeviceID][]protocol.Connection)
+ c.wantConnections = make(map[protocol.DeviceID]int)
+ }
+ // Add the connection to the list of current connections and remember
+ // how many total connections they want
+ d := conn.DeviceID()
+ c.connections[d] = append(c.connections[d], conn)
+ c.wantConnections[d] = int(h.NumConnections)
+ l.Debugf("Added connection for %s (now %d), they want %d connections", d.Short(), len(c.connections[d]), h.NumConnections)
+
+ // Close any connections we no longer want to retain.
+ c.closeWorsePriorityConnectionsLocked(d, conn.Priority()-upgradeThreshold)
+}
+
+func (c *deviceConnectionTracker) accountRemovedConnection(conn protocol.Connection) {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ d := conn.DeviceID()
+ cid := conn.ConnectionID()
+ // Remove the connection from the list of current connections
+ for i, conn := range c.connections[d] {
+ if conn.ConnectionID() == cid {
+ c.connections[d] = sliceutil.RemoveAndZero(c.connections[d], i)
+ break
+ }
+ }
+ // Clean up if required
+ if len(c.connections[d]) == 0 {
+ delete(c.connections, d)
+ delete(c.wantConnections, d)
+ }
+ l.Debugf("Removed connection for %s (now %d)", d.Short(), c.connections[d])
+}
+
+func (c *deviceConnectionTracker) numConnectionsForDevice(d protocol.DeviceID) int {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ return len(c.connections[d])
+}
+
+func (c *deviceConnectionTracker) wantConnectionsForDevice(d protocol.DeviceID) int {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ return c.wantConnections[d]
+}
+
+func (c *deviceConnectionTracker) numConnectedDevices() int {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ return len(c.connections)
+}
+
+func (c *deviceConnectionTracker) worstConnectionPriority(d protocol.DeviceID) int {
+ c.connectionsMut.Lock()
+ defer c.connectionsMut.Unlock()
+ if len(c.connections[d]) == 0 {
+ return math.MaxInt // worst possible priority
+ }
+ worstPriority := c.connections[d][0].Priority()
+ for _, conn := range c.connections[d][1:] {
+ if p := conn.Priority(); p > worstPriority {
+ worstPriority = p
+ }
+ }
+ return worstPriority
+}
+
+// closeWorsePriorityConnectionsLocked closes all connections to the given
+// device that are worse than the cutoff priority. Must be called with the
+// lock held.
+func (c *deviceConnectionTracker) closeWorsePriorityConnectionsLocked(d protocol.DeviceID, cutoff int) {
+ for _, conn := range c.connections[d] {
+ if p := conn.Priority(); p > cutoff {
+ l.Debugf("Closing connection %s to %s with priority %d (cutoff %d)", conn, d.Short(), p, cutoff)
+ go conn.Close(errReplacingConnection)
+ }
+ }
+}
+
+// newConnectionID generates a connection ID. The connection ID is designed
+// to be unique for each connection and chronologically sortable. It is
+// based on the sum of two timestamps: when we think the connection was
+// started, and when the other side thinks the connection was started. We
+// then add some random data for good measure. This way, even if the other
+// side does some funny business with the timestamp, we will get no worse
+// than random connection IDs.
+func newConnectionID(t0, t1 int64) string {
+ var buf [16]byte // 8 bytes timestamp, 8 bytes random
+ binary.BigEndian.PutUint64(buf[:], uint64(t0+t1))
+ _, _ = io.ReadFull(rand.Reader, buf[8:])
+ enc := base32.HexEncoding.WithPadding(base32.NoPadding)
+ // We encode the two parts separately and concatenate the results. The
+ // reason for this is that the timestamp (64 bits) doesn't precisely
+ // align to the base32 encoding (5 bits per character), so we'd get a
+ // character in the middle that is a mix of bits from the timestamp and
+ // from the random. We want the timestamp part deterministic.
+ return enc.EncodeToString(buf[:8]) + enc.EncodeToString(buf[8:])
+}
+
+// temporary implementations of min and max, to be removed once we can use
+// Go 1.21 builtins. :)
+
+func min[T constraints.Ordered](a, b T) T {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func max[T constraints.Ordered](a, b T) T {
+ if a > b {
+ return a
+ }
+ return b
+}
diff --git a/lib/connections/structs.go b/lib/connections/structs.go
index 1f5bf376b..37c8ef484 100644
--- a/lib/connections/structs.go
+++ b/lib/connections/structs.go
@@ -42,6 +42,7 @@ type internalConn struct {
isLocal bool
priority int
establishedAt time.Time
+ connectionID string // set after Hello exchange
}
type connType int
@@ -88,12 +89,13 @@ func (t connType) Transport() string {
}
func newInternalConn(tc tlsConn, connType connType, isLocal bool, priority int) internalConn {
+ now := time.Now()
return internalConn{
tlsConn: tc,
connType: connType,
isLocal: isLocal,
priority: priority,
- establishedAt: time.Now().Truncate(time.Second),
+ establishedAt: now.Truncate(time.Second),
}
}
@@ -138,12 +140,16 @@ func (c internalConn) EstablishedAt() time.Time {
return c.establishedAt
}
+func (c internalConn) ConnectionID() string {
+ return c.connectionID
+}
+
func (c internalConn) String() string {
t := "WAN"
if c.isLocal {
t = "LAN"
}
- return fmt.Sprintf("%s-%s/%s/%s/%s-P%d", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto(), t, c.Priority())
+ return fmt.Sprintf("%s-%s/%s/%s/%s-P%d-%s", c.LocalAddr(), c.RemoteAddr(), c.Type(), c.Crypto(), t, c.Priority(), c.connectionID)
}
type dialerFactory interface {
@@ -160,6 +166,7 @@ type commonDialer struct {
lanChecker *lanChecker
lanPriority int
wanPriority int
+ allowsMultiConns bool
}
func (d *commonDialer) RedialFrequency() time.Duration {
@@ -173,10 +180,15 @@ func (d *commonDialer) Priority(host string) int {
return d.wanPriority
}
+func (d *commonDialer) AllowsMultiConns() bool {
+ return d.allowsMultiConns
+}
+
type genericDialer interface {
Dial(context.Context, protocol.DeviceID, *url.URL) (internalConn, error)
RedialFrequency() time.Duration
Priority(host string) int
+ AllowsMultiConns() bool
}
type listenerFactory interface {
@@ -212,10 +224,7 @@ type genericListener interface {
type Model interface {
protocol.Model
AddConnection(conn protocol.Connection, hello protocol.Hello)
- NumConnections() int
- Connection(remoteID protocol.DeviceID) (protocol.Connection, bool)
OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error
- GetHello(protocol.DeviceID) protocol.HelloIntf
DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error)
}
diff --git a/lib/connections/tcp_dial.go b/lib/connections/tcp_dial.go
index 04a551e46..870575357 100644
--- a/lib/connections/tcp_dial.go
+++ b/lib/connections/tcp_dial.go
@@ -62,6 +62,7 @@ func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL)
if isLocal {
priority = d.lanPriority
}
+
return newInternalConn(tc, connTypeTCPClient, isLocal, priority), nil
}
@@ -73,9 +74,10 @@ func (tcpDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config
trafficClass: opts.TrafficClass,
reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
tlsCfg: tlsCfg,
+ lanChecker: lanChecker,
lanPriority: opts.ConnectionPriorityTCPLAN,
wanPriority: opts.ConnectionPriorityTCPWAN,
- lanChecker: lanChecker,
+ allowsMultiConns: true,
},
registry: registry,
}
diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go
index 6aa6d196a..9255d6eab 100644
--- a/lib/model/fakeconns_test.go
+++ b/lib/model/fakeconns_test.go
@@ -34,6 +34,7 @@ func newFakeConnection(id protocol.DeviceID, model Model) *fakeConnection {
return f.fileData[name], nil
})
f.DeviceIDReturns(id)
+ f.ConnectionIDReturns(rand.String(16))
f.CloseCalls(func(err error) {
f.closeOnce.Do(func() {
close(f.closed)
diff --git a/lib/model/folder_recvonly_test.go b/lib/model/folder_recvonly_test.go
index a30b6b240..b32f6dd33 100644
--- a/lib/model/folder_recvonly_test.go
+++ b/lib/model/folder_recvonly_test.go
@@ -530,7 +530,7 @@ func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.Cancel
cfg.Folders = []config.FolderConfiguration{fcfg}
replace(t, w, cfg)
- m := newModel(t, w, myID, "syncthing", "dev", nil)
+ m := newModel(t, w, myID, nil)
m.ServeBackground()
<-m.started
must(t, m.ScanFolder("ro"))
diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go
index 5be363000..0e54cbfee 100644
--- a/lib/model/folder_sendrecv.go
+++ b/lib/model/folder_sendrecv.go
@@ -507,7 +507,7 @@ nextFile:
devices := snap.Availability(fileName)
for _, dev := range devices {
- if _, ok := f.model.Connection(dev); ok {
+ if f.model.ConnectedTo(dev) {
// Handle the file normally, by copying and pulling, etc.
f.handleFile(fi, snap, copyChan)
continue nextFile
diff --git a/lib/model/mocks/model.go b/lib/model/mocks/model.go
index c0f8c95b8..bf29f47ec 100644
--- a/lib/model/mocks/model.go
+++ b/lib/model/mocks/model.go
@@ -76,18 +76,16 @@ type Model struct {
result1 model.FolderCompletion
result2 error
}
- ConnectionStub func(protocol.DeviceID) (protocol.Connection, bool)
- connectionMutex sync.RWMutex
- connectionArgsForCall []struct {
+ ConnectedToStub func(protocol.DeviceID) bool
+ connectedToMutex sync.RWMutex
+ connectedToArgsForCall []struct {
arg1 protocol.DeviceID
}
- connectionReturns struct {
- result1 protocol.Connection
- result2 bool
+ connectedToReturns struct {
+ result1 bool
}
- connectionReturnsOnCall map[int]struct {
- result1 protocol.Connection
- result2 bool
+ connectedToReturnsOnCall map[int]struct {
+ result1 bool
}
ConnectionStatsStub func() map[string]interface{}
connectionStatsMutex sync.RWMutex
@@ -262,17 +260,6 @@ type Model struct {
result1 map[string][]versioner.FileVersion
result2 error
}
- GetHelloStub func(protocol.DeviceID) protocol.HelloIntf
- getHelloMutex sync.RWMutex
- getHelloArgsForCall []struct {
- arg1 protocol.DeviceID
- }
- getHelloReturns struct {
- result1 protocol.HelloIntf
- }
- getHelloReturnsOnCall map[int]struct {
- result1 protocol.HelloIntf
- }
GetMtimeMappingStub func(string, string) (fs.MtimeMapping, error)
getMtimeMappingMutex sync.RWMutex
getMtimeMappingArgsForCall []struct {
@@ -378,16 +365,6 @@ type Model struct {
result3 []db.FileInfoTruncated
result4 error
}
- NumConnectionsStub func() int
- numConnectionsMutex sync.RWMutex
- numConnectionsArgsForCall []struct {
- }
- numConnectionsReturns struct {
- result1 int
- }
- numConnectionsReturnsOnCall map[int]struct {
- result1 int
- }
OnHelloStub func(protocol.DeviceID, net.Addr, protocol.Hello) error
onHelloMutex sync.RWMutex
onHelloArgsForCall []struct {
@@ -888,68 +865,65 @@ func (fake *Model) CompletionReturnsOnCall(i int, result1 model.FolderCompletion
}{result1, result2}
}
-func (fake *Model) Connection(arg1 protocol.DeviceID) (protocol.Connection, bool) {
- fake.connectionMutex.Lock()
- ret, specificReturn := fake.connectionReturnsOnCall[len(fake.connectionArgsForCall)]
- fake.connectionArgsForCall = append(fake.connectionArgsForCall, struct {
+func (fake *Model) ConnectedTo(arg1 protocol.DeviceID) bool {
+ fake.connectedToMutex.Lock()
+ ret, specificReturn := fake.connectedToReturnsOnCall[len(fake.connectedToArgsForCall)]
+ fake.connectedToArgsForCall = append(fake.connectedToArgsForCall, struct {
arg1 protocol.DeviceID
}{arg1})
- stub := fake.ConnectionStub
- fakeReturns := fake.connectionReturns
- fake.recordInvocation("Connection", []interface{}{arg1})
- fake.connectionMutex.Unlock()
+ stub := fake.ConnectedToStub
+ fakeReturns := fake.connectedToReturns
+ fake.recordInvocation("ConnectedTo", []interface{}{arg1})
+ fake.connectedToMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
- return ret.result1, ret.result2
+ return ret.result1
}
- return fakeReturns.result1, fakeReturns.result2
+ return fakeReturns.result1
}
-func (fake *Model) ConnectionCallCount() int {
- fake.connectionMutex.RLock()
- defer fake.connectionMutex.RUnlock()
- return len(fake.connectionArgsForCall)
+func (fake *Model) ConnectedToCallCount() int {
+ fake.connectedToMutex.RLock()
+ defer fake.connectedToMutex.RUnlock()
+ return len(fake.connectedToArgsForCall)
}
-func (fake *Model) ConnectionCalls(stub func(protocol.DeviceID) (protocol.Connection, bool)) {
- fake.connectionMutex.Lock()
- defer fake.connectionMutex.Unlock()
- fake.ConnectionStub = stub
+func (fake *Model) ConnectedToCalls(stub func(protocol.DeviceID) bool) {
+ fake.connectedToMutex.Lock()
+ defer fake.connectedToMutex.Unlock()
+ fake.ConnectedToStub = stub
}
-func (fake *Model) ConnectionArgsForCall(i int) protocol.DeviceID {
- fake.connectionMutex.RLock()
- defer fake.connectionMutex.RUnlock()
- argsForCall := fake.connectionArgsForCall[i]
+func (fake *Model) ConnectedToArgsForCall(i int) protocol.DeviceID {
+ fake.connectedToMutex.RLock()
+ defer fake.connectedToMutex.RUnlock()
+ argsForCall := fake.connectedToArgsForCall[i]
return argsForCall.arg1
}
-func (fake *Model) ConnectionReturns(result1 protocol.Connection, result2 bool) {
- fake.connectionMutex.Lock()
- defer fake.connectionMutex.Unlock()
- fake.ConnectionStub = nil
- fake.connectionReturns = struct {
- result1 protocol.Connection
- result2 bool
- }{result1, result2}
+func (fake *Model) ConnectedToReturns(result1 bool) {
+ fake.connectedToMutex.Lock()
+ defer fake.connectedToMutex.Unlock()
+ fake.ConnectedToStub = nil
+ fake.connectedToReturns = struct {
+ result1 bool
+ }{result1}
}
-func (fake *Model) ConnectionReturnsOnCall(i int, result1 protocol.Connection, result2 bool) {
- fake.connectionMutex.Lock()
- defer fake.connectionMutex.Unlock()
- fake.ConnectionStub = nil
- if fake.connectionReturnsOnCall == nil {
- fake.connectionReturnsOnCall = make(map[int]struct {
- result1 protocol.Connection
- result2 bool
+func (fake *Model) ConnectedToReturnsOnCall(i int, result1 bool) {
+ fake.connectedToMutex.Lock()
+ defer fake.connectedToMutex.Unlock()
+ fake.ConnectedToStub = nil
+ if fake.connectedToReturnsOnCall == nil {
+ fake.connectedToReturnsOnCall = make(map[int]struct {
+ result1 bool
})
}
- fake.connectionReturnsOnCall[i] = struct {
- result1 protocol.Connection
- result2 bool
- }{result1, result2}
+ fake.connectedToReturnsOnCall[i] = struct {
+ result1 bool
+ }{result1}
}
func (fake *Model) ConnectionStats() map[string]interface{} {
@@ -1797,67 +1771,6 @@ func (fake *Model) GetFolderVersionsReturnsOnCall(i int, result1 map[string][]ve
}{result1, result2}
}
-func (fake *Model) GetHello(arg1 protocol.DeviceID) protocol.HelloIntf {
- fake.getHelloMutex.Lock()
- ret, specificReturn := fake.getHelloReturnsOnCall[len(fake.getHelloArgsForCall)]
- fake.getHelloArgsForCall = append(fake.getHelloArgsForCall, struct {
- arg1 protocol.DeviceID
- }{arg1})
- stub := fake.GetHelloStub
- fakeReturns := fake.getHelloReturns
- fake.recordInvocation("GetHello", []interface{}{arg1})
- fake.getHelloMutex.Unlock()
- if stub != nil {
- return stub(arg1)
- }
- if specificReturn {
- return ret.result1
- }
- return fakeReturns.result1
-}
-
-func (fake *Model) GetHelloCallCount() int {
- fake.getHelloMutex.RLock()
- defer fake.getHelloMutex.RUnlock()
- return len(fake.getHelloArgsForCall)
-}
-
-func (fake *Model) GetHelloCalls(stub func(protocol.DeviceID) protocol.HelloIntf) {
- fake.getHelloMutex.Lock()
- defer fake.getHelloMutex.Unlock()
- fake.GetHelloStub = stub
-}
-
-func (fake *Model) GetHelloArgsForCall(i int) protocol.DeviceID {
- fake.getHelloMutex.RLock()
- defer fake.getHelloMutex.RUnlock()
- argsForCall := fake.getHelloArgsForCall[i]
- return argsForCall.arg1
-}
-
-func (fake *Model) GetHelloReturns(result1 protocol.HelloIntf) {
- fake.getHelloMutex.Lock()
- defer fake.getHelloMutex.Unlock()
- fake.GetHelloStub = nil
- fake.getHelloReturns = struct {
- result1 protocol.HelloIntf
- }{result1}
-}
-
-func (fake *Model) GetHelloReturnsOnCall(i int, result1 protocol.HelloIntf) {
- fake.getHelloMutex.Lock()
- defer fake.getHelloMutex.Unlock()
- fake.GetHelloStub = nil
- if fake.getHelloReturnsOnCall == nil {
- fake.getHelloReturnsOnCall = make(map[int]struct {
- result1 protocol.HelloIntf
- })
- }
- fake.getHelloReturnsOnCall[i] = struct {
- result1 protocol.HelloIntf
- }{result1}
-}
-
func (fake *Model) GetMtimeMapping(arg1 string, arg2 string) (fs.MtimeMapping, error) {
fake.getMtimeMappingMutex.Lock()
ret, specificReturn := fake.getMtimeMappingReturnsOnCall[len(fake.getMtimeMappingArgsForCall)]
@@ -2331,59 +2244,6 @@ func (fake *Model) NeedFolderFilesReturnsOnCall(i int, result1 []db.FileInfoTrun
}{result1, result2, result3, result4}
}
-func (fake *Model) NumConnections() int {
- fake.numConnectionsMutex.Lock()
- ret, specificReturn := fake.numConnectionsReturnsOnCall[len(fake.numConnectionsArgsForCall)]
- fake.numConnectionsArgsForCall = append(fake.numConnectionsArgsForCall, struct {
- }{})
- stub := fake.NumConnectionsStub
- fakeReturns := fake.numConnectionsReturns
- fake.recordInvocation("NumConnections", []interface{}{})
- fake.numConnectionsMutex.Unlock()
- if stub != nil {
- return stub()
- }
- if specificReturn {
- return ret.result1
- }
- return fakeReturns.result1
-}
-
-func (fake *Model) NumConnectionsCallCount() int {
- fake.numConnectionsMutex.RLock()
- defer fake.numConnectionsMutex.RUnlock()
- return len(fake.numConnectionsArgsForCall)
-}
-
-func (fake *Model) NumConnectionsCalls(stub func() int) {
- fake.numConnectionsMutex.Lock()
- defer fake.numConnectionsMutex.Unlock()
- fake.NumConnectionsStub = stub
-}
-
-func (fake *Model) NumConnectionsReturns(result1 int) {
- fake.numConnectionsMutex.Lock()
- defer fake.numConnectionsMutex.Unlock()
- fake.NumConnectionsStub = nil
- fake.numConnectionsReturns = struct {
- result1 int
- }{result1}
-}
-
-func (fake *Model) NumConnectionsReturnsOnCall(i int, result1 int) {
- fake.numConnectionsMutex.Lock()
- defer fake.numConnectionsMutex.Unlock()
- fake.NumConnectionsStub = nil
- if fake.numConnectionsReturnsOnCall == nil {
- fake.numConnectionsReturnsOnCall = make(map[int]struct {
- result1 int
- })
- }
- fake.numConnectionsReturnsOnCall[i] = struct {
- result1 int
- }{result1}
-}
-
func (fake *Model) OnHello(arg1 protocol.DeviceID, arg2 net.Addr, arg3 protocol.Hello) error {
fake.onHelloMutex.Lock()
ret, specificReturn := fake.onHelloReturnsOnCall[len(fake.onHelloArgsForCall)]
@@ -3419,8 +3279,8 @@ func (fake *Model) Invocations() map[string][][]interface{} {
defer fake.clusterConfigMutex.RUnlock()
fake.completionMutex.RLock()
defer fake.completionMutex.RUnlock()
- fake.connectionMutex.RLock()
- defer fake.connectionMutex.RUnlock()
+ fake.connectedToMutex.RLock()
+ defer fake.connectedToMutex.RUnlock()
fake.connectionStatsMutex.RLock()
defer fake.connectionStatsMutex.RUnlock()
fake.currentFolderFileMutex.RLock()
@@ -3449,8 +3309,6 @@ func (fake *Model) Invocations() map[string][][]interface{} {
defer fake.folderStatisticsMutex.RUnlock()
fake.getFolderVersionsMutex.RLock()
defer fake.getFolderVersionsMutex.RUnlock()
- fake.getHelloMutex.RLock()
- defer fake.getHelloMutex.RUnlock()
fake.getMtimeMappingMutex.RLock()
defer fake.getMtimeMappingMutex.RUnlock()
fake.globalDirectoryTreeMutex.RLock()
@@ -3465,8 +3323,6 @@ func (fake *Model) Invocations() map[string][][]interface{} {
defer fake.localChangedFolderFilesMutex.RUnlock()
fake.needFolderFilesMutex.RLock()
defer fake.needFolderFilesMutex.RUnlock()
- fake.numConnectionsMutex.RLock()
- defer fake.numConnectionsMutex.RUnlock()
fake.onHelloMutex.RLock()
defer fake.onHelloMutex.RUnlock()
fake.overrideMutex.RLock()
diff --git a/lib/model/model.go b/lib/model/model.go
index 02cf8636d..970d84c45 100644
--- a/lib/model/model.go
+++ b/lib/model/model.go
@@ -37,6 +37,7 @@ import (
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
+ "github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/semaphore"
"github.com/syncthing/syncthing/lib/stats"
@@ -108,6 +109,7 @@ type Model interface {
DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error)
FolderStatistics() (map[string]stats.FolderStatistics, error)
UsageReportingStats(report *contract.Report, version int, preview bool)
+ ConnectedTo(remoteID protocol.DeviceID) bool
PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error)
PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error)
@@ -124,8 +126,6 @@ type model struct {
// constructor parameters
cfg config.Wrapper
id protocol.DeviceID
- clientName string
- clientVersion string
db *db.Lowlevel
protectedFiles []string
evLogger events.Logger
@@ -143,6 +143,7 @@ type model struct {
fatalChan chan error
started chan struct{}
keyGen *protocol.KeyGenerator
+ promotionTimer *time.Timer
// fields protected by fmut
fmut sync.RWMutex
@@ -158,9 +159,11 @@ type model struct {
// fields protected by pmut
pmut sync.RWMutex
- conn map[protocol.DeviceID]protocol.Connection
+ connections map[string]protocol.Connection // connection ID -> connection
+ deviceConnIDs map[protocol.DeviceID][]string // device -> connection IDs (invariant: if the key exists, the value is len >= 1, with the primary connection at the start of the slice)
+ promotedConnID map[protocol.DeviceID]string // device -> latest promoted connection ID
connRequestLimiters map[protocol.DeviceID]*semaphore.Semaphore
- closed map[protocol.DeviceID]chan struct{}
+ closed map[string]chan struct{} // connection ID -> closed channel
helloMessages map[protocol.DeviceID]protocol.Hello
deviceDownloads map[protocol.DeviceID]*deviceDownloadState
remoteFolderStates map[protocol.DeviceID]map[string]remoteFolderState // deviceID -> folders
@@ -179,13 +182,11 @@ var folderFactories = make(map[config.FolderType]folderFactory)
var (
errDeviceUnknown = errors.New("unknown device")
errDevicePaused = errors.New("device is paused")
- errDeviceRemoved = errors.New("device has been removed")
ErrFolderPaused = errors.New("folder is paused")
ErrFolderNotRunning = errors.New("folder is not running")
ErrFolderMissing = errors.New("no such folder")
errNoVersioner = errors.New("folder has no versioner")
// errors about why a connection is closed
- errReplacingConnection = errors.New("replacing connection")
errStopped = errors.New("Syncthing is being stopped")
errEncryptionInvConfigLocal = errors.New("can't encrypt outgoing data because local data is encrypted (folder-type receive-encrypted)")
errEncryptionInvConfigRemote = errors.New("remote has encrypted data and encrypts that data for us - this is impossible")
@@ -203,7 +204,7 @@ var (
// NewModel creates and starts a new model. The model starts in read-only mode,
// where it sends index information to connected peers and responds to requests
// for file data without altering the local folder in any way.
-func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model {
+func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model {
spec := svcutil.SpecWithDebugLogger(l)
m := &model{
Supervisor: suture.New("model", spec),
@@ -211,8 +212,6 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
// constructor parameters
cfg: cfg,
id: id,
- clientName: clientName,
- clientVersion: clientVersion,
db: ldb,
protectedFiles: protectedFiles,
evLogger: evLogger,
@@ -226,6 +225,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
fatalChan: make(chan error),
started: make(chan struct{}),
keyGen: keyGen,
+ promotionTimer: time.NewTimer(0),
// fields protected by fmut
fmut: sync.NewRWMutex(),
@@ -240,16 +240,19 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
// fields protected by pmut
pmut: sync.NewRWMutex(),
- conn: make(map[protocol.DeviceID]protocol.Connection),
+ connections: make(map[string]protocol.Connection),
+ deviceConnIDs: make(map[protocol.DeviceID][]string),
+ promotedConnID: make(map[protocol.DeviceID]string),
connRequestLimiters: make(map[protocol.DeviceID]*semaphore.Semaphore),
- closed: make(map[protocol.DeviceID]chan struct{}),
+ closed: make(map[string]chan struct{}),
helloMessages: make(map[protocol.DeviceID]protocol.Hello),
deviceDownloads: make(map[protocol.DeviceID]*deviceDownloadState),
remoteFolderStates: make(map[protocol.DeviceID]map[string]remoteFolderState),
indexHandlers: newServiceMap[protocol.DeviceID, *indexHandlerRegistry](evLogger),
}
- for devID := range cfg.Devices() {
+ for devID, cfg := range cfg.Devices() {
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID)
+ m.setConnRequestLimitersPLocked(cfg)
}
m.Add(m.folderRunners)
m.Add(m.progressEmitter)
@@ -272,11 +275,18 @@ func (m *model) serve(ctx context.Context) error {
close(m.started)
- select {
- case <-ctx.Done():
- return ctx.Err()
- case err := <-m.fatalChan:
- return svcutil.AsFatalErr(err, svcutil.ExitError)
+ for {
+ select {
+ case <-ctx.Done():
+ l.Debugln(m, "context closed, stopping", ctx.Err())
+ return ctx.Err()
+ case err := <-m.fatalChan:
+ l.Debugln(m, "fatal error, stopping", err)
+ return svcutil.AsFatalErr(err, svcutil.ExitError)
+ case <-m.promotionTimer.C:
+ l.Debugln("promotion timer fired")
+ m.promoteConnections()
+ }
}
}
@@ -303,9 +313,9 @@ func (m *model) initFolders(cfg config.Configuration) error {
func (m *model) closeAllConnectionsAndWait() {
m.pmut.RLock()
- closed := make([]chan struct{}, 0, len(m.conn))
- for id, conn := range m.conn {
- closed = append(closed, m.closed[id])
+ closed := make([]chan struct{}, 0, len(m.connections))
+ for connID, conn := range m.connections {
+ closed = append(closed, m.closed[connID])
go conn.Close(errStopped)
}
m.pmut.RUnlock()
@@ -635,7 +645,7 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
// Transport stats
m.pmut.RLock()
- for _, conn := range m.conn {
+ for _, conn := range m.connections {
report.TransportStats[conn.Transport()]++
}
m.pmut.RUnlock()
@@ -699,22 +709,28 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
}
}
-type ConnectionInfo struct {
- protocol.Statistics
+type ConnectionStats struct {
+ protocol.Statistics // Total for primary + secondaries
+
Connected bool `json:"connected"`
Paused bool `json:"paused"`
- Address string `json:"address"`
ClientVersion string `json:"clientVersion"`
- Type string `json:"type"`
- IsLocal bool `json:"isLocal"`
- Crypto string `json:"crypto"`
+
+ Address string `json:"address"` // mirror values from Primary, for compatibility with <1.24.0
+ Type string `json:"type"` // mirror values from Primary, for compatibility with <1.24.0
+ IsLocal bool `json:"isLocal"` // mirror values from Primary, for compatibility with <1.24.0
+ Crypto string `json:"crypto"` // mirror values from Primary, for compatibility with <1.24.0
+
+ Primary ConnectionInfo `json:"primary,omitempty"`
+ Secondary []ConnectionInfo `json:"secondary,omitempty"`
}
-// NumConnections returns the current number of active connected devices.
-func (m *model) NumConnections() int {
- m.pmut.RLock()
- defer m.pmut.RUnlock()
- return len(m.conn)
+type ConnectionInfo struct {
+ protocol.Statistics
+ Address string `json:"address"`
+ Type string `json:"type"`
+ IsLocal bool `json:"isLocal"`
+ Crypto string `json:"crypto"`
}
// ConnectionStats returns a map with connection statistics for each device.
@@ -724,29 +740,59 @@ func (m *model) ConnectionStats() map[string]interface{} {
res := make(map[string]interface{})
devs := m.cfg.Devices()
- conns := make(map[string]ConnectionInfo, len(devs))
+ conns := make(map[string]ConnectionStats, len(devs))
for device, deviceCfg := range devs {
+ if device == m.id {
+ continue
+ }
hello := m.helloMessages[device]
versionString := hello.ClientVersion
if hello.ClientName != "syncthing" {
versionString = hello.ClientName + " " + hello.ClientVersion
}
- ci := ConnectionInfo{
- ClientVersion: strings.TrimSpace(versionString),
+ connIDs, ok := m.deviceConnIDs[device]
+ cs := ConnectionStats{
+ Connected: ok,
Paused: deviceCfg.Paused,
+ ClientVersion: strings.TrimSpace(versionString),
}
- if conn, ok := m.conn[device]; ok {
- ci.Type = conn.Type()
- ci.IsLocal = conn.IsLocal()
- ci.Crypto = conn.Crypto()
- ci.Connected = ok
- ci.Statistics = conn.Statistics()
- if addr := conn.RemoteAddr(); addr != nil {
- ci.Address = addr.String()
+ if ok {
+ conn := m.connections[connIDs[0]]
+
+ cs.Primary.Type = conn.Type()
+ cs.Primary.IsLocal = conn.IsLocal()
+ cs.Primary.Crypto = conn.Crypto()
+ cs.Primary.Statistics = conn.Statistics()
+ cs.Primary.Address = conn.RemoteAddr().String()
+
+ cs.Type = cs.Primary.Type
+ cs.IsLocal = cs.Primary.IsLocal
+ cs.Crypto = cs.Primary.Crypto
+ cs.Address = cs.Primary.Address
+ cs.Statistics = cs.Primary.Statistics
+
+ for _, connID := range connIDs[1:] {
+ conn = m.connections[connID]
+ sec := ConnectionInfo{
+ Statistics: conn.Statistics(),
+ Address: conn.RemoteAddr().String(),
+ Type: conn.Type(),
+ IsLocal: conn.IsLocal(),
+ Crypto: conn.Crypto(),
+ }
+ if sec.At.After(cs.At) {
+ cs.At = sec.At
+ }
+ if sec.StartedAt.Before(cs.StartedAt) {
+ cs.StartedAt = sec.StartedAt
+ }
+ cs.InBytesTotal += sec.InBytesTotal
+ cs.OutBytesTotal += sec.OutBytesTotal
+ cs.Secondary = append(cs.Secondary, sec)
}
}
- conns[device.String()] = ci
+ conns[device.String()] = cs
}
res["connections"] = conns
@@ -1138,17 +1184,16 @@ func (m *model) handleIndex(conn protocol.Connection, folder string, fs []protoc
}
m.pmut.RLock()
- indexHandler, ok := m.indexHandlers.Get(deviceID)
+ indexHandler, ok := m.getIndexHandlerPRLocked(conn)
m.pmut.RUnlock()
if !ok {
- // This should be impossible, as an index handler always exists for an
- // open connection, and this method can't be called on a closed
- // connection
+ // This should be impossible, as an index handler is registered when
+ // we send a cluster config, and that is what triggers index
+ // sending.
m.evLogger.Log(events.Failure, "index sender does not exist for connection on which indexes were received")
l.Debugf("%v for folder (ID %q) sent from device %q: missing index handler", op, folder, deviceID)
- return fmt.Errorf("index handler missing: %s", folder)
+ return fmt.Errorf("%s: %w", folder, ErrFolderNotRunning)
}
-
return indexHandler.ReceiveIndex(folder, fs, update, op)
}
@@ -1161,24 +1206,26 @@ type ClusterConfigReceivedEventData struct {
}
func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfig) error {
+ deviceID := conn.DeviceID()
+
+ if cm.Secondary {
+ // No handling of secondary connection ClusterConfigs; they merely
+ // indicate the connection is ready to start.
+ l.Debugf("Skipping secondary ClusterConfig from %v at %s", deviceID.Short(), conn)
+ return nil
+ }
+
// Check the peer device's announced folders against our own. Emits events
// for folders that we don't expect (unknown or not shared).
// Also, collect a list of folders we do share, and if he's interested in
// temporary indexes, subscribe the connection.
- deviceID := conn.DeviceID()
- l.Debugf("Handling ClusterConfig from %v", deviceID.Short())
-
- m.pmut.RLock()
- indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
- m.pmut.RUnlock()
- if !ok {
- panic("bug: ClusterConfig called on closed or nonexistent connection")
- }
+ l.Debugf("Handling ClusterConfig from %v at %s", deviceID.Short(), conn)
+ indexHandlerRegistry := m.ensureIndexHandler(conn)
deviceCfg, ok := m.cfg.Device(deviceID)
if !ok {
- l.Debugln("Device disappeared from config while processing cluster-config")
+ l.Debugf("Device %s disappeared from config while processing cluster-config", deviceID.Short())
return errDeviceUnknown
}
@@ -1198,11 +1245,11 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
}
}
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())
+ l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID.Short(), 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())
+ l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID.Short(), folder.Description())
return errMissingLocalInClusterConfig
}
ccDeviceInfos[folder.ID] = info
@@ -1260,12 +1307,16 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
})
if len(tempIndexFolders) > 0 {
+ var connOK bool
+ var conn protocol.Connection
m.pmut.RLock()
- conn, ok := m.conn[deviceID]
+ if connIDs, connIDOK := m.deviceConnIDs[deviceID]; connIDOK {
+ conn, connOK = m.connections[connIDs[0]]
+ }
m.pmut.RUnlock()
// In case we've got ClusterConfig, and the connection disappeared
// from infront of our nose.
- if ok {
+ if connOK {
m.progressEmitter.temporaryIndexSubscribe(conn, tempIndexFolders)
}
}
@@ -1291,6 +1342,57 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
return nil
}
+func (m *model) ensureIndexHandler(conn protocol.Connection) *indexHandlerRegistry {
+ deviceID := conn.DeviceID()
+ connID := conn.ConnectionID()
+
+ m.pmut.Lock()
+ defer m.pmut.Unlock()
+
+ indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
+ if ok && indexHandlerRegistry.conn.ConnectionID() == connID {
+ // This is an existing and proper index handler for this connection.
+ return indexHandlerRegistry
+ }
+
+ if ok {
+ // A handler exists, but it's for another connection than the one we
+ // now got a ClusterConfig on. This should be unusual as it means
+ // the other side has decided to start using a new primary
+ // connection but we haven't seen it close yet. Ideally it will
+ // close shortly by itself...
+ l.Infof("Abandoning old index handler for %s (%s) in favour of %s", deviceID.Short(), indexHandlerRegistry.conn.ConnectionID(), connID)
+ m.indexHandlers.RemoveAndWait(deviceID, 0)
+ }
+
+ // Create a new index handler for this device.
+ indexHandlerRegistry = newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger)
+ for id, fcfg := range m.folderCfgs {
+ l.Debugln("Registering folder", id, "for", deviceID.Short())
+ runner, _ := m.folderRunners.Get(id)
+ indexHandlerRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner)
+ }
+ m.indexHandlers.Add(deviceID, indexHandlerRegistry)
+
+ return indexHandlerRegistry
+}
+
+func (m *model) getIndexHandlerPRLocked(conn protocol.Connection) (*indexHandlerRegistry, bool) {
+ // Reads from index handlers, which requires pmut to be read locked
+
+ deviceID := conn.DeviceID()
+ connID := conn.ConnectionID()
+
+ indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
+ if ok && indexHandlerRegistry.conn.ConnectionID() == connID {
+ // This is an existing and proper index handler for this connection.
+ return indexHandlerRegistry, true
+ }
+
+ // There is no index handler, or it's not registered for this connection.
+ return nil, false
+}
+
func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.DeviceConfiguration, ccDeviceInfos map[string]*clusterConfigDeviceInfo, indexHandlers *indexHandlerRegistry) ([]string, map[string]remoteFolderState, error) {
var folderDevice config.FolderDeviceConfiguration
tempIndexFolders := make([]string, 0, len(folders))
@@ -1539,8 +1641,8 @@ func (m *model) sendClusterConfig(ids []protocol.DeviceID) {
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)
+ if connIDs, ok := m.deviceConnIDs[id]; ok {
+ ccConns = append(ccConns, m.connections[connIDs[0]])
}
}
m.pmut.RUnlock()
@@ -1777,33 +1879,62 @@ func (m *model) introduceDevice(device protocol.Device, introducerCfg config.Dev
// Closed is called when a connection has been closed
func (m *model) Closed(conn protocol.Connection, err error) {
- device := conn.DeviceID()
+ connID := conn.ConnectionID()
+ deviceID := conn.DeviceID()
+
m.pmut.Lock()
- conn, ok := m.conn[device]
+ conn, ok := m.connections[connID]
if !ok {
m.pmut.Unlock()
return
}
- delete(m.conn, device)
- delete(m.connRequestLimiters, device)
- delete(m.helloMessages, device)
- delete(m.deviceDownloads, device)
- delete(m.remoteFolderStates, device)
- closed := m.closed[device]
- delete(m.closed, device)
- wait := m.indexHandlers.RemoveAndWaitChan(device, 0)
+ closed := m.closed[connID]
+ delete(m.closed, connID)
+ delete(m.connections, connID)
+
+ removedIsPrimary := m.promotedConnID[deviceID] == connID
+ remainingConns := without(m.deviceConnIDs[deviceID], connID)
+ var wait <-chan error
+ if removedIsPrimary {
+ m.progressEmitter.temporaryIndexUnsubscribe(conn)
+ if idxh, ok := m.indexHandlers.Get(deviceID); ok && idxh.conn.ConnectionID() == connID {
+ wait = m.indexHandlers.RemoveAndWaitChan(deviceID, 0)
+ }
+ m.scheduleConnectionPromotion()
+ }
+ if len(remainingConns) == 0 {
+ // All device connections closed
+ delete(m.deviceConnIDs, deviceID)
+ delete(m.promotedConnID, deviceID)
+ delete(m.connRequestLimiters, deviceID)
+ delete(m.helloMessages, deviceID)
+ delete(m.remoteFolderStates, deviceID)
+ delete(m.deviceDownloads, deviceID)
+ } else {
+ // Some connections remain
+ m.deviceConnIDs[deviceID] = remainingConns
+ }
+
m.pmut.Unlock()
- <-wait
+ if wait != nil {
+ <-wait
+ }
+
+ m.fmut.RLock()
+ m.deviceDidCloseFRLocked(deviceID, time.Since(conn.EstablishedAt()))
+ m.fmut.RUnlock()
- m.progressEmitter.temporaryIndexUnsubscribe(conn)
- m.deviceDidClose(device, time.Since(conn.EstablishedAt()))
+ k := map[bool]string{false: "secondary", true: "primary"}[removedIsPrimary]
+ l.Infof("Lost %s connection to %s at %s: %v (%d remain)", k, deviceID.Short(), conn, err, len(remainingConns))
- l.Infof("Connection to %s at %s closed: %v", device, conn, err)
- m.evLogger.Log(events.DeviceDisconnected, map[string]string{
- "id": device.String(),
- "error": err.Error(),
- })
+ if len(remainingConns) == 0 {
+ l.Infof("Connection to %s at %s closed: %v", deviceID.Short(), conn, err)
+ m.evLogger.Log(events.DeviceDisconnected, map[string]string{
+ "id": deviceID.String(),
+ "error": err.Error(),
+ })
+ }
close(closed)
}
@@ -1852,36 +1983,36 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
if !ok {
// The folder might be already unpaused in the config, but not yet
// in the model.
- l.Debugf("Request from %s for file %s in unstarted folder %q", deviceID, name, folder)
+ l.Debugf("Request from %s for file %s in unstarted folder %q", deviceID.Short(), name, folder)
return nil, protocol.ErrGeneric
}
if !folderCfg.SharedWith(deviceID) {
- l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
+ l.Warnf("Request from %s for file %s in unshared folder %q", deviceID.Short(), name, folder)
return nil, protocol.ErrGeneric
}
if folderCfg.Paused {
- l.Debugf("Request from %s for file %s in paused folder %q", deviceID, name, folder)
+ l.Debugf("Request from %s for file %s in paused folder %q", deviceID.Short(), name, folder)
return nil, protocol.ErrGeneric
}
// Make sure the path is valid and in canonical form
if name, err = fs.Canonicalize(name); err != nil {
- l.Debugf("Request from %s in folder %q for invalid filename %s", deviceID, folder, name)
+ l.Debugf("Request from %s in folder %q for invalid filename %s", deviceID.Short(), folder, name)
return nil, protocol.ErrGeneric
}
if deviceID != protocol.LocalDeviceID {
- l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d t=%v", m, deviceID, folder, name, offset, size, fromTemporary)
+ l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d t=%v", m, deviceID.Short(), folder, name, offset, size, fromTemporary)
}
if fs.IsInternal(name) {
- l.Debugf("%v REQ(in) for internal file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
+ l.Debugf("%v REQ(in) for internal file: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrInvalid
}
if folderIgnores.Match(name).IsIgnored() {
- l.Debugf("%v REQ(in) for ignored file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
+ l.Debugf("%v REQ(in) for ignored file: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrInvalid
}
@@ -1908,7 +2039,7 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
folderFs := folderCfg.Filesystem(nil)
if err := osutil.TraversesSymlink(folderFs, filepath.Dir(name)); err != nil {
- l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
+ l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
}
@@ -1920,7 +2051,7 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
if info, err := folderFs.Lstat(tempFn); err != nil || !info.IsRegular() {
// Reject reads for anything that doesn't exist or is something
// other than a regular file.
- l.Debugf("%v REQ(in) failed stating temp file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
+ l.Debugf("%v REQ(in) failed stating temp file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
}
_, err := readOffsetIntoBuf(folderFs, tempFn, offset, res.data)
@@ -1934,13 +2065,13 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
if info, err := folderFs.Lstat(name); err != nil || !info.IsRegular() {
// Reject reads for anything that doesn't exist or is something
// other than a regular file.
- l.Debugf("%v REQ(in) failed stating file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
+ l.Debugf("%v REQ(in) failed stating file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
}
n, err := readOffsetIntoBuf(folderFs, name, offset, res.data)
if 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)
+ l.Debugf("%v REQ(in) file doesn't exist: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
} else if err == io.EOF {
// Read beyond end of file. This might indicate a problem, or it
@@ -1949,13 +2080,13 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
// next step take care of it, by only hashing the part we actually
// managed to read.
} 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)
+ l.Debugf("%v REQ(in) failed reading file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrGeneric
}
if folderCfg.Type != config.FolderTypeReceiveEncrypted && len(hash) > 0 && !scanner.Validate(res.data[:n], 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)
+ l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
return nil, protocol.ErrNoSuchFile
}
@@ -2074,15 +2205,12 @@ func (m *model) GetMtimeMapping(folder string, file string) (fs.MtimeMapping, er
return fs.GetMtimeMapping(fcfg.Filesystem(ffs), file)
}
-// Connection returns the current connection for device, and a boolean whether a connection was found.
-func (m *model) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) {
+// Connection returns if we are connected to the given device.
+func (m *model) ConnectedTo(deviceID protocol.DeviceID) bool {
m.pmut.RLock()
- cn, ok := m.conn[deviceID]
+ _, ok := m.deviceConnIDs[deviceID]
m.pmut.RUnlock()
- if ok {
- m.deviceWasSeen(deviceID)
- }
- return cn, ok
+ return ok
}
// LoadIgnores loads or refreshes the ignore patterns from disk, if the
@@ -2200,74 +2328,29 @@ func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
return nil
}
-// GetHello is called when we are about to connect to some remote device.
-func (m *model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
- name := ""
- if _, ok := m.cfg.Device(id); ok {
- // Set our name (from the config of our device ID) only if we already know about the other side device ID.
- if myCfg, ok := m.cfg.Device(m.id); ok {
- name = myCfg.Name
- }
- }
- return &protocol.Hello{
- DeviceName: name,
- ClientName: m.clientName,
- ClientVersion: m.clientVersion,
- }
-}
-
// AddConnection adds a new peer connection to the model. An initial index will
// be sent to the connected peer, thereafter index updates whenever the local
// folder changes.
func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
deviceID := conn.DeviceID()
- device, ok := m.cfg.Device(deviceID)
+ deviceCfg, ok := m.cfg.Device(deviceID)
if !ok {
l.Infoln("Trying to add connection to unknown device")
return
}
- // The slightly unusual locking sequence here is because we must acquire
- // fmut before pmut. (The locks can be *released* in any order.)
- m.fmut.RLock()
- m.pmut.Lock()
- if oldConn, ok := m.conn[deviceID]; ok {
- l.Infoln("Replacing old connection", oldConn, "with", conn, "for", deviceID)
- // There is an existing connection to this device that we are
- // replacing. We must close the existing connection and wait for the
- // close to complete before adding the new connection. We do the
- // actual close without holding pmut as the connection will call
- // back into Closed() for the cleanup.
- closed := m.closed[deviceID]
- m.fmut.RUnlock()
- m.pmut.Unlock()
- oldConn.Close(errReplacingConnection)
- <-closed
- // Again, lock fmut before pmut.
- m.fmut.RLock()
- m.pmut.Lock()
- }
-
- m.conn[deviceID] = conn
+ connID := conn.ConnectionID()
closed := make(chan struct{})
- m.closed[deviceID] = closed
- m.deviceDownloads[deviceID] = newDeviceDownloadState()
- indexRegistry := newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger)
- for id, fcfg := range m.folderCfgs {
- runner, _ := m.folderRunners.Get(id)
- indexRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner)
- }
- m.indexHandlers.Add(deviceID, indexRegistry)
- m.fmut.RUnlock()
- // 0: default, <0: no limiting
- switch {
- case device.MaxRequestKiB > 0:
- m.connRequestLimiters[deviceID] = semaphore.New(1024 * device.MaxRequestKiB)
- case device.MaxRequestKiB == 0:
- m.connRequestLimiters[deviceID] = semaphore.New(1024 * defaultPullerPendingKiB)
- }
+ m.pmut.Lock()
+
+ m.connections[connID] = conn
+ m.closed[connID] = closed
m.helloMessages[deviceID] = hello
+ m.deviceConnIDs[deviceID] = append(m.deviceConnIDs[deviceID], connID)
+ if m.deviceDownloads[deviceID] == nil {
+ m.deviceDownloads[deviceID] = newDeviceDownloadState()
+ }
event := map[string]string{
"id": deviceID.String(),
@@ -2284,17 +2367,15 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
m.evLogger.Log(events.DeviceConnected, event)
- l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
+ if len(m.deviceConnIDs[deviceID]) == 1 {
+ l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID.Short(), hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
+ } else {
+ l.Infof(`Additional connection (+%d) for device %s at %s`, len(m.deviceConnIDs[deviceID])-1, deviceID.Short(), conn)
+ }
- conn.Start()
m.pmut.Unlock()
- // Acquires fmut, so has to be done outside of pmut.
- cm, passwords := m.generateClusterConfig(deviceID)
- conn.SetFolderPasswords(passwords)
- conn.ClusterConfig(cm)
-
- if (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
+ if (deviceCfg.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
m.cfg.Modify(func(cfg *config.Configuration) {
for i := range cfg.Devices {
if cfg.Devices[i].DeviceID == deviceID {
@@ -2308,26 +2389,78 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
}
m.deviceWasSeen(deviceID)
+ m.scheduleConnectionPromotion()
+}
+
+func (m *model) scheduleConnectionPromotion() {
+ // Keeps deferring to prevent multiple executions in quick succession,
+ // e.g. if multiple connections to a single device are closed.
+ m.promotionTimer.Reset(time.Second)
+}
+
+// promoteConnections checks for devices that have connections, but where
+// the primary connection hasn't started index handlers etc. yet, and
+// promotes the primary connection to be the index handling one. This should
+// be called after adding new connections, and after closing a primary
+// device connection.
+func (m *model) promoteConnections() {
+ m.fmut.RLock() // for generateClusterConfigFRLocked
+ defer m.fmut.RUnlock()
+
+ m.pmut.Lock() // for most other things
+ defer m.pmut.Unlock()
+
+ for deviceID, connIDs := range m.deviceConnIDs {
+ cm, passwords := m.generateClusterConfigFRLocked(deviceID)
+ if m.promotedConnID[deviceID] != connIDs[0] {
+ // The previously promoted connection is not the current
+ // primary; we should promote the primary connection to be the
+ // index handling one. We do this by sending a ClusterConfig on
+ // it, which will cause the other side to start sending us index
+ // messages there. (On our side, we manage index handlers based
+ // on where we get ClusterConfigs from the peer.)
+ conn := m.connections[connIDs[0]]
+ l.Debugf("Promoting connection to %s at %s", deviceID.Short(), conn)
+ if conn.Statistics().StartedAt.IsZero() {
+ conn.SetFolderPasswords(passwords)
+ conn.Start()
+ }
+ conn.ClusterConfig(cm)
+ m.promotedConnID[deviceID] = connIDs[0]
+ }
+
+ // Make sure any other new connections also get started, and that
+ // they get a secondary-marked ClusterConfig.
+ for _, connID := range connIDs[1:] {
+ conn := m.connections[connID]
+ if conn.Statistics().StartedAt.IsZero() {
+ conn.SetFolderPasswords(passwords)
+ conn.Start()
+ conn.ClusterConfig(protocol.ClusterConfig{Secondary: true})
+ }
+ }
+ }
}
func (m *model) DownloadProgress(conn protocol.Connection, folder string, updates []protocol.FileDownloadProgressUpdate) error {
+ deviceID := conn.DeviceID()
+
m.fmut.RLock()
cfg, ok := m.folderCfgs[folder]
m.fmut.RUnlock()
- device := conn.DeviceID()
- if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(device) {
+ if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(deviceID) {
return nil
}
m.pmut.RLock()
- downloads := m.deviceDownloads[device]
+ downloads := m.deviceDownloads[deviceID]
m.pmut.RUnlock()
downloads.Update(folder, updates)
state := downloads.GetBlockCounts(folder)
m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{
- "device": device.String(),
+ "device": deviceID.String(),
"folder": folder,
"state": state,
})
@@ -2344,27 +2477,47 @@ func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
}
}
-func (m *model) deviceDidClose(deviceID protocol.DeviceID, duration time.Duration) {
- m.fmut.RLock()
- sr, ok := m.deviceStatRefs[deviceID]
- m.fmut.RUnlock()
- if ok {
+func (m *model) deviceDidCloseFRLocked(deviceID protocol.DeviceID, duration time.Duration) {
+ if sr, ok := m.deviceStatRefs[deviceID]; ok {
_ = sr.LastConnectionDuration(duration)
}
}
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) {
+ conn, connOK := m.requestConnectionForDevice(deviceID)
+ if !connOK {
+ return nil, fmt.Errorf("requestGlobal: no connection to device: %s", deviceID.Short())
+ }
+
+ l.Debugf("%v REQ(out): %s (%s): %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID.Short(), conn, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
+ return conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
+}
+
+// requestConnectionForDevice returns a connection to the given device, to
+// be used for sending a request. If there is only one device connection,
+// this is the one to use. If there are multiple then we avoid the first
+// ("primary") connection, which is dedicated to index data, and pick a
+// random one of the others.
+func (m *model) requestConnectionForDevice(deviceID protocol.DeviceID) (protocol.Connection, bool) {
m.pmut.RLock()
- nc, ok := m.conn[deviceID]
- m.pmut.RUnlock()
+ defer m.pmut.RUnlock()
+ connIDs, ok := m.deviceConnIDs[deviceID]
if !ok {
- return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID)
+ return nil, false
}
- 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)
+ // If there is an entry in deviceConns, it always contains at least one
+ // connection.
+ connID := connIDs[0]
+ if len(connIDs) > 1 {
+ // Pick a random connection of the non-primary ones
+ idx := rand.Intn(len(connIDs)-1) + 1
+ connID = connIDs[idx]
+ }
- return nc.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
+ conn, connOK := m.connections[connID]
+ return conn, connOK
}
func (m *model) ScanFolders() map[string]error {
@@ -2455,11 +2608,13 @@ func (m *model) numHashers(folder string) int {
// generateClusterConfig returns a ClusterConfigMessage that is correct and the
// set of folder passwords for the given peer device
func (m *model) generateClusterConfig(device protocol.DeviceID) (protocol.ClusterConfig, map[string]string) {
- var message protocol.ClusterConfig
-
m.fmut.RLock()
defer m.fmut.RUnlock()
+ return m.generateClusterConfigFRLocked(device)
+}
+func (m *model) generateClusterConfigFRLocked(device protocol.DeviceID) (protocol.ClusterConfig, map[string]string) {
+ var message protocol.ClusterConfig
folders := m.cfg.FolderList()
passwords := make(map[string]string, len(folders))
for _, folderCfg := range folders {
@@ -2771,7 +2926,7 @@ func (m *model) availabilityInSnapshotPRlocked(cfg config.FolderConfiguration, s
if state := m.remoteFolderStates[device][cfg.ID]; state != remoteFolderValid {
continue
}
- _, ok := m.conn[device]
+ _, ok := m.deviceConnIDs[device]
if ok {
availabilities = append(availabilities, Availability{ID: device, FromTemporary: false})
}
@@ -2904,13 +3059,6 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
}
}
- // Removing a device. We actually don't need to do anything.
- // Because folder config has changed (since the device lists do not match)
- // Folders for that had device got "restarted", which involves killing
- // connections to all devices that we were sharing the folder with.
- // At some point model.Close() will get called for that device which will
- // clean residue device state that is not part of any folder.
-
// Pausing a device, unpausing is handled by the connection service.
fromDevices := from.DeviceMap()
toDevices := to.DeviceMap()
@@ -2941,7 +3089,14 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
l.Infoln("Resuming", deviceID)
m.evLogger.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
}
+
+ if toCfg.MaxRequestKiB != fromCfg.MaxRequestKiB {
+ m.pmut.Lock()
+ m.setConnRequestLimitersPLocked(toCfg)
+ m.pmut.Unlock()
+ }
}
+
// Clean up after removed devices
removedDevices := make([]protocol.DeviceID, 0, len(fromDevices))
m.fmut.Lock()
@@ -2955,14 +3110,18 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
m.pmut.RLock()
for _, id := range closeDevices {
delete(clusterConfigDevices, id)
- if conn, ok := m.conn[id]; ok {
- go conn.Close(errDevicePaused)
+ if conns, ok := m.deviceConnIDs[id]; ok {
+ for _, connID := range conns {
+ go m.connections[connID].Close(errDevicePaused)
+ }
}
}
for _, id := range removedDevices {
delete(clusterConfigDevices, id)
- if conn, ok := m.conn[id]; ok {
- go conn.Close(errDeviceRemoved)
+ if conns, ok := m.deviceConnIDs[id]; ok {
+ for _, connID := range conns {
+ go m.connections[connID].Close(errDevicePaused)
+ }
}
}
m.pmut.RUnlock()
@@ -2986,6 +3145,17 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
return true
}
+func (m *model) setConnRequestLimitersPLocked(cfg config.DeviceConfiguration) {
+ // Touches connRequestLimiters which is protected by pmut.
+ // 0: default, <0: no limiting
+ switch {
+ case cfg.MaxRequestKiB > 0:
+ m.connRequestLimiters[cfg.DeviceID] = semaphore.New(1024 * cfg.MaxRequestKiB)
+ case cfg.MaxRequestKiB == 0:
+ m.connRequestLimiters[cfg.DeviceID] = semaphore.New(1024 * defaultPullerPendingKiB)
+ }
+}
+
func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.DeviceConfiguration, existingFolders map[string]config.FolderConfiguration, ignoredDevices deviceIDSet, removedFolders map[string]struct{}) {
var removedPendingFolders []map[string]string
pendingFolders, err := m.db.PendingFolders()
@@ -3332,3 +3502,12 @@ type redactedError struct {
error
redacted error
}
+
+func without[E comparable, S ~[]E](s S, e E) S {
+ for i, x := range s {
+ if x == e {
+ return append(s[:i], s[i+1:]...)
+ }
+ }
+ return s
+}
diff --git a/lib/model/model_test.go b/lib/model/model_test.go
index 508173285..3fbe60d57 100644
--- a/lib/model/model_test.go
+++ b/lib/model/model_test.go
@@ -270,7 +270,7 @@ func TestDeviceRename(t *testing.T) {
cfg, cfgCancel := newConfigWrapper(rawCfg)
defer cfgCancel()
- m := newModel(t, cfg, myID, "syncthing", "dev", nil)
+ m := newModel(t, cfg, myID, nil)
if cfg.Devices()[device1].Name != "" {
t.Errorf("Device already has a name")
@@ -422,7 +422,7 @@ func TestClusterConfig(t *testing.T) {
wrapper, cancel := newConfigWrapper(cfg)
defer cancel()
- m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
+ m := newModel(t, wrapper, myID, nil)
m.ServeBackground()
defer cleanupModel(m)
@@ -903,7 +903,7 @@ func TestIssue5063(t *testing.T) {
defer cancel()
m.pmut.Lock()
- for _, c := range m.conn {
+ for _, c := range m.connections {
conn := c.(*fakeConnection)
conn.CloseCalls(func(_ error) {})
defer m.Closed(c, errStopped) // to unblock deferred m.Stop()
@@ -1626,7 +1626,7 @@ func TestROScanRecovery(t *testing.T) {
},
})
defer cancel()
- m := newModel(t, cfg, myID, "syncthing", "dev", nil)
+ m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
@@ -1673,7 +1673,7 @@ func TestRWScanRecovery(t *testing.T) {
},
})
defer cancel()
- m := newModel(t, cfg, myID, "syncthing", "dev", nil)
+ m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
@@ -2073,7 +2073,7 @@ func TestIssue4357(t *testing.T) {
// Create a separate wrapper not to pollute other tests.
wrapper, cancel := newConfigWrapper(config.Configuration{Version: config.CurrentVersion})
defer cancel()
- m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
+ m := newModel(t, wrapper, myID, nil)
m.ServeBackground()
defer cleanupModel(m)
@@ -2125,7 +2125,7 @@ func TestIssue4357(t *testing.T) {
}
func TestIndexesForUnknownDevicesDropped(t *testing.T) {
- m := newModel(t, defaultCfgWrapper, myID, "syncthing", "dev", nil)
+ m := newModel(t, defaultCfgWrapper, myID, nil)
files := newFileSet(t, "default", m.db)
files.Drop(device1)
@@ -2231,7 +2231,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
t.Error("device still in config")
}
- if _, ok := m.conn[device2]; ok {
+ if _, ok := m.deviceConnIDs[device2]; ok {
t.Error("conn not missing")
}
@@ -2374,7 +2374,7 @@ func TestCustomMarkerName(t *testing.T) {
ffs := fcfg.Filesystem(nil)
- m := newModel(t, cfg, myID, "syncthing", "dev", nil)
+ m := newModel(t, cfg, myID, nil)
set := newFileSet(t, "default", m.db)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
@@ -2736,7 +2736,7 @@ func TestIssue4094(t *testing.T) {
// Create a separate wrapper not to pollute other tests.
wrapper, cancel := newConfigWrapper(config.Configuration{Version: config.CurrentVersion})
defer cancel()
- m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
+ m := newModel(t, wrapper, myID, nil)
m.ServeBackground()
defer cleanupModel(m)
@@ -2971,6 +2971,7 @@ func TestConnCloseOnRestart(t *testing.T) {
br := &testutil.BlockingRW{}
nw := &testutil.NoopRW{}
ci := &protocolmocks.ConnectionInfo{}
+ ci.ConnectionIDReturns(srand.String(16))
m.AddConnection(protocol.NewConnection(device1, br, nw, testutil.NoopCloser{}, m, ci, protocol.CompressionNever, nil, m.keyGen), protocol.Hello{})
m.pmut.RLock()
if len(m.closed) != 1 {
@@ -3639,6 +3640,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
})
m.AddConnection(fc1, protocol.Hello{})
m.AddConnection(fc2, protocol.Hello{})
+ m.promoteConnections()
// Initial CCs
select {
@@ -3690,7 +3692,7 @@ func TestIssue6961(t *testing.T) {
must(t, err)
waiter.Wait()
// Always recalc/repair when opening a fileset.
- m := newModel(t, wcfg, myID, "syncthing", "dev", nil)
+ m := newModel(t, wcfg, myID, nil)
m.db.Close()
m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond))
if err != nil {
@@ -3952,7 +3954,7 @@ func TestCCFolderNotRunning(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
tfs := fcfg.Filesystem(nil)
- m := newModel(t, w, myID, "syncthing", "dev", nil)
+ m := newModel(t, w, myID, nil)
defer cleanupModelAndRemoveDir(m, tfs.URI())
// A connection can happen before all the folders are started.
diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go
index 497342481..aa285c4ce 100644
--- a/lib/model/requests_test.go
+++ b/lib/model/requests_test.go
@@ -1214,7 +1214,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
// Initialise db with an entry and then stop everything again
must(t, tfs.Mkdir(dir1, 0o777))
- m := newModel(t, w, myID, "syncthing", "dev", nil)
+ m := newModel(t, w, myID, nil)
defer cleanupModelAndRemoveDir(m, tfs.URI())
m.ServeBackground()
m.ScanFolders()
@@ -1223,7 +1223,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
// Add connection (sends incoming cluster config) before starting the new model
m = &testModel{
- model: NewModel(m.cfg, m.id, m.clientName, m.clientVersion, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
+ model: NewModel(m.cfg, m.id, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
evCancel: m.evCancel,
stopped: make(chan struct{}),
}
diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go
index a558d9a9d..ccd852868 100644
--- a/lib/model/testutils_test.go
+++ b/lib/model/testutils_test.go
@@ -39,7 +39,9 @@ func init() {
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
device1Conn.DeviceIDReturns(device1)
+ device1Conn.ConnectionIDReturns(rand.String(16))
device2Conn.DeviceIDReturns(device2)
+ device2Conn.ConnectionIDReturns(rand.String(16))
cfg := config.New(myID)
cfg.Options.MinHomeDiskFree.Value = 0 // avoids unnecessary free space checks
@@ -127,7 +129,7 @@ func setupModelWithConnectionFromWrapper(t testing.TB, w config.Wrapper) (*testM
func setupModel(t testing.TB, w config.Wrapper) *testModel {
t.Helper()
- m := newModel(t, w, myID, "syncthing", "dev", nil)
+ m := newModel(t, w, myID, nil)
m.ServeBackground()
<-m.started
@@ -144,14 +146,14 @@ type testModel struct {
stopped chan struct{}
}
-func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, protectedFiles []string) *testModel {
+func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedFiles []string) *testModel {
t.Helper()
evLogger := events.NewLogger()
ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger)
if err != nil {
t.Fatal(err)
}
- m := NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
+ m := NewModel(cfg, id, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
ctx, cancel := context.WithCancel(context.Background())
go evLogger.Serve(ctx)
return &testModel{
diff --git a/lib/protocol/bep.pb.go b/lib/protocol/bep.pb.go
index 38a258a71..a86589ade 100644
--- a/lib/protocol/bep.pb.go
+++ b/lib/protocol/bep.pb.go
@@ -211,9 +211,11 @@ func (FileDownloadProgressUpdateType) EnumDescriptor() ([]byte, []int) {
}
type Hello struct {
- DeviceName string `protobuf:"bytes,1,opt,name=device_name,json=deviceName,proto3" json:"deviceName" xml:"deviceName"`
- ClientName string `protobuf:"bytes,2,opt,name=client_name,json=clientName,proto3" json:"clientName" xml:"clientName"`
- ClientVersion string `protobuf:"bytes,3,opt,name=client_version,json=clientVersion,proto3" json:"clientVersion" xml:"clientVersion"`
+ DeviceName string `protobuf:"bytes,1,opt,name=device_name,json=deviceName,proto3" json:"deviceName" xml:"deviceName"`
+ ClientName string `protobuf:"bytes,2,opt,name=client_name,json=clientName,proto3" json:"clientName" xml:"clientName"`
+ ClientVersion string `protobuf:"bytes,3,opt,name=client_version,json=clientVersion,proto3" json:"clientVersion" xml:"clientVersion"`
+ NumConnections int `protobuf:"varint,4,opt,name=num_connections,json=numConnections,proto3,casttype=int" json:"numConnections" xml:"numConnections"`
+ Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp" xml:"timestamp"`
}
func (m *Hello) Reset() { *m = Hello{} }
@@ -288,7 +290,8 @@ func (m *Header) XXX_DiscardUnknown() {
var xxx_messageInfo_Header proto.InternalMessageInfo
type ClusterConfig struct {
- Folders []Folder `protobuf:"bytes,1,rep,name=folders,proto3" json:"folders" xml:"folder"`
+ Folders []Folder `protobuf:"bytes,1,rep,name=folders,proto3" json:"folders" xml:"folder"`
+ Secondary bool `protobuf:"varint,2,opt,name=secondary,proto3" json:"secondary" xml:"secondary"`
}
func (m *ClusterConfig) Reset() { *m = ClusterConfig{} }
@@ -1142,205 +1145,211 @@ func init() {
func init() { proto.RegisterFile("lib/protocol/bep.proto", fileDescriptor_311ef540e10d9705) }
var fileDescriptor_311ef540e10d9705 = []byte{
- // 3163 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0x4d, 0x6c, 0x1b, 0xc7,
- 0xbd, 0x17, 0xc5, 0x0f, 0x51, 0x23, 0xc9, 0xa6, 0xc6, 0x5f, 0x0c, 0x6d, 0x6b, 0xf9, 0x26, 0xce,
- 0x7b, 0x8a, 0xf2, 0x62, 0x27, 0xca, 0xc7, 0xcb, 0x8b, 0xf3, 0x1c, 0x88, 0x22, 0x25, 0x33, 0x96,
- 0x49, 0x65, 0x28, 0xdb, 0xb1, 0xf1, 0x1e, 0x88, 0x15, 0x77, 0x44, 0x2d, 0x4c, 0xee, 0xf2, 0xed,
- 0x52, 0x5f, 0x41, 0x2f, 0x6d, 0x80, 0x20, 0xd0, 0xa1, 0x28, 0x72, 0x2a, 0x8a, 0x0a, 0x0d, 0x7a,
- 0xe9, 0xad, 0x40, 0x0f, 0xbd, 0xe4, 0xd4, 0xa3, 0x8f, 0x46, 0x80, 0x02, 0x45, 0x0f, 0x0b, 0xc4,
- 0xbe, 0xb4, 0xec, 0x8d, 0xc7, 0x9e, 0x8a, 0xf9, 0xcf, 0xec, 0xec, 0xac, 0x3e, 0x52, 0x39, 0x39,
- 0xf4, 0x64, 0xfe, 0x7f, 0xff, 0xdf, 0xff, 0xbf, 0xb3, 0xf3, 0xff, 0x9a, 0x59, 0x19, 0x5d, 0xec,
- 0xd8, 0xeb, 0x37, 0x7a, 0x9e, 0xdb, 0x77, 0x5b, 0x6e, 0xe7, 0xc6, 0x3a, 0xeb, 0x5d, 0x07, 0x01,
- 0x67, 0x43, 0xac, 0x30, 0xce, 0x76, 0xfb, 0x02, 0x2c, 0xbc, 0xec, 0xb1, 0x9e, 0xeb, 0x0b, 0xfa,
- 0xfa, 0xd6, 0xc6, 0x8d, 0xb6, 0xdb, 0x76, 0x41, 0x80, 0x5f, 0x82, 0x44, 0x9e, 0x25, 0x50, 0xfa,
- 0x36, 0xeb, 0x74, 0x5c, 0xbc, 0x88, 0x26, 0x2c, 0xb6, 0x6d, 0xb7, 0x58, 0xd3, 0x31, 0xbb, 0x2c,
- 0x9f, 0x28, 0x26, 0x66, 0xc7, 0x4b, 0x64, 0x10, 0x18, 0x48, 0xc0, 0x35, 0xb3, 0xcb, 0x86, 0x81,
- 0x91, 0xdb, 0xed, 0x76, 0xde, 0x27, 0x11, 0x44, 0xa8, 0xa6, 0xe7, 0x4e, 0x5a, 0x1d, 0x9b, 0x39,
- 0x7d, 0xe1, 0x64, 0x34, 0x72, 0x22, 0xe0, 0x98, 0x93, 0x08, 0x22, 0x54, 0xd3, 0xe3, 0x3a, 0x3a,
- 0x23, 0x9d, 0x6c, 0x33, 0xcf, 0xb7, 0x5d, 0x27, 0x9f, 0x04, 0x3f, 0xb3, 0x83, 0xc0, 0x98, 0x12,
- 0x9a, 0xfb, 0x42, 0x31, 0x0c, 0x8c, 0x73, 0x9a, 0x2b, 0x89, 0x12, 0x1a, 0x67, 0x91, 0xdf, 0x25,
- 0x50, 0xe6, 0x36, 0x33, 0x2d, 0xe6, 0xe1, 0x05, 0x94, 0xea, 0xef, 0xf5, 0xc4, 0xeb, 0x9d, 0x99,
- 0xbf, 0x70, 0x3d, 0xdc, 0xb8, 0xeb, 0x77, 0x99, 0xef, 0x9b, 0x6d, 0xb6, 0xb6, 0xd7, 0x63, 0xa5,
- 0x8b, 0x83, 0xc0, 0x00, 0xda, 0x30, 0x30, 0x10, 0xf8, 0xe7, 0x02, 0xa1, 0x80, 0x61, 0x0b, 0x4d,
- 0xb4, 0xdc, 0x6e, 0xcf, 0x63, 0x3e, 0xac, 0x6d, 0x14, 0x3c, 0x5d, 0x39, 0xe2, 0x69, 0x31, 0xe2,
- 0x94, 0xae, 0x0d, 0x02, 0x43, 0x37, 0x1a, 0x06, 0xc6, 0xb4, 0x58, 0x77, 0x84, 0x11, 0xaa, 0x33,
- 0xc8, 0xff, 0xa2, 0xa9, 0xc5, 0xce, 0x96, 0xdf, 0x67, 0xde, 0xa2, 0xeb, 0x6c, 0xd8, 0x6d, 0x7c,
- 0x07, 0x8d, 0x6d, 0xb8, 0x1d, 0x8b, 0x79, 0x7e, 0x3e, 0x51, 0x4c, 0xce, 0x4e, 0xcc, 0xe7, 0xa2,
- 0x47, 0x2e, 0x81, 0xa2, 0x64, 0x3c, 0x09, 0x8c, 0x91, 0x41, 0x60, 0x84, 0xc4, 0x61, 0x60, 0x4c,
- 0xc2, 0x63, 0x84, 0x4c, 0x68, 0xa8, 0x20, 0x5f, 0xa7, 0x50, 0x46, 0x18, 0xe1, 0xeb, 0x68, 0xd4,
- 0xb6, 0x64, 0xb8, 0x67, 0x9e, 0x05, 0xc6, 0x68, 0xb5, 0x3c, 0x08, 0x8c, 0x51, 0xdb, 0x1a, 0x06,
- 0x46, 0x16, 0xac, 0x6d, 0x8b, 0x7c, 0xf9, 0xf4, 0xda, 0x68, 0xb5, 0x4c, 0x47, 0x6d, 0x0b, 0x5f,
- 0x47, 0xe9, 0x8e, 0xb9, 0xce, 0x3a, 0x32, 0xb8, 0xf9, 0x41, 0x60, 0x08, 0x60, 0x18, 0x18, 0x13,
- 0xc0, 0x07, 0x89, 0x50, 0x81, 0xe2, 0x9b, 0x68, 0xdc, 0x63, 0xa6, 0xd5, 0x74, 0x9d, 0xce, 0x1e,
- 0x04, 0x32, 0x5b, 0x9a, 0x19, 0x04, 0x46, 0x96, 0x83, 0x75, 0xa7, 0xb3, 0x37, 0x0c, 0x8c, 0x33,
- 0x60, 0x16, 0x02, 0x84, 0x2a, 0x1d, 0x6e, 0x22, 0x6c, 0xb7, 0x1d, 0xd7, 0x63, 0xcd, 0x1e, 0xf3,
- 0xba, 0x36, 0x6c, 0x8d, 0x9f, 0x4f, 0x81, 0x97, 0x37, 0x06, 0x81, 0x31, 0x2d, 0xb4, 0xab, 0x91,
- 0x72, 0x18, 0x18, 0x97, 0xc4, 0xaa, 0x0f, 0x6b, 0x08, 0x3d, 0xca, 0xc6, 0x77, 0xd0, 0x94, 0x7c,
- 0x80, 0xc5, 0x3a, 0xac, 0xcf, 0xf2, 0x69, 0xf0, 0xfd, 0xef, 0x83, 0xc0, 0x98, 0x14, 0x8a, 0x32,
- 0xe0, 0xc3, 0xc0, 0xc0, 0x9a, 0x5b, 0x01, 0x12, 0x1a, 0xe3, 0x60, 0x0b, 0x9d, 0xb7, 0x6c, 0xdf,
- 0x5c, 0xef, 0xb0, 0x66, 0x9f, 0x75, 0x7b, 0x4d, 0xdb, 0xb1, 0xd8, 0x2e, 0xf3, 0xf3, 0x19, 0xf0,
- 0x39, 0x3f, 0x08, 0x0c, 0x2c, 0xf5, 0x6b, 0xac, 0xdb, 0xab, 0x0a, 0xed, 0x30, 0x30, 0xf2, 0xa2,
- 0xa6, 0x8e, 0xa8, 0x08, 0x3d, 0x86, 0x8f, 0xe7, 0x51, 0xa6, 0x67, 0x6e, 0xf9, 0xcc, 0xca, 0x8f,
- 0x81, 0xdf, 0xc2, 0x20, 0x30, 0x24, 0xa2, 0x02, 0x2e, 0x44, 0x42, 0x25, 0xce, 0x93, 0x47, 0x54,
- 0xa9, 0x9f, 0xcf, 0x1d, 0x4e, 0x9e, 0x32, 0x28, 0xa2, 0xe4, 0x91, 0x44, 0xe5, 0x4b, 0xc8, 0x84,
- 0x86, 0x0a, 0xf2, 0x87, 0x0c, 0xca, 0x08, 0x23, 0x5c, 0x52, 0xc9, 0x33, 0x59, 0x9a, 0xe7, 0x0e,
- 0xfe, 0x1c, 0x18, 0x59, 0xa1, 0xab, 0x96, 0x4f, 0x4a, 0xa6, 0x2f, 0x9e, 0x5e, 0x4b, 0x68, 0x09,
- 0x35, 0x87, 0x52, 0x5a, 0xb3, 0x80, 0xda, 0x73, 0x44, 0x9b, 0x10, 0xb5, 0xe7, 0x40, 0x83, 0x00,
- 0x0c, 0x7f, 0x80, 0xc6, 0x4d, 0xcb, 0xe2, 0x35, 0xc2, 0xfc, 0x7c, 0xb2, 0x98, 0xe4, 0x39, 0x3b,
- 0x08, 0x8c, 0x08, 0x1c, 0x06, 0xc6, 0x14, 0x58, 0x49, 0x84, 0xd0, 0x48, 0x87, 0xff, 0x2f, 0x5e,
- 0xb9, 0xa9, 0xc3, 0x3d, 0xe0, 0x87, 0x95, 0x2c, 0xcf, 0xf4, 0x16, 0xf3, 0x64, 0xeb, 0x4b, 0x8b,
- 0x82, 0xe2, 0x99, 0xce, 0x41, 0xd9, 0xf8, 0x44, 0xa6, 0x87, 0x00, 0xa1, 0x4a, 0x87, 0x97, 0xd1,
- 0x64, 0xd7, 0xdc, 0x6d, 0xfa, 0xec, 0xff, 0xb7, 0x98, 0xd3, 0x62, 0x90, 0x33, 0x49, 0xb1, 0x8a,
- 0xae, 0xb9, 0xdb, 0x90, 0xb0, 0x5a, 0x85, 0x86, 0x11, 0xaa, 0x33, 0x70, 0x09, 0x21, 0xdb, 0xe9,
- 0x7b, 0xae, 0xb5, 0xd5, 0x62, 0x9e, 0x4c, 0x11, 0xe8, 0xc0, 0x11, 0xaa, 0x3a, 0x70, 0x04, 0x11,
- 0xaa, 0xe9, 0x71, 0x1b, 0x65, 0x21, 0x77, 0x9b, 0xb6, 0x95, 0xcf, 0x16, 0x13, 0xb3, 0xa9, 0xd2,
- 0x8a, 0x0c, 0xee, 0x18, 0x64, 0x21, 0xc4, 0x36, 0xfc, 0xc9, 0x73, 0x06, 0xd8, 0x55, 0x4b, 0xed,
- 0xbe, 0x94, 0x79, 0xdf, 0x08, 0x69, 0xbf, 0x88, 0x7e, 0xd2, 0x90, 0x8f, 0x7f, 0x84, 0x0a, 0xfe,
- 0x63, 0x9b, 0x57, 0x8a, 0x78, 0x76, 0xdf, 0x76, 0x9d, 0xa6, 0xc7, 0xba, 0xee, 0xb6, 0xd9, 0xf1,
- 0xf3, 0xe3, 0xb0, 0xf8, 0x5b, 0x83, 0xc0, 0xc8, 0x73, 0x56, 0x55, 0x23, 0x51, 0xc9, 0x19, 0x06,
- 0xc6, 0x0c, 0x3c, 0xf1, 0x24, 0x02, 0xa1, 0x27, 0xda, 0xe2, 0x5d, 0xf4, 0x12, 0x73, 0x5a, 0xde,
- 0x5e, 0x0f, 0x1e, 0xdb, 0x33, 0x7d, 0x7f, 0xc7, 0xf5, 0xac, 0x66, 0xdf, 0x7d, 0xcc, 0x9c, 0x3c,
- 0x82, 0xa4, 0xfe, 0x60, 0x10, 0x18, 0x97, 0x22, 0xd2, 0xaa, 0xe4, 0xac, 0x71, 0xca, 0x30, 0x30,
- 0xae, 0xc2, 0xb3, 0x4f, 0xd0, 0x13, 0x7a, 0x92, 0x25, 0xf9, 0x49, 0x02, 0xa5, 0x61, 0x33, 0x78,
- 0x35, 0x8b, 0xa6, 0x2c, 0x5b, 0x30, 0x54, 0xb3, 0x40, 0x8e, 0xb4, 0x6f, 0x89, 0xe3, 0x0a, 0x4a,
- 0x6f, 0xd8, 0x1d, 0xe6, 0xe7, 0x47, 0xa1, 0x96, 0xb1, 0x36, 0x08, 0xec, 0x0e, 0xab, 0x3a, 0x1b,
- 0x6e, 0xe9, 0xb2, 0xac, 0x66, 0x41, 0x54, 0xb5, 0xc4, 0x25, 0x42, 0x05, 0x48, 0xbe, 0x48, 0xa0,
- 0x09, 0x58, 0xc4, 0xbd, 0x9e, 0x65, 0xf6, 0xd9, 0xbf, 0x72, 0x29, 0x9f, 0x4f, 0xa1, 0x6c, 0x68,
- 0xa0, 0x1a, 0x42, 0xe2, 0x14, 0x0d, 0x61, 0x0e, 0xa5, 0x7c, 0xfb, 0x53, 0x06, 0x83, 0x25, 0x29,
- 0xb8, 0x5c, 0x56, 0x5c, 0x2e, 0x10, 0x0a, 0x18, 0xfe, 0x10, 0xa1, 0xae, 0x6b, 0xd9, 0x1b, 0x36,
- 0xb3, 0x9a, 0x3e, 0x14, 0x68, 0xb2, 0x54, 0xe4, 0xdd, 0x23, 0x44, 0x1b, 0xc3, 0xc0, 0x38, 0x2b,
- 0xca, 0x2b, 0x44, 0x08, 0x8d, 0xb4, 0xbc, 0x7f, 0x28, 0x07, 0xeb, 0x7b, 0xf9, 0x49, 0xa8, 0x8c,
- 0x0f, 0xc2, 0xca, 0x68, 0x6c, 0xba, 0x5e, 0x1f, 0xca, 0x41, 0x3d, 0xa6, 0xb4, 0xa7, 0x4a, 0x2d,
- 0x82, 0x08, 0xaf, 0x04, 0x49, 0xa6, 0x1a, 0x15, 0xaf, 0xa0, 0xb1, 0xf0, 0xc0, 0xc3, 0x33, 0x3f,
- 0xd6, 0xa4, 0xef, 0xb3, 0x56, 0xdf, 0xf5, 0x4a, 0xc5, 0xb0, 0x49, 0x6f, 0xab, 0x03, 0x90, 0x28,
- 0xb8, 0xed, 0xf0, 0xe8, 0x13, 0x6a, 0xf0, 0xfb, 0x28, 0xab, 0x9a, 0x09, 0x82, 0x77, 0x85, 0x66,
- 0xe4, 0x47, 0x9d, 0x44, 0x34, 0x23, 0x5f, 0xb5, 0x11, 0xa5, 0xc3, 0x1f, 0xa1, 0xcc, 0x7a, 0xc7,
- 0x6d, 0x3d, 0x0e, 0xa7, 0xc5, 0xb9, 0x68, 0x21, 0x25, 0x8e, 0x43, 0x5c, 0xaf, 0xca, 0xb5, 0x48,
- 0xaa, 0x1a, 0xff, 0x20, 0x12, 0x2a, 0x61, 0x7e, 0x9a, 0xf3, 0xf7, 0xba, 0x1d, 0xdb, 0x79, 0xdc,
- 0xec, 0x9b, 0x5e, 0x9b, 0xf5, 0xf3, 0xd3, 0xd1, 0x69, 0x4e, 0x6a, 0xd6, 0x40, 0xa1, 0x4e, 0x73,
- 0x31, 0x94, 0xd0, 0x38, 0x8b, 0x9f, 0x31, 0x85, 0xeb, 0xe6, 0xa6, 0xe9, 0x6f, 0xe6, 0x31, 0xd4,
- 0x29, 0x74, 0x38, 0x01, 0xdf, 0x36, 0xfd, 0x4d, 0xb5, 0xed, 0x11, 0x44, 0xa8, 0xa6, 0xc7, 0xb7,
- 0xd0, 0xb8, 0xac, 0x4d, 0x66, 0xe5, 0xcf, 0x81, 0x0b, 0x48, 0x05, 0x05, 0xaa, 0x54, 0x50, 0x08,
- 0xa1, 0x91, 0x16, 0x97, 0xe4, 0x39, 0x52, 0x9c, 0xfe, 0x2e, 0x1e, 0x4d, 0xfb, 0x53, 0x1c, 0x24,
- 0x97, 0xd0, 0xc4, 0xe1, 0x53, 0xcd, 0x94, 0xe8, 0xf8, 0xbd, 0xd8, 0x79, 0x46, 0x74, 0xfc, 0x9e,
- 0x7e, 0x92, 0xd1, 0x19, 0xf8, 0x23, 0x2d, 0x2d, 0x1d, 0x3f, 0x3f, 0x51, 0x4c, 0xcc, 0xa6, 0x4b,
- 0xaf, 0xea, 0x79, 0x58, 0xf3, 0x8f, 0xe4, 0x61, 0xcd, 0x27, 0x7f, 0x0f, 0x8c, 0xa4, 0xed, 0xf4,
- 0xa9, 0x46, 0xc3, 0x1b, 0x48, 0xec, 0x52, 0x13, 0xaa, 0x6a, 0x0a, 0x5c, 0x2d, 0x3f, 0x0b, 0x8c,
- 0x49, 0x6a, 0xee, 0x40, 0xe8, 0x1b, 0xf6, 0xa7, 0x8c, 0x6f, 0xd4, 0x7a, 0x28, 0xa8, 0x8d, 0x52,
- 0x48, 0xe8, 0xf8, 0xcb, 0xa7, 0xd7, 0x62, 0x66, 0x34, 0x32, 0xc2, 0xf7, 0x51, 0xb6, 0xd7, 0x31,
- 0xfb, 0x1b, 0xae, 0xd7, 0xcd, 0x9f, 0x81, 0x64, 0xd7, 0xf6, 0x70, 0x55, 0x6a, 0xca, 0x66, 0xdf,
- 0x2c, 0x11, 0x99, 0x66, 0x8a, 0xaf, 0x32, 0x37, 0x04, 0x08, 0x55, 0x3a, 0x5c, 0x46, 0x13, 0x1d,
- 0xb7, 0x65, 0x76, 0x9a, 0x1b, 0x1d, 0xb3, 0xed, 0xe7, 0xff, 0x32, 0x06, 0x9b, 0x0a, 0xd9, 0x01,
- 0xf8, 0x12, 0x87, 0xd5, 0x66, 0x44, 0x10, 0xa1, 0x9a, 0x1e, 0xdf, 0x46, 0x93, 0xb2, 0x8c, 0x44,
- 0x8e, 0xfd, 0x75, 0x0c, 0x32, 0x04, 0x62, 0x23, 0x15, 0x32, 0xcb, 0xa6, 0xf5, 0xea, 0x13, 0x69,
- 0xa6, 0x33, 0xf0, 0xc7, 0xe8, 0xac, 0xed, 0xb8, 0x16, 0x6b, 0xb6, 0x36, 0x4d, 0xa7, 0xcd, 0x78,
- 0x7c, 0x06, 0x63, 0x50, 0x8d, 0x90, 0xff, 0xa0, 0x5b, 0x04, 0x15, 0xc4, 0xe8, 0x9c, 0x9c, 0x9e,
- 0x1a, 0x4a, 0x68, 0x9c, 0x85, 0x77, 0x91, 0x36, 0x56, 0x9a, 0x7d, 0xcf, 0xb4, 0x3b, 0xcc, 0x13,
- 0xf1, 0xfa, 0xdb, 0x18, 0x04, 0xec, 0xc3, 0x41, 0x60, 0x5c, 0x88, 0x38, 0x6b, 0x82, 0x22, 0x83,
- 0x75, 0xf9, 0xd0, 0xc8, 0xd2, 0xb4, 0x2a, 0x23, 0x8e, 0x37, 0xc6, 0xef, 0xf2, 0x53, 0x24, 0x3f,
- 0xe9, 0x5a, 0xf2, 0x48, 0x7b, 0x45, 0x9c, 0x17, 0x01, 0x52, 0xad, 0x48, 0xca, 0x70, 0x60, 0x84,
- 0x5f, 0x98, 0xa2, 0x31, 0xdb, 0xd9, 0x36, 0x3b, 0x76, 0x78, 0x64, 0x7d, 0xef, 0x59, 0x60, 0x20,
- 0x6a, 0xee, 0x54, 0x05, 0x2a, 0x4e, 0x10, 0xf0, 0x53, 0x3b, 0x41, 0x80, 0xcc, 0x4f, 0x10, 0x1a,
- 0x93, 0x86, 0x3c, 0xde, 0x56, 0x1c, 0x37, 0x76, 0x2b, 0xc8, 0x82, 0x6b, 0xd8, 0x56, 0xc7, 0x8d,
- 0xdf, 0x08, 0xc4, 0xb6, 0xc6, 0x50, 0x42, 0xe3, 0xac, 0xf7, 0x53, 0x3f, 0xff, 0xca, 0x18, 0x21,
- 0xdf, 0x26, 0xd0, 0xb8, 0x6a, 0x71, 0x7c, 0xba, 0x40, 0xfc, 0x93, 0x10, 0x7e, 0xa8, 0xe6, 0x4d,
- 0x11, 0x77, 0x51, 0xcd, 0x9b, 0x10, 0x70, 0xc0, 0xf8, 0xf4, 0x74, 0x37, 0x36, 0x7c, 0xd6, 0x87,
- 0xb9, 0x95, 0x14, 0xd3, 0x53, 0x20, 0x6a, 0x7a, 0x0a, 0x91, 0x50, 0x89, 0xe3, 0x37, 0xe5, 0xf4,
- 0x1a, 0x85, 0xb0, 0x5d, 0x3d, 0x7e, 0x7a, 0x85, 0x41, 0x11, 0x43, 0xec, 0x26, 0x1a, 0xdf, 0x61,
- 0xe6, 0x63, 0x91, 0x97, 0xa2, 0x65, 0x40, 0x5f, 0xe7, 0xa0, 0xcc, 0x49, 0x51, 0x1d, 0x21, 0x40,
- 0xa8, 0xd2, 0xc9, 0x77, 0x7c, 0x84, 0x32, 0x62, 0x9c, 0xe0, 0x55, 0x94, 0x6d, 0xb9, 0x5b, 0x4e,
- 0x3f, 0xba, 0x54, 0x4e, 0xeb, 0xa7, 0x61, 0xd0, 0x94, 0xfe, 0x2d, 0x2c, 0xc0, 0x90, 0xaa, 0x62,
- 0x24, 0x01, 0x7e, 0x8c, 0x95, 0x2a, 0xf2, 0x59, 0x02, 0x8d, 0x49, 0x43, 0x7c, 0x5b, 0x5d, 0x0e,
- 0x52, 0xa5, 0xf7, 0x0e, 0x4d, 0xc9, 0xef, 0xbe, 0x68, 0xea, 0x13, 0x52, 0xde, 0x39, 0xb7, 0xcd,
- 0xce, 0x96, 0xd8, 0xa8, 0x94, 0xb8, 0x73, 0x02, 0xa0, 0x86, 0x0e, 0x48, 0x84, 0x0a, 0x94, 0x7c,
- 0x96, 0x42, 0x93, 0x7a, 0x13, 0xe1, 0xed, 0x7a, 0xcb, 0xb1, 0x77, 0x61, 0x31, 0xb1, 0x53, 0xca,
- 0x3d, 0xc7, 0xde, 0x85, 0x36, 0x53, 0x78, 0x12, 0x18, 0x09, 0x1e, 0x00, 0xce, 0x53, 0x01, 0xe0,
- 0x02, 0xa1, 0x80, 0xe1, 0x8f, 0xd1, 0xd8, 0x8e, 0xed, 0x58, 0xee, 0x8e, 0x0f, 0xcb, 0x98, 0xd0,
- 0x6f, 0x0e, 0x0f, 0x84, 0x02, 0x3c, 0x15, 0xa5, 0xa7, 0x90, 0xad, 0xb6, 0x4b, 0xca, 0x84, 0x86,
- 0x1a, 0xbc, 0x8c, 0xd2, 0x1d, 0xdb, 0xd9, 0xda, 0x85, 0x04, 0x8b, 0x8d, 0xd9, 0x4f, 0xcc, 0x7e,
- 0xdf, 0x03, 0x77, 0x57, 0xa4, 0x3b, 0xc1, 0x8c, 0x2e, 0xd9, 0x5c, 0xe2, 0x97, 0x6c, 0xfe, 0x2f,
- 0xbe, 0x83, 0x32, 0x96, 0xe9, 0xed, 0xd8, 0xe2, 0x52, 0x73, 0x82, 0xa7, 0x19, 0xe9, 0x49, 0x52,
- 0xa3, 0x0b, 0x1e, 0x88, 0x84, 0x4a, 0x1c, 0x33, 0x34, 0xb6, 0xe1, 0x31, 0xb6, 0xee, 0x5b, 0x70,
- 0x48, 0x3a, 0xc1, 0xdb, 0xbb, 0xdc, 0x1b, 0xbf, 0x06, 0x2c, 0x79, 0x8c, 0x95, 0x1a, 0x70, 0x0d,
- 0x90, 0x66, 0xea, 0x8d, 0xa5, 0x0c, 0xd7, 0x00, 0x49, 0xa3, 0x21, 0x09, 0x37, 0x51, 0xc6, 0x61,
- 0x7d, 0xfe, 0x94, 0xcc, 0xc9, 0x4f, 0x99, 0x97, 0x4f, 0xc9, 0xd4, 0x58, 0x5f, 0x3c, 0x44, 0x1a,
- 0xa9, 0xd5, 0x0b, 0x91, 0x3f, 0x42, 0x72, 0xa8, 0x64, 0x90, 0xcf, 0x47, 0x51, 0x36, 0x8c, 0x2f,
- 0x3f, 0xfc, 0xb9, 0x3b, 0x0e, 0xf3, 0xf4, 0xaf, 0x5b, 0x30, 0xf1, 0x01, 0x95, 0xd7, 0x33, 0x31,
- 0xc8, 0x14, 0x42, 0x68, 0xa4, 0xe5, 0x0e, 0xda, 0x9e, 0xbb, 0xd5, 0xd3, 0xbf, 0x6c, 0x81, 0x03,
- 0x40, 0x63, 0x0e, 0x14, 0x42, 0x68, 0xa4, 0xc5, 0x37, 0x51, 0x72, 0xcb, 0xb6, 0x20, 0xd4, 0xe9,
- 0xd2, 0xab, 0xcf, 0x02, 0x23, 0x79, 0x0f, 0x2a, 0x80, 0xa3, 0xc3, 0xc0, 0x18, 0x17, 0x09, 0x67,
- 0x5b, 0xda, 0xf8, 0xe4, 0x0c, 0xca, 0xf5, 0xdc, 0xb8, 0x6d, 0x5b, 0x10, 0x5d, 0x69, 0xbc, 0x2c,
- 0x8c, 0xdb, 0x9a, 0x71, 0x3b, 0x6e, 0xbc, 0xcc, 0x8d, 0x39, 0xf6, 0xcb, 0x04, 0x9a, 0xd0, 0x32,
- 0xf4, 0x87, 0xef, 0xc5, 0x0a, 0x3a, 0x23, 0x1c, 0xd8, 0x7e, 0x13, 0x5e, 0x10, 0xf6, 0x43, 0x7e,
- 0x36, 0x01, 0x4d, 0xd5, 0x5f, 0xe6, 0xb8, 0xfa, 0x6c, 0xa2, 0x83, 0x84, 0xc6, 0x38, 0xa4, 0x81,
- 0xc6, 0x55, 0xc0, 0xf1, 0x12, 0xca, 0xec, 0x72, 0x21, 0x6c, 0x48, 0x67, 0x0f, 0x65, 0x45, 0x74,
- 0xec, 0x14, 0x34, 0x55, 0x10, 0x20, 0x12, 0x2a, 0x61, 0xd2, 0x42, 0x69, 0xe0, 0xbf, 0xd0, 0x6d,
- 0x22, 0xd6, 0x67, 0x26, 0xff, 0x79, 0x9f, 0xf9, 0x71, 0x0a, 0x8d, 0x51, 0x7e, 0x68, 0xf6, 0xfb,
- 0xf8, 0x1d, 0xd5, 0xed, 0xd2, 0xa5, 0x57, 0x4e, 0x6a, 0x6f, 0x51, 0x74, 0xc2, 0xaf, 0x1f, 0xd1,
- 0xa5, 0x6b, 0xf4, 0xd4, 0x97, 0xae, 0xf0, 0x95, 0x92, 0xa7, 0x78, 0xa5, 0x68, 0x2c, 0xa5, 0x5e,
- 0x78, 0x2c, 0xa5, 0x4f, 0x3f, 0x96, 0xc2, 0x49, 0x99, 0x39, 0xc5, 0xa4, 0xac, 0xa3, 0x33, 0x1b,
- 0x9e, 0xdb, 0x85, 0x6f, 0x64, 0xae, 0x67, 0x7a, 0x7b, 0xf2, 0x54, 0x00, 0xa3, 0x9b, 0x6b, 0xd6,
- 0x42, 0x85, 0x1a, 0xdd, 0x31, 0x94, 0xd0, 0x38, 0x2b, 0x3e, 0x13, 0xb3, 0x2f, 0x36, 0x13, 0xf1,
- 0x2d, 0x94, 0x15, 0x27, 0x5e, 0xc7, 0x85, 0x6b, 0x57, 0xba, 0xf4, 0x32, 0x6f, 0x65, 0x80, 0xd5,
- 0x5c, 0xd5, 0xca, 0xa4, 0xac, 0x5e, 0x3b, 0x24, 0x90, 0xdf, 0x26, 0x50, 0x96, 0x32, 0xbf, 0xe7,
- 0x3a, 0x3e, 0xfb, 0xbe, 0x49, 0x30, 0x87, 0x52, 0x96, 0xd9, 0x37, 0x65, 0xda, 0xc1, 0xee, 0x71,
- 0x59, 0xed, 0x1e, 0x17, 0x08, 0x05, 0x0c, 0x7f, 0x88, 0x52, 0x2d, 0xd7, 0x12, 0xc1, 0x3f, 0xa3,
- 0x37, 0xcd, 0x8a, 0xe7, 0xb9, 0xde, 0xa2, 0x6b, 0xc9, 0x6b, 0x07, 0x27, 0x29, 0x07, 0x5c, 0x20,
- 0x14, 0x30, 0xf2, 0x9b, 0x04, 0xca, 0x95, 0xdd, 0x1d, 0xa7, 0xe3, 0x9a, 0xd6, 0xaa, 0xe7, 0xb6,
- 0x3d, 0xe6, 0xfb, 0xdf, 0xeb, 0xee, 0xdf, 0x44, 0x63, 0x5b, 0xf0, 0xe5, 0x20, 0xbc, 0xfd, 0x5f,
- 0x8b, 0x5f, 0x83, 0x0e, 0x3f, 0x44, 0x7c, 0x66, 0x88, 0x3e, 0x34, 0x4a, 0x63, 0xe5, 0x5f, 0xc8,
- 0x84, 0x86, 0x0a, 0xf2, 0xeb, 0x24, 0x2a, 0x9c, 0xec, 0x08, 0x77, 0xd1, 0x84, 0x60, 0x36, 0xb5,
- 0x4f, 0xfa, 0xb3, 0xa7, 0x59, 0x03, 0x5c, 0xce, 0xe0, 0x52, 0xb0, 0xa5, 0x64, 0x75, 0x29, 0x88,
- 0x20, 0x42, 0x35, 0xfd, 0x0b, 0x7d, 0xa7, 0xd4, 0xae, 0xf2, 0xc9, 0x1f, 0x7e, 0x95, 0x6f, 0xa0,
- 0x29, 0x91, 0xa2, 0xe1, 0x07, 0xe5, 0x54, 0x31, 0x39, 0x9b, 0x2e, 0x5d, 0xe7, 0xdd, 0x76, 0x5d,
- 0x1c, 0x56, 0xc3, 0x4f, 0xc9, 0xd3, 0x51, 0xb2, 0x0a, 0x30, 0xcc, 0xb6, 0xdc, 0x08, 0x8d, 0x71,
- 0xf1, 0x52, 0xec, 0xa6, 0x27, 0x4a, 0xfd, 0x3f, 0x4e, 0x79, 0xb3, 0xd3, 0x6e, 0x72, 0x24, 0x83,
- 0x52, 0xab, 0xb6, 0xd3, 0x26, 0x37, 0x51, 0x7a, 0xb1, 0xe3, 0xfa, 0xd0, 0x71, 0x3c, 0x66, 0xfa,
- 0xae, 0xa3, 0xa7, 0x92, 0x40, 0x54, 0xa8, 0x85, 0x48, 0xa8, 0xc4, 0xe7, 0xbe, 0x4e, 0xa2, 0x09,
- 0xed, 0x2f, 0x30, 0xf8, 0x7f, 0xd0, 0xe5, 0xbb, 0x95, 0x46, 0x63, 0x61, 0xb9, 0xd2, 0x5c, 0x7b,
- 0xb8, 0x5a, 0x69, 0x2e, 0xae, 0xdc, 0x6b, 0xac, 0x55, 0x68, 0x73, 0xb1, 0x5e, 0x5b, 0xaa, 0x2e,
- 0xe7, 0x46, 0x0a, 0x57, 0xf6, 0x0f, 0x8a, 0x79, 0xcd, 0x22, 0xfe, 0xb7, 0x92, 0xff, 0x44, 0x38,
- 0x66, 0x5e, 0xad, 0x95, 0x2b, 0x9f, 0xe4, 0x12, 0x85, 0xf3, 0xfb, 0x07, 0xc5, 0x9c, 0x66, 0x25,
- 0x3e, 0xc1, 0xfd, 0x37, 0x7a, 0xe9, 0x28, 0xbb, 0x79, 0x6f, 0xb5, 0xbc, 0xb0, 0x56, 0xc9, 0x8d,
- 0x16, 0x0a, 0xfb, 0x07, 0xc5, 0x8b, 0x87, 0x8d, 0x64, 0x0a, 0xbe, 0x81, 0xce, 0xc7, 0x4c, 0x69,
- 0xe5, 0xe3, 0x7b, 0x95, 0xc6, 0x5a, 0x2e, 0x59, 0xb8, 0xb8, 0x7f, 0x50, 0xc4, 0x9a, 0x55, 0x38,
- 0x26, 0xe6, 0xd1, 0x85, 0x43, 0x16, 0x8d, 0xd5, 0x7a, 0xad, 0x51, 0xc9, 0xa5, 0x0a, 0x97, 0xf6,
- 0x0f, 0x8a, 0xe7, 0x62, 0x26, 0xb2, 0xab, 0x2c, 0xa2, 0x99, 0x98, 0x4d, 0xb9, 0xfe, 0xa0, 0xb6,
- 0x52, 0x5f, 0x28, 0x37, 0x57, 0x69, 0x7d, 0x99, 0x56, 0x1a, 0x8d, 0x5c, 0xba, 0x60, 0xec, 0x1f,
- 0x14, 0x2f, 0x6b, 0xc6, 0x47, 0x2a, 0x7c, 0x0e, 0x4d, 0xc7, 0x9c, 0xac, 0x56, 0x6b, 0xcb, 0xb9,
- 0x4c, 0xe1, 0xdc, 0xfe, 0x41, 0xf1, 0xac, 0x66, 0xc7, 0x63, 0x79, 0x64, 0xff, 0x16, 0x57, 0xea,
- 0x8d, 0x4a, 0x6e, 0xec, 0xc8, 0xfe, 0x41, 0xc0, 0xe7, 0x7e, 0x95, 0x40, 0xf8, 0xe8, 0x1f, 0xbd,
- 0xf0, 0x7b, 0x28, 0x1f, 0x3a, 0x59, 0xac, 0xdf, 0x5d, 0xe5, 0xeb, 0xac, 0xd6, 0x6b, 0xcd, 0x5a,
- 0xbd, 0x56, 0xc9, 0x8d, 0xc4, 0x76, 0x55, 0xb3, 0xaa, 0xb9, 0x0e, 0xc3, 0x75, 0x74, 0xe9, 0x38,
- 0xcb, 0x95, 0x47, 0x6f, 0xe7, 0x12, 0x85, 0xf9, 0xfd, 0x83, 0xe2, 0x85, 0xa3, 0x86, 0x2b, 0x8f,
- 0xde, 0xfe, 0xe6, 0xa7, 0xaf, 0x1c, 0xaf, 0x98, 0xe3, 0x07, 0x20, 0x7d, 0x69, 0x6f, 0xa2, 0xf3,
- 0xba, 0xe3, 0xbb, 0x95, 0xb5, 0x85, 0xf2, 0xc2, 0xda, 0x42, 0x6e, 0x44, 0xc4, 0x40, 0xa3, 0xde,
- 0x65, 0x7d, 0x13, 0xda, 0xee, 0x6b, 0x68, 0x3a, 0xf6, 0x16, 0x95, 0xfb, 0x15, 0x1a, 0x66, 0x94,
- 0xbe, 0x7e, 0xb6, 0xcd, 0x3c, 0xfc, 0x3a, 0xc2, 0x3a, 0x79, 0x61, 0xe5, 0xc1, 0xc2, 0xc3, 0x46,
- 0x6e, 0xb4, 0x70, 0x61, 0xff, 0xa0, 0x38, 0xad, 0xb1, 0x17, 0x3a, 0x3b, 0xe6, 0x9e, 0x3f, 0xf7,
- 0xfb, 0x51, 0x34, 0xa9, 0x7f, 0x37, 0xc2, 0xaf, 0xa3, 0x73, 0x4b, 0xd5, 0x15, 0x9e, 0x89, 0x4b,
- 0x75, 0x11, 0x01, 0x2e, 0xe6, 0x46, 0xc4, 0xe3, 0x74, 0x2a, 0xff, 0x8d, 0xff, 0x0b, 0xe5, 0x0f,
- 0xd1, 0xcb, 0x55, 0x5a, 0x59, 0x5c, 0xab, 0xd3, 0x87, 0xb9, 0x44, 0xe1, 0x25, 0xbe, 0x61, 0xba,
- 0x4d, 0xd9, 0xf6, 0xa0, 0x05, 0xed, 0xe1, 0x5b, 0xe8, 0xf2, 0x21, 0xc3, 0xc6, 0xc3, 0xbb, 0x2b,
- 0xd5, 0xda, 0x1d, 0xf1, 0xbc, 0xd1, 0xc2, 0xd5, 0xfd, 0x83, 0xe2, 0x25, 0xdd, 0xb6, 0x21, 0x3e,
- 0xc5, 0x71, 0x28, 0x9b, 0xc0, 0xb7, 0x51, 0xf1, 0x04, 0xfb, 0x68, 0x01, 0xc9, 0x02, 0xd9, 0x3f,
- 0x28, 0x5e, 0x39, 0xc6, 0x89, 0x5a, 0x47, 0x36, 0x81, 0xdf, 0x42, 0x17, 0x8f, 0xf7, 0x14, 0xd6,
- 0xc5, 0x31, 0xf6, 0x73, 0x7f, 0x4c, 0xa0, 0x71, 0x35, 0xf5, 0xf8, 0xa6, 0x55, 0x28, 0xad, 0xf3,
- 0x26, 0x51, 0xae, 0x34, 0x6b, 0xf5, 0x26, 0x48, 0xe1, 0xa6, 0x29, 0x5e, 0xcd, 0x85, 0x9f, 0x3c,
- 0xc7, 0x35, 0xfa, 0x72, 0xa5, 0x56, 0xa1, 0xd5, 0xc5, 0x30, 0xa2, 0x8a, 0xbd, 0xcc, 0x1c, 0xe6,
- 0xd9, 0x2d, 0xfc, 0x36, 0xba, 0x14, 0x77, 0xde, 0xb8, 0xb7, 0x78, 0x3b, 0xdc, 0x25, 0x58, 0xa0,
- 0xf6, 0x80, 0xc6, 0x56, 0x6b, 0x13, 0x02, 0xf3, 0x4e, 0xcc, 0xaa, 0x5a, 0xbb, 0xbf, 0xb0, 0x52,
- 0x2d, 0x0b, 0xab, 0x64, 0x21, 0xbf, 0x7f, 0x50, 0x3c, 0xaf, 0xac, 0xe4, 0x07, 0x0e, 0x6e, 0x36,
- 0xf7, 0x4d, 0x02, 0xcd, 0x7c, 0xf7, 0xf0, 0xc2, 0x0f, 0xd0, 0xab, 0xb0, 0x5f, 0x47, 0x5a, 0x81,
- 0xec, 0x5b, 0x62, 0x0f, 0x17, 0x56, 0x57, 0x2b, 0xb5, 0x72, 0x6e, 0xa4, 0x30, 0xbb, 0x7f, 0x50,
- 0xbc, 0xf6, 0xdd, 0x2e, 0x17, 0x7a, 0x3d, 0xe6, 0x58, 0xa7, 0x74, 0xbc, 0x54, 0xa7, 0xcb, 0x95,
- 0xb5, 0x5c, 0xe2, 0x34, 0x8e, 0x97, 0x5c, 0xaf, 0xcd, 0xfa, 0xa5, 0xbb, 0x4f, 0xbe, 0x9d, 0x19,
- 0x79, 0xfa, 0xed, 0xcc, 0xc8, 0x93, 0x67, 0x33, 0x89, 0xa7, 0xcf, 0x66, 0x12, 0x3f, 0x7b, 0x3e,
- 0x33, 0xf2, 0xd5, 0xf3, 0x99, 0xc4, 0xd3, 0xe7, 0x33, 0x23, 0x7f, 0x7a, 0x3e, 0x33, 0xf2, 0xe8,
- 0xb5, 0xb6, 0xdd, 0xdf, 0xdc, 0x5a, 0xbf, 0xde, 0x72, 0xbb, 0x37, 0xfc, 0x3d, 0xa7, 0xd5, 0xdf,
- 0xb4, 0x9d, 0xb6, 0xf6, 0x4b, 0xff, 0xcf, 0x0f, 0xeb, 0x19, 0xf8, 0xf5, 0xd6, 0x3f, 0x02, 0x00,
- 0x00, 0xff, 0xff, 0x68, 0x4a, 0x6e, 0xeb, 0x13, 0x21, 0x00, 0x00,
+ // 3251 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0x4b, 0x6c, 0x23, 0x47,
+ 0x7a, 0x16, 0x9f, 0xa2, 0x4a, 0x8f, 0xa1, 0x6a, 0x5e, 0x34, 0x67, 0xac, 0x66, 0x6a, 0x67, 0x13,
+ 0x59, 0x9b, 0x1d, 0xaf, 0xb5, 0xde, 0x8d, 0x63, 0x3b, 0x36, 0xc4, 0x87, 0x34, 0x5c, 0x6b, 0x48,
+ 0xb9, 0xa8, 0x19, 0xaf, 0x07, 0x08, 0x88, 0x16, 0xbb, 0x44, 0x35, 0x86, 0xec, 0x66, 0xba, 0x9b,
+ 0x7a, 0x2c, 0x72, 0x49, 0x16, 0x58, 0x2c, 0x74, 0x08, 0x82, 0x3d, 0x05, 0xc1, 0x0a, 0x59, 0xe4,
+ 0x92, 0x5b, 0x80, 0x1c, 0x72, 0xd9, 0x53, 0x8e, 0x73, 0x1c, 0x2c, 0x10, 0x20, 0xc8, 0xa1, 0x01,
+ 0xcf, 0x5c, 0x12, 0xe6, 0xc6, 0x63, 0x0e, 0x41, 0x50, 0x7f, 0x55, 0x57, 0x57, 0xeb, 0xe1, 0x68,
+ 0xec, 0x43, 0x4e, 0xc3, 0xfa, 0xfe, 0xef, 0xff, 0xab, 0xba, 0xea, 0x7f, 0x55, 0x69, 0xd0, 0x9d,
+ 0x81, 0xbd, 0xf7, 0xee, 0xc8, 0x73, 0x03, 0xb7, 0xe7, 0x0e, 0xde, 0xdd, 0x63, 0xa3, 0x87, 0x30,
+ 0xc0, 0x85, 0x08, 0x2b, 0xcf, 0xb1, 0xe3, 0x40, 0x80, 0xe5, 0xef, 0x78, 0x6c, 0xe4, 0xfa, 0x82,
+ 0xbe, 0x37, 0xde, 0x7f, 0xb7, 0xef, 0xf6, 0x5d, 0x18, 0xc0, 0x2f, 0x41, 0x22, 0xff, 0x93, 0x46,
+ 0xb9, 0x47, 0x6c, 0x30, 0x70, 0x71, 0x0d, 0xcd, 0x5b, 0xec, 0xd0, 0xee, 0xb1, 0xae, 0x63, 0x0e,
+ 0x59, 0x29, 0x55, 0x49, 0xad, 0xce, 0x55, 0xc9, 0x24, 0x34, 0x90, 0x80, 0x5b, 0xe6, 0x90, 0x4d,
+ 0x43, 0xa3, 0x78, 0x3c, 0x1c, 0x7c, 0x48, 0x62, 0x88, 0x50, 0x4d, 0xce, 0x8d, 0xf4, 0x06, 0x36,
+ 0x73, 0x02, 0x61, 0x24, 0x1d, 0x1b, 0x11, 0x70, 0xc2, 0x48, 0x0c, 0x11, 0xaa, 0xc9, 0x71, 0x1b,
+ 0x2d, 0x49, 0x23, 0x87, 0xcc, 0xf3, 0x6d, 0xd7, 0x29, 0x65, 0xc0, 0xce, 0xea, 0x24, 0x34, 0x16,
+ 0x85, 0xe4, 0xa9, 0x10, 0x4c, 0x43, 0xe3, 0xa6, 0x66, 0x4a, 0xa2, 0x84, 0x26, 0x59, 0xf8, 0x19,
+ 0xba, 0xe1, 0x8c, 0x87, 0xdd, 0x9e, 0xeb, 0x38, 0xac, 0x17, 0xd8, 0xae, 0xe3, 0x97, 0xb2, 0x95,
+ 0xd4, 0x6a, 0xae, 0xfa, 0xde, 0x24, 0x34, 0x96, 0x9c, 0xf1, 0xb0, 0x16, 0x4b, 0xa6, 0xa1, 0x71,
+ 0x0b, 0x4c, 0x26, 0x61, 0xf2, 0xdf, 0xa1, 0x91, 0xb1, 0x9d, 0x80, 0x9e, 0xa3, 0xe3, 0x4f, 0xd0,
+ 0x5c, 0x60, 0x0f, 0x99, 0x1f, 0x98, 0xc3, 0x51, 0x29, 0x57, 0x49, 0xad, 0x66, 0xaa, 0x95, 0x49,
+ 0x68, 0xc4, 0xe0, 0x34, 0x34, 0x6e, 0x80, 0x41, 0x85, 0x10, 0x1a, 0x4b, 0xc9, 0x3f, 0xa5, 0x50,
+ 0xfe, 0x11, 0x33, 0x2d, 0xe6, 0xe1, 0x0d, 0x94, 0x0d, 0x4e, 0x46, 0x62, 0xeb, 0x97, 0xd6, 0x6f,
+ 0x3f, 0x8c, 0x0e, 0xf5, 0xe1, 0x63, 0xe6, 0xfb, 0x66, 0x9f, 0xed, 0x9e, 0x8c, 0x58, 0xf5, 0xce,
+ 0x24, 0x34, 0x80, 0x36, 0x0d, 0x0d, 0x24, 0xec, 0x9e, 0x8c, 0x18, 0xa1, 0x80, 0x61, 0x0b, 0xcd,
+ 0xf7, 0xdc, 0xe1, 0xc8, 0x63, 0x3e, 0xec, 0x5b, 0x1a, 0x2c, 0xdd, 0xbf, 0x60, 0xa9, 0x16, 0x73,
+ 0xaa, 0x0f, 0x26, 0xa1, 0xa1, 0x2b, 0x4d, 0x43, 0x63, 0x59, 0xec, 0x69, 0x8c, 0x11, 0xaa, 0x33,
+ 0xc8, 0xaf, 0x53, 0x68, 0xb1, 0x36, 0x18, 0xfb, 0x01, 0xf3, 0x6a, 0xae, 0xb3, 0x6f, 0xf7, 0xf1,
+ 0x67, 0x68, 0x76, 0xdf, 0x1d, 0x58, 0xcc, 0xf3, 0x4b, 0xa9, 0x4a, 0x66, 0x75, 0x7e, 0xbd, 0x18,
+ 0xcf, 0xb9, 0x09, 0x82, 0xaa, 0xf1, 0x22, 0x34, 0x66, 0x26, 0xa1, 0x11, 0x11, 0xa7, 0xa1, 0xb1,
+ 0x00, 0xf3, 0x88, 0x31, 0xa1, 0x91, 0x80, 0x6f, 0xa9, 0xcf, 0x7a, 0xae, 0x63, 0x99, 0xde, 0x09,
+ 0x7c, 0x42, 0x41, 0x6c, 0xa9, 0x02, 0xd5, 0x96, 0x2a, 0x84, 0xd0, 0x58, 0x4a, 0x7e, 0x9b, 0x45,
+ 0x79, 0x31, 0x29, 0x7e, 0x88, 0xd2, 0xb6, 0x25, 0x7d, 0x79, 0xe5, 0x55, 0x68, 0xa4, 0x9b, 0xf5,
+ 0x49, 0x68, 0xa4, 0x6d, 0x6b, 0x1a, 0x1a, 0x05, 0x30, 0x61, 0x5b, 0xe4, 0x57, 0x2f, 0x1f, 0xa4,
+ 0x9b, 0x75, 0x9a, 0xb6, 0x2d, 0xfc, 0x10, 0xe5, 0x06, 0xe6, 0x1e, 0x1b, 0x48, 0xcf, 0x2d, 0x4d,
+ 0x42, 0x43, 0x00, 0xd3, 0xd0, 0x98, 0x07, 0x3e, 0x8c, 0x08, 0x15, 0x28, 0xfe, 0x08, 0xcd, 0x79,
+ 0xcc, 0xb4, 0xba, 0xae, 0x33, 0x38, 0x01, 0x2f, 0x2d, 0x54, 0x57, 0x26, 0xa1, 0x51, 0xe0, 0x60,
+ 0xdb, 0x19, 0xf0, 0x95, 0x2e, 0x81, 0x5a, 0x04, 0x10, 0xaa, 0x64, 0xb8, 0x8b, 0xb0, 0xdd, 0x77,
+ 0x5c, 0x8f, 0x75, 0x47, 0xcc, 0x1b, 0xda, 0xb0, 0xb7, 0xc2, 0x33, 0x0b, 0xd5, 0x1f, 0x4c, 0x42,
+ 0x63, 0x59, 0x48, 0x77, 0x62, 0xe1, 0x34, 0x34, 0xee, 0x8a, 0x55, 0x9f, 0x97, 0x10, 0x7a, 0x91,
+ 0x8d, 0x3f, 0x43, 0x8b, 0x72, 0x02, 0x8b, 0x0d, 0x58, 0xc0, 0xc0, 0x3f, 0x0b, 0xd5, 0xdf, 0x9f,
+ 0x84, 0xc6, 0x82, 0x10, 0xd4, 0x01, 0x9f, 0x86, 0x06, 0xd6, 0xcc, 0x0a, 0x90, 0xd0, 0x04, 0x07,
+ 0x5b, 0xe8, 0x96, 0x65, 0xfb, 0xe6, 0xde, 0x80, 0x75, 0x03, 0x36, 0x1c, 0x75, 0x6d, 0xc7, 0x62,
+ 0xc7, 0xcc, 0x2f, 0xe5, 0xc1, 0xe6, 0xfa, 0x24, 0x34, 0xb0, 0x94, 0xef, 0xb2, 0xe1, 0xa8, 0x29,
+ 0xa4, 0xd3, 0xd0, 0x28, 0x89, 0x84, 0x71, 0x41, 0x44, 0xe8, 0x25, 0x7c, 0xbc, 0x8e, 0xf2, 0x23,
+ 0x73, 0xec, 0x33, 0xab, 0x34, 0x0b, 0x76, 0xcb, 0x93, 0xd0, 0x90, 0x88, 0x72, 0x18, 0x31, 0x24,
+ 0x54, 0xe2, 0xdc, 0xf9, 0x44, 0x0a, 0xf2, 0x4b, 0xc5, 0xf3, 0xce, 0x57, 0x07, 0x41, 0xec, 0x7c,
+ 0x92, 0xa8, 0x6c, 0x89, 0x31, 0xa1, 0x91, 0x80, 0xfc, 0x4b, 0x1e, 0xe5, 0x85, 0x12, 0xae, 0x2a,
+ 0xe7, 0x59, 0xa8, 0xae, 0x73, 0x03, 0xff, 0x1e, 0x1a, 0x05, 0x21, 0x6b, 0xd6, 0xaf, 0x72, 0xa6,
+ 0x5f, 0xbe, 0x7c, 0x90, 0xd2, 0x1c, 0x6a, 0x0d, 0x65, 0xb5, 0x4c, 0x08, 0xc1, 0xeb, 0x88, 0x1c,
+ 0x28, 0x82, 0xd7, 0x81, 0xec, 0x07, 0x18, 0xfe, 0x18, 0xcd, 0x99, 0x96, 0xc5, 0x83, 0x8c, 0xf9,
+ 0xa5, 0x4c, 0x25, 0xc3, 0x7d, 0x96, 0xfb, 0xbd, 0x02, 0xa7, 0xa1, 0xb1, 0x08, 0x5a, 0x12, 0x21,
+ 0x34, 0x96, 0xe1, 0x3f, 0x4d, 0x86, 0x7e, 0xf6, 0x7c, 0x12, 0xf9, 0x76, 0x31, 0xcf, 0x3d, 0xbd,
+ 0xc7, 0x3c, 0x99, 0xd7, 0x73, 0x22, 0xa0, 0xb8, 0xa7, 0x73, 0x50, 0x66, 0x75, 0xe1, 0xe9, 0x11,
+ 0x40, 0xa8, 0x92, 0xe1, 0x2d, 0xb4, 0x30, 0x34, 0x8f, 0xbb, 0x3e, 0xfb, 0xb3, 0x31, 0x73, 0x7a,
+ 0x0c, 0x7c, 0x26, 0x23, 0x56, 0x31, 0x34, 0x8f, 0x3b, 0x12, 0x56, 0xab, 0xd0, 0x30, 0x42, 0x75,
+ 0x06, 0xae, 0x22, 0x64, 0x3b, 0x81, 0xe7, 0x5a, 0xe3, 0x1e, 0xf3, 0xa4, 0x8b, 0x40, 0x79, 0x89,
+ 0x51, 0x55, 0x5e, 0x62, 0x88, 0x50, 0x4d, 0x8e, 0xfb, 0xa8, 0x00, 0xbe, 0xdb, 0xb5, 0xad, 0x52,
+ 0xa1, 0x92, 0x5a, 0xcd, 0x56, 0xb7, 0xe5, 0xe1, 0xce, 0x82, 0x17, 0xc2, 0xd9, 0x46, 0x3f, 0xb9,
+ 0xcf, 0x00, 0xbb, 0x69, 0xa9, 0xdd, 0x97, 0x63, 0x9e, 0x37, 0x22, 0xda, 0xdf, 0xc6, 0x3f, 0x69,
+ 0xc4, 0xc7, 0x7f, 0x8e, 0xca, 0xfe, 0x73, 0x9b, 0x47, 0x8a, 0x98, 0x9b, 0x17, 0x8c, 0xae, 0xc7,
+ 0x86, 0xee, 0xa1, 0x39, 0xf0, 0x4b, 0x73, 0xb0, 0xf8, 0x4f, 0x26, 0xa1, 0x51, 0xe2, 0xac, 0xa6,
+ 0x46, 0xa2, 0x92, 0x33, 0x0d, 0x8d, 0x15, 0x91, 0xe7, 0xae, 0x20, 0x10, 0x7a, 0xa5, 0x2e, 0x3e,
+ 0x46, 0x6f, 0x31, 0xa7, 0xe7, 0x9d, 0x8c, 0x60, 0xda, 0x91, 0xe9, 0xfb, 0x47, 0xae, 0x67, 0x75,
+ 0x03, 0xf7, 0x39, 0x73, 0x4a, 0x08, 0x9c, 0xfa, 0xe3, 0x49, 0x68, 0xdc, 0x8d, 0x49, 0x3b, 0x92,
+ 0xb3, 0xcb, 0x29, 0xd3, 0xd0, 0x78, 0x1b, 0xe6, 0xbe, 0x42, 0x4e, 0xe8, 0x55, 0x9a, 0xe4, 0x2f,
+ 0x53, 0x28, 0x07, 0x9b, 0xc1, 0xa3, 0x59, 0x24, 0x75, 0x99, 0x82, 0x21, 0x9a, 0x05, 0x72, 0x21,
+ 0xfd, 0x4b, 0x1c, 0x37, 0x50, 0x6e, 0xdf, 0x1e, 0x30, 0xbf, 0x94, 0x86, 0x58, 0xc6, 0x5a, 0x21,
+ 0xb1, 0x07, 0xac, 0xe9, 0xec, 0xbb, 0xd5, 0x7b, 0x32, 0x9a, 0x05, 0x51, 0xc5, 0x12, 0x1f, 0x11,
+ 0x2a, 0x40, 0xf2, 0xcb, 0x14, 0x9a, 0x87, 0x45, 0x3c, 0x19, 0x59, 0x66, 0xc0, 0xfe, 0x3f, 0x97,
+ 0xf2, 0x8b, 0x45, 0x54, 0x88, 0x14, 0x54, 0x42, 0x48, 0x5d, 0x23, 0x21, 0xac, 0xa1, 0xac, 0x6f,
+ 0xff, 0x8c, 0x41, 0x61, 0xc9, 0x08, 0x2e, 0x1f, 0x2b, 0x2e, 0x1f, 0x10, 0x0a, 0x18, 0xfe, 0x14,
+ 0xa1, 0xa1, 0x6b, 0xd9, 0xfb, 0x36, 0xb3, 0xba, 0xbe, 0xde, 0x88, 0x44, 0x68, 0x47, 0x55, 0x4d,
+ 0x85, 0x10, 0x1a, 0x4b, 0x79, 0xfe, 0x50, 0x06, 0xf6, 0x4e, 0x4a, 0x0b, 0x10, 0x19, 0x1f, 0x47,
+ 0x91, 0xd1, 0x39, 0x70, 0xbd, 0x00, 0xc2, 0x41, 0x4d, 0x53, 0x3d, 0x51, 0xa1, 0x16, 0x43, 0x84,
+ 0x47, 0x82, 0x24, 0x53, 0x8d, 0x8a, 0xb7, 0xd1, 0x6c, 0xd4, 0xcd, 0x71, 0xcf, 0x4f, 0x24, 0xe9,
+ 0xa7, 0xac, 0x17, 0xb8, 0x5e, 0xb5, 0x12, 0x25, 0xe9, 0x43, 0xd5, 0xdd, 0x89, 0x80, 0x3b, 0x8c,
+ 0xfa, 0xba, 0x48, 0x82, 0x3f, 0x44, 0x05, 0x95, 0x4c, 0x10, 0x7c, 0x2b, 0x24, 0x23, 0x3f, 0xce,
+ 0x24, 0x4b, 0xb2, 0x41, 0x88, 0xd2, 0x88, 0x92, 0xe1, 0x9f, 0xa0, 0xfc, 0xde, 0xc0, 0xed, 0x3d,
+ 0x8f, 0xaa, 0xc5, 0xcd, 0x78, 0x21, 0x55, 0x8e, 0xc3, 0xb9, 0xbe, 0x2d, 0xd7, 0x22, 0xa9, 0xaa,
+ 0xfc, 0xc3, 0x90, 0x50, 0x09, 0xf3, 0x56, 0xd5, 0x3f, 0x19, 0x0e, 0x6c, 0xe7, 0x79, 0x37, 0x30,
+ 0xbd, 0x3e, 0x0b, 0x4a, 0xcb, 0x71, 0xab, 0x2a, 0x25, 0xbb, 0x20, 0x50, 0xad, 0x6a, 0x02, 0x25,
+ 0x34, 0xc9, 0xe2, 0x0d, 0xb4, 0x30, 0xdd, 0x3d, 0x30, 0xfd, 0x83, 0x12, 0x86, 0x38, 0x85, 0x0c,
+ 0x27, 0xe0, 0x47, 0xa6, 0x7f, 0xa0, 0xb6, 0x3d, 0x86, 0x08, 0xd5, 0xe4, 0xbc, 0x81, 0x92, 0xb1,
+ 0xc9, 0xac, 0xd2, 0x4d, 0x30, 0x01, 0xae, 0xa0, 0x40, 0xe5, 0x0a, 0x0a, 0x21, 0x34, 0x96, 0xe2,
+ 0xaa, 0x6c, 0x44, 0x45, 0xfb, 0x78, 0xe7, 0xa2, 0xdb, 0x5f, 0xa3, 0x13, 0xdd, 0x44, 0xf3, 0xe7,
+ 0xbb, 0x9a, 0x45, 0x91, 0xf1, 0x47, 0x89, 0x7e, 0x46, 0x64, 0xfc, 0x91, 0xde, 0xc9, 0xe8, 0x0c,
+ 0xfc, 0x13, 0xcd, 0x2d, 0x1d, 0xbf, 0x34, 0x0f, 0x7d, 0xfb, 0x3b, 0xba, 0x1f, 0xb6, 0xfc, 0x0b,
+ 0x7e, 0xd8, 0x8a, 0xfb, 0x75, 0x8d, 0x86, 0xf7, 0x91, 0xd8, 0xa5, 0x2e, 0x44, 0xd5, 0x22, 0x98,
+ 0xda, 0x7a, 0x15, 0x1a, 0x0b, 0xd4, 0x3c, 0x82, 0xa3, 0xef, 0xd8, 0x3f, 0x63, 0x7c, 0xa3, 0xf6,
+ 0xa2, 0x81, 0xda, 0x28, 0x85, 0x44, 0x86, 0x7f, 0xf5, 0xf2, 0x41, 0x42, 0x8d, 0xc6, 0x4a, 0xf8,
+ 0x29, 0x2a, 0x8c, 0x06, 0x66, 0xb0, 0xef, 0x7a, 0xc3, 0xd2, 0x12, 0x38, 0xbb, 0xb6, 0x87, 0x3b,
+ 0x52, 0x52, 0x37, 0x03, 0xb3, 0x4a, 0xa4, 0x9b, 0x29, 0xbe, 0xf2, 0xdc, 0x08, 0x20, 0x54, 0xc9,
+ 0x70, 0x1d, 0xcd, 0x0f, 0xdc, 0x9e, 0x39, 0xe8, 0xee, 0x0f, 0xcc, 0xbe, 0x5f, 0xfa, 0x8f, 0x59,
+ 0xd8, 0x54, 0xf0, 0x0e, 0xc0, 0x37, 0x39, 0xac, 0x36, 0x23, 0x86, 0x08, 0xd5, 0xe4, 0xf8, 0x11,
+ 0x5a, 0x90, 0x61, 0x24, 0x7c, 0xec, 0x3f, 0x67, 0xc1, 0x43, 0xe0, 0x6c, 0xa4, 0x40, 0x7a, 0xd9,
+ 0xb2, 0x1e, 0x7d, 0xc2, 0xcd, 0x74, 0x06, 0xfe, 0x1c, 0xdd, 0xb0, 0x1d, 0xd7, 0x62, 0xdd, 0xde,
+ 0x81, 0xe9, 0xf4, 0x19, 0x3f, 0x9f, 0xc9, 0x2c, 0x44, 0x23, 0xf8, 0x3f, 0xc8, 0x6a, 0x20, 0x82,
+ 0x33, 0xba, 0x29, 0xab, 0xa7, 0x86, 0x12, 0x9a, 0x64, 0xe1, 0x63, 0xa4, 0x95, 0x95, 0x6e, 0xe0,
+ 0x99, 0xf6, 0x80, 0x79, 0xe2, 0xbc, 0xfe, 0x6b, 0x16, 0x0e, 0xec, 0xd3, 0x49, 0x68, 0xdc, 0x8e,
+ 0x39, 0xbb, 0x82, 0x22, 0x0f, 0xeb, 0xde, 0xb9, 0x92, 0xa5, 0x49, 0x95, 0x47, 0x5c, 0xae, 0x8c,
+ 0x7f, 0xcc, 0xbb, 0x48, 0xde, 0xe9, 0x5a, 0xb2, 0xa5, 0xbd, 0x2f, 0xfa, 0x45, 0x80, 0x54, 0x2a,
+ 0x92, 0x63, 0x68, 0x18, 0xe1, 0x17, 0xa6, 0x68, 0xd6, 0x76, 0x0e, 0xcd, 0x81, 0x1d, 0xb5, 0xac,
+ 0x1f, 0xbc, 0x0a, 0x0d, 0x44, 0xcd, 0xa3, 0xa6, 0x40, 0x45, 0x07, 0x01, 0x3f, 0xb5, 0x0e, 0x02,
+ 0xc6, 0xbc, 0x83, 0xd0, 0x98, 0x34, 0xe2, 0xf1, 0xb4, 0xe2, 0xb8, 0x89, 0x5b, 0x41, 0x01, 0x4c,
+ 0xc3, 0xb6, 0x3a, 0x6e, 0xf2, 0x46, 0x20, 0xb6, 0x35, 0x81, 0x12, 0x9a, 0x64, 0x7d, 0x98, 0xfd,
+ 0x9b, 0xdf, 0x18, 0x33, 0xe4, 0xab, 0x14, 0x9a, 0x53, 0x29, 0x8e, 0x57, 0x17, 0x38, 0xff, 0x0c,
+ 0x1c, 0x3f, 0x44, 0xf3, 0x81, 0x38, 0x77, 0x11, 0xcd, 0x07, 0x70, 0xe0, 0x80, 0xf1, 0xea, 0xe9,
+ 0xee, 0xef, 0xfb, 0x2c, 0x80, 0xba, 0x95, 0x11, 0xd5, 0x53, 0x20, 0xaa, 0x7a, 0x8a, 0x21, 0xa1,
+ 0x12, 0xc7, 0xef, 0xc9, 0xea, 0x95, 0x86, 0x63, 0x7b, 0xfb, 0xf2, 0xea, 0x15, 0x1d, 0x8a, 0x28,
+ 0x62, 0x1f, 0xa1, 0xb9, 0x23, 0x66, 0x3e, 0x17, 0x7e, 0x29, 0x52, 0x06, 0xe4, 0x75, 0x0e, 0x4a,
+ 0x9f, 0x14, 0xd1, 0x11, 0x01, 0x84, 0x2a, 0x99, 0xfc, 0xc6, 0x67, 0x28, 0x2f, 0xca, 0x09, 0xde,
+ 0x41, 0x85, 0x9e, 0x3b, 0x76, 0x82, 0xf8, 0x52, 0xba, 0xac, 0x77, 0xc3, 0x20, 0xa9, 0xfe, 0x5e,
+ 0x14, 0x80, 0x11, 0x55, 0x9d, 0x91, 0x04, 0x78, 0x1b, 0x2b, 0x45, 0xe4, 0xe7, 0x29, 0x34, 0x2b,
+ 0x15, 0xf1, 0x23, 0x75, 0x39, 0xc8, 0x56, 0x3f, 0x38, 0x57, 0x25, 0xbf, 0xfe, 0xa2, 0xa9, 0x57,
+ 0x48, 0x79, 0xe7, 0x3c, 0x34, 0x07, 0x63, 0xb1, 0x51, 0x59, 0x71, 0xe7, 0x04, 0x40, 0x15, 0x1d,
+ 0x18, 0x11, 0x2a, 0x50, 0xf2, 0xf3, 0x2c, 0x5a, 0xd0, 0x93, 0x08, 0x4f, 0xd7, 0x63, 0xc7, 0x3e,
+ 0x86, 0xc5, 0x24, 0xba, 0x94, 0x27, 0x8e, 0x7d, 0x0c, 0x69, 0xa6, 0xfc, 0x22, 0x34, 0x52, 0xfc,
+ 0x00, 0x38, 0x4f, 0x1d, 0x00, 0x1f, 0x10, 0x0a, 0x18, 0xfe, 0x1c, 0xcd, 0x1e, 0xd9, 0x8e, 0xe5,
+ 0x1e, 0xf9, 0xb0, 0x8c, 0x79, 0xfd, 0xe6, 0xf0, 0x85, 0x10, 0x80, 0xa5, 0x8a, 0xb4, 0x14, 0xb1,
+ 0xd5, 0x76, 0xc9, 0x31, 0xa1, 0x91, 0x04, 0x6f, 0xa1, 0xdc, 0xc0, 0x76, 0xc6, 0xc7, 0xe0, 0x60,
+ 0x89, 0x32, 0xfb, 0x53, 0x33, 0x08, 0x3c, 0x30, 0x77, 0x5f, 0x9a, 0x13, 0xcc, 0xf8, 0x92, 0xcd,
+ 0x47, 0xfc, 0x92, 0xcd, 0xff, 0xc5, 0x9f, 0xa1, 0xbc, 0x65, 0x7a, 0x47, 0xb6, 0xb8, 0xd4, 0x5c,
+ 0x61, 0x69, 0x45, 0x5a, 0x92, 0xd4, 0xf8, 0x82, 0x07, 0x43, 0x42, 0x25, 0x8e, 0x19, 0x9a, 0xdd,
+ 0xf7, 0x18, 0xdb, 0xf3, 0x2d, 0x68, 0x92, 0xae, 0xb0, 0xf6, 0x63, 0x6e, 0x8d, 0x5f, 0x03, 0x36,
+ 0x3d, 0xc6, 0xaa, 0x1d, 0xb8, 0x06, 0x48, 0x35, 0xf5, 0xc5, 0x72, 0x0c, 0xd7, 0x00, 0x49, 0xa3,
+ 0x11, 0x09, 0x77, 0x51, 0xde, 0x61, 0x01, 0x9f, 0x25, 0x7f, 0xf5, 0x2c, 0xeb, 0x72, 0x96, 0x7c,
+ 0x8b, 0x05, 0x62, 0x12, 0xa9, 0xa4, 0x56, 0x2f, 0x86, 0x7c, 0x0a, 0xc9, 0xa1, 0x92, 0x41, 0x7e,
+ 0x91, 0x46, 0x85, 0xe8, 0x7c, 0x79, 0xf3, 0xe7, 0x1e, 0x39, 0xcc, 0xd3, 0x9f, 0xee, 0xa0, 0xe2,
+ 0x03, 0x2a, 0xaf, 0x67, 0xa2, 0x90, 0x29, 0x84, 0xd0, 0x58, 0xca, 0x0d, 0xf4, 0x3d, 0x77, 0x3c,
+ 0xd2, 0x9f, 0xed, 0xc0, 0x00, 0xa0, 0x09, 0x03, 0x0a, 0x21, 0x34, 0x96, 0xe2, 0x8f, 0x50, 0x66,
+ 0x6c, 0x5b, 0x70, 0xd4, 0xb9, 0xea, 0x3b, 0xaf, 0x42, 0x23, 0xf3, 0x04, 0x22, 0x80, 0xa3, 0xd3,
+ 0xd0, 0x98, 0x13, 0x0e, 0x67, 0x5b, 0x5a, 0xf9, 0xe4, 0x0c, 0xca, 0xe5, 0x5c, 0xb9, 0x6f, 0x5b,
+ 0xf2, 0x4d, 0x0e, 0x94, 0xb7, 0x84, 0x72, 0x5f, 0x53, 0xee, 0x27, 0x95, 0xb7, 0xb8, 0x32, 0xc7,
+ 0x7e, 0x9d, 0x42, 0xf3, 0x9a, 0x87, 0x7e, 0xfb, 0xbd, 0xd8, 0x46, 0x4b, 0xc2, 0x80, 0xed, 0x77,
+ 0xe1, 0x03, 0xe5, 0x1b, 0x14, 0x3c, 0x9b, 0x80, 0xa4, 0xe9, 0x6f, 0x71, 0x5c, 0x3d, 0x9b, 0xe8,
+ 0x20, 0xa1, 0x09, 0x0e, 0xe9, 0xa0, 0x39, 0x75, 0xe0, 0x78, 0x13, 0xe5, 0x8f, 0xf9, 0x20, 0x4a,
+ 0x48, 0x37, 0xce, 0x79, 0x45, 0xdc, 0x76, 0x0a, 0x9a, 0x0a, 0x08, 0x18, 0x12, 0x2a, 0x61, 0xd2,
+ 0x43, 0x39, 0xe0, 0xbf, 0xd1, 0x6d, 0x22, 0x91, 0x67, 0x16, 0xfe, 0xef, 0x3c, 0xf3, 0x17, 0x59,
+ 0x34, 0x4b, 0x79, 0xd3, 0xec, 0x07, 0xf8, 0x47, 0x2a, 0xdb, 0xe5, 0xaa, 0xdf, 0xbd, 0x2a, 0xbd,
+ 0xc5, 0xa7, 0x13, 0xbd, 0x7e, 0xc4, 0x97, 0xae, 0xf4, 0xb5, 0x2f, 0x5d, 0xd1, 0x27, 0x65, 0xae,
+ 0xf1, 0x49, 0x71, 0x59, 0xca, 0xbe, 0x71, 0x59, 0xca, 0x5d, 0xbf, 0x2c, 0x45, 0x95, 0x32, 0x7f,
+ 0x8d, 0x4a, 0xd9, 0x46, 0x4b, 0xfb, 0x9e, 0x3b, 0x84, 0x37, 0x32, 0xd7, 0x33, 0xbd, 0x13, 0xd9,
+ 0x15, 0x40, 0xe9, 0xe6, 0x92, 0xdd, 0x48, 0xa0, 0x4a, 0x77, 0x02, 0x25, 0x34, 0xc9, 0x4a, 0xd6,
+ 0xc4, 0xc2, 0x9b, 0xd5, 0x44, 0xfc, 0x09, 0x2a, 0x88, 0x8e, 0xd7, 0x71, 0xe1, 0xda, 0x95, 0xab,
+ 0x7e, 0x87, 0xa7, 0x32, 0xc0, 0x5a, 0xae, 0x4a, 0x65, 0x72, 0xac, 0x3e, 0x3b, 0x22, 0x90, 0x7f,
+ 0x4c, 0xa1, 0x02, 0x65, 0xfe, 0xc8, 0x75, 0x7c, 0xf6, 0x4d, 0x9d, 0x60, 0x0d, 0x65, 0x2d, 0x33,
+ 0x30, 0xa5, 0xdb, 0xc1, 0xee, 0xf1, 0xb1, 0xda, 0x3d, 0x3e, 0x20, 0x14, 0x30, 0xfc, 0x29, 0xca,
+ 0xf6, 0x5c, 0x4b, 0x1c, 0xfe, 0x92, 0x9e, 0x34, 0x1b, 0x9e, 0xe7, 0x7a, 0x35, 0xd7, 0x92, 0xd7,
+ 0x0e, 0x4e, 0x52, 0x06, 0xf8, 0x80, 0x50, 0xc0, 0xc8, 0x3f, 0xa4, 0x50, 0xb1, 0xee, 0x1e, 0x39,
+ 0x03, 0xd7, 0xb4, 0x76, 0x3c, 0xb7, 0xef, 0x31, 0xdf, 0xff, 0x46, 0x77, 0xff, 0x2e, 0x9a, 0x1d,
+ 0xc3, 0xcb, 0x41, 0x74, 0xfb, 0x7f, 0x90, 0xbc, 0x06, 0x9d, 0x9f, 0x44, 0x3c, 0x33, 0xc4, 0x0f,
+ 0x8d, 0x52, 0x59, 0xd9, 0x17, 0x63, 0x42, 0x23, 0x01, 0xf9, 0xfb, 0x0c, 0x2a, 0x5f, 0x6d, 0x08,
+ 0x0f, 0xd1, 0xbc, 0x60, 0x76, 0xb5, 0xbf, 0x09, 0xac, 0x5e, 0x67, 0x0d, 0x70, 0x39, 0x83, 0x4b,
+ 0xc1, 0x58, 0x8d, 0xd5, 0xa5, 0x20, 0x86, 0x08, 0xd5, 0xe4, 0x6f, 0xf4, 0x4e, 0xa9, 0x5d, 0xe5,
+ 0x33, 0xdf, 0xfe, 0x2a, 0xdf, 0x41, 0x8b, 0xc2, 0x45, 0xa3, 0x07, 0xe5, 0x6c, 0x25, 0xb3, 0x9a,
+ 0xab, 0x3e, 0xe4, 0xd9, 0x76, 0x4f, 0x34, 0xab, 0xd1, 0x53, 0xf2, 0x72, 0xec, 0xac, 0x02, 0x8c,
+ 0xbc, 0xad, 0x38, 0x43, 0x13, 0x5c, 0xbc, 0x99, 0xb8, 0xe9, 0x89, 0x50, 0xff, 0x83, 0x6b, 0xde,
+ 0xec, 0xb4, 0x9b, 0x1c, 0xc9, 0xa3, 0xec, 0x8e, 0xed, 0xf4, 0xc9, 0x47, 0x28, 0x57, 0x1b, 0xb8,
+ 0x3e, 0x64, 0x1c, 0x8f, 0x99, 0xbe, 0xeb, 0xe8, 0xae, 0x24, 0x10, 0x75, 0xd4, 0x62, 0x48, 0xa8,
+ 0xc4, 0xd7, 0x7e, 0x9b, 0x41, 0xf3, 0xda, 0x9f, 0x70, 0xf0, 0x9f, 0xa0, 0x7b, 0x8f, 0x1b, 0x9d,
+ 0xce, 0xc6, 0x56, 0xa3, 0xbb, 0xfb, 0xe5, 0x4e, 0xa3, 0x5b, 0xdb, 0x7e, 0xd2, 0xd9, 0x6d, 0xd0,
+ 0x6e, 0xad, 0xdd, 0xda, 0x6c, 0x6e, 0x15, 0x67, 0xca, 0xf7, 0x4f, 0xcf, 0x2a, 0x25, 0x4d, 0x23,
+ 0xf9, 0xb7, 0x96, 0x3f, 0x44, 0x38, 0xa1, 0xde, 0x6c, 0xd5, 0x1b, 0x3f, 0x2d, 0xa6, 0xca, 0xb7,
+ 0x4e, 0xcf, 0x2a, 0x45, 0x4d, 0x4b, 0x3c, 0xc1, 0xfd, 0x31, 0x7a, 0xeb, 0x22, 0xbb, 0xfb, 0x64,
+ 0xa7, 0xbe, 0xb1, 0xdb, 0x28, 0xa6, 0xcb, 0xe5, 0xd3, 0xb3, 0xca, 0x9d, 0xf3, 0x4a, 0xd2, 0x05,
+ 0x7f, 0x80, 0x6e, 0x25, 0x54, 0x69, 0xe3, 0xf3, 0x27, 0x8d, 0xce, 0x6e, 0x31, 0x53, 0xbe, 0x73,
+ 0x7a, 0x56, 0xc1, 0x9a, 0x56, 0x54, 0x26, 0xd6, 0xd1, 0xed, 0x73, 0x1a, 0x9d, 0x9d, 0x76, 0xab,
+ 0xd3, 0x28, 0x66, 0xcb, 0x77, 0x4f, 0xcf, 0x2a, 0x37, 0x13, 0x2a, 0x32, 0xab, 0xd4, 0xd0, 0x4a,
+ 0x42, 0xa7, 0xde, 0xfe, 0xa2, 0xb5, 0xdd, 0xde, 0xa8, 0x77, 0x77, 0x68, 0x7b, 0x8b, 0x36, 0x3a,
+ 0x9d, 0x62, 0xae, 0x6c, 0x9c, 0x9e, 0x55, 0xee, 0x69, 0xca, 0x17, 0x22, 0x7c, 0x0d, 0x2d, 0x27,
+ 0x8c, 0xec, 0x34, 0x5b, 0x5b, 0xc5, 0x7c, 0xf9, 0xe6, 0xe9, 0x59, 0xe5, 0x86, 0xa6, 0xc7, 0xcf,
+ 0xf2, 0xc2, 0xfe, 0xd5, 0xb6, 0xdb, 0x9d, 0x46, 0x71, 0xf6, 0xc2, 0xfe, 0xc1, 0x81, 0xaf, 0xfd,
+ 0x5d, 0x0a, 0xe1, 0x8b, 0x7f, 0x35, 0xc3, 0x1f, 0xa0, 0x52, 0x64, 0xa4, 0xd6, 0x7e, 0xbc, 0xc3,
+ 0xd7, 0xd9, 0x6c, 0xb7, 0xba, 0xad, 0x76, 0xab, 0x51, 0x9c, 0x49, 0xec, 0xaa, 0xa6, 0xd5, 0x72,
+ 0x1d, 0x86, 0xdb, 0xe8, 0xee, 0x65, 0x9a, 0xdb, 0xcf, 0xde, 0x2f, 0xa6, 0xca, 0xeb, 0xa7, 0x67,
+ 0x95, 0xdb, 0x17, 0x15, 0xb7, 0x9f, 0xbd, 0xff, 0xbb, 0xbf, 0xfa, 0xee, 0xe5, 0x82, 0x35, 0xde,
+ 0x00, 0xe9, 0x4b, 0x7b, 0x0f, 0xdd, 0xd2, 0x0d, 0x3f, 0x6e, 0xec, 0x6e, 0xd4, 0x37, 0x76, 0x37,
+ 0x8a, 0x33, 0xe2, 0x0c, 0x34, 0xea, 0x63, 0x16, 0x98, 0x90, 0x76, 0xbf, 0x87, 0x96, 0x13, 0x5f,
+ 0xd1, 0x78, 0xda, 0xa0, 0x91, 0x47, 0xe9, 0xeb, 0x67, 0x87, 0xcc, 0xc3, 0xdf, 0x47, 0x58, 0x27,
+ 0x6f, 0x6c, 0x7f, 0xb1, 0xf1, 0x65, 0xa7, 0x98, 0x2e, 0xdf, 0x3e, 0x3d, 0xab, 0x2c, 0x6b, 0xec,
+ 0x8d, 0xc1, 0x91, 0x79, 0xe2, 0xaf, 0xfd, 0x73, 0x1a, 0x2d, 0xe8, 0xef, 0x46, 0xf8, 0xfb, 0xe8,
+ 0xe6, 0x66, 0x73, 0x9b, 0x7b, 0xe2, 0x66, 0x5b, 0x9c, 0x00, 0x1f, 0x16, 0x67, 0xc4, 0x74, 0x3a,
+ 0x95, 0xff, 0xc6, 0x7f, 0x84, 0x4a, 0xe7, 0xe8, 0xf5, 0x26, 0x6d, 0xd4, 0x76, 0xdb, 0xf4, 0xcb,
+ 0x62, 0xaa, 0xfc, 0x16, 0xdf, 0x30, 0x5d, 0xa7, 0x6e, 0x7b, 0x90, 0x82, 0x4e, 0xf0, 0x27, 0xe8,
+ 0xde, 0x39, 0xc5, 0xce, 0x97, 0x8f, 0xb7, 0x9b, 0xad, 0xcf, 0xc4, 0x7c, 0xe9, 0xf2, 0xdb, 0xa7,
+ 0x67, 0x95, 0xbb, 0xba, 0x6e, 0x47, 0x3c, 0xc5, 0x71, 0xa8, 0x90, 0xc2, 0x8f, 0x50, 0xe5, 0x0a,
+ 0xfd, 0x78, 0x01, 0x99, 0x32, 0x39, 0x3d, 0xab, 0xdc, 0xbf, 0xc4, 0x88, 0x5a, 0x47, 0x21, 0x85,
+ 0x7f, 0x88, 0xee, 0x5c, 0x6e, 0x29, 0x8a, 0x8b, 0x4b, 0xf4, 0xd7, 0xfe, 0x35, 0x85, 0xe6, 0x54,
+ 0xd5, 0xe3, 0x9b, 0xd6, 0xa0, 0xb4, 0xcd, 0x93, 0x44, 0xbd, 0xd1, 0x6d, 0xb5, 0xbb, 0x30, 0x8a,
+ 0x36, 0x4d, 0xf1, 0x5a, 0x2e, 0xfc, 0xe4, 0x3e, 0xae, 0xd1, 0xb7, 0x1a, 0xad, 0x06, 0x6d, 0xd6,
+ 0xa2, 0x13, 0x55, 0xec, 0x2d, 0xe6, 0x30, 0xcf, 0xee, 0xe1, 0xf7, 0xd1, 0xdd, 0xa4, 0xf1, 0xce,
+ 0x93, 0xda, 0xa3, 0x68, 0x97, 0x60, 0x81, 0xda, 0x04, 0x9d, 0x71, 0xef, 0x00, 0x0e, 0xe6, 0x47,
+ 0x09, 0xad, 0x66, 0xeb, 0xe9, 0xc6, 0x76, 0xb3, 0x2e, 0xb4, 0x32, 0xe5, 0xd2, 0xe9, 0x59, 0xe5,
+ 0x96, 0xd2, 0x92, 0x0f, 0x1c, 0x5c, 0x6d, 0xed, 0x77, 0x29, 0xb4, 0xf2, 0xf5, 0xc5, 0x0b, 0x7f,
+ 0x81, 0xde, 0x81, 0xfd, 0xba, 0x90, 0x0a, 0x64, 0xde, 0x12, 0x7b, 0xb8, 0xb1, 0xb3, 0xd3, 0x68,
+ 0xd5, 0x8b, 0x33, 0xe5, 0xd5, 0xd3, 0xb3, 0xca, 0x83, 0xaf, 0x37, 0xb9, 0x31, 0x1a, 0x31, 0xc7,
+ 0xba, 0xa6, 0xe1, 0xcd, 0x36, 0xdd, 0x6a, 0xec, 0x16, 0x53, 0xd7, 0x31, 0xbc, 0xe9, 0x7a, 0x7d,
+ 0x16, 0x54, 0x1f, 0xbf, 0xf8, 0x6a, 0x65, 0xe6, 0xe5, 0x57, 0x2b, 0x33, 0x2f, 0x5e, 0xad, 0xa4,
+ 0x5e, 0xbe, 0x5a, 0x49, 0xfd, 0xf5, 0xeb, 0x95, 0x99, 0xdf, 0xbc, 0x5e, 0x49, 0xbd, 0x7c, 0xbd,
+ 0x32, 0xf3, 0x6f, 0xaf, 0x57, 0x66, 0x9e, 0x7d, 0xaf, 0x6f, 0x07, 0x07, 0xe3, 0xbd, 0x87, 0x3d,
+ 0x77, 0xf8, 0xae, 0x7f, 0xe2, 0xf4, 0x82, 0x03, 0xdb, 0xe9, 0x6b, 0xbf, 0xf4, 0xff, 0xd9, 0xb1,
+ 0x97, 0x87, 0x5f, 0x3f, 0xfc, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x35, 0x55, 0x08, 0x69, 0xf0,
+ 0x21, 0x00, 0x00,
}
func (m *Hello) Marshal() (dAtA []byte, err error) {
@@ -1363,6 +1372,16 @@ func (m *Hello) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
+ if m.Timestamp != 0 {
+ i = encodeVarintBep(dAtA, i, uint64(m.Timestamp))
+ i--
+ dAtA[i] = 0x28
+ }
+ if m.NumConnections != 0 {
+ i = encodeVarintBep(dAtA, i, uint64(m.NumConnections))
+ i--
+ dAtA[i] = 0x20
+ }
if len(m.ClientVersion) > 0 {
i -= len(m.ClientVersion)
copy(dAtA[i:], m.ClientVersion)
@@ -1440,6 +1459,16 @@ func (m *ClusterConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
+ if m.Secondary {
+ i--
+ if m.Secondary {
+ dAtA[i] = 1
+ } else {
+ dAtA[i] = 0
+ }
+ i--
+ dAtA[i] = 0x10
+ }
if len(m.Folders) > 0 {
for iNdEx := len(m.Folders) - 1; iNdEx >= 0; iNdEx-- {
{
@@ -2612,6 +2641,12 @@ func (m *Hello) ProtoSize() (n int) {
if l > 0 {
n += 1 + l + sovBep(uint64(l))
}
+ if m.NumConnections != 0 {
+ n += 1 + sovBep(uint64(m.NumConnections))
+ }
+ if m.Timestamp != 0 {
+ n += 1 + sovBep(uint64(m.Timestamp))
+ }
return n
}
@@ -2642,6 +2677,9 @@ func (m *ClusterConfig) ProtoSize() (n int) {
n += 1 + l + sovBep(uint64(l))
}
}
+ if m.Secondary {
+ n += 2
+ }
return n
}
@@ -3258,6 +3296,44 @@ func (m *Hello) Unmarshal(dAtA []byte) error {
}
m.ClientVersion = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
+ case 4:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field NumConnections", wireType)
+ }
+ m.NumConnections = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowBep
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.NumConnections |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 5:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
+ }
+ m.Timestamp = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowBep
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Timestamp |= int64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
default:
iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:])
@@ -3430,6 +3506,26 @@ func (m *ClusterConfig) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
+ case 2:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Secondary", wireType)
+ }
+ var v int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowBep
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ v |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ m.Secondary = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:])
diff --git a/lib/protocol/encryption.go b/lib/protocol/encryption.go
index 1149aeb28..ce883fdf0 100644
--- a/lib/protocol/encryption.go
+++ b/lib/protocol/encryption.go
@@ -43,12 +43,12 @@ const (
// 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 contextLessModel
+ model rawModel
folderKeys *folderKeyRegistry
keyGen *KeyGenerator
}
-func newEncryptedModel(model contextLessModel, folderKeys *folderKeyRegistry, keyGen *KeyGenerator) encryptedModel {
+func newEncryptedModel(model rawModel, folderKeys *folderKeyRegistry, keyGen *KeyGenerator) encryptedModel {
return encryptedModel{
model: model,
folderKeys: folderKeys,
diff --git a/lib/protocol/hello.go b/lib/protocol/hello.go
index 82d66746b..8a9c8c864 100644
--- a/lib/protocol/hello.go
+++ b/lib/protocol/hello.go
@@ -8,14 +8,6 @@ import (
"io"
)
-// The HelloIntf interface is implemented by the version specific hello
-// message. It knows its magic number and how to serialize itself to a byte
-// buffer.
-type HelloIntf interface {
- Magic() uint32
- Marshal() ([]byte, error)
-}
-
var (
// ErrTooOldVersion is returned by ExchangeHello when the other side
// speaks an older, incompatible version of the protocol.
@@ -25,7 +17,10 @@ var (
ErrUnknownMagic = errors.New("the remote device speaks an unknown (newer?) version of the protocol")
)
-func ExchangeHello(c io.ReadWriter, h HelloIntf) (Hello, error) {
+func ExchangeHello(c io.ReadWriter, h Hello) (Hello, error) {
+ if h.Timestamp == 0 {
+ panic("bug: missing timestamp in outgoing hello")
+ }
if err := writeHello(c, h); err != nil {
return Hello{}, err
}
@@ -80,7 +75,7 @@ func readHello(c io.Reader) (Hello, error) {
return Hello{}, ErrUnknownMagic
}
-func writeHello(c io.Writer, h HelloIntf) error {
+func writeHello(c io.Writer, h Hello) error {
msg, err := h.Marshal()
if err != nil {
return err
diff --git a/lib/protocol/hello_test.go b/lib/protocol/hello_test.go
index 6576a639e..ce3bfc984 100644
--- a/lib/protocol/hello_test.go
+++ b/lib/protocol/hello_test.go
@@ -35,10 +35,11 @@ func TestVersion14Hello(t *testing.T) {
conn := &readWriter{outBuf, inBuf}
- send := &Hello{
+ send := Hello{
DeviceName: "this device",
ClientName: "other client",
ClientVersion: "v0.14.6",
+ Timestamp: 1234567890,
}
res, err := ExchangeHello(conn, send)
@@ -80,10 +81,11 @@ func TestOldHelloMsgs(t *testing.T) {
conn := &readWriter{outBuf, inBuf}
- send := &Hello{
+ send := Hello{
DeviceName: "this device",
ClientName: "other client",
ClientVersion: "v1.0.0",
+ Timestamp: 1234567890,
}
_, err := ExchangeHello(conn, send)
diff --git a/lib/protocol/mocked_connection_info_test.go b/lib/protocol/mocked_connection_info_test.go
index aab43306f..85f46871c 100644
--- a/lib/protocol/mocked_connection_info_test.go
+++ b/lib/protocol/mocked_connection_info_test.go
@@ -8,6 +8,16 @@ import (
)
type mockedConnectionInfo struct {
+ ConnectionIDStub func() string
+ connectionIDMutex sync.RWMutex
+ connectionIDArgsForCall []struct {
+ }
+ connectionIDReturns struct {
+ result1 string
+ }
+ connectionIDReturnsOnCall map[int]struct {
+ result1 string
+ }
CryptoStub func() string
cryptoMutex sync.RWMutex
cryptoArgsForCall []struct {
@@ -92,6 +102,59 @@ type mockedConnectionInfo struct {
invocationsMutex sync.RWMutex
}
+func (fake *mockedConnectionInfo) ConnectionID() string {
+ fake.connectionIDMutex.Lock()
+ ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
+ fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
+ }{})
+ stub := fake.ConnectionIDStub
+ fakeReturns := fake.connectionIDReturns
+ fake.recordInvocation("ConnectionID", []interface{}{})
+ fake.connectionIDMutex.Unlock()
+ if stub != nil {
+ return stub()
+ }
+ if specificReturn {
+ return ret.result1
+ }
+ return fakeReturns.result1
+}
+
+func (fake *mockedConnectionInfo) ConnectionIDCallCount() int {
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
+ return len(fake.connectionIDArgsForCall)
+}
+
+func (fake *mockedConnectionInfo) ConnectionIDCalls(stub func() string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = stub
+}
+
+func (fake *mockedConnectionInfo) ConnectionIDReturns(result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ fake.connectionIDReturns = struct {
+ result1 string
+ }{result1}
+}
+
+func (fake *mockedConnectionInfo) ConnectionIDReturnsOnCall(i int, result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ if fake.connectionIDReturnsOnCall == nil {
+ fake.connectionIDReturnsOnCall = make(map[int]struct {
+ result1 string
+ })
+ }
+ fake.connectionIDReturnsOnCall[i] = struct {
+ result1 string
+ }{result1}
+}
+
func (fake *mockedConnectionInfo) Crypto() string {
fake.cryptoMutex.Lock()
ret, specificReturn := fake.cryptoReturnsOnCall[len(fake.cryptoArgsForCall)]
@@ -519,6 +582,8 @@ func (fake *mockedConnectionInfo) TypeReturnsOnCall(i int, result1 string) {
func (fake *mockedConnectionInfo) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
fake.cryptoMutex.RLock()
defer fake.cryptoMutex.RUnlock()
fake.establishedAtMutex.RLock()
diff --git a/lib/protocol/mocks/connection.go b/lib/protocol/mocks/connection.go
index 3c4f60b2c..de1d7c287 100644
--- a/lib/protocol/mocks/connection.go
+++ b/lib/protocol/mocks/connection.go
@@ -31,6 +31,16 @@ type Connection struct {
clusterConfigArgsForCall []struct {
arg1 protocol.ClusterConfig
}
+ ConnectionIDStub func() string
+ connectionIDMutex sync.RWMutex
+ connectionIDArgsForCall []struct {
+ }
+ connectionIDReturns struct {
+ result1 string
+ }
+ connectionIDReturnsOnCall map[int]struct {
+ result1 string
+ }
CryptoStub func() string
cryptoMutex sync.RWMutex
cryptoArgsForCall []struct {
@@ -315,6 +325,59 @@ func (fake *Connection) ClusterConfigArgsForCall(i int) protocol.ClusterConfig {
return argsForCall.arg1
}
+func (fake *Connection) ConnectionID() string {
+ fake.connectionIDMutex.Lock()
+ ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
+ fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
+ }{})
+ stub := fake.ConnectionIDStub
+ fakeReturns := fake.connectionIDReturns
+ fake.recordInvocation("ConnectionID", []interface{}{})
+ fake.connectionIDMutex.Unlock()
+ if stub != nil {
+ return stub()
+ }
+ if specificReturn {
+ return ret.result1
+ }
+ return fakeReturns.result1
+}
+
+func (fake *Connection) ConnectionIDCallCount() int {
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
+ return len(fake.connectionIDArgsForCall)
+}
+
+func (fake *Connection) ConnectionIDCalls(stub func() string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = stub
+}
+
+func (fake *Connection) ConnectionIDReturns(result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ fake.connectionIDReturns = struct {
+ result1 string
+ }{result1}
+}
+
+func (fake *Connection) ConnectionIDReturnsOnCall(i int, result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ if fake.connectionIDReturnsOnCall == nil {
+ fake.connectionIDReturnsOnCall = make(map[int]struct {
+ result1 string
+ })
+ }
+ fake.connectionIDReturnsOnCall[i] = struct {
+ result1 string
+ }{result1}
+}
+
func (fake *Connection) Crypto() string {
fake.cryptoMutex.Lock()
ret, specificReturn := fake.cryptoReturnsOnCall[len(fake.cryptoArgsForCall)]
@@ -1162,6 +1225,8 @@ func (fake *Connection) Invocations() map[string][][]interface{} {
defer fake.closedMutex.RUnlock()
fake.clusterConfigMutex.RLock()
defer fake.clusterConfigMutex.RUnlock()
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
fake.cryptoMutex.RLock()
defer fake.cryptoMutex.RUnlock()
fake.deviceIDMutex.RLock()
diff --git a/lib/protocol/mocks/connection_info.go b/lib/protocol/mocks/connection_info.go
index 9e9dd4728..5f176fc38 100644
--- a/lib/protocol/mocks/connection_info.go
+++ b/lib/protocol/mocks/connection_info.go
@@ -10,6 +10,16 @@ import (
)
type ConnectionInfo struct {
+ ConnectionIDStub func() string
+ connectionIDMutex sync.RWMutex
+ connectionIDArgsForCall []struct {
+ }
+ connectionIDReturns struct {
+ result1 string
+ }
+ connectionIDReturnsOnCall map[int]struct {
+ result1 string
+ }
CryptoStub func() string
cryptoMutex sync.RWMutex
cryptoArgsForCall []struct {
@@ -94,6 +104,59 @@ type ConnectionInfo struct {
invocationsMutex sync.RWMutex
}
+func (fake *ConnectionInfo) ConnectionID() string {
+ fake.connectionIDMutex.Lock()
+ ret, specificReturn := fake.connectionIDReturnsOnCall[len(fake.connectionIDArgsForCall)]
+ fake.connectionIDArgsForCall = append(fake.connectionIDArgsForCall, struct {
+ }{})
+ stub := fake.ConnectionIDStub
+ fakeReturns := fake.connectionIDReturns
+ fake.recordInvocation("ConnectionID", []interface{}{})
+ fake.connectionIDMutex.Unlock()
+ if stub != nil {
+ return stub()
+ }
+ if specificReturn {
+ return ret.result1
+ }
+ return fakeReturns.result1
+}
+
+func (fake *ConnectionInfo) ConnectionIDCallCount() int {
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
+ return len(fake.connectionIDArgsForCall)
+}
+
+func (fake *ConnectionInfo) ConnectionIDCalls(stub func() string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = stub
+}
+
+func (fake *ConnectionInfo) ConnectionIDReturns(result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ fake.connectionIDReturns = struct {
+ result1 string
+ }{result1}
+}
+
+func (fake *ConnectionInfo) ConnectionIDReturnsOnCall(i int, result1 string) {
+ fake.connectionIDMutex.Lock()
+ defer fake.connectionIDMutex.Unlock()
+ fake.ConnectionIDStub = nil
+ if fake.connectionIDReturnsOnCall == nil {
+ fake.connectionIDReturnsOnCall = make(map[int]struct {
+ result1 string
+ })
+ }
+ fake.connectionIDReturnsOnCall[i] = struct {
+ result1 string
+ }{result1}
+}
+
func (fake *ConnectionInfo) Crypto() string {
fake.cryptoMutex.Lock()
ret, specificReturn := fake.cryptoReturnsOnCall[len(fake.cryptoArgsForCall)]
@@ -521,6 +584,8 @@ func (fake *ConnectionInfo) TypeReturnsOnCall(i int, result1 string) {
func (fake *ConnectionInfo) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
+ fake.connectionIDMutex.RLock()
+ defer fake.connectionIDMutex.RUnlock()
fake.cryptoMutex.RLock()
defer fake.cryptoMutex.RUnlock()
fake.establishedAtMutex.RLock()
diff --git a/lib/protocol/nativemodel_darwin.go b/lib/protocol/nativemodel_darwin.go
index b51513df7..1cf6f5caf 100644
--- a/lib/protocol/nativemodel_darwin.go
+++ b/lib/protocol/nativemodel_darwin.go
@@ -9,27 +9,27 @@ package protocol
import "golang.org/x/text/unicode/norm"
-func makeNative(m contextLessModel) contextLessModel { return nativeModel{m} }
+func makeNative(m rawModel) rawModel { return nativeModel{m} }
type nativeModel struct {
- contextLessModel
+ rawModel
}
func (m nativeModel) Index(folder string, files []FileInfo) error {
for i := range files {
files[i].Name = norm.NFD.String(files[i].Name)
}
- return m.contextLessModel.Index(folder, files)
+ return m.rawModel.Index(folder, files)
}
func (m nativeModel) IndexUpdate(folder string, files []FileInfo) error {
for i := range files {
files[i].Name = norm.NFD.String(files[i].Name)
}
- return m.contextLessModel.IndexUpdate(folder, files)
+ return m.rawModel.IndexUpdate(folder, files)
}
func (m nativeModel) Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
name = norm.NFD.String(name)
- return m.contextLessModel.Request(folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
+ return m.rawModel.Request(folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}
diff --git a/lib/protocol/nativemodel_unix.go b/lib/protocol/nativemodel_unix.go
index c714309c6..08bcc193c 100644
--- a/lib/protocol/nativemodel_unix.go
+++ b/lib/protocol/nativemodel_unix.go
@@ -7,4 +7,4 @@ package protocol
// Normal Unixes uses NFC and slashes, which is the wire format.
-func makeNative(m contextLessModel) contextLessModel { return m }
+func makeNative(m rawModel) rawModel { return m }
diff --git a/lib/protocol/nativemodel_windows.go b/lib/protocol/nativemodel_windows.go
index efc47d172..e0aada52a 100644
--- a/lib/protocol/nativemodel_windows.go
+++ b/lib/protocol/nativemodel_windows.go
@@ -13,20 +13,20 @@ import (
"strings"
)
-func makeNative(m contextLessModel) contextLessModel { return nativeModel{m} }
+func makeNative(m rawModel) rawModel { return nativeModel{m} }
type nativeModel struct {
- contextLessModel
+ rawModel
}
func (m nativeModel) Index(folder string, files []FileInfo) error {
files = fixupFiles(files)
- return m.contextLessModel.Index(folder, files)
+ return m.rawModel.Index(folder, files)
}
func (m nativeModel) IndexUpdate(folder string, files []FileInfo) error {
files = fixupFiles(files)
- return m.contextLessModel.IndexUpdate(folder, files)
+ return m.rawModel.IndexUpdate(folder, files)
}
func (m nativeModel) Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
@@ -36,7 +36,7 @@ func (m nativeModel) Request(folder, name string, blockNo, size int32, offset in
}
name = filepath.FromSlash(name)
- return m.contextLessModel.Request(folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
+ return m.rawModel.Request(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 9189151c2..8e5f653d9 100644
--- a/lib/protocol/protocol.go
+++ b/lib/protocol/protocol.go
@@ -136,9 +136,9 @@ type Model interface {
DownloadProgress(conn Connection, folder string, updates []FileDownloadProgressUpdate) error
}
-// contextLessModel is the Model interface, but without the initial
-// Connection parameter. Internal use only.
-type contextLessModel interface {
+// rawModel is the Model interface, but without the initial Connection
+// parameter. Internal use only.
+type rawModel interface {
Index(folder string, files []FileInfo) error
IndexUpdate(folder string, files []FileInfo) error
Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
@@ -177,6 +177,7 @@ type ConnectionInfo interface {
String() string
Crypto() string
EstablishedAt() time.Time
+ ConnectionID() string
}
type rawConnection struct {
@@ -184,8 +185,9 @@ type rawConnection struct {
deviceID DeviceID
idString string
- model contextLessModel
+ model rawModel
startTime time.Time
+ started chan struct{}
cr *countingReader
cw *countingWriter
@@ -263,7 +265,7 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer
return wc
}
-func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver contextLessModel, connInfo ConnectionInfo, compress Compression) *rawConnection {
+func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver rawModel, connInfo ConnectionInfo, compress Compression) *rawConnection {
idString := deviceID.String()
cr := &countingReader{Reader: reader, idString: idString}
cw := &countingWriter{Writer: writer, idString: idString}
@@ -274,6 +276,7 @@ func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, clo
deviceID: deviceID,
idString: deviceID.String(),
model: receiver,
+ started: make(chan struct{}),
cr: cr,
cw: cw,
closer: closer,
@@ -315,6 +318,7 @@ func (c *rawConnection) Start() {
c.loopWG.Done()
}()
c.startTime = time.Now().Truncate(time.Second)
+ close(c.started)
}
func (c *rawConnection) DeviceID() DeviceID {
@@ -960,9 +964,9 @@ func (c *rawConnection) Close(err error) {
// internalClose is called if there is an unexpected error during normal operation.
func (c *rawConnection) internalClose(err error) {
c.closeOnce.Do(func() {
- l.Debugln("close due to", err)
+ l.Debugf("close connection to %s at %s due to %v", c.deviceID.Short(), c.ConnectionInfo, err)
if cerr := c.closer.Close(); cerr != nil {
- l.Debugln(c.deviceID, "failed to close underlying conn:", cerr)
+ l.Debugf("failed to close underlying conn %s at %s %v:", c.deviceID.Short(), c.ConnectionInfo, cerr)
}
close(c.closed)
@@ -975,7 +979,11 @@ func (c *rawConnection) internalClose(err error) {
}
c.awaitingMut.Unlock()
- <-c.dispatcherLoopStopped
+ if !c.startTime.IsZero() {
+ // Wait for the dispatcher loop to exit, if it was started to
+ // begin with.
+ <-c.dispatcherLoopStopped
+ }
c.model.Closed(err)
})
@@ -1108,7 +1116,7 @@ func messageContext(msg message) (string, error) {
// connectionWrappingModel takes the Model interface from the model package,
// which expects the Connection as the first parameter in all methods, and
-// wraps it to conform to the protocol.contextLessModel interface.
+// wraps it to conform to the rawModel interface.
type connectionWrappingModel struct {
conn Connection
model Model
diff --git a/lib/sliceutil/sliceutil.go b/lib/sliceutil/sliceutil.go
new file mode 100644
index 000000000..f16c67622
--- /dev/null
+++ b/lib/sliceutil/sliceutil.go
@@ -0,0 +1,16 @@
+// Copyright (C) 2023 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 sliceutil
+
+// RemoveAndZero removes the element at index i from slice s and returns the
+// resulting slice. The slice ordering is preserved; the last slice element
+// is zeroed before shrinking.
+func RemoveAndZero[E any, S ~[]E](s S, i int) S {
+ copy(s[i:], s[i+1:])
+ s[len(s)-1] = *new(E)
+ return s[:len(s)-1]
+}
diff --git a/lib/sliceutil/sliceutil_test.go b/lib/sliceutil/sliceutil_test.go
new file mode 100644
index 000000000..41387cd2b
--- /dev/null
+++ b/lib/sliceutil/sliceutil_test.go
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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 sliceutil_test
+
+import (
+ "testing"
+
+ "github.com/syncthing/syncthing/lib/sliceutil"
+ "golang.org/x/exp/slices"
+)
+
+func TestRemoveAndZero(t *testing.T) {
+ a := []int{1, 2, 3, 4, 5}
+ b := sliceutil.RemoveAndZero(a, 2)
+ exp := []int{1, 2, 4, 5}
+ if !slices.Equal(b, exp) {
+ t.Errorf("got %v, expected %v", b, exp)
+ }
+ for _, e := range a {
+ if e == 3 {
+ t.Errorf("element should have been zeroed")
+ }
+ }
+}
diff --git a/lib/syncthing/syncthing.go b/lib/syncthing/syncthing.go
index 4d1354e8d..be65f83cd 100644
--- a/lib/syncthing/syncthing.go
+++ b/lib/syncthing/syncthing.go
@@ -249,7 +249,7 @@ func (a *App) startup() error {
}
keyGen := protocol.NewKeyGenerator()
- m := model.NewModel(a.cfg, a.myID, "syncthing", build.Version, a.ll, protectedFiles, a.evLogger, keyGen)
+ m := model.NewModel(a.cfg, a.myID, a.ll, protectedFiles, a.evLogger, keyGen)
if a.opts.DeadlockTimeoutS > 0 {
m.StartDeadlockDetector(time.Duration(a.opts.DeadlockTimeoutS) * time.Second)
diff --git a/proto/lib/config/deviceconfiguration.proto b/proto/lib/config/deviceconfiguration.proto
index 616329b76..46afeed80 100644
--- a/proto/lib/config/deviceconfiguration.proto
+++ b/proto/lib/config/deviceconfiguration.proto
@@ -10,7 +10,7 @@ import "ext.proto";
message DeviceConfiguration {
bytes device_id = 1 [(ext.goname) = "DeviceID", (ext.xml) = "id,attr", (ext.json) = "deviceID", (ext.device_id) = true, (ext.nodefault) = true];
string name = 2 [(ext.xml) = "name,attr,omitempty"];
- repeated string addresses = 3 [(ext.xml) = "address,omitempty", (ext.default) = "dynamic"];
+ repeated string addresses = 3 [(ext.xml) = "address,omitempty"];
protocol.Compression compression = 4 [(ext.xml) = "compression,attr"];
string cert_name = 5 [(ext.xml) = "certName,attr,omitempty"];
bool introducer = 6 [(ext.xml) = "introducer,attr"];
@@ -26,4 +26,5 @@ message DeviceConfiguration {
int32 max_request_kib = 16 [(ext.goname) = "MaxRequestKiB", (ext.xml) = "maxRequestKiB", (ext.json) = "maxRequestKiB"];
bool untrusted = 17;
int32 remote_gui_port = 18 [(ext.goname) = "RemoteGUIPort", (ext.xml) = "remoteGUIPort", (ext.json) = "remoteGUIPort"];
+ int32 num_connections = 19 [(ext.goname) = "RawNumConnections"]; // attempt to establish this many connections to the device
}
diff --git a/proto/lib/protocol/bep.proto b/proto/lib/protocol/bep.proto
index 7e76464a2..ad7f5d0b8 100644
--- a/proto/lib/protocol/bep.proto
+++ b/proto/lib/protocol/bep.proto
@@ -8,9 +8,11 @@ import "repos/protobuf/gogoproto/gogo.proto";
// --- Pre-auth ---
message Hello {
- string device_name = 1;
- string client_name = 2;
- string client_version = 3;
+ string device_name = 1;
+ string client_name = 2;
+ string client_version = 3;
+ int32 num_connections = 4;
+ int64 timestamp = 5;
}
// --- Header ---
@@ -41,7 +43,8 @@ enum MessageCompression {
// Cluster Config
message ClusterConfig {
- repeated Folder folders = 1;
+ repeated Folder folders = 1;
+ bool secondary = 2;
}
message Folder {
diff --git a/test/h1/config.xml b/test/h1/config.xml
index a658d1bdb..80c02b902 100644
--- a/test/h1/config.xml
+++ b/test/h1/config.xml
@@ -1,13 +1,17 @@
-<configuration version="32">
- <folder id="default" label="" path="s1/" type="sendreceive" rescanIntervalS="10" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
- <filesystemType>basic</filesystemType>
- <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
- <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
- <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
- <device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" introducedBy=""></device>
+<configuration version="37">
+ <folder id="default" label="" path="s1?files=10000" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
+ <filesystemType>fake</filesystemType>
+ <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
+ <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
+ <fsPath></fsPath>
+ <fsType>basic</fsType>
</versioning>
<copiers>1</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
@@ -24,51 +28,21 @@
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
- <maxConcurrentWrites>0</maxConcurrentWrites>
+ <maxConcurrentWrites>2</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>true</junctionsAsDirs>
+ <syncOwnership>false</syncOwnership>
+ <sendOwnership>false</sendOwnership>
+ <syncXattrs>false</syncXattrs>
+ <sendXattrs>false</sendXattrs>
+ <xattrFilter>
+ <maxSingleEntrySize>0</maxSingleEntrySize>
+ <maxTotalSize>0</maxTotalSize>
+ </xattrFilter>
</folder>
- <folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-1/" type="sendreceive" rescanIntervalS="10" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
- <filesystemType>basic</filesystemType>
- <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
- <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
- <minDiskFree unit="%">1</minDiskFree>
- <versioning>
- <cleanupIntervalS>3600</cleanupIntervalS>
- </versioning>
- <copiers>1</copiers>
- <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
- <hashers>0</hashers>
- <order>random</order>
- <ignoreDelete>false</ignoreDelete>
- <scanProgressIntervalS>0</scanProgressIntervalS>
- <pullerPauseS>0</pullerPauseS>
- <maxConflicts>-1</maxConflicts>
- <disableSparseFiles>false</disableSparseFiles>
- <disableTempIndexes>false</disableTempIndexes>
- <paused>false</paused>
- <weakHashThresholdPct>25</weakHashThresholdPct>
- <markerName>.stfolder</markerName>
- <copyOwnershipFromParent>false</copyOwnershipFromParent>
- <modTimeWindowS>0</modTimeWindowS>
- <maxConcurrentWrites>0</maxConcurrentWrites>
- <disableFsync>false</disableFsync>
- <blockPullOrder>standard</blockPullOrder>
- <copyRangeMethod>standard</copyRangeMethod>
- <caseSensitiveFS>false</caseSensitiveFS>
- <junctionsAsDirs>true</junctionsAsDirs>
- </folder>
- <device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
- <address>tcp://127.0.0.1:22004</address>
- <paused>false</paused>
- <autoAcceptFolders>false</autoAcceptFolders>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
- <maxRequestKiB>0</maxRequestKiB>
- </device>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>tcp://127.0.0.1:22001</address>
<paused>false</paused>
@@ -76,30 +50,21 @@
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
</device>
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>tcp://127.0.0.1:22002</address>
+ <address>quic://127.0.0.1:22002</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
- </device>
- <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
- <address>tcp://127.0.0.1:22003</address>
- <paused>false</paused>
- <autoAcceptFolders>false</autoAcceptFolders>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
- <maxRequestKiB>0</maxRequestKiB>
- </device>
- <device id="7PBCTLL-JJRYBSA-MOWZRKL-MSDMN4N-4US4OMX-SYEXUS4-HSBGNRY-CZXRXAT" name="s4" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
- <address>tcp://127.0.0.1:22004</address>
- <paused>false</paused>
- <autoAcceptFolders>false</autoAcceptFolders>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
- <maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
</device>
<gui enabled="true" tls="false" debugging="true">
<address>127.0.0.1:8081</address>
@@ -111,6 +76,7 @@
<ldap></ldap>
<options>
<listenAddress>tcp://127.0.0.1:22001</listenAddress>
+ <listenAddress>quic://127.0.0.1:22001</listenAddress>
<globalAnnounceServer>default</globalAnnounceServer>
<globalAnnounceEnabled>false</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
@@ -132,7 +98,6 @@
<urURL>https://data.syncthing.net/newdata</urURL>
<urPostInsecurely>false</urPostInsecurely>
<urInitialDelayS>1800</urInitialDelayS>
- <restartOnWakeup>true</restartOnWakeup>
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
<upgradeToPreReleases>false</upgradeToPreReleases>
<keepTemporariesH>24</keepTemporariesH>
@@ -144,7 +109,6 @@
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
<tempIndexMinBlocks>10</tempIndexMinBlocks>
<trafficClass>0</trafficClass>
- <defaultFolderPath>~</defaultFolderPath>
<setLowPriority>true</setLowPriority>
<maxFolderConcurrency>0</maxFolderConcurrency>
<crashReportingURL>https://crash.syncthing.net/newcrash</crashReportingURL>
@@ -155,5 +119,70 @@
<databaseTuning>auto</databaseTuning>
<maxConcurrentIncomingRequestKiB>0</maxConcurrentIncomingRequestKiB>
<announceLANAddresses>true</announceLANAddresses>
+ <sendFullIndexOnUpgrade>false</sendFullIndexOnUpgrade>
+ <connectionLimitEnough>0</connectionLimitEnough>
+ <connectionLimitMax>0</connectionLimitMax>
+ <insecureAllowOldTLSVersions>false</insecureAllowOldTLSVersions>
+ <connectionPriorityTcpLan>10</connectionPriorityTcpLan>
+ <connectionPriorityQuicLan>20</connectionPriorityQuicLan>
+ <connectionPriorityTcpWan>30</connectionPriorityTcpWan>
+ <connectionPriorityQuicWan>40</connectionPriorityQuicWan>
+ <connectionPriorityRelay>50</connectionPriorityRelay>
+ <connectionPriorityUpgradeThreshold>0</connectionPriorityUpgradeThreshold>
</options>
+ <defaults>
+ <folder id="" label="" path="~" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
+ <filesystemType>basic</filesystemType>
+ <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
+ <minDiskFree unit="%">1</minDiskFree>
+ <versioning>
+ <cleanupIntervalS>3600</cleanupIntervalS>
+ <fsPath></fsPath>
+ <fsType>basic</fsType>
+ </versioning>
+ <copiers>0</copiers>
+ <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
+ <hashers>0</hashers>
+ <order>random</order>
+ <ignoreDelete>false</ignoreDelete>
+ <scanProgressIntervalS>0</scanProgressIntervalS>
+ <pullerPauseS>0</pullerPauseS>
+ <maxConflicts>10</maxConflicts>
+ <disableSparseFiles>false</disableSparseFiles>
+ <disableTempIndexes>false</disableTempIndexes>
+ <paused>false</paused>
+ <weakHashThresholdPct>25</weakHashThresholdPct>
+ <markerName>.stfolder</markerName>
+ <copyOwnershipFromParent>false</copyOwnershipFromParent>
+ <modTimeWindowS>0</modTimeWindowS>
+ <maxConcurrentWrites>2</maxConcurrentWrites>
+ <disableFsync>false</disableFsync>
+ <blockPullOrder>standard</blockPullOrder>
+ <copyRangeMethod>standard</copyRangeMethod>
+ <caseSensitiveFS>false</caseSensitiveFS>
+ <junctionsAsDirs>false</junctionsAsDirs>
+ <syncOwnership>false</syncOwnership>
+ <sendOwnership>false</sendOwnership>
+ <syncXattrs>false</syncXattrs>
+ <sendXattrs>false</sendXattrs>
+ <xattrFilter>
+ <maxSingleEntrySize>1024</maxSingleEntrySize>
+ <maxTotalSize>4096</maxTotalSize>
+ </xattrFilter>
+ </folder>
+ <device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
+ <address>dynamic</address>
+ <paused>false</paused>
+ <autoAcceptFolders>false</autoAcceptFolders>
+ <maxSendKbps>0</maxSendKbps>
+ <maxRecvKbps>0</maxRecvKbps>
+ <maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
+ </device>
+ <ignores></ignores>
+ </defaults>
</configuration>
diff --git a/test/h2/config.xml b/test/h2/config.xml
index 6b5552070..f98bd751a 100644
--- a/test/h2/config.xml
+++ b/test/h2/config.xml
@@ -1,14 +1,19 @@
-<configuration version="32">
- <folder id="default" label="" path="s2" type="sendreceive" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
- <filesystemType>basic</filesystemType>
- <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
- <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
- <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
+<configuration version="37">
+ <folder id="default" label="" path="s2" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
+ <filesystemType>fake</filesystemType>
+ <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
+ <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
+ <fsPath></fsPath>
+ <fsType>basic</fsType>
</versioning>
- <copiers>1</copiers>
+ <copiers>8</copiers>
<pullerMaxPendingKiB>0</pullerMaxPendingKiB>
<hashers>0</hashers>
<order>random</order>
@@ -23,80 +28,32 @@
<markerName>.stfolder</markerName>
<copyOwnershipFromParent>false</copyOwnershipFromParent>
<modTimeWindowS>0</modTimeWindowS>
- <maxConcurrentWrites>0</maxConcurrentWrites>
- <disableFsync>false</disableFsync>
- <blockPullOrder>standard</blockPullOrder>
- <copyRangeMethod>standard</copyRangeMethod>
- <caseSensitiveFS>false</caseSensitiveFS>
- <junctionsAsDirs>true</junctionsAsDirs>
- </folder>
- <folder id="s23" label="" path="s23-2" type="sendreceive" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
- <filesystemType>basic</filesystemType>
- <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
- <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy=""></device>
- <minDiskFree unit="%">1</minDiskFree>
- <versioning>
- <cleanupIntervalS>3600</cleanupIntervalS>
- </versioning>
- <copiers>1</copiers>
- <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
- <hashers>0</hashers>
- <order>random</order>
- <ignoreDelete>false</ignoreDelete>
- <scanProgressIntervalS>0</scanProgressIntervalS>
- <pullerPauseS>0</pullerPauseS>
- <maxConflicts>-1</maxConflicts>
- <disableSparseFiles>false</disableSparseFiles>
- <disableTempIndexes>false</disableTempIndexes>
- <paused>false</paused>
- <weakHashThresholdPct>25</weakHashThresholdPct>
- <markerName>.stfolder</markerName>
- <copyOwnershipFromParent>false</copyOwnershipFromParent>
- <modTimeWindowS>0</modTimeWindowS>
- <maxConcurrentWrites>0</maxConcurrentWrites>
- <disableFsync>false</disableFsync>
- <blockPullOrder>standard</blockPullOrder>
- <copyRangeMethod>standard</copyRangeMethod>
- <caseSensitiveFS>false</caseSensitiveFS>
- <junctionsAsDirs>true</junctionsAsDirs>
- </folder>
- <folder id="¯\_(ツ)_/¯ Räksmörgås 动作 Адрес" label="" path="s12-2" type="sendreceive" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
- <filesystemType>basic</filesystemType>
- <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""></device>
- <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy=""></device>
- <minDiskFree unit="%">1</minDiskFree>
- <versioning>
- <cleanupIntervalS>3600</cleanupIntervalS>
- </versioning>
- <copiers>1</copiers>
- <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
- <hashers>0</hashers>
- <order>random</order>
- <ignoreDelete>false</ignoreDelete>
- <scanProgressIntervalS>0</scanProgressIntervalS>
- <pullerPauseS>0</pullerPauseS>
- <maxConflicts>-1</maxConflicts>
- <disableSparseFiles>false</disableSparseFiles>
- <disableTempIndexes>false</disableTempIndexes>
- <paused>false</paused>
- <weakHashThresholdPct>25</weakHashThresholdPct>
- <markerName>.stfolder</markerName>
- <copyOwnershipFromParent>false</copyOwnershipFromParent>
- <modTimeWindowS>0</modTimeWindowS>
- <maxConcurrentWrites>0</maxConcurrentWrites>
+ <maxConcurrentWrites>8</maxConcurrentWrites>
<disableFsync>false</disableFsync>
<blockPullOrder>standard</blockPullOrder>
<copyRangeMethod>standard</copyRangeMethod>
<caseSensitiveFS>false</caseSensitiveFS>
<junctionsAsDirs>true</junctionsAsDirs>
+ <syncOwnership>false</syncOwnership>
+ <sendOwnership>false</sendOwnership>
+ <syncXattrs>false</syncXattrs>
+ <sendXattrs>false</sendXattrs>
+ <xattrFilter>
+ <maxSingleEntrySize>0</maxSingleEntrySize>
+ <maxTotalSize>0</maxTotalSize>
+ </xattrFilter>
</folder>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>tcp://127.0.0.1:22001</address>
+ <address>quic://127.0.0.1:22001</address>
<paused>false</paused>
<autoAcceptFolders>false</autoAcceptFolders>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
+ <maxSendKbps>800</maxSendKbps>
+ <maxRecvKbps>800</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
</device>
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
<address>tcp://127.0.0.1:22002</address>
@@ -105,14 +62,9 @@
<maxSendKbps>0</maxSendKbps>
<maxRecvKbps>0</maxRecvKbps>
<maxRequestKiB>0</maxRequestKiB>
- </device>
- <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
- <address>tcp://127.0.0.1:22003</address>
- <paused>false</paused>
- <autoAcceptFolders>false</autoAcceptFolders>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
- <maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
</device>
<gui enabled="true" tls="false" debugging="true">
<address>127.0.0.1:8082</address>
@@ -121,15 +73,15 @@
</gui>
<ldap></ldap>
<options>
- <listenAddress>dynamic+https://relays.syncthing.net/endpoint</listenAddress>
<listenAddress>tcp://127.0.0.1:22002</listenAddress>
+ <listenAddress>quic://127.0.0.1:22002</listenAddress>
<globalAnnounceServer>default</globalAnnounceServer>
<globalAnnounceEnabled>false</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<localAnnouncePort>21027</localAnnouncePort>
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
- <maxSendKbps>0</maxSendKbps>
- <maxRecvKbps>0</maxRecvKbps>
+ <maxSendKbps>1000</maxSendKbps>
+ <maxRecvKbps>1000</maxRecvKbps>
<reconnectionIntervalS>5</reconnectionIntervalS>
<relaysEnabled>true</relaysEnabled>
<relayReconnectIntervalM>10</relayReconnectIntervalM>
@@ -144,19 +96,17 @@
<urURL>https://data.syncthing.net/newdata</urURL>
<urPostInsecurely>false</urPostInsecurely>
<urInitialDelayS>1800</urInitialDelayS>
- <restartOnWakeup>true</restartOnWakeup>
<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
<upgradeToPreReleases>false</upgradeToPreReleases>
<keepTemporariesH>24</keepTemporariesH>
<cacheIgnoredFiles>false</cacheIgnoredFiles>
<progressUpdateIntervalS>5</progressUpdateIntervalS>
- <limitBandwidthInLan>false</limitBandwidthInLan>
+ <limitBandwidthInLan>true</limitBandwidthInLan>
<minHomeDiskFree unit="%">1</minHomeDiskFree>
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
<tempIndexMinBlocks>10</tempIndexMinBlocks>
<trafficClass>0</trafficClass>
- <defaultFolderPath>~</defaultFolderPath>
<setLowPriority>true</setLowPriority>
<maxFolderConcurrency>0</maxFolderConcurrency>
<crashReportingURL>https://crash.syncthing.net/newcrash</crashReportingURL>
@@ -167,5 +117,70 @@
<databaseTuning>auto</databaseTuning>
<maxConcurrentIncomingRequestKiB>0</maxConcurrentIncomingRequestKiB>
<announceLANAddresses>true</announceLANAddresses>
+ <sendFullIndexOnUpgrade>false</sendFullIndexOnUpgrade>
+ <connectionLimitEnough>0</connectionLimitEnough>
+ <connectionLimitMax>0</connectionLimitMax>
+ <insecureAllowOldTLSVersions>false</insecureAllowOldTLSVersions>
+ <connectionPriorityTcpLan>10</connectionPriorityTcpLan>
+ <connectionPriorityQuicLan>20</connectionPriorityQuicLan>
+ <connectionPriorityTcpWan>30</connectionPriorityTcpWan>
+ <connectionPriorityQuicWan>40</connectionPriorityQuicWan>
+ <connectionPriorityRelay>50</connectionPriorityRelay>
+ <connectionPriorityUpgradeThreshold>0</connectionPriorityUpgradeThreshold>
</options>
+ <defaults>
+ <folder id="" label="" path="~" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
+ <filesystemType>basic</filesystemType>
+ <device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" introducedBy="">
+ <encryptionPassword></encryptionPassword>
+ </device>
+ <minDiskFree unit="%">1</minDiskFree>
+ <versioning>
+ <cleanupIntervalS>3600</cleanupIntervalS>
+ <fsPath></fsPath>
+ <fsType>basic</fsType>
+ </versioning>
+ <copiers>0</copiers>
+ <pullerMaxPendingKiB>0</pullerMaxPendingKiB>
+ <hashers>0</hashers>
+ <order>random</order>
+ <ignoreDelete>false</ignoreDelete>
+ <scanProgressIntervalS>0</scanProgressIntervalS>
+ <pullerPauseS>0</pullerPauseS>
+ <maxConflicts>10</maxConflicts>
+ <disableSparseFiles>false</disableSparseFiles>
+ <disableTempIndexes>false</disableTempIndexes>
+ <paused>false</paused>
+ <weakHashThresholdPct>25</weakHashThresholdPct>
+ <markerName>.stfolder</markerName>
+ <copyOwnershipFromParent>false</copyOwnershipFromParent>
+ <modTimeWindowS>0</modTimeWindowS>
+ <maxConcurrentWrites>2</maxConcurrentWrites>
+ <disableFsync>false</disableFsync>
+ <blockPullOrder>standard</blockPullOrder>
+ <copyRangeMethod>standard</copyRangeMethod>
+ <caseSensitiveFS>false</caseSensitiveFS>
+ <junctionsAsDirs>false</junctionsAsDirs>
+ <syncOwnership>false</syncOwnership>
+ <sendOwnership>false</sendOwnership>
+ <syncXattrs>false</syncXattrs>
+ <sendXattrs>false</sendXattrs>
+ <xattrFilter>
+ <maxSingleEntrySize>1024</maxSingleEntrySize>
+ <maxTotalSize>4096</maxTotalSize>
+ </xattrFilter>
+ </folder>
+ <device id="" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
+ <address>dynamic</address>
+ <paused>false</paused>
+ <autoAcceptFolders>false</autoAcceptFolders>
+ <maxSendKbps>0</maxSendKbps>
+ <maxRecvKbps>0</maxRecvKbps>
+ <maxRequestKiB>0</maxRequestKiB>
+ <untrusted>false</untrusted>
+ <remoteGUIPort>0</remoteGUIPort>
+ <numConnections>3</numConnections>
+ </device>
+ <ignores></ignores>
+ </defaults>
</configuration>