You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

760 lines
23 KiB

  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import logging
  15. from typing import (
  16. TYPE_CHECKING,
  17. Dict,
  18. List,
  19. Mapping,
  20. Optional,
  21. Sequence,
  22. Tuple,
  23. Type,
  24. Union,
  25. )
  26. from matrix_common.versionstring import get_distribution_version_string
  27. from typing_extensions import Literal
  28. from synapse.api.errors import Codes, SynapseError
  29. from synapse.api.room_versions import RoomVersions
  30. from synapse.api.urls import FEDERATION_UNSTABLE_PREFIX, FEDERATION_V2_PREFIX
  31. from synapse.federation.transport.server._base import (
  32. Authenticator,
  33. BaseFederationServlet,
  34. )
  35. from synapse.http.servlet import (
  36. parse_boolean_from_args,
  37. parse_integer_from_args,
  38. parse_string_from_args,
  39. parse_strings_from_args,
  40. )
  41. from synapse.types import JsonDict
  42. from synapse.util.ratelimitutils import FederationRateLimiter
  43. if TYPE_CHECKING:
  44. from synapse.server import HomeServer
  45. logger = logging.getLogger(__name__)
  46. issue_8631_logger = logging.getLogger("synapse.8631_debug")
  47. class BaseFederationServerServlet(BaseFederationServlet):
  48. """Abstract base class for federation servlet classes which provides a federation server handler.
  49. See BaseFederationServlet for more information.
  50. """
  51. def __init__(
  52. self,
  53. hs: "HomeServer",
  54. authenticator: Authenticator,
  55. ratelimiter: FederationRateLimiter,
  56. server_name: str,
  57. ):
  58. super().__init__(hs, authenticator, ratelimiter, server_name)
  59. self.handler = hs.get_federation_server()
  60. class FederationSendServlet(BaseFederationServerServlet):
  61. PATH = "/send/(?P<transaction_id>[^/]*)/?"
  62. # We ratelimit manually in the handler as we queue up the requests and we
  63. # don't want to fill up the ratelimiter with blocked requests.
  64. RATELIMIT = False
  65. # This is when someone is trying to send us a bunch of data.
  66. async def on_PUT(
  67. self,
  68. origin: str,
  69. content: JsonDict,
  70. query: Dict[bytes, List[bytes]],
  71. transaction_id: str,
  72. ) -> Tuple[int, JsonDict]:
  73. """Called on PUT /send/<transaction_id>/
  74. Args:
  75. transaction_id: The transaction_id associated with this request. This
  76. is *not* None.
  77. Returns:
  78. Tuple of `(code, response)`, where
  79. `response` is a python dict to be converted into JSON that is
  80. used as the response body.
  81. """
  82. # Parse the request
  83. try:
  84. transaction_data = content
  85. logger.debug("Decoded %s: %s", transaction_id, str(transaction_data))
  86. logger.info(
  87. "Received txn %s from %s. (PDUs: %d, EDUs: %d)",
  88. transaction_id,
  89. origin,
  90. len(transaction_data.get("pdus", [])),
  91. len(transaction_data.get("edus", [])),
  92. )
  93. if issue_8631_logger.isEnabledFor(logging.DEBUG):
  94. DEVICE_UPDATE_EDUS = ["m.device_list_update", "m.signing_key_update"]
  95. device_list_updates = [
  96. edu.get("content", {})
  97. for edu in transaction_data.get("edus", [])
  98. if edu.get("edu_type") in DEVICE_UPDATE_EDUS
  99. ]
  100. if device_list_updates:
  101. issue_8631_logger.debug(
  102. "received transaction [%s] including device list updates: %s",
  103. transaction_id,
  104. device_list_updates,
  105. )
  106. except Exception as e:
  107. logger.exception(e)
  108. return 400, {"error": "Invalid transaction"}
  109. code, response = await self.handler.on_incoming_transaction(
  110. origin, transaction_id, self.server_name, transaction_data
  111. )
  112. return code, response
  113. class FederationEventServlet(BaseFederationServerServlet):
  114. PATH = "/event/(?P<event_id>[^/]*)/?"
  115. # This is when someone asks for a data item for a given server data_id pair.
  116. async def on_GET(
  117. self,
  118. origin: str,
  119. content: Literal[None],
  120. query: Dict[bytes, List[bytes]],
  121. event_id: str,
  122. ) -> Tuple[int, Union[JsonDict, str]]:
  123. return await self.handler.on_pdu_request(origin, event_id)
  124. class FederationStateV1Servlet(BaseFederationServerServlet):
  125. PATH = "/state/(?P<room_id>[^/]*)/?"
  126. # This is when someone asks for all data for a given room.
  127. async def on_GET(
  128. self,
  129. origin: str,
  130. content: Literal[None],
  131. query: Dict[bytes, List[bytes]],
  132. room_id: str,
  133. ) -> Tuple[int, JsonDict]:
  134. return await self.handler.on_room_state_request(
  135. origin,
  136. room_id,
  137. parse_string_from_args(query, "event_id", None, required=True),
  138. )
  139. class FederationStateIdsServlet(BaseFederationServerServlet):
  140. PATH = "/state_ids/(?P<room_id>[^/]*)/?"
  141. async def on_GET(
  142. self,
  143. origin: str,
  144. content: Literal[None],
  145. query: Dict[bytes, List[bytes]],
  146. room_id: str,
  147. ) -> Tuple[int, JsonDict]:
  148. return await self.handler.on_state_ids_request(
  149. origin,
  150. room_id,
  151. parse_string_from_args(query, "event_id", None, required=True),
  152. )
  153. class FederationBackfillServlet(BaseFederationServerServlet):
  154. PATH = "/backfill/(?P<room_id>[^/]*)/?"
  155. async def on_GET(
  156. self,
  157. origin: str,
  158. content: Literal[None],
  159. query: Dict[bytes, List[bytes]],
  160. room_id: str,
  161. ) -> Tuple[int, JsonDict]:
  162. versions = [x.decode("ascii") for x in query[b"v"]]
  163. limit = parse_integer_from_args(query, "limit", None)
  164. if not limit:
  165. return 400, {"error": "Did not include limit param"}
  166. return await self.handler.on_backfill_request(origin, room_id, versions, limit)
  167. class FederationTimestampLookupServlet(BaseFederationServerServlet):
  168. """
  169. API endpoint to fetch the `event_id` of the closest event to the given
  170. timestamp (`ts` query parameter) in the given direction (`dir` query
  171. parameter).
  172. Useful for other homeservers when they're unable to find an event locally.
  173. `ts` is a timestamp in milliseconds where we will find the closest event in
  174. the given direction.
  175. `dir` can be `f` or `b` to indicate forwards and backwards in time from the
  176. given timestamp.
  177. GET /_matrix/federation/unstable/org.matrix.msc3030/timestamp_to_event/<roomID>?ts=<timestamp>&dir=<direction>
  178. {
  179. "event_id": ...
  180. }
  181. """
  182. PATH = "/timestamp_to_event/(?P<room_id>[^/]*)/?"
  183. PREFIX = FEDERATION_UNSTABLE_PREFIX + "/org.matrix.msc3030"
  184. async def on_GET(
  185. self,
  186. origin: str,
  187. content: Literal[None],
  188. query: Dict[bytes, List[bytes]],
  189. room_id: str,
  190. ) -> Tuple[int, JsonDict]:
  191. timestamp = parse_integer_from_args(query, "ts", required=True)
  192. direction = parse_string_from_args(
  193. query, "dir", default="f", allowed_values=["f", "b"], required=True
  194. )
  195. return await self.handler.on_timestamp_to_event_request(
  196. origin, room_id, timestamp, direction
  197. )
  198. class FederationQueryServlet(BaseFederationServerServlet):
  199. PATH = "/query/(?P<query_type>[^/]*)"
  200. # This is when we receive a server-server Query
  201. async def on_GET(
  202. self,
  203. origin: str,
  204. content: Literal[None],
  205. query: Dict[bytes, List[bytes]],
  206. query_type: str,
  207. ) -> Tuple[int, JsonDict]:
  208. args = {k.decode("utf8"): v[0].decode("utf-8") for k, v in query.items()}
  209. args["origin"] = origin
  210. return await self.handler.on_query_request(query_type, args)
  211. class FederationMakeJoinServlet(BaseFederationServerServlet):
  212. PATH = "/make_join/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
  213. async def on_GET(
  214. self,
  215. origin: str,
  216. content: Literal[None],
  217. query: Dict[bytes, List[bytes]],
  218. room_id: str,
  219. user_id: str,
  220. ) -> Tuple[int, JsonDict]:
  221. """
  222. Args:
  223. origin: The authenticated server_name of the calling server
  224. content: (GETs don't have bodies)
  225. query: Query params from the request.
  226. **kwargs: the dict mapping keys to path components as specified in
  227. the path match regexp.
  228. Returns:
  229. Tuple of (response code, response object)
  230. """
  231. supported_versions = parse_strings_from_args(query, "ver", encoding="utf-8")
  232. if supported_versions is None:
  233. supported_versions = ["1"]
  234. result = await self.handler.on_make_join_request(
  235. origin, room_id, user_id, supported_versions=supported_versions
  236. )
  237. return 200, result
  238. class FederationMakeLeaveServlet(BaseFederationServerServlet):
  239. PATH = "/make_leave/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
  240. async def on_GET(
  241. self,
  242. origin: str,
  243. content: Literal[None],
  244. query: Dict[bytes, List[bytes]],
  245. room_id: str,
  246. user_id: str,
  247. ) -> Tuple[int, JsonDict]:
  248. result = await self.handler.on_make_leave_request(origin, room_id, user_id)
  249. return 200, result
  250. class FederationV1SendLeaveServlet(BaseFederationServerServlet):
  251. PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  252. async def on_PUT(
  253. self,
  254. origin: str,
  255. content: JsonDict,
  256. query: Dict[bytes, List[bytes]],
  257. room_id: str,
  258. event_id: str,
  259. ) -> Tuple[int, Tuple[int, JsonDict]]:
  260. result = await self.handler.on_send_leave_request(origin, content, room_id)
  261. return 200, (200, result)
  262. class FederationV2SendLeaveServlet(BaseFederationServerServlet):
  263. PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  264. PREFIX = FEDERATION_V2_PREFIX
  265. async def on_PUT(
  266. self,
  267. origin: str,
  268. content: JsonDict,
  269. query: Dict[bytes, List[bytes]],
  270. room_id: str,
  271. event_id: str,
  272. ) -> Tuple[int, JsonDict]:
  273. result = await self.handler.on_send_leave_request(origin, content, room_id)
  274. return 200, result
  275. class FederationMakeKnockServlet(BaseFederationServerServlet):
  276. PATH = "/make_knock/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
  277. async def on_GET(
  278. self,
  279. origin: str,
  280. content: Literal[None],
  281. query: Dict[bytes, List[bytes]],
  282. room_id: str,
  283. user_id: str,
  284. ) -> Tuple[int, JsonDict]:
  285. # Retrieve the room versions the remote homeserver claims to support
  286. supported_versions = parse_strings_from_args(
  287. query, "ver", required=True, encoding="utf-8"
  288. )
  289. result = await self.handler.on_make_knock_request(
  290. origin, room_id, user_id, supported_versions=supported_versions
  291. )
  292. return 200, result
  293. class FederationV1SendKnockServlet(BaseFederationServerServlet):
  294. PATH = "/send_knock/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  295. async def on_PUT(
  296. self,
  297. origin: str,
  298. content: JsonDict,
  299. query: Dict[bytes, List[bytes]],
  300. room_id: str,
  301. event_id: str,
  302. ) -> Tuple[int, JsonDict]:
  303. result = await self.handler.on_send_knock_request(origin, content, room_id)
  304. return 200, result
  305. class FederationEventAuthServlet(BaseFederationServerServlet):
  306. PATH = "/event_auth/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  307. async def on_GET(
  308. self,
  309. origin: str,
  310. content: Literal[None],
  311. query: Dict[bytes, List[bytes]],
  312. room_id: str,
  313. event_id: str,
  314. ) -> Tuple[int, JsonDict]:
  315. return await self.handler.on_event_auth(origin, room_id, event_id)
  316. class FederationV1SendJoinServlet(BaseFederationServerServlet):
  317. PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  318. async def on_PUT(
  319. self,
  320. origin: str,
  321. content: JsonDict,
  322. query: Dict[bytes, List[bytes]],
  323. room_id: str,
  324. event_id: str,
  325. ) -> Tuple[int, Tuple[int, JsonDict]]:
  326. # TODO(paul): assert that event_id parsed from path actually
  327. # match those given in content
  328. result = await self.handler.on_send_join_request(origin, content, room_id)
  329. return 200, (200, result)
  330. class FederationV2SendJoinServlet(BaseFederationServerServlet):
  331. PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  332. PREFIX = FEDERATION_V2_PREFIX
  333. def __init__(
  334. self,
  335. hs: "HomeServer",
  336. authenticator: Authenticator,
  337. ratelimiter: FederationRateLimiter,
  338. server_name: str,
  339. ):
  340. super().__init__(hs, authenticator, ratelimiter, server_name)
  341. self._msc3706_enabled = hs.config.experimental.msc3706_enabled
  342. async def on_PUT(
  343. self,
  344. origin: str,
  345. content: JsonDict,
  346. query: Dict[bytes, List[bytes]],
  347. room_id: str,
  348. event_id: str,
  349. ) -> Tuple[int, JsonDict]:
  350. # TODO(paul): assert that event_id parsed from path actually
  351. # match those given in content
  352. partial_state = False
  353. if self._msc3706_enabled:
  354. partial_state = parse_boolean_from_args(
  355. query, "org.matrix.msc3706.partial_state", default=False
  356. )
  357. result = await self.handler.on_send_join_request(
  358. origin, content, room_id, caller_supports_partial_state=partial_state
  359. )
  360. return 200, result
  361. class FederationV1InviteServlet(BaseFederationServerServlet):
  362. PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  363. async def on_PUT(
  364. self,
  365. origin: str,
  366. content: JsonDict,
  367. query: Dict[bytes, List[bytes]],
  368. room_id: str,
  369. event_id: str,
  370. ) -> Tuple[int, Tuple[int, JsonDict]]:
  371. # We don't get a room version, so we have to assume its EITHER v1 or
  372. # v2. This is "fine" as the only difference between V1 and V2 is the
  373. # state resolution algorithm, and we don't use that for processing
  374. # invites
  375. result = await self.handler.on_invite_request(
  376. origin, content, room_version_id=RoomVersions.V1.identifier
  377. )
  378. # V1 federation API is defined to return a content of `[200, {...}]`
  379. # due to a historical bug.
  380. return 200, (200, result)
  381. class FederationV2InviteServlet(BaseFederationServerServlet):
  382. PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  383. PREFIX = FEDERATION_V2_PREFIX
  384. async def on_PUT(
  385. self,
  386. origin: str,
  387. content: JsonDict,
  388. query: Dict[bytes, List[bytes]],
  389. room_id: str,
  390. event_id: str,
  391. ) -> Tuple[int, JsonDict]:
  392. # TODO(paul): assert that room_id/event_id parsed from path actually
  393. # match those given in content
  394. room_version = content["room_version"]
  395. event = content["event"]
  396. invite_room_state = content["invite_room_state"]
  397. # Synapse expects invite_room_state to be in unsigned, as it is in v1
  398. # API
  399. event.setdefault("unsigned", {})["invite_room_state"] = invite_room_state
  400. result = await self.handler.on_invite_request(
  401. origin, event, room_version_id=room_version
  402. )
  403. return 200, result
  404. class FederationThirdPartyInviteExchangeServlet(BaseFederationServerServlet):
  405. PATH = "/exchange_third_party_invite/(?P<room_id>[^/]*)"
  406. async def on_PUT(
  407. self,
  408. origin: str,
  409. content: JsonDict,
  410. query: Dict[bytes, List[bytes]],
  411. room_id: str,
  412. ) -> Tuple[int, JsonDict]:
  413. await self.handler.on_exchange_third_party_invite_request(content)
  414. return 200, {}
  415. class FederationClientKeysQueryServlet(BaseFederationServerServlet):
  416. PATH = "/user/keys/query"
  417. async def on_POST(
  418. self, origin: str, content: JsonDict, query: Dict[bytes, List[bytes]]
  419. ) -> Tuple[int, JsonDict]:
  420. return await self.handler.on_query_client_keys(origin, content)
  421. class FederationUserDevicesQueryServlet(BaseFederationServerServlet):
  422. PATH = "/user/devices/(?P<user_id>[^/]*)"
  423. async def on_GET(
  424. self,
  425. origin: str,
  426. content: Literal[None],
  427. query: Dict[bytes, List[bytes]],
  428. user_id: str,
  429. ) -> Tuple[int, JsonDict]:
  430. return await self.handler.on_query_user_devices(origin, user_id)
  431. class FederationClientKeysClaimServlet(BaseFederationServerServlet):
  432. PATH = "/user/keys/claim"
  433. async def on_POST(
  434. self, origin: str, content: JsonDict, query: Dict[bytes, List[bytes]]
  435. ) -> Tuple[int, JsonDict]:
  436. response = await self.handler.on_claim_client_keys(origin, content)
  437. return 200, response
  438. class FederationGetMissingEventsServlet(BaseFederationServerServlet):
  439. # TODO(paul): Why does this path alone end with "/?" optional?
  440. PATH = "/get_missing_events/(?P<room_id>[^/]*)/?"
  441. async def on_POST(
  442. self,
  443. origin: str,
  444. content: JsonDict,
  445. query: Dict[bytes, List[bytes]],
  446. room_id: str,
  447. ) -> Tuple[int, JsonDict]:
  448. limit = int(content.get("limit", 10))
  449. earliest_events = content.get("earliest_events", [])
  450. latest_events = content.get("latest_events", [])
  451. result = await self.handler.on_get_missing_events(
  452. origin,
  453. room_id=room_id,
  454. earliest_events=earliest_events,
  455. latest_events=latest_events,
  456. limit=limit,
  457. )
  458. return 200, result
  459. class On3pidBindServlet(BaseFederationServerServlet):
  460. PATH = "/3pid/onbind"
  461. REQUIRE_AUTH = False
  462. async def on_POST(
  463. self, origin: Optional[str], content: JsonDict, query: Dict[bytes, List[bytes]]
  464. ) -> Tuple[int, JsonDict]:
  465. if "invites" in content:
  466. last_exception = None
  467. for invite in content["invites"]:
  468. try:
  469. if "signed" not in invite or "token" not in invite["signed"]:
  470. message = (
  471. "Rejecting received notification of third-"
  472. "party invite without signed: %s" % (invite,)
  473. )
  474. logger.info(message)
  475. raise SynapseError(400, message)
  476. await self.handler.exchange_third_party_invite(
  477. invite["sender"],
  478. invite["mxid"],
  479. invite["room_id"],
  480. invite["signed"],
  481. )
  482. except Exception as e:
  483. last_exception = e
  484. if last_exception:
  485. raise last_exception
  486. return 200, {}
  487. class FederationVersionServlet(BaseFederationServlet):
  488. PATH = "/version"
  489. REQUIRE_AUTH = False
  490. async def on_GET(
  491. self,
  492. origin: Optional[str],
  493. content: Literal[None],
  494. query: Dict[bytes, List[bytes]],
  495. ) -> Tuple[int, JsonDict]:
  496. return (
  497. 200,
  498. {
  499. "server": {
  500. "name": "Synapse",
  501. "version": get_distribution_version_string("matrix-synapse"),
  502. }
  503. },
  504. )
  505. class FederationRoomHierarchyServlet(BaseFederationServlet):
  506. PATH = "/hierarchy/(?P<room_id>[^/]*)"
  507. def __init__(
  508. self,
  509. hs: "HomeServer",
  510. authenticator: Authenticator,
  511. ratelimiter: FederationRateLimiter,
  512. server_name: str,
  513. ):
  514. super().__init__(hs, authenticator, ratelimiter, server_name)
  515. self.handler = hs.get_room_summary_handler()
  516. async def on_GET(
  517. self,
  518. origin: str,
  519. content: Literal[None],
  520. query: Mapping[bytes, Sequence[bytes]],
  521. room_id: str,
  522. ) -> Tuple[int, JsonDict]:
  523. suggested_only = parse_boolean_from_args(query, "suggested_only", default=False)
  524. return 200, await self.handler.get_federation_hierarchy(
  525. origin, room_id, suggested_only
  526. )
  527. class FederationRoomHierarchyUnstableServlet(FederationRoomHierarchyServlet):
  528. PREFIX = FEDERATION_UNSTABLE_PREFIX + "/org.matrix.msc2946"
  529. class RoomComplexityServlet(BaseFederationServlet):
  530. """
  531. Indicates to other servers how complex (and therefore likely
  532. resource-intensive) a public room this server knows about is.
  533. """
  534. PATH = "/rooms/(?P<room_id>[^/]*)/complexity"
  535. PREFIX = FEDERATION_UNSTABLE_PREFIX
  536. def __init__(
  537. self,
  538. hs: "HomeServer",
  539. authenticator: Authenticator,
  540. ratelimiter: FederationRateLimiter,
  541. server_name: str,
  542. ):
  543. super().__init__(hs, authenticator, ratelimiter, server_name)
  544. self._store = self.hs.get_datastores().main
  545. async def on_GET(
  546. self,
  547. origin: str,
  548. content: Literal[None],
  549. query: Dict[bytes, List[bytes]],
  550. room_id: str,
  551. ) -> Tuple[int, JsonDict]:
  552. is_public = await self._store.is_room_world_readable_or_publicly_joinable(
  553. room_id
  554. )
  555. if not is_public:
  556. raise SynapseError(404, "Room not found", errcode=Codes.INVALID_PARAM)
  557. complexity = await self._store.get_room_complexity(room_id)
  558. return 200, complexity
  559. class FederationAccountStatusServlet(BaseFederationServerServlet):
  560. PATH = "/query/account_status"
  561. PREFIX = FEDERATION_UNSTABLE_PREFIX + "/org.matrix.msc3720"
  562. def __init__(
  563. self,
  564. hs: "HomeServer",
  565. authenticator: Authenticator,
  566. ratelimiter: FederationRateLimiter,
  567. server_name: str,
  568. ):
  569. super().__init__(hs, authenticator, ratelimiter, server_name)
  570. self._account_handler = hs.get_account_handler()
  571. async def on_POST(
  572. self,
  573. origin: str,
  574. content: JsonDict,
  575. query: Mapping[bytes, Sequence[bytes]],
  576. room_id: str,
  577. ) -> Tuple[int, JsonDict]:
  578. if "user_ids" not in content:
  579. raise SynapseError(
  580. 400, "Required parameter 'user_ids' is missing", Codes.MISSING_PARAM
  581. )
  582. statuses, failures = await self._account_handler.get_account_statuses(
  583. content["user_ids"],
  584. allow_remote=False,
  585. )
  586. return 200, {"account_statuses": statuses, "failures": failures}
  587. FEDERATION_SERVLET_CLASSES: Tuple[Type[BaseFederationServlet], ...] = (
  588. FederationSendServlet,
  589. FederationEventServlet,
  590. FederationStateV1Servlet,
  591. FederationStateIdsServlet,
  592. FederationBackfillServlet,
  593. FederationTimestampLookupServlet,
  594. FederationQueryServlet,
  595. FederationMakeJoinServlet,
  596. FederationMakeLeaveServlet,
  597. FederationEventServlet,
  598. FederationV1SendJoinServlet,
  599. FederationV2SendJoinServlet,
  600. FederationV1SendLeaveServlet,
  601. FederationV2SendLeaveServlet,
  602. FederationV1InviteServlet,
  603. FederationV2InviteServlet,
  604. FederationGetMissingEventsServlet,
  605. FederationEventAuthServlet,
  606. FederationClientKeysQueryServlet,
  607. FederationUserDevicesQueryServlet,
  608. FederationClientKeysClaimServlet,
  609. FederationThirdPartyInviteExchangeServlet,
  610. On3pidBindServlet,
  611. FederationVersionServlet,
  612. RoomComplexityServlet,
  613. FederationRoomHierarchyServlet,
  614. FederationRoomHierarchyUnstableServlet,
  615. FederationV1SendKnockServlet,
  616. FederationMakeKnockServlet,
  617. FederationAccountStatusServlet,
  618. )