1mod builder;
53mod crc;
54mod datatime;
55mod error;
56pub mod crypto;
57mod flag;
58mod frame;
59mod framer;
60mod packet;
61mod multimedia;
62
63pub use builder::{build_data_ack, build_exe_rtn, build_notify_ack, build_qn_rtn, CpBuilder, PayloadBuilder};
64pub use crc::{
65 crc16_ansi, crc16_hex_lower, crc16_hex_upper, crc16_modbus, crc16_modbus_hex_lower, crc16_modbus_hex_upper,
66};
67pub use crypto::{decrypt_data_segment, encrypt_data_segment, BlockCipher16, EncryptedDataSegment};
68pub use datatime::parse_datatime_to_utc;
69pub use error::Hj212Error;
70pub use flag::Hj212Flag;
71pub use frame::{build_frame, build_frame_compat, build_frame_standard, parse_frame, parse_frame_strict};
72pub use framer::Framer;
73pub use multimedia::{MediaFormat, MediaType};
74pub use packet::Hj212Packet;
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn build_and_parse_roundtrip() {
82 let payload = "QN=1;ST=22;CN=2011;PW=123;MN=ABC;Flag=7;CP=&&DataTime=20250101010101;a21026-Rtd=12.3&&";
83 let frame = build_frame(payload);
84 assert!(frame.ends_with("\r\n"));
85 let pkt = parse_frame(&frame).unwrap();
86 assert_eq!(pkt.mn.as_deref(), Some("ABC"));
87 assert_eq!(pkt.data_time.as_deref(), Some("20250101010101"));
88 assert_eq!(pkt.cp.get("a21026-Rtd").map(String::as_str), Some("12.3"));
89 }
90
91 #[test]
92 fn framer_extracts_frames() {
93 let payload = "QN=1;ST=22;CN=2011;PW=123;MN=ABC;Flag=7;CP=&&DataTime=20250101010101&&";
94 let frame = build_frame(payload);
95 let mut fr = Framer::new();
96 fr.push(frame[..5].as_bytes());
97 assert!(fr.next_frame().is_none());
98 fr.push(frame[5..].as_bytes());
99 let out = fr.next_frame().unwrap();
100 assert_eq!(String::from_utf8_lossy(&out), frame);
101 }
102
103 #[test]
104 fn standard_frame_roundtrip_and_suffix() {
105 let payload = "QN=20250101010101001;ST=22;CN=2011;PW=123;MN=ABC;Flag=9;CP=&&DataTime=20250101010101&&";
106 let frame = build_frame_standard(payload);
107 assert!(frame.ends_with("\r\n"));
108 let pkt = parse_frame_strict(&frame).unwrap();
109 let flag = pkt.flag_bits().unwrap();
110 assert_eq!(flag.version(), 2);
111 assert_eq!(flag.need_ack(), true);
112 assert_eq!(flag.has_packet(), false);
113 }
114
115 #[test]
116 fn strict_parse_standard_text_example_frame() {
117 let frame = "##0087QN=20240601085857223;ST=32;CN=1011;PW=123456;MN=010000A8900016F000169DC0;Flag=9;CP=&&&&2200\r\n";
119 let pkt = parse_frame_strict(frame).unwrap();
120
121 assert_eq!(pkt.length_hint, Some(87));
122 assert_eq!(pkt.crc_hex.as_deref(), Some("2200"));
123
124 assert_eq!(pkt.qn.as_deref(), Some("20240601085857223"));
125 assert_eq!(pkt.st.as_deref(), Some("32"));
126 assert_eq!(pkt.cn.as_deref(), Some("1011"));
127 assert_eq!(pkt.pw.as_deref(), Some("123456"));
128 assert_eq!(pkt.mn.as_deref(), Some("010000A8900016F000169DC0"));
129 assert_eq!(pkt.flag.as_deref(), Some("9"));
130
131 assert!(pkt.cp.is_empty());
133 }
134
135 #[test]
136 fn parse_user_provided_compat_frame_len453_crc_c0c1() {
137 let frame = "##453QN=20160801085000001;ST=22;CN=2011;PW=123456;MN=010000A8900016F000169DC0;Flag=7;CP=&&DataTime=20160801084000;a21005-Rtd=1.1,a21005-Flag=N;a21004-Rtd=112,a21004-Flag=N;a21026-Rtd=58,a21026-Flag=N;LA-td=50.1,LA-Flag=N;a34004-Rtd=207,a34004-Flag=N;a34002-Rtd=295,a34002-Flag=N;a01001-Rtd=12.6,a01001-Flag=N;a01002-Rtd=32,a01002-Flag=N;a01006-Rtd=101.02,a01006-Flag=N;a01007-Rtd=2.1,a01007-Flag=N;a01008-Rtd=120,a01008-Flag=N;a34001-Rtd=217,a34001-Flag=N;&&c0c1";
139
140 let bytes = frame.as_bytes();
142 let mut idx = 2;
143 while idx < bytes.len() && idx < 2 + 4 && (bytes[idx] as char).is_ascii_digit() {
144 idx += 1;
145 }
146 let len_str = std::str::from_utf8(&bytes[2..idx]).unwrap();
147 let declared_len: usize = len_str.parse().unwrap();
148 assert_eq!(declared_len, 453);
149 let payload_start = idx;
150 let payload_end = payload_start + declared_len;
151 let crc_end = payload_end + 4;
152 assert!(bytes.len() >= crc_end);
153 let payload = &bytes[payload_start..payload_end];
154 let got_crc = std::str::from_utf8(&bytes[payload_end..crc_end]).unwrap();
155 let expected_crc = crc16_hex_lower(payload);
156
157 assert_eq!(got_crc.to_lowercase(), "c0c1");
158 assert_eq!(expected_crc, "c0c1");
159
160 let pkt = parse_frame(frame).unwrap();
162 assert_eq!(pkt.length_hint, Some(453));
163 assert_eq!(pkt.crc_hex.as_deref(), Some("c0c1"));
164 assert_eq!(pkt.qn.as_deref(), Some("20160801085000001"));
165 assert_eq!(pkt.mn.as_deref(), Some("010000A8900016F000169DC0"));
166 assert_eq!(pkt.data_time.as_deref(), Some("20160801084000"));
167
168 assert_eq!(
170 pkt.cp.get("a21005-Rtd").map(String::as_str),
171 Some("1.1,a21005-Flag=N")
172 );
173 }
174}