1#![allow(clippy::too_many_arguments)]
2use crate::*;
3use crate::{coap, crypto, security, tlv};
4
5fn compute_hmac_opt(
6 uri_string: &str,
7 options: &SigNetOptions,
8 payload: &[u8],
9 signing_key: &[u8],
10) -> Result<[u8; HMAC_SHA256_LENGTH]> {
11 crypto::compute_packet_hmac(uri_string, options, payload, signing_key)
12}
13
14fn write_options_2076_2204(
15 buffer: &mut PacketBuffer,
16 tuid: &[u8; TUID_LENGTH],
17 endpoint: u16,
18 mfg_code: u16,
19 session_id: u32,
20 seq_num: u32,
21) -> Result<SigNetOptions> {
22 let mut options = SigNetOptions {
23 security_mode: SECURITY_MODE_HMAC_SHA256,
24 ..Default::default()
25 };
26 security::build_sender_id(tuid, endpoint, &mut options.sender_id);
27 options.mfg_code = mfg_code;
28 options.session_id = session_id;
29 options.seq_num = seq_num;
30 security::build_signet_options_without_hmac(buffer, &options, COAP_OPTION_URI_PATH)?;
31 Ok(options)
32}
33
34fn write_uri_path_segments(buffer: &mut PacketBuffer, segments: &[&str]) -> Result<()> {
35 let mut prev: u16 = 0;
36 for &seg in segments {
37 coap::encode_coap_option(buffer, COAP_OPTION_URI_PATH, prev, seg.as_bytes())?;
38 prev = COAP_OPTION_URI_PATH;
39 }
40 Ok(())
41}
42
43pub fn build_dmx_packet(
44 buffer: &mut PacketBuffer,
45 universe: u16,
46 dmx_data: &[u8],
47 slot_count: u16,
48 tuid: &[u8; TUID_LENGTH],
49 endpoint: u16,
50 mfg_code: u16,
51 session_id: u32,
52 seq_num: u32,
53 sender_key: &[u8],
54 message_id: u16,
55 scope: &str,
56) -> Result<()> {
57 if slot_count == 0 || slot_count > MAX_DMX_SLOTS || dmx_data.len() < slot_count as usize {
58 return Err(SigNetError::InvalidArgument);
59 }
60 let slots = &dmx_data[..slot_count as usize];
61
62 buffer.reset();
63 coap::build_coap_header(buffer, message_id)?;
64 coap::build_uri_path_options(buffer, universe, scope)?;
65
66 let options = write_options_2076_2204(buffer, tuid, endpoint, mfg_code, session_id, seq_num)?;
67
68 let mut payload_buf = PacketBuffer::new();
71 tlv::encode_tid_level(&mut payload_buf, slots)?;
72
73 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
74 let uri_len = coap::build_uri_string(universe, scope, &mut uri_buf)?;
75 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
76 .map_err(|_| SigNetError::Encode)?;
77
78 let hmac = compute_hmac_opt(uri_string, &options, payload_buf.as_slice(), sender_key)?;
79 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
80
81 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
82 buffer.write_bytes(payload_buf.as_slice())
83}
84
85pub fn build_announce_packet(
86 buffer: &mut PacketBuffer,
87 tuid: &[u8; TUID_LENGTH],
88 soem_code: SoemCode,
89 firmware_version_id: u32,
90 firmware_version_string: &str,
91 protocol_version: u8,
92 role_capability_bits: u8,
93 endpoint_count: u16,
94 change_count: u16,
95 session_id: u32,
96 seq_num: u32,
97 citizen_key: &[u8],
98 message_id: u16,
99 scope: &str,
100) -> Result<()> {
101 buffer.reset();
102
103 coap::build_coap_header(buffer, message_id)?;
104
105 let segments = [
106 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, SIGNET_URI_NODE,
107 ];
108 write_uri_path_segments(buffer, &segments)?;
109
110 let hex = TUID(*tuid).to_hex_upper();
111 let hex_str = core::str::from_utf8(&hex).map_err(|_| SigNetError::Encode)?;
112 let mut prev: u16 = COAP_OPTION_URI_PATH;
113 coap::encode_coap_option(buffer, COAP_OPTION_URI_PATH, prev, hex_str.as_bytes())?;
114 prev = COAP_OPTION_URI_PATH;
115 coap::encode_coap_option(buffer, COAP_OPTION_URI_PATH, prev, b"0")?;
116
117 let mfg_code = soem_code_mfg(soem_code);
118 let options = write_options_2076_2204(buffer, tuid, 0, mfg_code, session_id, seq_num)?;
119
120 let mut payload_buf = PacketBuffer::new();
122 tlv::build_startup_announce_payload(
123 &mut payload_buf, tuid,
124 soem_code,
125 firmware_version_id,
126 firmware_version_string,
127 protocol_version, role_capability_bits, endpoint_count, change_count,
128 0, None, )?;
131
132 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
133 let uri_len = coap::build_node_uri_string(tuid, 0, scope, &mut uri_buf)?;
134 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
135 .map_err(|_| SigNetError::Encode)?;
136
137 let hmac = compute_hmac_opt(uri_string, &options, payload_buf.as_slice(), citizen_key)?;
138 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
139
140 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
141 buffer.write_bytes(payload_buf.as_slice())
142}
143
144pub fn build_poll_packet(
145 buffer: &mut PacketBuffer,
146 manager_tuid: &[u8; TUID_LENGTH],
147 soem_code: SoemCode,
148 tuid_lo: &[u8; TUID_LENGTH],
149 tuid_hi: &[u8; TUID_LENGTH],
150 target_endpoint: u16,
151 query_level: u8,
152 session_id: u32,
153 seq_num: u32,
154 manager_global_key: &[u8],
155 message_id: u16,
156 scope: &str,
157) -> Result<()> {
158 buffer.reset();
159
160 coap::build_coap_header(buffer, message_id)?;
161
162 let segments = [
163 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, SIGNET_URI_POLL,
164 ];
165 write_uri_path_segments(buffer, &segments)?;
166
167 let mfg_code = soem_code_mfg(soem_code);
168 let options = write_options_2076_2204(buffer, manager_tuid, 0, mfg_code, session_id, seq_num)?;
169
170 let mut payload_buf = PacketBuffer::new();
172 tlv::encode_tid_poll(
173 &mut payload_buf, manager_tuid,
174 soem_code,
175 tuid_lo, tuid_hi, target_endpoint, query_level,
176 )?;
177
178 let poll_uri = format!(
179 "/{}/{}/{}/{}",
180 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, SIGNET_URI_POLL
181 );
182
183 let hmac = compute_hmac_opt(&poll_uri, &options, payload_buf.as_slice(), manager_global_key)?;
184 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
185
186 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
187 buffer.write_bytes(payload_buf.as_slice())
188}
189
190pub fn build_timecode_packet(
193 buffer: &mut PacketBuffer,
194 stream: u8,
195 hours: u8,
196 minutes: u8,
197 seconds: u8,
198 frames: u8,
199 tc_type: u8,
200 tuid: &[u8; TUID_LENGTH],
201 endpoint: u16,
202 mfg_code: u16,
203 session_id: u32,
204 seq_num: u32,
205 sender_key: &[u8],
206 message_id: u16,
207 scope: &str,
208) -> Result<()> {
209 buffer.reset();
210 coap::build_coap_header(buffer, message_id)?;
211
212 let stream_str = stream.to_string();
213 let segments = [
214 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, "timecode", &stream_str,
215 ];
216 write_uri_path_segments(buffer, &segments)?;
217
218 let options = write_options_2076_2204(buffer, tuid, endpoint, mfg_code, session_id, seq_num)?;
219
220 let mut payload_buf = PacketBuffer::new();
221 tlv::encode_tid_timecode(&mut payload_buf, hours, minutes, seconds, frames, tc_type)?;
222
223 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
224 let uri_len = coap::build_timecode_uri_string(stream, scope, &mut uri_buf)?;
225 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
226 .map_err(|_| SigNetError::Encode)?;
227
228 let hmac = compute_hmac_opt(uri_string, &options, payload_buf.as_slice(), sender_key)?;
229 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
230
231 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
232 buffer.write_bytes(payload_buf.as_slice())
233}
234
235pub fn build_preview_packet(
238 buffer: &mut PacketBuffer,
239 universe: u16,
240 dmx_data: &[u8],
241 tuid: &[u8; TUID_LENGTH],
242 endpoint: u16,
243 mfg_code: u16,
244 session_id: u32,
245 seq_num: u32,
246 sender_key: &[u8],
247 message_id: u16,
248 scope: &str,
249) -> Result<()> {
250 if dmx_data.is_empty() || dmx_data.len() > MAX_DMX_SLOTS as usize {
251 return Err(SigNetError::InvalidArgument);
252 }
253
254 buffer.reset();
255 coap::build_coap_header(buffer, message_id)?;
256
257 let universe_str = universe.to_string();
258 let segments = [
259 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, "preview", &universe_str,
260 ];
261 write_uri_path_segments(buffer, &segments)?;
262
263 let options = write_options_2076_2204(buffer, tuid, endpoint, mfg_code, session_id, seq_num)?;
264
265 let mut payload_buf = PacketBuffer::new();
266 tlv::encode_tid_preview(&mut payload_buf, dmx_data)?;
267
268 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
269 let uri_len = coap::build_preview_uri_string(universe, scope, &mut uri_buf)?;
270 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
271 .map_err(|_| SigNetError::Encode)?;
272
273 let hmac = compute_hmac_opt(uri_string, &options, payload_buf.as_slice(), sender_key)?;
274 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
275
276 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
277 buffer.write_bytes(payload_buf.as_slice())
278}
279
280pub fn build_beacon_packet(
283 buffer: &mut PacketBuffer,
284 tuid: &[u8; TUID_LENGTH],
285 soem_code: SoemCode,
286 device_label: &str,
287 endpoint_count: u16,
288 otw_capability: Option<(u16, u8)>,
289 message_id: u16,
290) -> Result<()> {
291 buffer.reset();
292 coap::build_coap_header(buffer, message_id)?;
293
294 let hex = TUID(*tuid).to_hex_upper();
295 let hex_str = core::str::from_utf8(&hex).map_err(|_| SigNetError::Encode)?;
296 let segments = [
297 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, "local", "node_beacon", hex_str, "0",
298 ];
299 write_uri_path_segments(buffer, &segments)?;
300
301 coap::encode_coap_option(buffer, SIGNET_OPTION_SECURITY_MODE, COAP_OPTION_URI_PATH, &[SECURITY_MODE_UNPROVISIONED])?;
303
304 let zero_hmac = [0u8; HMAC_SHA256_LENGTH];
305 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SECURITY_MODE, &zero_hmac)?;
306
307 let mut payload_buf = PacketBuffer::new();
309 tlv::encode_tid_poll_reply(&mut payload_buf, tuid, soem_code, 0)?;
310
311 let dl = device_label.as_bytes();
312 let label_tlv = TLVBlock { type_id: TID_RT_DEVICE_LABEL, value: dl };
313 tlv::encode_tlv(&mut payload_buf, &label_tlv)?;
314
315 let ec_bytes = endpoint_count.to_be_bytes();
316 let ec_tlv = TLVBlock { type_id: TID_RT_ENDPOINT_COUNT, value: &ec_bytes };
317 tlv::encode_tlv(&mut payload_buf, &ec_tlv)?;
318
319 if let Some((port, protocols)) = otw_capability {
320 tlv::encode_tid_rt_otw_capability(&mut payload_buf, port, protocols)?;
321 }
322
323 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
324 buffer.write_bytes(payload_buf.as_slice())
325}
326
327pub fn build_node_lost_packet(
330 buffer: &mut PacketBuffer,
331 tuid: &[u8; TUID_LENGTH],
332 soem_code: SoemCode,
333 endpoint_count: u16,
334 protocol_version: u8,
335 role_capability_bits: u8,
336 mult_override_state: u8,
337 otw_capability: Option<(u16, u8)>,
338 session_id: u32,
339 seq_num: u32,
340 citizen_key: &[u8],
341 message_id: u16,
342 scope: &str,
343) -> Result<()> {
344 buffer.reset();
345 coap::build_coap_header(buffer, message_id)?;
346
347 let hex = TUID(*tuid).to_hex_upper();
348 let hex_str = core::str::from_utf8(&hex).map_err(|_| SigNetError::Encode)?;
349 let segments = [
350 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, "node_lost", hex_str, "0",
351 ];
352 write_uri_path_segments(buffer, &segments)?;
353
354 let mfg_code = soem_code_mfg(soem_code);
355 let options = write_options_2076_2204(buffer, tuid, 0, mfg_code, session_id, seq_num)?;
356
357 let mut payload_buf = PacketBuffer::new();
358 tlv::encode_tid_poll_reply(&mut payload_buf, tuid, soem_code, 0)?;
361 tlv::encode_tid_rt_protocol_version(&mut payload_buf, protocol_version)?;
363 tlv::encode_tid_rt_role_capability(&mut payload_buf, role_capability_bits)?;
365 let ec_bytes = endpoint_count.to_be_bytes();
367 let ec_tlv = TLVBlock { type_id: TID_RT_ENDPOINT_COUNT, value: &ec_bytes };
368 tlv::encode_tlv(&mut payload_buf, &ec_tlv)?;
369 tlv::encode_tid_rt_mult_override(&mut payload_buf, mult_override_state)?;
371 if let Some((port, protocols)) = otw_capability {
373 tlv::encode_tid_rt_otw_capability(&mut payload_buf, port, protocols)?;
374 }
375
376 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
377 let uri_len = coap::build_node_lost_uri_string(tuid, scope, &mut uri_buf)?;
378 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
379 .map_err(|_| SigNetError::Encode)?;
380
381 let hmac = compute_hmac_opt(uri_string, &options, payload_buf.as_slice(), citizen_key)?;
382 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
383
384 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
385 buffer.write_bytes(payload_buf.as_slice())
386}
387
388pub fn build_manager_command_packet(
391 buffer: &mut PacketBuffer,
392 target_tuid: &[u8; TUID_LENGTH],
393 endpoint: u16,
394 tlv_payload: &[u8],
395 manager_tuid: &[u8; TUID_LENGTH],
396 mfg_code: u16,
397 session_id: u32,
398 seq_num: u32,
399 km_local: &[u8],
400 message_id: u16,
401 scope: &str,
402) -> Result<()> {
403 buffer.reset();
404 coap::build_coap_header(buffer, message_id)?;
405
406 let hex = TUID(*target_tuid).to_hex_upper();
407 let hex_str = core::str::from_utf8(&hex).map_err(|_| SigNetError::Encode)?;
408 let ep_str = endpoint.to_string();
409 let segments = [
410 SIGNET_URI_PREFIX, SIGNET_URI_VERSION, scope, "manager", hex_str, &ep_str,
411 ];
412 write_uri_path_segments(buffer, &segments)?;
413
414 let options = write_options_2076_2204(buffer, manager_tuid, endpoint, mfg_code, session_id, seq_num)?;
415
416 let mut uri_buf = [0u8; URI_STRING_MIN_BUFFER as usize];
417 let uri_len = coap::build_manager_uri_string(target_tuid, endpoint, scope, &mut uri_buf)?;
418 let uri_string = core::str::from_utf8(&uri_buf[..uri_len])
419 .map_err(|_| SigNetError::Encode)?;
420
421 let hmac = compute_hmac_opt(uri_string, &options, tlv_payload, km_local)?;
422 coap::encode_coap_option(buffer, SIGNET_OPTION_HMAC, SIGNET_OPTION_SEQ_NUM, &hmac)?;
423
424 buffer.write_byte(COAP_PAYLOAD_MARKER)?;
425 buffer.write_bytes(tlv_payload)
426}