1use flate2::read::DeflateDecoder;
36use flate2::write::DeflateEncoder;
37use flate2::Compression;
38use std::collections::HashMap;
39use std::error::Error;
40use std::fmt;
41use std::io::{Read, Write};
42use std::sync::LazyLock;
43
44pub const CHARSET: &str = "DXdx0123456789ABCEFGHIJKLMNOPQRSTUVWYZabcefghijklmnopqrstuvwyz-_";
46
47pub const MAGIC: u8 = 0x44;
49
50pub const PREFIX: &str = "dx";
52
53pub const PADDING: char = '=';
55
56const HEADER_SIZE: usize = 3;
58
59const TTL_HEADER_SIZE: usize = 8;
61
62const COMPRESSION_THRESHOLD: usize = 32;
64
65const FLAG_COMPRESSED: u8 = 0x01;
67const FLAG_ALGO_DEFLATE: u8 = 0x02;
68const FLAG_HAS_TTL: u8 = 0x04;
69
70const VALID_FLAGS_MASK: u8 = FLAG_COMPRESSED | FLAG_ALGO_DEFLATE | FLAG_HAS_TTL;
72
73static CHARSET_BYTES: LazyLock<Vec<u8>> = LazyLock::new(|| CHARSET.as_bytes().to_vec());
75
76static DECODE_MAP: LazyLock<HashMap<u8, u8>> = LazyLock::new(|| {
78 let mut map = HashMap::new();
79 for (i, &byte) in CHARSET_BYTES.iter().enumerate() {
80 map.insert(byte, i as u8);
81 }
82 map
83});
84
85static CRC16_TABLE: LazyLock<[u16; 256]> = LazyLock::new(|| {
87 let mut table = [0u16; 256];
88 for i in 0..256 {
89 let mut crc = (i as u16) << 8;
90 for _ in 0..8 {
91 if crc & 0x8000 != 0 {
92 crc = (crc << 1) ^ 0x1021;
93 } else {
94 crc <<= 1;
95 }
96 }
97 table[i] = crc;
98 }
99 table
100});
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum DxError {
105 InvalidPrefix,
107 InvalidLength,
109 InvalidCharacter(char),
111 Utf8Error(String),
113 ChecksumMismatch { expected: u16, actual: u16 },
115 InvalidHeader,
117 CompressionError(String),
119 InvalidFlags(u8),
121 TtlExpired {
123 created_at: u64,
124 ttl_seconds: u32,
125 expired_at: u64,
126 },
127}
128
129impl fmt::Display for DxError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 DxError::InvalidPrefix => write!(f, "无效的 DX 编码:缺少 dx 前缀"),
133 DxError::InvalidLength => write!(f, "无效的 DX 编码:长度不正确"),
134 DxError::InvalidCharacter(c) => write!(f, "无效的 DX 编码:包含非法字符 '{}'", c),
135 DxError::Utf8Error(s) => write!(f, "UTF-8 解码错误:{}", s),
136 DxError::ChecksumMismatch { expected, actual } => {
137 write!(
138 f,
139 "校验和不匹配:期望 0x{:04X},实际 0x{:04X}",
140 expected, actual
141 )
142 }
143 DxError::InvalidHeader => write!(f, "无效的格式头部"),
144 DxError::CompressionError(s) => write!(f, "压缩/解压缩错误:{}", s),
145 DxError::InvalidFlags(flags) => write!(f, "无效的 flags 字节:0x{:02X}", flags),
146 DxError::TtlExpired {
147 created_at,
148 ttl_seconds,
149 expired_at,
150 } => {
151 write!(
152 f,
153 "TTL 已过期:创建于 {},有效期 {} 秒,已于 {} 过期",
154 created_at, ttl_seconds, expired_at
155 )
156 }
157 }
158 }
159}
160
161
162impl Error for DxError {}
163
164pub type Result<T> = std::result::Result<T, DxError>;
166
167pub fn crc16(data: &[u8]) -> u16 {
169 let mut crc: u16 = 0xFFFF;
170 for &byte in data {
171 let index = ((crc >> 8) ^ (byte as u16)) as usize;
172 crc = (crc << 8) ^ CRC16_TABLE[index];
173 }
174 crc
175}
176
177fn compress_deflate(data: &[u8]) -> Result<Vec<u8>> {
179 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
180 encoder
181 .write_all(data)
182 .map_err(|e| DxError::CompressionError(e.to_string()))?;
183 encoder
184 .finish()
185 .map_err(|e| DxError::CompressionError(e.to_string()))
186}
187
188fn decompress_deflate(data: &[u8]) -> Result<Vec<u8>> {
190 let mut decoder = DeflateDecoder::new(data);
191 let mut decompressed = Vec::new();
192 decoder
193 .read_to_end(&mut decompressed)
194 .map_err(|e| DxError::CompressionError(e.to_string()))?;
195 Ok(decompressed)
196}
197
198fn encode_raw(data: &[u8]) -> String {
200 if data.is_empty() {
201 return String::new();
202 }
203
204 let mut result = String::with_capacity((data.len() + 2) / 3 * 4);
205 let charset = &*CHARSET_BYTES;
206
207 for chunk in data.chunks(3) {
209 let b0 = chunk[0];
210 let b1 = chunk.get(1).copied().unwrap_or(0);
211 let b2 = chunk.get(2).copied().unwrap_or(0);
212
213 let v0 = (b0 >> 2) & 0x3F;
215 let v1 = ((b0 & 0x03) << 4 | (b1 >> 4)) & 0x3F;
216 let v2 = ((b1 & 0x0F) << 2 | (b2 >> 6)) & 0x3F;
217 let v3 = b2 & 0x3F;
218
219 result.push(charset[((v0 ^ MAGIC) & 0x3F) as usize] as char);
221 result.push(charset[((v1 ^ MAGIC) & 0x3F) as usize] as char);
222
223 if chunk.len() > 1 {
224 result.push(charset[((v2 ^ MAGIC) & 0x3F) as usize] as char);
225 } else {
226 result.push(PADDING);
227 }
228
229 if chunk.len() > 2 {
230 result.push(charset[((v3 ^ MAGIC) & 0x3F) as usize] as char);
231 } else {
232 result.push(PADDING);
233 }
234 }
235
236 result
237}
238
239fn decode_raw(data: &str) -> Result<Vec<u8>> {
241 if data.is_empty() {
242 return Ok(Vec::new());
243 }
244
245 if data.len() % 4 != 0 {
247 return Err(DxError::InvalidLength);
248 }
249
250 let padding_count = if data.ends_with("==") {
252 2
253 } else if data.ends_with('=') {
254 1
255 } else {
256 0
257 };
258
259 let output_len = (data.len() / 4) * 3 - padding_count;
261 let mut result = Vec::with_capacity(output_len);
262
263 let decode_map = &*DECODE_MAP;
264 let data_bytes = data.as_bytes();
265
266 for chunk in data_bytes.chunks(4) {
268 let c0 = chunk[0];
269 let c1 = chunk[1];
270 let c2 = chunk[2];
271 let c3 = chunk[3];
272
273 let i0 = *decode_map
275 .get(&c0)
276 .ok_or_else(|| DxError::InvalidCharacter(c0 as char))?;
277 let i1 = *decode_map
278 .get(&c1)
279 .ok_or_else(|| DxError::InvalidCharacter(c1 as char))?;
280
281 let i2 = if c2 == PADDING as u8 {
282 0
283 } else {
284 *decode_map
285 .get(&c2)
286 .ok_or_else(|| DxError::InvalidCharacter(c2 as char))?
287 };
288
289 let i3 = if c3 == PADDING as u8 {
290 0
291 } else {
292 *decode_map
293 .get(&c3)
294 .ok_or_else(|| DxError::InvalidCharacter(c3 as char))?
295 };
296
297 let v0 = (i0 ^ MAGIC) & 0x3F;
299 let v1 = (i1 ^ MAGIC) & 0x3F;
300 let v2 = (i2 ^ MAGIC) & 0x3F;
301 let v3 = (i3 ^ MAGIC) & 0x3F;
302
303 let b0 = (v0 << 2) | (v1 >> 4);
305 let b1 = ((v1 & 0x0F) << 4) | (v2 >> 2);
306 let b2 = ((v2 & 0x03) << 6) | v3;
307
308 if result.len() < output_len {
309 result.push(b0);
310 }
311 if result.len() < output_len {
312 result.push(b1);
313 }
314 if result.len() < output_len {
315 result.push(b2);
316 }
317 }
318
319 Ok(result)
320}
321
322pub fn encode(data: &[u8]) -> String {
341 encode_with_options(data, true)
342}
343
344pub fn encode_with_options(data: &[u8], allow_compression: bool) -> String {
355 let checksum = crc16(data);
357
358 let (flags, payload) = if allow_compression && data.len() >= COMPRESSION_THRESHOLD {
360 match compress_deflate(data) {
362 Ok(compressed) => {
363 if compressed.len() + 2 < data.len() && data.len() <= 65535 {
366 let mut payload = Vec::with_capacity(2 + compressed.len());
368 payload.push((data.len() >> 8) as u8);
370 payload.push((data.len() & 0xFF) as u8);
371 payload.extend_from_slice(&compressed);
372 (FLAG_COMPRESSED | FLAG_ALGO_DEFLATE, payload)
373 } else {
374 (0u8, data.to_vec())
376 }
377 }
378 Err(_) => {
379 (0u8, data.to_vec())
381 }
382 }
383 } else {
384 (0u8, data.to_vec())
386 };
387
388 let header = [flags, (checksum >> 8) as u8, (checksum & 0xFF) as u8];
390
391 let mut combined = Vec::with_capacity(HEADER_SIZE + payload.len());
393 combined.extend_from_slice(&header);
394 combined.extend_from_slice(&payload);
395
396 let mut result = String::with_capacity(PREFIX.len() + (combined.len() + 2) / 3 * 4);
398 result.push_str(PREFIX);
399 result.push_str(&encode_raw(&combined));
400 result
401}
402
403pub fn encode_str(s: &str) -> String {
422 encode(s.as_bytes())
423}
424
425pub fn encode_str_with_options(s: &str, allow_compression: bool) -> String {
427 encode_with_options(s.as_bytes(), allow_compression)
428}
429
430pub fn decode(encoded: &str) -> Result<Vec<u8>> {
450 decode_with_options(encoded, true)
451}
452
453pub fn decode_with_options(encoded: &str, check_ttl: bool) -> Result<Vec<u8>> {
464 if !encoded.starts_with(PREFIX) {
466 return Err(DxError::InvalidPrefix);
467 }
468
469 let data = &encoded[PREFIX.len()..];
471
472 let combined = decode_raw(data)?;
474
475 if combined.len() < HEADER_SIZE {
477 return Err(DxError::InvalidHeader);
478 }
479
480 let flags = combined[0];
482 let expected_checksum = ((combined[1] as u16) << 8) | (combined[2] as u16);
483
484 if flags & !VALID_FLAGS_MASK != 0 {
486 return Err(DxError::InvalidFlags(flags));
487 }
488
489 let payload_start = if flags & FLAG_HAS_TTL != 0 {
491 if combined.len() < HEADER_SIZE + TTL_HEADER_SIZE {
493 return Err(DxError::InvalidHeader);
494 }
495
496 if check_ttl {
498 let created_at = u32::from_be_bytes([
499 combined[HEADER_SIZE],
500 combined[HEADER_SIZE + 1],
501 combined[HEADER_SIZE + 2],
502 combined[HEADER_SIZE + 3],
503 ]) as u64;
504
505 let ttl_seconds = u32::from_be_bytes([
506 combined[HEADER_SIZE + 4],
507 combined[HEADER_SIZE + 5],
508 combined[HEADER_SIZE + 6],
509 combined[HEADER_SIZE + 7],
510 ]);
511
512 if ttl_seconds > 0 {
514 let expires_at = created_at + ttl_seconds as u64;
515 let now = std::time::SystemTime::now()
516 .duration_since(std::time::UNIX_EPOCH)
517 .map(|d| d.as_secs())
518 .unwrap_or(0);
519
520 if now > expires_at {
521 return Err(DxError::TtlExpired {
522 created_at,
523 ttl_seconds,
524 expired_at: expires_at,
525 });
526 }
527 }
528 }
529
530 HEADER_SIZE + TTL_HEADER_SIZE
531 } else {
532 HEADER_SIZE
533 };
534
535 let payload = &combined[payload_start..];
537
538 let original_data = if flags & FLAG_COMPRESSED != 0 {
540 if payload.len() < 2 {
542 return Err(DxError::InvalidHeader);
543 }
544
545 let _original_size = ((payload[0] as usize) << 8) | (payload[1] as usize);
547
548 let compressed_data = &payload[2..];
550 decompress_deflate(compressed_data)?
551 } else {
552 payload.to_vec()
554 };
555
556 let actual_checksum = crc16(&original_data);
558 if expected_checksum != actual_checksum {
559 return Err(DxError::ChecksumMismatch {
560 expected: expected_checksum,
561 actual: actual_checksum,
562 });
563 }
564
565 Ok(original_data)
566}
567
568pub fn decode_str(encoded: &str) -> Result<String> {
588 decode_str_with_options(encoded, true)
589}
590
591pub fn decode_str_with_options(encoded: &str, check_ttl: bool) -> Result<String> {
602 let bytes = decode_with_options(encoded, check_ttl)?;
603 String::from_utf8(bytes).map_err(|e| DxError::Utf8Error(e.to_string()))
604}
605
606pub fn is_encoded(s: &str) -> bool {
626 if !s.starts_with(PREFIX) {
627 return false;
628 }
629
630 let data = &s[PREFIX.len()..];
631
632 if data.is_empty() || data.len() % 4 != 0 {
634 return false;
635 }
636
637 let decode_map = &*DECODE_MAP;
638
639 for (i, c) in data.bytes().enumerate() {
641 if c == PADDING as u8 {
642 if i < data.len() - 2 {
644 return false;
645 }
646 } else if !decode_map.contains_key(&c) {
647 return false;
648 }
649 }
650
651 true
652}
653
654pub fn verify(encoded: &str) -> Result<bool> {
673 match decode(encoded) {
674 Ok(_) => Ok(true),
675 Err(DxError::ChecksumMismatch { .. }) => Ok(false),
676 Err(e) => Err(e),
677 }
678}
679
680pub fn get_checksum(encoded: &str) -> Result<(u16, u16)> {
700 if !encoded.starts_with(PREFIX) {
702 return Err(DxError::InvalidPrefix);
703 }
704
705 let data = &encoded[PREFIX.len()..];
707
708 let combined = decode_raw(data)?;
710
711 if combined.len() < HEADER_SIZE {
713 return Err(DxError::InvalidHeader);
714 }
715
716 let flags = combined[0];
718 let stored = ((combined[1] as u16) << 8) | (combined[2] as u16);
719
720 let payload = &combined[HEADER_SIZE..];
722
723 let original_data = if flags & FLAG_COMPRESSED != 0 {
725 if payload.len() < 2 {
726 return Err(DxError::InvalidHeader);
727 }
728 let compressed_data = &payload[2..];
729 decompress_deflate(compressed_data)?
730 } else {
731 payload.to_vec()
732 };
733
734 let computed = crc16(&original_data);
735
736 Ok((stored, computed))
737}
738
739pub fn is_compressed(encoded: &str) -> Result<bool> {
761 if !encoded.starts_with(PREFIX) {
763 return Err(DxError::InvalidPrefix);
764 }
765
766 let data = &encoded[PREFIX.len()..];
768
769 let combined = decode_raw(data)?;
771
772 if combined.len() < HEADER_SIZE {
774 return Err(DxError::InvalidHeader);
775 }
776
777 let flags = combined[0];
779 Ok(flags & FLAG_COMPRESSED != 0)
780}
781
782#[derive(Debug, Clone)]
784pub struct TtlInfo {
785 pub created_at: u64,
787 pub ttl_seconds: u32,
789 pub expires_at: Option<u64>,
791 pub is_expired: bool,
793}
794
795pub fn has_ttl(encoded: &str) -> Result<bool> {
805 if !encoded.starts_with(PREFIX) {
807 return Err(DxError::InvalidPrefix);
808 }
809
810 let data = &encoded[PREFIX.len()..];
812
813 let combined = decode_raw(data)?;
815
816 if combined.len() < HEADER_SIZE {
818 return Err(DxError::InvalidHeader);
819 }
820
821 let flags = combined[0];
823 Ok(flags & FLAG_HAS_TTL != 0)
824}
825
826pub fn get_ttl_info(encoded: &str) -> Result<Option<TtlInfo>> {
848 if !encoded.starts_with(PREFIX) {
850 return Err(DxError::InvalidPrefix);
851 }
852
853 let data = &encoded[PREFIX.len()..];
855
856 let combined = decode_raw(data)?;
858
859 if combined.len() < HEADER_SIZE {
861 return Err(DxError::InvalidHeader);
862 }
863
864 let flags = combined[0];
865
866 if flags & FLAG_HAS_TTL == 0 {
868 return Ok(None);
869 }
870
871 if combined.len() < HEADER_SIZE + TTL_HEADER_SIZE {
873 return Err(DxError::InvalidHeader);
874 }
875
876 let created_at = u32::from_be_bytes([
878 combined[HEADER_SIZE],
879 combined[HEADER_SIZE + 1],
880 combined[HEADER_SIZE + 2],
881 combined[HEADER_SIZE + 3],
882 ]) as u64;
883
884 let ttl_seconds = u32::from_be_bytes([
885 combined[HEADER_SIZE + 4],
886 combined[HEADER_SIZE + 5],
887 combined[HEADER_SIZE + 6],
888 combined[HEADER_SIZE + 7],
889 ]);
890
891 let (expires_at, is_expired) = if ttl_seconds == 0 {
893 (None, false)
894 } else {
895 let expires = created_at + ttl_seconds as u64;
896 let now = std::time::SystemTime::now()
897 .duration_since(std::time::UNIX_EPOCH)
898 .map(|d| d.as_secs())
899 .unwrap_or(0);
900 (Some(expires), now > expires)
901 };
902
903 Ok(Some(TtlInfo {
904 created_at,
905 ttl_seconds,
906 expires_at,
907 is_expired,
908 }))
909}
910
911pub fn is_expired(encoded: &str) -> Result<bool> {
930 match get_ttl_info(encoded)? {
931 Some(info) => Ok(info.is_expired),
932 None => Ok(false), }
934}
935
936pub fn encode_with_ttl(data: &[u8], ttl_seconds: u32) -> String {
957 encode_with_ttl_and_options(data, ttl_seconds, true)
958}
959
960pub fn encode_with_ttl_and_options(data: &[u8], ttl_seconds: u32, allow_compression: bool) -> String {
972 let created_at = std::time::SystemTime::now()
974 .duration_since(std::time::UNIX_EPOCH)
975 .map(|d| d.as_secs() as u32)
976 .unwrap_or(0);
977
978 let checksum = crc16(data);
980
981 let (mut flags, payload) = if allow_compression && data.len() >= COMPRESSION_THRESHOLD {
983 match compress_deflate(data) {
984 Ok(compressed) => {
985 if compressed.len() + 2 < data.len() && data.len() <= 65535 {
986 let mut payload = Vec::with_capacity(2 + compressed.len());
987 payload.push((data.len() >> 8) as u8);
988 payload.push((data.len() & 0xFF) as u8);
989 payload.extend_from_slice(&compressed);
990 (FLAG_COMPRESSED | FLAG_ALGO_DEFLATE, payload)
991 } else {
992 (0u8, data.to_vec())
993 }
994 }
995 Err(_) => (0u8, data.to_vec()),
996 }
997 } else {
998 (0u8, data.to_vec())
999 };
1000
1001 flags |= FLAG_HAS_TTL;
1003
1004 let mut combined = Vec::with_capacity(HEADER_SIZE + TTL_HEADER_SIZE + payload.len());
1006 combined.push(flags);
1007 combined.push((checksum >> 8) as u8);
1008 combined.push((checksum & 0xFF) as u8);
1009 combined.extend_from_slice(&created_at.to_be_bytes());
1010 combined.extend_from_slice(&ttl_seconds.to_be_bytes());
1011 combined.extend_from_slice(&payload);
1012
1013 let mut result = String::with_capacity(PREFIX.len() + (combined.len() + 2) / 3 * 4);
1015 result.push_str(PREFIX);
1016 result.push_str(&encode_raw(&combined));
1017 result
1018}
1019
1020pub fn encode_str_with_ttl(s: &str, ttl_seconds: u32) -> String {
1040 encode_with_ttl(s.as_bytes(), ttl_seconds)
1041}
1042
1043#[derive(Debug, Clone)]
1045pub struct Info {
1046 pub name: &'static str,
1047 pub version: &'static str,
1048 pub author: &'static str,
1049 pub charset: &'static str,
1050 pub prefix: &'static str,
1051 pub magic: u8,
1052 pub padding: char,
1053 pub checksum: &'static str,
1054 pub compression: &'static str,
1055 pub compression_threshold: usize,
1056}
1057
1058pub fn get_info() -> Info {
1074 Info {
1075 name: "DX Encoding",
1076 version: "2.3.0",
1077 author: "Dogxi",
1078 charset: CHARSET,
1079 prefix: PREFIX,
1080 magic: MAGIC,
1081 padding: PADDING,
1082 checksum: "CRC16-CCITT",
1083 compression: "DEFLATE",
1084 compression_threshold: COMPRESSION_THRESHOLD,
1085 }
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090 use super::*;
1091
1092 #[test]
1093 fn test_simple_string() {
1094 let original = "Hello";
1095 let encoded = encode_str(original);
1096 let decoded = decode_str(&encoded).unwrap();
1097 assert_eq!(decoded, original);
1098 assert!(encoded.starts_with("dx"));
1099 }
1100
1101 #[test]
1102 fn test_chinese_string() {
1103 let original = "你好,世界!";
1104 let encoded = encode_str(original);
1105 let decoded = decode_str(&encoded).unwrap();
1106 assert_eq!(decoded, original);
1107 }
1108
1109 #[test]
1110 fn test_emoji() {
1111 let original = "🎉🚀✨";
1112 let encoded = encode_str(original);
1113 let decoded = decode_str(&encoded).unwrap();
1114 assert_eq!(decoded, original);
1115 }
1116
1117 #[test]
1118 fn test_empty_string() {
1119 let original = "";
1120 let encoded = encode_str(original);
1121 let decoded = decode_str(&encoded).unwrap();
1122 assert_eq!(decoded, original);
1123 assert!(encoded.starts_with("dx"));
1124 }
1125
1126 #[test]
1127 fn test_binary_data() {
1128 let original: Vec<u8> = vec![0x00, 0x01, 0x02, 0xFE, 0xFF];
1129 let encoded = encode(&original);
1130 let decoded = decode(&encoded).unwrap();
1131 assert_eq!(decoded, original);
1132 }
1133
1134 #[test]
1135 fn test_all_byte_values() {
1136 let original: Vec<u8> = (0..=255).collect();
1137 let encoded = encode(&original);
1138 let decoded = decode(&encoded).unwrap();
1139 assert_eq!(decoded, original);
1140 }
1141
1142 #[test]
1143 fn test_is_encoded() {
1144 let encoded = encode_str("Hello");
1145 assert!(is_encoded(&encoded));
1146 assert!(!is_encoded("hello"));
1147 }
1148
1149 #[test]
1150 fn test_decode_invalid_prefix() {
1151 let result = decode("invalid");
1152 assert!(matches!(result, Err(DxError::InvalidPrefix)));
1153 }
1154
1155 #[test]
1156 fn test_decode_invalid_length() {
1157 let result = decode("dxABC");
1158 assert!(matches!(result, Err(DxError::InvalidLength)));
1159 }
1160
1161 #[test]
1162 fn test_checksum_verification() {
1163 let encoded = encode_str("Hello");
1164 assert!(verify(&encoded).unwrap());
1165
1166 let (stored, computed) = get_checksum(&encoded).unwrap();
1167 assert_eq!(stored, computed);
1168 }
1169
1170 #[test]
1171 fn test_checksum_mismatch() {
1172 let encoded = encode_str("Hello World Test Data");
1173
1174 let mut chars: Vec<char> = encoded.chars().collect();
1176
1177 if chars.len() > 10 {
1179 let pos = 10;
1180 let original_char = chars[pos];
1181 chars[pos] = if original_char == 'A' { 'B' } else { 'A' };
1183 }
1184
1185 let modified: String = chars.into_iter().collect();
1186
1187 let result = decode(&modified);
1189 assert!(
1190 matches!(result, Err(DxError::ChecksumMismatch { .. }))
1191 || matches!(result, Err(DxError::InvalidCharacter(_)))
1192 );
1193 }
1194
1195 #[test]
1196 fn test_get_info() {
1197 let info = get_info();
1198 assert_eq!(info.name, "DX Encoding");
1199 assert_eq!(info.author, "Dogxi");
1200 assert_eq!(info.prefix, "dx");
1201 assert_eq!(info.magic, 0x44);
1202 assert_eq!(info.charset.len(), 64);
1203 assert_eq!(info.version, "2.3.0");
1204 assert_eq!(info.checksum, "CRC16-CCITT");
1205 assert_eq!(info.compression, "DEFLATE");
1206 }
1207
1208 #[test]
1209 fn test_various_lengths() {
1210 for length in 0..100 {
1211 let original: Vec<u8> = (0..length).map(|i| (i % 256) as u8).collect();
1212 let encoded = encode(&original);
1213 let decoded = decode(&encoded).unwrap();
1214 assert_eq!(decoded, original, "长度 {} 失败", length);
1215 }
1216 }
1217
1218 #[test]
1219 fn test_crc16() {
1220 assert_eq!(crc16(&[]), 0xFFFF);
1222
1223 let data = b"123456789";
1225 let crc = crc16(data);
1226 assert_eq!(crc, 0x29B1);
1227 }
1228
1229 #[test]
1230 fn test_crc16_deterministic() {
1231 let data = b"Hello, World!";
1232 let crc1 = crc16(data);
1233 let crc2 = crc16(data);
1234 assert_eq!(crc1, crc2);
1235 }
1236
1237 #[test]
1238 fn test_verify_function() {
1239 let encoded = encode_str("Test data for verification");
1240 assert!(verify(&encoded).unwrap());
1241 }
1242
1243 #[test]
1246 fn test_short_data_not_compressed() {
1247 let original = "Short";
1248 let encoded = encode_str(original);
1249 assert!(!is_compressed(&encoded).unwrap());
1250
1251 let decoded = decode_str(&encoded).unwrap();
1252 assert_eq!(decoded, original);
1253 }
1254
1255 #[test]
1256 fn test_long_repetitive_data_compressed() {
1257 let original = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
1259 let encoded = encode_str(original);
1260
1261 let decoded = decode_str(&encoded).unwrap();
1263 assert_eq!(decoded, original);
1264
1265 assert!(is_compressed(&encoded).unwrap());
1267 }
1268
1269 #[test]
1270 fn test_compression_saves_space() {
1271 let original = "Hello World! ".repeat(100);
1273 let encoded_compressed = encode_str(&original);
1274 let encoded_uncompressed = encode_str_with_options(&original, false);
1275
1276 assert!(
1278 encoded_compressed.len() < encoded_uncompressed.len(),
1279 "压缩版本 ({}) 应该比未压缩版本 ({}) 短",
1280 encoded_compressed.len(),
1281 encoded_uncompressed.len()
1282 );
1283
1284 assert_eq!(decode_str(&encoded_compressed).unwrap(), original);
1286 assert_eq!(decode_str(&encoded_uncompressed).unwrap(), original);
1287 }
1288
1289 #[test]
1290 fn test_incompressible_data() {
1291 let original: Vec<u8> = (0..100).map(|i| (i * 7 + 13) as u8).collect();
1293 let encoded = encode(&original);
1294
1295 let decoded = decode(&encoded).unwrap();
1297 assert_eq!(decoded, original);
1298 }
1299
1300 #[test]
1301 fn test_encode_without_compression() {
1302 let original = "A".repeat(100);
1303 let encoded = encode_str_with_options(&original, false);
1304
1305 assert!(!is_compressed(&encoded).unwrap());
1307
1308 let decoded = decode_str(&encoded).unwrap();
1310 assert_eq!(decoded, original);
1311 }
1312
1313 #[test]
1314 fn test_compression_threshold() {
1315 let short_data = "x".repeat(COMPRESSION_THRESHOLD - 1);
1317 let encoded_short = encode_str(&short_data);
1318 assert!(!is_compressed(&encoded_short).unwrap());
1319
1320 let long_data = "x".repeat(COMPRESSION_THRESHOLD + 10);
1322 let encoded_long = encode_str(&long_data);
1323 assert!(is_compressed(&encoded_long).unwrap());
1325 }
1326
1327 #[test]
1328 fn test_large_data_compression() {
1329 let original = "The quick brown fox jumps over the lazy dog. ".repeat(500);
1331 let encoded = encode_str(&original);
1332
1333 let decoded = decode_str(&encoded).unwrap();
1335 assert_eq!(decoded, original);
1336
1337 assert!(verify(&encoded).unwrap());
1339 }
1340
1341 #[test]
1344 fn test_encode_with_ttl() {
1345 let original = b"Secret Data";
1346 let encoded = encode_with_ttl(original, 3600); assert!(encoded.starts_with("dx"));
1349
1350 assert!(has_ttl(&encoded).unwrap());
1352
1353 let decoded = decode(&encoded).unwrap();
1355 assert_eq!(decoded, original);
1356 }
1357
1358 #[test]
1359 fn test_encode_str_with_ttl() {
1360 let original = "临时令牌";
1361 let encoded = encode_str_with_ttl(original, 1800); assert!(has_ttl(&encoded).unwrap());
1364
1365 let decoded = decode_str(&encoded).unwrap();
1366 assert_eq!(decoded, original);
1367 }
1368
1369 #[test]
1370 fn test_ttl_info() {
1371 let encoded = encode_with_ttl(b"Test", 3600);
1372
1373 let info = get_ttl_info(&encoded).unwrap().unwrap();
1374 assert_eq!(info.ttl_seconds, 3600);
1375 assert!(!info.is_expired);
1376 assert!(info.expires_at.is_some());
1377
1378 let now = std::time::SystemTime::now()
1380 .duration_since(std::time::UNIX_EPOCH)
1381 .unwrap()
1382 .as_secs();
1383 assert!(info.created_at <= now && info.created_at >= now - 5);
1384 }
1385
1386 #[test]
1387 fn test_ttl_zero_never_expires() {
1388 let encoded = encode_with_ttl(b"Forever", 0); let info = get_ttl_info(&encoded).unwrap().unwrap();
1391 assert_eq!(info.ttl_seconds, 0);
1392 assert!(info.expires_at.is_none());
1393 assert!(!info.is_expired);
1394 assert!(!is_expired(&encoded).unwrap());
1395 }
1396
1397 #[test]
1398 fn test_no_ttl_returns_none() {
1399 let encoded = encode(b"No TTL");
1400
1401 assert!(!has_ttl(&encoded).unwrap());
1402 assert!(get_ttl_info(&encoded).unwrap().is_none());
1403 assert!(!is_expired(&encoded).unwrap());
1404 }
1405
1406 #[test]
1407 fn test_ttl_with_compression() {
1408 let original = "Repeated data for compression test. ".repeat(50);
1410 let encoded = encode_str_with_ttl(&original, 7200);
1411
1412 assert!(has_ttl(&encoded).unwrap());
1414 assert!(is_compressed(&encoded).unwrap());
1415
1416 let decoded = decode_str(&encoded).unwrap();
1418 assert_eq!(decoded, original);
1419 }
1420
1421 #[test]
1422 fn test_ttl_without_compression() {
1423 let original = "Short";
1424 let encoded = encode_with_ttl_and_options(original.as_bytes(), 3600, false);
1425
1426 assert!(has_ttl(&encoded).unwrap());
1427 assert!(!is_compressed(&encoded).unwrap());
1428
1429 let decoded = decode_str(&encoded).unwrap();
1430 assert_eq!(decoded, original);
1431 }
1432
1433 #[test]
1434 fn test_decode_skip_ttl_check() {
1435 let encoded = encode_with_ttl(b"Data", 1);
1437
1438 let decoded = decode_with_options(&encoded, false).unwrap();
1440 assert_eq!(decoded, b"Data");
1441 }
1442
1443 #[test]
1444 fn test_is_expired_function() {
1445 let encoded = encode_with_ttl(b"Data", 86400); assert!(!is_expired(&encoded).unwrap());
1447 }
1448}