El grupo
A swarm (group chat) is a set of participants capable of resilient, decentralized communication. For example, if two participants lose connectivity with the rest of the group (e.g., during an Internet outage) but can still reach each other over a LAN or subnetwork, they can exchange messages locally and then synchronize with the rest of the group once connectivity is restored.
A swarm is defined by the following properties:
Ability to split and merge based on network connectivity.
History synchronization. Every participant must be able to send a message to the entire group.
No hay autoridad central, no puede confiar en ningún servidor.
Non-repudiation. Devices must be able to verify past messages” validity and to replay the entire history.
Perfect Forward Secrecy (PFS) is provided on the transport channels. Storage is handled by each device.
La idea principal es conseguir un árbol de Merkle sincronizado con los participantes.
We identified four modes for swarms that we want to implement:
ONE_TO_ONE: A private conversation between two endpoints—either between two users or with yourself.
ADMIN_INVITES_ONLY: A swarm in which only the administrator can invite members (for example, a teacher-managed classroom).
INVITES_ONLY: A closed swarm that admits members strictly by invitation; no one may join without explicit approval.
PUBLIC: A public swarm that anyone can join without prior invitation (For example a forum).
Escenarios
Crear un grupo
Bob quiere crear un nuevo enjambre
Bob crea un repositorio Git local.
Luego, crea un compromiso inicial firmado con lo siguiente:
Su llave pública en
/admins
Su certificado de dispositivo en ̀ /devices`
Su RCA en ̀ /crls`
El hash del primer compromiso se convierte en el ID de la conversación
Bob announces to his other devices that he created a new conversation. This is done via an invite to join the group sent through the DHT to other devices linked to that account.
Añadir a alguien
Bob adds Alice
Bob adds Alice to the repo:
Añade la URI invitada en
/invited
Añade la RLC a
/crls
Bob sends a request on the DHT.
Recibir una invitación
Alice gets the invite to join the previously created swarm
Alice accepts the invite (if she declines, nothing happens; she will remain in the «invited» list, and will never receive any messages)
A peer-to-peer connection is established between Alice and Bob.
Alice pulls the Git repository from Bob. WARNING this means that messages require a connection, not from the DHT as it is today.
Alice validates the commits from Bob.
To validate that Alice is a member, she removes the invite from
/invited
directory, then adds her certificate to the/members
directoryOnce all commits are validated and syncronized to her device, Alice discovers other members of the group. with these peers, she will then construct the DRT with Bob as a bootstrap.
Enviando un mensaje
Alice sends a message to Bob
Alice creates a commit message. She constructs a JSON payload containing the MIME type and message body. For example:
{
"type": "text/plain",
"body": "hello"
}
Alice ensure her device credentials are present. If Alice’s device certificate or its associated CRL isn’t already stored in the repository, she adds them so that other participants can verify the commit.
Alice commits to the repository (Because Jami relies primarily on commit-message metadata rather than file contents, merge conflicts are rare; the only potential conflicts would involve CRLs or certificates, which are versioned in a dedicated location).
Alice announces the commit via the DRT with a service message and pings the DHT for mobile devices (they must receive a push notification).
Nota
To notify other devices, the sender transmits a SIP message with type: application/im-gitmessage-id
.
The JSON payload includes the deviceId (the sender’s), the conversationId and the reference (hash) of the new commit.
Recibir un mensaje
Bob receives a message from Alice
Bob performs a Git pull on Alice’s repository.
All incoming commits MUST be verified by a hook.
If all commits are valid, commits are stored and displayed.Bob then announces the message via the DRT for other devices.
If any commit is invalid, pull is aborted. Alice must restore her repository to a correct state before retrying.
Validación de un compromiso
Para evitar que los usuarios empujen algunos compromisos no deseados (con conflictos, mensajes falsos, etc.), así es como cada compromiso (desde el más antiguo al más nuevo) DEBE ser validado antes de fusionar una rama remota:
Nota
Si la validación falla, se ignora la recuperación y no fusionamos la rama (y eliminamos los datos), y se debe notificar al usuario.
Si una búsqueda es demasiado grande, no se fusiona.
For each incoming commit, ensure that the sending device is currently authorized and that the issuer’s certificate exists under /members or /admins, and the device’s certificate under /devices.
Then handle one of three cases, based on the commit’s parent count:
Merge Commit (2 parents). No further validation is required, merges are always accepted.
Initial Commit (0 parents). Validate that this is the very first repository snapshot:
Admin certificate is added.
Device certificate is added.
CRLs (Certificate Revocation Lists) are added.
No other files are present.
Ordinary Commit (1 parent). The commit message must be JSON with a top‑level
type
field. Handle eachtype
as follows:If
text
(or any non–file‑modifying MIME type)Signature is valid against the author’s certificate in the repo.
No unexpected files are added or removed.
If
vote
voteType
is one of the supported values (e.g. «ban», «unban»).The vote matches the signing user.
The signer is an admin, their device is present, and not themselves banned.
No unexpected files are added or removed.
If
member
If
adds
Properly signed by the inviter.
New member’s URI appears under
/invited
.No unexpected files are added or removed.
If ONE_TO_ONE, ensure exactly one admin and one member.
If ADMIN_INVITES_ONLY, the inviter must be an admin.
If
joins
Properly signed by the joining device.
Device certificate added under
/devices
.Corresponding invite removed from
/invited
and certificate added to/members
.No unexpected files are added or removed.
If
banned
Vote is valid per the
vote
rules above.Ban is issued by an admin.
Target’s certificate moved to /banned.
Only files related to the ban vote are removed.
No unexpected files are added or removed.
Fallback. If the commit’s type or structure is unrecognized, reject it and notify the peer (or user) that they may be running an outdated version or attempting unauthorized changes.
Prohíbe un dispositivo
Importante
El código fuente de Jami tiende a usar los términos (un)ban, mientras que la interfaz de usuario usa los términos (un)block.
Alice, Bob, Carla, Denys are in a swarm. Alice issues a ban against Denys.
In a fully peer‑to‑peer system with no central authority, this simple action exposes three core challenges:
Untrusted Timestamps: Commit timestamps cannot be relied upon for ordering ban events, as any device can forge or replay commits with arbitrary dates.
Conflicting bans: In cases where multiple admin devices exist, network partitions can result in conflicting ban decisions. For instance, if Alice can communicate with Bob but not with Denys and Carla, while Carla can communicate with Denys, conflicting bans may occur. If Denys bans Alice while Alice bans Denys, the group’s state becomes unclear when all members eventually reconnect and merge their conversation histories.
Compromised or expired devices: Devices can be compromised, stolen, or have their certificates expire. The system must allow banning such devices and ensure they cannot manipulate their certificate or commit timestamps to send unauthorized messages or falsify their expiration status.
Los sistemas similares (con sistemas de grupos distribuidos) no son mucho, pero estos son algunos ejemplos:
[mpOTR no define cómo prohibir a alguien]
La señal, sin ningún servidor central para el chat de grupo (EDIT: recientemente cambiaron ese punto), no da la capacidad de prohibir a alguien de un grupo.
This voting system needs a human action to ban someone or must be based on the CRLs info from the repository (because we can not trust external CRLs).
Retirar un dispositivo de una conversación
Esta es la única parte que DEBE tener un consenso para evitar la división de la conversación, como si dos miembros se patean de la conversación, ¿qué verá el tercero?
Esto es necesario para detectar dispositivos revocados, o simplemente evitar que personas no deseadas estén presentes en una sala pública.
Alice elimina a Bob
Importante
Alice DEBE ser una administradora para votar.
Para ello, crea el archivo en /votes/ban/members/uri_bob/uri_alice (los miembros pueden ser reemplazados por dispositivos para un dispositivo, o invitados para invitaciones o administradores para administradores) y compromete
Luego comprueba si el voto está resuelto. Esto significa que >50% de los administradores están de acuerdo en prohibir a Bob (si ella está sola, seguramente es más del 50%).
Si se resuelve la votación, los archivos en /votes/ban se pueden eliminar, todos los archivos de Bob en /membres, /admins, /invitados, /CRLs, /dispositivos se pueden eliminar (o sólo en /dispositivos si es un dispositivo que está prohibido) y el certificado de Bob se puede colocar en /banned/membres/bob_uri.crt (o /banned/devices/uri.crt si un dispositivo está prohibido) y comprometido con el repo
Luego, Alice informa a otros usuarios (fuera de Bob)
Alice (admin) vuelve a agregar a Bob (miembro expulsado)
Si vota por eliminar a Bob de la prohibición. Para hacer eso, crea el archivo en /votes/unban/members/uri_bob/uri_alice (los miembros se pueden reemplazar por dispositivos para un dispositivo, o invitados para invitaciones o administradores para administradores) y confirma
Luego comprueba si el voto está resuelto. Esto significa que >50% de los administradores están de acuerdo en prohibir a Bob (si ella está sola, seguramente es más del 50%).
Si se resuelve la votación, los archivos en /votes/unban pueden ser eliminados, todos los archivos para Bob en /membres, /admins, /invitados, /CRLs, pueden ser re-agregados (o sólo en /dispositivos si es un dispositivo que no está prohibido) y comprometidos con el repo
Eliminar una conversación
Guardar en convInfos removed=time::now() (como eliminarContact guarda en contactos) que la conversación se elimina y se sincroniza con los dispositivos de otros usuarios
Ahora, si se recibe un nuevo compromiso para esta conversación se ignora
Ahora, si Jami está en marcha y el repo todavía está presente, la conversación no se anuncia a los clientes
Dos casos: a. Si no hay otro miembro en la conversación podemos eliminar inmediatamente el repositorio b. Si aún quedan otros miembros, comprométase a que abandonemos la conversación y ahora espere a que al menos otro dispositivo sincronice este mensaje. Esto evita el hecho de que otros miembros sigan detectando al usuario como miembro válido y sigan enviando notificaciones de mensajes nuevos.
Cuando estamos seguros de que alguien está sincronizado, eliminar borrado=time::now() y sincronizar con los dispositivos de otros usuarios
Todos los dispositivos propiedad del usuario ahora pueden borrar el repositorio y archivos relacionados
Cómo especificar un modo
Los modos no se pueden cambiar a través del tiempo. o es otra conversación. Así que, estos datos se almacenan en el mensaje de compromiso inicial. El mensaje de compromiso será el siguiente:
{
"type": "initial",
"mode": 0,
}
Por ahora, «modo» acepta valores 0 (ONE_TO_ONE), 1 (ADMIN_INVITES_ONLY), 2 (INVITES_ONLY), 3 (PUBLIC)
Processes for 1:1 chats
The goal here is to keep the old API (addContact/removeContact, sendTrustRequest/acceptTrustRequest/discardTrustRequest) to create a chat with a peer and its contact. This still implies some changes that we cannot ignore:
El proceso sigue siendo el mismo, una cuenta puede agregar un contacto a través de addContact, luego enviar una solicitud de confianza a través de la DHT. Pero dos cambios son necesarios:
La solicitud de confianza incorpora un «conversaciónId» para informar al compañero de la conversación que clonar al aceptar la solicitud
TrustRequest se vuelve a intentar cuando el contacto vuelve a la red. No es el caso hoy (ya que no queremos generar un nuevo TrustRequest si el compañero descarta el primero).
Luego, cuando un contacto acepta la solicitud, es necesario un período de sincronización, porque el contacto ahora necesita clonar la conversación.
RemoveContact() eliminará el contacto y las conversaciones 1:1 relacionadas (con el mismo proceso que «Remove a conversation»). La única nota aquí es que si prohíbemos un contacto, no esperamos a la sincronización, simplemente eliminamos todos los archivos relacionados.
Escenarios complicados
Hay algunos casos en los que se pueden crear dos conversaciones.
Alice agrega a Bob.
Bob acepta.
Alice elimina a Bob.
Alice agrega a Bob.
o
Alice agrega a Bob y Bob agrega a Alice al mismo tiempo, pero ambos no están conectados entre sí.
In this case, two conversations are generated. We don’t want to remove messages from users or choose one conversation here. So, sometimes two conversations between the same members will be shown. It will generate some bugs during the transition time (as we don’t want to break API, the inferred conversation will be one of the two shown conversations, but for now it’s «ok-ish», will be fixed when clients will fully handle conversationId for all APIs (calls, file transfer, etc)).
Importante
Después de aceptar la solicitud de una conversación, hay un momento en que el demonio necesita recuperar el repositorio distante. Durante este tiempo, los clientes DEBEN mostrar una vista de sincronización para proporcionar información al usuario. Mientras se sincroniza:
ConfigurationManager::getConversations() devolverá el id de la conversación incluso mientras se sincroniza.
ConfigurationManager::conversationInfos() devolverá {{«sincronización»: «verdadero»}} si se está sincronizando.
ConfigurationManager::getConversationMembers() devolverá un mapa de dos URI (la cuenta actual y el par que envió la solicitud).
Las conversaciones solicitan especificación
Las solicitudes de conversaciones se representan con un Map<String, String> con las siguientes teclas:
id: el ID de la conversación
de: URI del remitente
Recibida: sello de tiempo
Título: (opcional) nombre de la conversación
Descripción: (opcional)
avatar: (optional) the profile picture
Sincronización de perfil de conversación
Para ser identificable, una conversación generalmente necesita algunos metadatos, como un título (por ejemplo, Jami), una descripción (por ejemplo, algunos enlaces, qué es el proyecto, etc.), y una imagen (el logotipo del proyecto).
Almacenamiento en el depósito
El perfil de la conversación se almacena en un archivo vCard clásico en la raíz (/profile.vcf
) como:
BEGIN:VCARD
VERSION:2.1
FN:TITLE
DESCRIPTION:DESC
END:VCARD
Sincronización
Para actualizar la vCard, un usuario con suficientes permisos (por defecto: =ADMIN) necesita editar “/profile.vcf” y confirmará el archivo con el tipo mimetype “application/update-profile”` El nuevo mensaje se envía a través del mismo mecanismo y todos los pares recibirán la señal Mensaje recibido del demonio. La rama se descarta si la confirmación contiene otros archivos o es demasiado grande o si la realiza un miembro no autorizado (por defecto: <ADMIN).
Última vez mostrada
En los datos sincronizados, cada dispositivo envía a otros dispositivos el estado de las conversaciones. En este estado, se envía el último mostrado. Sin embargo, debido a que cada dispositivo puede tener su propio estado para cada conversación, y probablemente sin el mismo último compromiso en algún momento, hay varios escenarios que tener en cuenta:
Se apoyan cinco escenarios:
Si la última muestra enviada por otros dispositivos es la misma que la actual, no hay nada que hacer.
si no se muestra la última en el dispositivo actual, se utilizará el mensaje de visualización remota.
si el remoto último mostrado no está presente en el repo, significa que el commit se buscará más tarde, por lo que el resultado se almacenará en caché
si el control remoto ya está recogido, comprobamos que el local último mostrado es antes en la historia para reemplazarlo
Finalmente, si se anuncia un mensaje del mismo autor, significa que necesitamos actualizar el último que se muestra.
Preferencias
Cada conversación tiene adjuntadas preferencias establecidas por el usuario. Estas preferencias se sincronizan a través de los dispositivos del usuario. Este puede ser el color de la conversación, si el usuario quiere ignorar las notificaciones, el límite de tamaño de transferencia de archivos, etc. Por ahora, las claves reconocidas son:
«color» - el color de la conversación (formato #RRGGBB)
«ignore Notifications» - para ignorar las notificaciones de nuevos mensajes en esta conversación
«símbolo» - para definir un emoji predeterminado.
Estas preferencias se almacenan en un paquete MapStringString, almacenado en accountDir/conversation_data/conversationId/preferencias
y sólo se envían a través de dispositivos del mismo usuario a través de SyncMsg.
Las API para interactuar con las preferencias son:
// 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*/);
};
Gestión de conflictos de fusiones
Debido a que dos administradores pueden cambiar la descripción al mismo tiempo, puede ocurrir un conflicto de fusión en profile.vcf
. En este caso, se elegirá el commit con el hash más alto (por ejemplo ffffff > 000000).
Las API
El usuario tiene 2 métodos para obtener y configurar los metadatos de conversación:
<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>
donde infos
es un map<str, str>
con las siguientes teclas:
Modo: Sólo para leer
título
Descripción
avatar: the profile picture
Re-importación de una cuenta (enlace/export)
El archivo DEVE contener conversaciónId para poder recuperar conversaciones en nuevos comités después de una re-importación (porque no hay invitación en este punto).
La conversación está ahí, en este caso, el demonio es capaz de volver a clonar esta conversación
La conversaciónId está faltando, por lo que el demonio pide (a través de un mensaje
{{"aplicación/invita", conversaciónId}}
) una nueva invitación que el usuario necesita (re) aceptar
Importante
Solo se puede recuperar una conversación si hay allí un contacto u otro dispositivo, de lo contrario se perderá. No hay magia.
Protocolos utilizados
Git
¿Por qué esta elección
Cada conversación será un repositorio Git. Esta elección está motivada por:
Necesitamos sincronizar y ordenar mensajes. El Árbol de Merkle es la estructura perfecta para hacerlo y puede ser linearizado por la fusión de ramas. Además, debido a que es masivamente utilizado por Git, es fácil sincronizar entre dispositivos.
Distribuido por la naturaleza, utilizado masivamente, muchos backends y enchufables.
Puede verificar compromisos a través de ganchos y criptomonedas masivamente utilizadas
Puede almacenarse en una base de datos si es necesario
Los conflictos se evitan mediante el uso de mensajes de compromiso, no archivos.
Lo que tenemos que validar
¿Puede ser bajo el rendimiento?
Los ganchos en libgit2
¿Múltiples tiros al mismo tiempo?
Los límites
Para eliminar una conversación, el dispositivo tiene que dejar la conversación y crear otra.
Sin embargo, los mensajes no permanentes (como los mensajes que se pueden leer solo durante algunos minutos) pueden enviarse a través de un mensaje especial a través del DRT (como las notificaciones de mecanografía o lectura).
Estructura
/
- 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
Transferencia de archivos
This new system overhauls file sharing: the entire history is now kept in sync, so any device in the conversation can instantly access past files. Rather than forcing the sender to push files directly—an approach that was fragile in the face of connection drops and often required manual retries—devices simply download files when they need them. Moreover, once one device has downloaded a file, it can act as a host for others, ensuring files remain available even if the original sender goes offline.
Protocolo
El remitente agrega un nuevo compromiso en la conversación con el siguiente formato:
value["tid"] = "RANDOMID";
value["displayName"] = "DISPLAYNAME";
value["totalSize"] = "SIZE OF THE FILE";
value["sha3sum"] = "SHA3SUM OF THE FILE";
value["type"] = "application/data-transfer+json";
y crea un enlace en ${data_path}/conversation_data/${conversation_id}/${file_id}
donde file_id=${commitid}_${value["tiempo"]}.${extension}
Luego, el receptor puede descargar los archivos poniéndose en contacto con los dispositivos que alojan el archivo abriendo un canal con name="data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId
y almacenar la información que el archivo está esperando en ${data_path}/conversation_data/${conversation_id}/waiting
El dispositivo que recibe la conexión aceptará el canal verificando si el archivo puede ser enviado (si sha3sum es correcto y si el archivo existe). El receptor mantendrá el primer canal abierto, cerrará los demás y escribirá en un archivo (con el mismo camino que el remitente: ${data_path}/conversation_data/${conversation_id}/${file_id}
) todos los datos entrantes.
Cuando la transferencia se termina o el canal se cierra, la sha3sum se verifica para validar que el archivo es correcto (o se elimina).
En caso de fallo, cuando un dispositivo de la conversación vuelva a estar en línea, pediremos todos los archivos de espera de la misma manera.
Call in Swarm
La idea
Una conversación de enjambre puede tener múltiples encuentros. Una cita se define mediante el siguiente URI:
«accountUri/deviceId/conversationId/confId» donde cuentaUri/deviceId describe al host.
El anfitrión puede determinarse de dos maneras:
In the swarm metadatas. Where it’s stored like the title/desc/avatar (profile picture) of the room
O el primer llamador.
When starting a call, the host will add a new commit to the repository, with the URI to join (accountUri/deviceId/conversationId/confId). This will be valid till the end of the call (announced by a commit with the duration to show)
Así que cada parte recibirá la información de que una llamada ha comenzado y podrá unirse a ella llamándola.
¿Los ataques?
Evita las bombas Git
Las notas
La timestamp de un commit puede ser de confianza porque es editable. Sólo la timestamp del usuario puede ser de confianza.
TLS
Las operaciones de Git, los mensajes de control, los archivos y otras cosas usarán un enlace TLS v1.3 p2p con sólo cifrados que garantizan PFS. Así que cada clave se renegocia para cada nueva conexión.
DHT (UDP)
Se utiliza para enviar mensajes para móviles (para activar notificaciones push) e iniciar conexiones TCP.
Actividad de la red
Proceso para invitar a alguien
Alice quiere invitar a Bob:
Alice añade a Bob a una conversación
Alice genera una invitación: { «application/invite+json» : { «conversationId»: «$id», «miembros»: [{…}] }}
Dos posibilidades para enviar el mensaje a. Si no está conectado, a través de la DHT b. De otro modo, Alice envía en el canal SIP
Bob a. Recibe la invitación, se emite una señal para el cliente b. No está conectado, por lo que nunca recibirá la solicitud porque Alice no debe saber si Bob simplemente ignoró o bloqueó a Alice.
Proceso para enviar un mensaje a alguien
Alice quiere enviar un mensaje a Bob:
Alice añade un mensaje en el repo, dando una identificación
Alice recibe un mensaje recibido (de ella misma) si tiene éxito
En ambos casos se crea un mensaje: { «aplicación/im-gitmessage-id» : «{«id»:»\(convId", "commit":"\)commitId», «deviceId»: «$alice_device_hash»}»}.
Bob no está conectado a Alice, así que si confía en ella, pida una nueva conexión y vaya a b. b. Si está conectado, traiga de Alice y anuncie nuevos mensajes c. Bob no conoce esa conversación. Pida a través de la DHT que obtenga una invitación primero para poder aceptar esa conversación ({«aplicación/invite», conversaciónId}) d. Bob está desconectado (ninguna red, o simplemente cerrado).
Aplicación
¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡
Mensajes compatibles
Mensaje inicial
{
"type": "initial",
"mode": 0,
"invited": "URI"
}
Representa el primer commit de un repositorio y contiene el modo:
enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC }
e invitado
si el modo = 0.
Mensaje de texto
{
"type": "text/plain",
"body": "content",
"react-to": "id (optional)"
}
O para una edición:
{
"type": "application/edited-message",
"body": "content",
"edit": "id of the edited commit"
}
Llamadas
Mostrar el final de una llamada (duración en milisegundos):
{
"type": "application/call-history+json",
"to": "URI",
"duration": "3000"
}
O para alojar una llamada en un grupo (cuando comienza)
{
"type": "application/call-history+json",
"uri": "host URI",
"device": "device of the host",
"confId": "hosted confId"
}
Un segundo commit con el mismo JSON + duration
se añade al final de la llamada cuando se aloja.
Agregar un archivo
{
"type": "application/data-transfer+json",
"tid": "unique identifier of the file",
"displayName": "File name",
"totalSize": "3000",
"sha3sum": "a sha3 sum"
}
totalSize
está en bits,
Actualizando perfil
{
"type": "application/update-profile",
}
Acto de miembro
{
"type": "member",
"uri": "member URI",
"action": "add/join/remove/ban"
}
Cuando un miembro es invitado, se une o se va o es expulsado de una conversación
Votar acto
Generado por administradores para añadir un voto para expulsar o no expulsar a alguien.
{
"type": "vote",
"uri": "member URI",
"action": "ban/unban"
}
¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡
Nota
Las siguientes notas aún no están organizadas. Solo son una línea de pensamiento.
Mejoras en las criptomonedas.
Para una función de chat de grupo seria, también necesitamos criptografía seria. Con el diseño actual, si un certificado es robado como los valores DHT anteriores de una conversación, la conversación puede ser descifrada. Tal vez necesitamos ir a algo como ** Double ratchet**.
Nota
Podría existir una biblioteca para implementar conversaciones grupales.
Necesita apoyo de ECC en OpenDHT
Uso
¿Añadir papeles?
Hay dos casos de uso principales para las charlas grupales:
Algo como un Mattermost en una empresa, con canales privados, y algunos roles (admin/espectador/bot/etc) o para la educación (donde sólo unos pocos están activos).
Las conversaciones horizontales son como una conversación entre amigos.
¿Para quién será Jami?
Ideas de ejecución
Un certificado para un grupo que firma un usuario con una bandera para un papel.
Únete a una conversación
Sólo por invitación directa
Por medio de un enlace/Código QR/cualquier
¿A través de un nombre de habitación?
Lo que necesitamos
Confidencialidad: los miembros fuera del chat de grupo no deben poder leer mensajes en el grupo
Secreto de entrada: si alguna clave del grupo es comprometida, los mensajes anteriores deben permanecer confidenciales (al máximo posible)
Ordenación de mensajes: Es necesario tener mensajes en el orden correcto
Sincronización: También es necesario asegurarse de tener todos los mensajes lo antes posible.
Persistencia: En realidad, un mensaje en el DHT dura solo 10 minutos. Porque es el mejor momento calculado para este tipo de DHT. Para persistir en datos, el nodo debe volver a colocar el valor en el DHT cada 10 minutos. Otra forma de hacer cuando el nodo está fuera de línea es dejar que los nodos vuelvan a colocar los datos. Pero, si después de 10 minutos, 8 nodos siguen aquí, harán 64 solicitudes (y es exponencial). La forma actual de evitar el spamming para eso es consultada. Esto todavía hará 64 solicitudes pero limitará la redundancia máxima a 8 nodos.
Otras formas distribuidas
Necesitamos una investigación.
Necesitamos una investigación.
Necesitamos una investigación.
Basándonos en el trabajo actual que tenemos
El chat en grupo puede basarse en el mismo trabajo que ya tenemos para múltiples dispositivos (pero aquí, con un certificado de grupo).
Esto necesita mover la base de datos del cliente al demonio.
Si nadie está conectado, la sincronización no se puede hacer, y la persona nunca verá la conversación
Otro DHT dedicado
Como un DHT con un superusuario.
What’s next for file transfers
Currently, the file transfer algorithm is based on a TURN connection (See Transferencia de archivos). In the case of a big group, this will be bad. We first need a P2P connection for the file transfer. Implement the RFC for P2P transfer.
Other problem: currently there is no implementation for TCP support for ICE in PJSIP. This is mandatory for this point (in PJSIP or homemade)