package client import ( "fmt" "io" "os" "time" "forge.redroom.link/yves/meowlib" "github.com/google/uuid" "google.golang.org/protobuf/proto" ) // Peer manages the peer messaging functions // - Building simple user messages // - Utility functions for packing/unpacking, encrypting/decrypting messages for peer communication type Peer struct { Uid string `json:"uid,omitempty"` Name string `json:"name,omitempty"` Avatar string `json:"avatar,omitempty"` MyName string `json:"my_name,omitempty"` MyAvatar string `json:"my_avatar,omitempty"` // Conversation []InternalMessage `json:"conversation,omitempty"` // My own keys for that peer MyIdentity meowlib.KeyPair `json:"my_identity,omitempty"` MyEncryptionKp meowlib.KeyPair `json:"my_encryption_kp,omitempty"` MyLookupKp meowlib.KeyPair `json:"my_lookup_kp,omitempty"` MyPullServers []string `json:"my_pull_servers,omitempty"` // Peer keys and infos //Contact meowlib.ContactCard `json:"contact,omitempty"` // todo : remove ContactPublicKey string `json:"contact_public_key,omitempty"` ContactLookupKey string `json:"contact_lookup_key,omitempty"` ContactEncryption string `json:"contact_encryption,omitempty"` ContactPullServers []string `json:"contact_pull_servers,omitempty"` InvitationId string `json:"invitation_id,omitempty"` InvitationUrl string `json:"invitation_url,omitempty"` InvitationMessage string `json:"invitation_message,omitempty"` InvitationExpiry time.Time `json:"invitation_expiry,omitempty"` LastMessage *InternalUserMessage `json:"last_message,omitempty"` // Internal management attributes Visible bool `json:"visible,omitempty"` VisiblePassword string `json:"visible_password,omitempty"` PasswordType string `json:"password_type,omitempty"` Blocked bool `json:"blocked,omitempty"` MessageNotification string `json:"message_notification,omitempty"` MatriochkaMode bool `json:"matriochka_mode,omitempty"` DirectMode bool `json:"direct_mode,omitempty"` DbIds []string `json:"db_ids,omitempty"` IsDevice bool `json:"is_device,omitempty"` dbPassword string } // // getters and setters // func (p *Peer) GetMyContact() *meowlib.ContactCard { var c meowlib.ContactCard c.ContactPublicKey = p.MyIdentity.Public c.LookupPublicKey = p.MyLookupKp.Public c.EncryptionPublicKey = p.MyEncryptionKp.Public srvCards, err := GetConfig().GetIdentity().MessageServers.LoadServerCardsFromUids(p.MyPullServers) if err == nil { c.PullServers = srvCards } c.InvitationId = p.InvitationId c.InvitationMessage = p.InvitationMessage c.Name = p.MyName return &c } func (p *Peer) GetContact() *meowlib.ContactCard { var c meowlib.ContactCard c.ContactPublicKey = p.ContactPublicKey c.LookupPublicKey = p.ContactLookupKey c.EncryptionPublicKey = p.ContactEncryption srvCards, err := GetConfig().GetIdentity().MessageServers.LoadServerCardsFromUids(p.ContactPullServers) if err == nil { c.PullServers = srvCards } c.InvitationId = p.InvitationId c.InvitationMessage = p.InvitationMessage c.Name = p.Name return &c } func (p *Peer) InvitationPending() bool { if p.ContactPublicKey == "" { return true } return false } // // Messages building // func (p *Peer) BuildSimpleUserMessage(message []byte) (*meowlib.UserMessage, error) { var msg meowlib.UserMessage msg.Destination = p.ContactLookupKey msg.From = p.MyIdentity.Public msg.Data = message msg.Type = "1" msg.Status = &meowlib.ConversationStatus{} msg.Status.Uuid = uuid.New().String() return &msg, nil } func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowlib.UserMessage, error) { var msgs []meowlib.UserMessage fi, err := os.Stat(filename) if err != nil { return nil, err } file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() // declare chunk size maxSz := GetConfig().Chunksize // create buffer b := make([]byte, maxSz) chunk := 0 for { // read content to buffer readTotal, err := file.Read(b) if err != nil { if err != io.EOF { return nil, err } break } var msg meowlib.UserMessage var file meowlib.File msg.Destination = p.ContactLookupKey msg.From = p.MyIdentity.Public file.Filename = fi.Name() file.Chunk = uint32(chunk) file.Data = b[:readTotal] file.Size = uint64(fi.Size()) msg.Files = append(msg.Files, &file) msg.Type = "1" if chunk == 0 { msg.Status = &meowlib.ConversationStatus{} msg.Status.Uuid = uuid.New().String() } msgs = append(msgs, msg) chunk++ } return msgs, nil } // Builds an invitation answer user message. // it takes as input a contactcard generated by Identity.AnswerInvitation func (p *Peer) BuildInvitationAnswerMessage(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) { var msg meowlib.UserMessage var invitation meowlib.Invitation invitation.Step = 3 out, err := proto.Marshal(myContactCard) if err != nil { return nil, err } invitation.Uuid = p.InvitationId invitation.Payload = out msg.Destination = p.ContactLookupKey msg.Invitation = &invitation msg.From = p.MyIdentity.Public msg.Type = "1" return &msg, nil } // // Messages encryption and packaging // func (p *Peer) SerializeUserMessage(msg *meowlib.UserMessage) ([]byte, error) { out, err := proto.Marshal(msg) if err != nil { return nil, err } return out, nil } func (p *Peer) DeserializeUserMessage(data []byte) (*meowlib.UserMessage, error) { var msg meowlib.UserMessage err := proto.Unmarshal(data, &msg) if err != nil { return nil, err } return &msg, nil } // AsymEncryptMessage prepares a message to send to a specific peer contact func (p *Peer) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, error) { var enc *meowlib.EncryptedMessage // fmt.Println("[AsymEncryptMessage] Destination is:", p.ContactLookupKey) // fmt.Println("[AsymEncryptMessage] Contact encryption key is:", p.ContactEncryption) // fmt.Println("[AsymEncryptMessage] My signing key is:", p.MyIdentity.Private) // fmt.Println("[AsymEncryptMessage] Signature should be verified with:", p.MyIdentity.Public) enc, err := meowlib.AsymEncryptAndSign(p.ContactEncryption, p.MyIdentity.Private, Message) if err != nil { fmt.Println(err.Error()) return enc, err } return enc, err } // AsymDecryptMessage reads a message from a specific peer contact func (p *Peer) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMessage []byte, err error) { // fmt.Println("[AsymDecryptMessage] Decrypting key is:", p.MyEncryptionKp.Private) // fmt.Println("[AsymDecryptMessage] Should have been encrypted with:", p.MyEncryptionKp.Public) // fmt.Println("[AsymDecryptMessage] Signature will be verified with:", p.ContactPublicKey) DecryptedMessage, err = meowlib.AsymDecryptAndCheck(p.MyEncryptionKp.Private, p.ContactPublicKey, Message, Signature) if err != nil { fmt.Println(err.Error()) return nil, err } return DecryptedMessage, err } // PackUserMessage will package the previously encrypted message func (p *Peer) PackUserMessage(message []byte, signature []byte) *meowlib.PackedUserMessage { var msg meowlib.PackedUserMessage msg.Destination = p.ContactLookupKey msg.Payload = message msg.Signature = signature return &msg } // UnPackUserMessage unpacks a user message func (p *Peer) UnPackUserMessage(protoPackedMessage []byte) (payload []byte, signature []byte, err error) { msg := &meowlib.PackedServerMessage{} if err := proto.Unmarshal(protoPackedMessage, msg); err != nil { return nil, nil, err } return msg.Payload, msg.Signature, nil } func (p *Peer) GetConversationRequest() *meowlib.ConversationRequest { var cr meowlib.ConversationRequest return &cr } // ProcessOutboundUserMessage is a helper function that serializes, encrypts and packs a user message func (p *Peer) ProcessOutboundUserMessage(usermessage *meowlib.UserMessage) (*meowlib.PackedUserMessage, error) { serializedMessage, err := p.SerializeUserMessage(usermessage) if err != nil { return nil, err } // Encrypting it enc, err := p.AsymEncryptMessage(serializedMessage) if err != nil { return nil, err } // Packing it packedMsg := p.PackUserMessage(enc.Data, enc.Signature) return packedMsg, nil } // ProcessInboundUserMessage is a helper function that decrypts and deserializes a user message func (p *Peer) ProcessInboundUserMessage(message []byte, signature []byte) (*meowlib.UserMessage, error) { dec, err := p.AsymDecryptMessage(message, signature) if err != nil { return nil, err } msg, err := p.DeserializeUserMessage(dec) if err != nil { return nil, err } return msg, nil } // // Messages database // // SetDbPassword sets a specific password for a hidden user, it won't be saved, but kept in memory only for the next operations func (p *Peer) SetDbPassword(password string) { p.dbPassword = password } func (p *Peer) GetDbPassword() string { if p.dbPassword == "" { return GetConfig().memoryPassword } return p.dbPassword } func (p *Peer) StoreMessage(msg *meowlib.UserMessage, filenames []string) error { return StoreMessage(p, msg, filenames, p.GetDbPassword()) } func (p *Peer) GetFilePreview(filename string) ([]byte, error) { return FilePreview(filename, p.GetDbPassword()) } func (p *Peer) UpdateMessage(msg InternalUserMessage) error { return nil } func (p *Peer) LoadMessagesHistory(alreadyLoadedCount int, oldestMessageId int, qty int) ([]InternalUserMessage, error) { return GetMessagesHistory(p, alreadyLoadedCount, oldestMessageId, qty, p.GetDbPassword()) } func (p *Peer) LoadNewMessages(lastMessageId int) ([]*InternalUserMessage, error) { return GetNewMessages(p, lastMessageId, p.GetDbPassword()) } func (p *Peer) LoadMessage(uid string) (*InternalUserMessage, error) { return nil, nil } func (p *Peer) GetLastMessageUuid(msg []byte) { }