Skip to main content

tds_protocol/
token.rs

1//! TDS token stream definitions.
2//!
3//! Tokens are the fundamental units of TDS response data. The server sends
4//! a stream of tokens that describe metadata, rows, errors, and other information.
5//!
6//! ## Token Structure
7//!
8//! Each token begins with a 1-byte token type identifier, followed by
9//! token-specific data. Some tokens have fixed lengths, while others
10//! have length prefixes.
11//!
12//! ## Usage
13//!
14//! ```rust,no_run
15//! use tds_protocol::token::{Token, TokenParser};
16//! use bytes::Bytes;
17//!
18//! fn parse(data: Bytes) -> Result<(), tds_protocol::ProtocolError> {
19//!     let mut parser = TokenParser::new(data);
20//!
21//!     while let Some(token) = parser.next_token()? {
22//!         match token {
23//!             Token::Done(done) => println!("Rows affected: {}", done.row_count),
24//!             Token::Error(err) => eprintln!("Error {}: {}", err.number, err.message),
25//!             _ => {}
26//!         }
27//!     }
28//!     Ok(())
29//! }
30//! ```
31
32use bytes::{Buf, BufMut, Bytes};
33
34use crate::codec::{read_b_varchar, read_us_varchar};
35use crate::error::ProtocolError;
36use crate::prelude::*;
37use crate::types::TypeId;
38
39/// Token type identifier.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[repr(u8)]
42#[non_exhaustive]
43pub enum TokenType {
44    /// Column metadata (COLMETADATA).
45    ColMetaData = 0x81,
46    /// Error message (ERROR).
47    Error = 0xAA,
48    /// Informational message (INFO).
49    Info = 0xAB,
50    /// Login acknowledgment (LOGINACK).
51    LoginAck = 0xAD,
52    /// Row data (ROW).
53    Row = 0xD1,
54    /// Null bitmap compressed row (NBCROW).
55    NbcRow = 0xD2,
56    /// Environment change (ENVCHANGE).
57    EnvChange = 0xE3,
58    /// SSPI authentication (SSPI).
59    Sspi = 0xED,
60    /// Done (DONE).
61    Done = 0xFD,
62    /// Done in procedure (DONEINPROC).
63    DoneInProc = 0xFF,
64    /// Done procedure (DONEPROC).
65    DoneProc = 0xFE,
66    /// Return status (RETURNSTATUS).
67    ReturnStatus = 0x79,
68    /// Return value (RETURNVALUE).
69    ReturnValue = 0xAC,
70    /// Order (ORDER).
71    Order = 0xA9,
72    /// Feature extension acknowledgment (FEATUREEXTACK).
73    FeatureExtAck = 0xAE,
74    /// Session state (SESSIONSTATE).
75    SessionState = 0xE4,
76    /// Federated authentication info (FEDAUTHINFO).
77    FedAuthInfo = 0xEE,
78    /// Column info (COLINFO).
79    ColInfo = 0xA5,
80    /// Table name (TABNAME).
81    TabName = 0xA4,
82    /// Offset (OFFSET).
83    Offset = 0x78,
84}
85
86impl TokenType {
87    /// Create a token type from a raw byte.
88    pub fn from_u8(value: u8) -> Option<Self> {
89        match value {
90            0x81 => Some(Self::ColMetaData),
91            0xAA => Some(Self::Error),
92            0xAB => Some(Self::Info),
93            0xAD => Some(Self::LoginAck),
94            0xD1 => Some(Self::Row),
95            0xD2 => Some(Self::NbcRow),
96            0xE3 => Some(Self::EnvChange),
97            0xED => Some(Self::Sspi),
98            0xFD => Some(Self::Done),
99            0xFF => Some(Self::DoneInProc),
100            0xFE => Some(Self::DoneProc),
101            0x79 => Some(Self::ReturnStatus),
102            0xAC => Some(Self::ReturnValue),
103            0xA9 => Some(Self::Order),
104            0xAE => Some(Self::FeatureExtAck),
105            0xE4 => Some(Self::SessionState),
106            0xEE => Some(Self::FedAuthInfo),
107            0xA5 => Some(Self::ColInfo),
108            0xA4 => Some(Self::TabName),
109            0x78 => Some(Self::Offset),
110            _ => None,
111        }
112    }
113}
114
115/// Parsed TDS token.
116///
117/// This enum represents all possible tokens that can be received from SQL Server.
118/// Each variant contains the parsed token data.
119#[derive(Debug, Clone)]
120#[non_exhaustive]
121pub enum Token {
122    /// Column metadata describing result set structure.
123    ColMetaData(ColMetaData),
124    /// Row data.
125    Row(RawRow),
126    /// Null bitmap compressed row.
127    NbcRow(NbcRow),
128    /// Completion of a SQL statement.
129    Done(Done),
130    /// Completion of a stored procedure.
131    DoneProc(DoneProc),
132    /// Completion within a stored procedure.
133    DoneInProc(DoneInProc),
134    /// Return status from stored procedure.
135    ReturnStatus(i32),
136    /// Return value from stored procedure.
137    ReturnValue(ReturnValue),
138    /// Error message from server.
139    Error(ServerError),
140    /// Informational message from server.
141    Info(ServerInfo),
142    /// Login acknowledgment.
143    LoginAck(LoginAck),
144    /// Environment change notification.
145    EnvChange(EnvChange),
146    /// Column ordering information.
147    Order(Order),
148    /// Feature extension acknowledgment.
149    FeatureExtAck(FeatureExtAck),
150    /// SSPI authentication data.
151    Sspi(SspiToken),
152    /// Session state information.
153    SessionState(SessionState),
154    /// Federated authentication info.
155    FedAuthInfo(FedAuthInfo),
156}
157
158/// Column metadata token.
159#[derive(Debug, Clone, Default)]
160pub struct ColMetaData {
161    /// Column definitions.
162    pub columns: Vec<ColumnData>,
163    /// CEK table for Always Encrypted result sets.
164    /// Present only when the server sends encrypted column metadata.
165    pub cek_table: Option<crate::crypto::CekTable>,
166}
167
168/// Column definition within metadata.
169#[derive(Debug, Clone)]
170pub struct ColumnData {
171    /// Column name.
172    pub name: String,
173    /// Column data type ID.
174    pub type_id: TypeId,
175    /// Column data type raw byte (for unknown types).
176    pub col_type: u8,
177    /// Column flags.
178    pub flags: u16,
179    /// User type ID.
180    pub user_type: u32,
181    /// Type-specific metadata.
182    pub type_info: TypeInfo,
183    /// Per-column encryption metadata (Always Encrypted).
184    /// Present only for columns with the encrypted flag (0x0800) set.
185    pub crypto_metadata: Option<crate::crypto::CryptoMetadata>,
186}
187
188/// Type-specific metadata.
189#[derive(Debug, Clone, Default)]
190pub struct TypeInfo {
191    /// Maximum length for variable-length types.
192    pub max_length: Option<u32>,
193    /// Precision for numeric types.
194    pub precision: Option<u8>,
195    /// Scale for numeric types.
196    pub scale: Option<u8>,
197    /// Collation for string types.
198    pub collation: Option<Collation>,
199}
200
201/// SQL Server collation.
202///
203/// Collations in SQL Server define the character encoding and sorting rules
204/// for string data. For `VARCHAR` columns, the collation determines which
205/// code page (character encoding) is used to store the data.
206///
207/// # Encoding Support
208///
209/// When the `encoding` feature is enabled, the [`Collation::encoding()`] method
210/// returns the appropriate [`encoding_rs::Encoding`] for decoding `VARCHAR` data.
211///
212/// # Example
213///
214/// ```rust,ignore
215/// use tds_protocol::token::Collation;
216///
217/// let collation = Collation { lcid: 0x0804, sort_id: 0 }; // Chinese (PRC)
218/// if let Some(encoding) = collation.encoding() {
219///     let (decoded, _, _) = encoding.decode(raw_bytes);
220///     // decoded is now proper Chinese text
221/// }
222/// ```
223#[derive(Debug, Clone, Copy, Default)]
224pub struct Collation {
225    /// Locale ID (LCID).
226    ///
227    /// The LCID encodes both the language and region. The lower 16 bits
228    /// contain the primary language ID, and bits 16-19 contain the sort ID
229    /// for some collations.
230    ///
231    /// For UTF-8 collations (SQL Server 2019+), fUTF8 (bit 26, 0x0400_0000) is set.
232    pub lcid: u32,
233    /// Sort ID.
234    ///
235    /// Used with certain collations to specify sorting behavior.
236    pub sort_id: u8,
237}
238
239impl Collation {
240    /// Create a `Collation` from the 5-byte TDS wire format.
241    ///
242    /// Format: 4 bytes LCID (little-endian u32) + 1 byte sort ID.
243    pub fn from_bytes(bytes: &[u8; 5]) -> Self {
244        Self {
245            lcid: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
246            sort_id: bytes[4],
247        }
248    }
249
250    /// Serialize to the 5-byte TDS wire format.
251    ///
252    /// Format: 4 bytes LCID (little-endian u32) + 1 byte sort ID.
253    pub fn to_bytes(&self) -> [u8; 5] {
254        let b = self.lcid.to_le_bytes();
255        [b[0], b[1], b[2], b[3], self.sort_id]
256    }
257
258    /// Returns the character encoding for this collation.
259    ///
260    /// This method maps the collation's LCID to the appropriate character
261    /// encoding from the `encoding_rs` crate.
262    ///
263    /// # Returns
264    ///
265    /// - `Some(&Encoding)` - The encoding to use for decoding `VARCHAR` data
266    /// - `None` - If the collation uses UTF-8 (no transcoding needed) or
267    ///   the LCID is not recognized (caller should use Windows-1252 fallback)
268    ///
269    /// # UTF-8 Collations
270    ///
271    /// SQL Server 2019+ supports UTF-8 collations (identified by the `_UTF8`
272    /// suffix). These return `None` because no transcoding is needed.
273    ///
274    /// # Example
275    ///
276    /// ```rust,ignore
277    /// let collation = Collation { lcid: 0x0419, sort_id: 0 }; // Russian
278    /// if let Some(encoding) = collation.encoding() {
279    ///     // encoding is Windows-1251 for Cyrillic
280    ///     let (text, _, had_errors) = encoding.decode(&raw_bytes);
281    /// }
282    /// ```
283    #[cfg(feature = "encoding")]
284    pub fn encoding(&self) -> Option<&'static encoding_rs::Encoding> {
285        // A non-zero SortId means a SQL collation, whose code page derives
286        // from the SortId, not the LCID (MS-TDS). Consulting only the LCID
287        // silently decoded these as windows-1252 (issue #158).
288        if self.sort_id != 0 {
289            return crate::collation::encoding_for_sort_id(self.sort_id);
290        }
291        crate::collation::encoding_for_lcid(self.lcid)
292    }
293
294    /// Returns whether this collation uses UTF-8 encoding.
295    ///
296    /// UTF-8 collations were introduced in SQL Server 2019 and are
297    /// identified by the `_UTF8` suffix in the collation name.
298    #[cfg(feature = "encoding")]
299    pub fn is_utf8(&self) -> bool {
300        crate::collation::is_utf8_collation(self.lcid)
301    }
302
303    /// Returns the Windows code page number for this collation.
304    ///
305    /// Useful for error messages and debugging.
306    ///
307    /// # Returns
308    ///
309    /// The code page number (e.g., 1252 for Western European, 932 for Japanese).
310    #[cfg(feature = "encoding")]
311    pub fn code_page(&self) -> Option<u16> {
312        // SQL collations (non-zero SortId) derive their code page from the
313        // SortId, not the LCID (issue #158).
314        if self.sort_id != 0 {
315            return crate::collation::code_page_for_sort_id(self.sort_id);
316        }
317        crate::collation::code_page_for_lcid(self.lcid)
318    }
319
320    /// Returns the encoding name for this collation.
321    ///
322    /// Useful for error messages and debugging.
323    #[cfg(feature = "encoding")]
324    pub fn encoding_name(&self) -> &'static str {
325        if self.sort_id != 0 {
326            return match crate::collation::encoding_for_sort_id(self.sort_id) {
327                Some(enc) => enc.name(),
328                None => "unsupported",
329            };
330        }
331        crate::collation::encoding_name_for_lcid(self.lcid)
332    }
333}
334
335/// Raw row data (not yet decoded).
336#[derive(Debug, Clone)]
337pub struct RawRow {
338    /// Raw column values.
339    pub data: bytes::Bytes,
340}
341
342/// Null bitmap compressed row.
343#[derive(Debug, Clone)]
344pub struct NbcRow {
345    /// Null bitmap.
346    pub null_bitmap: Vec<u8>,
347    /// Raw non-null column values.
348    pub data: bytes::Bytes,
349}
350
351/// Done token indicating statement completion.
352#[derive(Debug, Clone, Copy)]
353pub struct Done {
354    /// Status flags.
355    pub status: DoneStatus,
356    /// Current command.
357    pub cur_cmd: u16,
358    /// Row count (if applicable).
359    pub row_count: u64,
360}
361
362/// Done status flags.
363#[derive(Debug, Clone, Copy, Default)]
364#[non_exhaustive]
365pub struct DoneStatus {
366    /// More results follow.
367    pub more: bool,
368    /// Error occurred.
369    pub error: bool,
370    /// Transaction in progress.
371    pub in_xact: bool,
372    /// Row count is valid.
373    pub count: bool,
374    /// Attention acknowledgment.
375    pub attn: bool,
376    /// Server error caused statement termination.
377    pub srverror: bool,
378}
379
380/// Done in procedure token.
381#[derive(Debug, Clone, Copy)]
382pub struct DoneInProc {
383    /// Status flags.
384    pub status: DoneStatus,
385    /// Current command.
386    pub cur_cmd: u16,
387    /// Row count.
388    pub row_count: u64,
389}
390
391/// Done procedure token.
392#[derive(Debug, Clone, Copy)]
393pub struct DoneProc {
394    /// Status flags.
395    pub status: DoneStatus,
396    /// Current command.
397    pub cur_cmd: u16,
398    /// Row count.
399    pub row_count: u64,
400}
401
402/// Return value from stored procedure.
403#[derive(Debug, Clone)]
404#[non_exhaustive]
405pub struct ReturnValue {
406    /// Parameter ordinal.
407    pub param_ordinal: u16,
408    /// Parameter name.
409    pub param_name: String,
410    /// Status flags.
411    pub status: u8,
412    /// User type.
413    pub user_type: u32,
414    /// Type flags.
415    pub flags: u16,
416    /// Raw column type byte from the wire.
417    pub col_type: u8,
418    /// Type info.
419    pub type_info: TypeInfo,
420    /// Value data.
421    pub value: bytes::Bytes,
422}
423
424/// Server error message.
425#[derive(Debug, Clone)]
426pub struct ServerError {
427    /// Error number.
428    pub number: i32,
429    /// Error state.
430    pub state: u8,
431    /// Error severity class.
432    pub class: u8,
433    /// Error message text.
434    pub message: String,
435    /// Server name.
436    pub server: String,
437    /// Procedure name.
438    pub procedure: String,
439    /// Line number.
440    pub line: i32,
441}
442
443/// Server informational message.
444#[derive(Debug, Clone)]
445pub struct ServerInfo {
446    /// Info number.
447    pub number: i32,
448    /// Info state.
449    pub state: u8,
450    /// Info class (severity).
451    pub class: u8,
452    /// Info message text.
453    pub message: String,
454    /// Server name.
455    pub server: String,
456    /// Procedure name.
457    pub procedure: String,
458    /// Line number.
459    pub line: i32,
460}
461
462/// Login acknowledgment token.
463#[derive(Debug, Clone)]
464pub struct LoginAck {
465    /// Interface type.
466    pub interface: u8,
467    /// TDS version.
468    pub tds_version: u32,
469    /// Program name.
470    pub prog_name: String,
471    /// Program version.
472    pub prog_version: u32,
473}
474
475/// Environment change token.
476#[derive(Debug, Clone)]
477pub struct EnvChange {
478    /// Type of environment change.
479    pub env_type: EnvChangeType,
480    /// New value.
481    pub new_value: EnvChangeValue,
482    /// Old value.
483    pub old_value: EnvChangeValue,
484}
485
486/// Environment change type.
487#[derive(Debug, Clone, Copy, PartialEq, Eq)]
488#[repr(u8)]
489#[non_exhaustive]
490pub enum EnvChangeType {
491    /// Database changed.
492    Database = 1,
493    /// Language changed.
494    Language = 2,
495    /// Character set changed.
496    CharacterSet = 3,
497    /// Packet size changed.
498    PacketSize = 4,
499    /// Unicode data sorting locale ID.
500    UnicodeSortingLocalId = 5,
501    /// Unicode comparison flags.
502    UnicodeComparisonFlags = 6,
503    /// SQL collation.
504    SqlCollation = 7,
505    /// Begin transaction.
506    BeginTransaction = 8,
507    /// Commit transaction.
508    CommitTransaction = 9,
509    /// Rollback transaction.
510    RollbackTransaction = 10,
511    /// Enlist DTC transaction.
512    EnlistDtcTransaction = 11,
513    /// Defect DTC transaction.
514    DefectTransaction = 12,
515    /// Real-time log shipping.
516    RealTimeLogShipping = 13,
517    /// Promote transaction.
518    PromoteTransaction = 15,
519    /// Transaction manager address.
520    TransactionManagerAddress = 16,
521    /// Transaction ended.
522    TransactionEnded = 17,
523    /// Reset connection completion acknowledgment.
524    ResetConnectionCompletionAck = 18,
525    /// User instance started.
526    UserInstanceStarted = 19,
527    /// Routing information.
528    Routing = 20,
529}
530
531/// Environment change value.
532#[derive(Debug, Clone)]
533#[non_exhaustive]
534pub enum EnvChangeValue {
535    /// String value.
536    String(String),
537    /// Binary value.
538    Binary(bytes::Bytes),
539    /// Routing information.
540    Routing {
541        /// Host name.
542        host: String,
543        /// Port number.
544        port: u16,
545    },
546}
547
548/// Column ordering information.
549#[derive(Debug, Clone)]
550pub struct Order {
551    /// Ordered column indices.
552    pub columns: Vec<u16>,
553}
554
555/// Feature extension acknowledgment.
556#[derive(Debug, Clone)]
557pub struct FeatureExtAck {
558    /// Acknowledged features.
559    pub features: Vec<FeatureAck>,
560}
561
562/// Individual feature acknowledgment.
563#[derive(Debug, Clone)]
564pub struct FeatureAck {
565    /// Feature ID.
566    pub feature_id: u8,
567    /// Feature data.
568    pub data: bytes::Bytes,
569}
570
571/// SSPI authentication token.
572#[derive(Debug, Clone)]
573pub struct SspiToken {
574    /// SSPI data.
575    pub data: bytes::Bytes,
576}
577
578/// Session state token.
579#[derive(Debug, Clone)]
580pub struct SessionState {
581    /// Session state data.
582    pub data: bytes::Bytes,
583}
584
585/// Federated authentication info.
586#[derive(Debug, Clone)]
587pub struct FedAuthInfo {
588    /// STS URL.
589    pub sts_url: String,
590    /// Service principal name.
591    pub spn: String,
592}
593
594// =============================================================================
595// ColMetaData and Row Parsing Implementation
596// =============================================================================
597
598/// Decode collation information (5 bytes).
599///
600/// Shared by ColMetaData column parsing and CryptoMetadata base type parsing.
601pub(crate) fn decode_collation(src: &mut impl Buf) -> Result<Collation, ProtocolError> {
602    if src.remaining() < 5 {
603        return Err(ProtocolError::UnexpectedEof);
604    }
605    // Collation: LCID (4 bytes) + Sort ID (1 byte)
606    let lcid = src.get_u32_le();
607    let sort_id = src.get_u8();
608    Ok(Collation { lcid, sort_id })
609}
610
611/// Decode type-specific metadata for a column based on its TypeId.
612///
613/// Shared by ColMetaData column parsing and CryptoMetadata base type parsing.
614pub(crate) fn decode_type_info(
615    src: &mut impl Buf,
616    type_id: TypeId,
617    col_type: u8,
618) -> Result<TypeInfo, ProtocolError> {
619    match type_id {
620        // Fixed-length types have no additional metadata
621        TypeId::Null => Ok(TypeInfo::default()),
622        TypeId::Int1 | TypeId::Bit => Ok(TypeInfo::default()),
623        TypeId::Int2 => Ok(TypeInfo::default()),
624        TypeId::Int4 => Ok(TypeInfo::default()),
625        TypeId::Int8 => Ok(TypeInfo::default()),
626        TypeId::Float4 => Ok(TypeInfo::default()),
627        TypeId::Float8 => Ok(TypeInfo::default()),
628        TypeId::Money => Ok(TypeInfo::default()),
629        TypeId::Money4 => Ok(TypeInfo::default()),
630        TypeId::DateTime => Ok(TypeInfo::default()),
631        TypeId::DateTime4 => Ok(TypeInfo::default()),
632
633        // Variable length integer/float/money (1-byte max length)
634        TypeId::IntN | TypeId::BitN | TypeId::FloatN | TypeId::MoneyN | TypeId::DateTimeN => {
635            if src.remaining() < 1 {
636                return Err(ProtocolError::UnexpectedEof);
637            }
638            let max_length = src.get_u8() as u32;
639            Ok(TypeInfo {
640                max_length: Some(max_length),
641                ..Default::default()
642            })
643        }
644
645        // GUID has 1-byte length
646        TypeId::Guid => {
647            if src.remaining() < 1 {
648                return Err(ProtocolError::UnexpectedEof);
649            }
650            let max_length = src.get_u8() as u32;
651            Ok(TypeInfo {
652                max_length: Some(max_length),
653                ..Default::default()
654            })
655        }
656
657        // Decimal/Numeric types (1-byte length + precision + scale)
658        TypeId::Decimal | TypeId::Numeric | TypeId::DecimalN | TypeId::NumericN => {
659            if src.remaining() < 3 {
660                return Err(ProtocolError::UnexpectedEof);
661            }
662            let max_length = src.get_u8() as u32;
663            let precision = src.get_u8();
664            let scale = src.get_u8();
665            Ok(TypeInfo {
666                max_length: Some(max_length),
667                precision: Some(precision),
668                scale: Some(scale),
669                ..Default::default()
670            })
671        }
672
673        // Old-style byte-length strings (Char, VarChar, Binary, VarBinary)
674        TypeId::Char | TypeId::VarChar | TypeId::Binary | TypeId::VarBinary => {
675            if src.remaining() < 1 {
676                return Err(ProtocolError::UnexpectedEof);
677            }
678            let max_length = src.get_u8() as u32;
679            Ok(TypeInfo {
680                max_length: Some(max_length),
681                ..Default::default()
682            })
683        }
684
685        // Big varchar/binary with 2-byte length + collation for strings
686        TypeId::BigVarChar | TypeId::BigChar => {
687            if src.remaining() < 7 {
688                // 2 (length) + 5 (collation)
689                return Err(ProtocolError::UnexpectedEof);
690            }
691            let max_length = src.get_u16_le() as u32;
692            let collation = decode_collation(src)?;
693            Ok(TypeInfo {
694                max_length: Some(max_length),
695                collation: Some(collation),
696                ..Default::default()
697            })
698        }
699
700        // Big binary (2-byte length, no collation)
701        TypeId::BigVarBinary | TypeId::BigBinary => {
702            if src.remaining() < 2 {
703                return Err(ProtocolError::UnexpectedEof);
704            }
705            let max_length = src.get_u16_le() as u32;
706            Ok(TypeInfo {
707                max_length: Some(max_length),
708                ..Default::default()
709            })
710        }
711
712        // Unicode strings (NChar, NVarChar) - 2-byte length + collation
713        TypeId::NChar | TypeId::NVarChar => {
714            if src.remaining() < 7 {
715                // 2 (length) + 5 (collation)
716                return Err(ProtocolError::UnexpectedEof);
717            }
718            let max_length = src.get_u16_le() as u32;
719            let collation = decode_collation(src)?;
720            Ok(TypeInfo {
721                max_length: Some(max_length),
722                collation: Some(collation),
723                ..Default::default()
724            })
725        }
726
727        // Date type (no additional metadata)
728        TypeId::Date => Ok(TypeInfo::default()),
729
730        // Time, DateTime2, DateTimeOffset have scale
731        TypeId::Time | TypeId::DateTime2 | TypeId::DateTimeOffset => {
732            if src.remaining() < 1 {
733                return Err(ProtocolError::UnexpectedEof);
734            }
735            let scale = src.get_u8();
736            Ok(TypeInfo {
737                scale: Some(scale),
738                ..Default::default()
739            })
740        }
741
742        // Text/NText/Image (deprecated LOB types)
743        TypeId::Text | TypeId::NText | TypeId::Image => {
744            // These have complex metadata: length (4) + collation (5) + table name parts
745            if src.remaining() < 4 {
746                return Err(ProtocolError::UnexpectedEof);
747            }
748            let max_length = src.get_u32_le();
749
750            // For Text/NText, read collation
751            let collation = if type_id == TypeId::Text || type_id == TypeId::NText {
752                if src.remaining() < 5 {
753                    return Err(ProtocolError::UnexpectedEof);
754                }
755                Some(decode_collation(src)?)
756            } else {
757                None
758            };
759
760            // Skip table name parts (variable length)
761            // Format: numParts (1 byte) followed by us_varchar for each part
762            if src.remaining() < 1 {
763                return Err(ProtocolError::UnexpectedEof);
764            }
765            let num_parts = src.get_u8();
766            for _ in 0..num_parts {
767                // Read and discard table name part
768                let _ = read_us_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
769            }
770
771            Ok(TypeInfo {
772                max_length: Some(max_length),
773                collation,
774                ..Default::default()
775            })
776        }
777
778        // XML type
779        TypeId::Xml => {
780            if src.remaining() < 1 {
781                return Err(ProtocolError::UnexpectedEof);
782            }
783            let schema_present = src.get_u8();
784
785            if schema_present != 0 {
786                // XML_INFO per MS-TDS §2.2.5.5.3: DBNAME and OWNING_SCHEMA are
787                // B_VARCHAR (1-byte length); only XML_SCHEMA_COLLECTION is
788                // US_VARCHAR (2-byte length).
789                let _ = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // db name
790                let _ = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // owning schema
791                let _ = read_us_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // xml schema collection
792            }
793
794            Ok(TypeInfo::default())
795        }
796
797        // UDT (User-defined type) - complex metadata
798        TypeId::Udt => {
799            // Max length (2 bytes)
800            if src.remaining() < 2 {
801                return Err(ProtocolError::UnexpectedEof);
802            }
803            let max_length = src.get_u16_le() as u32;
804
805            // UDT_INFO per MS-TDS: DB_NAME, SCHEMA_NAME, and TYPE_NAME are
806            // B_VARCHAR (1-byte length); only ASSEMBLY_QUALIFIED_NAME is
807            // US_VARCHAR (2-byte length).
808            let _ = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // db name
809            let _ = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // schema name
810            let _ = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // type name
811            let _ = read_us_varchar(src).ok_or(ProtocolError::UnexpectedEof)?; // assembly qualified name
812
813            Ok(TypeInfo {
814                max_length: Some(max_length),
815                ..Default::default()
816            })
817        }
818
819        // Table-valued parameter - complex metadata (skip for now)
820        TypeId::Tvp => {
821            // TVP has very complex metadata, not commonly used
822            // For now, we can't properly parse this
823            Err(ProtocolError::InvalidTokenType(col_type))
824        }
825
826        // SQL Variant - 4-byte length
827        TypeId::Variant => {
828            if src.remaining() < 4 {
829                return Err(ProtocolError::UnexpectedEof);
830            }
831            let max_length = src.get_u32_le();
832            Ok(TypeInfo {
833                max_length: Some(max_length),
834                ..Default::default()
835            })
836        }
837    }
838}
839
840impl ColMetaData {
841    /// Special value indicating no metadata.
842    pub const NO_METADATA: u16 = 0xFFFF;
843
844    /// Decode a COLMETADATA token from bytes.
845    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
846        if src.remaining() < 2 {
847            return Err(ProtocolError::UnexpectedEof);
848        }
849
850        let column_count = src.get_u16_le();
851
852        // 0xFFFF means no metadata present
853        if column_count == Self::NO_METADATA {
854            return Ok(Self {
855                columns: Vec::new(),
856                cek_table: None,
857            });
858        }
859
860        let mut columns = Vec::with_capacity(column_count as usize);
861
862        for _ in 0..column_count {
863            let column = Self::decode_column(src)?;
864            columns.push(column);
865        }
866
867        Ok(Self {
868            columns,
869            cek_table: None,
870        })
871    }
872
873    /// Decode a single column from the metadata.
874    fn decode_column(src: &mut impl Buf) -> Result<ColumnData, ProtocolError> {
875        // UserType (4 bytes) + Flags (2 bytes) + TypeId (1 byte)
876        if src.remaining() < 7 {
877            return Err(ProtocolError::UnexpectedEof);
878        }
879
880        let user_type = src.get_u32_le();
881        let flags = src.get_u16_le();
882        let col_type = src.get_u8();
883
884        // An unknown type byte must be a hard error: treating it as Null (a
885        // zero-length column) misaligns every subsequent column and row,
886        // producing plausible garbage values (issue #157).
887        let type_id = TypeId::from_u8(col_type).ok_or(ProtocolError::InvalidDataType(col_type))?;
888
889        // Parse type-specific metadata
890        let type_info = decode_type_info(src, type_id, col_type)?;
891
892        // Read column name (B_VARCHAR format - 1 byte length in characters)
893        let name = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
894
895        Ok(ColumnData {
896            name,
897            type_id,
898            col_type,
899            flags,
900            user_type,
901            type_info,
902            crypto_metadata: None,
903        })
904    }
905
906    /// Decode a COLMETADATA token with Always Encrypted support.
907    ///
908    /// When column encryption was negotiated in Login7, the server sends a CekTable
909    /// before column definitions and per-column CryptoMetadata for encrypted columns.
910    ///
911    /// # Wire Format (with encryption)
912    ///
913    /// ```text
914    /// column_count: USHORT
915    /// cek_table: CekTable (always present when encryption negotiated)
916    /// columns: ColumnData[column_count] (with CryptoMetadata for encrypted columns)
917    /// ```
918    pub fn decode_encrypted(src: &mut impl Buf) -> Result<Self, ProtocolError> {
919        if src.remaining() < 2 {
920            return Err(ProtocolError::UnexpectedEof);
921        }
922
923        let column_count = src.get_u16_le();
924
925        if column_count == Self::NO_METADATA {
926            return Ok(Self {
927                columns: Vec::new(),
928                cek_table: None,
929            });
930        }
931
932        // Parse CEK table (always present when encryption was negotiated)
933        let cek_table = crate::crypto::CekTable::decode(src)?;
934
935        let mut columns = Vec::with_capacity(column_count as usize);
936
937        for _ in 0..column_count {
938            let column = Self::decode_column_encrypted(src)?;
939            columns.push(column);
940        }
941
942        Ok(Self {
943            columns,
944            cek_table: Some(cek_table),
945        })
946    }
947
948    /// Decode a single column definition with Always Encrypted support.
949    ///
950    /// For encrypted columns (flags & 0x0800), parses CryptoMetadata after the type info.
951    fn decode_column_encrypted(src: &mut impl Buf) -> Result<ColumnData, ProtocolError> {
952        if src.remaining() < 7 {
953            return Err(ProtocolError::UnexpectedEof);
954        }
955
956        let user_type = src.get_u32_le();
957        let flags = src.get_u16_le();
958        let col_type = src.get_u8();
959
960        let type_id = TypeId::from_u8(col_type).ok_or(ProtocolError::InvalidDataType(col_type))?;
961
962        // Parse type-specific metadata (for encrypted columns, this is the transport type)
963        let type_info = decode_type_info(src, type_id, col_type)?;
964
965        // Parse CryptoMetadata if the column is encrypted
966        let crypto_metadata = if crate::crypto::is_column_encrypted(flags) {
967            Some(crate::crypto::CryptoMetadata::decode(src)?)
968        } else {
969            None
970        };
971
972        // Read column name
973        let name = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
974
975        Ok(ColumnData {
976            name,
977            type_id,
978            col_type,
979            flags,
980            user_type,
981            type_info,
982            crypto_metadata,
983        })
984    }
985
986    /// Get the number of columns.
987    #[must_use]
988    pub fn column_count(&self) -> usize {
989        self.columns.len()
990    }
991
992    /// Check if this represents no metadata.
993    #[must_use]
994    pub fn is_empty(&self) -> bool {
995        self.columns.is_empty()
996    }
997}
998
999impl ColumnData {
1000    /// Check if this column is nullable.
1001    #[must_use]
1002    pub fn is_nullable(&self) -> bool {
1003        (self.flags & 0x0001) != 0
1004    }
1005
1006    /// Get the fixed size in bytes for this column, if applicable.
1007    ///
1008    /// Returns `None` for variable-length types.
1009    #[must_use]
1010    pub fn fixed_size(&self) -> Option<usize> {
1011        match self.type_id {
1012            TypeId::Null => Some(0),
1013            TypeId::Int1 | TypeId::Bit => Some(1),
1014            TypeId::Int2 => Some(2),
1015            TypeId::Int4 => Some(4),
1016            TypeId::Int8 => Some(8),
1017            TypeId::Float4 => Some(4),
1018            TypeId::Float8 => Some(8),
1019            TypeId::Money => Some(8),
1020            TypeId::Money4 => Some(4),
1021            TypeId::DateTime => Some(8),
1022            TypeId::DateTime4 => Some(4),
1023            TypeId::Date => Some(3),
1024            _ => None,
1025        }
1026    }
1027}
1028
1029// =============================================================================
1030// Row Parsing Implementation
1031// =============================================================================
1032
1033impl RawRow {
1034    /// Decode a ROW token from bytes.
1035    ///
1036    /// This function requires the column metadata to know how to parse the row.
1037    /// The row data is stored as raw bytes for later parsing.
1038    pub fn decode(src: &mut impl Buf, metadata: &ColMetaData) -> Result<Self, ProtocolError> {
1039        let mut data = bytes::BytesMut::new();
1040
1041        for col in &metadata.columns {
1042            Self::decode_column_value(src, col, &mut data)?;
1043        }
1044
1045        Ok(Self {
1046            data: data.freeze(),
1047        })
1048    }
1049
1050    /// Decode a single column value and append to the output buffer.
1051    fn decode_column_value(
1052        src: &mut impl Buf,
1053        col: &ColumnData,
1054        dst: &mut bytes::BytesMut,
1055    ) -> Result<(), ProtocolError> {
1056        match col.type_id {
1057            // Fixed-length types
1058            TypeId::Null => {
1059                // No data
1060            }
1061            TypeId::Int1 | TypeId::Bit => {
1062                if src.remaining() < 1 {
1063                    return Err(ProtocolError::UnexpectedEof);
1064                }
1065                dst.extend_from_slice(&[src.get_u8()]);
1066            }
1067            TypeId::Int2 => {
1068                if src.remaining() < 2 {
1069                    return Err(ProtocolError::UnexpectedEof);
1070                }
1071                dst.extend_from_slice(&src.get_u16_le().to_le_bytes());
1072            }
1073            TypeId::Int4 => {
1074                if src.remaining() < 4 {
1075                    return Err(ProtocolError::UnexpectedEof);
1076                }
1077                dst.extend_from_slice(&src.get_u32_le().to_le_bytes());
1078            }
1079            TypeId::Int8 => {
1080                if src.remaining() < 8 {
1081                    return Err(ProtocolError::UnexpectedEof);
1082                }
1083                dst.extend_from_slice(&src.get_u64_le().to_le_bytes());
1084            }
1085            TypeId::Float4 => {
1086                if src.remaining() < 4 {
1087                    return Err(ProtocolError::UnexpectedEof);
1088                }
1089                dst.extend_from_slice(&src.get_u32_le().to_le_bytes());
1090            }
1091            TypeId::Float8 => {
1092                if src.remaining() < 8 {
1093                    return Err(ProtocolError::UnexpectedEof);
1094                }
1095                dst.extend_from_slice(&src.get_u64_le().to_le_bytes());
1096            }
1097            TypeId::Money => {
1098                if src.remaining() < 8 {
1099                    return Err(ProtocolError::UnexpectedEof);
1100                }
1101                let hi = src.get_u32_le();
1102                let lo = src.get_u32_le();
1103                dst.extend_from_slice(&hi.to_le_bytes());
1104                dst.extend_from_slice(&lo.to_le_bytes());
1105            }
1106            TypeId::Money4 => {
1107                if src.remaining() < 4 {
1108                    return Err(ProtocolError::UnexpectedEof);
1109                }
1110                dst.extend_from_slice(&src.get_u32_le().to_le_bytes());
1111            }
1112            TypeId::DateTime => {
1113                if src.remaining() < 8 {
1114                    return Err(ProtocolError::UnexpectedEof);
1115                }
1116                let days = src.get_u32_le();
1117                let time = src.get_u32_le();
1118                dst.extend_from_slice(&days.to_le_bytes());
1119                dst.extend_from_slice(&time.to_le_bytes());
1120            }
1121            TypeId::DateTime4 => {
1122                if src.remaining() < 4 {
1123                    return Err(ProtocolError::UnexpectedEof);
1124                }
1125                dst.extend_from_slice(&src.get_u32_le().to_le_bytes());
1126            }
1127            // DATE type uses 1-byte length prefix (can be NULL)
1128            TypeId::Date => {
1129                Self::decode_bytelen_type(src, dst)?;
1130            }
1131
1132            // Variable-length nullable types (length-prefixed)
1133            TypeId::IntN | TypeId::BitN | TypeId::FloatN | TypeId::MoneyN | TypeId::DateTimeN => {
1134                Self::decode_bytelen_type(src, dst)?;
1135            }
1136
1137            TypeId::Guid => {
1138                Self::decode_bytelen_type(src, dst)?;
1139            }
1140
1141            TypeId::Decimal | TypeId::Numeric | TypeId::DecimalN | TypeId::NumericN => {
1142                Self::decode_bytelen_type(src, dst)?;
1143            }
1144
1145            // Old-style byte-length strings
1146            TypeId::Char | TypeId::VarChar | TypeId::Binary | TypeId::VarBinary => {
1147                Self::decode_bytelen_type(src, dst)?;
1148            }
1149
1150            // 2-byte length strings (or PLP for MAX types)
1151            TypeId::BigVarChar | TypeId::BigVarBinary => {
1152                // max_length == 0xFFFF indicates VARCHAR(MAX) or VARBINARY(MAX), which uses PLP
1153                if col.type_info.max_length == Some(0xFFFF) {
1154                    Self::decode_plp_type(src, dst)?;
1155                } else {
1156                    Self::decode_ushortlen_type(src, dst)?;
1157                }
1158            }
1159
1160            // Fixed-length types that don't have MAX variants
1161            TypeId::BigChar | TypeId::BigBinary => {
1162                Self::decode_ushortlen_type(src, dst)?;
1163            }
1164
1165            // Unicode strings (2-byte length in bytes, or PLP for NVARCHAR(MAX))
1166            TypeId::NVarChar => {
1167                // max_length == 0xFFFF indicates NVARCHAR(MAX), which uses PLP
1168                if col.type_info.max_length == Some(0xFFFF) {
1169                    Self::decode_plp_type(src, dst)?;
1170                } else {
1171                    Self::decode_ushortlen_type(src, dst)?;
1172                }
1173            }
1174
1175            // Fixed-length NCHAR doesn't have MAX variant
1176            TypeId::NChar => {
1177                Self::decode_ushortlen_type(src, dst)?;
1178            }
1179
1180            // Time types with scale
1181            TypeId::Time | TypeId::DateTime2 | TypeId::DateTimeOffset => {
1182                Self::decode_bytelen_type(src, dst)?;
1183            }
1184
1185            // TEXT/NTEXT/IMAGE - deprecated LOB types using textptr format
1186            TypeId::Text | TypeId::NText | TypeId::Image => {
1187                Self::decode_textptr_type(src, dst)?;
1188            }
1189
1190            // XML - uses actual PLP format
1191            TypeId::Xml => {
1192                Self::decode_plp_type(src, dst)?;
1193            }
1194
1195            // Complex types
1196            TypeId::Variant => {
1197                Self::decode_intlen_type(src, dst)?;
1198            }
1199
1200            TypeId::Udt => {
1201                // UDT uses PLP encoding
1202                Self::decode_plp_type(src, dst)?;
1203            }
1204
1205            TypeId::Tvp => {
1206                // TVP not supported in row data
1207                return Err(ProtocolError::InvalidTokenType(col.col_type));
1208            }
1209        }
1210
1211        Ok(())
1212    }
1213
1214    /// Decode a 1-byte length-prefixed value.
1215    fn decode_bytelen_type(
1216        src: &mut impl Buf,
1217        dst: &mut bytes::BytesMut,
1218    ) -> Result<(), ProtocolError> {
1219        if src.remaining() < 1 {
1220            return Err(ProtocolError::UnexpectedEof);
1221        }
1222        let len = src.get_u8() as usize;
1223        if len == 0xFF {
1224            // NULL value - store as zero-length with NULL marker
1225            dst.extend_from_slice(&[0xFF]);
1226        } else if len == 0 {
1227            // Empty value
1228            dst.extend_from_slice(&[0x00]);
1229        } else {
1230            if src.remaining() < len {
1231                return Err(ProtocolError::UnexpectedEof);
1232            }
1233            dst.extend_from_slice(&[len as u8]);
1234            for _ in 0..len {
1235                dst.extend_from_slice(&[src.get_u8()]);
1236            }
1237        }
1238        Ok(())
1239    }
1240
1241    /// Decode a 2-byte length-prefixed value.
1242    fn decode_ushortlen_type(
1243        src: &mut impl Buf,
1244        dst: &mut bytes::BytesMut,
1245    ) -> Result<(), ProtocolError> {
1246        if src.remaining() < 2 {
1247            return Err(ProtocolError::UnexpectedEof);
1248        }
1249        let len = src.get_u16_le() as usize;
1250        if len == 0xFFFF {
1251            // NULL value
1252            dst.extend_from_slice(&0xFFFFu16.to_le_bytes());
1253        } else if len == 0 {
1254            // Empty value
1255            dst.extend_from_slice(&0u16.to_le_bytes());
1256        } else {
1257            if src.remaining() < len {
1258                return Err(ProtocolError::UnexpectedEof);
1259            }
1260            dst.extend_from_slice(&(len as u16).to_le_bytes());
1261            for _ in 0..len {
1262                dst.extend_from_slice(&[src.get_u8()]);
1263            }
1264        }
1265        Ok(())
1266    }
1267
1268    /// Decode a 4-byte length-prefixed value.
1269    fn decode_intlen_type(
1270        src: &mut impl Buf,
1271        dst: &mut bytes::BytesMut,
1272    ) -> Result<(), ProtocolError> {
1273        if src.remaining() < 4 {
1274            return Err(ProtocolError::UnexpectedEof);
1275        }
1276        let len = src.get_u32_le() as usize;
1277        if len == 0xFFFFFFFF {
1278            // NULL value
1279            dst.extend_from_slice(&0xFFFFFFFFu32.to_le_bytes());
1280        } else if len == 0 {
1281            // Empty value
1282            dst.extend_from_slice(&0u32.to_le_bytes());
1283        } else {
1284            if src.remaining() < len {
1285                return Err(ProtocolError::UnexpectedEof);
1286            }
1287            dst.extend_from_slice(&(len as u32).to_le_bytes());
1288            for _ in 0..len {
1289                dst.extend_from_slice(&[src.get_u8()]);
1290            }
1291        }
1292        Ok(())
1293    }
1294
1295    /// Decode a TEXT/NTEXT/IMAGE type (textptr format).
1296    ///
1297    /// These deprecated LOB types use a special format:
1298    /// - 1 byte: textptr_len (0 = NULL)
1299    /// - textptr_len bytes: textptr (if not NULL)
1300    /// - 8 bytes: timestamp (if not NULL)
1301    /// - 4 bytes: data length (if not NULL)
1302    /// - data_len bytes: the actual data (if not NULL)
1303    ///
1304    /// We convert this to PLP format for the client to parse:
1305    /// - 8 bytes: total length (0xFFFFFFFFFFFFFFFF = NULL)
1306    /// - 4 bytes: chunk length (= data length)
1307    /// - chunk data
1308    /// - 4 bytes: 0 (terminator)
1309    fn decode_textptr_type(
1310        src: &mut impl Buf,
1311        dst: &mut bytes::BytesMut,
1312    ) -> Result<(), ProtocolError> {
1313        if src.remaining() < 1 {
1314            return Err(ProtocolError::UnexpectedEof);
1315        }
1316
1317        let textptr_len = src.get_u8() as usize;
1318
1319        if textptr_len == 0 {
1320            // NULL value - write PLP NULL marker
1321            dst.extend_from_slice(&0xFFFFFFFFFFFFFFFFu64.to_le_bytes());
1322            return Ok(());
1323        }
1324
1325        // Skip textptr bytes
1326        if src.remaining() < textptr_len {
1327            return Err(ProtocolError::UnexpectedEof);
1328        }
1329        src.advance(textptr_len);
1330
1331        // Skip 8-byte timestamp
1332        if src.remaining() < 8 {
1333            return Err(ProtocolError::UnexpectedEof);
1334        }
1335        src.advance(8);
1336
1337        // Read data length
1338        if src.remaining() < 4 {
1339            return Err(ProtocolError::UnexpectedEof);
1340        }
1341        let data_len = src.get_u32_le() as usize;
1342
1343        if src.remaining() < data_len {
1344            return Err(ProtocolError::UnexpectedEof);
1345        }
1346
1347        // Write in PLP format for client parsing:
1348        // - 8 bytes: total length
1349        // - 4 bytes: chunk length
1350        // - chunk data
1351        // - 4 bytes: 0 (terminator)
1352        dst.extend_from_slice(&(data_len as u64).to_le_bytes());
1353        dst.extend_from_slice(&(data_len as u32).to_le_bytes());
1354        for _ in 0..data_len {
1355            dst.extend_from_slice(&[src.get_u8()]);
1356        }
1357        dst.extend_from_slice(&0u32.to_le_bytes()); // PLP terminator
1358
1359        Ok(())
1360    }
1361
1362    /// Decode a PLP (Partially Length-Prefixed) value.
1363    ///
1364    /// PLP format:
1365    /// - 8 bytes: total length (0xFFFFFFFFFFFFFFFE = unknown, 0xFFFFFFFFFFFFFFFF = NULL)
1366    /// - If not NULL: chunks of (4 byte chunk length + data) until chunk length = 0
1367    fn decode_plp_type(src: &mut impl Buf, dst: &mut bytes::BytesMut) -> Result<(), ProtocolError> {
1368        if src.remaining() < 8 {
1369            return Err(ProtocolError::UnexpectedEof);
1370        }
1371
1372        let total_len = src.get_u64_le();
1373
1374        // Store the total length marker
1375        dst.extend_from_slice(&total_len.to_le_bytes());
1376
1377        if total_len == 0xFFFFFFFFFFFFFFFF {
1378            // NULL value - no more data
1379            return Ok(());
1380        }
1381
1382        // Read chunks until terminator
1383        loop {
1384            if src.remaining() < 4 {
1385                return Err(ProtocolError::UnexpectedEof);
1386            }
1387            let chunk_len = src.get_u32_le() as usize;
1388            dst.extend_from_slice(&(chunk_len as u32).to_le_bytes());
1389
1390            if chunk_len == 0 {
1391                // End of PLP data
1392                break;
1393            }
1394
1395            if src.remaining() < chunk_len {
1396                return Err(ProtocolError::UnexpectedEof);
1397            }
1398
1399            for _ in 0..chunk_len {
1400                dst.extend_from_slice(&[src.get_u8()]);
1401            }
1402        }
1403
1404        Ok(())
1405    }
1406}
1407
1408// =============================================================================
1409// NbcRow Parsing Implementation
1410// =============================================================================
1411
1412impl NbcRow {
1413    /// Decode an NBCROW token from bytes.
1414    ///
1415    /// NBCROW (Null Bitmap Compressed Row) stores a bitmap indicating which
1416    /// columns are NULL, followed by only the non-NULL values.
1417    pub fn decode(src: &mut impl Buf, metadata: &ColMetaData) -> Result<Self, ProtocolError> {
1418        let col_count = metadata.columns.len();
1419        let bitmap_len = col_count.div_ceil(8);
1420
1421        if src.remaining() < bitmap_len {
1422            return Err(ProtocolError::UnexpectedEof);
1423        }
1424
1425        // Read null bitmap
1426        let mut null_bitmap = vec![0u8; bitmap_len];
1427        for byte in &mut null_bitmap {
1428            *byte = src.get_u8();
1429        }
1430
1431        // Read non-null values
1432        let mut data = bytes::BytesMut::new();
1433
1434        for (i, col) in metadata.columns.iter().enumerate() {
1435            let byte_idx = i / 8;
1436            let bit_idx = i % 8;
1437            let is_null = (null_bitmap[byte_idx] & (1 << bit_idx)) != 0;
1438
1439            if !is_null {
1440                // Read the value - for NBCROW, we read without the length prefix
1441                // for fixed-length types, and with length prefix for variable types
1442                RawRow::decode_column_value(src, col, &mut data)?;
1443            }
1444        }
1445
1446        Ok(Self {
1447            null_bitmap,
1448            data: data.freeze(),
1449        })
1450    }
1451
1452    /// Check if a column at the given index is NULL.
1453    #[must_use]
1454    pub fn is_null(&self, column_index: usize) -> bool {
1455        let byte_idx = column_index / 8;
1456        let bit_idx = column_index % 8;
1457        if byte_idx < self.null_bitmap.len() {
1458            (self.null_bitmap[byte_idx] & (1 << bit_idx)) != 0
1459        } else {
1460            true // Out of bounds = NULL
1461        }
1462    }
1463}
1464
1465// =============================================================================
1466// ReturnValue Parsing Implementation
1467// =============================================================================
1468
1469impl ReturnValue {
1470    /// Decode a RETURNVALUE token from bytes.
1471    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1472        // MS-TDS §2.2.7.18: the RETURNVALUE token has no length prefix —
1473        // it begins directly with the 2-byte ParamOrdinal. The previous
1474        // spurious 2-byte read consumed the ordinal and shifted every
1475        // subsequent field, leaving the stream parser two bytes ahead and
1476        // reading value bytes as the next token type (e.g. `0x74` from a
1477        // Unicode name fragment was misread as an unknown token).
1478        if src.remaining() < 2 {
1479            return Err(ProtocolError::UnexpectedEof);
1480        }
1481        let param_ordinal = src.get_u16_le();
1482
1483        // Parameter name (B_VARCHAR)
1484        let param_name = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1485
1486        // Status (1 byte)
1487        if src.remaining() < 1 {
1488            return Err(ProtocolError::UnexpectedEof);
1489        }
1490        let status = src.get_u8();
1491
1492        // User type (4 bytes) + flags (2 bytes) + type id (1 byte)
1493        if src.remaining() < 7 {
1494            return Err(ProtocolError::UnexpectedEof);
1495        }
1496        let user_type = src.get_u32_le();
1497        let flags = src.get_u16_le();
1498        let col_type = src.get_u8();
1499
1500        let type_id = TypeId::from_u8(col_type).ok_or(ProtocolError::InvalidDataType(col_type))?;
1501
1502        // Parse type info
1503        let type_info = decode_type_info(src, type_id, col_type)?;
1504
1505        // Read the value data
1506        let mut value_buf = bytes::BytesMut::new();
1507
1508        // Create a temporary column for value parsing
1509        let temp_col = ColumnData {
1510            name: String::new(),
1511            type_id,
1512            col_type,
1513            flags,
1514            user_type,
1515            type_info: type_info.clone(),
1516            crypto_metadata: None,
1517        };
1518
1519        RawRow::decode_column_value(src, &temp_col, &mut value_buf)?;
1520
1521        Ok(Self {
1522            param_ordinal,
1523            param_name,
1524            status,
1525            user_type,
1526            flags,
1527            col_type,
1528            type_info,
1529            value: value_buf.freeze(),
1530        })
1531    }
1532}
1533
1534// =============================================================================
1535// SessionState Parsing Implementation
1536// =============================================================================
1537
1538impl SessionState {
1539    /// Decode a SESSIONSTATE token from bytes.
1540    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1541        if src.remaining() < 4 {
1542            return Err(ProtocolError::UnexpectedEof);
1543        }
1544
1545        let length = src.get_u32_le() as usize;
1546
1547        if src.remaining() < length {
1548            return Err(ProtocolError::IncompletePacket {
1549                expected: length,
1550                actual: src.remaining(),
1551            });
1552        }
1553
1554        let data = src.copy_to_bytes(length);
1555
1556        Ok(Self { data })
1557    }
1558}
1559
1560// =============================================================================
1561// Token Parsing Implementation
1562// =============================================================================
1563
1564/// Done token status flags bit positions.
1565mod done_status_bits {
1566    pub const DONE_MORE: u16 = 0x0001;
1567    pub const DONE_ERROR: u16 = 0x0002;
1568    pub const DONE_INXACT: u16 = 0x0004;
1569    pub const DONE_COUNT: u16 = 0x0010;
1570    pub const DONE_ATTN: u16 = 0x0020;
1571    pub const DONE_SRVERROR: u16 = 0x0100;
1572}
1573
1574impl DoneStatus {
1575    /// Parse done status from raw bits.
1576    #[must_use]
1577    pub fn from_bits(bits: u16) -> Self {
1578        use done_status_bits::*;
1579        Self {
1580            more: (bits & DONE_MORE) != 0,
1581            error: (bits & DONE_ERROR) != 0,
1582            in_xact: (bits & DONE_INXACT) != 0,
1583            count: (bits & DONE_COUNT) != 0,
1584            attn: (bits & DONE_ATTN) != 0,
1585            srverror: (bits & DONE_SRVERROR) != 0,
1586        }
1587    }
1588
1589    /// Convert to raw bits.
1590    #[must_use]
1591    pub fn to_bits(&self) -> u16 {
1592        use done_status_bits::*;
1593        let mut bits = 0u16;
1594        if self.more {
1595            bits |= DONE_MORE;
1596        }
1597        if self.error {
1598            bits |= DONE_ERROR;
1599        }
1600        if self.in_xact {
1601            bits |= DONE_INXACT;
1602        }
1603        if self.count {
1604            bits |= DONE_COUNT;
1605        }
1606        if self.attn {
1607            bits |= DONE_ATTN;
1608        }
1609        if self.srverror {
1610            bits |= DONE_SRVERROR;
1611        }
1612        bits
1613    }
1614}
1615
1616impl Done {
1617    /// Size of the DONE token in bytes (excluding token type byte).
1618    pub const SIZE: usize = 12; // 2 (status) + 2 (curcmd) + 8 (rowcount)
1619
1620    /// Decode a DONE token from bytes.
1621    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1622        if src.remaining() < Self::SIZE {
1623            return Err(ProtocolError::IncompletePacket {
1624                expected: Self::SIZE,
1625                actual: src.remaining(),
1626            });
1627        }
1628
1629        let status = DoneStatus::from_bits(src.get_u16_le());
1630        let cur_cmd = src.get_u16_le();
1631        let row_count = src.get_u64_le();
1632
1633        Ok(Self {
1634            status,
1635            cur_cmd,
1636            row_count,
1637        })
1638    }
1639
1640    /// Encode the DONE token to bytes.
1641    pub fn encode(&self, dst: &mut impl BufMut) {
1642        dst.put_u8(TokenType::Done as u8);
1643        dst.put_u16_le(self.status.to_bits());
1644        dst.put_u16_le(self.cur_cmd);
1645        dst.put_u64_le(self.row_count);
1646    }
1647
1648    /// Check if more results follow this DONE token.
1649    #[must_use]
1650    pub const fn has_more(&self) -> bool {
1651        self.status.more
1652    }
1653
1654    /// Check if an error occurred.
1655    #[must_use]
1656    pub const fn has_error(&self) -> bool {
1657        self.status.error
1658    }
1659
1660    /// Check if the row count is valid.
1661    #[must_use]
1662    pub const fn has_count(&self) -> bool {
1663        self.status.count
1664    }
1665}
1666
1667impl DoneProc {
1668    /// Size of the DONEPROC token in bytes (excluding token type byte).
1669    pub const SIZE: usize = 12;
1670
1671    /// Decode a DONEPROC token from bytes.
1672    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1673        if src.remaining() < Self::SIZE {
1674            return Err(ProtocolError::IncompletePacket {
1675                expected: Self::SIZE,
1676                actual: src.remaining(),
1677            });
1678        }
1679
1680        let status = DoneStatus::from_bits(src.get_u16_le());
1681        let cur_cmd = src.get_u16_le();
1682        let row_count = src.get_u64_le();
1683
1684        Ok(Self {
1685            status,
1686            cur_cmd,
1687            row_count,
1688        })
1689    }
1690
1691    /// Encode the DONEPROC token to bytes.
1692    pub fn encode(&self, dst: &mut impl BufMut) {
1693        dst.put_u8(TokenType::DoneProc as u8);
1694        dst.put_u16_le(self.status.to_bits());
1695        dst.put_u16_le(self.cur_cmd);
1696        dst.put_u64_le(self.row_count);
1697    }
1698}
1699
1700impl DoneInProc {
1701    /// Size of the DONEINPROC token in bytes (excluding token type byte).
1702    pub const SIZE: usize = 12;
1703
1704    /// Decode a DONEINPROC token from bytes.
1705    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1706        if src.remaining() < Self::SIZE {
1707            return Err(ProtocolError::IncompletePacket {
1708                expected: Self::SIZE,
1709                actual: src.remaining(),
1710            });
1711        }
1712
1713        let status = DoneStatus::from_bits(src.get_u16_le());
1714        let cur_cmd = src.get_u16_le();
1715        let row_count = src.get_u64_le();
1716
1717        Ok(Self {
1718            status,
1719            cur_cmd,
1720            row_count,
1721        })
1722    }
1723
1724    /// Encode the DONEINPROC token to bytes.
1725    pub fn encode(&self, dst: &mut impl BufMut) {
1726        dst.put_u8(TokenType::DoneInProc as u8);
1727        dst.put_u16_le(self.status.to_bits());
1728        dst.put_u16_le(self.cur_cmd);
1729        dst.put_u64_le(self.row_count);
1730    }
1731}
1732
1733impl ServerError {
1734    /// Decode an ERROR token from bytes.
1735    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1736        // ERROR token: length (2) + number (4) + state (1) + class (1) +
1737        //              message (us_varchar) + server (b_varchar) + procedure (b_varchar) + line (4)
1738        if src.remaining() < 2 {
1739            return Err(ProtocolError::UnexpectedEof);
1740        }
1741
1742        let _length = src.get_u16_le();
1743
1744        if src.remaining() < 6 {
1745            return Err(ProtocolError::UnexpectedEof);
1746        }
1747
1748        let number = src.get_i32_le();
1749        let state = src.get_u8();
1750        let class = src.get_u8();
1751
1752        let message = read_us_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1753        let server = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1754        let procedure = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1755
1756        if src.remaining() < 4 {
1757            return Err(ProtocolError::UnexpectedEof);
1758        }
1759        let line = src.get_i32_le();
1760
1761        Ok(Self {
1762            number,
1763            state,
1764            class,
1765            message,
1766            server,
1767            procedure,
1768            line,
1769        })
1770    }
1771
1772    /// Check if this is a fatal error (severity >= 20).
1773    #[must_use]
1774    pub const fn is_fatal(&self) -> bool {
1775        self.class >= 20
1776    }
1777
1778    /// Check if this error indicates the batch was aborted (severity >= 16).
1779    #[must_use]
1780    pub const fn is_batch_abort(&self) -> bool {
1781        self.class >= 16
1782    }
1783}
1784
1785impl ServerInfo {
1786    /// Decode an INFO token from bytes.
1787    ///
1788    /// INFO tokens have the same structure as ERROR tokens but with lower severity.
1789    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1790        if src.remaining() < 2 {
1791            return Err(ProtocolError::UnexpectedEof);
1792        }
1793
1794        let _length = src.get_u16_le();
1795
1796        if src.remaining() < 6 {
1797            return Err(ProtocolError::UnexpectedEof);
1798        }
1799
1800        let number = src.get_i32_le();
1801        let state = src.get_u8();
1802        let class = src.get_u8();
1803
1804        let message = read_us_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1805        let server = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1806        let procedure = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1807
1808        if src.remaining() < 4 {
1809            return Err(ProtocolError::UnexpectedEof);
1810        }
1811        let line = src.get_i32_le();
1812
1813        Ok(Self {
1814            number,
1815            state,
1816            class,
1817            message,
1818            server,
1819            procedure,
1820            line,
1821        })
1822    }
1823}
1824
1825impl LoginAck {
1826    /// Decode a LOGINACK token from bytes.
1827    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1828        // LOGINACK: length (2) + interface (1) + tds_version (4) + prog_name (b_varchar) + prog_version (4)
1829        if src.remaining() < 2 {
1830            return Err(ProtocolError::UnexpectedEof);
1831        }
1832
1833        let _length = src.get_u16_le();
1834
1835        if src.remaining() < 5 {
1836            return Err(ProtocolError::UnexpectedEof);
1837        }
1838
1839        let interface = src.get_u8();
1840        let tds_version = src.get_u32_le();
1841        let prog_name = read_b_varchar(src).ok_or(ProtocolError::UnexpectedEof)?;
1842
1843        if src.remaining() < 4 {
1844            return Err(ProtocolError::UnexpectedEof);
1845        }
1846        let prog_version = src.get_u32_le();
1847
1848        Ok(Self {
1849            interface,
1850            tds_version,
1851            prog_name,
1852            prog_version,
1853        })
1854    }
1855
1856    /// Get the TDS version as a `TdsVersion`.
1857    #[must_use]
1858    pub fn tds_version(&self) -> crate::version::TdsVersion {
1859        crate::version::TdsVersion::new(self.tds_version)
1860    }
1861}
1862
1863impl EnvChangeType {
1864    /// Create from raw byte value.
1865    pub fn from_u8(value: u8) -> Option<Self> {
1866        match value {
1867            1 => Some(Self::Database),
1868            2 => Some(Self::Language),
1869            3 => Some(Self::CharacterSet),
1870            4 => Some(Self::PacketSize),
1871            5 => Some(Self::UnicodeSortingLocalId),
1872            6 => Some(Self::UnicodeComparisonFlags),
1873            7 => Some(Self::SqlCollation),
1874            8 => Some(Self::BeginTransaction),
1875            9 => Some(Self::CommitTransaction),
1876            10 => Some(Self::RollbackTransaction),
1877            11 => Some(Self::EnlistDtcTransaction),
1878            12 => Some(Self::DefectTransaction),
1879            13 => Some(Self::RealTimeLogShipping),
1880            15 => Some(Self::PromoteTransaction),
1881            16 => Some(Self::TransactionManagerAddress),
1882            17 => Some(Self::TransactionEnded),
1883            18 => Some(Self::ResetConnectionCompletionAck),
1884            19 => Some(Self::UserInstanceStarted),
1885            20 => Some(Self::Routing),
1886            _ => None,
1887        }
1888    }
1889}
1890
1891impl EnvChange {
1892    /// Decode an ENVCHANGE token from bytes.
1893    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
1894        if src.remaining() < 3 {
1895            return Err(ProtocolError::UnexpectedEof);
1896        }
1897
1898        let length = src.get_u16_le() as usize;
1899        if length == 0 {
1900            // The frame must at least contain the type byte; reading it from
1901            // outside a zero-length frame would consume the next token.
1902            return Err(ProtocolError::UnexpectedEof);
1903        }
1904        if src.remaining() < length {
1905            return Err(ProtocolError::IncompletePacket {
1906                expected: length,
1907                actual: src.remaining(),
1908            });
1909        }
1910
1911        // Frame-strict decoding (issue #145): the value decoders below only
1912        // bounds-check against the *buffer*, so on an under-declared frame
1913        // they could read past the declared length into the next token's
1914        // bytes. Slice exactly the declared frame and decode from that:
1915        // over-read attempts now hit frame end and take the lenient
1916        // empty-value fallbacks (preserving the #140 hostile-input
1917        // behavior), and the outer buffer always advances by exactly
1918        // `length`.
1919        let mut frame = src.copy_to_bytes(length);
1920        let src = &mut frame;
1921
1922        let env_type_byte = src.get_u8();
1923        let env_type = EnvChangeType::from_u8(env_type_byte)
1924            .ok_or(ProtocolError::InvalidTokenType(env_type_byte))?;
1925
1926        let (new_value, old_value) = match env_type {
1927            EnvChangeType::Routing => {
1928                // Routing has special format
1929                let new_value = Self::decode_routing_value(src)?;
1930                let old_value = EnvChangeValue::Binary(Bytes::new());
1931                (new_value, old_value)
1932            }
1933            EnvChangeType::BeginTransaction
1934            | EnvChangeType::CommitTransaction
1935            | EnvChangeType::RollbackTransaction
1936            | EnvChangeType::EnlistDtcTransaction
1937            | EnvChangeType::SqlCollation => {
1938                // These use binary format per MS-TDS spec:
1939                // - Transaction tokens: transaction descriptor (8 bytes)
1940                // - SqlCollation: collation info (5 bytes: LCID + sort flags)
1941                // The declared ENVCHANGE `length` can be shorter than this
1942                // branch needs (e.g. covers only the type byte), so the
1943                // length-prefix reads must be bounds-checked individually:
1944                // `get_u8` on an empty buffer panics. Match the branch's
1945                // existing graceful style — a missing prefix means empty.
1946                let new_len = if src.has_remaining() {
1947                    src.get_u8() as usize
1948                } else {
1949                    0
1950                };
1951                let new_value = if new_len > 0 && src.remaining() >= new_len {
1952                    EnvChangeValue::Binary(src.copy_to_bytes(new_len))
1953                } else {
1954                    EnvChangeValue::Binary(Bytes::new())
1955                };
1956
1957                let old_len = if src.has_remaining() {
1958                    src.get_u8() as usize
1959                } else {
1960                    0
1961                };
1962                let old_value = if old_len > 0 && src.remaining() >= old_len {
1963                    EnvChangeValue::Binary(src.copy_to_bytes(old_len))
1964                } else {
1965                    EnvChangeValue::Binary(Bytes::new())
1966                };
1967
1968                (new_value, old_value)
1969            }
1970            _ => {
1971                // String format for most env changes
1972                let new_value = read_b_varchar(src)
1973                    .map(EnvChangeValue::String)
1974                    .unwrap_or(EnvChangeValue::String(String::new()));
1975
1976                let old_value = read_b_varchar(src)
1977                    .map(EnvChangeValue::String)
1978                    .unwrap_or(EnvChangeValue::String(String::new()));
1979
1980                (new_value, old_value)
1981            }
1982        };
1983
1984        // No frame-boundary fixup needed: the whole declared frame was
1985        // consumed from the outer buffer up front, so decoders that
1986        // under-consume (e.g. Routing's implicit zero-length OldValue) just
1987        // leave bytes behind in the dropped sub-frame.
1988
1989        Ok(Self {
1990            env_type,
1991            new_value,
1992            old_value,
1993        })
1994    }
1995
1996    fn decode_routing_value(src: &mut impl Buf) -> Result<EnvChangeValue, ProtocolError> {
1997        // Routing format: length (2) + protocol (1) + port (2) + server_len (2) + server (utf16)
1998        if src.remaining() < 2 {
1999            return Err(ProtocolError::UnexpectedEof);
2000        }
2001
2002        let _routing_len = src.get_u16_le();
2003
2004        if src.remaining() < 5 {
2005            return Err(ProtocolError::UnexpectedEof);
2006        }
2007
2008        let _protocol = src.get_u8();
2009        let port = src.get_u16_le();
2010        let server_len = src.get_u16_le() as usize;
2011
2012        // Read UTF-16LE server name
2013        if src.remaining() < server_len * 2 {
2014            return Err(ProtocolError::UnexpectedEof);
2015        }
2016
2017        let mut chars = Vec::with_capacity(server_len);
2018        for _ in 0..server_len {
2019            chars.push(src.get_u16_le());
2020        }
2021
2022        let host = String::from_utf16(&chars).map_err(|_| {
2023            ProtocolError::StringEncoding(
2024                #[cfg(feature = "std")]
2025                "invalid UTF-16 in routing hostname".to_string(),
2026                #[cfg(not(feature = "std"))]
2027                "invalid UTF-16 in routing hostname",
2028            )
2029        })?;
2030
2031        Ok(EnvChangeValue::Routing { host, port })
2032    }
2033
2034    /// Check if this is a routing redirect.
2035    #[must_use]
2036    pub fn is_routing(&self) -> bool {
2037        self.env_type == EnvChangeType::Routing
2038    }
2039
2040    /// Get routing information if this is a routing change.
2041    #[must_use]
2042    pub fn routing_info(&self) -> Option<(&str, u16)> {
2043        if let EnvChangeValue::Routing { host, port } = &self.new_value {
2044            Some((host, *port))
2045        } else {
2046            None
2047        }
2048    }
2049
2050    /// Get the new database name if this is a database change.
2051    #[must_use]
2052    pub fn new_database(&self) -> Option<&str> {
2053        if self.env_type == EnvChangeType::Database {
2054            if let EnvChangeValue::String(s) = &self.new_value {
2055                return Some(s);
2056            }
2057        }
2058        None
2059    }
2060}
2061
2062impl Order {
2063    /// Decode an ORDER token from bytes.
2064    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
2065        if src.remaining() < 2 {
2066            return Err(ProtocolError::UnexpectedEof);
2067        }
2068
2069        let length = src.get_u16_le() as usize;
2070        let column_count = length / 2;
2071
2072        if src.remaining() < length {
2073            return Err(ProtocolError::IncompletePacket {
2074                expected: length,
2075                actual: src.remaining(),
2076            });
2077        }
2078
2079        let mut columns = Vec::with_capacity(column_count);
2080        for _ in 0..column_count {
2081            columns.push(src.get_u16_le());
2082        }
2083
2084        Ok(Self { columns })
2085    }
2086}
2087
2088impl FeatureExtAck {
2089    /// Feature terminator byte.
2090    pub const TERMINATOR: u8 = 0xFF;
2091
2092    /// Decode a FEATUREEXTACK token from bytes.
2093    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
2094        let mut features = Vec::new();
2095
2096        loop {
2097            if !src.has_remaining() {
2098                return Err(ProtocolError::UnexpectedEof);
2099            }
2100
2101            let feature_id = src.get_u8();
2102            if feature_id == Self::TERMINATOR {
2103                break;
2104            }
2105
2106            if src.remaining() < 4 {
2107                return Err(ProtocolError::UnexpectedEof);
2108            }
2109
2110            let data_len = src.get_u32_le() as usize;
2111
2112            if src.remaining() < data_len {
2113                return Err(ProtocolError::IncompletePacket {
2114                    expected: data_len,
2115                    actual: src.remaining(),
2116                });
2117            }
2118
2119            let data = src.copy_to_bytes(data_len);
2120            features.push(FeatureAck { feature_id, data });
2121        }
2122
2123        Ok(Self { features })
2124    }
2125}
2126
2127impl SspiToken {
2128    /// Decode an SSPI token from bytes.
2129    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
2130        if src.remaining() < 2 {
2131            return Err(ProtocolError::UnexpectedEof);
2132        }
2133
2134        let length = src.get_u16_le() as usize;
2135
2136        if src.remaining() < length {
2137            return Err(ProtocolError::IncompletePacket {
2138                expected: length,
2139                actual: src.remaining(),
2140            });
2141        }
2142
2143        let data = src.copy_to_bytes(length);
2144        Ok(Self { data })
2145    }
2146}
2147
2148impl FedAuthInfo {
2149    /// `FedAuthInfoID` for the STS URL (MS-TDS §2.2.7.12: %0x01 = STSURL).
2150    const ID_STSURL: u8 = 0x01;
2151    /// `FedAuthInfoID` for the service principal name (MS-TDS §2.2.7.12:
2152    /// %0x02 = SPN).
2153    const ID_SPN: u8 = 0x02;
2154    /// Size of one `FedAuthInfoOpt` header: ID (1) + DataLen (4) + DataOffset (4).
2155    const OPT_HEADER_LEN: usize = 9;
2156
2157    /// Decode a FEDAUTHINFO token from bytes.
2158    ///
2159    /// Wire layout per MS-TDS §2.2.7.12 (after the 0xEE token byte):
2160    /// `TokenLength` (DWORD) covering everything that follows, then
2161    /// `CountOfInfoIDs` (DWORD), then `CountOfInfoIDs` option headers of
2162    /// ID (BYTE) + `FedAuthInfoDataLen` (DWORD) + `FedAuthInfoDataOffset`
2163    /// (DWORD), then the data block. Offsets are relative to the start of
2164    /// the `CountOfInfoIDs` field, and the option data is UTF-16LE.
2165    ///
2166    /// Exactly `TokenLength` bytes are consumed, so tokens that follow
2167    /// FEDAUTHINFO in the login stream (LOGINACK, DONE) are preserved.
2168    pub fn decode(src: &mut impl Buf) -> Result<Self, ProtocolError> {
2169        if src.remaining() < 4 {
2170            return Err(ProtocolError::UnexpectedEof);
2171        }
2172        let token_len = src.get_u32_le() as usize;
2173        if src.remaining() < token_len {
2174            return Err(ProtocolError::UnexpectedEof);
2175        }
2176
2177        // Offsets in the option headers are relative to the start of this
2178        // region (the CountOfInfoIDs field), so address into it directly.
2179        let region = src.copy_to_bytes(token_len);
2180        if region.len() < 4 {
2181            return Err(ProtocolError::UnexpectedEof);
2182        }
2183        let count = u32::from_le_bytes([region[0], region[1], region[2], region[3]]) as usize;
2184
2185        // All headers must fit between the count field and the end of the
2186        // token. The checked math also rejects hostile counts that would
2187        // overflow the offset arithmetic.
2188        let headers_end = count
2189            .checked_mul(Self::OPT_HEADER_LEN)
2190            .and_then(|n| n.checked_add(4))
2191            .ok_or(ProtocolError::UnexpectedEof)?;
2192        if headers_end > region.len() {
2193            return Err(ProtocolError::UnexpectedEof);
2194        }
2195
2196        let mut sts_url = String::new();
2197        let mut spn = String::new();
2198
2199        for i in 0..count {
2200            let h = 4 + i * Self::OPT_HEADER_LEN;
2201            let info_id = region[h];
2202            let data_len =
2203                u32::from_le_bytes([region[h + 1], region[h + 2], region[h + 3], region[h + 4]])
2204                    as usize;
2205            let data_off =
2206                u32::from_le_bytes([region[h + 5], region[h + 6], region[h + 7], region[h + 8]])
2207                    as usize;
2208
2209            // Unknown IDs are skipped without validating their data, per the
2210            // spec's instruction to ignore unrecognized options.
2211            if info_id != Self::ID_SPN && info_id != Self::ID_STSURL {
2212                continue;
2213            }
2214
2215            let data_end = data_off
2216                .checked_add(data_len)
2217                .ok_or(ProtocolError::UnexpectedEof)?;
2218            if data_end > region.len() {
2219                return Err(ProtocolError::UnexpectedEof);
2220            }
2221            if data_len % 2 != 0 {
2222                return Err(ProtocolError::StringEncoding(
2223                    #[cfg(feature = "std")]
2224                    "FEDAUTHINFO option data has odd length, not UTF-16".to_string(),
2225                    #[cfg(not(feature = "std"))]
2226                    "FEDAUTHINFO option data has odd length, not UTF-16",
2227                ));
2228            }
2229
2230            let chars: Vec<u16> = region[data_off..data_end]
2231                .chunks_exact(2)
2232                .map(|b| u16::from_le_bytes([b[0], b[1]]))
2233                .collect();
2234            let value = String::from_utf16(&chars).map_err(|_| {
2235                ProtocolError::StringEncoding(
2236                    #[cfg(feature = "std")]
2237                    "invalid UTF-16 in FEDAUTHINFO option".to_string(),
2238                    #[cfg(not(feature = "std"))]
2239                    "invalid UTF-16 in FEDAUTHINFO option",
2240                )
2241            })?;
2242
2243            if info_id == Self::ID_SPN {
2244                spn = value;
2245            } else {
2246                sts_url = value;
2247            }
2248        }
2249
2250        Ok(Self { sts_url, spn })
2251    }
2252}
2253
2254// =============================================================================
2255// Token Parser
2256// =============================================================================
2257
2258/// Token stream parser.
2259///
2260/// Parses a stream of TDS tokens from a byte buffer.
2261///
2262/// # Basic vs Context-Aware Parsing
2263///
2264/// Some tokens (like `Done`, `Error`, `LoginAck`) can be parsed without context.
2265/// Use [`next_token()`](TokenParser::next_token) for these.
2266///
2267/// Other tokens (like `ColMetaData`, `Row`, `NbcRow`) require column metadata
2268/// to parse correctly. Use [`next_token_with_metadata()`](TokenParser::next_token_with_metadata)
2269/// for these.
2270///
2271/// # Example
2272///
2273/// ```rust,ignore
2274/// let mut parser = TokenParser::new(data);
2275/// let mut metadata = None;
2276///
2277/// while let Some(token) = parser.next_token_with_metadata(metadata.as_ref())? {
2278///     match token {
2279///         Token::ColMetaData(meta) => {
2280///             metadata = Some(meta);
2281///         }
2282///         Token::Row(row) => {
2283///             // Process row using metadata
2284///         }
2285///         Token::Done(done) => {
2286///             if !done.has_more() {
2287///                 break;
2288///             }
2289///         }
2290///         _ => {}
2291///     }
2292/// }
2293/// ```
2294pub struct TokenParser {
2295    data: Bytes,
2296    position: usize,
2297    /// Whether Always Encrypted was negotiated for this connection.
2298    /// When true, ColMetaData tokens are parsed with CekTable and per-column CryptoMetadata.
2299    encryption_enabled: bool,
2300}
2301
2302impl TokenParser {
2303    /// Create a new token parser from bytes.
2304    #[must_use]
2305    pub fn new(data: Bytes) -> Self {
2306        Self {
2307            data,
2308            position: 0,
2309            encryption_enabled: false,
2310        }
2311    }
2312
2313    /// Enable Always Encrypted metadata parsing.
2314    ///
2315    /// When enabled, ColMetaData tokens are parsed using the encrypted format
2316    /// which includes a CekTable and per-column CryptoMetadata.
2317    #[must_use]
2318    pub fn with_encryption(mut self, enabled: bool) -> Self {
2319        self.encryption_enabled = enabled;
2320        self
2321    }
2322
2323    /// Get remaining bytes in the buffer.
2324    #[must_use]
2325    pub fn remaining(&self) -> usize {
2326        self.data.len().saturating_sub(self.position)
2327    }
2328
2329    /// Check if there are more bytes to parse.
2330    #[must_use]
2331    pub fn has_remaining(&self) -> bool {
2332        self.position < self.data.len()
2333    }
2334
2335    /// Peek at the next token type without consuming it.
2336    #[must_use]
2337    pub fn peek_token_type(&self) -> Option<TokenType> {
2338        if self.position < self.data.len() {
2339            TokenType::from_u8(self.data[self.position])
2340        } else {
2341            None
2342        }
2343    }
2344
2345    /// Parse the next token from the stream.
2346    ///
2347    /// This method can only parse context-independent tokens. For tokens that
2348    /// require column metadata (ColMetaData, Row, NbcRow), use
2349    /// [`next_token_with_metadata()`](TokenParser::next_token_with_metadata).
2350    ///
2351    /// Returns `None` if no more tokens are available.
2352    pub fn next_token(&mut self) -> Result<Option<Token>, ProtocolError> {
2353        self.next_token_with_metadata(None)
2354    }
2355
2356    /// Parse the next token with optional column metadata context.
2357    ///
2358    /// When `metadata` is provided, this method can parse Row and NbcRow tokens.
2359    /// Without metadata, those tokens will return an error.
2360    ///
2361    /// Returns `None` if no more tokens are available.
2362    pub fn next_token_with_metadata(
2363        &mut self,
2364        metadata: Option<&ColMetaData>,
2365    ) -> Result<Option<Token>, ProtocolError> {
2366        if !self.has_remaining() {
2367            return Ok(None);
2368        }
2369
2370        let mut buf = &self.data[self.position..];
2371        let start_pos = self.position;
2372
2373        let token_type_byte = buf.get_u8();
2374        let token_type = TokenType::from_u8(token_type_byte);
2375
2376        let token = match token_type {
2377            Some(TokenType::Done) => {
2378                let done = Done::decode(&mut buf)?;
2379                Token::Done(done)
2380            }
2381            Some(TokenType::DoneProc) => {
2382                let done = DoneProc::decode(&mut buf)?;
2383                Token::DoneProc(done)
2384            }
2385            Some(TokenType::DoneInProc) => {
2386                let done = DoneInProc::decode(&mut buf)?;
2387                Token::DoneInProc(done)
2388            }
2389            Some(TokenType::Error) => {
2390                let error = ServerError::decode(&mut buf)?;
2391                Token::Error(error)
2392            }
2393            Some(TokenType::Info) => {
2394                let info = ServerInfo::decode(&mut buf)?;
2395                Token::Info(info)
2396            }
2397            Some(TokenType::LoginAck) => {
2398                let login_ack = LoginAck::decode(&mut buf)?;
2399                Token::LoginAck(login_ack)
2400            }
2401            Some(TokenType::EnvChange) => {
2402                let env_change = EnvChange::decode(&mut buf)?;
2403                Token::EnvChange(env_change)
2404            }
2405            Some(TokenType::Order) => {
2406                let order = Order::decode(&mut buf)?;
2407                Token::Order(order)
2408            }
2409            Some(TokenType::FeatureExtAck) => {
2410                let ack = FeatureExtAck::decode(&mut buf)?;
2411                Token::FeatureExtAck(ack)
2412            }
2413            Some(TokenType::Sspi) => {
2414                let sspi = SspiToken::decode(&mut buf)?;
2415                Token::Sspi(sspi)
2416            }
2417            Some(TokenType::FedAuthInfo) => {
2418                let info = FedAuthInfo::decode(&mut buf)?;
2419                Token::FedAuthInfo(info)
2420            }
2421            Some(TokenType::ReturnStatus) => {
2422                if buf.remaining() < 4 {
2423                    return Err(ProtocolError::UnexpectedEof);
2424                }
2425                let status = buf.get_i32_le();
2426                Token::ReturnStatus(status)
2427            }
2428            Some(TokenType::ColMetaData) => {
2429                let col_meta = if self.encryption_enabled {
2430                    ColMetaData::decode_encrypted(&mut buf)?
2431                } else {
2432                    ColMetaData::decode(&mut buf)?
2433                };
2434                Token::ColMetaData(col_meta)
2435            }
2436            Some(TokenType::Row) => {
2437                let meta = metadata.ok_or_else(|| {
2438                    ProtocolError::StringEncoding(
2439                        #[cfg(feature = "std")]
2440                        "Row token requires column metadata".to_string(),
2441                        #[cfg(not(feature = "std"))]
2442                        "Row token requires column metadata",
2443                    )
2444                })?;
2445                let row = RawRow::decode(&mut buf, meta)?;
2446                Token::Row(row)
2447            }
2448            Some(TokenType::NbcRow) => {
2449                let meta = metadata.ok_or_else(|| {
2450                    ProtocolError::StringEncoding(
2451                        #[cfg(feature = "std")]
2452                        "NbcRow token requires column metadata".to_string(),
2453                        #[cfg(not(feature = "std"))]
2454                        "NbcRow token requires column metadata",
2455                    )
2456                })?;
2457                let row = NbcRow::decode(&mut buf, meta)?;
2458                Token::NbcRow(row)
2459            }
2460            Some(TokenType::ReturnValue) => {
2461                let ret_val = ReturnValue::decode(&mut buf)?;
2462                Token::ReturnValue(ret_val)
2463            }
2464            Some(TokenType::SessionState) => {
2465                let session = SessionState::decode(&mut buf)?;
2466                Token::SessionState(session)
2467            }
2468            Some(TokenType::ColInfo) | Some(TokenType::TabName) | Some(TokenType::Offset) => {
2469                // These tokens are rarely used and have complex formats.
2470                // Skip them by reading the length and advancing.
2471                if buf.remaining() < 2 {
2472                    return Err(ProtocolError::UnexpectedEof);
2473                }
2474                let length = buf.get_u16_le() as usize;
2475                if buf.remaining() < length {
2476                    return Err(ProtocolError::IncompletePacket {
2477                        expected: length,
2478                        actual: buf.remaining(),
2479                    });
2480                }
2481                // Skip the data
2482                buf.advance(length);
2483                // Recursively get the next token
2484                self.position = start_pos + (self.data.len() - start_pos - buf.remaining());
2485                return self.next_token_with_metadata(metadata);
2486            }
2487            None => {
2488                return Err(ProtocolError::InvalidTokenType(token_type_byte));
2489            }
2490        };
2491
2492        // Update position based on how much was consumed
2493        let consumed = self.data.len() - start_pos - buf.remaining();
2494        self.position = start_pos + consumed;
2495
2496        Ok(Some(token))
2497    }
2498
2499    /// Skip the current token without fully parsing it.
2500    ///
2501    /// This is useful for skipping unknown or uninteresting tokens.
2502    pub fn skip_token(&mut self) -> Result<(), ProtocolError> {
2503        if !self.has_remaining() {
2504            return Ok(());
2505        }
2506
2507        let token_type_byte = self.data[self.position];
2508        let token_type = TokenType::from_u8(token_type_byte);
2509
2510        // Calculate how many bytes to skip based on token type
2511        let skip_amount = match token_type {
2512            // Fixed-size tokens
2513            Some(TokenType::Done) | Some(TokenType::DoneProc) | Some(TokenType::DoneInProc) => {
2514                1 + Done::SIZE // token type + 12 bytes
2515            }
2516            Some(TokenType::ReturnStatus) => {
2517                1 + 4 // token type + 4 bytes
2518            }
2519            // Variable-length tokens with 2-byte length prefix
2520            Some(TokenType::Error)
2521            | Some(TokenType::Info)
2522            | Some(TokenType::LoginAck)
2523            | Some(TokenType::EnvChange)
2524            | Some(TokenType::Order)
2525            | Some(TokenType::Sspi)
2526            | Some(TokenType::ColInfo)
2527            | Some(TokenType::TabName)
2528            | Some(TokenType::Offset)
2529            | Some(TokenType::ReturnValue) => {
2530                if self.remaining() < 3 {
2531                    return Err(ProtocolError::UnexpectedEof);
2532                }
2533                let length = u16::from_le_bytes([
2534                    self.data[self.position + 1],
2535                    self.data[self.position + 2],
2536                ]) as usize;
2537                1 + 2 + length // token type + length prefix + data
2538            }
2539            // Tokens with 4-byte length prefix
2540            Some(TokenType::SessionState) | Some(TokenType::FedAuthInfo) => {
2541                if self.remaining() < 5 {
2542                    return Err(ProtocolError::UnexpectedEof);
2543                }
2544                let length = u32::from_le_bytes([
2545                    self.data[self.position + 1],
2546                    self.data[self.position + 2],
2547                    self.data[self.position + 3],
2548                    self.data[self.position + 4],
2549                ]) as usize;
2550                1 + 4 + length
2551            }
2552            // FeatureExtAck has no length prefix - must parse
2553            Some(TokenType::FeatureExtAck) => {
2554                // Parse to find end
2555                let mut buf = &self.data[self.position + 1..];
2556                let _ = FeatureExtAck::decode(&mut buf)?;
2557                self.data.len() - self.position - buf.remaining()
2558            }
2559            // ColMetaData, Row, NbcRow require context and can't be easily skipped
2560            Some(TokenType::ColMetaData) | Some(TokenType::Row) | Some(TokenType::NbcRow) => {
2561                return Err(ProtocolError::InvalidTokenType(token_type_byte));
2562            }
2563            None => {
2564                return Err(ProtocolError::InvalidTokenType(token_type_byte));
2565            }
2566        };
2567
2568        if self.remaining() < skip_amount {
2569            return Err(ProtocolError::UnexpectedEof);
2570        }
2571
2572        self.position += skip_amount;
2573        Ok(())
2574    }
2575
2576    /// Get the current position in the buffer.
2577    #[must_use]
2578    pub fn position(&self) -> usize {
2579        self.position
2580    }
2581
2582    /// Reset the parser to the beginning.
2583    pub fn reset(&mut self) {
2584        self.position = 0;
2585    }
2586}
2587
2588// =============================================================================
2589// Tests
2590// =============================================================================
2591
2592#[cfg(test)]
2593#[allow(clippy::unwrap_used, clippy::panic)]
2594mod tests {
2595    use super::*;
2596    use bytes::BytesMut;
2597
2598    #[test]
2599    fn test_done_roundtrip() {
2600        let done = Done {
2601            status: DoneStatus {
2602                more: false,
2603                error: false,
2604                in_xact: false,
2605                count: true,
2606                attn: false,
2607                srverror: false,
2608            },
2609            cur_cmd: 193, // SELECT
2610            row_count: 42,
2611        };
2612
2613        let mut buf = BytesMut::new();
2614        done.encode(&mut buf);
2615
2616        // Skip the token type byte
2617        let mut cursor = &buf[1..];
2618        let decoded = Done::decode(&mut cursor).unwrap();
2619
2620        assert_eq!(decoded.status.count, done.status.count);
2621        assert_eq!(decoded.cur_cmd, done.cur_cmd);
2622        assert_eq!(decoded.row_count, done.row_count);
2623    }
2624
2625    #[test]
2626    fn test_done_status_bits() {
2627        let status = DoneStatus {
2628            more: true,
2629            error: true,
2630            in_xact: true,
2631            count: true,
2632            attn: false,
2633            srverror: false,
2634        };
2635
2636        let bits = status.to_bits();
2637        let restored = DoneStatus::from_bits(bits);
2638
2639        assert_eq!(status.more, restored.more);
2640        assert_eq!(status.error, restored.error);
2641        assert_eq!(status.in_xact, restored.in_xact);
2642        assert_eq!(status.count, restored.count);
2643    }
2644
2645    #[test]
2646    fn test_token_parser_done() {
2647        // DONE token: type (1) + status (2) + curcmd (2) + rowcount (8)
2648        let data = Bytes::from_static(&[
2649            0xFD, // DONE token type
2650            0x10, 0x00, // status: DONE_COUNT
2651            0xC1, 0x00, // cur_cmd: 193 (SELECT)
2652            0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // row_count: 5
2653        ]);
2654
2655        let mut parser = TokenParser::new(data);
2656        let token = parser.next_token().unwrap().unwrap();
2657
2658        match token {
2659            Token::Done(done) => {
2660                assert!(done.status.count);
2661                assert!(!done.status.more);
2662                assert_eq!(done.cur_cmd, 193);
2663                assert_eq!(done.row_count, 5);
2664            }
2665            _ => panic!("Expected Done token"),
2666        }
2667
2668        // No more tokens
2669        assert!(parser.next_token().unwrap().is_none());
2670    }
2671
2672    #[test]
2673    fn test_env_change_type_from_u8() {
2674        assert_eq!(EnvChangeType::from_u8(1), Some(EnvChangeType::Database));
2675        assert_eq!(EnvChangeType::from_u8(20), Some(EnvChangeType::Routing));
2676        assert_eq!(EnvChangeType::from_u8(100), None);
2677    }
2678
2679    /// A spec-faithful Routing ENVCHANGE (MS-TDS 2.2.7.9) carries a
2680    /// zero-length OldValue (two bytes) after the routing data. The decoder
2681    /// reads only the NewValue, so it must skip to the declared frame
2682    /// boundary — otherwise the leftover `00 00` is misparsed as the next
2683    /// token type and the rest of the login response is garbage. Azure SQL
2684    /// Gateway redirects send exactly this shape.
2685    #[test]
2686    fn test_env_change_routing_consumes_declared_length() {
2687        let host = "redirect.example";
2688        let host_utf16: Vec<u16> = host.encode_utf16().collect();
2689
2690        let mut data = BytesMut::new();
2691        // RoutingDataValue: length + protocol + port + server_len + server
2692        let routing_len = 1 + 2 + 2 + host_utf16.len() * 2;
2693        // ENVCHANGE length: type byte + routing length prefix + routing data
2694        // + zero-length OldValue
2695        let env_len = 1 + 2 + routing_len + 2;
2696        data.put_u16_le(env_len as u16);
2697        data.put_u8(20); // Routing
2698        data.put_u16_le(routing_len as u16);
2699        data.put_u8(0); // protocol: TCP
2700        data.put_u16_le(11000); // port
2701        data.put_u16_le(host_utf16.len() as u16);
2702        for c in &host_utf16 {
2703            data.put_u16_le(*c);
2704        }
2705        data.put_u16_le(0); // OldValue: zero-length US_VARBYTE
2706        // A trailing DONE token type byte that must remain for the next read.
2707        data.put_u8(0xFD);
2708
2709        let mut buf: &[u8] = &data;
2710        let env = EnvChange::decode(&mut buf).unwrap();
2711        assert_eq!(env.routing_info(), Some((host, 11000)));
2712        assert_eq!(
2713            buf,
2714            &[0xFD],
2715            "decode must consume exactly the declared ENVCHANGE frame"
2716        );
2717    }
2718
2719    fn put_b_varchar(buf: &mut BytesMut, s: &str) {
2720        let utf16: Vec<u16> = s.encode_utf16().collect();
2721        buf.put_u8(utf16.len() as u8);
2722        for c in utf16 {
2723            buf.put_u16_le(c);
2724        }
2725    }
2726
2727    fn put_us_varchar(buf: &mut BytesMut, s: &str) {
2728        let utf16: Vec<u16> = s.encode_utf16().collect();
2729        buf.put_u16_le(utf16.len() as u16);
2730        for c in utf16 {
2731            buf.put_u16_le(c);
2732        }
2733    }
2734
2735    /// UDT_INFO regression (issue #154): per MS-TDS, DB_NAME, SCHEMA_NAME,
2736    /// and TYPE_NAME are B_VARCHAR (1-byte length); only
2737    /// ASSEMBLY_QUALIFIED_NAME is US_VARCHAR. Reading all four as US_VARCHAR
2738    /// misaligned the stream, so every query selecting a UDT column
2739    /// (geography, geometry, hierarchyid, CLR UDTs) failed with
2740    /// UnexpectedEof.
2741    #[test]
2742    fn test_udt_info_metadata_uses_b_varchar_names() {
2743        let mut data = BytesMut::new();
2744        data.put_u16_le(0xFFFF); // MAX_BYTE_SIZE
2745        put_b_varchar(&mut data, "master");
2746        put_b_varchar(&mut data, "dbo");
2747        put_b_varchar(&mut data, "hierarchyid");
2748        put_us_varchar(
2749            &mut data,
2750            "Microsoft.SqlServer.Types.SqlHierarchyId, Microsoft.SqlServer.Types",
2751        );
2752        // A trailing token type byte that must remain for the next read.
2753        data.put_u8(0xFD);
2754
2755        let mut buf: &[u8] = &data;
2756        let info = decode_type_info(&mut buf, TypeId::Udt, TypeId::Udt as u8).unwrap();
2757        assert_eq!(info.max_length, Some(0xFFFF));
2758        assert_eq!(
2759            buf,
2760            &[0xFD],
2761            "decode must consume exactly the UDT_INFO frame"
2762        );
2763    }
2764
2765    /// XML_INFO regression (issue #154): per MS-TDS §2.2.5.5.3, DBNAME and
2766    /// OWNING_SCHEMA are B_VARCHAR; only XML_SCHEMA_COLLECTION is US_VARCHAR.
2767    /// Schema-bound xml columns (SCHEMA_PRESENT=1) previously misparsed.
2768    #[test]
2769    fn test_xml_info_schema_bound_uses_b_varchar_names() {
2770        let mut data = BytesMut::new();
2771        data.put_u8(1); // SCHEMA_PRESENT
2772        put_b_varchar(&mut data, "master");
2773        put_b_varchar(&mut data, "dbo");
2774        put_us_varchar(&mut data, "MyXmlSchemaCollection");
2775        data.put_u8(0xFD);
2776
2777        let mut buf: &[u8] = &data;
2778        decode_type_info(&mut buf, TypeId::Xml, TypeId::Xml as u8).unwrap();
2779        assert_eq!(
2780            buf,
2781            &[0xFD],
2782            "decode must consume exactly the XML_INFO frame"
2783        );
2784    }
2785
2786    #[test]
2787    fn hostile_env_change_binary_truncated_is_not_panic() {
2788        // length=1 covers only the type byte (0x08 = BeginTransaction, a
2789        // binary-format type); the new_len/old_len prefix reads then hit an
2790        // empty buffer. Must decode gracefully, never panic (found by the
2791        // parse_env_change and parse_token fuzz targets).
2792        let data = [0x01, 0x00, 0x08];
2793        let mut buf: &[u8] = &data;
2794        let env = EnvChange::decode(&mut buf).unwrap();
2795        assert_eq!(env.env_type, EnvChangeType::BeginTransaction);
2796    }
2797
2798    /// Issue #145: an under-declared frame must not let the value decoders
2799    /// read past the declared length into the next token's bytes.
2800    #[test]
2801    fn hostile_env_change_under_declared_cannot_steal_following_bytes() {
2802        // length=1 covers only the type byte; the bytes after the frame are
2803        // shaped exactly like the transaction-descriptor payload the old
2804        // buffer-bounded decoder would have consumed (new_len=8, descriptor,
2805        // old_len=0).
2806        let mut data = BytesMut::new();
2807        data.put_u16_le(1); // declared frame: type byte only
2808        data.put_u8(0x08); // BeginTransaction
2809        let following: &[u8] = &[0x08, 1, 2, 3, 4, 5, 6, 7, 8, 0x00];
2810        data.extend_from_slice(following);
2811
2812        let mut buf: &[u8] = &data;
2813        let env = EnvChange::decode(&mut buf).unwrap();
2814        assert_eq!(env.env_type, EnvChangeType::BeginTransaction);
2815        match &env.new_value {
2816            EnvChangeValue::Binary(b) => {
2817                assert!(
2818                    b.is_empty(),
2819                    "under-declared frame yields the lenient empty value"
2820                );
2821            }
2822            other => panic!("expected empty Binary value, got {other:?}"),
2823        }
2824        assert_eq!(
2825            buf, following,
2826            "bytes beyond the declared frame belong to the next token"
2827        );
2828    }
2829
2830    /// Issue #145: a zero-length frame cannot supply a type byte; reading
2831    /// one from beyond the frame would consume the next token.
2832    #[test]
2833    fn hostile_env_change_zero_length_frame_errors() {
2834        let data = [0x00, 0x00, 0xFD];
2835        let mut buf: &[u8] = &data;
2836        assert!(EnvChange::decode(&mut buf).is_err());
2837    }
2838
2839    #[test]
2840    fn test_colmetadata_no_columns() {
2841        // No metadata marker (0xFFFF)
2842        let data = Bytes::from_static(&[0xFF, 0xFF]);
2843        let mut cursor: &[u8] = &data;
2844        let meta = ColMetaData::decode(&mut cursor).unwrap();
2845        assert!(meta.is_empty());
2846        assert_eq!(meta.column_count(), 0);
2847    }
2848
2849    #[test]
2850    fn test_colmetadata_single_int_column() {
2851        // COLMETADATA with 1 INT column
2852        // Format: column_count (2) + [user_type (4) + flags (2) + type_id (1) + name (b_varchar)]
2853        let mut data = BytesMut::new();
2854        data.extend_from_slice(&[0x01, 0x00]); // 1 column
2855        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // user_type = 0
2856        data.extend_from_slice(&[0x01, 0x00]); // flags (nullable)
2857        data.extend_from_slice(&[0x38]); // TypeId::Int4
2858        // Column name "id" as B_VARCHAR (1 byte length + UTF-16LE)
2859        data.extend_from_slice(&[0x02]); // 2 characters
2860        data.extend_from_slice(&[b'i', 0x00, b'd', 0x00]); // "id" in UTF-16LE
2861
2862        let mut cursor: &[u8] = &data;
2863        let meta = ColMetaData::decode(&mut cursor).unwrap();
2864
2865        assert_eq!(meta.column_count(), 1);
2866        assert_eq!(meta.columns[0].name, "id");
2867        assert_eq!(meta.columns[0].type_id, TypeId::Int4);
2868        assert!(meta.columns[0].is_nullable());
2869    }
2870
2871    #[test]
2872    fn test_colmetadata_nvarchar_column() {
2873        // COLMETADATA with 1 NVARCHAR(50) column
2874        let mut data = BytesMut::new();
2875        data.extend_from_slice(&[0x01, 0x00]); // 1 column
2876        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // user_type = 0
2877        data.extend_from_slice(&[0x01, 0x00]); // flags (nullable)
2878        data.extend_from_slice(&[0xE7]); // TypeId::NVarChar
2879        // Type info: max_length (2 bytes) + collation (5 bytes)
2880        data.extend_from_slice(&[0x64, 0x00]); // max_length = 100 (50 chars * 2)
2881        data.extend_from_slice(&[0x09, 0x04, 0xD0, 0x00, 0x34]); // collation
2882        // Column name "name"
2883        data.extend_from_slice(&[0x04]); // 4 characters
2884        data.extend_from_slice(&[b'n', 0x00, b'a', 0x00, b'm', 0x00, b'e', 0x00]);
2885
2886        let mut cursor: &[u8] = &data;
2887        let meta = ColMetaData::decode(&mut cursor).unwrap();
2888
2889        assert_eq!(meta.column_count(), 1);
2890        assert_eq!(meta.columns[0].name, "name");
2891        assert_eq!(meta.columns[0].type_id, TypeId::NVarChar);
2892        assert_eq!(meta.columns[0].type_info.max_length, Some(100));
2893        assert!(meta.columns[0].type_info.collation.is_some());
2894    }
2895
2896    #[test]
2897    fn test_raw_row_decode_int() {
2898        // Create metadata for a single INT column
2899        let metadata = ColMetaData {
2900            cek_table: None,
2901            columns: vec![ColumnData {
2902                name: "id".to_string(),
2903                type_id: TypeId::Int4,
2904                col_type: 0x38,
2905                flags: 0,
2906                user_type: 0,
2907                type_info: TypeInfo::default(),
2908                crypto_metadata: None,
2909            }],
2910        };
2911
2912        // Row data: just 4 bytes for the int value 42
2913        let data = Bytes::from_static(&[0x2A, 0x00, 0x00, 0x00]); // 42 in little-endian
2914        let mut cursor: &[u8] = &data;
2915        let row = RawRow::decode(&mut cursor, &metadata).unwrap();
2916
2917        // The raw data should contain the 4 bytes
2918        assert_eq!(row.data.len(), 4);
2919        assert_eq!(&row.data[..], &[0x2A, 0x00, 0x00, 0x00]);
2920    }
2921
2922    #[test]
2923    fn test_raw_row_decode_nullable_int() {
2924        // Create metadata for a nullable INT column (IntN)
2925        let metadata = ColMetaData {
2926            cek_table: None,
2927            columns: vec![ColumnData {
2928                name: "id".to_string(),
2929                type_id: TypeId::IntN,
2930                col_type: 0x26,
2931                flags: 0x01, // nullable
2932                user_type: 0,
2933                type_info: TypeInfo {
2934                    max_length: Some(4),
2935                    ..Default::default()
2936                },
2937                crypto_metadata: None,
2938            }],
2939        };
2940
2941        // Row data with value: 1 byte length + 4 bytes value
2942        let data = Bytes::from_static(&[0x04, 0x2A, 0x00, 0x00, 0x00]); // length=4, value=42
2943        let mut cursor: &[u8] = &data;
2944        let row = RawRow::decode(&mut cursor, &metadata).unwrap();
2945
2946        assert_eq!(row.data.len(), 5);
2947        assert_eq!(row.data[0], 4); // length
2948        assert_eq!(&row.data[1..], &[0x2A, 0x00, 0x00, 0x00]);
2949    }
2950
2951    #[test]
2952    fn test_raw_row_decode_null_value() {
2953        // Create metadata for a nullable INT column (IntN)
2954        let metadata = ColMetaData {
2955            cek_table: None,
2956            columns: vec![ColumnData {
2957                name: "id".to_string(),
2958                type_id: TypeId::IntN,
2959                col_type: 0x26,
2960                flags: 0x01, // nullable
2961                user_type: 0,
2962                type_info: TypeInfo {
2963                    max_length: Some(4),
2964                    ..Default::default()
2965                },
2966                crypto_metadata: None,
2967            }],
2968        };
2969
2970        // NULL value: length = 0xFF (for bytelen types)
2971        let data = Bytes::from_static(&[0xFF]);
2972        let mut cursor: &[u8] = &data;
2973        let row = RawRow::decode(&mut cursor, &metadata).unwrap();
2974
2975        assert_eq!(row.data.len(), 1);
2976        assert_eq!(row.data[0], 0xFF); // NULL marker
2977    }
2978
2979    #[test]
2980    fn test_nbcrow_null_bitmap() {
2981        let row = NbcRow {
2982            null_bitmap: vec![0b00000101], // columns 0 and 2 are NULL
2983            data: Bytes::new(),
2984        };
2985
2986        assert!(row.is_null(0));
2987        assert!(!row.is_null(1));
2988        assert!(row.is_null(2));
2989        assert!(!row.is_null(3));
2990    }
2991
2992    #[test]
2993    fn test_token_parser_colmetadata() {
2994        // Build a COLMETADATA token with 1 INT column
2995        let mut data = BytesMut::new();
2996        data.extend_from_slice(&[0x81]); // COLMETADATA token type
2997        data.extend_from_slice(&[0x01, 0x00]); // 1 column
2998        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // user_type = 0
2999        data.extend_from_slice(&[0x01, 0x00]); // flags (nullable)
3000        data.extend_from_slice(&[0x38]); // TypeId::Int4
3001        data.extend_from_slice(&[0x02]); // column name length
3002        data.extend_from_slice(&[b'i', 0x00, b'd', 0x00]); // "id"
3003
3004        let mut parser = TokenParser::new(data.freeze());
3005        let token = parser.next_token().unwrap().unwrap();
3006
3007        match token {
3008            Token::ColMetaData(meta) => {
3009                assert_eq!(meta.column_count(), 1);
3010                assert_eq!(meta.columns[0].name, "id");
3011                assert_eq!(meta.columns[0].type_id, TypeId::Int4);
3012            }
3013            _ => panic!("Expected ColMetaData token"),
3014        }
3015    }
3016
3017    #[test]
3018    fn test_token_parser_row_with_metadata() {
3019        // Build metadata
3020        let metadata = ColMetaData {
3021            cek_table: None,
3022            columns: vec![ColumnData {
3023                name: "id".to_string(),
3024                type_id: TypeId::Int4,
3025                col_type: 0x38,
3026                flags: 0,
3027                user_type: 0,
3028                type_info: TypeInfo::default(),
3029                crypto_metadata: None,
3030            }],
3031        };
3032
3033        // Build ROW token
3034        let mut data = BytesMut::new();
3035        data.extend_from_slice(&[0xD1]); // ROW token type
3036        data.extend_from_slice(&[0x2A, 0x00, 0x00, 0x00]); // value = 42
3037
3038        let mut parser = TokenParser::new(data.freeze());
3039        let token = parser
3040            .next_token_with_metadata(Some(&metadata))
3041            .unwrap()
3042            .unwrap();
3043
3044        match token {
3045            Token::Row(row) => {
3046                assert_eq!(row.data.len(), 4);
3047            }
3048            _ => panic!("Expected Row token"),
3049        }
3050    }
3051
3052    #[test]
3053    fn test_token_parser_row_without_metadata_fails() {
3054        // Build ROW token
3055        let mut data = BytesMut::new();
3056        data.extend_from_slice(&[0xD1]); // ROW token type
3057        data.extend_from_slice(&[0x2A, 0x00, 0x00, 0x00]); // value = 42
3058
3059        let mut parser = TokenParser::new(data.freeze());
3060        let result = parser.next_token(); // No metadata provided
3061
3062        assert!(result.is_err());
3063    }
3064
3065    #[test]
3066    fn test_token_parser_peek() {
3067        let data = Bytes::from_static(&[
3068            0xFD, // DONE token type
3069            0x10, 0x00, // status
3070            0xC1, 0x00, // cur_cmd
3071            0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // row_count
3072        ]);
3073
3074        let parser = TokenParser::new(data);
3075        assert_eq!(parser.peek_token_type(), Some(TokenType::Done));
3076    }
3077
3078    #[test]
3079    fn test_column_data_fixed_size() {
3080        let col = ColumnData {
3081            name: String::new(),
3082            type_id: TypeId::Int4,
3083            col_type: 0x38,
3084            flags: 0,
3085            user_type: 0,
3086            type_info: TypeInfo::default(),
3087            crypto_metadata: None,
3088        };
3089        assert_eq!(col.fixed_size(), Some(4));
3090
3091        let col2 = ColumnData {
3092            name: String::new(),
3093            type_id: TypeId::NVarChar,
3094            col_type: 0xE7,
3095            flags: 0,
3096            user_type: 0,
3097            type_info: TypeInfo::default(),
3098            crypto_metadata: None,
3099        };
3100        assert_eq!(col2.fixed_size(), None);
3101    }
3102
3103    // ========================================================================
3104    // End-to-End Decode Tests (Wire → Stored → Verification)
3105    // ========================================================================
3106    //
3107    // These tests verify that RawRow::decode_column_value correctly stores
3108    // column values in a format that can be parsed back.
3109
3110    #[test]
3111    fn test_decode_nvarchar_then_intn_roundtrip() {
3112        // Simulate wire data for: "World" (NVarChar), 42 (IntN)
3113        // This tests the scenario from the MCP parameterized query
3114
3115        // Build wire data (what the server sends)
3116        let mut wire_data = BytesMut::new();
3117
3118        // Column 0: NVarChar "World" - 2-byte length prefix in bytes
3119        // "World" in UTF-16LE: W=0x0057, o=0x006F, r=0x0072, l=0x006C, d=0x0064
3120        let word = "World";
3121        let utf16: Vec<u16> = word.encode_utf16().collect();
3122        wire_data.put_u16_le((utf16.len() * 2) as u16); // byte length = 10
3123        for code_unit in &utf16 {
3124            wire_data.put_u16_le(*code_unit);
3125        }
3126
3127        // Column 1: IntN 42 - 1-byte length prefix
3128        wire_data.put_u8(4); // 4 bytes for INT
3129        wire_data.put_i32_le(42);
3130
3131        // Build column metadata
3132        let metadata = ColMetaData {
3133            cek_table: None,
3134            columns: vec![
3135                ColumnData {
3136                    name: "greeting".to_string(),
3137                    type_id: TypeId::NVarChar,
3138                    col_type: 0xE7,
3139                    flags: 0x01,
3140                    user_type: 0,
3141                    type_info: TypeInfo {
3142                        max_length: Some(10), // non-MAX
3143                        precision: None,
3144                        scale: None,
3145                        collation: None,
3146                    },
3147                    crypto_metadata: None,
3148                },
3149                ColumnData {
3150                    name: "number".to_string(),
3151                    type_id: TypeId::IntN,
3152                    col_type: 0x26,
3153                    flags: 0x01,
3154                    user_type: 0,
3155                    type_info: TypeInfo {
3156                        max_length: Some(4),
3157                        precision: None,
3158                        scale: None,
3159                        collation: None,
3160                    },
3161                    crypto_metadata: None,
3162                },
3163            ],
3164        };
3165
3166        // Decode the wire data into stored format
3167        let mut wire_cursor = wire_data.freeze();
3168        let raw_row = RawRow::decode(&mut wire_cursor, &metadata).unwrap();
3169
3170        // Verify wire data was fully consumed
3171        assert_eq!(
3172            wire_cursor.remaining(),
3173            0,
3174            "wire data should be fully consumed"
3175        );
3176
3177        // Now parse the stored data
3178        let mut stored_cursor: &[u8] = &raw_row.data;
3179
3180        // Parse column 0 (NVarChar)
3181        // Stored format for non-MAX NVarChar: [2-byte len][data]
3182        assert!(
3183            stored_cursor.remaining() >= 2,
3184            "need at least 2 bytes for length"
3185        );
3186        let len0 = stored_cursor.get_u16_le() as usize;
3187        assert_eq!(len0, 10, "NVarChar length should be 10 bytes");
3188        assert!(
3189            stored_cursor.remaining() >= len0,
3190            "need {len0} bytes for data"
3191        );
3192
3193        // Read UTF-16LE and convert to string
3194        let mut utf16_read = Vec::new();
3195        for _ in 0..(len0 / 2) {
3196            utf16_read.push(stored_cursor.get_u16_le());
3197        }
3198        let string0 = String::from_utf16(&utf16_read).unwrap();
3199        assert_eq!(string0, "World", "column 0 should be 'World'");
3200
3201        // Parse column 1 (IntN)
3202        // Stored format for IntN: [1-byte len][data]
3203        assert!(
3204            stored_cursor.remaining() >= 1,
3205            "need at least 1 byte for length"
3206        );
3207        let len1 = stored_cursor.get_u8();
3208        assert_eq!(len1, 4, "IntN length should be 4");
3209        assert!(stored_cursor.remaining() >= 4, "need 4 bytes for INT data");
3210        let int1 = stored_cursor.get_i32_le();
3211        assert_eq!(int1, 42, "column 1 should be 42");
3212
3213        // Verify stored data was fully consumed
3214        assert_eq!(
3215            stored_cursor.remaining(),
3216            0,
3217            "stored data should be fully consumed"
3218        );
3219    }
3220
3221    #[test]
3222    fn test_decode_nvarchar_max_then_intn_roundtrip() {
3223        // Test NVARCHAR(MAX) followed by IntN - uses PLP encoding
3224
3225        // Build wire data for PLP NVARCHAR(MAX) + IntN
3226        let mut wire_data = BytesMut::new();
3227
3228        // Column 0: NVARCHAR(MAX) "Hello" - PLP format
3229        // PLP: 8-byte total length, then chunks
3230        let word = "Hello";
3231        let utf16: Vec<u16> = word.encode_utf16().collect();
3232        let byte_len = (utf16.len() * 2) as u64;
3233
3234        wire_data.put_u64_le(byte_len); // total length = 10
3235        wire_data.put_u32_le(byte_len as u32); // chunk length = 10
3236        for code_unit in &utf16 {
3237            wire_data.put_u16_le(*code_unit);
3238        }
3239        wire_data.put_u32_le(0); // terminating zero-length chunk
3240
3241        // Column 1: IntN 99
3242        wire_data.put_u8(4);
3243        wire_data.put_i32_le(99);
3244
3245        // Build metadata with MAX type
3246        let metadata = ColMetaData {
3247            cek_table: None,
3248            columns: vec![
3249                ColumnData {
3250                    name: "text".to_string(),
3251                    type_id: TypeId::NVarChar,
3252                    col_type: 0xE7,
3253                    flags: 0x01,
3254                    user_type: 0,
3255                    type_info: TypeInfo {
3256                        max_length: Some(0xFFFF), // MAX indicator
3257                        precision: None,
3258                        scale: None,
3259                        collation: None,
3260                    },
3261                    crypto_metadata: None,
3262                },
3263                ColumnData {
3264                    name: "num".to_string(),
3265                    type_id: TypeId::IntN,
3266                    col_type: 0x26,
3267                    flags: 0x01,
3268                    user_type: 0,
3269                    type_info: TypeInfo {
3270                        max_length: Some(4),
3271                        precision: None,
3272                        scale: None,
3273                        collation: None,
3274                    },
3275                    crypto_metadata: None,
3276                },
3277            ],
3278        };
3279
3280        // Decode wire data
3281        let mut wire_cursor = wire_data.freeze();
3282        let raw_row = RawRow::decode(&mut wire_cursor, &metadata).unwrap();
3283
3284        // Verify wire data was fully consumed
3285        assert_eq!(
3286            wire_cursor.remaining(),
3287            0,
3288            "wire data should be fully consumed"
3289        );
3290
3291        // Parse stored PLP data for column 0
3292        let mut stored_cursor: &[u8] = &raw_row.data;
3293
3294        // PLP stored format: [8-byte total][chunks...][4-byte 0]
3295        let total_len = stored_cursor.get_u64_le();
3296        assert_eq!(total_len, 10, "PLP total length should be 10");
3297
3298        let chunk_len = stored_cursor.get_u32_le();
3299        assert_eq!(chunk_len, 10, "PLP chunk length should be 10");
3300
3301        let mut utf16_read = Vec::new();
3302        for _ in 0..(chunk_len / 2) {
3303            utf16_read.push(stored_cursor.get_u16_le());
3304        }
3305        let string0 = String::from_utf16(&utf16_read).unwrap();
3306        assert_eq!(string0, "Hello", "column 0 should be 'Hello'");
3307
3308        let terminator = stored_cursor.get_u32_le();
3309        assert_eq!(terminator, 0, "PLP should end with 0");
3310
3311        // Parse IntN
3312        let len1 = stored_cursor.get_u8();
3313        assert_eq!(len1, 4);
3314        let int1 = stored_cursor.get_i32_le();
3315        assert_eq!(int1, 99, "column 1 should be 99");
3316
3317        // Verify fully consumed
3318        assert_eq!(
3319            stored_cursor.remaining(),
3320            0,
3321            "stored data should be fully consumed"
3322        );
3323    }
3324
3325    // ========================================================================
3326    // ReturnStatus Token Tests
3327    // ========================================================================
3328
3329    #[test]
3330    fn test_return_status_via_parser() {
3331        // RETURNSTATUS token: type (0x79) + value (i32 LE)
3332        let data = Bytes::from_static(&[
3333            0x79, // RETURNSTATUS token type
3334            0x00, 0x00, 0x00, 0x00, // return value = 0 (success)
3335        ]);
3336
3337        let mut parser = TokenParser::new(data);
3338        let token = parser.next_token().unwrap().unwrap();
3339
3340        match token {
3341            Token::ReturnStatus(status) => {
3342                assert_eq!(status, 0);
3343            }
3344            _ => panic!("Expected ReturnStatus token, got {token:?}"),
3345        }
3346
3347        assert!(parser.next_token().unwrap().is_none());
3348    }
3349
3350    #[test]
3351    fn test_return_status_nonzero() {
3352        // Return value = -6 (common for error returns)
3353        let mut buf = BytesMut::new();
3354        buf.put_u8(0x79); // RETURNSTATUS
3355        buf.put_i32_le(-6);
3356
3357        let mut parser = TokenParser::new(buf.freeze());
3358        let token = parser.next_token().unwrap().unwrap();
3359
3360        match token {
3361            Token::ReturnStatus(status) => {
3362                assert_eq!(status, -6);
3363            }
3364            _ => panic!("Expected ReturnStatus token"),
3365        }
3366    }
3367
3368    // ========================================================================
3369    // DoneProc Token Tests
3370    // ========================================================================
3371
3372    #[test]
3373    fn test_done_proc_roundtrip() {
3374        let done = DoneProc {
3375            status: DoneStatus {
3376                more: false,
3377                error: false,
3378                in_xact: false,
3379                count: true,
3380                attn: false,
3381                srverror: false,
3382            },
3383            cur_cmd: 0x00C6, // EXECUTE (198)
3384            row_count: 100,
3385        };
3386
3387        let mut buf = BytesMut::new();
3388        done.encode(&mut buf);
3389
3390        // Verify token type byte
3391        assert_eq!(buf[0], 0xFE);
3392
3393        // Skip token type byte and decode
3394        let mut cursor = &buf[1..];
3395        let decoded = DoneProc::decode(&mut cursor).unwrap();
3396
3397        assert!(decoded.status.count);
3398        assert!(!decoded.status.more);
3399        assert!(!decoded.status.error);
3400        assert_eq!(decoded.cur_cmd, 0x00C6);
3401        assert_eq!(decoded.row_count, 100);
3402    }
3403
3404    #[test]
3405    fn test_done_proc_via_parser() {
3406        let data = Bytes::from_static(&[
3407            0xFE, // DONEPROC token type
3408            0x00, 0x00, // status: no flags
3409            0xC6, 0x00, // cur_cmd: EXECUTE (198)
3410            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // row_count: 0
3411        ]);
3412
3413        let mut parser = TokenParser::new(data);
3414        let token = parser.next_token().unwrap().unwrap();
3415
3416        match token {
3417            Token::DoneProc(done) => {
3418                assert!(!done.status.count);
3419                assert!(!done.status.more);
3420                assert_eq!(done.cur_cmd, 198);
3421                assert_eq!(done.row_count, 0);
3422            }
3423            _ => panic!("Expected DoneProc token"),
3424        }
3425    }
3426
3427    #[test]
3428    fn test_done_proc_with_error_flag() {
3429        let mut buf = BytesMut::new();
3430        buf.put_u8(0xFE); // DONEPROC
3431        buf.put_u16_le(0x0002); // status: DONE_ERROR
3432        buf.put_u16_le(0x00C6); // cur_cmd: EXECUTE
3433        buf.put_u64_le(0); // row_count
3434
3435        let mut parser = TokenParser::new(buf.freeze());
3436        let token = parser.next_token().unwrap().unwrap();
3437
3438        match token {
3439            Token::DoneProc(done) => {
3440                assert!(done.status.error);
3441                assert!(!done.status.count);
3442                assert!(!done.status.more);
3443            }
3444            _ => panic!("Expected DoneProc token"),
3445        }
3446    }
3447
3448    // ========================================================================
3449    // DoneInProc Token Tests
3450    // ========================================================================
3451
3452    #[test]
3453    fn test_done_in_proc_roundtrip() {
3454        let done = DoneInProc {
3455            status: DoneStatus {
3456                more: true,
3457                error: false,
3458                in_xact: false,
3459                count: true,
3460                attn: false,
3461                srverror: false,
3462            },
3463            cur_cmd: 193, // SELECT
3464            row_count: 7,
3465        };
3466
3467        let mut buf = BytesMut::new();
3468        done.encode(&mut buf);
3469
3470        assert_eq!(buf[0], 0xFF);
3471
3472        let mut cursor = &buf[1..];
3473        let decoded = DoneInProc::decode(&mut cursor).unwrap();
3474
3475        assert!(decoded.status.more);
3476        assert!(decoded.status.count);
3477        assert!(!decoded.status.error);
3478        assert_eq!(decoded.cur_cmd, 193);
3479        assert_eq!(decoded.row_count, 7);
3480    }
3481
3482    #[test]
3483    fn test_done_in_proc_via_parser() {
3484        let data = Bytes::from_static(&[
3485            0xFF, // DONEINPROC token type
3486            0x11, 0x00, // status: MORE | COUNT
3487            0xC1, 0x00, // cur_cmd: SELECT (193)
3488            0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // row_count: 3
3489        ]);
3490
3491        let mut parser = TokenParser::new(data);
3492        let token = parser.next_token().unwrap().unwrap();
3493
3494        match token {
3495            Token::DoneInProc(done) => {
3496                assert!(done.status.more);
3497                assert!(done.status.count);
3498                assert_eq!(done.cur_cmd, 193);
3499                assert_eq!(done.row_count, 3);
3500            }
3501            _ => panic!("Expected DoneInProc token"),
3502        }
3503    }
3504
3505    // ========================================================================
3506    // ServerError Token Tests
3507    // ========================================================================
3508
3509    #[test]
3510    fn test_server_error_decode() {
3511        // Build a realistic ERROR token (without the 0xAA type byte,
3512        // since decode() is called after the parser strips it).
3513        let mut buf = BytesMut::new();
3514
3515        // Construct the message fields first to compute length
3516        let msg_utf16: Vec<u16> = "Invalid column name 'foo'.".encode_utf16().collect();
3517        let srv_utf16: Vec<u16> = "SQLDB01".encode_utf16().collect();
3518        let proc_utf16: Vec<u16> = "".encode_utf16().collect();
3519
3520        // Length = number(4) + state(1) + class(1)
3521        //        + us_varchar(message): 2 + msg_utf16.len()*2
3522        //        + b_varchar(server): 1 + srv_utf16.len()*2
3523        //        + b_varchar(procedure): 1 + proc_utf16.len()*2
3524        //        + line(4)
3525        let length: u16 = (4
3526            + 1
3527            + 1
3528            + 2
3529            + (msg_utf16.len() * 2)
3530            + 1
3531            + (srv_utf16.len() * 2)
3532            + 1
3533            + (proc_utf16.len() * 2)
3534            + 4) as u16;
3535
3536        buf.put_u16_le(length);
3537        buf.put_i32_le(207); // error number: Invalid column
3538        buf.put_u8(1); // state
3539        buf.put_u8(16); // class (severity 16)
3540
3541        // Message (US_VARCHAR: 2-byte char count + UTF-16LE)
3542        buf.put_u16_le(msg_utf16.len() as u16);
3543        for &c in &msg_utf16 {
3544            buf.put_u16_le(c);
3545        }
3546
3547        // Server (B_VARCHAR: 1-byte char count + UTF-16LE)
3548        buf.put_u8(srv_utf16.len() as u8);
3549        for &c in &srv_utf16 {
3550            buf.put_u16_le(c);
3551        }
3552
3553        // Procedure (B_VARCHAR: empty)
3554        buf.put_u8(proc_utf16.len() as u8);
3555
3556        // Line number
3557        buf.put_i32_le(42);
3558
3559        let mut cursor = buf.freeze();
3560        let error = ServerError::decode(&mut cursor).unwrap();
3561
3562        assert_eq!(error.number, 207);
3563        assert_eq!(error.state, 1);
3564        assert_eq!(error.class, 16);
3565        assert_eq!(error.message, "Invalid column name 'foo'.");
3566        assert_eq!(error.server, "SQLDB01");
3567        assert_eq!(error.procedure, "");
3568        assert_eq!(error.line, 42);
3569    }
3570
3571    #[test]
3572    fn test_server_error_severity_helpers() {
3573        let fatal = ServerError {
3574            number: 4014,
3575            state: 1,
3576            class: 20,
3577            message: "Fatal error".to_string(),
3578            server: String::new(),
3579            procedure: String::new(),
3580            line: 0,
3581        };
3582        assert!(fatal.is_fatal());
3583        assert!(fatal.is_batch_abort());
3584
3585        let batch_abort = ServerError {
3586            number: 547,
3587            state: 0,
3588            class: 16,
3589            message: "Constraint violation".to_string(),
3590            server: String::new(),
3591            procedure: String::new(),
3592            line: 1,
3593        };
3594        assert!(!batch_abort.is_fatal());
3595        assert!(batch_abort.is_batch_abort());
3596
3597        let informational = ServerError {
3598            number: 5701,
3599            state: 2,
3600            class: 10,
3601            message: "Changed db context".to_string(),
3602            server: String::new(),
3603            procedure: String::new(),
3604            line: 0,
3605        };
3606        assert!(!informational.is_fatal());
3607        assert!(!informational.is_batch_abort());
3608    }
3609
3610    #[test]
3611    fn test_server_error_via_parser() {
3612        // Build an ERROR token with the 0xAA type byte for the parser
3613        let mut buf = BytesMut::new();
3614        buf.put_u8(0xAA); // ERROR token type
3615
3616        let msg_utf16: Vec<u16> = "Syntax error".encode_utf16().collect();
3617        let srv_utf16: Vec<u16> = "SRV".encode_utf16().collect();
3618        let proc_utf16: Vec<u16> = "sp_test".encode_utf16().collect();
3619
3620        let length: u16 = (4
3621            + 1
3622            + 1
3623            + 2
3624            + (msg_utf16.len() * 2)
3625            + 1
3626            + (srv_utf16.len() * 2)
3627            + 1
3628            + (proc_utf16.len() * 2)
3629            + 4) as u16;
3630
3631        buf.put_u16_le(length);
3632        buf.put_i32_le(102); // Syntax error
3633        buf.put_u8(1);
3634        buf.put_u8(15);
3635
3636        buf.put_u16_le(msg_utf16.len() as u16);
3637        for &c in &msg_utf16 {
3638            buf.put_u16_le(c);
3639        }
3640        buf.put_u8(srv_utf16.len() as u8);
3641        for &c in &srv_utf16 {
3642            buf.put_u16_le(c);
3643        }
3644        buf.put_u8(proc_utf16.len() as u8);
3645        for &c in &proc_utf16 {
3646            buf.put_u16_le(c);
3647        }
3648        buf.put_i32_le(5);
3649
3650        let mut parser = TokenParser::new(buf.freeze());
3651        let token = parser.next_token().unwrap().unwrap();
3652
3653        match token {
3654            Token::Error(err) => {
3655                assert_eq!(err.number, 102);
3656                assert_eq!(err.class, 15);
3657                assert_eq!(err.message, "Syntax error");
3658                assert_eq!(err.server, "SRV");
3659                assert_eq!(err.procedure, "sp_test");
3660                assert_eq!(err.line, 5);
3661            }
3662            _ => panic!("Expected Error token"),
3663        }
3664    }
3665
3666    // ========================================================================
3667    // ReturnValue Token Tests
3668    // ========================================================================
3669
3670    /// Helper: build a ReturnValue token (without the 0xAC type byte)
3671    /// for an IntN output parameter.
3672    fn build_return_value_intn(
3673        ordinal: u16,
3674        name: &str,
3675        status: u8,
3676        value: Option<i32>,
3677    ) -> BytesMut {
3678        let mut inner = BytesMut::new();
3679
3680        // param_ordinal
3681        inner.put_u16_le(ordinal);
3682
3683        // param_name (B_VARCHAR)
3684        let name_utf16: Vec<u16> = name.encode_utf16().collect();
3685        inner.put_u8(name_utf16.len() as u8);
3686        for &c in &name_utf16 {
3687            inner.put_u16_le(c);
3688        }
3689
3690        // status
3691        inner.put_u8(status);
3692
3693        // user_type (4 bytes)
3694        inner.put_u32_le(0);
3695
3696        // flags (2 bytes)
3697        inner.put_u16_le(0x0001); // nullable
3698
3699        // type_id: IntN = 0x26
3700        inner.put_u8(0x26);
3701
3702        // type_info for IntN: 1-byte max_length
3703        inner.put_u8(4);
3704
3705        // value (TYPE_VARBYTE for IntN: 1-byte length + data)
3706        match value {
3707            Some(v) => {
3708                inner.put_u8(4); // length = 4
3709                inner.put_i32_le(v);
3710            }
3711            None => {
3712                inner.put_u8(0); // length = 0 means NULL
3713            }
3714        }
3715
3716        // RETURNVALUE has no outer length prefix (MS-TDS §2.2.7.18) — the
3717        // decoder walks the inner fields directly after the 0xAC token byte.
3718        inner
3719    }
3720
3721    #[test]
3722    fn test_return_value_int_output() {
3723        let buf = build_return_value_intn(1, "@result", 0x01, Some(42));
3724        let mut cursor = buf.freeze();
3725        let rv = ReturnValue::decode(&mut cursor).unwrap();
3726
3727        assert_eq!(rv.param_ordinal, 1);
3728        assert_eq!(rv.param_name, "@result");
3729        assert_eq!(rv.status, 0x01); // OUTPUT
3730        assert_eq!(rv.col_type, 0x26); // IntN
3731        assert_eq!(rv.type_info.max_length, Some(4));
3732        // Value should contain: length byte (4) + i32 LE (42)
3733        assert_eq!(rv.value.len(), 5);
3734        assert_eq!(rv.value[0], 4);
3735        assert_eq!(
3736            i32::from_le_bytes([rv.value[1], rv.value[2], rv.value[3], rv.value[4]]),
3737            42
3738        );
3739    }
3740
3741    #[test]
3742    fn test_return_value_null_output() {
3743        let buf = build_return_value_intn(2, "@count", 0x01, None);
3744        let mut cursor = buf.freeze();
3745        let rv = ReturnValue::decode(&mut cursor).unwrap();
3746
3747        assert_eq!(rv.param_ordinal, 2);
3748        assert_eq!(rv.param_name, "@count");
3749        assert_eq!(rv.status, 0x01);
3750        assert_eq!(rv.col_type, 0x26);
3751        // NULL value: length byte = 0
3752        assert_eq!(rv.value.len(), 1);
3753        assert_eq!(rv.value[0], 0);
3754    }
3755
3756    #[test]
3757    fn test_return_value_udf_status() {
3758        // UDF return value has status = 0x02
3759        let buf = build_return_value_intn(0, "@RETURN_VALUE", 0x02, Some(-1));
3760        let mut cursor = buf.freeze();
3761        let rv = ReturnValue::decode(&mut cursor).unwrap();
3762
3763        assert_eq!(rv.param_ordinal, 0);
3764        assert_eq!(rv.param_name, "@RETURN_VALUE");
3765        assert_eq!(rv.status, 0x02); // UDF return value
3766        assert_eq!(rv.value[0], 4);
3767        assert_eq!(
3768            i32::from_le_bytes([rv.value[1], rv.value[2], rv.value[3], rv.value[4]]),
3769            -1
3770        );
3771    }
3772
3773    #[test]
3774    fn test_return_value_nvarchar_output() {
3775        // Build a ReturnValue for NVARCHAR(100) output parameter
3776        let mut inner = BytesMut::new();
3777
3778        // param_ordinal
3779        inner.put_u16_le(1);
3780
3781        // param_name "@name"
3782        let name_utf16: Vec<u16> = "@name".encode_utf16().collect();
3783        inner.put_u8(name_utf16.len() as u8);
3784        for &c in &name_utf16 {
3785            inner.put_u16_le(c);
3786        }
3787
3788        // status = OUTPUT
3789        inner.put_u8(0x01);
3790        // user_type
3791        inner.put_u32_le(0);
3792        // flags (nullable)
3793        inner.put_u16_le(0x0001);
3794        // type_id: NVarChar = 0xE7
3795        inner.put_u8(0xE7);
3796        // type_info for NVarChar: 2-byte max_length + 5-byte collation
3797        inner.put_u16_le(200); // max 100 chars * 2 bytes
3798        inner.put_u32_le(0x0904D000); // collation LCID
3799        inner.put_u8(0x34); // collation sort_id
3800
3801        // value: "Hello" in UTF-16LE with 2-byte length prefix
3802        let val_utf16: Vec<u16> = "Hello".encode_utf16().collect();
3803        let byte_len = (val_utf16.len() * 2) as u16;
3804        inner.put_u16_le(byte_len);
3805        for &c in &val_utf16 {
3806            inner.put_u16_le(c);
3807        }
3808
3809        let mut cursor = inner.freeze();
3810        let rv = ReturnValue::decode(&mut cursor).unwrap();
3811
3812        assert_eq!(rv.param_ordinal, 1);
3813        assert_eq!(rv.param_name, "@name");
3814        assert_eq!(rv.status, 0x01);
3815        assert_eq!(rv.col_type, 0xE7); // NVarChar
3816        assert_eq!(rv.type_info.max_length, Some(200));
3817        assert!(rv.type_info.collation.is_some());
3818
3819        // Value: 2-byte length (10) + "Hello" in UTF-16LE
3820        assert_eq!(rv.value.len(), 12); // 2 + 10
3821        let val_len = u16::from_le_bytes([rv.value[0], rv.value[1]]);
3822        assert_eq!(val_len, 10);
3823    }
3824
3825    #[test]
3826    fn test_return_value_via_parser() {
3827        // Build a full ReturnValue token with the 0xAC type byte
3828        let mut data = BytesMut::new();
3829        data.put_u8(0xAC); // RETURNVALUE token type
3830        data.extend_from_slice(&build_return_value_intn(0, "@out", 0x01, Some(99)));
3831
3832        let mut parser = TokenParser::new(data.freeze());
3833        let token = parser.next_token().unwrap().unwrap();
3834
3835        match token {
3836            Token::ReturnValue(rv) => {
3837                assert_eq!(rv.param_name, "@out");
3838                assert_eq!(rv.param_ordinal, 0);
3839                assert_eq!(rv.status, 0x01);
3840                assert_eq!(rv.col_type, 0x26);
3841            }
3842            _ => panic!("Expected ReturnValue token"),
3843        }
3844    }
3845
3846    // ========================================================================
3847    // Multi-Token Stream Tests
3848    // ========================================================================
3849
3850    #[test]
3851    fn test_multi_token_stored_proc_response() {
3852        // Simulate a stored procedure response:
3853        // DoneInProc (result set done) → ReturnStatus → DoneProc
3854        let mut data = BytesMut::new();
3855
3856        // Token 1: DONEINPROC — result set with 3 rows
3857        data.put_u8(0xFF); // DONEINPROC
3858        data.put_u16_le(0x0010); // status: COUNT
3859        data.put_u16_le(0x00C1); // cur_cmd: SELECT
3860        data.put_u64_le(3); // row_count
3861
3862        // Token 2: RETURNSTATUS — procedure returned 0
3863        data.put_u8(0x79); // RETURNSTATUS
3864        data.put_i32_le(0);
3865
3866        // Token 3: DONEPROC — final
3867        data.put_u8(0xFE); // DONEPROC
3868        data.put_u16_le(0x0000); // status: no flags
3869        data.put_u16_le(0x00C6); // cur_cmd: EXECUTE
3870        data.put_u64_le(0);
3871
3872        let mut parser = TokenParser::new(data.freeze());
3873
3874        // Token 1: DoneInProc
3875        let t1 = parser.next_token().unwrap().unwrap();
3876        match t1 {
3877            Token::DoneInProc(done) => {
3878                assert!(done.status.count);
3879                assert_eq!(done.row_count, 3);
3880                assert_eq!(done.cur_cmd, 193);
3881            }
3882            _ => panic!("Expected DoneInProc, got {t1:?}"),
3883        }
3884
3885        // Token 2: ReturnStatus
3886        let t2 = parser.next_token().unwrap().unwrap();
3887        match t2 {
3888            Token::ReturnStatus(status) => {
3889                assert_eq!(status, 0);
3890            }
3891            _ => panic!("Expected ReturnStatus, got {t2:?}"),
3892        }
3893
3894        // Token 3: DoneProc
3895        let t3 = parser.next_token().unwrap().unwrap();
3896        match t3 {
3897            Token::DoneProc(done) => {
3898                assert!(!done.status.count);
3899                assert!(!done.status.more);
3900                assert_eq!(done.cur_cmd, 198);
3901            }
3902            _ => panic!("Expected DoneProc, got {t3:?}"),
3903        }
3904
3905        // No more tokens
3906        assert!(parser.next_token().unwrap().is_none());
3907    }
3908
3909    #[test]
3910    fn test_multi_token_error_in_stream() {
3911        // Simulate: ERROR → DONE (error during query)
3912        let mut data = BytesMut::new();
3913
3914        // Token 1: ERROR
3915        data.put_u8(0xAA);
3916
3917        let msg_utf16: Vec<u16> = "Deadlock".encode_utf16().collect();
3918        let srv_utf16: Vec<u16> = "DB1".encode_utf16().collect();
3919
3920        let length: u16 = (4 + 1 + 1
3921            + 2 + (msg_utf16.len() * 2)
3922            + 1 + (srv_utf16.len() * 2)
3923            + 1  // empty procedure
3924            + 4) as u16;
3925
3926        data.put_u16_le(length);
3927        data.put_i32_le(1205); // deadlock
3928        data.put_u8(51); // state
3929        data.put_u8(13); // class
3930
3931        data.put_u16_le(msg_utf16.len() as u16);
3932        for &c in &msg_utf16 {
3933            data.put_u16_le(c);
3934        }
3935        data.put_u8(srv_utf16.len() as u8);
3936        for &c in &srv_utf16 {
3937            data.put_u16_le(c);
3938        }
3939        data.put_u8(0); // empty procedure
3940        data.put_i32_le(0);
3941
3942        // Token 2: DONE with error flag
3943        data.put_u8(0xFD);
3944        data.put_u16_le(0x0002); // DONE_ERROR
3945        data.put_u16_le(0x00C1); // SELECT
3946        data.put_u64_le(0);
3947
3948        let mut parser = TokenParser::new(data.freeze());
3949
3950        // Token 1: Error
3951        let t1 = parser.next_token().unwrap().unwrap();
3952        match t1 {
3953            Token::Error(err) => {
3954                assert_eq!(err.number, 1205);
3955                assert_eq!(err.class, 13);
3956                assert_eq!(err.message, "Deadlock");
3957                assert_eq!(err.server, "DB1");
3958            }
3959            _ => panic!("Expected Error token, got {t1:?}"),
3960        }
3961
3962        // Token 2: Done with error
3963        let t2 = parser.next_token().unwrap().unwrap();
3964        match t2 {
3965            Token::Done(done) => {
3966                assert!(done.status.error);
3967                assert!(!done.status.count);
3968            }
3969            _ => panic!("Expected Done token, got {t2:?}"),
3970        }
3971
3972        assert!(parser.next_token().unwrap().is_none());
3973    }
3974
3975    #[test]
3976    fn test_multi_token_proc_with_return_value() {
3977        // Simulate stored proc: ReturnValue → ReturnStatus → DoneProc
3978        let mut data = BytesMut::new();
3979
3980        // Token 1: ReturnValue (@result = 42)
3981        data.put_u8(0xAC);
3982        data.extend_from_slice(&build_return_value_intn(1, "@result", 0x01, Some(42)));
3983
3984        // Token 2: ReturnStatus = 0
3985        data.put_u8(0x79);
3986        data.put_i32_le(0);
3987
3988        // Token 3: DoneProc
3989        data.put_u8(0xFE);
3990        data.put_u16_le(0x0000);
3991        data.put_u16_le(0x00C6);
3992        data.put_u64_le(0);
3993
3994        let mut parser = TokenParser::new(data.freeze());
3995
3996        let t1 = parser.next_token().unwrap().unwrap();
3997        match t1 {
3998            Token::ReturnValue(rv) => {
3999                assert_eq!(rv.param_name, "@result");
4000                assert_eq!(rv.param_ordinal, 1);
4001            }
4002            _ => panic!("Expected ReturnValue, got {t1:?}"),
4003        }
4004
4005        let t2 = parser.next_token().unwrap().unwrap();
4006        assert!(matches!(t2, Token::ReturnStatus(0)));
4007
4008        let t3 = parser.next_token().unwrap().unwrap();
4009        assert!(matches!(t3, Token::DoneProc(_)));
4010
4011        assert!(parser.next_token().unwrap().is_none());
4012    }
4013
4014    // ========================================================================
4015    // EOF / Truncation Edge Cases
4016    // ========================================================================
4017
4018    #[test]
4019    fn test_return_status_truncated() {
4020        // Only 3 bytes instead of 4 for i32
4021        let data = Bytes::from_static(&[0x79, 0x01, 0x02, 0x03]);
4022        let mut parser = TokenParser::new(data);
4023        assert!(parser.next_token().is_err());
4024    }
4025
4026    #[test]
4027    fn test_done_proc_truncated() {
4028        // Only 8 bytes instead of 12
4029        let data = Bytes::from_static(&[0xFE, 0x00, 0x00, 0xC1, 0x00, 0x01, 0x00, 0x00, 0x00]);
4030        let mut parser = TokenParser::new(data);
4031        assert!(parser.next_token().is_err());
4032    }
4033
4034    #[test]
4035    fn test_server_error_truncated() {
4036        // ERROR token with only the length field (body truncated)
4037        let data = Bytes::from_static(&[0xAA, 0x20, 0x00]);
4038        let mut parser = TokenParser::new(data);
4039        assert!(parser.next_token().is_err());
4040    }
4041
4042    // ========================================================================
4043    // FEDAUTHINFO (issue #189: parser must follow MS-TDS §2.2.7.12)
4044    // ========================================================================
4045
4046    /// Build a spec-exact FEDAUTHINFO token (including the 0xEE type byte):
4047    /// DWORD TokenLength, DWORD CountOfInfoIDs, option headers of
4048    /// ID/DataLen/DataOffset, then UTF-16LE data addressed by the offsets
4049    /// (relative to the start of the count field).
4050    fn build_fed_auth_info_token(options: &[(u8, &str)]) -> Vec<u8> {
4051        let headers_end = 4 + options.len() * 9;
4052        let mut data_block = Vec::new();
4053        let mut headers = Vec::new();
4054        for (id, value) in options {
4055            let encoded: Vec<u8> = value.encode_utf16().flat_map(u16::to_le_bytes).collect();
4056            let offset = headers_end + data_block.len();
4057            headers.push(*id);
4058            headers.extend_from_slice(&u32::try_from(encoded.len()).unwrap().to_le_bytes());
4059            headers.extend_from_slice(&u32::try_from(offset).unwrap().to_le_bytes());
4060            data_block.extend_from_slice(&encoded);
4061        }
4062
4063        let token_len = 4 + headers.len() + data_block.len();
4064        let mut out = vec![0xEE];
4065        out.extend_from_slice(&u32::try_from(token_len).unwrap().to_le_bytes());
4066        out.extend_from_slice(&u32::try_from(options.len()).unwrap().to_le_bytes());
4067        out.extend_from_slice(&headers);
4068        out.extend_from_slice(&data_block);
4069        out
4070    }
4071
4072    #[test]
4073    fn test_fed_auth_info_decodes_spec_layout() {
4074        const STS: &str = "https://login.microsoftonline.com/common";
4075        const SPN: &str = "https://database.windows.net/";
4076        // STSURL (0x01) listed first, SPN (0x02) second. Real Azure servers
4077        // list SPN first (see the captured-token test below); decoding must
4078        // not depend on option order.
4079        let token = build_fed_auth_info_token(&[(0x01, STS), (0x02, SPN)]);
4080
4081        let mut parser = TokenParser::new(Bytes::from(token));
4082        let parsed = parser.next_token().unwrap().unwrap();
4083        let Token::FedAuthInfo(info) = parsed else {
4084            panic!("expected FedAuthInfo, got {parsed:?}");
4085        };
4086        assert_eq!(info.sts_url, STS);
4087        assert_eq!(info.spn, SPN);
4088        assert!(parser.next_token().unwrap().is_none(), "exact consumption");
4089    }
4090
4091    #[test]
4092    fn test_fed_auth_info_preserves_following_tokens() {
4093        // The old parser looped over the whole remaining stream, swallowing
4094        // the tokens that follow FEDAUTHINFO during login. A DONE token
4095        // appended after it must survive.
4096        let mut stream = build_fed_auth_info_token(&[
4097            (0x01, "https://sts.example/"),
4098            (0x02, "https://db.example/"),
4099        ]);
4100        stream.push(0xFD); // DONE
4101        stream.extend_from_slice(&0u16.to_le_bytes()); // status
4102        stream.extend_from_slice(&0u16.to_le_bytes()); // curcmd
4103        stream.extend_from_slice(&0u64.to_le_bytes()); // rowcount
4104
4105        let mut parser = TokenParser::new(Bytes::from(stream));
4106        assert!(matches!(
4107            parser.next_token().unwrap(),
4108            Some(Token::FedAuthInfo(_))
4109        ));
4110        assert!(
4111            matches!(parser.next_token().unwrap(), Some(Token::Done(_))),
4112            "DONE after FEDAUTHINFO must not be swallowed"
4113        );
4114        assert!(parser.next_token().unwrap().is_none());
4115    }
4116
4117    #[test]
4118    fn test_fed_auth_info_unknown_ids_ignored() {
4119        // Spec: unrecognized FedAuthInfoIDs must be ignored.
4120        let token =
4121            build_fed_auth_info_token(&[(0x7F, "ignore-me"), (0x01, "https://sts.example/")]);
4122        let mut parser = TokenParser::new(Bytes::from(token));
4123        let Some(Token::FedAuthInfo(info)) = parser.next_token().unwrap() else {
4124            panic!("expected FedAuthInfo");
4125        };
4126        assert_eq!(info.sts_url, "https://sts.example/");
4127        assert_eq!(info.spn, "");
4128    }
4129
4130    #[test]
4131    fn test_fed_auth_info_hostile_inputs_error() {
4132        // TokenLength longer than the buffer.
4133        let mut truncated = build_fed_auth_info_token(&[(0x02, "https://sts.example/")]);
4134        truncated.truncate(truncated.len() - 4);
4135        assert!(
4136            TokenParser::new(Bytes::from(truncated))
4137                .next_token()
4138                .is_err()
4139        );
4140
4141        // CountOfInfoIDs claims more headers than the token holds
4142        // (also covers hostile counts whose header math would overflow).
4143        let mut bad_count = build_fed_auth_info_token(&[(0x02, "https://sts.example/")]);
4144        bad_count[5..9].copy_from_slice(&u32::MAX.to_le_bytes());
4145        assert!(
4146            TokenParser::new(Bytes::from(bad_count))
4147                .next_token()
4148                .is_err()
4149        );
4150
4151        // Data offset pointing past the end of the token.
4152        let mut bad_offset = build_fed_auth_info_token(&[(0x02, "https://sts.example/")]);
4153        bad_offset[14..18].copy_from_slice(&u32::MAX.to_le_bytes());
4154        assert!(
4155            TokenParser::new(Bytes::from(bad_offset))
4156                .next_token()
4157                .is_err()
4158        );
4159
4160        // Odd data length cannot be UTF-16.
4161        let mut odd_len = build_fed_auth_info_token(&[(0x02, "https://sts.example/")]);
4162        odd_len[10..14].copy_from_slice(&3u32.to_le_bytes());
4163        assert!(TokenParser::new(Bytes::from(odd_len)).next_token().is_err());
4164    }
4165
4166    #[test]
4167    fn test_fed_auth_info_parse_and_skip_agree() {
4168        // Issue #189: decode() and skip_token() must consume the same bytes
4169        // (the old decode ran past the token while skip honored the length).
4170        let token = build_fed_auth_info_token(&[(0x02, "https://sts.example/")]);
4171        let total = token.len();
4172
4173        let mut parser = TokenParser::new(Bytes::from(token.clone()));
4174        parser.next_token().unwrap();
4175        assert_eq!(parser.position(), total, "decode consumption");
4176
4177        let mut skipper = TokenParser::new(Bytes::from(token));
4178        skipper.skip_token().unwrap();
4179        assert_eq!(skipper.position(), total, "skip consumption");
4180    }
4181
4182    /// A FEDAUTHINFO token captured from a live Azure SQL Database login on
4183    /// 2026-06-12 (the client declared the ADAL library in LOGIN7; the server
4184    /// responded with this token). The tenant GUID inside the STS URL is
4185    /// replaced with an all-zero GUID of identical length, so every offset
4186    /// and length is byte-identical to the wire capture.
4187    ///
4188    /// This is the regression test deferred from PR #193, and it earns its
4189    /// keep: the real token proves FedAuthInfoID 0x01 = STSURL and
4190    /// 0x02 = SPN (Azure lists SPN first), which the synthetic tests
4191    /// originally had swapped.
4192    #[test]
4193    fn test_fed_auth_info_captured_from_azure() {
4194        const CAPTURED: &[u8] = &[
4195            0xEE, 0xCC, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x3A, 0x00, 0x00, 0x00,
4196            0x16, 0x00, 0x00, 0x00, 0x01, 0x7C, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x68,
4197            0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x73, 0x00, 0x3A, 0x00, 0x2F, 0x00, 0x2F,
4198            0x00, 0x64, 0x00, 0x61, 0x00, 0x74, 0x00, 0x61, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73,
4199            0x00, 0x65, 0x00, 0x2E, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x6F,
4200            0x00, 0x77, 0x00, 0x73, 0x00, 0x2E, 0x00, 0x6E, 0x00, 0x65, 0x00, 0x74, 0x00, 0x2F,
4201            0x00, 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x73, 0x00, 0x3A, 0x00, 0x2F,
4202            0x00, 0x2F, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x67, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x2E,
4203            0x00, 0x77, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73,
4204            0x00, 0x2E, 0x00, 0x6E, 0x00, 0x65, 0x00, 0x74, 0x00, 0x2F, 0x00, 0x30, 0x00, 0x30,
4205            0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x2D,
4206            0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x2D, 0x00, 0x30, 0x00, 0x30,
4207            0x00, 0x30, 0x00, 0x30, 0x00, 0x2D, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30,
4208            0x00, 0x2D, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30,
4209            0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
4210        ];
4211
4212        let mut parser = TokenParser::new(Bytes::from_static(CAPTURED));
4213        let Some(Token::FedAuthInfo(info)) = parser.next_token().unwrap() else {
4214            panic!("expected FedAuthInfo");
4215        };
4216        assert_eq!(
4217            info.sts_url,
4218            "https://login.windows.net/00000000-0000-0000-0000-000000000000"
4219        );
4220        assert_eq!(info.spn, "https://database.windows.net/");
4221        assert!(
4222            parser.next_token().unwrap().is_none(),
4223            "the captured token must be consumed exactly"
4224        );
4225    }
4226}