1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::SqlReadBytes;
// The ordered column indexes are decoded and traced at TRACE on receipt but are
// not otherwise consumed; retained for `Debug` diagnostics and future surfacing
// of result ordering to callers.
#[allow(dead_code)]
#[derive(Debug)]
pub struct TokenOrder {
pub(crate) column_indexes: Vec<u16>,
}
impl TokenOrder {
pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
where
R: SqlReadBytes + Unpin,
{
// `Length` is the byte length of the column-index list; each index is a
// 2-byte USHORT, so it must be even. An odd length would truncate on the
// `/ 2` below and leave a stray byte unconsumed, desyncing the stream.
let raw_len = src.read_u16_le().await?;
if raw_len % 2 != 0 {
return Err(crate::Error::Protocol(
format!("ORDER token length {raw_len} is not a multiple of 2").into(),
));
}
let len = raw_len / 2;
// `len` is derived from an untrusted u16; cap the up-front reservation
// (the Vec still grows as indexes are actually read).
let mut column_indexes =
Vec::with_capacity((len as usize).min(crate::tds::codec::column_data::MAX_PREALLOC));
for _ in 0..len {
column_indexes.push(src.read_u16_le().await?);
}
Ok(TokenOrder { column_indexes })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
#[tokio::test]
async fn decodes_column_indexes() {
let mut buf = BytesMut::new();
// length is in bytes; three u16 indexes => 6 bytes
buf.put_u16_le(6);
buf.put_u16_le(1);
buf.put_u16_le(2);
buf.put_u16_le(3);
let order = TokenOrder::decode(&mut buf.into_sql_read_bytes())
.await
.unwrap();
assert_eq!(order.column_indexes, vec![1, 2, 3]);
}
#[tokio::test]
async fn decodes_empty() {
let mut buf = BytesMut::new();
buf.put_u16_le(0);
let order = TokenOrder::decode(&mut buf.into_sql_read_bytes())
.await
.unwrap();
assert!(order.column_indexes.is_empty());
}
#[tokio::test]
async fn rejects_odd_length() {
// An odd byte length cannot hold a whole number of 2-byte indexes; it
// must be a protocol error rather than silently truncating and leaving a
// stray byte on the wire.
let mut buf = BytesMut::new();
buf.put_u16_le(3); // odd
buf.put_u16_le(1);
let err = TokenOrder::decode(&mut buf.into_sql_read_bytes())
.await
.expect_err("odd length must be rejected");
assert!(matches!(err, crate::Error::Protocol(_)));
}
#[tokio::test]
async fn consumes_exactly_declared_length() {
// The decoder must read exactly `Length` bytes of index data and stop,
// leaving anything that follows untouched. A trailing sentinel that
// reads back cleanly proves the token did not over- or under-consume
// and so did not desync the stream for the next token.
let mut buf = BytesMut::new();
buf.put_u16_le(4); // two u16 indexes
buf.put_u16_le(10);
buf.put_u16_le(20);
buf.put_u16_le(0xBEEF); // sentinel: belongs to the *next* token
let mut reader = buf.into_sql_read_bytes();
let order = TokenOrder::decode(&mut reader).await.unwrap();
assert_eq!(order.column_indexes, vec![10, 20]);
let sentinel = reader
.read_u16_le()
.await
.expect("the sentinel following the ORDER token must still be readable");
assert_eq!(
sentinel, 0xBEEF,
"decode consumed past its declared length and desynced the stream"
);
}
#[tokio::test]
async fn rejects_length_longer_than_content() {
// `Length` claims three indexes (6 bytes) but only one is present. The
// decoder must surface a clean error when the stream runs out rather
// than panicking or looping.
let mut buf = BytesMut::new();
buf.put_u16_le(6); // three indexes declared
buf.put_u16_le(1); // only one provided
let err = TokenOrder::decode(&mut buf.into_sql_read_bytes())
.await
.expect_err("a length longer than the content must error");
// An out-of-data read surfaces as an IO error through the reader.
assert!(matches!(err, crate::Error::Io { .. }));
}
}