https://github.com/broxus/tl-proto
A collection of traits for working with TL
https://github.com/broxus/tl-proto
everscale tl
Last synced: 6 months ago
JSON representation
A collection of traits for working with TL
- Host: GitHub
- URL: https://github.com/broxus/tl-proto
- Owner: broxus
- License: mit
- Created: 2021-12-22T18:40:50.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-03-25T12:57:21.000Z (about 1 year ago)
- Last Synced: 2024-04-26T14:41:40.522Z (about 1 year ago)
- Topics: everscale, tl
- Language: Rust
- Homepage: https://docs.rs/tl-proto
- Size: 265 KB
- Stars: 4
- Watchers: 4
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
## tl-proto [![Latest Version]][crates.io] [![tl-proto: rustc 1.56+]][Rust 1.56] [![Workflow badge]][Workflow] [![License MIT badge]][License MIT]
[Latest Version]: https://img.shields.io/crates/v/tl-proto.svg
[crates.io]: https://crates.io/crates/tl-proto
[tl-proto: rustc 1.56+]: https://img.shields.io/badge/rustc-1.56+-lightgray.svg
[Rust 1.56]: https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html
[Workflow badge]: https://img.shields.io/github/actions/workflow/status/broxus/tl-proto/master.yml?branch=master
[Workflow]: https://github.com/broxus/tl-proto/actions?query=workflow%3Amaster
[License MIT badge]: https://img.shields.io/badge/license-MIT-blue.svg
[License MIT]: https://opensource.org/licenses/MIT
A collection of traits for working with [TL](https://core.telegram.org/mtproto/TL) serialization/deserialization.
### Example
```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`](./scheme) crate at compile time.
> It doesn't cover full TL grammer, but it's enough for most of the cases.```rust,ignore
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(v: &T, p: &mut P);`
/// For `TlRead`:
/// - `fn read(packet: &[u8], offset: &mut usize) -> TlResult;`
///
/// `write_with` must point to a function with the following signature:
/// `fn write(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;`
#[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(shard: &u64, packet: &mut P) {
shard.write_to(packet);
}pub fn read(packet: &mut &[u8]) -> tl_proto::TlResult {
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,
/// 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,// Or you can use the shorter syntax:
//
// #[tl(flags_bit = "mode.2")]
// utime: Option,
}#[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
/// `::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>(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::
(&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, len < 254`) |[len as u8, …x, …padding_to_4(len)]
|
| `Vec, len ≥ 254`) |[254, …little_endian(x)[0..=2], …x, …padding_to_4(len)]
|
| `Vec` | `[…little_endian(len as u32), …map(…x, repr)]` |
| `(T0, … , Tn)` | `[…repr(T0), … , …repr(Tn)]` |
| `Option` | `{ Some(x) ⇒ repr(x), None ⇒ [] }` |
| `enum { T0, …, Tn }` | `{ T0(x) ⇒ […id(T0), …repr(x)], …, Tn(x) ⇒ […id(Tn), …repr(x)] }` |