feat: add standalone Worker protocol SDK and ACK-loss regression

This commit is contained in:
2026-09-17 13:59:33 +08:00
parent cd0dc7011b
commit 1753fcbccb
19 changed files with 1995 additions and 16 deletions

138
sdk/go/worker_codec.go Normal file
View File

@@ -0,0 +1,138 @@
package orbit
import (
"bytes"
"encoding/json"
"io"
"strconv"
"unicode/utf8"
"github.com/coder/websocket"
)
func parseWorkerFrame(kind websocket.MessageType, raw []byte) (workerFrame, error) {
var f workerFrame
if kind != websocket.MessageText || len(raw) > 512<<10 || validateWorkerJSON(raw) != nil {
return f, ErrWorkerProtocol
}
// Envelope 字段严格区分大小写,防止 encoding/json 的大小写别名覆盖控制字段。
var fields map[string]json.RawMessage
if json.Unmarshal(raw, &fields) != nil || fields == nil {
return f, ErrWorkerProtocol
}
for k := range fields {
switch k {
case "protocol_version", "type", "message_id", "reply_to", "session_id", "body", "error":
default:
return f, ErrWorkerProtocol
}
}
if json.Unmarshal(raw, &f) != nil || f.Version != "1" || !validID(f.MessageID) || f.ReplyTo != "" && !validID(f.ReplyTo) || f.SessionID != "" && !validID(f.SessionID) {
return f, ErrWorkerProtocol
}
if f.Type == "error" {
if f.Error == nil || !workerName(f.Error.Code, 64) || len(f.Error.Message) > 1024 || len(f.Body) > 0 {
return f, ErrWorkerProtocol
}
} else if f.Error != nil || len(f.Body) == 0 || bytes.Equal(f.Body, []byte("null")) {
return f, ErrWorkerProtocol
}
return f, nil
}
// validateWorkerJSON 拒绝重复键、过深嵌套及非法 Unicode,保留数字原文。
// 数值的业务范围仍由服务端规范化器最终验证;SDK 不经由 float64 重编码。
func validateWorkerJSON(raw []byte) error {
if len(raw) == 0 || !utf8.Valid(raw) || !json.Valid(raw) || !workerUnicode(raw) {
return ErrWorkerProtocol
}
d := json.NewDecoder(bytes.NewReader(raw))
d.UseNumber()
var value func(int) error
value = func(depth int) error {
if depth > 32 {
return ErrWorkerProtocol
}
t, err := d.Token()
if err != nil {
return err
}
switch t {
case json.Delim('{'):
keys := map[string]bool{}
for d.More() {
k, err := d.Token()
if err != nil {
return err
}
s, ok := k.(string)
if !ok || keys[s] {
return ErrWorkerProtocol
}
keys[s] = true
if err := value(depth + 1); err != nil {
return err
}
}
_, err = d.Token()
return err
case json.Delim('['):
for d.More() {
if err := value(depth + 1); err != nil {
return err
}
}
_, err = d.Token()
return err
}
return nil
}
if err := value(0); err != nil {
return err
}
if _, err := d.Token(); err != io.EOF {
return ErrWorkerProtocol
}
return nil
}
// JSON 的未配对 UTF-16 转义不能被静默替换为 U+FFFD。
func workerUnicode(raw []byte) bool {
inside := false
for i := 0; i < len(raw); i++ {
if raw[i] == '"' {
inside = !inside
continue
}
if !inside || raw[i] != '\\' {
continue
}
i++
if i >= len(raw) {
return false
}
if raw[i] != 'u' {
continue
}
if i+4 >= len(raw) {
return false
}
n, err := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16)
if err != nil {
return false
}
i += 4
if n < 0xd800 || n > 0xdfff {
continue
}
if n > 0xdbff || i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' {
return false
}
low, err := strconv.ParseUint(string(raw[i+3:i+7]), 16, 16)
if err != nil || low < 0xdc00 || low > 0xdfff {
return false
}
i += 6
}
return !inside
}