DMC.Chat closed alpha docs

Abstract:

DMC.chat is an alpha-stage online chat protocol and system based on the WebSocket and MessagePack technologies with a focus on decentralization, censorship resistance, privacy and usability.

Requests

MessagePack formatted requests sent by the client. All Requests are required to get a sequence id assigned by the client. Responses to requests always contain the same sequence id as their corresponding request. The structure of a request is as follows:

{ type: [REQUEST TYPE], s: [SEQUENCE ID], data: { [REQUEST PARAMETERS] } }

The structure of a response looks like this:

{ type: [REQUEST TYPE], s: [SEQUENCE ID], data: { [RESPONSE DATA] } }

In the case of an error the server returns a JSON object with the following structure:

{ type: "error", s: [SEQUENCE ID], data: { message: [ERROR MESSAGE], code: [ERROR CODE] } }

auth

Authorization. (Most) important step of the entire thing rn. requires multiple steps, all of them get sent via type: "auth".

Auth process step by step:

Prerequisites
The client must possess an ECDH keypair, generated on the curve secp521r1 (this may change or become more flexible in the future). The client needs to be able to compute shared ECDH keys and create SHA-512 hashes.


Step 1
After connecting, the client sends their public key to the server in the following format:

{ type: "auth", s: [SEQUENCE ID], data: { key: [ECDH PUBLIC KEY:BIN] } }

The ECDH key must represent a valid public key for the secp521r1 (short: P-521) elliptic curve. The server now computes the shared secret, hashes it and uses that hash to cipher some random bytes.
After that it responds like this:

{ type: "auth", s: [SEQUENCE ID], data: { key: [SERVER PUBLIC KEY:BIN], ciphertext: [AES-256-CBC ENCRYPTED RANDOM BYTES:BIN], iv: [IV USED FOR THE AES CIPHER:BIN] } }

The ciphertext is random bytes ciphered using AES-256-CBC with a SHA256 hash of the ECDH shared secret as key and the passed iv as iv.


Step 2
Now that the client has the servers public key, it too can compute the shared secret. After doing so, it hashes the shared secret with SHA256.
This hash is used to decrypt the passed ciphertext. It is to be decrypted using the hash as key and the passed iv.
After deciphering the random bytes they are hashed using SHA256. The hash is then sent to the server in the following format:

{ type: "auth", s: [SEQUENCE ID], data: { hash: [SHA256 HASHED BYTES:BIN], name: [USERNAME], /* can only be freely picked on first connect. for changes the user needs the required permissions */ avi: [STRING], // not implemented yet. Just pass any string value right now status: [USER STATUS, STRING] } }

If the hash is correct the client has now successfully confirmed that they own the fitting private key to their public key.
If everything worked out the server returns a User object of the requesting client.

getusers

Returns array of all online users.

Response:

Array of User objects

getuserbyid

Gets a user by user-id. Currently not implemented. Possible complete removal.

Parameters:

uId: [USER ID]

Response:

User object

getchannels

Gets all channels the requesting user has read access to. The response does not contain any permission data. Permissions are received via getroles.

Response:

Array of channels

getroles

Gets all roles on the server.

Response:

Array of roles

gethistory

Gets message history in specified channel. This request is important to execute as early as possible because upon execution the user gets registered as a listener for events in all channels they can see

Parameters:

cId: [CHANNEL-ID], amount: [AMOUNT (1-100)] before: [OPTIONAL, LOAD MESSAGES BEFORE THIS MESSAGE ID]

Response:

Array of message ojects

getpinned

Gets all pinned messages for specified channel.

Parameters:

cId: [CHANNEL ID]

Response:

Array of message objects

togglepinmsg

Toggles the pin state of a message. (if pinned gets unpinned, if unpinned gets pinned)

Events:

msgpinned, msgunpinned

Parameters:

mId: [MESSAGE ID]

Response:

the pinned message object

sendmsg

Sends a message to a channel.

Event:

newmsg

Parameters:

cId: [CHANNEL ID], content: [MESSAGE TEXT], attachments: { files: [ARRAY OF SHA256 FILEHASHES], embeds: [LEAVE EMPTY, WIP] }

Response:

message object

deletemsg

deletes a specified message.

Event:

msgdeleted

Parameters:

mId: [MESSAGE ID]

Response:

message object of edited message

editmsg

edits a specified messages text.

Event:

msgedited

Parameters:

mId: [MESSAGE ID], content: [NEW MESSAGE TEXT]

Response:

message object of edited message

addchannel

adds a channel.

Event:

channeladded

Parameters:

name: [NAME OF CHANNEL], // 1-64 chars description: [DESCRIPTION OF CHANNEL], // 0-4000 chars pId: [PARENT-ID OF CHANNEL], // Can only be a category channel (type 1) oId: [ORDER-ID OF CHANNEL], /* Reflects channel position in scope. Lower number means higher position. */ type: [TYPE OF CHANNEL], // 0: Text channel, 1: Category perms: { [ROLE ID]: [PERM STRING], [ROLE ID]: [PERM STRING]... /* Not every role has to be defined. [If it isnt defined here or if the defined value goes above the invoking users permissions, the roles base-channel-permission is used. If additionally the channel has a parent, the role permission defined in the parent is used. */ }

Response:

message object

editchannel

edits a channel.

Event:

channeledited

Parameters:

cId: [ID OF CHANNEL], [PARAM]: [NEW VALUE], [PARAM]: [NEW VALUE]...

Response:

channel object of edited channel

deletechannel

deletes a channel.

Event:

channeldeleted

Parameters:

cID: [CHANNEL ID]

Response:

id of deleted channel

addrole

adds a role.

Event:

roleadded

Parameters:

name: [NAME OF CHANNEL], // 1-64 chars color: [#RRGGBB], priority: [INT], // cannot be above or equal to the invoking users highest roles priority! displayType: [BOOL], // determines if users should be grouped by this role in the sidebar or not globalPerms: [BASE32 PERMISSION STRING], // cant be greater than the invoking users perms. baseChannelPerms: [BASE32 PERMISSION STRING] // cant be greater than the invoking users perms.

Response:

role object of created role

editrole

edits a role.

Event:

roleedited

Parameters:

rId: [ID OF ROLE], [PARAM]: [NEW VALUE], [PARAM]: [NEW VALUE]...

Response:

role object of edited role

deleterole

deletes a role.

Event:

roledeleted

Parameters:

rId: [ID OF ROLE]

Response:

id of deleted role

addusertorole

adds a role to a user.

Event:

roleaddedtouser

Parameters:

rId: [ID OF ROLE] uId: [ID OF USER]

Response:

object containing the ids of the role and the user.

removeuserfromrole

removes a user from a role.

Event:

roleremovedfromuser

Parameters:

rId: [ID OF ROLE], uId: [ID OF USER]

Response:

object containing the ids of the role and the user.

uploadfile

Uploads a file to the server. Only multi-step task besides auth.
Has to be done, in order to attach the file to messages.
Any error during any part of the process immediatly stops the uploading process. Upload process step by step:

Prerequisites:
The client must calculate a SHA256 hash of a file.
The client must split the file into parts, which have a size that will be variable in the future but is currently hard-coded to 2048 bytes.
Important notice: This task requires the continuous use of a single sequence id until the process is done!

Step 1: Metadata
At this stage the client sends the metadata of the file:

{ type: "uploadfile", s: [SEQUENCE ID], data: { hash: [SHA256 FILE-HASH:bin], name: [FULL FILENAME + EXTENSION:string], contentType: [MIME TYPE OF FILE:string], size: [SIZE IN BYTES:int(for now)] } }

The server can now respond in two ways:

File with this hash already exists on the server!
The server will send the already existing file object so the client can use it in attachments:

{ type: "uploadfile", s: [SEQUENCE ID], data: { [FILE OBJECT] } }

File doesnt exist yet!
The server will send an empty response to signify successful receival:

{ type: "uploadfile", s: [SEQUENCE ID], data: {} }

Step 2: Uploading
The client will now send the numbered file-parts to the server.
After each file part the client waits for the servers response.
This step is repeated until all fileparts are sent.

{ type: "uploadfile", s: [SEQUENCE ID], data: { part: [FILEPART NUMBER:int], bin: [BINARY DATA:Buffer/UInt8Array] } }

Upon successful receival, the server responds as follows:

{ type: "uploadfile", s: [SEQUENCE ID], data: { part: [FILEPART NUMBER:int], } }

Final response after all fileparts have been uploaded:
file object of the uploaded file

setavi

sets an uploaded file as avi of invoking user. File has to be a square image of size n (n set to 2MB on test server).

Event:

useredited

Parameters:

hash: [SHA256 FILEHASH:bin]

Response:

user object of edited user

Events

MessagePack formatted data sent by the server with no request being needed.

Structure:

{ type: "event", name: [EVENT NAME], data: [EVENT DATA] }

newmsg

Triggered when a new message gets sent in any channel the client has reading permissions to.

Event Data:

message

msgedited

Triggered when a message gets edited in any channel the client has reading permissions to.

Event Data:

message

msgdeleted

Triggered when a message gets deleted in any channel the client has reading permissions to.

Event Data:

message

msgpinned

Triggered when a message gets pinned in any channel the client has reading permissions to.

Event Data:

message

msgunpinned

Triggered when a message gets unpinned in any channel the client has reading permissions to.

Event Data:

message

channeladded

Triggered when a channel gets created and the user has read permissions.

Event Data:

channel

channeledited

Triggered when a channel gets edited and the user has read permissions.

Event Data:

channel

channeldeleted

Triggered when a channel gets deleted and the user has read permissions.

Event Data:

id of deleted channel.

roleadded

Triggered when a role gets created.

Event Data:

role

roleedited

Triggered when a role gets edited.

Event Data:

role

roledeleted

Triggered when a role gets deleted.

Event Data:

id of deleted role.

roleaddedtouser

Triggered when a role gets added to a user.

Event Data:

id of role and user.

roleremovedfromuser

Triggered when a role gets removed from a user.

Event Data:

id of role and user.

useredited

Triggered when userinfo gets edited.

Event Data:

user

Types

all response data the server sends in events or responses is structured in the following types.

user

{ id: [user id:int], name: [username:string], nick: [nickname:string], joinedTime: [account creation unix timestamp/1000:int], state: [online state (0: offline, 1: online, 2: afk, 3: busy):int], // semi functional status: [custom 'doing right now' message:string], // semi functional aviHash: [hash to avi:bin], messageCount: [amount of messages posted in total:int], // not functional roles: [array of role ids that are assigned to the user:array(int)] }

channel

{ id: [channel id:int], name: [channel name:string], description: [channel description:string], type: [channel type (0: text, 1: category):int], pId: [id of parent channel (category):int], oId: [order id of channel, specifies position in its parent:int], perms: [object mapping role ids to permission strings:object(roleid=>permission)] }

role

{ id: [role id:int], name: [role name:string], color: [role color:string], priority: [role priority (defines role order):int], group: [group role in sidebar?:boolean], globalPerms: [role global perms:string], baseChannelPerms: [initial channel role perms (can be overriden by channel settings):string] }

message

Info about markup:
The markup gets handeled by the client.
*message*: italic
**message**: bold
__message__: underlined
~~message~~: crossed out
(come up with something cool yourself if youre bored)
Oh btw if the user has HTML markup perms the client handles that too.

{ id: [message id:int], content: [message text:string], attachments: { files: [ARRAY OF FILE OBJECTS], embeds: [] // not functional }, uId: [id of creator:int], cId: [channel id:int], time: [unix timestamp at the moment of posting:int], pinned: [is the message pinned?:boolean], edited: [is the message edited?:boolean], editorid: [if edited this is the id of the editor, else null] }

file

{ hash: [SHA256 FILE-HASH:bin], name: [FULL FILENAME + EXTENSION:string], contentType: [MIME TYPE OF FILE:string], size: [SIZE IN BYTES:int(for now)], width: [IMAGE WIDTH. ONLY FOR IMAGE FILES] height: [IMAGE HEIGHT. ONLY FOR IMAGE FILES] }

Channel Permstring

New: The first segment of permission strings now always has a leading 1 after being decoded to make it easier to handle. Will prolly be removed when i stop being lazy
Permission string for permissions that can be overriden by channel specific permissions. sent as a base32 string, more readable as base3. After a conversion to base3, "0" equals undefined, "1" equals disallowed and "2" equals allowed.
All following segments are encoded as base32 aswell but meant to be decoded to base10. They represent cooldowns, maximum upload sizes, etc.
Example of a decoded permission string: [base3 number]/[base10 number]/[base10 number]
As you can see, segments are divided using slashes.

toggles: { // all toggles are in the same segment // [PERM NAME]: [POSITION IN FIRST SEGMENT STRING] READMSG: 0, WRITEMSG: 1, EDITOWN: 2, EDITOTHER: 3, DELETEOWN: 4, DELETEOTHER: 5, TOGGLEPIN: 6, USEMARKUP: 7, USEADVANCEDMARKUP: 8, USEHTML: 9, UPLOADFILE: 10, ADDATTACHMENT: 11, READHISTORY: 12, MENTIONEVERYONE: 13, MENTIONONLINE: 14, USEEMOTE: 15, ADDREACTION: 16, ADDNEWREACTION: 17, EDITCHANNELNAME: 18, EDITCHANNELINFO: 19, EDITCHANNELPERMS: 20, EDITCHANNELPARENTID: 21, EDITCHANNELORDERID: 22, ADDCHANNEL: 23, DELETECHANNEL: 24, CLEARCHANNEL: 25 }, numbers: { // [PERM NAME]: [POSITION IN SEGMENT ARRAY] ATTACHMENTLIMIT: 1, POSTTIMEOUT: 2 }

Global Permstring

Permission string for global permissions. Get defined once in the role and cant be overruled by anything other than higher priority roles. The base structure is the same as the channel perms just a little smaller cause less global perms exist atm.

toggles: { EDITROLEPERMS: 0, EDITROLENAME: 1, EDITROLECOLOR: 2, EDITROLEDISPLAYTYPE: 3, EDITROLEPRIORITY: 4, CREATEROLE: 5, DELETEROLE: 6, CHANGENICKS: 7, CHANGEOWNNICK: 8, CHANGEDISPLAYNAMES: 9, CHANGEOWNDISPLAYNAME: 10, CHANGEONLINESTATE: 11, CHANGECURRENTLYDOING: 12, SETAVI: 13, ADDROLETOUSER: 14, REMOVEROLEFROMUSER: 15, ADDEMOTE: 16, EDITEMOTE: 17, DELETEEMOTE: 18, VIEWLOGS: 19, BAN: 20 }, numbers: { UPLOADSIZELIMIT: 21, BANCOOLDOWN: 22, MAXBANTIME: 23 }

HTTP Content Delivery

Files are downloaded via a new HTTP endpoint. On the test server that endpoint is currently https://files.dmc.chat/.

serving files

Files are publicly served via the following link structure:
https://files.dmc.chat/[HEX-FILEHASH]/[FILENAME]
Avis are publicly served via the following link structure:
https://files.dmc.chat/avi/[USERID]