connection_layer/lib.rs
1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/nimble-rust/workspace
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod prelude;
6mod client_to_host;
7mod host_to_client;
8mod host_codec;
9mod client_codec;
10
11use flood_rs::prelude::*;
12use hexify::format_hex_u32_be;
13use mash_rs::murmur3_32;
14use std::io;
15use std::io::{Error, ErrorKind, Result};
16
17pub type RequestId = u64; // So it is very likely that this number will change for each connection attempt
18
19
20/// A seed used for generating a [Murmur3 hash](https://en.wikipedia.org/wiki/MurmurHash#MurmurHash3) for connection validation.
21
22/// Represents a unique connection identifier for the session.
23#[derive(Eq, PartialEq, Copy, Clone, Default, Debug)]
24pub struct ConnectionId {
25 pub value: u8,
26}
27
28impl ConnectionId {
29 /// Writes the connection identifier to the provided output stream.
30 ///
31 /// # Arguments
32 ///
33 /// * `stream` - A mutable reference to a stream implementing `WriteOctetStream`.
34 ///
35 /// # Errors
36 ///
37 /// Returns an `io::Result` error if writing to the stream fails.
38 pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> Result<()> {
39 stream.write_u8(self.value)
40 }
41
42 /// Reads a connection identifier from the provided input stream.
43 ///
44 /// # Arguments
45 ///
46 /// * `stream` - A mutable reference to a stream implementing `ReadOctetStream`.
47 ///
48 /// # Returns
49 ///
50 /// A `Result` containing the `ConnectionId` if successful, or an `io::Result` error if reading fails.
51 pub fn from_stream(stream: &mut impl ReadOctetStream) -> Result<Self> {
52 Ok(Self {
53 value: stream.read_u8()?,
54 })
55 }
56}
57
58/// Represents the header of a connection with an ID and a Murmur3 hash.
59#[derive(Eq, PartialEq, Debug)]
60pub struct ConnectionLayer {
61 pub connection_id: ConnectionId,
62 pub murmur3_hash: u32,
63}
64
65/// Represents the mode of a connection layer, which can be either [Out-Of-Band (OOB)](https://en.wikipedia.org/wiki/Out-of-band_data) or an active connection.
66#[derive(Eq, PartialEq, Debug)]
67pub enum ConnectionLayerMode {
68 OOB,
69 Connection(ConnectionLayer),
70}
71
72impl ConnectionLayerMode {
73 /// Serializes the `ConnectionLayerMode` into the provided output stream.
74 ///
75 /// # Arguments
76 ///
77 /// * `stream` - A mutable reference to a stream implementing [`WriteOctetStream`].
78 ///
79 /// # Errors
80 ///
81 /// Returns an `io::Result` error if writing to the stream fails.
82 pub fn to_stream(&self, stream: &mut impl WriteOctetStream) -> Result<()> {
83 match self {
84 ConnectionLayerMode::OOB => ConnectionId::default().to_stream(stream),
85 ConnectionLayerMode::Connection(layer) => {
86 layer.connection_id.to_stream(stream)?;
87 stream.write_u32(layer.murmur3_hash)
88 }
89 }
90 }
91
92 /// Deserializes a `ConnectionLayerMode` from the provided input stream.
93 ///
94 /// # Arguments
95 ///
96 /// * `stream` - A mutable reference to a stream implementing [`ReadOctetStream`].
97 ///
98 /// # Returns
99 ///
100 /// A `Result` containing the `ConnectionLayerMode` if successful, or an `io::Result` error if reading fails.
101 pub fn from_stream(stream: &mut impl ReadOctetStream) -> Result<Self> {
102 let connection_id = ConnectionId::from_stream(stream)?;
103 let mode = match connection_id.value {
104 0 => ConnectionLayerMode::OOB,
105 _ => ConnectionLayerMode::Connection(ConnectionLayer {
106 connection_id,
107 murmur3_hash: stream.read_u32()?,
108 }),
109 };
110
111 Ok(mode)
112 }
113}
114
115#[derive(Debug, Copy, Clone)]
116pub struct ConnectionSecretSeed(u32);
117
118/// Writes a connection header and a payload to the provided stream, including a Murmur3 hash for validation.
119///
120/// # Arguments
121///
122/// * `stream` - A mutable reference to a stream implementing `WriteOctetStream`.
123/// * `connection_id` - The `ConnectionId` to write to the stream.
124/// * `seed` - A `ConnectionSecretSeed` used for generating the Murmur3 hash.
125/// * `payload` - The payload data to be written and hashed.
126///
127/// # Errors
128///
129/// Returns an `io::Result` error if writing to the stream fails.
130pub fn write_to_stream(
131 stream: &mut impl WriteOctetStream,
132 connection_id: ConnectionId,
133 seed: ConnectionSecretSeed,
134 payload: &[u8],
135) -> Result<()> {
136 let calculated_hash = murmur3_32(payload, seed.0);
137 ConnectionLayerMode::Connection(ConnectionLayer {
138 connection_id,
139 murmur3_hash: calculated_hash,
140 })
141 .to_stream(stream)
142}
143
144pub fn write_empty(stream: &mut impl WriteOctetStream) -> Result<()> {
145 let zero_connection_id = ConnectionId { value: 0 };
146 ConnectionLayerMode::Connection(ConnectionLayer {
147 connection_id: zero_connection_id,
148 murmur3_hash: 0,
149 })
150 .to_stream(stream)
151}
152
153/// Verifies the integrity of a payload against an expected Murmur3 hash.
154///
155/// # Arguments
156///
157/// * `expected_hash` - The expected Murmur3 hash value.
158/// * `seed` - The `ConnectionSecretSeed` used for generating the hash.
159/// * `payload` - The payload data to be hashed and compared.
160///
161/// # Errors
162///
163/// Returns an `io::Result` error if the calculated hash does not match the expected hash.
164pub fn verify_hash(expected_hash: u32, seed: ConnectionSecretSeed, payload: &[u8]) -> Result<()> {
165 let calculated_hash = murmur3_32(payload, seed.0);
166 if calculated_hash != expected_hash {
167 Err(Error::new(
168 ErrorKind::InvalidData,
169 format!(
170 "hash mismatch: the data does not match the expected hash. calculated {} but payload provided hash {}",
171 format_hex_u32_be(calculated_hash), format_hex_u32_be(expected_hash),
172 ),
173 ))
174 } else {
175 Ok(())
176 }
177}
178
179
180#[derive(Debug)]
181struct Version {
182 pub major: u8,
183 pub minor: u8,
184}
185
186impl Serialize for Version {
187 fn serialize(&self, stream: &mut impl WriteOctetStream) -> io::Result<()>
188 where
189 Self: Sized,
190 {
191 stream.write_u8(self.major)?;
192 stream.write_u8(self.minor)
193 }
194}
195
196impl Deserialize for Version {
197 fn deserialize(stream: &mut impl ReadOctetStream) -> io::Result<Self>
198 where
199 Self: Sized,
200 {
201 Ok(Self {
202 major: stream.read_u8()?,
203 minor: stream.read_u8()?,
204 })
205 }
206}