diff --git a/socketserver/server/commands.go b/socketserver/server/commands.go index 4dcebd2d..d9df3fd2 100644 --- a/socketserver/server/commands.go +++ b/socketserver/server/commands.go @@ -84,6 +84,8 @@ func callHandler(handler CommandHandler, conn *websocket.Conn, client *ClientInf return handler(conn, client, cmsg) } +var lastVersionWithoutReplyWithServerTime = VersionFromString("ffz_3.5.78") + // C2SHello implements the `hello` C2S Command. // It calls SubscribeGlobal() and SubscribeDefaults() with the client, and fills out ClientInfo.Version and ClientInfo.ClientID. func C2SHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { @@ -92,7 +94,9 @@ func C2SHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg return } - client.Version = version + client.VersionString = version + client.Version = VersionFromString(version) + client.ClientID = uuid.FromStringOrNil(clientID) if client.ClientID == uuid.Nil { client.ClientID = uuid.NewV4() @@ -101,9 +105,18 @@ func C2SHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg SubscribeGlobal(client) SubscribeDefaults(client) - return ClientMessage{ - Arguments: client.ClientID.String(), - }, nil + if client.Version.After(lastVersionWithoutReplyWithServerTime) { + return ClientMessage{ + Arguments: []interface{}{ + client.ClientID.String(), + time.Now().Unix(), + }, + } + } else { + return ClientMessage{ + Arguments: client.ClientID.String(), + }, nil + } } func C2SReady(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rmsg ClientMessage, err error) { diff --git a/socketserver/server/handlecore.go b/socketserver/server/handlecore.go index fd3c8c6f..4157a8fa 100644 --- a/socketserver/server/handlecore.go +++ b/socketserver/server/handlecore.go @@ -261,7 +261,7 @@ RunLoop: break RunLoop case msg := <-clientChan: - if client.Version == "" && msg.Command != HelloCommand { + if client.VersionString == "" && msg.Command != HelloCommand { CloseConnection(conn, &CloseFirstMessageNotHello) Statistics.FirstNotHelloDisconnects++ break RunLoop diff --git a/socketserver/server/types.go b/socketserver/server/types.go index 7a3b2f99..8dbf5ca2 100644 --- a/socketserver/server/types.go +++ b/socketserver/server/types.go @@ -6,6 +6,7 @@ import ( "net" "sync" "time" + "fmt" ) const CryptoBoxKeyLength = 32 @@ -55,14 +56,22 @@ type AuthInfo struct { UsernameValidated bool } +type ClientVersion struct { + Major int + Minor int + Revision int +} + type ClientInfo struct { // The client ID. // This must be written once by the owning goroutine before the struct is passed off to any other goroutines. ClientID uuid.UUID - // The client's version. + // The client's literal version string. // This must be written once by the owning goroutine before the struct is passed off to any other goroutines. - Version string + VersionString string + + Version ClientVersion // This mutex protects writable data in this struct. // If it seems to be a performance problem, we can split this. @@ -103,6 +112,36 @@ type ClientInfo struct { pingCount int } +func VersionFromString(v string) ClientVersion { + var cv ClientVersion + fmt.Sscanf(v, "ffz_%d.%d.%d", &cv.Major, &cv.Minor, &cv.Revision) + return cv +} + +func (cv *ClientVersion) After(cv2 *ClientVersion) bool { + if cv.Major > cv2.Major { + return true + } else if cv.Major < cv2.Major { + return false + } + if cv.Minor > cv2.Minor { + return true + } else if cv.Minor < cv2.Minor { + return false + } + if cv.Revision > cv2.Revision { + return true + } else if cv.Revision < cv2.Revision { + return false + } + + return false // equal +} + +func (cv *ClientVersion) Equal(cv2 *ClientVersion) bool { + return cv.Major == cv2.Major && cv.Minor == cv2.Minor && cv.Revision == cv2.Revision +} + const usePendingSubscrptionsBacklog = false type tgmarray []TimestampedGlobalMessage