///|
fn read_u16(bytes : Bytes, offset : Int) -> Int raise RadiusError {
if offset < 0 || offset + 1 >= bytes.length() {
raise TooShort
}
(bytes[offset].to_int() << 8) | bytes[offset + 1].to_int()
}
///|
fn byte_at(value : Int) -> Byte {
(value & 255).to_byte()
}
///|
fn join_bytes(left : Bytes, right : Bytes) -> Bytes {
Bytes::makei(left.length() + right.length(), fn(index) {
if index < left.length() {
left[index]
} else {
right[index - left.length()]
}
})
}
///|
fn bytes_from_array(values : Array[Byte]) -> Bytes {
Bytes::makei(values.length(), fn(index) { values[index] })
}
///|
pub fn encode(packet : Packet) -> Bytes raise RadiusError {
if packet.header.authenticator.length() != 16 {
raise InvalidAuthenticator
}
let mut body = b""
for attribute in packet.attributes {
if attribute.kind.to_int() == 0 {
raise InvalidAttribute("attribute type zero is reserved")
}
if attribute.value.length() > 253 {
raise InvalidAttribute("attribute value exceeds 253 bytes")
}
let length = attribute.value.length() + 2
let prefix = bytes_from_array([attribute.kind, byte_at(length)])
body = join_bytes(body, join_bytes(prefix, attribute.value))
}
let total = 20 + body.length()
if total > 65535 {
raise InvalidLength(total)
}
let mut header = bytes_from_array([
byte_at(packet.header.code.to_int()),
packet.header.identifier,
byte_at(total >> 8),
byte_at(total),
])
header = join_bytes(header, packet.header.authenticator)
join_bytes(header, body)
}
///|
/// Encode only the attribute stream, preserving duplicate and unknown entries.
pub fn encode_attributes(
attributes : Array[Attribute],
) -> Bytes raise RadiusError {
let mut body = b""
for attribute in attributes {
if attribute.kind.to_int() == 0 {
raise InvalidAttribute("attribute type zero is reserved")
}
if attribute.value.length() > 253 {
raise InvalidAttribute("attribute value exceeds 253 bytes")
}
let length = attribute.value.length() + 2
let prefix = bytes_from_array([attribute.kind, byte_at(length)])
body = join_bytes(body, join_bytes(prefix, attribute.value))
}
body
}
///|
/// Return the number of bytes occupied by a complete packet.
pub fn packet_length(packet : Packet) -> Int raise RadiusError {
20 + encode_attributes(packet.attributes).length()
}
///|
pub fn decode(bytes : Bytes) -> Packet raise RadiusError {
if bytes.length() < 20 {
raise TooShort
}
let code_value = bytes[0].to_int()
let code = Code::from_int(code_value)
if code is Unknown(_) {
raise InvalidCode(code_value)
}
let declared = read_u16(bytes, 2)
if declared < 20 || declared != bytes.length() {
raise InvalidLength(declared)
}
let header = Header::{
code,
identifier: bytes[1],
authenticator: Bytes::makei(16, fn(index) { bytes[4 + index] }),
}
let attributes : Array[Attribute] = []
let mut offset = 20
while offset < bytes.length() {
if offset + 1 >= bytes.length() {
raise AttributeTruncated(offset)
}
let kind = bytes[offset]
if kind.to_int() == 0 {
raise InvalidAttribute("attribute type zero is reserved")
}
let length = bytes[offset + 1].to_int()
if length < 2 {
raise AttributeTooShort(offset)
}
if offset + length > bytes.length() {
raise AttributeTruncated(offset)
}
let value = Bytes::makei(length - 2, fn(index) { bytes[offset + 2 + index] })
attributes.push(Attribute::{ kind, value, })
offset = offset + length
}
Packet::{ header, attributes, }
}