// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package tentaclewire import ( "bytes" "encoding/json" "unicode" "unicode/utf8" ) const MaxPresentationBytes = 8192 // Per channel, measured in UTF-8 bytes. // Presentation is a command result, never an unsolicited announcement. Empty // channels are omitted; neither channel supplies positions or routing targets. type Presentation struct { Speech string `json:"speech,omitempty"` Braille string `json:"braille,omitempty"` Uninterruptible bool `json:"uninterruptible,omitempty"` } func DecodePresentation(data []byte) (Presentation, error) { var value Presentation if err := DecodeStrict(data, &value); err != nil { return value, err } var fields map[string]json.RawMessage if json.Unmarshal(data, &fields) != nil || fields == nil || len(fields) == 0 { return value, ErrProtocol } for _, field := range fields { if bytes.Equal(field, []byte("null")) { return value, ErrProtocol } } if value.Speech == "" && value.Braille == "" { return value, ErrProtocol } if value.Uninterruptible && value.Speech == "" { return value, ErrProtocol } for _, text := range []string{value.Speech, value.Braille} { if len(text) > MaxPresentationBytes { return value, ErrLimit } if !utf8.ValidString(text) { return value, ErrProtocol } for _, char := range text { if unicode.IsControl(char) && char != '\n' && char != '\t' { return value, ErrProtocol } } } return value, nil } // The experimental optional API is additive to the command-only handshake. func ValidAPIs(apis, capabilities []string) bool { if len(apis) != len(capabilities) || len(apis) < 1 || len(apis) > 3 || apis[0] != "commands" { return false } if len(apis) >= 2 && apis[1] != "presentation" || len(apis) == 3 && apis[2] != "interaction" { return false } for index := range apis { if apis[index] != capabilities[index] { return false } } return true }