You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
2.6 KiB
96 lines
2.6 KiB
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
|
|
}
|
|
|