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}