211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
// Package nvda2cthulhu accepts the local WebSocket protocol used by NVDA
|
|
// compatibility bridges and routes it to Cthulhu's speech and braille queues.
|
|
package nvda2cthulhu
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
)
|
|
|
|
type messagePackBinary string
|
|
|
|
func decodeMessagePack(reader *bytes.Reader) (any, error) {
|
|
return decodeMessagePackDepth(reader, 0)
|
|
}
|
|
|
|
func decodeMessagePackDepth(reader *bytes.Reader, depth int) (any, error) {
|
|
if depth > 64 {
|
|
return nil, errors.New("MessagePack nesting exceeds limit")
|
|
}
|
|
code, err := reader.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch {
|
|
case code <= 0x7f:
|
|
return int64(code), nil
|
|
case code >= 0xe0:
|
|
return int64(int8(code)), nil
|
|
case code >= 0x80 && code <= 0x8f:
|
|
return decodeMessagePackMap(reader, int(code&0x0f), depth)
|
|
case code >= 0x90 && code <= 0x9f:
|
|
return decodeMessagePackArray(reader, int(code&0x0f), depth)
|
|
case code >= 0xa0 && code <= 0xbf:
|
|
return decodeMessagePackString(reader, int(code&0x1f))
|
|
case code == 0xc0:
|
|
return nil, nil
|
|
case code == 0xc2:
|
|
return false, nil
|
|
case code == 0xc3:
|
|
return true, nil
|
|
case code == 0xc4 || code == 0xc5 || code == 0xc6:
|
|
length, err := decodeLength(reader, code, 0xc4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value := make([]byte, length)
|
|
if _, err := io.ReadFull(reader, value); err != nil {
|
|
return nil, err
|
|
}
|
|
return messagePackBinary(value), nil
|
|
case code >= 0xca && code <= 0xcb:
|
|
return decodeMessagePackFloat(reader, code)
|
|
case code >= 0xcc && code <= 0xd3:
|
|
return decodeMessagePackInteger(reader, code)
|
|
case code == 0xd9 || code == 0xda || code == 0xdb:
|
|
length, err := decodeLength(reader, code, 0xd9)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeMessagePackString(reader, length)
|
|
case code == 0xdc || code == 0xdd:
|
|
length, err := decodeLength(reader, code, 0xdc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeMessagePackArray(reader, length, depth)
|
|
case code == 0xde || code == 0xdf:
|
|
length, err := decodeLength(reader, code, 0xde)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeMessagePackMap(reader, length, depth)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported MessagePack type 0x%02x", code)
|
|
}
|
|
}
|
|
|
|
func decodeLength(reader *bytes.Reader, code, base byte) (int, error) {
|
|
var size int
|
|
switch code - base {
|
|
case 0:
|
|
size = 1
|
|
case 1:
|
|
size = 2
|
|
case 2:
|
|
size = 4
|
|
default:
|
|
return 0, fmt.Errorf("invalid length type 0x%02x", code)
|
|
}
|
|
buffer := make([]byte, size)
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return 0, err
|
|
}
|
|
value := uint64(0)
|
|
for _, octet := range buffer {
|
|
value = value<<8 | uint64(octet)
|
|
}
|
|
if value > uint64(maxPayload) {
|
|
return 0, errors.New("MessagePack value exceeds 64 KiB")
|
|
}
|
|
return int(value), nil
|
|
}
|
|
|
|
func decodeMessagePackString(reader *bytes.Reader, length int) (string, error) {
|
|
value := make([]byte, length)
|
|
if _, err := io.ReadFull(reader, value); err != nil {
|
|
return "", err
|
|
}
|
|
return string(value), nil
|
|
}
|
|
|
|
func decodeMessagePackFloat(reader *bytes.Reader, code byte) (any, error) {
|
|
var buffer []byte
|
|
if code == 0xca {
|
|
buffer = make([]byte, 4)
|
|
} else {
|
|
buffer = make([]byte, 8)
|
|
}
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return nil, err
|
|
}
|
|
if code == 0xca {
|
|
bits := binary.BigEndian.Uint32(buffer)
|
|
return float64(math.Float32frombits(bits)), nil
|
|
}
|
|
return math.Float64frombits(binary.BigEndian.Uint64(buffer)), nil
|
|
}
|
|
|
|
func decodeMessagePackInteger(reader *bytes.Reader, code byte) (any, error) {
|
|
switch code {
|
|
case 0xcc, 0xd0:
|
|
buffer := make([]byte, 1)
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return nil, err
|
|
}
|
|
if code == 0xcc {
|
|
return uint64(buffer[0]), nil
|
|
}
|
|
return int64(int8(buffer[0])), nil
|
|
case 0xcd, 0xd1:
|
|
buffer := make([]byte, 2)
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return nil, err
|
|
}
|
|
value := binary.BigEndian.Uint16(buffer)
|
|
if code == 0xcd {
|
|
return uint64(value), nil
|
|
}
|
|
return int64(int16(value)), nil
|
|
case 0xce, 0xd2:
|
|
buffer := make([]byte, 4)
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return nil, err
|
|
}
|
|
value := binary.BigEndian.Uint32(buffer)
|
|
if code == 0xce {
|
|
return uint64(value), nil
|
|
}
|
|
return int64(int32(value)), nil
|
|
case 0xcf, 0xd3:
|
|
buffer := make([]byte, 8)
|
|
if _, err := io.ReadFull(reader, buffer); err != nil {
|
|
return nil, err
|
|
}
|
|
value := binary.BigEndian.Uint64(buffer)
|
|
if code == 0xcf {
|
|
return value, nil
|
|
}
|
|
return int64(value), nil
|
|
default:
|
|
return nil, fmt.Errorf("invalid integer type 0x%02x", code)
|
|
}
|
|
}
|
|
|
|
func decodeMessagePackArray(reader *bytes.Reader, length, depth int) ([]any, error) {
|
|
values := make([]any, length)
|
|
for index := range values {
|
|
value, err := decodeMessagePackDepth(reader, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values[index] = value
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
func decodeMessagePackMap(reader *bytes.Reader, length, depth int) (map[any]any, error) {
|
|
values := make(map[any]any, length)
|
|
for range length {
|
|
key, err := decodeMessagePackDepth(reader, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch key.(type) {
|
|
case string, messagePackBinary:
|
|
default:
|
|
return nil, errors.New("MessagePack map key must be text")
|
|
}
|
|
value, err := decodeMessagePackDepth(reader, depth+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values[key] = value
|
|
}
|
|
return values, nil
|
|
}
|