use std::ops::Range;
use sark_core::http::LocalFrameBytes;
use super::ChunkerCore;
use super::chain::{SplitFrameChain, SplitFrameIter};
trait AsciiInt: Copy {
fn parse_ascii(bytes: &[u8]) -> Option<Self>;
fn fold_ascii(acc: Self, chunk: &[u8]) -> Option<Self>;
fn zero() -> Self;
}
impl AsciiInt for usize {
fn parse_ascii(bytes: &[u8]) -> Option<Self> {
Ascii::parse_usize(bytes)
}
fn fold_ascii(acc: Self, chunk: &[u8]) -> Option<Self> {
Ascii::fold_usize(acc, chunk)
}
fn zero() -> Self {
0
}
}
impl AsciiInt for u64 {
fn parse_ascii(bytes: &[u8]) -> Option<Self> {
Ascii::parse_u64(bytes)
}
fn fold_ascii(acc: Self, chunk: &[u8]) -> Option<Self> {
Ascii::fold_u64(acc, chunk)
}
fn zero() -> Self {
0
}
}
trait BytesCmp {
fn eq(a: &[u8], b: &[u8]) -> bool;
}
struct ExactEq;
impl BytesCmp for ExactEq {
fn eq(a: &[u8], b: &[u8]) -> bool {
a == b
}
}
struct AsciiCaseEq;
impl BytesCmp for AsciiCaseEq {
fn eq(a: &[u8], b: &[u8]) -> bool {
a.eq_ignore_ascii_case(b)
}
}
use sark_core::utils::bytes::Ascii;
use crate::routes::path::seg_next;
#[allow(private_interfaces)]
#[derive(Clone, Copy)]
pub enum PathView<'a> {
Slice(&'a [u8]),
Local {
frame: &'a LocalFrameBytes,
start: usize,
end: usize,
},
Chain {
chain: &'a SplitFrameChain,
start: usize,
end: usize,
},
}
impl<'a> PathView<'a> {
pub(crate) fn len(self) -> usize {
match self {
Self::Slice(path) => path.len(),
Self::Local { start, end, .. } => end - start,
Self::Chain { start, end, .. } => end - start,
}
}
pub fn as_slice(self) -> Option<&'a [u8]> {
match self {
Self::Slice(path) => Some(path),
Self::Local {
frame, start, end, ..
} => Some(&frame.as_bytes()[start..end]),
Self::Chain { chain, start, end } => chain.direct_bytes_range(start..end),
}
}
pub fn to_vec(self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.len());
self.append_to(&mut out);
out
}
pub(crate) fn copy_range_local(self, start: usize, end: usize) -> Option<LocalFrameBytes> {
if end < start || end > self.len() {
return None;
}
match self {
Self::Slice(path) => Some(LocalFrameBytes::from_shared(
o3::buffer::Shared::copy_from_slice(&path[start..end]),
)),
Self::Local {
frame, start: base, ..
} => Some(frame.clone().slice((base + start)..(base + end))),
Self::Chain {
chain, start: base, ..
} => Some(chain.compact().clone().slice((base + start)..(base + end))),
}
}
pub(crate) fn append_to(self, out: &mut impl o3::buffer::Write) {
match self {
Self::Slice(path) => out.put_slice(path),
Self::Local {
frame, start, end, ..
} => out.put_slice(&frame.as_bytes()[start..end]),
Self::Chain { chain, start, end } => {
let _ = chain.for_each_range(start..end, |chunk| out.put_slice(chunk));
}
}
}
pub(crate) fn parse_u64(self) -> Option<u64> {
self.parse_u64_range(0, self.len())
}
fn parse_int_range<T: AsciiInt>(self, start: usize, end: usize) -> Option<T> {
if end < start || end > self.len() || start == end {
return None;
}
match self {
Self::Slice(path) => T::parse_ascii(&path[start..end]),
Self::Local {
frame, start: base, ..
} => T::parse_ascii(&frame.as_bytes()[(base + start)..(base + end)]),
Self::Chain {
chain, start: base, ..
} => {
let mut acc = Some(T::zero());
let _ = chain.for_each_range((base + start)..(base + end), |chunk| {
acc = acc.and_then(|v| T::fold_ascii(v, chunk));
});
acc
}
}
}
pub(crate) fn parse_usize_range(self, start: usize, end: usize) -> Option<usize> {
self.parse_int_range::<usize>(start, end)
}
pub(crate) fn parse_u64_range(self, start: usize, end: usize) -> Option<u64> {
self.parse_int_range::<u64>(start, end)
}
pub(crate) fn eq_bytes(self, expected: &[u8]) -> bool {
self.eq_range(0, self.len(), expected)
}
fn eq_range_with<C: BytesCmp>(self, start: usize, end: usize, expected: &[u8]) -> bool {
if end < start || end > self.len() || expected.len() != end - start {
return false;
}
match self {
Self::Slice(path) => C::eq(&path[start..end], expected),
Self::Local {
frame, start: base, ..
} => C::eq(&frame.as_bytes()[(base + start)..(base + end)], expected),
Self::Chain {
chain, start: base, ..
} => {
let mut matched = 0usize;
let mut ok = true;
let _ = chain.for_each_range((base + start)..(base + end), |chunk| {
if !ok {
return;
}
let next = matched + chunk.len();
if !C::eq(chunk, &expected[matched..next]) {
ok = false;
return;
}
matched = next;
});
ok && matched == expected.len()
}
}
}
pub(crate) fn eq_range(self, start: usize, end: usize, expected: &[u8]) -> bool {
self.eq_range_with::<ExactEq>(start, end, expected)
}
pub(crate) fn eq_range_ignore_ascii_case(
self,
start: usize,
end: usize,
expected: &[u8],
) -> bool {
self.eq_range_with::<AsciiCaseEq>(start, end, expected)
}
pub(crate) fn next_seg(self, idx: usize) -> Option<(usize, usize, usize)> {
if idx >= self.len() {
return None;
}
match self {
Self::Slice(path) => seg_next(path, idx),
Self::Local { frame, start, end } => seg_next(&frame.as_bytes()[start..end], idx),
Self::Chain { chain, start, end } => {
let mut pos = 0usize;
let mut seg_start = None;
let mut seg_end = None;
let mut done = false;
let _ = chain.for_each_range(start..end, |chunk| {
if done {
return;
}
let mut i = 0usize;
while i < chunk.len() {
let abs = pos + i;
let b = chunk[i];
if seg_start.is_none() {
if abs < idx || b == b'/' {
i += 1;
continue;
}
seg_start = Some(abs);
}
if b == b'/' {
seg_end = Some(abs);
done = true;
break;
}
i += 1;
}
pos += chunk.len();
});
let start = seg_start?;
let end = seg_end.unwrap_or(self.len());
Some((start, end, end))
}
}
}
}
pub(crate) type BodyChunks<'a> = BodyChainIter<'a>;
pub struct BodyChainIter<'a> {
frames: SplitFrameIter<'a>,
core: ChunkerCore,
}
impl<'a> BodyChainIter<'a> {
pub(super) fn new(chain: &'a SplitFrameChain, range: Range<usize>) -> Self {
debug_assert!(range.start <= range.end, "invalid body chunk range");
debug_assert!(
range.end <= chain.len(),
"body chunk range exceeds split-chain length"
);
Self {
frames: chain.iter_frames(),
core: ChunkerCore {
skip: range.start,
remaining: range.end - range.start,
},
}
}
}
impl<'a> Iterator for BodyChainIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
while self.core.remaining != 0 {
let bytes = self.frames.next()?.as_bytes();
if let Some(out) = self.core.step(bytes) {
return Some(out);
}
}
None
}
}