diff --git a/mtproto/rpc/rpc.go b/mtproto/rpc/rpc.go new file mode 100644 index 0000000..dc07a21 --- /dev/null +++ b/mtproto/rpc/rpc.go @@ -0,0 +1,5 @@ +package rpc + +type RPC interface { + Bytes() []byte +} diff --git a/mtproto/rpc/rpc_nonce_request.go b/mtproto/rpc/rpc_nonce_request.go new file mode 100644 index 0000000..8dd29e4 --- /dev/null +++ b/mtproto/rpc/rpc_nonce_request.go @@ -0,0 +1,67 @@ +package rpc + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "time" + + "github.com/juju/errors" +) + +const ( + RPCNonceSeqNo = -2 + + rpcNonceLength = 16 + rpcNonceKeySelectorLength = 4 + rpcNonceCryptoTSLength = 4 + rpcNonceTagLength = 4 + rpcNonceCryptoAESLength = 4 + + rpcNonceRequestLength = rpcNonceTagLength + rpcNonceKeySelectorLength + + rpcNonceCryptoAESLength + rpcNonceCryptoTSLength + rpcNonceLength +) + +var ( + rpcNonceTag = [rpcNonceTagLength]byte{0xaa, 0x87, 0xcb, 0x7a} + rpcNonceCryptoAESTag = [rpcNonceCryptoAESLength]byte{0x01, 0x00, 0x00, 0x00} +) + +type RPCNonceRequest struct { + KeySelector [rpcNonceKeySelectorLength]byte + CryptoTS [rpcNonceCryptoTSLength]byte + Nonce [rpcNonceLength]byte +} + +func (r *RPCNonceRequest) Bytes() []byte { + buf := &bytes.Buffer{} + buf.Grow(rpcNonceRequestLength) + + buf.Write(rpcNonceTag[:]) + buf.Write(r.KeySelector[:]) + buf.Write(rpcNonceCryptoAESTag[:]) + buf.Write(r.CryptoTS[:]) + buf.Write(r.Nonce[:]) + + return buf.Bytes() +} + +func NewRPCNonceRequest(proxySecret []byte) (*RPCNonceRequest, error) { + var nonce [rpcNonceLength]byte + var keySelector [rpcNonceKeySelectorLength]byte + var cryptoTS [rpcNonceCryptoTSLength]byte + + if _, err := rand.Read(nonce[:]); err != nil { + return nil, errors.Annotate(err, "Cannot generate nonce") + } + copy(keySelector[:], proxySecret) + + timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name + binary.LittleEndian.PutUint32(cryptoTS[:], uint32(timestamp)) + + return &RPCNonceRequest{ + KeySelector: keySelector, + CryptoTS: cryptoTS, + Nonce: nonce, + }, nil +} diff --git a/mtproto/rpc/rpc_nonce_response.go b/mtproto/rpc/rpc_nonce_response.go new file mode 100644 index 0000000..7cf90fc --- /dev/null +++ b/mtproto/rpc/rpc_nonce_response.go @@ -0,0 +1,58 @@ +package rpc + +import ( + "bytes" + + "github.com/juju/errors" +) + +const rpcNonceResponseLength = rpcNonceRequestLength + +type RPCNonceResponse struct { + RPCNonceRequest + + RPCType [rpcNonceTagLength]byte + Crypto [rpcNonceCryptoAESLength]byte +} + +func (r *RPCNonceResponse) Bytes() []byte { + buf := &bytes.Buffer{} + buf.Grow(rpcNonceResponseLength) + + buf.Write(r.RPCType[:]) + buf.Write(r.KeySelector[:]) + buf.Write(r.Crypto[:]) + buf.Write(r.CryptoTS[:]) + buf.Write(r.Nonce[:]) + + return buf.Bytes() +} + +func (r *RPCNonceResponse) Valid(req *RPCNonceRequest) error { + if r.RPCType != rpcNonceTag { + return errors.New("Unexpected RPC type") + } + if r.Crypto != rpcNonceCryptoAESTag { + return errors.New("Unexpected crypto type") + } + if r.KeySelector != req.KeySelector { + return errors.New("Unexpected key selector") + } + + return nil +} + +func NewRPCNonceResponse(data []byte) (*RPCNonceResponse, error) { + if len(data) != rpcNonceResponseLength { + return nil, errors.New("Unexpected message length") + } + + resp := RPCNonceResponse{} + copy(resp.RPCType[:], data[:4]) + copy(resp.KeySelector[:], data[4:8]) + copy(resp.Crypto[:], data[8:12]) + copy(resp.CryptoTS[:], data[12:16]) + copy(resp.Nonce[:], data[16:]) + + return &resp, nil +}