107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
// 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
|
|
}
|