From c5cec1cc77029c21f0117c318c522ab320de3923 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Fri, 17 Oct 2014 16:50:04 +0100 Subject: Rename 'meta' to 'unsigned' --- docs/server-server/signing.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/server-server/signing.rst b/docs/server-server/signing.rst index dae10f121b..60c701ca91 100644 --- a/docs/server-server/signing.rst +++ b/docs/server-server/signing.rst @@ -1,13 +1,13 @@ Signing JSON ============ -JSON is signed by encoding the JSON object without ``signatures`` or ``meta`` +JSON is signed by encoding the JSON object without ``signatures`` or ``unsigned`` keys using a canonical encoding. The JSON bytes are then signed using the signature algorithm and the signature encoded using base64 with the padding stripped. The resulting base64 signature is added to an object under the *signing key identifier* which is added to the ``signatures`` object under the name of the server signing it which is added back to the original JSON object -along with the ``meta`` object. +along with the ``unsigned`` object. The *signing key identifier* is the concatenation of the *signing algorithm* and a *key version*. The *signing algorithm* identifies the algorithm used to @@ -15,8 +15,8 @@ sign the JSON. The currently support value for *signing algorithm* is ``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key version* is used to distinguish between different signing keys used by the same entity. -The ``meta`` object and the ``signatures`` object are not covered by the -signature. Therefore intermediate servers can add metadata such as time stamps +The ``unsigned`` object and the ``signatures`` object are not covered by the +signature. Therefore intermediate servers can add unsigneddata such as time stamps and additional signatures. @@ -27,7 +27,7 @@ and additional signatures. "signing_keys": { "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" }, - "meta": { + "unsigned": { "retrieved_ts_ms": 922834800000 }, "signatures": { @@ -41,7 +41,7 @@ and additional signatures. def sign_json(json_object, signing_key, signing_name): signatures = json_object.pop("signatures", {}) - meta = json_object.pop("meta", None) + unsigned = json_object.pop("unsigned", None) signed = signing_key.sign(encode_canonical_json(json_object)) signature_base64 = encode_base64(signed.signature) @@ -50,8 +50,8 @@ and additional signatures. signatures.setdefault(sigature_name, {})[key_id] = signature_base64 json_object["signatures"] = signatures - if meta is not None: - json_object["meta"] = meta + if unsigned is not None: + json_object["unsigned"] = unsigned return json_object -- cgit 1.4.1 From 0b51d970b4d53ef9c2c66e3537f56331f436ae76 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 10 Nov 2014 18:43:16 +0000 Subject: document up the current architecture a bit based on the workshop the other week --- docs/client-server/OLD_specification.rst | 2 +- docs/implementation-notes/architecture.rst | 65 +++++++++++++++++++++++ docs/implementation-notes/python_architecture.rst | 6 +++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 docs/implementation-notes/architecture.rst (limited to 'docs') diff --git a/docs/client-server/OLD_specification.rst b/docs/client-server/OLD_specification.rst index 47fba5eeac..425ae57d93 100644 --- a/docs/client-server/OLD_specification.rst +++ b/docs/client-server/OLD_specification.rst @@ -4,7 +4,7 @@ Matrix Client-Server API .. WARNING:: - This specification is old. Please see /docs/specification.rst instead. + This specification is old. Please see matrix-doc/specification instead. diff --git a/docs/implementation-notes/architecture.rst b/docs/implementation-notes/architecture.rst new file mode 100644 index 0000000000..b447858202 --- /dev/null +++ b/docs/implementation-notes/architecture.rst @@ -0,0 +1,65 @@ +Synapse Architecture +==================== + +As of the end of Oct 2014, Synapse's overall architecture looks like:: + + Notifier + ^ | + | | + .------------|------. + | handlers/ | | + | v | + | Event*Handler<---------> rest/* <=> Client + | Rooms*Handler | + HSes <=> federation/* <==> FederationHandler | + | | PresenceHandler | + | | TypingHandler | + | '-------------------' + | | | + | state/* | + | | | + | v v + `--------------> storage/* + | + v + .----. + | DB | + '----' + +* Handlers: business logic of synapse itself. Follows a set contract of BaseHandler: + + * BaseHandler gives us onNewRoomEvent which: (TODO: flesh this out and make it less cryptic): + + * handle_state(event) + * auth(event) + * persist_event(event) + * notify notifier or federation(event) + + * PresenceHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + * TypingHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + * EventsHandler: handles the events stream... + * FederationHandler: - gets PDU from Federation Layer; turns into an event; + follows basehandler functionality. + * RoomsHandler: does all the room logic, including members - lots of classes in + RoomsHandler. + * ProfileHandler: talks to the storage to store/retrieve profile info. + +* EventFactory: generates events of particular event types. +* Notifier: Backs the events handler +* REST: Interfaces handlers and events to the outside world via HTTP/JSON. + Converts events back and forth from JSON. +* Federation: holds the HTTP client & server to talk to other servers. Does + replication to make sure there's nothing missing in the graph. Handles + reliability. Handles txns. +* Distributor: generic event bus. used for presence & typing only currently. + Notifier could be implemented using Distributor - so far we are only using for + things which actually /require/ dynamic pluggability however as it can + obfuscate the actual flow of control. +* Auth: helper singleton to say whether a given event is allowed to do a given + thing (TODO: put this on the diagram) +* State: helper singleton: does state conflict resolution. You give it an event + and it tells you if it actually updates the state or not, and annotates the + event up properly and handles merge conflict resolution. +* Storage: abstracts the storage engine. diff --git a/docs/implementation-notes/python_architecture.rst b/docs/implementation-notes/python_architecture.rst index 8beaa615d0..2a5a2613c4 100644 --- a/docs/implementation-notes/python_architecture.rst +++ b/docs/implementation-notes/python_architecture.rst @@ -1,3 +1,9 @@ +.. WARNING:: + These architecture notes are spectacularly old, and date back to when Synapse + was just federation code in isolation. This should be merged into the main + spec. + + = Server to Server = == Server to Server Stack == -- cgit 1.4.1 From f987393b32212f4fea4efa88a8d79a0d677e6f30 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 10 Nov 2014 21:56:52 +0000 Subject: moar boxes. --- docs/implementation-notes/architecture.rst | 39 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) (limited to 'docs') diff --git a/docs/implementation-notes/architecture.rst b/docs/implementation-notes/architecture.rst index b447858202..5b6463d78a 100644 --- a/docs/implementation-notes/architecture.rst +++ b/docs/implementation-notes/architecture.rst @@ -3,24 +3,27 @@ Synapse Architecture As of the end of Oct 2014, Synapse's overall architecture looks like:: - Notifier - ^ | - | | - .------------|------. - | handlers/ | | - | v | - | Event*Handler<---------> rest/* <=> Client - | Rooms*Handler | - HSes <=> federation/* <==> FederationHandler | - | | PresenceHandler | - | | TypingHandler | - | '-------------------' - | | | - | state/* | - | | | - | v v - `--------------> storage/* - | + synapse + .-----------------------------------------------------. + | Notifier | + | ^ | | + | | | | + | .------------|------. | + | | handlers/ | | | + | | v | | + | | Event*Handler<---------> rest/* <=> Client + | | Rooms*Handler | | + HSes <=> federation/* <==> FederationHandler | | + | | | PresenceHandler | | + | | | TypingHandler | | + | | '-------------------' | + | | | | | + | | state/* | | + | | | | | + | | v v | + | `--------------> storage/* | + | | | + '--------------------------|--------------------------' v .----. | DB | -- cgit 1.4.1 From f45a6a70041dfb38a8f3266d17d4b9269dca0bf7 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Mon, 10 Nov 2014 22:07:08 +0000 Subject: Fix RST sublist formatting bug --- docs/implementation-notes/architecture.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'docs') diff --git a/docs/implementation-notes/architecture.rst b/docs/implementation-notes/architecture.rst index 5b6463d78a..b8476ddbf6 100644 --- a/docs/implementation-notes/architecture.rst +++ b/docs/implementation-notes/architecture.rst @@ -31,23 +31,23 @@ As of the end of Oct 2014, Synapse's overall architecture looks like:: * Handlers: business logic of synapse itself. Follows a set contract of BaseHandler: - * BaseHandler gives us onNewRoomEvent which: (TODO: flesh this out and make it less cryptic): + - BaseHandler gives us onNewRoomEvent which: (TODO: flesh this out and make it less cryptic): - * handle_state(event) - * auth(event) - * persist_event(event) - * notify notifier or federation(event) + + handle_state(event) + + auth(event) + + persist_event(event) + + notify notifier or federation(event) - * PresenceHandler: use distributor to get EDUs out of Federation. Very - lightweight logic built on the distributor - * TypingHandler: use distributor to get EDUs out of Federation. Very - lightweight logic built on the distributor - * EventsHandler: handles the events stream... - * FederationHandler: - gets PDU from Federation Layer; turns into an event; - follows basehandler functionality. - * RoomsHandler: does all the room logic, including members - lots of classes in - RoomsHandler. - * ProfileHandler: talks to the storage to store/retrieve profile info. + - PresenceHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + - TypingHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + - EventsHandler: handles the events stream... + - FederationHandler: - gets PDU from Federation Layer; turns into an event; + follows basehandler functionality. + - RoomsHandler: does all the room logic, including members - lots of classes in + RoomsHandler. + - ProfileHandler: talks to the storage to store/retrieve profile info. * EventFactory: generates events of particular event types. * Notifier: Backs the events handler -- cgit 1.4.1 From 303b455965b3a9a4045fefa4f329ac805fb034e8 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 04:35:16 +0000 Subject: trivial spacing fix --- docs/implementation-notes/architecture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/implementation-notes/architecture.rst b/docs/implementation-notes/architecture.rst index b8476ddbf6..98050428b9 100644 --- a/docs/implementation-notes/architecture.rst +++ b/docs/implementation-notes/architecture.rst @@ -11,7 +11,7 @@ As of the end of Oct 2014, Synapse's overall architecture looks like:: | .------------|------. | | | handlers/ | | | | | v | | - | | Event*Handler<---------> rest/* <=> Client + | | Event*Handler <--------> rest/* <=> Client | | Rooms*Handler | | HSes <=> federation/* <==> FederationHandler | | | | | PresenceHandler | | -- cgit 1.4.1 From bebca337c4c19b653d69536f9915ca185bade5c0 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 20:43:36 +0200 Subject: this has been merged into matrix-doc/specification/30_server_server_api.rst --- docs/server-server/specification.rst | 231 ----------------------------------- 1 file changed, 231 deletions(-) delete mode 100644 docs/server-server/specification.rst (limited to 'docs') diff --git a/docs/server-server/specification.rst b/docs/server-server/specification.rst deleted file mode 100644 index 17cffafdd4..0000000000 --- a/docs/server-server/specification.rst +++ /dev/null @@ -1,231 +0,0 @@ -=========================== -Matrix Server-to-Server API -=========================== - -A description of the protocol used to communicate between Matrix home servers; -also known as Federation. - - -Overview -======== - -The server-server API is a mechanism by which two home servers can exchange -Matrix event messages, both as a real-time push of current events, and as a -historic fetching mechanism to synchronise past history for clients to view. It -uses HTTP connections between each pair of servers involved as the underlying -transport. Messages are exchanged between servers in real-time by active pushing -from each server's HTTP client into the server of the other. Queries to fetch -historic data for the purpose of back-filling scrollback buffers and the like -can also be performed. - - - { Matrix clients } { Matrix clients } - ^ | ^ | - | events | | events | - | V | V - +------------------+ +------------------+ - | |---------( HTTP )---------->| | - | Home Server | | Home Server | - | |<--------( HTTP )-----------| | - +------------------+ +------------------+ - -There are three main kinds of communication that occur between home servers: - - * Queries - These are single request/response interactions between a given pair of - servers, initiated by one side sending an HTTP request to obtain some - information, and responded by the other. They are not persisted and contain - no long-term significant history. They simply request a snapshot state at the - instant the query is made. - - * EDUs - Ephemeral Data Units - These are notifications of events that are pushed from one home server to - another. They are not persisted and contain no long-term significant history, - nor does the receiving home server have to reply to them. - - * PDUs - Persisted Data Units - These are notifications of events that are broadcast from one home server to - any others that are interested in the same "context" (namely, a Room ID). - They are persisted to long-term storage and form the record of history for - that context. - -Where Queries are presented directly across the HTTP connection as GET requests -to specific URLs, EDUs and PDUs are further wrapped in an envelope called a -Transaction, which is transferred from the origin to the destination home server -using a PUT request. - - -Transactions and EDUs/PDUs -========================== - -The transfer of EDUs and PDUs between home servers is performed by an exchange -of Transaction messages, which are encoded as JSON objects with a dict as the -top-level element, passed over an HTTP PUT request. A Transaction is meaningful -only to the pair of home servers that exchanged it; they are not globally- -meaningful. - -Each transaction has an opaque ID and timestamp (UNIX epoch time in -milliseconds) generated by its origin server, an origin and destination server -name, a list of "previous IDs", and a list of PDUs - the actual message payload -that the Transaction carries. - - {"transaction_id":"916d630ea616342b42e98a3be0b74113", - "ts":1404835423000, - "origin":"red", - "destination":"blue", - "prev_ids":["e1da392e61898be4d2009b9fecce5325"], - "pdus":[...], - "edus":[...]} - -The "previous IDs" field will contain a list of previous transaction IDs that -the origin server has sent to this destination. Its purpose is to act as a -sequence checking mechanism - the destination server can check whether it has -successfully received that Transaction, or ask for a retransmission if not. - -The "pdus" field of a transaction is a list, containing zero or more PDUs.[*] -Each PDU is itself a dict containing a number of keys, the exact details of -which will vary depending on the type of PDU. Similarly, the "edus" field is -another list containing the EDUs. This key may be entirely absent if there are -no EDUs to transfer. - -(* Normally the PDU list will be non-empty, but the server should cope with -receiving an "empty" transaction, as this is useful for informing peers of other -transaction IDs they should be aware of. This effectively acts as a push -mechanism to encourage peers to continue to replicate content.) - -All PDUs have an ID, a context, a declaration of their type, a list of other PDU -IDs that have been seen recently on that context (regardless of which origin -sent them), and a nested content field containing the actual event content. - -[[TODO(paul): Update this structure so that 'pdu_id' is a two-element -[origin,ref] pair like the prev_pdus are]] - - {"pdu_id":"a4ecee13e2accdadf56c1025af232176", - "context":"#example.green", - "origin":"green", - "ts":1404838188000, - "pdu_type":"m.text", - "prev_pdus":[["blue","99d16afbc857975916f1d73e49e52b65"]], - "content":... - "is_state":false} - -In contrast to the transaction layer, it is important to note that the prev_pdus -field of a PDU refers to PDUs that any origin server has sent, rather than -previous IDs that this origin has sent. This list may refer to other PDUs sent -by the same origin as the current one, or other origins. - -Because of the distributed nature of participants in a Matrix conversation, it -is impossible to establish a globally-consistent total ordering on the events. -However, by annotating each outbound PDU at its origin with IDs of other PDUs it -has received, a partial ordering can be constructed allowing causallity -relationships to be preserved. A client can then display these messages to the -end-user in some order consistent with their content and ensure that no message -that is semantically in reply of an earlier one is ever displayed before it. - -PDUs fall into two main categories: those that deliver Events, and those that -synchronise State. For PDUs that relate to State synchronisation, additional -keys exist to support this: - - {..., - "is_state":true, - "state_key":TODO - "power_level":TODO - "prev_state_id":TODO - "prev_state_origin":TODO} - -[[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.]] - -EDUs, by comparison to PDUs, do not have an ID, a context, or a list of -"previous" IDs. The only mandatory fields for these are the type, origin and -destination home server names, and the actual nested content. - - {"edu_type":"m.presence", - "origin":"blue", - "destination":"orange", - "content":...} - - -Protocol URLs -============= - -All these URLs are namespaced within a prefix of - - /_matrix/federation/v1/... - -For active pushing of messages representing live activity "as it happens": - - PUT .../send/:transaction_id/ - Body: JSON encoding of a single Transaction - - Response: [[TODO(paul): I don't actually understand what - ReplicationLayer.on_transaction() is doing here, so I'm not sure what the - response ought to be]] - - The transaction_id path argument will override any ID given in the JSON body. - The destination name will be set to that of the receiving server itself. Each - embedded PDU in the transaction body will be processed. - - -To fetch a particular PDU: - - GET .../pdu/:origin/:pdu_id/ - - Response: JSON encoding of a single Transaction containing one PDU - - Retrieves a given PDU from the server. The response will contain a single new - Transaction, inside which will be the requested PDU. - - -To fetch all the state of a given context: - - GET .../state/:context/ - - Response: JSON encoding of a single Transaction containing multiple PDUs - - Retrieves a snapshot of the entire current state of the given context. The - response will contain a single Transaction, inside which will be a list of - PDUs that encode the state. - - -To backfill events on a given context: - - GET .../backfill/:context/ - Query args: v, limit - - Response: JSON encoding of a single Transaction containing multiple PDUs - - 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. These are then returned in a new Transaction containing all - off the PDUs. - - -To stream events all the events: - - GET .../pull/ - Query args: origin, v - - Response: JSON encoding of a single Transaction consisting of multiple PDUs - - Retrieves all of the transactions later than any version given by the "v" - arguments. [[TODO(paul): I'm not sure what the "origin" argument does because - I think at some point in the code it's got swapped around.]] - - -To make a query: - - GET .../query/:query_type - Query args: as specified by the individual query types - - Response: JSON encoding of a response object - - Performs a single query request on the receiving home server. The Query Type - part of the path specifies the kind of query being made, and its query - arguments have a meaning specific to that kind of query. The response is a - JSON-encoded object whose meaning also depends on the kind of query. -- cgit 1.4.1 From 216d5f6b521b08ac29e1a8039968f2b6ffe2a5ed Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 20:44:28 +0200 Subject: this is obsolete and lives in matrix-doc in specification/30_server_server_api.rst now --- docs/server-server/protocol-format.rst | 59 ---------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 docs/server-server/protocol-format.rst (limited to 'docs') diff --git a/docs/server-server/protocol-format.rst b/docs/server-server/protocol-format.rst deleted file mode 100644 index 2838253ab7..0000000000 --- a/docs/server-server/protocol-format.rst +++ /dev/null @@ -1,59 +0,0 @@ - -Transaction -=========== - -Required keys: - -============ =================== =============================================== - Key Type Description -============ =================== =============================================== -origin String DNS name of homeserver making this transaction. -ts Integer Timestamp in milliseconds on originating - homeserver when this transaction started. -previous_ids List of Strings List of transactions that were sent immediately - prior to this transaction. -pdus List of Objects List of updates contained in this transaction. -============ =================== =============================================== - - -PDU -=== - -Required keys: - -============ ================== ================================================ - Key Type Description -============ ================== ================================================ -context String Event context identifier -origin String DNS name of homeserver that created this PDU. -pdu_id String Unique identifier for PDU within the context for - the originating homeserver. -ts Integer Timestamp in milliseconds on originating - homeserver when this PDU was created. -pdu_type String PDU event type. -prev_pdus List of Pairs The originating homeserver and PDU ids of the - of Strings most recent PDUs the homeserver was aware of for - this context when it made this PDU. -depth Integer The maximum depth of the previous PDUs plus one. -============ ================== ================================================ - -Keys for state updates: - -================== ============ ================================================ - Key Type Description -================== ============ ================================================ -is_state Boolean True if this PDU is updating state. -state_key String Optional key identifying the updated state within - the context. -power_level Integer The asserted power level of the user performing - the update. -min_update Integer The required power level needed to replace this - update. -prev_state_id String The homeserver of the update this replaces -prev_state_origin String The PDU id of the update this replaces. -user String The user updating the state. -================== ============ ================================================ - - - - -- cgit 1.4.1 From b6c48a694b8f9d0ebef29024163a0763d40f1b30 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 20:45:11 +0200 Subject: haven't i already moved you to matrix-doc twice? :/ --- docs/server-server/signing.rst | 151 ----------------------------------------- 1 file changed, 151 deletions(-) delete mode 100644 docs/server-server/signing.rst (limited to 'docs') diff --git a/docs/server-server/signing.rst b/docs/server-server/signing.rst deleted file mode 100644 index 60c701ca91..0000000000 --- a/docs/server-server/signing.rst +++ /dev/null @@ -1,151 +0,0 @@ -Signing JSON -============ - -JSON is signed by encoding the JSON object without ``signatures`` or ``unsigned`` -keys using a canonical encoding. The JSON bytes are then signed using the -signature algorithm and the signature encoded using base64 with the padding -stripped. The resulting base64 signature is added to an object under the -*signing key identifier* which is added to the ``signatures`` object under the -name of the server signing it which is added back to the original JSON object -along with the ``unsigned`` object. - -The *signing key identifier* is the concatenation of the *signing algorithm* -and a *key version*. The *signing algorithm* identifies the algorithm used to -sign the JSON. The currently support value for *signing algorithm* is -``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key version* -is used to distinguish between different signing keys used by the same entity. - -The ``unsigned`` object and the ``signatures`` object are not covered by the -signature. Therefore intermediate servers can add unsigneddata such as time stamps -and additional signatures. - - -:: - - { - "name": "example.org", - "signing_keys": { - "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" - }, - "unsigned": { - "retrieved_ts_ms": 922834800000 - }, - "signatures": { - "example.org": { - "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" - } - } - } - -:: - - def sign_json(json_object, signing_key, signing_name): - signatures = json_object.pop("signatures", {}) - unsigned = json_object.pop("unsigned", None) - - signed = signing_key.sign(encode_canonical_json(json_object)) - signature_base64 = encode_base64(signed.signature) - - key_id = "%s:%s" % (signing_key.alg, signing_key.version) - signatures.setdefault(sigature_name, {})[key_id] = signature_base64 - - json_object["signatures"] = signatures - if unsigned is not None: - json_object["unsigned"] = unsigned - - return json_object - -Checking for a Signature ------------------------- - -To check if an entity has signed a JSON object a server does the following - -1. Checks if the ``signatures`` object contains an entry with the name of the - entity. If the entry is missing then the check fails. -2. Removes any *signing key identifiers* from the entry with algorithms it - doesn't understand. If there are no *signing key identifiers* left then the - check fails. -3. Looks up *verification keys* for the remaining *signing key identifiers* - either from a local cache or by consulting a trusted key server. If it - cannot find a *verification key* then the check fails. -4. Decodes the base64 encoded signature bytes. If base64 decoding fails then - the check fails. -5. Checks the signature bytes using the *verification key*. If this fails then - the check fails. Otherwise the check succeeds. - -Canonical JSON --------------- - -The canonical JSON encoding for a value is the shortest UTF-8 JSON encoding -with dictionary keys lexicographically sorted by unicode codepoint. Numbers in -the JSON value must be integers in the range [-(2**53)+1, (2**53)-1]. - -:: - - import json - - def canonical_json(value): - return json.dumps( - value, - ensure_ascii=False, - separators=(',',':'), - sort_keys=True, - ).encode("UTF-8") - -Grammar -+++++++ - -Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing -insignificant whitespace, fractions, exponents and redundant character escapes - -:: - - value = false / null / true / object / array / number / string - false = %x66.61.6c.73.65 - null = %x6e.75.6c.6c - true = %x74.72.75.65 - object = %x7B [ member *( %x2C member ) ] %7D - member = string %x3A value - array = %x5B [ value *( %x2C value ) ] %5B - number = [ %x2D ] int - int = %x30 / ( %x31-39 *digit ) - digit = %x30-39 - string = %x22 *char %x22 - char = unescaped / %x5C escaped - unescaped = %x20-21 / %x23-5B / %x5D-10FFFF - escaped = %x22 ; " quotation mark U+0022 - / %x5C ; \ reverse solidus U+005C - / %x62 ; b backspace U+0008 - / %x66 ; f form feed U+000C - / %x6E ; n line feed U+000A - / %x72 ; r carriage return U+000D - / %x74 ; t tab U+0009 - / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X - / %x75.30.30.31 (%x30-39 / %x61-66) ; u001X - -Signing Events -============== - -Signing events is a more complicated process since servers can choose to redact -non-essential event contents. Before signing the event it is encoded as -Canonical JSON and hashed using SHA-256. The resulting hash is then stored -in the event JSON in a ``hash`` object under a ``sha256`` key. Then all -non-essential keys are stripped from the event object, and the resulting object -which included the ``hash`` key is signed using the JSON signing algorithm. - -Servers can then transmit the entire event or the event with the non-essential -keys removed. Receiving servers can then check the entire event if it is -present by computing the SHA-256 of the event excluding the ``hash`` object, or -by using the ``hash`` object included in the event if keys have been redacted. - -New hash functions can be introduced by adding additional keys to the ``hash`` -object. Since the ``hash`` object cannot be redacted a server shouldn't allow -too many hashes to be listed, otherwise a server might embed illict data within -the ``hash`` object. For similar reasons a server shouldn't allow hash values -that are too long. - -[[TODO(markjh): We might want to specify a maximum number of keys for the -``hash`` and we might want to specify the maximum output size of a hash]] - -[[TODO(markjh) We might want to allow the server to omit the output of well -known hash functions like SHA-256 when none of the keys have been redacted]] -- cgit 1.4.1 From 7e1779d48c5ef9a90b60a409286f9830c76eb8ae Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 20:49:03 +0200 Subject: this is ancient and has been moved to matrix-doc/drafts/federated_versioning_design_notes.rst --- docs/server-server/versioning.rst | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 docs/server-server/versioning.rst (limited to 'docs') diff --git a/docs/server-server/versioning.rst b/docs/server-server/versioning.rst deleted file mode 100644 index ffda60633f..0000000000 --- a/docs/server-server/versioning.rst +++ /dev/null @@ -1,11 +0,0 @@ -Versioning is, like, hard for backfilling backwards because of the number of Home Servers involved. - -The way we solve this is by doing versioning as an acyclic directed graph of PDUs. For backfilling purposes, this is done on a per context basis. -When we send a PDU we include all PDUs that have been received for that context that hasn't been subsequently listed in a later PDU. The trivial case is a simple list of PDUs, e.g. A <- B <- C. However, if two servers send out a PDU at the same to, both B and C would point at A - a later PDU would then list both B and C. - -Problems with opaque version strings: - - How do you do clustering without mandating that a cluster can only have one transaction in flight to a given remote home server at a time. - If you have multiple transactions sent at once, then you might drop one transaction, receive another with a version that is later than the dropped transaction and which point ARGH WE LOST A TRANSACTION. - - How do you do backfilling? A version string defines a point in a stream w.r.t. a single home server, not a point in the context. - -We only need to store the ends of the directed graph, we DO NOT need to do the whole one table of nodes and one of edges. -- cgit 1.4.1 From 0c59bc5e359c54092cc41457a86db562bb8c6157 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 21:03:10 +0200 Subject: move stuff out of implementation-notes - /everything/ here should be implementation-notes now --- docs/ancient_architecture_notes.rst | 59 ++++++++++++++++++++ docs/architecture.rst | 68 +++++++++++++++++++++++ docs/code_style.rst | 18 ++++++ docs/implementation-notes/architecture.rst | 68 ----------------------- docs/implementation-notes/code_style.rst | 18 ------ docs/implementation-notes/python_architecture.rst | 59 -------------------- 6 files changed, 145 insertions(+), 145 deletions(-) create mode 100644 docs/ancient_architecture_notes.rst create mode 100644 docs/architecture.rst create mode 100644 docs/code_style.rst delete mode 100644 docs/implementation-notes/architecture.rst delete mode 100644 docs/implementation-notes/code_style.rst delete mode 100644 docs/implementation-notes/python_architecture.rst (limited to 'docs') diff --git a/docs/ancient_architecture_notes.rst b/docs/ancient_architecture_notes.rst new file mode 100644 index 0000000000..2a5a2613c4 --- /dev/null +++ b/docs/ancient_architecture_notes.rst @@ -0,0 +1,59 @@ +.. WARNING:: + These architecture notes are spectacularly old, and date back to when Synapse + was just federation code in isolation. This should be merged into the main + spec. + + += Server to Server = + +== Server to Server Stack == + +To use the server to server stack, home servers should only need to interact with the Messaging layer. + +The server to server side of things is designed into 4 distinct layers: + + 1. Messaging Layer + 2. Pdu Layer + 3. Transaction Layer + 4. Transport Layer + +Where the bottom (the transport layer) is what talks to the internet via HTTP, and the top (the messaging layer) talks to the rest of the Home Server with a domain specific API. + +1. Messaging Layer + This is what the rest of the Home Server hits to send messages, join rooms, etc. It also allows you to register callbacks for when it get's notified by lower levels that e.g. a new message has been received. + + It is responsible for serializing requests to send to the data layer, and to parse requests received from the data layer. + + +2. PDU Layer + This layer handles: + * duplicate pdu_id's - i.e., it makes sure we ignore them. + * responding to requests for a given pdu_id + * responding to requests for all metadata for a given context (i.e. room) + * handling incoming backfill requests + + So it has to parse incoming messages to discover which are metadata and which aren't, and has to correctly clobber existing metadata where appropriate. + + For incoming PDUs, it has to check the PDUs it references to see if we have missed any. If we have go and ask someone (another home server) for it. + + +3. Transaction Layer + This layer makes incoming requests idempotent. I.e., it stores which transaction id's we have seen and what our response were. If we have already seen a message with the given transaction id, we do not notify higher levels but simply respond with the previous response. + +transaction_id is from "GET /send//" + + It's also responsible for batching PDUs into single transaction for sending to remote destinations, so that we only ever have one transaction in flight to a given destination at any one time. + + This is also responsible for answering requests for things after a given set of transactions, i.e., ask for everything after 'ver' X. + + +4. Transport Layer + This is responsible for starting a HTTP server and hitting the correct callbacks on the Transaction layer, as well as sending both data and requests for data. + + +== Persistence == + +We persist things in a single sqlite3 database. All database queries get run on a separate, dedicated thread. This that we only ever have one query running at a time, making it a lot easier to do things in a safe manner. + +The queries are located in the synapse.persistence.transactions module, and the table information in the synapse.persistence.tables module. + diff --git a/docs/architecture.rst b/docs/architecture.rst new file mode 100644 index 0000000000..98050428b9 --- /dev/null +++ b/docs/architecture.rst @@ -0,0 +1,68 @@ +Synapse Architecture +==================== + +As of the end of Oct 2014, Synapse's overall architecture looks like:: + + synapse + .-----------------------------------------------------. + | Notifier | + | ^ | | + | | | | + | .------------|------. | + | | handlers/ | | | + | | v | | + | | Event*Handler <--------> rest/* <=> Client + | | Rooms*Handler | | + HSes <=> federation/* <==> FederationHandler | | + | | | PresenceHandler | | + | | | TypingHandler | | + | | '-------------------' | + | | | | | + | | state/* | | + | | | | | + | | v v | + | `--------------> storage/* | + | | | + '--------------------------|--------------------------' + v + .----. + | DB | + '----' + +* Handlers: business logic of synapse itself. Follows a set contract of BaseHandler: + + - BaseHandler gives us onNewRoomEvent which: (TODO: flesh this out and make it less cryptic): + + + handle_state(event) + + auth(event) + + persist_event(event) + + notify notifier or federation(event) + + - PresenceHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + - TypingHandler: use distributor to get EDUs out of Federation. Very + lightweight logic built on the distributor + - EventsHandler: handles the events stream... + - FederationHandler: - gets PDU from Federation Layer; turns into an event; + follows basehandler functionality. + - RoomsHandler: does all the room logic, including members - lots of classes in + RoomsHandler. + - ProfileHandler: talks to the storage to store/retrieve profile info. + +* EventFactory: generates events of particular event types. +* Notifier: Backs the events handler +* REST: Interfaces handlers and events to the outside world via HTTP/JSON. + Converts events back and forth from JSON. +* Federation: holds the HTTP client & server to talk to other servers. Does + replication to make sure there's nothing missing in the graph. Handles + reliability. Handles txns. +* Distributor: generic event bus. used for presence & typing only currently. + Notifier could be implemented using Distributor - so far we are only using for + things which actually /require/ dynamic pluggability however as it can + obfuscate the actual flow of control. +* Auth: helper singleton to say whether a given event is allowed to do a given + thing (TODO: put this on the diagram) +* State: helper singleton: does state conflict resolution. You give it an event + and it tells you if it actually updates the state or not, and annotates the + event up properly and handles merge conflict resolution. +* Storage: abstracts the storage engine. diff --git a/docs/code_style.rst b/docs/code_style.rst new file mode 100644 index 0000000000..d7e2d5e69e --- /dev/null +++ b/docs/code_style.rst @@ -0,0 +1,18 @@ +Basically, PEP8 + +- Max line width: 80 chars. +- Use camel case for class and type names +- Use underscores for functions and variables. +- Use double quotes. +- Use parentheses instead of '\' for line continuation where ever possible (which is pretty much everywhere) +- There should be max a single new line between: + - statements + - functions in a class +- There should be two new lines between: + - definitions in a module (e.g., between different classes) +- There should be spaces where spaces should be and not where there shouldn't be: + - a single space after a comma + - a single space before and after for '=' when used as assignment + - no spaces before and after for '=' for default values and keyword arguments. + +Comments should follow the google code style. This is so that we can generate documentation with sphinx (http://sphinxcontrib-napoleon.readthedocs.org/en/latest/) diff --git a/docs/implementation-notes/architecture.rst b/docs/implementation-notes/architecture.rst deleted file mode 100644 index 98050428b9..0000000000 --- a/docs/implementation-notes/architecture.rst +++ /dev/null @@ -1,68 +0,0 @@ -Synapse Architecture -==================== - -As of the end of Oct 2014, Synapse's overall architecture looks like:: - - synapse - .-----------------------------------------------------. - | Notifier | - | ^ | | - | | | | - | .------------|------. | - | | handlers/ | | | - | | v | | - | | Event*Handler <--------> rest/* <=> Client - | | Rooms*Handler | | - HSes <=> federation/* <==> FederationHandler | | - | | | PresenceHandler | | - | | | TypingHandler | | - | | '-------------------' | - | | | | | - | | state/* | | - | | | | | - | | v v | - | `--------------> storage/* | - | | | - '--------------------------|--------------------------' - v - .----. - | DB | - '----' - -* Handlers: business logic of synapse itself. Follows a set contract of BaseHandler: - - - BaseHandler gives us onNewRoomEvent which: (TODO: flesh this out and make it less cryptic): - - + handle_state(event) - + auth(event) - + persist_event(event) - + notify notifier or federation(event) - - - PresenceHandler: use distributor to get EDUs out of Federation. Very - lightweight logic built on the distributor - - TypingHandler: use distributor to get EDUs out of Federation. Very - lightweight logic built on the distributor - - EventsHandler: handles the events stream... - - FederationHandler: - gets PDU from Federation Layer; turns into an event; - follows basehandler functionality. - - RoomsHandler: does all the room logic, including members - lots of classes in - RoomsHandler. - - ProfileHandler: talks to the storage to store/retrieve profile info. - -* EventFactory: generates events of particular event types. -* Notifier: Backs the events handler -* REST: Interfaces handlers and events to the outside world via HTTP/JSON. - Converts events back and forth from JSON. -* Federation: holds the HTTP client & server to talk to other servers. Does - replication to make sure there's nothing missing in the graph. Handles - reliability. Handles txns. -* Distributor: generic event bus. used for presence & typing only currently. - Notifier could be implemented using Distributor - so far we are only using for - things which actually /require/ dynamic pluggability however as it can - obfuscate the actual flow of control. -* Auth: helper singleton to say whether a given event is allowed to do a given - thing (TODO: put this on the diagram) -* State: helper singleton: does state conflict resolution. You give it an event - and it tells you if it actually updates the state or not, and annotates the - event up properly and handles merge conflict resolution. -* Storage: abstracts the storage engine. diff --git a/docs/implementation-notes/code_style.rst b/docs/implementation-notes/code_style.rst deleted file mode 100644 index d7e2d5e69e..0000000000 --- a/docs/implementation-notes/code_style.rst +++ /dev/null @@ -1,18 +0,0 @@ -Basically, PEP8 - -- Max line width: 80 chars. -- Use camel case for class and type names -- Use underscores for functions and variables. -- Use double quotes. -- Use parentheses instead of '\' for line continuation where ever possible (which is pretty much everywhere) -- There should be max a single new line between: - - statements - - functions in a class -- There should be two new lines between: - - definitions in a module (e.g., between different classes) -- There should be spaces where spaces should be and not where there shouldn't be: - - a single space after a comma - - a single space before and after for '=' when used as assignment - - no spaces before and after for '=' for default values and keyword arguments. - -Comments should follow the google code style. This is so that we can generate documentation with sphinx (http://sphinxcontrib-napoleon.readthedocs.org/en/latest/) diff --git a/docs/implementation-notes/python_architecture.rst b/docs/implementation-notes/python_architecture.rst deleted file mode 100644 index 2a5a2613c4..0000000000 --- a/docs/implementation-notes/python_architecture.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. WARNING:: - These architecture notes are spectacularly old, and date back to when Synapse - was just federation code in isolation. This should be merged into the main - spec. - - -= Server to Server = - -== Server to Server Stack == - -To use the server to server stack, home servers should only need to interact with the Messaging layer. - -The server to server side of things is designed into 4 distinct layers: - - 1. Messaging Layer - 2. Pdu Layer - 3. Transaction Layer - 4. Transport Layer - -Where the bottom (the transport layer) is what talks to the internet via HTTP, and the top (the messaging layer) talks to the rest of the Home Server with a domain specific API. - -1. Messaging Layer - This is what the rest of the Home Server hits to send messages, join rooms, etc. It also allows you to register callbacks for when it get's notified by lower levels that e.g. a new message has been received. - - It is responsible for serializing requests to send to the data layer, and to parse requests received from the data layer. - - -2. PDU Layer - This layer handles: - * duplicate pdu_id's - i.e., it makes sure we ignore them. - * responding to requests for a given pdu_id - * responding to requests for all metadata for a given context (i.e. room) - * handling incoming backfill requests - - So it has to parse incoming messages to discover which are metadata and which aren't, and has to correctly clobber existing metadata where appropriate. - - For incoming PDUs, it has to check the PDUs it references to see if we have missed any. If we have go and ask someone (another home server) for it. - - -3. Transaction Layer - This layer makes incoming requests idempotent. I.e., it stores which transaction id's we have seen and what our response were. If we have already seen a message with the given transaction id, we do not notify higher levels but simply respond with the previous response. - -transaction_id is from "GET /send//" - - It's also responsible for batching PDUs into single transaction for sending to remote destinations, so that we only ever have one transaction in flight to a given destination at any one time. - - This is also responsible for answering requests for things after a given set of transactions, i.e., ask for everything after 'ver' X. - - -4. Transport Layer - This is responsible for starting a HTTP server and hitting the correct callbacks on the Transaction layer, as well as sending both data and requests for data. - - -== Persistence == - -We persist things in a single sqlite3 database. All database queries get run on a separate, dedicated thread. This that we only ever have one query running at a time, making it a lot easier to do things in a safe manner. - -The queries are located in the synapse.persistence.transactions module, and the table information in the synapse.persistence.tables module. - -- cgit 1.4.1 From 774cff3c724c288172dbebf206dcd21d8dfdb011 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 11 Nov 2014 21:10:36 +0200 Subject: move swagger impl to matrix-doc --- docs/client-server/web/README | 5 - docs/client-server/web/files/backbone-min.js | 38 - docs/client-server/web/files/css | 16 - docs/client-server/web/files/handlebars-1.0.0.js | 2278 ---------------- docs/client-server/web/files/highlight.7.3.pack.js | 1 - docs/client-server/web/files/jquery-1.8.0.min.js | 2 - docs/client-server/web/files/jquery.ba-bbq.min.js | 18 - docs/client-server/web/files/jquery.slideto.min.js | 1 - docs/client-server/web/files/jquery.wiggle.min.js | 8 - docs/client-server/web/files/reset.css | 125 - docs/client-server/web/files/screen.css | 1221 --------- docs/client-server/web/files/shred.bundle.js | 2765 -------------------- docs/client-server/web/files/swagger-oauth.js | 211 -- docs/client-server/web/files/swagger-ui.js | 2315 ---------------- docs/client-server/web/files/swagger.js | 1604 ------------ docs/client-server/web/files/underscore-min.js | 32 - docs/client-server/web/swagger.html | 78 - 17 files changed, 10718 deletions(-) delete mode 100644 docs/client-server/web/README delete mode 100644 docs/client-server/web/files/backbone-min.js delete mode 100644 docs/client-server/web/files/css delete mode 100644 docs/client-server/web/files/handlebars-1.0.0.js delete mode 100644 docs/client-server/web/files/highlight.7.3.pack.js delete mode 100644 docs/client-server/web/files/jquery-1.8.0.min.js delete mode 100644 docs/client-server/web/files/jquery.ba-bbq.min.js delete mode 100644 docs/client-server/web/files/jquery.slideto.min.js delete mode 100644 docs/client-server/web/files/jquery.wiggle.min.js delete mode 100644 docs/client-server/web/files/reset.css delete mode 100644 docs/client-server/web/files/screen.css delete mode 100644 docs/client-server/web/files/shred.bundle.js delete mode 100644 docs/client-server/web/files/swagger-oauth.js delete mode 100644 docs/client-server/web/files/swagger-ui.js delete mode 100644 docs/client-server/web/files/swagger.js delete mode 100644 docs/client-server/web/files/underscore-min.js delete mode 100644 docs/client-server/web/swagger.html (limited to 'docs') diff --git a/docs/client-server/web/README b/docs/client-server/web/README deleted file mode 100644 index 315d5794ba..0000000000 --- a/docs/client-server/web/README +++ /dev/null @@ -1,5 +0,0 @@ -To get this running: - ln -s ../swagger_matrix - python -m SimpleHTTPServer - -Go to http://localhost:8000/swagger.html diff --git a/docs/client-server/web/files/backbone-min.js b/docs/client-server/web/files/backbone-min.js deleted file mode 100644 index c1c0d4fff2..0000000000 --- a/docs/client-server/web/files/backbone-min.js +++ /dev/null @@ -1,38 +0,0 @@ -// Backbone.js 0.9.2 - -// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// http://backbonejs.org -(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks= -{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g= -z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent= -{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null== -b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent: -b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)}; -a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error, -h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t(); -return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending= -{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length|| -!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator); -this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i('