mssql-tds 0.1.0

Rust implementation of the TDS (Tabular Data Stream) protocol for SQL Server
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! # INFO Token Parser
//!
//! Parses INFO tokens (0xAB) which report informational messages from SQL Server.
//! These are similar to ERROR tokens but represent warnings or informational messages
//! rather than actual errors (typically severity < 10).
//!
//! ## Token Byte Layout
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────────────┐
//! │                       INFO Token (variable length)                   │
//! ├─────────┬──────────┬───────┬──────────┬──────────────┬──────────────┤
//! │ Length  │  Number  │ State │ Severity │   Message    │ Server Name  │
//! │(2 bytes)│ (4 bytes)│(1 byte│ (1 byte) │ (2+N chars)  │ (1+N chars)  │
//! │ UINT16  │  UINT32  │  BYTE │   BYTE   │US_VARCHAR(2) │B_VARCHAR(1)  │
//! └─────────┴──────────┴───────┴──────────┴──────────────┴──────────────┘
//!     0-1       2-5        6        7        8-9+N       10+N-11+M
//!
//! ┌──────────────┬──────────────┐
//! │   Proc Name  │ Line Number  │
//! │ (1+N chars)  │  (4 bytes)   │
//! │ B_VARCHAR(1) │   UINT32     │
//! └──────────────┴──────────────┘
//!   12+M-13+P      14+P-17+P
//!
//! Fields: (identical structure to ERROR token)
//!   Length    - Total token length excluding this field
//!   Number    - Message number (informational code)
//!   State     - State code
//!   Severity  - Message severity (typically 0-10 for INFO tokens)
//!                 0-9: Informational messages
//!                 10: Status messages
//!   Message   - Human-readable message (UTF-16 LE)
//!   ServerName- Name of SQL Server instance (UTF-16 LE)
//!   ProcName  - Stored procedure name if applicable (UTF-16 LE)
//!   LineNumber- Line number in batch/proc
//! ```
//!
//! ## Common Info Messages
//!
//! - **5701**: Changed database context to 'DatabaseName'
//! - **5703**: Changed language setting to 'LanguageName'
//! - **0**: Generic informational messages (PRINT statements)
//!
//! ## Difference from ERROR Token
//!
//! INFO tokens have the same structure as ERROR tokens but:
//! - Token type is 0xAB (INFO) instead of 0xAA (ERROR)
//! - Severity is typically < 11 (informational, not errors)
//! - Don't cause statement failure (execution continues)
//! - Often generated by PRINT/RAISERROR with severity <= 10
//!
//! ## Example
//!
//! ```text
//! // After executing "PRINT 'Hello World'"
//! // Server sends INFO token:
//! //   Number   = 0
//! //   State    = 1
//! //   Class    = 0 (informational)
//! //   Message  = "Hello World"
//! ```

use async_trait::async_trait;
use tracing::event;

use super::super::tokens::Tokens;
use super::common::TokenParser;
use crate::{core::TdsResult, io::packet_reader::TdsPacketReader};
use crate::{io::token_stream::ParserContext, token::tokens::InfoToken};

/// Parser for INFO token (0xAB) - reports informational messages
/// Parser for INFO token (0xAB) - reports informational messages
#[derive(Default)]
pub(crate) struct InfoTokenParser {
    // fields omitted
}

