Easy protocol definitions in Rust.
This crate adds a custom derive that can be added to types, allowing structured data to be sent and received from any IO stream. macro_rules
macros also exist which can be used to define sendable/receivable types too.
Networking is built-in, with special support for TCP and UDP.
The protocol you define can be used outside of networking too - see the Parcel::from_raw_bytes
and Parcel::raw_bytes
methods.
This crate also provides:
Parcel
sCheckout the examples folder for usage.
Add this to your Cargo.toml
:
[dependencies] protocol = "0.3" protocol-derive = "0.3"
The most interesting part here is the protocol::Parcel
trait. Any type that implements this trait can then be serialized to and from a byte stream. All primitive types, standard collections, tuples, and arrays implement this trait.
This crate becomes particularly useful when you define your own Parcel
types. You can use #[derive(Protocol)]
to do this, or you can use the define_composite_type!
macro instead. Note that in order for a type to implement Parcel
, it must also implement Clone
, Debug
, and PartialEq
.
#[derive(Parcel, Clone, Debug, PartialEq] pub struct Health(f32); #[derive(Parcel, Clone, Debug, PartialEq] pub struct SetPlayerPosition { pub position: (f32, f32), pub health: Health, pub values: Vec<String>, }
Any user-defined type can have the Parcel
trait automatically derived.
#[macro_use] extern crate protocol_derive; #[macro_use] extern crate protocol; #[derive(Protocol, Clone, Debug, PartialEq)] pub struct Handshake; #[derive(Protocol, Clone, Debug, PartialEq)] pub struct Hello { id: i64, data: Vec<u8>, } #[derive(Protocol, Clone, Debug, PartialEq)] pub struct Goodbye { id: i64, reason: String, } #[derive(Protocol, Clone, Debug, PartialEq)] pub struct Node { name: String, enabled: bool } // Defines a packet kind enum. define_packet_kind!(Packet: u32 { 0x00 => Handshake, 0x01 => Hello, 0x02 => Goodbye }); fn main() { use std::net::TcpStream; let stream = TcpStream::connect("127.0.0.1:34254").unwrap(); let mut connection = protocol::wire::stream::Connection::new(stream, protocol::wire::middleware::pipeline::default()); connection.send_packet(&Packet::Handshake(Handshake)).unwrap(); connection.send_packet(&Packet::Hello(Hello { id: 0, data: vec![ 55 ]})).unwrap(); connection.send_packet(&Packet::Goodbye(Goodbye { id: 0, reason: "leaving".to_string() })).unwrap(); loop { if let Some(response) = connection.receive_packet().unwrap() { println!("{:?}", response); break; } } }
Enum values can be transmitted either by their 1-based variant index, or by transmitting the string name of each variant.
NOTE: The default behaviour is to use the 1-based variant index (integer
).
This behaviour can be changed by the #[protocol(discriminant = "string")]
attribute.
Supported discriminant types:
integer
(default)string
N.B. string
should become the default discriminant type for #[derive(Protocol)]
. This would be a breaking change. Perhaps a major release?
#[derive(Protocol, Clone, Debug, PartialEq)] #[protocol(discriminant = "string")] pub enum PlayerState { Stationary, Flying { velocity: (f32,f32,f32) }, Jumping { height: f32 }, }
You can rename the variant for their serialisation.
#[derive(Protocol, Clone, Debug, PartialEq)] #[protocol(discriminant = "string")] pub enum Foo { Bar, #[protocol(name = "Biz")] // the Bing variant will be send/received as 'Biz'. Bing, Baz, }