diff --git a/api/check_examples.py b/api/check_examples.py new file mode 100755 index 00000000..00e75263 --- /dev/null +++ b/api/check_examples.py @@ -0,0 +1,84 @@ +#! /usr/bin/env python + +import sys +import json +import os + + +def import_error(module, package, debian, error): + sys.stderr.write(( + "Error importing %(module)s: %(error)r\n" + "To install %(module)s run:\n" + " pip install %(package)s\n" + "or on Debian run:\n" + " sudo apt-get install python-%(debian)s\n" + ) % locals()) + if __name__ == '__main__': + sys.exit(1) + +try: + import jsonschema +except ImportError as e: + import_error("jsonschema", "jsonschema", "jsonschema", e) + raise + +try: + import yaml +except ImportError as e: + import_error("yaml", "PyYAML", "yaml", e) + raise + + +def check_response(filepath, request, code, response): + example = None + try: + example_json = response.get('examples', {}).get('application/json') + if example_json: + example = json.loads(example_json) + except Exception as e: + raise ValueError("Error parsing JSON example response for %r %r" % ( + request, code + ), e) + schema = response.get('schema') + fileurl = "file://" + os.path.abspath(filepath) + if example and schema: + try: + print ("Checking schema for: %r %r %r" % (filepath, request, code)) + # Setting the 'id' tells jsonschema where the file is so that it + # can correctly resolve relative $ref references in the schema + schema['id'] = fileurl + jsonschema.validate(example, schema) + except Exception as e: + raise ValueError("Error validating JSON schema for %r %r" % ( + request, code + ), e) + + +def check_swagger_file(filepath): + with open(filepath) as f: + swagger = yaml.load(f) + + for path, path_api in swagger.get('paths', {}).items(): + for method, request_api in path_api.items(): + request = "%s %s" % (method.upper(), path) + try: + responses = request_api['responses'] + except KeyError: + raise ValueError("No responses for %r" % (request,)) + for code, response in responses.items(): + check_response(filepath, request, code, response) + + +if __name__ == '__main__': + paths = sys.argv[1:] + if not paths: + paths = [] + for (root, dirs, files) in os.walk(os.curdir): + for filename in files: + if filename.endswith(".yaml"): + paths.append(os.path.join(root, filename)) + for path in paths: + try: + check_swagger_file(path) + except Exception as e: + raise ValueError("Error checking file %r" % (path,), e) diff --git a/api/client-server/v1/core b/api/client-server/v1/core deleted file mode 120000 index 9fc0cd87..00000000 --- a/api/client-server/v1/core +++ /dev/null @@ -1 +0,0 @@ -events/core \ No newline at end of file diff --git a/api/client-server/v1/core-event-schema b/api/client-server/v1/core-event-schema new file mode 120000 index 00000000..045aecb0 --- /dev/null +++ b/api/client-server/v1/core-event-schema @@ -0,0 +1 @@ +v1-event-schema/core-event-schema \ No newline at end of file diff --git a/api/client-server/v1/presence.yaml b/api/client-server/v1/presence.yaml index 472a2d7f..5684398b 100644 --- a/api/client-server/v1/presence.yaml +++ b/api/client-server/v1/presence.yaml @@ -101,7 +101,7 @@ paths: The length of time in milliseconds since an action was performed by this user. status_msg: - type: string + type: [string, "null"] description: The state message for this user if one was set. 404: description: |- @@ -185,7 +185,7 @@ paths: "last_active_ago": 395, "presence": "offline", "user_id": "@alice:matrix.org" - } + }, "type": "m.presence" }, { @@ -195,7 +195,7 @@ paths: "last_active_ago": 16874, "presence": "online", "user_id": "@marisa:matrix.org" - } + }, "type": "m.presence" } ] @@ -205,4 +205,4 @@ paths: type: object title: PresenceEvent allOf: - - "$ref": "events/core/event.json" + - "$ref": "core-event-schema/event.json" diff --git a/api/client-server/v1/sync.yaml b/api/client-server/v1/sync.yaml index b9525491..833c425a 100644 --- a/api/client-server/v1/sync.yaml +++ b/api/client-server/v1/sync.yaml @@ -82,7 +82,7 @@ paths: type: object title: RoomEvent allOf: - - "$ref": "events/core/room_event.json" + - "$ref": "core-event-schema/room_event.json" 400: description: "Bad pagination ``from`` parameter." "/initialSync": @@ -253,7 +253,7 @@ paths: type: object title: Event allOf: - - "$ref": "events/core/event.json" + - "$ref": "core-event-schema/event.json" rooms: type: array items: @@ -294,7 +294,7 @@ paths: type: object title: RoomEvent allOf: - - "$ref": "events/core/room_event.json" + - "$ref": "core-event-schema/room_event.json" required: ["start", "end", "chunk"] state: type: array @@ -307,7 +307,7 @@ paths: title: StateEvent type: object allOf: - - "$ref": "events/core/state_event.json" + - "$ref": "core-event-schema/state_event.json" visibility: type: string enum: ["private", "public"] @@ -343,13 +343,13 @@ paths: "body": "Hello world!", "msgtype": "m.text" }, - "room_id:" "!wfgy43Sg4a:matrix.org", + "room_id:": "!wfgy43Sg4a:matrix.org", "user_id": "@bob:matrix.org", "event_id": "$asfDuShaf7Gafaw:matrix.org", "type": "m.room.message" } schema: allOf: - - "$ref": "events/core/event.json" + - "$ref": "core-event-schema/event.json" 404: description: The event was not found or you do not have permission to read this event. diff --git a/api/client-server/v1/events b/api/client-server/v1/v1-event-schema similarity index 100% rename from api/client-server/v1/events rename to api/client-server/v1/v1-event-schema diff --git a/event-schemas/schema/v1/core-event-schema/core-event-schema b/event-schemas/schema/v1/core-event-schema/core-event-schema new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/event-schemas/schema/v1/core-event-schema/core-event-schema @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/event-schemas/schema/v1/core/event.json b/event-schemas/schema/v1/core-event-schema/event.json similarity index 100% rename from event-schemas/schema/v1/core/event.json rename to event-schemas/schema/v1/core-event-schema/event.json diff --git a/event-schemas/schema/v1/core/msgtype_infos/image_info.json b/event-schemas/schema/v1/core-event-schema/msgtype_infos/image_info.json similarity index 100% rename from event-schemas/schema/v1/core/msgtype_infos/image_info.json rename to event-schemas/schema/v1/core-event-schema/msgtype_infos/image_info.json diff --git a/event-schemas/schema/v1/core/room_event.json b/event-schemas/schema/v1/core-event-schema/room_event.json similarity index 93% rename from event-schemas/schema/v1/core/room_event.json rename to event-schemas/schema/v1/core-event-schema/room_event.json index 7ac66b30..d5413f8a 100644 --- a/event-schemas/schema/v1/core/room_event.json +++ b/event-schemas/schema/v1/core-event-schema/room_event.json @@ -3,7 +3,7 @@ "title": "Room Event", "description": "In addition to the Event fields, Room Events MUST have the following additional field.", "allOf":[{ - "$ref": "core/event.json" + "$ref": "core-event-schema/event.json" }], "properties": { "room_id": { diff --git a/event-schemas/schema/v1/core/state_event.json b/event-schemas/schema/v1/core-event-schema/state_event.json similarity index 93% rename from event-schemas/schema/v1/core/state_event.json rename to event-schemas/schema/v1/core-event-schema/state_event.json index 60de9413..f70118f4 100644 --- a/event-schemas/schema/v1/core/state_event.json +++ b/event-schemas/schema/v1/core-event-schema/state_event.json @@ -3,7 +3,7 @@ "title": "State Event", "description": "In addition to the Room Event fields, State Events have the following additional fields.", "allOf":[{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "state_key": { diff --git a/event-schemas/schema/v1/m.call.answer b/event-schemas/schema/v1/m.call.answer index 3b2c48d4..f11a61ba 100644 --- a/event-schemas/schema/v1/m.call.answer +++ b/event-schemas/schema/v1/m.call.answer @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by the callee when they wish to answer the call.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.candidates b/event-schemas/schema/v1/m.call.candidates index f3c86296..52e06705 100644 --- a/event-schemas/schema/v1/m.call.candidates +++ b/event-schemas/schema/v1/m.call.candidates @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by callers after sending an invite and by the callee after answering. Its purpose is to give the other party additional ICE candidates to try using to communicate.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.hangup b/event-schemas/schema/v1/m.call.hangup index 0f253ef9..02667635 100644 --- a/event-schemas/schema/v1/m.call.hangup +++ b/event-schemas/schema/v1/m.call.hangup @@ -2,7 +2,7 @@ "type": "object", "description": "Sent by either party to signal their termination of the call. This can be sent either once the call has has been established or before to abort the call.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.invite b/event-schemas/schema/v1/m.call.invite index 393e2455..585403d0 100644 --- a/event-schemas/schema/v1/m.call.invite +++ b/event-schemas/schema/v1/m.call.invite @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by the caller when they wish to establish a call.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.aliases b/event-schemas/schema/v1/m.room.aliases index 38fd7d18..43cc9dbc 100644 --- a/event-schemas/schema/v1/m.room.aliases +++ b/event-schemas/schema/v1/m.room.aliases @@ -3,7 +3,7 @@ "title": "Informs the room about what room aliases it has been given.", "description": "This event is sent by a homeserver directly to inform of changes to the list of aliases it knows about for that room. The ``state_key`` for this event is set to the homeserver which owns the room alias. The entire set of known aliases for the room is the union of all the ``m.room.aliases`` events, one for each homeserver. Clients **should** check the validity of any room alias given in this list before presenting it to the user as trusted fact. The lists given by this event should be considered simply as advice on which aliases might exist, for which the client can perform the lookup to confirm whether it receives the correct room ID.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.canonical_alias b/event-schemas/schema/v1/m.room.canonical_alias index f79ce234..25cd00c0 100644 --- a/event-schemas/schema/v1/m.room.canonical_alias +++ b/event-schemas/schema/v1/m.room.canonical_alias @@ -3,7 +3,7 @@ "title": "Informs the room as to which alias is the canonical one.", "description": "This event is used to inform the room about which alias should be considered the canonical one. This could be for display purposes or as suggestion to users which alias to use to advertise the room.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.create b/event-schemas/schema/v1/m.room.create index 9561f19b..f2e1ee92 100644 --- a/event-schemas/schema/v1/m.room.create +++ b/event-schemas/schema/v1/m.room.create @@ -3,7 +3,7 @@ "title": "The first event in the room.", "description": "This is the first event in a room and cannot be changed. It acts as the root of all other events.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.history_visibility b/event-schemas/schema/v1/m.room.history_visibility index 22b39623..bc33f4b3 100644 --- a/event-schemas/schema/v1/m.room.history_visibility +++ b/event-schemas/schema/v1/m.room.history_visibility @@ -3,7 +3,7 @@ "title": "Controls visibility of history.", "description": "This event controls whether a member of a room can see the events that happened in a room from before they joined.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.join_rules b/event-schemas/schema/v1/m.room.join_rules index 095ccfa7..70c36ccb 100644 --- a/event-schemas/schema/v1/m.room.join_rules +++ b/event-schemas/schema/v1/m.room.join_rules @@ -3,7 +3,7 @@ "title": "Describes how users are allowed to join the room.", "description": "A room may be ``public`` meaning anyone can join the room without any prior action. Alternatively, it can be ``invite`` meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room. Currently, ``knock`` and ``private`` are reserved keywords which are not implemented.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member index 5acc5f7c..48f1b034 100644 --- a/event-schemas/schema/v1/m.room.member +++ b/event-schemas/schema/v1/m.room.member @@ -3,7 +3,7 @@ "title": "The current membership state of a user in the room.", "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message b/event-schemas/schema/v1/m.room.message index c8c5931e..27b7e925 100644 --- a/event-schemas/schema/v1/m.room.message +++ b/event-schemas/schema/v1/m.room.message @@ -3,7 +3,7 @@ "title": "Message", "description": "This event is used when sending messages in a room. Messages are not limited to be text. The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc. The ``body`` key is text and MUST be used with every kind of ``msgtype`` as a fallback mechanism for when a client cannot render a message.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message#m.audio b/event-schemas/schema/v1/m.room.message#m.audio index f943fc7f..0a6625f5 100644 --- a/event-schemas/schema/v1/m.room.message#m.audio +++ b/event-schemas/schema/v1/m.room.message#m.audio @@ -3,7 +3,7 @@ "title": "AudioMessage", "description": "This message represents a single audio clip.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message#m.emote b/event-schemas/schema/v1/m.room.message#m.emote index 3379d635..690600f4 100644 --- a/event-schemas/schema/v1/m.room.message#m.emote +++ b/event-schemas/schema/v1/m.room.message#m.emote @@ -3,7 +3,7 @@ "title": "EmoteMessage", "description": "This message is similar to ``m.text`` except that the sender is 'performing' the action contained in the ``body`` key, similar to ``/me`` in IRC. This message should be prefixed by the name of the sender. This message could also be represented in a different colour to distinguish it from regular ``m.text`` messages.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message#m.file b/event-schemas/schema/v1/m.room.message#m.file index b1cdff22..c97480ef 100644 --- a/event-schemas/schema/v1/m.room.message#m.file +++ b/event-schemas/schema/v1/m.room.message#m.file @@ -3,7 +3,7 @@ "title": "FileMessage", "description": "This message represents a generic file.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { @@ -49,7 +49,7 @@ "title": "ImageInfo", "description": "Metadata about the image referred to in ``thumbnail_url``.", "allOf": [{ - "$ref": "core/msgtype_infos/image_info.json" + "$ref": "core-event-schema/msgtype_infos/image_info.json" }] } }, diff --git a/event-schemas/schema/v1/m.room.message#m.image b/event-schemas/schema/v1/m.room.message#m.image index 5d4f811b..af78096d 100644 --- a/event-schemas/schema/v1/m.room.message#m.image +++ b/event-schemas/schema/v1/m.room.message#m.image @@ -3,7 +3,7 @@ "title": "ImageMessage", "description": "This message represents a single image and an optional thumbnail.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { @@ -30,7 +30,7 @@ "title": "ImageInfo", "description": "Metadata about the image referred to in ``thumbnail_url``.", "allOf": [{ - "$ref": "core/msgtype_infos/image_info.json" + "$ref": "core-event-schema/msgtype_infos/image_info.json" }] }, "info": { diff --git a/event-schemas/schema/v1/m.room.message#m.location b/event-schemas/schema/v1/m.room.message#m.location index f02bea78..ef4b5e81 100644 --- a/event-schemas/schema/v1/m.room.message#m.location +++ b/event-schemas/schema/v1/m.room.message#m.location @@ -3,7 +3,7 @@ "title": "LocationMessage", "description": "This message represents a real-world location.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { @@ -29,7 +29,7 @@ "type": "object", "title": "ImageInfo", "allOf": [{ - "$ref": "core/msgtype_infos/image_info.json" + "$ref": "core-event-schema/msgtype_infos/image_info.json" }] } }, diff --git a/event-schemas/schema/v1/m.room.message#m.notice b/event-schemas/schema/v1/m.room.message#m.notice index 972eb45e..195c56a3 100644 --- a/event-schemas/schema/v1/m.room.message#m.notice +++ b/event-schemas/schema/v1/m.room.message#m.notice @@ -3,7 +3,7 @@ "title": "NoticeMessage", "description": "A m.notice message should be considered similar to a plain m.text message except that clients should visually distinguish it in some way. It is intended to be used by automated clients, such as bots, bridges, and other entities, rather than humans. Additionally, such automated agents which watch a room for messages and respond to them ought to ignore m.notice messages. This helps to prevent infinite-loop situations where two automated clients continuously exchange messages, as each responds to the other.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message#m.text b/event-schemas/schema/v1/m.room.message#m.text index 00aa1818..de59cf0e 100644 --- a/event-schemas/schema/v1/m.room.message#m.text +++ b/event-schemas/schema/v1/m.room.message#m.text @@ -3,7 +3,7 @@ "title": "TextMessage", "description": "This message is the most basic message and is used to represent text.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.message#m.video b/event-schemas/schema/v1/m.room.message#m.video index b91fd48a..ad68e72a 100644 --- a/event-schemas/schema/v1/m.room.message#m.video +++ b/event-schemas/schema/v1/m.room.message#m.video @@ -3,7 +3,7 @@ "title": "VideoMessage", "description": "This message represents a single video clip.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { @@ -54,7 +54,7 @@ "type": "object", "title": "ImageInfo", "allOf": [{ - "$ref": "core/msgtype_infos/image_info.json" + "$ref": "core-event-schema/msgtype_infos/image_info.json" }] } } diff --git a/event-schemas/schema/v1/m.room.message.feedback b/event-schemas/schema/v1/m.room.message.feedback index 3a069654..1bbfc1ba 100644 --- a/event-schemas/schema/v1/m.room.message.feedback +++ b/event-schemas/schema/v1/m.room.message.feedback @@ -3,7 +3,7 @@ "title": "MessageFeedback", "description": "Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: ``delivered`` (sent when the event has been received) and ``read`` (sent when the event has been observed by the end-user). The ``target_event_id`` should reference the ``m.room.message`` event being acknowledged. N.B. not implemented in Synapse, and superceded in v2 CS API by the ``relates_to`` event field.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.name b/event-schemas/schema/v1/m.room.name index 2a72feeb..b43f02cc 100644 --- a/event-schemas/schema/v1/m.room.name +++ b/event-schemas/schema/v1/m.room.name @@ -3,7 +3,7 @@ "description": "A room has an opaque room ID which is not human-friendly to read. A room alias is human-friendly, but not all rooms have room aliases. The room name is a human-friendly string designed to be displayed to the end-user. The room name is not unique, as multiple rooms can have the same room name set. The room name can also be set when creating a room using ``/createRoom`` with the ``name`` key.", "type": "object", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.power_levels b/event-schemas/schema/v1/m.room.power_levels index b5cb1049..2ef3bfa3 100644 --- a/event-schemas/schema/v1/m.room.power_levels +++ b/event-schemas/schema/v1/m.room.power_levels @@ -3,7 +3,7 @@ "title": "Defines the power levels (privileges) of users in the room.", "description": "This event specifies the minimum level a user must have in order to perform a certain action. It also specifies the levels of each user in the room. If a ``user_id`` is in the ``users`` list, then that ``user_id`` has the associated power level. Otherwise they have the default level ``users_default``. If ``users_default`` is not supplied, it is assumed to be 0. The level required to send a certain event is governed by ``events``, ``state_default`` and ``events_default``. If an event type is specified in ``events``, then the user must have at least the level specified in order to send that event. If the event type is not supplied, it defaults to ``events_default`` for Message Events and ``state_default`` for State Events.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.redaction b/event-schemas/schema/v1/m.room.redaction index 970679c7..5c3ef391 100644 --- a/event-schemas/schema/v1/m.room.redaction +++ b/event-schemas/schema/v1/m.room.redaction @@ -3,7 +3,7 @@ "title": "Redaction", "description": "Events can be redacted by either room or server admins. Redacting an event means that all keys not required by the protocol are stripped off, allowing admins to remove offensive or illegal content that may have been attached to any event. This cannot be undone, allowing server owners to physically delete the offending data. There is also a concept of a moderator hiding a message event, which can be undone, but cannot be applied to state events. The event that has been redacted is specified in the ``redacts`` event level key.", "allOf": [{ - "$ref": "core/room_event.json" + "$ref": "core-event-schema/room_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.room.topic b/event-schemas/schema/v1/m.room.topic index a9956228..d13f0c23 100644 --- a/event-schemas/schema/v1/m.room.topic +++ b/event-schemas/schema/v1/m.room.topic @@ -3,7 +3,7 @@ "title": "Topic", "description": "A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using ``/createRoom`` with the ``topic`` key.", "allOf": [{ - "$ref": "core/state_event.json" + "$ref": "core-event-schema/state_event.json" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/v1-event-schema b/event-schemas/schema/v1/v1-event-schema new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/event-schemas/schema/v1/v1-event-schema @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/scripts/continuserv/main.go b/scripts/continuserv/main.go index e7757c06..658ae0fb 100644 --- a/scripts/continuserv/main.go +++ b/scripts/continuserv/main.go @@ -56,7 +56,7 @@ func main() { go doPopulate(ch, dir) go watchFS(ch, w) - + fmt.Printf("Listening on port %d\n", *port) http.HandleFunc("/", serve) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) diff --git a/scripts/gendoc.py b/scripts/gendoc.py index ed172726..3521efed 100755 --- a/scripts/gendoc.py +++ b/scripts/gendoc.py @@ -4,6 +4,7 @@ from docutils.core import publish_file import fileinput import glob import os +import re import shutil import subprocess import sys @@ -14,11 +15,65 @@ stylesheets = { "stylesheet_path": ["basic.css", "nature.css"] } -def glob_spec_to(out_file_name): +title_style_matchers = { + "=": re.compile("^=+$"), + "-": re.compile("^-+$") +} +TOP_LEVEL = "=" +SECOND_LEVEL = "-" +FILE_FORMAT_MATCHER = re.compile("^[0-9]+_[0-9]{2}[a-z]*_.*\.rst$") + + +def check_valid_section(filename, section): + if not re.match(FILE_FORMAT_MATCHER, filename): + raise Exception( + "The filename of " + filename + " does not match the expected format " + + "of '##_##_words-go-here.rst'" + ) + + # we need TWO new lines else the next file's title gets merged + # the last paragraph *WITHOUT RST PRODUCING A WARNING* + if not section[-2:] == "\n\n": + raise Exception( + "The file " + filename + " does not end with 2 new lines." + ) + + # Enforce some rules to reduce the risk of having mismatched title + # styles. + title_line = section.split("\n")[1] + if title_line != (len(title_line) * title_line[0]): + raise Exception( + "The file " + filename + " doesn't have a title style line on line 2" + ) + + # anything marked as xx_00_ is the start of a new top-level section + if re.match("^[0-9]+_00_", filename): + if not title_style_matchers[TOP_LEVEL].match(title_line): + raise Exception( + "The file " + filename + " is a top-level section because it matches " + + "the filename format ##_00_something.rst but has the wrong title " + + "style: expected '" + TOP_LEVEL + "' but got '" + + title_line[0] + "'" + ) + # anything marked as xx_xx_ is the start of a sub-section + elif re.match("^[0-9]+_[0-9]{2}_", filename): + if not title_style_matchers[SECOND_LEVEL].match(title_line): + raise Exception( + "The file " + filename + " is a 2nd-level section because it matches " + + "the filename format ##_##_something.rst but has the wrong title " + + "style: expected '" + SECOND_LEVEL + "' but got '" + + title_line[0] + "' - If this is meant to be a 3rd/4th/5th-level section " + + "then use the form '##_##b_something.rst' which will not apply this " + + "check." + ) + +def cat_spec_sections_to(out_file_name): with open(out_file_name, "wb") as outfile: for f in sorted(glob.glob("../specification/*.rst")): with open(f, "rb") as infile: - outfile.write(infile.read()) + section = infile.read() + check_valid_section(os.path.basename(f), section) + outfile.write(section) def rst2html(i, o): @@ -49,7 +104,7 @@ def run_through_template(input): ) except subprocess.CalledProcessError as e: with open(tmpfile, 'r') as f: - print f.read() + sys.stderr.write(f.read() + "\n") raise def prepare_env(): @@ -67,7 +122,7 @@ def cleanup_env(): def main(): prepare_env() - glob_spec_to("tmp/full_spec.rst") + cat_spec_sections_to("tmp/full_spec.rst") run_through_template("tmp/full_spec.rst") shutil.copy("../supporting-docs/howtos/client-server.rst", "tmp/howto.rst") run_through_template("tmp/howto.rst") diff --git a/specification/00_basis.rst b/specification/00_00_intro.rst similarity index 97% rename from specification/00_basis.rst rename to specification/00_00_intro.rst index 10709f1d..a5196e99 100644 --- a/specification/00_basis.rst +++ b/specification/00_00_intro.rst @@ -79,7 +79,7 @@ The functionality that Matrix provides includes: - Extensible user management (inviting, joining, leaving, kicking, banning) mediated by a power-level based user privilege system. - Extensible room state management (room naming, aliasing, topics, bans) -- Extensible user profile management (avatars, displaynames, etc) +- Extensible user profile management (avatars, display names, etc) - Managing user accounts (registration, login, logout) - Use of 3rd Party IDs (3PIDs) such as email addresses, phone numbers, Facebook accounts to authenticate, identify and discover users on Matrix. @@ -91,7 +91,7 @@ The functionality that Matrix provides includes: The end goal of Matrix is to be a ubiquitous messaging layer for synchronising arbitrary data between sets of people, devices and services - be that for instant messages, VoIP call setups, or any other objects that need to be -reliably and persistently pushed from A to B in an interoperable and federated +reliably and persistently pushed from A to B in an inter-operable and federated manner. Overview @@ -171,20 +171,21 @@ All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event. Each event has a ``type`` which is used to differentiate different kinds of data. ``type`` values MUST be uniquely globally namespaced following Java's `package naming -conventions -`, e.g. +conventions`_, e.g. ``com.example.myapp.event``. The special top-level namespace ``m.`` is reserved for events defined in the Matrix specification - for instance ``m.room.message`` is the event type for instant messages. Events are usually sent in the context of a "Room". +.. _package naming conventions: https://en.wikipedia.org/wiki/Java_package#Package_naming_conventions + Event Graphs ~~~~~~~~~~~~ Events exchanged in the context of a room are stored in a directed acyclic graph (DAG) called an ``event graph``. The partial ordering of this graph gives the chronological ordering of events within the room. Each event in the graph has a -list of zero or more ``parent`` events, which refer to any preceeding events +list of zero or more ``parent`` events, which refer to any preceding events which have no chronological successor from the perspective of the homeserver which created the event. @@ -367,7 +368,8 @@ room). An example of a non-proactive client activity would be a client setting key called ``last_active_ago``, which gives the relative number of milliseconds since the message is generated/emitted that the user was last seen active. -N.B. in v1 API, status/online/idle state are muxed into a single 'presence' field on the m.presence event. +N.B. in v1 API, status/online/idle state are muxed into a single 'presence' +field on the ``m.presence`` event. Presence Lists ~~~~~~~~~~~~~~ @@ -385,7 +387,7 @@ Profiles Users may publish arbitrary key/value data associated with their account - such as a human readable ``display name``, a profile photo URL, contact information -(email address, phone nubers, website URLs etc). +(email address, phone numbers, website URLs etc). In Client-Server API v2, profile data is typed using namespaced keys for interoperability, much like events - e.g. ``m.profile.display_name``. @@ -442,7 +444,7 @@ The ``error`` string will be a human-readable error message, usually a sentence explaining what went wrong. The ``errcode`` string will be a unique string which can be used to handle an error message e.g. ``M_FORBIDDEN``. These error codes should have their namespace first in ALL CAPS, followed by a single _ to -ease seperating the namespace from the error code.. For example, if there was a +ease separating the namespace from the error code. For example, if there was a custom namespace ``com.mydomain.here``, and a ``FORBIDDEN`` code, the error code should look like ``COM.MYDOMAIN.HERE_FORBIDDEN``. There may be additional keys depending on the diff --git a/specification/10_client_server_api.rst b/specification/01_00_client_server_api.rst similarity index 97% rename from specification/10_client_server_api.rst rename to specification/01_00_client_server_api.rst index 2c85a4c2..ad39dafc 100644 --- a/specification/10_client_server_api.rst +++ b/specification/01_00_client_server_api.rst @@ -8,11 +8,11 @@ The client-server API provides a simple lightweight API to let clients send messages, control rooms and synchronise conversation history. It is designed to support both lightweight clients which store no state and lazy-load data from the server as required - as well as heavyweight clients which maintain a full -local peristent copy of server state. +local persistent copy of server state. This mostly describes v1 of the Client-Server API as featured in the original September 2014 launch of Matrix, apart from user-interactive authentication where it is -encouraged to move to V2, therefore this is the version documented here. +encouraged to move to v2, therefore this is the version documented here. Version 2 is currently in development (as of Jan-March 2015) as an incremental but backwards-incompatible refinement of Version 1 and will be released shortly. @@ -154,7 +154,7 @@ Matrix client, for example, an email confirmation may be completed when the user clicks on the link in the email. In this case, the client retries the request with an auth dict containing only the session key. The response to this will be the same as if the client were attempting to complete an auth state normally, -ie. the request will either complete or request auth, with the presence or +i.e. the request will either complete or request auth, with the presence or absence of that login stage type in the 'completed' array indicating whether that stage is complete. @@ -204,7 +204,7 @@ Password-based :Type: ``m.login.password`` :Description: - The client submits a username and secret password, both sent in plaintext. + The client submits a username and secret password, both sent in plain-text. To respond to this type, reply with an auth dict as follows:: @@ -247,10 +247,10 @@ service which the home server accepts when logging in, this indirection can be skipped and the "uri" key can be the ``Authorization Request URI``. The client then visits the ``Authorization Request URI``, which then shows the -OAuth2 Allow/Deny prompt. Hitting 'Allow' returns the [XXX: redirects to the?]``redirect URI`` with the -auth code. Home servers can choose any path for the ``redirect URI``. Once the -OAuth flow has completed, the client retries the request with the session only, -as above. +OAuth2 Allow/Deny prompt. Hitting 'Allow' redirects to the ``redirect URI`` with +the auth code. Home servers can choose any path for the ``redirect URI``. Once +the OAuth flow has completed, the client retries the request with the session +only, as above. Email-based (identity server) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -308,7 +308,7 @@ Where ``stage type`` is the type name of the stage it is attempting and ``session id`` is the ID of the session given by the home server. This MUST return an HTML page which can perform this authentication stage. This -page must attempt to call the Javascript function ``window.onAuthDone`` when +page must attempt to call the JavaScript function ``window.onAuthDone`` when the authentication has been completed. Pagination @@ -373,7 +373,7 @@ now show page 3 (rooms R11 -> 15):: Returns: R11,R12,R13,R14,R15 Note that tokens are treated in an *exclusive*, not inclusive, manner. The end -token from the intial request was '9' which corresponded to R10. When the 2nd +token from the initial request was '9' which corresponded to R10. When the 2nd request was made, R10 did not appear again, even though from=9 was specified. If you know the token, you already have the data. @@ -425,9 +425,9 @@ You can visualise the range of events being returned as:: | | start: '1-2-3' end: 'a-b-c' -Now, to receive future events in realtime on the eventstream, you simply GET +Now, to receive future events in real-time on the eventstream, you simply GET $PREFIX/events with a ``from`` parameter of 'a-b-c': in other words passing in the -``end`` token returned by initialsync. The request blocks until new events are +``end`` token returned by initial sync. The request blocks until new events are available or until your specified timeout elapses, and then returns a new paginatable chunk of events alongside new start and end parameters:: @@ -467,7 +467,7 @@ event stream. When the request returns, an ``end`` token is included in the response. This token can be used in the next request to continue where the last request left off. -All events must be deduplicated based on their event ID. +All events must be de-duplicated based on their event ID. .. TODO is deduplication actually a hard requirement in CS v2? @@ -493,7 +493,7 @@ Room events are split into two categories: :Message events: These are events which describe transient "once-off" activity in a room: - typically communication such as sending an instant messaage or setting up a + typically communication such as sending an instant message or setting up a VoIP call. These used to be called 'non-state' events. This specification outlines several events, all with the event type prefix @@ -890,11 +890,8 @@ directly by sending the following request to "membership": "leave" } -See the `Room events`_ section for more information on ``m.room.member``. - -Once a user has left a room, that room will no longer appear on the -|initialSync|_ API. - +See the `Room events`_ section for more information on ``m.room.member``. Once a +user has left a room, that room will no longer appear on the |initialSync|_ API. If all members in a room leave, that room becomes eligible for deletion. Banning users in a room @@ -932,7 +929,7 @@ Registering for a user account is done using the request:: POST $V2PREFIX/register This API endpoint uses the User-Interactive Authentication API. -This API endoint does not require an access token. +This API endpoint does not require an access token. The body of the POST request is a JSON object containing: @@ -1020,7 +1017,7 @@ The third party identifier credentials object comprises: id_server The colon-separated hostname and port of the Identity Server used to - authenticate the third party identifer. If the port is the default, it and the + authenticate the third party identifier. If the port is the default, it and the colon should be omitted. sid The session ID given by the Identity Server diff --git a/specification/45_typing_notifications.rst b/specification/01_01_typing_notifications.rst similarity index 74% rename from specification/45_typing_notifications.rst rename to specification/01_01_typing_notifications.rst index 7df9a238..25b714ab 100644 --- a/specification/45_typing_notifications.rst +++ b/specification/01_01_typing_notifications.rst @@ -1,25 +1,27 @@ Typing Notifications -==================== +-------------------- Client APIs ------------ +~~~~~~~~~~~ To set "I am typing for the next N msec":: + PUT .../rooms//typing/ Content: { "typing": true, "timeout": N } - # timeout is in msec; I suggest no more than 20 or 30 seconds + # timeout is in milliseconds; suggested no more than 20 or 30 seconds This should be re-sent by the client to continue informing the server the user -is still typing; I suggest a safety margin of 5 seconds before the expected -timeout runs out. Just keep declaring a new timeout, it will replace the old -one. +is still typing; a safety margin of 5 seconds before the expected +timeout runs out is recommended. Just keep declaring a new timeout, it will +replace the old one. To set "I am no longer typing":: + PUT ../rooms//typing/ Content: { "typing": false } Client Events -------------- +~~~~~~~~~~~~~ All room members will receive an event on the event stream:: @@ -37,7 +39,7 @@ users who are not currently typing, as that list gets big quickly. The client should mark as not typing, any user ID who is not in that list. Server APIs ------------ +~~~~~~~~~~~ Servers will emit EDUs in the following form:: @@ -46,13 +48,14 @@ Servers will emit EDUs in the following form:: "content": { "room_id": "!room-id-here:matrix.org", "user_id": "@user-id-here:matrix.org", - "typing": true/false, + "typing": true/false } } Server EDUs don't (currently) contain timing information; it is up to originating HSes to ensure they eventually send "stop" notifications. -((This will eventually need addressing, as part of the wider typing/presence -timer addition work)) +.. TODO + ((This will eventually need addressing, as part of the wider typing/presence + timer addition work)) diff --git a/specification/46_receipts.rst b/specification/01_02_receipts.rst similarity index 76% rename from specification/46_receipts.rst rename to specification/01_02_receipts.rst index 6428d6b5..e2f83eea 100644 --- a/specification/46_receipts.rst +++ b/specification/01_02_receipts.rst @@ -1,14 +1,13 @@ Receipts -======== +-------- Receipts are used to publish which events in a room the user or their devices -have interacted with. For example, which events the user has read. - -For efficiency this is done as "up to" markers, i.e. marking a particular event +have interacted with. For example, which events the user has read. For +efficiency this is done as "up to" markers, i.e. marking a particular event as, say, ``read`` indicates the user has read all events *up to* that event. Client-Server API ------------------ +~~~~~~~~~~~~~~~~~ Clients will receive receipts in the following format:: @@ -43,14 +42,11 @@ For example:: } For efficiency, receipts are batched into one event per room. In the initialSync -and v2 sync APIs the receipts are listed in a seperate top level ``receipts`` -key. - -Each ``user_id``, ``receipt_type`` pair must be associated with only a single -``event_id``. - -New receipts that come down the event streams are deltas. Deltas update -existing mappings, clobbering based on ``user_id``, ``receipt_type`` pairs. +and v2 sync APIs the receipts are listed in a separate top level ``receipts`` +key. Each ``user_id``, ``receipt_type`` pair must be associated with only a +single ``event_id``. New receipts that come down the event streams are deltas. +Deltas update existing mappings, clobbering based on ``user_id``, +``receipt_type`` pairs. A client can update the markers for its user by issuing a request:: @@ -62,7 +58,7 @@ other users. The server will automatically set the ``ts`` field. Server-Server API ------------------ +~~~~~~~~~~~~~~~~~ Receipts are sent across federation as EDUs with type ``m.receipt``. The format of the EDUs are:: @@ -77,5 +73,5 @@ format of the EDUs are:: ... } -These are always sent as deltas to previously sent reciepts. +These are always sent as deltas to previously sent receipts. diff --git a/specification/47_history_visibility.rst b/specification/01_03_history_visibility.rst similarity index 97% rename from specification/47_history_visibility.rst rename to specification/01_03_history_visibility.rst index b1630f1e..01c2e419 100644 --- a/specification/47_history_visibility.rst +++ b/specification/01_03_history_visibility.rst @@ -1,5 +1,5 @@ Room History Visibility -======================= +----------------------- Whether a member of a room can see the events that happened in a room from before they joined the room is controlled by the ``history_visibility`` key diff --git a/specification/20_events.rst b/specification/02_00_events.rst similarity index 58% rename from specification/20_events.rst rename to specification/02_00_events.rst index b0d5d716..ce36b040 100644 --- a/specification/20_events.rst +++ b/specification/02_00_events.rst @@ -88,70 +88,3 @@ users, they should include the displayname and avatar URL fields in these events so that clients already have these details to hand, and do not have to perform extra roundtrips to query it. -Voice over IP -------------- -Matrix can also be used to set up VoIP calls. This is part of the core -specification, although is at a relatively early stage. Voice (and video) over -Matrix is built on the WebRTC 1.0 standard. - -Call events are sent to a room, like any other event. This means that clients -must only send call events to rooms with exactly two participants as currently -the WebRTC standard is based around two-party communication. - -{{voip_events}} - -Message Exchange -~~~~~~~~~~~~~~~~ -A call is set up with messages exchanged as follows: - -:: - - Caller Callee - m.call.invite -----------> - m.call.candidate --------> - [more candidates events] - User answers call - <------ m.call.answer - [...] - <------ m.call.hangup - -Or a rejected call: - -:: - - Caller Callee - m.call.invite -----------> - m.call.candidate --------> - [more candidates events] - User rejects call - <------- m.call.hangup - -Calls are negotiated according to the WebRTC specification. - - -Glare -~~~~~ -This specification aims to address the problem of two users calling each other -at roughly the same time and their invites crossing on the wire. It is a far -better experience for the users if their calls are connected if it is clear -that their intention is to set up a call with one another. - -In Matrix, calls are to rooms rather than users (even if those rooms may only -contain one other user) so we consider calls which are to the same room. - -The rules for dealing with such a situation are as follows: - - - If an invite to a room is received whilst the client is preparing to send an - invite to the same room, the client should cancel its outgoing call and - instead automatically accept the incoming call on behalf of the user. - - If an invite to a room is received after the client has sent an invite to - the same room and is waiting for a response, the client should perform a - lexicographical comparison of the call IDs of the two calls and use the - lesser of the two calls, aborting the greater. If the incoming call is the - lesser, the client should accept this call on behalf of the user. - -The call setup should appear seamless to the user as if they had simply placed -a call and the other party had accepted. Thusly, any media stream that had been -setup for use on a call should be transferred and used for the call that -replaces it. - diff --git a/specification/02_01_voip_events.rst b/specification/02_01_voip_events.rst new file mode 100644 index 00000000..a5468237 --- /dev/null +++ b/specification/02_01_voip_events.rst @@ -0,0 +1,66 @@ +Voice over IP +------------- +Matrix can also be used to set up VoIP calls. This is part of the core +specification, although is at a relatively early stage. Voice (and video) over +Matrix is built on the WebRTC 1.0 standard. Call events are sent to a room, like +any other event. This means that clients must only send call events to rooms +with exactly two participants as currently the WebRTC standard is based around +two-party communication. + +{{voip_events}} + +Message Exchange +~~~~~~~~~~~~~~~~ +A call is set up with messages exchanged as follows: + +:: + + Caller Callee + [Place Call] + m.call.invite -----------> + m.call.candidate --------> + [..candidates..] --------> + [Answers call] + <--------------- m.call.answer + [Call is active and ongoing] + <--------------- m.call.hangup + +Or a rejected call: + +:: + + Caller Callee + m.call.invite ------------> + m.call.candidate ---------> + [..candidates..] ---------> + [Rejects call] + <-------------- m.call.hangup + +Calls are negotiated according to the WebRTC specification. + + +Glare +~~~~~ +This specification aims to address the problem of two users calling each other +at roughly the same time and their invites crossing on the wire. It is a far +better experience for the users if their calls are connected if it is clear +that their intention is to set up a call with one another. + +In Matrix, calls are to rooms rather than users (even if those rooms may only +contain one other user) so we consider calls which are to the same room. The +rules for dealing with such a situation are as follows: + + - If an invite to a room is received whilst the client is preparing to send an + invite to the same room, the client should cancel its outgoing call and + instead automatically accept the incoming call on behalf of the user. + - If an invite to a room is received after the client has sent an invite to + the same room and is waiting for a response, the client should perform a + lexicographical comparison of the call IDs of the two calls and use the + lesser of the two calls, aborting the greater. If the incoming call is the + lesser, the client should accept this call on behalf of the user. + +The call setup should appear seamless to the user as if they had simply placed +a call and the other party had accepted. Thusly, any media stream that had been +setup for use on a call should be transferred and used for the call that +replaces it. + diff --git a/specification/31_event_signing.rst b/specification/02_02_event_signing.rst similarity index 98% rename from specification/31_event_signing.rst rename to specification/02_02_event_signing.rst index 9351cd57..bde58f0c 100644 --- a/specification/31_event_signing.rst +++ b/specification/02_02_event_signing.rst @@ -1,8 +1,8 @@ Signing Events -============== +-------------- Canonical JSON --------------- +~~~~~~~~~~~~~~ Matrix events are represented using JSON objects. If we want to sign JSON events we need to encode the JSON as a binary string. Unfortunately the same @@ -30,7 +30,7 @@ using this representation. value, # Encode code-points outside of ASCII as UTF-8 rather than \u escapes ensure_ascii=False, - # Remove unecessary white space. + # Remove unnecessary white space. separators=(',',':'), # Sort the keys of dictionaries. sort_keys=True, @@ -38,7 +38,7 @@ using this representation. ).encode("UTF-8") Grammar -~~~~~~~ ++++++++ Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing insignificant whitespace, fractions, exponents and redundant character escapes @@ -69,14 +69,14 @@ insignificant whitespace, fractions, exponents and redundant character escapes / %x75.30.30.31 (%x30-39 / %x61-66) ; u001X Signing JSON ------------- +~~~~~~~~~~~~ We can now sign a JSON object by encoding it as a sequence of bytes, computing the signature for that sequence and then adding the signature to the original JSON object. Signing Details -~~~~~~~~~~~~~~~ ++++++++++++++++ JSON is signed by encoding the JSON object without ``signatures`` or keys grouped as ``unsigned``, using the canonical encoding described above. The JSON bytes are then signed using the @@ -133,7 +133,7 @@ and additional signatures. return json_object Checking for a Signature -~~~~~~~~~~~~~~~~~~~~~~~~ +++++++++++++++++++++++++ To check if an entity has signed a JSON object a server does the following @@ -151,7 +151,7 @@ To check if an entity has signed a JSON object a server does the following the check fails. Otherwise the check succeeds. Signing Events --------------- +~~~~~~~~~~~~~~ Signing events is a more complicated process since servers can choose to redact non-essential parts of an event. Before signing the event it is encoded as diff --git a/specification/25_application_service_api.rst b/specification/03_00_application_service_api.rst similarity index 99% rename from specification/25_application_service_api.rst rename to specification/03_00_application_service_api.rst index 8236b2de..9b58c861 100644 --- a/specification/25_application_service_api.rst +++ b/specification/03_00_application_service_api.rst @@ -66,13 +66,13 @@ An example HS configuration required to pass traffic to the AS is: application service is merely augmenting the room itself (e.g. providing logging or searching facilities). - Namespaces are represented by POSIX extended regular expressions, - e.g.: + e.g: .. code-block:: yaml users: - exclusive: true - regex: @irc.freenode.net/.* + regex: @irc.freenode.net_.* Home Server -> Application Service API @@ -326,7 +326,7 @@ but only if the application service has defined the namespace as ``exclusive``. ID conventions ~~~~~~~~~~~~~~ -.. NOTE:: +.. TODO-spec - Giving HSes the freedom to namespace still feels like the Right Thing here. - Exposing a public API provides the consistency which was the main complaint against namespacing. @@ -345,7 +345,7 @@ types, including: - MSISDNs (``tel``) - Email addresses (``mailto``) - IRC nicks (``irc`` - https://tools.ietf.org/html/draft-butcher-irc-url-04) -- XMPP (xep-0032) +- XMPP (XEP-0032) - SIP URIs (RFC 3261) As a result, virtual user IDs SHOULD relate to their URI counterpart. This @@ -403,6 +403,8 @@ blog comment traffic in & out of matrix Active Application Services ---------------------------- +.. NOTE:: + This section is a work in progress. .. TODO-spec API that provides hooks into the server so that you can intercept and @@ -419,3 +421,4 @@ Policy Servers Enforcing policies ------------------ + diff --git a/specification/30_server_server_api.rst b/specification/04_00_server_server_api.rst similarity index 94% rename from specification/30_server_server_api.rst rename to specification/04_00_server_server_api.rst index 1c7bf3ed..f5cadabf 100644 --- a/specification/30_server_server_api.rst +++ b/specification/04_00_server_server_api.rst @@ -2,10 +2,9 @@ Federation API ============== Matrix home servers use the Federation APIs (also known as server-server APIs) -to communicate with each other. -Home servers use these APIs to push messages to each other in real-time, to -request historic messages from each other, and to query profile and presence -information about users on each other's servers. +to communicate with each other. Home servers use these APIs to push messages to +each other in real-time, to request historic messages from each other, and to +query profile and presence information about users on each other's servers. The APIs are implemented using HTTPS GETs and PUTs between each of the servers. These HTTPS requests are strongly authenticated using public key @@ -21,7 +20,7 @@ Persisted Data Units (PDUs): context. Like email, it is the responsibility of the originating server of a PDU - to deliver that event to its recepient servers. However PDUs are signed + to deliver that event to its recipient servers. However PDUs are signed using the originating server's public key so that it is possible to deliver them through third-party servers. @@ -84,18 +83,19 @@ directly or by querying an intermediate notary server using a response with their own key. A server may query multiple notary servers to ensure that they all report the same public keys. -This approach is borrowed from the Perspectives Project -(http://perspectives-project.org/), but modified to include the NACL keys and to -use JSON instead of XML. It has the advantage of avoiding a single trust-root -since each server is free to pick which notary servers they trust and can -corroborate the keys returned by a given notary server by querying other -servers. +This approach is borrowed from the `Perspectives Project`_, but modified to +include the NACL keys and to use JSON instead of XML. It has the advantage of +avoiding a single trust-root since each server is free to pick which notary +servers they trust and can corroborate the keys returned by a given notary +server by querying other servers. + +.. _Perspectives Project: http://perspectives-project.org/ Publishing Keys _______________ Home servers publish the allowed TLS fingerprints and signing keys in a JSON -object at ``/_matrix/key/v2/server/${key_id}``. The response contains a list of +object at ``/_matrix/key/v2/server/{key_id}``. The response contains a list of ``verify_keys`` that are valid for signing federation requests made by the server and for signing events. It contains a list of ``old_verify_keys`` which are only valid for signing events. Finally the response contains a list @@ -510,7 +510,7 @@ To backfill events on a given context:: Retrieves a sliding-window history of previous PDUs that occurred on the given context. Starting from the PDU ID(s) given in the "v" argument, the PDUs that -preceeded it are retrieved, up to a total number given by the "limit" argument. +preceded it are retrieved, up to a total number given by the "limit" argument. These are then returned in a new Transaction containing all of the PDUs. @@ -554,9 +554,7 @@ Every HTTP request made by a homeserver is authenticated using public key digital signatures. The request method, target and body are signed by wrapping them in a JSON object and signing it using the JSON signing algorithm. The resulting signatures are added as an Authorization header with an auth scheme -of X-Matrix. - -Note that the target field should include the full path starting with +of X-Matrix. Note that the target field should include the full path starting with ``/_matrix/...``, including the ``?`` and any query parameters if present, but should not include the leading ``https:``, nor the destination server's hostname. @@ -656,12 +654,12 @@ State Conflict Resolution - How does this work with deleting current state - How do we reject invalid federation traffic? -[[TODO(paul): At this point we should probably have a long description of how -State management works, with descriptions of clobbering rules, power levels, etc -etc... But some of that detail is rather up-in-the-air, on the whiteboard, and -so on. This part needs refining. And writing in its own document as the details -relate to the server/system as a whole, not specifically to server-server -federation.]] + [[TODO(paul): At this point we should probably have a long description of how + State management works, with descriptions of clobbering rules, power levels, etc + etc... But some of that detail is rather up-in-the-air, on the whiteboard, and + so on. This part needs refining. And writing in its own document as the details + relate to the server/system as a whole, not specifically to server-server + federation.]] Presence -------- @@ -677,8 +675,8 @@ Performing a presence update and poll subscription request:: Each should be an object with the following keys: user_id: string containing a User ID presence: "offline"|"unavailable"|"online"|"free_for_chat" - status_msg: (optional) string of freeform text - last_active_ago: miliseconds since the last activity by the user + status_msg: (optional) string of free-form text + last_active_ago: milliseconds since the last activity by the user poll: (optional): list of strings giving User IDs @@ -696,7 +694,7 @@ removed until explicitly requested by a later ``unpoll``. On receipt of a message containing a non-empty ``poll`` list, the receiving server should immediately send the sending server a presence update EDU of its own, containing in a ``push`` list the current state of every user that was in -the orginal EDU's ``poll`` list. +the original EDU's ``poll`` list. Sending a presence invite:: @@ -721,7 +719,7 @@ Rejecting a presence invite:: Content keys - as for m.presence_invite .. TODO-doc - - Explain the timing-based roundtrip reduction mechanism for presence + - Explain the timing-based round-trip reduction mechanism for presence messages - Explain the zero-byte presence inference logic See also: docs/client-server/model/presence @@ -742,8 +740,8 @@ Querying profile information:: field: (optional) string giving a field name Returns: JSON object containing the following keys: - displayname: string of freeform text - avatar_url: string containing an http-scheme URL + displayname: string of free-form text + avatar_url: string containing an HTTP-scheme URL If the query contains the optional ``field`` key, it should give the name of a result field. If such is present, then the result should contain only a field @@ -769,3 +767,4 @@ Querying directory information:: The list of join candidates is a list of server names that are likely to hold the given room; these are servers that the requesting server may wish to try joining with. This list may or may not include the server answering the query. + diff --git a/specification/40_content_repository.rst b/specification/05_00_content_repository.rst similarity index 93% rename from specification/40_content_repository.rst rename to specification/05_00_content_repository.rst index df04605a..2c45ced7 100644 --- a/specification/40_content_repository.rst +++ b/specification/05_00_content_repository.rst @@ -39,10 +39,10 @@ thumbnailing method:: -The thumbnail methods are "crop" and "scale". "scale" trys to return an +The thumbnail methods are "crop" and "scale". "scale" tries to return an image where either the width or the height is smaller than the requested size. The client should then scale and letterbox the image if it needs to -fit within a given rectangle. "crop" trys to return an image where the +fit within a given rectangle. "crop" tries to return an image where the width and height are close to the requested size and the aspect matches the requested size. The client should scale the image if it needs to fit within a given rectangle. @@ -53,8 +53,8 @@ the content. Homeservers may return thumbnails of a different size to that requested. However homeservers should provide exact matches where reasonable. Homeservers must never upscale images. -Security --------- +Security considerations +----------------------- Clients may try to upload very large files. Homeservers should not store files that are too large and should not serve them to clients. diff --git a/specification/41_end_to_end_encryption.rst b/specification/06_00_end_to_end_encryption.rst similarity index 100% rename from specification/41_end_to_end_encryption.rst rename to specification/06_00_end_to_end_encryption.rst diff --git a/specification/42_push_overview.rst b/specification/07_00_push_overview.rst similarity index 100% rename from specification/42_push_overview.rst rename to specification/07_00_push_overview.rst diff --git a/specification/43_push_cs_api.rst b/specification/07_01_push_cs_api.rst similarity index 96% rename from specification/43_push_cs_api.rst rename to specification/07_01_push_cs_api.rst index 022f50c3..b78b44fd 100644 --- a/specification/43_push_cs_api.rst +++ b/specification/07_01_push_cs_api.rst @@ -70,7 +70,7 @@ Room Rules Sender These rules configure notification behaviour for messages from a specific, named Matrix user ID. The rule_id of Sender rules is always the Matrix user - ID of the user whose messages theyt apply to. + ID of the user whose messages they'd apply to. Underride These are identical to override rules, but have a lower priority than content, room and sender rules. @@ -112,7 +112,7 @@ In addition, all rules may be enabled or disabled. Disabled rules never match. If no rules match an event, the Home Server should not notify for the message (that is to say, the default action is "dont-notify"). Events that the user sent -themself are never alerted for. +themselves are never alerted for. Predefined Rules ---------------- @@ -128,7 +128,7 @@ with these IDs, their semantics should match those given below: { "rule_id": ".m.rule.contains_user_name" - "pattern": "[the lcoal part of the user's Matrix ID]", + "pattern": "[the local part of the user's Matrix ID]", "actions": [ "notify", { @@ -220,7 +220,7 @@ with these IDs, their semantics should match those given below: Push Rules: Actions: -------------------- All rules have an associated list of 'actions'. An action affects if and how a -notification is delievered for a matching event. This standard defines the +notification is delivered for a matching event. This standard defines the following actions, although if Home servers wish to support more, they are free to do so: @@ -241,11 +241,11 @@ set_tweak Actions that have no parameters are represented as a string. Otherwise, they are represented as a dictionary with a key equal to their name and other keys as -their parameters, eg. { "set_tweak": "sound", "value": "default" } +their parameters, e.g. ``{ "set_tweak": "sound", "value": "default" }`` Push Rules: Actions: Tweaks --------------------------- -The 'set_tweak' key action is used to add an entry to the 'tweaks' dictionary +The ``set_tweak`` key action is used to add an entry to the 'tweaks' dictionary that is sent in the notification poke. The following tweaks are defined: sound @@ -275,7 +275,7 @@ do so: event_match This is a glob pattern match on a field of the event. Parameters: - * 'key': The dot-separated field of the event to match, eg. content.body + * 'key': The dot-separated field of the event to match, e.g. content.body * 'pattern': The glob-style pattern to match against. Patterns with no special glob characters should be treated as having asterisks prepended and appended when testing the condition. @@ -295,7 +295,7 @@ room_member_count '>=' or '<='. A prefix of '<' matches rooms where the member count is strictly less than the given number and so forth. If no prefix is present, this matches rooms where the member count is exactly equal to the given - number (ie. the same as '=='). + number (i.e. the same as '=='). Room, Sender, User and Content rules do not have conditions in the same way, but instead have predefined conditions, the behaviour of which can be configured @@ -314,7 +314,7 @@ scope Either 'global' or 'device/' to specify global rules or device rules for the given profile_tag. kind - The kind of rule, ie. 'override', 'underride', 'sender', 'room', 'content'. + The kind of rule, i.e. 'override', 'underride', 'sender', 'room', 'content'. rule_id The identifier for the rule. @@ -330,7 +330,7 @@ after rule. All requests to the push rules API also require an access_token as a query -paraemter. +parameter. The content of the PUT request is a JSON object with a list of actions under the 'actions' key and either conditions (under the 'conditions' key) or the diff --git a/specification/44_push_push_gw_api.rst b/specification/07_02_push_push_gw_api.rst similarity index 93% rename from specification/44_push_push_gw_api.rst rename to specification/07_02_push_push_gw_api.rst index b182503b..c970e93e 100644 --- a/specification/44_push_push_gw_api.rst +++ b/specification/07_02_push_push_gw_api.rst @@ -1,7 +1,7 @@ HTTP Notification Protocol -------------------------- -This describes the format used by "http" pushers to send notifications of +This describes the format used by "HTTP" pushers to send notifications of events. Notifications are sent as HTTP POST requests to the URL configured when the @@ -77,10 +77,10 @@ counts This is a dictionary of the current number of unacknowledged communications for the recipient user. Counts whose value is zero are omitted. unread - The number of unread messages a user has accross all of the rooms they are a + The number of unread messages a user has across all of the rooms they are a member of. missed_calls - The number of unacknowledged missed calls a user has accross all rooms of + The number of unacknowledged missed calls a user has across all rooms of which they are a member. device This is an array of devices that the notification should be sent to. @@ -104,13 +104,13 @@ And additional key is defined but only present on member events: user_is_target This is true if the user receiving the notification is the subject of a member - event (ie. the state_key of the member event is equal to the user's Matrix + event (i.e. the state_key of the member event is equal to the user's Matrix ID). The recipient of an HTTP notification should respond with an HTTP 2xx response when the notification has been processed. If the endpoint returns an HTTP error code, the Home Server should retry for a reasonable amount of time with a -reasonable backoff scheme. +reasonable back-off scheme. The endpoint should return a JSON dictionary as follows:: diff --git a/specification/49_other_non_core_apis.rst b/specification/08_00_address_book_repo.rst similarity index 100% rename from specification/49_other_non_core_apis.rst rename to specification/08_00_address_book_repo.rst index 8855ce9e..d6315a96 100644 --- a/specification/49_other_non_core_apis.rst +++ b/specification/08_00_address_book_repo.rst @@ -3,10 +3,10 @@ Address book repository .. NOTE:: This section is a work in progress. - Do we even need it? Clients can use out-of-band addressbook servers for now; - this should definitely not be core. .. TODO-spec + Do we even need it? Clients can use out-of-band addressbook servers for now; + this should definitely not be core. - format: POST(?) wodges of json, some possible processing, then return wodges of json on GET. - processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of contacts), etc. diff --git a/specification/09_00_identity_servers.rst b/specification/09_00_identity_servers.rst new file mode 100644 index 00000000..6ec013bd --- /dev/null +++ b/specification/09_00_identity_servers.rst @@ -0,0 +1,8 @@ +Identity Servers +================ +.. NOTE:: + This section is a work in progress. + +.. TODO-doc Dave + - 3PIDs and identity server, functions + diff --git a/specification/50_appendices.rst b/specification/10_00_appendices.rst similarity index 96% rename from specification/50_appendices.rst rename to specification/10_00_appendices.rst index 295c8f69..de1ac290 100644 --- a/specification/50_appendices.rst +++ b/specification/10_00_appendices.rst @@ -128,11 +128,3 @@ Threat: Disclosure to Servers Within Chatroom An attacker could take control of a server within a chatroom to expose message contents or metadata for messages in that room. - -Identity Servers -================ -.. NOTE:: - This section is a work in progress. - -.. TODO-doc Dave - - 3PIDs and identity server, functions diff --git a/templating/matrix_templates/units.py b/templating/matrix_templates/units.py index e2615c7d..84c96ce3 100644 --- a/templating/matrix_templates/units.py +++ b/templating/matrix_templates/units.py @@ -8,6 +8,16 @@ import subprocess import urllib import yaml +V1_CLIENT_API = "../api/client-server/v1" +V1_EVENT_EXAMPLES = "../event-schemas/examples/v1" +V1_EVENT_SCHEMA = "../event-schemas/schema/v1" +CORE_EVENT_SCHEMA = "../event-schemas/schema/v1/core-event-schema" +CHANGELOG = "../CHANGELOG.rst" + +ROOM_EVENT = "core-event-schema/room_event.json" +STATE_EVENT = "core-event-schema/state_event.json" + + def get_json_schema_object_fields(obj, enforce_title=False): # Algorithm: # f.e. property => add field info (if field is object then recurse) @@ -87,6 +97,8 @@ def get_json_schema_object_fields(obj, enforce_title=False): desc += ( " Must be '%s'." % props[key_name]["enum"][0] ) + if isinstance(value_type, list): + value_type = " or ".join(value_type) fields["rows"].append({ "key": key_name, @@ -266,7 +278,7 @@ class MatrixUnits(Units): } def load_swagger_apis(self): - path = "../api/client-server/v1" + path = V1_CLIENT_API apis = {} for filename in os.listdir(path): if not filename.endswith(".yaml"): @@ -281,7 +293,7 @@ class MatrixUnits(Units): return apis def load_common_event_fields(self): - path = "../event-schemas/schema/v1/core" + path = CORE_EVENT_SCHEMA event_types = {} for (root, dirs, files) in os.walk(path): @@ -320,7 +332,7 @@ class MatrixUnits(Units): return event_types def load_event_examples(self): - path = "../event-schemas/examples/v1" + path = V1_EVENT_EXAMPLES examples = {} for filename in os.listdir(path): if not filename.startswith("m."): @@ -332,7 +344,7 @@ class MatrixUnits(Units): return examples def load_event_schemas(self): - path = "../event-schemas/schema/v1" + path = V1_EVENT_SCHEMA schemata = {} for filename in os.listdir(path): @@ -361,8 +373,8 @@ class MatrixUnits(Units): # add typeof base_defs = { - "core#/definitions/room_event": "Message Event", - "core#/definitions/state_event": "State Event" + ROOM_EVENT: "Message Event", + STATE_EVENT: "State Event" } if type(json_schema.get("allOf")) == list: schema["typeof"] = base_defs.get( @@ -399,7 +411,6 @@ class MatrixUnits(Units): "`m.room.message msgtypes`_." ) - # Assign state key info if it has some if schema["typeof"] == "State Event": skey_desc = Units.prop( @@ -413,7 +424,7 @@ class MatrixUnits(Units): return schemata def load_spec_meta(self): - path = "../CHANGELOG.rst" + path = CHANGELOG title_part = None version = None changelog_lines = []