Merge remote-tracking branch 'origin/master' into markjh/document_v1_rooms_api

pull/977/head
Mark Haines 9 years ago
commit f33c0846c3

@ -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)

@ -0,0 +1 @@
v1-event-schema/core-event-schema

@ -101,7 +101,7 @@ paths:
The length of time in milliseconds since an action was performed The length of time in milliseconds since an action was performed
by this user. by this user.
status_msg: status_msg:
type: string type: [string, "null"]
description: The state message for this user if one was set. description: The state message for this user if one was set.
404: 404:
description: |- description: |-
@ -185,7 +185,7 @@ paths:
"last_active_ago": 395, "last_active_ago": 395,
"presence": "offline", "presence": "offline",
"user_id": "@alice:matrix.org" "user_id": "@alice:matrix.org"
} },
"type": "m.presence" "type": "m.presence"
}, },
{ {
@ -195,7 +195,7 @@ paths:
"last_active_ago": 16874, "last_active_ago": 16874,
"presence": "online", "presence": "online",
"user_id": "@marisa:matrix.org" "user_id": "@marisa:matrix.org"
} },
"type": "m.presence" "type": "m.presence"
} }
] ]
@ -205,4 +205,4 @@ paths:
type: object type: object
title: PresenceEvent title: PresenceEvent
allOf: allOf:
- "$ref": "events/core/event.json" - "$ref": "core-event-schema/event.json"

@ -82,7 +82,7 @@ paths:
type: object type: object
title: RoomEvent title: RoomEvent
allOf: allOf:
- "$ref": "events/core/room_event.json" - "$ref": "core-event-schema/room_event.json"
400: 400:
description: "Bad pagination ``from`` parameter." description: "Bad pagination ``from`` parameter."
"/initialSync": "/initialSync":
@ -253,7 +253,7 @@ paths:
type: object type: object
title: Event title: Event
allOf: allOf:
- "$ref": "events/core/event.json" - "$ref": "core-event-schema/event.json"
rooms: rooms:
type: array type: array
items: items:
@ -294,7 +294,7 @@ paths:
type: object type: object
title: RoomEvent title: RoomEvent
allOf: allOf:
- "$ref": "events/core/room_event.json" - "$ref": "core-event-schema/room_event.json"
required: ["start", "end", "chunk"] required: ["start", "end", "chunk"]
state: state:
type: array type: array
@ -307,7 +307,7 @@ paths:
title: StateEvent title: StateEvent
type: object type: object
allOf: allOf:
- "$ref": "events/core/state_event.json" - "$ref": "core-event-schema/state_event.json"
visibility: visibility:
type: string type: string
enum: ["private", "public"] enum: ["private", "public"]
@ -343,13 +343,13 @@ paths:
"body": "Hello world!", "body": "Hello world!",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"room_id:" "!wfgy43Sg4a:matrix.org", "room_id:": "!wfgy43Sg4a:matrix.org",
"user_id": "@bob:matrix.org", "user_id": "@bob:matrix.org",
"event_id": "$asfDuShaf7Gafaw:matrix.org", "event_id": "$asfDuShaf7Gafaw:matrix.org",
"type": "m.room.message" "type": "m.room.message"
} }
schema: schema:
allOf: allOf:
- "$ref": "events/core/event.json" - "$ref": "core-event-schema/event.json"
404: 404:
description: The event was not found or you do not have permission to read this event. description: The event was not found or you do not have permission to read this event.

