// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // Package tentaclewire shares bounded framing between the host and Go SDK. package tentaclewire import ( "bufio" "bytes" "encoding/json" "errors" "io" "unicode/utf8" ) const MaxFrameBytes = 64 * 1024 var ErrProtocol = errors.New("invalid tentacle protocol message") var ErrLimit = errors.New("tentacle resource limit exceeded") // ReadFrame bounds allocation even when the child never writes a newline. func ReadFrame(reader *bufio.Reader) ([]byte, error) { var frame []byte for { part, err := reader.ReadSlice('\n') if len(frame)+len(part) > MaxFrameBytes+1 { return nil, ErrLimit } frame = append(frame, part...) if err == nil { return frame[:len(frame)-1], nil } if errors.Is(err, bufio.ErrBufferFull) { continue } if errors.Is(err, io.EOF) && len(frame) != 0 { return nil, io.ErrUnexpectedEOF } return nil, err } } // Reject duplicate keys and excessive nesting, including in command arguments // and results. Different languages must not interpret the same frame differently. func ValidateJSON(data []byte) error { if !utf8.Valid(data) { return ErrProtocol } decoder := json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := validate_value(decoder, 0); err != nil { return ErrProtocol } if _, err := decoder.Token(); err != io.EOF { return ErrProtocol } return nil } func validate_value(decoder *json.Decoder, depth int) error { if depth > 32 { return ErrLimit } token, err := decoder.Token() if err != nil { return err } delimiter, composite := token.(json.Delim) if !composite { return nil } if delimiter != '{' && delimiter != '[' { return ErrProtocol } keys := map[string]bool{} for decoder.More() { if delimiter == '{' { keyToken, err := decoder.Token() if err != nil { return err } key, ok := keyToken.(string) if !ok || keys[key] { return ErrProtocol } keys[key] = true } if err := validate_value(decoder, depth+1); err != nil { return err } } _, err = decoder.Token() return err } func DecodeStrict(data []byte, target any) error { if err := ValidateJSON(data); err != nil { return err } decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { return ErrProtocol } return nil }