remote.braille requests and events carry a typed op under a braille-stream lease: the controller's width and gestures for a controlled reader, rendered cells back to its child, and cells, local geometry and display gestures for a controller. Direction is validated per role; a full mailbox revokes the lease. No display owner is attached yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
3.9 KiB
Go
122 lines
3.9 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package tentaclewire
|
|
|
|
import "encoding/json"
|
|
|
|
const (
|
|
RemoteBrailleMaxCells = 4096
|
|
RemoteBrailleQueueSize = 16
|
|
)
|
|
|
|
// RemoteBraille carries one Braille operation. Exactly one form is valid per
|
|
// direction and role:
|
|
//
|
|
// - "size" (controlled child to host): Width is the controller's display
|
|
// width; zero stops export.
|
|
// - "gesture" (controlled child to host, or host to controller child):
|
|
// Gesture is "pan-left", "pan-right" or "route"; Index is the route cell.
|
|
// - "cells" (controller child to host, or host to controlled child): final
|
|
// dot cells, 1 to 4,096 values of 0 to 255, never text to translate.
|
|
// - "geometry" (controller child to host asks; host to controller child
|
|
// answers with Width, zero meaning no local display).
|
|
type RemoteBraille struct {
|
|
Op string `json:"op"`
|
|
Width int `json:"width,omitempty"`
|
|
Gesture string `json:"gesture,omitempty"`
|
|
Index int `json:"index,omitempty"`
|
|
Cells []int `json:"cells,omitempty"`
|
|
}
|
|
|
|
type RemoteBrailleRequest struct {
|
|
RemoteOperation
|
|
Braille RemoteBraille `json:"braille"`
|
|
}
|
|
|
|
func validCells(cells []int) bool {
|
|
if len(cells) == 0 || len(cells) > RemoteBrailleMaxCells {
|
|
return false
|
|
}
|
|
for _, cell := range cells {
|
|
if cell < 0 || cell > 255 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ValidRemoteBraille checks representation only. Role and direction are
|
|
// checked by ValidRemoteBrailleFor.
|
|
func ValidRemoteBraille(value RemoteBraille) bool {
|
|
switch value.Op {
|
|
case "size":
|
|
return value.Width >= 0 && value.Width <= RemoteBrailleMaxCells && value.Gesture == "" && value.Index == 0 && value.Cells == nil
|
|
case "geometry":
|
|
return value.Width >= 0 && value.Width <= RemoteBrailleMaxCells && value.Gesture == "" && value.Index == 0 && value.Cells == nil
|
|
case "gesture":
|
|
switch value.Gesture {
|
|
case "pan-left", "pan-right":
|
|
return value.Index == 0 && value.Width == 0 && value.Cells == nil
|
|
case "route":
|
|
return value.Index >= 0 && value.Index < RemoteBrailleMaxCells && value.Width == 0 && value.Cells == nil
|
|
}
|
|
return false
|
|
case "cells":
|
|
return validCells(value.Cells) && value.Width == 0 && value.Gesture == "" && value.Index == 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidRemoteBrailleFor checks direction: request is true for child-to-host.
|
|
func ValidRemoteBrailleFor(role string, request bool, value RemoteBraille) bool {
|
|
if !ValidRemoteBraille(value) {
|
|
return false
|
|
}
|
|
switch {
|
|
case role == "controlled" && request:
|
|
return value.Op == "size" || value.Op == "gesture"
|
|
case role == "controlled":
|
|
return value.Op == "cells"
|
|
case role == "controller" && request:
|
|
return value.Op == "cells" || value.Op == "geometry" && value.Width == 0
|
|
case role == "controller":
|
|
return value.Op == "gesture" || value.Op == "geometry"
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ValidRemoteBrailleRequest(request RemoteBrailleRequest) bool {
|
|
return len(request.Lease) == 32 && request.Epoch != 0 && request.Sequence != 0 && ValidRemoteBraille(request.Braille)
|
|
}
|
|
|
|
func (value *RemoteBraille) UnmarshalJSON(data []byte) error {
|
|
var fields map[string]json.RawMessage
|
|
if DecodeStrict(data, &fields) != nil || fields["op"] == nil {
|
|
return ErrProtocol
|
|
}
|
|
type plain RemoteBraille
|
|
var decoded plain
|
|
if DecodeStrict(data, &decoded) != nil || !ValidRemoteBraille(RemoteBraille(decoded)) {
|
|
return ErrProtocol
|
|
}
|
|
*value = RemoteBraille(decoded)
|
|
return nil
|
|
}
|
|
|
|
func (request *RemoteBrailleRequest) UnmarshalJSON(data []byte) error {
|
|
var fields map[string]json.RawMessage
|
|
if DecodeStrict(data, &fields) != nil || len(fields) != 4 || fields["lease"] == nil || fields["epoch"] == nil ||
|
|
fields["sequence"] == nil || fields["braille"] == nil {
|
|
return ErrProtocol
|
|
}
|
|
type plain RemoteBrailleRequest
|
|
var decoded plain
|
|
if DecodeStrict(data, &decoded) != nil || !ValidRemoteBrailleRequest(RemoteBrailleRequest(decoded)) {
|
|
return ErrProtocol
|
|
}
|
|
*request = RemoteBrailleRequest(decoded)
|
|
return nil
|
|
}
|