Skip to content
Open
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
369 changes: 369 additions & 0 deletions proposals/3079-low-bandwidth-csapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
# MSC3079: Low Bandwidth Client-Server API
Comment thread
kegsay marked this conversation as resolved.

The Client-Server API is not bandwidth efficient, relying on HTTP and human-readable JSON as a data
format. It is desirable to provide low bandwidth alternatives for mobile or low power devices. This
proposal specifies a CoAP/CBOR alternative to the existing HTTP/JSON API. This proposal
*will not replace* the existing HTTP/JSON APIs. Both protocols are expected to co-exist together.
This MSC has the potential to have a significant beneficial impact on mobile devices power
(and hence battery) consumption. It would also open up the possibility of the `/sync` API being used
in the background of mobile devices as a form of Matrix Push Notification, removing the need for
Comment thread
kegsay marked this conversation as resolved.
proprietary push systems such as GCM/APNS. Furthermore, it allows Matrix to run faster on
low bitrate links (e.g 1-5Kbps).

## Proposal

The current Client-Server API stack, along with the proposed alternatives are as follows:
- JSON -> CBOR
- HTTP -> CoAP
- HTTP long-polling -> CoAP OBSERVE (optional)
- TLS/TCP -> DTLS/UDP

Media endpoints are not covered by this proposal. Clients will have to use the standard HTTP CS API to upload
or download files. This proposal covers everything under `/_matrix/client`.

The rest of this proposal breaks down each option and fleshes out the implementation/rationale.

### JSON -> CBOR
Comment thread
kegsay marked this conversation as resolved.
Comment thread
kegsay marked this conversation as resolved.

