Enxame
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.
Não há autoridade central, não pode confiar em nenhum 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.
A ideia principal é obter uma árvore de Merkle sincronizada com os 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).
Cenários
Criar um enxame
Bob quer criar um novo enxame
Bob creates a local Git repository.
Depois, ele cria um compromisso inicial assinado com o seguinte:
Sua chave pública em
/admins
O certificado de dispositivo em ̀ /devices`
O seu RLC em ̀ /crls`
O hash do primeiro commit se torna o ID da conversa
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.
Adicionar alguém
Bob adds Alice
Bob adds Alice to the repo:
Adiciona o URI convidado em
/invited
Adiciona o RLC a
/crls
Bob sends a request on the DHT.
Recebendo um convite
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.
Enviar uma mensagem
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.
Recebendo uma mensagem
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.
Validação de um compromisso
Para evitar que os usuários empurrem alguns compromissos indesejados (com conflitos, mensagens falsas, etc.), é assim que cada compromisso (do mais antigo ao mais recente) DEVE ser validado antes de fundir um ramo remoto:
Nota
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.
If a fetch is too big, it’s not merged.
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.
Proibir um dispositivo
Importante
Jami source code tends to use the terms (un)ban, while the user interface uses the terms (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.
Os sistemas semelhantes (com sistemas de grupos distribuídos) não são muito, mas estes são alguns exemplos:
O sinal, sem qualquer servidor central para o chat de grupo (EDIT: eles recentemente mudam esse ponto), não dá a capacidade de proibir alguém de um 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).
Remover um dispositivo de uma conversa
Essa é a única parte que DEVE ter um consenso para evitar a divisão da conversa, por exemplo, se dois membros expulsarem um ao outro da conversa, o que acontecerá com o terceiro?
Isso é necessário para detectar dispositivos revogados ou simplesmente para evitar a presença de pessoas indesejadas em uma sala pública. O processo é bastante semelhante entre um membro e um dispositivo:
Alice remove o Bob
Importante
Alice MUST be an admin to vote.
Primeiro, ela vota para proibir o Bob. Para isso, ela cria o arquivo em /votes/ban/membres/uri_bob/uri_alice (os membros podem ser substituídos por dispositivos para um dispositivo, ou convidados para convites ou administradores para administradores) e compromete-se a
Depois ela verifica se a votação está resolvida. Isto significa que > 50% dos administradores concordam em proibir Bob (se ela está sozinha, é certo que é mais de 50%).
Se a votação for resolvida, os arquivos para /votes/ban podem ser removidos, todos os arquivos para Bob em /membres, /admins, /invitados, /CRLs, /dispositivos podem ser removidos (ou apenas em /dispositivos se for um dispositivo que é proibido) e o certificado de Bob pode ser colocado em /banned/membres/bob_uri.crt (ou /banned/devices/uri.crt se um dispositivo é proibido) e comprometido com o repo
Depois, Alice informa outros usuários (fora de Bob)
Alice (admin) re-adds Bob (banned member)
If she votes for unbanning Bob. To do that, she creates the file in /votes/unban/members/uri_bob/uri_alice (members can be replaced by devices for a device, or invited for invites or admins for admins) and commits
Depois ela verifica se a votação está resolvida. Isto significa que > 50% dos administradores concordam em proibir Bob (se ela está sozinha, é certo que é mais de 50%).
Se a votação for resolvida, os arquivos em /votes/unban podem ser removidos, todos os arquivos para Bob em /membres, /admins, /invitados, /CRLs, podem ser adicionados novamente (ou apenas em /dispositivos se for um dispositivo que não é proibido) e comprometidos com o repo
Remover uma conversa
Salvar em convInfos remove=time::now() (como removeContact salva em contatos) que a conversa é removida e sincronizada com outros dispositivos do usuário
Se recebe um novo compromisso para esta conversa, é ignorado.
Se o Jami começar e o repo estiver presente, a conversa não será anunciada aos clientes.
Two cases: a. If no other member in the conversation we can immediately remove the repository b. If still other members, commit that we leave the conversation, and now wait that at least another device sync this message. This avoids the fact that other members will still detect the user as a valid member and still sends new message notifications.
Quando tivermos certeza de que alguém está sincronizado, remova erased=time::now() e sincronize com os dispositivos de outros usuários
Todos os dispositivos de propriedade do usuário podem agora apagar o repositório e os arquivos relacionados
Como especificar um modo
Os modos não podem ser alterados ao longo do tempo. Ou é outra conversa. Então, esses dados são armazenados na mensagem de compromisso inicial.
{
"type": "initial",
"mode": 0,
}
Por enquanto, “modo” aceita 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:
O processo ainda é o mesmo, uma conta pode adicionar um contato através do addContact, e depois enviar um TrustRequest através do DHT. Mas duas mudanças são necessárias:
O TrustRequest incorpora um “conversationId” para informar o colega qual conversa clonar ao aceitar o pedido
TrustRequest são retestados quando o contato volta online. Não é o caso hoje (como não queremos gerar um novo TrustRequest se o peer descartar o primeiro). Então, se uma conta recebe um pedido de confiança, ele será automaticamente ignorado se o pedido com uma conversa relacionada for rejeitado (como convRequests são sincronizados)
Então, quando um contato aceita o pedido, é necessário um período de sincronização, porque o contato agora precisa clonar a conversa.
removeContact() removerá o contato e as conversas relacionadas 1:1 (com o mesmo processo que “Remover uma conversa”). A única nota aqui é que se proibirmos um contato, não esperamos para a sincronização, apenas removemos todos os arquivos relacionados.
Cenários complicados
Há alguns casos em que podem ser criadas duas conversas.
Alice adds Bob.
Bob accepts.
Alice removes Bob.
Alice adds Bob.
ou
Alice adds Bob and Bob adds Alice at the same time, but both are not connected together.
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
After accepting a conversation’s request, there is a time the daemon needs to retrieve the distant repository. During this time, clients MUST show a syncing view to give informations to the user. While syncing:
ConfigurationManager::getConversations() will return the conversation’s id even while syncing.
ConfigurationManager::conversationInfos() retornará {{“syncing”: “true”}} se se a sincronização for feita.
ConfigurationManager::getConversationMembers() will return a map of two URIs (the current account and the peer who sent the request).
Conversas pedindo especificação
As solicitações de conversação são representadas por um Map<String, String> com as seguintes teclas:
id: the conversation ID
from: URI of the sender
Recebido: timestamp
Título: Nome (opcional) da conversa
Descrição: (opcional)
avatar: (optional) the profile picture
Sincronização do perfil da conversa
Para ser identificável, uma conversa geralmente precisa de alguns metadados, como um título (por exemplo, Jami), uma descrição (por exemplo, alguns links, o que é o projeto, etc.), e uma imagem (o logotipo do projeto).
Armazenamento no repositório
O perfil da conversa é armazenado em um arquivo vCard clássico na raiz (/profile.vcf
) como:
BEGIN:VCARD
VERSION:2.1
FN:TITLE
DESCRIPTION:DESC
END:VCARD
Sincronização
To update the vCard, a user with enough permissions (by default: =ADMIN) needs to edit /profile.vcf
and will commit the file with the mimetype application/update-profile
.
The new message is sent via the same mechanism and all peers will receive the MessageReceived signal from the daemon.
The branch is dropped if the commit contains other files or too big or if done by a non-authorized member (by default: <ADMIN).
Última exposição
No conjunto de dados sincronizados, cada dispositivo envia para outros dispositivos o estado das conversas. Neste estado, o último exibido é enviado. No entanto, como cada dispositivo pode ter seu próprio estado para cada conversa, e provavelmente sem o mesmo compromisso final em algum momento, existem vários cenários a serem tidos em conta:
Há suporte para 5 cenários:
Se o último exibido enviado por outros dispositivos for o mesmo que o atual, não há nada a fazer.
Se não houver a última visualização para o dispositivo atual, a mensagem exibida remotamente é utilizada.
Se o remoto último exibido não estiver presente no repo, significa que o commit será recuperado mais tarde, então cache o resultado
Se o controle remoto já foi recuperado, verificamos que o local último exibido é anterior no histórico para substituí-lo
Finalmente, se uma mensagem for anunciada pelo mesmo autor, significa que precisamos atualizar a última exibida.
Preferências
Cada conversa tem anexadas preferências definidas pelo usuário. Essas preferências são sincronizadas em todos os dispositivos do usuário. Esta pode ser a cor da conversa, se o usuário quiser ignorar notificações, limite de tamanho de transferência de arquivo, etc. Por enquanto, as chaves reconhecidas são:
“color” - a cor da conversação (formato #RRGGBB)
“ignoreNotifications” - para ignorar as notificações de novas mensagens nessa conversa
“symbol” - para definir um emoji padrão.
Essas preferências são armazenadas em um pacote MapStringString, armazenadas em accountDir/conversation_data/conversationId/preferências
e apenas enviadas através de dispositivos do mesmo usuário através de SyncMsg.
As API para interagir com as preferências são:
// 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*/);
};
Gerenciamento de conflitos de fusão
Como dois administradores podem alterar a descrição ao mesmo tempo, pode ocorrer um conflito de fusão em profile.vcf
. Neste caso, será escolhido o commit com o hash mais alto (por exemplo, ffffff > 000000).
APIs
O usuário tem 2 métodos para obter e definir os metadados da conversa:
<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>
onde infos
é um map<str, str>
com as seguintes teclas:
mode: SOMENTE-LEITURA
title
description
avatar: the profile picture
Reimportar uma conta (link / exportação)
O arquivo DEVE conter conversationId para que seja possível recuperar conversas em novos commits após uma reimportação (porque não há convite nesse momento). Se houver um commit para uma conversa não presente, há duas possibilidades:
A conversa está lá, neste caso, o demônio é capaz de re-clonar esta conversa
O conversationId está faltando, então o daemon solicita (por meio de uma mensagem
{{“application/invite”, conversationId}}
) um novo convite que o usuário precisa (re)aceitar
Importante
A conversation can only be retrieved if a contact or another device is there, else it will be lost. There is no magic.
Protocolos utilizados
Git
Por que essa escolha
Each conversation will be a Git repository. This choice is motivated by:
A árvore Merkle é a estrutura perfeita para isso e pode ser linearizada pela fusão de ramos. Além disso, porque é muito usado pelo Git, é fácil sincronizar entre dispositivos.
Distribuído pela natureza, usado em massa, com muitos backends e plug-in.
Pode verificar compromissos através de ganchos e criptomoedas em massa
Pode ser armazenado em uma base de dados, se necessário
Os conflitos são evitados usando mensagens de compromisso, não arquivos.
O que temos de validar
Performance?
git.lock
pode ser baixoGanchos em libgit2
Vários pulls ao mesmo tempo?
Limite
O histórico não pode ser excluído. Para excluir uma conversa, o dispositivo tem que deixar a conversa e criar outra.
No entanto, as mensagens não permanentes (como as mensagens que só podem ser lidas por alguns minutos) podem ser enviadas através de uma mensagem especial através do DRT (como as notificações de digitação ou leitura).
Estrutura
/
- 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
Transferência de arquivos
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
O remetente adiciona um novo compromisso na conversa com o seguinte 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";
e cria um link em ${data_path}/conversation_data/${conversation_id}/${file_id}
onde file_id=${commitid}_${value[“tid”]}.${extension}
Em seguida, o receptor pode baixar os arquivos entrando em contato com os dispositivos que hospedam o arquivo, abrindo um canal com name=“data-transfer://” + conversationId + “/” + currentDeviceId() + “/” + fileId
e armazenar a informação de que o arquivo está aguardando em ${data_path}/conversation_data/${conversation_id}/waiting
O dispositivo que receber a conexão aceitará o canal verificando se o arquivo pode ser enviado (se o sha3sum estiver correto e se o arquivo existir). O receptor manterá o primeiro canal aberto, fechará os outros e gravará em um arquivo (com o mesmo caminho do remetente: ${data_path}/conversation_data/${conversation_id}/${file_id}
) todos os dados recebidos.
Quando a transferência é concluída ou o canal fechado, o sha3sum é verificado para validar que o arquivo é correto (ou é excluído).
Em caso de falha, quando um dispositivo da conversa voltar a ligar, pediremos todos os arquivos de espera da mesma forma.
Call in Swarm
Ideia
A swarm conversation can have multiple rendez-vous. A rendez-vous is defined by the following URI:
“accountUri/deviceId/conversationId/confId” onde o accountUri/deviceId descreve o host.
O hospedeiro pode ser determinado por duas formas:
In the swarm metadatas. Where it’s stored like the title/desc/avatar (profile picture) of the room
Ou o primeiro a ligar.
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)
Assim, cada parte receberá a informação de que uma chamada foi iniciada e poderá juntar-se a ela chamando-a.
Ataques?
Avoid Git bombs
Notas
A marca de tempo de um compromisso pode ser confiável porque é editável. Somente a marca de tempo do usuário pode ser confiável.
TLS
As operações Git, mensagens de controle, arquivos e outras coisas usarão um link P2p TLS v1.3 com apenas cifras que garantem PFS.
DHT (UDP)
Usado para enviar mensagens para celulares (para desencadear notificações push) e para iniciar conexões TCP.
Atividade de rede
Processo para convidar alguém
A Alice quer convidar o Bob:
Alice adiciona Bob a uma conversa
A Alice gera um convite: { “application/invite+json” : { “conversationId”: “$id”, “members”: [{…}] }}
Duas possibilidades para enviar a mensagem a. Se não está conectada, através do DHT b. De outra forma, Alice envia no canal SIP
Duas possibilidades para Bob a. Recebe o convite, um sinal é emitido para o cliente b. Não está conectado, portanto, nunca receberá a solicitação, pois Alice não pode saber se Bob simplesmente ignorou ou bloqueou Alice. A única maneira é gerar novamente um novo convite por meio de uma nova mensagem (veja o próximo cenário)
Processos para enviar uma mensagem para alguém
A Alice quer enviar uma mensagem ao Bob:
Alice adiciona uma mensagem no repo, dando uma identificação
Alice recebe uma mensagem recebida (de si mesma) se tiver sucesso
Duas possibilidades: Alice e Bob estão conectados ou não. Em ambos os casos, uma mensagem é criada: { “application/im-gitmessage-id” : “{”id“:”\(convId“, ‘commit’:”\)commitId”, ‘deviceId’: “$alice_device_hash”}”}. a. Se não estiver conectada, por meio do DHT b. Caso contrário, Alice envia no canal SIP
Quatro possibilidades para Bob: a. Bob não está conectado a Alice, portanto, se ele confia em Alice, solicite uma nova conexão e vá para b. b. Se estiver conectado, busque de Alice e anuncie novas mensagens c. Bob não conhece essa conversa. Peça por meio do DHT para obter um convite primeiro para poder aceitar essa conversa ({“application/invite”, conversationId}) d. Bob está desconectado (sem rede ou apenas fechado). Ele não receberá a nova mensagem, mas tentará sincronizar quando ocorrer a próxima conexão
Implementação
Mensagens suportadas
Mensagem inicial
{
"type": "initial",
"mode": 0,
"invited": "URI"
}
Representa o primeiro commit de um repositório e contém o modo:
enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC }
e invited
se o modo = 0.
Mensagem de texto
{
"type": "text/plain",
"body": "content",
"react-to": "id (optional)"
}
Ou para uma edição:
{
"type": "application/edited-message",
"body": "content",
"edit": "id of the edited commit"
}
Chamadas
Mostrar o fim de uma chamada (duração em milissegundos):
{
"type": "application/call-history+json",
"to": "URI",
"duration": "3000"
}
Ou para hospedar uma chamada num grupo (quando começa)
{
"type": "application/call-history+json",
"uri": "host URI",
"device": "device of the host",
"confId": "hosted confId"
}
Um segundo commit com o mesmo JSON + duration
é adicionado no final da chamada quando hospedado.
Adicionar um arquivo
{
"type": "application/data-transfer+json",
"tid": "unique identifier of the file",
"displayName": "File name",
"totalSize": "3000",
"sha3sum": "a sha3 sum"
}
totalSize
em bits,
Atualização do perfil
{
"type": "application/update-profile",
}
Evento de membro
{
"type": "member",
"uri": "member URI",
"action": "add/join/remove/ban"
}
Quando um membro é convidado, entra, sai ou é expulso de uma conversa
Evento de votação
Gerado pelos administradores para adicionar um voto para expulsar ou não expulsar alguém.
{
"type": "vote",
"uri": "member URI",
"action": "ban/unban"
}
!! RASCUNHO ANTIGO !!
Nota
Following notes are not organized yet. Just some line of thoughts.
Melhorias criptográficas.
Para um recurso sério de bate-papo em grupo, também precisamos de criptografia séria. Com o design atual, se um certificado for roubado como os valores DHT anteriores de uma conversa, a conversa poderá ser descriptografada. Talvez precisemos adotar algo como Double ratchet.
Nota
A lib might exist to implement group conversations.
Necessidade de apoio ECC em OpenDHT
Utilização
Adicionar papéis?
Há dois casos de uso principais para conversas em grupo:
Algo como um Mattermost numa empresa, com canais privados, e alguns papéis (admin/espectador/bot/etc) ou para educação (onde apenas alguns são ativos).
Conversas horizontais como uma conversa entre amigos.
O Jami será para qual deles?
Ideia de execução
Certificado para um grupo que assinar usuário com uma bandeira para um papel.
Junte-se a uma conversa
Só por convite direto
Por meio de um link / código QR / qualquer coisa
Por meio de um nome de sala? (um hash no DHT)
O que precisamos
Confidencialidade: os membros fora do grupo de conversação não devem poder ler as mensagens no grupo
Se qualquer chave do grupo for comprometida, as mensagens anteriores devem permanecer confidenciais (o máximo possível)
Ordenamento de mensagens: é necessário ter mensagens na ordem certa
Sincronização: Também é necessário ter certeza de que todas as mensagens estão disponíveis o mais rapidamente possível.
Persistência: Na verdade, uma mensagem no DHT dura apenas 10 minutos. Porque é o melhor momento calculado para este tipo de DHT. Para persistir dados, o nó deve reinsertar o valor no DHT a cada 10 minutos. Outra maneira de fazer quando o nó está offline é deixar os nós reinsertar os dados. Mas, se depois de 10 minutos, 8 nós ainda estiverem aqui, eles farão 64 solicitações (e é exponencial). A maneira atual de evitar spam para isso é consultada. Isso ainda fará 64 solicitações, mas limitará a redundância máxima para 8 nós.
Outros modos distribuídos
IPFS: precisa de alguma investigação
BitMessage: precisa de alguma investigação
Maidsafe: precisa de alguma investigação
Com base no trabalho atual que temos
O chat em grupo pode ser baseado no mesmo trabalho que já temos para vários dispositivos (mas aqui, com um certificado em grupo).
Simcronização de História. Isto precisa de mover o banco de dados do cliente para o demônio.
Se ninguém estiver conectado, a sincronização não pode ser feita, e a pessoa nunca verá a conversa
Outro DHT dedicado
Como um DHT com um superusuário. (Não estou convencido)
What’s next for file transfers
Currently, the file transfer algorithm is based on a TURN connection (See Transferência de arquivos). 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)