Enxame
Importante
Jami source code tends to use the terms (un)ban, while the user interface uses the terms (un)block.
Sinopse
O objetivo deste documento é descrever como as conversações de grupo (também conhecidas como conversações enxame) serão implementados no Jami.
Um enxame é um grupo capaz de discutir sem qualquer autoridade central de uma forma resiliente. De facto, se duas pessoas não tiverem qualquer conetividade com o resto do grupo (por exemplo, uma falha na Internet), mas puderem contactar-se mutuamente (numa LAN, por exemplo, ou numa sub-rede), poderão enviar mensagens umas às outras e, em seguida, poderão sincronizar-se com o resto do grupo quando for possível.
Assim, o enxame é definido por:
Capacidade de se dividir e fundir após a conectividade.
Sincronização do histórico. Qualquer pessoa deve poder enviar uma mensagem a todo o grupo.
Não existe uma autoridade central. Não se pode confiar em nenhum servidor.
Não repúdio. Os dispositivos devem ser capazes de verificar a validade das mensagens antigas e de reproduzir todo o historial.
PFS no transporte. O armazenamento é gerido pelo dispositivo.
A ideia principal é obter uma árvore de Merkle sincronizada com os participantes.
Identificámos quatro modos de conversação em enxame que pretendemos implementar:
UM_PARA_UM, basicamente o caso que temos hoje quando se fala com um amigo
APENAS_CONVITES_DE_ADMINISTRADORES geralmente uma aula em que o professor pode convidar pessoas, mas não os alunos
APENAS_CONVITES um grupo privado de amigos
PÚBLICO basicamente um fórum aberto
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 anuncia aos seus outros dispositivos que ele cria uma nova conversa. Isso é feito através de um convite para se juntar ao enxame enviado através do DHT para outros dispositivos vinculados a essa conta.
Adicionar alguém
Alice acrescenta Bob
Alice acrescenta Bob ao repo:
Adiciona o URI convidado em
/invited
Adiciona o RLC a
/crls
Alice envia um pedido para a DHT
Receber um convite
Alice recebe o convite para se juntar ao enxame criado anteriormente
Ela aceita o convite (se recusar, não faz nada, o convite mantém-se e a Alice nunca recebe qualquer mensagem)
É estabelecida uma ligação ponto a ponto entre Alice e Bob.
Alice pull the Git repo of Bob. WARNING this means that messages need a connection, not from the DHT like today.
Alice valida os commits de Bob
Para validar que Alice é um membro, ela remove o convite do diretório
/invited
e, em seguida, adiciona seu certificado no diretório/members
Depois de todos os commits validados e no seu dispositivo, outros membros do grupo são descobertos pela Alice. Com estes pares, ela constrói o DRT (explicado abaixo) com o Bob como bootstrap.
Enviar uma mensagem
Alice envia uma mensagem
O envio de uma mensagem é bastante simples. Alice escreve uma mensagem de confirmação no seguinte formato:
{
"type": "text/plain",
"body": "coucou"
}
and adds her device and CRL to the repository if missing (others must be able to verify the commit). Merge conflicts are avoided because we are mostly based on commit messages, not files (unless CRLS + certificates but they are located). Then she announces the new commit via the DRT with a service message (explained later) and pings the DHT for mobile devices (they must receive a push notification).
Para fazer ping noutros dispositivos, o remetente envia aos outros membros uma mensagem SIP com mimetype = “application/im-gitmessage-id” contendo um JSON com o “deviceId” que envia a mensagem, o “id” da conversa relacionada e o “commit”
Receber uma mensagem
O Bob recebe a mensagem da Alice
Bob do a Git pull on Alice
Os commits DEVEM ser verificados através de um hook
Se todos os commits forem válidos, os commits são armazenados e exibidos. Em seguida, Bob anuncia a mensagem através do DRT para outros dispositivos.
Se todos os commits não forem válidos, o pull é cancelado. Alice deve restabelecer o seu estado para um estado correto.
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.
Para cada commit, verifique se o dispositivo que tenta enviar o commit está autorizado neste momento e se os certificados estão presentes (em /devices para o dispositivo, e em /members ou /admins para o emissor).
3 casos. O commit tem 2 pais, por isso é uma fusão, não há mais nada a validar aqui
O commit tem 0 pais, é o commit inicial:
Verifique se o certificado de administrador foi adicionado
Verifique se o certificado do dispositivo foi adicionado
Verifique os CRLs adicionados
Verifique se nenhum outro ficheiro foi adicionado
O commit tem 1 pai, a mensagem do commit é um JSON com um tipo:
Se texto (ou outro tipo de mime que não altera ficheiros)
Verifique a assinatura do certificado no repositório
Verifique se nenhum ficheiro estranho é adicionado fora do certificado de dispositivo nem removido
Se votar
Verifique se o voteType é suportado (ban, unban)
Verifique se a votação é para o utilizador que assina o commit
Check that vote is from an admin and device present and not banned
Verifique se nenhum ficheiro estranho foi adicionado ou removido
Se membro
Se adiciona
Verificar se o commit está corretamente assinado
Verifique se o certificado foi adicionado em /invited
Verifique se nenhum ficheiro estranho foi adicionado ou removido
Se ONE_TO_ONE, verifique se temos apenas um administrador, um membro
Se ADMIN_INVITES_ONLY, verificar se o convite é de um administrador
Se se juntar
Verificar se o commit está corretamente assinado
Verifique se o dispositivo foi adicionado
Verifique se o convite foi transferido para os membros
Verifique se nenhum ficheiro estranho foi adicionado ou removido
Se banido
Verifique se a votação é válida
Verifique se o utilizador foi banido por um administrador
Verifique se o certificado do membro ou do dispositivo foi movido para banned/
Verifique se são removidos apenas os ficheiros relacionados com a votação
Verifique se nenhum ficheiro estranho foi adicionado ou removido
senão falha. Notifique o utilizador de que poderá estar a utilizar uma versão antiga ou que um colega tentou submeter commits indesejados
Banir um dispositivo
A Alice, o Bob, a Carla e o Denys estão num enxame. A Alice bane o Denys
Este é um dos cenários mais difíceis no nosso contexto. Sem uma autoridade central, não podemos confiar:
Registos temporais dos commits gerados
Conflitos com dispositivos banidos. Se estiverem presentes vários dispositivos de administração e se a Alice puder falar com o Bob mas não com o Denys e a Carla; se a Carla puder falar com o Denys; o Denys banir a Alice, a Alice proibir o Denys, qual será o estado quando os 4 membros fundirem as conversas.
Um dispositivo pode ser comprometido, roubado ou o seu certificado pode expirar. Devemos poder banir um dispositivo e evitar que ele minta sobre a sua expiração ou envie mensagens no passado (alterando o seu certificado ou o carimbo de data/hora do seu commit).
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.
Este sistema de votação necessita de uma ação humana para banir alguém ou deve basear-se na informação dos LCR do repositório (porque não podemos confiar em LCR externos)
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 swarms
O objetivo aqui é manter a antiga API (addContact/removeContact, sendTrustRequest/acceptTrustRequest/discardTrustRequest) para gerar swarm com um par e o seu contacto. Isto ainda implica algumas mudanças que não podemos ignorar:
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.
Neste caso, são geradas duas conversas. Não queremos remover mensagens de utilizadores ou escolher uma conversa aqui. Assim, por vezes, serão mostrados dois enxames 1:1 entre os mesmos membros. Isto irá gerar alguns bugs durante o tempo de transição (como não queremos quebrar a API, a conversa inferida será uma das duas conversas mostradas, mas por agora está «ok-ish», será corrigido quando os clientes tratarem completamente o conversationId para todas as APIs (chamadas, transferência de ficheiros, 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: (opcional)
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
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
O Swarm muda radicalmente a transferência de ficheiros. Agora, todo o histórico é sincronizado, permitindo que todos os dispositivos na conversa recuperem facilmente ficheiros antigos. Estas alterações permitem-nos passar de uma lógica em que o remetente empurrava o ficheiro para outros dispositivos, tentando conectar-se aos seus dispositivos (o que era mau porque não era realmente resistente a mudanças/falhas de conexão e necessitava de uma nova tentativa manual) para uma lógica em que o remetente permite que outros dispositivos façam o descarregamento. Além disso, qualquer dispositivo que tenha o ficheiro pode ser o hospedeiro de outros dispositivos, permitindo a obtenção de ficheiros mesmo que o remetente não esteja presente.
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.
Chamar o enxame
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:
Nos metadados do enxame. Onde é armazenado como o title/desc/avatar da sala
Ou o autor da primeira chamada.
Ao iniciar uma chamada, o hospedeiro adiciona um novo commit ao enxame, com o URI para aderir (accountUri/deviceId/conversationId/confId). Este será válido até ao final da chamada (anunciado por um commit com a duração a mostrar)
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)
Transferência de ficheiros
Atualmente, o algoritmo de transferência de ficheiros é baseado numa conexão TURN (ver Transferência de ficheiros). No caso de um grupo grande, isto será mau. Precisamos primeiro de uma implementação p2p para a transferência de ficheiros. Implementar o RFC para transferência p2p.
Outro problema: atualmente não existe uma implementação de suporte TCP para ICE no PJSIP. Isto é obrigatório para este ponto (no pjsip ou caseiro)
Recursos
https://eprint.iacr.org/2017/666.pdf
Sincronização distribuída robusta de sistemas lineares em rede com informação intermitente (Sean Phillips e Ricardo G. Sanfelice)