package directstream import ( "bytes" "encoding/json" "errors" "io" ) const ( Subprotocol = "tera.direct.v1" MaxControlFrameBytes = 4 * 1024 MaxDataFrameBytes = 64 * 1024 AccessViewer = "viewer" AccessWriter = "writer" ControlAuthenticate = "authenticate" ControlWriterRequest = "writer.request" ControlWriterRelease = "writer.release" ) var ( ErrInvalidControlFrame = errors.New("invalid direct stream control frame") ErrInvalidDataFrame = errors.New("invalid direct stream data frame") ErrStreamUnauthenticated = errors.New("direct stream is not authenticated") ErrWriterRequired = errors.New("direct stream writer access is required") ErrInvalidChannel = errors.New("direct stream channel must be CH01 through CH48") ErrInvalidAccess = errors.New("direct stream access must be viewer or writer") ) type ControlFrame struct { Type string `json:"type"` Ticket string `json:"ticket,omitempty"` } func DecodeControlFrame(payload []byte) (ControlFrame, error) { if len(payload) == 0 || len(payload) > MaxControlFrameBytes { return ControlFrame{}, ErrInvalidControlFrame } decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() var frame ControlFrame if err := decoder.Decode(&frame); err != nil { return ControlFrame{}, ErrInvalidControlFrame } var extra any if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { return ControlFrame{}, ErrInvalidControlFrame } switch frame.Type { case ControlAuthenticate: if frame.Ticket == "" { return ControlFrame{}, ErrInvalidControlFrame } case ControlWriterRequest, ControlWriterRelease: if frame.Ticket != "" { return ControlFrame{}, ErrInvalidControlFrame } default: return ControlFrame{}, ErrInvalidControlFrame } return frame, nil } func ValidateClientData(access string, authenticated bool, payload []byte) error { if !authenticated { return ErrStreamUnauthenticated } if access != AccessWriter { return ErrWriterRequired } if len(payload) == 0 || len(payload) > MaxDataFrameBytes { return ErrInvalidDataFrame } return nil } func ValidateChannel(channel string) error { if len(channel) != 4 || channel[0] != 'C' || channel[1] != 'H' || channel[2] < '0' || channel[2] > '9' || channel[3] < '0' || channel[3] > '9' { return ErrInvalidChannel } number := int(channel[2]-'0')*10 + int(channel[3]-'0') if number < 1 || number > 48 { return ErrInvalidChannel } return nil } func ValidateAccess(access string) error { if access != AccessViewer && access != AccessWriter { return ErrInvalidAccess } return nil }