Hi there, I'm writing a protocol that operates over TCP. Some time down the line I'll be very interested to post a full code review on my code and the protocol itself, but right now it's too young for that. Also keep in mind that this is my first heavy project in Rust or any systems language.
These are the building blocks of my protocol:
// Types of messages sent
#[derive(FromPrimitive, ToPrimitive, Debug, PartialEq, Serialize, Deserialize)]
pub enum Type {
DscReq= 1, // Discovery Request
DscRes= 2, // Discover Response
SeqReq= 3, // Sequence Request
SeqRes= 4, // Sequence Response
}
// Header of a message
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Header {
pub id: u16,
pub size: u16,
pub meta: u16,
pub parcel_type: Type, // u16
}
impl Header {
pub fn new(id: u16, size: u16, meta: u16, parcel_type: Type) -> Self {
Header { id, size, meta, parcel_type }
}
}
// Body of a Message with the different types
#[derive(Serialize, Deserialize)]
pub enum Body {
DscReq { identity: Node },
DscRes {
size: u16,
neighbours: Vec<Node>,
},
}
#[derive(Serialize, Deserialize)]
pub struct ProtoParcel {
pub header: Header,
pub body: Body,
}
And this is what I'm trying to implement.
I'm open to all types of anecdotes, architectural and code-based. I started out with doing manual byte serialization/deserialization but then realized that serde does that for you. I thought it would be a good idea that the protocol has an 8-byte header and variable sized body, followed by the application-specific bytes. Ignore what is said in the protocol doc about byte ordering, I'll probably stick to a widely-used standard for serialization such as CBOR or MessagePack and ditch bincode since it's too language/library-specific. My protocol approach is probably bad and I should just forget the whole header/body abstraction and use a single struct/enum for the whole message. Any thoughts on this?
This has turned into more of a code-review for now but there's still a lot of things I need to figure out.
My general programming question is: How should I structure my message abstraction properly while still keeping protocol correctness? As in, I still want my header to contain a Type
field that defines the body that follows but I don't want that field to belong to the Parcel
(the whole message). And how would I implement the following rusty non-compileable pseudocode:
let mut res_parcel: ProtoParcel = bincode::deserialize(res.as_slice()).unwrap();
let body = res_parcel.body as Body::DscRes; // DscRes expected
Ok(body.neighbours) // return field
I also want to say that I've been really enjoying the language and system programming as a whole!