@ -3,7 +3,7 @@
"title": "Room Event", "title": "Room Event",
"description": "In addition to the Event fields, Room Events MUST have the following additional field.", "description": "In addition to the Event fields, Room Events MUST have the following additional field.",
"allOf":[{ "allOf":[{
"$ref": "core/event.json" "$ref": "core-event-schema/event.json"
}], }],
"properties": { "properties": {
"room_id": { "room_id": {

@ -3,7 +3,7 @@
"title": "State Event", "title": "State Event",
"description": "In addition to the Room Event fields, State Events have the following additional fields.", "description": "In addition to the Room Event fields, State Events have the following additional fields.",
"allOf":[{ "allOf":[{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"state_key": { "state_key": {

@ -2,7 +2,7 @@
"type": "object", "type": "object",
"description": "This event is sent by the callee when they wish to answer the call.", "description": "This event is sent by the callee when they wish to answer the call.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -2,7 +2,7 @@
"type": "object", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -2,7 +2,7 @@
"type": "object", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -2,7 +2,7 @@
"type": "object", "type": "object",
"description": "This event is sent by the caller when they wish to establish a call.", "description": "This event is sent by the caller when they wish to establish a call.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Informs the room about what room aliases it has been given.", "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.", "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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Informs the room as to which alias is the canonical one.", "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.", "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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "The first event in the room.", "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.", "description": "This is the first event in a room and cannot be changed. It acts as the root of all other events.",
"allOf": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Controls visibility of history.", "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.", "description": "This event controls whether a member of a room can see the events that happened in a room from before they joined.",
"allOf": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Describes how users are allowed to join the room.", "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.", "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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "The current membership state of a user in the room.", "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/<room id>/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.", "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms/<room id>/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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Message", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "AudioMessage", "title": "AudioMessage",
"description": "This message represents a single audio clip.", "description": "This message represents a single audio clip.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "EmoteMessage", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "FileMessage", "title": "FileMessage",
"description": "This message represents a generic file.", "description": "This message represents a generic file.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {
@ -49,7 +49,7 @@
"title": "ImageInfo", "title": "ImageInfo",
"description": "Metadata about the image referred to in ``thumbnail_url``.", "description": "Metadata about the image referred to in ``thumbnail_url``.",
"allOf": [{ "allOf": [{
"$ref": "core/msgtype_infos/image_info.json" "$ref": "core-event-schema/msgtype_infos/image_info.json"
}] }]
} }
}, },

@ -3,7 +3,7 @@
"title": "ImageMessage", "title": "ImageMessage",
"description": "This message represents a single image and an optional thumbnail.", "description": "This message represents a single image and an optional thumbnail.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {
@ -30,7 +30,7 @@
"title": "ImageInfo", "title": "ImageInfo",
"description": "Metadata about the image referred to in ``thumbnail_url``.", "description": "Metadata about the image referred to in ``thumbnail_url``.",
"allOf": [{ "allOf": [{
"$ref": "core/msgtype_infos/image_info.json" "$ref": "core-event-schema/msgtype_infos/image_info.json"
}] }]
}, },
"info": { "info": {

@ -3,7 +3,7 @@
"title": "LocationMessage", "title": "LocationMessage",
"description": "This message represents a real-world location.", "description": "This message represents a real-world location.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {
@ -29,7 +29,7 @@
"type": "object", "type": "object",
"title": "ImageInfo", "title": "ImageInfo",
"allOf": [{ "allOf": [{
"$ref": "core/msgtype_infos/image_info.json" "$ref": "core-event-schema/msgtype_infos/image_info.json"
}] }]
} }
}, },

@ -3,7 +3,7 @@
"title": "NoticeMessage", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "TextMessage", "title": "TextMessage",
"description": "This message is the most basic message and is used to represent text.", "description": "This message is the most basic message and is used to represent text.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "VideoMessage", "title": "VideoMessage",
"description": "This message represents a single video clip.", "description": "This message represents a single video clip.",
"allOf": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {
@ -54,7 +54,7 @@
"type": "object", "type": "object",
"title": "ImageInfo", "title": "ImageInfo",
"allOf": [{ "allOf": [{
"$ref": "core/msgtype_infos/image_info.json" "$ref": "core-event-schema/msgtype_infos/image_info.json"
}] }]
} }
} }

