#tl #traits #data-encoding #deserialize #collection #serialization #proto

tl-proto

一组用于处理TL序列化和反序列化的特性行为

37个版本

0.4.7 2024年7月18日
0.4.6 2024年3月25日
0.4.4 2023年11月28日
0.4.2 2023年3月31日
0.1.10 2022年3月14日

#400编码

Download history 107/week @ 2024-04-28 37/week @ 2024-05-05 31/week @ 2024-05-12 74/week @ 2024-05-19 86/week @ 2024-05-26 125/week @ 2024-06-02 87/week @ 2024-06-09 63/week @ 2024-06-16 80/week @ 2024-06-23 50/week @ 2024-06-30 78/week @ 2024-07-07 220/week @ 2024-07-14 190/week @ 2024-07-21 58/week @ 2024-07-28 92/week @ 2024-08-04 109/week @ 2024-08-11

每月467次下载
用于5个crate(直接使用3个)

MIT许可证

51KB
1.5K SLoC

tl-proto   最新版本 tl-proto: rustc 1.56+ Workflow徽章 许可证 MIT徽章

一组用于处理TL序列化和反序列化的特性行为。

示例

/* 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;

注意:TL方案在编译时由tl-scheme crate解析。它不涵盖完整的TL语法,但对于大多数情况来说已经足够了。

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: &[u8], offset: &mut usize) -> tl_proto::TlResult<u64> {
        let shard = u64::read_from(packet, offset)?;
        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,
        }
    ));
}

规范

类型 伪代码
() []
i32,u32,i64,u64 little_endian(x)
true [0xb5, 0x75, 0x72, 0x99]
false [0x37, 0x97, 0x79, 0xbc]
[u8;N],N% 40) […x]
Vec<u8>,len< 254) [lenas 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(lenas 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)] }

依赖项

~0.7–1.7MB
~37K SLoC