oracle_rs/
capabilities.rs

1//! Connection capabilities negotiation
2//!
3//! This module handles the compile-time (CCAP) and runtime (RCAP) capabilities
4//! that are negotiated between client and server during connection establishment.
5
6use crate::constants::{
7    accept_flags, ccap_index, ccap_value, charset, rcap_index, rcap_value, service_options,
8    version,
9};
10
11/// Driver name sent during protocol negotiation
12pub const DRIVER_NAME: &str = "oracle-rs : 0.1.0";
13
14/// Capabilities negotiated between client and server
15#[derive(Debug, Clone)]
16pub struct Capabilities {
17    /// Negotiated protocol version
18    pub protocol_version: u16,
19    /// Protocol options from server
20    pub protocol_options: u16,
21    /// Character set ID for database communication
22    pub charset_id: u16,
23    /// National character set ID
24    pub ncharset_id: u16,
25    /// Compile-time capabilities array (CCAP)
26    pub compile_caps: Vec<u8>,
27    /// Runtime capabilities array (RCAP)
28    pub runtime_caps: Vec<u8>,
29    /// TTC field version
30    pub ttc_field_version: u8,
31    /// Negotiated SDU size
32    pub sdu: u32,
33    /// Maximum string size (4000 or 32767)
34    pub max_string_size: u32,
35    /// Whether fast authentication is supported
36    pub supports_fast_auth: bool,
37    /// Whether OOB (out of band) is supported
38    pub supports_oob: bool,
39    /// Whether end-of-response markers are supported
40    pub supports_end_of_response: bool,
41    /// Whether pipelining is supported
42    pub supports_pipelining: bool,
43    /// Whether request boundaries are supported
44    pub supports_request_boundaries: bool,
45    /// Combo key derived during authentication (for encryption)
46    pub combo_key: Option<Vec<u8>>,
47}
48
49impl Default for Capabilities {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Capabilities {
56    /// Create new capabilities with default client values
57    pub fn new() -> Self {
58        let mut caps = Self {
59            protocol_version: 0,
60            protocol_options: 0,
61            charset_id: charset::UTF8,
62            ncharset_id: charset::UTF16,
63            compile_caps: vec![0; ccap_index::MAX],
64            runtime_caps: vec![0; rcap_index::MAX],
65            ttc_field_version: ccap_value::FIELD_VERSION_MAX,
66            sdu: 8192,
67            max_string_size: 4000,
68            supports_fast_auth: false,
69            supports_oob: false,
70            supports_end_of_response: false,
71            supports_pipelining: false,
72            supports_request_boundaries: false,
73            combo_key: None,
74        };
75
76        caps.init_compile_caps();
77        caps.init_runtime_caps();
78        caps
79    }
80
81    /// Initialize compile-time capabilities
82    fn init_compile_caps(&mut self) {
83        use ccap_index::*;
84        use ccap_value::*;
85
86        self.compile_caps[SQL_VERSION] = SQL_VERSION_MAX;
87
88        self.compile_caps[LOGON_TYPES] =
89            O5LOGON | O5LOGON_NP | O7LOGON | O8LOGON_LONG_IDENTIFIER | O9LOGON_LONG_PASSWORD;
90
91        self.compile_caps[FEATURE_BACKPORT] = CTB_IMPLICIT_POOL | CTB_OAUTH_MSG_ON_ERR;
92
93        self.compile_caps[FIELD_VERSION] = self.ttc_field_version;
94
95        self.compile_caps[SERVER_DEFINE_CONV] = 1;
96        self.compile_caps[DEQUEUE_WITH_SELECTOR] = 1;
97
98        self.compile_caps[TTC1] = FAST_BVEC | END_OF_CALL_STATUS | IND_RCD;
99
100        self.compile_caps[OCI1] = FAST_SESSION_PROPAGATE | APP_CTX_PIGGYBACK;
101
102        self.compile_caps[TDS_VERSION] = TDS_VERSION_MAX;
103        self.compile_caps[RPC_VERSION] = RPC_VERSION_MAX;
104        self.compile_caps[RPC_SIG] = RPC_SIG_VALUE;
105        self.compile_caps[DBF_VERSION] = DBF_VERSION_MAX;
106
107        self.compile_caps[LOB] = LOB_UB8_SIZE
108            | LOB_ENCS
109            | LOB_PREFETCH_LENGTH
110            | LOB_TEMP_SIZE
111            | LOB_12C
112            | LOB_PREFETCH_DATA;
113
114        self.compile_caps[UB2_DTY] = 1;
115
116        self.compile_caps[LOB2] = LOB2_QUASI | LOB2_2GB_PREFETCH;
117
118        self.compile_caps[TTC3] = IMPLICIT_RESULTS | BIG_CHUNK_CLR | KEEP_OUT_ORDER | LTXID;
119
120        self.compile_caps[TTC2] = ZLNP;
121
122        self.compile_caps[OCI2] = DRCP;
123
124        self.compile_caps[OCI3] = OCI3_OCSSYNC;
125
126        self.compile_caps[CLIENT_FN] = CLIENT_FN_MAX;
127
128        self.compile_caps[SESS_SIGNATURE_VERSION] = FIELD_VERSION_12_2;
129
130        self.compile_caps[TTC4] = INBAND_NOTIFICATION | EXPLICIT_BOUNDARY;
131
132        self.compile_caps[TTC5] = VECTOR_SUPPORT
133            | TOKEN_SUPPORTED
134            | PIPELINING_SUPPORT
135            | PIPELINING_BREAK
136            | SESSIONLESS_TXNS;
137
138        self.compile_caps[VECTOR_FEATURES] = VECTOR_FEATURE_BINARY | VECTOR_FEATURE_SPARSE;
139    }
140
141    /// Initialize runtime capabilities
142    fn init_runtime_caps(&mut self) {
143        use rcap_index::*;
144        use rcap_value::*;
145
146        self.runtime_caps[COMPAT] = COMPAT_81;
147        self.runtime_caps[TTC] = TTC_ZERO_COPY | TTC_32K;
148    }
149
150    /// Adjust capabilities based on ACCEPT packet response
151    pub fn adjust_for_protocol(
152        &mut self,
153        protocol_version: u16,
154        protocol_options: u16,
155        flags2: u32,
156    ) {
157        self.protocol_version = protocol_version;
158        self.protocol_options = protocol_options;
159
160        // Check OOB support
161        self.supports_oob = (protocol_options & service_options::CAN_RECV_ATTENTION) != 0;
162
163        // Check fast auth support
164        if (flags2 & accept_flags::FAST_AUTH) != 0 {
165            self.supports_fast_auth = true;
166        }
167
168        // Check end of response support
169        if protocol_version >= version::MIN_END_OF_RESPONSE
170            && (flags2 & accept_flags::HAS_END_OF_RESPONSE) != 0
171        {
172            self.compile_caps[ccap_index::TTC4] |= ccap_value::END_OF_REQUEST;
173            self.supports_end_of_response = true;
174            self.supports_pipelining = true;
175        }
176    }
177
178    /// Adjust capabilities based on server's compile-time capabilities
179    pub fn adjust_for_server_compile_caps(&mut self, server_caps: &[u8]) {
180        // Adjust field version to minimum of client and server
181        if server_caps.len() > ccap_index::FIELD_VERSION {
182            let server_version = server_caps[ccap_index::FIELD_VERSION];
183            if server_version < self.ttc_field_version {
184                self.ttc_field_version = server_version;
185                self.compile_caps[ccap_index::FIELD_VERSION] = server_version;
186            }
187        }
188
189        // Check for explicit boundary support
190        if server_caps.len() > ccap_index::TTC4 {
191            if (server_caps[ccap_index::TTC4] & ccap_value::EXPLICIT_BOUNDARY) != 0 {
192                self.supports_request_boundaries = true;
193            }
194        }
195
196        // Disable end of response if field version is too old
197        if self.ttc_field_version < ccap_value::FIELD_VERSION_23_4 && self.supports_end_of_response
198        {
199            self.compile_caps[ccap_index::TTC4] &= !ccap_value::END_OF_REQUEST;
200            self.supports_end_of_response = false;
201        }
202    }
203
204    /// Adjust capabilities based on server's runtime capabilities
205    pub fn adjust_for_server_runtime_caps(&mut self, server_caps: &[u8]) {
206        // Check max string size
207        if server_caps.len() > rcap_index::TTC {
208            if (server_caps[rcap_index::TTC] & rcap_value::TTC_32K) != 0 {
209                self.max_string_size = 32767;
210            } else {
211                self.max_string_size = 4000;
212            }
213
214            // Check session state ops support
215            if (server_caps[rcap_index::TTC] & rcap_value::TTC_SESSION_STATE_OPS) == 0 {
216                self.supports_request_boundaries = false;
217            }
218        }
219    }
220
221    /// Check if the national character set is supported
222    pub fn check_ncharset_id(&self) -> crate::error::Result<()> {
223        if self.ncharset_id != charset::UTF16 && self.ncharset_id != charset::AL16UTF8 {
224            return Err(crate::error::Error::FeatureNotSupported(format!(
225                "national character set {} is not supported (only UTF16 and AL16UTF8)",
226                self.ncharset_id
227            )));
228        }
229        Ok(())
230    }
231
232    /// Check if we support boolean type (Oracle 23.1+)
233    pub fn supports_bool(&self) -> bool {
234        self.ttc_field_version >= ccap_value::FIELD_VERSION_23_1
235    }
236
237    /// Check if we support large OSON field names (Oracle 23.1+)
238    pub fn supports_large_oson_fname(&self) -> bool {
239        self.ttc_field_version >= ccap_value::FIELD_VERSION_23_1
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_capabilities_default() {
249        let caps = Capabilities::new();
250
251        assert_eq!(caps.charset_id, charset::UTF8);
252        assert_eq!(caps.ncharset_id, charset::UTF16);
253        assert_eq!(caps.compile_caps.len(), ccap_index::MAX);
254        assert_eq!(caps.runtime_caps.len(), rcap_index::MAX);
255        assert_eq!(caps.ttc_field_version, ccap_value::FIELD_VERSION_MAX);
256        assert!(!caps.supports_fast_auth);
257        assert!(!caps.supports_oob);
258    }
259
260    #[test]
261    fn test_compile_caps_initialization() {
262        let caps = Capabilities::new();
263
264        assert_eq!(
265            caps.compile_caps[ccap_index::SQL_VERSION],
266            ccap_value::SQL_VERSION_MAX
267        );
268        assert_eq!(
269            caps.compile_caps[ccap_index::FIELD_VERSION],
270            ccap_value::FIELD_VERSION_MAX
271        );
272        assert_ne!(caps.compile_caps[ccap_index::LOGON_TYPES], 0);
273        assert_ne!(caps.compile_caps[ccap_index::TTC1], 0);
274    }
275
276    #[test]
277    fn test_runtime_caps_initialization() {
278        let caps = Capabilities::new();
279
280        assert_eq!(caps.runtime_caps[rcap_index::COMPAT], rcap_value::COMPAT_81);
281        assert_ne!(caps.runtime_caps[rcap_index::TTC], 0);
282    }
283
284    #[test]
285    fn test_adjust_for_protocol() {
286        let mut caps = Capabilities::new();
287
288        // Simulate ACCEPT response with fast auth and end of response
289        caps.adjust_for_protocol(
290            319,
291            service_options::CAN_RECV_ATTENTION,
292            accept_flags::FAST_AUTH | accept_flags::HAS_END_OF_RESPONSE,
293        );
294
295        assert_eq!(caps.protocol_version, 319);
296        assert!(caps.supports_fast_auth);
297        assert!(caps.supports_oob);
298        assert!(caps.supports_end_of_response);
299        assert!(caps.supports_pipelining);
300    }
301
302    #[test]
303    fn test_adjust_for_protocol_no_features() {
304        let mut caps = Capabilities::new();
305
306        caps.adjust_for_protocol(315, 0, 0);
307
308        assert_eq!(caps.protocol_version, 315);
309        assert!(!caps.supports_fast_auth);
310        assert!(!caps.supports_oob);
311        assert!(!caps.supports_end_of_response);
312    }
313
314    #[test]
315    fn test_adjust_for_server_compile_caps() {
316        let mut caps = Capabilities::new();
317
318        // Server has lower field version
319        let mut server_caps = vec![0; ccap_index::MAX];
320        server_caps[ccap_index::FIELD_VERSION] = ccap_value::FIELD_VERSION_12_2;
321
322        caps.adjust_for_server_compile_caps(&server_caps);
323
324        assert_eq!(caps.ttc_field_version, ccap_value::FIELD_VERSION_12_2);
325        assert_eq!(
326            caps.compile_caps[ccap_index::FIELD_VERSION],
327            ccap_value::FIELD_VERSION_12_2
328        );
329    }
330
331    #[test]
332    fn test_adjust_for_server_runtime_caps() {
333        let mut caps = Capabilities::new();
334
335        // Server supports 32K strings
336        let mut server_caps = vec![0; rcap_index::MAX];
337        server_caps[rcap_index::TTC] = rcap_value::TTC_32K;
338
339        caps.adjust_for_server_runtime_caps(&server_caps);
340
341        assert_eq!(caps.max_string_size, 32767);
342    }
343
344    #[test]
345    fn test_adjust_for_server_runtime_caps_no_32k() {
346        let mut caps = Capabilities::new();
347
348        // Server doesn't support 32K strings
349        let server_caps = vec![0; rcap_index::MAX];
350
351        caps.adjust_for_server_runtime_caps(&server_caps);
352
353        assert_eq!(caps.max_string_size, 4000);
354    }
355
356    #[test]
357    fn test_check_ncharset_id_valid() {
358        let mut caps = Capabilities::new();
359
360        caps.ncharset_id = charset::UTF16;
361        assert!(caps.check_ncharset_id().is_ok());
362
363        caps.ncharset_id = charset::AL16UTF8;
364        assert!(caps.check_ncharset_id().is_ok());
365    }
366
367    #[test]
368    fn test_check_ncharset_id_invalid() {
369        let mut caps = Capabilities::new();
370
371        caps.ncharset_id = 999;
372        assert!(caps.check_ncharset_id().is_err());
373    }
374
375    #[test]
376    fn test_supports_bool() {
377        let mut caps = Capabilities::new();
378
379        caps.ttc_field_version = ccap_value::FIELD_VERSION_23_1;
380        assert!(caps.supports_bool());
381
382        caps.ttc_field_version = ccap_value::FIELD_VERSION_12_2;
383        assert!(!caps.supports_bool());
384    }
385}