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:

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

  2. Sincronização do histórico. Qualquer pessoa deve poder enviar uma mensagem a todo o grupo.

  3. Não existe uma autoridade central. Não se pode confiar em nenhum servidor.

  4. Não repúdio. Os dispositivos devem ser capazes de verificar a validade das mensagens antigas e de reproduzir todo o historial.

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

  1. Bob creates a local Git repository.

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

  3. O hash do primeiro commit torna-se 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 para a DHT

Receber um convite

Alice recebe o convite para se juntar ao enxame criado anteriormente

  1. Ela aceita o convite (se recusar, não faz nada, o convite mantém-se e a Alice nunca recebe qualquer mensagem)

  2. É estabelecida uma ligação ponto a ponto entre Alice e Bob.

  3. Alice pull the Git repo of Bob. WARNING this means that messages need a connection, not from the DHT like today.

  4. Alice valida os commits de Bob

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

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

  1. Bob do a Git pull on Alice

  2. Os commits DEVEM ser verificados através de um hook

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

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

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

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

  1. Registos temporais dos commits gerados

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

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

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

  1. Guardar em convInfos removed=time::now() (tal como removeContact guarda nos contactos) que a conversa é removida e sincronizada com os dispositivos de outros utilizadores

  2. Agora, se for recebido um novo commit para esta conversa, ele é ignorado

  3. Agora, se o Jami iniciar e o repositório ainda estiver presente, a conversa não é anunciada aos clientes

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

  5. Quando tivermos a certeza de que alguém está sincronizado, remover erased=time::now() e sincronizar com os dispositivos de outros utilizadores

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

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

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

  1. Alice adds Bob.

  2. Bob accepts.

  3. Alice removes Bob.

  4. Alice adds Bob.

ou

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

Protocolos utilizados

Git

Porquê esta escolha

Each conversation will be a Git repository. This choice is motivated by:

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

  2. Distribuído pela natureza. Muito utilizado. Muitos backends e plugáveis.

  3. Pode verificar os commits através de hooks e criptografia utilizada em massa

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

  5. 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 baixo

  • Hooks 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:

  1. A Alice adiciona o 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 estiver ligada, através do DHT b. Caso contrário, a Alice envia no canal SIP

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

  1. A Alice adiciona uma mensagem no repositório, dando um ID

  2. A Alice recebe uma mensagem recebida (dela própria) se for bem sucedida

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

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

Diagrama: classes de conversação do 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": "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:

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

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

  1. Sincronização do histórico. Isto precisa de mover a base de dados do cliente para o daemon.

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