#[async_trait]
impl<T> TokenParser<T> for InfoTokenParser
where
    T: TdsPacketReader + Send + Sync,
{
    async fn parse(&self, reader: &mut T, _context: &ParserContext) -> TdsResult<Tokens> {
        // Read token length (2 bytes) - total length excluding this field
        let _length = reader.read_uint16().await?;

        // Read message number (4 bytes) - informational code
        let number = reader.read_uint32().await?;

        // Read state (1 byte) - internal state code
        let state = reader.read_byte().await?;

        // Read severity (1 byte) - typically 0-10 for INFO tokens
        let severity = reader.read_byte().await?;

        // Read message text (US_VARCHAR with 2-byte length prefix)
        // Message is in UTF-16 LE format
        let message = reader.read_varchar_u16_length().await?;

        // Read server name (B_VARCHAR with 1-byte length prefix)
        let server_name = reader.read_varchar_u8_length().await?;

        // Read procedure name (B_VARCHAR with 1-byte length prefix)
        let proc_name = reader.read_varchar_u8_length().await?;

        // Read line number (4 bytes) - line where message originated
        let line_number = reader.read_uint32().await?;

        event!(tracing::Level::INFO, "Info message: {:?}", message);

        Ok(Tokens::from(InfoToken {
            number,
            state,
            severity,
            message: message.unwrap_or_default(),
            server_name,
            proc_name,
            line_number,
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::message::messages::PacketType;
    use crate::test_packet_support::{TestPacketBuilder, create_network_transport_with_data};

    fn encode_utf16_string(s: &str) -> Vec<u8> {
        let utf16_units: Vec<u16> = s.encode_utf16().collect();
        let mut bytes = Vec::with_capacity(utf16_units.len() * 2);
        for unit in utf16_units {
            bytes.push((unit & 0xFF) as u8);
            bytes.push((unit >> 8) as u8);
        }
        bytes
    }

    #[tokio::test]
    async fn test_parse_info_token_basic() {
        // Test parsing basic INFO token
        let mut builder = TestPacketBuilder::new(PacketType::PreLogin);

        // Build the token manually
        let message = "Test info message";
        let server_name = "TestServer";
        let proc_name = "";

        let message_bytes = encode_utf16_string(message);
        let server_bytes = encode_utf16_string(server_name);
        let proc_bytes = encode_utf16_string(proc_name);

        // Calculate total length
        let length = 4 + 1 + 1 // number, state, severity
            + 2 + message_bytes.len() // message length + message
            + 1 + server_bytes.len() // server name length + server name
            + 1 + proc_bytes.len() // proc name length + proc name
            + 4; // line number

        builder.append_u16(length as u16); // token length
        builder.append_u32(5701); // number (database context change)
        builder.append_byte(1); // state
        builder.append_byte(0); // severity (informational)

        // Message with 2-byte length prefix (character count)
        builder.append_u16(message.len() as u16);
        builder.append_bytes(&message_bytes);

        // Server name with 1-byte length prefix (character count)
        builder.append_byte(server_name.len() as u8);
        builder.append_bytes(&server_bytes);

        // Proc name with 1-byte length prefix (character count)
        builder.append_byte(proc_name.len() as u8);
        builder.append_bytes(&proc_bytes);

        // Line number
        builder.append_u32(1);

        let mut packet_reader = create_network_transport_with_data(&builder.build());

        let parser = InfoTokenParser::default();
        let context = ParserContext::default();
        let result = parser.parse(&mut packet_reader, &context).await.unwrap();

        match result {
            Tokens::Info(token) => {
                assert_eq!(token.number, 5701);
                assert_eq!(token.state, 1);
                assert_eq!(token.severity, 0);
                assert_eq!(token.message, message);
                assert_eq!(token.server_name, server_name);
                assert_eq!(token.proc_name, proc_name);
                assert_eq!(token.line_number, 1);
            }
            _ => panic!("Expected Info token"),
        }
    }

    #[tokio::test]
    async fn test_parse_info_token_with_proc() {
        // Test parsing INFO token with procedure name
        let mut builder = TestPacketBuilder::new(PacketType::PreLogin);

        let message = "Info from proc";
        let server_name = "Server";
        let proc_name = "sp_test";

        let message_bytes = encode_utf16_string(message);
        let server_bytes = encode_utf16_string(server_name);
        let proc_bytes = encode_utf16_string(proc_name);

        let length =
            4 + 1 + 1 + 2 + message_bytes.len() + 1 + server_bytes.len() + 1 + proc_bytes.len() + 4;

        builder.append_u16(length as u16);
        builder.append_u32(0); // generic info
        builder.append_byte(1);
        builder.append_byte(10); // severity 10

        builder.append_u16(message.len() as u16);
        builder.append_bytes(&message_bytes);

        builder.append_byte(server_name.len() as u8);
        builder.append_bytes(&server_bytes);

        builder.append_byte(proc_name.len() as u8);
        builder.append_bytes(&proc_bytes);

        builder.append_u32(42); // line number

        let mut packet_reader = create_network_transport_with_data(&builder.build());

        let parser = InfoTokenParser::default();
        let context = ParserContext::default();
        let result = parser.parse(&mut packet_reader, &context).await.unwrap();

        match result {
            Tokens::Info(token) => {
                assert_eq!(token.number, 0);
                assert_eq!(token.state, 1);
                assert_eq!(token.severity, 10);
                assert_eq!(token.message, message);
                assert_eq!(token.server_name, server_name);
                assert_eq!(token.proc_name, proc_name);
                assert_eq!(token.line_number, 42);
            }
            _ => panic!("Expected Info token"),
        }
    }

    #[tokio::test]
    async fn test_parse_info_token_empty_message() {
        // Test parsing INFO token with empty message
        let mut builder = TestPacketBuilder::new(PacketType::PreLogin);

        let message = "";
        let server_name = "";
        let proc_name = "";

        let message_bytes = encode_utf16_string(message);
        let server_bytes = encode_utf16_string(server_name);
        let proc_bytes = encode_utf16_string(proc_name);

        let length =
            4 + 1 + 1 + 2 + message_bytes.len() + 1 + server_bytes.len() + 1 + proc_bytes.len() + 4;

        builder.append_u16(length as u16);
        builder.append_u32(0);
        builder.append_byte(0);
        builder.append_byte(0);

        builder.append_u16(0); // empty message
        builder.append_byte(0); // empty server name
        builder.append_byte(0); // empty proc name
        builder.append_u32(0);

        let mut packet_reader = create_network_transport_with_data(&builder.build());

        let parser = InfoTokenParser::default();
        let context = ParserContext::default();
        let result = parser.parse(&mut packet_reader, &context).await.unwrap();

        match result {
            Tokens::Info(token) => {
                assert_eq!(token.message, "");
                assert_eq!(token.server_name, "");
                assert_eq!(token.proc_name, "");
            }
            _ => panic!("Expected Info token"),
        }
    }
}