tds_protocol/
codec.rs

1//! Codec utilities for TDS protocol encoding and decoding.
2//!
3//! This module provides low-level encoding and decoding utilities used
4//! throughout the TDS protocol implementation.
5
6use bytes::{Buf, BufMut};
7
8/// Read a length-prefixed UTF-16LE string.
9///
10/// The format is: 1-byte length (in characters) followed by UTF-16LE bytes.
11pub fn read_b_varchar(src: &mut impl Buf) -> Option<String> {
12    if src.remaining() < 1 {
13        return None;
14    }
15    let len = src.get_u8() as usize;
16    read_utf16_string(src, len)
17}
18
19/// Read a length-prefixed UTF-16LE string with 2-byte length.
20///
21/// The format is: 2-byte length (in characters) followed by UTF-16LE bytes.
22pub fn read_us_varchar(src: &mut impl Buf) -> Option<String> {
23    if src.remaining() < 2 {
24        return None;
25    }
26    let len = src.get_u16_le() as usize;
27    read_utf16_string(src, len)
28}
29
30/// Read a UTF-16LE string of specified character length.
31pub fn read_utf16_string(src: &mut impl Buf, char_count: usize) -> Option<String> {
32    let byte_count = char_count * 2;
33    if src.remaining() < byte_count {
34        return None;
35    }
36
37    let mut chars = Vec::with_capacity(char_count);
38    for _ in 0..char_count {
39        chars.push(src.get_u16_le());
40    }
41
42    String::from_utf16(&chars).ok()
43}
44
45/// Write a length-prefixed UTF-16LE string (1-byte length).
46pub fn write_b_varchar(dst: &mut impl BufMut, s: &str) {
47    let chars: Vec<u16> = s.encode_utf16().collect();
48    let len = chars.len().min(255) as u8;
49    dst.put_u8(len);
50    for &c in &chars[..len as usize] {
51        dst.put_u16_le(c);
52    }
53}
54
55/// Write a length-prefixed UTF-16LE string (2-byte length).
56pub fn write_us_varchar(dst: &mut impl BufMut, s: &str) {
57    let chars: Vec<u16> = s.encode_utf16().collect();
58    let len = chars.len().min(65535) as u16;
59    dst.put_u16_le(len);
60    for &c in &chars[..len as usize] {
61        dst.put_u16_le(c);
62    }
63}
64
65/// Write a UTF-16LE string without length prefix.
66pub fn write_utf16_string(dst: &mut impl BufMut, s: &str) {
67    for c in s.encode_utf16() {
68        dst.put_u16_le(c);
69    }
70}
71
72/// Read a null-terminated ASCII string.
73pub fn read_null_terminated_ascii(src: &mut impl Buf) -> Option<String> {
74    let mut bytes = Vec::new();
75    while src.has_remaining() {
76        let b = src.get_u8();
77        if b == 0 {
78            break;
79        }
80        bytes.push(b);
81    }
82    String::from_utf8(bytes).ok()
83}
84
85/// Calculate the byte length of a UTF-16 encoded string.
86#[must_use]
87pub fn utf16_byte_len(s: &str) -> usize {
88    s.encode_utf16().count() * 2
89}
90
91#[cfg(not(feature = "std"))]
92use alloc::string::String;
93#[cfg(not(feature = "std"))]
94use alloc::vec::Vec;
95
96#[cfg(test)]
97#[allow(clippy::unwrap_used)]
98mod tests {
99    use super::*;
100    use bytes::BytesMut;
101
102    #[test]
103    fn test_b_varchar_roundtrip() {
104        let original = "Hello, 世界!";
105        let mut buf = BytesMut::new();
106        write_b_varchar(&mut buf, original);
107
108        let mut cursor = buf.freeze();
109        let decoded = read_b_varchar(&mut cursor).unwrap();
110        assert_eq!(decoded, original);
111    }
112
113    #[test]
114    fn test_us_varchar_roundtrip() {
115        let original = "Test string with Unicode: αβγ";
116        let mut buf = BytesMut::new();
117        write_us_varchar(&mut buf, original);
118
119        let mut cursor = buf.freeze();
120        let decoded = read_us_varchar(&mut cursor).unwrap();
121        assert_eq!(decoded, original);
122    }
123
124    #[test]
125    fn test_utf16_byte_len() {
126        assert_eq!(utf16_byte_len("Hello"), 10);
127        assert_eq!(utf16_byte_len("世界"), 4);
128    }
129}