pub mod srvsvc;
use crate::error::Result;
use crate::pack::guid::Guid;
use crate::pack::{Pack, ReadCursor, WriteCursor};
use crate::Error;
const RPC_VERSION_MAJOR: u8 = 5;
const RPC_VERSION_MINOR: u8 = 0;
const DATA_REP: [u8; 4] = [0x10, 0x00, 0x00, 0x00];
const PDU_TYPE_REQUEST: u8 = 0;
const PDU_TYPE_RESPONSE: u8 = 2;
const PDU_TYPE_BIND: u8 = 11;
const PDU_TYPE_BIND_ACK: u8 = 12;
const MAX_XMIT_FRAG: u16 = 4280;
const MAX_RECV_FRAG: u16 = 4280;
const PFC_FIRST_FRAG: u8 = 0x01;
const PFC_LAST_FRAG: u8 = 0x02;
const SRVSVC_UUID: Guid = Guid {
data1: 0x4B324FC8,
data2: 0x1670,
data3: 0x01D3,
data4: [0x12, 0x78, 0x5A, 0x47, 0xBF, 0x6E, 0xE1, 0x88],
};
const SRVSVC_VERSION: u32 = 3;
const NDR_UUID: Guid = Guid {
data1: 0x8A885D04,
data2: 0x1CEB,
data3: 0x11C9,
data4: [0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60],
};
const NDR_VERSION: u32 = 2;
const RPC_HEADER_SIZE: usize = 16;
pub fn build_srvsvc_bind(call_id: u32) -> Vec<u8> {
let mut w = WriteCursor::with_capacity(72);
w.write_u8(RPC_VERSION_MAJOR);
w.write_u8(RPC_VERSION_MINOR);
w.write_u8(PDU_TYPE_BIND);
w.write_u8(PFC_FIRST_FRAG | PFC_LAST_FRAG);
w.write_bytes(&DATA_REP);
let frag_len_pos = w.position();
w.write_u16_le(0); w.write_u16_le(0); w.write_u32_le(call_id);
w.write_u16_le(MAX_XMIT_FRAG);
w.write_u16_le(MAX_RECV_FRAG);
w.write_u32_le(0);
w.write_u8(1); w.write_bytes(&[0, 0, 0]);
w.write_u16_le(0); w.write_u8(1); w.write_u8(0);
SRVSVC_UUID.pack(&mut w);
w.write_u32_le(SRVSVC_VERSION);
NDR_UUID.pack(&mut w);
w.write_u32_le(NDR_VERSION);
let total_len = w.position();
w.set_u16_le_at(frag_len_pos, total_len as u16);
w.into_inner()
}
pub fn parse_bind_ack(data: &[u8]) -> Result<()> {
let mut r = ReadCursor::new(data);
let version = r.read_u8()?;
let version_minor = r.read_u8()?;
if version != RPC_VERSION_MAJOR || version_minor != RPC_VERSION_MINOR {
return Err(Error::invalid_data(format!(
"unexpected RPC version {version}.{version_minor}, expected 5.0"
)));
}
let ptype = r.read_u8()?;
if ptype != PDU_TYPE_BIND_ACK {
return Err(Error::invalid_data(format!(
"expected BIND_ACK (type 12), got type {ptype}"
)));
}
let _flags = r.read_u8()?;
let _data_rep = r.read_bytes(4)?;
let _frag_length = r.read_u16_le()?;
let _auth_length = r.read_u16_le()?;
let _call_id = r.read_u32_le()?;
let _max_xmit_frag = r.read_u16_le()?;
let _max_recv_frag = r.read_u16_le()?;
let _assoc_group = r.read_u32_le()?;
let sec_addr_len = r.read_u16_le()?;
r.skip(sec_addr_len as usize)?;
let consumed = 2 + sec_addr_len as usize;
let padding = (4 - (consumed % 4)) % 4;
r.skip(padding)?;
let num_results = r.read_u8()?;
r.skip(3)?;
if num_results == 0 {
return Err(Error::invalid_data("BIND_ACK has no context results"));
}
let result = r.read_u16_le()?;
if result != 0 {
let reason = r.read_u16_le()?;
return Err(Error::invalid_data(format!(
"BIND rejected: result={result}, reason={reason}"
)));
}
Ok(())
}
pub fn build_request(call_id: u32, opnum: u16, stub_data: &[u8]) -> Vec<u8> {
let mut w = WriteCursor::with_capacity(RPC_HEADER_SIZE + 8 + stub_data.len());
w.write_u8(RPC_VERSION_MAJOR);
w.write_u8(RPC_VERSION_MINOR);
w.write_u8(PDU_TYPE_REQUEST);
w.write_u8(PFC_FIRST_FRAG | PFC_LAST_FRAG);
w.write_bytes(&DATA_REP);
let frag_len_pos = w.position();
w.write_u16_le(0); w.write_u16_le(0); w.write_u32_le(call_id);
w.write_u32_le(stub_data.len() as u32); w.write_u16_le(0); w.write_u16_le(opnum);
w.write_bytes(stub_data);
let total_len = w.position();
w.set_u16_le_at(frag_len_pos, total_len as u16);
w.into_inner()
}
pub fn parse_response(data: &[u8]) -> Result<&[u8]> {
let mut r = ReadCursor::new(data);
let version = r.read_u8()?;
let version_minor = r.read_u8()?;
if version != RPC_VERSION_MAJOR || version_minor != RPC_VERSION_MINOR {
return Err(Error::invalid_data(format!(
"unexpected RPC version {version}.{version_minor}, expected 5.0"
)));
}
let ptype = r.read_u8()?;
if ptype != PDU_TYPE_RESPONSE {
return Err(Error::invalid_data(format!(
"expected RESPONSE (type 2), got type {ptype}"
)));
}
let _flags = r.read_u8()?;
let _data_rep = r.read_bytes(4)?;
let frag_length = r.read_u16_le()?;
let _auth_length = r.read_u16_le()?;
let _call_id = r.read_u32_le()?;
let _alloc_hint = r.read_u32_le()?;
let _context_id = r.read_u16_le()?;
let _cancel_count = r.read_u8()?;
let _reserved = r.read_u8()?;
let stub_len = frag_length as usize - r.position();
let stub_data = r.read_bytes(stub_len)?;
Ok(stub_data)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pack::Unpack;
#[test]
fn bind_request_has_correct_header() {
let pdu = build_srvsvc_bind(1);
assert_eq!(pdu[0], RPC_VERSION_MAJOR, "version major");
assert_eq!(pdu[1], RPC_VERSION_MINOR, "version minor");
assert_eq!(pdu[2], PDU_TYPE_BIND, "packet type");
assert_eq!(pdu[3], PFC_FIRST_FRAG | PFC_LAST_FRAG, "flags");
assert_eq!(&pdu[4..8], &DATA_REP);
let frag_len = u16::from_le_bytes([pdu[8], pdu[9]]);
assert_eq!(frag_len as usize, pdu.len());
let auth_len = u16::from_le_bytes([pdu[10], pdu[11]]);
assert_eq!(auth_len, 0);
let call_id = u32::from_le_bytes([pdu[12], pdu[13], pdu[14], pdu[15]]);
assert_eq!(call_id, 1);
}
#[test]
fn bind_request_contains_srvsvc_uuid() {
let pdu = build_srvsvc_bind(1);
let uuid_offset = 32;
let mut cursor = ReadCursor::new(&pdu[uuid_offset..]);
let guid = Guid::unpack(&mut cursor).unwrap();
assert_eq!(guid, SRVSVC_UUID);
let version = cursor.read_u32_le().unwrap();
assert_eq!(version, SRVSVC_VERSION);
}
#[test]
fn bind_request_contains_ndr_transfer_syntax() {
let pdu = build_srvsvc_bind(1);
let transfer_offset = 32 + 20;
let mut cursor = ReadCursor::new(&pdu[transfer_offset..]);
let guid = Guid::unpack(&mut cursor).unwrap();
assert_eq!(guid, NDR_UUID);
let version = cursor.read_u32_le().unwrap();
assert_eq!(version, NDR_VERSION);
}
#[test]
fn bind_request_total_length() {
let pdu = build_srvsvc_bind(1);
assert_eq!(pdu.len(), 72);
}
#[test]
fn parse_valid_bind_ack() {
let ack = build_test_bind_ack(0); assert!(parse_bind_ack(&ack).is_ok());
}
#[test]
fn parse_rejected_bind_ack() {
let ack = build_test_bind_ack(2); let err = parse_bind_ack(&ack).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("rejected"),
"error should mention rejection: {msg}"
);
}
#[test]
fn parse_bind_ack_wrong_version() {
let mut ack = build_test_bind_ack(0);
ack[0] = 4; assert!(parse_bind_ack(&ack).is_err());
}
#[test]
fn parse_bind_ack_wrong_type() {
let mut ack = build_test_bind_ack(0);
ack[2] = PDU_TYPE_BIND; assert!(parse_bind_ack(&ack).is_err());
}
#[test]
fn request_pdu_has_correct_opnum() {
let stub = vec![0xAA, 0xBB, 0xCC];
let pdu = build_request(1, 15, &stub);
let opnum = u16::from_le_bytes([pdu[22], pdu[23]]);
assert_eq!(opnum, 15);
}
#[test]
fn request_pdu_has_correct_alloc_hint() {
let stub = vec![0xAA, 0xBB, 0xCC];
let pdu = build_request(1, 15, &stub);
let alloc_hint = u32::from_le_bytes([pdu[16], pdu[17], pdu[18], pdu[19]]);
assert_eq!(alloc_hint, 3);
}
#[test]
fn request_pdu_contains_stub_data() {
let stub = vec![0xAA, 0xBB, 0xCC];
let pdu = build_request(1, 15, &stub);
assert_eq!(&pdu[24..], &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn request_pdu_frag_length_matches() {
let stub = vec![0xAA, 0xBB, 0xCC];
let pdu = build_request(1, 15, &stub);
let frag_len = u16::from_le_bytes([pdu[8], pdu[9]]);
assert_eq!(frag_len as usize, pdu.len());
}
#[test]
fn parse_response_extracts_stub() {
let stub = b"hello stub data";
let response_pdu = build_test_response(1, stub);
let extracted = parse_response(&response_pdu).unwrap();
assert_eq!(extracted, stub);
}
#[test]
fn parse_response_wrong_version() {
let mut pdu = build_test_response(1, b"data");
pdu[0] = 4; assert!(parse_response(&pdu).is_err());
}
#[test]
fn parse_response_wrong_type() {
let mut pdu = build_test_response(1, b"data");
pdu[2] = PDU_TYPE_REQUEST; assert!(parse_response(&pdu).is_err());
}
fn build_test_bind_ack(result: u16) -> Vec<u8> {
let mut w = WriteCursor::with_capacity(64);
w.write_u8(RPC_VERSION_MAJOR);
w.write_u8(RPC_VERSION_MINOR);
w.write_u8(PDU_TYPE_BIND_ACK);
w.write_u8(PFC_FIRST_FRAG | PFC_LAST_FRAG);
w.write_bytes(&DATA_REP);
let frag_len_pos = w.position();
w.write_u16_le(0); w.write_u16_le(0); w.write_u32_le(1);
w.write_u16_le(MAX_XMIT_FRAG);
w.write_u16_le(MAX_RECV_FRAG);
w.write_u32_le(0x12345);
w.write_u16_le(0); w.write_bytes(&[0, 0]);
w.write_u8(1); w.write_bytes(&[0, 0, 0]);
w.write_u16_le(result); w.write_u16_le(0); NDR_UUID.pack(&mut w);
w.write_u32_le(NDR_VERSION);
let total_len = w.position();
w.set_u16_le_at(frag_len_pos, total_len as u16);
w.into_inner()
}
fn build_test_response(call_id: u32, stub: &[u8]) -> Vec<u8> {
let mut w = WriteCursor::with_capacity(RPC_HEADER_SIZE + 8 + stub.len());
w.write_u8(RPC_VERSION_MAJOR);
w.write_u8(RPC_VERSION_MINOR);
w.write_u8(PDU_TYPE_RESPONSE);
w.write_u8(PFC_FIRST_FRAG | PFC_LAST_FRAG);
w.write_bytes(&DATA_REP);
let frag_len_pos = w.position();
w.write_u16_le(0); w.write_u16_le(0); w.write_u32_le(call_id);
w.write_u32_le(stub.len() as u32); w.write_u16_le(0); w.write_u8(0); w.write_u8(0);
w.write_bytes(stub);
let total_len = w.position();
w.set_u16_le_at(frag_len_pos, total_len as u16);
w.into_inner()
}
}