@ -3,7 +3,7 @@
"title": "MessageFeedback", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -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.", "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", "type": "object",
"allOf": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Defines the power levels (privileges) of users in the room.", "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.", "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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Redaction", "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.", "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": [{ "allOf": [{
"$ref": "core/room_event.json" "$ref": "core-event-schema/room_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -3,7 +3,7 @@
"title": "Topic", "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.", "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": [{ "allOf": [{
"$ref": "core/state_event.json" "$ref": "core-event-schema/state_event.json"
}], }],
"properties": { "properties": {
"content": { "content": {

@ -56,7 +56,7 @@ func main() {
go doPopulate(ch, dir) go doPopulate(ch, dir)
go watchFS(ch, w) go watchFS(ch, w)
fmt.Printf("Listening on port %d\n", *port)
http.HandleFunc("/", serve) http.HandleFunc("/", serve)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))

@ -4,6 +4,7 @@ from docutils.core import publish_file
import fileinput import fileinput
import glob import glob
import os import os
import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
@ -14,11 +15,65 @@ stylesheets = {
"stylesheet_path": ["basic.css", "nature.css"] "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: with open(out_file_name, "wb") as outfile:
for f in sorted(glob.glob("../specification/*.rst")): for f in sorted(glob.glob("../specification/*.rst")):
with open(f, "rb") as infile: 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): def rst2html(i, o):
@ -49,7 +104,7 @@ def run_through_template(input):
) )
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
with open(tmpfile, 'r') as f: with open(tmpfile, 'r') as f:
print f.read() sys.stderr.write(f.read() + "\n")
raise raise
def prepare_env(): def prepare_env():
@ -67,7 +122,7 @@ def cleanup_env():
def main(): def main():
prepare_env() prepare_env()
glob_spec_to("tmp/full_spec.rst") cat_spec_sections_to("tmp/full_spec.rst")
run_through_template("tmp/full_spec.rst") run_through_template("tmp/full_spec.rst")
shutil.copy("../supporting-docs/howtos/client-server.rst", "tmp/howto.rst") shutil.copy("../supporting-docs/howtos/client-server.rst", "tmp/howto.rst")
run_through_template("tmp/howto.rst") run_through_template("tmp/howto.rst")

@ -79,7 +79,7 @@ The functionality that Matrix provides includes:
- Extensible user management (inviting, joining, leaving, kicking, banning) - Extensible user management (inviting, joining, leaving, kicking, banning)
mediated by a power-level based user privilege system. mediated by a power-level based user privilege system.
- Extensible room state management (room naming, aliasing, topics, bans) - 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) - Managing user accounts (registration, login, logout)
- Use of 3rd Party IDs (3PIDs) such as email addresses, phone numbers, - Use of 3rd Party IDs (3PIDs) such as email addresses, phone numbers,
Facebook accounts to authenticate, identify and discover users on Matrix. 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 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 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 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. manner.
Overview 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 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`` has a ``type`` which is used to differentiate different kinds of data. ``type``
values MUST be uniquely globally namespaced following Java's `package naming values MUST be uniquely globally namespaced following Java's `package naming
conventions conventions`_, e.g.
<http://docs.oracle.com/javase/specs/jls/se5.0/html/packages.html#7.7>`, e.g.
``com.example.myapp.event``. The special top-level namespace ``m.`` is reserved ``com.example.myapp.event``. The special top-level namespace ``m.`` is reserved
for events defined in the Matrix specification - for instance ``m.room.message`` 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 is the event type for instant messages. Events are usually sent in the context
of a "Room". of a "Room".
.. _package naming conventions: https://en.wikipedia.org/wiki/Java_package#Package_naming_conventions
Event Graphs Event Graphs
~~~~~~~~~~~~ ~~~~~~~~~~~~
Events exchanged in the context of a room are stored in a directed acyclic graph 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 (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 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 have no chronological successor from the perspective of the homeserver
which created the event. 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 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. 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 Presence Lists
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -385,7 +387,7 @@ Profiles
Users may publish arbitrary key/value data associated with their account - such Users may publish arbitrary key/value data associated with their account - such
as a human readable ``display name``, a profile photo URL, contact information 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 In Client-Server API v2, profile data is typed using namespaced keys for
interoperability, much like events - e.g. ``m.profile.display_name``. 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 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 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 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 custom namespace ``com.mydomain.here``, and a
``FORBIDDEN`` code, the error code should look like ``FORBIDDEN`` code, the error code should look like
``COM.MYDOMAIN.HERE_FORBIDDEN``. There may be additional keys depending on the ``COM.MYDOMAIN.HERE_FORBIDDEN``. There may be additional keys depending on the

@ -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 messages, control rooms and synchronise conversation history. It is designed to
support both lightweight clients which store no state and lazy-load data from 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 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 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 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 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 but backwards-incompatible refinement of Version 1 and will be released
shortly. 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 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 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, 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 absence of that login stage type in the 'completed' array indicating whether
that stage is complete. that stage is complete.
@ -204,7 +204,7 @@ Password-based
:Type: :Type:
``m.login.password`` ``m.login.password``
:Description: :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:: 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``. skipped and the "uri" key can be the ``Authorization Request URI``.
The client then visits the ``Authorization Request URI``, which then shows the 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 OAuth2 Allow/Deny prompt. Hitting 'Allow' redirects to the ``redirect URI`` with
auth code. Home servers can choose any path for the ``redirect URI``. Once the the auth code. Home servers can choose any path for the ``redirect URI``. Once
OAuth flow has completed, the client retries the request with the session only, the OAuth flow has completed, the client retries the request with the session
as above. only, as above.
Email-based (identity server) 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. ``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 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. the authentication has been completed.
Pagination Pagination
@ -373,7 +373,7 @@ now show page 3 (rooms R11 -> 15)::
Returns: R11,R12,R13,R14,R15 Returns: R11,R12,R13,R14,R15
Note that tokens are treated in an *exclusive*, not inclusive, manner. The end 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 request was made, R10 did not appear again, even though from=9 was specified. If
you know the token, you already have the data. 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' 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 $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 available or until your specified timeout elapses, and then returns a
new paginatable chunk of events alongside new start and end parameters:: 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 response. This token can be used in the next request to continue where the
last request left off. 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 .. TODO
is deduplication actually a hard requirement in CS v2? is deduplication actually a hard requirement in CS v2?
@ -493,7 +493,7 @@ Room events are split into two categories:
:Message events: :Message events:
These are events which describe transient "once-off" activity in a room: 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. VoIP call. These used to be called 'non-state' events.
This specification outlines several events, all with the event type prefix This specification outlines several events, all with the event type prefix
@ -890,11 +890,8 @@ directly by sending the following request to
"membership": "leave" "membership": "leave"
} }
See the `Room events`_ section for more information on ``m.room.member``. 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.
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. If all members in a room leave, that room becomes eligible for deletion.
Banning users in a room Banning users in a room
@ -932,7 +929,7 @@ Registering for a user account is done using the request::
POST $V2PREFIX/register POST $V2PREFIX/register
This API endpoint uses the User-Interactive Authentication API. 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: The body of the POST request is a JSON object containing:
@ -1020,7 +1017,7 @@ The third party identifier credentials object comprises:
id_server id_server
The colon-separated hostname and port of the Identity Server used to 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. colon should be omitted.
sid sid
The session ID given by the Identity Server The session ID given by the Identity Server

