/* SPDX-License-Identifier: MIT
*
* Phobos
*/
package phobos
import "encoding/binary"
const (
s5FramePayloadMax = 1024
s5AccMax = 2048
s5EncodeReadMax = 12288
s5BufferSize = 16384
rtpTCPHeaderSize = 2 + rtpHeaderSize
tlsRecordHeader = 5
)
type s5Encoder struct {
stream rtpStream
rng rng32
}
func (e *s5Encoder) frameSize(masking Masking, payload int) int {
switch masking {
case MaskingSTUN:
return stunDataIndHeaderSize + payload + stunPadding(payload)
case MaskingTLS:
return tlsRecordHeader + payload
default:
return rtpTCPHeaderSize + payload
}
}
func stunPadding(payload int) int {
return (4 - payload&3) & 3
}
func (e *s5Encoder) encode(masking Masking, params MediaParams, src, out []byte) int {
if masking == MaskingMEDIA && !e.stream.initialized {
e.stream.init(params, &e.rng)
}
written := 0
for len(src) > 0 {
chunk := min(len(src), s5FramePayloadMax)
if written+e.frameSize(masking, chunk) > len(out) {
return -1
}
switch masking {
case MaskingSTUN:
written += e.encodeSTUN(src[:chunk], out[written:])
case MaskingTLS:
written += encodeTLS(src[:chunk], out[written:])
default:
written += e.encodeMedia(src[:chunk], out[written:])
}
src = src[chunk:]
}
return written
}
func (e *s5Encoder) encodeSTUN(src, out []byte) int {
payload := len(src)
padding := stunPadding(payload)
messageLength := 4 + payload + padding
binary.BigEndian.PutUint16(out, stunDataIndication)
binary.BigEndian.PutUint16(out[2:], uint16(messageLength))
copy(out[4:8], stunCookie[:])
e.rng.fill(out[8:20])
binary.BigEndian.PutUint16(out[20:], stunAttrData)
binary.BigEndian.PutUint16(out[22:], uint16(payload))
copy(out[24:], src)
clear(out[24+payload : 24+payload+padding])
return stunDataIndHeaderSize + payload + padding
}
func encodeTLS(src, out []byte) int {
out[0] = 0x17
out[1] = 0x03
out[2] = 0x03
binary.BigEndian.PutUint16(out[3:], uint16(len(src)))
copy(out[tlsRecordHeader:], src)
return tlsRecordHeader + len(src)
}
func (e *s5Encoder) encodeMedia(src, out []byte) int {
binary.BigEndian.PutUint16(out, uint16(rtpHeaderSize+len(src)))
e.stream.writeHeader(out[2:])
copy(out[rtpTCPHeaderSize:], src)
return rtpTCPHeaderSize + len(src)
}
type s5Decoder struct {
acc [s5AccMax]byte
used int
work [s5AccMax + s5EncodeReadMax + 16]byte
}
func (d *s5Decoder) decode(masking Masking, params MediaParams, in, out []byte) int {
if len(in) > s5EncodeReadMax {
return -1
}
total := copy(d.work[:], d.acc[:d.used])
total += copy(d.work[total:], in)
pos, written := 0, 0
for {
consumed, produced := d.decodeFrame(masking, params, d.work[pos:total], out[written:])
if consumed < 0 {
return -1
}
if consumed == 0 {
break
}
pos += consumed
written += produced
}
left := total - pos
if left > s5AccMax {
return -1
}
copy(d.acc[:left], d.work[pos:total])
d.used = left
return written
}
func (d *s5Decoder) decodeFrame(masking Masking, params MediaParams, in, out []byte) (int, int) {
switch masking {
case MaskingSTUN:
return decodeSTUNFrame(in, out)
case MaskingTLS:
return decodeTLSFrame(in, out)
default:
return decodeMediaFrame(params, in, out)
}
}
func decodeSTUNFrame(in, out []byte) (int, int) {
if len(in) < stunDataIndHeaderSize {
return 0, 0
}
if [4]byte(in[4:8]) != stunCookie {
return -1, 0
}
if binary.BigEndian.Uint16(in) != stunDataIndication {
return -1, 0
}
total := stunHeaderSize + int(binary.BigEndian.Uint16(in[2:]))
if total < stunDataIndHeaderSize || total > s5AccMax {
return -1, 0
}
if len(in) < total {
return 0, 0
}
if binary.BigEndian.Uint16(in[20:]) != stunAttrData {
return -1, 0
}
payload := int(binary.BigEndian.Uint16(in[22:]))
if stunDataIndHeaderSize+payload > total || payload > len(out) {
return -1, 0
}
copy(out, in[stunDataIndHeaderSize:stunDataIndHeaderSize+payload])
return total, payload
}
func decodeTLSFrame(in, out []byte) (int, int) {
if len(in) < tlsRecordHeader {
return 0, 0
}
if in[0] != 0x17 || in[1] != 0x03 || in[2] != 0x03 {
return -1, 0
}
payload := int(binary.BigEndian.Uint16(in[3:]))
if payload < 1 || payload > s5AccMax {
return -1, 0
}
if len(in) < tlsRecordHeader+payload {
return 0, 0
}
if payload > len(out) {
return -1, 0
}
copy(out, in[tlsRecordHeader:tlsRecordHeader+payload])
return tlsRecordHeader + payload, payload
}
func decodeMediaFrame(params MediaParams, in, out []byte) (int, int) {
if len(in) < 2 {
return 0, 0
}
length := int(binary.BigEndian.Uint16(in))
if length < rtpHeaderSize || length > s5AccMax {
return -1, 0
}
if len(in) < 2+length {
return 0, 0
}
if in[2]&0xC0 != 0x80 {
return -1, 0
}
if params.PayloadType != 0 && in[3]&0x7F != params.PayloadType {
return -1, 0
}
payload := length - rtpHeaderSize
if payload > len(out) {
return -1, 0
}
copy(out, in[rtpTCPHeaderSize:2+length])
return 2 + length, payload
}