Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

broxus/tl-proto

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

91 Commits
91 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tl-proto   Latest Version tl-proto: rustc 1.56+ Workflow badge License MIT badge

A collection of traits for working with TL serialization/deserialization.

Example

/* my_proto.tl */

int ? = Int;
long ? = Long;
string ? = String;
bytes data:string = Bytes;

int256 8*[ int ] = Int256;

pub.ed25519 key:int256 = PublicKey;
pub.aes key:int256 = PublicKey;
pub.overlay name:bytes = PublicKey;

adnl.address.udp ip:int port:int = adnl.Address;

tonNode.blockId workchain:int shard:long seqno:int = tonNode.BlockId;

--- functions ---

liteServer.lookupBlock mode:# id:tonNode.blockId lt:mode.1?long utime:mode.2?int = liteServer.BlockHeader;

NOTE: TL scheme is parsed by tl-scheme crate at compile time. It doesn't cover full TL grammer, but it's enough for most of the cases.

use tl_proto::{TlRead, TlWrite};

/// You can declare "bare" structs, which
/// doesn't have an associated TL id.
///
/// NOTE: enums can only be used as bare
/// with TlWrite, because there is no way to
/// know the exact variant without an id to
/// implement TlRead.
#[derive(TlRead, TlWrite)]
#[tl(size_hint = 32)]
struct HashRef<'tl>(&'tl [u8; 32]);