@ -1,25 +1,27 @@
Typing Notifications Typing Notifications
==================== --------------------
Client APIs Client APIs
----------- ~~~~~~~~~~~
To set "I am typing for the next N msec":: To set "I am typing for the next N msec"::
PUT .../rooms/<room_id>/typing/<user_id> PUT .../rooms/<room_id>/typing/<user_id>
Content: { "typing": true, "timeout": N } 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 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 is still typing; a safety margin of 5 seconds before the expected
timeout runs out. Just keep declaring a new timeout, it will replace the old timeout runs out is recommended. Just keep declaring a new timeout, it will
one. replace the old one.
To set "I am no longer typing":: To set "I am no longer typing"::
PUT ../rooms/<room_id>/typing/<user_id> PUT ../rooms/<room_id>/typing/<user_id>
Content: { "typing": false } Content: { "typing": false }
Client Events Client Events
------------- ~~~~~~~~~~~~~
All room members will receive an event on the event stream:: 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. should mark as not typing, any user ID who is not in that list.
Server APIs Server APIs
----------- ~~~~~~~~~~~
Servers will emit EDUs in the following form:: Servers will emit EDUs in the following form::
@ -46,13 +48,14 @@ Servers will emit EDUs in the following form::
"content": { "content": {
"room_id": "!room-id-here:matrix.org", "room_id": "!room-id-here:matrix.org",
"user_id": "@user-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 Server EDUs don't (currently) contain timing information; it is up to
originating HSes to ensure they eventually send "stop" notifications. originating HSes to ensure they eventually send "stop" notifications.
((This will eventually need addressing, as part of the wider typing/presence .. TODO
timer addition work)) ((This will eventually need addressing, as part of the wider typing/presence
timer addition work))

@ -1,14 +1,13 @@
Receipts Receipts
======== --------
Receipts are used to publish which events in a room the user or their devices 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. 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
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. as, say, ``read`` indicates the user has read all events *up to* that event.
Client-Server API Client-Server API
----------------- ~~~~~~~~~~~~~~~~~
Clients will receive receipts in the following format:: 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 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`` and v2 sync APIs the receipts are listed in a separate top level ``receipts``
key. 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.
Each ``user_id``, ``receipt_type`` pair must be associated with only a single Deltas update existing mappings, clobbering based on ``user_id``,
``event_id``. ``receipt_type`` pairs.
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:: 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 Server-Server API
----------------- ~~~~~~~~~~~~~~~~~
Receipts are sent across federation as EDUs with type ``m.receipt``. The Receipts are sent across federation as EDUs with type ``m.receipt``. The
format of the EDUs are:: 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.

@ -1,5 +1,5 @@
Room History Visibility Room History Visibility
======================= -----------------------
Whether a member of a room can see the events that happened in a room from 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 before they joined the room is controlled by the ``history_visibility`` key

@ -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 events so that clients already have these details to hand, and do not have to
perform extra roundtrips to query it. 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.

@ -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.

@ -1,8 +1,8 @@
Signing Events Signing Events
============== --------------
Canonical JSON Canonical JSON
-------------- ~~~~~~~~~~~~~~
Matrix events are represented using JSON objects. If we want to sign 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 events we need to encode the JSON as a binary string. Unfortunately the same
@ -30,7 +30,7 @@ using this representation.
value, value,
# Encode code-points outside of ASCII as UTF-8 rather than \u escapes # Encode code-points outside of ASCII as UTF-8 rather than \u escapes
ensure_ascii=False, ensure_ascii=False,
# Remove unecessary white space. # Remove unnecessary white space.
separators=(',',':'), separators=(',',':'),
# Sort the keys of dictionaries. # Sort the keys of dictionaries.
sort_keys=True, sort_keys=True,
@ -38,7 +38,7 @@ using this representation.
).encode("UTF-8") ).encode("UTF-8")
Grammar Grammar
~~~~~~~ +++++++
Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing
insignificant whitespace, fractions, exponents and redundant character escapes 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 / %x75.30.30.31 (%x30-39 / %x61-66) ; u001X
Signing JSON Signing JSON
------------ ~~~~~~~~~~~~
We can now sign a JSON object by encoding it as a sequence of bytes, computing 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 the signature for that sequence and then adding the signature to the original
JSON object. JSON object.
Signing Details Signing Details
~~~~~~~~~~~~~~~ +++++++++++++++
JSON is signed by encoding the JSON object without ``signatures`` or keys grouped 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 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 return json_object
Checking for a Signature Checking for a Signature
~~~~~~~~~~~~~~~~~~~~~~~~ ++++++++++++++++++++++++
To check if an entity has signed a JSON object a server does the following 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. the check fails. Otherwise the check succeeds.
Signing Events Signing Events
-------------- ~~~~~~~~~~~~~~
Signing events is a more complicated process since servers can choose to redact 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 non-essential parts of an event. Before signing the event it is encoded as

