@@ -146,6 +146,8 @@ type Connection struct {
146
146
lenbuf [PacketLengthBytes ]byte
147
147
148
148
lastStreamId uint64
149
+
150
+ serverIdInfo IdInfo
149
151
}
150
152
151
153
var _ = Connector (& Connection {}) // Check compatibility with connector interface.
@@ -269,6 +271,14 @@ type Opts struct {
269
271
Transport string
270
272
// SslOpts is used only if the Transport == 'ssl' is set.
271
273
Ssl SslOpts
274
+ // Minimal protocol version that should be supported by
275
+ // Go connection client and Tarantool server. By default
276
+ // it is equal to 0 (no restrictions)
277
+ RequiredProtocolVersion ProtocolVersion
278
+ // List of features that should be supported by
279
+ // Go connection client and Tarantool server. By default
280
+ // it is a nil array (no restrictions)
281
+ RequiredFeatures []Feature
272
282
}
273
283
274
284
// SslOpts is a way to configure ssl transport.
@@ -351,6 +361,16 @@ func Connect(addr string, opts Opts) (conn *Connection, err error) {
351
361
}
352
362
}
353
363
364
+ if err = checkProtocolVersion (opts .RequiredProtocolVersion , conn .ClientProtocolVersion ()); err != nil {
365
+ return nil , fmt .Errorf ("client: %w" , err )
366
+ }
367
+
368
+ requiredFeatures := make ([]Feature , len (opts .RequiredFeatures ))
369
+ copy (requiredFeatures , opts .RequiredFeatures )
370
+ if err = checkFeatures (requiredFeatures , conn .ClientFeatures ()); err != nil {
371
+ return nil , fmt .Errorf ("client: %w" , err )
372
+ }
373
+
354
374
if conn .opts .Logger == nil {
355
375
conn .opts .Logger = defaultLogger {}
356
376
}
@@ -502,6 +522,25 @@ func (conn *Connection) dial() (err error) {
502
522
conn .Greeting .Version = bytes .NewBuffer (greeting [:64 ]).String ()
503
523
conn .Greeting .auth = bytes .NewBuffer (greeting [64 :108 ]).String ()
504
524
525
+ // IPROTO_ID requests can be processed without authentication.
526
+ // https://www.tarantool.io/en/doc/latest/dev_guide/internals/iproto/requests/#iproto-id
527
+ if err = conn .identify (w , r ); err != nil {
528
+ connection .Close ()
529
+ return err
530
+ }
531
+
532
+ if err = checkProtocolVersion (opts .RequiredProtocolVersion , conn .ServerProtocolVersion ()); err != nil {
533
+ connection .Close ()
534
+ return fmt .Errorf ("server: %w" , err )
535
+ }
536
+
537
+ requiredFeatures := make ([]Feature , len (opts .RequiredFeatures ))
538
+ copy (requiredFeatures , opts .RequiredFeatures )
539
+ if err = checkFeatures (requiredFeatures , conn .ServerFeatures ()); err != nil {
540
+ connection .Close ()
541
+ return fmt .Errorf ("server: %w" , err )
542
+ }
543
+
505
544
// Auth
506
545
if opts .User != "" {
507
546
scr , err := scramble (conn .Greeting .auth , opts .Pass )
@@ -608,6 +647,18 @@ func (conn *Connection) writeAuthRequest(w *bufio.Writer, scramble []byte) (err
608
647
return nil
609
648
}
610
649
650
+ func (conn * Connection ) writeIdRequest (w * bufio.Writer , version ProtocolVersion ,
651
+ features []Feature ) (err error ) {
652
+ req := NewIdRequest (version , features )
653
+
654
+ err = conn .writeRequest (w , req )
655
+ if err != nil {
656
+ return fmt .Errorf ("id: %w" , err )
657
+ }
658
+
659
+ return nil
660
+ }
661
+
611
662
func (conn * Connection ) readResponse (r io.Reader ) (resp Response , err error ) {
612
663
respBytes , err := conn .read (r )
613
664
if err != nil {
@@ -639,6 +690,15 @@ func (conn *Connection) readAuthResponse(r io.Reader) (err error) {
639
690
return nil
640
691
}
641
692
693
+ func (conn * Connection ) readIdResponse (r io.Reader ) (resp Response , err error ) {
694
+ resp , err = conn .readResponse (r )
695
+ if err != nil {
696
+ return resp , fmt .Errorf ("id: %w" , err )
697
+ }
698
+
699
+ return resp , nil
700
+ }
701
+
642
702
func (conn * Connection ) createConnection (reconnect bool ) (err error ) {
643
703
var reconnects uint
644
704
for conn .c == nil && conn .state == connDisconnected {
@@ -1182,3 +1242,97 @@ func (conn *Connection) NewStream() (*Stream, error) {
1182
1242
Conn : conn ,
1183
1243
}, nil
1184
1244
}
1245
+
1246
+ // checkProtocolVersion checks that expected protocol version is supported.
1247
+ func checkProtocolVersion (expected ProtocolVersion , actual ProtocolVersion ) error {
1248
+ if expected > actual {
1249
+ return fmt .Errorf ("protocol version %d is not supported" , expected )
1250
+ }
1251
+
1252
+ return nil
1253
+ }
1254
+
1255
+ // checkFeatures checks that expected features are supported.
1256
+ func checkFeatures (expectedList []Feature , actualList []Feature ) error {
1257
+ var found bool
1258
+ // It seems that iterating over a small list is way faster
1259
+ // than building a map: https://stackoverflow.com/a/52710077/11646599
1260
+ for _ , expected := range expectedList {
1261
+ found = false
1262
+ for _ , actual := range actualList {
1263
+ if expected == actual {
1264
+ found = true
1265
+ }
1266
+ }
1267
+ if ! found {
1268
+ // "Feature" already is a part of the String().
1269
+ return fmt .Errorf ("%s is not supported" , expected )
1270
+ }
1271
+ }
1272
+
1273
+ return nil
1274
+ }
1275
+
1276
+ // identify sends info about client protocol, receives info
1277
+ // about server protocol in response and store it in the connection.
1278
+ func (conn * Connection ) identify (w * bufio.Writer , r * bufio.Reader ) error {
1279
+ var ok bool
1280
+
1281
+ werr := conn .writeIdRequest (w , conn .ClientProtocolVersion (), conn .ClientFeatures ())
1282
+ if werr != nil {
1283
+ return werr
1284
+ }
1285
+
1286
+ resp , rerr := conn .readIdResponse (r )
1287
+ if rerr != nil {
1288
+ if resp .Code == ErrUnknownRequestType {
1289
+ // IPROTO_ID requests are not supported by server.
1290
+ return nil
1291
+ }
1292
+
1293
+ return rerr
1294
+ }
1295
+
1296
+ if len (resp .Data ) == 0 {
1297
+ return fmt .Errorf ("identify: unexpected response: no data" )
1298
+ }
1299
+
1300
+ conn .serverIdInfo , ok = resp .Data [0 ].(IdInfo )
1301
+ if ! ok {
1302
+ return fmt .Errorf ("identify: unexpected response: wrong data" )
1303
+ }
1304
+
1305
+ return nil
1306
+ }
1307
+
1308
+ // ServerProtocolVersion returns protocol version supported by
1309
+ // connected Tarantool server. Beware that values might be outdated
1310
+ // if connection is in a disconnected state.
1311
+ // Since 1.10.0
1312
+ func (conn * Connection ) ServerProtocolVersion () ProtocolVersion {
1313
+ return conn .serverIdInfo .Version
1314
+ }
1315
+
1316
+ // ClientProtocolVersion returns protocol version supported by
1317
+ // Go connection client.
1318
+ // Since 1.10.0
1319
+ func (conn * Connection ) ClientProtocolVersion () ProtocolVersion {
1320
+ return сlientProtocolVersion
1321
+ }
1322
+
1323
+ // ServerFeatures returns features supported by connected Tarantool server.
1324
+ // Beware that values might be outdated if connection is in a disconnected state.
1325
+ // Since 1.10.0
1326
+ func (conn * Connection ) ServerFeatures () []Feature {
1327
+ res := make ([]Feature , len (conn .serverIdInfo .Features ))
1328
+ copy (res , conn .serverIdInfo .Features )
1329
+ return res
1330
+ }
1331
+
1332
+ // ClientFeatures returns features supported by Go connection client.
1333
+ // Since 1.10.0
1334
+ func (conn * Connection ) ClientFeatures () []Feature {
1335
+ res := make ([]Feature , len (сlientFeatures ))
1336
+ copy (res , сlientFeatures )
1337
+ return res
1338
+ }
0 commit comments