1#![forbid(unsafe_code)]
2
3use super::*;
4
5pub(crate) fn encode_oracle_date(
6 year: i32,
7 month: u8,
8 day: u8,
9 hour: u8,
10 minute: u8,
11 second: u8,
12) -> Result<[u8; ORA_TYPE_SIZE_DATE as usize]> {
13 if !(1..=9999).contains(&year)
14 || !(1..=12).contains(&month)
15 || !(1..=31).contains(&day)
16 || hour > 23
17 || minute > 59
18 || second > 59
19 {
20 return Err(ProtocolError::TtcDecode("invalid DATE bind"));
21 }
22 let century = year / 100 + 100;
23 let year_in_century = year % 100 + 100;
24 Ok([
25 u8::try_from(century).map_err(|_| ProtocolError::TtcDecode("invalid DATE century"))?,
26 u8::try_from(year_in_century).map_err(|_| ProtocolError::TtcDecode("invalid DATE year"))?,
27 month,
28 day,
29 hour + 1,
30 minute + 1,
31 second + 1,
32 ])
33}
34
35pub(crate) fn encode_oracle_timestamp(
36 year: i32,
37 month: u8,
38 day: u8,
39 hour: u8,
40 minute: u8,
41 second: u8,
42 nanosecond: u32,
43) -> Result<Vec<u8>> {
44 if nanosecond > 999_999_999 {
45 return Err(ProtocolError::TtcDecode("invalid TIMESTAMP fraction"));
46 }
47 let date = encode_oracle_date(year, month, day, hour, minute, second)?;
48 if nanosecond == 0 {
49 return Ok(date.to_vec());
50 }
51 let mut bytes = Vec::with_capacity(ORA_TYPE_SIZE_TIMESTAMP as usize);
52 bytes.extend_from_slice(&date);
53 bytes.extend_from_slice(&nanosecond.to_be_bytes());
54 Ok(bytes)
55}
56
57pub(crate) fn encode_oracle_timestamp_tz(
58 year: i32,
59 month: u8,
60 day: u8,
61 hour: u8,
62 minute: u8,
63 second: u8,
64 nanosecond: u32,
65) -> Result<Vec<u8>> {
66 encode_oracle_timestamp_tz_with_offset(year, month, day, hour, minute, second, nanosecond, 0)
67}
68
69#[allow(clippy::too_many_arguments)]
70pub(crate) fn encode_oracle_timestamp_tz_with_offset(
71 year: i32,
72 month: u8,
73 day: u8,
74 hour: u8,
75 minute: u8,
76 second: u8,
77 nanosecond: u32,
78 offset_minutes: i32,
79) -> Result<Vec<u8>> {
80 if nanosecond > 999_999_999 {
81 return Err(ProtocolError::TtcDecode(
82 "invalid TIMESTAMP WITH TIME ZONE fraction",
83 ));
84 }
85 if !valid_tz_offset_minutes(offset_minutes) {
86 return Err(ProtocolError::TtcDecode(
87 "invalid TIMESTAMP WITH TIME ZONE offset",
88 ));
89 }
90 let offset_hours = offset_minutes / 60;
91 let offset_minute_part = offset_minutes % 60;
92 let encoded_hour = offset_hours + i32::from(TZ_HOUR_OFFSET);
93 let encoded_minute = offset_minute_part + i32::from(TZ_MINUTE_OFFSET);
94 let encoded_hour = u8::try_from(encoded_hour)
95 .map_err(|_| ProtocolError::TtcDecode("invalid TIMESTAMP WITH TIME ZONE offset hour"))?;
96 let encoded_minute = u8::try_from(encoded_minute)
97 .map_err(|_| ProtocolError::TtcDecode("invalid TIMESTAMP WITH TIME ZONE offset minute"))?;
98 let mut bytes = Vec::with_capacity(ORA_TYPE_SIZE_TIMESTAMP_TZ as usize);
99 let date = encode_oracle_date(year, month, day, hour, minute, second)?;
100 bytes.extend_from_slice(&date);
101 bytes.extend_from_slice(&nanosecond.to_be_bytes());
102 bytes.push(encoded_hour);
103 bytes.push(encoded_minute);
104 Ok(bytes)
105}
106
107pub fn decode_datetime_value(bytes: &[u8]) -> Result<QueryValue> {
108 if !matches!(
109 bytes.len(),
110 len if len == ORA_TYPE_SIZE_DATE as usize
111 || len == ORA_TYPE_SIZE_TIMESTAMP as usize
112 || len == ORA_TYPE_SIZE_TIMESTAMP_TZ as usize
113 ) {
114 return Err(ProtocolError::TtcDecode("invalid DATE/TIMESTAMP length"));
115 }
116 let year = (i32::from(bytes[0]) - 100) * 100 + i32::from(bytes[1]) - 100;
117 if !(1..=9999).contains(&year) {
118 return Err(ProtocolError::TtcDecode("invalid DATE year"));
119 }
120 let month = bytes[2];
121 if !(1..=12).contains(&month) {
122 return Err(ProtocolError::TtcDecode("invalid DATE month"));
123 }
124 let day = bytes[3];
125 if !(1..=31).contains(&day) {
126 return Err(ProtocolError::TtcDecode("invalid DATE day"));
127 }
128 let hour = decode_offset_time_byte(bytes[4], 23, "hour")?;
129 let minute = decode_offset_time_byte(bytes[5], 59, "minute")?;
130 let second = decode_offset_time_byte(bytes[6], 59, "second")?;
131 let nanosecond = if bytes.len() >= ORA_TYPE_SIZE_TIMESTAMP as usize {
132 let value = u32::from_be_bytes(
133 bytes[7..11]
134 .try_into()
135 .map_err(|_| ProtocolError::TtcDecode("invalid TIMESTAMP fraction"))?,
136 );
137 if value > 999_999_999 {
138 return Err(ProtocolError::TtcDecode("invalid TIMESTAMP fraction"));
139 }
140 value
141 } else {
142 0
143 };
144 if bytes.len() == ORA_TYPE_SIZE_TIMESTAMP_TZ as usize {
145 if bytes[11] == 0 || bytes[12] == 0 {
146 return Err(ProtocolError::TtcDecode(
147 "invalid TIMESTAMP WITH TIME ZONE offset",
148 ));
149 }
150 if bytes[11] & TNS_HAS_REGION_ID != 0 {
151 return Err(ProtocolError::UnsupportedFeature(
152 "named TIMESTAMP WITH TIME ZONE region",
153 ));
154 }
155 if !(1..(TZ_MINUTE_OFFSET * 2)).contains(&bytes[12]) {
156 return Err(ProtocolError::TtcDecode(
157 "invalid TIMESTAMP WITH TIME ZONE offset minute",
158 ));
159 }
160 let offset_minutes = (i32::from(bytes[11]) - i32::from(TZ_HOUR_OFFSET)) * 60
161 + i32::from(bytes[12])
162 - i32::from(TZ_MINUTE_OFFSET);
163 if !valid_tz_offset_minutes(offset_minutes) {
164 return Err(ProtocolError::TtcDecode(
165 "invalid TIMESTAMP WITH TIME ZONE offset",
166 ));
167 }
168 return Ok(QueryValue::TimestampTz {
169 year,
170 month,
171 day,
172 hour,
173 minute,
174 second,
175 nanosecond,
176 offset_minutes,
177 });
178 }
179 Ok(QueryValue::DateTime {
180 year,
181 month,
182 day,
183 hour,
184 minute,
185 second,
186 nanosecond,
187 })
188}
189
190fn valid_tz_offset_minutes(offset_minutes: i32) -> bool {
191 (-1439..=1439).contains(&offset_minutes)
192}
193
194fn decode_offset_time_byte(byte: u8, max: u8, field: &'static str) -> Result<u8> {
195 let Some(value) = byte.checked_sub(1) else {
196 return Err(ProtocolError::TtcDecode(match field {
197 "hour" => "invalid DATE hour",
198 "minute" => "invalid DATE minute",
199 _ => "invalid DATE second",
200 }));
201 };
202 if value > max {
203 return Err(ProtocolError::TtcDecode(match field {
204 "hour" => "invalid DATE hour",
205 "minute" => "invalid DATE minute",
206 _ => "invalid DATE second",
207 }));
208 }
209 Ok(value)
210}
211
212pub(crate) fn adjust_datetime_by_minutes(
213 year: i32,
214 month: u8,
215 day: u8,
216 hour: u8,
217 minute: u8,
218 second: u8,
219 offset_minutes: i32,
220) -> Result<(i32, u8, u8, u8, u8, u8)> {
221 let days = days_from_civil(year, month, day)?;
222 let seconds_of_day = i64::from(hour) * 3_600 + i64::from(minute) * 60 + i64::from(second);
223 let total_seconds = days
224 .checked_mul(86_400)
225 .and_then(|value| value.checked_add(seconds_of_day))
226 .and_then(|value| value.checked_add(i64::from(offset_minutes) * 60))
227 .ok_or(ProtocolError::TtcDecode(
228 "TIMESTAMP WITH TIME ZONE offset overflow",
229 ))?;
230 let adjusted_days = total_seconds.div_euclid(86_400);
231 let adjusted_seconds = total_seconds.rem_euclid(86_400);
232 let (year, month, day) = civil_from_days(adjusted_days)?;
233 let hour = u8::try_from(adjusted_seconds / 3_600)
234 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP hour"))?;
235 let minute = u8::try_from((adjusted_seconds % 3_600) / 60)
236 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP minute"))?;
237 let second = u8::try_from(adjusted_seconds % 60)
238 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP second"))?;
239 Ok((year, month, day, hour, minute, second))
240}
241
242pub(crate) fn days_from_civil(year: i32, month: u8, day: u8) -> Result<i64> {
243 if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
244 return Err(ProtocolError::TtcDecode("invalid TIMESTAMP date"));
245 }
246 let year = year - i32::from(month <= 2);
247 let era = if year >= 0 { year } else { year - 399 } / 400;
248 let year_of_era = year - era * 400;
249 let month = i32::from(month);
250 let day = i32::from(day);
251 let month_prime = month + if month > 2 { -3 } else { 9 };
252 let day_of_year = (153 * month_prime + 2) / 5 + day - 1;
253 let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
254 Ok(i64::from(era) * 146_097 + i64::from(day_of_era) - 719_468)
255}
256
257pub(crate) fn civil_from_days(days: i64) -> Result<(i32, u8, u8)> {
258 let days = days + 719_468;
259 let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
260 let day_of_era = days - era * 146_097;
261 let year_of_era =
262 (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
263 let year = year_of_era + era * 400;
264 let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
265 let month_prime = (5 * day_of_year + 2) / 153;
266 let day = day_of_year - (153 * month_prime + 2) / 5 + 1;
267 let month = month_prime + if month_prime < 10 { 3 } else { -9 };
268 let year = year + i64::from(month <= 2);
269 Ok((
270 i32::try_from(year)
271 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP year"))?,
272 u8::try_from(month)
273 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP month"))?,
274 u8::try_from(day)
275 .map_err(|_| ProtocolError::TtcDecode("invalid adjusted TIMESTAMP day"))?,
276 ))
277}
278
279pub(crate) fn encode_binary_double(value: f64) -> [u8; 8] {
280 let mut bytes = value.to_bits().to_be_bytes();
281 if bytes[0] & 0x80 == 0 {
282 bytes[0] |= 0x80;
283 } else {
284 for byte in &mut bytes {
285 *byte = !*byte;
286 }
287 }
288 bytes
289}
290
291pub(crate) fn encode_binary_float(value: f32) -> [u8; 4] {
292 let mut bytes = value.to_bits().to_be_bytes();
293 if bytes[0] & 0x80 == 0 {
294 bytes[0] |= 0x80;
295 } else {
296 for byte in &mut bytes {
297 *byte = !*byte;
298 }
299 }
300 bytes
301}
302
303pub(crate) fn decode_binary_float(bytes: &[u8]) -> Result<f32> {
304 let bytes: [u8; 4] = bytes
305 .try_into()
306 .map_err(|_| ProtocolError::TtcDecode("invalid BINARY_FLOAT length"))?;
307 let mut decoded = bytes;
308 if decoded[0] & 0x80 != 0 {
309 decoded[0] &= 0x7f;
310 } else {
311 for byte in &mut decoded {
312 *byte = !*byte;
313 }
314 }
315 Ok(f32::from_bits(u32::from_be_bytes(decoded)))
316}
317
318pub(crate) fn encode_interval_ds(days: i32, seconds: i32, nanoseconds: i32) -> Result<[u8; 11]> {
319 let mut bytes = [0u8; 11];
320 let wire_days = u32::try_from(i64::from(days) + TNS_DURATION_MID)
321 .map_err(|_| ProtocolError::TtcDecode("INTERVAL DS days out of range"))?;
322 bytes[..4].copy_from_slice(&wire_days.to_be_bytes());
323 let to_offset_byte = |value: i32| -> Result<u8> {
324 u8::try_from(value + TNS_DURATION_OFFSET)
325 .map_err(|_| ProtocolError::TtcDecode("INTERVAL DS component out of range"))
326 };
327 bytes[4] = to_offset_byte(seconds / 3600)?;
328 bytes[5] = to_offset_byte((seconds % 3600) / 60)?;
329 bytes[6] = to_offset_byte(seconds % 60)?;
330 let fseconds = i64::from(nanoseconds);
331 let wire_fseconds = u32::try_from(fseconds + TNS_DURATION_MID)
332 .map_err(|_| ProtocolError::TtcDecode("INTERVAL DS fractional seconds out of range"))?;
333 bytes[7..].copy_from_slice(&wire_fseconds.to_be_bytes());
334 Ok(bytes)
335}
336
337pub(crate) fn decode_interval_ds(bytes: &[u8]) -> Result<QueryValue> {
338 if bytes.len() < 11 {
339 return Err(ProtocolError::TtcDecode("invalid INTERVAL DS length"));
340 }
341 let days_wire = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
342 let fseconds_wire = u32::from_be_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]);
343 let to_component = |value: i64| -> Result<i32> {
344 i32::try_from(value).map_err(|_| ProtocolError::TtcDecode("INTERVAL DS out of range"))
345 };
346 Ok(QueryValue::IntervalDS {
347 days: to_component(i64::from(days_wire) - TNS_DURATION_MID)?,
348 hours: i32::from(bytes[4]) - TNS_DURATION_OFFSET,
349 minutes: i32::from(bytes[5]) - TNS_DURATION_OFFSET,
350 seconds: i32::from(bytes[6]) - TNS_DURATION_OFFSET,
351 fseconds: to_component(i64::from(fseconds_wire) - TNS_DURATION_MID)?,
352 })
353}
354
355pub(crate) fn encode_interval_ym(years: i32, months: i32) -> Result<[u8; 5]> {
359 let mut bytes = [0u8; 5];
360 let wire_years = u32::try_from(i64::from(years) + TNS_DURATION_MID)
361 .map_err(|_| ProtocolError::TtcDecode("INTERVAL YM years out of range"))?;
362 bytes[..4].copy_from_slice(&wire_years.to_be_bytes());
363 bytes[4] = u8::try_from(months + TNS_DURATION_OFFSET)
364 .map_err(|_| ProtocolError::TtcDecode("INTERVAL YM months out of range"))?;
365 Ok(bytes)
366}
367
368pub(crate) fn decode_interval_ym(bytes: &[u8]) -> Result<QueryValue> {
372 if bytes.len() < 5 {
373 return Err(ProtocolError::TtcDecode("invalid INTERVAL YM length"));
374 }
375 let years_wire = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
376 let years = i32::try_from(i64::from(years_wire) - TNS_DURATION_MID)
377 .map_err(|_| ProtocolError::TtcDecode("INTERVAL YM out of range"))?;
378 Ok(QueryValue::IntervalYM {
379 years,
380 months: i32::from(bytes[4]) - TNS_DURATION_OFFSET,
381 })
382}
383
384pub(crate) fn decode_binary_double(bytes: &[u8]) -> Result<f64> {
385 let bytes: [u8; 8] = bytes
386 .try_into()
387 .map_err(|_| ProtocolError::TtcDecode("invalid BINARY_DOUBLE length"))?;
388 let mut decoded = bytes;
389 if decoded[0] & 0x80 != 0 {
390 decoded[0] &= 0x7f;
391 } else {
392 for byte in &mut decoded {
393 *byte = !*byte;
394 }
395 }
396 Ok(f64::from_bits(u64::from_be_bytes(decoded)))
397}
398
399pub fn encode_number_text(value: &str) -> Result<Vec<u8>> {
404 let value = value.as_bytes();
405 if value.is_empty() {
406 return Err(ProtocolError::TtcDecode("empty NUMBER bind"));
407 }
408 if value.len() > NUMBER_AS_TEXT_CHARS {
409 return Err(ProtocolError::TtcDecode("NUMBER bind text too long"));
410 }
411
412 let mut pos = 0;
413 let mut is_negative = false;
414 if matches!(value.first(), Some(&b'-')) {
415 is_negative = true;
416 pos += 1;
417 }
418
419 let mut digits = Vec::with_capacity(NUMBER_AS_TEXT_CHARS);
420 while let Some(byte) = value.get(pos).copied() {
421 if matches!(byte, b'.' | b'e' | b'E') {
422 break;
423 }
424 if !byte.is_ascii_digit() {
425 return Err(ProtocolError::TtcDecode("invalid NUMBER bind"));
426 }
427 let digit = byte - b'0';
428 pos += 1;
429 if digit == 0 && digits.is_empty() {
430 continue;
431 }
432 digits.push(digit);
433 }
434 let mut decimal_point_index = i32::try_from(digits.len()).unwrap_or(i32::MAX);
435
436 if matches!(value.get(pos), Some(&b'.')) {
437 pos += 1;
438 while let Some(byte) = value.get(pos).copied() {
439 if matches!(byte, b'e' | b'E') {
440 break;
441 }
442 if !byte.is_ascii_digit() {
443 return Err(ProtocolError::TtcDecode("invalid NUMBER bind"));
444 }
445 let digit = byte - b'0';
446 pos += 1;
447 if digit == 0 && digits.is_empty() {
448 decimal_point_index -= 1;
449 continue;
450 }
451 digits.push(digit);
452 }
453 }
454
455 if matches!(value.get(pos).copied(), Some(b'e' | b'E')) {
456 pos += 1;
457 let mut exponent_is_negative = false;
458 if let Some(byte) = value.get(pos).copied() {
459 if byte == b'-' {
460 exponent_is_negative = true;
461 pos += 1;
462 } else if byte == b'+' {
463 pos += 1;
464 }
465 }
466 let exponent_start = pos;
467 while let Some(byte) = value.get(pos).copied() {
468 if !byte.is_ascii_digit() {
469 return Err(ProtocolError::TtcDecode("invalid NUMBER exponent"));
470 }
471 pos += 1;
472 }
473 if exponent_start == pos {
474 return Err(ProtocolError::TtcDecode("empty NUMBER exponent"));
475 }
476 let exponent_text = std::str::from_utf8(&value[exponent_start..pos])
477 .map_err(|_| ProtocolError::TtcDecode("invalid NUMBER exponent"))?;
478 let mut exponent = exponent_text
479 .parse::<i32>()
480 .map_err(|_| ProtocolError::TtcDecode("invalid NUMBER exponent"))?;
481 if exponent_is_negative {
482 exponent = -exponent;
483 }
484 decimal_point_index = decimal_point_index
491 .checked_add(exponent)
492 .ok_or(ProtocolError::TtcDecode("NUMBER bind out of range"))?;
493 }
494
495 if pos < value.len() {
496 return Err(ProtocolError::TtcDecode("invalid NUMBER bind suffix"));
497 }
498
499 while digits.last().is_some_and(|digit| *digit == 0) {
500 digits.pop();
501 }
502 if digits.len() > NUMBER_MAX_DIGITS || !(-129..=126).contains(&decimal_point_index) {
503 return Err(ProtocolError::TtcDecode("NUMBER bind out of range"));
504 }
505
506 let mut prepend_zero = false;
507 if decimal_point_index % 2 != 0 {
508 prepend_zero = true;
509 if !digits.is_empty() {
510 digits.push(0);
511 decimal_point_index += 1;
512 }
513 }
514 if digits.len() % 2 == 1 {
515 digits.push(0);
516 }
517
518 if digits.is_empty() {
519 return Ok(vec![128]);
520 }
521
522 let mut encoded = Vec::with_capacity(digits.len() / 2 + 2);
523 let exponent_on_wire = decimal_point_index / 2 + 192;
524 if !(0..=255).contains(&exponent_on_wire) {
525 return Err(ProtocolError::TtcDecode(
526 "NUMBER bind exponent out of range",
527 ));
528 }
529 let exponent_byte = exponent_on_wire as u8;
530 encoded.push(if is_negative {
531 !exponent_byte
532 } else {
533 exponent_byte
534 });
535
536 let mut digit_pos = 0;
537 for pair_num in 0..(digits.len() / 2) {
538 let mut digit = if pair_num == 0 && prepend_zero {
539 let digit = digits[digit_pos];
540 digit_pos += 1;
541 digit
542 } else {
543 let digit = digits[digit_pos] * 10 + digits[digit_pos + 1];
544 digit_pos += 2;
545 digit
546 };
547 if is_negative {
548 digit = 101 - digit;
549 } else {
550 digit += 1;
551 }
552 encoded.push(digit);
553 }
554
555 if is_negative && digits.len() < NUMBER_MAX_DIGITS {
556 encoded.push(102);
557 }
558
559 Ok(encoded)
560}
561
562pub fn decode_number_value(bytes: &[u8]) -> Result<QueryValue> {
563 Ok(QueryValue::Number(super::number::OracleNumber::from_wire(
564 bytes,
565 )?))
566}
567
568pub fn decode_number_text_into(
580 bytes: &[u8],
581 digits: &mut Vec<u8>,
582 text: &mut String,
583) -> Result<bool> {
584 match decode_number_parts(bytes, digits, text)? {
585 super::number::DecodedNumber::Text { is_integer } => Ok(is_integer),
587 super::number::DecodedNumber::Parts {
588 is_negative,
589 decimal_point_index,
590 is_integer,
591 } => {
592 format_number_digits(digits, is_negative, decimal_point_index, text);
593 Ok(is_integer)
594 }
595 }
596}
597
598pub(crate) fn decode_number_parts(
607 bytes: &[u8],
608 digits: &mut Vec<u8>,
609 text: &mut String,
610) -> Result<super::number::DecodedNumber> {
611 use super::number::DecodedNumber;
612
613 if bytes.len() > 21 {
614 return Err(ProtocolError::TtcDecode("encoded NUMBER too long"));
615 }
616 let Some(&first) = bytes.first() else {
617 return Err(ProtocolError::TtcDecode("empty NUMBER"));
618 };
619 let is_positive = first & 0x80 != 0;
620 digits.clear();
621 if bytes.len() == 1 {
622 if is_positive {
623 text.push('0');
624 } else {
625 text.push_str("-1e126");
626 }
627 return Ok(DecodedNumber::Text { is_integer: true });
628 }
629
630 let exponent_byte = if is_positive { first } else { !first };
631 let exponent = i16::from(exponent_byte) - 193;
632 let mut decimal_point_index = exponent * 2 + 2;
633 let mut end = bytes.len();
634 if !is_positive && bytes[end - 1] == 102 {
635 end -= 1;
636 }
637
638 for (index, encoded) in bytes.iter().enumerate().take(end).skip(1) {
639 let value = if is_positive {
640 encoded.saturating_sub(1)
641 } else {
642 101u8.saturating_sub(*encoded)
643 };
644
645 let first_digit = value / 10;
646 if first_digit == 0 && digits.is_empty() {
647 decimal_point_index -= 1;
648 } else if first_digit == 10 {
649 digits.push(1);
650 digits.push(0);
651 decimal_point_index += 1;
652 } else if first_digit != 0 || index > 0 {
653 digits.push(first_digit);
654 }
655
656 let second_digit = value % 10;
657 if second_digit != 0 || index < end - 1 {
658 digits.push(second_digit);
659 }
660 }
661
662 let len = i16::try_from(digits.len()).unwrap_or(i16::MAX);
666 let is_integer = decimal_point_index > 0 && decimal_point_index >= len;
667
668 Ok(DecodedNumber::Parts {
669 is_negative: !is_positive,
670 decimal_point_index,
671 is_integer,
672 })
673}
674
675pub(crate) fn decode_number_parts_stack(
684 bytes: &[u8],
685 digit_buf: &mut [u8],
686) -> Result<super::number::DecodedNumberStack> {
687 use super::number::DecodedNumberStack;
688
689 if bytes.len() > 21 {
690 return Err(ProtocolError::TtcDecode("encoded NUMBER too long"));
691 }
692 let Some(&first) = bytes.first() else {
693 return Err(ProtocolError::TtcDecode("empty NUMBER"));
694 };
695 let is_positive = first & 0x80 != 0;
696 if bytes.len() == 1 {
697 return Ok(DecodedNumberStack::Sentinel {
698 text: if is_positive { "0" } else { "-1e126" },
699 is_integer: true,
700 });
701 }
702
703 let exponent_byte = if is_positive { first } else { !first };
704 let exponent = i16::from(exponent_byte) - 193;
705 let mut decimal_point_index = exponent * 2 + 2;
706 let mut end = bytes.len();
707 if !is_positive && bytes[end - 1] == 102 {
708 end -= 1;
709 }
710
711 let mut len = 0usize;
712 let mut coeff: Option<i128> = Some(0);
719 let push = |buf: &mut [u8], d: u8, len: &mut usize, coeff: &mut Option<i128>| {
723 if *len < buf.len() {
724 buf[*len] = d;
725 *len += 1;
726 }
727 *coeff = coeff
728 .and_then(|acc| acc.checked_mul(10))
729 .and_then(|acc| acc.checked_add(i128::from(d)));
730 };
731
732 for (index, encoded) in bytes.iter().enumerate().take(end).skip(1) {
733 let value = if is_positive {
734 encoded.saturating_sub(1)
735 } else {
736 101u8.saturating_sub(*encoded)
737 };
738
739 let first_digit = value / 10;
740 if first_digit == 0 && len == 0 {
741 decimal_point_index -= 1;
742 } else if first_digit == 10 {
743 push(digit_buf, 1, &mut len, &mut coeff);
744 push(digit_buf, 0, &mut len, &mut coeff);
745 decimal_point_index += 1;
746 } else if first_digit != 0 || index > 0 {
747 push(digit_buf, first_digit, &mut len, &mut coeff);
748 }
749
750 let second_digit = value % 10;
751 if second_digit != 0 || index < end - 1 {
752 push(digit_buf, second_digit, &mut len, &mut coeff);
753 }
754 }
755
756 let len_i16 = i16::try_from(len).unwrap_or(i16::MAX);
757 let is_integer = decimal_point_index > 0 && decimal_point_index >= len_i16;
758
759 let coefficient = coeff.map(|acc| if is_positive { acc } else { -acc });
764
765 Ok(DecodedNumberStack::Parts {
766 digit_len: len,
767 is_negative: !is_positive,
768 decimal_point_index,
769 is_integer,
770 coefficient,
771 })
772}
773
774pub(crate) fn format_number_digits(
780 digits: &[u8],
781 is_negative: bool,
782 decimal_point_index: i16,
783 text: &mut String,
784) {
785 if is_negative {
786 text.push('-');
787 }
788 if decimal_point_index <= 0 {
789 text.push_str("0.");
790 for _ in decimal_point_index..0 {
791 text.push('0');
792 }
793 }
794 for (index, digit) in digits.iter().enumerate() {
795 if index > 0
796 && matches!(
797 i16::try_from(index)
798 .unwrap_or(i16::MAX)
799 .cmp(&decimal_point_index),
800 std::cmp::Ordering::Equal
801 )
802 {
803 text.push('.');
804 }
805 text.push(char::from(b'0' + *digit));
806 }
807 if decimal_point_index > i16::try_from(digits.len()).unwrap_or(i16::MAX) {
808 for _ in i16::try_from(digits.len()).unwrap_or(i16::MAX)..decimal_point_index {
809 text.push('0');
810 }
811 }
812}
813
814pub(crate) fn decode_text_value(bytes: &[u8], csfrm: u8) -> Result<String> {
815 if csfrm == CS_FORM_NCHAR {
816 let units = bytes
817 .chunks_exact(2)
818 .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
819 .collect::<Vec<_>>();
820 if units.len() * 2 != bytes.len() {
821 return Err(ProtocolError::TtcDecode("invalid UTF-16 text length"));
822 }
823 String::from_utf16(&units).map_err(|_| ProtocolError::TtcDecode("invalid UTF-16 text"))
824 } else {
825 String::from_utf8(bytes.to_vec())
826 .map_err(|_| ProtocolError::TtcDecode("invalid UTF-8 text"))
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833
834 #[test]
840 fn number_text_huge_exponent_rejected_not_panicked() {
841 let crafted = format!("{}e+2147483647", "1".repeat(160));
843 assert_eq!(crafted.len(), NUMBER_AS_TEXT_CHARS);
844 assert!(encode_number_text(&crafted).is_err());
845
846 let crafted_neg = format!("0.{}e-2147483647", "0".repeat(158));
848 assert!(encode_number_text(&crafted_neg).is_err());
849 }
850
851 #[test]
852 fn number_text_ordinary_values_still_encode() {
853 for ok in [
854 "0",
855 "1",
856 "-1",
857 "3.14159",
858 "1e10",
859 "-2.5e-3",
860 "12345678901234567890",
861 ] {
862 assert!(encode_number_text(ok).is_ok(), "expected {ok} to encode");
863 }
864 }
865
866 #[test]
867 fn interval_ds_roundtrip_preserves_nanoseconds() {
868 let wire = encode_interval_ds(2, 3 * 3600 + 4 * 60 + 5, 123_456_789)
869 .expect("encode nanosecond interval");
870 assert_eq!(
871 decode_interval_ds(&wire).expect("decode nanosecond interval"),
872 QueryValue::IntervalDS {
873 days: 2,
874 hours: 3,
875 minutes: 4,
876 seconds: 5,
877 fseconds: 123_456_789,
878 }
879 );
880 }
881
882 #[test]
883 fn interval_ds_rejects_truncated_wire_value() {
884 let wire = encode_interval_ds(-2, -(3 * 3600 + 4 * 60 + 5), -123_456_789)
885 .expect("encode negative interval");
886 assert!(decode_interval_ds(&wire[..10]).is_err());
887 }
888
889 #[test]
890 fn datetime_decode_rejects_malformed_wire_fields() {
891 let valid_date = encode_oracle_date(2026, 7, 13, 12, 34, 56).expect("valid date");
892 for (index, value, label) in [
893 (0, 99, "year"),
894 (2, 0, "month zero"),
895 (2, 13, "month high"),
896 (3, 0, "day zero"),
897 (3, 32, "day high"),
898 (4, 0, "hour zero"),
899 (4, 25, "hour high"),
900 (5, 0, "minute zero"),
901 (5, 61, "minute high"),
902 (6, 0, "second zero"),
903 (6, 61, "second high"),
904 ] {
905 let mut malformed = valid_date;
906 malformed[index] = value;
907 assert!(
908 decode_datetime_value(&malformed).is_err(),
909 "{label} must fail closed"
910 );
911 }
912 }
913
914 #[test]
915 fn timestamp_decode_rejects_invalid_length_and_fraction() {
916 let timestamp =
917 encode_oracle_timestamp(2026, 7, 13, 12, 34, 56, 123_456_789).expect("timestamp");
918 for len in [6, 8, 10, 12, 14] {
919 let mut malformed = timestamp.clone();
920 malformed.resize(len, 0);
921 assert!(
922 decode_datetime_value(&malformed).is_err(),
923 "length {len} must fail closed"
924 );
925 }
926
927 let mut malformed = timestamp;
928 malformed[7..11].copy_from_slice(&1_000_000_000_u32.to_be_bytes());
929 assert!(
930 decode_datetime_value(&malformed).is_err(),
931 "nanosecond above Oracle range must fail closed"
932 );
933 }
934
935 #[test]
936 fn timestamp_tz_decode_rejects_missing_or_malformed_offset_bytes() {
937 let valid = encode_oracle_timestamp_tz_with_offset(2026, 7, 13, 12, 34, 56, 0, -330)
938 .expect("timestamp with time zone");
939 for (hour_byte, minute_byte, label) in [
940 (0, valid[12], "missing offset hour"),
941 (valid[11], 0, "missing offset minute"),
942 (0, 0, "missing full offset"),
943 (valid[11], TZ_MINUTE_OFFSET * 2, "invalid offset minute"),
944 (TZ_HOUR_OFFSET + 24, TZ_MINUTE_OFFSET, "invalid +24h offset"),
945 (127, TZ_MINUTE_OFFSET, "absurd offset hour"),
946 ] {
947 let mut malformed = valid.clone();
948 malformed[11] = hour_byte;
949 malformed[12] = minute_byte;
950 assert!(
951 decode_datetime_value(&malformed).is_err(),
952 "{label} must fail closed"
953 );
954 }
955
956 for offset_minutes in [-1440, 1440, 6420] {
957 assert!(
958 encode_oracle_timestamp_tz_with_offset(2026, 7, 13, 12, 34, 56, 0, offset_minutes,)
959 .is_err(),
960 "outbound offset {offset_minutes} must fail closed"
961 );
962 }
963 }
964
965 #[test]
966 fn number_decode_heap_and_stack_paths_match_edge_corpus() {
967 for (text, is_integer) in [
968 ("0", true),
969 ("0.01", false),
970 ("0.1", false),
971 ("0.99", false),
972 ("9.9", false),
973 ("10", true),
974 ("99", true),
975 ("100", true),
976 ("101", true),
977 ("-0.01", false),
978 ("-0.1", false),
979 ("-0.99", false),
980 ("-9.9", false),
981 ("-10", true),
982 ("-99", true),
983 ("-100", true),
984 ("-101", true),
985 ("12345678901234567890123456789012345678", true),
986 ("-12345678901234567890123456789012345678", true),
987 ] {
988 let wire = encode_number_text(text).expect("encode NUMBER edge");
989 let mut digits = Vec::new();
990 let mut decoded = String::new();
991 let decoded_is_integer =
992 decode_number_text_into(&wire, &mut digits, &mut decoded).expect("decode text");
993 let owned = decode_number_value(&wire).expect("decode owned NUMBER");
994
995 assert_eq!(decoded, text, "canonical NUMBER text for wire {wire:02x?}");
996 assert_eq!(
997 decoded_is_integer, is_integer,
998 "integer flag for NUMBER wire {wire:02x?}"
999 );
1000 assert_eq!(
1001 owned.as_number_text().as_deref(),
1002 Some(decoded.as_str()),
1003 "owned stack decode must match heap text decode for {text}"
1004 );
1005 }
1006 }
1007
1008 #[test]
1009 fn number_part_decoders_emit_exact_digits_decimal_and_coefficient() {
1010 use super::super::number::{DecodedNumber, DecodedNumberStack, MAX_DIGITS};
1011
1012 struct Case<'a> {
1013 wire: &'a [u8],
1014 digits: &'a [u8],
1015 is_negative: bool,
1016 decimal_point_index: i16,
1017 is_integer: bool,
1018 coefficient: Option<i128>,
1019 text: &'a str,
1020 }
1021
1022 let cases = [
1023 Case {
1027 wire: &[193, 101],
1028 digits: &[1, 0],
1029 is_negative: false,
1030 decimal_point_index: 3,
1031 is_integer: true,
1032 coefficient: Some(10),
1033 text: "100",
1034 },
1035 Case {
1039 wire: &[193, 1, 2],
1040 digits: &[0, 0, 1],
1041 is_negative: false,
1042 decimal_point_index: 1,
1043 is_integer: false,
1044 coefficient: Some(1),
1045 text: "0.01",
1046 },
1047 Case {
1049 wire: &[62, 100, 102],
1050 digits: &[1],
1051 is_negative: true,
1052 decimal_point_index: 1,
1053 is_integer: true,
1054 coefficient: Some(-1),
1055 text: "-1",
1056 },
1057 Case {
1061 wire: &[
1062 211, 13, 35, 57, 79, 91, 13, 35, 57, 79, 91, 13, 35, 57, 79, 91, 13, 35, 57,
1063 79, 91,
1064 ],
1065 digits: &[
1066 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6,
1067 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1068 ],
1069 is_negative: false,
1070 decimal_point_index: 38,
1071 is_integer: false,
1072 coefficient: Some(123456789012345678901234567890123456789),
1073 text: "12345678901234567890123456789012345678.9",
1074 },
1075 ];
1076
1077 for case in cases {
1078 let mut heap_digits = Vec::new();
1079 let mut text = String::new();
1080 let decoded = decode_number_parts(case.wire, &mut heap_digits, &mut text)
1081 .expect("heap NUMBER part decode");
1082 assert!(
1083 matches!(decoded, DecodedNumber::Parts { .. }),
1084 "expected heap NUMBER parts for {:02x?}",
1085 case.wire
1086 );
1087 if let DecodedNumber::Parts {
1088 is_negative,
1089 decimal_point_index,
1090 is_integer,
1091 } = decoded
1092 {
1093 assert_eq!(
1094 heap_digits, case.digits,
1095 "heap digits for {:02x?}",
1096 case.wire
1097 );
1098 assert_eq!(
1099 is_negative, case.is_negative,
1100 "heap sign for {:02x?}",
1101 case.wire
1102 );
1103 assert_eq!(
1104 decimal_point_index, case.decimal_point_index,
1105 "heap decimal point for {:02x?}",
1106 case.wire
1107 );
1108 assert_eq!(
1109 is_integer, case.is_integer,
1110 "heap integer flag for {:02x?}",
1111 case.wire
1112 );
1113 format_number_digits(&heap_digits, is_negative, decimal_point_index, &mut text);
1114 assert_eq!(text, case.text, "heap text for {:02x?}", case.wire);
1115 }
1116
1117 let mut stack_digits = [0u8; MAX_DIGITS];
1118 let decoded = decode_number_parts_stack(case.wire, &mut stack_digits)
1119 .expect("stack NUMBER part decode");
1120 assert!(
1121 matches!(decoded, DecodedNumberStack::Parts { .. }),
1122 "expected stack NUMBER parts for {:02x?}",
1123 case.wire
1124 );
1125 if let DecodedNumberStack::Parts {
1126 digit_len,
1127 is_negative,
1128 decimal_point_index,
1129 is_integer,
1130 coefficient,
1131 } = decoded
1132 {
1133 assert_eq!(
1134 &stack_digits[..digit_len],
1135 case.digits,
1136 "stack digits for {:02x?}",
1137 case.wire
1138 );
1139 assert_eq!(
1140 is_negative, case.is_negative,
1141 "stack sign for {:02x?}",
1142 case.wire
1143 );
1144 assert_eq!(
1145 decimal_point_index, case.decimal_point_index,
1146 "stack decimal point for {:02x?}",
1147 case.wire
1148 );
1149 assert_eq!(
1150 is_integer, case.is_integer,
1151 "stack integer flag for {:02x?}",
1152 case.wire
1153 );
1154 assert_eq!(
1155 coefficient, case.coefficient,
1156 "stack coefficient for {:02x?}",
1157 case.wire
1158 );
1159 let mut stack_text = String::new();
1160 format_number_digits(
1161 &stack_digits[..digit_len],
1162 is_negative,
1163 decimal_point_index,
1164 &mut stack_text,
1165 );
1166 assert_eq!(stack_text, case.text, "stack text for {:02x?}", case.wire);
1167 }
1168 }
1169 }
1170}