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}