Les éclats
Synospis
L’objectif de ce document est de décrire comment les discussions de groupe (a.k.a. swarm chat) seront mises en œuvre à Jami.
Un swarm est un groupe capable de discuter sans aucune autorité centrale de manière résiliente. En effet, si deux personnes n’ont aucune connectivité avec le reste du groupe (c’est-à-dire une panne d’Internet) mais peuvent se contacter (dans un réseau local par exemple ou dans un sous-réseau), elles seront en mesure d’envoyer des messages les unes aux autres et ensuite, seront en mesure de synchroniser avec le reste du groupe lorsque cela est possible.
Donc, le swarm est défini par:
Capacité de séparation et de fusion à la suite de la connectivité.
Tout le monde doit pouvoir envoyer un message à tout le groupe.
Aucune autorité centrale, aucun serveur.
Les appareils doivent être capables de vérifier la validité des messages anciens et de reproduire l’historique.
Le stockage est géré par l’appareil.
L’idée principale est d’obtenir un arbre de Merkle synchronisé avec les participants.
Nous avons identifié quatre modes de chat en essaim que nous voulons mettre en œuvre:
UN_TO_ONE, c’est le cas de notre époque quand on discute avec un ami
ADMIN_INVITES_ONLY généralement une classe où l’enseignant peut inviter des personnes, mais pas des étudiants
INVITES_ONLY un groupe privé d’amis
Public est essentiellement un forum ouvert
Scenarios
Créer une masse
Bob veut créer un nouvel essaim
Bob crée un référentiel de données.
Il crée ensuite un premier engagement signé avec les éléments suivants:
Sa clé publique en
/admins
Son certificat de dispositif en ̀ /devices `
Son RRC est en ̀ /crls`
Le hash du premier comit devient l’ID de la conversation.
Bob annonce à ses autres appareils qu’il crée une nouvelle conversation, ce qui se fait par une invitation à rejoindre l’envahisseur envoyé par le DHT à d’autres appareils liés à ce compte.
Ajouter quelqu’un
Alice ajoute Bob
Alice ajoute Bob au référentiel:
Ajout de l’URI invité en
/invited
Ajout de la RLC à
/crls
Alice envoie une demande sur le DHT
Recevoir une invitation
Alice est invitée à rejoindre le swarm créé précédemment
Elle accepte l’invitation (si elle refuse, ne fait rien, elle restera dans l’invité et Alice ne recevra jamais de message)
Une connexion entre Alice et Bob est terminée.
Alice tire le repo de Bob. ** AVERTISSEMENT Cela signifie que les messages ont besoin d’une connexion, pas du DHT comme aujourd’hui**
Alice valide les engagements de Bob
Pour valider que Alice est membre, elle supprime l’invitation du répertoire
/invited
, puis ajoute son certificat dans le répertoire/members
Une fois tous les engagements validés et sur son appareil, les autres membres du groupe sont découverts par Alice. Avec ces pairs, elle construira le DRT (expliqué ci-dessous) avec Bob comme une bande de démarrage.
Envoyer un message
Alice envoie un message
Envoyer un message est assez simple. Alice écrit un message de commande dans le format suivant:
{
"type": "text/plain",
"body": "coucou"
}
Elle ajoute son appareil et son CRL au référentiel si elles manquent (d’autres doivent être en mesure de vérifier le commit). Les conflits de fusion sont évités parce que nous sommes principalement basés sur des messages de commit, pas sur des fichiers (sauf si des certificats CRLS + mais ils sont localisés).
Pour pinger d’autres appareils, l’expéditeur envoie aux autres membres un message SIP avec mimetype = « application/im-gitmessage-id » contenant un JSON avec le « deviceId » qui envoie le message, le « id » de la conversation liée et le « commit »
Reçoit un message
Bob reçoit le message d’Alice
Bob fait un coup sur Alice
Les engagements doivent être vérifiés par un crochet
Si tous les comités sont valides, les comités sont stockés et affichés.
If all commits are not valid, pull is canceled. Alice must reestablish her state to a correct state.
Validation d’un engagement
Pour éviter que les utilisateurs ne poussent certains commissions indésirables (avec des conflits, de faux messages, etc.), voici comment chaque commande (de la plus ancienne à la plus récente) DOIT être validée avant de fusionner une branche distante:
Note: if the validation fails, the fetch is ignored and we do not merge the branch (and remove the data), and the user should be notified Note2: If a fetch is too big, it’s not merged
Pour chaque engagement, vérifiez que le dispositif qui tente d’envoyer le engagement est autorisé à ce moment-là et que les certificats sont présents (dans les appareils du dispositif et dans les membres ou administrateurs de l’émetteur).
Le comité a 2 parents, donc c’est une fusion, rien de plus à valider ici
Le compromis a 0 parents, c’est le compromis initial:
Vérifiez que le certificat d’administration a été ajouté
Vérifiez que le certificat de l’appareil est ajouté
L’ajout des LCR de vérification
Vérifiez qu” aucun autre fichier n” est ajouté
Le comit a 1 parent, le message comit est un JSON avec un type:
Si le texte (ou autre type de mime qui ne change pas de fichiers)
Contrôle de la signature du certificat dans le repo
Vérifiez qu” aucun fichier étrange n” est ajouté en dehors du certificat de périphérique ni supprimé
Si vous votez
Vérifiez que voteType est pris en charge (interdiction, déinterdiction)
Vérifiez que le vote est pour l’utilisateur qui signe le compromis
Vérifiez que le vote est d” un administrateur et un appareil présent & non interdit
Vérifiez qu” aucun fichier étrange n” est ajouté ou supprimé
Si le membre
Si ajoutée
Vérifiez que le contrat est correctement signé
Vérifiez que le certificat est ajouté dans / invité
Vérifiez qu” aucun fichier étrange n” est ajouté ou supprimé
Si ONE_TO_ONE, vérifiez que nous n’avons qu’un seul administrateur, un seul membre
Si ADMIN_INVITES_ONLY, vérifiez que l’invitation est d’un administrateur
Si elle est jointe
Vérifiez que le contrat est correctement signé
Vérifiez que le dispositif est ajouté
Vérifiez que l’invitation est transférée aux membres
Vérifiez qu” aucun fichier étrange n” est ajouté ou supprimé
Si c’est interdit
Vérifiez que le vote est valide
Vérifiez que l’utilisateur est interdit via un administrateur
Vérifiez que le certificat de membre ou de dispositif est déplacé vers interdit/
Vérifiez que seuls les fichiers liés au vote sont supprimés
Vérifiez qu” aucun fichier étrange n” est ajouté ou supprimé
notifier à l’utilisateur qu’il peut être avec une ancienne version ou que le paire a essayé de soumettre des commissions indésirables
Interdiction d’un appareil
Alice, Bob, Carla, Denys sont dans un essaim.
C’est l’un des scénarios les plus difficiles de notre contexte.
Temps de l’engagement généré
Si plusieurs appareils d’administration sont présents et si Alice peut parler à Bob mais pas à Denys et Carla; Carla peut parler à Denys; Denys interdit Alice, Alice interdit Denys, quel sera l’état lorsque les 4 membres fusionneront les conversations.
Un appareil peut être compromis, volé ou son certificat peut expirer. Nous devrions pouvoir interdire un appareil et éviter qu’il mente sur son expiration ou envoie des messages dans le passé (en modifiant son certificat ou le timestamp de son engagement).
Les systèmes similaires (avec des systèmes de groupe distribués) ne sont pas si nombreux, mais voici quelques exemples:
[mpOTR ne définit pas comment interdire quelqu’un]
Signal, sans serveur central pour le chat de groupe (EDIT: ils ont récemment changé ce point), ne donne pas la possibilité d’interdire quelqu’un d’un groupe.
Ce système de vote a besoin d’une action humaine pour interdire quelqu’un ou doit être basé sur les informations des RLC du référentiel (car nous ne pouvons pas faire confiance aux RLC externes)
Retirez un appareil d’une conversation
C’est la seule partie où il faut un consensus pour éviter la fracture de la conversation, comme si deux membres se tirent dessus, que verra le troisième?
Ceci est nécessaire pour détecter les appareils révoqués, ou tout simplement pour éviter de faire venir des personnes indésirables dans une salle publique.
Alice enlève Bob
Note: Alice doit être administrateur pour voter
Pour ce faire, elle crée le fichier dans /votes/ban/membres/uri_bob/uri_alice (les membres peuvent être remplacés par des appareils pour un appareil, ou invités à des invitations ou des administrateurs pour les administrateurs) et s’engage à
Elle vérifie ensuite si le vote est résolu. Cela signifie que > 50% des administrateurs sont d’accord pour interdire Bob (si elle est seule, c’est certainement plus de 50%).
Si le vote est résolu, les fichiers dans /votes/ban peuvent être supprimés, tous les fichiers pour Bob dans /membres, /admins, /invités, /CRLs, /appareils peuvent être supprimés (ou seulement dans /appareils si c’est un appareil qui est interdit) et le certificat de Bob peut être placé dans /banned/membres/bob_uri.crt (ou /banned/devices/uri.crt si un appareil est interdit) et engagé à la repo
Alice informe ensuite les autres utilisateurs (à l’exception de Bob)
*Alice (administrateur) ajoute à nouveau Bob (membre interdit)
Pour ce faire, elle crée le fichier dans /votes/unban/membres/uri_bob/uri_alice (les membres peuvent être remplacés par des appareils pour un appareil, ou invités à des invitations ou des administrateurs pour les administrateurs) et s’engage à
Elle vérifie ensuite si le vote est résolu. Cela signifie que > 50% des administrateurs sont d’accord pour interdire Bob (si elle est seule, c’est certainement plus de 50%).
Si le vote est résolu, les fichiers dans /votes/unban peuvent être supprimés, tous les fichiers pour Bob dans /membres, /admins, /invités, /CRLs, peuvent être ajoutés à nouveau (ou seulement dans /appareils si c’est un appareil qui est non interdit) et engagés dans le repo
Retirez une conversation
Enregistrer dans convInfos removed=time::now() (comme enleverContact enregistre dans les contacts) que la conversation est supprimée et synchronisée avec les appareils d’autres utilisateurs
Si on reçoit un nouveau commentaire pour cette conversation, on l’ignore.
Si Jami est en activité et que le repo est toujours présent, la conversation n’est pas annoncée aux clients.
Deux cas: a. Si aucun autre membre de la conversation ne nous est possible de supprimer immédiatement le référentiel b. Si d’autres membres restent, engagez-vous à quitter la conversation, et maintenant attendez que au moins un autre appareil synchronise ce message. Cela évite le fait que d’autres membres détecteront toujours l’utilisateur comme un membre valide et envoie encore de nouvelles notifications de message.
Lorsque nous sommes sûrs que quelqu’un est synchronisé, supprimer effacé=time::now() et synchroniser avec les appareils d’autres utilisateurs
Tous les appareils appartenant à l’utilisateur peuvent désormais effacer le référentiel et les fichiers connexes
Comment spécifier un mode
Les modes ne peuvent pas être modifiés au fil du temps. Ou c’est une autre conversation. Donc, ces données sont stockées dans le message de commande initial.
{
"type": "initial",
"mode": 0,
}
Pour l’instant, le « mode » accepte des valeurs 0 (ONE_TO_ONE), 1 (ADMIN_INVITES_ONLY), 2 (INVITES_ONLY), 3 (PUBLIC)
Processus pour les essaims 1:1
L’objectif ici est de conserver l’ancienne API (addContact/removeContact, sendTrustRequest/acceptTrustRequest/discardTrustRequest) pour générer un essaim avec un paire et son contact.
Le processus est toujours le même, un compte peut ajouter un contact via addContact, puis envoyer une demande de confiance via le DHT. Mais deux changements sont nécessaires:
La demande de confiance intègre un « conversationId » pour informer le paire de quelle conversation cloner lors de l’acceptation de la demande
TrustRequest est retesté lorsque le contact revient en ligne. Ce n’est pas le cas aujourd’hui (car nous ne voulons pas générer une nouvelle TrustRequest si le pair rejette la première).
Ensuite, lorsqu’un contact accepte la demande, une période de synchronisation est nécessaire, car le contact doit maintenant cloner la conversation.
RemoveContact() supprimera le contact et les conversations 1:1 connexes (avec le même processus que « Remove a conversation »). La seule note ici est que si nous interdisons un contact, nous n’attendons pas la synchronisation, nous supprimons simplement tous les fichiers connexes.
Des scénarios difficiles
Il y a des cas où deux conversations peuvent être créées.
Alice ajoute Bob
Bob accepte
Alice enlève Bob
Alice ajoute Bob
ou
1, Alice ajoute Bob et Bob ajoute Alice en même temps, mais les deux ne sont pas connectés ensemble
Dans ce cas, deux conversations sont générées. Nous ne voulons pas supprimer les messages des utilisateurs ou choisir une conversation ici. Donc, parfois deux essaim 1:1 entre les mêmes membres seront montrés. Cela générera quelques bugs pendant le temps de transition (comme nous ne voulons pas casser l’API, la conversation déduite sera l’une des deux conversations montrées, mais pour l’instant c’est « ok-ish », sera corrigé lorsque les clients géreront pleinement la conversationId pour toutes les API (appels, transfert de fichiers, etc.).
Note lors de la synchronisation
Après avoir accepté la demande d’une conversation, il y a un moment où le daemon doit récupérer le référentiel distant. Pendant ce temps, les clients DOIVENT afficher une vue de synchronisation pour donner des informations à l’utilisateur.
ConfigurationManager::getConversations() renvoie l’id de la conversation même pendant la synchronisation
ConfigurationManager::conversationInfos() renvoie {{« synchronisation »: « true »}} si la synchronisation est effectuée.
ConfigurationManager::getConversationMembres() renvoie une carte de deux URI (le compte courant et le paire qui a envoyé la demande)
Les discussions demandent des spécifications
Les demandes de conversation sont représentées par une Map<String, String> avec les touches suivantes:
id: le numéro de conversation
de: uri de l’expéditeur
reçu: timestamp
titre: nom (optionnel) de la conversation
Description: (facultatif)
Avatar: (optionnel)
Synchronisation du profil de la conversation
Pour être identifiable, une conversation a généralement besoin de certaines métadonnées, comme un titre (par exemple: Jami), une description (par exemple: quelques liens, quel est le projet, etc.), et une image (le logo du projet). Ces métadonnées sont facultatives mais partagées entre tous les membres, il faut donc les synchroniser et les intégrer dans les demandes.
Conservation dans le référentiel
Le profil de la conversation est stocké dans un fichier vCard classique à la racine (/profile.vcf
) comme:
BEGIN:VCARD
VERSION:2.1
FN:TITLE
DESCRIPTION:DESC
END:VCARD
Synchronisation
Pour mettre à jour la vCard, un utilisateur avec suffisamment d’autorisations (par défaut: =ADMIN) doit modifier /profil.vcf
. et engagera le fichier avec le mimetype application/update-profile
. Le nouveau message est envoyé via le même mécanisme et tous les pairs recevront le signal MessageReceived du daemon. La branche est abandonnée si le commit contient d’autres fichiers ou trop gros ou si fait par un membre non autorisé (par défaut: <ADMIN).
Dernière mise en valeur
Dans les données synchronisées, chaque appareil envoie aux autres appareils l’état des conversations. Dans cet état, l’état affiché est envoyé. Cependant, comme chaque appareil peut avoir son propre état pour chaque conversation, et sans probablement le même dernier engagement à un moment donné, il existe plusieurs scénarios à prendre en compte:
5 scénarios sont pris en charge:
si la dernière affichage envoyé par d’autres appareils est la même que celle actuelle, il n’y a rien à faire.
si le dernier message n’est pas affiché pour le dispositif actuel, le message affiché à distance est utilisé.
si la télécommande dernière affichée n’est pas présente dans la repo, cela signifie que le comit sera récupéré plus tard, donc cache le résultat
si la télécommande est déjà récupérée, nous vérifions que la dernière localisation affichée est précédente dans l’histoire pour la remplacer
Enfin, si un message est annoncé par le même auteur, cela signifie que nous devons mettre à jour le dernier message affiché.
Préférences
Chaque conversation a des préférences fixées par l’utilisateur. Ces préférences sont synchronisées sur les appareils de l’utilisateur.
« color » - the color of the conversation (#RRGGBB format)
« ignorer les notifications » - pour ignorer les notifications de nouveaux messages dans cette conversation
« symbole » - pour définir un emoji par défaut.
Ces préférences sont stockées dans un paquet MapStringString, stockées dans accountDir/conversation_data/conversationId/preférences
et uniquement envoyées sur les appareils du même utilisateur via SyncMsg.
L’API pour interagir avec les préférences est:
// Update preferences
void setConversationPreferences(const std::string& accountId,
const std::string& conversationId,
const std::map<std::string, std::string>& prefs);
// Retrieve preferences
std::map<std::string, std::string> getConversationPreferences(const std::string& accountId,
const std::string& conversationId);
// Emitted when preferences are updated (via setConversationPreferences or by syncing with other devices)
struct ConversationPreferencesUpdated
{
constexpr static const char* name = "ConversationPreferencesUpdated";
using cb_type = void(const std::string& /*accountId*/,
const std::string& /*conversationId*/,
std::map<std::string, std::string> /*preferences*/);
};
Gestion des conflits de fusion
Parce que deux administrateurs peuvent modifier la description en même temps, un conflit de fusion peut survenir sur profile.vcf
. Dans ce cas, le commit avec le hash plus élevé (par exemple ffffff > 000000) sera choisi.
Les API
L’utilisateur a 2 méthodes pour obtenir et définir les métadonnées de la conversation:
<method name="updateConversationInfos" tp:name-for-bindings="updateConversationInfos">
<tp:added version="10.0.0"/>
<tp:docstring>
Update conversation's infos (supported keys: title, description, avatar)
</tp:docstring>
<arg type="s" name="accountId" direction="in"/>
<arg type="s" name="conversationId" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="VectorMapStringString"/>
<arg type="a{ss}" name="infos" direction="in"/>
</method>
<method name="conversationInfos" tp:name-for-bindings="conversationInfos">
<tp:added version="10.0.0"/>
<tp:docstring>
Get conversation's infos (mode, title, description, avatar)
</tp:docstring>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/>
<arg type="a{ss}" name="infos" direction="out"/>
<arg type="s" name="accountId" direction="in"/>
<arg type="s" name="conversationId" direction="in"/>
</method>
où infos
est une map<str, str>
avec les touches suivantes:
Mode: uniquement en lecture
titre
détail
avatar
Récupération de l’importation d’un compte (lien/exportation)
L’archivage DOIT contenir conversationId pour pouvoir récupérer les conversations sur de nouveaux comités après une réimportation (parce qu’il n’y a pas d’invitation à ce stade).
La conversation est là, dans ce cas, le démon est capable de cloner cette conversation
La conversationId est manquante, donc le daemon demande (via un message
{{"application/invite", conversationId}}
) une nouvelle invitation que l’utilisateur doit (re) accepter
Remarquez, une conversation ne peut être récupérée que si un contact ou un autre appareil est là, sinon elle sera perdue.
Protocoles utilisés
Je suis là.
Pourquoi ce choix?
Chaque conversation sera un référentiel de données.
Nous devons synchroniser et commander les messages. L’arbre de Merkle est la structure parfaite pour cela et peut être linéaire en fusionnant des branches. En outre, parce qu’il est largement utilisé par Git, il est facile de synchroniser entre les appareils.
Distribué par la nature, largement utilisé, beaucoup de backends et pluggable.
Peut vérifier les engagements via des crochets et des cryptos largement utilisés
Peut être stockée dans une base de données si nécessaire
Les conflits sont évités en utilisant des messages de mise en œuvre, pas des fichiers.
Ce que nous devons valider
Les performances peuvent être faibles.
Crocs dans libgit2
Plusieurs tirages en même temps?
Limits
Pour supprimer une conversation, l’appareil doit quitter la conversation et en créer une autre.
Cependant, les messages non permanents (comme les messages lisibles seulement pendant quelques minutes) peuvent être envoyés par un message spécial via le DRT (comme les notifications de typage ou de lecture).
La structure
/
- invited
- admins (public keys)
- members (public keys)
- devices (certificates of authors to verify commits)
- banned
- devices
- invited
- admins
- members
- votes
- ban
- members
- uri
- uriAdmin
- devices
- uri
- uriAdmin
- unban
- members
- uri
- uriAdmin
- CRLs
Transfert de fichiers
Swarm modifie massivement le transfert de fichiers. Maintenant, toute l’historique est synchronisée, permettant à tous les appareils de la conversation de récupérer facilement des fichiers anciens. Ces changements nous permettent de passer d’une logique où l’expéditeur a poussé le fichier sur d’autres appareils, en essayant de se connecter à leurs appareils (c’était mauvais car il n’était pas vraiment résistant aux changements de connexion / défaillances et avait besoin d’une nouvelle tentative manuelle) à une logique où l’expéditeur permet aux autres appareils de télécharger.
Protocole
L’expéditeur ajoute un nouveau commette à la conversation avec le format suivant:
value["tid"] = "RANDOMID";
value["displayName"] = "DISPLAYNAME";
value["totalSize"] = "SIZE OF THE FILE";
value["sha3sum"] = "SHA3SUM OF THE FILE";
value["type"] = "application/data-transfer+json";
et crée un lien dans ${data_path}/conversation_data/${conversation_id}/${file_id}
où file_id=${commitid}_${value["tide"]}.${extension}
Ensuite, le récepteur peut maintenant télécharger les fichiers en contactant les appareils hébergeant le fichier en ouvrant un canal avec name="data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId
et stocker les informations que le fichier attend dans ${data_path}/conversation_data/${conversation_id}/waiting
Le périphérique recevant la connexion acceptera le canal en vérifiant si le fichier peut être envoyé (si sha3sum est correct et si le fichier existe). Le récepteur conservera le premier canal ouvert, fermera les autres et écrira dans un fichier (avec le même chemin que l’expéditeur: ${data_path}/conversation_data/${conversation_id}/${file_id}
) toutes les données entrant.
Lorsque le transfert est terminé ou que le canal est fermé, le sha3sum est vérifié pour valider que le fichier est correct (ou il est supprimé).
En cas d’échec, lorsque l’appareil de la conversation sera redémarré, nous demanderons tous les fichiers d’attente de la même manière.
Appelle le swarm
Idée
Une conversation en essaim peut avoir plusieurs rendez-vous.
« accountUri/deviceId/conversationId/confId » où accountUri/deviceId décrit l’hôte.
L’hôte peut être déterminé de deux façons:
Dans le métadonnées en essaim, où elles sont stockées comme le titre/desc/avatar de la pièce.
Ou le premier appelant.
Lors du démarrage d’un appel, l’hôte ajoutera un nouveau engagement au swarm, avec l’URI à rejoindre (accountUri/deviceId/conversationId/confId).
Ainsi, chaque partie recevra l’information qu’un appel a commencé et pourra y rejoindre en l’appelant.
Des attaques?
Évitez les bombes de git
Notes
Le timestamp d’un commit peut être fiable car il est éditable. Seul le timestamp de l’utilisateur peut être fiable.
TLS
Les opérations Git, les messages de contrôle, les fichiers et autres choses utiliseront un lien TLS v1.3 p2p avec seulement des chiffres qui garantissent le PFS.
DHT (udp)
Utilisé pour envoyer des messages pour les mobiles (pour déclencher des notifications push) et pour initier des connexions TCP.
Activité du réseau
Processus d’invitation
Alice veut inviter Bob:
Alice ajoute Bob à une conversation
Alice generates an invite: { « application/invite+json » : { « conversationId »: « $id », « members »: [{…}] }}
Deux possibilités pour envoyer le message a. Si pas connecté, via le DHT b. Autrement, Alice envoie sur le canal SIP
Deux possibilités pour Bob a. Reçoit l’invitation, un signal est émis pour le client b. Non connecté, donc ne recevra jamais la demande car Alice ne doit pas savoir si Bob a simplement ignoré ou bloqué Alice.
Processus pour envoyer un message à quelqu’un
Alice veut envoyer un message à Bob:
Alice ajoute un message dans le repo, donnant une pièce d’identité
Alice reçoit un message reçu (de lui-même) si elle réussit
Dans les deux cas, un message est créé: { « application/im-gitmessage-id » : « {« id »: »$convId », « commit »: »$commitId », « deviceId »: « $alice_device_hash »} »}. a. Si elle n’est pas connectée, via le DHT b. Autrement, Alice envoie sur le canal SIP
4 possibilités pour Bob: a. Bob n’est pas connecté à Alice, alors s’il fait confiance à Alice, demandez une nouvelle connexion et allez à b. b. Si connecté, appelez Alice et annoncez de nouveaux messages c. Bob ne connaît pas cette conversation. Demandez à travers le DHT d’obtenir une invitation d’abord pour être en mesure d’accepter cette conversation ({« application/invitation », conversationId}) d. Bob est déconnecté (aucun réseau, ou juste fermé). Il ne recevra pas le nouveau message mais tentera de synchroniser lorsque la connexion suivante se produira
Mise en œuvre
! [Diagramme: cours de chat en essaim]
Supported messages
Initial message
{
"type": "initial",
"mode": 0,
"invited": "uri"
}
Represents the first commit of a repository and contains the mode:
enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC }
and invited
if mode = 0.
Text message
{
"type": "text/plain",
"body": "content",
"react-to": "id (optional)"
}
Or for an edition:
{
"type": "application/edited-message",
"body": "content",
"edit": "id of the edited commit"
}
Appels
Show the end of a call (duration in milliseconds):
{
"type": "application/call-history+json",
"to": "uri",
"duration": "3000"
}
Or for hosting a call in a group (when it starts)
{
"type": "application/call-history+json",
"uri": "uri of the host",
"device": "device of the host",
"confId": "hosted confId"
}
A second commit with the same JSON + duration
is added at the end of the call when hosted.
Add a file
{
"type": "application/data-transfer+json",
"tid": "unique identifier of the file",
"displayName": "File name",
"totalSize": "3000",
"sha3sum": "a sha3 sum"
}
totalSize
is in bits,
Updating profile
{
"type": "application/update-profile",
}
Member event
{
"type": "member",
"uri": "uri of the member",
"action": "add/join/remove/ban"
}
When a member is invited, join or leave or is kicked from a conversation
Vote event
Generated by administrators to add a vote for kicking or un-kicking someone.
{
"type": "vote",
"uri": "uri of the member",
"action": "ban/unban"
}
!! Le vieux projet!!
Remarque: Les notes suivantes ne sont pas encore organisées.
Des améliorations de la cryptographie.
Pour une fonctionnalité de chat de groupe sérieuse, nous avons également besoin de crypto sérieuse. Avec la conception actuelle, si un certificat est volé comme les valeurs DHT précédentes d’une conversation, la conversation peut être décryptée. Peut-être que nous devons aller à quelque chose comme ** Double ratchet**.
Note: a lib might exist to implement group conversations.
Il a besoin d’un soutien ECC dans OpenDHT
Utilisation
Ajouter des rôles?
Il existe deux cas d’utilisation principaux pour les discussions de groupe:
Quelque chose comme un Mattermost dans une entreprise, avec des canaux privés, et certains rôles (admin/spectateur/bot/etc) ou pour l’éducation (où seulement quelques-uns sont actifs).
Des conversations horizontales comme une conversation entre amis.
Jami will be for which one?
Idée de mise en œuvre
Un certificat pour un groupe qui signe un utilisateur avec un drapeau pour un rôle.
Joignez-vous à une conversation
Uniquement par invitation directe
Par un lien/code QR/tout ce qui est
Par le nom de la chambre?
Ce dont nous avons besoin
Confidentialité: les membres en dehors du chat de groupe ne devraient pas pouvoir lire les messages dans le groupe
Confidentialité: si une clé du groupe est compromise, les messages précédents doivent rester confidentiels (le plus possible)
Ordonnage des messages: Il faut avoir les messages dans le bon ordre
Synchronisation: Il faut aussi s’assurer que tous les messages sont disponibles le plus rapidement possible.
Persistance: En fait, un message sur le DHT ne dure que 10 minutes. Parce que c’est le meilleur moment calculé pour ce type de DHT. Pour persister les données, le nœud doit réinsérer la valeur du DHT toutes les 10 minutes. Une autre façon de le faire lorsque le nœud est hors ligne est de laisser les nœuds réinsérer les données. Mais, si après 10 minutes, 8 nœuds sont toujours là, ils feront 64 demandes (et c’est exponentiel). La façon actuelle d’éviter le spam pour cela est interroge. Cela fera toujours 64 demandes mais limitera la redondance maximale à 8 nœuds.
Autres voies distribuées
Il faut faire des recherches.
J’ai besoin d’une enquête.
Il faut enquêter.
En fonction des travaux actuels que nous avons
Le chat de groupe peut être basé sur le même travail que celui que nous avons déjà pour les multiples appareils (mais ici, avec un certificat de groupe).
Il faut déplacer la base de données du client vers le daemon.
Si personne n’est connecté, la synchronisation ne peut pas être faite, et la personne ne verra jamais la conversation
Un autre DHT dédié
Comme un DHT avec un superutilisateur.
Transfert de fichiers
Actuellement, l’algorithme de transfert de fichiers est basé sur une connexion TURN (voir Transfert de fichiers). Dans le cas d’un grand groupe, cela ne sera pas bon. Nous avons d’abord besoin d’un implement p2p pour le transfert de fichiers.
Autre problème: actuellement, il n’existe pas de mise en œuvre de l’assistance TCP pour ICE dans PJSIP.
Les ressources
Les résultats de l’enquête ont été publiés sur le site Web de l’Union européenne.
Synchronisation répartie robuste des systèmes linéaires en réseau avec des informations intermittentes (Sean Phillips et Ricardo G. Sanfelice)