Enxame

Sinóspis

O objetivo deste documento é descrever como os bate-papos em grupo (também conhecidos como bate-papo de enxame) serão implementados no Jami.

Um enxame é um grupo capaz de discutir sem nenhuma autoridade central de forma resiliente. De fato, se duas pessoas não tiverem nenhuma conectividade com o restante do grupo (ou seja, falta de conexão com a Internet), mas puderem entrar em contato umas com as outras (em uma LAN, por exemplo, ou em uma sub-rede), elas poderão enviar mensagens umas para as outras e, em seguida, poderão sincronizar-se com o restante do grupo quando for possível.

Então, o “espartilho” é definido por:

  1. Capacidade de se dividir e fundir após a conectividade.

  2. Simcronização da história. Qualquer pessoa deve ser capaz de enviar uma mensagem para todo o grupo.

  3. Não há autoridade central, não pode confiar em nenhum servidor.

  4. Não-repudiamento. Os dispositivos devem poder verificar a validade das mensagens antigas e reproduzir todo o histórico.

  5. O armazenamento é gerenciado pelo dispositivo.

A ideia principal é obter uma árvore de Merkle sincronizada com os participantes.

Identificámos quatro modos para o chat enxame que queremos implementar:

  • UM_PARA_UM, basicamente o caso que temos hoje, quando você conversa com um amigo

  • SOMENTE_CONVITES_DE_ADMINISTRADORES geralmente uma aula em que o professor pode convidar pessoas, mas não os alunos

  • SOMENTE_CONVIDADOS um grupo privado de amigos

  • PÚBLICO basicamente um fórum aberto

Cenários

Criar um enxame

