package httpserver import ( "bytes" "encoding/json" "io" "mime" "net/http" "reflect" "strconv" "strings" "time" "git.yuntuo.hk/k2safe/orbit/internal/fault" "git.yuntuo.hk/k2safe/orbit/internal/jsonvalue" core "git.yuntuo.hk/k2safe/orbit/model" ) const EventSubprotocol = "orbit.events.v1" const eventMaxFrame = 512 << 10 type eventFrame struct { Version string `json:"protocol_version"` Type string `json:"type"` MessageID core.ID `json:"message_id"` ReplyTo core.ID `json:"reply_to,omitempty"` SessionID core.ID `json:"session_id,omitempty"` Body json.RawMessage `json:"body,omitempty"` Error *fault.Error `json:"error,omitempty"` } type eventHello struct { SubscriptionID core.ID `json:"subscription_id"` Capacity uint32 `json:"capacity"` } type eventWelcome struct { SessionID core.ID `json:"session_id"` SubscriptionID core.ID `json:"subscription_id"` Capacity uint32 `json:"capacity"` LeaseDurationMS int64 `json:"lease_duration_ms"` HeartbeatIntervalMS int64 `json:"heartbeat_interval_ms"` ServerTime time.Time `json:"server_time"` Enabled bool `json:"enabled"` } type eventPong struct { ServerTime time.Time `json:"server_time"` Enabled bool `json:"enabled"` } func decodeEventBody(r *http.Request, out any) error { content, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil || content != "application/json" { return fault.New("UNSUPPORTED_MEDIA_TYPE", "Content-Type must be application/json") } raw, err := io.ReadAll(io.LimitReader(r.Body, eventMaxFrame+1)) if err != nil { return fault.Invalid("cannot read request body") } if len(raw) > eventMaxFrame { return fault.New("BODY_TOO_LARGE", "request body exceeds 512 KiB") } return decodeEventJSON(raw, out) } func decodeEventJSON(raw []byte, out any) error { if len(raw) == 0 || len(raw) > eventMaxFrame { return fault.Invalid("invalid event message size") } if _, err := jsonvalue.Canonical(raw); err != nil { return fault.Invalid("invalid event JSON") } typ := reflect.TypeOf(out).Elem() if exactFields(raw, typ) != nil || requiredEventFields(raw, typ) != nil { return fault.Invalid("missing, unknown or invalid event field") } d := json.NewDecoder(bytes.NewReader(raw)) d.DisallowUnknownFields() if d.Decode(out) != nil { return fault.Invalid("invalid event field value") } return nil } // requiredEventFields 同时验证显式控制意图及规范十进制字符串;可选历史游标允许0和null。 func requiredEventFields(raw []byte, typ reflect.Type) error { if typ == reflect.TypeOf(json.RawMessage{}) || typ == reflect.TypeOf(time.Time{}) { return nil } if typ.Kind() == reflect.Pointer { if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return nil } return requiredEventFields(raw, typ.Elem()) } if typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice { var values []json.RawMessage if json.Unmarshal(raw, &values) != nil { return fault.Invalid("invalid event array") } for _, v := range values { if err := requiredEventFields(v, typ.Elem()); err != nil { return err } } return nil } if typ.Kind() != reflect.Struct { return nil } var fields map[string]json.RawMessage if json.Unmarshal(raw, &fields) != nil { return fault.Invalid("invalid event object") } for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) tags := strings.Split(field.Tag.Get("json"), ",") if tags[0] == "" || tags[0] == "-" { continue } optional, numberString := false, false for _, v := range tags[1:] { optional = optional || v == "omitempty" numberString = numberString || v == "string" } value, found := fields[tags[0]] if !found { if optional { continue } return fault.Invalid("missing event field") } ft := field.Type nullable := ft.Kind() == reflect.Pointer if nullable { if bytes.Equal(bytes.TrimSpace(value), []byte("null")) { continue } ft = ft.Elem() } if ft.Kind() == reflect.Uint64 && numberString { var text string if json.Unmarshal(value, &text) != nil { return fault.Invalid("event counter must be a decimal string") } n, err := strconv.ParseUint(text, 10, 64) if err != nil || strconv.FormatUint(n, 10) != text || (!nullable && n == 0) { return fault.Invalid("invalid event counter") } } if err := requiredEventFields(value, field.Type); err != nil { return err } } return nil } func decodeEventRequest(raw []byte, hello bool) (eventFrame, error) { var frame eventFrame if err := decodeEventJSON(raw, &frame); err != nil { return frame, err } if frame.Version != "1" || frame.MessageID.Validate() != nil || len(frame.Body) == 0 { return frame, fault.Invalid("invalid event envelope") } var fields map[string]json.RawMessage _ = json.Unmarshal(raw, &fields) if fields["error"] != nil || fields["reply_to"] != nil { return frame, fault.Invalid("response fields are not allowed in event requests") } if hello { if frame.Type != "events.hello" || fields["session_id"] != nil { return frame, fault.Invalid("invalid event hello") } } else if frame.SessionID.Validate() != nil || (frame.Type != "event.ack" && frame.Type != "events.ping") { return frame, fault.Invalid("invalid event session or type") } return frame, nil }