umsh_node/
mac_command.rs

1use crate::app_util::{copy_into, fixed, push_byte};
2use crate::{AppEncodeError, AppParseError};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[repr(u8)]
6pub enum CommandId {
7    BeaconRequest = 0,
8    IdentityRequest = 1,
9    SignalReportRequest = 2,
10    SignalReportResponse = 3,
11    EchoRequest = 4,
12    EchoResponse = 5,
13    PfsSessionRequest = 6,
14    PfsSessionResponse = 7,
15    EndPfsSession = 8,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum MacCommand<'a> {
20    BeaconRequest {
21        nonce: Option<u32>,
22    },
23    IdentityRequest,
24    SignalReportRequest,
25    SignalReportResponse {
26        rssi: u8,
27        snr: i8,
28    },
29    EchoRequest {
30        data: &'a [u8],
31    },
32    EchoResponse {
33        data: &'a [u8],
34    },
35    PfsSessionRequest {
36        ephemeral_key: umsh_core::PublicKey,
37        duration_minutes: u16,
38    },
39    PfsSessionResponse {
40        ephemeral_key: umsh_core::PublicKey,
41        duration_minutes: u16,
42    },
43    EndPfsSession,
44}
45
46pub fn parse(payload: &[u8]) -> Result<MacCommand<'_>, AppParseError> {
47    let (&command_id, body) = payload
48        .split_first()
49        .ok_or(AppParseError::Core(umsh_core::ParseError::Truncated))?;
50
51    match command_id {
52        0 => match body {
53            [] => Ok(MacCommand::BeaconRequest { nonce: None }),
54            [a, b, c, d] => Ok(MacCommand::BeaconRequest {
55                nonce: Some(u32::from_be_bytes([*a, *b, *c, *d])),
56            }),
57            _ => Err(AppParseError::InvalidLength {
58                expected: 4,
59                actual: body.len(),
60            }),
61        },
62        1 => {
63            if body.is_empty() {
64                Ok(MacCommand::IdentityRequest)
65            } else {
66                Err(AppParseError::InvalidOptionValue)
67            }
68        }
69        2 => {
70            if body.is_empty() {
71                Ok(MacCommand::SignalReportRequest)
72            } else {
73                Err(AppParseError::InvalidOptionValue)
74            }
75        }
76        3 => match body {
77            [rssi, snr] => Ok(MacCommand::SignalReportResponse {
78                rssi: *rssi,
79                snr: *snr as i8,
80            }),
81            _ => Err(AppParseError::InvalidLength {
82                expected: 2,
83                actual: body.len(),
84            }),
85        },
86        4 => Ok(MacCommand::EchoRequest { data: body }),
87        5 => Ok(MacCommand::EchoResponse { data: body }),
88        6 => parse_pfs(body, true),
89        7 => parse_pfs(body, false),
90        8 => {
91            if body.is_empty() {
92                Ok(MacCommand::EndPfsSession)
93            } else {
94                Err(AppParseError::InvalidOptionValue)
95            }
96        }
97        other => Err(AppParseError::InvalidCommandId(other)),
98    }
99}
100
101fn parse_pfs(payload: &[u8], request: bool) -> Result<MacCommand<'_>, AppParseError> {
102    if payload.len() != 34 {
103        return Err(AppParseError::InvalidLength {
104            expected: 34,
105            actual: payload.len(),
106        });
107    }
108    let ephemeral_key = umsh_core::PublicKey(*fixed(&payload[..32])?);
109    let duration_minutes = u16::from_be_bytes(*fixed(&payload[32..34])?);
110    Ok(if request {
111        MacCommand::PfsSessionRequest {
112            ephemeral_key,
113            duration_minutes,
114        }
115    } else {
116        MacCommand::PfsSessionResponse {
117            ephemeral_key,
118            duration_minutes,
119        }
120    })
121}
122
123pub fn encode(cmd: &MacCommand<'_>, buf: &mut [u8]) -> Result<usize, AppEncodeError> {
124    let mut pos = 0usize;
125    match cmd {
126        MacCommand::BeaconRequest { nonce } => {
127            push_byte(buf, &mut pos, CommandId::BeaconRequest as u8)?;
128            if let Some(nonce) = nonce {
129                copy_into(buf, &mut pos, &nonce.to_be_bytes())?;
130            }
131        }
132        MacCommand::IdentityRequest => push_byte(buf, &mut pos, CommandId::IdentityRequest as u8)?,
133        MacCommand::SignalReportRequest => {
134            push_byte(buf, &mut pos, CommandId::SignalReportRequest as u8)?;
135        }
136        MacCommand::SignalReportResponse { rssi, snr } => {
137            push_byte(buf, &mut pos, CommandId::SignalReportResponse as u8)?;
138            push_byte(buf, &mut pos, *rssi)?;
139            push_byte(buf, &mut pos, *snr as u8)?;
140        }
141        MacCommand::EchoRequest { data } => {
142            push_byte(buf, &mut pos, CommandId::EchoRequest as u8)?;
143            copy_into(buf, &mut pos, data)?;
144        }
145        MacCommand::EchoResponse { data } => {
146            push_byte(buf, &mut pos, CommandId::EchoResponse as u8)?;
147            copy_into(buf, &mut pos, data)?;
148        }
149        MacCommand::PfsSessionRequest {
150            ephemeral_key,
151            duration_minutes,
152        } => {
153            push_byte(buf, &mut pos, CommandId::PfsSessionRequest as u8)?;
154            copy_into(buf, &mut pos, &ephemeral_key.0)?;
155            copy_into(buf, &mut pos, &duration_minutes.to_be_bytes())?;
156        }
157        MacCommand::PfsSessionResponse {
158            ephemeral_key,
159            duration_minutes,
160        } => {
161            push_byte(buf, &mut pos, CommandId::PfsSessionResponse as u8)?;
162            copy_into(buf, &mut pos, &ephemeral_key.0)?;
163            copy_into(buf, &mut pos, &duration_minutes.to_be_bytes())?;
164        }
165        MacCommand::EndPfsSession => push_byte(buf, &mut pos, CommandId::EndPfsSession as u8)?,
166    }
167    Ok(pos)
168}