mirror of
https://github.com/soypat/lneto.git
synced 2026-08-14 03:43:45 +00:00
215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
package tls
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"unsafe"
|
|
)
|
|
|
|
// maxBuilderNest is the deepest length-prefix nesting the [Builder] supports.
|
|
// The deepest real TLS 1.3 structure is the ClientHello key_share extension:
|
|
// handshake body, extensions block, extension, client_shares list,
|
|
// key_exchange. Eight leaves generous headroom.
|
|
const maxBuilderNest = 8
|
|
|
|
// Builder writes TLS wire structures into a caller-supplied buffer that never
|
|
// grows. It exists because TLS nests length-prefixed vectors several levels
|
|
// deep and the length of each is only known after its contents are written;
|
|
// the standard answer, golang.org/x/crypto/cryptobyte, allocates.
|
|
//
|
|
// Open a length prefix, write the contents, then Close to backpatch the real
|
|
// length:
|
|
//
|
|
// var b tls.Builder
|
|
// b.Reset(buf)
|
|
// b.AddU16(uint16(tls.ExtSupportedVersions))
|
|
// b.OpenU16() // extension_data length
|
|
// b.AddU16(tls.VersionTLS13)
|
|
// b.Close()
|
|
// out, err := b.Bytes()
|
|
//
|
|
// Errors are sticky: once the destination buffer overflows or the nesting is
|
|
// misused, every later call is a no-op and [Builder.Err] reports the first
|
|
// failure. Callers therefore need to check only once, at the end.
|
|
//
|
|
// The zero Builder is not usable; call [Builder.Reset] first.
|
|
type Builder struct {
|
|
buf []byte
|
|
stack [maxBuilderNest]int32 // offset of each open length field
|
|
width [maxBuilderNest]int8 // 1, 2 or 3 byte length prefix
|
|
n int8 // open prefix count
|
|
err error
|
|
}
|
|
|
|
// Reset prepares the Builder to write into dst, discarding any previous state
|
|
// and error. The Builder writes into dst[:0] and never reallocates, so dst's
|
|
// capacity is a hard ceiling on the structure being built.
|
|
func (b *Builder) Reset(dst []byte) {
|
|
b.buf = dst[:0]
|
|
b.n = 0
|
|
b.err = nil
|
|
}
|
|
|
|
// Err returns the first error encountered since [Builder.Reset], if any.
|
|
func (b *Builder) Err() error { return b.err }
|
|
|
|
// Len returns the number of bytes written so far, including the placeholder
|
|
// bytes of any currently open length prefix.
|
|
func (b *Builder) Len() int { return len(b.buf) }
|
|
|
|
// Bytes returns the built structure. It reports an error if the Builder failed
|
|
// at any point, or if a length prefix was opened and never closed, since the
|
|
// resulting bytes would contain an unpatched placeholder.
|
|
func (b *Builder) Bytes() ([]byte, error) {
|
|
if b.err != nil {
|
|
return nil, b.err
|
|
}
|
|
if b.n != 0 {
|
|
return nil, errBuilderUnbal
|
|
}
|
|
return b.buf, nil
|
|
}
|
|
|
|
// fail records err if no error has been recorded yet.
|
|
func (b *Builder) fail(err error) {
|
|
if b.err == nil {
|
|
b.err = err
|
|
}
|
|
}
|
|
|
|
// grow extends the buffer by n bytes and returns the new region, or nil if the
|
|
// buffer is full or the Builder has already failed.
|
|
func (b *Builder) grow(n int) []byte {
|
|
if b.err != nil {
|
|
return nil
|
|
}
|
|
if n > cap(b.buf)-len(b.buf) {
|
|
b.fail(errShortBuffer)
|
|
return nil
|
|
}
|
|
start := len(b.buf)
|
|
b.buf = b.buf[:start+n]
|
|
return b.buf[start:]
|
|
}
|
|
|
|
// AddU8 appends a single byte.
|
|
func (b *Builder) AddU8(v uint8) {
|
|
if p := b.grow(1); p != nil {
|
|
p[0] = v
|
|
}
|
|
}
|
|
|
|
// AddU16 appends a big-endian uint16.
|
|
func (b *Builder) AddU16(v uint16) {
|
|
if p := b.grow(2); p != nil {
|
|
binary.BigEndian.PutUint16(p, v)
|
|
}
|
|
}
|
|
|
|
// AddU24 appends a big-endian 24-bit value, the length encoding used by
|
|
// handshake message headers and certificate entries.
|
|
func (b *Builder) AddU24(v uint32) {
|
|
if p := b.grow(3); p != nil {
|
|
p[0] = byte(v >> 16)
|
|
p[1] = byte(v >> 8)
|
|
p[2] = byte(v)
|
|
}
|
|
}
|
|
|
|
// AddU32 appends a big-endian uint32.
|
|
func (b *Builder) AddU32(v uint32) {
|
|
if p := b.grow(4); p != nil {
|
|
binary.BigEndian.PutUint32(p, v)
|
|
}
|
|
}
|
|
|
|
// AddBytes appends raw bytes.
|
|
func (b *Builder) AddBytes(v []byte) {
|
|
if p := b.grow(len(v)); p != nil {
|
|
copy(p, v)
|
|
}
|
|
}
|
|
|
|
// AddString appends the bytes of s without allocating. TLS labels and ALPN
|
|
// protocol names are naturally string constants, and converting them with
|
|
// []byte(s) would copy to the heap on every call.
|
|
func (b *Builder) AddString(s string) {
|
|
if len(s) == 0 {
|
|
return
|
|
}
|
|
// unsafe.Slice over the string's backing array; the bytes are only read,
|
|
// and only for the duration of the copy inside AddBytes.
|
|
b.AddBytes(unsafe.Slice(unsafe.StringData(s), len(s)))
|
|
}
|
|
|
|
// openN reserves width bytes for a length prefix whose value is filled in by
|
|
// the matching [Builder.Close].
|
|
func (b *Builder) openN(width int8) {
|
|
if b.err != nil {
|
|
return
|
|
}
|
|
if int(b.n) >= maxBuilderNest {
|
|
b.fail(errBuilderNest)
|
|
return
|
|
}
|
|
start := len(b.buf)
|
|
if b.grow(int(width)) == nil {
|
|
return
|
|
}
|
|
b.stack[b.n] = int32(start)
|
|
b.width[b.n] = width
|
|
b.n++
|
|
}
|
|
|
|
// OpenU8 begins a vector with a one-byte length prefix.
|
|
func (b *Builder) OpenU8() { b.openN(1) }
|
|
|
|
// OpenU16 begins a vector with a two-byte length prefix.
|
|
func (b *Builder) OpenU16() { b.openN(2) }
|
|
|
|
// OpenU24 begins a vector with a three-byte length prefix, as used by the
|
|
// handshake message header.
|
|
func (b *Builder) OpenU24() { b.openN(3) }
|
|
|
|
// Close ends the innermost open vector and backpatches its length prefix with
|
|
// the number of bytes written since the matching Open.
|
|
//
|
|
// A length that does not fit the reserved prefix width is a failure rather
|
|
// than a silent truncation: writing a value modulo 2^16 into a two-byte prefix
|
|
// would produce a structurally valid but semantically wrong record.
|
|
func (b *Builder) Close() {
|
|
if b.err != nil {
|
|
return
|
|
}
|
|
if b.n == 0 {
|
|
b.fail(errBuilderUnbal)
|
|
return
|
|
}
|
|
b.n--
|
|
start := int(b.stack[b.n])
|
|
width := int(b.width[b.n])
|
|
n := len(b.buf) - start - width
|
|
p := b.buf[start:]
|
|
switch width {
|
|
case 1:
|
|
if n > 0xff {
|
|
b.fail(errBadLength)
|
|
return
|
|
}
|
|
p[0] = byte(n)
|
|
case 2:
|
|
if n > 0xffff {
|
|
b.fail(errBadLength)
|
|
return
|
|
}
|
|
binary.BigEndian.PutUint16(p, uint16(n))
|
|
case 3:
|
|
if n > 0xffffff {
|
|
b.fail(errBadLength)
|
|
return
|
|
}
|
|
p[0] = byte(n >> 16)
|
|
p[1] = byte(n >> 8)
|
|
p[2] = byte(n)
|
|
}
|
|
}
|