mirror of
https://github.com/logos-messaging/logos-chat.git
synced 2026-02-27 20:33:07 +00:00
Remove stale files
This commit is contained in:
parent
e55518c3e5
commit
e7a938a7c2
@ -1,16 +0,0 @@
|
||||
import ./conversations/[convo_type, message]
|
||||
import identity
|
||||
import types
|
||||
|
||||
type ConvoId = string
|
||||
|
||||
type
|
||||
ConversationStore* = concept
|
||||
proc addConversation(self: Self, convo: Conversation)
|
||||
proc getConversation(self: Self, convoId: string): Conversation
|
||||
proc identity(self: Self): Identity
|
||||
proc getId(self: Self): string
|
||||
|
||||
proc notifyNewMessage(self: Self, convo: Conversation, msg: ReceivedMessage)
|
||||
proc notifyDeliveryAck(self: Self, convo: Conversation,
|
||||
msgId: MessageId)
|
||||
@ -1,5 +0,0 @@
|
||||
import
|
||||
./conversations/[convo_type, private_v1, message]
|
||||
|
||||
|
||||
export private_v1, convo_type, message
|
||||
@ -1,28 +0,0 @@
|
||||
import ../conversation_store
|
||||
import ../conversations
|
||||
import ../inbox
|
||||
|
||||
|
||||
proc getType(convo: Conversation): ConvoTypes =
|
||||
if convo of Inbox:
|
||||
return InboxV1Type
|
||||
|
||||
elif convo of PrivateV1:
|
||||
return PrivateV1Type
|
||||
|
||||
else:
|
||||
raise newException(Defect, "Conversation Type not processed")
|
||||
|
||||
proc handleFrame*[T: ConversationStore](convo: Conversation, client: T,
|
||||
bytes: seq[byte]) =
|
||||
|
||||
case convo.getType():
|
||||
of InboxV1Type:
|
||||
let inbox = Inbox(convo)
|
||||
inbox.handleFrame(client, bytes)
|
||||
|
||||
of PrivateV1Type:
|
||||
let priv = PrivateV1(convo)
|
||||
priv.handleFrame(client, bytes)
|
||||
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import chronos
|
||||
import strformat
|
||||
import strutils
|
||||
|
||||
import ../proto_types
|
||||
import ../utils
|
||||
import ../types
|
||||
|
||||
type
|
||||
ConvoTypes* = enum
|
||||
InboxV1Type, PrivateV1Type
|
||||
|
||||
type
|
||||
Conversation* = ref object of RootObj
|
||||
name: string
|
||||
|
||||
proc `$`(conv: Conversation): string =
|
||||
fmt"Convo: {conv.name}"
|
||||
|
||||
# TODO: Removing the raises clause and the exception raise causes this
|
||||
# error --> ...src/chat_sdk/client.nim(166, 9) Error: addConversation(client, convo) can raise an unlisted exception: Exception
|
||||
# Need better understanding of NIMs Exception model
|
||||
method id*(self: Conversation): string {.raises: [Defect, ValueError].} =
|
||||
# TODO: make this a compile time check
|
||||
panic("ProgramError: Missing concrete implementation")
|
||||
|
||||
method sendMessage*(convo: Conversation, content_frame: Content) : Future[MessageId] {.async, base, gcsafe.} =
|
||||
# TODO: make this a compile time check
|
||||
panic("ProgramError: Missing concrete implementation")
|
||||
@ -1,11 +0,0 @@
|
||||
import ../crypto
|
||||
|
||||
# How to surface different verifability of properties across conversation types
|
||||
|
||||
|
||||
type ReceivedMessage* = ref object of RootObj
|
||||
sender*: PublicKey
|
||||
timestamp*: int64
|
||||
content*: seq[byte]
|
||||
|
||||
|
||||
@ -1,282 +0,0 @@
|
||||
|
||||
import blake2
|
||||
import chronicles
|
||||
import chronos
|
||||
import sds
|
||||
import std/[sequtils, strutils, strformat]
|
||||
import std/algorithm
|
||||
import sugar
|
||||
import tables
|
||||
|
||||
import ../conversation_store
|
||||
import ../crypto
|
||||
import ../delivery/waku_client
|
||||
|
||||
import ../[
|
||||
identity,
|
||||
errors,
|
||||
proto_types,
|
||||
types,
|
||||
utils
|
||||
]
|
||||
import convo_type
|
||||
import message
|
||||
|
||||
import ../../naxolotl as nax
|
||||
|
||||
const TopicPrefixPrivateV1 = "/convo/private/"
|
||||
|
||||
type
|
||||
ReceivedPrivateV1Message* = ref object of ReceivedMessage
|
||||
|
||||
proc initReceivedMessage(sender: PublicKey, timestamp: int64, content: Content) : ReceivedPrivateV1Message =
|
||||
ReceivedPrivateV1Message(sender:sender, timestamp:timestamp, content:content)
|
||||
|
||||
|
||||
type
|
||||
PrivateV1* = ref object of Conversation
|
||||
ds: WakuClient
|
||||
sdsClient: ReliabilityManager
|
||||
owner: Identity
|
||||
participant: PublicKey
|
||||
discriminator: string
|
||||
doubleratchet: naxolotl.Doubleratchet
|
||||
|
||||
proc derive_topic(participant: PublicKey): string =
|
||||
## Derives a topic from the participants' public keys.
|
||||
return TopicPrefixPrivateV1 & participant.get_addr()
|
||||
|
||||
proc getTopicInbound*(self: PrivateV1): string =
|
||||
## Returns the topic where the local client is listening for messages
|
||||
return derive_topic(self.owner.getPubkey())
|
||||
|
||||
proc getTopicOutbound*(self: PrivateV1): string =
|
||||
## Returns the topic where the remote recipient is listening for messages
|
||||
return derive_topic(self.participant)
|
||||
|
||||
## Parses the topic to extract the conversation ID.
|
||||
proc parseTopic*(topic: string): Result[string, ChatError] =
|
||||
if not topic.startsWith(TopicPrefixPrivateV1):
|
||||
return err(ChatError(code: errTopic, context: "Invalid topic prefix"))
|
||||
|
||||
let id = topic.split('/')[^1]
|
||||
if id == "":
|
||||
return err(ChatError(code: errTopic, context: "Empty conversation ID"))
|
||||
|
||||
return ok(id)
|
||||
|
||||
proc allParticipants(self: PrivateV1): seq[PublicKey] =
|
||||
return @[self.owner.getPubkey(), self.participant]
|
||||
|
||||
proc getConvoIdRaw(participants: seq[PublicKey],
|
||||
discriminator: string): string =
|
||||
# This is a placeholder implementation.
|
||||
var addrs = participants.map(x => x.get_addr());
|
||||
addrs.sort()
|
||||
addrs.add(discriminator)
|
||||
let raw = addrs.join("|")
|
||||
return utils.hash_func(raw)
|
||||
|
||||
proc getConvoId*(self: PrivateV1): string =
|
||||
return getConvoIdRaw(@[self.owner.getPubkey(), self.participant], self.discriminator)
|
||||
|
||||
|
||||
proc calcMsgId(self: PrivateV1, msgBytes: seq[byte]): string =
|
||||
let s = fmt"{self.getConvoId()}|{msgBytes}"
|
||||
result = getBlake2b(s, 16, "")
|
||||
|
||||
|
||||
proc encrypt*(convo: PrivateV1, plaintext: var seq[byte]): EncryptedPayload =
|
||||
|
||||
let (header, ciphertext) = convo.doubleratchet.encrypt(plaintext) #TODO: Associated Data
|
||||
|
||||
result = EncryptedPayload(doubleratchet: proto_types.DoubleRatchet(
|
||||
dh: toSeq(header.dhPublic),
|
||||
msgNum: header.msgNumber,
|
||||
prevChainLen: header.prevChainLen,
|
||||
ciphertext: ciphertext)
|
||||
)
|
||||
|
||||
proc decrypt*(convo: PrivateV1, enc: EncryptedPayload): Result[seq[byte], ChatError] =
|
||||
# Ensure correct type as received
|
||||
if enc.doubleratchet.ciphertext == @[]:
|
||||
return err(ChatError(code: errTypeError, context: "Expected doubleratchet encrypted payload got ???"))
|
||||
|
||||
let dr = enc.doubleratchet
|
||||
|
||||
var header = DrHeader(
|
||||
msgNumber: dr.msgNum,
|
||||
prevChainLen: dr.prevChainLen
|
||||
)
|
||||
copyMem(addr header.dhPublic[0], unsafeAddr dr.dh[0], dr.dh.len) # TODO: Avoid this copy
|
||||
|
||||
convo.doubleratchet.decrypt(header, dr.ciphertext, @[]).mapErr(proc(e: NaxolotlError): ChatError = ChatError(code: errWrapped, context: repr(e) ))
|
||||
|
||||
|
||||
|
||||
proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc(
|
||||
conversation: Conversation,
|
||||
msgId: string): Future[void] {.async.} = nil) =
|
||||
## Accepts lambdas/functions to be called from Reliability Manager callbacks.
|
||||
let funcMsg = proc(messageId: SdsMessageID,
|
||||
channelId: SdsChannelID) {.gcsafe.} =
|
||||
debug "sds message ready", messageId = messageId,
|
||||
channelId = channelId
|
||||
|
||||
let funcDeliveryAck = proc(messageId: SdsMessageID,
|
||||
channelId: SdsChannelID) {.gcsafe.} =
|
||||
debug "sds message ack", messageId = messageId,
|
||||
channelId = channelId
|
||||
|
||||
if deliveryAckCb != nil:
|
||||
asyncSpawn deliveryAckCb(convo, messageId)
|
||||
|
||||
let funcDroppedMsg = proc(messageId: SdsMessageID, missingDeps: seq[
|
||||
SdsMessageID], channelId: SdsChannelID) {.gcsafe.} =
|
||||
debug "sds message missing", messageId = messageId,
|
||||
missingDeps = missingDeps, channelId = channelId
|
||||
|
||||
convo.sdsClient.setCallbacks(
|
||||
funcMsg, funcDeliveryAck, funcDroppedMsg
|
||||
)
|
||||
|
||||
|
||||
|
||||
proc initPrivateV1*(owner: Identity, ds:WakuClient, participant: PublicKey, seedKey: array[32, byte],
|
||||
discriminator: string = "default", isSender: bool, deliveryAckCb: proc(
|
||||
conversation: Conversation,
|
||||
msgId: string): Future[void] {.async.} = nil):
|
||||
PrivateV1 =
|
||||
|
||||
var rm = newReliabilityManager().valueOr:
|
||||
raise newException(ValueError, fmt"sds initialization: {repr(error)}")
|
||||
|
||||
let dr = if isSender:
|
||||
initDoubleratchetSender(seedKey, participant.bytes)
|
||||
else:
|
||||
initDoubleratchetRecipient(seedKey, owner.privateKey.bytes)
|
||||
|
||||
result = PrivateV1(
|
||||
ds: ds,
|
||||
sdsClient: rm,
|
||||
owner: owner,
|
||||
participant: participant,
|
||||
discriminator: discriminator,
|
||||
doubleratchet: dr
|
||||
)
|
||||
|
||||
result.wireCallbacks(deliveryAckCb)
|
||||
|
||||
result.sdsClient.ensureChannel(result.getConvoId()).isOkOr:
|
||||
raise newException(ValueError, "bad sds channel")
|
||||
|
||||
proc encodeFrame*(self: PrivateV1, msg: PrivateV1Frame): (MessageId, EncryptedPayload) =
|
||||
|
||||
let frameBytes = encode(msg)
|
||||
let msgId = self.calcMsgId(frameBytes)
|
||||
var sdsPayload = self.sdsClient.wrapOutgoingMessage(frameBytes, msgId,
|
||||
self.getConvoId()).valueOr:
|
||||
raise newException(ValueError, fmt"sds wrapOutgoingMessage failed: {repr(error)}")
|
||||
|
||||
result = (msgId, self.encrypt(sdsPayload))
|
||||
|
||||
proc sendFrame(self: PrivateV1, ds: WakuClient,
|
||||
msg: PrivateV1Frame): Future[MessageId]{.async.} =
|
||||
let (msgId, encryptedPayload) = self.encodeFrame(msg)
|
||||
discard ds.sendPayload(self.getTopicOutbound(), encryptedPayload.toEnvelope(
|
||||
self.getConvoId()))
|
||||
|
||||
result = msgId
|
||||
|
||||
|
||||
method id*(self: PrivateV1): string =
|
||||
return getConvoIdRaw(self.allParticipants(), self.discriminator)
|
||||
|
||||
proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
|
||||
encPayload: EncryptedPayload) =
|
||||
## Dispatcher for Incoming `PrivateV1Frames`.
|
||||
## Calls further processing depending on the kind of frame.
|
||||
|
||||
if convo.doubleratchet.dhSelfPublic() == encPayload.doubleratchet.dh:
|
||||
info "outgoing message, no need to handle", convo = convo.id()
|
||||
return
|
||||
|
||||
let plaintext = convo.decrypt(encPayload).valueOr:
|
||||
error "decryption failed", error = error
|
||||
return
|
||||
|
||||
let (frameData, missingDeps, channelId) = convo.sdsClient.unwrapReceivedMessage(
|
||||
plaintext).valueOr:
|
||||
raise newException(ValueError, fmt"Failed to unwrap SDS message:{repr(error)}")
|
||||
|
||||
debug "sds unwrap", convo = convo.id(), missingDeps = missingDeps,
|
||||
channelId = channelId
|
||||
|
||||
let frame = decode(frameData, PrivateV1Frame).valueOr:
|
||||
raise newException(ValueError, "Failed to decode SdsM: " & error)
|
||||
|
||||
if frame.sender == @(convo.owner.getPubkey().bytes()):
|
||||
notice "Self Message", convo = convo.id()
|
||||
return
|
||||
|
||||
case frame.getKind():
|
||||
of typeContent:
|
||||
# TODO: Using client.getId() results in an error in this context
|
||||
client.notifyNewMessage(convo, initReceivedMessage(convo.participant, frame.timestamp, frame.content))
|
||||
|
||||
of typePlaceholder:
|
||||
notice "Got Placeholder", text = frame.placeholder.counter
|
||||
|
||||
proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
|
||||
bytes: seq[byte]) =
|
||||
## Dispatcher for Incoming `PrivateV1Frames`.
|
||||
## Calls further processing depending on the kind of frame.
|
||||
let encPayload = decode(bytes, EncryptedPayload).valueOr:
|
||||
raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}")
|
||||
|
||||
convo.handleFrame(client,encPayload)
|
||||
|
||||
|
||||
method sendMessage*(convo: PrivateV1, content_frame: Content) : Future[MessageId] {.async.} =
|
||||
|
||||
try:
|
||||
let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()),
|
||||
timestamp: getCurrentTimestamp(), content: content_frame)
|
||||
|
||||
result = await convo.sendFrame(convo.ds, frame)
|
||||
except Exception as e:
|
||||
error "Unknown error in PrivateV1:SendMessage"
|
||||
|
||||
|
||||
## Encrypts content without sending it.
|
||||
proc encryptMessage*(self: PrivateV1, content_frame: Content) : (MessageId, EncryptedPayload) =
|
||||
|
||||
try:
|
||||
let frame = PrivateV1Frame(
|
||||
sender: @(self.owner.getPubkey().bytes()),
|
||||
timestamp: getCurrentTimestamp(),
|
||||
content: content_frame
|
||||
)
|
||||
|
||||
result = self.encodeFrame(frame)
|
||||
|
||||
except Exception as e:
|
||||
error "Unknown error in PrivateV1:EncryptMessage"
|
||||
|
||||
proc initPrivateV1Sender*(sender:Identity,
|
||||
ds: WakuClient,
|
||||
participant: PublicKey,
|
||||
seedKey: array[32, byte],
|
||||
content: Content,
|
||||
deliveryAckCb: proc(conversation: Conversation, msgId: string): Future[void] {.async.} = nil): (PrivateV1, EncryptedPayload) =
|
||||
let convo = initPrivateV1(sender, ds, participant, seedKey, "default", true, deliveryAckCb)
|
||||
|
||||
# Encrypt Content with Convo
|
||||
let contentFrame = PrivateV1Frame(sender: @(sender.getPubkey().bytes()), timestamp: getCurrentTimestamp(), content: content)
|
||||
let (msg_id, encPayload) = convo.encryptMessage(content)
|
||||
result = (convo, encPayload)
|
||||
|
||||
|
||||
proc initPrivateV1Recipient*(owner:Identity,ds: WakuClient, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc(
|
||||
conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 =
|
||||
initPrivateV1(owner,ds, participant, seedKey, "default", false, deliveryAckCb)
|
||||
@ -1,35 +0,0 @@
|
||||
import proto_types
|
||||
|
||||
import strformat
|
||||
import crypto/ecdh
|
||||
import std/[sysrand]
|
||||
import results
|
||||
import utils
|
||||
|
||||
export PublicKey, PrivateKey, bytes, createRandomKey, loadPrivateKeyFromBytes, loadPublicKeyFromBytes,
|
||||
getPublicKey, Dh, Result, get_addr, `$`
|
||||
|
||||
|
||||
proc encrypt_plain*[T: EncryptableTypes](frame: T): EncryptedPayload =
|
||||
return EncryptedPayload(
|
||||
plaintext: Plaintext(payload: encode(frame)),
|
||||
)
|
||||
|
||||
proc decrypt_plain*[T: EncryptableTypes](ciphertext: Plaintext, t: typedesc[
|
||||
T]): Result[T, string] =
|
||||
|
||||
let obj = decode(ciphertext.payload, T)
|
||||
if obj.isErr:
|
||||
return err("Protobuf decode failed: " & obj.error)
|
||||
result = ok(obj.get())
|
||||
|
||||
proc generate_key*(): PrivateKey =
|
||||
createRandomKey().get()
|
||||
|
||||
|
||||
proc toHex*(key: PublicKey): string =
|
||||
bytesToHex(key.bytes())
|
||||
|
||||
proc `$`*(key: PublicKey): string =
|
||||
let byteStr = toHex(key)
|
||||
fmt"{byteStr[0..3]}..{byteStr[^4 .. ^1]}"
|
||||
@ -1,171 +0,0 @@
|
||||
import std/[strutils]
|
||||
|
||||
import
|
||||
chronicles,
|
||||
chronos,
|
||||
results,
|
||||
strformat
|
||||
|
||||
import
|
||||
conversations/convo_type,
|
||||
conversations,
|
||||
conversation_store,
|
||||
crypto,
|
||||
delivery/waku_client,
|
||||
errors,
|
||||
identity,
|
||||
proto_types,
|
||||
types
|
||||
|
||||
logScope:
|
||||
topics = "chat inbox"
|
||||
|
||||
type
|
||||
Inbox* = ref object of Conversation
|
||||
identity: Identity
|
||||
inbox_addr: string
|
||||
|
||||
const
|
||||
TopicPrefixInbox = "/inbox/"
|
||||
|
||||
proc `$`*(conv: Inbox): string =
|
||||
fmt"Inbox: addr->{conv.inbox_addr}"
|
||||
|
||||
|
||||
proc initInbox*(ident: Identity): Inbox =
|
||||
## Initializes an Inbox object with the given address and invite callback.
|
||||
return Inbox(identity: ident)
|
||||
|
||||
proc encrypt*(frame: InboxV1Frame): EncryptedPayload =
|
||||
return encrypt_plain(frame)
|
||||
|
||||
proc decrypt*(inbox: Inbox, encbytes: EncryptedPayload): Result[InboxV1Frame, string] =
|
||||
let res_frame = decrypt_plain(encbytes.plaintext, InboxV1Frame)
|
||||
if res_frame.isErr:
|
||||
error "Failed to decrypt frame: ", err = res_frame.error
|
||||
return err("Failed to decrypt frame: " & res_frame.error)
|
||||
result = res_frame
|
||||
|
||||
proc wrap_env*(payload: EncryptedPayload, convo_id: string): WapEnvelopeV1 =
|
||||
let bytes = encode(payload)
|
||||
let salt = generateSalt()
|
||||
|
||||
return WapEnvelopeV1(
|
||||
payload: bytes,
|
||||
salt: salt,
|
||||
conversation_hint: convo_id,
|
||||
)
|
||||
|
||||
proc conversation_id_for*(pubkey: PublicKey): string =
|
||||
## Generates a conversation ID based on the public key.
|
||||
return "/convo/inbox/v1/" & pubkey.get_addr()
|
||||
|
||||
# TODO derive this from instance of Inbox
|
||||
proc topic_inbox*(client_addr: string): string =
|
||||
return TopicPrefixInbox & client_addr
|
||||
|
||||
proc parseTopic*(topic: string): Result[string, ChatError] =
|
||||
if not topic.startsWith(TopicPrefixInbox):
|
||||
return err(ChatError(code: errTopic, context: "Invalid inbox topic prefix"))
|
||||
|
||||
let id = topic.split('/')[^1]
|
||||
if id == "":
|
||||
return err(ChatError(code: errTopic, context: "Empty inbox id"))
|
||||
|
||||
return ok(id)
|
||||
|
||||
method id*(convo: Inbox): string =
|
||||
return conversation_id_for(convo.identity.getPubkey())
|
||||
|
||||
## Encrypt and Send a frame to the remote account
|
||||
proc sendFrame(ds: WakuClient, remote: PublicKey, frame: InboxV1Frame ): Future[void] {.async.} =
|
||||
let env = wrapEnv(encrypt(frame),conversation_id_for(remote) )
|
||||
await ds.sendPayload(topic_inbox(remote.get_addr()), env)
|
||||
|
||||
|
||||
proc newPrivateInvite(initator_static: PublicKey,
|
||||
initator_ephemeral: PublicKey,
|
||||
recipient_static: PublicKey,
|
||||
recipient_ephemeral: uint32,
|
||||
payload: EncryptedPayload) : InboxV1Frame =
|
||||
|
||||
let invite = InvitePrivateV1(
|
||||
initiator: @(initator_static.bytes()),
|
||||
initiatorEphemeral: @(initator_ephemeral.bytes()),
|
||||
participant: @(recipient_static.bytes()),
|
||||
participantEphemeralId: 0,
|
||||
discriminator: "",
|
||||
initial_message: payload
|
||||
)
|
||||
result = InboxV1Frame(invitePrivateV1: invite, recipient: "")
|
||||
|
||||
#################################################
|
||||
# Conversation Creation
|
||||
#################################################
|
||||
|
||||
## Establish a PrivateConversation with a remote client
|
||||
proc inviteToPrivateConversation*(self: Inbox, ds: Wakuclient, remote_static: PublicKey, remote_ephemeral: PublicKey, content: Content ) : Future[PrivateV1] {.async.} =
|
||||
# Create SeedKey
|
||||
# TODO: Update key derivations when noise is integrated
|
||||
var local_ephemeral = generateKey()
|
||||
var sk{.noInit.} : array[32, byte] = default(array[32, byte])
|
||||
|
||||
# Initialize PrivateConversation
|
||||
let (convo, encPayload) = initPrivateV1Sender(self.identity, ds, remote_static, sk, content, nil)
|
||||
result = convo
|
||||
|
||||
# # Build Invite
|
||||
let frame = newPrivateInvite(self.identity.getPubkey(), local_ephemeral.getPublicKey(), remote_static, 0, encPayload)
|
||||
|
||||
# Send
|
||||
await sendFrame(ds, remote_static, frame)
|
||||
|
||||
## Receive am Invitation to create a new private conversation
|
||||
proc createPrivateV1FromInvite*[T: ConversationStore](client: T,
|
||||
invite: InvitePrivateV1) =
|
||||
|
||||
let destPubkey = loadPublicKeyFromBytes(invite.initiator).valueOr:
|
||||
raise newException(ValueError, "Invalid public key in intro bundle.")
|
||||
|
||||
let deliveryAckCb = proc(
|
||||
conversation: Conversation,
|
||||
msgId: string): Future[void] {.async.} =
|
||||
client.notifyDeliveryAck(conversation, msgId)
|
||||
|
||||
# TODO: remove placeholder key
|
||||
var key : array[32, byte] = default(array[32,byte])
|
||||
|
||||
let convo = initPrivateV1Recipient(client.identity(), client.ds, destPubkey, key, deliveryAckCb)
|
||||
notice "Creating PrivateV1 conversation", client = client.getId(),
|
||||
convoId = convo.getConvoId()
|
||||
|
||||
convo.handleFrame(client, invite.initial_message)
|
||||
|
||||
# Calling `addConversation` must only occur after the conversation is completely configured.
|
||||
# The client calls the OnNewConversation callback, which returns execution to the application.
|
||||
client.addConversation(convo)
|
||||
|
||||
proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[
|
||||
byte]) =
|
||||
## Dispatcher for Incoming `InboxV1Frames`.
|
||||
## Calls further processing depending on the kind of frame.
|
||||
|
||||
let enc = decode(bytes, EncryptedPayload).valueOr:
|
||||
raise newException(ValueError, "Failed to decode payload")
|
||||
|
||||
let frame = convo.decrypt(enc).valueOr:
|
||||
error "Decrypt failed", client = client.getId(), error = error
|
||||
raise newException(ValueError, "Failed to Decrypt MEssage: " &
|
||||
error)
|
||||
|
||||
case getKind(frame):
|
||||
of typeInvitePrivateV1:
|
||||
createPrivateV1FromInvite(client, frame.invitePrivateV1)
|
||||
|
||||
of typeNote:
|
||||
notice "Receive Note", client = client.getId(), text = frame.note.text
|
||||
|
||||
|
||||
method sendMessage*(convo: Inbox, content_frame: Content) : Future[MessageId] {.async.} =
|
||||
warn "Cannot send message to Inbox"
|
||||
result = "program_error"
|
||||
@ -1,58 +0,0 @@
|
||||
import base64
|
||||
import chronos
|
||||
import strformat
|
||||
import strutils
|
||||
|
||||
import libp2p/crypto/crypto
|
||||
|
||||
import ../content_types/all
|
||||
import proto_types
|
||||
import utils
|
||||
|
||||
|
||||
#################################################
|
||||
# Link Generation
|
||||
#################################################
|
||||
|
||||
proc toBundle*(link: string): Result[IntroBundle, string] =
|
||||
# Check scheme
|
||||
if not link.startsWith("wap://"):
|
||||
return err("InvalidScheme")
|
||||
|
||||
# Remove scheme
|
||||
let path = link[6..^1]
|
||||
|
||||
# Split by '/'
|
||||
let parts = path.split('/')
|
||||
|
||||
# Expected format: ident/{ident}/ephemeral/{ephemeral}/eid/{eid}
|
||||
if parts.len != 6:
|
||||
return err("InvalidFormat")
|
||||
|
||||
# Validate structure
|
||||
if parts[0] != "ident" or parts[2] != "ephemeral" or parts[4] != "eid":
|
||||
return err("InvalidFormat")
|
||||
|
||||
# Extract values
|
||||
let ident = decode(parts[1]).toBytes()
|
||||
let ephemeral = decode(parts[3]).toBytes()
|
||||
|
||||
let eid = int32(parseInt(parts[5])) # TODO: catch parse error
|
||||
|
||||
# Validate non-empty
|
||||
if ident.len == 0:
|
||||
return err("MissingIdent")
|
||||
if ephemeral.len == 0:
|
||||
return err("MissingEphemeral")
|
||||
|
||||
return ok(IntroBundle(
|
||||
ident: ident,
|
||||
ephemeral: ephemeral,
|
||||
ephemeral_id: eid
|
||||
))
|
||||
|
||||
|
||||
proc toLink*(intro: IntroBundle): string =
|
||||
let ident = encode(intro.ident, safe = true)
|
||||
let ephemeral = intro.ephemeral.toHex()
|
||||
result = fmt"wap://ident/{ident}/ephemeral/{ephemeral}/eid/{intro.ephemeral_id}"
|
||||
@ -1,104 +0,0 @@
|
||||
# Can this be an external package? It would be preferable to have these types
|
||||
# easy to import and use.
|
||||
|
||||
import protobuf_serialization # This import is needed or th macro will not work
|
||||
import protobuf_serialization/proto_parser
|
||||
import results
|
||||
import std/random
|
||||
|
||||
export protobuf_serialization
|
||||
|
||||
import_proto3 "../../protos/inbox.proto"
|
||||
# import_proto3 "../protos/invite.proto" // Import3 follows protobuf includes so this will result in a redefinition error
|
||||
import_proto3 "../../protos/envelope.proto"
|
||||
|
||||
import_proto3 "../../protos/private_v1.proto"
|
||||
|
||||
type EncryptableTypes = InboxV1Frame | EncryptedPayload
|
||||
|
||||
export EncryptedPayload
|
||||
export InboxV1Frame
|
||||
export PrivateV1Frame
|
||||
|
||||
export EncryptableTypes
|
||||
|
||||
|
||||
|
||||
|
||||
proc encode*(frame: object): seq[byte] =
|
||||
## Encodes the frame into a byte sequence using Protobuf serialization.
|
||||
result = Protobuf.encode(frame)
|
||||
|
||||
|
||||
proc decode*[T: object] (bytes: seq[byte], proto: typedesc[
|
||||
T]): Result[T, string] =
|
||||
## Encodes the frame into a byte sequence using Protobuf serialization.
|
||||
|
||||
try:
|
||||
result = ok(Protobuf.decode(bytes, proto))
|
||||
except ProtobufError as e:
|
||||
result = err("Failed to decode payload: " & e.msg)
|
||||
|
||||
type
|
||||
IntroBundle {.proto3.} = object
|
||||
ident* {.fieldNumber: 1.}: seq[byte]
|
||||
ephemeral* {.fieldNumber: 2.}: seq[byte]
|
||||
ephemeral_id* {.fieldNumber: 3.}: int32
|
||||
|
||||
|
||||
export IntroBundle
|
||||
|
||||
proc generateSalt*(): uint64 =
|
||||
randomize()
|
||||
result = 0
|
||||
for i in 0 ..< 8:
|
||||
result = result or (uint64(rand(255)) shl (i * 8))
|
||||
|
||||
|
||||
proc toEnvelope*(payload: EncryptedPayload, convo_id: string): WapEnvelopeV1 =
|
||||
let bytes = encode(payload)
|
||||
let salt = generateSalt()
|
||||
|
||||
# TODO: Implement hinting
|
||||
return WapEnvelopeV1(
|
||||
payload: bytes,
|
||||
salt: salt,
|
||||
conversation_hint: convo_id,
|
||||
)
|
||||
|
||||
###########################################################
|
||||
# nim-serialize-protobuf does not support oneof fields.
|
||||
# As a stop gap each object using oneof fields, needs
|
||||
# a implementation to look up the type.
|
||||
#
|
||||
# The valid field is determined by the fields which
|
||||
# is not set to the default value
|
||||
###########################################################
|
||||
|
||||
type
|
||||
InboxV1FrameType* = enum
|
||||
type_InvitePrivateV1, type_Note
|
||||
|
||||
proc getKind*(obj: InboxV1Frame): InboxV1FrameType =
|
||||
|
||||
if obj.invite_private_v1 != InvitePrivateV1():
|
||||
return type_InvitePrivateV1
|
||||
|
||||
if obj.note != Note():
|
||||
return type_Note
|
||||
|
||||
raise newException(ValueError, "Un handled one of type")
|
||||
|
||||
type
|
||||
PrivateV1FrameType* = enum
|
||||
type_Content, type_Placeholder
|
||||
|
||||
proc getKind*(obj: PrivateV1Frame): PrivateV1FrameType =
|
||||
|
||||
if obj.content != @[]:
|
||||
return type_Content
|
||||
|
||||
if obj.placeholder != Placeholder():
|
||||
return type_Placeholder
|
||||
|
||||
raise newException(ValueError, "Un handled one of type")
|
||||
Loading…
x
Reference in New Issue
Block a user