diff --git a/internal/mtproto/messages/messages.go b/internal/mtproto/messages/messages.go index c4c9b75..94365d1 100644 --- a/internal/mtproto/messages/messages.go +++ b/internal/mtproto/messages/messages.go @@ -18,6 +18,7 @@ import ( ige "github.com/xelaj/mtproto/internal/aes_ige" "github.com/xelaj/mtproto/internal/encoding/tl" + "github.com/xelaj/mtproto/internal/mtproto" "github.com/xelaj/mtproto/internal/utils" ) @@ -30,7 +31,7 @@ type Common interface { type Encrypted struct { Msg []byte - MsgID int64 + MsgID mtproto.MsgID AuthKeyHash []byte Salt int64 @@ -82,7 +83,7 @@ func DeserializeEncrypted(data, authKey []byte) (*Encrypted, error) { } msg.Salt = d.PopLong() msg.SessionID = d.PopLong() - msg.MsgID = d.PopLong() + msg.MsgID = mtproto.MsgID(d.PopLong()) msg.SeqNo = d.PopInt() messageLen := d.PopInt() @@ -119,7 +120,7 @@ func (msg *Encrypted) GetSeqNo() int { type Unencrypted struct { Msg []byte - MsgID int64 + MsgID mtproto.MsgID } func (msg *Unencrypted) Serialize(client MessageInformator) ([]byte, error) { @@ -127,7 +128,7 @@ func (msg *Unencrypted) Serialize(client MessageInformator) ([]byte, error) { e := tl.NewEncoder(buf) // authKeyHash, always 0 if unencrypted e.PutLong(0) - e.PutLong(msg.MsgID) + e.PutLong(int64(msg.MsgID)) e.PutInt(int32(len(msg.Msg))) e.PutRawBytes(msg.Msg) return buf.Bytes(), nil @@ -138,7 +139,7 @@ func DeserializeUnencrypted(data []byte) (*Unencrypted, error) { d, _ := tl.NewDecoder(bytes.NewBuffer(data)) _ = d.PopRawBytes(tl.LongLen) // authKeyHash, always 0 if unencrypted - msg.MsgID = d.PopLong() + msg.MsgID = mtproto.MsgID(d.PopLong()) mod := msg.MsgID & 3 if mod != 1 && mod != 3 { @@ -183,7 +184,7 @@ type MessageInformator interface { GetAuthKey() []byte } -func serializePacket(client MessageInformator, msg []byte, messageID int64, requireToAck bool) []byte { +func serializePacket(client MessageInformator, msg []byte, id mtproto.MsgID, requireToAck bool) []byte { buf := bytes.NewBuffer(nil) d := tl.NewEncoder(buf) @@ -191,7 +192,7 @@ func serializePacket(client MessageInformator, msg []byte, messageID int64, requ binary.LittleEndian.PutUint64(saltBytes, uint64(client.GetServerSalt())) d.PutRawBytes(saltBytes) d.PutLong(client.GetSessionID()) - d.PutLong(messageID) + d.PutLong(int64(id)) if requireToAck { // не спрашивай, как это работает d.PutInt(client.GetSeqNo() | 1) // почему тут добавляется бит не ебу } else { diff --git a/internal/mtproto/msg_id.go b/internal/mtproto/msg_id.go new file mode 100644 index 0000000..a7b35ea --- /dev/null +++ b/internal/mtproto/msg_id.go @@ -0,0 +1,53 @@ +package mtproto + +import ( + "crypto/rand" + "encoding/binary" + "math" + "time" +) + +const ( + minDiff = 300 * time.Second + maxDiff = -30 * time.Second +) + +type MsgTyp uint8 + +const ( + MsgClient MsgTyp = 0b00 + MsgServerResp MsgTyp = 0b01 + MsgServerUpd MsgTyp = 0b11 +) + +// https://core.telegram.org/mtproto/description#message-identifier-msg-id +// 1-32: current unix time +// 33-62: random bits +// 63: for server-side only: 0 if msg is answer to request, 1 if server update +// 64: 1 if msg server-side, 0 if client-side +type MsgID uint64 + +func (m MsgID) At() time.Time { return time.Unix(int64(m)>>32, 0) } + +func (m MsgID) IsValid(at time.Time) bool { + t := m.At().Sub(at) + return minDiff > t && t > maxDiff +} + +func NewMsgID(at time.Time, typ MsgTyp) MsgID { + if typ >= 4 { + panic("invalid type") + } + + timePart := uint64(at.Unix()) << 32 + randPart := randUint32() & (math.MaxUint32 - 3) + return MsgID(timePart) | MsgID(randPart) | MsgID(typ) +} + +func randUint32() uint32 { + b := make([]byte, 4) + if _, err := rand.Read(b); err != nil { + panic("unreachable") // to be sure that we will never get any problems + } + return binary.LittleEndian.Uint32(b) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 3de36d8..165ccc0 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -18,12 +18,16 @@ import ( // GenerateMessageId отдает по сути unix timestamp но ужасно специфическим образом // TODO: нахуя нужно битовое и на -4?? -func GenerateMessageId() int64 { +func GenerateMessageId(prevID int64) int64 { const billion = 1000 * 1000 * 1000 unixnano := time.Now().UnixNano() seconds := unixnano / billion nanoseconds := unixnano % billion - return (seconds << 32) | (nanoseconds & -4) + newID := (seconds << 32) | (nanoseconds & -4) + if newID <= prevID { + return GenerateMessageId(prevID) + } + return newID } func AuthKeyHash(key []byte) []byte { diff --git a/network.go b/network.go index e583c32..8b96461 100644 --- a/network.go +++ b/network.go @@ -8,14 +8,15 @@ package mtproto import ( "reflect" "strconv" + "time" "github.com/pkg/errors" "github.com/xelaj/errs" "github.com/xelaj/mtproto/internal/encoding/tl" + "github.com/xelaj/mtproto/internal/mtproto" "github.com/xelaj/mtproto/internal/mtproto/messages" "github.com/xelaj/mtproto/internal/mtproto/objects" - "github.com/xelaj/mtproto/internal/utils" ) func (m *MTProto) sendPacket(request tl.Object, expectedTypes ...reflect.Type) (chan tl.Object, error) { @@ -26,7 +27,7 @@ func (m *MTProto) sendPacket(request tl.Object, expectedTypes ...reflect.Type) ( var ( data messages.Common - msgID = utils.GenerateMessageId() + msgID = mtproto.NewMsgID(time.Now(), mtproto.MsgClient) ) // adding types for parser if required