This commit is contained in:
Jakob Borg 2023-05-23 14:53:48 +02:00
parent da9726ddca
commit f19a9c49af
21 changed files with 690 additions and 664 deletions

View File

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

View File

@ -171,6 +171,9 @@ type service struct {
listenersMut sync.RWMutex listenersMut sync.RWMutex
listeners map[string]genericListener listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken listenerTokens map[string]suture.ServiceToken
connectionsMut sync.Mutex
connections map[protocol.DeviceID]int
} }
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger, registry *registry.Registry, keyGen *protocol.KeyGenerator) Service { func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder, bepProtocolName string, tlsDefaultCommonName string, evLogger events.Logger, registry *registry.Registry, keyGen *protocol.KeyGenerator) Service {
@ -202,6 +205,9 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
listenersMut: sync.NewRWMutex(), listenersMut: sync.NewRWMutex(),
listeners: make(map[string]genericListener), listeners: make(map[string]genericListener),
listenerTokens: make(map[string]suture.ServiceToken), listenerTokens: make(map[string]suture.ServiceToken),
connectionsMut: sync.NewMutex(),
connections: make(map[protocol.DeviceID]int),
} }
cfg.Subscribe(service) cfg.Subscribe(service)
@ -316,7 +322,7 @@ func (s *service) connectionCheckEarly(remoteID protocol.DeviceID, c internalCon
if ct, ok := s.model.Connection(remoteID); ok { if ct, ok := s.model.Connection(remoteID); ok {
if ct.Priority() > c.priority || time.Since(ct.Statistics().StartedAt) > minConnectionReplaceAge { if ct.Priority() > c.priority || time.Since(ct.Statistics().StartedAt) > minConnectionReplaceAge {
l.Debugf("Switching connections %s (existing: %s new: %s)", remoteID, ct, c) l.Debugf("Switching connections %s (existing: %s new: %s)", remoteID, ct, c)
} else { } else if cfg.MultipleConnections <= s.connectionsForDevice(cfg.DeviceID) {
// We should not already be connected to the other party. TODO: This // We should not already be connected to the other party. TODO: This
// could use some better handling. If the old connection is dead but // 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 // hasn't timed out yet we may want to drop *that* connection and keep
@ -412,9 +418,16 @@ func (s *service) handleHellos(ctx context.Context) error {
// connections are limited. // connections are limited.
rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal()) rd, wr := s.limiter.getLimiters(remoteID, c, c.IsLocal())
// Amazing hack: We need to pass the connection to the model, but we
// also need to pass the model to the connection. The
// connectionContextModel handles the mediation by being constructed
// in two stages...
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID), s.keyGen) protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID), s.keyGen)
s.accountAddedConnection(remoteID)
go func() { go func() {
<-protoConn.Closed() <-protoConn.Closed()
s.accountRemovedConnection(remoteID)
s.dialNowDevicesMut.Lock() s.dialNowDevicesMut.Lock()
s.dialNowDevices[remoteID] = struct{}{} s.dialNowDevices[remoteID] = struct{}{}
s.scheduleDialNow() s.scheduleDialNow()
@ -552,9 +565,10 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
// we don't attempt dialers that aren't considered a worthy upgrade. // we don't attempt dialers that aren't considered a worthy upgrade.
priorityCutoff -= cfg.Options.ConnectionPriorityUpgradeThreshold priorityCutoff -= cfg.Options.ConnectionPriorityUpgradeThreshold
if bestDialerPriority >= priorityCutoff { if bestDialerPriority >= priorityCutoff && deviceCfg.MultipleConnections <= s.connectionsForDevice(deviceCfg.DeviceID) {
// Our best dialer is not any better than what we already // 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.
continue continue
} }
} }
@ -666,7 +680,7 @@ func (s *service) resolveDialTargets(ctx context.Context, now time.Time, cfg con
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg, s.registry, s.lanChecker) dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg, s.registry, s.lanChecker)
priority := dialer.Priority(uri.Host) priority := dialer.Priority(uri.Host)
if priority >= priorityCutoff { if priority >= priorityCutoff && deviceCfg.MultipleConnections <= s.connectionsForDevice(deviceCfg.DeviceID) {
l.Debugf("Not dialing using %s as priority is not better than current connection (%d >= %d)", dialerFactory, priority, priorityCutoff) l.Debugf("Not dialing using %s as priority is not better than current connection (%d >= %d)", dialerFactory, priority, priorityCutoff)
continue continue
} }
@ -1183,6 +1197,24 @@ func (s *service) validateIdentity(c internalConn, expectedID protocol.DeviceID)
return nil return nil
} }
func (s *service) accountAddedConnection(d protocol.DeviceID) {
s.connectionsMut.Lock()
defer s.connectionsMut.Unlock()
s.connections[d]++
}
func (s *service) accountRemovedConnection(d protocol.DeviceID) {
s.connectionsMut.Lock()
defer s.connectionsMut.Unlock()
s.connections[d]--
}
func (s *service) connectionsForDevice(d protocol.DeviceID) int {
s.connectionsMut.Lock()
defer s.connectionsMut.Unlock()
return s.connections[d]
}
type nextDialRegistry map[protocol.DeviceID]nextDialDevice type nextDialRegistry map[protocol.DeviceID]nextDialDevice
type nextDialDevice struct { type nextDialDevice struct {

View File

@ -37,7 +37,7 @@ func newFakeConnection(id protocol.DeviceID, model Model) *fakeConnection {
f.closeOnce.Do(func() { f.closeOnce.Do(func() {
close(f.closed) close(f.closed)
}) })
model.Closed(f.ConnectionID(), err) model.Closed(f, err)
f.ClosedReturns(f.closed) f.ClosedReturns(f.closed)
}) })
return f return f
@ -157,7 +157,7 @@ func (f *fakeConnection) sendIndexUpdate() {
for i := range f.files { for i := range f.files {
toSend[i] = prepareFileInfoForIndex(f.files[i]) toSend[i] = prepareFileInfoForIndex(f.files[i])
} }
f.model.IndexUpdate(f.id, f.folder, toSend) f.model.IndexUpdate(f, f.folder, toSend)
} }
func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConnection { func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConnection {
@ -165,7 +165,7 @@ func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConn
fc.folder = folderID fc.folder = folderID
m.AddConnection(fc, protocol.Hello{}) m.AddConnection(fc, protocol.Hello{})
m.ClusterConfig(dev, protocol.ClusterConfig{ m.ClusterConfig(fc, protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: folderID, ID: folderID,

View File

@ -45,7 +45,7 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// Send and index update for the known stuff // Send and index update for the known stuff
must(t, m.Index(device1, "ro", knownFiles)) must(t, m.Index(device1Conn, "ro", knownFiles))
f.updateLocalsFromScanning(knownFiles) f.updateLocalsFromScanning(knownFiles)
size := globalSize(t, m, "ro") size := globalSize(t, m, "ro")
@ -122,7 +122,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Send and index update for the known stuff // Send and index update for the known stuff
must(t, m.Index(device1, "ro", knownFiles)) must(t, m.Index(device1Conn, "ro", knownFiles))
f.updateLocalsFromScanning(knownFiles) f.updateLocalsFromScanning(knownFiles)
// Scan the folder. // Scan the folder.
@ -212,7 +212,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Send an index update for the known stuff // Send an index update for the known stuff
must(t, m.Index(device1, "ro", knownFiles)) must(t, m.Index(device1Conn, "ro", knownFiles))
f.updateLocalsFromScanning(knownFiles) f.updateLocalsFromScanning(knownFiles)
// Scan the folder. // Scan the folder.
@ -282,7 +282,7 @@ func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
// Send an index update for the known stuff // Send an index update for the known stuff
must(t, m.Index(device1, "ro", knownFiles)) must(t, m.Index(device1Conn, "ro", knownFiles))
f.updateLocalsFromScanning(knownFiles) f.updateLocalsFromScanning(knownFiles)
// Scan the folder. // Scan the folder.
@ -347,7 +347,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
// Send an index update for the known stuff // Send an index update for the known stuff
must(t, m.Index(device1, "ro", knownFiles)) must(t, m.Index(device1Conn, "ro", knownFiles))
f.updateLocalsFromScanning(knownFiles) f.updateLocalsFromScanning(knownFiles)
// Scan the folder. // Scan the folder.
@ -402,7 +402,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
return true return true
}) })
snap.Release() snap.Release()
must(t, m.IndexUpdate(device1, "ro", files)) must(t, m.IndexUpdate(device1Conn, "ro", files))
// Ensure the pull to resolve conflicts (content identical) happened // Ensure the pull to resolve conflicts (content identical) happened
must(t, f.doInSync(func() error { must(t, f.doInSync(func() error {
@ -470,7 +470,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) {
}() }()
// Receive an index update with an older version, but valid and then revert // Receive an index update with an older version, but valid and then revert
must(t, m.Index(device1, f.ID, []protocol.FileInfo{fi})) must(t, m.Index(device1Conn, f.ID, []protocol.FileInfo{fi}))
f.Revert() f.Revert()
select { select {

View File

@ -1296,7 +1296,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
if !ok { if !ok {
t.Fatal("file missing") t.Fatal("file missing")
} }
must(t, m.Index(device1, f.ID, []protocol.FileInfo{{Name: name, Type: protocol.FileInfoTypeSymlink, Version: file.Version.Update(device1.Short())}})) must(t, m.Index(device1Conn, f.ID, []protocol.FileInfo{{Name: name, Type: protocol.FileInfoTypeSymlink, Version: file.Version.Update(device1.Short())}}))
scanChan := make(chan string) scanChan := make(chan string)

View File

@ -44,16 +44,16 @@ type Model struct {
arg1 string arg1 string
arg2 string arg2 string
} }
ClosedStub func(string, error) ClosedStub func(protocol.Connection, error)
closedMutex sync.RWMutex closedMutex sync.RWMutex
closedArgsForCall []struct { closedArgsForCall []struct {
arg1 string arg1 protocol.Connection
arg2 error arg2 error
} }
ClusterConfigStub func(protocol.DeviceID, protocol.ClusterConfig) error ClusterConfigStub func(protocol.Connection, protocol.ClusterConfig) error
clusterConfigMutex sync.RWMutex clusterConfigMutex sync.RWMutex
clusterConfigArgsForCall []struct { clusterConfigArgsForCall []struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 protocol.ClusterConfig arg2 protocol.ClusterConfig
} }
clusterConfigReturns struct { clusterConfigReturns struct {
@ -200,10 +200,10 @@ type Model struct {
dismissPendingFolderReturnsOnCall map[int]struct { dismissPendingFolderReturnsOnCall map[int]struct {
result1 error result1 error
} }
DownloadProgressStub func(protocol.DeviceID, string, []protocol.FileDownloadProgressUpdate) error DownloadProgressStub func(protocol.Connection, string, []protocol.FileDownloadProgressUpdate) error
downloadProgressMutex sync.RWMutex downloadProgressMutex sync.RWMutex
downloadProgressArgsForCall []struct { downloadProgressArgsForCall []struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileDownloadProgressUpdate arg3 []protocol.FileDownloadProgressUpdate
} }
@ -303,10 +303,10 @@ type Model struct {
result1 []*model.TreeEntry result1 []*model.TreeEntry
result2 error result2 error
} }
IndexStub func(protocol.DeviceID, string, []protocol.FileInfo) error IndexStub func(protocol.Connection, string, []protocol.FileInfo) error
indexMutex sync.RWMutex indexMutex sync.RWMutex
indexArgsForCall []struct { indexArgsForCall []struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileInfo arg3 []protocol.FileInfo
} }
@ -316,10 +316,10 @@ type Model struct {
indexReturnsOnCall map[int]struct { indexReturnsOnCall map[int]struct {
result1 error result1 error
} }
IndexUpdateStub func(protocol.DeviceID, string, []protocol.FileInfo) error IndexUpdateStub func(protocol.Connection, string, []protocol.FileInfo) error
indexUpdateMutex sync.RWMutex indexUpdateMutex sync.RWMutex
indexUpdateArgsForCall []struct { indexUpdateArgsForCall []struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileInfo arg3 []protocol.FileInfo
} }
@ -447,10 +447,10 @@ type Model struct {
result1 []db.FileInfoTruncated result1 []db.FileInfoTruncated
result2 error result2 error
} }
RequestStub func(protocol.DeviceID, string, string, int32, int32, int64, []byte, uint32, bool) (protocol.RequestResponse, error) RequestStub func(protocol.Connection, string, string, int32, int32, int64, []byte, uint32, bool) (protocol.RequestResponse, error)
requestMutex sync.RWMutex requestMutex sync.RWMutex
requestArgsForCall []struct { requestArgsForCall []struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 string arg3 string
arg4 int32 arg4 int32
@ -728,10 +728,10 @@ func (fake *Model) BringToFrontArgsForCall(i int) (string, string) {
return argsForCall.arg1, argsForCall.arg2 return argsForCall.arg1, argsForCall.arg2
} }
func (fake *Model) Closed(arg1 string, arg2 error) { func (fake *Model) Closed(arg1 protocol.Connection, arg2 error) {
fake.closedMutex.Lock() fake.closedMutex.Lock()
fake.closedArgsForCall = append(fake.closedArgsForCall, struct { fake.closedArgsForCall = append(fake.closedArgsForCall, struct {
arg1 string arg1 protocol.Connection
arg2 error arg2 error
}{arg1, arg2}) }{arg1, arg2})
stub := fake.ClosedStub stub := fake.ClosedStub
@ -748,24 +748,24 @@ func (fake *Model) ClosedCallCount() int {
return len(fake.closedArgsForCall) return len(fake.closedArgsForCall)
} }
func (fake *Model) ClosedCalls(stub func(string, error)) { func (fake *Model) ClosedCalls(stub func(protocol.Connection, error)) {
fake.closedMutex.Lock() fake.closedMutex.Lock()
defer fake.closedMutex.Unlock() defer fake.closedMutex.Unlock()
fake.ClosedStub = stub fake.ClosedStub = stub
} }
func (fake *Model) ClosedArgsForCall(i int) (string, error) { func (fake *Model) ClosedArgsForCall(i int) (protocol.Connection, error) {
fake.closedMutex.RLock() fake.closedMutex.RLock()
defer fake.closedMutex.RUnlock() defer fake.closedMutex.RUnlock()
argsForCall := fake.closedArgsForCall[i] argsForCall := fake.closedArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2 return argsForCall.arg1, argsForCall.arg2
} }
func (fake *Model) ClusterConfig(arg1 protocol.DeviceID, arg2 protocol.ClusterConfig) error { func (fake *Model) ClusterConfig(arg1 protocol.Connection, arg2 protocol.ClusterConfig) error {
fake.clusterConfigMutex.Lock() fake.clusterConfigMutex.Lock()
ret, specificReturn := fake.clusterConfigReturnsOnCall[len(fake.clusterConfigArgsForCall)] ret, specificReturn := fake.clusterConfigReturnsOnCall[len(fake.clusterConfigArgsForCall)]
fake.clusterConfigArgsForCall = append(fake.clusterConfigArgsForCall, struct { fake.clusterConfigArgsForCall = append(fake.clusterConfigArgsForCall, struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 protocol.ClusterConfig arg2 protocol.ClusterConfig
}{arg1, arg2}) }{arg1, arg2})
stub := fake.ClusterConfigStub stub := fake.ClusterConfigStub
@ -787,13 +787,13 @@ func (fake *Model) ClusterConfigCallCount() int {
return len(fake.clusterConfigArgsForCall) return len(fake.clusterConfigArgsForCall)
} }
func (fake *Model) ClusterConfigCalls(stub func(protocol.DeviceID, protocol.ClusterConfig) error) { func (fake *Model) ClusterConfigCalls(stub func(protocol.Connection, protocol.ClusterConfig) error) {
fake.clusterConfigMutex.Lock() fake.clusterConfigMutex.Lock()
defer fake.clusterConfigMutex.Unlock() defer fake.clusterConfigMutex.Unlock()
fake.ClusterConfigStub = stub fake.ClusterConfigStub = stub
} }
func (fake *Model) ClusterConfigArgsForCall(i int) (protocol.DeviceID, protocol.ClusterConfig) { func (fake *Model) ClusterConfigArgsForCall(i int) (protocol.Connection, protocol.ClusterConfig) {
fake.clusterConfigMutex.RLock() fake.clusterConfigMutex.RLock()
defer fake.clusterConfigMutex.RUnlock() defer fake.clusterConfigMutex.RUnlock()
argsForCall := fake.clusterConfigArgsForCall[i] argsForCall := fake.clusterConfigArgsForCall[i]
@ -1484,7 +1484,7 @@ func (fake *Model) DismissPendingFolderReturnsOnCall(i int, result1 error) {
}{result1} }{result1}
} }
func (fake *Model) DownloadProgress(arg1 protocol.DeviceID, arg2 string, arg3 []protocol.FileDownloadProgressUpdate) error { func (fake *Model) DownloadProgress(arg1 protocol.Connection, arg2 string, arg3 []protocol.FileDownloadProgressUpdate) error {
var arg3Copy []protocol.FileDownloadProgressUpdate var arg3Copy []protocol.FileDownloadProgressUpdate
if arg3 != nil { if arg3 != nil {
arg3Copy = make([]protocol.FileDownloadProgressUpdate, len(arg3)) arg3Copy = make([]protocol.FileDownloadProgressUpdate, len(arg3))
@ -1493,7 +1493,7 @@ func (fake *Model) DownloadProgress(arg1 protocol.DeviceID, arg2 string, arg3 []
fake.downloadProgressMutex.Lock() fake.downloadProgressMutex.Lock()
ret, specificReturn := fake.downloadProgressReturnsOnCall[len(fake.downloadProgressArgsForCall)] ret, specificReturn := fake.downloadProgressReturnsOnCall[len(fake.downloadProgressArgsForCall)]
fake.downloadProgressArgsForCall = append(fake.downloadProgressArgsForCall, struct { fake.downloadProgressArgsForCall = append(fake.downloadProgressArgsForCall, struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileDownloadProgressUpdate arg3 []protocol.FileDownloadProgressUpdate
}{arg1, arg2, arg3Copy}) }{arg1, arg2, arg3Copy})
@ -1516,13 +1516,13 @@ func (fake *Model) DownloadProgressCallCount() int {
return len(fake.downloadProgressArgsForCall) return len(fake.downloadProgressArgsForCall)
} }
func (fake *Model) DownloadProgressCalls(stub func(protocol.DeviceID, string, []protocol.FileDownloadProgressUpdate) error) { func (fake *Model) DownloadProgressCalls(stub func(protocol.Connection, string, []protocol.FileDownloadProgressUpdate) error) {
fake.downloadProgressMutex.Lock() fake.downloadProgressMutex.Lock()
defer fake.downloadProgressMutex.Unlock() defer fake.downloadProgressMutex.Unlock()
fake.DownloadProgressStub = stub fake.DownloadProgressStub = stub
} }
func (fake *Model) DownloadProgressArgsForCall(i int) (protocol.DeviceID, string, []protocol.FileDownloadProgressUpdate) { func (fake *Model) DownloadProgressArgsForCall(i int) (protocol.Connection, string, []protocol.FileDownloadProgressUpdate) {
fake.downloadProgressMutex.RLock() fake.downloadProgressMutex.RLock()
defer fake.downloadProgressMutex.RUnlock() defer fake.downloadProgressMutex.RUnlock()
argsForCall := fake.downloadProgressArgsForCall[i] argsForCall := fake.downloadProgressArgsForCall[i]
@ -1990,7 +1990,7 @@ func (fake *Model) GlobalDirectoryTreeReturnsOnCall(i int, result1 []*model.Tree
}{result1, result2} }{result1, result2}
} }
func (fake *Model) Index(arg1 protocol.DeviceID, arg2 string, arg3 []protocol.FileInfo) error { func (fake *Model) Index(arg1 protocol.Connection, arg2 string, arg3 []protocol.FileInfo) error {
var arg3Copy []protocol.FileInfo var arg3Copy []protocol.FileInfo
if arg3 != nil { if arg3 != nil {
arg3Copy = make([]protocol.FileInfo, len(arg3)) arg3Copy = make([]protocol.FileInfo, len(arg3))
@ -1999,7 +1999,7 @@ func (fake *Model) Index(arg1 protocol.DeviceID, arg2 string, arg3 []protocol.Fi
fake.indexMutex.Lock() fake.indexMutex.Lock()
ret, specificReturn := fake.indexReturnsOnCall[len(fake.indexArgsForCall)] ret, specificReturn := fake.indexReturnsOnCall[len(fake.indexArgsForCall)]
fake.indexArgsForCall = append(fake.indexArgsForCall, struct { fake.indexArgsForCall = append(fake.indexArgsForCall, struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileInfo arg3 []protocol.FileInfo
}{arg1, arg2, arg3Copy}) }{arg1, arg2, arg3Copy})
@ -2022,13 +2022,13 @@ func (fake *Model) IndexCallCount() int {
return len(fake.indexArgsForCall) return len(fake.indexArgsForCall)
} }
func (fake *Model) IndexCalls(stub func(protocol.DeviceID, string, []protocol.FileInfo) error) { func (fake *Model) IndexCalls(stub func(protocol.Connection, string, []protocol.FileInfo) error) {
fake.indexMutex.Lock() fake.indexMutex.Lock()
defer fake.indexMutex.Unlock() defer fake.indexMutex.Unlock()
fake.IndexStub = stub fake.IndexStub = stub
} }
func (fake *Model) IndexArgsForCall(i int) (protocol.DeviceID, string, []protocol.FileInfo) { func (fake *Model) IndexArgsForCall(i int) (protocol.Connection, string, []protocol.FileInfo) {
fake.indexMutex.RLock() fake.indexMutex.RLock()
defer fake.indexMutex.RUnlock() defer fake.indexMutex.RUnlock()
argsForCall := fake.indexArgsForCall[i] argsForCall := fake.indexArgsForCall[i]
@ -2058,7 +2058,7 @@ func (fake *Model) IndexReturnsOnCall(i int, result1 error) {
}{result1} }{result1}
} }
func (fake *Model) IndexUpdate(arg1 protocol.DeviceID, arg2 string, arg3 []protocol.FileInfo) error { func (fake *Model) IndexUpdate(arg1 protocol.Connection, arg2 string, arg3 []protocol.FileInfo) error {
var arg3Copy []protocol.FileInfo var arg3Copy []protocol.FileInfo
if arg3 != nil { if arg3 != nil {
arg3Copy = make([]protocol.FileInfo, len(arg3)) arg3Copy = make([]protocol.FileInfo, len(arg3))
@ -2067,7 +2067,7 @@ func (fake *Model) IndexUpdate(arg1 protocol.DeviceID, arg2 string, arg3 []proto
fake.indexUpdateMutex.Lock() fake.indexUpdateMutex.Lock()
ret, specificReturn := fake.indexUpdateReturnsOnCall[len(fake.indexUpdateArgsForCall)] ret, specificReturn := fake.indexUpdateReturnsOnCall[len(fake.indexUpdateArgsForCall)]
fake.indexUpdateArgsForCall = append(fake.indexUpdateArgsForCall, struct { fake.indexUpdateArgsForCall = append(fake.indexUpdateArgsForCall, struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 []protocol.FileInfo arg3 []protocol.FileInfo
}{arg1, arg2, arg3Copy}) }{arg1, arg2, arg3Copy})
@ -2090,13 +2090,13 @@ func (fake *Model) IndexUpdateCallCount() int {
return len(fake.indexUpdateArgsForCall) return len(fake.indexUpdateArgsForCall)
} }
func (fake *Model) IndexUpdateCalls(stub func(protocol.DeviceID, string, []protocol.FileInfo) error) { func (fake *Model) IndexUpdateCalls(stub func(protocol.Connection, string, []protocol.FileInfo) error) {
fake.indexUpdateMutex.Lock() fake.indexUpdateMutex.Lock()
defer fake.indexUpdateMutex.Unlock() defer fake.indexUpdateMutex.Unlock()
fake.IndexUpdateStub = stub fake.IndexUpdateStub = stub
} }
func (fake *Model) IndexUpdateArgsForCall(i int) (protocol.DeviceID, string, []protocol.FileInfo) { func (fake *Model) IndexUpdateArgsForCall(i int) (protocol.Connection, string, []protocol.FileInfo) {
fake.indexUpdateMutex.RLock() fake.indexUpdateMutex.RLock()
defer fake.indexUpdateMutex.RUnlock() defer fake.indexUpdateMutex.RUnlock()
argsForCall := fake.indexUpdateArgsForCall[i] argsForCall := fake.indexUpdateArgsForCall[i]
@ -2666,7 +2666,7 @@ func (fake *Model) RemoteNeedFolderFilesReturnsOnCall(i int, result1 []db.FileIn
}{result1, result2} }{result1, result2}
} }
func (fake *Model) Request(arg1 protocol.DeviceID, arg2 string, arg3 string, arg4 int32, arg5 int32, arg6 int64, arg7 []byte, arg8 uint32, arg9 bool) (protocol.RequestResponse, error) { func (fake *Model) Request(arg1 protocol.Connection, arg2 string, arg3 string, arg4 int32, arg5 int32, arg6 int64, arg7 []byte, arg8 uint32, arg9 bool) (protocol.RequestResponse, error) {
var arg7Copy []byte var arg7Copy []byte
if arg7 != nil { if arg7 != nil {
arg7Copy = make([]byte, len(arg7)) arg7Copy = make([]byte, len(arg7))
@ -2675,7 +2675,7 @@ func (fake *Model) Request(arg1 protocol.DeviceID, arg2 string, arg3 string, arg
fake.requestMutex.Lock() fake.requestMutex.Lock()
ret, specificReturn := fake.requestReturnsOnCall[len(fake.requestArgsForCall)] ret, specificReturn := fake.requestReturnsOnCall[len(fake.requestArgsForCall)]
fake.requestArgsForCall = append(fake.requestArgsForCall, struct { fake.requestArgsForCall = append(fake.requestArgsForCall, struct {
arg1 protocol.DeviceID arg1 protocol.Connection
arg2 string arg2 string
arg3 string arg3 string
arg4 int32 arg4 int32
@ -2704,13 +2704,13 @@ func (fake *Model) RequestCallCount() int {
return len(fake.requestArgsForCall) return len(fake.requestArgsForCall)
} }
func (fake *Model) RequestCalls(stub func(protocol.DeviceID, string, string, int32, int32, int64, []byte, uint32, bool) (protocol.RequestResponse, error)) { func (fake *Model) RequestCalls(stub func(protocol.Connection, string, string, int32, int32, int64, []byte, uint32, bool) (protocol.RequestResponse, error)) {
fake.requestMutex.Lock() fake.requestMutex.Lock()
defer fake.requestMutex.Unlock() defer fake.requestMutex.Unlock()
fake.RequestStub = stub fake.RequestStub = stub
} }
func (fake *Model) RequestArgsForCall(i int) (protocol.DeviceID, string, string, int32, int32, int64, []byte, uint32, bool) { func (fake *Model) RequestArgsForCall(i int) (protocol.Connection, string, string, int32, int32, int64, []byte, uint32, bool) {
fake.requestMutex.RLock() fake.requestMutex.RLock()
defer fake.requestMutex.RUnlock() defer fake.requestMutex.RUnlock()
argsForCall := fake.requestArgsForCall[i] argsForCall := fake.requestArgsForCall[i]

View File

@ -1120,14 +1120,14 @@ func (p *pager) done() bool {
// Index is called when a new device is connected and we receive their full index. // Index is called when a new device is connected and we receive their full index.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) error { func (m *model) Index(conn protocol.Connection, folder string, fs []protocol.FileInfo) error {
return m.handleIndex(deviceID, folder, fs, false) return m.handleIndex(conn.ID(), folder, fs, false)
} }
// IndexUpdate is called for incremental updates to connected devices' indexes. // IndexUpdate is called for incremental updates to connected devices' indexes.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo) error { func (m *model) IndexUpdate(conn protocol.Connection, folder string, fs []protocol.FileInfo) error {
return m.handleIndex(deviceID, folder, fs, true) return m.handleIndex(conn.ID(), folder, fs, true)
} }
func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) error { func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []protocol.FileInfo, update bool) error {
@ -1169,13 +1169,20 @@ type ClusterConfigReceivedEventData struct {
Device protocol.DeviceID `json:"device"` Device protocol.DeviceID `json:"device"`
} }
func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterConfig) error { func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfig) error {
if cm.Secondary {
// No handling of secondary connection ClusterConfigs; they merely
// indicate the connection is ready to start.
return nil
}
// Check the peer device's announced folders against our own. Emits events // Check the peer device's announced folders against our own. Emits events
// for folders that we don't expect (unknown or not shared). // 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 // Also, collect a list of folders we do share, and if he's interested in
// temporary indexes, subscribe the connection. // temporary indexes, subscribe the connection.
l.Debugf("Handling ClusterConfig from %v", deviceID.Short()) deviceID := conn.ID()
l.Debugf("Handling ClusterConfig from %v/%s", deviceID.Short(), conn.ConnectionID())
m.pmut.RLock() m.pmut.RLock()
indexHandlerRegistry, ok := m.indexHandlers[deviceID] indexHandlerRegistry, ok := m.indexHandlers[deviceID]
@ -1788,7 +1795,10 @@ func (m *model) introduceDevice(device protocol.Device, introducerCfg config.Dev
} }
// Closed is called when a connection has been closed // Closed is called when a connection has been closed
func (m *model) Closed(connID string, err error) { func (m *model) Closed(conn protocol.Connection, err error) {
connID := conn.ConnectionID()
deviceID := conn.ID()
m.pmut.Lock() m.pmut.Lock()
conn, ok := m.conns[connID] conn, ok := m.conns[connID]
if !ok { if !ok {
@ -1800,32 +1810,31 @@ func (m *model) Closed(connID string, err error) {
delete(m.closed, connID) delete(m.closed, connID)
delete(m.conns, connID) delete(m.conns, connID)
device := conn.ID() removedIsPrimary := m.deviceConns[deviceID][0] == connID
removedIsPrimary := m.deviceConns[device][0] == connID remainingConns := without(m.deviceConns[deviceID], connID)
remainingConns := without(m.deviceConns[device], connID)
// XXX: all the below needs more thinking about when to remove what // XXX: all the below needs more thinking about when to remove what
if removedIsPrimary { if removedIsPrimary {
m.progressEmitter.temporaryIndexUnsubscribe(conn) m.progressEmitter.temporaryIndexUnsubscribe(conn)
delete(m.indexHandlers, device) delete(m.indexHandlers, deviceID)
delete(m.deviceDownloads, device) delete(m.deviceDownloads, deviceID)
m.scheduleConnectionPromotion() m.scheduleConnectionPromotion()
} }
if len(remainingConns) == 0 { if len(remainingConns) == 0 {
// All device connections closed // All device connections closed
delete(m.deviceConns, device) delete(m.deviceConns, deviceID)
delete(m.connRequestLimiters, device) delete(m.connRequestLimiters, deviceID)
delete(m.helloMessages, device) delete(m.helloMessages, deviceID)
delete(m.remoteFolderStates, device) delete(m.remoteFolderStates, deviceID)
m.deviceDidClose(device, time.Since(conn.EstablishedAt())) m.deviceDidClose(deviceID, time.Since(conn.EstablishedAt()))
} else { } else {
// Some connections remain // Some connections remain
m.deviceConns[device] = remainingConns m.deviceConns[deviceID] = remainingConns
} }
m.pmut.Unlock() m.pmut.Unlock()
l.Infof("Connection to %s at %s closed: %v", device, conn, err) l.Infof("Connection to %s at %s closed: %v", deviceID, conn, err)
m.evLogger.Log(events.DeviceDisconnected, map[string]string{ m.evLogger.Log(events.DeviceDisconnected, map[string]string{
"id": device.String(), "id": deviceID.String(),
"error": err.Error(), "error": err.Error(),
}) })
close(closed) close(closed)
@ -1862,11 +1871,14 @@ func (r *requestResponse) Wait() {
// Request returns the specified data segment by reading it from local disk. // Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface. // Implements the protocol.Model interface.
func (m *model) Request(deviceID protocol.DeviceID, folder, name string, _, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) { func (m *model) Request(conn protocol.Connection, folder, name string, _, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (out protocol.RequestResponse, err error) {
if size < 0 || offset < 0 { if size < 0 || offset < 0 {
return nil, protocol.ErrInvalid return nil, protocol.ErrInvalid
} }
deviceID := conn.ID()
connID := conn.ConnectionID()
m.fmut.RLock() m.fmut.RLock()
folderCfg, ok := m.folderCfgs[folder] folderCfg, ok := m.folderCfgs[folder]
folderIgnores := m.folderIgnores[folder] folderIgnores := m.folderIgnores[folder]
@ -1894,16 +1906,16 @@ func (m *model) Request(deviceID protocol.DeviceID, folder, name string, _, size
} }
if deviceID != protocol.LocalDeviceID { 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/%s: %q / %q o=%d s=%d t=%v", m, deviceID, connID, folder, name, offset, size, fromTemporary)
} }
if fs.IsInternal(name) { 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/%s: %q / %q o=%d s=%d", m, deviceID, connID, folder, name, offset, size)
return nil, protocol.ErrInvalid return nil, protocol.ErrInvalid
} }
if folderIgnores.Match(name).IsIgnored() { 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/%s: %q / %q o=%d s=%d", m, deviceID, connID, folder, name, offset, size)
return nil, protocol.ErrInvalid return nil, protocol.ErrInvalid
} }
@ -2281,8 +2293,6 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn) l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
conn.Start()
m.pmut.Unlock() m.pmut.Unlock()
if (deviceCfg.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" { if (deviceCfg.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
@ -2319,11 +2329,29 @@ func (m *model) promoteConnections() {
defer m.pmut.Unlock() defer m.pmut.Unlock()
for deviceID, connIDs := range m.deviceConns { for deviceID, connIDs := range m.deviceConns {
cm, passwords := m.generateClusterConfigFRLocked(deviceID)
if _, ok := m.indexHandlers[deviceID]; !ok { if _, ok := m.indexHandlers[deviceID]; !ok {
// Connected device lacks and index handler. We should promote // Connected device lacks an index handler. We should promote
// the primary connection to be the index handling one. // the primary connection to be the index handling one.
l.Infoln("Promoting connection", connIDs[0], "to", deviceID) l.Infoln("Promoting connection", connIDs[0], "to", deviceID)
m.promoteDeviceConnectionLocked(m.conns[connIDs[0]]) conn := m.conns[connIDs[0]]
m.promoteDeviceConnectionLocked(conn)
if conn.Statistics().StartedAt.IsZero() {
conn.SetFolderPasswords(passwords)
conn.Start()
conn.ClusterConfig(cm)
}
}
// Make sure any new connections also get started, and an empty
// cluster config.
for _, connID := range connIDs[1:] {
conn := m.conns[connID]
if conn.Statistics().StartedAt.IsZero() {
conn.SetFolderPasswords(passwords)
conn.Start()
conn.ClusterConfig(protocol.ClusterConfig{Secondary: true})
}
} }
} }
} }
@ -2333,35 +2361,32 @@ func (m *model) promoteDeviceConnectionLocked(conn protocol.Connection) {
connID := conn.ConnectionID() connID := conn.ConnectionID()
m.deviceDownloads[deviceID] = newDeviceDownloadState() m.deviceDownloads[deviceID] = newDeviceDownloadState()
indexRegistry := newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.closed[connID], m.Supervisor, m.evLogger) indexRegistry := newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.closed[connID], m.Supervisor, m.evLogger)
for id, fcfg := range m.folderCfgs { for id, fcfg := range m.folderCfgs {
indexRegistry.RegisterFolderState(fcfg, m.folderFiles[id], m.folderRunners[id]) indexRegistry.RegisterFolderState(fcfg, m.folderFiles[id], m.folderRunners[id])
} }
m.indexHandlers[deviceID] = indexRegistry m.indexHandlers[deviceID] = indexRegistry
cm, passwords := m.generateClusterConfigFRLocked(deviceID)
conn.SetFolderPasswords(passwords)
conn.ClusterConfig(cm)
} }
func (m *model) DownloadProgress(device protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) error { func (m *model) DownloadProgress(conn protocol.Connection, folder string, updates []protocol.FileDownloadProgressUpdate) error {
deviceID := conn.ID()
m.fmut.RLock() m.fmut.RLock()
cfg, ok := m.folderCfgs[folder] cfg, ok := m.folderCfgs[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(device) { if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(deviceID) {
return nil return nil
} }
m.pmut.RLock() m.pmut.RLock()
downloads := m.deviceDownloads[device] downloads := m.deviceDownloads[deviceID]
m.pmut.RUnlock() m.pmut.RUnlock()
downloads.Update(folder, updates) downloads.Update(folder, updates)
state := downloads.GetBlockCounts(folder) state := downloads.GetBlockCounts(folder)
m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{ m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{
"device": device.String(), "device": deviceID.String(),
"folder": folder, "folder": folder,
"state": state, "state": state,
}) })
@ -2406,7 +2431,7 @@ func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, f
return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID) return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID)
} }
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) l.Debugf("%v REQ(out): %s (%s): %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID, conn.String(), folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
return conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary) return conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
} }

View File

@ -94,7 +94,7 @@ func TestRequest(t *testing.T) {
m.ScanFolder("default") m.ScanFolder("default")
// Existing, shared file // Existing, shared file
res, err := m.Request(device1, "default", "foo", 0, 6, 0, nil, 0, false) res, err := m.Request(device1Conn, "default", "foo", 0, 6, 0, nil, 0, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -104,35 +104,35 @@ func TestRequest(t *testing.T) {
} }
// Existing, nonshared file // Existing, nonshared file
_, err = m.Request(device2, "default", "foo", 0, 6, 0, nil, 0, false) _, err = m.Request(device2Conn, "default", "foo", 0, 6, 0, nil, 0, false)
if err == nil { if err == nil {
t.Error("Unexpected nil error on insecure file read") t.Error("Unexpected nil error on insecure file read")
} }
// Nonexistent file // Nonexistent file
_, err = m.Request(device1, "default", "nonexistent", 0, 6, 0, nil, 0, false) _, err = m.Request(device1Conn, "default", "nonexistent", 0, 6, 0, nil, 0, false)
if err == nil { if err == nil {
t.Error("Unexpected nil error on insecure file read") t.Error("Unexpected nil error on insecure file read")
} }
// Shared folder, but disallowed file name // Shared folder, but disallowed file name
_, err = m.Request(device1, "default", "../walk.go", 0, 6, 0, nil, 0, false) _, err = m.Request(device1Conn, "default", "../walk.go", 0, 6, 0, nil, 0, false)
if err == nil { if err == nil {
t.Error("Unexpected nil error on insecure file read") t.Error("Unexpected nil error on insecure file read")
} }
// Negative offset // Negative offset
_, err = m.Request(device1, "default", "foo", 0, -4, 0, nil, 0, false) _, err = m.Request(device1Conn, "default", "foo", 0, -4, 0, nil, 0, false)
if err == nil { if err == nil {
t.Error("Unexpected nil error on insecure file read") t.Error("Unexpected nil error on insecure file read")
} }
// Larger block than available // Larger block than available
_, err = m.Request(device1, "default", "foo", 0, 42, 0, []byte("hash necessary but not checked"), 0, false) _, err = m.Request(device1Conn, "default", "foo", 0, 42, 0, []byte("hash necessary but not checked"), 0, false)
if err == nil { if err == nil {
t.Error("Unexpected nil error on read past end of file") t.Error("Unexpected nil error on read past end of file")
} }
_, err = m.Request(device1, "default", "foo", 0, 42, 0, nil, 0, false) _, err = m.Request(device1Conn, "default", "foo", 0, 42, 0, nil, 0, false)
if err != nil { if err != nil {
t.Error("Unexpected error when large read should be permitted") t.Error("Unexpected error when large read should be permitted")
} }
@ -168,11 +168,11 @@ func benchmarkIndex(b *testing.B, nfiles int) {
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI()) defer cleanupModelAndRemoveDir(m, fcfg.Filesystem(nil).URI())
files := genFiles(nfiles) files := genFiles(nfiles)
must(b, m.Index(device1, fcfg.ID, files)) must(b, m.Index(device1Conn, fcfg.ID, files))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
must(b, m.Index(device1, fcfg.ID, files)) must(b, m.Index(device1Conn, fcfg.ID, files))
} }
b.ReportAllocs() b.ReportAllocs()
} }
@ -197,11 +197,11 @@ func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
files := genFiles(nfiles) files := genFiles(nfiles)
ufiles := genFiles(nufiles) ufiles := genFiles(nufiles)
must(b, m.Index(device1, fcfg.ID, files)) must(b, m.Index(device1Conn, fcfg.ID, files))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
must(b, m.IndexUpdate(device1, fcfg.ID, ufiles)) must(b, m.IndexUpdate(device1Conn, fcfg.ID, ufiles))
} }
b.ReportAllocs() b.ReportAllocs()
} }
@ -218,7 +218,7 @@ func BenchmarkRequestOut(b *testing.B) {
fc.addFile(f.Name, 0o644, protocol.FileInfoTypeFile, []byte("some data to return")) fc.addFile(f.Name, 0o644, protocol.FileInfoTypeFile, []byte("some data to return"))
} }
m.AddConnection(fc, protocol.Hello{}) m.AddConnection(fc, protocol.Hello{})
must(b, m.Index(device1, "default", files)) must(b, m.Index(device1Conn, "default", files))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -247,7 +247,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if _, err := m.Request(device1, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, 128<<10, 0, nil, 0, false); err != nil { if _, err := m.Request(device1Conn, "default", "request/for/a/file/in/a/couple/of/dirs/128k", 0, 128<<10, 0, nil, 0, false); err != nil {
b.Error(err) b.Error(err)
} }
} }
@ -287,7 +287,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device already has a name") t.Errorf("Device already has a name")
} }
m.Closed(conn.ConnectionID(), protocol.ErrTimeout) m.Closed(conn, protocol.ErrTimeout)
hello.DeviceName = "tester" hello.DeviceName = "tester"
m.AddConnection(conn, hello) m.AddConnection(conn, hello)
@ -295,7 +295,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device did not get a name") t.Errorf("Device did not get a name")
} }
m.Closed(conn.ConnectionID(), protocol.ErrTimeout) m.Closed(conn, protocol.ErrTimeout)
hello.DeviceName = "tester2" hello.DeviceName = "tester2"
m.AddConnection(conn, hello) m.AddConnection(conn, hello)
@ -317,7 +317,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device name not saved in config") t.Errorf("Device name not saved in config")
} }
m.Closed(conn.ConnectionID(), protocol.ErrTimeout) m.Closed(conn, protocol.ErrTimeout)
waiter, err := cfg.Modify(func(cfg *config.Configuration) { waiter, err := cfg.Modify(func(cfg *config.Configuration) {
cfg.Options.OverwriteRemoteDevNames = true cfg.Options.OverwriteRemoteDevNames = true
@ -528,7 +528,7 @@ func TestIntroducer(t *testing.T) {
SkipIntroductionRemovals: true, SkipIntroductionRemovals: true,
EncryptionPasswordToken: []byte("faketoken"), EncryptionPasswordToken: []byte("faketoken"),
}) })
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
if newDev, ok := m.cfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals { if newDev, ok := m.cfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals {
t.Error("device 2 missing or wrong flags") t.Error("device 2 missing or wrong flags")
@ -584,7 +584,7 @@ func TestIntroducer(t *testing.T) {
Introducer: true, Introducer: true,
SkipIntroductionRemovals: true, SkipIntroductionRemovals: true,
}) })
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
// Should not get introducer, as it's already unset, and it's an existing device. // Should not get introducer, as it's already unset, and it's an existing device.
if newDev, ok := m.cfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals { if newDev, ok := m.cfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals {
@ -634,7 +634,7 @@ func TestIntroducer(t *testing.T) {
}, },
}, },
}) })
m.ClusterConfig(device1, protocol.ClusterConfig{}) m.ClusterConfig(device1Conn, protocol.ClusterConfig{})
if _, ok := m.cfg.Device(device2); ok { if _, ok := m.cfg.Device(device2); ok {
t.Error("device 2 should have been removed") t.Error("device 2 should have been removed")
@ -686,7 +686,7 @@ func TestIntroducer(t *testing.T) {
}, },
}, },
}) })
m.ClusterConfig(device1, protocol.ClusterConfig{}) m.ClusterConfig(device1Conn, protocol.ClusterConfig{})
if _, ok := m.cfg.Device(device2); !ok { if _, ok := m.cfg.Device(device2); !ok {
t.Error("device 2 should not have been removed") t.Error("device 2 should not have been removed")
@ -743,7 +743,7 @@ func TestIntroducer(t *testing.T) {
Introducer: true, Introducer: true,
SkipIntroductionRemovals: true, SkipIntroductionRemovals: true,
}) })
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
if _, ok := m.cfg.Device(device2); !ok { if _, ok := m.cfg.Device(device2); !ok {
t.Error("device 2 should not have been removed") t.Error("device 2 should not have been removed")
@ -794,7 +794,7 @@ func TestIntroducer(t *testing.T) {
}, },
}, },
}) })
m.ClusterConfig(device1, protocol.ClusterConfig{}) m.ClusterConfig(device1Conn, protocol.ClusterConfig{})
if _, ok := m.cfg.Device(device2); !ok { if _, ok := m.cfg.Device(device2); !ok {
t.Error("device 2 should not have been removed") t.Error("device 2 should not have been removed")
@ -847,7 +847,7 @@ func TestIntroducer(t *testing.T) {
}) })
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, protocol.ClusterConfig{}) m.ClusterConfig(device1Conn, protocol.ClusterConfig{})
if _, ok := m.cfg.Device(device2); !ok { if _, ok := m.cfg.Device(device2); !ok {
t.Error("device 2 should not have been removed") t.Error("device 2 should not have been removed")
@ -906,14 +906,14 @@ func TestIssue5063(t *testing.T) {
for _, c := range m.conns { for _, c := range m.conns {
conn := c.(*fakeConnection) conn := c.(*fakeConnection)
conn.CloseCalls(func(_ error) {}) conn.CloseCalls(func(_ error) {})
defer m.Closed(c.ConnectionID(), errStopped) // to unblock deferred m.Stop() defer m.Closed(c, errStopped) // to unblock deferred m.Stop()
} }
m.pmut.Unlock() m.pmut.Unlock()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
addAndVerify := func(id string) { addAndVerify := func(id string) {
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id) t.Error("expected shared", id)
} }
@ -951,7 +951,7 @@ func TestAutoAcceptRejected(t *testing.T) {
// defer cleanupModel(m) // defer cleanupModel(m)
defer cancel() defer cancel()
id := srand.String(8) id := srand.String(8)
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if cfg, ok := m.cfg.Folder(id); ok && cfg.SharedWith(device1) { if cfg, ok := m.cfg.Folder(id); ok && cfg.SharedWith(device1) {
t.Error("unexpected shared", id) t.Error("unexpected shared", id)
@ -964,7 +964,7 @@ func TestAutoAcceptNewFolder(t *testing.T) {
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
id := srand.String(8) id := srand.String(8)
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id) t.Error("expected shared", id)
} }
@ -976,14 +976,14 @@ func TestAutoAcceptNewFolderFromTwoDevices(t *testing.T) {
defer cancel() defer cancel()
id := srand.String(8) id := srand.String(8)
defer os.RemoveAll(id) defer os.RemoveAll(id)
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id) t.Error("expected shared", id)
} }
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id) t.Error("unexpected expected shared", id)
} }
m.ClusterConfig(device2, createClusterConfig(device2, id)) m.ClusterConfig(device2Conn, createClusterConfig(device2, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device2) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device2) {
t.Error("expected shared", id) t.Error("expected shared", id)
} }
@ -997,14 +997,14 @@ func TestAutoAcceptNewFolderFromOnlyOneDevice(t *testing.T) {
defer os.RemoveAll(id) defer os.RemoveAll(id)
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id) t.Error("expected shared", id)
} }
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id) t.Error("unexpected expected shared", id)
} }
m.ClusterConfig(device2, createClusterConfig(device2, id)) m.ClusterConfig(device2Conn, createClusterConfig(device2, id))
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) { if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected shared", id) t.Error("unexpected shared", id)
} }
@ -1035,10 +1035,10 @@ func TestAutoAcceptNewFolderPremutationsNoPanic(t *testing.T) {
cfg.Folders = append(cfg.Folders, fcfg) cfg.Folders = append(cfg.Folders, fcfg)
} }
m, cancel := newState(t, cfg) m, cancel := newState(t, cfg)
m.ClusterConfig(device1, protocol.ClusterConfig{ m.ClusterConfig(device1Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{dev1folder}, Folders: []protocol.Folder{dev1folder},
}) })
m.ClusterConfig(device2, protocol.ClusterConfig{ m.ClusterConfig(device2Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{dev2folder}, Folders: []protocol.Folder{dev2folder},
}) })
cleanupModel(m) cleanupModel(m)
@ -1058,7 +1058,7 @@ func TestAutoAcceptMultipleFolders(t *testing.T) {
m, cancel := newState(t, defaultAutoAcceptCfg) m, cancel := newState(t, defaultAutoAcceptCfg)
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, createClusterConfig(device1, id1, id2)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id1, id2))
if fcfg, ok := m.cfg.Folder(id1); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id1); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id1) t.Error("expected shared", id1)
} }
@ -1086,7 +1086,7 @@ func TestAutoAcceptExistingFolder(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || fcfg.SharedWith(device1) {
t.Error("missing folder, or shared", id) t.Error("missing folder, or shared", id)
} }
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || fcfg.Path != idOther { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) || fcfg.Path != idOther {
t.Error("missing folder, or unshared, or path changed", id) t.Error("missing folder, or unshared, or path changed", id)
@ -1112,7 +1112,7 @@ func TestAutoAcceptNewAndExistingFolder(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id1); !ok || fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id1); !ok || fcfg.SharedWith(device1) {
t.Error("missing folder, or shared", id1) t.Error("missing folder, or shared", id1)
} }
m.ClusterConfig(device1, createClusterConfig(device1, id1, id2)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id1, id2))
for i, id := range []string{id1, id2} { for i, id := range []string{id1, id2} {
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
@ -1143,7 +1143,7 @@ func TestAutoAcceptAlreadyShared(t *testing.T) {
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("missing folder, or not shared", id) t.Error("missing folder, or not shared", id)
} }
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) { if fcfg, ok := m.cfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("missing folder, or not shared", id) t.Error("missing folder, or not shared", id)
@ -1159,7 +1159,7 @@ func TestAutoAcceptNameConflict(t *testing.T) {
m, cancel := newState(t, defaultAutoAcceptCfg) m, cancel := newState(t, defaultAutoAcceptCfg)
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, protocol.ClusterConfig{ m.ClusterConfig(device1Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: id, ID: id,
@ -1179,7 +1179,7 @@ func TestAutoAcceptPrefersLabel(t *testing.T) {
label := srand.String(8) label := srand.String(8)
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{ m.ClusterConfig(device1Conn, addFolderDevicesToClusterConfig(protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: id, ID: id,
@ -1203,7 +1203,7 @@ func TestAutoAcceptFallsBackToID(t *testing.T) {
} }
defer cleanupModel(m) defer cleanupModel(m)
defer cancel() defer cancel()
m.ClusterConfig(device1, addFolderDevicesToClusterConfig(protocol.ClusterConfig{ m.ClusterConfig(device1Conn, addFolderDevicesToClusterConfig(protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: id, ID: id,
@ -1245,7 +1245,7 @@ func TestAutoAcceptPausedWhenFolderConfigChanged(t *testing.T) {
t.Fatal("folder running?") t.Fatal("folder running?")
} }
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
m.generateClusterConfig(device1) m.generateClusterConfig(device1)
if fcfg, ok := m.cfg.Folder(id); !ok { if fcfg, ok := m.cfg.Folder(id); !ok {
@ -1294,7 +1294,7 @@ func TestAutoAcceptPausedWhenFolderConfigNotChanged(t *testing.T) {
t.Fatal("folder running?") t.Fatal("folder running?")
} }
m.ClusterConfig(device1, createClusterConfig(device1, id)) m.ClusterConfig(device1Conn, createClusterConfig(device1, id))
m.generateClusterConfig(device1) m.generateClusterConfig(device1)
if fcfg, ok := m.cfg.Folder(id); !ok { if fcfg, ok := m.cfg.Folder(id); !ok {
@ -1338,7 +1338,7 @@ func TestAutoAcceptEnc(t *testing.T) {
// would panic. // would panic.
clusterConfig := func(deviceID protocol.DeviceID, cm protocol.ClusterConfig) { clusterConfig := func(deviceID protocol.DeviceID, cm protocol.ClusterConfig) {
m.AddConnection(newFakeConnection(deviceID, m), protocol.Hello{}) m.AddConnection(newFakeConnection(deviceID, m), protocol.Hello{})
m.ClusterConfig(deviceID, cm) m.ClusterConfig(&protocolmocks.Connection{IDStub: func() protocol.DeviceID { return deviceID }}, cm)
} }
clusterConfig(device1, basicCC()) clusterConfig(device1, basicCC())
@ -1806,7 +1806,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
return string(bytes) return string(bytes)
} }
must(t, m.Index(device1, "default", testdata)) must(t, m.Index(device1Conn, "default", testdata))
result, _ := m.GlobalDirectoryTree("default", "", -1, false) result, _ := m.GlobalDirectoryTree("default", "", -1, false)
@ -2013,7 +2013,7 @@ func benchmarkTree(b *testing.B, n1, n2 int) {
m.ScanFolder(fcfg.ID) m.ScanFolder(fcfg.ID)
files := genDeepFiles(n1, n2) files := genDeepFiles(n1, n2)
must(b, m.Index(device1, fcfg.ID, files)) must(b, m.Index(device1Conn, fcfg.ID, files))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -2159,7 +2159,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
conn2 := newFakeConnection(device2, m) conn2 := newFakeConnection(device2, m)
m.AddConnection(conn2, protocol.Hello{}) m.AddConnection(conn2, protocol.Hello{})
m.ClusterConfig(device1, protocol.ClusterConfig{ m.ClusterConfig(device1Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: "default", ID: "default",
@ -2171,7 +2171,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
}, },
}, },
}) })
m.ClusterConfig(device2, protocol.ClusterConfig{ m.ClusterConfig(device2Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: "default", ID: "default",
@ -2424,7 +2424,7 @@ func TestRemoveDirWithContent(t *testing.T) {
file.Deleted = true file.Deleted = true
file.Version = file.Version.Update(device1.Short()).Update(device1.Short()) file.Version = file.Version.Update(device1.Short()).Update(device1.Short())
must(t, m.IndexUpdate(device1, fcfg.ID, []protocol.FileInfo{dir, file})) must(t, m.IndexUpdate(device1Conn, fcfg.ID, []protocol.FileInfo{dir, file}))
// Is there something we could trigger on instead of just waiting? // Is there something we could trigger on instead of just waiting?
timeout := time.NewTimer(5 * time.Second) timeout := time.NewTimer(5 * time.Second)
@ -2919,14 +2919,14 @@ func TestRequestLimit(t *testing.T) {
m.ScanFolder("default") m.ScanFolder("default")
befReq := time.Now() befReq := time.Now()
first, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false) first, err := m.Request(device1Conn, "default", file, 0, 2000, 0, nil, 0, false)
if err != nil { if err != nil {
t.Fatalf("First request failed: %v", err) t.Fatalf("First request failed: %v", err)
} }
reqDur := time.Since(befReq) reqDur := time.Since(befReq)
returned := make(chan struct{}) returned := make(chan struct{})
go func() { go func() {
second, err := m.Request(device1, "default", file, 0, 2000, 0, nil, 0, false) second, err := m.Request(device1Conn, "default", file, 0, 2000, 0, nil, 0, false)
if err != nil { if err != nil {
t.Errorf("Second request failed: %v", err) t.Errorf("Second request failed: %v", err)
} }
@ -2964,13 +2964,15 @@ func TestConnCloseOnRestart(t *testing.T) {
br := &testutils.BlockingRW{} br := &testutils.BlockingRW{}
nw := &testutils.NoopRW{} nw := &testutils.NoopRW{}
conn := protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, new(protocolmocks.ConnectionInfo), protocol.CompressionNever, nil, m.keyGen) m.AddConnection(protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, new(protocolmocks.ConnectionInfo), protocol.CompressionNever, nil, m.keyGen), protocol.Hello{})
m.AddConnection(conn, protocol.Hello{})
m.pmut.RLock() m.pmut.RLock()
if len(m.closed) != 1 { if len(m.closed) != 1 {
t.Fatalf("Expected just one conn (len(m.conns) == %v)", len(m.conns)) t.Fatalf("Expected just one conn (len(m.closed) == %v)", len(m.closed))
}
var closed chan struct{}
for _, c := range m.closed {
closed = c
} }
closed := m.closed[conn.ConnectionID()]
m.pmut.RUnlock() m.pmut.RUnlock()
waiter, err := w.RemoveDevice(device1) waiter, err := w.RemoveDevice(device1)
@ -3065,7 +3067,10 @@ func TestDevicePause(t *testing.T) {
defer sub.Unsubscribe() defer sub.Unsubscribe()
m.pmut.RLock() m.pmut.RLock()
closed := m.closed[m.deviceConns[device1][0]] var closed chan struct{}
for _, c := range m.closed {
closed = c
}
m.pmut.RUnlock() m.pmut.RUnlock()
pauseDevice(t, m.cfg, device1, true) pauseDevice(t, m.cfg, device1, true)
@ -3581,7 +3586,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
} }
// A remote must have the file, otherwise the deletion below is // A remote must have the file, otherwise the deletion below is
// automatically resolved as not a ro-changed item. // automatically resolved as not a ro-changed item.
must(t, m.IndexUpdate(device1, fcfg.ID, []protocol.FileInfo{file})) must(t, m.IndexUpdate(device1Conn, fcfg.ID, []protocol.FileInfo{file}))
must(t, ffs.Remove(name)) must(t, ffs.Remove(name))
m.ScanFolders() m.ScanFolders()
@ -3694,9 +3699,9 @@ func TestIssue6961(t *testing.T) {
version := protocol.Vector{}.Update(device1.Short()) version := protocol.Vector{}.Update(device1.Short())
// Remote, valid and existing file // Remote, valid and existing file
must(t, m.Index(device1, fcfg.ID, []protocol.FileInfo{{Name: name, Version: version, Sequence: 1}})) must(t, m.Index(device1Conn, fcfg.ID, []protocol.FileInfo{{Name: name, Version: version, Sequence: 1}}))
// Remote, invalid (receive-only) and existing file // Remote, invalid (receive-only) and existing file
must(t, m.Index(device2, fcfg.ID, []protocol.FileInfo{{Name: name, RawInvalid: true, Sequence: 1}})) must(t, m.Index(device2Conn, fcfg.ID, []protocol.FileInfo{{Name: name, RawInvalid: true, Sequence: 1}}))
// Create a local file // Create a local file
if fd, err := tfs.OpenFile(name, fs.OptCreate, 0o666); err != nil { if fd, err := tfs.OpenFile(name, fs.OptCreate, 0o666); err != nil {
t.Fatal(err) t.Fatal(err)
@ -3722,7 +3727,7 @@ func TestIssue6961(t *testing.T) {
m.ScanFolders() m.ScanFolders()
// Drop the remote index, add some other file. // Drop the remote index, add some other file.
must(t, m.Index(device2, fcfg.ID, []protocol.FileInfo{{Name: "bar", RawInvalid: true, Sequence: 1}})) must(t, m.Index(device2Conn, fcfg.ID, []protocol.FileInfo{{Name: "bar", RawInvalid: true, Sequence: 1}}))
// Pause and unpause folder to create new db.FileSet and thus recalculate everything // Pause and unpause folder to create new db.FileSet and thus recalculate everything
pauseFolder(t, wcfg, fcfg.ID, true) pauseFolder(t, wcfg, fcfg.ID, true)
@ -3745,7 +3750,7 @@ func TestCompletionEmptyGlobal(t *testing.T) {
m.fmut.Unlock() m.fmut.Unlock()
files[0].Deleted = true files[0].Deleted = true
files[0].Version = files[0].Version.Update(device1.Short()) files[0].Version = files[0].Version.Update(device1.Short())
must(t, m.IndexUpdate(device1, fcfg.ID, files)) must(t, m.IndexUpdate(device1Conn, fcfg.ID, files))
comp := m.testCompletion(protocol.LocalDeviceID, fcfg.ID) comp := m.testCompletion(protocol.LocalDeviceID, fcfg.ID)
if comp.CompletionPct != 95 { if comp.CompletionPct != 95 {
t.Error("Expected completion of 95%, got", comp.CompletionPct) t.Error("Expected completion of 95%, got", comp.CompletionPct)
@ -3766,26 +3771,26 @@ func TestNeedMetaAfterIndexReset(t *testing.T) {
// Start with two remotes having one file, then both deleting it, then // Start with two remotes having one file, then both deleting it, then
// only one adding it again. // only one adding it again.
must(t, m.Index(device1, fcfg.ID, files)) must(t, m.Index(device1Conn, fcfg.ID, files))
must(t, m.Index(device2, fcfg.ID, files)) must(t, m.Index(device2Conn, fcfg.ID, files))
seq++ seq++
files[0].SetDeleted(device2.Short()) files[0].SetDeleted(device2.Short())
files[0].Sequence = seq files[0].Sequence = seq
must(t, m.IndexUpdate(device2, fcfg.ID, files)) must(t, m.IndexUpdate(device2Conn, fcfg.ID, files))
must(t, m.IndexUpdate(device1, fcfg.ID, files)) must(t, m.IndexUpdate(device1Conn, fcfg.ID, files))
seq++ seq++
files[0].Deleted = false files[0].Deleted = false
files[0].Size = 20 files[0].Size = 20
files[0].Version = files[0].Version.Update(device1.Short()) files[0].Version = files[0].Version.Update(device1.Short())
files[0].Sequence = seq files[0].Sequence = seq
must(t, m.IndexUpdate(device1, fcfg.ID, files)) must(t, m.IndexUpdate(device1Conn, fcfg.ID, files))
if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 { if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 {
t.Error("Expected one needed item for device2, got", comp.NeedItems) t.Error("Expected one needed item for device2, got", comp.NeedItems)
} }
// Pretend we had an index reset on device 1 // Pretend we had an index reset on device 1
must(t, m.Index(device1, fcfg.ID, files)) must(t, m.Index(device1Conn, fcfg.ID, files))
if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 { if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 {
t.Error("Expected one needed item for device2, got", comp.NeedItems) t.Error("Expected one needed item for device2, got", comp.NeedItems)
} }

View File

@ -107,7 +107,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
<-done <-done
// Request a file by traversing the symlink // Request a file by traversing the symlink
res, err := m.Request(device1, "default", "symlink/requests_test.go", 0, 10, 0, nil, 0, false) res, err := m.Request(device1Conn, "default", "symlink/requests_test.go", 0, 10, 0, nil, 0, false)
if err == nil || res != nil { if err == nil || res != nil {
t.Error("Managed to traverse symlink") t.Error("Managed to traverse symlink")
} }
@ -439,7 +439,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash) t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
} }
res, err := m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false) res, err := m.Request(device1Conn, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -453,7 +453,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
writeFile(t, tfs, "foo", payload) writeFile(t, tfs, "foo", payload)
_, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false) _, err = m.Request(device1Conn, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
if err == nil { if err == nil {
t.Fatalf("expected failure") t.Fatalf("expected failure")
} }
@ -1122,7 +1122,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
cc := basicClusterConfig(device1, myID, fcfg.ID) cc := basicClusterConfig(device1, myID, fcfg.ID)
cc.Folders[0].Paused = true cc.Folders[0].Paused = true
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
seq++ seq++
files[0].Sequence = seq files[0].Sequence = seq
@ -1143,7 +1143,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
// Remote unpaused // Remote unpaused
cc.Folders[0].Paused = false cc.Folders[0].Paused = false
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
select { select {
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
t.Fatal("timed out before receiving index") t.Fatal("timed out before receiving index")
@ -1168,12 +1168,12 @@ func TestRequestIndexSenderPause(t *testing.T) {
// Local and remote paused, then first resume remote, then local // Local and remote paused, then first resume remote, then local
cc.Folders[0].Paused = true cc.Folders[0].Paused = true
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
pauseFolder(t, m.cfg, fcfg.ID, true) pauseFolder(t, m.cfg, fcfg.ID, true)
cc.Folders[0].Paused = false cc.Folders[0].Paused = false
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
pauseFolder(t, m.cfg, fcfg.ID, false) pauseFolder(t, m.cfg, fcfg.ID, false)
@ -1190,7 +1190,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
// Folder removed on remote // Folder removed on remote
cc = protocol.ClusterConfig{} cc = protocol.ClusterConfig{}
m.ClusterConfig(device1, cc) m.ClusterConfig(device1Conn, cc)
seq++ seq++
files[0].Sequence = seq files[0].Sequence = seq
@ -1304,7 +1304,7 @@ func TestRequestReceiveEncrypted(t *testing.T) {
return nil return nil
}) })
m.AddConnection(fc, protocol.Hello{}) m.AddConnection(fc, protocol.Hello{})
m.ClusterConfig(device1, protocol.ClusterConfig{ m.ClusterConfig(device1Conn, protocol.ClusterConfig{
Folders: []protocol.Folder{ Folders: []protocol.Folder{
{ {
ID: "default", ID: "default",
@ -1354,7 +1354,7 @@ func TestRequestReceiveEncrypted(t *testing.T) {
} }
// Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash // Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash
_, err := m.Request(device1, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false) _, err := m.Request(device1Conn, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false)
must(t, err) must(t, err)
changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10) changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10)
@ -1405,7 +1405,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) {
file := fc.files[0] file := fc.files[0]
fc.mut.Unlock() fc.mut.Unlock()
file.SetIgnored() file.SetIgnored()
m.IndexUpdate(device2, fcfg.ID, []protocol.FileInfo{prepareFileInfoForIndex(file)}) m.IndexUpdate(device2Conn, fcfg.ID, []protocol.FileInfo{prepareFileInfoForIndex(file)})
// Wait for the ignored file to be received and possible pulled // Wait for the ignored file to be received and possible pulled
timeout := time.After(10 * time.Second) timeout := time.After(10 * time.Second)

View File

@ -19,6 +19,7 @@ import (
"github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore" "github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/protocol/mocks"
"github.com/syncthing/syncthing/lib/rand" "github.com/syncthing/syncthing/lib/rand"
) )
@ -29,6 +30,16 @@ var (
defaultFolderConfig config.FolderConfiguration defaultFolderConfig config.FolderConfiguration
defaultCfg config.Configuration defaultCfg config.Configuration
defaultAutoAcceptCfg config.Configuration defaultAutoAcceptCfg config.Configuration
device1Conn = &mocks.Connection{
IDStub: func() protocol.DeviceID {
return device1
},
}
device2Conn = &mocks.Connection{
IDStub: func() protocol.DeviceID {
return device2
},
}
) )
func init() { func init() {

View File

@ -167,15 +167,15 @@ func negotiateTLS(cert tls.Certificate, conn0, conn1 net.Conn) (net.Conn, net.Co
type fakeModel struct{} type fakeModel struct{}
func (*fakeModel) Index(_ DeviceID, _ string, _ []FileInfo) error { func (*fakeModel) Index(Connection, string, []FileInfo) error {
return nil return nil
} }
func (*fakeModel) IndexUpdate(_ DeviceID, _ string, _ []FileInfo) error { func (*fakeModel) IndexUpdate(Connection, string, []FileInfo) error {
return nil return nil
} }
func (*fakeModel) Request(_ DeviceID, _, _ string, _, size int32, offset int64, _ []byte, _ uint32, _ bool) (RequestResponse, error) { func (*fakeModel) Request(_ Connection, _, _ string, _, size int32, offset int64, _ []byte, _ uint32, _ bool) (RequestResponse, error) {
// We write the offset to the end of the buffer, so the receiver // We write the offset to the end of the buffer, so the receiver
// can verify that it did in fact get some data back over the // can verify that it did in fact get some data back over the
// connection. // connection.
@ -184,13 +184,13 @@ func (*fakeModel) Request(_ DeviceID, _, _ string, _, size int32, offset int64,
return &fakeRequestResponse{buf}, nil return &fakeRequestResponse{buf}, nil
} }
func (*fakeModel) ClusterConfig(_ DeviceID, _ ClusterConfig) error { func (*fakeModel) ClusterConfig(Connection, ClusterConfig) error {
return nil return nil
} }
func (*fakeModel) Closed(string, error) { func (*fakeModel) Closed(Connection, error) {
} }
func (*fakeModel) DownloadProgress(_ DeviceID, _ string, _ []FileDownloadProgressUpdate) error { func (*fakeModel) DownloadProgress(Connection, string, []FileDownloadProgressUpdate) error {
return nil return nil
} }

View File

@ -288,7 +288,8 @@ func (m *Header) XXX_DiscardUnknown() {
var xxx_messageInfo_Header proto.InternalMessageInfo var xxx_messageInfo_Header proto.InternalMessageInfo
type ClusterConfig struct { 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{} } func (m *ClusterConfig) Reset() { *m = ClusterConfig{} }
@ -1142,205 +1143,206 @@ func init() {
func init() { proto.RegisterFile("lib/protocol/bep.proto", fileDescriptor_311ef540e10d9705) } func init() { proto.RegisterFile("lib/protocol/bep.proto", fileDescriptor_311ef540e10d9705) }
var fileDescriptor_311ef540e10d9705 = []byte{ var fileDescriptor_311ef540e10d9705 = []byte{
// 3163 bytes of a gzipped FileDescriptorProto // 3183 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0x4d, 0x6c, 0x1b, 0xc7, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x5a, 0x4b, 0x6c, 0x1b, 0xc7,
0xbd, 0x17, 0xc5, 0x0f, 0x51, 0x23, 0xc9, 0xa6, 0xc6, 0x5f, 0x0c, 0x6d, 0x6b, 0xf9, 0x26, 0xce, 0xf9, 0x17, 0xc5, 0x87, 0xa8, 0xd1, 0xc3, 0xd4, 0xf8, 0xc5, 0xd0, 0xb6, 0x96, 0xff, 0x89, 0xf3,
0x7b, 0x8a, 0xf2, 0x62, 0x27, 0xca, 0xc7, 0xcb, 0x8b, 0xf3, 0x1c, 0x88, 0x22, 0x25, 0x33, 0x96, 0xaf, 0xa2, 0x34, 0x76, 0xa2, 0x3c, 0x9a, 0xc6, 0xa9, 0x03, 0x51, 0xa4, 0x64, 0xc6, 0x32, 0xa9,
0x49, 0x65, 0x28, 0xdb, 0xb1, 0xf1, 0x1e, 0x88, 0x15, 0x77, 0x44, 0x2d, 0x4c, 0xee, 0xf2, 0xed, 0x0c, 0x65, 0x3b, 0x36, 0x50, 0x10, 0x2b, 0xee, 0x88, 0x5a, 0x98, 0xdc, 0x65, 0x77, 0xa9, 0x57,
0x52, 0x5f, 0x41, 0x2f, 0x6d, 0x80, 0x20, 0xd0, 0xa1, 0x28, 0x72, 0x2a, 0x8a, 0x0a, 0x0d, 0x7a, 0xd0, 0x4b, 0x1b, 0x20, 0x08, 0x74, 0x28, 0x8a, 0x9c, 0x8a, 0x22, 0x42, 0x83, 0x5e, 0x7a, 0x2b,
0xe9, 0xad, 0x40, 0x0f, 0xbd, 0xe4, 0xd4, 0xa3, 0x8f, 0x46, 0x80, 0x02, 0x45, 0x0f, 0x0b, 0xc4, 0xd0, 0x43, 0x2f, 0x39, 0xf5, 0xe8, 0xa3, 0x11, 0xa0, 0x40, 0xd1, 0xc3, 0x02, 0xb1, 0x2f, 0x2d,
0xbe, 0xb4, 0xec, 0x8d, 0xc7, 0x9e, 0x8a, 0xf9, 0xcf, 0xec, 0xec, 0xac, 0x3e, 0x52, 0x39, 0x39, 0x7b, 0xd3, 0xb1, 0xa7, 0x62, 0xbe, 0x99, 0x9d, 0x9d, 0xd5, 0x23, 0x95, 0x93, 0x43, 0x4f, 0xe6,
0xf4, 0x64, 0xfe, 0x7f, 0xff, 0xdf, 0xff, 0xbf, 0xb3, 0xf3, 0xff, 0x9a, 0x59, 0x19, 0x5d, 0xec, 0xf7, 0xfb, 0x7e, 0xdf, 0xb7, 0xb3, 0x33, 0xdf, 0x6b, 0x56, 0x46, 0x17, 0x3a, 0xf6, 0xda, 0xf5,
0xd8, 0xeb, 0x37, 0x7a, 0x9e, 0xdb, 0x77, 0x5b, 0x6e, 0xe7, 0xc6, 0x3a, 0xeb, 0x5d, 0x07, 0x01, 0x9e, 0xe7, 0xf6, 0xdd, 0x96, 0xdb, 0xb9, 0xbe, 0xc6, 0x7a, 0xd7, 0x40, 0xc0, 0xd9, 0x10, 0x2b,
0x67, 0x43, 0xac, 0x30, 0xce, 0x76, 0xfb, 0x02, 0x2c, 0xbc, 0xec, 0xb1, 0x9e, 0xeb, 0x0b, 0xfa, 0x8c, 0xb2, 0x9d, 0xbe, 0x00, 0x0b, 0x2f, 0x7a, 0xac, 0xe7, 0xfa, 0x82, 0xbe, 0xb6, 0xb9, 0x7e,
0xfa, 0xd6, 0xc6, 0x8d, 0xb6, 0xdb, 0x76, 0x41, 0x80, 0x5f, 0x82, 0x44, 0x9e, 0x25, 0x50, 0xfa, 0xbd, 0xed, 0xb6, 0x5d, 0x10, 0xe0, 0x97, 0x20, 0x91, 0xa7, 0x09, 0x94, 0xbe, 0xc5, 0x3a, 0x1d,
0x36, 0xeb, 0x74, 0x5c, 0xbc, 0x88, 0x26, 0x2c, 0xb6, 0x6d, 0xb7, 0x58, 0xd3, 0x31, 0xbb, 0x2c, 0x17, 0x2f, 0xa0, 0x31, 0x8b, 0x6d, 0xd9, 0x2d, 0xd6, 0x74, 0xcc, 0x2e, 0xcb, 0x27, 0x8a, 0x89,
0x9f, 0x28, 0x26, 0x66, 0xc7, 0x4b, 0x64, 0x10, 0x18, 0x48, 0xc0, 0x35, 0xb3, 0xcb, 0x86, 0x81, 0x99, 0xd1, 0x12, 0x19, 0x04, 0x06, 0x12, 0x70, 0xcd, 0xec, 0xb2, 0x83, 0xc0, 0xc8, 0xed, 0x74,
0x91, 0xdb, 0xed, 0x76, 0xde, 0x27, 0x11, 0x44, 0xa8, 0xa6, 0xe7, 0x4e, 0x5a, 0x1d, 0x9b, 0x39, 0x3b, 0xef, 0x92, 0x08, 0x22, 0x54, 0xd3, 0x73, 0x27, 0xad, 0x8e, 0xcd, 0x9c, 0xbe, 0x70, 0x32,
0x7d, 0xe1, 0x64, 0x34, 0x72, 0x22, 0xe0, 0x98, 0x93, 0x08, 0x22, 0x54, 0xd3, 0xe3, 0x3a, 0x3a, 0x1c, 0x39, 0x11, 0x70, 0xcc, 0x49, 0x04, 0x11, 0xaa, 0xe9, 0x71, 0x1d, 0x4d, 0x4a, 0x27, 0x5b,
0x23, 0x9d, 0x6c, 0x33, 0xcf, 0xb7, 0x5d, 0x27, 0x9f, 0x04, 0x3f, 0xb3, 0x83, 0xc0, 0x98, 0x12, 0xcc, 0xf3, 0x6d, 0xd7, 0xc9, 0x27, 0xc1, 0xcf, 0xcc, 0x20, 0x30, 0x26, 0x84, 0xe6, 0x9e, 0x50,
0x9a, 0xfb, 0x42, 0x31, 0x0c, 0x8c, 0x73, 0x9a, 0x2b, 0x89, 0x12, 0x1a, 0x67, 0x91, 0xdf, 0x25, 0x1c, 0x04, 0xc6, 0x59, 0xcd, 0x95, 0x44, 0x09, 0x8d, 0xb3, 0xc8, 0x9f, 0x12, 0x28, 0x73, 0x8b,
0x50, 0xe6, 0x36, 0x33, 0x2d, 0xe6, 0xe1, 0x05, 0x94, 0xea, 0xef, 0xf5, 0xc4, 0xeb, 0x9d, 0x99, 0x99, 0x16, 0xf3, 0xf0, 0x3c, 0x4a, 0xf5, 0x77, 0x7b, 0xe2, 0xf5, 0x26, 0xe7, 0xce, 0x5f, 0x0b,
0xbf, 0x70, 0x3d, 0xdc, 0xb8, 0xeb, 0x77, 0x99, 0xef, 0x9b, 0x6d, 0xb6, 0xb6, 0xd7, 0x63, 0xa5, 0x37, 0xee, 0xda, 0x1d, 0xe6, 0xfb, 0x66, 0x9b, 0xad, 0xee, 0xf6, 0x58, 0xe9, 0xc2, 0x20, 0x30,
0x8b, 0x83, 0xc0, 0x00, 0xda, 0x30, 0x30, 0x10, 0xf8, 0xe7, 0x02, 0xa1, 0x80, 0x61, 0x0b, 0x4d, 0x80, 0x76, 0x10, 0x18, 0x08, 0xfc, 0x73, 0x81, 0x50, 0xc0, 0xb0, 0x85, 0xc6, 0x5a, 0x6e, 0xb7,
0xb4, 0xdc, 0x6e, 0xcf, 0x63, 0x3e, 0xac, 0x6d, 0x14, 0x3c, 0x5d, 0x39, 0xe2, 0x69, 0x31, 0xe2, 0xe7, 0x31, 0x1f, 0xd6, 0x36, 0x0c, 0x9e, 0x2e, 0x1f, 0xf1, 0xb4, 0x10, 0x71, 0x4a, 0x57, 0x07,
0x94, 0xae, 0x0d, 0x02, 0x43, 0x37, 0x1a, 0x06, 0xc6, 0xb4, 0x58, 0x77, 0x84, 0x11, 0xaa, 0x33, 0x81, 0xa1, 0x1b, 0x1d, 0x04, 0xc6, 0x94, 0x58, 0x77, 0x84, 0x11, 0xaa, 0x33, 0xc8, 0x17, 0x09,
0xc8, 0xff, 0xa2, 0xa9, 0xc5, 0xce, 0x96, 0xdf, 0x67, 0xde, 0xa2, 0xeb, 0x6c, 0xd8, 0x6d, 0x7c, 0x34, 0xb1, 0xd0, 0xd9, 0xf4, 0xfb, 0xcc, 0x5b, 0x70, 0x9d, 0x75, 0xbb, 0x8d, 0x6f, 0xa3, 0x91,
0x07, 0x8d, 0x6d, 0xb8, 0x1d, 0x8b, 0x79, 0x7e, 0x3e, 0x51, 0x4c, 0xce, 0x4e, 0xcc, 0xe7, 0xa2, 0x75, 0xb7, 0x63, 0x31, 0xcf, 0xcf, 0x27, 0x8a, 0xc9, 0x99, 0xb1, 0xb9, 0x5c, 0xf4, 0xcc, 0x45,
0x47, 0x2e, 0x81, 0xa2, 0x64, 0x3c, 0x09, 0x8c, 0x91, 0x41, 0x60, 0x84, 0xc4, 0x61, 0x60, 0x4c, 0x50, 0x94, 0x8c, 0xc7, 0x81, 0x31, 0x34, 0x08, 0x8c, 0x90, 0x78, 0x10, 0x18, 0xe3, 0xf0, 0x1c,
0xc2, 0x63, 0x84, 0x4c, 0x68, 0xa8, 0x20, 0x5f, 0xa7, 0x50, 0x46, 0x18, 0xe1, 0xeb, 0x68, 0xd4, 0x21, 0x13, 0x1a, 0x2a, 0xf0, 0x4d, 0x34, 0xea, 0xb3, 0x96, 0xeb, 0x58, 0xa6, 0xb7, 0x0b, 0xaf,
0xb6, 0x64, 0xb8, 0x67, 0x9e, 0x05, 0xc6, 0x68, 0xb5, 0x3c, 0x08, 0x8c, 0x51, 0xdb, 0x1a, 0x06, 0x90, 0x2d, 0x15, 0x07, 0x81, 0x11, 0x81, 0x07, 0x81, 0x71, 0x06, 0x4c, 0x15, 0x42, 0x68, 0xa4,
0x46, 0x16, 0xac, 0x6d, 0x8b, 0x7c, 0xf9, 0xf4, 0xda, 0x68, 0xb5, 0x4c, 0x47, 0x6d, 0x0b, 0x5f, 0x25, 0x5f, 0xa5, 0x50, 0x46, 0x3c, 0x14, 0x5f, 0x43, 0xc3, 0xb6, 0x25, 0xe3, 0x65, 0xfa, 0x69,
0x47, 0xe9, 0x8e, 0xb9, 0xce, 0x3a, 0x32, 0xb8, 0xf9, 0x41, 0x60, 0x08, 0x60, 0x18, 0x18, 0x13, 0x60, 0x0c, 0x57, 0xcb, 0x83, 0xc0, 0x18, 0xb6, 0xad, 0x83, 0xc0, 0xc8, 0x82, 0x0b, 0xdb, 0x22,
0xc0, 0x07, 0x89, 0x50, 0x81, 0xe2, 0x9b, 0x68, 0xdc, 0x63, 0xa6, 0xd5, 0x74, 0x9d, 0xce, 0x1e, 0x9f, 0x3f, 0xb9, 0x3a, 0x5c, 0x2d, 0xd3, 0x61, 0xdb, 0xc2, 0xd7, 0x50, 0xba, 0x63, 0xae, 0xb1,
0x04, 0x32, 0x5b, 0x9a, 0x19, 0x04, 0x46, 0x96, 0x83, 0x75, 0xa7, 0xb3, 0x37, 0x0c, 0x8c, 0x33, 0x8e, 0x8c, 0x8e, 0xfc, 0x20, 0x30, 0x04, 0x70, 0x10, 0x18, 0x63, 0xc0, 0x07, 0x89, 0x50, 0x81,
0x60, 0x16, 0x02, 0x84, 0x2a, 0x1d, 0x6e, 0x22, 0x6c, 0xb7, 0x1d, 0xd7, 0x63, 0xcd, 0x1e, 0xf3, 0xe2, 0x1b, 0x68, 0xd4, 0x63, 0xa6, 0xd5, 0x74, 0x9d, 0xce, 0x2e, 0x44, 0x42, 0xb6, 0x34, 0x3d,
0xba, 0x36, 0x6c, 0x8d, 0x9f, 0x4f, 0x81, 0x97, 0x37, 0x06, 0x81, 0x31, 0x2d, 0xb4, 0xab, 0x91, 0x08, 0x8c, 0x2c, 0x07, 0xeb, 0x4e, 0x87, 0xaf, 0x74, 0x12, 0xcc, 0x42, 0x80, 0x50, 0xa5, 0xc3,
0x72, 0x18, 0x18, 0x97, 0xc4, 0xaa, 0x0f, 0x6b, 0x08, 0x3d, 0xca, 0xc6, 0x77, 0xd0, 0x94, 0x7c, 0x4d, 0x84, 0xed, 0xb6, 0xe3, 0x7a, 0xac, 0xd9, 0x63, 0x5e, 0xd7, 0x86, 0xbd, 0xf5, 0xf3, 0x29,
0x80, 0xc5, 0x3a, 0xac, 0xcf, 0xf2, 0x69, 0xf0, 0xfd, 0xef, 0x83, 0xc0, 0x98, 0x14, 0x8a, 0x32, 0xf0, 0xf2, 0xda, 0x20, 0x30, 0xa6, 0x84, 0x76, 0x25, 0x52, 0x1e, 0x04, 0xc6, 0x45, 0xb1, 0xea,
0xe0, 0xc3, 0xc0, 0xc0, 0x9a, 0x5b, 0x01, 0x12, 0x1a, 0xe3, 0x60, 0x0b, 0x9d, 0xb7, 0x6c, 0xdf, 0xc3, 0x1a, 0x42, 0x8f, 0xb2, 0xf1, 0x6d, 0x34, 0x21, 0x1f, 0x60, 0xb1, 0x0e, 0xeb, 0xb3, 0x7c,
0x5c, 0xef, 0xb0, 0x66, 0x9f, 0x75, 0x7b, 0x4d, 0xdb, 0xb1, 0xd8, 0x2e, 0xf3, 0xf3, 0x19, 0xf0, 0x1a, 0x7c, 0xff, 0xff, 0x20, 0x30, 0xc6, 0x85, 0xa2, 0x0c, 0xf8, 0x41, 0x60, 0x60, 0xcd, 0xad,
0x39, 0x3f, 0x08, 0x0c, 0x2c, 0xf5, 0x6b, 0xac, 0xdb, 0xab, 0x0a, 0xed, 0x30, 0x30, 0xf2, 0xa2, 0x00, 0x09, 0x8d, 0x71, 0xb0, 0x85, 0xce, 0x59, 0xb6, 0x6f, 0xae, 0x75, 0x58, 0xb3, 0xcf, 0xba,
0xa6, 0x8e, 0xa8, 0x08, 0x3d, 0x86, 0x8f, 0xe7, 0x51, 0xa6, 0x67, 0x6e, 0xf9, 0xcc, 0xca, 0x8f, 0xbd, 0xa6, 0xed, 0x58, 0x6c, 0x87, 0xf9, 0xf9, 0x0c, 0xf8, 0x9c, 0x1b, 0x04, 0x06, 0x96, 0xfa,
0x81, 0xdf, 0xc2, 0x20, 0x30, 0x24, 0xa2, 0x02, 0x2e, 0x44, 0x42, 0x25, 0xce, 0x93, 0x47, 0x54, 0x55, 0xd6, 0xed, 0x55, 0x85, 0xf6, 0x20, 0x30, 0xf2, 0x22, 0x29, 0x8f, 0xa8, 0x08, 0x3d, 0x86,
0xa9, 0x9f, 0xcf, 0x1d, 0x4e, 0x9e, 0x32, 0x28, 0xa2, 0xe4, 0x91, 0x44, 0xe5, 0x4b, 0xc8, 0x84, 0x8f, 0xe7, 0x50, 0xa6, 0x67, 0x6e, 0xfa, 0xcc, 0xca, 0x8f, 0x80, 0xdf, 0xc2, 0x20, 0x30, 0x24,
0x86, 0x0a, 0xf2, 0x87, 0x0c, 0xca, 0x08, 0x23, 0x5c, 0x52, 0xc9, 0x33, 0x59, 0x9a, 0xe7, 0x0e, 0xa2, 0x02, 0x46, 0x88, 0x84, 0x4a, 0x9c, 0x07, 0x9f, 0x48, 0x73, 0x3f, 0x9f, 0x3b, 0x1c, 0x7c,
0xfe, 0x1c, 0x18, 0x59, 0xa1, 0xab, 0x96, 0x4f, 0x4a, 0xa6, 0x2f, 0x9e, 0x5e, 0x4b, 0x68, 0x09, 0x65, 0x50, 0x44, 0xc1, 0x27, 0x89, 0xca, 0x97, 0x90, 0x09, 0x0d, 0x15, 0xe4, 0x2f, 0x19, 0x94,
0x35, 0x87, 0x52, 0x5a, 0xb3, 0x80, 0xda, 0x73, 0x44, 0x9b, 0x10, 0xb5, 0xe7, 0x40, 0x83, 0x00, 0x11, 0x46, 0xb8, 0xa4, 0x82, 0x67, 0xbc, 0x34, 0xc7, 0x1d, 0xfc, 0x3d, 0x30, 0xb2, 0x42, 0x57,
0x0c, 0x7f, 0x80, 0xc6, 0x4d, 0xcb, 0xe2, 0x35, 0xc2, 0xfc, 0x7c, 0xb2, 0x98, 0xe4, 0x39, 0x3b, 0x2d, 0x9f, 0x14, 0x4c, 0x9f, 0x3d, 0xb9, 0x9a, 0xd0, 0x02, 0x6a, 0x16, 0xa5, 0xb4, 0x6a, 0x03,
0x08, 0x8c, 0x08, 0x1c, 0x06, 0xc6, 0x14, 0x58, 0x49, 0x84, 0xd0, 0x48, 0x87, 0xff, 0x2f, 0x5e, 0xc9, 0xeb, 0x88, 0x3a, 0x23, 0x92, 0xd7, 0x81, 0x0a, 0x03, 0x18, 0x7e, 0x0f, 0x8d, 0x9a, 0x96,
0xb9, 0xa9, 0xc3, 0x3d, 0xe0, 0x87, 0x95, 0x2c, 0xcf, 0xf4, 0x16, 0xf3, 0x64, 0xeb, 0x4b, 0x8b, 0xc5, 0x93, 0x8c, 0xf9, 0xf9, 0x64, 0x31, 0xc9, 0x63, 0x96, 0xc7, 0xbd, 0x02, 0x0f, 0x02, 0x63,
0x82, 0xe2, 0x99, 0xce, 0x41, 0xd9, 0xf8, 0x44, 0xa6, 0x87, 0x00, 0xa1, 0x4a, 0x87, 0x97, 0xd1, 0x02, 0xac, 0x24, 0x42, 0x68, 0xa4, 0xc3, 0x3f, 0x8d, 0xa7, 0x7e, 0xea, 0x70, 0x11, 0xf9, 0x7e,
0x64, 0xd7, 0xdc, 0x6d, 0xfa, 0xec, 0xff, 0xb7, 0x98, 0xd3, 0x62, 0x90, 0x33, 0x49, 0xb1, 0x8a, 0x39, 0xcf, 0x23, 0xbd, 0xc5, 0x3c, 0x59, 0x3b, 0xd3, 0x22, 0xa1, 0x78, 0xa4, 0x73, 0x50, 0x56,
0xae, 0xb9, 0xdb, 0x90, 0xb0, 0x5a, 0x85, 0x86, 0x11, 0xaa, 0x33, 0x70, 0x09, 0x21, 0xdb, 0xe9, 0x4e, 0x11, 0xe9, 0x21, 0x40, 0xa8, 0xd2, 0xe1, 0x25, 0x34, 0xde, 0x35, 0x77, 0x9a, 0x3e, 0xfb,
0x7b, 0xae, 0xb5, 0xd5, 0x62, 0x9e, 0x4c, 0x11, 0xe8, 0xc0, 0x11, 0xaa, 0x3a, 0x70, 0x04, 0x11, 0xd9, 0x26, 0x73, 0x5a, 0x0c, 0x62, 0x26, 0x29, 0x56, 0xd1, 0x35, 0x77, 0x1a, 0x12, 0x56, 0xab,
0xaa, 0xe9, 0x71, 0x1b, 0x65, 0x21, 0x77, 0x9b, 0xb6, 0x95, 0xcf, 0x16, 0x13, 0xb3, 0xa9, 0xd2, 0xd0, 0x30, 0x42, 0x75, 0x06, 0x2e, 0x21, 0x64, 0x3b, 0x7d, 0xcf, 0xb5, 0x36, 0x5b, 0xcc, 0x93,
0x8a, 0x0c, 0xee, 0x18, 0x64, 0x21, 0xc4, 0x36, 0xfc, 0xc9, 0x73, 0x06, 0xd8, 0x55, 0x4b, 0xed, 0x21, 0x02, 0x25, 0x3c, 0x42, 0x55, 0x09, 0x8f, 0x20, 0x42, 0x35, 0x3d, 0x6e, 0xa3, 0x2c, 0xc4,
0xbe, 0x94, 0x79, 0xdf, 0x08, 0x69, 0xbf, 0x88, 0x7e, 0xd2, 0x90, 0x8f, 0x7f, 0x84, 0x0a, 0xfe, 0x6e, 0xd3, 0xb6, 0xf2, 0xd9, 0x62, 0x62, 0x26, 0x55, 0x5a, 0x96, 0x87, 0x3b, 0x02, 0x51, 0x08,
0x63, 0x9b, 0x57, 0x8a, 0x78, 0x76, 0xdf, 0x76, 0x9d, 0xa6, 0xc7, 0xba, 0xee, 0xb6, 0xd9, 0xf1, 0x67, 0x1b, 0xfe, 0xe4, 0x31, 0x03, 0xec, 0xaa, 0xa5, 0x76, 0x5f, 0xca, 0xbc, 0x6e, 0x84, 0xb4,
0xf3, 0xe3, 0xb0, 0xf8, 0x5b, 0x83, 0xc0, 0xc8, 0x73, 0x56, 0x55, 0x23, 0x51, 0xc9, 0x19, 0x06, 0xdf, 0x46, 0x3f, 0x69, 0xc8, 0xc7, 0x3f, 0x47, 0x05, 0xff, 0x91, 0xcd, 0x33, 0x45, 0x3c, 0xbb,
0xc6, 0x0c, 0x3c, 0xf1, 0x24, 0x02, 0xa1, 0x27, 0xda, 0xe2, 0x5d, 0xf4, 0x12, 0x73, 0x5a, 0xde, 0x6f, 0xbb, 0x4e, 0xd3, 0x63, 0x5d, 0x77, 0xcb, 0xec, 0xf8, 0xf9, 0x51, 0x58, 0xfc, 0xcd, 0x41,
0x5e, 0x0f, 0x1e, 0xdb, 0x33, 0x7d, 0x7f, 0xc7, 0xf5, 0xac, 0x66, 0xdf, 0x7d, 0xcc, 0x9c, 0x3c, 0x60, 0xe4, 0x39, 0xab, 0xaa, 0x91, 0xa8, 0xe4, 0x1c, 0x04, 0xc6, 0xb4, 0xa8, 0x73, 0x27, 0x10,
0x82, 0xa4, 0xfe, 0x60, 0x10, 0x18, 0x97, 0x22, 0xd2, 0xaa, 0xe4, 0xac, 0x71, 0xca, 0x30, 0x30, 0x08, 0x3d, 0xd1, 0x16, 0xef, 0xa0, 0x17, 0x98, 0xd3, 0xf2, 0x76, 0x7b, 0xf0, 0xd8, 0x9e, 0xe9,
0xae, 0xc2, 0xb3, 0x4f, 0xd0, 0x13, 0x7a, 0x92, 0x25, 0xf9, 0x49, 0x02, 0xa5, 0x61, 0x33, 0x78, 0xfb, 0xdb, 0xae, 0x67, 0x35, 0xfb, 0xee, 0x23, 0xe6, 0xe4, 0x11, 0x04, 0xf5, 0x7b, 0x83, 0xc0,
0x35, 0x8b, 0xa6, 0x2c, 0x5b, 0x30, 0x54, 0xb3, 0x40, 0x8e, 0xb4, 0x6f, 0x89, 0xe3, 0x0a, 0x4a, 0xb8, 0x18, 0x91, 0x56, 0x24, 0x67, 0x95, 0x53, 0x0e, 0x02, 0xe3, 0x0a, 0x3c, 0xfb, 0x04, 0x3d,
0x6f, 0xd8, 0x1d, 0xe6, 0xe7, 0x47, 0xa1, 0x96, 0xb1, 0x36, 0x08, 0xec, 0x0e, 0xab, 0x3a, 0x1b, 0xa1, 0x27, 0x59, 0x92, 0x5f, 0x26, 0x50, 0x1a, 0x36, 0x83, 0x67, 0xb3, 0x28, 0xea, 0xb2, 0x04,
0x6e, 0xe9, 0xb2, 0xac, 0x66, 0x41, 0x54, 0xb5, 0xc4, 0x25, 0x42, 0x05, 0x48, 0xbe, 0x48, 0xa0, 0x43, 0x36, 0x0b, 0xe4, 0x48, 0xf9, 0x97, 0x38, 0xae, 0xa0, 0xf4, 0xba, 0xdd, 0x61, 0x7e, 0x7e,
0x09, 0x58, 0xc4, 0xbd, 0x9e, 0x65, 0xf6, 0xd9, 0xbf, 0x72, 0x29, 0x9f, 0x4f, 0xa1, 0x6c, 0x68, 0x18, 0x72, 0x19, 0x6b, 0x8d, 0xc4, 0xee, 0xb0, 0xaa, 0xb3, 0xee, 0x96, 0x2e, 0xc9, 0x6c, 0x16,
0xa0, 0x1a, 0x42, 0xe2, 0x14, 0x0d, 0x61, 0x0e, 0xa5, 0x7c, 0xfb, 0x53, 0x06, 0x83, 0x25, 0x29, 0x44, 0x95, 0x4b, 0x5c, 0x22, 0x54, 0x80, 0xe4, 0xb3, 0x04, 0x1a, 0x83, 0x45, 0xdc, 0xed, 0x59,
0xb8, 0x5c, 0x56, 0x5c, 0x2e, 0x10, 0x0a, 0x18, 0xfe, 0x10, 0xa1, 0xae, 0x6b, 0xd9, 0x1b, 0x36, 0x66, 0x9f, 0xfd, 0x2f, 0x97, 0xf2, 0xe9, 0x04, 0xca, 0x86, 0x06, 0xaa, 0x20, 0x24, 0x4e, 0x51,
0xb3, 0x9a, 0x3e, 0x14, 0x68, 0xb2, 0x54, 0xe4, 0xdd, 0x23, 0x44, 0x1b, 0xc3, 0xc0, 0x38, 0x2b, 0x10, 0x66, 0x51, 0xca, 0xb7, 0x3f, 0x66, 0xd0, 0x58, 0x92, 0x82, 0xcb, 0x65, 0xc5, 0xe5, 0x02,
0xca, 0x2b, 0x44, 0x08, 0x8d, 0xb4, 0xbc, 0x7f, 0x28, 0x07, 0xeb, 0x7b, 0xf9, 0x49, 0xa8, 0x8c, 0xa1, 0x80, 0xe1, 0xf7, 0x11, 0xea, 0xba, 0x96, 0xbd, 0x6e, 0x33, 0xab, 0xe9, 0x43, 0x82, 0x26,
0x0f, 0xc2, 0xca, 0x68, 0x6c, 0xba, 0x5e, 0x1f, 0xca, 0x41, 0x3d, 0xa6, 0xb4, 0xa7, 0x4a, 0x2d, 0x45, 0xd7, 0x0c, 0xd1, 0x86, 0xea, 0x9a, 0x0a, 0x21, 0x34, 0xd2, 0xf2, 0xfa, 0xa1, 0x1c, 0xac,
0x82, 0x08, 0xaf, 0x04, 0x49, 0xa6, 0x1a, 0x15, 0xaf, 0xa0, 0xb1, 0xf0, 0xc0, 0xc3, 0x33, 0x3f, 0xed, 0xe6, 0xc7, 0x21, 0x33, 0xde, 0x0b, 0x33, 0xa3, 0xb1, 0xe1, 0x7a, 0x7d, 0x48, 0x07, 0xf5,
0xd6, 0xa4, 0xef, 0xb3, 0x56, 0xdf, 0xf5, 0x4a, 0xc5, 0xb0, 0x49, 0x6f, 0xab, 0x03, 0x90, 0x28, 0x98, 0xd2, 0xae, 0x4a, 0xb5, 0x08, 0x22, 0x3c, 0x13, 0x24, 0x99, 0x6a, 0x54, 0xbc, 0x8c, 0x46,
0xb8, 0xed, 0xf0, 0xe8, 0x13, 0x6a, 0xf0, 0xfb, 0x28, 0xab, 0x9a, 0x09, 0x82, 0x77, 0x85, 0x66, 0xc2, 0x89, 0x89, 0x47, 0x7e, 0xac, 0x48, 0xdf, 0x63, 0xad, 0xbe, 0xeb, 0x95, 0x8a, 0x61, 0x91,
0xe4, 0x47, 0x9d, 0x44, 0x34, 0x23, 0x5f, 0xb5, 0x11, 0xa5, 0xc3, 0x1f, 0xa1, 0xcc, 0x7a, 0xc7, 0xde, 0x52, 0x13, 0x94, 0x48, 0xb8, 0xad, 0x70, 0x76, 0x0a, 0x35, 0xf8, 0x5d, 0x94, 0x55, 0xc5,
0x6d, 0x3d, 0x0e, 0xa7, 0xc5, 0xb9, 0x68, 0x21, 0x25, 0x8e, 0x43, 0x5c, 0xaf, 0xca, 0xb5, 0x48, 0x04, 0xc1, 0xbb, 0x42, 0x31, 0xf2, 0xa3, 0x4a, 0x32, 0x29, 0x07, 0x84, 0xb0, 0x8c, 0x28, 0x1d,
0xaa, 0x1a, 0xff, 0x20, 0x12, 0x2a, 0x61, 0x7e, 0x9a, 0xf3, 0xf7, 0xba, 0x1d, 0xdb, 0x79, 0xdc, 0xfe, 0x00, 0x65, 0xd6, 0x3a, 0x6e, 0xeb, 0x51, 0xd8, 0x2d, 0xce, 0x46, 0x0b, 0x29, 0x71, 0x1c,
0xec, 0x9b, 0x5e, 0x9b, 0xf5, 0xf3, 0xd3, 0xd1, 0x69, 0x4e, 0x6a, 0xd6, 0x40, 0xa1, 0x4e, 0x73, 0xce, 0xf5, 0x8a, 0x5c, 0x8b, 0xa4, 0xaa, 0xf6, 0x0f, 0x22, 0xa1, 0x12, 0xe6, 0xe3, 0xa0, 0xbf,
0x31, 0x94, 0xd0, 0x38, 0x8b, 0x9f, 0x31, 0x85, 0xeb, 0xe6, 0xa6, 0xe9, 0x6f, 0xe6, 0x31, 0xd4, 0xdb, 0xed, 0xd8, 0xce, 0xa3, 0x66, 0xdf, 0xf4, 0xda, 0xac, 0x9f, 0x9f, 0x8a, 0xc6, 0x41, 0xa9,
0x29, 0x74, 0x38, 0x01, 0xdf, 0x36, 0xfd, 0x4d, 0xb5, 0xed, 0x11, 0x44, 0xa8, 0xa6, 0xc7, 0xb7, 0x59, 0x05, 0x85, 0x1a, 0x07, 0x63, 0x28, 0xa1, 0x71, 0x16, 0x1f, 0x52, 0x85, 0xeb, 0xe6, 0x86,
0xd0, 0xb8, 0xac, 0x4d, 0x66, 0xe5, 0xcf, 0x81, 0x0b, 0x48, 0x05, 0x05, 0xaa, 0x54, 0x50, 0x08, 0xe9, 0x6f, 0xe4, 0x31, 0xe4, 0x29, 0x54, 0x38, 0x01, 0xdf, 0x32, 0xfd, 0x0d, 0xb5, 0xed, 0x11,
0xa1, 0x91, 0x16, 0x97, 0xe4, 0x39, 0x52, 0x9c, 0xfe, 0x2e, 0x1e, 0x4d, 0xfb, 0x53, 0x1c, 0x24, 0x44, 0xa8, 0xa6, 0xe7, 0x03, 0x94, 0xcc, 0x4d, 0x66, 0xe5, 0xcf, 0x82, 0x0b, 0x08, 0x05, 0x05,
0x97, 0xd0, 0xc4, 0xe1, 0x53, 0xcd, 0x94, 0xe8, 0xf8, 0xbd, 0xd8, 0x79, 0x46, 0x74, 0xfc, 0x9e, 0xaa, 0x50, 0x50, 0x08, 0xa1, 0x91, 0x16, 0x97, 0xe4, 0x20, 0x2a, 0xc6, 0xc7, 0x0b, 0x47, 0xc3,
0x7e, 0x92, 0xd1, 0x19, 0xf8, 0x23, 0x2d, 0x2d, 0x1d, 0x3f, 0x3f, 0x51, 0x4c, 0xcc, 0xa6, 0x4b, 0xfe, 0x14, 0x93, 0xe8, 0x22, 0x1a, 0x3b, 0x3c, 0xd5, 0x4c, 0x88, 0x8a, 0xdf, 0x8b, 0xcd, 0x33,
0xaf, 0xea, 0x79, 0x58, 0xf3, 0x8f, 0xe4, 0x61, 0xcd, 0x27, 0x7f, 0x0f, 0x8c, 0xa4, 0xed, 0xf4, 0xa2, 0xe2, 0xf7, 0xf4, 0x49, 0x46, 0x67, 0xe0, 0x0f, 0xb4, 0xb0, 0x74, 0xfc, 0xfc, 0x58, 0x31,
0xa9, 0x46, 0xc3, 0x1b, 0x48, 0xec, 0x52, 0x13, 0xaa, 0x6a, 0x0a, 0x5c, 0x2d, 0x3f, 0x0b, 0x8c, 0x31, 0x93, 0x2e, 0xbd, 0xac, 0xc7, 0x61, 0xcd, 0x3f, 0x12, 0x87, 0x35, 0x9f, 0xfc, 0x3b, 0x30,
0x49, 0x6a, 0xee, 0x40, 0xe8, 0x1b, 0xf6, 0xa7, 0x8c, 0x6f, 0xd4, 0x7a, 0x28, 0xa8, 0x8d, 0x52, 0x92, 0xb6, 0xd3, 0xa7, 0x1a, 0x0d, 0xaf, 0x23, 0xb1, 0x4b, 0x4d, 0xc8, 0xaa, 0x09, 0x70, 0xb5,
0x48, 0xe8, 0xf8, 0xcb, 0xa7, 0xd7, 0x62, 0x66, 0x34, 0x32, 0xc2, 0xf7, 0x51, 0xb6, 0xd7, 0x31, 0xf4, 0x34, 0x30, 0xc6, 0xa9, 0xb9, 0x0d, 0x47, 0xdf, 0xb0, 0x3f, 0x66, 0x7c, 0xa3, 0xd6, 0x42,
0xfb, 0x1b, 0xae, 0xd7, 0xcd, 0x9f, 0x81, 0x64, 0xd7, 0xf6, 0x70, 0x55, 0x6a, 0xca, 0x66, 0xdf, 0x41, 0x6d, 0x94, 0x42, 0x42, 0xc7, 0x9f, 0x3f, 0xb9, 0x1a, 0x33, 0xa3, 0x91, 0x11, 0xbe, 0x87,
0x2c, 0x11, 0x99, 0x66, 0x8a, 0xaf, 0x32, 0x37, 0x04, 0x08, 0x55, 0x3a, 0x5c, 0x46, 0x13, 0x1d, 0xb2, 0xbd, 0x8e, 0xd9, 0x5f, 0x77, 0xbd, 0x6e, 0x7e, 0x12, 0x82, 0x5d, 0xdb, 0xc3, 0x15, 0xa9,
0xb7, 0x65, 0x76, 0x9a, 0x1b, 0x1d, 0xb3, 0xed, 0xe7, 0xff, 0x32, 0x06, 0x9b, 0x0a, 0xd9, 0x01, 0x29, 0x9b, 0x7d, 0xb3, 0x44, 0x64, 0x98, 0x29, 0xbe, 0x8a, 0xdc, 0x10, 0x20, 0x54, 0xe9, 0x70,
0xf8, 0x12, 0x87, 0xd5, 0x66, 0x44, 0x10, 0xa1, 0x9a, 0x1e, 0xdf, 0x46, 0x93, 0xb2, 0x8c, 0x44, 0x19, 0x8d, 0x75, 0xdc, 0x96, 0xd9, 0x69, 0xae, 0x77, 0xcc, 0xb6, 0x9f, 0xff, 0xc7, 0x08, 0x6c,
0x8e, 0xfd, 0x75, 0x0c, 0x32, 0x04, 0x62, 0x23, 0x15, 0x32, 0xcb, 0xa6, 0xf5, 0xea, 0x13, 0x69, 0x2a, 0x44, 0x07, 0xe0, 0x8b, 0x1c, 0x56, 0x9b, 0x11, 0x41, 0x84, 0x6a, 0x7a, 0x7c, 0x0b, 0x8d,
0xa6, 0x33, 0xf0, 0xc7, 0xe8, 0xac, 0xed, 0xb8, 0x16, 0x6b, 0xb6, 0x36, 0x4d, 0xa7, 0xcd, 0x78, 0xcb, 0x34, 0x12, 0x31, 0xf6, 0xcf, 0x11, 0x88, 0x10, 0x38, 0x1b, 0xa9, 0x90, 0x51, 0x36, 0xa5,
0x7c, 0x06, 0x63, 0x50, 0x8d, 0x90, 0xff, 0xa0, 0x5b, 0x04, 0x15, 0xc4, 0xe8, 0x9c, 0x9c, 0x9e, 0x67, 0x9f, 0x08, 0x33, 0x9d, 0x81, 0x3f, 0x44, 0x67, 0x6c, 0xc7, 0xb5, 0x58, 0xb3, 0xb5, 0x61,
0x1a, 0x4a, 0x68, 0x9c, 0x85, 0x77, 0x91, 0x36, 0x56, 0x9a, 0x7d, 0xcf, 0xb4, 0x3b, 0xcc, 0x13, 0x3a, 0x6d, 0xc6, 0xcf, 0x67, 0x30, 0x02, 0xd9, 0x08, 0xf1, 0x0f, 0xba, 0x05, 0x50, 0xc1, 0x19,
0xf1, 0xfa, 0xdb, 0x18, 0x04, 0xec, 0xc3, 0x41, 0x60, 0x5c, 0x88, 0x38, 0x6b, 0x82, 0x22, 0x83, 0x9d, 0x95, 0xdd, 0x53, 0x43, 0x09, 0x8d, 0xb3, 0xf0, 0x0e, 0xd2, 0xda, 0x4a, 0xb3, 0xef, 0x99,
0x75, 0xf9, 0xd0, 0xc8, 0xd2, 0xb4, 0x2a, 0x23, 0x8e, 0x37, 0xc6, 0xef, 0xf2, 0x53, 0x24, 0x3f, 0x76, 0x87, 0x79, 0xe2, 0xbc, 0xfe, 0x35, 0x02, 0x07, 0xf6, 0xfe, 0x20, 0x30, 0xce, 0x47, 0x9c,
0xe9, 0x5a, 0xf2, 0x48, 0x7b, 0x45, 0x9c, 0x17, 0x01, 0x52, 0xad, 0x48, 0xca, 0x70, 0x60, 0x84, 0x55, 0x41, 0x91, 0x87, 0x75, 0xe9, 0x50, 0xcb, 0xd2, 0xb4, 0x2a, 0x22, 0x8e, 0x37, 0xc6, 0x6f,
0x5f, 0x98, 0xa2, 0x31, 0xdb, 0xd9, 0x36, 0x3b, 0x76, 0x78, 0x64, 0x7d, 0xef, 0x59, 0x60, 0x20, 0xf3, 0x29, 0x92, 0x4f, 0xba, 0x96, 0x1c, 0x69, 0x2f, 0x8b, 0x79, 0x11, 0x20, 0x55, 0x8a, 0xa4,
0x6a, 0xee, 0x54, 0x05, 0x2a, 0x4e, 0x10, 0xf0, 0x53, 0x3b, 0x41, 0x80, 0xcc, 0x4f, 0x10, 0x1a, 0x0c, 0x03, 0x23, 0xfc, 0xc2, 0x14, 0x8d, 0xd8, 0xce, 0x96, 0xd9, 0xb1, 0xc3, 0x91, 0xf5, 0x9d,
0x93, 0x86, 0x3c, 0xde, 0x56, 0x1c, 0x37, 0x76, 0x2b, 0xc8, 0x82, 0x6b, 0xd8, 0x56, 0xc7, 0x8d, 0xa7, 0x81, 0x81, 0xa8, 0xb9, 0x5d, 0x15, 0xa8, 0x98, 0x20, 0xe0, 0xa7, 0x36, 0x41, 0x80, 0xcc,
0xdf, 0x08, 0xc4, 0xb6, 0xc6, 0x50, 0x42, 0xe3, 0xac, 0xf7, 0x53, 0x3f, 0xff, 0xca, 0x18, 0x21, 0x27, 0x08, 0x8d, 0x49, 0x43, 0x1e, 0x2f, 0x2b, 0x8e, 0x1b, 0xbb, 0x15, 0x64, 0xc1, 0x35, 0x6c,
0xdf, 0x26, 0xd0, 0xb8, 0x6a, 0x71, 0x7c, 0xba, 0x40, 0xfc, 0x93, 0x10, 0x7e, 0xa8, 0xe6, 0x4d, 0xab, 0xe3, 0xc6, 0x6f, 0x04, 0x62, 0x5b, 0x63, 0x28, 0xa1, 0x71, 0xd6, 0xbb, 0xa9, 0xdf, 0x7c,
0x11, 0x77, 0x51, 0xcd, 0x9b, 0x10, 0x70, 0xc0, 0xf8, 0xf4, 0x74, 0x37, 0x36, 0x7c, 0xd6, 0x87, 0x69, 0x0c, 0x91, 0x6f, 0x12, 0x68, 0x54, 0x95, 0x38, 0xde, 0x5d, 0xe0, 0xfc, 0x93, 0x70, 0xfc,
0xb9, 0x95, 0x14, 0xd3, 0x53, 0x20, 0x6a, 0x7a, 0x0a, 0x91, 0x50, 0x89, 0xe3, 0x37, 0xe5, 0xf4, 0x90, 0xcd, 0x1b, 0xe2, 0xdc, 0x45, 0x36, 0x6f, 0xc0, 0x81, 0x03, 0xc6, 0xbb, 0xa7, 0xbb, 0xbe,
0x1a, 0x85, 0xb0, 0x5d, 0x3d, 0x7e, 0x7a, 0x85, 0x41, 0x11, 0x43, 0xec, 0x26, 0x1a, 0xdf, 0x61, 0xee, 0xb3, 0x3e, 0xf4, 0xad, 0xa4, 0xe8, 0x9e, 0x02, 0x51, 0xdd, 0x53, 0x88, 0x84, 0x4a, 0x1c,
0xe6, 0x63, 0x91, 0x97, 0xa2, 0x65, 0x40, 0x5f, 0xe7, 0xa0, 0xcc, 0x49, 0x51, 0x1d, 0x21, 0x40, 0xbf, 0x2e, 0xbb, 0xd7, 0x30, 0x1c, 0xdb, 0x95, 0xe3, 0xbb, 0x57, 0x78, 0x28, 0xa2, 0x89, 0xdd,
0xa8, 0xd2, 0xc9, 0x77, 0x7c, 0x84, 0x32, 0x62, 0x9c, 0xe0, 0x55, 0x94, 0x6d, 0xb9, 0x5b, 0x4e, 0x40, 0xa3, 0xdb, 0xcc, 0x7c, 0x24, 0xe2, 0x52, 0x94, 0x0c, 0xa8, 0xeb, 0x1c, 0x94, 0x31, 0x29,
0x3f, 0xba, 0x54, 0x4e, 0xeb, 0xa7, 0x61, 0xd0, 0x94, 0xfe, 0x2d, 0x2c, 0xc0, 0x90, 0xaa, 0x62, 0xb2, 0x23, 0x04, 0x08, 0x55, 0x3a, 0xf9, 0x8e, 0x0f, 0x51, 0x46, 0xb4, 0x13, 0xbc, 0x82, 0xb2,
0x24, 0x01, 0x7e, 0x8c, 0x95, 0x2a, 0xf2, 0x59, 0x02, 0x8d, 0x49, 0x43, 0x7c, 0x5b, 0x5d, 0x0e, 0x2d, 0x77, 0xd3, 0xe9, 0x47, 0x97, 0xd2, 0x29, 0x7d, 0x1a, 0x06, 0x4d, 0xe9, 0xff, 0xc2, 0x04,
0x52, 0xa5, 0xf7, 0x0e, 0x4d, 0xc9, 0xef, 0xbe, 0x68, 0xea, 0x13, 0x52, 0xde, 0x39, 0xb7, 0xcd, 0x0c, 0xa9, 0xea, 0x8c, 0x24, 0xc0, 0xc7, 0x58, 0xa9, 0x22, 0x9f, 0x24, 0xd0, 0x88, 0x34, 0xc4,
0xce, 0x96, 0xd8, 0xa8, 0x94, 0xb8, 0x73, 0x02, 0xa0, 0x86, 0x0e, 0x48, 0x84, 0x0a, 0x94, 0x7c, 0xb7, 0xd4, 0xe5, 0x20, 0x55, 0x7a, 0xe7, 0x50, 0x97, 0xfc, 0xf6, 0x8b, 0xa6, 0xde, 0x21, 0xe5,
0x96, 0x42, 0x93, 0x7a, 0x13, 0xe1, 0xed, 0x7a, 0xcb, 0xb1, 0x77, 0x61, 0x31, 0xb1, 0x53, 0xca, 0x9d, 0x73, 0xcb, 0xec, 0x6c, 0x8a, 0x8d, 0x4a, 0x89, 0x3b, 0x27, 0x00, 0xaa, 0xe9, 0x80, 0x44,
0x3d, 0xc7, 0xde, 0x85, 0x36, 0x53, 0x78, 0x12, 0x18, 0x09, 0x1e, 0x00, 0xce, 0x53, 0x01, 0xe0, 0xa8, 0x40, 0xc9, 0x27, 0x29, 0x34, 0xae, 0x17, 0x11, 0x5e, 0xae, 0x37, 0x1d, 0x7b, 0x07, 0x16,
0x02, 0xa1, 0x80, 0xe1, 0x8f, 0xd1, 0xd8, 0x8e, 0xed, 0x58, 0xee, 0x8e, 0x0f, 0xcb, 0x98, 0xd0, 0x13, 0x9b, 0x52, 0xee, 0x3a, 0xf6, 0x0e, 0x94, 0x99, 0xc2, 0xe3, 0xc0, 0x48, 0xf0, 0x03, 0xe0,
0x6f, 0x0e, 0x0f, 0x84, 0x02, 0x3c, 0x15, 0xa5, 0xa7, 0x90, 0xad, 0xb6, 0x4b, 0xca, 0x84, 0x86, 0x3c, 0x75, 0x00, 0x5c, 0x20, 0x14, 0x30, 0xfc, 0x21, 0x1a, 0xd9, 0xb6, 0x1d, 0xcb, 0xdd, 0xf6,
0x1a, 0xbc, 0x8c, 0xd2, 0x1d, 0xdb, 0xd9, 0xda, 0x85, 0x04, 0x8b, 0x8d, 0xd9, 0x4f, 0xcc, 0x7e, 0x61, 0x19, 0x63, 0xfa, 0xcd, 0xe1, 0xbe, 0x50, 0x80, 0xa7, 0xa2, 0xf4, 0x14, 0xb2, 0xd5, 0x76,
0xdf, 0x03, 0x77, 0x57, 0xa4, 0x3b, 0xc1, 0x8c, 0x2e, 0xd9, 0x5c, 0xe2, 0x97, 0x6c, 0xfe, 0x2f, 0x49, 0x99, 0xd0, 0x50, 0x83, 0x97, 0x50, 0xba, 0x63, 0x3b, 0x9b, 0x3b, 0x10, 0x60, 0xb1, 0x36,
0xbe, 0x83, 0x32, 0x96, 0xe9, 0xed, 0xd8, 0xe2, 0x52, 0x73, 0x82, 0xa7, 0x19, 0xe9, 0x49, 0x52, 0xfb, 0x91, 0xd9, 0xef, 0x7b, 0xe0, 0xee, 0xb2, 0x74, 0x27, 0x98, 0xd1, 0x25, 0x9b, 0x4b, 0xfc,
0xa3, 0x0b, 0x1e, 0x88, 0x84, 0x4a, 0x1c, 0x33, 0x34, 0xb6, 0xe1, 0x31, 0xb6, 0xee, 0x5b, 0x70, 0x92, 0xcd, 0xff, 0xc5, 0xb7, 0x51, 0xc6, 0x32, 0xbd, 0x6d, 0x5b, 0x5c, 0x6a, 0x4e, 0xf0, 0x34,
0x48, 0x3a, 0xc1, 0xdb, 0xbb, 0xdc, 0x1b, 0xbf, 0x06, 0x2c, 0x79, 0x8c, 0x95, 0x1a, 0x70, 0x0d, 0x2d, 0x3d, 0x49, 0x6a, 0x74, 0xc1, 0x03, 0x91, 0x50, 0x89, 0x63, 0x86, 0x46, 0xd6, 0x3d, 0xc6,
0x90, 0x66, 0xea, 0x8d, 0xa5, 0x0c, 0xd7, 0x00, 0x49, 0xa3, 0x21, 0x09, 0x37, 0x51, 0xc6, 0x61, 0xd6, 0x7c, 0x0b, 0x86, 0xa4, 0x13, 0xbc, 0xbd, 0xcd, 0xbd, 0xf1, 0x6b, 0xc0, 0xa2, 0xc7, 0x58,
0x7d, 0xfe, 0x94, 0xcc, 0xc9, 0x4f, 0x99, 0x97, 0x4f, 0xc9, 0xd4, 0x58, 0x5f, 0x3c, 0x44, 0x1a, 0xa9, 0x01, 0xd7, 0x00, 0x69, 0xa6, 0xde, 0x58, 0xca, 0x70, 0x0d, 0x90, 0x34, 0x1a, 0x92, 0x70,
0xa9, 0xd5, 0x0b, 0x91, 0x3f, 0x42, 0x72, 0xa8, 0x64, 0x90, 0xcf, 0x47, 0x51, 0x36, 0x8c, 0x2f, 0x13, 0x65, 0x1c, 0xd6, 0xe7, 0x4f, 0xc9, 0x9c, 0xfc, 0x94, 0x39, 0xf9, 0x94, 0x4c, 0x8d, 0xf5,
0x3f, 0xfc, 0xb9, 0x3b, 0x0e, 0xf3, 0xf4, 0xaf, 0x5b, 0x30, 0xf1, 0x01, 0x95, 0xd7, 0x33, 0x31, 0xc5, 0x43, 0xa4, 0x91, 0x5a, 0xbd, 0x10, 0xf9, 0x23, 0x24, 0x87, 0x4a, 0x06, 0xf9, 0x74, 0x18,
0xc8, 0x14, 0x42, 0x68, 0xa4, 0xe5, 0x0e, 0xda, 0x9e, 0xbb, 0xd5, 0xd3, 0xbf, 0x6c, 0x81, 0x03, 0x65, 0xc3, 0xf3, 0xe5, 0xc3, 0x9f, 0xbb, 0xed, 0x30, 0x4f, 0xff, 0x3c, 0x06, 0x1d, 0x1f, 0x50,
0x40, 0x63, 0x0e, 0x14, 0x42, 0x68, 0xa4, 0xc5, 0x37, 0x51, 0x72, 0xcb, 0xb6, 0x20, 0xd4, 0xe9, 0x79, 0x3d, 0x13, 0x8d, 0x4c, 0x21, 0x84, 0x46, 0x5a, 0xee, 0xa0, 0xed, 0xb9, 0x9b, 0x3d, 0xfd,
0xd2, 0xab, 0xcf, 0x02, 0x23, 0x79, 0x0f, 0x2a, 0x80, 0xa3, 0xc3, 0xc0, 0x18, 0x17, 0x09, 0x67, 0xd3, 0x18, 0x38, 0x00, 0x34, 0xe6, 0x40, 0x21, 0x84, 0x46, 0x5a, 0x7c, 0x03, 0x25, 0x37, 0x6d,
0x5b, 0xda, 0xf8, 0xe4, 0x0c, 0xca, 0xf5, 0xdc, 0xb8, 0x6d, 0x5b, 0x10, 0x5d, 0x69, 0xbc, 0x2c, 0x0b, 0x8e, 0x3a, 0x5d, 0x7a, 0xf9, 0x69, 0x60, 0x24, 0xef, 0x42, 0x06, 0x70, 0xf4, 0x20, 0x30,
0x8c, 0xdb, 0x9a, 0x71, 0x3b, 0x6e, 0xbc, 0xcc, 0x8d, 0x39, 0xf6, 0xcb, 0x04, 0x9a, 0xd0, 0x32, 0x46, 0x45, 0xc0, 0xd9, 0x96, 0xd6, 0x3e, 0x39, 0x83, 0x72, 0x3d, 0x37, 0x6e, 0xdb, 0x16, 0x9c,
0xf4, 0x87, 0xef, 0xc5, 0x0a, 0x3a, 0x23, 0x1c, 0xd8, 0x7e, 0x13, 0x5e, 0x10, 0xf6, 0x43, 0x7e, 0xae, 0x34, 0x5e, 0x12, 0xc6, 0x6d, 0xcd, 0xb8, 0x1d, 0x37, 0x5e, 0xe2, 0xc6, 0x1c, 0xfb, 0x22,
0x36, 0x01, 0x4d, 0xd5, 0x5f, 0xe6, 0xb8, 0xfa, 0x6c, 0xa2, 0x83, 0x84, 0xc6, 0x38, 0xa4, 0x81, 0x81, 0xc6, 0xb4, 0x08, 0xfd, 0xfe, 0x7b, 0xb1, 0x8c, 0x26, 0x85, 0x03, 0xdb, 0x6f, 0xc2, 0x0b,
0xc6, 0x55, 0xc0, 0xf1, 0x12, 0xca, 0xec, 0x72, 0x21, 0x6c, 0x48, 0x67, 0x0f, 0x65, 0x45, 0x74, 0xca, 0x6f, 0x50, 0xf0, 0xd9, 0x04, 0x34, 0x55, 0x7f, 0x89, 0xe3, 0xea, 0xb3, 0x89, 0x0e, 0x12,
0xec, 0x14, 0x34, 0x55, 0x10, 0x20, 0x12, 0x2a, 0x61, 0xd2, 0x42, 0x69, 0xe0, 0xbf, 0xd0, 0x6d, 0x1a, 0xe3, 0x90, 0x06, 0x1a, 0x55, 0x07, 0x8e, 0x17, 0x51, 0x66, 0x87, 0x0b, 0x61, 0x41, 0x3a,
0x22, 0xd6, 0x67, 0x26, 0xff, 0x79, 0x9f, 0xf9, 0x71, 0x0a, 0x8d, 0x51, 0x7e, 0x68, 0xf6, 0xfb, 0x73, 0x28, 0x2a, 0xa2, 0xb1, 0x53, 0xd0, 0x54, 0x42, 0x80, 0x48, 0xa8, 0x84, 0x49, 0x0b, 0xa5,
0xf8, 0x1d, 0xd5, 0xed, 0xd2, 0xa5, 0x57, 0x4e, 0x6a, 0x6f, 0x51, 0x74, 0xc2, 0xaf, 0x1f, 0xd1, 0x81, 0xff, 0x5c, 0xb7, 0x89, 0x58, 0x9d, 0x19, 0xff, 0xef, 0x75, 0xe6, 0x17, 0x29, 0x34, 0x42,
0xa5, 0x6b, 0xf4, 0xd4, 0x97, 0xae, 0xf0, 0x95, 0x92, 0xa7, 0x78, 0xa5, 0x68, 0x2c, 0xa5, 0x5e, 0xf9, 0xd0, 0xec, 0xf7, 0xf1, 0x5b, 0xaa, 0xda, 0xa5, 0x4b, 0x2f, 0x9d, 0x54, 0xde, 0xa2, 0xd3,
0x78, 0x2c, 0xa5, 0x4f, 0x3f, 0x96, 0xc2, 0x49, 0x99, 0x39, 0xc5, 0xa4, 0xac, 0xa3, 0x33, 0x1b, 0x09, 0xbf, 0x7e, 0x44, 0x97, 0xae, 0xe1, 0x53, 0x5f, 0xba, 0xc2, 0x57, 0x4a, 0x9e, 0xe2, 0x95,
0x9e, 0xdb, 0x85, 0x6f, 0x64, 0xae, 0x67, 0x7a, 0x7b, 0xf2, 0x54, 0x00, 0xa3, 0x9b, 0x6b, 0xd6, 0xa2, 0xb6, 0x94, 0x7a, 0xee, 0xb6, 0x94, 0x3e, 0x7d, 0x5b, 0x0a, 0x3b, 0x65, 0xe6, 0x14, 0x9d,
0x42, 0x85, 0x1a, 0xdd, 0x31, 0x94, 0xd0, 0x38, 0x2b, 0x3e, 0x13, 0xb3, 0x2f, 0x36, 0x13, 0xf1, 0xb2, 0x8e, 0x26, 0xd7, 0x3d, 0xb7, 0x0b, 0xdf, 0xc8, 0x5c, 0xcf, 0xf4, 0x76, 0xe5, 0x54, 0x00,
0x2d, 0x94, 0x15, 0x27, 0x5e, 0xc7, 0x85, 0x6b, 0x57, 0xba, 0xf4, 0x32, 0x6f, 0x65, 0x80, 0xd5, 0xad, 0x9b, 0x6b, 0x56, 0x43, 0x85, 0x6a, 0xdd, 0x31, 0x94, 0xd0, 0x38, 0x2b, 0xde, 0x13, 0xb3,
0x5c, 0xd5, 0xca, 0xa4, 0xac, 0x5e, 0x3b, 0x24, 0x90, 0xdf, 0x26, 0x50, 0x96, 0x32, 0xbf, 0xe7, 0xcf, 0xd7, 0x13, 0xf1, 0x4d, 0x94, 0x15, 0x13, 0xaf, 0xe3, 0xc2, 0xb5, 0x2b, 0x5d, 0x7a, 0x91,
0x3a, 0x3e, 0xfb, 0xbe, 0x49, 0x30, 0x87, 0x52, 0x96, 0xd9, 0x37, 0x65, 0xda, 0xc1, 0xee, 0x71, 0x97, 0x32, 0xc0, 0x6a, 0xae, 0x2a, 0x65, 0x52, 0x56, 0xaf, 0x1d, 0x12, 0xc8, 0x1f, 0x13, 0x28,
0x59, 0xed, 0x1e, 0x17, 0x08, 0x05, 0x0c, 0x7f, 0x88, 0x52, 0x2d, 0xd7, 0x12, 0xc1, 0x3f, 0xa3, 0x4b, 0x99, 0xdf, 0x73, 0x1d, 0x9f, 0x7d, 0xd7, 0x20, 0x98, 0x45, 0x29, 0xcb, 0xec, 0x9b, 0x32,
0x37, 0xcd, 0x8a, 0xe7, 0xb9, 0xde, 0xa2, 0x6b, 0xc9, 0x6b, 0x07, 0x27, 0x29, 0x07, 0x5c, 0x20, 0xec, 0x60, 0xf7, 0xb8, 0xac, 0x76, 0x8f, 0x0b, 0x84, 0x02, 0x86, 0xdf, 0x47, 0xa9, 0x96, 0x6b,
0x14, 0x30, 0xf2, 0x9b, 0x04, 0xca, 0x95, 0xdd, 0x1d, 0xa7, 0xe3, 0x9a, 0xd6, 0xaa, 0xe7, 0xb6, 0x89, 0xc3, 0x9f, 0xd4, 0x8b, 0x66, 0xc5, 0xf3, 0x5c, 0x6f, 0xc1, 0xb5, 0xe4, 0xb5, 0x83, 0x93,
0x3d, 0xe6, 0xfb, 0xdf, 0xeb, 0xee, 0xdf, 0x44, 0x63, 0x5b, 0xf0, 0xe5, 0x20, 0xbc, 0xfd, 0x5f, 0x94, 0x03, 0x2e, 0x10, 0x0a, 0x18, 0xf9, 0x43, 0x02, 0xe5, 0xca, 0xee, 0xb6, 0xd3, 0x71, 0x4d,
0x8b, 0x5f, 0x83, 0x0e, 0x3f, 0x44, 0x7c, 0x66, 0x88, 0x3e, 0x34, 0x4a, 0x63, 0xe5, 0x5f, 0xc8, 0x6b, 0xc5, 0x73, 0xdb, 0x1e, 0xf3, 0xfd, 0xef, 0x74, 0xf7, 0x6f, 0xa2, 0x91, 0x4d, 0xf8, 0x72,
0x84, 0x86, 0x0a, 0xf2, 0xeb, 0x24, 0x2a, 0x9c, 0xec, 0x08, 0x77, 0xd1, 0x84, 0x60, 0x36, 0xb5, 0x10, 0xde, 0xfe, 0xaf, 0xc6, 0xaf, 0x41, 0x87, 0x1f, 0x22, 0x3e, 0x33, 0x44, 0x1f, 0x1a, 0xa5,
0x4f, 0xfa, 0xb3, 0xa7, 0x59, 0x03, 0x5c, 0xce, 0xe0, 0x52, 0xb0, 0xa5, 0x64, 0x75, 0x29, 0x88, 0xb1, 0xf2, 0x2f, 0x64, 0x42, 0x43, 0x05, 0xf9, 0x7d, 0x12, 0x15, 0x4e, 0x76, 0x84, 0xbb, 0x68,
0x20, 0x42, 0x35, 0xfd, 0x0b, 0x7d, 0xa7, 0xd4, 0xae, 0xf2, 0xc9, 0x1f, 0x7e, 0x95, 0x6f, 0xa0, 0x4c, 0x30, 0x9b, 0xda, 0xdf, 0x04, 0x66, 0x4e, 0xb3, 0x06, 0xb8, 0x9c, 0xc1, 0xa5, 0x60, 0x53,
0x29, 0x91, 0xa2, 0xe1, 0x07, 0xe5, 0x54, 0x31, 0x39, 0x9b, 0x2e, 0x5d, 0xe7, 0xdd, 0x76, 0x5d, 0xc9, 0xea, 0x52, 0x10, 0x41, 0x84, 0x6a, 0xfa, 0xe7, 0xfa, 0x4e, 0xa9, 0x5d, 0xe5, 0x93, 0xdf,
0x1c, 0x56, 0xc3, 0x4f, 0xc9, 0xd3, 0x51, 0xb2, 0x0a, 0x30, 0xcc, 0xb6, 0xdc, 0x08, 0x8d, 0x71, 0xff, 0x2a, 0xdf, 0x40, 0x13, 0x22, 0x44, 0xc3, 0x0f, 0xca, 0xa9, 0x62, 0x72, 0x26, 0x5d, 0xba,
0xf1, 0x52, 0xec, 0xa6, 0x27, 0x4a, 0xfd, 0x3f, 0x4e, 0x79, 0xb3, 0xd3, 0x6e, 0x72, 0x24, 0x83, 0xc6, 0xab, 0xed, 0x9a, 0x18, 0x56, 0xc3, 0x4f, 0xc9, 0x53, 0x51, 0xb0, 0x0a, 0x30, 0x8c, 0xb6,
0x52, 0xab, 0xb6, 0xd3, 0x26, 0x37, 0x51, 0x7a, 0xb1, 0xe3, 0xfa, 0xd0, 0x71, 0x3c, 0x66, 0xfa, 0xdc, 0x10, 0x8d, 0x71, 0xf1, 0x62, 0xec, 0xa6, 0x27, 0x52, 0xfd, 0x07, 0xa7, 0xbc, 0xd9, 0x69,
0xae, 0xa3, 0xa7, 0x92, 0x40, 0x54, 0xa8, 0x85, 0x48, 0xa8, 0xc4, 0xe7, 0xbe, 0x4e, 0xa2, 0x09, 0x37, 0x39, 0x92, 0x41, 0xa9, 0x15, 0xdb, 0x69, 0x93, 0x1b, 0x28, 0xbd, 0xd0, 0x71, 0x7d, 0xa8,
0xed, 0x2f, 0x30, 0xf8, 0x7f, 0xd0, 0xe5, 0xbb, 0x95, 0x46, 0x63, 0x61, 0xb9, 0xd2, 0x5c, 0x7b, 0x38, 0x1e, 0x33, 0x7d, 0xd7, 0xd1, 0x43, 0x49, 0x20, 0xea, 0xa8, 0x85, 0x48, 0xa8, 0xc4, 0x67,
0xb8, 0x5a, 0x69, 0x2e, 0xae, 0xdc, 0x6b, 0xac, 0x55, 0x68, 0x73, 0xb1, 0x5e, 0x5b, 0xaa, 0x2e, 0xbf, 0x4a, 0xa2, 0x31, 0xed, 0x4f, 0x38, 0xf8, 0x27, 0xe8, 0xd2, 0x9d, 0x4a, 0xa3, 0x31, 0xbf,
0xe7, 0x46, 0x0a, 0x57, 0xf6, 0x0f, 0x8a, 0x79, 0xcd, 0x22, 0xfe, 0xb7, 0x92, 0xff, 0x44, 0x38, 0x54, 0x69, 0xae, 0x3e, 0x58, 0xa9, 0x34, 0x17, 0x96, 0xef, 0x36, 0x56, 0x2b, 0xb4, 0xb9, 0x50,
0x66, 0x5e, 0xad, 0x95, 0x2b, 0x9f, 0xe4, 0x12, 0x85, 0xf3, 0xfb, 0x07, 0xc5, 0x9c, 0x66, 0x25, 0xaf, 0x2d, 0x56, 0x97, 0x72, 0x43, 0x85, 0xcb, 0x7b, 0xfb, 0xc5, 0xbc, 0x66, 0x11, 0xff, 0x5b,
0x3e, 0xc1, 0xfd, 0x37, 0x7a, 0xe9, 0x28, 0xbb, 0x79, 0x6f, 0xb5, 0xbc, 0xb0, 0x56, 0xc9, 0x8d, 0xcb, 0x0f, 0x11, 0x8e, 0x99, 0x57, 0x6b, 0xe5, 0xca, 0x47, 0xb9, 0x44, 0xe1, 0xdc, 0xde, 0x7e,
0x16, 0x0a, 0xfb, 0x07, 0xc5, 0x8b, 0x87, 0x8d, 0x64, 0x0a, 0xbe, 0x81, 0xce, 0xc7, 0x4c, 0x69, 0x31, 0xa7, 0x59, 0x89, 0x4f, 0x70, 0x3f, 0x46, 0x2f, 0x1c, 0x65, 0x37, 0xef, 0xae, 0x94, 0xe7,
0xe5, 0xe3, 0x7b, 0x95, 0xc6, 0x5a, 0x2e, 0x59, 0xb8, 0xb8, 0x7f, 0x50, 0xc4, 0x9a, 0x55, 0x38, 0x57, 0x2b, 0xb9, 0xe1, 0x42, 0x61, 0x6f, 0xbf, 0x78, 0xe1, 0xb0, 0x91, 0x0c, 0xc1, 0xd7, 0xd0,
0x26, 0xe6, 0xd1, 0x85, 0x43, 0x16, 0x8d, 0xd5, 0x7a, 0xad, 0x51, 0xc9, 0xa5, 0x0a, 0x97, 0xf6, 0xb9, 0x98, 0x29, 0xad, 0x7c, 0x78, 0xb7, 0xd2, 0x58, 0xcd, 0x25, 0x0b, 0x17, 0xf6, 0xf6, 0x8b,
0x0f, 0x8a, 0xe7, 0x62, 0x26, 0xb2, 0xab, 0x2c, 0xa2, 0x99, 0x98, 0x4d, 0xb9, 0xfe, 0xa0, 0xb6, 0x58, 0xb3, 0x0a, 0xdb, 0xc4, 0x1c, 0x3a, 0x7f, 0xc8, 0xa2, 0xb1, 0x52, 0xaf, 0x35, 0x2a, 0xb9,
0x52, 0x5f, 0x28, 0x37, 0x57, 0x69, 0x7d, 0x99, 0x56, 0x1a, 0x8d, 0x5c, 0xba, 0x60, 0xec, 0x1f, 0x54, 0xe1, 0xe2, 0xde, 0x7e, 0xf1, 0x6c, 0xcc, 0x44, 0x56, 0x95, 0x05, 0x34, 0x1d, 0xb3, 0x29,
0x14, 0x2f, 0x6b, 0xc6, 0x47, 0x2a, 0x7c, 0x0e, 0x4d, 0xc7, 0x9c, 0xac, 0x56, 0x6b, 0xcb, 0xb9, 0xd7, 0xef, 0xd7, 0x96, 0xeb, 0xf3, 0xe5, 0xe6, 0x0a, 0xad, 0x2f, 0xd1, 0x4a, 0xa3, 0x91, 0x4b,
0x4c, 0xe1, 0xdc, 0xfe, 0x41, 0xf1, 0xac, 0x66, 0xc7, 0x63, 0x79, 0x64, 0xff, 0x16, 0x57, 0xea, 0x17, 0x8c, 0xbd, 0xfd, 0xe2, 0x25, 0xcd, 0xf8, 0x48, 0x86, 0xcf, 0xa2, 0xa9, 0x98, 0x93, 0x95,
0x8d, 0x4a, 0x6e, 0xec, 0xc8, 0xfe, 0x41, 0xc0, 0xe7, 0x7e, 0x95, 0x40, 0xf8, 0xe8, 0x1f, 0xbd, 0x6a, 0x6d, 0x29, 0x97, 0x29, 0x9c, 0xdd, 0xdb, 0x2f, 0x9e, 0xd1, 0xec, 0xf8, 0x59, 0x1e, 0xd9,
0xf0, 0x7b, 0x28, 0x1f, 0x3a, 0x59, 0xac, 0xdf, 0x5d, 0xe5, 0xeb, 0xac, 0xd6, 0x6b, 0xcd, 0x5a, 0xbf, 0x85, 0xe5, 0x7a, 0xa3, 0x92, 0x1b, 0x39, 0xb2, 0x7f, 0x70, 0xe0, 0xb3, 0xbf, 0x4b, 0x20,
0xbd, 0x56, 0xc9, 0x8d, 0xc4, 0x76, 0x55, 0xb3, 0xaa, 0xb9, 0x0e, 0xc3, 0x75, 0x74, 0xe9, 0x38, 0x7c, 0xf4, 0xaf, 0x66, 0xf8, 0x1d, 0x94, 0x0f, 0x9d, 0x2c, 0xd4, 0xef, 0xac, 0xf0, 0x75, 0x56,
0xcb, 0x95, 0x47, 0x6f, 0xe7, 0x12, 0x85, 0xf9, 0xfd, 0x83, 0xe2, 0x85, 0xa3, 0x86, 0x2b, 0x8f, 0xeb, 0xb5, 0x66, 0xad, 0x5e, 0xab, 0xe4, 0x86, 0x62, 0xbb, 0xaa, 0x59, 0xd5, 0x5c, 0x87, 0xe1,
0xde, 0xfe, 0xe6, 0xa7, 0xaf, 0x1c, 0xaf, 0x98, 0xe3, 0x07, 0x20, 0x7d, 0x69, 0x6f, 0xa2, 0xf3, 0x3a, 0xba, 0x78, 0x9c, 0xe5, 0xf2, 0xc3, 0x37, 0x73, 0x89, 0xc2, 0xdc, 0xde, 0x7e, 0xf1, 0xfc,
0xba, 0xe3, 0xbb, 0x95, 0xb5, 0x85, 0xf2, 0xc2, 0xda, 0x42, 0x6e, 0x44, 0xc4, 0x40, 0xa3, 0xde, 0x51, 0xc3, 0xe5, 0x87, 0x6f, 0x7e, 0xfd, 0xab, 0x97, 0x8e, 0x57, 0xcc, 0xf2, 0x01, 0x48, 0x5f,
0x65, 0x7d, 0x13, 0xda, 0xee, 0x6b, 0x68, 0x3a, 0xf6, 0x16, 0x95, 0xfb, 0x15, 0x1a, 0x66, 0x94, 0xda, 0xeb, 0xe8, 0x9c, 0xee, 0xf8, 0x4e, 0x65, 0x75, 0xbe, 0x3c, 0xbf, 0x3a, 0x9f, 0x1b, 0x12,
0xbe, 0x7e, 0xb6, 0xcd, 0x3c, 0xfc, 0x3a, 0xc2, 0x3a, 0x79, 0x61, 0xe5, 0xc1, 0xc2, 0xc3, 0x46, 0x67, 0xa0, 0x51, 0xef, 0xb0, 0xbe, 0x09, 0x65, 0xf7, 0x15, 0x34, 0x15, 0x7b, 0x8b, 0xca, 0xbd,
0x6e, 0xb4, 0x70, 0x61, 0xff, 0xa0, 0x38, 0xad, 0xb1, 0x17, 0x3a, 0x3b, 0xe6, 0x9e, 0x3f, 0xf7, 0x0a, 0x0d, 0x23, 0x4a, 0x5f, 0x3f, 0xdb, 0x62, 0x1e, 0x7e, 0x15, 0x61, 0x9d, 0x3c, 0xbf, 0x7c,
0xfb, 0x51, 0x34, 0xa9, 0x7f, 0x37, 0xc2, 0xaf, 0xa3, 0x73, 0x4b, 0xd5, 0x15, 0x9e, 0x89, 0x4b, 0x7f, 0xfe, 0x41, 0x23, 0x37, 0x5c, 0x38, 0xbf, 0xb7, 0x5f, 0x9c, 0xd2, 0xd8, 0xf3, 0x9d, 0x6d,
0x75, 0x11, 0x01, 0x2e, 0xe6, 0x46, 0xc4, 0xe3, 0x74, 0x2a, 0xff, 0x8d, 0xff, 0x0b, 0xe5, 0x0f, 0x73, 0xd7, 0x9f, 0xfd, 0xf3, 0x30, 0x1a, 0xd7, 0xbf, 0x1b, 0xe1, 0x57, 0xd1, 0xd9, 0xc5, 0xea,
0xd1, 0xcb, 0x55, 0x5a, 0x59, 0x5c, 0xab, 0xd3, 0x87, 0xb9, 0x44, 0xe1, 0x25, 0xbe, 0x61, 0xba, 0x32, 0x8f, 0xc4, 0xc5, 0xba, 0x38, 0x01, 0x2e, 0xe6, 0x86, 0xc4, 0xe3, 0x74, 0x2a, 0xff, 0x8d,
0x4d, 0xd9, 0xf6, 0xa0, 0x05, 0xed, 0xe1, 0x5b, 0xe8, 0xf2, 0x21, 0xc3, 0xc6, 0xc3, 0xbb, 0x2b, 0x7f, 0x84, 0xf2, 0x87, 0xe8, 0xe5, 0x2a, 0xad, 0x2c, 0xac, 0xd6, 0xe9, 0x83, 0x5c, 0xa2, 0xf0,
0xd5, 0xda, 0x1d, 0xf1, 0xbc, 0xd1, 0xc2, 0xd5, 0xfd, 0x83, 0xe2, 0x25, 0xdd, 0xb6, 0x21, 0x3e, 0x02, 0xdf, 0x30, 0xdd, 0xa6, 0x6c, 0x7b, 0x50, 0x82, 0x76, 0xf1, 0x4d, 0x74, 0xe9, 0x90, 0x61,
0xc5, 0x71, 0x28, 0x9b, 0xc0, 0xb7, 0x51, 0xf1, 0x04, 0xfb, 0x68, 0x01, 0xc9, 0x02, 0xd9, 0x3f, 0xe3, 0xc1, 0x9d, 0xe5, 0x6a, 0xed, 0xb6, 0x78, 0xde, 0x70, 0xe1, 0xca, 0xde, 0x7e, 0xf1, 0xa2,
0x28, 0x5e, 0x39, 0xc6, 0x89, 0x5a, 0x47, 0x36, 0x81, 0xdf, 0x42, 0x17, 0x8f, 0xf7, 0x14, 0xd6, 0x6e, 0xdb, 0x10, 0x9f, 0xe2, 0x38, 0x94, 0x4d, 0xe0, 0x5b, 0xa8, 0x78, 0x82, 0x7d, 0xb4, 0x80,
0xc5, 0x31, 0xf6, 0x73, 0x7f, 0x4c, 0xa0, 0x71, 0x35, 0xf5, 0xf8, 0xa6, 0x55, 0x28, 0xad, 0xf3, 0x64, 0x81, 0xec, 0xed, 0x17, 0x2f, 0x1f, 0xe3, 0x44, 0xad, 0x23, 0x9b, 0xc0, 0x6f, 0xa0, 0x0b,
0x26, 0x51, 0xae, 0x34, 0x6b, 0xf5, 0x26, 0x48, 0xe1, 0xa6, 0x29, 0x5e, 0xcd, 0x85, 0x9f, 0x3c, 0xc7, 0x7b, 0x0a, 0xf3, 0xe2, 0x18, 0xfb, 0xd9, 0xbf, 0x26, 0xd0, 0xa8, 0xea, 0x7a, 0x7c, 0xd3,
0xc7, 0x35, 0xfa, 0x72, 0xa5, 0x56, 0xa1, 0xd5, 0xc5, 0x30, 0xa2, 0x8a, 0xbd, 0xcc, 0x1c, 0xe6, 0x2a, 0x94, 0xd6, 0x79, 0x91, 0x28, 0x57, 0x9a, 0xb5, 0x7a, 0x13, 0xa4, 0x70, 0xd3, 0x14, 0xaf,
0xd9, 0x2d, 0xfc, 0x36, 0xba, 0x14, 0x77, 0xde, 0xb8, 0xb7, 0x78, 0x3b, 0xdc, 0x25, 0x58, 0xa0, 0xe6, 0xc2, 0x4f, 0x1e, 0xe3, 0x1a, 0x7d, 0xa9, 0x52, 0xab, 0xd0, 0xea, 0x42, 0x78, 0xa2, 0x8a,
0xf6, 0x80, 0xc6, 0x56, 0x6b, 0x13, 0x02, 0xf3, 0x4e, 0xcc, 0xaa, 0x5a, 0xbb, 0xbf, 0xb0, 0x52, 0xbd, 0xc4, 0x1c, 0xe6, 0xd9, 0x2d, 0xfc, 0x26, 0xba, 0x18, 0x77, 0xde, 0xb8, 0xbb, 0x70, 0x2b,
0x2d, 0x0b, 0xab, 0x64, 0x21, 0xbf, 0x7f, 0x50, 0x3c, 0xaf, 0xac, 0xe4, 0x07, 0x0e, 0x6e, 0x36, 0xdc, 0x25, 0x58, 0xa0, 0xf6, 0x80, 0xc6, 0x66, 0x6b, 0x03, 0x0e, 0xe6, 0xad, 0x98, 0x55, 0xb5,
0xf7, 0x4d, 0x02, 0xcd, 0x7c, 0xf7, 0xf0, 0xc2, 0x0f, 0xd0, 0xab, 0xb0, 0x5f, 0x47, 0x5a, 0x81, 0x76, 0x6f, 0x7e, 0xb9, 0x5a, 0x16, 0x56, 0xc9, 0x42, 0x7e, 0x6f, 0xbf, 0x78, 0x4e, 0x59, 0xc9,
0xec, 0x5b, 0x62, 0x0f, 0x17, 0x56, 0x57, 0x2b, 0xb5, 0x72, 0x6e, 0xa4, 0x30, 0xbb, 0x7f, 0x50, 0x0f, 0x1c, 0xdc, 0x6c, 0xf6, 0xeb, 0x04, 0x9a, 0xfe, 0xf6, 0xe6, 0x85, 0xef, 0xa3, 0x97, 0x61,
0xbc, 0xf6, 0xdd, 0x2e, 0x17, 0x7a, 0x3d, 0xe6, 0x58, 0xa7, 0x74, 0xbc, 0x54, 0xa7, 0xcb, 0x95, 0xbf, 0x8e, 0x94, 0x02, 0x59, 0xb7, 0xc4, 0x1e, 0xce, 0xaf, 0xac, 0x54, 0x6a, 0xe5, 0xdc, 0x50,
0xb5, 0x5c, 0xe2, 0x34, 0x8e, 0x97, 0x5c, 0xaf, 0xcd, 0xfa, 0xa5, 0xbb, 0x4f, 0xbe, 0x9d, 0x19, 0x61, 0x66, 0x6f, 0xbf, 0x78, 0xf5, 0xdb, 0x5d, 0xce, 0xf7, 0x7a, 0xcc, 0xb1, 0x4e, 0xe9, 0x78,
0x79, 0xfa, 0xed, 0xcc, 0xc8, 0x93, 0x67, 0x33, 0x89, 0xa7, 0xcf, 0x66, 0x12, 0x3f, 0x7b, 0x3e, 0xb1, 0x4e, 0x97, 0x2a, 0xab, 0xb9, 0xc4, 0x69, 0x1c, 0x2f, 0xba, 0x5e, 0x9b, 0xf5, 0x4b, 0x77,
0x33, 0xf2, 0xd5, 0xf3, 0x99, 0xc4, 0xd3, 0xe7, 0x33, 0x23, 0x7f, 0x7a, 0x3e, 0x33, 0xf2, 0xe8, 0x1e, 0x7f, 0x33, 0x3d, 0xf4, 0xe4, 0x9b, 0xe9, 0xa1, 0xc7, 0x4f, 0xa7, 0x13, 0x4f, 0x9e, 0x4e,
0xb5, 0xb6, 0xdd, 0xdf, 0xdc, 0x5a, 0xbf, 0xde, 0x72, 0xbb, 0x37, 0xfc, 0x3d, 0xa7, 0xd5, 0xdf, 0x27, 0x7e, 0xfd, 0x6c, 0x7a, 0xe8, 0xcb, 0x67, 0xd3, 0x89, 0x27, 0xcf, 0xa6, 0x87, 0xfe, 0xf6,
0xb4, 0x9d, 0xb6, 0xf6, 0x4b, 0xff, 0xcf, 0x0f, 0xeb, 0x19, 0xf8, 0xf5, 0xd6, 0x3f, 0x02, 0x00, 0x6c, 0x7a, 0xe8, 0xe1, 0x2b, 0x6d, 0xbb, 0xbf, 0xb1, 0xb9, 0x76, 0xad, 0xe5, 0x76, 0xaf, 0xfb,
0x00, 0xff, 0xff, 0x68, 0x4a, 0x6e, 0xeb, 0x13, 0x21, 0x00, 0x00, 0xbb, 0x4e, 0xab, 0xbf, 0x61, 0x3b, 0x6d, 0xed, 0x97, 0xfe, 0xbf, 0x27, 0xd6, 0x32, 0xf0, 0xeb,
0x8d, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xa2, 0xde, 0xa7, 0x53, 0x54, 0x21, 0x00, 0x00,
} }
func (m *Hello) Marshal() (dAtA []byte, err error) { func (m *Hello) Marshal() (dAtA []byte, err error) {
@ -1440,6 +1442,16 @@ func (m *ClusterConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
if m.Secondary {
i--
if m.Secondary {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x10
}
if len(m.Folders) > 0 { if len(m.Folders) > 0 {
for iNdEx := len(m.Folders) - 1; iNdEx >= 0; iNdEx-- { for iNdEx := len(m.Folders) - 1; iNdEx >= 0; iNdEx-- {
{ {
@ -2642,6 +2654,9 @@ func (m *ClusterConfig) ProtoSize() (n int) {
n += 1 + l + sovBep(uint64(l)) n += 1 + l + sovBep(uint64(l))
} }
} }
if m.Secondary {
n += 2
}
return n return n
} }
@ -3430,6 +3445,26 @@ func (m *ClusterConfig) Unmarshal(dAtA []byte) error {
return err return err
} }
iNdEx = postIndex 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: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipBep(dAtA[iNdEx:]) skippy, err := skipBep(dAtA[iNdEx:])

View File

@ -13,8 +13,8 @@ type TestModel struct {
hash []byte hash []byte
weakHash uint32 weakHash uint32
fromTemporary bool fromTemporary bool
indexFn func(DeviceID, string, []FileInfo) indexFn func(string, []FileInfo)
ccFn func(DeviceID, ClusterConfig) ccFn func(ClusterConfig)
closedCh chan struct{} closedCh chan struct{}
closedErr error closedErr error
} }
@ -25,18 +25,18 @@ func newTestModel() *TestModel {
} }
} }
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo) error { func (t *TestModel) Index(_ Connection, folder string, files []FileInfo) error {
if t.indexFn != nil { if t.indexFn != nil {
t.indexFn(deviceID, folder, files) t.indexFn(folder, files)
} }
return nil return nil
} }
func (*TestModel) IndexUpdate(_ DeviceID, _ string, _ []FileInfo) error { func (*TestModel) IndexUpdate(Connection, string, []FileInfo) error {
return nil return nil
} }
func (t *TestModel) Request(_ DeviceID, folder, name string, _, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) { func (t *TestModel) Request(_ Connection, folder, name string, _, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
t.folder = folder t.folder = folder
t.name = name t.name = name
t.offset = offset t.offset = offset
@ -49,19 +49,19 @@ func (t *TestModel) Request(_ DeviceID, folder, name string, _, size int32, offs
return &fakeRequestResponse{buf}, nil return &fakeRequestResponse{buf}, nil
} }
func (t *TestModel) Closed(_ string, err error) { func (t *TestModel) Closed(_ Connection, err error) {
t.closedErr = err t.closedErr = err
close(t.closedCh) close(t.closedCh)
} }
func (t *TestModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) error { func (t *TestModel) ClusterConfig(_ Connection, config ClusterConfig) error {
if t.ccFn != nil { if t.ccFn != nil {
t.ccFn(deviceID, config) t.ccFn(config)
} }
return nil return nil
} }
func (*TestModel) DownloadProgress(DeviceID, string, []FileDownloadProgressUpdate) error { func (*TestModel) DownloadProgress(Connection, string, []FileDownloadProgressUpdate) error {
return nil return nil
} }

View File

@ -43,12 +43,12 @@ const (
// receives encrypted metadata and requests from the untrusted device, so it // receives encrypted metadata and requests from the untrusted device, so it
// must decrypt those and answer requests by encrypting the data. // must decrypt those and answer requests by encrypting the data.
type encryptedModel struct { type encryptedModel struct {
model Model model contextLessModel
folderKeys *folderKeyRegistry folderKeys *folderKeyRegistry
keyGen *KeyGenerator keyGen *KeyGenerator
} }
func newEncryptedModel(model Model, folderKeys *folderKeyRegistry, keyGen *KeyGenerator) encryptedModel { func newEncryptedModel(model contextLessModel, folderKeys *folderKeyRegistry, keyGen *KeyGenerator) encryptedModel {
return encryptedModel{ return encryptedModel{
model: model, model: model,
folderKeys: folderKeys, folderKeys: folderKeys,
@ -56,30 +56,30 @@ func newEncryptedModel(model Model, folderKeys *folderKeyRegistry, keyGen *KeyGe
} }
} }
func (e encryptedModel) Index(deviceID DeviceID, folder string, files []FileInfo) error { func (e encryptedModel) Index(folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys.get(folder); ok { if folderKey, ok := e.folderKeys.get(folder); ok {
// incoming index data to be decrypted // incoming index data to be decrypted
if err := decryptFileInfos(e.keyGen, files, folderKey); err != nil { if err := decryptFileInfos(e.keyGen, files, folderKey); err != nil {
return err return err
} }
} }
return e.model.Index(deviceID, folder, files) return e.model.Index(folder, files)
} }
func (e encryptedModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error { func (e encryptedModel) IndexUpdate(folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys.get(folder); ok { if folderKey, ok := e.folderKeys.get(folder); ok {
// incoming index data to be decrypted // incoming index data to be decrypted
if err := decryptFileInfos(e.keyGen, files, folderKey); err != nil { if err := decryptFileInfos(e.keyGen, files, folderKey); err != nil {
return err return err
} }
} }
return e.model.IndexUpdate(deviceID, folder, files) return e.model.IndexUpdate(folder, files)
} }
func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) { func (e encryptedModel) Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
folderKey, ok := e.folderKeys.get(folder) folderKey, ok := e.folderKeys.get(folder)
if !ok { if !ok {
return e.model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary) return e.model.Request(folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
} }
// Figure out the real file name, offset and size from the encrypted / // Figure out the real file name, offset and size from the encrypted /
@ -120,7 +120,7 @@ func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo,
// Perform that request and grab the data. // Perform that request and grab the data.
resp, err := e.model.Request(deviceID, folder, realName, blockNo, realSize, realOffset, realHash, 0, false) resp, err := e.model.Request(folder, realName, blockNo, realSize, realOffset, realHash, 0, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,21 +142,21 @@ func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo,
return rawResponse{enc}, nil return rawResponse{enc}, nil
} }
func (e encryptedModel) DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error { func (e encryptedModel) DownloadProgress(folder string, updates []FileDownloadProgressUpdate) error {
if _, ok := e.folderKeys.get(folder); !ok { if _, ok := e.folderKeys.get(folder); !ok {
return e.model.DownloadProgress(deviceID, folder, updates) return e.model.DownloadProgress(folder, updates)
} }
// Encrypted devices shouldn't send these - ignore them. // Encrypted devices shouldn't send these - ignore them.
return nil return nil
} }
func (e encryptedModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) error { func (e encryptedModel) ClusterConfig(config ClusterConfig) error {
return e.model.ClusterConfig(deviceID, config) return e.model.ClusterConfig(config)
} }
func (e encryptedModel) Closed(connID string, err error) { func (e encryptedModel) Closed(err error) {
e.model.Closed(connID, err) e.model.Closed(err)
} }
// The encryptedConnection sits between the model and the encrypted device. It // The encryptedConnection sits between the model and the encrypted device. It

View File

@ -9,27 +9,27 @@ package protocol
import "golang.org/x/text/unicode/norm" import "golang.org/x/text/unicode/norm"
func makeNative(m Model) Model { return nativeModel{m} } func makeNative(m contextLessModel) contextLessModel { return nativeModel{m} }
type nativeModel struct { type nativeModel struct {
Model contextLessModel
} }
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo) error { func (m nativeModel) Index(folder string, files []FileInfo) error {
for i := range files { for i := range files {
files[i].Name = norm.NFD.String(files[i].Name) files[i].Name = norm.NFD.String(files[i].Name)
} }
return m.Model.Index(deviceID, folder, files) return m.contextLessModel.Index(folder, files)
} }
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error { func (m nativeModel) IndexUpdate(folder string, files []FileInfo) error {
for i := range files { for i := range files {
files[i].Name = norm.NFD.String(files[i].Name) files[i].Name = norm.NFD.String(files[i].Name)
} }
return m.Model.IndexUpdate(deviceID, folder, files) return m.contextLessModel.IndexUpdate(folder, files)
} }
func (m nativeModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) { 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) name = norm.NFD.String(name)
return m.Model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary) return m.contextLessModel.Request(folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
} }

View File

@ -121,19 +121,34 @@ var (
errFileHasNoBlocks = errors.New("file with empty block list") errFileHasNoBlocks = errors.New("file with empty block list")
) )
type contextLessModel interface {
// An index was received from the peer device
Index(folder string, files []FileInfo) error
// An index update was received from the peer device
IndexUpdate(folder string, files []FileInfo) error
// A request was made by the peer device
Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
// A cluster configuration message was received
ClusterConfig(config ClusterConfig) error
// The peer device closed the connection or an error occurred
Closed(err error)
// The peer device sent progress updates for the files it is currently downloading
DownloadProgress(folder string, updates []FileDownloadProgressUpdate) error
}
type Model interface { type Model interface {
// An index was received from the peer device // An index was received from the peer device
Index(deviceID DeviceID, folder string, files []FileInfo) error Index(conn Connection, folder string, files []FileInfo) error
// An index update was received from the peer device // An index update was received from the peer device
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error IndexUpdate(conn Connection, folder string, files []FileInfo) error
// A request was made by the peer device // A request was made by the peer device
Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) Request(conn Connection, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
// A cluster configuration message was received // A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfig) error ClusterConfig(conn Connection, config ClusterConfig) error
// The peer device closed the connection or an error occurred // The peer device closed the connection or an error occurred
Closed(connID string, err error) Closed(conn Connection, err error)
// The peer device sent progress updates for the files it is currently downloading // The peer device sent progress updates for the files it is currently downloading
DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error DownloadProgress(conn Connection, folder string, updates []FileDownloadProgressUpdate) error
} }
type RequestResponse interface { type RequestResponse interface {
@ -173,7 +188,7 @@ type rawConnection struct {
ConnectionInfo ConnectionInfo
id DeviceID id DeviceID
receiver Model model contextLessModel
startTime time.Time startTime time.Time
cr *countingReader cr *countingReader
@ -230,10 +245,16 @@ const (
// Should not be modified in production code, just for testing. // Should not be modified in production code, just for testing.
var CloseTimeout = 10 * time.Second var CloseTimeout = 10 * time.Second
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression, passwords map[string]string, keyGen *KeyGenerator) Connection { func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, model Model, connInfo ConnectionInfo, compress Compression, passwords map[string]string, keyGen *KeyGenerator) Connection {
// We create the wrapper for the model first, as it needs to be passed
// in at the lowest level in the stack. At the end of construction,
// before returning, we add the connection to cwm so that it can be used
// by the model.
cwm := &connectionWrappingModel{model: model}
// Encryption / decryption is first (outermost) before conversion to // Encryption / decryption is first (outermost) before conversion to
// native path formats. // native path formats.
nm := makeNative(receiver) nm := makeNative(cwm)
em := newEncryptedModel(nm, newFolderKeyRegistry(keyGen, passwords), keyGen) em := newEncryptedModel(nm, newFolderKeyRegistry(keyGen, passwords), keyGen)
// We do the wire format conversion first (outermost) so that the // We do the wire format conversion first (outermost) so that the
@ -242,17 +263,18 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer
ec := newEncryptedConnection(rc, rc, em.folderKeys, keyGen) ec := newEncryptedConnection(rc, rc, em.folderKeys, keyGen)
wc := wireFormatConnection{ec} wc := wireFormatConnection{ec}
cwm.conn = wc
return wc return wc
} }
func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) *rawConnection { func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver contextLessModel, connInfo ConnectionInfo, compress Compression) *rawConnection {
cr := &countingReader{Reader: reader} cr := &countingReader{Reader: reader}
cw := &countingWriter{Writer: writer} cw := &countingWriter{Writer: writer}
return &rawConnection{ return &rawConnection{
ConnectionInfo: connInfo, ConnectionInfo: connInfo,
id: deviceID, id: deviceID,
receiver: receiver, model: receiver,
cr: cr, cr: cr,
cw: cw, cw: cw,
closer: closer, closer: closer,
@ -463,7 +485,7 @@ func (c *rawConnection) dispatcherLoop() (err error) {
switch msg := msg.(type) { switch msg := msg.(type) {
case *ClusterConfig: case *ClusterConfig:
err = c.receiver.ClusterConfig(c.id, *msg) err = c.model.ClusterConfig(*msg)
case *Index: case *Index:
err = c.handleIndex(*msg) err = c.handleIndex(*msg)
@ -478,7 +500,7 @@ func (c *rawConnection) dispatcherLoop() (err error) {
c.handleResponse(*msg) c.handleResponse(*msg)
case *DownloadProgress: case *DownloadProgress:
err = c.receiver.DownloadProgress(c.id, msg.Folder, msg.Updates) err = c.model.DownloadProgress(msg.Folder, msg.Updates)
} }
if err != nil { if err != nil {
return newHandleError(err, msgContext) return newHandleError(err, msgContext)
@ -581,12 +603,12 @@ func (c *rawConnection) readHeader(fourByteBuf []byte) (Header, error) {
func (c *rawConnection) handleIndex(im Index) error { func (c *rawConnection) handleIndex(im Index) error {
l.Debugf("Index(%v, %v, %d file)", c.id, im.Folder, len(im.Files)) l.Debugf("Index(%v, %v, %d file)", c.id, im.Folder, len(im.Files))
return c.receiver.Index(c.id, im.Folder, im.Files) return c.model.Index(im.Folder, im.Files)
} }
func (c *rawConnection) handleIndexUpdate(im IndexUpdate) error { func (c *rawConnection) handleIndexUpdate(im IndexUpdate) error {
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Folder, len(im.Files)) l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
return c.receiver.IndexUpdate(c.id, im.Folder, im.Files) return c.model.IndexUpdate(im.Folder, im.Files)
} }
// checkIndexConsistency verifies a number of invariants on FileInfos received in // checkIndexConsistency verifies a number of invariants on FileInfos received in
@ -652,7 +674,7 @@ func checkFilename(name string) error {
} }
func (c *rawConnection) handleRequest(req Request) { func (c *rawConnection) handleRequest(req Request) {
res, err := c.receiver.Request(c.id, req.Folder, req.Name, int32(req.BlockNo), int32(req.Size), req.Offset, req.Hash, req.WeakHash, req.FromTemporary) res, err := c.model.Request(req.Folder, req.Name, int32(req.BlockNo), int32(req.Size), req.Offset, req.Hash, req.WeakHash, req.FromTemporary)
if err != nil { if err != nil {
c.send(context.Background(), &Response{ c.send(context.Background(), &Response{
ID: req.ID, ID: req.ID,
@ -941,7 +963,7 @@ func (c *rawConnection) internalClose(err error) {
<-c.dispatcherLoopStopped <-c.dispatcherLoopStopped
c.receiver.Closed(c.ConnectionID(), err) c.model.Closed(err)
}) })
} }
@ -1069,3 +1091,35 @@ func messageContext(msg message) (string, error) {
return "", errors.New("unknown or empty message") return "", errors.New("unknown or empty message")
} }
} }
// 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.Model interface.
type connectionWrappingModel struct {
conn Connection
model Model
}
func (c *connectionWrappingModel) Index(folder string, files []FileInfo) error {
return c.model.Index(c.conn, folder, files)
}
func (c *connectionWrappingModel) IndexUpdate(folder string, files []FileInfo) error {
return c.model.IndexUpdate(c.conn, folder, files)
}
func (c *connectionWrappingModel) Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
return c.model.Request(c.conn, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}
func (c *connectionWrappingModel) ClusterConfig(config ClusterConfig) error {
return c.model.ClusterConfig(c.conn, config)
}
func (c *connectionWrappingModel) Closed(err error) {
c.model.Closed(c.conn, err)
}
func (c *connectionWrappingModel) DownloadProgress(folder string, updates []FileDownloadProgressUpdate) error {
return c.model.DownloadProgress(c.conn, folder, updates)
}

View File

@ -145,7 +145,7 @@ func TestCloseRace(t *testing.T) {
indexReceived := make(chan struct{}) indexReceived := make(chan struct{})
unblockIndex := make(chan struct{}) unblockIndex := make(chan struct{})
m0 := newTestModel() m0 := newTestModel()
m0.indexFn = func(_ DeviceID, _ string, _ []FileInfo) { m0.indexFn = func(string, []FileInfo) {
close(indexReceived) close(indexReceived)
<-unblockIndex <-unblockIndex
} }
@ -924,7 +924,7 @@ func TestDispatcherToCloseDeadlock(t *testing.T) {
m := newTestModel() m := newTestModel()
rw := testutils.NewBlockingRW() rw := testutils.NewBlockingRW()
c := getRawConnection(NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen)) c := getRawConnection(NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil, testKeyGen))
m.ccFn = func(devID DeviceID, cc ClusterConfig) { m.ccFn = func(ClusterConfig) {
c.Close(errManual) c.Close(errManual)
} }
c.Start() c.Start()

View File

@ -26,4 +26,5 @@ message DeviceConfiguration {
int32 max_request_kib = 16 [(ext.goname) = "MaxRequestKiB", (ext.xml) = "maxRequestKiB", (ext.json) = "maxRequestKiB"]; int32 max_request_kib = 16 [(ext.goname) = "MaxRequestKiB", (ext.xml) = "maxRequestKiB", (ext.json) = "maxRequestKiB"];
bool untrusted = 17; bool untrusted = 17;
int32 remote_gui_port = 18 [(ext.goname) = "RemoteGUIPort", (ext.xml) = "remoteGUIPort", (ext.json) = "remoteGUIPort"]; int32 remote_gui_port = 18 [(ext.goname) = "RemoteGUIPort", (ext.xml) = "remoteGUIPort", (ext.json) = "remoteGUIPort"];
int32 multiple_connections = 19; // if >1 and supported by the other side, attempt to establish this many connections to the device
} }

View File

@ -41,7 +41,8 @@ enum MessageCompression {
// Cluster Config // Cluster Config
message ClusterConfig { message ClusterConfig {
repeated Folder folders = 1; repeated Folder folders = 1;
bool secondary = 2;
} }
message Folder { message Folder {

View File

@ -1,5 +1,5 @@
<configuration version="37"> <configuration version="37">
<folder id="default" label="" path="s1/" type="sendreceive" rescanIntervalS="10" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true"> <folder id="default" label="" path="s1/" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType> <filesystemType>basic</filesystemType>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""> <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy="">
<encryptionPassword></encryptionPassword> <encryptionPassword></encryptionPassword>
@ -49,60 +49,6 @@
<maxTotalSize>0</maxTotalSize> <maxTotalSize>0</maxTotalSize>
</xattrFilter> </xattrFilter>
</folder> </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="">
<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>
<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>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>
<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>
<untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort>
</device>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy=""> <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>tcp://127.0.0.1:22001</address>
<paused>false</paused> <paused>false</paused>
@ -122,26 +68,7 @@
<maxRequestKiB>0</maxRequestKiB> <maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted> <untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort> <remoteGUIPort>0</remoteGUIPort>
</device> <multipleConnections>4</multipleConnections>
<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>
</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>
</device> </device>
<gui enabled="true" tls="false" debugging="true"> <gui enabled="true" tls="false" debugging="true">
<address>127.0.0.1:8081</address> <address>127.0.0.1:8081</address>

View File

@ -1,5 +1,5 @@
<configuration version="37"> <configuration version="37">
<folder id="default" label="" path="s2" type="sendreceive" rescanIntervalS="15" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true"> <folder id="default" label="" path="s2" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<filesystemType>basic</filesystemType> <filesystemType>basic</filesystemType>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy=""> <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" introducedBy="">
<encryptionPassword></encryptionPassword> <encryptionPassword></encryptionPassword>
@ -46,94 +46,6 @@
<maxTotalSize>0</maxTotalSize> <maxTotalSize>0</maxTotalSize>
</xattrFilter> </xattrFilter>
</folder> </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="">
<encryptionPassword></encryptionPassword>
</device>
<device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" introducedBy="">
<encryptionPassword></encryptionPassword>
</device>
<minDiskFree unit="%">1</minDiskFree>
<versioning>
<cleanupIntervalS>3600</cleanupIntervalS>
<fsPath></fsPath>
<fsType>basic</fsType>
</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>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-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="">
<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>
<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>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>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy=""> <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>tcp://127.0.0.1:22001</address>
<paused>false</paused> <paused>false</paused>
@ -143,6 +55,7 @@
<maxRequestKiB>0</maxRequestKiB> <maxRequestKiB>0</maxRequestKiB>
<untrusted>false</untrusted> <untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort> <remoteGUIPort>0</remoteGUIPort>
<multipleConnections>4</multipleConnections>
</device> </device>
<device id="MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC" name="s2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy=""> <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>tcp://127.0.0.1:22002</address>
@ -154,16 +67,6 @@
<untrusted>false</untrusted> <untrusted>false</untrusted>
<remoteGUIPort>0</remoteGUIPort> <remoteGUIPort>0</remoteGUIPort>
</device> </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>
</device>
<gui enabled="true" tls="false" debugging="true"> <gui enabled="true" tls="false" debugging="true">
<address>127.0.0.1:8082</address> <address>127.0.0.1:8082</address>
<apikey>abc123</apikey> <apikey>abc123</apikey>