1use std::fmt;
24
25pub const VERSION: u8 = 1;
27
28pub const HEADER_LEN: usize = 8;
30
31pub const MAX_MESSAGE_LEN: usize = 65_535;
35
36pub const MAX_USER_DATA_LEN: usize = ((MAX_MESSAGE_LEN - HEADER_LEN) / 4) * 4 - 4 - 12;
59
60pub const CLASS_MGMT: u8 = 0;
65pub const CLASS_TRANSFER: u8 = 1;
66pub const CLASS_SSNM: u8 = 2;
67pub const CLASS_ASPSM: u8 = 3;
68pub const CLASS_ASPTM: u8 = 4;
69pub const CLASS_RKM: u8 = 9;
70
71pub const MGMT_ERR: u8 = 0;
73pub const MGMT_NTFY: u8 = 1;
74
75pub const TRANSFER_DATA: u8 = 1;
77
78pub const SSNM_DUNA: u8 = 1;
80pub const SSNM_DAVA: u8 = 2;
81pub const SSNM_DAUD: u8 = 3;
82pub const SSNM_SCON: u8 = 4;
83pub const SSNM_DUPU: u8 = 5;
84pub const SSNM_DRST: u8 = 6;
85
86pub const ASPSM_ASPUP: u8 = 1;
88pub const ASPSM_ASPDN: u8 = 2;
89pub const ASPSM_BEAT: u8 = 3;
90pub const ASPSM_ASPUP_ACK: u8 = 4;
91pub const ASPSM_ASPDN_ACK: u8 = 5;
92pub const ASPSM_BEAT_ACK: u8 = 6;
93
94pub const ASPTM_ASPAC: u8 = 1;
96pub const ASPTM_ASPIA: u8 = 2;
97pub const ASPTM_ASPAC_ACK: u8 = 3;
98pub const ASPTM_ASPIA_ACK: u8 = 4;
99
100pub const RKM_REG_REQ: u8 = 1;
102pub const RKM_REG_RSP: u8 = 2;
103pub const RKM_DEREG_REQ: u8 = 3;
104pub const RKM_DEREG_RSP: u8 = 4;
105
106pub const TAG_INFO_STRING: u16 = 0x0004;
112pub const TAG_ROUTING_CONTEXT: u16 = 0x0006;
113pub const TAG_DIAGNOSTIC_INFO: u16 = 0x0007;
114pub const TAG_HEARTBEAT_DATA: u16 = 0x0009;
115pub const TAG_TRAFFIC_MODE_TYPE: u16 = 0x000b;
116pub const TAG_ERROR_CODE: u16 = 0x000c;
117pub const TAG_STATUS: u16 = 0x000d;
118pub const TAG_ASP_IDENTIFIER: u16 = 0x0011;
119pub const TAG_AFFECTED_POINT_CODE: u16 = 0x0012;
120pub const TAG_CORRELATION_ID: u16 = 0x0013;
121
122pub const TAG_NETWORK_APPEARANCE: u16 = 0x0200;
124pub const TAG_USER_CAUSE: u16 = 0x0204;
125pub const TAG_CONGESTION_INDICATIONS: u16 = 0x0205;
126pub const TAG_CONCERNED_DESTINATION: u16 = 0x0206;
127pub const TAG_ROUTING_KEY: u16 = 0x0207;
128pub const TAG_REGISTRATION_RESULT: u16 = 0x0208;
129pub const TAG_DEREGISTRATION_RESULT: u16 = 0x0209;
130pub const TAG_LOCAL_ROUTING_KEY_ID: u16 = 0x020a;
131pub const TAG_DESTINATION_POINT_CODE: u16 = 0x020b;
132pub const TAG_SERVICE_INDICATORS: u16 = 0x020c;
133pub const TAG_ORIGINATING_POINT_CODE_LIST: u16 = 0x020e;
134pub const TAG_PROTOCOL_DATA: u16 = 0x0210;
135pub const TAG_REGISTRATION_STATUS: u16 = 0x0212;
136pub const TAG_DEREGISTRATION_STATUS: u16 = 0x0213;
137
138pub const ERR_INVALID_VERSION: u32 = 0x01;
143pub const ERR_UNSUPPORTED_MESSAGE_CLASS: u32 = 0x03;
144pub const ERR_UNSUPPORTED_MESSAGE_TYPE: u32 = 0x04;
145pub const ERR_UNSUPPORTED_TRAFFIC_MODE: u32 = 0x05;
146pub const ERR_UNEXPECTED_MESSAGE: u32 = 0x06;
147pub const ERR_PROTOCOL_ERROR: u32 = 0x07;
148pub const ERR_INVALID_STREAM_IDENTIFIER: u32 = 0x09;
149pub const ERR_REFUSED_MANAGEMENT_BLOCKING: u32 = 0x0d;
150pub const ERR_ASP_IDENTIFIER_REQUIRED: u32 = 0x0e;
151pub const ERR_INVALID_ASP_IDENTIFIER: u32 = 0x0f;
152pub const ERR_INVALID_PARAMETER_VALUE: u32 = 0x11;
153pub const ERR_PARAMETER_FIELD_ERROR: u32 = 0x12;
154pub const ERR_UNEXPECTED_PARAMETER: u32 = 0x13;
155pub const ERR_DESTINATION_STATUS_UNKNOWN: u32 = 0x14;
156pub const ERR_INVALID_NETWORK_APPEARANCE: u32 = 0x15;
157pub const ERR_MISSING_PARAMETER: u32 = 0x16;
158pub const ERR_INVALID_ROUTING_CONTEXT: u32 = 0x19;
159pub const ERR_NO_CONFIGURED_AS_FOR_ASP: u32 = 0x1a;
160
161pub const TRAFFIC_MODE_OVERRIDE: u32 = 1;
163pub const TRAFFIC_MODE_LOADSHARE: u32 = 2;
164pub const TRAFFIC_MODE_BROADCAST: u32 = 3;
165
166pub const STATUS_TYPE_AS_STATE_CHANGE: u16 = 1;
168pub const STATUS_TYPE_OTHER: u16 = 2;
169
170pub const STATUS_AS_INACTIVE: u16 = 2;
172pub const STATUS_AS_ACTIVE: u16 = 3;
173pub const STATUS_AS_PENDING: u16 = 4;
174
175pub const STATUS_INSUFFICIENT_ASP_RESOURCES: u16 = 1;
177pub const STATUS_ALTERNATE_ASP_ACTIVE: u16 = 2;
178pub const STATUS_ASP_FAILURE: u16 = 3;
179
180pub fn class_name(class: u8) -> &'static str {
182 match class {
183 CLASS_MGMT => "MGMT",
184 CLASS_TRANSFER => "Transfer",
185 CLASS_SSNM => "SSNM",
186 CLASS_ASPSM => "ASPSM",
187 CLASS_ASPTM => "ASPTM",
188 5 => "RKM(reserved)",
189 6 => "IIM",
190 CLASS_RKM => "RKM",
191 _ => "unknown",
192 }
193}
194
195pub fn message_name(class: u8, msg_type: u8) -> &'static str {
197 match (class, msg_type) {
198 (CLASS_MGMT, MGMT_ERR) => "ERR",
199 (CLASS_MGMT, MGMT_NTFY) => "NTFY",
200 (CLASS_TRANSFER, TRANSFER_DATA) => "DATA",
201 (CLASS_SSNM, SSNM_DUNA) => "DUNA",
202 (CLASS_SSNM, SSNM_DAVA) => "DAVA",
203 (CLASS_SSNM, SSNM_DAUD) => "DAUD",
204 (CLASS_SSNM, SSNM_SCON) => "SCON",
205 (CLASS_SSNM, SSNM_DUPU) => "DUPU",
206 (CLASS_SSNM, SSNM_DRST) => "DRST",
207 (CLASS_ASPSM, ASPSM_ASPUP) => "ASPUP",
208 (CLASS_ASPSM, ASPSM_ASPDN) => "ASPDN",
209 (CLASS_ASPSM, ASPSM_BEAT) => "BEAT",
210 (CLASS_ASPSM, ASPSM_ASPUP_ACK) => "ASPUP ACK",
211 (CLASS_ASPSM, ASPSM_ASPDN_ACK) => "ASPDN ACK",
212 (CLASS_ASPSM, ASPSM_BEAT_ACK) => "BEAT ACK",
213 (CLASS_ASPTM, ASPTM_ASPAC) => "ASPAC",
214 (CLASS_ASPTM, ASPTM_ASPIA) => "ASPIA",
215 (CLASS_ASPTM, ASPTM_ASPAC_ACK) => "ASPAC ACK",
216 (CLASS_ASPTM, ASPTM_ASPIA_ACK) => "ASPIA ACK",
217 (CLASS_RKM, RKM_REG_REQ) => "REG REQ",
218 (CLASS_RKM, RKM_REG_RSP) => "REG RSP",
219 (CLASS_RKM, RKM_DEREG_REQ) => "DEREG REQ",
220 (CLASS_RKM, RKM_DEREG_RSP) => "DEREG RSP",
221 _ => "unknown",
222 }
223}
224
225pub fn si_name(si: u8) -> &'static str {
230 match si {
231 0 => "SNM",
232 1 => "MTP testing",
233 2 => "special MTP testing",
234 3 => "SCCP",
235 4 => "TUP",
236 5 => "ISUP",
237 6 => "DUP (call and circuit related)",
238 7 => "DUP (facility registration/cancellation)",
239 8 => "MTP testing user part",
240 9 => "B-ISUP",
241 10 => "SAT-ISUP",
242 _ => "unassigned",
243 }
244}
245
246pub fn error_code_name(code: u32) -> &'static str {
248 match code {
249 ERR_INVALID_VERSION => "Invalid Version",
250 ERR_UNSUPPORTED_MESSAGE_CLASS => "Unsupported Message Class",
251 ERR_UNSUPPORTED_MESSAGE_TYPE => "Unsupported Message Type",
252 ERR_UNSUPPORTED_TRAFFIC_MODE => "Unsupported Traffic Mode Type",
253 ERR_UNEXPECTED_MESSAGE => "Unexpected Message",
254 ERR_PROTOCOL_ERROR => "Protocol Error",
255 ERR_INVALID_STREAM_IDENTIFIER => "Invalid Stream Identifier",
256 ERR_REFUSED_MANAGEMENT_BLOCKING => "Refused - Management Blocking",
257 ERR_ASP_IDENTIFIER_REQUIRED => "ASP Identifier Required",
258 ERR_INVALID_ASP_IDENTIFIER => "Invalid ASP Identifier",
259 ERR_INVALID_PARAMETER_VALUE => "Invalid Parameter Value",
260 ERR_PARAMETER_FIELD_ERROR => "Parameter Field Error",
261 ERR_UNEXPECTED_PARAMETER => "Unexpected Parameter",
262 ERR_DESTINATION_STATUS_UNKNOWN => "Destination Status Unknown",
263 ERR_INVALID_NETWORK_APPEARANCE => "Invalid Network Appearance",
264 ERR_MISSING_PARAMETER => "Missing Parameter",
265 ERR_INVALID_ROUTING_CONTEXT => "Invalid Routing Context",
266 ERR_NO_CONFIGURED_AS_FOR_ASP => "No Configured AS for ASP",
267 _ => "unassigned",
268 }
269}
270
271#[derive(Clone, Debug, PartialEq, Eq)]
281pub struct WireError {
282 pub error_code: u32,
283 pub reason: String,
284}
285
286impl WireError {
287 fn new(error_code: u32, reason: impl Into<String>) -> Self {
288 Self {
289 error_code,
290 reason: reason.into(),
291 }
292 }
293}
294
295impl fmt::Display for WireError {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 write!(
298 f,
299 "{} (error code 0x{:02x} {})",
300 self.reason,
301 self.error_code,
302 error_code_name(self.error_code)
303 )
304 }
305}
306
307impl std::error::Error for WireError {}
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq)]
315pub struct CommonHeader {
316 pub version: u8,
317 pub reserved: u8,
318 pub class: u8,
319 pub msg_type: u8,
320 pub length: u32,
322}
323
324pub fn parse_header(bytes: &[u8]) -> Result<CommonHeader, WireError> {
329 if bytes.len() < HEADER_LEN {
330 return Err(WireError::new(
331 ERR_PROTOCOL_ERROR,
332 format!("common header truncated at {} octets", bytes.len()),
333 ));
334 }
335 let version = bytes[0];
336 if version != VERSION {
337 return Err(WireError::new(
338 ERR_INVALID_VERSION,
339 format!("M3UA version {version} is not 1"),
340 ));
341 }
342 let length = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
343 if (length as usize) < HEADER_LEN {
344 return Err(WireError::new(
345 ERR_PROTOCOL_ERROR,
346 format!("message length {length} is shorter than the common header"),
347 ));
348 }
349 if length as usize > MAX_MESSAGE_LEN {
350 return Err(WireError::new(
351 ERR_PROTOCOL_ERROR,
352 format!("message length {length} exceeds the {MAX_MESSAGE_LEN}-octet ceiling"),
353 ));
354 }
355 Ok(CommonHeader {
356 version,
357 reserved: bytes[1],
358 class: bytes[2],
359 msg_type: bytes[3],
360 length,
361 })
362}
363
364pub fn alignment_slack(length: u32) -> usize {
372 padding_for(length as usize)
373}
374
375pub fn padding_for(len: usize) -> usize {
377 (4 - (len % 4)) % 4
378}
379
380#[derive(Clone, Debug, PartialEq, Eq)]
388pub struct Parameter {
389 pub tag: u16,
390 pub value: Vec<u8>,
391}
392
393impl Parameter {
394 pub fn new(tag: u16, value: impl Into<Vec<u8>>) -> Self {
395 Self {
396 tag,
397 value: value.into(),
398 }
399 }
400
401 pub fn u32(tag: u16, value: u32) -> Self {
403 Self::new(tag, value.to_be_bytes().to_vec())
404 }
405
406 pub fn declared_len(&self) -> usize {
409 4 + self.value.len()
410 }
411
412 pub fn wire_len(&self) -> usize {
414 let declared = self.declared_len();
415 declared + padding_for(declared)
416 }
417
418 pub fn write_into(&self, out: &mut Vec<u8>) -> Result<(), WireError> {
427 let declared = self.declared_len();
428 if declared > u16::MAX as usize {
429 return Err(WireError::new(
430 ERR_PROTOCOL_ERROR,
431 format!(
432 "parameter 0x{:04x} declares {declared} octets, past the {} a 16-bit \
433 Parameter Length can carry",
434 self.tag,
435 u16::MAX
436 ),
437 ));
438 }
439 out.extend_from_slice(&self.tag.to_be_bytes());
440 out.extend_from_slice(&(declared as u16).to_be_bytes());
441 out.extend_from_slice(&self.value);
442 out.resize(out.len() + padding_for(declared), 0);
443 Ok(())
444 }
445
446 pub fn as_u32(&self) -> Option<u32> {
448 let bytes: [u8; 4] = self.value.as_slice().try_into().ok()?;
449 Some(u32::from_be_bytes(bytes))
450 }
451}
452
453pub fn parse_parameters(body: &[u8]) -> Result<Vec<Parameter>, WireError> {
458 let mut out = Vec::new();
459 let mut offset = 0usize;
460 while offset < body.len() {
461 let remaining = body.len() - offset;
462 if remaining < 4 {
463 if body[offset..].iter().all(|b| *b == 0) {
466 break;
467 }
468 return Err(WireError::new(
469 ERR_PARAMETER_FIELD_ERROR,
470 format!("{remaining} trailing octets are too short for a parameter header"),
471 ));
472 }
473 let tag = u16::from_be_bytes([body[offset], body[offset + 1]]);
474 let declared = u16::from_be_bytes([body[offset + 2], body[offset + 3]]) as usize;
475 if declared < 4 {
476 return Err(WireError::new(
477 ERR_PARAMETER_FIELD_ERROR,
478 format!("parameter 0x{tag:04x} declares length {declared}, below the 4-octet TLV header"),
479 ));
480 }
481 let end = offset + declared;
482 if end > body.len() {
483 return Err(WireError::new(
484 ERR_PARAMETER_FIELD_ERROR,
485 format!(
486 "parameter 0x{tag:04x} declares {declared} octets but only {remaining} remain"
487 ),
488 ));
489 }
490 out.push(Parameter {
491 tag,
492 value: body[offset + 4..end].to_vec(),
493 });
494 offset = (end + padding_for(declared)).min(body.len());
497 }
498 Ok(out)
499}
500
501#[derive(Clone, Debug, PartialEq, Eq)]
507pub struct Message {
508 pub class: u8,
509 pub msg_type: u8,
510 pub parameters: Vec<Parameter>,
511}
512
513impl Message {
514 pub fn new(class: u8, msg_type: u8) -> Self {
515 Self {
516 class,
517 msg_type,
518 parameters: Vec::new(),
519 }
520 }
521
522 pub fn with(mut self, parameter: Parameter) -> Self {
523 self.parameters.push(parameter);
524 self
525 }
526
527 pub fn with_opt_u32(self, tag: u16, value: Option<u32>) -> Self {
530 match value {
531 Some(v) => self.with(Parameter::u32(tag, v)),
532 None => self,
533 }
534 }
535
536 pub fn with_opt(self, tag: u16, value: Option<Vec<u8>>) -> Self {
537 match value {
538 Some(v) => self.with(Parameter::new(tag, v)),
539 None => self,
540 }
541 }
542
543 pub fn encode(&self) -> Result<Vec<u8>, WireError> {
552 let body_len: usize = self.parameters.iter().map(|p| p.wire_len()).sum();
553 let total = HEADER_LEN + body_len;
554 if total > MAX_MESSAGE_LEN {
555 return Err(WireError::new(
556 ERR_PROTOCOL_ERROR,
557 format!(
558 "message of {} parameter(s) encodes to {total} octets, past the \
559 {MAX_MESSAGE_LEN}-octet ceiling this codec's own parser enforces",
560 self.parameters.len()
561 ),
562 ));
563 }
564 let mut out = Vec::with_capacity(total);
565 out.push(VERSION);
566 out.push(0); out.push(self.class);
568 out.push(self.msg_type);
569 out.extend_from_slice(&(total as u32).to_be_bytes());
571 for parameter in &self.parameters {
572 parameter.write_into(&mut out)?;
573 }
574 Ok(out)
575 }
576
577 pub fn parse(full: &[u8]) -> Result<Message, WireError> {
580 let header = parse_header(full)?;
581 let declared = header.length as usize;
582 if full.len() < declared {
583 return Err(WireError::new(
584 ERR_PROTOCOL_ERROR,
585 format!(
586 "message declares {declared} octets but only {} were supplied",
587 full.len()
588 ),
589 ));
590 }
591 let parameters = parse_parameters(&full[HEADER_LEN..declared])?;
592 Ok(Message {
593 class: header.class,
594 msg_type: header.msg_type,
595 parameters,
596 })
597 }
598
599 pub fn param(&self, tag: u16) -> Option<&Parameter> {
600 self.parameters.iter().find(|p| p.tag == tag)
601 }
602
603 pub fn param_u32(&self, tag: u16) -> Option<u32> {
604 self.param(tag).and_then(|p| p.as_u32())
605 }
606
607 pub fn name(&self) -> &'static str {
608 message_name(self.class, self.msg_type)
609 }
610}
611
612pub fn peek_class_type(bytes: &[u8]) -> Option<(u8, u8)> {
619 if bytes.len() < HEADER_LEN || bytes[0] != VERSION {
620 return None;
621 }
622 Some((bytes[2], bytes[3]))
623}
624
625pub const PROTOCOL_DATA_FIXED_LEN: usize = 12;
631
632#[derive(Clone, Debug, PartialEq, Eq)]
637pub struct ProtocolData {
638 pub opc: u32,
639 pub dpc: u32,
640 pub si: u8,
641 pub ni: u8,
642 pub mp: u8,
643 pub sls: u8,
644 pub payload: Vec<u8>,
645}
646
647impl ProtocolData {
648 pub fn parse(value: &[u8]) -> Result<Self, WireError> {
649 if value.len() < PROTOCOL_DATA_FIXED_LEN {
650 return Err(WireError::new(
651 ERR_PARAMETER_FIELD_ERROR,
652 format!(
653 "Protocol Data is {} octets, below the {PROTOCOL_DATA_FIXED_LEN}-octet \
654 routing label",
655 value.len()
656 ),
657 ));
658 }
659 Ok(Self {
660 opc: u32::from_be_bytes([value[0], value[1], value[2], value[3]]),
661 dpc: u32::from_be_bytes([value[4], value[5], value[6], value[7]]),
662 si: value[8],
663 ni: value[9],
664 mp: value[10],
665 sls: value[11],
666 payload: value[PROTOCOL_DATA_FIXED_LEN..].to_vec(),
667 })
668 }
669
670 pub fn to_value(&self) -> Vec<u8> {
672 let mut out = Vec::with_capacity(PROTOCOL_DATA_FIXED_LEN + self.payload.len());
673 out.extend_from_slice(&self.opc.to_be_bytes());
674 out.extend_from_slice(&self.dpc.to_be_bytes());
675 out.push(self.si);
676 out.push(self.ni);
677 out.push(self.mp);
678 out.push(self.sls);
679 out.extend_from_slice(&self.payload);
680 out
681 }
682}
683
684pub fn aspup_ack(info_string: Option<&str>) -> Result<Vec<u8>, WireError> {
690 Message::new(CLASS_ASPSM, ASPSM_ASPUP_ACK)
691 .with_opt(TAG_INFO_STRING, info_string.map(|s| s.as_bytes().to_vec()))
692 .encode()
693}
694
695pub fn aspdn_ack() -> Result<Vec<u8>, WireError> {
697 Message::new(CLASS_ASPSM, ASPSM_ASPDN_ACK).encode()
698}
699
700pub fn beat_ack(heartbeat_data: Option<&[u8]>) -> Result<Vec<u8>, WireError> {
703 Message::new(CLASS_ASPSM, ASPSM_BEAT_ACK)
704 .with_opt(TAG_HEARTBEAT_DATA, heartbeat_data.map(|d| d.to_vec()))
705 .encode()
706}
707
708pub fn aspac_ack(
710 traffic_mode: Option<u32>,
711 routing_context: Option<u32>,
712 info_string: Option<&str>,
713) -> Result<Vec<u8>, WireError> {
714 Message::new(CLASS_ASPTM, ASPTM_ASPAC_ACK)
715 .with_opt_u32(TAG_TRAFFIC_MODE_TYPE, traffic_mode)
716 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
717 .with_opt(TAG_INFO_STRING, info_string.map(|s| s.as_bytes().to_vec()))
718 .encode()
719}
720
721pub fn aspia_ack(routing_context: Option<u32>) -> Result<Vec<u8>, WireError> {
723 Message::new(CLASS_ASPTM, ASPTM_ASPIA_ACK)
724 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
725 .encode()
726}
727
728pub fn error(error_code: u32, routing_context: Option<u32>) -> Result<Vec<u8>, WireError> {
734 Message::new(CLASS_MGMT, MGMT_ERR)
735 .with(Parameter::u32(TAG_ERROR_CODE, error_code))
736 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
737 .encode()
738}
739
740pub fn notify(
743 status_type: u16,
744 status_info: u16,
745 asp_identifier: Option<u32>,
746 routing_context: Option<u32>,
747 info_string: Option<&str>,
748) -> Result<Vec<u8>, WireError> {
749 let status = ((status_type as u32) << 16) | status_info as u32;
750 Message::new(CLASS_MGMT, MGMT_NTFY)
751 .with(Parameter::u32(TAG_STATUS, status))
752 .with_opt_u32(TAG_ASP_IDENTIFIER, asp_identifier)
753 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
754 .with_opt(TAG_INFO_STRING, info_string.map(|s| s.as_bytes().to_vec()))
755 .encode()
756}
757
758pub fn data(
760 protocol_data: &ProtocolData,
761 network_appearance: Option<u32>,
762 routing_context: Option<u32>,
763 correlation_id: Option<u32>,
764) -> Result<Vec<u8>, WireError> {
765 Message::new(CLASS_TRANSFER, TRANSFER_DATA)
766 .with_opt_u32(TAG_NETWORK_APPEARANCE, network_appearance)
767 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
768 .with(Parameter::new(TAG_PROTOCOL_DATA, protocol_data.to_value()))
769 .with_opt_u32(TAG_CORRELATION_ID, correlation_id)
770 .encode()
771}
772
773pub fn aspup(asp_identifier: Option<u32>, info_string: Option<&str>) -> Result<Vec<u8>, WireError> {
779 Message::new(CLASS_ASPSM, ASPSM_ASPUP)
780 .with_opt_u32(TAG_ASP_IDENTIFIER, asp_identifier)
781 .with_opt(TAG_INFO_STRING, info_string.map(|s| s.as_bytes().to_vec()))
782 .encode()
783}
784
785pub fn aspdn() -> Result<Vec<u8>, WireError> {
787 Message::new(CLASS_ASPSM, ASPSM_ASPDN).encode()
788}
789
790pub fn aspac(
792 traffic_mode: Option<u32>,
793 routing_context: Option<u32>,
794) -> Result<Vec<u8>, WireError> {
795 Message::new(CLASS_ASPTM, ASPTM_ASPAC)
796 .with_opt_u32(TAG_TRAFFIC_MODE_TYPE, traffic_mode)
797 .with_opt_u32(TAG_ROUTING_CONTEXT, routing_context)
798 .encode()
799}
800
801pub fn beat(heartbeat_data: &[u8]) -> Result<Vec<u8>, WireError> {
803 Message::new(CLASS_ASPSM, ASPSM_BEAT)
804 .with(Parameter::new(TAG_HEARTBEAT_DATA, heartbeat_data.to_vec()))
805 .encode()
806}