// Overflow-safe ASCII decimal parsing.
//
// ISO 28500 derives framing decisions from untrusted digit strings
// (Content-Length, WARC-Segment-Number, WARC-Segment-Total-Length), so
// parsing is checked digit by digit: negative values, silent wrapping
// and panics are impossible by construction.
///|
/// Number of leading ASCII digits in `data[start:end]`.
pub fn digit_count(data : Bytes, start : Int, end : Int) -> Int {
let mut i = start
while i < end && is_digit(data[i]) {
i = i + 1
}
i - start
}
///|
/// Parse `data[start:end]`, which must consist entirely of ASCII
/// digits, as an `Int64`.
///
/// - an empty span fails with `InvalidContentLength` (a mandatory
/// numeric value is missing);
/// - more than `max_digits` digits or a value beyond `Int64` fails with
/// `IntegerOverflow`.
///
/// `context` names the field for error reporting (e.g. "Content-Length").
pub fn parse_decimal(
data : Bytes,
start : Int,
end : Int,
max_digits : Int,
context : String,
) -> Result[Int64, WarcError] {
if end - start == 0 {
return Err(
WarcError::new(
WarcErrorStage::Number,
WarcErrorKind::InvalidContentLength,
start.to_int64(),
0L,
context + ": empty value",
),
)
}
if end - start > max_digits {
return Err(
WarcError::new(
WarcErrorStage::Number,
WarcErrorKind::IntegerOverflow,
start.to_int64(),
0L,
context + ": too many digits",
),
)
}
let mut value = 0L
let mut i = start
while i < end {
let d = digit_value(data[i])
if d < 0 {
return Err(
WarcError::new(
WarcErrorStage::Number,
WarcErrorKind::InvalidContentLength,
i.to_int64(),
0L,
context + ": non-digit byte 0x\{data[i].to_int().to_string(radix=16)}",
),
)
}
let next = append_decimal_digit(value, d)
match next {
Some(v) => value = v
None =>
return Err(
WarcError::new(
WarcErrorStage::Number,
WarcErrorKind::IntegerOverflow,
start.to_int64(),
0L,
context + ": value exceeds Int64",
),
)
}
i = i + 1
}
Ok(value)
}
///|
/// Append one decimal digit to `value`, returning `None` on overflow.
/// This is also the primitive used by the incremental streaming decoder
/// when a Content-Length is split across input chunks.
pub fn append_decimal_digit(value : Int64, d : Int) -> Int64? {
if d < 0 || d > 9 {
return None
}
// value * 10 + d <= Int64::max_value without overflow.
if value > (9_223_372_036_854_775_807L - d.to_int64()) / 10L {
return None
}
Some(value * 10L + d.to_int64())
}