///|
const MAGIC_COOKIE : UInt = 0x2112a442U
///|
const HEADER_LENGTH : Int = 20
///|
fn Method::code(self : Method) -> UInt16 {
match self {
Binding => 0x001
Allocate => 0x003
Refresh => 0x004
Send => 0x006
Data => 0x007
CreatePermission => 0x008
ChannelBind => 0x009
Unknown(code) => code
}
}
///|
fn Method::from_code(code : UInt16) -> Method {
match code.to_uint() {
0x001U => Binding
0x003U => Allocate
0x004U => Refresh
0x006U => Send
0x007U => Data
0x008U => CreatePermission
0x009U => ChannelBind
_ => Unknown(code)
}
}
///|
fn MessageClass::code(self : MessageClass) -> UInt {
match self {
Request => 0U
Indication => 1U
SuccessResponse => 2U
ErrorResponse => 3U
}
}
///|
fn MessageClass::from_code(code : UInt) -> MessageClass {
match code {
0U => Request
1U => Indication
2U => SuccessResponse
_ => ErrorResponse
}
}
///|
fn encode_message_type(stun_method : Method, class : MessageClass) -> UInt16 {
let method_code = stun_method.code().to_uint()
let class_code = class.code()
((method_code & 0x000fU) |
((method_code & 0x0070U) << 1) |
((method_code & 0x0f80U) << 2) |
((class_code & 0x01U) << 4) |
((class_code & 0x02U) << 7)).to_uint16()
}
///|
fn decode_message_type(
encoded : UInt16,
) -> (Method, MessageClass) raise StunError {
let encoded = encoded.to_uint()
if (encoded & 0xc000U) != 0U {
raise InvalidMessage("the two most-significant STUN type bits must be zero")
}
let method_code = ((encoded & 0x000fU) |
((encoded & 0x00e0U) >> 1) |
((encoded & 0x3e00U) >> 2)).to_uint16()
let class_code = ((encoded >> 4) & 0x01U) | ((encoded >> 7) & 0x02U)
(Method::from_code(method_code), MessageClass::from_code(class_code))
}
///|
fn read_u16(reader : @codec.Reader) -> UInt16 raise StunError {
reader.read_u16_be() catch {
InvalidLength(length) =>
raise InvalidMessage("invalid codec length \{length}")
Truncated(needed~, remaining~) =>
raise InvalidMessage(
"truncated STUN message: need \{needed} bytes, have \{remaining}",
)
}
}
///|
fn read_u32(reader : @codec.Reader) -> UInt raise StunError {
reader.read_u32_be() catch {
InvalidLength(length) =>
raise InvalidMessage("invalid codec length \{length}")
Truncated(needed~, remaining~) =>
raise InvalidMessage(
"truncated STUN message: need \{needed} bytes, have \{remaining}",
)
}
}
///|
fn read_bytes(reader : @codec.Reader, length : Int) -> Bytes raise StunError {
reader.read_bytes(length) catch {
InvalidLength(invalid_length) =>
raise InvalidMessage("invalid codec length \{invalid_length}")
Truncated(needed~, remaining~) =>
raise InvalidMessage(
"truncated STUN message: need \{needed} bytes, have \{remaining}",
)
}
}
///|
fn new_writer(capacity : Int) -> @codec.Writer raise StunError {
@codec.Writer::new(capacity~) catch {
InvalidLength(length) =>
raise InvalidMessage("invalid codec capacity \{length}")
Truncated(needed~, remaining~) =>
raise InvalidMessage(
"unexpected codec truncation: need \{needed}, have \{remaining}",
)
}
}
///|
fn Attribute::wire_length(self : Attribute) -> Int raise StunError {
validate_attribute(self.attribute_type, self.value)
let expected_padding = padding_length(self.value.length())
if self.padding.length() != expected_padding {
raise InvalidMessage("attribute padding has invalid length")
}
4 + self.value.length() + self.padding.length()
}
///|
fn Message::body_length(
self : Message,
attribute_count : Int,
) -> Int raise StunError {
if attribute_count < 0 || attribute_count > self.attributes.length() {
raise InvalidMessage("attribute count is out of bounds")
}
let mut result = 0
for index = 0; index < attribute_count; index = index + 1 {
result += self.attributes[index].wire_length()
if result > 0xffff {
raise InvalidMessage("STUN message body exceeds 65535 bytes")
}
}
result
}
///|
fn Message::encode_prefix(
self : Message,
attribute_count : Int,
declared_body_length : Int,
) -> Bytes raise StunError {
if declared_body_length < 0 ||
declared_body_length > 0xffff ||
declared_body_length % 4 != 0 {
raise InvalidMessage("invalid declared STUN body length")
}
let actual_body_length = self.body_length(attribute_count)
let writer = new_writer(HEADER_LENGTH + actual_body_length)
writer.write_u16_be(encode_message_type(self.stun_method, self.class))
writer.write_u16_be(declared_body_length.to_uint16())
writer.write_u32_be(MAGIC_COOKIE)
writer.write_bytes(self.transaction_id.as_bytes())
for index = 0; index < attribute_count; index = index + 1 {
let attribute = self.attributes[index]
writer.write_u16_be(attribute.attribute_type.code())
writer.write_u16_be(attribute.value.length().to_uint16())
writer.write_bytes(attribute.value)
writer.write_bytes(attribute.padding)
}
writer.finish()
}
///|
pub fn Message::encode(self : Message) -> Bytes raise StunError {
let body_length = self.body_length(self.attributes.length())
self.encode_prefix(self.attributes.length(), body_length)
}
///|
pub fn Message::is_stun(data : Bytes) -> Bool {
if data.length() < HEADER_LENGTH || (data[0].to_uint() & 0xc0U) != 0U {
return false
}
let body_length = ((data[2].to_uint() << 8) | data[3].to_uint()).reinterpret_as_int()
if body_length % 4 != 0 || data.length() != HEADER_LENGTH + body_length {
return false
}
let cookie = (data[4].to_uint() << 24) |
(data[5].to_uint() << 16) |
(data[6].to_uint() << 8) |
data[7].to_uint()
cookie == MAGIC_COOKIE
}
///|
pub fn Message::decode(data : Bytes) -> Message raise StunError {
if data.length() < HEADER_LENGTH {
raise InvalidMessage("STUN message is shorter than its 20-byte header")
}
let reader = @codec.Reader::new(data)
let encoded_type = read_u16(reader)
let body_length = read_u16(reader).to_int()
let cookie = read_u32(reader)
let transaction_id = TransactionId::from_bytes(read_bytes(reader, 12))
let (stun_method, class) = decode_message_type(encoded_type)
if cookie != MAGIC_COOKIE {
raise InvalidMessage("invalid STUN magic cookie")
}
if body_length % 4 != 0 {
raise InvalidMessage("STUN body length must be a multiple of four")
}
if data.length() != HEADER_LENGTH + body_length {
raise InvalidMessage("STUN body length does not match the datagram length")
}
let attributes : Array[Attribute] = []
while reader.remaining() > 0 {
if reader.remaining() < 4 {
raise InvalidMessage("truncated STUN attribute header")
}
let attribute_type = AttributeType::from_code(read_u16(reader))
let value_length = read_u16(reader).to_int()
let value = read_bytes(reader, value_length)
let padding = read_bytes(reader, padding_length(value_length))
attributes.push(Attribute::from_wire(attribute_type, value, padding))
}
Message::new(class~, stun_method~, transaction_id~, attributes~)
}