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