Skip to content

Commit f1f3ea8

Browse files
committed
Merged PR 7117: Sync development with main
Sync development with main ---- #### AI description (iteration 1) #### PR Classification This pull request is a large-scale code refactoring and documentation enhancement that introduces robust fuzz testing support. #### PR Summary The changes improve the internal APIs, module visibility, and documentation while adding extensive fuzz support and updating tests and utility scripts. - Added a new fuzz support module (`src/fuzz_support.rs`) that re-exports internal types and provides helper functions for fuzz targets. - Updated module visibility in core files (e.g. `lib.rs`, `connection`, `message`) and enhanced API documentation (including a new README and a documentation plan file). - Adjusted type conversion and metadata extraction functions (in files like `bulk_copy_metadata.rs` and `query/metadata.rs`) to return more explicit types (e.g. `Option<u8>` for scale). - Revised tests and added utility scripts (e.g. `/dev/build-fuzz.sh`) to support the new fuzz and documentation build flows. <!-- GitOpsUserAgent=GitOps.Apps.Server.pullrequestcopilot --> Related work items: #40377, #40829, #40924, #41302, #41412, #41497, #41728, #41762, #41830, #41851, #42206, #42218, #42220, #42221, #42278, #43063, #43130, #43131, #43132, #43133, #43134, #43135, #43136, #43137, #43144
2 parents dd21960 + 8d9153f commit f1f3ea8

63 files changed

Lines changed: 2142 additions & 2152 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/prompts/plan-documentMssqlTdsPublicApi.prompt.md

Lines changed: 342 additions & 0 deletions
Large diffs are not rendered by default.

dev/build-fuzz.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Build all fuzz targets for mssql-tds.
5+
# Requires: nightly toolchain and cargo-fuzz.
6+
7+
if ! rustup toolchain list | grep -q "nightly"; then
8+
echo "Installing nightly toolchain..."
9+
rustup toolchain install nightly
10+
fi
11+
12+
if ! cargo +nightly fuzz --version &> /dev/null 2>&1; then
13+
echo "Installing cargo-fuzz..."
14+
cargo +nightly install cargo-fuzz
15+
fi
16+
17+
cd "$(dirname "$0")/../mssql-tds"
18+
cargo +nightly fuzz build

mssql-tds/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ version = "0.1.0"
44
edition = "2024"
55
description = "Rust implementation of the TDS (Tabular Data Stream) protocol for SQL Server"
66
license = "MIT"
7+
readme = "README.md"
8+
keywords = ["sql-server", "tds", "mssql", "database", "async"]
9+
categories = ["database", "network-programming"]
710

811
[features]
912
default = ["integrated-auth"]

mssql-tds/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# mssql-tds
2+
3+
Async Rust implementation of the TDS (Tabular Data Stream) protocol for SQL Server
4+
and Azure SQL Database.
5+
6+
## What it does
7+
8+
Low-level TDS client handling connection negotiation (prelogin, TLS, login7),
9+
query execution, result set streaming, bulk copy, RPC calls, and transaction
10+
management. Built on Tokio.
11+
12+
## Feature flags
13+
14+
| Flag | Default | Description |
15+
|------|---------|-------------|
16+
| `integrated-auth` | **yes** | Enables both `sspi` and `gssapi` |
17+
| `sspi` | via `integrated-auth` | Windows SSPI (Kerberos/NTLM) |
18+
| `gssapi` | via `integrated-auth` | Unix GSSAPI (Kerberos) via runtime `dlopen` |
19+
20+
Disable the default to drop platform-specific auth dependencies:
21+
22+
```toml
23+
mssql-tds = { version = "0.1", default-features = false }
24+
```
25+
26+
## Quick start
27+
28+
```rust,no_run
29+
use mssql_tds::connection::client_context::ClientContext;
30+
use mssql_tds::connection::tds_client::ResultSetClient;
31+
use mssql_tds::connection_provider::tds_connection_provider::TdsConnectionProvider;
32+
use mssql_tds::core::TdsResult;
33+
34+
#[tokio::main]
35+
async fn main() -> TdsResult<()> {
36+
let mut context = ClientContext::default();
37+
context.user_name = std::env::var("DB_USER").unwrap_or("<user>".into());
38+
context.password = std::env::var("DB_PASSWORD").unwrap_or("<password>".into());
39+
context.database = "master".into();
40+
41+
let provider = TdsConnectionProvider {};
42+
let mut client = provider
43+
.create_client(context, "tcp:localhost,1433", None)
44+
.await?;
45+
46+
client
47+
.execute("SELECT 1 AS value".into(), None, None)
48+
.await?;
49+
50+
if let Some(rs) = client.get_current_resultset() {
51+
while let Some(row) = rs.next_row().await? {
52+
println!("{row:?}");
53+
}
54+
}
55+
56+
client.close_query().await?;
57+
Ok(())
58+
}
59+
```
60+
61+
## License
62+
63+
MIT

mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider.rs

Lines changed: 1 addition & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -20,252 +20,7 @@
2020

2121
use libfuzzer_sys::fuzz_target;
2222
use mssql_tds::connection::client_context::ClientContext;
23-
use mssql_tds::fuzz_support::{MockTransport, TdsConnectionProvider, TdsPacketReader};
24-
use mssql_tds::core::TdsResult;
25-
use std::io::{Error, ErrorKind};
26-
27-
/// Simple reader that wraps fuzz input data
28-
struct FuzzReader {
29-
data: Vec<u8>,
30-
position: usize,
31-
}
32-
33-
impl FuzzReader {
34-
fn new(data: &[u8]) -> Self {
35-
Self {
36-
data: data.to_vec(),
37-
position: 0,
38-
}
39-
}
40-
}
41-
42-
#[async_trait::async_trait]
43-
impl TdsPacketReader for FuzzReader {
44-
async fn read_byte(&mut self) -> TdsResult<u8> {
45-
if self.position >= self.data.len() {
46-
return Err(mssql_tds::error::Error::Io(Error::new(
47-
ErrorKind::UnexpectedEof,
48-
"EOF",
49-
)));
50-
}
51-
let byte = self.data[self.position];
52-
self.position += 1;
53-
Ok(byte)
54-
}
55-
56-
async fn read_int16_big_endian(&mut self) -> TdsResult<i16> {
57-
let mut buf = [0u8; 2];
58-
self.read_bytes(&mut buf).await?;
59-
Ok(i16::from_be_bytes(buf))
60-
}
61-
62-
async fn read_int32_big_endian(&mut self) -> TdsResult<i32> {
63-
let mut buf = [0u8; 4];
64-
self.read_bytes(&mut buf).await?;
65-
Ok(i32::from_be_bytes(buf))
66-
}
67-
68-
async fn read_uint40(&mut self) -> TdsResult<u64> {
69-
let mut buf = [0u8; 8];
70-
self.read_bytes(&mut buf[..5]).await?;
71-
Ok(u64::from_le_bytes(buf))
72-
}
73-
74-
async fn read_float32(&mut self) -> TdsResult<f32> {
75-
let mut buf = [0u8; 4];
76-
self.read_bytes(&mut buf).await?;
77-
Ok(f32::from_le_bytes(buf))
78-
}
79-
80-
async fn read_float64(&mut self) -> TdsResult<f64> {
81-
let mut buf = [0u8; 8];
82-
self.read_bytes(&mut buf).await?;
83-
Ok(f64::from_le_bytes(buf))
84-
}
85-
86-
async fn read_uint16(&mut self) -> TdsResult<u16> {
87-
let mut buf = [0u8; 2];
88-
self.read_bytes(&mut buf).await?;
89-
Ok(u16::from_le_bytes(buf))
90-
}
91-
92-
async fn read_uint32(&mut self) -> TdsResult<u32> {
93-
let mut buf = [0u8; 4];
94-
self.read_bytes(&mut buf).await?;
95-
Ok(u32::from_le_bytes(buf))
96-
}
97-
98-
async fn read_uint64(&mut self) -> TdsResult<u64> {
99-
let mut buf = [0u8; 8];
100-
self.read_bytes(&mut buf).await?;
101-
Ok(u64::from_le_bytes(buf))
102-
}
103-
104-
async fn read_int16(&mut self) -> TdsResult<i16> {
105-
let mut buf = [0u8; 2];
106-
self.read_bytes(&mut buf).await?;
107-
Ok(i16::from_le_bytes(buf))
108-
}
109-
110-
async fn read_uint24(&mut self) -> TdsResult<u32> {
111-
let mut buf = [0u8; 4];
112-
self.read_bytes(&mut buf[..3]).await?;
113-
Ok(u32::from_le_bytes(buf))
114-
}
115-
116-
async fn read_int32(&mut self) -> TdsResult<i32> {
117-
let mut buf = [0u8; 4];
118-
self.read_bytes(&mut buf).await?;
119-
Ok(i32::from_le_bytes(buf))
120-
}
121-
122-
async fn read_int64(&mut self) -> TdsResult<i64> {
123-
let mut buf = [0u8; 8];
124-
self.read_bytes(&mut buf).await?;
125-
Ok(i64::from_le_bytes(buf))
126-
}
127-
128-
async fn read_bytes(&mut self, buf: &mut [u8]) -> TdsResult<usize> {
129-
// Use checked arithmetic to prevent overflow
130-
let end_position = self.position.checked_add(buf.len()).ok_or_else(|| {
131-
mssql_tds::error::Error::Io(Error::new(
132-
ErrorKind::InvalidInput,
133-
"buffer length causes position overflow",
134-
))
135-
})?;
136-
137-
if end_position > self.data.len() {
138-
return Err(mssql_tds::error::Error::Io(Error::new(
139-
ErrorKind::UnexpectedEof,
140-
"EOF",
141-
)));
142-
}
143-
buf.copy_from_slice(&self.data[self.position..end_position]);
144-
self.position = end_position;
145-
Ok(buf.len())
146-
}
147-
148-
async fn read_u8_varbyte(&mut self) -> TdsResult<Vec<u8>> {
149-
let len = self.read_byte().await? as usize;
150-
const MAX_ALLOC: usize = 1024 * 1024;
151-
if len > MAX_ALLOC {
152-
return Err(mssql_tds::error::Error::Io(Error::new(
153-
ErrorKind::InvalidData,
154-
format!("Allocation size {} exceeds max {}", len, MAX_ALLOC),
155-
)));
156-
}
157-
let mut buf = vec![0u8; len];
158-
self.read_bytes(&mut buf).await?;
159-
Ok(buf)
160-
}
161-
162-
async fn read_u16_varbyte(&mut self) -> TdsResult<Vec<u8>> {
163-
let len = self.read_uint16().await? as usize;
164-
const MAX_ALLOC: usize = 1024 * 1024;
165-
if len > MAX_ALLOC {
166-
return Err(mssql_tds::error::Error::Io(Error::new(
167-
ErrorKind::InvalidData,
168-
format!("Allocation size {} exceeds max {}", len, MAX_ALLOC),
169-
)));
170-
}
171-
let mut buf = vec![0u8; len];
172-
self.read_bytes(&mut buf).await?;
173-
Ok(buf)
174-
}
175-
176-
async fn read_varchar_u16_length(&mut self) -> TdsResult<Option<String>> {
177-
let len = self.read_uint16().await?;
178-
if len == 0xFFFF {
179-
return Ok(None);
180-
}
181-
let byte_len = (len as usize) * 2;
182-
const MAX_ALLOC: usize = 1024 * 1024;
183-
if byte_len > MAX_ALLOC {
184-
return Err(mssql_tds::error::Error::Io(Error::new(
185-
ErrorKind::InvalidData,
186-
format!("Allocation size {} exceeds max {}", byte_len, MAX_ALLOC),
187-
)));
188-
}
189-
let mut buf = vec![0u8; byte_len];
190-
self.read_bytes(&mut buf).await?;
191-
String::from_utf16(&buf.chunks_exact(2).map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])).collect::<Vec<u16>>())
192-
.map(Some)
193-
.map_err(|e| mssql_tds::error::Error::Io(Error::new(ErrorKind::InvalidData, e)))
194-
}
195-
196-
async fn read_varchar_u8_length(&mut self) -> TdsResult<String> {
197-
let len = self.read_byte().await? as usize;
198-
let byte_len = len * 2;
199-
const MAX_ALLOC: usize = 1024 * 1024;
200-
if byte_len > MAX_ALLOC {
201-
return Err(mssql_tds::error::Error::Io(Error::new(
202-
ErrorKind::InvalidData,
203-
format!("Allocation size {} exceeds max {}", byte_len, MAX_ALLOC),
204-
)));
205-
}
206-
let mut buf = vec![0u8; byte_len];
207-
self.read_bytes(&mut buf).await?;
208-
String::from_utf16(&buf.chunks_exact(2).map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])).collect::<Vec<u16>>())
209-
.map_err(|e| mssql_tds::error::Error::Io(Error::new(ErrorKind::InvalidData, e)))
210-
}
211-
212-
async fn read_unicode(&mut self, string_length: usize) -> TdsResult<String> {
213-
let byte_len = string_length * 2;
214-
const MAX_ALLOC: usize = 1024 * 1024;
215-
if byte_len > MAX_ALLOC {
216-
return Err(mssql_tds::error::Error::Io(Error::new(
217-
ErrorKind::InvalidData,
218-
format!("Allocation size {} exceeds max {}", byte_len, MAX_ALLOC),
219-
)));
220-
}
221-
let mut buf = vec![0u8; byte_len];
222-
self.read_bytes(&mut buf).await?;
223-
String::from_utf16(&buf.chunks_exact(2).map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])).collect::<Vec<u16>>())
224-
.map_err(|e| mssql_tds::error::Error::Io(Error::new(ErrorKind::InvalidData, e)))
225-
}
226-
227-
async fn read_unicode_with_byte_length(&mut self, byte_length: usize) -> TdsResult<String> {
228-
const MAX_ALLOC: usize = 1024 * 1024;
229-
if byte_length > MAX_ALLOC {
230-
return Err(mssql_tds::error::Error::Io(Error::new(
231-
ErrorKind::InvalidData,
232-
format!("Allocation size {} exceeds max {}", byte_length, MAX_ALLOC),
233-
)));
234-
}
235-
let mut buf = vec![0u8; byte_length];
236-
self.read_bytes(&mut buf).await?;
237-
String::from_utf16(&buf.chunks_exact(2).map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])).collect::<Vec<u16>>())
238-
.map_err(|e| mssql_tds::error::Error::Io(Error::new(ErrorKind::InvalidData, e)))
239-
}
240-
241-
async fn skip_bytes(&mut self, skip_count: usize) -> TdsResult<()> {
242-
// Use checked arithmetic to prevent overflow
243-
let new_position = self.position.checked_add(skip_count).ok_or_else(|| {
244-
mssql_tds::error::Error::Io(Error::new(
245-
ErrorKind::InvalidInput,
246-
"skip_count causes position overflow",
247-
))
248-
})?;
249-
250-
if new_position > self.data.len() {
251-
return Err(mssql_tds::error::Error::Io(Error::new(
252-
ErrorKind::UnexpectedEof,
253-
"EOF",
254-
)));
255-
}
256-
self.position = new_position;
257-
Ok(())
258-
}
259-
260-
async fn cancel_read_stream(&mut self) -> TdsResult<()> {
261-
// No-op for fuzzing
262-
Ok(())
263-
}
264-
265-
fn reset_reader(&mut self) {
266-
self.position = 0;
267-
}
268-
}
23+
use mssql_tds::fuzz_support::{FuzzReader, MockTransport, TdsConnectionProvider};
26924

27025
fuzz_target!(|data: &[u8]| {
27126
// Need at least some data to work with

0 commit comments

Comments
 (0)