Source File
message_id.go
Belonging Package
github.com/gotd/td/internal/proto
package proto
import (
)
// Message identifiers are coupled to message creation time.
//
// https://core.telegram.org/mtproto/description#message-identifier-msg-id
const (
yieldClient = 0
yieldServerResponse = 1
yieldFromServer = 3
messageIDModulo = 4
)
func ( int64, int) int64 {
const = 1e9
// Must approximately equal unixtime*2^32.
// Important: to counter replay-attacks the lower 32 bits of msg_id
// passed by the client must not be empty and must present a
// fractional part of the time point when the message was created.
:= /
:= %
// Ensure that fracPart % 4 == 0.
&= -messageIDModulo
// Adding modulo 4 yield to ensure message type.
+= int64()
return ( << 32) |
}
// MessageID represents 64-bit message id.
type MessageID int64
func ( MessageID) () string {
return fmt.Sprintf("%x (%s, %s)",
int64(), .Type(), .Time().Format(time.RFC3339),
)
}
// MessageType is type of message determined by message id.
//
// A message is rejected over 300 seconds after it is created or
// 30 seconds before it is created (this is needed to protect from replay attacks).
//
// The identifier of a message container must be strictly greater than those of
// its nested messages.
type MessageType byte
const (
// MessageUnknown reports that message id has unknown time and probably
// should be ignored.
MessageUnknown MessageType = iota
// MessageFromClient is client message identifiers.
MessageFromClient
// MessageServerResponse is a response to a client message.
MessageServerResponse
// MessageFromServer is a message from the server.
MessageFromServer
)
func ( MessageType) () string {
switch {
case MessageFromClient:
return "FromClient"
case MessageServerResponse:
return "ServerResponse"
case MessageFromServer:
return "FromServer"
default:
return "Unknown"
}
}
// Time returns approximate time when MessageID were generated.
func ( MessageID) () time.Time {
:= int64() >> 32
:= int64(int32())
return time.Unix(, ).UTC()
}
// Type returns message type.
func ( MessageID) () MessageType {
switch % messageIDModulo {
case yieldClient:
return MessageFromClient
case yieldServerResponse:
return MessageServerResponse
case yieldFromServer:
return MessageFromServer
default:
return MessageUnknown
}
}
// NewMessageID returns new message id for provided time and type.
func ( time.Time, MessageType) MessageID {
return NewMessageIDNano(.UnixNano(), )
}
// NewMessageIDNano returns new message id for provided current unix
// nanoseconds and type.
func ( int64, MessageType) MessageID {
var int
switch {
case MessageFromClient:
= yieldClient
case MessageFromServer:
= yieldFromServer
case MessageServerResponse:
= yieldServerResponse
default:
= yieldClient
}
return MessageID(newMessageID(, ))
}
// MessageIDGen is message id generator that provides collision prevention.
//
// The main reason of such structure is that now() can return same time during
// multiple calls and that leads to duplicate message id.
type MessageIDGen struct {
mux sync.Mutex
nano int64
now func() time.Time
}
// New generates new message id for provided type, protecting from collisions
// that are caused by low system time resolution.
func ( *MessageIDGen) ( MessageType) int64 {
.mux.Lock()
defer .mux.Unlock()
// Minimum resolution is required because id is only approximately
// equal to unix nano time, some part is replaced by message type.
const = 10
:= .now().UnixNano()
if > .nano {
.nano =
} else {
.nano +=
}
return int64(NewMessageIDNano(.nano, ))
}
// NewMessageIDGen creates new message id generator.
//
// Current time will be provided by now() function.
//
// This generator compensates time resolution problem removing
// probability of id collision.
//
// Such problem can be observed for relatively high RPS, sequential calls to
// time.Now() will return same time which leads to equal ids.
func ( func() time.Time) *MessageIDGen {
return &MessageIDGen{
now: ,
}
}
// MessageIDBuf stores last N message ids and is used in replay attack mitigation.
type MessageIDBuf struct {
mux sync.Mutex
buf []int64
}
// NewMessageIDBuf initializes new message id buffer for last N stored values.
func ( int) *MessageIDBuf {
return &MessageIDBuf{
buf: make([]int64, ),
}
}
// Consume returns false if message should be discarded.
func ( *MessageIDBuf) ( int64) bool {
// In addition, the identifiers (msg_id) of the last N messages received
// from the other side must be stored, and if a message comes in with an
// msg_id lower than all or equal to any of the stored values, that message
// is to be ignored. Otherwise, the new message msg_id is added to the set,
// and, if the number of stored msg_id values is greater than N, the oldest
// (i. e. the lowest) is discarded.
//
// https://core.telegram.org/mtproto/security_guidelines#checking-msg-id
.mux.Lock()
defer .mux.Unlock()
var (
int
int64
)
for , := range .buf {
if == {
// Equal to stored value.
return false
}
// Searching for minimum value.
if < {
=
=
}
}
if < {
// Lower than all stored values.
return false
}
// Message is accepted. Replacing lowest message id with new id.
.buf[] =
return true
}
The pages are generated with Golds v0.6.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |