package event import ( "encoding/json" "fmt" "github.com/dariuszjaniak/warp/server/internal/matrix/canonicaljson" "github.com/dariuszjaniak/warp/server/internal/matrix/id" "github.com/dariuszjaniak/warp/server/internal/matrix/roomversion" ) // The limits the spec puts on an event. const ( // MaxEventSize is the largest an event may be as canonical JSON, // signatures included. 64 KiB is generous for a message and small // enough that a room's state cannot be used to exhaust a server. MaxEventSize = 65536 // MaxFieldLength caps the type and state_key properties. MaxFieldLength = 255 // MaxPrevEvents and MaxAuthEvents cap the DAG references an event may // carry. The auth events selection algorithm never needs more than a // handful, so a longer list is a sign of something wrong rather than a // legitimate event. MaxPrevEvents = 20 MaxAuthEvents = 10 // MaxDepth is the largest depth an event may claim. Once a room gets // here every later event stays at the limit. MaxDepth = canonicaljson.MaxInt ) // validate applies the structural rules: the properties the spec marks // required are present and of the right type, the identifiers are // well-formed, and nothing is over its size limit. // // It deliberately does not look at the room state. Everything checkable // from the event alone is checked here so that the authorisation rules can // assume a well-formed event and concern themselves only with permission. func (e *Event) validate() error { if e.rules.EventIDFormat == roomversion.EventIDFormatExplicit { // Unreachable today: Get refuses to return rules for room versions // 1 and 2. The check is here so that adding them to the registry // fails loudly rather than computing nonsense event IDs. return fmt.Errorf("%w: room version %s carries event IDs in the event, which Warp cannot read yet", ErrInvalidEvent, e.rules.Version) } if n := len(e.raw); n > MaxEventSize { return fmt.Errorf("%w: event is %d bytes, the limit is %d", ErrInvalidEvent, n, MaxEventSize) } if e.fields.Type == "" { return fmt.Errorf("%w: type is required", ErrInvalidEvent) } if n := len(e.fields.Type); n > MaxFieldLength { return fmt.Errorf("%w: type is %d bytes, the limit is %d", ErrInvalidEvent, n, MaxFieldLength) } if err := id.ValidateUserID(e.fields.Sender); err != nil { return fmt.Errorf("%w: sender: %w", ErrInvalidEvent, err) } if sk, ok := e.StateKey(); ok { if n := len(sk); n > MaxFieldLength { return fmt.Errorf("%w: state_key is %d bytes, the limit is %d", ErrInvalidEvent, n, MaxFieldLength) } } if err := e.validateRoomID(); err != nil { return err } if err := e.validateContent(); err != nil { return err } if err := e.validateDAGReferences(); err != nil { return err } if e.fields.Depth > MaxDepth { return fmt.Errorf("%w: depth %d is above the limit of %d", ErrInvalidEvent, e.fields.Depth, MaxDepth) } if e.fields.Hashes.SHA256 == "" { return fmt.Errorf("%w: hashes.sha256 is required", ErrInvalidEvent) } if len(e.fields.Signatures) == 0 { return fmt.Errorf("%w: signatures is required", ErrInvalidEvent) } return nil } // validateRoomID checks the room_id property, which from room version 12 is // absent on the create event because the room ID is derived from it. func (e *Event) validateRoomID() error { isCreate := e.fields.Type == TypeCreate if e.rules.RoomIDIsCreateEventID && isCreate { if e.fields.RoomID != "" { // Rule 1.2 for this room version rejects it as well; failing // here too means we never compute an ID for an event whose // room is ambiguous. return fmt.Errorf("%w: an m.room.create event in a version %s room must not have a room_id", ErrInvalidEvent, e.rules.Version) } return nil } if err := id.ValidateRoomID(e.fields.RoomID); err != nil { return fmt.Errorf("%w: room_id: %w", ErrInvalidEvent, err) } return nil } // validateContent checks that content is present and is an object. The // spec marks it required on every event, and the authorisation rules and // the redaction algorithm both read into it. func (e *Event) validateContent() error { if len(e.fields.Content) == 0 { return fmt.Errorf("%w: content is required", ErrInvalidEvent) } var obj map[string]json.RawMessage if err := json.Unmarshal(e.fields.Content, &obj); err != nil { return fmt.Errorf("%w: content must be an object: %w", ErrInvalidEvent, err) } if obj == nil { return fmt.Errorf("%w: content must be an object, not null", ErrInvalidEvent) } return nil } // validateDAGReferences checks prev_events and auth_events: within their // limits, and every entry a well-formed event ID. func (e *Event) validateDAGReferences() error { if n := len(e.fields.PrevEvents); n > MaxPrevEvents { return fmt.Errorf("%w: %d prev_events, the limit is %d", ErrInvalidEvent, n, MaxPrevEvents) } if n := len(e.fields.AuthEvents); n > MaxAuthEvents { return fmt.Errorf("%w: %d auth_events, the limit is %d", ErrInvalidEvent, n, MaxAuthEvents) } for _, eventID := range e.fields.PrevEvents { if err := id.ValidateEventID(eventID); err != nil { return fmt.Errorf("%w: prev_events: %w", ErrInvalidEvent, err) } } for _, eventID := range e.fields.AuthEvents { if err := id.ValidateEventID(eventID); err != nil { return fmt.Errorf("%w: auth_events: %w", ErrInvalidEvent, err) } } return nil }