// Package mls implements MLS RFC 9420 cipher suite 0x0003. // // Wire-width discipline (see CLAUDE.md "Integer widths on the wire"): // every function here that touches the wire uses explicit-width integer // types (uint8/uint16/uint32/uint64). Bare int is avoided on all wire // paths. Varints decode into uint32 — MLS §5.1.3 caps varint values at // 2^30-1 so uint32 is the exact upper bound, not a best-guess. package mls // MLS wire encoding (RFC 9420 §6). // Replaces cryptobyte.Builder/String from the Go version with direct // byte manipulation suitable for Moxie's string=[]byte model. import "errors" var ( errUnexpectedEOF = errors.New("mls: unexpected EOF") errVarintOverflow = errors.New("mls: varint exceeds 30 bits") errExcessBytes = errors.New("mls: excess bytes after unmarshal") ) // --- Reader --- // Reader consumes bytes sequentially from a buffer. type Reader struct { data []byte pos int } func newReader(data []byte) Reader { return Reader{data: data} } func (r *Reader) empty() bool { return r.pos >= len(r.data) } func (r *Reader) remaining() int { return len(r.data) - r.pos } // readByte reads one byte. func (r *Reader) readByte() (byte, bool) { if r.pos >= len(r.data) { return 0, false } b := r.data[r.pos] r.pos++ return b, true } // readN reads n bytes as a slice of the underlying buffer (no copy). func (r *Reader) readN(n int) ([]byte, bool) { if r.remaining() < n { return nil, false } out := r.data[r.pos : r.pos+n] r.pos += n return out, true } // readUint16 reads a big-endian uint16. func (r *Reader) readUint16() (uint16, bool) { if r.remaining() < 2 { return 0, false } v := uint16(r.data[r.pos])<<8 | uint16(r.data[r.pos+1]) r.pos += 2 return v, true } // readUint32 reads a big-endian uint32. func (r *Reader) readUint32() (uint32, bool) { if r.remaining() < 4 { return 0, false } v := uint32(r.data[r.pos])<<24 | uint32(r.data[r.pos+1])<<16 | uint32(r.data[r.pos+2])<<8 | uint32(r.data[r.pos+3]) r.pos += 4 return v, true } // readUint64 reads a big-endian uint64. func (r *Reader) readUint64() (uint64, bool) { if r.remaining() < 8 { return 0, false } v := uint64(r.data[r.pos])<<56 | uint64(r.data[r.pos+1])<<48 | uint64(r.data[r.pos+2])<<40 | uint64(r.data[r.pos+3])<<32 | uint64(r.data[r.pos+4])<<24 | uint64(r.data[r.pos+5])<<16 | uint64(r.data[r.pos+6])<<8 | uint64(r.data[r.pos+7]) r.pos += 8 return v, true } // readVarint reads a QUIC-style variable-length integer (RFC 9000 §16). // 2-bit prefix encodes length: 00=1byte/6bits, 01=2bytes/14bits, 10=4bytes/30bits. // MLS §5.1.3 caps values at 2^30-1, which fits in uint32 exactly — the // return type is the tight upper bound, not a convenience cast. func (r *Reader) readVarint() (uint32, bool) { b, ok := r.readByte() if !ok { return 0, false } prefix := b >> 6 if prefix == 3 { return 0, false } n := int(1) << uint(prefix) // total bytes: 1, 2, or 4 v := uint32(b & 0x3F) for i := 0; i < n-1; i++ { b, ok = r.readByte() if !ok { return 0, false } v = (v << 8) | uint32(b) } // reject non-minimal encodings if prefix >= 1 && v < uint32(1)<>8), byte(v)) } func (w *Writer) addUint32(v uint32) { if w.err != nil { return } w.buf = append(w.buf, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } func (w *Writer) addUint64(v uint64) { if w.err != nil { return } w.buf = append(w.buf, byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) } // writeVarint encodes a QUIC-style variable-length integer. // MLS §5.1.3 caps values at 2^30-1; larger values yield errVarintOverflow. func (w *Writer) writeVarint(n uint32) { if w.err != nil { return } switch { case n < 1<<6: w.buf = append(w.buf, byte(n)) case n < 1<<14: w.buf = append(w.buf, 0x40|byte(n>>8), byte(n)) case n < 1<<30: w.buf = append(w.buf, 0x80|byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) default: w.setError(errVarintOverflow) } } // writeOpaqueVec writes a varint-length-prefixed byte vector. func (w *Writer) writeOpaqueVec(data []byte) { if w.err != nil { return } w.writeVarint(uint32(len(data))) w.addBytes(data) } // writeVector encodes n elements into a varint-length-prefixed block. // Each element is written by calling f(child, i). func (w *Writer) writeVector(n int, f func(w *Writer, i int)) { if w.err != nil { return } var child Writer for i := 0; i < n; i++ { f(&child, i) if child.err != nil { w.setError(child.err) return } } w.writeOpaqueVec(child.buf) } // writeOptional writes a 1-byte presence flag. func (w *Writer) writeOptional(present bool) { if present { w.addByte(1) } else { w.addByte(0) } } func (w *Writer) bytes() ([]byte, error) { return w.buf, w.err } // --- Marshal / Unmarshal interfaces --- type unmarshaler interface { unmarshal(r *Reader) error } type marshaler interface { marshal(w *Writer) } func unmarshalRaw(raw []byte, v unmarshaler) error { r := newReader(raw) if err := v.unmarshal(&r); err != nil { return err } if !r.empty() { return errExcessBytes } return nil } func marshalRaw(v marshaler) ([]byte, error) { var w Writer v.marshal(&w) return w.bytes() }