Bob quer criar um novo enxame

  1. O Bob cria um repositório local de dados.

  2. 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`

  3. O hash do primeiro commit se torna o ID da conversa

  4. 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

  1. Alice acrescenta Bob ao repo:

    • Adiciona o URI convidado em /invited

    • Adiciona o RLC a /crls

  2. Alice envia um pedido sobre o DHT

Recebendo um convite

Alice recebe o convite para se juntar ao enxame criado anteriormente

  1. Ela aceita o convite (se recusar, não fazer nada, ele ficará no convidado e Alice nunca receberá nenhuma mensagem)

  2. Uma conexão entre Alice e Bob está feita.

  3. Alice faz pull do repositório git de Bob. AVISO: isso significa que as mensagens precisam de uma conexão, não do DHT como hoje

  4. Alice valida compromissos de Bob

  5. Para validar que Alice é membro, ela remove o convite do diretório /invited, e depois adiciona seu certificado no diretório /members

  6. Uma vez que todos os compromissos são validados e em seu dispositivo, outros membros do grupo são descobertos por Alice. Com esses pares, ela irá construir o DRT (explicado abaixo) com Bob como um bootstrap.

Enviar uma mensagem

Alice envia uma mensagem

Enviar uma mensagem é bastante simples. Alice escreve uma mensagem de compromisso no seguinte formato:

{
    "type": "text/plain",
    "body": "coucou"
}

e adiciona seu dispositivo e sua CRL ao repositório, caso estejam faltando (outras pessoas devem poder verificar a confirmação). Os conflitos de mesclagem são evitados porque nos baseamos principalmente em mensagens de confirmação, não em arquivos (a menos que o CRLS + certificados estejam localizados). Em seguida, ela anuncia a nova confirmação por meio do DRT com uma mensagem de serviço (explicada posteriormente) e faz ping no DHT para dispositivos móveis (eles devem receber uma notificação push).

Para pingar outros dispositivos, o remetente envia a 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 “comit”

Recebendo uma mensagem

O Bob recebe a mensagem da Alice.

  1. Bob faz um tiro na Alice

  2. Os compromissos devem ser verificados através de um gancho

  3. Se todos os comit são válidos, os comit são armazenados e exibidos.

  4. Se todos os commits não forem válidos, o pull é cancelado. Alice deve restabelecer o seu estado para um estado correto.

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: se a validação falhar, a pesquisa é ignorada e não fundimos o ramo (e removemos os dados), e o utilizador deve ser notificado Nota 2: se uma pesquisa for demasiado grande, não é fundida

  • Para cada compromisso, verifique se o dispositivo que tenta enviar o compromisso está autorizado neste momento e se os certificados estão presentes (em /dispositivos para o dispositivo e em /membros ou /administradores para o emissor).

  • 3 casos. O commit tem 2 pais, portanto, é uma mesclagem, nada mais a ser validado aqui

  • O compromisso tem 0 pais, é o compromisso inicial:

    • Verifique se o certificado de administração foi adicionado

    • Verifique se o certificado do dispositivo foi adicionado

    • Verificação de RLC adicionados

    • Verifique se nenhum outro arquivo é adicionado

  • O commit tem 1 pai, o message de commit é um JSON com um tipo:

    • Se texto (ou outro tipo de mime que não muda arquivos)

      • Verificação da assinatura do certificado no repo

      • Verifique se nenhum arquivo estranho é adicionado fora do certificado de dispositivo nem removido

    • Se votar

      • Verifique se o voteType é suportado (ban, unban)

      • Verifique se o voto é para o usuário que assinou o compromisso

      • Verifique se o voto é de um administrador e dispositivo presente e não proibido

      • Verifique se nenhum arquivo estranho é adicionado ou removido

    • Se membro

      • Se adiciona

        • Verifique se o compromisso foi corretamente assinado

        • Verifique se o certificado é adicionado em / convidado

        • Verifique se nenhum arquivo estranho é adicionado ou removido

        • Se ONE_TO_ONE, verifique se temos apenas um administrador, um membro

        • Se Admin_INVITES_ONLY, verifique se o convite é de um administrador

      • Se se juntar

        • Verifique se o compromisso foi corretamente assinado

        • Verifique se o dispositivo está adicionado

        • Verifique se o convite é transferido para os membros

        • Verifique se nenhum arquivo estranho é adicionado ou removido

      • Se for proibido

        • Verifique se a votação é válida

        • Verifique se o usuário está proibido através de um administrador

        • Verifique se o certificado de membro ou dispositivo foi transferido para proibido/

        • Verifique se só os arquivos relacionados à votação são removidos

        • Verifique se nenhum arquivo estranho é adicionado ou removido

    • notificar o usuário que pode estar com uma versão antiga ou que o colega tentou enviar compromissos indesejados

Proibir um dispositivo

Alice, Bob, Carla, Denys estão num enxame.

Este é um dos cenários mais difíceis do nosso contexto.

  1. As marcas de tempo dos compromissos gerados

  2. Conflitos com dispositivos proibidos. Se vários dispositivos de administração estiverem presentes e se Alice puder falar com Bob, mas não com Denys e Carla; Carla pode falar com Denys; Denys proíbe Alice, Alice proíbe Denys, qual será o estado quando os 4 membros fundirão as conversas.

  3. O sistema de segurança deve ser utilizado para garantir a segurança dos dispositivos e evitar que eles mentam sobre a sua expiração ou enviem mensagens no passado (alteração do certificado ou do tempo de compromisso).

Os sistemas semelhantes (com sistemas de grupos distribuídos) não são muito, mas estes são alguns exemplos:

  • O mpOTR não define como banir alguém

  • 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.

Este sistema de votação precisa de uma ação humana para proibir alguém ou deve ser baseado nas informações dos RLCs do repositório (porque não podemos confiar nos RLCs externos)

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

Nota: Alice tem de ser administradora para votar

  • 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 (administrador) adiciona Bob (membro proibido)

  • Para isso, ela cria o arquivo em /votes/unban/members/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 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

  1. Salvar em convInfos remove=time::now() (como removeContact salva em contatos) que a conversa é removida e sincronizada com outros dispositivos do usuário

  2. Se recebe um novo compromisso para esta conversa, é ignorado.

  3. Se o Jami começar e o repo estiver presente, a conversa não será anunciada aos clientes.

  4. Dois casos: a. Se não houver outro membro na conversa, podemos remover imediatamente o repositório. b. Se ainda houver outros membros, comprometemo-nos a sair da conversa e agora esperamos que pelo menos outro dispositivo sincronize essa mensagem. Isso evita o fato de que outros membros ainda detectarão o usuário como um membro válido e ainda enviarão notificações de novas mensagens.

  5. Quando tivermos certeza de que alguém está sincronizado, remova erased=time::now() e sincronize com os dispositivos de outros usuários

  6. 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)

Processos para enxames de 1: 1

O objetivo aqui é manter a antiga API (addContact/removeContact, sendTrustRequest/acceptTrustRequest/discardTrustRequest) para gerar enxame com um peer e seu contato.

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:

  1. O TrustRequest incorpora um “conversationId” para informar o colega qual conversa clonar ao aceitar o pedido

  2. 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.

  1. Alice acrescenta Bob

  2. Bob aceita.

  3. Alice remove o Bob

  4. Alice acrescenta Bob

ou

1, Alice adiciona Bob e Bob adiciona Alice ao mesmo tempo, mas ambos não estão conectados

Neste caso, duas conversas são geradas. Não queremos remover mensagens dos usuários ou escolher uma conversa aqui. Então, às vezes, dois enxames de 1:1 entre os mesmos membros serão mostrados. Isso gerará alguns bugs durante o tempo de transição (como não queremos quebrar API, a conversa inferida será uma das duas conversas mostradas, mas por enquanto é “ok-ish”, será corrigido quando os clientes lidarem totalmente com a conversa ID para todas as API (chamadas, transferência de arquivos, etc)).

Nota durante a sincronização

Após aceitar o pedido de uma conversa, há um tempo em que o daemon precisa recuperar o repositório distante. Durante esse tempo, os clientes devem mostrar uma visão de sincronização para fornecer informações ao usuário.

  • ConfigurationManager::getConversations() irá devolver o id da conversa mesmo durante a sincronização

  • ConfigurationManager::conversationInfos() retornará {{“syncing”: “true”}} se se a sincronização for feita.

  • ConfigurationManager::getConversationMembres() devolverá um mapa de dois URI (a conta corrente e o colega que enviou o pedido)

Conversas pedindo especificação

As solicitações de conversação são representadas por um Map<String, String> com as seguintes teclas:

  • ID: o ID da conversa

  • de: uri do remetente

  • Recebido: timestamp

  • Título: Nome (opcional) da conversa

  • Descrição: (opcional)

  • Avatar: (opcional)

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

Para atualizar o vCard, um usuário com permissões suficientes (por padrão: =ADMIN) precisa editar /profile.vcf. e compromete o arquivo com o mimetipo application/update-profile. A nova mensagem é enviada através do mesmo mecanismo e todos os colegas receberão o sinal MessageReceived do daemon. O ramo é retirado se o commit contém outros arquivos ou muito grande ou se feito por um membro não autorizado (por padrão: <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

Protocolos utilizados

Git

Por que essa escolha

Cada conversa será um repositório de dados.

  1. 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.

  2. Distribuído pela natureza, usado em massa, com muitos backends e plug-in.

  3. Pode verificar compromissos através de ganchos e criptomoedas em massa

  4. Pode ser armazenado em uma base de dados, se necessário

  5. Os conflitos são evitados usando mensagens de compromisso, não arquivos.

O que temos de validar

  • Performance? git.lock pode ser baixo

  • Ganchos 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

Swarm modifica massivamente a transferência de arquivos. Agora, todo o histórico está sincronizando, permitindo que todos os dispositivos da conversa recuperem facilmente arquivos antigos. Essas mudanças nos permitem passar de uma lógica onde o remetente empurrou o arquivo em outros dispositivos, através de tentar se conectar aos seus dispositivos (Isso foi ruim porque não era realmente resistente às alterações / falhas de conexão e precisava de uma retoma manual) para uma lógica onde o remetente permite que outros dispositivos baixem. Além disso, qualquer dispositivo com o arquivo pode ser o hospedeiro para outros dispositivos, permitindo recuperar arquivos mesmo que o remetente não esteja lá.

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.

Chamem-me para o enxame.

Ideia

Uma conversa em enxame pode ter vários encontros.

“accountUri/deviceId/conversationId/confId” onde o accountUri/deviceId descreve o host.

O hospedeiro pode ser determinado por duas formas:

  • No enxame de metadados, onde é armazenado como o título/desc/avatar da sala.

  • Ou o primeiro a ligar.

Ao iniciar uma chamada, o host adicionará um novo commit ao swarm, com o URI para participar (accountUri/deviceId/conversationId/confId). Isso será válido até o final da chamada (anunciado por um commit com a duração a ser exibida)

Assim, cada parte receberá a informação de que uma chamada foi iniciada e poderá juntar-se a ela chamando-a.

Ataques?

  • Evite bombas de git

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:

  1. Alice adiciona Bob a uma conversa

  2. A Alice gera um convite: { “application/invite+json” : { “conversationId”: “$id”, “members”: [{…}] }}

  3. 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

  4. 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:

  1. Alice adiciona uma mensagem no repo, dando uma identificação

  2. Alice recebe uma mensagem recebida (de si mesma) se tiver sucesso

  3. 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

  4. 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

Diagrama: classes de bate-papo de enxame

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": "uri of the host",
    "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": "uri of the member",
    "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": "uri of the member",
    "action": "ban/unban"
}

!! RASCUNHO ANTIGO !!

Nota: As seguintes anotações ainda não estão organizadas.

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: pode existir uma biblioteca para implementar as conversações em grupo.

Necessidade de apoio ECC em OpenDHT

Utilização

Adicionar papéis?

Há dois casos de uso principais para conversas em grupo:

  1. 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).

  2. 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).

  1. Simcronização de História. Isto precisa de mover o banco de dados do cliente para o demônio.

  2. 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)

Transferência de arquivos

Atualmente, o algoritmo de transferência de arquivos é baseado em uma conexão TURN (veja Transferência de arquivos). No caso de um grande grupo, isso será ruim. Primeiro precisamos de um implementador p2p para a transferência de arquivos. Implementar o RFC para a transferência p2p.

Outro problema: atualmente não existe implementação para o suporte TCP para ICE no PJSIP.

Recursos

  • https://eprint.iacr.org/2017/666.pdf

  • Simcronização distribuída robusta de sistemas lineares em rede com informações intermitentes (Sean Phillips e Ricardo G. Sanfelice)