diff --git a/federationclient.go b/federationclient.go index a244213b..a102860c 100644 --- a/federationclient.go +++ b/federationclient.go @@ -101,10 +101,32 @@ func (ac *FederationClient) MakeJoin( // See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms func (ac *FederationClient) SendJoin( ctx context.Context, s ServerName, event *Event, +) (res RespSendJoin, err error) { + return ac.sendJoin(ctx, s, event, false) +} + +// SendJoinPartialState sends a join m.room.member event obtained using MakeJoin via a +// remote matrix server, with a parameter indicating we support partial state in +// the response. +// This is used to join a room the local server isn't a member of. +// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms +func (ac *FederationClient) SendJoinPartialState( + ctx context.Context, s ServerName, event *Event, +) (res RespSendJoin, err error) { + return ac.sendJoin(ctx, s, event, true) +} + +// sendJoin is an internal implementation shared between SendJoin and SendJoinPartialState +func (ac *FederationClient) sendJoin( + ctx context.Context, s ServerName, event *Event, partialState bool, ) (res RespSendJoin, err error) { path := federationPathPrefixV2 + "/send_join/" + url.PathEscape(event.RoomID()) + "/" + url.PathEscape(event.EventID()) + if partialState { + path += "?org.matrix.msc3706.partial_state=true" + } + req := NewFederationRequest("PUT", s, path) if err = req.SetContent(event); err != nil { return diff --git a/federationtypes.go b/federationtypes.go index c0f0d574..cd8fef20 100644 --- a/federationtypes.go +++ b/federationtypes.go @@ -473,6 +473,10 @@ type RespSendJoin struct { // The returned join event from the remote server. Used for restricted joins, // but not guaranteed to be present as it's only since MSC3083. Event *Event `json:"event,omitempty"` + // true if the state is incomplete + PartialState bool `json:"org.matrix.msc3706.partial_state"` + // a list of servers in the room. Only returned if partial_state is set. + ServersInRoom []string `json:"org.matrix.msc3706.servers_in_room"` } // MarshalJSON implements json.Marshaller @@ -488,7 +492,17 @@ func (r RespSendJoin) MarshalJSON() ([]byte, error) { if len(fields.StateEvents) == 0 { fields.StateEvents = EventJSONs{} } - return json.Marshal(fields) + + if !r.PartialState { + return json.Marshal(fields) + } + + partialJoinFields := respSendJoinPartialStateFields{ + respSendJoinFields: fields, + PartialState: true, + ServersInRoom: r.ServersInRoom, + } + return json.Marshal(partialJoinFields) } // ToRespState returns a new RespState with the same data from the given RespPeek @@ -505,12 +519,21 @@ func (r RespPeek) ToRespState() RespState { } } +// respSendJoinFields is an intermediate struct used in RespSendJoin.MarshalJSON type respSendJoinFields struct { StateEvents EventJSONs `json:"state"` AuthEvents EventJSONs `json:"auth_chain"` Origin ServerName `json:"origin"` } +// respSendJoinPartialStateFields extends respSendJoinFields with the fields added +// when the response has incomplete state. +type respSendJoinPartialStateFields struct { + respSendJoinFields + PartialState bool `json:"org.matrix.msc3706.partial_state"` + ServersInRoom []string `json:"org.matrix.msc3706.servers_in_room"` +} + // ToRespState returns a new RespState with the same data from the given RespSendJoin func (r RespSendJoin) ToRespState() RespState { if len(r.StateEvents) == 0 { diff --git a/federationtypes_test.go b/federationtypes_test.go index 9978cf8f..391b49ed 100644 --- a/federationtypes_test.go +++ b/federationtypes_test.go @@ -2,7 +2,11 @@ package gomatrixserverlib import ( "encoding/json" + "strings" "testing" + "unicode" + + "github.com/google/go-cmp/cmp" ) const emptyRespStateResponse = `{"pdus":[],"auth_chain":[]}` @@ -90,38 +94,68 @@ func TestRespStateUnmarshalJSON(t *testing.T) { } func TestRespSendJoinMarshalJSON(t *testing.T) { + // we unmarshall and marshall an empty send-join response, and check it round-trips correctly. inputData := `{"state":[],"auth_chain":[],"origin":""}` var input RespSendJoin if err := json.Unmarshal([]byte(inputData), &input); err != nil { t.Fatal(err) } + want := RespSendJoin{ + StateEvents: []RawJSON{}, + AuthEvents: []RawJSON{}, + Origin: "", + } + if !cmp.Equal(input, want, cmp.AllowUnexported(RespSendJoin{})) { + t.Errorf("json.Unmarshal(%s): wanted %+v, got %+v", inputData, want, input) + } + gotBytes, err := json.Marshal(input) if err != nil { t.Fatal(err) } - got := string(gotBytes) - if emptyRespSendJoinResponse != got { - t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, emptyRespStateResponse, got) + t.Errorf("json.Marshal(%+v): wanted '%s', got '%s'", input, emptyRespSendJoinResponse, got) } } -func TestRespSendJoinUnmarshalJSON(t *testing.T) { - inputData := `{"state":[],"auth_chain":[],"origin":""}` +func TestRespSendJoinMarshalJSONPartialState(t *testing.T) { + inputData := `{ + "state":[],"auth_chain":[],"origin":"o1", + "org.matrix.msc3706.partial_state":true, + "org.matrix.msc3706.servers_in_room":["s1", "s2"] + }` + var input RespSendJoin if err := json.Unmarshal([]byte(inputData), &input); err != nil { t.Fatal(err) } + want := RespSendJoin{ + StateEvents: []RawJSON{}, + AuthEvents: []RawJSON{}, + Origin: "o1", + PartialState: true, + ServersInRoom: []string{"s1", "s2"}, + } + if !cmp.Equal(input, want, cmp.AllowUnexported(RespSendJoin{})) { + t.Errorf("json.Unmarshal(%s): wanted %+v, got %+v", inputData, want, input) + } + gotBytes, err := json.Marshal(input) if err != nil { t.Fatal(err) } got := string(gotBytes) - - if emptyRespSendJoinResponse != got { - t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, emptyRespStateResponse, got) + // the result should be the input, with spaces removed + wantJSON := strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, inputData) + if wantJSON != got { + t.Errorf("json.Marshal(%+v):\n wanted: '%s'\n got: '%s'", input, wantJSON, got) } } diff --git a/go.mod b/go.mod index d0eddbc4..8f6ce898 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/matrix-org/gomatrixserverlib require ( github.com/frankban/quicktest v1.7.2 // indirect - github.com/google/go-cmp v0.4.0 // indirect + github.com/google/go-cmp v0.4.0 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26