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 existe uma autoridade central. Não se 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
*O Bob quer criar um novo enxame
Bob creates a local Git repository.
De seguida, cria uma confirmação inicial assinada com o seguinte:
A sua chave pública em
/admins
O seu certificado de dispositivo em ̀/devices`
O seu RLC em ̀/crls`
O hash do primeiro commit torna-se 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.
Receber 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.
Receber 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.
Validar um commit
Para evitar que os utilizadores enviem alguns commits indesejados (com conflitos, mensagens falsas, etc.), é assim que cada commit (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.
Banir 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 grupo distribuídos) não são tanto assim, mas estes são alguns exemplos:
O Signal, sem qualquer servidor central para a conversação em grupo (EDIÇÃO: alteraram recentemente esse ponto), não permite banir 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
Esta é a única parte que TEM de haver um consenso para evitar que a conversa se divida, por exemplo, se dois membros se expulsarem um do outro da conversa, o que verá o terceiro?
Isto é necessário para detetar dispositivos revogados ou simplesmente para evitar a presença de pessoas indesejadas numa sala pública. O processo é bastante semelhante entre um membro e um dispositivo:
A Alice remove o Bob
Importante
Alice MUST be an admin to vote.
Primeiro, ela vota para banir o Bob. Para isso, cria o ficheiro em /votes/ban/members/uri_bob/uri_alice (members pode ser substituído por devices para um dispositivo, ou invited para invites ou admins para admins) e faz o commit
De seguida, verifica se a votação está resolvida. Isto significa que >50% dos administradores concordam em banir o Bob (se ela estiver sozinha, é certo que é mais de 50%).
Se a votação for resolvida, os ficheiros em /votes/ban podem ser removidos, todos os ficheiros para o Bob em /members, /admins, /invited, /CRLs, /devices podem ser removidos (ou apenas em /devices se for um dispositivo que está banido) e o certificado do Bob pode ser colocado em /banned/members/bob_uri.crt (ou /banned/devices/uri.crt se um dispositivo estiver banido) e enviado para o repo
Depois, Alice informa os outros utilizadores (fora do 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
De seguida, verifica se a votação está resolvida. Isto significa que >50% dos administradores concordam em banir o Bob (se ela estiver sozinha, é certo que é mais de 50%).
Se a votação for resolvida, os ficheiros em /votes/unban podem ser removidos, todos os ficheiros para o Bob em /members, /admins, /invited, /CRLs, podem ser adicionados de novo (ou apenas em /devices se for um dispositivo que não tenha sido banido) e enviados para o repositório
Remover uma conversa
Guardar em convInfos removed=time::now() (tal como removeContact guarda nos contactos) que a conversa é removida e sincronizada com os dispositivos de outros utilizadores
Agora, se for recebido um novo commit para esta conversa, ele é ignorado
Agora, se o Jami iniciar e o repositório ainda estiver presente, a conversa não é 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 a certeza de que alguém está sincronizado, remover erased=time::now() e sincronizar com os dispositivos de outros utilizadores
Todos os dispositivos pertencentes ao utilizador podem agora apagar o repositório e os ficheiros relacionados
Como especificar um modo
Os modos não podem ser alterados ao longo do tempo. Ou é outra conversa. Assim, estes dados são armazenados na mensagem de commit inicial. A mensagem de commit será a seguinte:
{
"type": "initial",
"mode": 0,
}
Para já, “mode” aceita os 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 continua a ser o mesmo, uma conta pode adicionar um contacto através do addContact e, em seguida, enviar um TrustRequest através do DHT. Mas são necessárias duas alterações:
O TrustRequest incorpora um “conversationId” para informar o par de qual conversa clonar ao aceitar o pedido
Os TrustRequest são repetidos quando o contacto volta a estar online. Atualmente, não é esse o caso (uma vez que não queremos gerar um novo TrustRequest se o contacto descartar o primeiro). Assim, se uma conta receber um pedido de confiança, este será automaticamente ignorado se o pedido com uma conversa relacionada for recusado (uma vez que os convRequests são sincronizados)
Depois, quando um contacto aceita o pedido, é necessário um período de sincronização, porque o contacto precisa agora de clonar a conversa.
removeContact() irá remover o contacto e as conversas 1:1 relacionadas (com o mesmo processo que «Remover uma conversa»). A única nota aqui é que, se banirmos um contacto, não esperamos pela sincronização, apenas removemos todos os ficheiros relacionados.
Cenários complicados
Há alguns casos em que podem ser criadas duas conversas. Este é pelo menos dois desses cenários:
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 a sincronização for feita.
ConfigurationManager::getConversationMembers() will return a map of two URIs (the current account and the peer who sent the request).
Especificação dos pedidos de conversação
Os pedidos de conversação são representados por um Map<String, String> com as seguintes chaves:
id: the conversation ID
from: URI of the sender
received: carimbo de data e hora
title: nome para a conversa (opcional)
description: descrição (opcional)
avatar: (optional) the profile picture
Sincronização do perfil da conversa
Para ser identificável, uma conversa precisa geralmente de alguns metadados, como um título (por exemplo: Jami), uma descrição (por exemplo: algumas ligações, o que é o projeto, etc.) e uma imagem (o logótipo do projeto). Estes metadados são opcionais mas partilhados por todos os membros, pelo que têm de ser sincronizados e incorporados nos pedidos.
Armazenamento no repositório
O perfil da conversa é armazenado num ficheiro 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
Nos dados sincronizados, cada dispositivo envia aos outros dispositivos o estado das conversações. Neste estado, é enviada a última exibição. No entanto, como cada dispositivo pode ter o seu próprio estado para cada conversa, e provavelmente sem o mesmo último commit em algum momento, há vários cenários a ter em conta:
São suportados 5 cenários:
se o último ecrã enviado por outros dispositivos for o mesmo que o atual, não há nada a fazer.
se não for apresentada a última mensagem para o dispositivo atual, é utilizada a mensagem apresentada remotamente.
se a última exibição remota não estiver presente no repositório, isso significa que o commit será obtido mais tarde, portanto, armazene o resultado em cache
se o remoto já tiver sido obtido, verificamos se o último local apresentado está antes no histórico para o substituir
Finalmente, se for anunciada uma mensagem do mesmo autor, isso significa que é necessário atualizar a última mensagem apresentada.
Preferências
Cada conversa tem preferências anexas definidas pelo utilizador. Essas preferências são sincronizadas entre os dispositivos do utilizador. Pode ser a cor da conversa, se o utilizador pretende ignorar as notificações, o limite de tamanho da transferência de ficheiros, etc. Para já, as teclas reconhecidas são:
«color» - a cor da conversação (formato #RRGGBB)
«ignoreNotifications» - para ignorar as notificações de novas mensagens nesta conversa
«symbol» - para definir um emoji predefinido.
Essas preferências são armazenadas num pacote MapStringString, armazenado em accountDir/conversation_data/conversationId/preferences
e apenas enviado entre dispositivos do mesmo utilizador através de SyncMsg.
A API para interagir com as preferências é:
// 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*/);
};
Gestão de conflitos de fusão
Como dois administradores podem alterar a descrição ao mesmo tempo, um conflito de fusão pode ocorrer em profile.vcf
. Neste caso, o commit com o hash mais alto (por exemplo, ffffff > 000000) será escolhido.
APIs
O utilizador tem 2 métodos para obter e definir metadados de conversação:
<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 chaves:
modo: APENAS-LEITURA
título
descrição
avatar: the profile picture
Reimportar uma conta (vincular / exportar)
O arquivo DEVE conter conversationId para poder recuperar as conversas em novos commits após uma reimportação (porque não há nenhum convite neste momento). Se um commit chegar para uma conversa não presente, há duas possibilidades:
O conversationId está lá, neste caso, o daemon é capaz de clonar novamente esta conversa
O conversationId está em falta, pelo que o daemon pede (através de uma mensagem
{{"application/invite", conversationId}}
) um novo convite que o utilizador tem de (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
Porquê esta escolha
Each conversation will be a Git repository. This choice is motivated by:
Precisamos de sincronizar e ordenar as mensagens. A Árvore de Merkle é a estrutura perfeita para fazer isso e pode ser linearizada através da fusão de ramos. Além disso, como é muito usada pelo Git, é fácil de sincronizar entre dispositivos.
Distribuído pela natureza. Muito utilizado. Muitos backends e plugáveis.
Pode verificar os commits através de hooks e criptografia utilizada em massa
Pode ser armazenado numa base de dados, se necessário
Os conflitos são evitados através da utilização de mensagens de commit e não de ficheiros.
O que temos de validar
Desempenho?
git.lock
pode ser baixoHooks em libgit2
Vários pulls ao mesmo tempo?
Limites
O histórico não pode ser eliminado. Para eliminar uma conversa, o dispositivo tem de sair da conversa e criar outra.
No entanto, as mensagens não permanentes (como as mensagens que só podem ser lidas durante alguns minutos) podem ser enviadas através de uma mensagem especial através do DRT (como as notificações de dactilografia ou de 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 ficheiros
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 commit 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}
where file_id=${commitid}_${value["tid"]}.${extension}
Em seguida, o recetor pode agora transferir os ficheiros contactando os dispositivos que alojam o ficheiro, abrindo um canal com name="data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId
e armazenar a informação de que o ficheiro está em espera em ${data_path}/conversation_data/${conversation_id}/waiting
O dispositivo que recebe a conexão aceita o canal verificando se o ficheiro pode ser enviado (se o sha3sum está correto e se o ficheiro existe). O recetor manterá o primeiro canal aberto, fechará os outros e escreverá num ficheiro (com o mesmo caminho que o remetente: ${data_path}/conversation_data/${conversation_id}/${file_id}
) todos os dados recebidos.
Quando a transferência termina ou o canal é fechado, o sha3sum é verificado para validar se o ficheiro está correto (caso contrário, é eliminado). Se for válido, o ficheiro será removido da lista de espera.
Em caso de falha, quando um dispositivo da conversa voltar a estar online, pediremos todos os ficheiros em 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 accountUri/deviceId descreve o hospedeiro.
O hospedeiro pode ser determinado de duas formas:
In the swarm metadatas. Where it’s stored like the title/desc/avatar (profile picture) of the room
Ou o autor da primeira chamada.
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
O carimbo de data e hora (timestamp) de um commit pode ser confiável porque é editável. Apenas o carimbo de data e hora do utilizador é de confiança.
TLS
As operações Git, mensagens de controlo, ficheiros e outras coisas utilizarão uma ligação p2p TLS v1.3 apenas com cifras que garantam o PFS. Assim, cada chave é renegociada para cada nova conexão.
DHT (UDP)
Utilizado para enviar mensagens para telemóveis (para acionar notificações push) e para iniciar ligações TCP.
Atividade da rede
Processo para convidar alguém
A Alice quer convidar o Bob:
A Alice adiciona o 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 estiver ligada, através do DHT b. Caso contrário, a Alice envia no canal SIP
Duas possibilidades para o Bob a. Recebe o convite, é emitido um sinal para o cliente b. Não está ligado, pelo que nunca receberá o pedido, porque a Alice não pode saber se o Bob apenas ignorou ou bloqueou a Alice. A única forma é gerar novamente um novo convite através de uma nova mensagem (cf. cenário seguinte)
Processo para enviar uma mensagem a alguém
A Alice quer enviar uma mensagem ao Bob:
A Alice adiciona uma mensagem no repositório, dando um ID
A Alice recebe uma mensagem recebida (dela própria) se for bem sucedida
Há duas possibilidades: Alice e Bob estão ligados ou não. Em ambos os casos, é criada uma mensagem: { “application/im-gitmessage-id” : “{”id“:”\(convId“, ‘commit’:”\)commitId», ‘deviceId’: “$alice_device_hash”}»}. a. Se não estiver ligado, através do DHT b. Caso contrário, a Alice envia no canal SIP
Quatro possibilidades para o Bob: a. O Bob não está ligado à Alice, por isso, se confia na Alice, pede uma nova ligação e vai para b. b. Se estiver ligado, vai buscar a Alice e anuncia novas mensagens c. O Bob não conhece essa conversa. Pede através do DHT para obter um convite primeiro para poder aceitar essa conversa ({“application/invite”, conversationId}) d. O Bob está desligado (sem rede, ou apenas fechado). Não receberá a nova mensagem, mas tentará sincronizar-se quando ocorrer a próxima ligaçã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 ficheiro
{
"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, se junta ou 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"
}
!! PROJETO ANTIGO !!
Nota
Following notes are not organized yet. Just some line of thoughts.
Melhorias na criptografia.
Para uma funcionalidade séria de conversação 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 pode ser desencriptada. Talvez precisemos de algo como Double ratchet.
Nota
A lib might exist to implement group conversations.
Necessita de suporte ECC no OpenDHT
Utilização
Adicionar papéis?
Há dois casos de utilização principais para as conversas em grupo:
Algo semelhante a um Mattermost numa empresa, com canais privados e algumas funções (administrador/espetador/bot/etc) ou para a educação (onde apenas alguns estão ativos).
Conversas horizontais como uma conversa entre amigos.
O Jami será para qual deles?
Ideia de implementação
Um certificado para um grupo que assina um utilizador com uma marca para uma função. A adição ou revogação também pode ser feita.
Participar numa conversa
Apenas através de um convite direto
Através de uma hiperligação / código QR / qualquer coisa
Através de um nome de sala? (um hash no DHT)
O que precisamos
Confidencialidade: os membros fora da conversa de grupo não devem poder ler as mensagens do grupo
Segredo de transmissão: se uma chave do grupo for comprometida, as mensagens anteriores devem permanecer confidenciais (tanto quanto possível)
Ordenação das mensagens: é necessário que as mensagens estejam na ordem correta
Sincronização: também é necessário ter a certeza de que todas as mensagens são recebidas o mais rapidamente possível.
Persistência: atualmente, uma mensagem no DHT vive apenas 10 minutos. Porque é o melhor tempo calculado para este tipo de DHT. Para persistir os dados, o nó deve recolocar o valor na DHT a cada 10 minutos. Outra forma de o fazer quando o nó está offline é deixar que os nós voltem a colocar os dados. Mas, se após 10 minutos, 8 nós ainda estiverem aqui, eles farão 64 requisições (e isso é exponencial). A forma atual de evitar o spam é o queried. Isso ainda fará 64 solicitações, mas limitará a redundância máxima a 8 nós.
Outros modos distribuídos
IPFS: é necessário fazer alguma investigação
BitMessage: é necessário fazer alguma investigação
Maidsafe: é necessário fazer alguma investigação
Com base no trabalho atual que temos
A conversação em grupo pode basear-se no mesmo trabalho que já temos para os vários dispositivos (mas aqui, com um certificado de grupo). Problemas a resolver:
Sincronização do histórico. Isto precisa de mover a base de dados do cliente para o daemon.
Se ninguém estiver ligado, a sincronização não pode ser efetuada e a pessoa nunca verá a conversa
Outro DHT dedicado
Como um DHT com um superutilizador. (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 ficheiros). 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)