tds_protocol/
version.rs

1//! TDS protocol version definitions.
2
3use core::fmt;
4
5/// TDS protocol version.
6///
7/// Represents the version of the TDS protocol used for communication
8/// with SQL Server.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct TdsVersion(u32);
11
12impl TdsVersion {
13    /// TDS 7.0 (SQL Server 7.0)
14    pub const V7_0: Self = Self(0x70000000);
15
16    /// TDS 7.1 (SQL Server 2000)
17    pub const V7_1: Self = Self(0x71000000);
18
19    /// TDS 7.1 Revision 1 (SQL Server 2000 SP1)
20    pub const V7_1_REV1: Self = Self(0x71000001);
21
22    /// TDS 7.2 (SQL Server 2005)
23    pub const V7_2: Self = Self(0x72090002);
24
25    /// TDS 7.3A (SQL Server 2008)
26    pub const V7_3A: Self = Self(0x730A0003);
27
28    /// TDS 7.3B (SQL Server 2008 R2)
29    pub const V7_3B: Self = Self(0x730B0003);
30
31    /// TDS 7.4 (SQL Server 2012+)
32    pub const V7_4: Self = Self(0x74000004);
33
34    /// TDS 8.0 (SQL Server 2022+ strict encryption mode)
35    pub const V8_0: Self = Self(0x08000000);
36
37    /// Create a new TDS version from raw bytes.
38    #[must_use]
39    pub const fn new(version: u32) -> Self {
40        Self(version)
41    }
42
43    /// Get the raw version value.
44    #[must_use]
45    pub const fn raw(self) -> u32 {
46        self.0
47    }
48
49    /// Check if this version supports TDS 8.0 strict encryption.
50    #[must_use]
51    pub const fn is_tds_8(self) -> bool {
52        // TDS 8.0 uses a different version format
53        self.0 == Self::V8_0.0
54    }
55
56    /// Check if this version requires pre-login encryption negotiation.
57    ///
58    /// TDS 7.x versions negotiate encryption during pre-login.
59    /// TDS 8.0 requires TLS before any TDS traffic.
60    #[must_use]
61    pub const fn requires_prelogin_encryption_negotiation(self) -> bool {
62        !self.is_tds_8()
63    }
64
65    /// Get the major version number.
66    #[must_use]
67    pub const fn major(self) -> u8 {
68        if self.is_tds_8() {
69            8
70        } else {
71            ((self.0 >> 24) & 0xFF) as u8
72        }
73    }
74
75    /// Get the minor version number.
76    #[must_use]
77    pub const fn minor(self) -> u8 {
78        if self.is_tds_8() {
79            0
80        } else {
81            ((self.0 >> 16) & 0xFF) as u8
82        }
83    }
84}
85
86impl Default for TdsVersion {
87    fn default() -> Self {
88        Self::V7_4
89    }
90}
91
92impl fmt::Display for TdsVersion {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        if self.is_tds_8() {
95            write!(f, "TDS 8.0")
96        } else {
97            write!(f, "TDS {}.{}", self.major(), self.minor())
98        }
99    }
100}
101
102impl From<u32> for TdsVersion {
103    fn from(value: u32) -> Self {
104        Self(value)
105    }
106}
107
108impl From<TdsVersion> for u32 {
109    fn from(version: TdsVersion) -> Self {
110        version.0
111    }
112}
113
114#[cfg(test)]
115#[allow(clippy::unwrap_used)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_version_comparison() {
121        assert!(TdsVersion::V7_4 > TdsVersion::V7_3B);
122        assert!(TdsVersion::V7_3B > TdsVersion::V7_3A);
123    }
124
125    #[test]
126    fn test_tds_8_detection() {
127        assert!(TdsVersion::V8_0.is_tds_8());
128        assert!(!TdsVersion::V7_4.is_tds_8());
129    }
130
131    #[test]
132    fn test_prelogin_requirement() {
133        assert!(TdsVersion::V7_4.requires_prelogin_encryption_negotiation());
134        assert!(!TdsVersion::V8_0.requires_prelogin_encryption_negotiation());
135    }
136}