pub fn encode(payload: &[u8]) -> Vec<u8> {
let len = payload.len() + 4;
debug_assert!(len <= 65520, "pkt-line payload too large: {len}");
let mut out = format!("{:04x}", len).into_bytes();
out.extend_from_slice(payload);
out
}
pub const FLUSH: &[u8] = b"0000";
#[derive(Debug, Default, Clone)]
pub struct UploadPackRequest {
pub wants: Vec<String>,
pub haves: Vec<String>,
pub capabilities: Vec<String>,
pub done: bool,
}
pub fn parse_upload_pack_request(body: &[u8]) -> UploadPackRequest {
let mut request = UploadPackRequest::default();
let mut offset = 0usize;
while offset + 4 <= body.len() {
let header = match std::str::from_utf8(&body[offset..offset + 4]) {
Ok(h) => h,
Err(_) => break,
};
let len = match usize::from_str_radix(header, 16) {
Ok(l) => l,
Err(_) => break,
};
offset += 4;
if len == 0 || len == 1 {
continue;
}
if len < 4 {
break;
}
let payload_len = len - 4;
if offset + payload_len > body.len() {
break;
}
let payload = &body[offset..offset + payload_len];
offset += payload_len;
let line = String::from_utf8_lossy(payload);
let line = line.trim_end_matches(['\n', '\r']);
if let Some(rest) = line.strip_prefix("want ") {
let mut parts = rest.split(' ');
if let Some(oid) = parts.next() {
if is_object_id(oid) {
request.wants.push(oid.to_ascii_lowercase());
}
}
if request.capabilities.is_empty() {
request.capabilities = parts.map(|c| c.to_string()).collect();
}
} else if let Some(oid) = line.strip_prefix("have ") {
let oid = oid.split(' ').next().unwrap_or("");
if is_object_id(oid) {
request.haves.push(oid.to_ascii_lowercase());
}
} else if line == "done" {
request.done = true;
}
}
request
}
fn is_object_id(value: &str) -> bool {
value.len() == 40 && value.bytes().all(|b| b.is_ascii_hexdigit())
}