use core::convert::TryFrom;
use crate::{
checks::application::srvloc::{ensure_len, validate_packet_not_empty},
errors::application::srvloc::SrvlocPacketParseError,
};
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug)]
pub struct SrvlocPacket {
pub header: SrvlocHeader,
pub payload: SrvlocMessage,
}
#[derive(Debug)]
pub enum SrvlocHeader {
V1(SrvlocHeaderV1),
V2(SrvlocHeaderV2),
}
#[derive(Debug)]
pub struct SrvlocHeaderV2 {
pub version: u8,
pub function: u8,
pub packet_length: u32,
pub flags: u16,
pub next_extension_offset: u32,
pub xid: u16,
pub lang_tag_len: u16,
pub lang_tag: String,
}
#[derive(Debug)]
pub struct SrvlocHeaderV1 {
pub version: u8,
pub function: u8,
pub packet_length: u16, pub flags: u8,
pub dialect: u8,
pub language: String,
pub encoding: u8,
pub transaction_id: u16,
pub error_code: u16,
pub url_length: u16,
pub url: String,
pub scope_list_lengh: u16,
pub scope_list: String,
}
#[derive(Debug)]
pub enum SrvlocMessage {
Raw(Vec<u8>),
}
fn read_u16(buf: &[u8], offset: &mut usize) -> Result<u16, SrvlocPacketParseError> {
ensure_len(buf, *offset + 2)?;
let v = u16::from_be_bytes([buf[*offset], buf[*offset + 1]]);
*offset += 2;
Ok(v)
}
fn read_u24(buf: &[u8], offset: &mut usize) -> Result<u32, SrvlocPacketParseError> {
ensure_len(buf, *offset + 3)?;
let v = ((buf[*offset] as u32) << 16)
| ((buf[*offset + 1] as u32) << 8)
| (buf[*offset + 2] as u32);
*offset += 3;
Ok(v)
}
fn read_string(
buf: &[u8],
offset: &mut usize,
len: usize,
field: &'static str,
) -> Result<String, SrvlocPacketParseError> {
ensure_len(buf, *offset + len)?;
let slice = &buf[*offset..*offset + len];
*offset += len;
String::from_utf8(slice.to_vec()).map_err(|_| SrvlocPacketParseError::InvalidUtf8(field))
}
impl TryFrom<&[u8]> for SrvlocPacket {
type Error = SrvlocPacketParseError;
fn try_from(payload: &[u8]) -> Result<Self, Self::Error> {
validate_packet_not_empty(payload)?;
let version = payload[0];
match version {
1 => parse_v1_packet(payload),
2 => parse_v2_packet(payload),
other => Err(SrvlocPacketParseError::UnsupportedVersion(other)),
}
}
}
fn parse_v1_packet(payload: &[u8]) -> Result<SrvlocPacket, SrvlocPacketParseError> {
ensure_len(payload, 8)?;
let version = payload[0];
let function = payload[1];
let packet_length = u16::from_be_bytes([payload[2], payload[3]]);
let flags = payload[4];
let dialect = payload[5];
let lang_bytes = [payload[6], payload[7]];
let language = String::from_utf8(lang_bytes.to_vec())
.map_err(|_| SrvlocPacketParseError::InvalidUtf8("language"))?;
let mut offset = 8;
ensure_len(payload, offset + 1)?;
let encoding = payload[offset];
offset += 1;
let transaction_id = read_u16(payload, &mut offset)?;
let error_code = read_u16(payload, &mut offset)?;
let url_length = read_u16(payload, &mut offset)?;
let url = read_string(payload, &mut offset, url_length as usize, "url")?;
let scope_list_lengh = read_u16(payload, &mut offset)?;
let scope_list = read_string(
payload,
&mut offset,
scope_list_lengh as usize,
"scope_list",
)?;
let header_v1 = SrvlocHeaderV1 {
version,
function,
packet_length,
flags,
dialect,
language,
encoding,
transaction_id,
error_code,
url_length,
url,
scope_list_lengh,
scope_list,
};
let remaining = if offset < payload.len() {
payload[offset..].to_vec()
} else {
Vec::new()
};
Ok(SrvlocPacket {
header: SrvlocHeader::V1(header_v1),
payload: SrvlocMessage::Raw(remaining),
})
}
fn parse_v2_packet(payload: &[u8]) -> Result<SrvlocPacket, SrvlocPacketParseError> {
ensure_len(payload, 14)?;
let mut offset = 0;
let version = payload[offset];
offset += 1;
let function = payload[offset];
offset += 1;
let packet_length = read_u24(payload, &mut offset)?;
let flags = read_u16(payload, &mut offset)?;
let next_extension_offset = read_u24(payload, &mut offset)?;
let xid = read_u16(payload, &mut offset)?;
let lang_tag_len = read_u16(payload, &mut offset)?;
let lang_tag = read_string(payload, &mut offset, lang_tag_len as usize, "lang_tag")?;
let header_v2 = SrvlocHeaderV2 {
version,
function,
packet_length,
flags,
next_extension_offset,
xid,
lang_tag_len,
lang_tag,
};
let remaining = if offset < payload.len() {
payload[offset..].to_vec()
} else {
Vec::new()
};
Ok(SrvlocPacket {
header: SrvlocHeader::V2(header_v2),
payload: SrvlocMessage::Raw(remaining),
})
}