JSON is human-readable and not bandwidth efficient at all. It is extensible (you can add custom keys)
and this is a property which MUST be preserved. This makes static schema based data formats such as
Protocol Buffers inappropriate for use. CBOR is widely used and is standardised as
[RFC 8949: Concise Binary Object Representation](https://tools.ietf.org/html/rfc8949). Implementations
exist in [many languages](https://cbor.io/impls.html). Any valid JSON can be represented as CBOR with
[sensible representations](https://www.rfc-editor.org/rfc/rfc8949.html#name-converting-from-json-to-cbo).

This proposal goes beyond just "Use CBOR" however. This proposal also specifies integer keys which act
Comment thread
kegsay marked this conversation as resolved.
as shorthand for long string keys. This compresses data more efficiently. This shorthand may look similar
to:
```
{
"custom_key": "value",
8: 18572837543,
}
```
Where `8` is statically mapped to a string key e.g `origin_server_ts`. This means it is possible for
a key to be defined twice: once as a number and once as a string. If this happens, the string key
MUST be used. It is NOT required that keys which have integers are represented as such; the integer
Comment thread
kegsay marked this conversation as resolved.
mapping is entirely optional. String representations of numbers e.g `"8"` MUST NOT be expanded, and
should be treated literally. As JSON does not allow integer keys, this prevents any ambiguity when
converting from CBOR to JSON. The complete key enum list is in Appendix A and represents version 1
of the CBOR key mappings.

Clients MUST set the `Content-Type` header to `application/cbor` when sending CBOR objects.
Comment thread
kegsay marked this conversation as resolved.
Similarly, servers MUST set the `Content-Type` header to `application/cbor` when responding with CBOR
to requests. Servers MUST NOT respond to requests with `Content-Type: application/json` with CBOR,
unless the `Accept` header in the client's request includes `application/cbor`.

TODO: Define test objects to and from JSON
Comment thread
kegsay marked this conversation as resolved.
Outdated

### HTTP -> CoAP

CoAP is defined in [RFC 7252: Contrained Application Protocol](https://tools.ietf.org/html/rfc7252)
Comment thread
kegsay marked this conversation as resolved.
Outdated
and is intended to be bandwidth efficient. It defines mappings to and from HTTP. Implementations exist
in [many languages](https://coap.technology/impls.html). This allows the existing HTTP CS API to be mapped
Comment thread
kegsay marked this conversation as resolved.
to CoAP request/responses.

This proposal goes beyond just "Use CoAP" however. This proposal also specifies single character path
enums which act as shorthand for certain commonly used CS API paths. This compresses data more efficiently.
This shorthand may look similar to:
```
/9/!7mqP7DYBUOmwAweF:localhost/m.room.message/$.AAABeH6obLU
Comment thread
kegsay marked this conversation as resolved.
```
which is the collapsed form of:
```
/_matrix/client/r0/rooms/!7mqP7DYBUOmwAweF:localhost/send/m.room.message/$.AAABeH6obLU
Comment thread
kegsay marked this conversation as resolved.
```
Where `9` is statically mapped to the path regexp `/_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`.
Comment thread
kegsay marked this conversation as resolved.
The path parameters in the mapping should be read left to right to determine where they should exist in the
shortened form. In this example, the short mapping is `/9/{roomId}/{eventType}/{txnId}`. Paths which do
Comment thread
kegsay marked this conversation as resolved.
not have any path parameters only have one path segment e.g `/_matrix/client/r0/sync` is just `/7`.
This path mapping is optional, and servers MUST accept the full paths. The complete path enum list is in
Appendix B and represents version 1 of the CoAP path mappings.

#### Access Tokens

CoAP defines many mappings but notably has no Option mapping for `Authorization` headers. This proposal marks
the option ID 256 as `Authorization` in accordance with the [CoAP Option Registry](https://tools.ietf.org/html/rfc7252#section-12.2)
which mandates that options IDs 0-255 are reserved for IETF review. This Option should have the same Critical,
UnSafe, NoCacheKey, Format and Length values as the `Uri-Query` Option.

In addition, this Option MAY be omitted on subsequent requests, in which case the server MUST use the last
used value for this Option. This reduces bandwidth usage by only sending the access token once per connection.
Upon establishing a new CoAP connection (determined by the underlying transport), the client MUST send the
Option again. If this Option is set on subsequent requests, servers MUST use the Option in the latest request
and discard the previously stored Option value. This makes this functionality optional from a client's perspective,
but required from a server's perspective.


### HTTP long-polling -> CoAP OBSERVE

*This feature is entirely optional. Clients can continue using HTTP long-polling with CoAP requests.*

The CS API currently relies on long-polling to retrieve new events from the server. This is bandwidth inefficient
as the background traffic is high when there are no events. This proposal allows the `/sync` endpoint to be observed
using [RFC 7641: CoAP OBSERVE](https://tools.ietf.org/html/rfc7641). The registration SHOULD be keyed off the tuple
of (access_token, coap token ID). This allows the same device to have multiple OBSERVE registrations. All pushed
Comment thread
kegsay marked this conversation as resolved.
events MUST be sent as Confirmable messages to ensure that the client received the events. The pushed events MUST
look identical to a normal `/sync` response, which includes the `next_batch` token. When the client disconnects and
reconnects with a new registration, they can use the `next_batch` token to consume where they left off. If a client
Comment thread
kegsay marked this conversation as resolved.
Outdated
doesn't acknowledge an event despite multiple attempts to do so, the registration SHOULD be removed to avoid consuming
server resources and network bandwidth.
Comment thread
kegsay marked this conversation as resolved.

Clients MAY re-register with the `/sync` endpoint if they believe their registration was deleted e.g due to loss of
internet connectivity. Servers SHOULD retry sending events in the event of a re-register request, if the registration
is active but backing off. This ensures that servers proactively send events to the client in a timely manner.

Large `/sync` responses cannot be sent as a single message. In this case, RFC 7641 mandates that the client performs
a GET request to the resource (`/sync`) and performs blockwise transfers to retrieve the data. Servers MUST ensure
that the blockwise data returned from this request matches the data being pushed e.g by remembering which `/sync`
response was last pushed to the client.

### TLS/TCP -> DTLS/UDP

This proposal allows servers to listen on UDP ports for CoAP requests. It is recommended that the port listened
on matches the same TCP port for the same functionality, e.g port 8008/udp for the CS API. Management and configuration
of the DTLS connection is out of scope for this proposal.
Comment thread
kegsay marked this conversation as resolved.

CoAP requests MUST be supported on this UDP port.

DEFLATE is a compression algorithm which can further reduce bandwidth usage. Blanket compression
Comment thread
kegsay marked this conversation as resolved.
algorithms are vulnerable to the [CRIME attack](https://tools.ietf.org/html/rfc7525#section-3.3), so any
compression algorithm must be explicitly opt-in. DTLS connections MAY support DEFLATE as a
[compression method](https://tools.ietf.org/html/rfc3749#section-2.1), and should be negotiated in the TLS
handshake.

### Versioning

In order to aid discovery, servers MUST add an extra key to `/_matrix/client/r0/versions` which indicates
Comment thread
kegsay marked this conversation as resolved.
Outdated
which low bandwidth features are supported on this server. This object looks like:
```
"m.low_bandwidth": {
"dtls": 8008, // advertise that this server runs a UDP listener for DTLS here.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"dtls": 8008, // advertise that this server runs a UDP listener for DTLS here.
"dtls_port": 8008, // advertise that this server runs a UDP listener for DTLS here.

Also, couldn't it be possible here to specify a host+port for the endpoint seperately?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm debating if that information should live in .well-known as well... Not sure.

"cbor_enum_version": 1, // which table is used for integer keys. This proposal is version 1. Omission indicates no support.
"coap_enum_version": 1, // which table is used for coap path enums. This proposal is version 1. Omission indicates no support.
Comment thread
kegsay marked this conversation as resolved.
Outdated
"observe": 1, // Set to 1 if /sync supports OBSERVE. Omission indicates no support for OBSERVE.
Comment thread
kegsay marked this conversation as resolved.
Outdated
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add enum shorthands for these fields as well?

Copy link
Copy Markdown
Member Author

@kegsay kegsay Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's tricky, because if the client doesn't support the enums then they can't read the fields to determine the versions of things they may support e.g CoAP path enums. So probably not, though that would be a good reason to make these keys shorter!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the enum is backwards compatible and these values are defined in the first version, it should work I think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client could cache the response of this for longer time, if low-bandwidth is enabled and it isn't getting errors back from the server; so trying to save every single byte on the version response doesn't make much sense to soru

```

### Summary of required features

Many features in this proposal are optional. The fundamental features required in order for a server to declare itself
as "low-bandwidth" in the `/versions` response are:
- Accept `application/cbor`. CBOR integer keys are optional.
- Accept CoAP requests with access token Option stickiness. CoAP path enums are optional.
- Accept DTLS/UDP requests.
Comment thread
kegsay marked this conversation as resolved.

Servers which support low bandwidth can advertise this by the presence of the `m.low_bandwidth` key in the `/versions` response.

## Potential issues

Browsers are currently unsupported due to their inability to send UDP traffic.
[RFC 8323: CoAP over TCP, TLS, and WebSockets](https://tools.ietf.org/html/rfc8323) may provide a
way for browsers to participate over WebSockets but this is out of scope for this proposal.

DTLS operates over UDP which is "connectionless". This makes restarting connections after restarting the server difficult.
TCP has the concept of FIN packets, which let the client know that the connection they currently are using is dead and
should be terminated. UDP has no equivalent. For this reason, restarting the server will take up to the connection timeout
value for clients to detect a dead connection. This can be shortened by modifying DTLS to send `CloseNotify` alerts when
the UDP socket receives unrecognised data.
Comment on lines +215 to +216
Copy link
Copy Markdown
Contributor

@ShadowJonathan ShadowJonathan Dec 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has a few security implications, problems, and nuances, as I note over at ShadowJonathan/DusTLS#7.

There are two issues here, both linked to UDP address spoofing (which is very easy to do);

First, an agent could send garbage data, modifying their own source address, to effectively get an amplification attack out of the server, if it continually sends CloseNotify's to the address it believes is responsible for the garbage data. This can be somewhat mitigated by having a timeout on retransmission of the CloseNotify, but this could make matrix servers (even if so slightly) a vector for DDoS amplification.

Second, this would make MITM resetting connections extremely easy, as a server needs to have a "security context" (active encryption on the connection) to be able to send alerts (of which CloseNotify is one of) securely, this would effectively allow an unsecured CloseNotify packet to control a connection, allowing DoS, combined with the ease of UDP address spoofing, this would be a major vector. This could maybe be mitigated by smart application-level handling of closed connections, and a timeout on how quickly plaintext CloseNotifys can control a connection. The connection itself could be transiently not interrupted by immediately sending a TLS Resume to the server, though then that would become a requirement for any DTLS implementation.

While both could be mitigated, I want to urge that these effectively turn the underlying DTLS layer into an attack vector, so these should really be also noted in the Security Considerations section.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make some good points. I don't really see an alternative here though. When the server gets restarted, the client doesn't know this. The client sends encrypted packets and then gets no response. Is that because of network issues or is that because the server has no idea what you're talking about anymore? When does the client decide to send a TLS Resume? Waiting for the timeout takes a very long time, which degrades the user experience significantly.

In addition, OBSERVE subscriptions are lost when the connection is interrupted in this way (as the subscriptions are all in-memory) so it becomes difficult to know if a resubscription is required or not. I don't have any good solutions here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is okay to opt-in to a "have DTLS implementations close connections upon receiving a CloseNotify from the server", but the tradeoffs should be weighed, so i'm basically only asking for this security angle to be repeated in the security considerations sector, with some warnings to implementations to not allow someone to spam CloseNotifys without any realistic interval in between, then i believe this has a proper tradeoff with the problem.


Matrix servers perform "remote echo", where messages a client send get returned in the `/sync` stream. Changing this
behaviour would invasively modify the behaviour of servers and clients. Clients and test suites often rely on this
remote echo to determine when the message has been fully processed in the server. Servers rely on incrementing sync
tokens for each new event. Suppressing remote echo would add extra complexity to handling sync streams. For these
reasons, the sync stream will remain as it is for now. In the future, low bandwidth modifications may be applied
to the sync stream, which will be negotiated when registering an OBSERVE request.

## Alternatives

HTTP/3 over QUIC (which is UDP) was considered but rejected due to large initial connection sizes, which
mandate 1200 bytes of padding.
Comment thread
kegsay marked this conversation as resolved.

## Security considerations

DTLS connections should have the same security guarantees as TLS connections. Weak ciphers should not be used.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is extremely vague, some examples or links to articles which give a better opinion on this would be appreciated.

Copy link
Copy Markdown
Contributor

@ShadowJonathan ShadowJonathan Dec 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently came across the official IANA TLS Parameters register, which provides a section of cipher suites, which are then annotated with "DTLS Readiness" and "Recommendation", maybe referencing that, and/or providing one or two "must support" ciphers, could work.

Personally, for those "must support" ciphers, i'll recommend;

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

But i'll also mention TLS_ECDHE_ECDSA_WITH_AES_128_CCM for it's ease of implementation and performance on embedded devices. Note however that it comes with a decreased security guarantee due to the use of CCM, which has been regarded as weak.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended cipher suites change over time which is why I'm not naming any specific ones. I don't mind indirecting to say "if it's on this list then you should probably support it" but I'm wary about mandating any one cipher suite in this document as that is really not the place of this MSC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not mandating, but giving implementation suggestions to start off with for interoperability, such as those specific cipher suites.

Though, maybe this is going too much into detail, I think there's not a lot of DTLS implementations out there to really have this matter all that much, if one could potentially know all of them at once at this point.


If a DTLS connection is hijacked, an attacker can send requests without the need for an access token.

OBSERVE could potentially send encrypted traffic to addresses which, in the worst case scenario, are attacker
controlled. Without the DTLS session keys this traffic should remain secure.


## Unstable prefix

The `/versions` response should use `org.matrix.msc3079.low_bandwidth` instead of `m.low_bandwidth` whilst the proposal is in review.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about also moving that dict into unstable_features?


### Appendix A: CBOR integer keys
Comment thread
kegsay marked this conversation as resolved.

This is version 1 of this table.

| Key Name | Integer |
|----------|---------|
|event_id| 1|
|type| 2|
|content| 3|
|state_key| 4|
|room_id| 5|
|sender| 6|
|user_id| 7|
|origin_server_ts| 8|
|unsigned| 9|
|prev_content| 10|
|state| 11|
|timeline| 12|
|events| 13|
|limited| 14|
|prev_batch| 15|
|transaction_id| 16|
|age| 17|
|redacted_because| 18|
|next_batch| 19|
|presence| 20|
|avatar_url| 21|
|account_data| 22|
|rooms| 23|
|join| 24|
|membership| 25|
|displayname| 26|
|body| 27|
|msgtype| 28|
|format| 29|
|formatted_body| 30|
|ephemeral| 31|
|invite_state| 32|
|leave| 33|
|third_party_invite| 34|
|is_direct| 35|
|hashes| 36|
|signatures| 37|
|depth| 38|
|prev_events| 39|
|prev_state| 40|
|auth_events| 41|
|origin| 42|
|creator| 43|
|join_rule| 44|
|history_visibility| 45|
|ban| 46|
|events_default| 47|
|kick| 48|
|redact| 49|
|state_default| 50|
|users| 51|
|users_default| 52|
|reason| 53|
|visibility| 54|
|room_alias_name| 55|
|name| 56|
|topic| 57|
|invite| 58|
|invite_3pid| 59|
|room_version| 60|
|creation_content| 61|
|initial_state| 62|
|preset| 63|
|servers| 64|
|identifier| 65|
|user| 66|
|medium| 67|
|address| 68|
|password| 69|
|token| 70|
|device_id| 71|
|initial_device_display_name| 72|
|access_token| 73|
|home_server| 74|
|well_known| 75|
|base_url| 76|
|device_lists| 77|
|to_device| 78|
|peek| 79|
|last_seen_ip| 80|
|display_name| 81|
|typing| 82|
|last_seen_ts| 83|
|algorithm| 84|
|sender_key| 85|
|session_id| 86|
|ciphertext| 87|
|one_time_keys| 88|
|timeout| 89|
|recent_rooms| 90|
|chunk| 91|
|m.fully_read| 92|
|device_keys| 93|
|failures| 94|
|device_display_name| 95|
|prev_sender| 96|
|replaces_state| 97|
|changed| 98|
|unstable_features| 99|
|versions| 100|
|devices| 101|
|errcode| 102|
|error| 103|
|room_alias| 104|

### Appendix B: CoAP Path Enums
Comment thread
kegsay marked this conversation as resolved.

This is version 1 of this table.

| Path Enum | Matrix Path Regexp |
|-----------|--------------------|
|0| /_matrix/client/versions|
|1| /_matrix/client/r0/login|
|2| /_matrix/client/r0/capabilities|
|3| /_matrix/client/r0/logout|
|4| /_matrix/client/r0/register|
|5| /_matrix/client/r0/user/{userId}/filter|
|6| /_matrix/client/r0/user/{userId}/filter/{filterId}|
|7| /_matrix/client/r0/sync|
|8| /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}|
|9| /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}|
|A| /_matrix/client/r0/rooms/{roomId}/event/{eventId}|
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we explicitly document which paths are reserved for this mode? For example is everything that isn't _matrix as the first component reserved? That seems a little broad. Is anything that doesn't start with _ reserved?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? I'm being explicit on a per path basis. Anything not in this table doesn't have an enum version. I see no benefit to saying "stuff under X may be enummed" without being more specific.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I think you mean to be explicit on the enum path e.g for reverse proxies to let through paths according to a pattern or something? Not that reverse proxies would be able to read this..

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is basically the idea. I think there will be a number of use cases including proxying, monitoring as well as just understandably to a causal reader. it would be useful to sort these into "full path" or "enummed path". Furthermore as we are reserving more enumed paths and adding more paths to the specification it would make sense to ensure that these sets don't ever collide without needing to explicitly check between the two. For example if there is a path /id I don't know if we have moved onto 2 letter enummed paths or just a short full path without reading the spec.

Additionally I assume that the /_matrix prefix was to allow the possibility of serving a homepage, .well-known and similar on the same HTTP host, it would be nice to know what set of paths an operator needs to keep free for these enummed paths.

|B| /_matrix/client/r0/rooms/{roomId}/state|
|C| /_matrix/client/r0/rooms/{roomId}/members|
|D| /_matrix/client/r0/rooms/{roomId}/joined_members|
|E| /_matrix/client/r0/rooms/{roomId}/messages|
|F| /_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}|
|G| /_matrix/client/r0/createRoom|
|H| /_matrix/client/r0/directory/room/{roomAlias}|
|I| /_matrix/client/r0/joined_rooms|
|J| /_matrix/client/r0/rooms/{roomId}/invite|
|K| /_matrix/client/r0/rooms/{roomId}/join|
|L| /_matrix/client/r0/join/{roomIdOrAlias}|
|M| /_matrix/client/r0/rooms/{roomId}/leave|
|N| /_matrix/client/r0/rooms/{roomId}/forget|
|O| /_matrix/client/r0/rooms/{roomId}/kick|
|P| /_matrix/client/r0/rooms/{roomId}/ban|
|Q| /_matrix/client/r0/rooms/{roomId}/unban|
|R| /_matrix/client/r0/directory/list/room/{roomId}|
|S| /_matrix/client/r0/publicRooms|
|T| /_matrix/client/r0/user_directory/search|
|U| /_matrix/client/r0/profile/{userId}/displayname|
|V| /_matrix/client/r0/profile/{userId}/avatar_url|
|W| /_matrix/client/r0/profile/{userId}|
|X| /_matrix/client/r0/voip/turnServer|
|Y| /_matrix/client/r0/rooms/{roomId}/typing/{userId}|
|Z| /_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}|
|a| /_matrix/client/r0/rooms/{roomId}/read_markers|
|b| /_matrix/client/r0/presence/{userId}/status|
|c| /_matrix/client/r0/sendToDevice/{eventType}/{txnId}|
|d| /_matrix/client/r0/devices|
|e| /_matrix/client/r0/devices/{deviceId}|
|f| /_matrix/client/r0/delete_devices|
|g| /_matrix/client/r0/keys/upload|
|h| /_matrix/client/r0/keys/query|
|i| /_matrix/client/r0/keys/claim|
|j| /_matrix/client/r0/keys/changes|
|k| /_matrix/client/r0/pushers|
|l| /_matrix/client/r0/pushers/set|
|m| /_matrix/client/r0/notifications|
|n| /_matrix/client/r0/pushrules/|
|o| /_matrix/client/r0/search|
|p| /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags|
|q| /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}|
|r| /_matrix/client/r0/user/{userId}/account_data/{type}|
|s| /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}|
|t| /_matrix/client/r0/rooms/{roomId}/context/{eventId}|
|u| /_matrix/client/r0/rooms/{roomId}/report/{eventId}|