El grupo
Un enjambre (chat de grupo) es un conjunto de participantes capaces de una comunicación resiliente y descentralizada. Por ejemplo, si dos participantes pierden la conectividad con el resto del grupo (por ejemplo, durante una interrupción de Internet) pero aún pueden comunicarse entre sí a través de una LAN o subred, pueden intercambiar mensajes localmente y luego sincronizarse con el resto del grupo una vez que se restablezca la conectividad.
Se define un enjambre mediante las siguientes propiedades:
Capacidad para dividir y fusionar en función de la conectividad de red.
Sincronización del historial. Cada participante debe poder enviar un mensaje a todo el grupo.
No hay autoridad central, no puede confiar en ningún servidor.
No repudio. Los dispositivos deben poder verificar la validez de los mensajes pasados y reproducir todo el historial.
El Secreto Directo Perfecto (PFS) se proporciona en los canales de transporte. Cada dispositivo maneja el almacenamiento.
La idea principal es conseguir un árbol de Merkle sincronizado con los participantes.
Identificamos cuatro modos para enjambres que queremos implementar:
ONE_TO_ONE:Una conversación privada entre dos puntos finales, ya sea entre dos usuarios o contigo mismo.
ADMIN_INVITES_ONLY: Un enjambre en el que solo el administrador puede invitar a miembros (por ejemplo, un aula administrada por un profesor).
INVITES_ONLY: Un enjambre cerrado que admite miembros estrictamente por invitación; nadie puede unirse sin aprobación explícita.
PUBLIC: Un enjambre público al que cualquiera puede unirse sin invitación previa (por ejemplo, un foro).
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 anuncia a sus otros dispositivos que creó una nueva conversación. Esto se hace a través de una invitación para unirse al grupo enviada a través del DHT a otros dispositivos vinculados a esa cuenta.
Añadir a alguien
Bob agrega a Alice
Bob agrega Alice al repositorio:
Añade la URI invitada en
/invited
Añade la RLC a
/crls
Bob envía una solicitud en el DHT.
Recibir una invitación
Alice recibe la invitación para unirse al enjambre creado previamente
Alice acepta la invitación (si rechaza, no pasa nada; seguirá en la lista de «invitados» y nunca recibirá ningún mensaje)
Se establece una conexión punto a punto entre * Alice* y Bob.
Alice extrae el repositorio Git de Bob. ATENCIÓN esto significa que los mensajes requieren una conexión, no del DHT como es hoy.
Alice valida las confirmaciones de Bob.
Para validar que Alice es miembro, elimina la invitación del directorio “/invited
y luego agrega su certificado al directorio
/members”Una vez que todas las confirmaciones se validan y sincronizan con su dispositivo, Alice descubre a otros miembros del grupo. con estos compañeros, ella construirá el DRT con Bob como arranque.
Enviando un mensaje
Alice envía un mensaje a Bob
Alice crea un mensaje de confirmación. Ella construye una carga útil JSON que contiene el tipo MIME y el cuerpo del mensaje. Por ejemplo:
{
"type": "text/plain",
"body": "hello"
}
Alice asegúrese de que las credenciales de su dispositivo estén presentes. Si el certificado de dispositivo de Aliceo su CRL asociada aún no están almacenados en el repositorio, los agrega para que otros participantes puedan verificar la confirmación.
Alice se compromete con el repositorio (Debido a que Jami se basa principalmente en los metadatos del mensaje de confirmación en lugar del contenido del archivo, los conflictos de fusión son raros; los únicos conflictos potenciales involucrarían CRL o certificados, que están versionados en una ubicación dedicada).
Alice anuncia la confirmación a través del DRT con un mensaje de servicio y hace ping al DHT para dispositivos móviles (deben recibir una notificación push).
Nota
Para notificar a otros dispositivos, el remitente transmite un mensaje SIP con “ type: application/im-gitmessage-id`. La carga útil JSON incluye el ID de dispositivo (el del remitente), el ID de conversación y la referencia (hash) de la nueva confirmación.
Recibir un mensaje
Bob recibe un mensaje de Alice
Bob realiza una extracción de Git en el repositorio de Alice.
Todas las confirmaciones entrantes DEBEN verificarse mediante un gancho.
Si todas las confirmaciones son válidas, las confirmaciones se almacenan y muestran. Bob luego anuncia el mensaje a través del DRT para otros dispositivos.
Si alguna confirmación no es válida, se cancela la extracción. Alice debe restaurar su repositorio a un estado correcto antes de volver a intentarlo.
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.
Para cada confirmación entrante, asegúrese de que el dispositivo emisor esté actualmente autorizado y de que el certificado del emisor exista en /miembros o /dministradores, y el certificado del dispositivo en /dispositivos.
Luego maneje uno de los tres casos, según el recuento principal de la confirmación:
Fusionar confirmación (2 padres). No se requiere validación adicional, las fusiones siempre se aceptan.
Confirmación inicial (0 padres). Valide que esta sea la primera instantánea del repositorio:
Agregado el certificado de administrador.
Añadido el certificado del dispositivo.
CRL (Listas de Revocación de certificados) añadidas.
No hay otros archivos presentes.
Confirmación ordinaria (1 padre). El mensaje de confirmación debe ser JSON con un campo “tipo” de nivel superior. Maneje cada “tipo” de la siguiente manera:
Si es “texto” (o cualquier tipo MIME que no modifique archivos)
La firma es válida contra el certificado del autor en el repositorio.
No se agregan ni eliminan archivos inesperados.
Si “vota`
voteType
es uno de los valores admitidos (e.g. «ban», «unban»).El voto coincide con el usuario firmante.
El firmante es un administrador, su dispositivo está presente y no están prohibidos ellos mismos.
No se agregan ni eliminan archivos inesperados.
Sí
member
Sí
adds
Debidamente firmado por el invitado.
El URI del nuevo miembro aparece debajo
/invited
.No se agregan ni eliminan archivos inesperados.
If ONE_TO_ONE, asegúrese de tener exactamente un administrador y un miembro.
If ADMIN_INVITES_ONLY, quien invita debe ser un administrador.
Sí
joins
Debidamente firmado por el dispositivo de unión.
Certificado de dispositivo agregado en
/devices
.Invitación correspondiente eliminada de
/invited
y certificado agregado a/members
.No se agregan ni eliminan archivos inesperados.
Sí
banned
Vote es válido según las reglas de `vote” anteriores.
La expulsión la emite un administrador.
El certificado del objetivo se movió a /banned.
Solo se eliminan los archivos relacionados con la expulsión.
No se agregan ni eliminan archivos inesperados.
Fallback. Si el tipo o estructura de la confirmación no se reconoce, rechácela y notifique al igual (o usuario) que puede estar ejecutando una versión desactualizada o intentando cambios no autorizados.
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 están en un enjambre. Alice emite una prohibición contra Denys.
En un sistema totalmente peer‑to‑peer sin autoridad central, esta simple acción expone tres desafíos centrales:
Untrusted Timestamps: No se puede confiar en las marcas de tiempo de confirmación para ordenar los eventos de prohibición, ya que cualquier dispositivo puede falsificar o reproducir las confirmaciones con fechas arbitrarias.
Prohibiciones contradictorias: En los casos en que existen varios dispositivos de administración, las particiones de red pueden dar lugar a decisiones de prohibición contradictorias. Por ejemplo, si Alice puede comunicarse con Bob pero no con Dennis y Carla, mientras que Carla puede comunicarse con Denys, pueden ocurrir prohibiciones contradictorias. Si Denys prohíbe a Alice mientras Alice prohíbe a Denys, el estado del grupo no queda claro cuando todos los miembros finalmente se vuelven a conectar y fusionan sus historiales de conversación.
Dispositivos comprometidos o caducados: Los dispositivos pueden verse comprometidos, robados o que sus certificados caduquen. El sistema debe permitir prohibir dichos dispositivos y asegurarse de que no puedan manipular su certificado o confirmar marcas de tiempo para enviar mensajes no autorizados o falsificar su estado de vencimiento.
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.
Este sistema de votación necesita una acción humana para prohibir a alguien o debe basarse en la información de las CRL del repositorio (porque no podemos confiar en las CRL externas).
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)
Procesos para chats 1:1
El objetivo aquí es mantener la API anterior (addContact/removeContact, sendTrustRequest/acceptTrustRequest/discardTrustRequest) para crear un chat con un par y su contacto. Esto todavía implica algunos cambios que no se pueden ignorar:
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í.
En este caso, se generan dos conversaciones. No queremos eliminar mensajes de los usuarios ni elegir una conversación aquí. Entonces, a veces se mostrarán dos conversaciones entre los mismos miembros. Generará algunos errores durante el tiempo de transición (como no queremos romper la API, la conversación inferida será una de las dos conversaciones mostradas, pero por ahora está «bien», se solucionará cuando los clientes manejen completamente conversationID para todas las API (llamadas, transferencia de archivos, 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: (opcional) la foto de perfil
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: la foto de perfil
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
Esta nueva revisión del sistema para compartir archivos: todo el historial no se mantiene sincronizado, por lo que cualquier dispositivo en la conversación puede acceder instantáneamente a archivos pasados. En lugar de obligar al remitente a enviar archivos directamente, un enfoque que era frágil ante las caídas de conexión y que a menudo requería reintentos manuales, los dispositivos simplemente descargan archivos cuando los necesitan. Además, una vez que un dispositivo descarga un archivo, puede actuar como host para otros, asegurando así que los archivos permanezcan disponibles incluso si el remitente original se desconecta.
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.
Llamada en 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:
En los metadatos del enjambre. Dónde se almacena como tittle/desc/avatar (foto de perfil) de la sala
O el primer llamador.
Al iniciar una llamada, el host agregará una nueva confirmación al repositorio, con el URI al que unirse (accountUri/DeviceID/ConversationID/confId). Esto será válido hasta el final de la llamada (anunciado por un compromiso con la duración a mostrar)
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.
Qué sigue para las transferencias de archivos
Actualmente, el algoritmo de transferencia de archivos se basa en una conexión TURN (ver Transferencia de archivos). En el caso de un grupo grande, esto será malo. Primero necesitamos una conexión P2P para la transferencia de archivos. Implemente el RFC para la transferencia P2P.
Otro problema: actualmente no hay implementación de soporte TCP para ICE en PJSIP. Esto es obligatorio para este punto (en [PJSIP] (https://www.pjsip.org/) o casero)