This repository has been archived by the owner on Aug 30, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: ======== 1. add a `Header` type for the common fields 2. add a `LengthValueBuffer` type to handle the variable length fields 3. decouple the crypto and parsing parts 4. add a `Message` type that wraps sum, update and sum2 messages 5. have an `Owned` and a `Borrowed` version of every type 7. small bug fixes & improvements: - not using `usize` as a field since it's platform dependent - detection of truncated local seed dictionary - `XxxBuffer` does not allocate a `Vec` - remove impls for specific types (`impl TryFrom<Vec<u8>> for XxxBuffer`, `impl XxxBuffer<Vec<u8>>` etc) - make the `XxxBuffer()` APIs more consistent, opening the door to code generation via macros - use a custom `DecodeError` type that contains the whole stack of errors, which makes debugging easier Details: ======== 1. Introduce a `Header` type for the common fields -------------------------------------------------- All the messages share common fields. In networking protocol, these common fields are usually handled separately from the rest of the message. They are usually called headers, and the rest of the message is the payload. We defined the following header: ```rust /// A header, common to all the message pub struct Header<CK, PK, C> { /// Type of message pub tag: Tag, /// Coordinator public key pub coordinator_pk: CK, /// Participant public key pub participant_pk: PK, /// A certificate that identifies the author of the message pub certificate: Option<C>, } ``` Currently the code to handle these common fields lives in the `MessageBuffer` trait which is implemented for each message type, thus reducing boilerplate for parsing these fields. I can see several downsides to using a trait for this though: a. the `serialize()` and `deserialize()` methods must call the `MessageBuffer` methods for each of these common fields. b. it is difficult to handle variable length fields in the header (which maybe is why the optional certificate is handled by each message separately?) c. tests require full messages `a.` is not too much of a problem, and I'm not sure to what extent `b.` holds true. But removing the header logic does lead to much simpler tests. 2. Add a `LengthValueBuffer` type to handle the variable length fields ---------------------------------------------------------------------- This reduces error prone boilerplate, like we had in `update.rs`: ```rust if buffer.len() >= Self::LOCAL_SEED_DICT_LEN_RANGE.end { buffer.certificate_range = Self::LOCAL_SEED_DICT_LEN_RANGE.end ..Self::LOCAL_SEED_DICT_LEN_RANGE.end + usize::from_le_bytes(buffer.certificate_len().try_into().unwrap()); buffer.masked_model_range = buffer.certificate_range.end ..buffer.certificate_range.end + usize::from_le_bytes(buffer.masked_model_len().try_into().unwrap()); buffer.local_seed_dict_range = buffer.masked_model_range.end ..buffer.masked_model_range.end + usize::from_le_bytes(buffer.local_seed_dict_len().try_into().unwrap()); } ``` This also allows us to automate the serialization/deserialization of length-variable types like masks, certificates and masked models (see `impl_traits_for_length_value_types!`) 3. decouple the crypto and parsing parts ---------------------------------------- The message signature and encryption is the very last/first step for every message, and is always exactly the same, so it makes sense to keep it separate. Therefore we removed the `open()` and `seal()` methods for the messages themself and moved the logic to two dedicated types: `MessageSeal` for signing and encrypting messages, and `MessageOpener` for decrypting and verifying message signatures. With this, we could now move the crypto logic to the _transport_ layer, and only handle fully fledged messages in the business logic layer. 4. add a `Message` type that wraps sum, update and sum2 messages ---------------------------------------------------------------- ```rust pub struct Message { pub header: Header, pub payload: Payload, } pub enum Payload { Sum(SumMessage), Sum2(Sum2Message), Update(UpdateMessage), } ``` This is actually needed if we want to move message serialization/deserialization to Tokio. By doing so, we don't have to repeat the same code for the fields that are common to all the messages. 5. have an `Owned` and a `Borrowed` version of every type --------------------------------------------------------- Each type comes in two flavours: `Owned` and `Borrowed`. An `XxxOwned` type _owns_ their fields, while an `XxxBorrowed` type may only have references to some of the fields. The `Borrowed` variant is needed because we have some potentially large fields that we don't want to clone when emitting a message. For instance, it would be wasteful for an update participant sending an update message with a large local seed dictionary to clone that dictionary.
- Loading branch information