💾 Archived View for blakes.dev › hdoc › yQCRixlTQsGFtTPayGpdoQ.gmi captured on 2024-12-17 at 09:20:23. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-05-10)
-=-=-=-=-=-=-
Source file (imported manually from Hedgedoc)
This is a rough draft specification.
The Micro Messaging Protocol aims to enable secure, unencumbered, and interoperable instant messaging, built on top of and alongside existing standards. Existing standards in this space fail to accomplish their goals for various reasons: Matrix is encumbered by its event graph, making self-hosting prohibitively expensive, and a confusingly large and growing single specification with no extension standardization framework, and the Extensible Messaging and Presence Protocol (XMPP) is encumbered by years of rotting code, reliance on dated technology, and contradicting extensions in crucial areas.
MMP endeavors to avoid these shortcomings by providing extensibility governed by a standards body, and by considering features in the base protocol which enable more advanced features, while keeping those advanced features separate.
Frame
: A packet of data which contains the information in transit. This corresponds to a Protobuf message. It's a metaphor: a frame _contains_ a message. It also can contain other information about the message, including delivery information, sender, recipients, and what type of content is contained in the message.
Conversation
: A conceptual unit, determined by its participants (to and from) and its conversation ID which acts as a disambiguator; it can include messages and state. Most conversations have an ID of 0, as multiple conversations between the same group of people is not always useful but it can be.
: **Note:** Group chats could end up with a large conversation ID as a consequence of adding and removing participants. This practice is not recommended and a chat room, to be defined in a companion specification, should be used instead.
Message
: A single, user-specified, sequential element of a conversation. It may be a text message such as "Hello!", or a group of images. One cognitive unit should be represented per message: an album of images or a single image, a text message, or a captioned location are good examples.
In both the client-server and server-server (federation) APIs, Protobuf is used as the wire format for frames. The following rules apply to all Protobuf "messages" as used in MMP:
* Tip: to parse this, use `message MMP_Ping` first to determine the type, then if the type is known, re-parse the frame with that type.
* Frame type URIs which refer to frames specified in this document are in the URN namespace `urn:mmp:mmp1:*`; for example, the "ping" message is `urn:mmp:mmp1:ping`.
* Extensions and specifications related to MMP **MUST NOT** use `911` as anything other than as specified here!
* Error messages MUST be generated and interpreted as plain text. They MAY, however, contain URLs which clients SHOULD make clickable.
* While error codes are specified, error messages are not. They may be in any language and include server or situation specific information. However, when in doubt: use English and be specific about the problem without giving away sensitive details.
MMP data is to be accepted by servers and sent to clients as Protocol Buffers (Protobuf) messages, specified below, over a secure WebSocket at a server-specified endpoint on the domain. This endpoint is specified relative to the given login domain as plain text at the well-known path `/.well-known/mmp-client-socket` over HTTPS. This is done to allow alternate APIs to be added on top.
An authenticated client MUST send a ping at least as often as once per 30 seconds. Clients SHOULD send pings as often as every 10 seconds. Servers SHOULD disconnect clients that do not send a heartbeat for 30 seconds. It's your loss if you decide not to do that.
The Protobuf message for a ping looks like this.
syntax = "proto3" message MMP_Ping { string frameType = 1; // == "urn:mmp:mmp1:ping" optional string errorMessage = 112; optional string errorCode = 911; }
Every frame type is based on this frame type; or, in other words, this is the most minimal frame type MMP allows.
Servers are expected to authenticate clients via an HTTP header, typically either the Cookie or Authorization headers, when a client attempts to establish the WebSocket connection.
If a client is not authenticated and wishes to be (so that they can use the Client-Server API), it is to send an empty, unauthenticated HTTP POST request to the Client-Server websocket endpoint. The appropriate response is a Protobuf message according to the following:
message MMP_Auth { string frameType = 1; // == "urn:mmp:mmp1:auth" // First listed type is preferred. repeated AuthType loginType = 2; // If this is the empty string ("") or unset, registration is disabled. // If this is "urn:mmp:mmp1:auth#registration", in-band registration is to be used. // Otherwise, this SHOULD be an HTTPS URI to send the user to in a Web browser to complete registration. string registrationEndpoint = 3; // Required when using OAuth2, OpenID, or IndieAuth. string authEndpoint = 16; // If this is set, it MUST be presented to the user with a check-box before allowing them to register. optional string termsOfServiceUrl = 24; // If this is set, it MUST be presented to the user with a check-box before allowing them to register. optional string privacyPolicyUrl = 25; // Clients SHOULD present this to the user as a link before logging in or registering. // Recommended labels include "Server legal documents" or "example.org Legal". optional string legalUrl = 26; // A plain-text string, which MAY include clickable URLs, // documenting the requirements for a password if in-band // registration is in use. optional string passwordRequirements = 31; enum AuthType { OAUTH2 = 1; // Common in the decentralized world. // Use of either OpenID or IndieAuth // is recommended. OPENID = 2; INDIEAUTH = 3; // If username and password authentication is expected. USERNAME_AND_PASSWORD = 16; // Should be treated identically to the above except that it shows an email box instead of a username box. // Prefer username and password over this, because that may skip a step on many clients. EMAIL_AND_PASSWORD = 17; } optional string errorMessage = 112; optional string errorCode = 911; }
If a client wishes to log in with a password (assuming the server listed and allows it), the client is to send a POST request over valid HTTPS (with any content type, as it should be ignored) with a frame conforming to the following format:
message MMP_UsernameLogin { string frameType = 1; // == "urn:mmp:mmp1:auth#username" OR "urn:mmp:mmp1:auth#email" string username = 2; // email goes in this field too string password = 3; }
The HTTP response, with a server-selected appropriate status code, will obey the following format:
message MMP_LoginResponse { string frameType = 1; // == "urn:mmp:mmp1:auth#granted" string accessToken = 2; optional string errorMessage = 112; optional string errorCode = 911; }
The value of `accessToken`, if set, should be considered a Bearer token, and when connecting to the WebSocket, a header like this is to be set:
Authorization: Bearer ACCESS_TOKEN_HERE
Error codes that may be used here:
Registration, when done "in-band," occurs via the same HTTPS endpoint used for password-based login, if it is accepted (see `message MMP_Auth#registrationEndpoint` above).
At least one of email-and-password or username-and-password based login MUST be enabled to facilitate logging in after registration.
The POST body should contain the following frame:
message MMP_AuthRegistration { string frameType = 1; // == "urn:mmp:mmp1:auth#registering" // The username that the user requests. // This becomes part of the user's public ID. string username = 2; // The server MAY accept registrations without // this field, but doing so may mean users will // not be able to recover their account should // they lose their password. // This can be used for login but you probably // want it for administrative purposes. string email = 3; string password = 8; }
The HTTP response, with appropriate status code, should conform to this frame:
message MMP_Success { string frameType = 1; // == "urn:mmp:mmp1:auth#registered" bool success = 2; optional string errorMessage = 112; optional string errorCode = 911; }
Assuming the response is `message MMP_Success` and no error code was given, the client should then attempt to log in with their credentials.
Error codes that may be used here include:
To send a message, send a Message frame to the server.
message MMP_Message { string frameType = 1; // == "urn:mmp:mmp1:message" // An opaque ID. // Servers MUST include this. // Clients sending new messages MUST exclude this. // Clients sending edited messages MUST include this, // and it MUST match a message which the user is allowed to edit. // If a client sets this and it doesn't match an existing message, // servers SHOULD throw an error instead of ignoring it. string id = 2; // The sender's chat address. Clients MUST set this to the empty string // or an address they control (servers may allow, for example, plus-addressing). // If a server sees an invalid "from" address, it is expected to either reduce it to a form that is valid, replace it with a default address for the user, or throw an error. string from = 3; // Addresses to send the message to. // Replies in a conversation MUST be treated as "reply all" // by default. // This may include a chatroom. If that is the case, it SHOULD // be the only "to" listed, even if you are the recipient. repeated string to = 4; // 5 and 6 are set aside for CC and BCC, respectively. // They are not implemented in this spec and they may never // be useful so they may never be used. // The address to send any replies to. // This may be useful to redirect replies into a separate channel, // such as if a message was forwarded. // If this is set, clients MUST obey it and make sure their users // are aware that the reply will be sent in a different chat. string replyTo = 7; // Servers MUST include this. // Clients MUST exclude this. // Represents the date and time the message was sent // (or, in an untrusted scenario, when it was first known to the server). Timestamp date = 13; // The MIME type (or Matrix type) of the content. No restrictions are placed on // what type a message can be. To have multiple equivalent bodies, // multipart/alternate is recommended. // This MUST be set. It cannot be blank. string mimeType = 14; // The body of the content. If a Matrix type is specified, // the body should be encoded as either JSON or MessagePack. bytes content = 15; // The conversation ID to which this message belongs. // See Conversation in the Concepts section. int32 conversation = 16; // In each flag, everything after the first = is considered a value. // Values are optional. // Flags are to be specfied by extensions. repeated string flags = 17; // These fields are only used in responses. // They MUST NOT be set when making a request. // If they are set, servers SHOULD ignore them. optional string errorMessage = 112; optional string errorCode = 911; }
Servers MUST send a [`message MMP_Success`](#MMP_Success) response, indicating whether the message was sent and, if it wasn't, why not (via an error code and optional message).
HTML messages are NOT RECOMMENED due to the complexity of HTML. Instead, prefer Markdown.
Error codes that may be returned here:
If you have a message or event ID, you can try to find that message by sending this frame:
message MMP_GetMessageByID { string type = 1; // == "urn:mmp:mmp1:message#by-id" // The message ID to get. string id = 2; // The server MAY require this to distinguish // messages by conversation. // It's okay to omit this if you don't know it, // but you may not get a result. optional Conversation conversation; // The identifying bits of a conversation. message Conversation { string from = 3; repeated string to = 4; int32 conversation = 16; } }
The response will be a [`message MMP_Message`](#MMP_Message) frame, which, if necessary, may contain an error.
Such errors may include:
To retrieve a message by conversation, send a frame with this format:
message MMP_ConversationFrame { string frameType = 1; // == "urn:mmp:mmp1:message#by-conversation" // This always includes an address belonging to the current client. repeated string participants = 2; }
This returns a message list frame:
message MMP_MessageList { string frameType = 1; // == "urn:mmp:mmp1:message#list-by-conversation" repeated Message messages = 2; optional string errorMessage = 112; optional string errorCode = 911; // This type is equivalent to MMP_Message but without // fields 1, 112, and 911 (because it's not a frame). // If this and MMP_Message mismatch apart from fields 1, // 112, and 911, MMP_Message is the correct pattern to follow. // Report the issue if you see it. message Message { string id = 2; string from = 3; repeated string to = 4; string replyTo = 7; Timestamp date = 13; string mimeType = 14; bytes content = 15; int32 conversation = 16; repeated string flags = 17; } }
TODO
Errors may be under any namespace, but generic errors defined in this document use the `urn:mmp:mmp1:error#*` namespace.
`urn:mmp:mmp1:error#missing`
: The requested resource could not be found.
`urn:mmp:mmp1:error#delivery-failed`
: The remote server could not be reached or delivery failed for another reason.
`urn:mmp:mmp1:error#disabled`
: The requested feature is supported, but the server administrator has disabled it.
`urn:mmp:mmp1:error#unimplemented`
: The requested feature, frame type, or other action has not been implemented.
`urn:mmp:mmp1:auth#incorrect`
: The supplied credentials are in the correct format but do not match any records available to the server.
`urn:mmp:mmp1:auth#invalid`
: The supplied credentials are malformed. Reasons may include:
`urn:mmp:mmp1:auth#banned`
: The user was banned from the server.
: The IP address, IP address range, internet service provider, or location was banned from the server; typically, this is a rate limit or a spam protection, but may also be enacted for legal reasons.
: A remote server being interacted with has banned the user from participating in conversations on that server.
`urn:mmp:mmp1:auth#badname`
: The requested username includes a banned word. Servers may return this to refuse names including certain words such as slurs, curse words, or reserved system terms, which should be accompanied by a (temporary) IP block, during registration.
`urn:mmp:mmp1:auth#exists`
: The requested username is already in use.
: The email address given is already associated with an account.
`urn:mmp:mmp1:message#invalid`
: TODO: this should be a generic error
: Required fields were missing.
: Fields excluded from certain requests were set.
`urn:mmp:mmp1:message#toobig`
: The message submitted exceeds the maximum size allowed by the server.
`urn:mmp:mmp1:message#ambiguous`
: The message requested matches more than one message.
`urn:mmp:mmp1:profile#missing`
: The user exists but does not have profile information.
: Profile information for the user is not known to the server.
: The user is missing (especially when other things can be missing).