@@ -146,6 +146,8 @@ type Connection struct {
146146 lenbuf [PacketLengthBytes ]byte
147147
148148 lastStreamId uint64
149+
150+ serverProtocolInfo ProtocolInfo
149151}
150152
151153var _ = Connector (& Connection {}) // Check compatibility with connector interface.
@@ -269,6 +271,14 @@ type Opts struct {
269271 Transport string
270272 // SslOpts is used only if the Transport == 'ssl' is set.
271273 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 protocol features that should be supported by
279+ // Go connection client and Tarantool server. By default
280+ // it is a nil array (no restrictions)
281+ RequiredProtocolFeatures []ProtocolFeature
272282}
273283
274284// SslOpts is a way to configure ssl transport.
@@ -296,6 +306,9 @@ type SslOpts struct {
296306func (opts Opts ) Copy () (optsCopy Opts ) {
297307 optsCopy = opts
298308
309+ optsCopy .RequiredProtocolFeatures = make ([]ProtocolFeature , len (opts .RequiredProtocolFeatures ))
310+ copy (optsCopy .RequiredProtocolFeatures , opts .RequiredProtocolFeatures )
311+
299312 return optsCopy
300313}
301314
@@ -508,6 +521,23 @@ func (conn *Connection) dial() (err error) {
508521 conn .Greeting .Version = bytes .NewBuffer (greeting [:64 ]).String ()
509522 conn .Greeting .auth = bytes .NewBuffer (greeting [64 :108 ]).String ()
510523
524+ // IPROTO_ID requests can be processed without authentication.
525+ // https://www.tarantool.io/en/doc/latest/dev_guide/internals/iproto/requests/#iproto-id
526+ if err = conn .identify (w , r ); err != nil {
527+ connection .Close ()
528+ return err
529+ }
530+
531+ if err = checkProtocolVersion (opts .RequiredProtocolVersion , conn .ServerProtocolVersion ()); err != nil {
532+ connection .Close ()
533+ return fmt .Errorf ("identify: %w" , err )
534+ }
535+
536+ if err = checkProtocolFeatures (opts .RequiredProtocolFeatures , conn .ServerProtocolFeatures ()); err != nil {
537+ connection .Close ()
538+ return fmt .Errorf ("identify: %w" , err )
539+ }
540+
511541 // Auth
512542 if opts .User != "" {
513543 scr , err := scramble (conn .Greeting .auth , opts .Pass )
@@ -592,13 +622,13 @@ func (conn *Connection) writeRequest(w *bufio.Writer, req Request) (err error) {
592622 err = pack (& packet , newEncoder (& packet ), 0 , req , ignoreStreamId , conn .Schema )
593623
594624 if err != nil {
595- return fmt .Errorf ("pack error %w" , err )
625+ return fmt .Errorf ("pack error: %w" , err )
596626 }
597627 if err := write (w , packet .b ); err != nil {
598- return fmt .Errorf ("write error %w" , err )
628+ return fmt .Errorf ("write error: %w" , err )
599629 }
600630 if err = w .Flush (); err != nil {
601- return fmt .Errorf ("flush error %w" , err )
631+ return fmt .Errorf ("flush error: %w" , err )
602632 }
603633 return
604634}
@@ -614,23 +644,35 @@ func (conn *Connection) writeAuthRequest(w *bufio.Writer, scramble []byte) (err
614644 return nil
615645}
616646
647+ func (conn * Connection ) writeIdRequest (w * bufio.Writer , version ProtocolVersion ,
648+ features []ProtocolFeature ) (err error ) {
649+ req := NewIdRequest (version , features )
650+
651+ err = conn .writeRequest (w , req )
652+ if err != nil {
653+ return fmt .Errorf ("identify: %w" , err )
654+ }
655+
656+ return nil
657+ }
658+
617659func (conn * Connection ) readResponse (r io.Reader ) (resp Response , err error ) {
618660 respBytes , err := conn .read (r )
619661 if err != nil {
620- return resp , fmt .Errorf ("read error %w" , err )
662+ return resp , fmt .Errorf ("read error: %w" , err )
621663 }
622664 resp = Response {buf : smallBuf {b : respBytes }}
623665 err = resp .decodeHeader (conn .dec )
624666 if err != nil {
625- return resp , fmt .Errorf ("decode response header error %w" , err )
667+ return resp , fmt .Errorf ("decode response header error: %w" , err )
626668 }
627669 err = resp .decodeBody ()
628670 if err != nil {
629671 switch err .(type ) {
630672 case Error :
631673 return resp , err
632674 default :
633- return resp , fmt .Errorf ("decode response body error %w" , err )
675+ return resp , fmt .Errorf ("decode response body error: %w" , err )
634676 }
635677 }
636678 return resp , nil
@@ -645,6 +687,15 @@ func (conn *Connection) readAuthResponse(r io.Reader) (err error) {
645687 return nil
646688}
647689
690+ func (conn * Connection ) readIdResponse (r io.Reader ) (resp Response , err error ) {
691+ resp , err = conn .readResponse (r )
692+ if err != nil {
693+ return resp , fmt .Errorf ("identify: %w" , err )
694+ }
695+
696+ return resp , nil
697+ }
698+
648699func (conn * Connection ) createConnection (reconnect bool ) (err error ) {
649700 var reconnects uint
650701 for conn .c == nil && conn .state == connDisconnected {
@@ -1188,3 +1239,97 @@ func (conn *Connection) NewStream() (*Stream, error) {
11881239 Conn : conn ,
11891240 }, nil
11901241}
1242+
1243+ // checkProtocolVersion checks that expected protocol version is supported.
1244+ func checkProtocolVersion (expected ProtocolVersion , actual ProtocolVersion ) error {
1245+ if expected > actual {
1246+ return fmt .Errorf ("protocol version %d is not supported" , expected )
1247+ }
1248+
1249+ return nil
1250+ }
1251+
1252+ // checkProtocolFeatures checks that expected protocol features are supported.
1253+ func checkProtocolFeatures (expectedList []ProtocolFeature , actualList []ProtocolFeature ) error {
1254+ var found bool
1255+ // It seems that iterating over a small list is way faster
1256+ // than building a map: https://stackoverflow.com/a/52710077/11646599
1257+ for _ , expected := range expectedList {
1258+ found = false
1259+ for _ , actual := range actualList {
1260+ if expected == actual {
1261+ found = true
1262+ }
1263+ }
1264+ if ! found {
1265+ // "Feature" already is a part of the String().
1266+ return fmt .Errorf ("%s is not supported" , expected )
1267+ }
1268+ }
1269+
1270+ return nil
1271+ }
1272+
1273+ // identify sends info about client protocol, receives info
1274+ // about server protocol in response and store it in the connection.
1275+ func (conn * Connection ) identify (w * bufio.Writer , r * bufio.Reader ) error {
1276+ var ok bool
1277+
1278+ werr := conn .writeIdRequest (w , conn .ClientProtocolVersion (), conn .ClientProtocolFeatures ())
1279+ if werr != nil {
1280+ return werr
1281+ }
1282+
1283+ resp , rerr := conn .readIdResponse (r )
1284+ if rerr != nil {
1285+ if resp .Code == ErrUnknownRequestType {
1286+ // IPROTO_ID requests are not supported by server.
1287+ return nil
1288+ }
1289+
1290+ return rerr
1291+ }
1292+
1293+ if len (resp .Data ) == 0 {
1294+ return fmt .Errorf ("identify: unexpected response: no data" )
1295+ }
1296+
1297+ conn .serverProtocolInfo , ok = resp .Data [0 ].(ProtocolInfo )
1298+ if ! ok {
1299+ return fmt .Errorf ("identify: unexpected response: wrong data" )
1300+ }
1301+
1302+ return nil
1303+ }
1304+
1305+ // ServerProtocolVersion returns protocol version supported by
1306+ // connected Tarantool server. Beware that values might be outdated
1307+ // if connection is in a disconnected state.
1308+ // Since 1.10.0
1309+ func (conn * Connection ) ServerProtocolVersion () ProtocolVersion {
1310+ return conn .serverProtocolInfo .Version
1311+ }
1312+
1313+ // ClientProtocolVersion returns protocol version supported by
1314+ // Go connection client.
1315+ // Since 1.10.0
1316+ func (conn * Connection ) ClientProtocolVersion () ProtocolVersion {
1317+ return сlientProtocolVersion
1318+ }
1319+
1320+ // ServerFeatures returns protocol features supported by connected Tarantool server.
1321+ // Beware that values might be outdated if connection is in a disconnected state.
1322+ // Since 1.10.0
1323+ func (conn * Connection ) ServerProtocolFeatures () []ProtocolFeature {
1324+ res := make ([]ProtocolFeature , len (conn .serverProtocolInfo .Features ))
1325+ copy (res , conn .serverProtocolInfo .Features )
1326+ return res
1327+ }
1328+
1329+ // ClientFeatures returns protocol features supported by Go connection client.
1330+ // Since 1.10.0
1331+ func (conn * Connection ) ClientProtocolFeatures () []ProtocolFeature {
1332+ res := make ([]ProtocolFeature , len (сlientProtocolFeatures ))
1333+ copy (res , сlientProtocolFeatures )
1334+ return res
1335+ }
0 commit comments