2018-03-09 20:44:13 +00:00
package model
import (
"crypto/rand"
2020-07-14 00:46:05 +00:00
"cwtch.im/cwtch/protocol/groups"
2018-10-05 03:18:34 +00:00
"encoding/base32"
2021-05-03 23:32:48 +00:00
"encoding/base64"
2019-01-29 20:56:59 +00:00
"encoding/hex"
2019-01-21 20:11:40 +00:00
"encoding/json"
2018-05-09 19:09:00 +00:00
"errors"
2020-02-10 22:09:24 +00:00
"git.openprivacy.ca/openprivacy/connectivity/tor"
2018-03-09 20:44:13 +00:00
"golang.org/x/crypto/ed25519"
2018-05-28 17:44:47 +00:00
"io"
2019-01-29 20:56:59 +00:00
"path/filepath"
2018-10-05 03:18:34 +00:00
"strings"
2018-05-30 17:41:02 +00:00
"sync"
2018-03-30 21:16:51 +00:00
"time"
2018-03-09 20:44:13 +00:00
)
2020-06-16 00:16:04 +00:00
// Authorization is a type determining client assigned authorization to a peer
type Authorization string
const (
2020-07-08 18:29:33 +00:00
// AuthUnknown is an initial state for a new unseen peer
2020-06-16 00:16:04 +00:00
AuthUnknown Authorization = "unknown"
// AuthApproved means the client has approved the peer, it can send messages to us, perform GetVals, etc
AuthApproved Authorization = "approved"
// AuthBlocked means the client has blocked the peer, it's messages and connections should be rejected
AuthBlocked Authorization = "blocked"
)
2018-03-15 16:33:26 +00:00
// PublicProfile is a local copy of a CwtchIdentity
2018-03-09 20:44:13 +00:00
type PublicProfile struct {
2019-10-18 23:56:10 +00:00
Name string
Ed25519PublicKey ed25519 . PublicKey
2020-06-16 00:16:04 +00:00
Authorization Authorization
DeprecatedBlocked bool ` json:"Blocked" `
2019-10-18 23:56:10 +00:00
Onion string
Attributes map [ string ] string
Timeline Timeline ` json:"-" `
LocalID string // used by storage engine
State string ` json:"-" `
lock sync . Mutex
2020-10-08 19:40:27 +00:00
UnacknowledgedMessages map [ string ] int
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
2018-03-09 20:44:13 +00:00
type Profile struct {
PublicProfile
2018-05-03 19:23:02 +00:00
Contacts map [ string ] * PublicProfile
2018-03-09 20:44:13 +00:00
Ed25519PrivateKey ed25519 . PrivateKey
Groups map [ string ] * Group
}
2018-11-28 19:50:32 +00:00
// MaxGroupMessageLength is the maximum length of a message posted to a server group.
// TODO: Should this be per server?
2019-01-19 21:09:38 +00:00
const MaxGroupMessageLength = 1800
2018-11-28 19:50:32 +00:00
2019-12-12 20:21:14 +00:00
// GenerateRandomID generates a random 16 byte hex id code
func GenerateRandomID ( ) string {
2019-01-29 20:56:59 +00:00
randBytes := make ( [ ] byte , 16 )
rand . Read ( randBytes )
return filepath . Join ( hex . EncodeToString ( randBytes ) )
}
2018-10-31 23:24:12 +00:00
func ( p * PublicProfile ) init ( ) {
2019-02-11 21:40:20 +00:00
if p . Attributes == nil {
p . Attributes = make ( map [ string ] string )
}
2020-10-08 19:40:27 +00:00
p . UnacknowledgedMessages = make ( map [ string ] int )
2019-12-12 20:21:14 +00:00
p . LocalID = GenerateRandomID ( )
2018-10-31 23:24:12 +00:00
}
// SetAttribute allows applications to store arbitrary configuration info at the profile level.
func ( p * PublicProfile ) SetAttribute ( name string , value string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
p . Attributes [ name ] = value
}
2020-07-23 21:40:44 +00:00
// IsServer returns true if the profile is associated with a server.
func ( p * PublicProfile ) IsServer ( ) ( isServer bool ) {
_ , isServer = p . GetAttribute ( string ( KeyTypeServerOnion ) )
return
2020-07-22 17:14:32 +00:00
}
2018-10-31 23:24:12 +00:00
// GetAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false.
func ( p * PublicProfile ) GetAttribute ( name string ) ( value string , exists bool ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
value , exists = p . Attributes [ name ]
return
}
2018-03-15 16:33:26 +00:00
// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
2018-03-09 20:44:13 +00:00
func GenerateNewProfile ( name string ) * Profile {
p := new ( Profile )
2019-01-21 20:11:40 +00:00
p . init ( )
2018-03-09 20:44:13 +00:00
p . Name = name
pub , priv , _ := ed25519 . GenerateKey ( rand . Reader )
p . Ed25519PublicKey = pub
p . Ed25519PrivateKey = priv
2020-02-10 22:09:24 +00:00
p . Onion = tor . GetTorV3Hostname ( pub )
2018-03-09 20:44:13 +00:00
2018-05-03 19:23:02 +00:00
p . Contacts = make ( map [ string ] * PublicProfile )
2018-05-09 19:09:00 +00:00
p . Contacts [ p . Onion ] = & p . PublicProfile
2018-03-09 20:44:13 +00:00
p . Groups = make ( map [ string ] * Group )
return p
}
// AddContact allows direct manipulation of cwtch contacts
2018-05-03 19:23:02 +00:00
func ( p * Profile ) AddContact ( onion string , profile * PublicProfile ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-10-31 23:24:12 +00:00
profile . init ( )
2020-11-05 21:26:03 +00:00
// We expect callers to verify addresses before we get to this point, so if this isn't a
// valid address this is a noop.
if tor . IsValidHostname ( onion ) {
decodedPub , err := base32 . StdEncoding . DecodeString ( strings . ToUpper ( onion [ : 56 ] ) )
if err == nil {
profile . Ed25519PublicKey = ed25519 . PublicKey ( decodedPub [ : 32 ] )
p . Contacts [ onion ] = profile
}
}
2018-05-30 17:41:02 +00:00
p . lock . Unlock ( )
}
2019-08-07 05:27:11 +00:00
// DeleteContact deletes a peer contact
func ( p * Profile ) DeleteContact ( onion string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
delete ( p . Contacts , onion )
}
// DeleteGroup deletes a group
func ( p * Profile ) DeleteGroup ( groupID string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
delete ( p . Groups , groupID )
}
2018-05-30 17:41:02 +00:00
// RejectInvite rejects and removes a group invite
func ( p * Profile ) RejectInvite ( groupID string ) {
p . lock . Lock ( )
delete ( p . Groups , groupID )
p . lock . Unlock ( )
}
2019-10-18 23:56:10 +00:00
// AddSentMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
func ( p * Profile ) AddSentMessageToContactTimeline ( onion string , messageTxt string , sent time . Time , eventID string ) * Message {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
now := time . Now ( )
sig := p . SignMessage ( onion + messageTxt + sent . String ( ) + now . String ( ) )
message := & Message { PeerID : p . Onion , Message : messageTxt , Timestamp : sent , Received : now , Signature : sig , Acknowledged : false }
2020-10-07 20:53:22 +00:00
if contact . UnacknowledgedMessages == nil {
2020-10-08 19:40:27 +00:00
contact . UnacknowledgedMessages = make ( map [ string ] int )
2019-10-18 23:56:10 +00:00
}
2020-10-08 19:51:17 +00:00
contact . Timeline . Insert ( message )
2020-10-08 20:08:20 +00:00
contact . UnacknowledgedMessages [ eventID ] = contact . Timeline . Len ( ) - 1
2019-10-18 23:56:10 +00:00
return message
}
return nil
}
2018-11-21 21:23:59 +00:00
// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
2019-10-18 23:56:10 +00:00
func ( p * Profile ) AddMessageToContactTimeline ( onion string , messageTxt string , sent time . Time ) ( message * Message ) {
2018-11-21 21:23:59 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
// We don't really need a Signature here, but we use it to maintain order
now := time . Now ( )
2019-10-18 23:56:10 +00:00
sig := p . SignMessage ( onion + messageTxt + sent . String ( ) + now . String ( ) )
if ok {
message = & Message { PeerID : onion , Message : messageTxt , Timestamp : sent , Received : now , Signature : sig , Acknowledged : true }
contact . Timeline . Insert ( message )
}
return
}
// ErrorSentMessageToPeer sets a sent message's error message and removes it from the unacknowledged list
func ( p * Profile ) ErrorSentMessageToPeer ( onion string , eventID string , error string ) {
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
2018-11-21 21:23:59 +00:00
if ok {
2020-10-08 19:40:27 +00:00
mIdx , ok := contact . UnacknowledgedMessages [ eventID ]
2019-10-18 23:56:10 +00:00
if ok {
2020-10-08 19:40:27 +00:00
p . Timeline . Messages [ mIdx ] . Error = error
2020-10-07 20:53:22 +00:00
delete ( contact . UnacknowledgedMessages , eventID )
2019-10-18 23:56:10 +00:00
}
}
}
// AckSentMessageToPeer sets mesage to a peer as acknowledged
2021-05-03 18:35:35 +00:00
func ( p * Profile ) AckSentMessageToPeer ( onion string , eventID string ) int {
2019-10-18 23:56:10 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
2020-10-08 19:40:27 +00:00
mIdx , ok := contact . UnacknowledgedMessages [ eventID ]
2019-10-18 23:56:10 +00:00
if ok {
2020-10-08 20:08:20 +00:00
contact . Timeline . Messages [ mIdx ] . Acknowledged = true
2020-10-07 20:53:22 +00:00
delete ( contact . UnacknowledgedMessages , eventID )
2021-05-03 18:35:35 +00:00
return mIdx
2019-10-18 23:56:10 +00:00
}
}
2021-05-03 18:35:35 +00:00
return - 1
2019-10-18 23:56:10 +00:00
}
// AddGroupSentMessageError searches matching groups for the message by sig and marks it as an error
func ( p * Profile ) AddGroupSentMessageError ( groupServer string , signature string , error string ) {
for _ , group := range p . Groups {
if group . GroupServer == groupServer {
if group . ErrorSentMessage ( [ ] byte ( signature ) , error ) {
break
}
2018-11-21 21:23:59 +00:00
}
}
}
2018-05-30 17:41:02 +00:00
// AcceptInvite accepts a group invite
2018-06-03 19:02:42 +00:00
func ( p * Profile ) AcceptInvite ( groupID string ) ( err error ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
group , ok := p . Groups [ groupID ]
if ok {
group . Accepted = true
2018-06-03 19:02:42 +00:00
} else {
err = errors . New ( "group does not exist" )
2018-05-30 17:41:02 +00:00
}
2018-06-03 19:02:42 +00:00
return
2018-05-30 17:41:02 +00:00
}
2018-06-19 22:28:44 +00:00
// GetGroups returns an unordered list of group IDs associated with this profile.
func ( p * Profile ) GetGroups ( ) [ ] string {
p . lock . Lock ( )
defer p . lock . Unlock ( )
var keys [ ] string
for onion := range p . Groups {
keys = append ( keys , onion )
}
return keys
}
// GetContacts returns an unordered list of contact onions associated with this profile.
func ( p * Profile ) GetContacts ( ) [ ] string {
p . lock . Lock ( )
defer p . lock . Unlock ( )
var keys [ ] string
for onion := range p . Contacts {
if onion != p . Onion {
keys = append ( keys , onion )
}
}
return keys
}
2020-06-16 00:16:04 +00:00
// SetContactAuthorization sets the authoirization level of a peer
func ( p * Profile ) SetContactAuthorization ( onion string , auth Authorization ) ( err error ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
contact , ok := p . Contacts [ onion ]
if ok {
2020-06-16 00:16:04 +00:00
contact . Authorization = auth
2018-06-03 19:02:42 +00:00
} else {
err = errors . New ( "peer does not exist" )
2018-05-30 17:41:02 +00:00
}
2018-06-03 19:02:42 +00:00
return
2018-05-30 17:41:02 +00:00
}
2020-06-16 00:16:04 +00:00
// GetContactAuthorization returns the contact's authorization level
func ( p * Profile ) GetContactAuthorization ( onion string ) Authorization {
2019-08-07 18:49:44 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
2020-06-16 00:16:04 +00:00
return contact . Authorization
2019-08-07 18:49:44 +00:00
}
2020-06-16 00:16:04 +00:00
return AuthUnknown
2019-08-07 18:49:44 +00:00
}
2020-06-16 00:16:04 +00:00
// ContactsAuthorizations calculates a list of Peers who are at the supplied auth levels
func ( p * Profile ) ContactsAuthorizations ( authorizationFilter ... Authorization ) map [ string ] Authorization {
authorizations := map [ string ] Authorization { }
2019-05-15 20:12:11 +00:00
for _ , contact := range p . GetContacts ( ) {
c , _ := p . GetContact ( contact )
2020-06-16 00:16:04 +00:00
authorizations [ c . Onion ] = c . Authorization
2018-05-30 17:41:02 +00:00
}
2020-06-16 00:16:04 +00:00
return authorizations
2018-05-30 17:41:02 +00:00
}
2018-06-15 16:21:07 +00:00
// GetContact returns a contact if the profile has it
2018-05-30 17:41:02 +00:00
func ( p * Profile ) GetContact ( onion string ) ( * PublicProfile , bool ) {
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
contact , ok := p . Contacts [ onion ]
return contact , ok
2018-03-09 20:44:13 +00:00
}
2018-05-16 20:53:09 +00:00
// VerifyGroupMessage confirms the authenticity of a message given an onion, message and signature.
2018-06-22 18:11:23 +00:00
func ( p * Profile ) VerifyGroupMessage ( onion string , groupID string , message string , timestamp int32 , ciphertext [ ] byte , signature [ ] byte ) bool {
2019-10-31 21:39:31 +00:00
group := p . GetGroup ( groupID )
2018-06-22 18:11:23 +00:00
if group == nil {
return false
}
2018-05-03 04:12:45 +00:00
if onion == p . Onion {
2018-06-22 18:11:23 +00:00
m := groupID + group . GroupServer + string ( ciphertext )
2018-05-03 04:12:45 +00:00
return ed25519 . Verify ( p . Ed25519PublicKey , [ ] byte ( m ) , signature )
}
2018-10-05 03:18:34 +00:00
m := groupID + group . GroupServer + string ( ciphertext )
decodedPub , err := base32 . StdEncoding . DecodeString ( strings . ToUpper ( onion ) )
2019-11-08 00:39:27 +00:00
if err == nil && len ( decodedPub ) >= 32 {
2018-10-05 03:18:34 +00:00
return ed25519 . Verify ( decodedPub [ : 32 ] , [ ] byte ( m ) , signature )
2018-03-31 19:33:32 +00:00
}
return false
2018-03-30 21:16:51 +00:00
}
2018-03-15 16:33:26 +00:00
// SignMessage takes a given message and returns an Ed21159 signature
2018-03-09 20:44:13 +00:00
func ( p * Profile ) SignMessage ( message string ) [ ] byte {
sig := ed25519 . Sign ( p . Ed25519PrivateKey , [ ] byte ( message ) )
return sig
}
2018-10-05 03:18:34 +00:00
// StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed
2018-03-15 16:33:26 +00:00
// invite which can be sent on the wire.
2021-05-03 23:32:48 +00:00
func ( p * Profile ) StartGroup ( server string ) ( groupID string , invite string , err error ) {
2018-09-27 00:08:54 +00:00
group , err := NewGroup ( server )
if err != nil {
2021-05-03 23:32:48 +00:00
return "" , "" , err
2018-09-27 00:08:54 +00:00
}
2018-03-09 20:44:13 +00:00
groupID = group . GroupID
2018-05-16 20:53:09 +00:00
signedGroupID := p . SignMessage ( groupID + server )
group . SignGroup ( signedGroupID )
2021-05-03 23:32:48 +00:00
invite , err = group . Invite ( )
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-09 19:09:00 +00:00
p . Groups [ group . GroupID ] = group
2018-03-09 20:44:13 +00:00
return
}
2019-10-31 21:39:31 +00:00
// GetGroup a pointer to a Group by the group Id, returns nil if no group found.
func ( p * Profile ) GetGroup ( groupID string ) ( g * Group ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-06-03 19:02:42 +00:00
defer p . lock . Unlock ( )
2018-05-30 17:41:02 +00:00
g = p . Groups [ groupID ]
2018-06-03 19:02:42 +00:00
return
2018-03-15 20:53:22 +00:00
}
2019-09-19 23:14:35 +00:00
// ProcessInvite adds a new group invite to the profile. returns the new group ID
2021-05-03 23:32:48 +00:00
func ( p * Profile ) ProcessInvite ( invite string ) ( string , error ) {
if strings . HasPrefix ( invite , "torv3" ) {
data , err := base64 . StdEncoding . DecodeString ( invite [ 5 : ] )
if err == nil {
var gci groups . GroupInvite
err := json . Unmarshal ( data , & gci )
if err == nil {
group := new ( Group )
group . Version = CurrentGroupVersion
group . GroupID = gci . GroupID
group . LocalID = GenerateRandomID ( )
group . SignedGroupID = gci . SignedGroupID
copy ( group . GroupKey [ : ] , gci . SharedKey [ : ] )
group . GroupServer = gci . ServerHost
group . Accepted = false
group . Attributes = make ( map [ string ] string )
p . AddGroup ( group )
return group . GroupID , nil
}
}
2019-09-19 23:14:35 +00:00
}
2021-05-03 23:32:48 +00:00
return "" , errors . New ( "unsupported exported group type" )
2018-03-09 20:44:13 +00:00
}
2018-03-31 19:33:32 +00:00
// AddGroup is a convenience method for adding a group to a profile.
2018-03-09 20:44:13 +00:00
func ( p * Profile ) AddGroup ( group * Group ) {
2021-05-03 23:32:48 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
2019-01-19 23:16:38 +00:00
_ , exists := p . Groups [ group . GroupID ]
2018-03-15 20:53:22 +00:00
if ! exists {
p . Groups [ group . GroupID ] = group
}
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
2019-10-18 23:56:10 +00:00
// If successful, adds the message to the group's timeline
2019-02-03 03:24:42 +00:00
func ( p * Profile ) AttemptDecryption ( ciphertext [ ] byte , signature [ ] byte ) ( bool , string , * Message , bool ) {
2018-03-15 20:53:22 +00:00
for _ , group := range p . Groups {
success , dgm := group . DecryptMessage ( ciphertext )
2018-03-09 20:44:13 +00:00
if success {
2020-07-14 00:46:05 +00:00
verified := p . VerifyGroupMessage ( dgm . Onion , group . GroupID , dgm . Text , int32 ( dgm . Timestamp ) , ciphertext , signature )
2018-10-05 03:18:34 +00:00
// So we have a message that has a valid group key, but the signature can't be verified.
// The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious)
// Either way, someone who has the private key is being detectably bad so we are just going to throw this message away and mark the group as Compromised.
if ! verified {
group . Compromised ( )
2019-02-03 03:24:42 +00:00
return false , group . GroupID , nil , false
2018-10-05 03:18:34 +00:00
}
2019-02-03 03:24:42 +00:00
message , seen := group . AddMessage ( dgm , signature )
return true , group . GroupID , message , seen
2018-03-09 20:44:13 +00:00
}
}
2019-01-19 21:09:38 +00:00
// If we couldn't find a group to decrypt the message with we just return false. This is an expected case
2019-02-03 03:24:42 +00:00
return false , "" , nil , false
2018-03-09 20:44:13 +00:00
}
2018-05-28 17:44:47 +00:00
func getRandomness ( arr * [ ] byte ) {
if _ , err := io . ReadFull ( rand . Reader , ( * arr ) [ : ] ) ; err != nil {
2020-09-28 22:07:22 +00:00
if err != nil {
// If we can't do randomness, just crash something is very very wrong and we are not going
// to resolve it here....
panic ( err . Error ( ) )
}
2018-05-28 17:44:47 +00:00
}
}
2018-03-15 16:33:26 +00:00
// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and
// profile
2018-06-22 18:11:23 +00:00
func ( p * Profile ) EncryptMessageToGroup ( message string , groupID string ) ( [ ] byte , [ ] byte , error ) {
2018-11-28 19:50:32 +00:00
if len ( message ) > MaxGroupMessageLength {
return nil , nil , errors . New ( "group message is too long" )
}
2019-10-31 21:39:31 +00:00
group := p . GetGroup ( groupID )
2018-05-09 19:09:00 +00:00
if group != nil {
timestamp := time . Now ( ) . Unix ( )
2018-06-22 18:11:23 +00:00
2018-05-09 19:09:00 +00:00
var prevSig [ ] byte
2019-02-03 01:18:33 +00:00
if len ( group . Timeline . Messages ) > 0 {
prevSig = group . Timeline . Messages [ len ( group . Timeline . Messages ) - 1 ] . Signature
2018-05-09 19:09:00 +00:00
} else {
2018-05-28 17:44:47 +00:00
prevSig = group . SignedGroupID
2018-05-09 19:09:00 +00:00
}
2018-05-28 17:44:47 +00:00
2018-11-28 19:50:32 +00:00
lenPadding := MaxGroupMessageLength - len ( message )
2018-05-28 17:44:47 +00:00
padding := make ( [ ] byte , lenPadding )
getRandomness ( & padding )
2020-07-14 00:46:05 +00:00
dm := & groups . DecryptedGroupMessage {
Onion : p . Onion ,
Text : message ,
SignedGroupID : group . SignedGroupID [ : ] ,
Timestamp : uint64 ( timestamp ) ,
2018-05-09 19:09:00 +00:00
PreviousMessageSig : prevSig ,
2018-05-28 17:44:47 +00:00
Padding : padding [ : ] ,
2018-05-09 19:09:00 +00:00
}
2019-11-08 00:39:27 +00:00
2018-09-27 00:08:54 +00:00
ciphertext , err := group . EncryptMessage ( dm )
if err != nil {
return nil , nil , err
}
2018-06-22 18:11:23 +00:00
signature := p . SignMessage ( groupID + group . GroupServer + string ( ciphertext ) )
2019-02-20 20:03:04 +00:00
group . AddSentMessage ( dm , signature )
2018-06-22 18:11:23 +00:00
return ciphertext , signature , nil
2018-03-15 20:53:22 +00:00
}
2018-06-22 18:11:23 +00:00
return nil , nil , errors . New ( "group does not exist" )
2018-03-09 20:44:13 +00:00
}
2019-01-21 20:11:40 +00:00
2019-07-19 17:27:50 +00:00
// GetCopy returns a full deep copy of the Profile struct and its members (timeline inclusion control by arg)
func ( p * Profile ) GetCopy ( timeline bool ) * Profile {
2019-01-21 20:11:40 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
newp := new ( Profile )
bytes , _ := json . Marshal ( p )
json . Unmarshal ( bytes , & newp )
2019-07-19 17:27:50 +00:00
if timeline {
for groupID := range newp . Groups {
newp . Groups [ groupID ] . Timeline = * p . Groups [ groupID ] . Timeline . GetCopy ( )
}
2020-07-08 18:29:33 +00:00
for peerID := range newp . Contacts {
newp . Contacts [ peerID ] . Timeline = * p . Contacts [ peerID ] . Timeline . GetCopy ( )
}
2019-07-19 17:27:50 +00:00
}
2019-01-21 20:11:40 +00:00
return newp
}