package tce import ( "sort" "unicode/utf8" ) // Encoder builds canonical TCE bytes. // // The encoder is append-only and records the first error it encounters, // after which every further operation is a no-op. This means a caller writes // a straight sequence of field writes and checks once at the end, without a // partially built object ever escaping. type Encoder struct { buf []byte err error } // NewEncoder returns an encoder with space reserved for a typical object. func NewEncoder() *Encoder { return &Encoder{buf: make([]byte, 0, 256)} } // Err returns the first error recorded, if any. func (e *Encoder) Err() error { return e.err } // fail records the first error. func (e *Encoder) fail(field string, err error) { if e.err == nil { e.err = fieldErr(field, err) } } // Bytes returns the encoded object, or an error if any write failed. // // The returned slice is a copy, so a caller cannot alter the encoder's buffer // afterwards and no two callers share backing storage. func (e *Encoder) Bytes() ([]byte, error) { if e.err != nil { return nil, e.err } out := make([]byte, len(e.buf)) copy(out, e.buf) return out, nil } // Header writes the magic, the object tag and the object version. It must be // the first call on an encoder. func (e *Encoder) Header(tag ObjectTag) { if !knownTag(tag) { e.fail("object tag", ErrObjectTag) return } e.buf = append(e.buf, Magic...) e.buf = append(e.buf, byte(tag)) e.Uvarint(Version) } // Uvarint appends a canonical LEB128 unsigned varint. func (e *Encoder) Uvarint(n uint64) { if e.err != nil { return } e.buf = AppendUvarint(e.buf, n) } // AppendUvarint appends the canonical LEB128 encoding of n to dst. // // Canonical means shortest: the loop emits a continuation byte only while // bits remain, so no encoding ever ends in a redundant 0x00 group. func AppendUvarint(dst []byte, n uint64) []byte { for n >= 0x80 { dst = append(dst, byte(n)|0x80) n >>= 7 } return append(dst, byte(n)) } // UvarintLen returns the number of bytes AppendUvarint would write. func UvarintLen(n uint64) int { l := 1 for n >= 0x80 { n >>= 7 l++ } return l } // RawBytes appends a length-prefixed byte string with no length limit of its // own. Callers use FixedBytes or the field-specific helpers instead where a // limit applies. func (e *Encoder) RawBytes(field string, b []byte) { if e.err != nil { return } e.buf = AppendUvarint(e.buf, uint64(len(b))) e.buf = append(e.buf, b...) } // FixedBytes appends a length-prefixed byte string that must have exactly the // given length. // // The length prefix is written even though the width is fixed, so that every // field stays self-delimiting and a decoder never depends on out-of-band // knowledge of a field's size. func (e *Encoder) FixedBytes(field string, b []byte, want int) { if e.err != nil { return } if len(b) != want { e.fail(field, ErrFieldSize) return } e.RawBytes(field, b) } // String appends a length-prefixed UTF-8 string after validating it. func (e *Encoder) String(field, s string, maxLen int) { if e.err != nil { return } if err := ValidateString(s, maxLen); err != nil { e.fail(field, err) return } e.RawBytes(field, []byte(s)) } // Identity appends an identity field: the address version, then the // length-prefixed raw public key. // // The raw key is encoded rather than the bech32m address text. The address is // a presentation format; the key is the identity. Signing the key means a // change to address rendering cannot invalidate existing signatures. func (e *Encoder) Identity(field string, pubkey []byte) { if e.err != nil { return } if len(pubkey) != PubKeySize { e.fail(field, ErrFieldSize) return } e.Uvarint(AddressVersion) e.RawBytes(field, pubkey) } // Timestamp appends a timestamp, enforcing the protocol's range. // // allowZero permits the single exception where 0 means "does not expire". func (e *Encoder) Timestamp(field string, ts uint64, allowZero bool) { if e.err != nil { return } if err := ValidateTimestamp(ts, allowZero); err != nil { e.fail(field, err) return } e.Uvarint(ts) } // ValidateTimestamp checks a timestamp against the specification's bounds. func ValidateTimestamp(ts uint64, allowZero bool) error { if allowZero && ts == 0 { return nil } if ts < MinTimestamp || ts > MaxTimestamp { return ErrTimestamp } return nil } // ValidateString applies the protocol's string rules. // // No normalisation is performed. The bytes are signed exactly as supplied, // because silently rewriting a user's text before signing it would mean the // user signs something other than what they reviewed. func ValidateString(s string, maxLen int) error { if len(s) > maxLen { return ErrTooLong } if !utf8.ValidString(s) { return ErrUTF8 } for _, r := range s { // utf8.ValidString already rejects surrogates and overlong forms. if r <= 0x1f || r == 0x7f || (r >= 0x80 && r <= 0x9f) { return ErrControlChar } } return nil } // Value appends a typed claim or payload value. func (e *Encoder) Value(field string, v Value) { if e.err != nil { return } switch v.tag { case ValNull, ValFalse, ValTrue: e.buf = append(e.buf, byte(v.tag)) case ValString: if err := ValidateString(v.str, MaxStringValue); err != nil { e.fail(field, err) return } e.buf = append(e.buf, byte(ValString)) e.RawBytes(field, []byte(v.str)) case ValNumber: canon, err := CanonicalNumber(v.str) if err != nil { e.fail(field, err) return } e.buf = append(e.buf, byte(ValNumber)) e.RawBytes(field, []byte(canon)) default: e.fail(field, ErrValueTag) } } // Map appends a map of keys to values in canonical order. // // Entries are sorted by raw key bytes, unsigned bytewise ascending. Duplicate // keys are an error rather than a last-one-wins situation, because leaving // the winner to the implementation would mean two conforming encoders // disagreed about the meaning of the same input. func (e *Encoder) Map(field string, m map[string]Value, minEntries int) { if e.err != nil { return } if len(m) < minEntries { e.fail(field, ErrEmptyMap) return } if len(m) > MaxMapEntries { e.fail(field, ErrTooLong) return } keys := make([]string, 0, len(m)) for k := range m { if err := ValidateKey(k); err != nil { e.fail(field, err) return } keys = append(keys, k) } // Go string comparison is bytewise on the underlying bytes, which is the // ordering the specification requires. sort.Strings(keys) // A Go map cannot hold duplicate keys, but the check is kept so that the // invariant is enforced here as well as in the decoder. for i := 1; i < len(keys); i++ { if keys[i] == keys[i-1] { e.fail(field, ErrDuplicateKey) return } } e.Uvarint(uint64(len(keys))) for _, k := range keys { e.RawBytes(field, []byte(k)) e.Value(field, m[k]) if e.err != nil { return } } } // ValidateKey applies the map key grammar from PROTOCOL.md section 6.2: // // [a-z][a-z0-9]*([._-][a-z0-9]+)* // // This is a lexical rule with no semantics attached. The protocol never // interprets a key; the restriction exists so that keys sort predictably and // cannot carry homoglyphs or bidirectional overrides. func ValidateKey(k string) error { if len(k) == 0 || len(k) > MaxKeyLen { return ErrTooLong } if k[0] < 'a' || k[0] > 'z' { return ErrKeyGrammar } prevSep := false for i := 1; i < len(k); i++ { c := k[i] switch { case c >= 'a' && c <= 'z', c >= '0' && c <= '9': prevSep = false case c == '.' || c == '_' || c == '-': // No repeated separators and none at the end. if prevSep || i == len(k)-1 { return ErrKeyGrammar } prevSep = true default: return ErrKeyGrammar } } return nil } // checkSize enforces the per-object maximum size. func (e *Encoder) checkSize(limit int) { if e.err == nil && len(e.buf) > limit { e.err = ErrObjectTooLarge } }