Skip to main content

oracledb_protocol/thin/
lob.rs

1#![forbid(unsafe_code)]
2
3use super::*;
4use crate::wire::ProtocolLimits;
5
6pub fn build_lob_read_payload_with_seq(
7    locator: &[u8],
8    offset: u64,
9    amount: u64,
10    seq_num: u8,
11    ttc_field_version: u8,
12) -> Result<Vec<u8>> {
13    let locator_len =
14        u32::try_from(locator.len()).map_err(|_| ProtocolError::InvalidPacketLength {
15            length: locator.len(),
16            minimum: 0,
17        })?;
18    let mut writer = TtcWriter::new();
19    writer.write_function_header(TNS_FUNC_LOB_OP, seq_num, ttc_field_version);
20    writer.write_u8(1);
21    writer.write_ub4(locator_len);
22    writer.write_u8(0);
23    writer.write_ub4(0);
24    writer.write_ub4(0);
25    writer.write_ub4(0);
26    writer.write_u8(0);
27    writer.write_u8(0);
28    writer.write_u8(0);
29    writer.write_ub4(TNS_LOB_OP_READ);
30    writer.write_u8(0);
31    writer.write_u8(0);
32    writer.write_ub8(offset);
33    writer.write_ub8(0);
34    writer.write_u8(1);
35    for _ in 0..3 {
36        writer.write_u16be(0);
37    }
38    writer.write_raw(locator);
39    writer.write_ub8(amount);
40    Ok(writer.into_bytes())
41}
42
43#[allow(clippy::too_many_arguments)]
44pub(crate) fn write_lob_op_header(
45    writer: &mut TtcWriter,
46    locator: &[u8],
47    seq_num: u8,
48    ttc_field_version: u8,
49    operation: u32,
50    dest_length: u32,
51    source_offset: u64,
52    dest_offset: u64,
53    pointer_charset: bool,
54    pointer_null_lob: bool,
55    send_amount: bool,
56) -> Result<()> {
57    let locator_len =
58        u32::try_from(locator.len()).map_err(|_| ProtocolError::InvalidPacketLength {
59            length: locator.len(),
60            minimum: 0,
61        })?;
62    writer.write_function_header(TNS_FUNC_LOB_OP, seq_num, ttc_field_version);
63    writer.write_u8(1);
64    writer.write_ub4(locator_len);
65    writer.write_u8(0);
66    writer.write_ub4(dest_length);
67    writer.write_ub4(0);
68    writer.write_ub4(0);
69    writer.write_u8(u8::from(pointer_charset));
70    writer.write_u8(0);
71    writer.write_u8(u8::from(pointer_null_lob));
72    writer.write_ub4(operation);
73    writer.write_u8(0);
74    writer.write_u8(0);
75    writer.write_ub8(source_offset);
76    writer.write_ub8(dest_offset);
77    writer.write_u8(u8::from(send_amount));
78    for _ in 0..3 {
79        writer.write_u16be(0);
80    }
81    writer.write_raw(locator);
82    Ok(())
83}
84
85pub fn build_lob_create_temp_payload_with_seq(
86    ora_type_num: u8,
87    csfrm: u8,
88    seq_num: u8,
89    ttc_field_version: u8,
90) -> Result<Vec<u8>> {
91    let mut writer = TtcWriter::new();
92    write_lob_op_header(
93        &mut writer,
94        &[0; 40],
95        seq_num,
96        ttc_field_version,
97        TNS_LOB_OP_CREATE_TEMP,
98        TNS_DURATION_SESSION,
99        u64::from(csfrm),
100        u64::from(ora_type_num),
101        true,
102        true,
103        false,
104    )?;
105    writer.write_ub4(TNS_CHARSET_UTF8.into());
106    Ok(writer.into_bytes())
107}
108
109pub fn build_lob_write_payload_with_seq(
110    locator: &[u8],
111    offset: u64,
112    data: &[u8],
113    seq_num: u8,
114    ttc_field_version: u8,
115) -> Result<Vec<u8>> {
116    let mut writer = TtcWriter::new();
117    write_lob_op_header(
118        &mut writer,
119        locator,
120        seq_num,
121        ttc_field_version,
122        TNS_LOB_OP_WRITE,
123        0,
124        offset,
125        0,
126        false,
127        false,
128        false,
129    )?;
130    writer.write_u8(TNS_MSG_TYPE_LOB_DATA);
131    writer.write_bytes_with_length(data)?;
132    Ok(writer.into_bytes())
133}
134
135pub fn build_lob_trim_payload_with_seq(
136    locator: &[u8],
137    new_size: u64,
138    seq_num: u8,
139    ttc_field_version: u8,
140) -> Result<Vec<u8>> {
141    let mut writer = TtcWriter::new();
142    write_lob_op_header(
143        &mut writer,
144        locator,
145        seq_num,
146        ttc_field_version,
147        TNS_LOB_OP_TRIM,
148        0,
149        0,
150        0,
151        false,
152        false,
153        true,
154    )?;
155    writer.write_ub8(new_size);
156    Ok(writer.into_bytes())
157}
158
159pub fn lob_locator_is_temporary(locator: &[u8]) -> bool {
160    locator
161        .get(TNS_LOB_LOC_OFFSET_FLAG_1)
162        .is_some_and(|flags| flags & TNS_LOB_LOC_FLAGS_ABSTRACT != 0)
163        || locator
164            .get(TNS_LOB_LOC_OFFSET_FLAG_4)
165            .is_some_and(|flags| flags & TNS_LOB_LOC_FLAGS_TEMP != 0)
166}
167
168pub fn build_lob_free_temp_payload_with_seq(
169    locators: &[Vec<u8>],
170    seq_num: u8,
171    ttc_field_version: u8,
172) -> Result<Vec<u8>> {
173    let total_size = locators.iter().try_fold(0u32, |total, locator| {
174        let locator_len =
175            u32::try_from(locator.len()).map_err(|_| ProtocolError::InvalidPacketLength {
176                length: locator.len(),
177                minimum: 0,
178            })?;
179        total
180            .checked_add(locator_len)
181            .ok_or(ProtocolError::PacketTooLarge { length: usize::MAX })
182    })?;
183    let mut writer = TtcWriter::new();
184    writer.write_function_header(TNS_FUNC_LOB_OP, seq_num, ttc_field_version);
185    writer.write_u8(1);
186    writer.write_ub4(total_size);
187    writer.write_u8(0);
188    writer.write_ub4(0);
189    writer.write_ub4(0);
190    writer.write_ub4(0);
191    writer.write_u8(0);
192    writer.write_u8(0);
193    writer.write_u8(0);
194    writer.write_ub4(TNS_LOB_OP_FREE_TEMP | TNS_LOB_OP_ARRAY);
195    writer.write_u8(0);
196    writer.write_ub4(0);
197    writer.write_ub8(0);
198    writer.write_ub8(0);
199    writer.write_u8(0);
200    writer.write_u8(0);
201    writer.write_ub4(0);
202    writer.write_u8(0);
203    writer.write_ub4(0);
204    writer.write_u8(0);
205    writer.write_ub4(0);
206    for locator in locators {
207        writer.write_raw(locator);
208    }
209    Ok(writer.into_bytes())
210}
211
212pub fn parse_lob_read_response(
213    payload: &[u8],
214    capabilities: ClientCapabilities,
215    locator: &[u8],
216) -> Result<LobReadResult> {
217    parse_lob_read_response_with_limits(payload, capabilities, locator, ProtocolLimits::DEFAULT)
218}
219
220pub fn parse_lob_read_response_with_limits(
221    payload: &[u8],
222    capabilities: ClientCapabilities,
223    locator: &[u8],
224    limits: ProtocolLimits,
225) -> Result<LobReadResult> {
226    parse_lob_op_response_with_limits(payload, capabilities, locator, false, true, limits)
227}
228
229pub fn parse_lob_create_temp_response(
230    payload: &[u8],
231    capabilities: ClientCapabilities,
232) -> Result<LobReadResult> {
233    parse_lob_create_temp_response_with_limits(payload, capabilities, ProtocolLimits::DEFAULT)
234}
235
236pub fn parse_lob_create_temp_response_with_limits(
237    payload: &[u8],
238    capabilities: ClientCapabilities,
239    limits: ProtocolLimits,
240) -> Result<LobReadResult> {
241    parse_lob_op_response_with_limits(payload, capabilities, &[0; 40], true, false, limits)
242}
243
244pub fn parse_lob_write_response(
245    payload: &[u8],
246    capabilities: ClientCapabilities,
247    locator: &[u8],
248) -> Result<LobReadResult> {
249    parse_lob_write_response_with_limits(payload, capabilities, locator, ProtocolLimits::DEFAULT)
250}
251
252pub fn parse_lob_write_response_with_limits(
253    payload: &[u8],
254    capabilities: ClientCapabilities,
255    locator: &[u8],
256    limits: ProtocolLimits,
257) -> Result<LobReadResult> {
258    parse_lob_op_response_with_limits(payload, capabilities, locator, false, false, limits)
259}
260
261pub fn parse_lob_trim_response(
262    payload: &[u8],
263    capabilities: ClientCapabilities,
264    locator: &[u8],
265) -> Result<LobReadResult> {
266    parse_lob_trim_response_with_limits(payload, capabilities, locator, ProtocolLimits::DEFAULT)
267}
268
269pub fn parse_lob_trim_response_with_limits(
270    payload: &[u8],
271    capabilities: ClientCapabilities,
272    locator: &[u8],
273    limits: ProtocolLimits,
274) -> Result<LobReadResult> {
275    parse_lob_op_response_with_limits(payload, capabilities, locator, false, true, limits)
276}
277
278pub fn parse_lob_free_temp_response(
279    payload: &[u8],
280    capabilities: ClientCapabilities,
281    returned_parameter_len: usize,
282) -> Result<()> {
283    parse_lob_free_temp_response_with_limits(
284        payload,
285        capabilities,
286        returned_parameter_len,
287        ProtocolLimits::DEFAULT,
288    )
289}
290
291pub fn parse_lob_free_temp_response_with_limits(
292    payload: &[u8],
293    capabilities: ClientCapabilities,
294    returned_parameter_len: usize,
295    limits: ProtocolLimits,
296) -> Result<()> {
297    limits.check_frame_bytes(returned_parameter_len)?;
298    let mut reader = TtcReader::with_limits(payload, limits)?;
299    while reader.remaining() > 0 {
300        let message_type = reader.read_u8()?;
301        match message_type {
302            0 => {}
303            TNS_MSG_TYPE_STATUS => {
304                let _call_status = reader.read_ub4()?;
305                let _seq = reader.read_ub2()?;
306            }
307            TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
308                let _ = skip_server_side_piggyback(&mut reader)?;
309            }
310            TNS_MSG_TYPE_END_OF_RESPONSE => break,
311            TNS_MSG_TYPE_ERROR => {
312                let info = parse_server_error_info(&mut reader, capabilities.ttc_field_version)?;
313                if info.number != 0 {
314                    return Err(ProtocolError::ServerError(info.message));
315                }
316            }
317            TNS_MSG_TYPE_PARAMETER => reader.skip(returned_parameter_len)?,
318            _ => {
319                return Err(ProtocolError::UnknownMessageType {
320                    message_type,
321                    position: reader.position().saturating_sub(1),
322                })
323            }
324        }
325    }
326    Ok(())
327}
328
329/// Scan a plain function response (ping/commit/rollback) for a server error.
330/// Unknown message types end the scan without error so payload shapes that
331/// were previously tolerated (responses used to go unparsed) keep working.
332/// Returns whether a server-side transaction is in progress, sampled from the
333/// final end-of-call status bit (reference protocol.pyx `_process_call_status`).
334pub fn parse_plain_function_response(
335    payload: &[u8],
336    capabilities: ClientCapabilities,
337) -> Result<bool> {
338    parse_plain_function_response_with_limits(payload, capabilities, ProtocolLimits::DEFAULT)
339}
340
341pub fn parse_plain_function_response_with_limits(
342    payload: &[u8],
343    capabilities: ClientCapabilities,
344    limits: ProtocolLimits,
345) -> Result<bool> {
346    let mut reader = TtcReader::with_limits(payload, limits)?;
347    let mut txn_in_progress = false;
348    while reader.remaining() > 0 {
349        let message_type = reader.read_u8()?;
350        match message_type {
351            0 => {}
352            TNS_MSG_TYPE_STATUS => {
353                let call_status = reader.read_ub4()?;
354                let _seq = reader.read_ub2()?;
355                txn_in_progress = call_status & TNS_EOCS_FLAGS_TXN_IN_PROGRESS != 0;
356            }
357            TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
358                let _ = skip_server_side_piggyback(&mut reader)?;
359            }
360            TNS_MSG_TYPE_END_OF_RESPONSE => break,
361            TNS_MSG_TYPE_ERROR => {
362                let info = parse_server_error_info(&mut reader, capabilities.ttc_field_version)?;
363                // The end-of-call ERROR (number 0 on success) carries the
364                // end-of-call status; sample the transaction-in-progress bit.
365                txn_in_progress = info.call_status & TNS_EOCS_FLAGS_TXN_IN_PROGRESS != 0;
366                if info.number != 0 {
367                    return Err(ProtocolError::ServerError(info.message));
368                }
369            }
370            _ => break,
371        }
372    }
373    Ok(txn_in_progress)
374}
375
376pub(crate) fn parse_lob_op_response_with_limits(
377    payload: &[u8],
378    capabilities: ClientCapabilities,
379    locator: &[u8],
380    is_create_temp: bool,
381    read_amount: bool,
382    limits: ProtocolLimits,
383) -> Result<LobReadResult> {
384    let mut reader = TtcReader::with_limits(payload, limits)?;
385    let mut result = LobReadResult {
386        locator: locator.to_vec(),
387        ..LobReadResult::default()
388    };
389    while reader.remaining() > 0 {
390        let message_type = reader.read_u8()?;
391        match message_type {
392            0 => {}
393            TNS_MSG_TYPE_LOB_DATA => {
394                result.data = reader.read_bytes()?;
395            }
396            TNS_MSG_TYPE_PARAMETER => {
397                if !result.locator.is_empty() {
398                    result.locator = reader.read_raw(result.locator.len())?.to_vec();
399                }
400                if is_create_temp {
401                    let _charset = reader.read_ub2()?;
402                    reader.skip(1)?;
403                } else if read_amount {
404                    let amount = reader.read_sb8()?;
405                    if amount > 0 {
406                        result.amount = amount as u64;
407                    }
408                }
409            }
410            TNS_MSG_TYPE_STATUS => {
411                let _call_status = reader.read_ub4()?;
412                let _seq = reader.read_ub2()?;
413            }
414            TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
415                let _ = skip_server_side_piggyback(&mut reader)?;
416            }
417            TNS_MSG_TYPE_END_OF_RESPONSE => break,
418            TNS_MSG_TYPE_ERROR => {
419                let info = parse_server_error_info(&mut reader, capabilities.ttc_field_version)?;
420                if info.number != 0 {
421                    return Err(ProtocolError::ServerError(info.message));
422                }
423            }
424            _ => {
425                return Err(ProtocolError::UnknownMessageType {
426                    message_type,
427                    position: reader.position().saturating_sub(1),
428                })
429            }
430        }
431    }
432    Ok(result)
433}