Files
go-omni/ws/rpc/protocol.go

130 lines
3.1 KiB
Go

package rpc
import (
"bytes"
"encoding/json"
"io"
"strings"
"unicode/utf8"
)
type envelope struct {
Version int `json:"v"`
Type string `json:"type"`
ID string `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
TimeoutMS int64 `json:"timeoutMs,omitempty"`
}
func validName(s string, max int) bool {
if s == "" || len(s) > max {
return false
}
for _, c := range s {
if !(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || strings.ContainsRune("._-/:", c)) {
return false
}
}
return true
}
func validError(e *Error) bool {
return e != nil && validName(e.Code, 64) && len(e.Message) <= 1024 && utf8.ValidString(e.Message)
}
func decode(data []byte, limit int) (envelope, error) {
var e envelope
if len(data) > limit || !utf8.Valid(data) {
return e, ErrProtocol
}
// Reject duplicate keys at every depth instead of accepting last-key-wins.
scan := json.NewDecoder(bytes.NewReader(data))
scan.UseNumber()
if err := uniqueValue(scan, 0); err != nil {
return e, ErrProtocol
}
if _, err := scan.Token(); err != io.EOF {
return e, ErrProtocol
}
d := json.NewDecoder(bytes.NewReader(data))
d.DisallowUnknownFields()
if err := d.Decode(&e); err != nil {
return e, ErrProtocol
}
if e.Version != 1 || e.TimeoutMS < 0 || e.TimeoutMS > int64((24*60*60)*1000) {
return e, ErrProtocol
}
switch e.Type {
case "call":
if !validName(e.ID, 64) || !validName(e.Method, 128) || len(e.Params) == 0 || len(e.Result) != 0 || e.Error != nil {
return e, ErrProtocol
}
case "event":
if e.ID != "" || !validName(e.Method, 128) || len(e.Params) == 0 || len(e.Result) != 0 || e.Error != nil || e.TimeoutMS != 0 {
return e, ErrProtocol
}
case "reply":
if !validName(e.ID, 64) || e.Method != "" || len(e.Params) != 0 || e.TimeoutMS != 0 || (len(e.Result) == 0) == (e.Error == nil) || (e.Error != nil && !validError(e.Error)) {
return e, ErrProtocol
}
case "cancel":
if !validName(e.ID, 64) || e.Method != "" || len(e.Params) != 0 || len(e.Result) != 0 || e.Error != nil || e.TimeoutMS != 0 {
return e, ErrProtocol
}
default:
return e, ErrProtocol
}
return e, nil
}
func uniqueValue(d *json.Decoder, depth int) error {
if depth > 32 {
return ErrProtocol
}
t, err := d.Token()
if err != nil {
return err
}
delim, ok := t.(json.Delim)
if !ok {
return nil
}
switch delim {
case '{':
seen := make(map[string]bool)
for d.More() {
k, err := d.Token()
if err != nil {
return err
}
key, ok := k.(string)
if !ok || seen[key] {
return ErrProtocol
}
seen[key] = true
if err := uniqueValue(d, depth+1); err != nil {
return err
}
}
t, err = d.Token()
if err != nil || t != json.Delim('}') {
return ErrProtocol
}
case '[':
for d.More() {
if err := uniqueValue(d, depth+1); err != nil {
return err
}
}
t, err = d.Token()
if err != nil || t != json.Delim(']') {
return ErrProtocol
}
default:
return ErrProtocol
}
return nil
}