@ -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 application service is merely augmenting the room itself (e.g. providing
logging or searching facilities). logging or searching facilities).
- Namespaces are represented by POSIX extended regular expressions, - Namespaces are represented by POSIX extended regular expressions,
e.g.: e.g:
.. code-block:: yaml .. code-block:: yaml
users: users:
- exclusive: true - exclusive: true
regex: @irc.freenode.net/.* regex: @irc.freenode.net_.*
Home Server -> Application Service API Home Server -> Application Service API
@ -326,7 +326,7 @@ but only if the application service has defined the namespace as ``exclusive``.
ID conventions ID conventions
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
.. NOTE:: .. TODO-spec
- Giving HSes the freedom to namespace still feels like the Right Thing here. - 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 - Exposing a public API provides the consistency which was the main complaint
against namespacing. against namespacing.
@ -345,7 +345,7 @@ types, including:
- MSISDNs (``tel``) - MSISDNs (``tel``)
- Email addresses (``mailto``) - Email addresses (``mailto``)
- IRC nicks (``irc`` - https://tools.ietf.org/html/draft-butcher-irc-url-04) - IRC nicks (``irc`` - https://tools.ietf.org/html/draft-butcher-irc-url-04)
- XMPP (xep-0032) - XMPP (XEP-0032)
- SIP URIs (RFC 3261) - SIP URIs (RFC 3261)
As a result, virtual user IDs SHOULD relate to their URI counterpart. This 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 Active Application Services
---------------------------- ----------------------------
.. NOTE::
This section is a work in progress.
.. TODO-spec .. TODO-spec
API that provides hooks into the server so that you can intercept and API that provides hooks into the server so that you can intercept and
@ -419,3 +421,4 @@ Policy Servers
Enforcing policies Enforcing policies
------------------ ------------------

@ -2,10 +2,9 @@ Federation API
============== ==============
Matrix home servers use the Federation APIs (also known as server-server APIs) Matrix home servers use the Federation APIs (also known as server-server APIs)
to communicate with each other. to communicate with each other. Home servers use these APIs to push messages to
Home servers use these APIs to push messages to each other in real-time, to each other in real-time, to request historic messages from each other, and to
request historic messages from each other, and to query profile and presence query profile and presence information about users on each other's servers.
information about users on each other's servers.
The APIs are implemented using HTTPS GETs and PUTs between each of the The APIs are implemented using HTTPS GETs and PUTs between each of the
servers. These HTTPS requests are strongly authenticated using public key servers. These HTTPS requests are strongly authenticated using public key
@ -21,7 +20,7 @@ Persisted Data Units (PDUs):
context. context.
Like email, it is the responsibility of the originating server of a PDU 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 using the originating server's public key so that it is possible to
deliver them through third-party servers. 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 response with their own key. A server may query multiple notary servers to
ensure that they all report the same public keys. ensure that they all report the same public keys.
This approach is borrowed from the Perspectives Project This approach is borrowed from the `Perspectives Project`_, but modified to
(http://perspectives-project.org/), but modified to include the NACL keys and to include the NACL keys and to use JSON instead of XML. It has the advantage of
use JSON instead of XML. It has the advantage of avoiding a single trust-root avoiding a single trust-root since each server is free to pick which notary
since each server is free to pick which notary servers they trust and can servers they trust and can corroborate the keys returned by a given notary
corroborate the keys returned by a given notary server by querying other server by querying other servers.
servers.
.. _Perspectives Project: http://perspectives-project.org/
Publishing Keys Publishing Keys
_______________ _______________
Home servers publish the allowed TLS fingerprints and signing keys in a JSON 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 ``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`` 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 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 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 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. 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 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 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 resulting signatures are added as an Authorization header with an auth scheme
of X-Matrix. of X-Matrix. Note that the target field should include the full path starting with
Note that the target field should include the full path starting with
``/_matrix/...``, including the ``?`` and any query parameters if present, but ``/_matrix/...``, including the ``?`` and any query parameters if present, but
should not include the leading ``https:``, nor the destination server's should not include the leading ``https:``, nor the destination server's
hostname. hostname.
@ -656,12 +654,12 @@ State Conflict Resolution
- How does this work with deleting current state - How does this work with deleting current state
- How do we reject invalid federation traffic? - How do we reject invalid federation traffic?
[[TODO(paul): At this point we should probably have a long description of how [[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 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 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 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 relate to the server/system as a whole, not specifically to server-server
federation.]] federation.]]
Presence Presence
-------- --------
@ -677,8 +675,8 @@ Performing a presence update and poll subscription request::
Each should be an object with the following keys: Each should be an object with the following keys:
user_id: string containing a User ID user_id: string containing a User ID
presence: "offline"|"unavailable"|"online"|"free_for_chat" presence: "offline"|"unavailable"|"online"|"free_for_chat"
status_msg: (optional) string of freeform text status_msg: (optional) string of free-form text
last_active_ago: miliseconds since the last activity by the user last_active_ago: milliseconds since the last activity by the user
poll: (optional): list of strings giving User IDs 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 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 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 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:: Sending a presence invite::
@ -721,7 +719,7 @@ Rejecting a presence invite::
Content keys - as for m.presence_invite Content keys - as for m.presence_invite
.. TODO-doc .. TODO-doc
- Explain the timing-based roundtrip reduction mechanism for presence - Explain the timing-based round-trip reduction mechanism for presence
messages messages
- Explain the zero-byte presence inference logic - Explain the zero-byte presence inference logic
See also: docs/client-server/model/presence See also: docs/client-server/model/presence
@ -742,8 +740,8 @@ Querying profile information::
field: (optional) string giving a field name field: (optional) string giving a field name
Returns: JSON object containing the following keys: Returns: JSON object containing the following keys:
displayname: string of freeform text displayname: string of free-form text
avatar_url: string containing an http-scheme URL avatar_url: string containing an HTTP-scheme URL
If the query contains the optional ``field`` key, it should give the name of a 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 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 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 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. joining with. This list may or may not include the server answering the query.

@ -39,10 +39,10 @@ thumbnailing method::
<thumbnail> <thumbnail>
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 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 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 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 the requested size. The client should scale the image if it needs to fit
within a given rectangle. 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. requested. However homeservers should provide exact matches where reasonable.
Homeservers must never upscale images. Homeservers must never upscale images.
Security Security considerations
-------- -----------------------
Clients may try to upload very large files. Homeservers should not store files Clients may try to upload very large files. Homeservers should not store files
that are too large and should not serve them to clients. that are too large and should not serve them to clients.

@ -70,7 +70,7 @@ Room Rules
Sender Sender
These rules configure notification behaviour for messages from a specific, 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 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 Underride
These are identical to override rules, but have a lower priority than content, These are identical to override rules, but have a lower priority than content,
room and sender rules. 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 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 (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 Predefined Rules
---------------- ----------------
@ -128,7 +128,7 @@ with these IDs, their semantics should match those given below:
{ {
"rule_id": ".m.rule.contains_user_name" "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": [ "actions": [
"notify", "notify",
{ {
@ -220,7 +220,7 @@ with these IDs, their semantics should match those given below:
Push Rules: Actions: Push Rules: Actions:
-------------------- --------------------
All rules have an associated list of 'actions'. An action affects if and how a 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 following actions, although if Home servers wish to support more, they are free
to do so: to do so:
@ -241,11 +241,11 @@ set_tweak
Actions that have no parameters are represented as a string. Otherwise, they are 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 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 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: that is sent in the notification poke. The following tweaks are defined:
sound sound
@ -275,7 +275,7 @@ do so:
event_match event_match
This is a glob pattern match on a field of the event. Parameters: 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 * 'pattern': The glob-style pattern to match against. Patterns with no
special glob characters should be treated as having asterisks special glob characters should be treated as having asterisks
prepended and appended when testing the condition. 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 '>=' 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, 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 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, 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 but instead have predefined conditions, the behaviour of which can be configured
@ -314,7 +314,7 @@ scope
Either 'global' or 'device/<profile_tag>' to specify global rules or Either 'global' or 'device/<profile_tag>' to specify global rules or
device rules for the given profile_tag. device rules for the given profile_tag.
kind kind
The kind of rule, ie. 'override', 'underride', 'sender', 'room', 'content'. The kind of rule, i.e. 'override', 'underride', 'sender', 'room', 'content'.
rule_id rule_id
The identifier for the rule. The identifier for the rule.
@ -330,7 +330,7 @@ after
rule. rule.
All requests to the push rules API also require an access_token as a query 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 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 'actions' key and either conditions (under the 'conditions' key) or the

@ -1,7 +1,7 @@
HTTP Notification Protocol 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. events.
Notifications are sent as HTTP POST requests to the URL configured when the 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 This is a dictionary of the current number of unacknowledged communications
for the recipient user. Counts whose value is zero are omitted. for the recipient user. Counts whose value is zero are omitted.
unread 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. member of.
missed_calls 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. which they are a member.
device device
This is an array of devices that the notification should be sent to. 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 user_is_target
This is true if the user receiving the notification is the subject of a member 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). ID).
The recipient of an HTTP notification should respond with an HTTP 2xx response 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 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 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:: The endpoint should return a JSON dictionary as follows::

@ -3,10 +3,10 @@ Address book repository
.. NOTE:: .. NOTE::
This section is a work in progress. 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 .. 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. - 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 - processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of
contacts), etc. contacts), etc.

@ -0,0 +1,8 @@
Identity Servers
================
.. NOTE::
This section is a work in progress.
.. TODO-doc Dave
- 3PIDs and identity server, functions

@ -128,11 +128,3 @@ Threat: Disclosure to Servers Within Chatroom
An attacker could take control of a server within a chatroom to expose message An attacker could take control of a server within a chatroom to expose message
contents or metadata for messages in that room. 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

@ -8,6 +8,16 @@ import subprocess
import urllib import urllib
import yaml 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): def get_json_schema_object_fields(obj, enforce_title=False):
# Algorithm: # Algorithm:
# f.e. property => add field info (if field is object then recurse) # 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 += ( desc += (
" Must be '%s'." % props[key_name]["enum"][0] " Must be '%s'." % props[key_name]["enum"][0]
) )
if isinstance(value_type, list):
value_type = " or ".join(value_type)
fields["rows"].append({ fields["rows"].append({
"key": key_name, "key": key_name,
@ -266,7 +278,7 @@ class MatrixUnits(Units):
} }
def load_swagger_apis(self): def load_swagger_apis(self):
path = "../api/client-server/v1" path = V1_CLIENT_API
apis = {} apis = {}
for filename in os.listdir(path): for filename in os.listdir(path):
if not filename.endswith(".yaml"): if not filename.endswith(".yaml"):
@ -281,7 +293,7 @@ class MatrixUnits(Units):
return apis return apis
def load_common_event_fields(self): def load_common_event_fields(self):
path = "../event-schemas/schema/v1/core" path = CORE_EVENT_SCHEMA
event_types = {} event_types = {}
for (root, dirs, files) in os.walk(path): for (root, dirs, files) in os.walk(path):
@ -320,7 +332,7 @@ class MatrixUnits(Units):
return event_types return event_types
def load_event_examples(self): def load_event_examples(self):
path = "../event-schemas/examples/v1" path = V1_EVENT_EXAMPLES
examples = {} examples = {}
for filename in os.listdir(path): for filename in os.listdir(path):
if not filename.startswith("m."): if not filename.startswith("m."):
@ -332,7 +344,7 @@ class MatrixUnits(Units):
return examples return examples
def load_event_schemas(self): def load_event_schemas(self):
path = "../event-schemas/schema/v1" path = V1_EVENT_SCHEMA
schemata = {} schemata = {}
for filename in os.listdir(path): for filename in os.listdir(path):
@ -361,8 +373,8 @@ class MatrixUnits(Units):
# add typeof # add typeof
base_defs = { base_defs = {
"core#/definitions/room_event": "Message Event", ROOM_EVENT: "Message Event",
"core#/definitions/state_event": "State Event" STATE_EVENT: "State Event"
} }
if type(json_schema.get("allOf")) == list: if type(json_schema.get("allOf")) == list:
schema["typeof"] = base_defs.get( schema["typeof"] = base_defs.get(
@ -399,7 +411,6 @@ class MatrixUnits(Units):
"`m.room.message msgtypes`_." "`m.room.message msgtypes`_."
) )
# Assign state key info if it has some # Assign state key info if it has some
if schema["typeof"] == "State Event": if schema["typeof"] == "State Event":
skey_desc = Units.prop( skey_desc = Units.prop(
@ -413,7 +424,7 @@ class MatrixUnits(Units):
return schemata return schemata
def load_spec_meta(self): def load_spec_meta(self):
path = "../CHANGELOG.rst" path = CHANGELOG
title_part = None title_part = None
version = None version = None
changelog_lines = [] changelog_lines = []

Loading…
Cancel
Save