/// Or you can declare "boxed" structs, which
/// have one or more associated TL ids.
///
/// NOTE: in case of boxed enum with provided scheme,
/// all variants must have the same constructor kind
/// (all functions or all types). And if all variants
/// are types, they must refer to the same boxed type.
#[derive(TlRead, TlWrite)]
#[tl(boxed, scheme = "my_proto.tl")]
enum PublicKey<'tl> {
    /// `id` attribute is required for boxed enums.
    /// It can be either a raw id or a name of a variant
    /// from the scheme (in later case, `scheme` or `scheme_inline`
    /// container attribute is required).
    #[tl(id = "pub.aes")]
    Aes { key: HashRef<'tl> },

    /// `size_hint` is used to optimize `TlWrite::max_size_hint`
    /// implementation. If this attribute is specified, it
    /// will be used as is instead of computing the size of fields.
    #[tl(id = "pub.ed25519", size_hint = 32)]
    Ed25519 { key: HashRef<'tl> },

    /// Note that lifetime is called `'tl`, it is a special
    /// name which refers to the lifetime of the buffer
    /// from which this struct was deserialized.
    #[tl(id = "pub.overlay")]
    Overlay { name: &'tl [u8] },
}

#[derive(TlRead, TlWrite)]
#[tl(boxed)]
enum Address<'tl> {
    /// You can also specify raw id of a variant
    #[tl(id = 0x670da6e7)]
    Udp { ip: i32, port: i32 },

    #[tl(id = 0xe31d63fa)]
    Udp6 { ip: &'tl [u8; 16], port: i32 },

    #[tl(id = 0x092b02eb)]
    Tunnel {
        to: HashRef<'tl>,
        pubkey: PublicKey<'tl>,
    },
}

#[derive(TlRead, TlWrite)]
struct BlockId {
    workchain: i32,
    /// If you need custom deserialization logic for the field,
    /// you can specify either `with` attribute or separate
    /// `write_with`/`read_with` attributes.
    ///
    /// `with` must point to a module which must contain the
    /// following public functions:
    /// For `TlWrite`:
    ///   - `fn size_hint(v: &T) -> usize;`
    ///   - `fn write<P: TlPacket>(v: &T, p: &mut P);`
    /// For `TlRead`:
    ///   - `fn read(packet: &[u8], offset: &mut usize) -> TlResult<T>;`
    ///
    /// `write_with` must point to a function with the following signature:
    /// `fn write<P: TlPacket>(v: &T, p: &mut P);`
    /// NOTE: `write_with` requires `size_hint` attribute.
    ///
    /// `read_with` must point to a function with the following signature:
    /// `fn read(packet: &[u8], offset: &mut usize) -> TlResult<T>;`
    #[tl(with = "tl_shard")]
    shard: u64,
    seqno: u32,
}

/// `with` is similar to the same attribute in serde
mod tl_shard {
    use tl_proto::{TlPacket, TlRead, TlWrite};

    pub const fn size_hint(_: &u64) -> usize { 8 }

    pub fn write<P: TlPacket>(shard: &u64, packet: &mut P) {
        shard.write_to(packet);
    }

    pub fn read(packet: &mut &[u8]) -> tl_proto::TlResult<u64> {
        let shard = u64::read_from(packet)?;
        if shard % 10000 == 0 {
            Ok(shard)
        } else {
            Err(tl_proto::TlError::InvalidData)
        }
    }
}

/// You can also declare "bare" structs and specify
/// the type id of their boxed variant, so something
/// like `tl_proto::BoxedWrapper` can be used later.
///
/// See also:
/// - `tl_proto::deserialize_as_boxed` - read bare type as boxed
/// - `tl_proto::serialize_as_boxed` - write bare type as boxed
/// - `tl_proto::hash_as_boxed` - compute hash of the boxed repr
impl tl_proto::BoxedConstructor for BlockId {
    /// There is a way to compute id of a variant at
    /// compile time using the provided scheme
    const TL_ID: u32 = tl_proto::id!("liteServer.lookupBlock", scheme = "my_proto.tl");
}

/// There s a way to have a struct with optional fields
#[derive(TlRead, TlWrite)]
#[tl(boxed, id = "liteServer.lookupBlock", scheme = "my_proto.tl")]
struct LookupBlock {
    /// At first, there must be a field, marked with `flags` attribute.
    ///
    /// NOTE: It must precede the fields that depend on it.
    #[tl(flags)]
    mode: (),
    id: BlockId,
    /// Fields with `flags_bit` attribute must be `Option`s
    #[tl(flags_bit = 1)]
    lt: Option<u64>,
    /// You can also explicitly specify the flags field
    /// (e.g. when multiple fields with `flags` attribute are used)
    #[tl(flags_field = "mode", flags_bit = 2)]
    utime: Option<u32>,

    // Or you can use the shorter syntax:
    //
    // #[tl(flags_bit = "mode.2")]
    // utime: Option<u32>,
}

#[derive(TlWrite)]
struct StructWithSignature {
    value: u64,
    /// `signature` is used by `TlWrite` to simplify signature
    /// verification. In most cases you sign a data with an empty signature,
    /// so this attribute just writes `&[]` to the packet of type `P` if
    /// `<P as TlPacket>::TARGET == TlTarget::Hasher`
    #[tl(signature)]
    my_signature: [u8; 64],
}

/// You can constraint the type by its representation
/// (`tl_proto::Bare` / `tl_proto::Boxed`)
fn ultra_hash<T: TlWrite<Repr = tl_proto::Boxed>>(object: T) -> u32 {
    tl_proto::serialize(object).len() as u32
}

fn main() {
    // When the struct or enum has `TlRead` derive macro
    // and it is marked `boxed`, it also exposes
    // either `TL_ID` constant (in case of struct)
    // or `TL_ID_*` constants (in case of enum) where
    // `*` is a variant name in screaming snake case
    assert_eq!(PublicKey::TL_ID_AES, 0x2dbcadd4);

    let bytes = tl_proto::serialize(&Address::Udp {
        ip: 123,
        port: 3000,
    });

    let decoded = tl_proto::deserialize::<Address>(&bytes).unwrap();
    assert!(matches!(
        decoded,
        Address::Udp {
            ip: 123,
            port: 3000,
        }
    ));
}

Specification

Type Pseudocode
() []
i32,u32,i64,u64 little_endian(x)
true [0xb5, 0x75, 0x72, 0x99]
false [0x37, 0x97, 0x79, 0xbc]
[u8; N], N % 4 ≡ 0) […x]
Vec<u8>, len < 254) [len as u8, …x, …padding_to_4(len)]
Vec<u8>, len ≥ 254) [254, …little_endian(x)[0..=2], …x, …padding_to_4(len)]
Vec<T> […little_endian(len as u32), …map(…x, repr)]
(T0, … , Tn) […repr(T0), … , …repr(Tn)]
Option<T> { Some(x) ⇒ repr(x), None ⇒ [] }
enum { T0, …, Tn } { T0(x) ⇒ […id(T0), …repr(x)], …, Tn(x) ⇒ […id(Tn), …repr(x)] }

About

A collection of traits for working with TL

Topics

Resources

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.