leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use bytes::{BufMut, Bytes};

use super::{FrameHeader, FrameType};
use crate::h2::H2Error;
use crate::h2::error::ErrorCode;

pub mod flags {
    pub const END_STREAM: u8 = 0x1;
    pub const PADDED: u8 = 0x8;
}

#[derive(Debug)]
pub struct DataFrame {
    pub stream_id: u32,
    pub end_stream: bool,
    pub data: Bytes,
    pub wire_len: u64,
}

impl DataFrame {
    pub fn parse(header: FrameHeader, payload: Bytes) -> Result<Self, H2Error> {
        if header.stream_id == 0 {
            return Err(H2Error::Connection {
                code: ErrorCode::ProtocolError,
                reason: "DATA frame on stream 0".into(),
            });
        }

        let end_stream = header.flags & flags::END_STREAM != 0;
        let padded = header.flags & flags::PADDED != 0;

        let data = if padded && !payload.is_empty() {
            let pad_len = payload[0] as usize;
            if pad_len >= payload.len() {
                return Err(H2Error::Connection {
                    code: ErrorCode::ProtocolError,
                    reason: "DATA padding exceeds payload".into(),
                });
            }
            payload.slice(1..payload.len() - pad_len)
        } else {
            payload
        };

        Ok(Self {
            stream_id: header.stream_id,
            end_stream,
            data,
            wire_len: header.length as u64,
        })
    }

    pub fn encode(&self, buf: &mut impl BufMut) {
        let mut flag = 0u8;
        if self.end_stream {
            flag |= flags::END_STREAM;
        }
        let header = FrameHeader {
            length: self.data.len() as u32,
            frame_type: FrameType::Data as u8,
            flags: flag,
            stream_id: self.stream_id,
        };
        header.encode(buf);
        buf.put_slice(&self.data);
    }
}

#[cfg(test)]
mod tests;