diff --git a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider.rs b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider.rs index 79e1d18e..0b3adae9 100644 --- a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider.rs +++ b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider.rs @@ -20,7 +20,7 @@ use libfuzzer_sys::fuzz_target; use mssql_tds::connection::client_context::ClientContext; -use mssql_tds::fuzz_support::{FuzzReader, MockTransport, TdsConnectionProvider}; +use mssql_tds::fuzz_support::{FuzzPacketReader, MockTransport, TdsConnectionProvider}; fuzz_target!(|data: &[u8]| { // Need at least some data to work with @@ -37,7 +37,7 @@ fuzz_target!(|data: &[u8]| { async fn fuzz_connection_provider(data: &[u8]) { // Create a fuzz reader with the input data - let reader = Box::new(FuzzReader::new(data)); + let reader = FuzzPacketReader::from_data(data); let packet_size = 4096; // Create a mock transport with fuzzed data diff --git a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_context.rs b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_context.rs index 7e63f883..6b959e07 100644 --- a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_context.rs +++ b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_context.rs @@ -20,7 +20,7 @@ use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use mssql_tds::connection::client_context::{ClientContext, TdsAuthenticationMethod}; use mssql_tds::message::login_options::ApplicationIntent; -use mssql_tds::fuzz_support::{EmptyReader, MockTransport, TdsConnectionProvider}; +use mssql_tds::fuzz_support::{FuzzPacketReader, MockTransport, TdsConnectionProvider}; #[derive(Debug, Arbitrary)] struct FuzzClientContext { @@ -101,7 +101,7 @@ fuzz_target!(|fuzz_context: FuzzClientContext| { }); async fn fuzz_client_context(fuzz_context: FuzzClientContext) { - let reader = Box::new(EmptyReader); + let reader = FuzzPacketReader::empty(); let packet_size = 4096; let transport = MockTransport::new(reader, packet_size); diff --git a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_network.rs b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_network.rs index 0242281f..f76c4d5d 100644 --- a/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_network.rs +++ b/mssql-tds/fuzz/fuzz_targets/fuzz_connection_provider_network.rs @@ -18,7 +18,7 @@ use libfuzzer_sys::fuzz_target; use mssql_tds::connection::client_context::ClientContext; -use mssql_tds::fuzz_support::{FuzzReader, MockTransport, TdsConnectionProvider}; +use mssql_tds::fuzz_support::{FuzzPacketReader, MockTransport, TdsConnectionProvider}; fuzz_target!(|data: &[u8]| { if data.is_empty() { @@ -32,7 +32,7 @@ fuzz_target!(|data: &[u8]| { }); async fn fuzz_network_response(data: &[u8]) { - let reader = Box::new(FuzzReader::new(data)); + let reader = FuzzPacketReader::from_data(data); let packet_size = 4096; let transport = MockTransport::new(reader, packet_size); diff --git a/mssql-tds/fuzz/fuzz_targets/fuzz_tds_client.rs b/mssql-tds/fuzz/fuzz_targets/fuzz_tds_client.rs index b7c25ad5..656dc83e 100644 --- a/mssql-tds/fuzz/fuzz_targets/fuzz_tds_client.rs +++ b/mssql-tds/fuzz/fuzz_targets/fuzz_tds_client.rs @@ -19,7 +19,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use mssql_tds::fuzz_support::{FuzzReader, create_fuzz_tds_client}; +use mssql_tds::fuzz_support::{FuzzPacketReader, create_fuzz_tds_client}; fuzz_target!(|data: &[u8]| { // Need at least 2 bytes: 1 for scenario, 1+ for token data @@ -39,7 +39,7 @@ fuzz_target!(|data: &[u8]| { rt.block_on(async { // Create TdsClient with mock transport using fuzzer data - let packet_reader = Box::new(FuzzReader::new(token_data)); + let packet_reader = FuzzPacketReader::from_data(token_data); let mut client = create_fuzz_tds_client(packet_reader, 4096); // Execute scenario based on fuzzer input diff --git a/mssql-tds/src/connection/tds_client.rs b/mssql-tds/src/connection/tds_client.rs index f00fcebe..f0961e98 100644 --- a/mssql-tds/src/connection/tds_client.rs +++ b/mssql-tds/src/connection/tds_client.rs @@ -4880,7 +4880,6 @@ mod tests { } } - #[async_trait::async_trait] impl crate::io::packet_reader::TdsPacketReader for TestTransport { async fn read_byte(&mut self) -> TdsResult { Ok(self.take_packet_bytes(1)?[0]) diff --git a/mssql-tds/src/connection/transport/network_transport.rs b/mssql-tds/src/connection/transport/network_transport.rs index e0b14746..700aa19f 100644 --- a/mssql-tds/src/connection/transport/network_transport.rs +++ b/mssql-tds/src/connection/transport/network_transport.rs @@ -1099,7 +1099,6 @@ impl TransportSslHandler for NetworkTransport { } } -#[async_trait] impl TdsPacketReader for NetworkTransport { fn reset_reader(&mut self) { // Make sure that we have read all the data from the buffer. diff --git a/mssql-tds/src/datatypes/decoder.rs b/mssql-tds/src/datatypes/decoder.rs index acf90efc..94fd6d2e 100644 --- a/mssql-tds/src/datatypes/decoder.rs +++ b/mssql-tds/src/datatypes/decoder.rs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use async_trait::async_trait; use bigdecimal::num_bigint::BigUint; use bigdecimal::num_traits::ToPrimitive; use core::fmt; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; use std::{fmt::Debug, io::Error, vec}; @@ -144,9 +145,12 @@ macro_rules! safe_vec { }}; } -#[async_trait] pub(crate) trait SqlTypeDecode { - async fn decode(&self, reader: &mut T, metadata: &ColumnMetadata) -> TdsResult + fn decode( + &self, + reader: &mut T, + metadata: &ColumnMetadata, + ) -> impl Future> + Send where T: TdsPacketReader + Send + Sync; } @@ -201,9 +205,10 @@ impl PlpChunkStreamReader { } } - pub(crate) async fn begin( - reader: &mut (dyn TdsPacketReader + Send + Sync), - ) -> TdsResult> { + pub(crate) async fn begin(reader: &mut T) -> TdsResult> + where + T: TdsPacketReader + Send + Sync, + { let raw_len_i64 = reader.read_int64().await?; let raw_len = raw_len_i64 as u64; let raw_len_usize = raw_len as usize; @@ -247,10 +252,10 @@ impl PlpChunkStreamReader { self.reached_end } - async fn ensure_active_chunk( - &mut self, - reader: &mut (dyn TdsPacketReader + Send + Sync), - ) -> TdsResult { + async fn ensure_active_chunk(&mut self, reader: &mut T) -> TdsResult + where + T: TdsPacketReader + Send + Sync, + { if self.reached_end { return Ok(false); } @@ -314,11 +319,10 @@ impl PlpChunkStreamReader { Ok(true) } - pub(crate) async fn read_into( - &mut self, - reader: &mut (dyn TdsPacketReader + Send + Sync), - out: &mut [u8], - ) -> TdsResult { + pub(crate) async fn read_into(&mut self, reader: &mut T, out: &mut [u8]) -> TdsResult + where + T: TdsPacketReader + Send + Sync, + { // Supports the msodbcsql-style cbRequest==0 pattern to consume a // pending terminator after all data bytes were already read. if out.is_empty() { @@ -356,10 +360,10 @@ impl PlpChunkStreamReader { Ok(written) } - pub(crate) async fn skip_to_end( - &mut self, - reader: &mut (dyn TdsPacketReader + Send + Sync), - ) -> TdsResult<()> { + pub(crate) async fn skip_to_end(&mut self, reader: &mut T) -> TdsResult<()> + where + T: TdsPacketReader + Send + Sync, + { while self.ensure_active_chunk(reader).await? { if self.chunk_remaining > 0 { reader.skip_bytes(self.chunk_remaining).await?; @@ -417,10 +421,13 @@ impl PlpColumnStream { /// - `Ok(None)` for SQL NULL /// - `Ok(Some(stream))` ready for incremental reads /// - `Err` if the column is not PLP-typed or the header is malformed - pub(crate) async fn begin( + pub(crate) async fn begin( metadata: &ColumnMetadata, - reader: &mut (dyn TdsPacketReader + Send + Sync), - ) -> TdsResult> { + reader: &mut T, + ) -> TdsResult> + where + T: TdsPacketReader + Send + Sync, + { let (plp_type, collation) = Self::type_from_metadata(metadata)?; let inner = match PlpChunkStreamReader::begin(reader).await? { None => return Ok(None), @@ -460,19 +467,18 @@ impl PlpColumnStream { } /// Incrementally reads PLP payload bytes into `out`. - pub(crate) async fn read_into( - &mut self, - reader: &mut (dyn TdsPacketReader + Send + Sync), - out: &mut [u8], - ) -> TdsResult { + pub(crate) async fn read_into(&mut self, reader: &mut T, out: &mut [u8]) -> TdsResult + where + T: TdsPacketReader + Send + Sync, + { self.inner.read_into(reader, out).await } /// Discards all remaining PLP payload and terminator bytes. - pub(crate) async fn skip_to_end( - &mut self, - reader: &mut (dyn TdsPacketReader + Send + Sync), - ) -> TdsResult<()> { + pub(crate) async fn skip_to_end(&mut self, reader: &mut T) -> TdsResult<()> + where + T: TdsPacketReader + Send + Sync, + { self.inner.skip_to_end(reader).await } @@ -508,6 +514,24 @@ impl GenericDecoder { #[cfg(not(fuzzing))] const MAX_PLP_CHUNK_SIZE: usize = 16 * 1024 * 1024; + /// Boxed re-entry into [`SqlTypeDecode::decode`], used only by SQL_VARIANT. + /// + /// SQL_VARIANT embeds a base type, so decoding one re-enters the type switch. Native + /// `async fn` cannot express that cycle: the opaque return type would contain itself. + /// Naming a concrete `Pin>` in the signature severs the dependency, at + /// the cost of one allocation per nested variant column — a rare type, never on the hot + /// path of ordinary scalar columns. + fn decode_boxed<'a, T>( + &'a self, + reader: &'a mut T, + metadata: &'a ColumnMetadata, + ) -> Pin> + Send + 'a>> + where + T: TdsPacketReader + Send + Sync, + { + Box::pin(self.decode(reader, metadata)) + } + // Reads a SQL_VARIANT type from the TDS stream. async fn read_sql_variant(&self, reader: &mut T) -> TdsResult where @@ -584,7 +608,7 @@ impl GenericDecoder { multi_part_name: None, crypto_metadata: None, }; - self.decode(reader, &variant_actual_type_md).await + self.decode_boxed(reader, &variant_actual_type_md).await } _ => { // If the type is not a fixed length type, we should not reach here. @@ -1383,7 +1407,6 @@ impl GenericDecoder { } } -#[async_trait] impl SqlTypeDecode for GenericDecoder { async fn decode(&self, reader: &mut T, metadata: &ColumnMetadata) -> TdsResult where @@ -1774,7 +1797,6 @@ impl StringDecoder { } } -#[async_trait] impl SqlTypeDecode for StringDecoder { async fn decode(&self, reader: &mut T, metadata: &ColumnMetadata) -> TdsResult where @@ -1846,7 +1868,7 @@ impl SqlTypeDecode for StringDecoder { } else { let length = reader.read_uint16().await? as usize; if length == 0xFFFF { - return Ok(ColumnValues::Null); + Ok(ColumnValues::Null) } else { let mut buffer = vec![0u8; length]; reader.read_bytes(&mut buffer).await?; @@ -3229,7 +3251,7 @@ mod test { } mod decode_into_tests { - use async_trait::async_trait; + use byteorder::{ByteOrder, LittleEndian}; use crate::core::TdsResult; @@ -3270,7 +3292,6 @@ mod test { } } - #[async_trait] impl TdsPacketReader for ByteReader { async fn read_byte(&mut self) -> TdsResult { Ok(self.take(1)?[0]) diff --git a/mssql-tds/src/fuzz_support.rs b/mssql-tds/src/fuzz_support.rs index 4af3f3c8..65df6f9b 100644 --- a/mssql-tds/src/fuzz_support.rs +++ b/mssql-tds/src/fuzz_support.rs @@ -66,7 +66,6 @@ impl FuzzReader { } } -#[async_trait] impl TdsPacketReader for FuzzReader { async fn read_byte(&mut self) -> TdsResult { if self.position >= self.data.len() { @@ -271,7 +270,6 @@ impl TdsPacketReader for FuzzReader { /// Always-EOF reader for fuzz targets that only care about context variations. pub struct EmptyReader; -#[async_trait] impl TdsPacketReader for EmptyReader { async fn read_byte(&mut self) -> TdsResult { Err(mssql_tds_error_eof()) @@ -432,10 +430,196 @@ impl NetworkWriter for MockWriter { } } +/// Concrete packet-reader used by the fuzz harness. +/// +/// `TdsPacketReader` returns `impl Future` per method, which makes it dyn-incompatible. +/// The harness only ever supplies one of two readers, so an enum recovers the runtime +/// choice that `Box` previously provided, with static dispatch. +pub enum FuzzPacketReader { + /// Reads from the fuzzer-supplied byte slice. + Fuzz(FuzzReader), + /// Always reports end-of-stream. + Empty(EmptyReader), +} + +impl FuzzPacketReader { + /// Builds a reader over the fuzzer-supplied input. + pub fn from_data(data: &[u8]) -> Self { + Self::Fuzz(FuzzReader::new(data)) + } + + /// Builds a reader that always reports end-of-stream. + pub fn empty() -> Self { + Self::Empty(EmptyReader) + } +} + +impl TdsPacketReader for FuzzPacketReader { + async fn read_byte(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_byte().await, + Self::Empty(r) => r.read_byte().await, + } + } + + async fn read_int16_big_endian(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_int16_big_endian().await, + Self::Empty(r) => r.read_int16_big_endian().await, + } + } + + async fn read_int32_big_endian(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_int32_big_endian().await, + Self::Empty(r) => r.read_int32_big_endian().await, + } + } + + async fn read_uint40(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_uint40().await, + Self::Empty(r) => r.read_uint40().await, + } + } + + async fn read_float32(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_float32().await, + Self::Empty(r) => r.read_float32().await, + } + } + + async fn read_float64(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_float64().await, + Self::Empty(r) => r.read_float64().await, + } + } + + async fn read_int16(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_int16().await, + Self::Empty(r) => r.read_int16().await, + } + } + + async fn read_uint16(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_uint16().await, + Self::Empty(r) => r.read_uint16().await, + } + } + + async fn read_uint24(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_uint24().await, + Self::Empty(r) => r.read_uint24().await, + } + } + + async fn read_int32(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_int32().await, + Self::Empty(r) => r.read_int32().await, + } + } + + async fn read_uint32(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_uint32().await, + Self::Empty(r) => r.read_uint32().await, + } + } + + async fn read_int64(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_int64().await, + Self::Empty(r) => r.read_int64().await, + } + } + + async fn read_uint64(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_uint64().await, + Self::Empty(r) => r.read_uint64().await, + } + } + + async fn read_bytes(&mut self, buffer: &mut [u8]) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_bytes(buffer).await, + Self::Empty(r) => r.read_bytes(buffer).await, + } + } + + async fn read_u8_varbyte(&mut self) -> TdsResult> { + match self { + Self::Fuzz(r) => r.read_u8_varbyte().await, + Self::Empty(r) => r.read_u8_varbyte().await, + } + } + + async fn read_u16_varbyte(&mut self) -> TdsResult> { + match self { + Self::Fuzz(r) => r.read_u16_varbyte().await, + Self::Empty(r) => r.read_u16_varbyte().await, + } + } + + async fn read_varchar_u16_length(&mut self) -> TdsResult> { + match self { + Self::Fuzz(r) => r.read_varchar_u16_length().await, + Self::Empty(r) => r.read_varchar_u16_length().await, + } + } + + async fn read_varchar_u8_length(&mut self) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_varchar_u8_length().await, + Self::Empty(r) => r.read_varchar_u8_length().await, + } + } + + async fn read_unicode(&mut self, string_length: usize) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_unicode(string_length).await, + Self::Empty(r) => r.read_unicode(string_length).await, + } + } + + async fn read_unicode_with_byte_length(&mut self, byte_length: usize) -> TdsResult { + match self { + Self::Fuzz(r) => r.read_unicode_with_byte_length(byte_length).await, + Self::Empty(r) => r.read_unicode_with_byte_length(byte_length).await, + } + } + + async fn skip_bytes(&mut self, skip_count: usize) -> TdsResult<()> { + match self { + Self::Fuzz(r) => r.skip_bytes(skip_count).await, + Self::Empty(r) => r.skip_bytes(skip_count).await, + } + } + + async fn cancel_read_stream(&mut self) -> TdsResult<()> { + match self { + Self::Fuzz(r) => r.cancel_read_stream().await, + Self::Empty(r) => r.cancel_read_stream().await, + } + } + + fn reset_reader(&mut self) { + match self { + Self::Fuzz(r) => r.reset_reader(), + Self::Empty(r) => r.reset_reader(), + } + } +} + /// MockTransport simulates a transport layer for fuzzing pub struct MockTransport { - token_stream_reader: - TokenStreamReader, GenericTokenParserRegistry>, + token_stream_reader: TokenStreamReader, mock_writer: MockWriter, packet_size: u32, encryption_setting: NegotiatedEncryptionSetting, @@ -451,7 +635,7 @@ impl std::fmt::Debug for MockTransport { } impl MockTransport { - pub fn new(packet_reader: Box, packet_size: u32) -> Self { + pub fn new(packet_reader: FuzzPacketReader, packet_size: u32) -> Self { let parser_registry = Box::new(GenericTokenParserRegistry::default()); let token_stream_reader = TokenStreamReader::new(packet_reader, parser_registry); @@ -615,7 +799,6 @@ impl TdsTransport for MockTransport { } } -#[async_trait] impl TdsPacketReader for MockTransport { async fn read_byte(&mut self) -> TdsResult { self.token_stream_reader.packet_reader.read_byte().await @@ -756,10 +939,7 @@ pub fn create_test_execution_context() -> crate::connection::execution_context:: } /// Helper function to create TdsClient for fuzzing -pub fn create_fuzz_tds_client( - packet_reader: Box, - packet_size: u32, -) -> TdsClient { +pub fn create_fuzz_tds_client(packet_reader: FuzzPacketReader, packet_size: u32) -> TdsClient { let mock_transport = MockTransport::new(packet_reader, packet_size); let negotiated_settings = create_test_negotiated_settings(); let execution_context = create_test_execution_context(); diff --git a/mssql-tds/src/io/packet_reader.rs b/mssql-tds/src/io/packet_reader.rs index da238125..cf9c9329 100644 --- a/mssql-tds/src/io/packet_reader.rs +++ b/mssql-tds/src/io/packet_reader.rs @@ -1,168 +1,84 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use async_trait::async_trait; +use std::future::Future; use crate::core::TdsResult; /// Sentinel `u16` length marking a length-prefixed varchar field as NULL. pub(crate) const LENGTH_NULL: u16 = 0xffff; -#[async_trait] #[cfg(not(fuzzing))] pub(crate) trait TdsPacketReader { - async fn read_byte(&mut self) -> TdsResult; - async fn read_int16_big_endian(&mut self) -> TdsResult; - async fn read_int32_big_endian(&mut self) -> TdsResult; - async fn read_uint40(&mut self) -> TdsResult; - - async fn read_float32(&mut self) -> TdsResult; - async fn read_float64(&mut self) -> TdsResult; - async fn read_int16(&mut self) -> TdsResult; - async fn read_uint16(&mut self) -> TdsResult; - async fn read_uint24(&mut self) -> TdsResult; - async fn read_int32(&mut self) -> TdsResult; - async fn read_uint32(&mut self) -> TdsResult; - async fn read_int64(&mut self) -> TdsResult; - async fn read_uint64(&mut self) -> TdsResult; - - async fn read_bytes(&mut self, buffer: &mut [u8]) -> TdsResult; - async fn read_u8_varbyte(&mut self) -> TdsResult>; + fn read_byte(&mut self) -> impl Future> + Send; + fn read_int16_big_endian(&mut self) -> impl Future> + Send; + fn read_int32_big_endian(&mut self) -> impl Future> + Send; + fn read_uint40(&mut self) -> impl Future> + Send; + + fn read_float32(&mut self) -> impl Future> + Send; + fn read_float64(&mut self) -> impl Future> + Send; + fn read_int16(&mut self) -> impl Future> + Send; + fn read_uint16(&mut self) -> impl Future> + Send; + fn read_uint24(&mut self) -> impl Future> + Send; + fn read_int32(&mut self) -> impl Future> + Send; + fn read_uint32(&mut self) -> impl Future> + Send; + fn read_int64(&mut self) -> impl Future> + Send; + fn read_uint64(&mut self) -> impl Future> + Send; + + fn read_bytes(&mut self, buffer: &mut [u8]) -> impl Future> + Send; + fn read_u8_varbyte(&mut self) -> impl Future>> + Send; #[allow(dead_code)] - async fn read_u16_varbyte(&mut self) -> TdsResult>; - async fn read_varchar_u16_length(&mut self) -> TdsResult>; - async fn read_varchar_u8_length(&mut self) -> TdsResult; + fn read_u16_varbyte(&mut self) -> impl Future>> + Send; + fn read_varchar_u16_length(&mut self) + -> impl Future>> + Send; + fn read_varchar_u8_length(&mut self) -> impl Future> + Send; #[allow(dead_code)] - async fn read_unicode(&mut self, string_length: usize) -> TdsResult; - async fn read_unicode_with_byte_length(&mut self, byte_length: usize) -> TdsResult; - async fn skip_bytes(&mut self, skip_count: usize) -> TdsResult<()>; - async fn cancel_read_stream(&mut self) -> TdsResult<()>; + fn read_unicode( + &mut self, + string_length: usize, + ) -> impl Future> + Send; + fn read_unicode_with_byte_length( + &mut self, + byte_length: usize, + ) -> impl Future> + Send; + fn skip_bytes(&mut self, skip_count: usize) -> impl Future> + Send; + fn cancel_read_stream(&mut self) -> impl Future> + Send; fn reset_reader(&mut self); } /// Low-level TDS packet reading operations (public under `fuzzing` cfg). -#[async_trait] #[cfg(fuzzing)] pub trait TdsPacketReader { - async fn read_byte(&mut self) -> TdsResult; - async fn read_int16_big_endian(&mut self) -> TdsResult; - async fn read_int32_big_endian(&mut self) -> TdsResult; - async fn read_uint40(&mut self) -> TdsResult; - - async fn read_float32(&mut self) -> TdsResult; - async fn read_float64(&mut self) -> TdsResult; - async fn read_int16(&mut self) -> TdsResult; - async fn read_uint16(&mut self) -> TdsResult; - async fn read_uint24(&mut self) -> TdsResult; - async fn read_int32(&mut self) -> TdsResult; - async fn read_uint32(&mut self) -> TdsResult; - async fn read_int64(&mut self) -> TdsResult; - async fn read_uint64(&mut self) -> TdsResult; - - async fn read_bytes(&mut self, buffer: &mut [u8]) -> TdsResult; - async fn read_u8_varbyte(&mut self) -> TdsResult>; - async fn read_u16_varbyte(&mut self) -> TdsResult>; - async fn read_varchar_u16_length(&mut self) -> TdsResult>; - async fn read_varchar_u8_length(&mut self) -> TdsResult; - async fn read_unicode(&mut self, string_length: usize) -> TdsResult; - async fn read_unicode_with_byte_length(&mut self, byte_length: usize) -> TdsResult; - async fn skip_bytes(&mut self, skip_count: usize) -> TdsResult<()>; - async fn cancel_read_stream(&mut self) -> TdsResult<()>; + fn read_byte(&mut self) -> impl Future> + Send; + fn read_int16_big_endian(&mut self) -> impl Future> + Send; + fn read_int32_big_endian(&mut self) -> impl Future> + Send; + fn read_uint40(&mut self) -> impl Future> + Send; + + fn read_float32(&mut self) -> impl Future> + Send; + fn read_float64(&mut self) -> impl Future> + Send; + fn read_int16(&mut self) -> impl Future> + Send; + fn read_uint16(&mut self) -> impl Future> + Send; + fn read_uint24(&mut self) -> impl Future> + Send; + fn read_int32(&mut self) -> impl Future> + Send; + fn read_uint32(&mut self) -> impl Future> + Send; + fn read_int64(&mut self) -> impl Future> + Send; + fn read_uint64(&mut self) -> impl Future> + Send; + + fn read_bytes(&mut self, buffer: &mut [u8]) -> impl Future> + Send; + fn read_u8_varbyte(&mut self) -> impl Future>> + Send; + fn read_u16_varbyte(&mut self) -> impl Future>> + Send; + fn read_varchar_u16_length(&mut self) + -> impl Future>> + Send; + fn read_varchar_u8_length(&mut self) -> impl Future> + Send; + fn read_unicode( + &mut self, + string_length: usize, + ) -> impl Future> + Send; + fn read_unicode_with_byte_length( + &mut self, + byte_length: usize, + ) -> impl Future> + Send; + fn skip_bytes(&mut self, skip_count: usize) -> impl Future> + Send; + fn cancel_read_stream(&mut self) -> impl Future> + Send; fn reset_reader(&mut self); } - -// Blanket implementation for Box to enable dynamic dispatch -#[async_trait] -impl TdsPacketReader for Box { - async fn read_byte(&mut self) -> TdsResult { - (**self).read_byte().await - } - - async fn read_int16_big_endian(&mut self) -> TdsResult { - (**self).read_int16_big_endian().await - } - - async fn read_int32_big_endian(&mut self) -> TdsResult { - (**self).read_int32_big_endian().await - } - - async fn read_uint40(&mut self) -> TdsResult { - (**self).read_uint40().await - } - - async fn read_float32(&mut self) -> TdsResult { - (**self).read_float32().await - } - - async fn read_float64(&mut self) -> TdsResult { - (**self).read_float64().await - } - - async fn read_int16(&mut self) -> TdsResult { - (**self).read_int16().await - } - - async fn read_uint16(&mut self) -> TdsResult { - (**self).read_uint16().await - } - - async fn read_uint24(&mut self) -> TdsResult { - (**self).read_uint24().await - } - - async fn read_int32(&mut self) -> TdsResult { - (**self).read_int32().await - } - - async fn read_uint32(&mut self) -> TdsResult { - (**self).read_uint32().await - } - - async fn read_int64(&mut self) -> TdsResult { - (**self).read_int64().await - } - - async fn read_uint64(&mut self) -> TdsResult { - (**self).read_uint64().await - } - - async fn read_bytes(&mut self, buffer: &mut [u8]) -> TdsResult { - (**self).read_bytes(buffer).await - } - - async fn read_u8_varbyte(&mut self) -> TdsResult> { - (**self).read_u8_varbyte().await - } - - async fn read_u16_varbyte(&mut self) -> TdsResult> { - (**self).read_u16_varbyte().await - } - - async fn read_varchar_u16_length(&mut self) -> TdsResult> { - (**self).read_varchar_u16_length().await - } - - async fn read_varchar_u8_length(&mut self) -> TdsResult { - (**self).read_varchar_u8_length().await - } - - async fn read_unicode(&mut self, string_length: usize) -> TdsResult { - (**self).read_unicode(string_length).await - } - - async fn read_unicode_with_byte_length(&mut self, byte_length: usize) -> TdsResult { - (**self).read_unicode_with_byte_length(byte_length).await - } - - async fn skip_bytes(&mut self, skip_count: usize) -> TdsResult<()> { - (**self).skip_bytes(skip_count).await - } - - async fn cancel_read_stream(&mut self) -> TdsResult<()> { - (**self).cancel_read_stream().await - } - - fn reset_reader(&mut self) { - (**self).reset_reader() - } -} diff --git a/mssql-tds/src/io/token_stream.rs b/mssql-tds/src/io/token_stream.rs index 38853b3b..2e483577 100644 --- a/mssql-tds/src/io/token_stream.rs +++ b/mssql-tds/src/io/token_stream.rs @@ -474,14 +474,14 @@ fn pause_after_column( /// bytes, or pause. This single loop replaces the former /// `decode_row_columns` / `decode_nbcrow_columns` pair and their /// `writer.pause_*` polling. -async fn drive_row_columns( +async fn drive_row_columns( reader: &mut R, metadata: &Arc, decryptor: Option<&Arc>, bitmap: Option<&[u8]>, start_col: usize, plan: ColumnPolicy, - writer: &mut (dyn RowWriter + Send), + writer: &mut W, ) -> TdsResult { let decoder = GenericDecoder::default(); let columns = &metadata.columns; @@ -559,13 +559,16 @@ async fn drive_row_columns( Ok(RowReadResult::RowWritten) } -async fn decode_or_decrypt_column( +async fn decode_or_decrypt_column< + R: TdsPacketReader + Send + Sync, + W: RowWriter + Send + ?Sized, +>( decoder: &GenericDecoder, reader: &mut R, meta: &ColumnMetadata, decryptor: Option<&Arc>, col: usize, - writer: &mut (dyn RowWriter + Send), + writer: &mut W, ) -> TdsResult<()> { match (meta.crypto_metadata.is_some(), decryptor) { (true, Some(dec)) => { @@ -588,12 +591,15 @@ async fn decode_or_decrypt_column( Ok(()) } -pub(crate) async fn receive_row_into_internal( +pub(crate) async fn receive_row_into_internal< + R: TdsPacketReader + Send + Sync, + W: RowWriter + Send + ?Sized, +>( reader: &mut R, registry: &impl TokenParserRegistry, context: &ParserContext, plan: ColumnPolicy, - writer: &mut (dyn RowWriter + Send), + writer: &mut W, ) -> TdsResult { let token_type_byte = reader.read_byte().await?; let token_type: TokenType = token_type_byte.try_into()?; @@ -664,11 +670,14 @@ pub(crate) async fn receive_row_header_internal( +pub(crate) async fn resume_row_into_internal< + R: TdsPacketReader + Send + Sync, + W: RowWriter + Send + ?Sized, +>( reader: &mut R, pause_state: RowPauseState, plan: ColumnPolicy, - writer: &mut (dyn RowWriter + Send), + writer: &mut W, ) -> TdsResult { let RowPauseState { next_column_index, @@ -1056,10 +1065,92 @@ mod tests { use crate::datatypes::sqldatatypes::{TdsDataType, TypeInfo}; use crate::io::packet_reader::TdsPacketReader; use crate::token::tokens::{SqlCollation, TokenType}; - use async_trait::async_trait; + use std::collections::HashMap; use std::sync::Arc; + /// Companion to `row_fetch_futures_stay_small` (#225) for the decode chain below + /// `Box`. That guard measures futures built on `TdsClient`, which + /// re-boxes at the transport boundary, so it cannot observe anything in this file: + /// its four futures are byte-identical before and after this chain roughly doubled. + #[test] + fn row_decode_futures_stay_small() { + const MAX: usize = 4096; + + let metadata = Arc::new(ColMetadataToken { + column_count: 0, + columns: vec![], + cek_table: vec![], + }); + let context = ParserContext::ColumnMetadata(Arc::clone(&metadata), None); + let registry = GenericTokenParserRegistry::default(); + let mut reader = TestByteReader::new(vec![TokenType::Row as u8]); + let mut sink = DiscardRowWriter; + + // Constructing an async fn's future runs none of its body, so these are free to + // build and drop unpolled. Each borrow ends with its statement. + // + // Both instantiations are measured: `dyn` is what production reaches today, and + // the monomorphic one is what a concrete writer gets once the transport boundary + // stops erasing it (#265). + let receive_dyn = size_of_val(&receive_row_into_internal( + &mut reader, + ®istry, + &context, + ColumnPolicy::DecodeAll, + &mut sink as &mut (dyn RowWriter + Send), + )); + let receive_mono = size_of_val(&receive_row_into_internal( + &mut reader, + ®istry, + &context, + ColumnPolicy::DecodeAll, + &mut sink, + )); + let drive_dyn = size_of_val(&drive_row_columns( + &mut reader, + &metadata, + None, + None, + 0, + ColumnPolicy::DecodeAll, + &mut sink as &mut (dyn RowWriter + Send), + )); + let drive_mono = size_of_val(&drive_row_columns( + &mut reader, + &metadata, + None, + None, + 0, + ColumnPolicy::DecodeAll, + &mut sink, + )); + let resume_dyn = size_of_val(&resume_row_into_internal( + &mut reader, + RowPauseState { + next_column_index: 0, + metadata: Arc::clone(&metadata), + nbc_null_bitmap: None, + decryptor: None, + }, + ColumnPolicy::DecodeAll, + &mut sink as &mut (dyn RowWriter + Send), + )); + + for (name, size) in [ + ("receive_row_into_internal (dyn)", receive_dyn), + ("receive_row_into_internal (mono)", receive_mono), + ("drive_row_columns (dyn)", drive_dyn), + ("drive_row_columns (mono)", drive_mono), + ("resume_row_into_internal (dyn)", resume_dyn), + ] { + assert!( + size <= MAX, + "{name} future is {size} B, expected <= {MAX} B" + ); + } + } + #[test] fn test_parser_context_default() { let context = ParserContext::default(); @@ -1173,7 +1264,6 @@ mod tests { } } - #[async_trait] impl TdsPacketReader for TestByteReader { async fn read_byte(&mut self) -> TdsResult { Ok(self.take(1)?[0]) diff --git a/mssql-tds/src/message/prelogin.rs b/mssql-tds/src/message/prelogin.rs index cc80119a..e6a7f4e8 100644 --- a/mssql-tds/src/message/prelogin.rs +++ b/mssql-tds/src/message/prelogin.rs @@ -437,7 +437,7 @@ pub(crate) mod tests { use crate::io::packet_reader::TdsPacketReader; use crate::io::packet_writer::PacketWriter; use crate::io::packet_writer::tests::MockNetworkWriter; - use async_trait::async_trait; + use byteorder::{BigEndian, ReadBytesExt}; use futures::executor::block_on; @@ -446,7 +446,6 @@ pub(crate) mod tests { mockall::mock! { pub TestPacketReader {} - #[async_trait] impl TdsPacketReader for TestPacketReader { async fn read_byte(&mut self) -> TdsResult; async fn read_int16_big_endian(&mut self) -> TdsResult; diff --git a/mssql-tds/src/token/parsers/common.rs b/mssql-tds/src/token/parsers/common.rs index a442853e..4141f177 100644 --- a/mssql-tds/src/token/parsers/common.rs +++ b/mssql-tds/src/token/parsers/common.rs @@ -67,7 +67,6 @@ pub(crate) mod test_utils { } } - #[async_trait] impl TdsPacketReader for MockReader { async fn read_byte(&mut self) -> TdsResult { if self.position >= self.data.len() { diff --git a/mssql-tds/src/token/parsers/nbcrow_parser.rs b/mssql-tds/src/token/parsers/nbcrow_parser.rs index 5df89010..f80dc40b 100644 --- a/mssql-tds/src/token/parsers/nbcrow_parser.rs +++ b/mssql-tds/src/token/parsers/nbcrow_parser.rs @@ -104,8 +104,6 @@ impl TokenParser

mod tests { use std::sync::Arc; - use async_trait::async_trait; - use super::*; use crate::datatypes::sqldatatypes::{ FixedLengthTypes, TdsDataType, TypeInfo, TypeInfoVariant, @@ -118,7 +116,6 @@ mod tests { #[derive(Default)] struct MockDecoder; - #[async_trait] impl SqlTypeDecode for MockDecoder { async fn decode( &self, @@ -308,7 +305,6 @@ mod tests { #[derive(Default)] struct FailingDecoder; - #[async_trait] impl SqlTypeDecode for FailingDecoder { async fn decode( &self, diff --git a/mssql-tds/src/token/parsers/row_parser.rs b/mssql-tds/src/token/parsers/row_parser.rs index c4919aa8..e57587ae 100644 --- a/mssql-tds/src/token/parsers/row_parser.rs +++ b/mssql-tds/src/token/parsers/row_parser.rs @@ -182,8 +182,6 @@ impl mod tests { use std::sync::Arc; - use async_trait::async_trait; - use super::*; use crate::datatypes::sqldatatypes::{ FixedLengthTypes, TdsDataType, TypeInfo, TypeInfoVariant, @@ -196,7 +194,6 @@ mod tests { #[derive(Default)] struct MockDecoder; - #[async_trait] impl SqlTypeDecode for MockDecoder { async fn decode( &self, @@ -301,7 +298,6 @@ mod tests { #[derive(Default)] struct FailingDecoder; - #[async_trait] impl SqlTypeDecode for FailingDecoder { async fn decode( &self,