From 2e26deb37709724cbd4e69e97ea08af8aabdce26 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 11 Apr 2023 14:31:30 +0200 Subject: [PATCH] Stun protocol encoding / decoding --- tests/teststun.nim | 14 ++++++++ webrtc.nimble | 3 +- webrtc/stun.nim | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/teststun.nim create mode 100644 webrtc/stun.nim diff --git a/tests/teststun.nim b/tests/teststun.nim new file mode 100644 index 0000000..8b5eb28 --- /dev/null +++ b/tests/teststun.nim @@ -0,0 +1,14 @@ +import ../webrtc/stun +import ./asyncunit +import binary_serialization + +suite "Stun suite": + test "Stun encoding/decoding with padding": + let msg = @[ 0x00'u8, 0x01, 0x00, 0xa4, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d, 0x2b, 0x00, 0x06, 0x00, 0x63, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2b, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2b, 0x76, 0x31, 0x2f, 0x62, 0x71, 0x36, 0x67, 0x69, 0x43, 0x75, 0x4a, 0x38, 0x6e, 0x78, 0x59, 0x46, 0x4a, 0x36, 0x43, 0x63, 0x67, 0x45, 0x59, 0x58, 0x58, 0x2f, 0x78, 0x51, 0x58, 0x56, 0x4c, 0x74, 0x39, 0x71, 0x7a, 0x3a, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2b, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2b, 0x76, 0x31, 0x2f, 0x62, 0x71, 0x36, 0x67, 0x69, 0x43, 0x75, 0x4a, 0x38, 0x6e, 0x78, 0x59, 0x46, 0x4a, 0x36, 0x43, 0x63, 0x67, 0x45, 0x59, 0x58, 0x58, 0x2f, 0x78, 0x51, 0x58, 0x56, 0x4c, 0x74, 0x39, 0x71, 0x7a, 0x00, 0xc0, 0x57, 0x00, 0x04, 0x00, 0x00, 0x03, 0xe7, 0x80, 0x2a, 0x00, 0x08, 0x86, 0x63, 0xfd, 0x45, 0xa9, 0xe5, 0x4c, 0xdb, 0x00, 0x24, 0x00, 0x04, 0x6e, 0x00, 0x1e, 0xff, 0x00, 0x08, 0x00, 0x14, 0x16, 0xff, 0x70, 0x8d, 0x97, 0x0b, 0xd6, 0xa3, 0x5b, 0xac, 0x8f, 0x4c, 0x85, 0xe6, 0xa6, 0xac, 0xaa, 0x7a, 0x68, 0x27, 0x80, 0x28, 0x00, 0x04, 0x79, 0x5e, 0x03, 0xd8 ] + check msg == encode(StunMessage.decode(msg)) + + test "Error while encoding": + let msgLengthFailed = @[ 0x00'u8, 0x01, 0x00, 0xa4, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d ] + expect AssertionDefect: discard StunMessage.decode(msgLengthFailed) + let msgAttrFailed = @[ 0x00'u8, 0x01, 0x00, 0x08, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d, 0x2b, 0x28, 0x00, 0x05, 0x79, 0x5e, 0x03, 0xd8 ] + expect AssertionDefect: discard StunMessage.decode(msgAttrFailed) diff --git a/webrtc.nimble b/webrtc.nimble index b6d70e0..11c781d 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -7,4 +7,5 @@ license = "MIT" requires "nim >= 1.2.0", "chronicles >= 0.10.2", - "chronos >= 3.0.6" + "chronos >= 3.0.6", + "https://github.com/status-im/nim-binary-serialization.git" diff --git a/webrtc/stun.nim b/webrtc/stun.nim new file mode 100644 index 0000000..21e5cc6 --- /dev/null +++ b/webrtc/stun.nim @@ -0,0 +1,88 @@ +import bitops +import chronos, chronicles +import binary_serialization + +logScope: + topics = "webrtc stun" + +const + msgHeaderSize = 20 + magicCookieSeq = @[ 0x21'u8, 0x12, 0xa4, 0x42 ] + magicCookie = 0x2112a442 + +type +# Stun Attribute +# 0 1 2 3 +# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | Type | Length | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | Value (variable) .... +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + StunAttribute* = object + attributeType*: uint16 + length* {.bin_value: it.value.len.}: uint16 + value* {.bin_len: it.length.}: seq[byte] + +proc decode(T: typedesc[StunAttribute], cnt: seq[byte]): seq[StunAttribute] = + const val = @[0, 3, 2, 1] + var padding = 0 + while padding < cnt.len(): + let attr = Binary.decode(cnt[padding ..^ 1], StunAttribute) + result.add(attr) + padding += 4 + attr.value.len() + padding += val[padding mod 4] + +proc seqAttrLen(s: seq[StunAttribute]): uint16 = + for it in s: + result = it.length + 4 + +type +# Stun Header +# 0 1 2 3 +# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# |0 0| STUN Message Type | Message Length | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | Magic Cookie | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | | +# | Transaction ID (96 bits) | +# | | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + StunMessageInner = object + msgType: uint16 + length* {.bin_value: it.content.len().}: uint16 + magicCookie: uint32 + transactionId: array[12, byte] + content* {.bin_len: it.length.}: seq[byte] + + StunMessage* = object + msgType*: uint16 + transactionId*: array[12, byte] + attributes*: seq[StunAttribute] + + Stun* = object + +proc isMessage*(T: typedesc[Stun], msg: seq[byte]): bool = + msg.len >= msgHeaderSize and msg[4..<8] == magicCookie and bitand(0xC0'u8, msg[0]) == 0'u8 + +proc decode*(T: typedesc[StunMessage], msg: seq[byte]): StunMessage = + let smi = Binary.decode(msg, StunMessageInner) + return T(msgType: smi.msgType, + transactionId: smi.transactionId, + attributes: StunAttribute.decode(smi.content)) + +proc encode*(msg: StunMessage): seq[byte] = + const val = @[0, 3, 2, 1] + var smi = StunMessageInner(msgType: msg.msgType, + magicCookie: magicCookie, + transactionId: msg.transactionId) + for attr in msg.attributes: + smi.content.add(Binary.encode(attr)) + smi.content.add(newSeq[byte](val[smi.content.len() mod 4])) + + return Binary.encode(smi) + +proc new*(T: typedesc[Stun]): T = + result = T()