A collection of traits for working with TL serialization/deserialization.
```text /* 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.
```rust 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.
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.
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] },
}
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>,
},
}
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 tlshard {
use tlproto::{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 tlproto::BoxedConstructor for BlockId {
/// There is a way to compute id of a variant at
/// compile time using the provided scheme
const TLID: u32 = tlproto::id!("liteServer.lookupBlock", scheme = "myproto.tl");
}
/// There s a way to have a struct with optional fields
struct LookupBlock {
/// At first, there must be a field, marked with flags
attribute.
///
/// NOTE 1: It must precede the fields that depend on it.
/// NOTE 2: At this moment you can only specify one such field.
#[tl(flags)]
mode: (),
id: BlockId,
/// Fields with flags_bit
attribute must be Option
s
#[tl(flagsbit = 1)]
lt: Option
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 ultrahash
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
asserteq!(PublicKey::TLID_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,
}
));
} ```
| 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, …paddingto4(len)]
|
| Vec<u8>, len ≥ 254
) | [254, …littleendian(x)[0..=2], …x, …paddingto_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)] }
|