Compare commits

...

101 Commits

Author SHA1 Message Date
ycc
5aec7b3ad4 refactor router
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-10 11:18:42 +02:00
ycc
d709cb9454 Update PrepareUserMessage function to include replyToUid parameter 2024-04-09 20:58:29 +02:00
ycc
05df08efcb Add GetFilePreview method to Peer
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-06 16:04:41 +02:00
ycc
813611bde7 add preview
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-06 15:44:30 +02:00
ycc
7fa997d443 message preview in progress
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-06 12:55:27 +02:00
ycc
2513f0303a Refactor PrepareUserMessage function to store message after processing outbound user message, store is destructive for a usermessage
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-06 10:13:25 +02:00
ycc
99a9aa14af store securefiles in messages and not filenames
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-05 23:07:23 +02:00
ycc
09892709ec Fix function name and update file path handling
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-05 18:07:00 +02:00
ycc
3ac6b02e56 Refactor file detachment logic in ReadMessage function
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-01 20:56:45 +02:00
ycc
8fca09d853 Add securefiles directory for storing encrypted files
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 19:39:50 +02:00
ycc
aaa4d88a2f Refactor message storage and retrieval
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 19:04:37 +02:00
ycc
3bf75eb990 Fix invitationCreateHelper.go to use the correct name parameter
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 18:36:51 +02:00
ycc
54c36c68ad Fix invitation answer message building with inv_id
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 18:22:17 +02:00
ycc
caab80f346 Fix condition for saving server message in backgroundHelper.go for nil safety
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 17:58:41 +02:00
ycc
dfa2b5fa83 Set invitation UUID in BuildInvitationAnswerMessage function
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 17:24:21 +02:00
ycc
2ac70d5448 Fix error handling in GetAnswerToInvitation function
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 17:05:37 +02:00
ycc
2a246744db Fix invitation handling in RedisRouter
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 16:24:25 +02:00
ycc
b4f7071990 Update Redis key in GetAnswerToInvitation function
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 16:13:08 +02:00
ycc
903702c719 Fix server public key assignment and rename Wipe() to WipeFolder()
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-31 15:52:50 +02:00
ycc
54b932e9c1 Add Wipe function to Identity
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-30 22:44:59 +01:00
ycc
13cfda928d Remove message files after ReadMessage processing
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 21:08:09 +01:00
ycc
b556cd0361 Add logging and create files folder for detach files
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 20:49:18 +01:00
ycc
657fdbbf48 readmessage cleanup
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 19:31:48 +01:00
ycc
a65d4f1a69 bugfix empty messages saved...
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 19:25:15 +01:00
ycc
a5cfbf854d add identity folder creation
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 18:26:41 +01:00
ycc
385c5f3298 Remove temporary patch for generating UUID in Identity.Save() function
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 18:16:34 +01:00
ycc
31df45e771 store msg db and inbox by identity
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 18:07:06 +01:00
ycc
0b8e3c4c90 declare identity to ease debug
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 16:42:03 +01:00
ycc
ce479cc5b9 Remove unused variable in ReadMessage function
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 16:22:26 +01:00
ycc
bdfa8c7bb1 ReadMessage fix
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 16:18:24 +01:00
ycc
8300a699a3 background return improve
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 16:17:07 +01:00
ycc
729ba7e02a add server pub key to background process
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 15:59:33 +01:00
ycc
b15f571938 server invitatio
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 15:40:46 +01:00
ycc
ead810e666 server store invitation answer by lookupkey + refactor background check
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-28 18:41:30 +01:00
ycc
4b412ae0f3 InvitationAnswerMessageReadResponse
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-28 00:45:40 +01:00
ycc
e4efff1824 Server side invitation step 3/4 preserve From public key / Client finalize in progress
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 23:42:56 +01:00
ycc
788512c391 Server invitation step 3/4
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 20:22:56 +01:00
ycc
6bf6fadaaa fix invitation code separator
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 15:29:52 +01:00
ycc
0070a64d5f server from uid weak patch
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 15:23:24 +01:00
ycc
1398c6040a moewurl bugfixes
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 15:15:41 +01:00
ycc
12ad5ced49 meowurls
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:57:33 +01:00
ycc
53145f1c5e create server from uid added
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:12:32 +01:00
ycc
6867086c4f Http helper function added
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-25 13:24:43 +01:00
ycc
f80411bf21 add uid to peer + helper methods + http
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-23 20:09:14 +01:00
ycc
0a70206b11 handle no db yet in message retrieve
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-05 23:15:19 +01:00
ycc
f40f6520d2 add last message to peers
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-05 20:15:48 +01:00
ycc
b47ef2480c typo... useful line ?
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-02 18:11:15 +01:00
ycc
cba13ad91a adding usermessage answer_to_usermessage in status
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-02 18:05:13 +01:00
ycc
1ba84dcefc bugfix
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-02 14:10:16 +01:00
ycc
0c0aa6e807 try to avoid protobuf omitted
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-02 10:45:05 +01:00
ycc
3467ea15d9 Change Peers to * Peers in identity to retrieve pointer to the real peer
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-02 10:07:59 +01:00
ycc
aa63bb745f refactor
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-29 21:14:46 +01:00
ycc
f20681adab Store messages with DbMessage Type
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-29 21:03:15 +01:00
ycc
4b3d7548bd store fix + add peer invitationPending Check
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-25 20:15:35 +01:00
ycc
c4b61e16c5 Add invitation message to CheckInvitation function
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-20 22:44:19 +01:00
ycc
9dcb579d93 Refactor GetMessagesHistory and LoadLastMessages functions to use slice of InternalUserMessage instead of pointer to InternalUserMessage.
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-20 20:30:55 +01:00
ycc
defafcf996 Refactor invitation handling in router.go
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-20 20:25:07 +01:00
ycc
db4c3cbbc8 From moved to invitation message
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-18 13:56:33 +01:00
ycc
07dfae8f0e message storage and from removed from packedusermessage (weakness)
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-18 13:46:11 +01:00
ycc
05cc2ee218 Refactor client message handling and storage
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-17 19:30:25 +01:00
ycc
034dcf5215 Add invitation to user message and comment out debug print statements
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-16 19:03:45 +01:00
ycc
c58199385e Addinf trace function for invitation debug
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-15 23:24:07 +01:00
ycc
b6b9dc238a protobug03
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 23:25:37 +01:00
ycc
9ec682d708 protobug 02
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 22:40:41 +01:00
ycc
465a366e79 protobug test01
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 22:26:52 +01:00
ycc
2969227656 new version of from to trick protobuf ?
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 22:15:25 +01:00
ycc
9561531c7c temporary disable from for cli debug
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 22:11:53 +01:00
ycc
8c8326780f Add 'from' field to PackedUserMessage
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-13 21:00:50 +01:00
ycc
0466b1fe05 Debug GetRequestJobs function and add unit tests
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-12 20:02:02 +01:00
ycc
69a07d77d5 Fix error handling in GetMyContact and GetContact functions
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 23:35:41 +01:00
ycc
a19f228c8e Fix issue with loading contact cards from peer
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 23:30:29 +01:00
ycc
bcb3489de4 BugFix pullservers allocation in finalize
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 22:58:25 +01:00
ycc
0fd7548ba4 Contact struct removed from peer
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 22:36:25 +01:00
ycc
24183ff581 Remove MyContact contactcard from peer
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 15:05:02 +01:00
ycc
df9c6b5d46 Refactor GetUid() method and add new tests for server storage
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-10 11:32:44 +01:00
ycc
423ef5c4b1 Update badger db path
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-09 23:28:43 +01:00
ycc
b87c0bff3e add LoadAllServerCards from badger
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-09 22:30:00 +01:00
ycc
978b6fdfd1 badgerdb messageservers storage
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-08 22:17:16 +01:00
ycc
93e972900f Refactor structs with some getters/setters - Peers part
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-07 16:08:24 +01:00
ycc
f8a1cb6f68 begin adding device to device communication
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-18 22:43:41 +01:00
ycc
44661de993 conversation_request cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-14 15:49:24 +01:00
ycc
48b2e78b41 request jobs generation
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-12 23:17:34 +01:00
ycc
a9f3b548e5 permission fixes
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-05 17:01:36 +01:00
ycc
379b40b2fb answer invitation bugfix
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-03 23:11:23 +01:00
ycc
cbedad7178 typo, + invitation return value changed from ContactCard to Peer
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-03 19:40:38 +01:00
ycc
9283764f42 proto contactcard to _
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-01 22:45:54 +01:00
ycc
25cf1808e3 added an invitation message
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-01 13:50:00 +01:00
ycc
6788487368 restoring, pb was in client code
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-31 23:46:24 +01:00
ycc
e406010374 #2
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-31 23:39:30 +01:00
ycc
289e39c677 invitation bytearray copy debug #1
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-31 23:28:10 +01:00
ycc
6511ff6280 add debug test code for redis binary store
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-31 22:44:21 +01:00
ycc
206dda0761 fix missing init
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-31 20:33:01 +01:00
ycc
7a6c1cd085 temporary remove invitation expiry
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-31 20:22:56 +01:00
ycc
04e81fcef1 lib invitation improvement for GUI
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-31 18:43:55 +01:00
ycc
0998845817 Invitation process store my server card+ server invitation fields
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-31 10:24:15 +01:00
ycc
922668e2a3 Invitation struct initt fix
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-30 19:08:44 +01:00
ycc
5eb6be1415 test changing cameCase for _ in protobuf servercard
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-28 16:57:34 +01:00
ycc
a3b2473eed avatar field rename
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-26 17:51:42 +01:00
ycc
043980042d peer cleanup
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-26 13:54:20 +01:00
ycc
58118bd6bb dependency upgrade
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-25 16:46:03 +01:00
ycc
86d51d6dfb cleanup
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-25 16:44:58 +01:00
48 changed files with 3120 additions and 852 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ client/test.cfg
.VSCodeCouter/
meowlib-sources.jar
meowlib.aar
client/test.db

View File

@ -19,3 +19,10 @@ run the shell scripts
# Tests
# Design notes
Config is written as a json file
Identity is stored as an encrypted json file
Message servers (messaging and my contact's messaging) are stored in an encrypted badger db with server url as key
Received servers are stored in a sqlite db for selective searches, with storage limits
Messages are stored in several badger? or sqlite? db per user with send/receive time as key

View File

@ -8,8 +8,10 @@ import (
type Config struct {
// UserConfig
SavePassword bool `json:"save_password,omitempty"`
SavedPassword string `json:"saved_password,omitempty"`
SavePassword bool `json:"save_password,omitempty"`
SavedPassword string `json:"saved_password,omitempty"`
PasswordTip string `json:"password_tip,omitempty"`
PasswordTipUnlock string `json:"password_tip_unlock,omitempty"`
// Technical
IdentityFile string `json:"identity_file,omitempty"`
StoragePath string `json:"storage_path,omitempty"`
@ -42,9 +44,9 @@ type Config struct {
DbSuffix string `json:"db_suffix,omitempty"`
// Inner
memoryPassword string `json:"memory_password,omitempty"`
additionalPasswords []string `json:"additional_passwords,omitempty"`
me *Identity `json:"me,omitempty"`
memoryPassword string
additionalPasswords []string
me *Identity
}
var instance *Config
@ -74,7 +76,7 @@ func (c *Config) Save(filename string) error {
if err != nil {
return err
}
os.WriteFile(filename, data, 0644)
os.WriteFile(filename, data, 0600)
if err != nil {
return err
}

38
client/dbmessage.go Normal file
View File

@ -0,0 +1,38 @@
package client
import (
"forge.redroom.link/yves/meowlib"
)
func DbMessageToInternalUserMessage(id int64, dbFile string, dbm *meowlib.DbMessage) *InternalUserMessage {
var ium InternalUserMessage
ium.Dbid = id
ium.Dbfile = dbFile
if dbm.Outbound {
ium.Outbound = true
} else {
ium.Outbound = false
}
ium.Message = string(dbm.Data)
ium.ConversationStatus = dbm.Status
ium.Contact = dbm.Contact
ium.CurrentLocation = dbm.CurrentLocation
ium.Messagetype = dbm.Type
ium.Appdata = dbm.Appdata
ium.FilePaths = dbm.FilePaths
return &ium
}
func UserMessageToDbMessage(outbound bool, um *meowlib.UserMessage, filepaths []string) *meowlib.DbMessage {
var dbm meowlib.DbMessage
dbm.Outbound = outbound
dbm.Type = um.Type
dbm.Data = um.Data
dbm.Appdata = um.Appdata
dbm.Contact = um.Contact
dbm.CurrentLocation = um.CurrentLocation
dbm.Status = um.Status
dbm.FilePaths = filepaths
return &dbm
}

View File

@ -0,0 +1,185 @@
package helpers
import (
"errors"
"log"
"os"
"path/filepath"
"strconv"
"time"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)
type ReceivedMessage struct {
Text string
files []string
Server string
Sent uint64
Received uint64
LocalUuid string
LocalSequence uint64
AppData string
Location meowlib.Location
}
// CheckForMessages checks for messages on a single server
func CheckForMessages(storage_path string, job *client.RequestsJob) (int, string, error) {
count := 0
// if folder does not exist, create it
if _, err := os.Stat(filepath.Join(storage_path, "inbox")); os.IsNotExist(err) {
err := os.MkdirAll(filepath.Join(storage_path, "inbox"), 0700)
if err != nil {
return -1, "CheckMessages: MkdirAll", err
}
}
//convert server to a server object
var crl []*meowlib.ConversationRequest
// build conversation requests
if job.LookupKeys != nil {
for _, key := range job.LookupKeys {
var cr meowlib.ConversationRequest
cr.LookupKey = key.Public
cr.SendTimestamp = time.Now().UTC().Unix()
// todo sign it
//cr.LookupSignature =
crl = append(crl, &cr)
}
// get server public key
if job.Server.PublicKey == "" {
key, err := meowlib.HttpGetId(job.Server.Url)
if err != nil {
return -1, "CheckMessages: HttpGetId", err
}
job.Server.PublicKey = key["publicKey"]
}
// build server message
var toSrv meowlib.ToServerMessage
toSrv.PullRequest = crl
toSrv.From = job.Server.UserKp.Public
data, err := job.Server.ProcessOutboundMessage(&toSrv)
if err != nil {
return -1, "CheckMessages: ProcessOutboundMessage", err
}
response, err := meowlib.HttpPostMessage(job.Server.Url, data)
if err != nil {
return -1, "CheckMessages: httpPostMessage", err
}
fs_msg, err := job.Server.ProcessInboundServerResponse(response)
if err != nil {
return -1, "CheckMessages: ProcessInboundServerResponse", err
}
if len(fs_msg.Chat) > 0 || (fs_msg.Invitation != nil && fs_msg.Invitation.Step == 3) {
// chat or invitation answer => save the server message
out, err := proto.Marshal(fs_msg)
if err != nil {
return -1, "CheckMessages: protobuf marshal", err
}
if err := os.WriteFile(filepath.Join(storage_path, "inbox", strconv.FormatInt(time.Now().UTC().UnixNano(), 10)), out, 0644); err != nil {
return -1, "CheckMessages: WriteFile", err
}
}
count = len(fs_msg.Chat)
} else {
// manage non uszer messages like devices or server
}
return count, "", nil
}
// SaveCheckJobs
func SaveCheckJobs() (string, error) {
me := client.GetConfig().GetIdentity()
err := me.SaveBackgroundJob()
if err != nil {
return "CheckMessages: json.Marshal", err
}
return "", nil
}
// ReadMessage
func ReadMessage(messageFilename string) ([]string, []string, string, error) {
messagesOverview := []string{}
filenames := []string{}
identity := client.GetConfig().GetIdentity()
// read message file
msg, err := os.ReadFile(messageFilename)
if err != nil {
return nil, nil, "ReadMessage: ReadFile", err
}
// protobuf unmarshal message
var fromServerMessage meowlib.FromServerMessage
err = proto.Unmarshal(msg, &fromServerMessage)
if err != nil {
return nil, nil, "ReadMessage: Unmarshal FromServerMessage", err
}
// check if invitation answer
if fromServerMessage.Invitation != nil {
invitationGetAnswerReadResponse(fromServerMessage.Invitation)
}
// Chat messages
if len(fromServerMessage.Chat) > 0 {
for _, packedUserMessage := range fromServerMessage.Chat {
// find the peer with that lookup key
peer := identity.Peers.GetFromMyLookupKey(packedUserMessage.Destination)
if peer == nil {
return nil, nil, "ReadMessage: GetFromMyLookupKey", errors.New("no visible peer for that message")
}
// Unpack the message
usermsg, err := peer.ProcessInboundUserMessage(packedUserMessage.Payload, packedUserMessage.Signature)
if err != nil {
return nil, nil, "ReadMessage: ProcessInboundUserMessage", err
}
//fmt.Println("From:", usermsg.From)
//jsonUserMessage, _ := json.Marshal(usermsg)
//fmt.Println(string(jsonUserMessage))
peer = client.GetConfig().GetIdentity().Peers.GetFromPublicKey(usermsg.From)
// detach files
if usermsg.Files != nil {
// create files folder
if _, err := os.Stat(filepath.Join(client.GetConfig().StoragePath, identity.Uuid, "files")); os.IsNotExist(err) {
err = os.MkdirAll(filepath.Join(client.GetConfig().StoragePath, identity.Uuid, "files"), 0700)
if err != nil {
log.Fatal(err)
}
}
for _, file := range usermsg.Files {
filename := uuid.New().String() + "_" + file.Filename
filenames = append(filenames, peer.Name+" sent: "+filename)
// detach file
os.WriteFile(filepath.Join(client.GetConfig().StoragePath, identity.Uuid, "files", filename), file.Data, 0600)
}
//? result["invitation finalized"] = peer.Name
}
// user message
messagesOverview = append(messagesOverview, peer.Name+" > "+string(usermsg.Data))
// add message to storage
err = peer.StoreMessage(usermsg, filenames)
if err != nil {
return nil, nil, "ReadMessage: StoreMessage", err
}
}
}
err = os.Remove(messageFilename)
if err != nil {
return nil, nil, "ReadMessage: Remove", err
}
// list of messages & detached files
return messagesOverview, filenames, "", nil
}

View File

@ -0,0 +1 @@
package helpers

View File

@ -0,0 +1,153 @@
package helpers
import (
"C"
"fmt"
"os"
"strings"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
)
import (
"errors"
)
// InvitationAnswer
func InvitationAnswer(cc *meowlib.ContactCard, nickname string, myNickname string, serverUids []string) (*client.Peer, string, error) {
mynick := myNickname
// my nickname for that contact
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
// build my contact card for that friend
peer := client.GetConfig().GetIdentity().AnswerInvitation(mynick, nickname, serverUids, cc)
//peerstr, err := json.Marshal(peer)
//fmt.Println("InvitationAnswer: " + string(peerstr))
c := client.GetConfig()
c.GetIdentity().Save()
return peer, "", nil
}
// InvitationAnswerFile
func InvitationAnswerFile(invitationFile string, nickname string, myNickname string, serverUids []string) (string, error) {
format := "qr"
var filename string = ""
var cc *meowlib.ContactCard
c := client.GetConfig()
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
return "InvitationAnswerFile : os.Stat", err
}
if strings.HasSuffix(invitationFile, ".mwiv") {
format = "mwiv"
data, err := os.ReadFile(invitationFile)
if err != nil {
return "InvitationAnswerFile : os.ReadFile", err
}
cc, err = meowlib.NewContactCardFromCompressed(data)
if err != nil {
return "InvitationAnswerFile : NewContactCardFromCompressed", err
}
}
identity := client.GetConfig().GetIdentity()
if cc != nil {
isAnswer, proposed, received, _ := identity.CheckInvitation(cc)
if isAnswer {
fmt.Fprintln(os.Stdout, "This is already a response "+proposed+" to your invitation.")
fmt.Fprintln(os.Stdout, "You cannot answer again.")
fmt.Fprintln(os.Stdout, "You should finalize it by importing "+proposed+" contact card to your meow.")
fmt.Fprintln(os.Stdout, "Use : 'meow invitation finalize "+invitationFile+"' to do it.")
} else {
mynick := myNickname
// my nickname for that contact
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
response := identity.AnswerInvitation(mynick, nickname, serverUids, cc)
fmt.Fprintln(os.Stdout, "Invitation sent by "+received)
if format == "qr" {
filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".png"
response.GetMyContact().WriteQr(filename)
} else {
filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
response.GetMyContact().WriteCompressed(filename)
}
client.GetConfig().GetIdentity().Save()
}
}
return "", nil
}
// InvitationAnswerMessage
func InvitationAnswerMessage(invitationId string, invitationServerUid string, timeout int) ([]byte, string, error) {
// find the peer with that invitation id
var peer *client.Peer
for i := len(client.GetConfig().GetIdentity().Peers) - 1; i >= 0; i-- { //! to allow self invitation : testing only, findinc the received peer before myself
// for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
peer = client.GetConfig().GetIdentity().Peers[i]
break
}
}
if peer == nil {
// declare a custom go error for no peer found
return nil, "InvitationAnswerMessage: loop for peer", errors.New("no peer with that invitation id")
}
answermsg, err := peer.BuildInvitationAnswerMessage(peer.GetMyContact())
if err != nil {
return nil, "InvitationAnswerMessage: BuildInvitationAnswserMessage", err
}
// Server: get the invitation server
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationAnswerMessage: LoadServer", err
}
// this will be the invitation's payload
packedMsg, err := peer.ProcessOutboundUserMessage(answermsg)
if err != nil {
return nil, "InvitationAnswerMessage: ProcessOutboundUserMessage", err
}
// Creating Server message for transporting the user message
toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout)
if err != nil {
return nil, "InvitationAnswerMessage: BuildToServerMessageInvitationAnswer", err
}
// Server outbound processing
bytemsg, err := invitationServer.ProcessOutboundMessage(toServerMessage)
if err != nil {
return nil, "InvitationAnswerMessage: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationAnswerMessageReadResponse
// Called by the invitation receiver
// invitationData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func InvitationAnswerMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.Invitation, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationAnswerMessageReadResponse: LoadServer", err
}
// Server inbound processing : get the invitation server
serverMsg, err := server.ProcessInboundServerResponse(invitationData)
if err != nil {
return nil, "InvitationAnswerMessageReadResponse: ProcessInboundServerResponse", err
}
return serverMsg.Invitation, "", nil
}

View File

@ -0,0 +1,128 @@
package helpers
import (
"strings"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
)
// InvitationCheck
// todo
/*
func InvitationCheck(invitationdata []byte) *C.char {
var jsoninv map[string]interface{}
err := json.Unmarshal([]byte(C.GoString(invitationdata)), &jsoninv)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
var cc *meowlib.ContactCard
if _, err := os.Stat(jsoninv["filename"].(string)); os.IsNotExist(err) {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
if strings.HasSuffix(jsoninv["filename"].(string), ".mwiv") {
data, err := os.ReadFile(jsoninv["filename"].(string))
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
cc, err = meowlib.NewContactCardFromCompressed(data)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
}
identity := client.GetConfig().GetIdentity()
result := map[string]string{}
if cc != nil {
isAnswer, proposed, received, invitationMessage := identity.CheckInvitation(cc)
if isAnswer { // answer to infitation
result["type"] = "answer"
result["to"] = proposed
result["from"] = received
result["invitation_message"] = invitationMessage
//fmt.Fprintln(os.Stdout, "Invitation sent to "+proposed+" received with "+received+" as suggested nickname")
} else { // finalization message
result["type"] = "finalize"
result["from"] = received
//fmt.Fprintln(os.Stdout, "Invitation sent by "+received)
}
}
val, err := json.Marshal(result)
if err != nil {
return C.CString(errorToJson(err, "InvitationCheck: "))
}
return C.CString(string(val))
}*/
// InvitationGetMessage
// Called by the invitation receiver
// invitationUrl: the url of server holding the invitation
// serverPublicKey: the public key of the server holding the invitation
// invitationPassword: the password of the invitation
func InvitationGetMessage(invitationUrl string, serverPublicKey string, invitationPassword string) ([]byte, string, error) {
meowurl := strings.Split(invitationUrl[7:], "?")
serverurl := meowurl[0]
shortcode := meowurl[1]
srv := client.Server{}
// check if already in msg servers
dbsrv, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(serverurl)
if err != nil {
return nil, "InvitationGetMessage: LoadServer", err
}
if dbsrv == nil {
// create a server object with url & pubkey
srv.Url = serverurl
srv.PublicKey = serverPublicKey
srv.UserKp = meowlib.NewKeyPair()
// save it
err = client.GetConfig().GetIdentity().MessageServers.StoreServer(&srv)
if err != nil {
return nil, "InvitationGetMessage: StoreServer", err
}
} else {
if dbsrv.PublicKey != serverPublicKey {
dbsrv.PublicKey = serverPublicKey
}
srv = *dbsrv
}
// buildserver message
toSrvMsg, err := srv.BuildToServerMessageInvitationRequest(shortcode, invitationPassword)
if err != nil {
return nil, "InvitationGetMessage: BuildToServerMessageInvitationRequest", err
}
// processoutbound
bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg)
if err != nil {
return nil, "InvitationGetMessage: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationGetMessageReadResponse
// Called by the invitation receiver
// invitationData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func InvitationGetMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.ContactCard, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationGetMessageReadResponse: LoadServer", err
}
// Server inbound processing : get the invitation server
serverMsg, err := server.ProcessInboundServerResponse(invitationData)
if err != nil {
return nil, "InvitationGetMessageReadResponse: ProcessInboundServerResponse", err
}
// fmt.Println("Inbound OK, Invitation Step: ", serverMsg.Invitation.Step, len(serverMsg.Invitation.Payload))
// fmt.Println("Invitation Check")
// fmt.Println(hex.EncodeToString(serverMsg.Invitation.Payload))
// contactCard decode
cc, err := meowlib.NewContactCardFromCompressed(serverMsg.Invitation.Payload)
if err != nil {
return nil, "InvitationGetMessageReadResponse: NewContactCardFromCompressed", err
}
return cc, "", nil
}

View File

@ -0,0 +1,135 @@
package helpers
import (
"os"
"time"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
)
// InvitationCreatePeer creates a new peer and returns it
// Called by invitation initiator
// name: the name of the peer
// myNickname: my nickname for that peer
// invitationMessage: the message to send to the peer
// serverUids: the list of server uids
func InvitationCreatePeer(name string, myNickname string, invitationMessage string, serverUids []string) (*client.Peer, string, error) {
mynick := myNickname
if myNickname == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
// build my contact card for that friend
peer, err := client.GetConfig().GetIdentity().InvitePeer(mynick, name, serverUids, invitationMessage)
if err != nil {
return nil, "InvitationCreate: InvitePeer", err
}
client.GetConfig().GetIdentity().Save()
return peer, "", nil
}
// InvitationCreateFile creates a new peer and writes the invitation to a file
// Called by invitation initiator
// name: the name of the peer
// myNickname: my nickname for that peer
// invitationMessage: the message to send to the peer
// serverUids: the list of server uids
// format: the format of the file (qr or mwiv)
func InvitationCreateFile(name string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, string, error) {
peer, errdata, err := InvitationCreatePeer(name, myNickname, invitationMessage, serverUids)
if err != nil {
return nil, errdata, err
}
c := client.GetConfig()
var filename string = ""
if format == "qr" {
filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png"
err := peer.GetMyContact().WriteQr(filename)
if err != nil {
return nil, "InvitationCreateFile: WriteQr", err
}
} else {
filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv"
err := peer.GetMyContact().WriteCompressed(filename)
if err != nil {
return nil, "InvitationCreateFile: WriteCompressed", err
}
}
return peer, "", nil
}
// InvitationCreateMessage creates a new invitation message for an invited peer
// Called by invitation initiator
// invitationId: the invitation id of the peer
// invitationServerUid: the uid of the server for sending the invitation
// timeOut: the timeout for the invitation
// urlLen: the length of the invitation url
// password: the password for the invitation
func InvitationCreateMessage(invitationId string, invitationServerUid string, timeOut int, urlLen int, password string) ([]byte, string, error) {
// lookup for peer with "invitation_id"
var myContact *meowlib.ContactCard
for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
myContact = client.GetConfig().GetIdentity().Peers[i].GetMyContact()
break
}
}
// todo handle not found !!
// lookup for message server with "invitation_server"
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) //.GetServerByIdx(int(jsoninv["invitation_server"].(float64)))
if err != nil {
return nil, "InvitationCreateMessage: LoadServer", err
}
// call server.buildinviattion
msg, err := invitationServer.BuildToServerMessageInvitationCreation(myContact, password, timeOut, urlLen)
if err != nil {
return nil, "InvitationCreateMessage: BuildToServerMessageInvitationCreation", err
}
// fmt.Println("Invitation Create")
// fmt.Println(hex.EncodeToString(msg.Invitation.Payload))
bytemsg, err := invitationServer.ProcessOutboundMessage(msg)
if err != nil {
return nil, "InvitationCreateMessage: ProcessOutboundMessage", err
}
return bytemsg, "", nil
}
// InvitationCreateReadResponse reads the response of an invitation creation (url, expiry)
// Called by invitation initiator
// invitationServerUid: the uid of the server where we sent the invitation
// invitationResponse: the response we got from the server
func InvitationCreateReadResponse(invitationServerUid string, invitationResponse []byte) (*meowlib.Invitation, string, error) {
server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, "InvitationCreateReadResponse: LoadServer", err
}
serverMsg, err := server.ProcessInboundServerResponse(invitationResponse)
if err != nil {
return nil, "InvitationCreateReadResponse: ProcessInboundServerResponse", err
}
return serverMsg.Invitation, "", nil
}
// InvitationSetUrlInfo sets the url info for an invitation
// Called by invitation initiator
// invitationId: the invitation id of the peer
// url: the url of the invitation we got from the server
func InvitationSetUrlInfo(invitationId string, url string, expiry int64) {
for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ {
if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId {
client.GetConfig().GetIdentity().Peers[i].InvitationUrl = url
client.GetConfig().GetIdentity().Peers[i].InvitationExpiry = time.Unix(expiry, 0)
break
}
}
client.GetConfig().GetIdentity().Save()
}

View File

@ -0,0 +1,53 @@
package helpers
import (
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
"google.golang.org/protobuf/proto"
)
// Got it by the message background check
// => noInvitationGetAnswer
// invitationGetAnswerReadResponse
// Called by the initiator's background service only
// invitationAnswerData: the data received from the server
// invitationServerUid: the uid of the server holding the invitation
func invitationGetAnswerReadResponse(invitation *meowlib.Invitation) (*client.Peer, string, error) {
// decode the payload
var invitationAnswer meowlib.PackedUserMessage
err := proto.Unmarshal(invitation.Payload, &invitationAnswer)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: Unmarshal", err
}
// retreive user public key to check usermessage signature
// contactPublikKey := serverMsg.Invitation.From
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
peer.ContactPublicKey = invitation.From
if peer != nil {
// process the packed user message
usermsg, err := peer.ProcessInboundUserMessage(invitationAnswer.Payload, invitationAnswer.Signature)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: ProcessInboundUserMessage", err
}
decodedInvitation := usermsg.Invitation
var cc meowlib.ContactCard
err = proto.Unmarshal(decodedInvitation.Payload, &cc)
if err != nil {
return nil, "InvitationGetAnswerReadResponse: Unmarshal", err
}
// finalize the invitation
// id := client.GetConfig().GetIdentity()
peer.ContactLookupKey = cc.ContactPublicKey
peer.ContactEncryption = cc.EncryptionPublicKey
for _, server := range cc.PullServers {
peer.ContactPullServers = append(peer.ContactPullServers, server.GetUid())
}
client.GetConfig().GetIdentity().Save()
}
return peer, "", nil
}

View File

@ -0,0 +1,41 @@
package helpers
import "forge.redroom.link/yves/meowlib/client"
func PrepareUserMessage(message string, srvuid string, peer_idx int, replyToUid string, filelist []string) ([]byte, string, error) {
peer := client.GetConfig().GetIdentity().Peers[peer_idx]
srv, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(srvuid)
if err != nil {
return nil, "PrepareServerMessage : LoadServer", err
}
// Creating User message
usermessage, err := peer.BuildSimpleUserMessage([]byte(message))
if err != nil {
return nil, "PrepareServerMessage : BuildSimpleUserMessage", err
}
for _, file := range filelist {
err = usermessage.AddFile(file, client.GetConfig().Chunksize)
if err != nil {
return nil, "PrepareServerMessage : AddFile", err
}
}
usermessage.Status.AnswerToUuid = replyToUid
// Prepare cyphered + packed user message
packedMsg, err := peer.ProcessOutboundUserMessage(usermessage)
if err != nil {
return nil, "PrepareServerMessage : ProcessOutboundUserMessage", err
}
// Creating Server message for transporting the user message
toServerMessage := srv.BuildToServerMessageFromUserMessage(packedMsg)
data, err := srv.ProcessOutboundMessage(toServerMessage)
if err != nil {
return nil, "PrepareServerMessage : ProcessOutboundMessage", err
}
// Store message
err = peer.StoreMessage(usermessage, filelist)
if err != nil {
return nil, "PrepareServerMessage : StoreMessage", err
}
return data, "", nil
}

View File

@ -0,0 +1,30 @@
package helpers
import (
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
)
func HttpSendMessage(serverUid string, message []byte) ([]byte, error) {
id := client.GetConfig().GetIdentity()
srv, err := id.MessageServers.LoadServer(serverUid)
if err != nil {
return nil, err
}
// gettig server Public key if missing
if srv.PublicKey == "" {
srvdata, err := meowlib.HttpGetId(srv.Url)
if err != nil {
return nil, err
}
//print(srvdata["publicKey"])
srv.PublicKey = srvdata["publicKey"]
id.MessageServers.StoreServer(srv)
}
response, err := meowlib.HttpPostMessage(srv.Url, message)
if err != nil {
return nil, err
}
return response, nil
}

View File

@ -0,0 +1 @@
package helpers

View File

@ -0,0 +1,15 @@
package helpers
import (
"forge.redroom.link/yves/meowlib/client"
)
func LoadMessagesHistory(peer_id int) ([]client.InternalUserMessage, string, error) {
id := client.GetConfig().GetIdentity()
peer := id.Peers[peer_id]
msgs, err := peer.LoadMessagesHistory(0, 0, 50)
if err != nil {
return nil, "LoadLastMessages: LoadMessagesHistory", err
}
return msgs, "", nil
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"math/rand"
"os"
"path/filepath"
"time"
"forge.redroom.link/yves/meowlib"
@ -16,75 +17,106 @@ const maxHiddenCount = 30
type Identity struct {
Nickname string `json:"nickname,omitempty"`
DefaultAvatarUuid string `json:"default_avatar_uuid,omitempty"`
DefaultAvatar string `json:"default_avatar,omitempty"`
RootKp meowlib.KeyPair `json:"id_kp,omitempty"`
Status string `json:"status,omitempty"`
Peers PeerList `json:"peers,omitempty"`
HiddenPeers [][]byte `json:"hiddend_peers,omitempty"`
HiddenPeers [][]byte `json:"hidden_peers,omitempty"`
Device meowlib.KeyPair `json:"device,omitempty"`
KnownServers ServerList `json:"known_servers,omitempty"`
MessageServers ServerList `json:"message_servers,omitempty"`
MessageServers ServerStorage `json:"message_servers,omitempty"`
DefaultDbPassword string `json:"default_db_password,omitempty"`
DbPasswordStore bool `json:"db_password_store,omitempty"`
OwnedDevices PeerList `json:"owned_devices,omitempty"`
StaticMtkServerPaths []ServerList `json:"static_mtk_server_paths,omitempty"`
DynamicMtkServeRules []string `json:"dynamic_mtk_serve_rules,omitempty"`
InvitationTimeout int `json:"invitation_timeout,omitempty"`
Uuid string `json:"uuid,omitempty"`
unlockedHiddenPeers PeerList
}
func CreateIdentity(nickname string) *Identity {
func CreateIdentity(nickname string) (*Identity, error) {
var id Identity
id.Nickname = nickname
id.Uuid = uuid.New().String()
id.RootKp = meowlib.NewKeyPair()
GetConfig().me = &id
id.MessageServers = ServerStorage{DbFile: uuid.NewString()}
id.generateRandomHiddenStuff()
return &id
err := id.CreateFolder()
if err != nil {
return nil, err
}
return &id, nil
}
// Creates an invitation for a peer, returns the peer containing
func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerIdxs []int) (*Peer, *meowlib.ContactCard, error) {
func (id *Identity) CreateFolder() error {
err := os.MkdirAll(filepath.Join(GetConfig().StoragePath, id.Uuid), 0700)
if err != nil {
return err
}
return nil
}
func (id *Identity) WipeFolder() error {
err := os.RemoveAll(filepath.Join(GetConfig().StoragePath, id.Uuid))
if err != nil {
return err
}
return nil
}
// Creates an invitation for a peer, returns the newly created peer including infos to provide a ContactCard
func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerUids []string, InvitationMessage string) (*Peer, error) {
var peer Peer
var myContactCard meowlib.ContactCard
peer.Uid = uuid.New().String()
peer.MyIdentity = meowlib.NewKeyPair()
peer.MyEncryptionKp = meowlib.NewKeyPair()
peer.MyLookupKp = meowlib.NewKeyPair()
peer.Name = ContactName
peer.InvitationId = uuid.New().String()
if id.MessageServers.Servers == nil {
return nil, nil, errors.New("no message servers defined in your identity")
}
for _, i := range MessageServerIdxs {
if i > len(id.MessageServers.Servers)-1 {
return nil, nil, errors.New("requested server out of range of defined message servers")
peer.InvitationId = uuid.New().String() // todo as param to identify then update url
/* if id.MessageServers.Servers == nil {
return nil, errors.New("no message servers defined in your identity")
}
}
for _, i := range MessageServerIdxs {
srv := &id.MessageServers.Servers[i].ServerData
myContactCard.PullServers = append(myContactCard.PullServers, srv)
}
myContactCard.Name = MyName
myContactCard.ContactPublicKey = peer.MyIdentity.Public
myContactCard.EncryptionPublicKey = peer.MyEncryptionKp.Public
myContactCard.LookupPublicKey = peer.MyLookupKp.Public
myContactCard.InvitationId = peer.InvitationId
id.Peers = append(id.Peers, peer)
for _, i := range MessageServerIdxs {
if i > len(id.MessageServers.Servers)-1 {
return nil, errors.New("requested server out of range of defined message servers")
}
}
for _, i := range MessageServerIdxs {
srv := id.MessageServers.Servers[i].GetServerCard()
peer.MyContact.PullServers = append(peer.MyContact.PullServers, srv)
}*/
/* pullServers, err := id.MessageServers.LoadServerCardsFromUids(MessageServerUids)
if err != nil {
return nil, err
}*/
peer.MyPullServers = MessageServerUids
peer.MyName = MyName
peer.InvitationMessage = InvitationMessage
id.Peers = append(id.Peers, &peer)
return &peer, &myContactCard, nil
return &peer, nil
}
func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string) {
// Checks if the received contact card is an answer to an invitation, returns true if it is, and the proposed and received nicknames
func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string, invitationMessage string) {
// invitation Id found, this is an answer to an invitation
for _, p := range id.Peers {
if p.InvitationId == ReceivedContact.InvitationId {
return true, p.Name, ReceivedContact.Name
return true, p.Name, ReceivedContact.Name, ReceivedContact.InvitationMessage
}
}
return false, "", ReceivedContact.Name
// it's an invitation
return false, "", ReceivedContact.Name, ReceivedContact.InvitationMessage
}
func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageServerIdxs []int, ReceivedContact *meowlib.ContactCard) *meowlib.ContactCard {
// Answers an invitation, returns the newly created peer including infos to provide a ContactCard
func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageServerIdxs []string, ReceivedContact *meowlib.ContactCard) *Peer {
var peer Peer
var myContactCard meowlib.ContactCard
//var myContactCard meowlib.ContactCard
peer.Uid = uuid.New().String()
peer.MyIdentity = meowlib.NewKeyPair()
peer.MyEncryptionKp = meowlib.NewKeyPair()
peer.MyLookupKp = meowlib.NewKeyPair()
@ -93,32 +125,50 @@ func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageS
} else {
peer.Name = ReceivedContact.Name
}
peer.Contact = *ReceivedContact
for _, i := range MessageServerIdxs {
srv := id.MessageServers.Servers[i].ServerData
myContactCard.PullServers = append(myContactCard.PullServers, &srv)
peer.ContactEncryption = ReceivedContact.EncryptionPublicKey
peer.ContactLookupKey = ReceivedContact.LookupPublicKey
peer.ContactPublicKey = ReceivedContact.ContactPublicKey
peer.InvitationId = ReceivedContact.InvitationId
peer.InvitationMessage = ReceivedContact.InvitationMessage
for srv := range ReceivedContact.PullServers {
peer.ContactPullServers = append(peer.ContactPullServers, ReceivedContact.PullServers[srv].GetUid())
}
myContactCard.Name = MyName
myContactCard.ContactPublicKey = peer.MyIdentity.Public
myContactCard.EncryptionPublicKey = peer.MyEncryptionKp.Public
myContactCard.LookupPublicKey = peer.MyLookupKp.Public
myContactCard.InvitationId = ReceivedContact.InvitationId
/* for _, i := range MessageServerIdxs {
srv := id.MessageServers.Servers[i].GetServerCard()
peer.MyContact.PullServers = append(peer.MyContact.PullServers, srv)
}*/
/* srvCards, err := GetConfig().GetIdentity().MessageServers.LoadServerCardsFromUids(MessageServerIdxs)
if err != nil {
peer.MyContact.PullServers = srvCards
}*/
peer.MyPullServers = MessageServerIdxs
peer.MyName = MyName
peer.InvitationId = ReceivedContact.InvitationId
id.Peers = append(id.Peers, &peer)
id.Peers = append(id.Peers, peer)
return &myContactCard
return &peer
}
// Finalizes an invitation, returns nil if successful
func (id *Identity) FinalizeInvitation(ReceivedContact *meowlib.ContactCard) error {
for i, p := range id.Peers {
if p.InvitationId == ReceivedContact.InvitationId {
id.Peers[i].Contact = *ReceivedContact
//id.Peers[i].Name = ReceivedContact.Name
id.Peers[i].ContactEncryption = ReceivedContact.EncryptionPublicKey
id.Peers[i].ContactLookupKey = ReceivedContact.LookupPublicKey
id.Peers[i].ContactPublicKey = ReceivedContact.ContactPublicKey
srvs := []string{}
for srv := range ReceivedContact.PullServers {
srvs = append(srvs, ReceivedContact.PullServers[srv].GetUid())
}
id.Peers[i].ContactPullServers = srvs
return nil
}
}
return errors.New("no matching contact found for invitationId " + ReceivedContact.InvitationId)
}
// LoadIdentity loads an identity from an encrypted file
func LoadIdentity(filename string, password string) (*Identity, error) {
var id Identity
GetConfig().memoryPassword = password
@ -132,6 +182,9 @@ func LoadIdentity(filename string, password string) (*Identity, error) {
return nil, err
}
err = json.Unmarshal([]byte(pass), &id)
if err != nil {
return nil, err
}
GetConfig().me = &id
return &id, err
}
@ -160,7 +213,7 @@ func (id *Identity) TryUnlockHidden(password string) error {
return err
}
p.dbPassword = password
id.unlockedHiddenPeers = append(id.unlockedHiddenPeers, p)
id.unlockedHiddenPeers = append(id.unlockedHiddenPeers, &p)
found = true
}
}
@ -187,36 +240,104 @@ func (id *Identity) HidePeer(peerIdx int, password string) error {
}
func (id *Identity) generateRandomHiddenStuff() {
rand.Seed(time.Now().UnixNano())
count := rand.Intn(maxHiddenCount) + 1
r := rand.New(rand.NewSource(time.Now().UnixNano()))
count := r.Intn(maxHiddenCount) + 1
for i := 1; i < count; i++ {
var p Peer
p.Name = randomLenString(4, 20)
p.MyEncryptionKp = meowlib.NewKeyPair()
p.MyIdentity = meowlib.NewKeyPair()
p.MyLookupKp = meowlib.NewKeyPair()
p.Contact.Name = randomLenString(4, 20)
p.Contact.ContactPublicKey = p.MyLookupKp.Public
p.Contact.EncryptionPublicKey = p.MyIdentity.Public
p.Contact.LookupPublicKey = p.MyEncryptionKp.Public
p.Contact.AddUrls([]string{randomLenString(14, 60), randomLenString(14, 60)})
id.Peers = append(id.Peers, p)
p.Name = randomLenString(4, 20)
p.ContactPublicKey = p.MyLookupKp.Public
p.ContactEncryption = p.MyIdentity.Public
p.ContactLookupKey = p.MyEncryptionKp.Public
// p.Contact.AddUrls([]string{randomLenString(14, 60), randomLenString(14, 60)}) // todo add servers
id.Peers = append(id.Peers, &p)
id.HidePeer(0, randomLenString(8, 14))
// TODO Add conversations
// TODO Add random conversations
}
}
type BackgroundJob struct {
RootPublic string `json:"root_public,omitempty"`
Device meowlib.KeyPair `json:"device,omitempty"`
Jobs []*RequestsJob `json:"jobs,omitempty"`
}
type RequestsJob struct {
Server *Server `json:"server,omitempty"`
LookupKeys []meowlib.KeyPair `json:"lookup_keys,omitempty"`
}
func (id *Identity) GetRequestJobs() []*RequestsJob {
var list []*RequestsJob
srvs := map[string]*RequestsJob{}
// get all servers
servers, err := id.MessageServers.LoadAllServers()
if err == nil {
// build a server map
for _, server := range servers {
var rj RequestsJob
rj.Server = server
srvs[server.GetServerCard().GetUid()] = &rj
}
// add ids to the map
for _, peer := range id.Peers {
// check if peer inviation is accepted
for _, server := range peer.MyPullServers {
srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp)
}
}
// add hidden peers
for _, peer := range id.unlockedHiddenPeers {
for _, server := range peer.MyPullServers {
srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp)
}
}
// todo add garbage
// todo random reorder
// build list
for _, srv := range srvs {
if len(srv.LookupKeys) > 0 {
list = append(list, srv)
}
}
}
return list
}
func (id *Identity) SaveBackgroundJob() error {
var bj BackgroundJob
bj.Jobs = id.GetRequestJobs()
bj.RootPublic = id.RootKp.Public
bj.Device = id.Device
jsonjobs, err := json.Marshal(bj)
if err != nil {
return err
}
id.CreateFolder()
err = os.WriteFile(filepath.Join(GetConfig().StoragePath, id.Uuid, ".jobs"), jsonjobs, 0600)
if err != nil {
return err
}
return nil
}
func randomLenString(min int, max int) string {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(max-min) + min
r := rand.New(rand.NewSource(time.Now().UnixNano()))
n := r.Intn(max-min) + min
return randomString(n)
}
func randomString(n int) string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
s := make([]rune, n)
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
s[i] = letters[r.Intn(len(letters))]
}
return string(s)
}

View File

@ -3,6 +3,7 @@ package client
import (
"log"
"os"
"strconv"
"testing"
"forge.redroom.link/yves/meowlib"
@ -23,8 +24,11 @@ func createId() *Identity {
config.memoryPassword = "generalPassword"
// ! Extension to quickly open db : Debug only !
config.DbSuffix = ".sqlite"
id := CreateIdentity("myname")
err := id.Save()
id, err := CreateIdentity("myname")
if err != nil {
log.Fatal("CreateIdentity failed")
}
err = id.Save()
if err != nil {
log.Fatal("Save failed")
}
@ -33,12 +37,12 @@ func createId() *Identity {
p.MyEncryptionKp = meowlib.NewKeyPair()
p.MyIdentity = meowlib.NewKeyPair()
p.MyLookupKp = meowlib.NewKeyPair()
p.Contact.Name = "foo"
p.Contact.ContactPublicKey = p.MyLookupKp.Public
p.Contact.EncryptionPublicKey = p.MyIdentity.Public
p.Contact.LookupPublicKey = p.MyEncryptionKp.Public
p.Contact.AddUrls([]string{"http:/127.0.0.1/meow", "tcp://localhost:1234"})
id.Peers = append(id.Peers, p)
p.Name = "foo"
p.ContactPublicKey = p.MyLookupKp.Public
p.ContactEncryption = p.MyIdentity.Public
p.ContactLookupKey = p.MyEncryptionKp.Public
//p.Contact.AddUrls([]string{"http:/127.0.0.1/meow", "tcp://localhost:1234"}) //todo add servers
id.Peers = append(id.Peers, &p)
return id
}
@ -48,7 +52,10 @@ func TestLoad(t *testing.T) {
}
id, err := LoadIdentity("test.id", "toto")
if err != nil {
id := CreateIdentity("myname")
id, err1 := CreateIdentity("myname")
if err1 != nil {
log.Fatal("CreateIdentity failed")
}
id.Save()
} else {
log.Println(id.Nickname)
@ -79,3 +86,52 @@ func TestHidePeer(t *testing.T) {
os.Remove("test.id")
}
}
// test GetRequestJobs
func TestGetRequestJobs(t *testing.T) {
// Create a mock Identity object
id := &Identity{
Peers: []*Peer{
{
MyPullServers: []string{"server1", "server2"},
MyLookupKp: meowlib.NewKeyPair(),
},
{
MyPullServers: []string{"server3", "server4"},
MyLookupKp: meowlib.NewKeyPair(),
},
},
unlockedHiddenPeers: []*Peer{
{
MyPullServers: []string{"server5", "server6"},
MyLookupKp: meowlib.NewKeyPair(),
},
},
}
id.MessageServers = ServerStorage{
DbFile: "test.db",
}
GetConfig().SetMemPass("test")
GetConfig().SetIdentity(id)
for i := 1; i < 10; i++ {
// initialize a Server with name "server+i"
srv := CreateServerFromUrl("server" + strconv.Itoa(i))
id.MessageServers.StoreServer(srv)
}
// Call GetRequestJobs
jobs := id.GetRequestJobs()
// Check that the returned list is as expected
assert.Equal(t, 6, len(jobs), "Expected 6 jobs")
// Check that each job has the correct server and lookup keys
for _, job := range jobs {
//fmt.Println(job.Server.GetUid(), job.LookupKeys)
assert.Contains(t, []string{"server1", "server2", "server3", "server4", "server5", "server6"}, job.Server.GetUid(), "Unexpected server UID")
assert.Len(t, job.LookupKeys, 1, "Expected 1 lookup key per job")
}
// Clean up
// recursively remove the test.db folder
os.RemoveAll("test.db")
}

View File

@ -26,14 +26,14 @@ func ProcessForOutput(usermessage *meowlib.UserMessage, peer *Peer, servers *Ser
srv = &servers.Servers[i]
var toServerMessage meowlib.ToServerMessage
toServerMessage.MatriochkaMessage.Data = lastmsg
toServerMessage.MatriochkaMessage.Next.Url = servers.Servers[i+1].ServerData.Url
toServerMessage.MatriochkaMessage.Next.PublicKey = servers.Servers[i+1].ServerData.PublicKey
toServerMessage.MatriochkaMessage.Next.Url = servers.Servers[i+1].Url
toServerMessage.MatriochkaMessage.Next.PublicKey = servers.Servers[i+1].PublicKey
toServerMessage.MatriochkaMessage.Next.Delay = int32(servers.Servers[i+1].AllowedDelay)
if trackingLookupKey != "" {
toServerMessage.MatriochkaMessage.Next.Uuid = lastuuid // change tracking uuid at each server
if i > 0 {
toServerMessage.MatriochkaMessage.Prev.Url = servers.Servers[i-1].ServerData.Url
toServerMessage.MatriochkaMessage.Prev.PublicKey = servers.Servers[i+1].ServerData.PublicKey
toServerMessage.MatriochkaMessage.Prev.Url = servers.Servers[i-1].Url
toServerMessage.MatriochkaMessage.Prev.PublicKey = servers.Servers[i+1].PublicKey
toServerMessage.MatriochkaMessage.Prev.Delay = int32(servers.Servers[i-1].AllowedDelay)
toServerMessage.MatriochkaMessage.Prev.Uuid = uuid.NewString()
lastuuid = toServerMessage.MatriochkaMessage.Prev.Uuid

View File

@ -3,8 +3,32 @@ package client
import "forge.redroom.link/yves/meowlib"
type InternalUserMessage struct {
message *meowlib.UserMessage
dbid int64
Outbound bool `json:"outbound"`
Messagetype string `json:"messagetype,omitempty"`
Message string `json:"message,omitempty"`
ConversationStatus *meowlib.ConversationStatus `json:"conversation_status,omitempty"`
Contact *meowlib.ContactCard `json:"contact,omitempty"`
//Group group
FilePaths []string `json:"file_paths,omitempty"`
CurrentLocation *meowlib.Location `json:"current_location,omitempty"`
Appdata []byte `json:"appdata,omitempty"`
Dbfile string `json:"dbfile,omitempty"`
Dbid int64 `json:"dbid,omitempty"`
}
// InternalUserMessageFromUserMessage creates an InternalUserMessage from a UserMessage
func InternalUserMessageFromUserMessage(peer *Peer, msg *meowlib.UserMessage) *InternalUserMessage {
iu := new(InternalUserMessage)
if peer.ContactPublicKey == msg.From {
iu.Outbound = false
} else {
iu.Outbound = true
}
iu.Messagetype = msg.Type
iu.Message = string(msg.Data)
iu.ConversationStatus = msg.Status
iu.Contact = msg.Contact
return iu
}
func ProcessOutboundTextMessage(peer *Peer, text string, srv *Server) ([]byte, error) {

334
client/messagestorage.go Normal file
View File

@ -0,0 +1,334 @@
package client
import (
"database/sql"
"math"
"os"
"path/filepath"
"forge.redroom.link/yves/meowlib"
"github.com/google/uuid"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/protobuf/proto"
)
func StoreMessage(peer *Peer, usermessage *meowlib.UserMessage, filenames []string, password string) error {
var dbid string
cfg := GetConfig()
identity := cfg.GetIdentity()
// If no db/no ID create DB + Tablz
// TODO : if file size > X new db
if len(peer.DbIds) == 0 {
dbid = uuid.NewString()
peer.DbIds = []string{dbid}
identity.Save()
identity.CreateFolder()
file, err := os.Create(filepath.Join(cfg.StoragePath, identity.Uuid, dbid+GetConfig().DbSuffix))
if err != nil {
return err
}
file.Close()
peer.DbIds = append(peer.DbIds, dbid)
sqliteDatabase, _ := sql.Open("sqlite3", filepath.Join(cfg.StoragePath, identity.Uuid, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
err = createMessageTable(sqliteDatabase)
if err != nil {
return err
}
sqliteDatabase.Close()
} else {
dbid = peer.DbIds[len(peer.DbIds)-1]
}
// Open Db
db, _ := sql.Open("sqlite3", filepath.Join(cfg.StoragePath, identity.Uuid, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
// Detach Files
hiddenFilenames := []string{}
if len(usermessage.Files) > 0 {
for _, f := range usermessage.Files {
hiddenFilename := uuid.NewString()
// Cypher file
encData, err := meowlib.SymEncrypt(password, f.Data)
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles")); os.IsNotExist(err) {
err = os.MkdirAll(filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles"), 0755)
if err != nil {
return err
}
}
os.WriteFile(filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles", hiddenFilename), encData, 0600)
hiddenFilenames = append(hiddenFilenames, filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles", hiddenFilename))
// replace f.Data by uuid filename
f.Data = []byte(filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles", hiddenFilename))
}
}
outbound := true
if usermessage.From == peer.ContactPublicKey {
outbound = false
}
// Convert UserMessage to DbMessage
dbm := UserMessageToDbMessage(outbound, usermessage, hiddenFilenames)
// Encrypt message
out, err := proto.Marshal(dbm)
if err != nil {
return err
}
encData, err := meowlib.SymEncrypt(password, out)
if err != nil {
return err
}
// Insert message
insertMessageSQL := `INSERT INTO message(m) VALUES (?) RETURNING ID`
statement, err := db.Prepare(insertMessageSQL) // Prepare statement.
if err != nil {
return err
}
result, err := statement.Exec(encData)
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
ium := DbMessageToInternalUserMessage(id, dbid, dbm)
peer.LastMessage = ium
identity.Save()
return nil
}
// Get new messages from a peer
func GetNewMessages(peer *Peer, lastDbId int, password string) ([]*InternalUserMessage, error) {
var messages []*InternalUserMessage
cfg := GetConfig()
identity := cfg.GetIdentity()
// handle no db yet
if len(peer.DbIds) == 0 {
return messages, nil
}
fileidx := len(peer.DbIds) - 1
// There fileidx should provide the db that we need (unless wantMore overlaps the next DB)
db, _ := sql.Open("sqlite3", filepath.Join(cfg.StoragePath, identity.Uuid, peer.DbIds[fileidx]+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
// if it's first app query, it won't hold a lastIndex, so let's start from end
if lastDbId == 0 {
lastDbId = math.MaxInt64
}
stm, err := db.Prepare("SELECT id, m FROM message WHERE id > ? ORDER BY id DESC")
if err != nil {
return nil, err
}
defer stm.Close()
rows, err := stm.Query(lastDbId)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var ium *InternalUserMessage
var dbm meowlib.DbMessage
var id int64
var m []byte
err = rows.Scan(&id, &m)
if err != nil {
return nil, err
}
decdata, err := meowlib.SymDecrypt(password, m)
if err != nil {
return nil, err
}
err = proto.Unmarshal(decdata, &dbm)
if err != nil {
return nil, err
}
ium = DbMessageToInternalUserMessage(id, peer.DbIds[fileidx], &dbm)
ium.Dbid = id
ium.Dbfile = peer.DbIds[fileidx]
messages = append(messages, ium)
}
// TODO DB overlap
return messages, nil
}
// Get old messages from a peer
func GetMessagesHistory(peer *Peer, inAppMsgCount int, lastDbId int, wantMore int, password string) ([]InternalUserMessage, error) {
var messages []InternalUserMessage
// handle no db yet
if len(peer.DbIds) == 0 {
return messages, nil
}
fileidx := len(peer.DbIds) - 1
// initialize count with last db message count
countStack, err := getMessageCount(peer.DbIds[fileidx])
if err != nil {
return nil, err
}
// while the db message count < what we already have in app, step to next db file
for inAppMsgCount > countStack {
fileidx--
if fileidx < 0 {
return nil, nil
}
newCount, err := getMessageCount(peer.DbIds[fileidx])
if err != nil {
return nil, err
}
countStack += newCount
}
// There fileidx should provide the db that we need (unless wantMore overlaps the next DB)
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, GetConfig().GetIdentity().Uuid, peer.DbIds[fileidx]+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
// if it's first app query, it won't hold a lastIndex, so let's start from end
if lastDbId == 0 {
lastDbId = math.MaxInt64
}
stm, err := db.Prepare("SELECT id, m FROM message WHERE id < ? ORDER BY id DESC LIMIT ?")
if err != nil {
return nil, err
}
defer stm.Close()
rows, err := stm.Query(lastDbId, wantMore)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var ium *InternalUserMessage
var dbm meowlib.DbMessage
var id int64
var m []byte
err = rows.Scan(&id, &m)
if err != nil {
return nil, err
}
decdata, err := meowlib.SymDecrypt(password, m)
if err != nil {
return nil, err
}
err = proto.Unmarshal(decdata, &dbm)
if err != nil {
return nil, err
}
ium = DbMessageToInternalUserMessage(id, peer.DbIds[fileidx], &dbm)
ium.Dbid = id
ium.Dbfile = peer.DbIds[fileidx]
messages = append(messages, *ium)
}
// TODO DB overlap
return messages, nil
}
// Get old messages from a peer
func GetMessagePreview(peer *Peer, dbFile string, dbId int64, password string) ([]byte, error) {
// There fileidx should provide the db that we need (unless wantMore overlaps the next DB)
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, GetConfig().GetIdentity().Uuid, dbFile+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
stm, err := db.Prepare("SELECT id, m FROM message WHERE id=?")
if err != nil {
return nil, err
}
defer stm.Close()
rows, err := stm.Query(dbId)
if err != nil {
return nil, err
}
defer rows.Close()
var dbm meowlib.DbMessage
for rows.Next() {
var id int64
var m []byte
err = rows.Scan(&id, &m)
if err != nil {
return nil, err
}
decdata, err := meowlib.SymDecrypt(password, m)
if err != nil {
return nil, err
}
err = proto.Unmarshal(decdata, &dbm)
if err != nil {
return nil, err
}
}
return FilePreview(dbm.FilePaths[0], password)
}
func FilePreview(filename string, password string) ([]byte, error) {
// get the hidden file
encData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
// decrypt the file
data, err := meowlib.SymDecrypt(password, encData)
if err != nil {
return nil, err
}
return data, nil
}
// make an image from the files content (loads the first image, or build a more complex view)
func InternalUserMessagePreview(msg *InternalUserMessage, password string) ([]byte, error) {
// get the hidden file name
if len(msg.FilePaths) == 0 {
return nil, nil
}
return FilePreview(msg.FilePaths[0], password)
}
func getMessageCount(dbid string) (int, error) {
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, GetConfig().GetIdentity().Uuid, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
var count int
query := "SELECT COUNT(*) FROM message"
err := db.QueryRow(query).Scan(&count)
if err != nil {
return 0, err
}
return count, nil
}
func createMessageTable(db *sql.DB) error {
createMessageTableSQL := `CREATE TABLE message (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"m" BLOB);` // SQL Statement for Create Table
statement, err := db.Prepare(createMessageTableSQL) // Prepare SQL Statement
if err != nil {
return err
}
statement.Exec() // Execute SQL Statements
return nil
}
func createServerTable(db *sql.DB) error {
createServerTableSQL := `CREATE TABLE servers (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"country" varchar(2),
"public" bool,
"uptime" int,
"bandwith" float,
"load" float,
"url" varchar(2000)
"name" varchar(255);
"description" varchar(5000)
"publickey" varchar(10000)
)` // SQL Statement for Create Table
statement, err := db.Prepare(createServerTableSQL) // Prepare SQL Statement
if err != nil {
return err
}
statement.Exec() // Execute SQL Statements
return nil
}

View File

@ -14,17 +14,17 @@ func TestStoreMessage(t *testing.T) {
id := createId()
var um meowlib.UserMessage
um.Data = []byte("blabla")
err := StoreMessage(&id.Peers[0], &um, GetConfig().memoryPassword)
err := StoreMessage(id.Peers[0], &um, []string{}, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
messages, err := GetLastMessages(&id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
messages, err := GetMessagesHistory(id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
// Checks
assert.Equal(t, len(messages), 1, "not 1 message")
assert.Equal(t, messages[0].message.Data, um.Data, "not 1 message")
assert.Equal(t, messages[0].Message, string(um.Data), "not 1 message")
// Cleanup
if exists("test.id") {
os.Remove("test.id")
@ -42,15 +42,21 @@ func TestStoreMessage(t *testing.T) {
func TestManyStoreMessage(t *testing.T) {
id := createId()
// test with zero messages
messages, err := GetMessagesHistory(id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
assert.Equal(t, len(messages), 0, "not 0 message")
for i := 1; i < 100; i++ {
var um meowlib.UserMessage
um.Data = []byte(randomLenString(20, 200))
err := StoreMessage(&id.Peers[0], &um, GetConfig().memoryPassword)
err := StoreMessage(id.Peers[0], &um, []string{}, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
}
messages, err := GetLastMessages(&id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
messages, err = GetMessagesHistory(id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}

View File

@ -15,43 +15,95 @@ import (
// - 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 []meowlib.ServerCard `json:"my_pull_servers,omitempty"`
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"`
InvitationId string `json:"invitation_id,omitempty"`
//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"`
OnionMode bool `json:"onion_mode,omitempty"`
LastMessage time.Time `json:"last_message,omitempty"`
DbIds []string `json:"db_ids,omitempty"`
AvatarUuid string `json:"avatar_uid,omitempty"`
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.Contact.LookupPublicKey
msg.Destination = p.ContactLookupKey
msg.From = p.MyIdentity.Public
msg.Data = message
msg.Type = "1"
msg.Status = &meowlib.ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
msg.Status.Uuid = uuid.New().String()
return &msg, nil
}
@ -82,7 +134,7 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
}
var msg meowlib.UserMessage
var file meowlib.File
msg.Destination = p.Contact.LookupPublicKey
msg.Destination = p.ContactLookupKey
msg.From = p.MyIdentity.Public
file.Filename = fi.Name()
file.Chunk = uint32(chunk)
@ -92,7 +144,7 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
msg.Type = "2"
if chunk == 0 {
msg.Status = &meowlib.ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
msg.Status.Uuid = uuid.New().String()
}
msgs = append(msgs, msg)
chunk++
@ -102,7 +154,7 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
// Builds an invitation answer user message.
// it takes as input a contactcard generated by Identity.AnswerInvitation
func (p *Peer) BuildInvitationAnswserMessage(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) {
func (p *Peer) BuildInvitationAnswerMessage(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) {
var msg meowlib.UserMessage
var invitation meowlib.Invitation
invitation.Step = 3
@ -110,8 +162,10 @@ func (p *Peer) BuildInvitationAnswserMessage(myContactCard *meowlib.ContactCard)
if err != nil {
return nil, err
}
invitation.Uuid = p.InvitationId
invitation.Payload = out
msg.Destination = p.Contact.LookupPublicKey
msg.Destination = p.ContactLookupKey
msg.Invitation = &invitation
msg.From = p.MyIdentity.Public
msg.Type = "1"
return &msg, nil
@ -141,7 +195,11 @@ func (p *Peer) DeserializeUserMessage(data []byte) (*meowlib.UserMessage, error)
// AsymEncryptMessage prepares a message to send to a specific peer contact
func (p *Peer) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, error) {
var enc *meowlib.EncryptedMessage
enc, err := meowlib.AsymEncryptAndSign(p.Contact.EncryptionPublicKey, p.MyIdentity.Private, Message)
// 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
@ -151,7 +209,10 @@ func (p *Peer) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, er
// AsymDecryptMessage reads a message from a specific peer contact
func (p *Peer) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMessage []byte, err error) {
DecryptedMessage, err = meowlib.AsymDecryptAndCheck(p.MyEncryptionKp.Private, p.Contact.ContactPublicKey, Message, Signature)
// 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
@ -162,7 +223,7 @@ func (p *Peer) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMe
// PackUserMessage will package the previously encrypted message
func (p *Peer) PackUserMessage(message []byte, signature []byte) *meowlib.PackedUserMessage {
var msg meowlib.PackedUserMessage
msg.Destination = p.Contact.LookupPublicKey
msg.Destination = p.ContactLookupKey
msg.Payload = message
msg.Signature = signature
return &msg
@ -221,19 +282,35 @@ func (p *Peer) SetDbPassword(password string) {
}
func (p *Peer) GetDbPassword() string {
if p.dbPassword == "" {
return GetConfig().memoryPassword
}
return p.dbPassword
}
func (p *Peer) StoreMessage(msg []byte) {
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) LoadMessage(uid string) {
func (p *Peer) LoadNewMessages(lastMessageId int) ([]*InternalUserMessage, error) {
return GetNewMessages(p, lastMessageId, p.GetDbPassword())
}
func (p *Peer) LoadLastMessages(qty int) {
func (p *Peer) LoadMessage(uid string) (*InternalUserMessage, error) {
return nil, nil
}
func (p *Peer) GetLastMessageUuid(msg []byte) {

View File

@ -8,13 +8,16 @@ import (
)
func TestGetFromPublicKey(t *testing.T) {
id := CreateIdentity("test")
id, err := CreateIdentity("test")
if err != nil {
t.Fatal("CreateIdentity failed")
}
id.Save()
for i := 1; i < 10; i++ {
var p Peer
p.Name = "test" + strconv.Itoa(i)
p.Contact.ContactPublicKey = "stringToFind" + strconv.Itoa(i)
id.Peers = append(id.Peers, p)
p.ContactPublicKey = "stringToFind" + strconv.Itoa(i)
id.Peers = append(id.Peers, &p)
}
p5 := id.Peers.GetFromPublicKey("stringToFind5")
assert.Equal(t, p5.Name, "test5")

View File

@ -4,12 +4,21 @@ import (
"forge.redroom.link/yves/meowlib"
)
type PeerList []Peer
type PeerList []*Peer
func (pl *PeerList) GetFromPublicKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.Contact.ContactPublicKey == publickey {
return &peer
if peer.ContactPublicKey == publickey {
return peer
}
}
return nil
}
func (pl *PeerList) GetFromInvitationId(invitationId string) *Peer {
for _, peer := range *pl {
if peer.InvitationId == invitationId {
return peer
}
}
return nil
@ -18,7 +27,7 @@ func (pl *PeerList) GetFromPublicKey(publickey string) *Peer {
func (pl *PeerList) GetFromMyLookupKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.MyLookupKp.Public == publickey {
return &peer
return peer
}
}
return nil
@ -26,13 +35,14 @@ func (pl *PeerList) GetFromMyLookupKey(publickey string) *Peer {
func (pl *PeerList) GetFromName(name string) *Peer {
for _, peer := range *pl {
if peer.Contact.Name == name {
return &peer
if peer.Name == name {
return peer
}
}
return nil
}
// ! Wrong implementation, does not discriminate on different servers
func (pl *PeerList) GetConversationRequests() []*meowlib.ConversationRequest {
var list []*meowlib.ConversationRequest
for _, peer := range *pl {

View File

@ -2,6 +2,7 @@ package client
import (
"fmt"
"strings"
"time"
"forge.redroom.link/yves/meowlib"
@ -17,30 +18,95 @@ import (
// - Utility functions for packing/unpacking, encrypting/decrypting messages for server communication
// - Server remote management if ManagerKp is available for that server
type Server struct {
ServerData meowlib.ServerCard `json:"server_data,omitempty"`
Presence bool `json:"presence,omitempty"`
LastCheck time.Time `json:"last_check,omitempty"`
Uptime time.Duration `json:"uptime,omitempty"`
Login string `json:"login,omitempty"`
Password string `json:"password,omitempty"`
UserKp meowlib.KeyPair `json:"user_kp,omitempty"`
ManagerKp meowlib.KeyPair `json:"manager_kp,omitempty"`
Country string `json:"country,omitempty"`
AllowedDelay int `json:"allowed_delay,omitempty"`
Backup bool `json:"backup,omitempty"`
//ServerCard meowlib.ServerCard `json:"server_data,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
PublicKey string `json:"public_key,omitempty"`
Url string `json:"url,omitempty"`
Login string `json:"login,omitempty"`
Password string `json:"password,omitempty"`
Presence bool `json:"presence,omitempty"`
LastCheck time.Time `json:"last_check,omitempty"`
Uptime time.Duration `json:"uptime,omitempty"`
UserKp meowlib.KeyPair `json:"user_kp,omitempty"`
ManagerKp meowlib.KeyPair `json:"manager_kp,omitempty"`
Country string `json:"country,omitempty"`
AllowedDelay int `json:"allowed_delay,omitempty"`
Backup bool `json:"backup,omitempty"`
}
// CreateServerFromUrl creates a server from a basic url, ex : https://my.meowserver.example:8443/meow/
func CreateServerFromUrl(url string) *Server {
var is Server
is.ServerData.Url = url
is.Url = url
return &is
}
// CreateServerFromUid creates a server from a uid string, ex : mylogin:mypassword@https://my.meowserver.example:8443/meow/
func CreateServerFromUid(uid string) *Server {
var is Server
uidTable := strings.Split(uid, "@") //! Weak test, use regexp
if len(uidTable) == 2 {
loginpw := strings.Split(uidTable[0], ":")
is.Url = uidTable[1]
is.Login = loginpw[0]
is.Password = loginpw[1]
} else {
is.Url = uidTable[0]
}
return &is
}
// CreateServerFromMeowUrl creates a server from a meow url, ex : meow://mylogin:mypassword@https://my.meowserver.example:8443/meow/
func CreateServerFromMeowUrl(meowurl string) *Server {
uid := meowurl[7:]
return CreateServerFromUid(uid)
}
// CreateServerFromInvitationLink creates a server from a meow url, ex : meow://mylogin:mypassword@https://my.meowserver.example:8443/meow?invitationCode
func CreateServerFromInvitationLink(meowurl string) *Server {
// remove the invitation code, last token after a /
meowurlTable := strings.Split(meowurl, "?")
// join all elements with / except the last one
meowSrvUrl := meowurlTable[0]
return CreateServerFromMeowUrl(meowSrvUrl)
}
// GetServerCard returns a server card from a server
func (ints *Server) GetServerCard() *meowlib.ServerCard {
var sc meowlib.ServerCard
sc.Name = ints.Name
sc.PublicKey = ints.PublicKey
sc.Description = ints.Description
sc.Url = ints.Url
sc.Login = ints.Login
sc.Password = ints.Password
return &sc
}
func (sc *Server) GetUid() string {
if len(sc.Login) > 0 || len(sc.Password) > 0 {
return sc.Login + ":" + sc.Password + "@" + sc.Url
}
return sc.Url
}
func (sc *Server) GetMeowUrl() string {
if len(sc.Login) > 0 || len(sc.Password) > 0 {
return sc.Login + ":" + sc.Password + "@" + sc.Url
}
return "meow://" + sc.Url
}
// Create a server from a server card
func CreateServerFromServerCard(server *meowlib.ServerCard) *Server {
var is Server
is.ServerData = *server
is.Name = server.Name
is.PublicKey = server.PublicKey
is.Description = server.Description
is.Url = server.Url
is.Login = server.Login
is.Password = server.Password
is.UserKp = meowlib.NewKeyPair()
return &is
}
@ -48,7 +114,7 @@ func CreateServerFromServerCard(server *meowlib.ServerCard) *Server {
// AsymEncryptMessage prepares a message to send to a specific internal server
func (ints *Server) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, error) {
var enc *meowlib.EncryptedMessage
enc, err := meowlib.AsymEncryptAndSign(ints.ServerData.PublicKey, ints.UserKp.Private, Message)
enc, err := meowlib.AsymEncryptAndSign(ints.PublicKey, ints.UserKp.Private, Message)
if err != nil {
fmt.Println(err.Error())
return nil, err
@ -58,7 +124,7 @@ func (ints *Server) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessag
// AsymDecryptMessage reads a message from a specific internal server
func (ints *Server) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMessage []byte, err error) {
DecryptedMessage, err = meowlib.AsymDecryptAndCheck(ints.UserKp.Private, ints.ServerData.PublicKey, Message, Signature)
DecryptedMessage, err = meowlib.AsymDecryptAndCheck(ints.UserKp.Private, ints.PublicKey, Message, Signature)
if err != nil {
fmt.Println(err.Error())
return nil, err
@ -101,7 +167,7 @@ func (ints *Server) BuildMessageRequestMessage(lookupKeys []string) ([]byte, err
// BuildToServerMessageInvitation creates an invitation message to server and returns it as a meowlib.ToServerMessage
// it takes as input a contactcard generated by Identity.InvitePeer
func (ints *Server) BuildToServerMessageInvitationCreation(invitation *meowlib.ContactCard, password string, timeout int, invitationIdLen int) (*meowlib.ToServerMessage, error) {
func (ints *Server) BuildToServerMessageInvitationCreation(invitation *meowlib.ContactCard, password string, timeout int, shortCodeLen int) (*meowlib.ToServerMessage, error) {
var msg meowlib.ToServerMessage
var inv meowlib.Invitation
payload, err := invitation.Compress()
@ -113,7 +179,7 @@ func (ints *Server) BuildToServerMessageInvitationCreation(invitation *meowlib.C
inv.Step = 1
inv.Password = password
inv.Timeout = int32(timeout)
inv.ShortcodeLen = int32(invitationIdLen)
inv.ShortcodeLen = int32(shortCodeLen)
inv.Payload = payload
msg.Invitation = &inv
return &msg, nil
@ -132,6 +198,37 @@ func (ints *Server) BuildToServerMessageInvitationRequest(shortcode string, pass
return &msg, nil
}
// BuildToServerMessageInvitationAnswer creates an invitation answer to server and returns it as a meowlib.ToServerMessage
// it takes as input a contactcard generated by Identity.InvitePeer
func (ints *Server) BuildToServerMessageInvitationAnswer(invitationAnswer *meowlib.PackedUserMessage, myPublicKeyForThatPeer string, invitation_id string, timeout int) (*meowlib.ToServerMessage, error) {
var msg meowlib.ToServerMessage
var inv meowlib.Invitation
invitationPayload, err := proto.Marshal(invitationAnswer)
if err != nil {
return nil, err
}
inv.Step = 3
inv.Uuid = invitation_id
msg.Type = "1"
msg.From = ints.UserKp.Public
inv.From = myPublicKeyForThatPeer
inv.Payload = invitationPayload
msg.Invitation = &inv
return &msg, nil
}
// BuildToServerMessageInvitationAnswerRequest requests invitation answer with provided id from server and returns it as a meowlib.ToServerMessage
func (ints *Server) BuildToServerMessageInvitationAnswerRequest(invitationId string) (*meowlib.ToServerMessage, error) {
var msg meowlib.ToServerMessage
var inv meowlib.Invitation
msg.Type = "1"
msg.From = ints.UserKp.Public
inv.Step = 4
inv.Uuid = invitationId
msg.Invitation = &inv
return &msg, nil
}
// PackServerMessage
func (ints *Server) PackServerMessage(payload []byte, signature []byte) (protoPackedMessage []byte, err error) {
var msg meowlib.PackedServerMessage

View File

@ -37,7 +37,7 @@ func (sl *ServerList) GetServerByIdx(idx int) (server *Server, err error) {
// GetServerByPubkey returns a server from it's public key
func (sl *ServerList) GetServerByPubkey(pubkey string) (filtered *Server) {
for _, srv := range sl.Servers {
if srv.ServerData.PublicKey == pubkey {
if srv.PublicKey == pubkey {
return &srv
}
}

235
client/serverstorage.go Normal file
View File

@ -0,0 +1,235 @@
package client
//
// Storage
//
import (
"crypto/sha256"
"encoding/json"
"path/filepath"
"forge.redroom.link/yves/meowlib"
"github.com/dgraph-io/badger"
)
type ServerStorage struct {
DbFile string `json:"db_file,omitempty"`
db *badger.DB
}
// Open a badger database from struct ServerStorage
func (ss *ServerStorage) open() error {
opts := badger.DefaultOptions(filepath.Join(GetConfig().StoragePath, GetConfig().GetIdentity().Uuid, ss.DbFile))
opts.Logger = nil
var err error
ss.db, err = badger.Open(opts)
if err != nil {
return err
}
return nil
}
// Store function StoreServer stores a server in a badger database with Server.GetUid() as key
func (ss *ServerStorage) StoreServer(sc *Server) error {
err := ss.open()
if err != nil {
return err
}
defer ss.close()
// first marshal the Server to bytes with protobuf
jsonsrv, err := json.Marshal(sc)
if err != nil {
return err
}
data, err := meowlib.SymEncrypt(GetConfig().memoryPassword, jsonsrv)
if err != nil {
return err
}
shakey := sha256.Sum256([]byte(sc.GetServerCard().GetUid()))
key := shakey[:]
// then store it in the database
return ss.db.Update(func(txn *badger.Txn) error {
return txn.Set(key, data)
})
}
// LoadServer function loads a Server from a badger database with Server.GetUid() as key
func (ss *ServerStorage) LoadServer(uid string) (*Server, error) {
var sc Server
err := ss.open()
if err != nil {
return nil, err
}
defer ss.close()
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
err = ss.db.View(func(txn *badger.Txn) error {
item, err := txn.Get(key)
if err != nil {
return err
}
return item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(GetConfig().memoryPassword, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
})
return &sc, err
}
// DeleteServer function deletes a Server from a badger database with Server.GetUid() as key
func (ss *ServerStorage) DeleteServer(uid string) error {
err := ss.open()
if err != nil {
return err
}
defer ss.close()
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
return ss.db.Update(func(txn *badger.Txn) error {
return txn.Delete(key)
})
}
// LoadAllServers function loads all Servers from a badger database
func (ss *ServerStorage) LoadAllServers() ([]*Server, error) {
var scs []*Server
err := ss.open()
if err != nil {
return nil, err
}
defer ss.close()
err = ss.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
var sc Server
err := item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(GetConfig().memoryPassword, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
if err != nil {
return err
}
scs = append(scs, &sc)
}
return nil
})
return scs, err
}
// LoadAllServers function loads all ServersCards from a badger database
func (ss *ServerStorage) LoadAllServerCards() ([]*meowlib.ServerCard, error) {
var scs []*meowlib.ServerCard
err := ss.open()
if err != nil {
return nil, err
}
defer ss.close()
err = ss.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
var sc Server
err := item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(GetConfig().memoryPassword, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
if err != nil {
return err
}
scs = append(scs, sc.GetServerCard())
}
return nil
})
return scs, err
}
// LoadServersFromUids function loads Servers with id in []Uid parameter from a badger database
func (ss *ServerStorage) LoadServersFromUids(uids []string) ([]*Server, error) {
var scs []*Server
err := ss.open()
if err != nil {
return nil, err
}
defer ss.close()
err = ss.db.View(func(txn *badger.Txn) error {
for _, uid := range uids {
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
item, err := txn.Get(key)
if err != nil {
return err
}
var sc Server
err = item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(GetConfig().memoryPassword, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
if err != nil {
return err
}
scs = append(scs, &sc)
}
return nil
})
return scs, err
}
// LoadServersFromUids function loads Servers with id in []Uid parameter from a badger database
func (ss *ServerStorage) LoadServerCardsFromUids(uids []string) ([]*meowlib.ServerCard, error) {
var scs []*meowlib.ServerCard
err := ss.open()
if err != nil {
return nil, err
}
defer ss.close()
err = ss.db.View(func(txn *badger.Txn) error {
for _, uid := range uids {
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
item, err := txn.Get(key)
if err != nil {
return err
}
var sc Server
err = item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(GetConfig().memoryPassword, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
if err != nil {
return err
}
scs = append(scs, sc.GetServerCard())
}
return nil
})
return scs, err
}
// close a badger database
func (ss *ServerStorage) close() {
ss.db.Close()
}

View File

@ -0,0 +1,103 @@
package client
import (
"log"
"os"
"testing"
"forge.redroom.link/yves/meowlib"
)
func TestGetUid(t *testing.T) {
srv := Server{
Name: "test",
Url: "http://127.0.0.1:8080",
PublicKey: meowlib.NewKeyPair().Public,
}
uid := srv.GetUid()
if uid != "http://127.0.0.1:8080" {
log.Fatal("uid not correct")
}
}
func TestStoreServer(t *testing.T) {
GetConfig().SetMemPass("test")
ss := ServerStorage{DbFile: "test.db"}
srv := Server{
Name: "test",
Url: "http://127.0.0.1",
PublicKey: meowlib.NewKeyPair().Public,
}
err := ss.StoreServer(&srv)
if err != nil {
log.Fatal(err)
}
sout, err := ss.LoadServer(srv.GetServerCard().GetUid())
if err != nil {
log.Fatal(err)
}
if sout == nil {
log.Fatal("server not found")
}
if sout.Name != srv.Name {
log.Fatal("name not found")
}
// Clean up
// recursively remove the test.db folder
os.RemoveAll("test.db")
}
func TestLoadServersFromUids(t *testing.T) {
GetConfig().SetMemPass("test")
ss := ServerStorage{DbFile: "test.db"}
srv := Server{
Name: "test",
Url: "http://localhost:8080",
PublicKey: meowlib.NewKeyPair().Public,
}
err := ss.StoreServer(&srv)
if err != nil {
log.Fatal(err)
}
sout, err := ss.LoadServersFromUids([]string{srv.GetServerCard().GetUid()})
if err != nil {
log.Fatal(err)
}
if sout == nil {
log.Fatal("server not found")
}
if sout[0].Name != srv.Name {
log.Fatal("name not found")
}
// Clean up
// recursively remove the test.db folder
os.RemoveAll("test.db")
}
func TestLoadServerCardsFromUids(t *testing.T) {
GetConfig().SetMemPass("test")
ss := ServerStorage{DbFile: "test.db"}
srv := Server{
Name: "test",
Url: "http://localhost:8080",
PublicKey: meowlib.NewKeyPair().Public,
}
err := ss.StoreServer(&srv)
if err != nil {
log.Fatal(err)
}
sout, err := ss.LoadServerCardsFromUids([]string{srv.GetServerCard().GetUid()})
if err != nil {
log.Fatal(err)
}
if sout == nil {
log.Fatal("server not found")
}
if sout[0].Name != srv.Name {
log.Fatal("name not found")
}
// Clean up
// recursively remove the test.db folder
os.RemoveAll("test.db")
}

View File

@ -1,184 +0,0 @@
package client
import (
"database/sql"
"math"
"os"
"path/filepath"
"forge.redroom.link/yves/meowlib"
"github.com/google/uuid"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/protobuf/proto"
)
func StoreMessage(peer *Peer, usermessage *meowlib.UserMessage, password string) error {
var dbid string
// If no db/no ID create DB + Tablz
// TODO : if file size > X new db
if len(peer.DbIds) == 0 {
dbid = uuid.NewString()
file, err := os.Create(filepath.Join(GetConfig().StoragePath, dbid+GetConfig().DbSuffix))
if err != nil {
return err
}
file.Close()
peer.DbIds = append(peer.DbIds, dbid)
sqliteDatabase, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
err = createMessageTable(sqliteDatabase)
if err != nil {
return err
}
sqliteDatabase.Close()
GetConfig().me.Save()
} else {
dbid = peer.DbIds[len(peer.DbIds)-1]
}
// Open Db
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
// Detach Files
if len(usermessage.Files) > 0 {
for _, f := range usermessage.Files {
hiddenFilename := uuid.NewString()
// Cypher file
encData, err := meowlib.SymEncrypt(password, f.Data)
if err != nil {
return err
}
os.WriteFile(hiddenFilename, encData, 0600)
// replace f.Data by uuid filename
f.Data = []byte(hiddenFilename)
}
}
// Encrypt message
out, err := proto.Marshal(usermessage)
if err != nil {
return err
}
encData, err := meowlib.SymEncrypt(password, out)
if err != nil {
return err
}
// Insert message
insertMessageSQL := `INSERT INTO message(m) VALUES (?) RETURNING ID`
statement, err := db.Prepare(insertMessageSQL) // Prepare statement.
if err != nil {
return err
}
_, err = statement.Exec(encData)
if err != nil {
return err
}
return nil
}
// Get last messages from a peer
func GetLastMessages(peer *Peer, inAppMsgCount int, lastDbId int, wantMore int, password string) ([]InternalUserMessage, error) {
var messages []InternalUserMessage
fileidx := len(peer.DbIds) - 1
// initialize count with last db message count
countStack, err := getMessageCount(peer.DbIds[fileidx])
if err != nil {
return nil, err
}
// while the db message count < what we already have in app, step to next db file
for inAppMsgCount > countStack {
fileidx--
if fileidx < 0 {
return nil, nil
}
newCount, err := getMessageCount(peer.DbIds[fileidx])
if err != nil {
return nil, err
}
countStack += newCount
}
// There fileidx should provide the db that we need (unless wantMore overlaps the next DB)
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, peer.DbIds[fileidx]+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
// if it's first app query, it won't hold a lastIndex, so let's start from end
if lastDbId == 0 {
lastDbId = math.MaxInt64
}
stm, err := db.Prepare("SELECT id, m FROM message WHERE id < ? ORDER BY id DESC LIMIT ?")
if err != nil {
return nil, err
}
defer stm.Close()
rows, err := stm.Query(lastDbId, wantMore)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var ium InternalUserMessage
var um meowlib.UserMessage
var id int64
var m []byte
err = rows.Scan(&id, &m)
if err != nil {
return nil, err
}
decdata, err := meowlib.SymDecrypt(password, m)
if err != nil {
return nil, err
}
err = proto.Unmarshal(decdata, &um)
if err != nil {
return nil, err
}
ium.dbid = id
ium.message = &um
messages = append(messages, ium)
}
// TODO DB overlap
return messages, nil
}
func getMessageCount(dbid string) (int, error) {
db, _ := sql.Open("sqlite3", filepath.Join(GetConfig().StoragePath, dbid+GetConfig().DbSuffix)) // Open the created SQLite File
defer db.Close()
var count int
query := "SELECT COUNT(*) FROM message"
err := db.QueryRow(query).Scan(&count)
if err != nil {
return 0, err
}
return count, nil
}
func createMessageTable(db *sql.DB) error {
createMessageTableSQL := `CREATE TABLE message (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"m" BLOB);` // SQL Statement for Create Table
statement, err := db.Prepare(createMessageTableSQL) // Prepare SQL Statement
if err != nil {
return err
}
statement.Exec() // Execute SQL Statements
return nil
}
func createServerTable(db *sql.DB) error {
createServerTableSQL := `CREATE TABLE servers (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"country" varchar(2),
"public" bool,
"uptime" int,
"bandwith" float,
"load" float,
"url" varchar(2000)
"name" varchar(255);
"description" varchar(5000)
"publickey" varchar(10000)
)` // SQL Statement for Create Table
statement, err := db.Prepare(createServerTableSQL) // Prepare SQL Statement
if err != nil {
return err
}
statement.Exec() // Execute SQL Statements
return nil
}

View File

@ -1,10 +1,13 @@
package meowlib
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCompress(t *testing.T) {
func TestCompressAndJson(t *testing.T) {
kp1 := NewKeyPair()
kp2 := NewKeyPair()
kp3 := NewKeyPair()
@ -13,9 +16,50 @@ func TestCompress(t *testing.T) {
cc.ContactPublicKey = kp1.Public
cc.EncryptionPublicKey = kp2.Public
cc.LookupPublicKey = kp3.Public
cc.InvitationMessage = "hello, it's me"
cc.AddUrls([]string{"https://meow.myfirstdomain.com/services/meow:8080", "https://meow.myseconddomain.com/services/meow:8080", "http://meow.mythirddomain.com/services/meow:8080"})
serialized, _ := cc.Serialize()
println(len(serialized))
compressed, _ := cc.Compress()
println(len(compressed))
ncc, err := NewContactCardFromCompressed(compressed)
if err != nil {
println(err)
}
assert.Equal(t, ncc.Name, cc.Name)
if err != nil {
println(err)
}
jsoncc, err := json.Marshal(cc)
var cc1 ContactCard
err = json.Unmarshal(jsoncc, &cc1)
if err != nil {
println(err)
}
}
func TestStaticJson(t *testing.T) {
ccsrt := `
{
"contact_public_key": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tClZlcnNpb246IEdvcGVuUEdQIDIuNy40CkNvbW1lbnQ6IGh0dHBzOi8vZ29wZW5wZ3Aub3JnCgp4ak1FWlpST1pCWUpLd1lCQkFIYVJ3OEJBUWRBZy9iRnR4WG5hRjZLOFEzdGVWcmt3Y3YvRTV6TE94WnVZaXVvCm5MVkdtdWZOQzI1aGJXVWdQRzFoYVd3K3dvOEVFeFlJQUVFRkFtV1VUbVFKRUhmcmlVWXZsWnhyRmlFRXFFMDIKVVRmOTRTY1hJWVdjZCt1SlJpK1ZuR3NDR3dNQ0hnRUNHUUVEQ3drSEFoVUlBeFlBQWdVbkNRSUhBZ0FBYmN3QgpBT1VsTHJEbWpCM0pKeGNWUFNHaU1KTlZrem1idlhMTDVSSnh4aTNuNVVrMUFRQ2NHN29QeDYwTUdHRVNhc0V0CnBTS2VqUGFmNjNTVXhMelRoRFFacTlqOUI4NDRCR1dVVG1RU0Npc0dBUVFCbDFVQkJRRUJCMEJXeXMvaFZHSGcKRFN0V2Jid3VnbnlCdTFWdUlJbVBZMDRsKzRCQWd4QUFZQU1CQ2duQ2VBUVlGZ2dBS2dVQ1paUk9aQWtRZCt1SgpSaStWbkdzV0lRU29UVFpSTi8zaEp4Y2hoWngzNjRsR0w1V2Nhd0liREFBQWkyTUEvMTdUYksyT3FMdzZDSWZmCkE3YnlwYitxNzdHVmZlQmtmY2l3aXlCM2xRSGxBUUROZzJONisxcklEbG40cXRRc0pFSWR1OUlMMzVlMjR6cWwKbEJMSVR0YVBBQT09Cj1KeHZrCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0=",
"encryption_public_key": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCkNvbW1lbnQ6IGh0dHBzOi8vZ29wZW5wZ3Aub3JnClZlcnNpb246IEdvcGVuUEdQIDIuNy40Cgp4ak1FWlpST1pCWUpLd1lCQkFIYVJ3OEJBUWRBY3FocFM5dnFzVGE2WXJCWVEra1JaY1VDWnFSRUhVUTMrbWQyClZOdlNLYS9OQzI1aGJXVWdQRzFoYVd3K3dvOEVFeFlJQUVFRkFtV1VUbVFKRUNHb3JhR2xRMVVoRmlFRVcrZVUKRzJjZnVSaU5CVDNwSWFpdG9hVkRWU0VDR3dNQ0hnRUNHUUVEQ3drSEFoVUlBeFlBQWdVbkNRSUhBZ0FBNmRRQgpBSVN3OUFKeVphem83TWs2R2NVZzR6ZDROR1p2dVorZnNxMThoTmtNd0EwRUFRQzNaNmNUT2kraGlURjJLazVGClBtSnI4aHlQWlREVGgwa1I5NE14TzhBa0NzNDRCR1dVVG1RU0Npc0dBUVFCbDFVQkJRRUJCMEQzYWZpeS9ZT3YKZFRxMXJ0UTZhVTVLNS9COTFKTW5SaVptSGtTYW9YRDZYd01CQ2duQ2VBUVlGZ2dBS2dVQ1paUk9aQWtRSWFpdApvYVZEVlNFV0lRUmI1NVFiWngrNUdJMEZQZWtocUsyaHBVTlZJUUliREFBQVpxZ0EvMElaRTR2MmRZbFB5NURJCnNPeTlDbTNFakRJSy80SElJK1VuRm5qeGFtVENBUDQwbUJqaEJKZVRNbzV5ZWpDN2xlYXliUlNMcE1yY1NIeEcKSmpnOGJ0eDVBdz09Cj1rd1VTCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0=",
"invitation_id": "38373ab8-f423-48bc-9136-628f2a7e0e18",
"invitation_message": "Hi ! it's me !",
"lookup_public_key": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tClZlcnNpb246IEdvcGVuUEdQIDIuNy40CkNvbW1lbnQ6IGh0dHBzOi8vZ29wZW5wZ3Aub3JnCgp4ak1FWlpST1pCWUpLd1lCQkFIYVJ3OEJBUWRBWmh2NGY3WG55aGhoU3JPQkUrc0VteGsrWkFGNFdjTjY4KythCnAwV25FU0xOQzI1aGJXVWdQRzFoYVd3K3dvOEVFeFlJQUVFRkFtV1VUbVFKRUVPTnRxN202L2h3RmlFRU9EaWMKL1cyMVJqNmJMWkdXUTQyMnJ1YnIrSEFDR3dNQ0hnRUNHUUVEQ3drSEFoVUlBeFlBQWdVbkNRSUhBZ0FBbi84QQovMWRjRHVIY3Fjd3JrNW9sclVPMUlIbVhSYWg1aC9wbm9HSDZMM0JSdlphbkFRRHozZTc0TmxZWFpnank3SVBFCittbnM5MDR6TnVqdnpFYks2SHg2dTh1VkJNNDRCR1dVVG1RU0Npc0dBUVFCbDFVQkJRRUJCMEFzVnBZd000TEMKK0JNT201WXFRWUEzRlFiTXI4alp3Wk5IelFvQ29URDRJZ01CQ2duQ2VBUVlGZ2dBS2dVQ1paUk9aQWtRUTQyMgpydWJyK0hBV0lRUTRPSno5YmJWR1Bwc3RrWlpEamJhdTV1djRjQUliREFBQUJxb0EvaUFOMGpIV0FHTXV2MTYxCkdxcXF6aGxPQXFzVjVKNW5iMy9LMk43TU9Pek1BUURzZEVaMlU5VmxXY3ljWDFuZGFoMnkzUnEvQ1QwWkJ0R2IKUzM1dU5HbDFBZz09Cj1kT2lnCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0=",
"name": "me",
"pull_servers": [
{
"name": "local",
"public_key": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tClZlcnNpb246IEdvcGVuUEdQIDIuNS4wCkNvbW1lbnQ6IGh0dHBzOi8vZ29wZW5wZ3Aub3JnCgp4ak1FWSs2TUd4WUpLd1lCQkFIYVJ3OEJBUWRBVmk1ZnQyTmlmSzByVmVmYzNQd3JTRXhxNHRhVUtaTzhQeXprClB4SmNTT1BOQzI1aGJXVWdQRzFoYVd3K3dvd0VFeFlJQUQ0RkFtUHVqQnNKa0M4aTlzQjVvVjNyRmlFRWpNeEgKZldiZmxMblRhZzFRTHlMMndIbWhYZXNDR3dNQ0hnRUNHUUVEQ3drSEFoVUlBeFlBQWdJaUFRQUF4L0lCQU5kMgpVK3hZM09LQVk5elFSbmlXQXdlVEpoMWxySEpaMHd6RmVQS3JycUJvQVAwYjRISHBoT2dJWWx2TXlqajZ0TXZRCk01RTIzY3ZiWjRPZXRjNmNmeWxIQ2M0NEJHUHVqQnNTQ2lzR0FRUUJsMVVCQlFFQkIwQ2t1bWlndUpNT003Sy8KNEl1NVppYkJUYXAwSzBkNXNybkNCN2tIU2pObGV3TUJDZ25DZUFRWUZnZ0FLZ1VDWSs2TUd3bVFMeUwyd0htaApYZXNXSVFTTXpFZDladCtVdWROcURWQXZJdmJBZWFGZDZ3SWJEQUFBMlcwQS9qY2pZTUtQY3ZXcTA2QVpVKzRHClQwUmQxU2VNUXpzNndCUU9ZejEwQkVMWEFRQ3hTQ2kvN2RqRjZUWFl0SFpBSytrVUEvUHpYaW14bnRvVFpKbjMKV3l1Z0NnPT0KPTFjU20KLS0tLS1FTkQgUEdQIFBVQkxJQyBLRVkgQkxPQ0stLS0tLQ==",
"url": "http://localhost:8080"
}
]
}
`
var cc1 ContactCard
jsoncc := []byte(ccsrt)
err := json.Unmarshal(jsoncc, &cc1)
if err != nil {
println(err)
}
}

View File

@ -5,8 +5,13 @@ go-plantuml generate -o generated/client.puml -d ../client
sed -i 's/\.\.\/client/client/g' generated/client.puml
go-plantuml generate -o generated/server.puml -d ../server
sed -i 's/\.\.\/server/server/g' generated/server.puml
cp *.puml generated/
cd generated
plantuml .
plantuml -latex .
mv *.tex tex/
mv *.png png/
rm *.log *.aux *.fdb_latexmk *.fls *.gz *.puml

View File

@ -43,10 +43,17 @@ The server requires very few ressources and will run on any low cost single boar
\textffm{Meow} also provides an anonymizing transfer service very similar to the Tor Onion protocol, we call it the Matriochka protocol.
Any server can be used for building the transfer chain.
Some of them might be marked as trusted.
Random delays and random payload padding might be set for each forwarding step, making the overall message tracking much more difficult, even for organizations having capabilities of global network surveillance.
Random delays and random size payload padding might be set for each forwarding step, making the overall message tracking much more difficult, even for organizations having capabilities of global network surveillance.
It is strongly advised to use trusted servers as your first node and message server (the one that holds your incoming messages).
\subsubsection{Presence protocol for direct messaging}
\subsubsection{Message lookup obfuscation}
Your device will request for messages using conversation keys on a very regular basis to your messaging(s) server(s).
The device will check for conversation keys for all your contacts. If you check that option, it will also check for hidden contact keys.
In case of data interception on your device link, in order to prevent statistical analysis, every request might be answered with size useful data (server's known server list).
Moreover, some random keys will be added to your requests list.
\subsubsection{Presence protocol for direct messaging TBC}
A presence service associating your conversation keys to your IP address for direct peer to peer connection is also provided.
The presence protocol is simply activated by setting a flag in the message poll requests.
If that flag is set, your encrypted IP will be published on the server, allowing your only your peer(s) to decrypt it and directly communicate with your terminal.
@ -208,7 +215,7 @@ You just have to live with the goverment decisions.
In the best scenario that government was elected, and might represent at most 25\% of the population.
In most case, they will vote laws to satisfy the powerful people who supported their election, and the most powerful lobbies.
\textffm{Meow} Nations aim to be the next lobbying power to influence real life politics, "the poor man's lobby".
\textffm{Meow} Nations aims to be the next lobbying power to influence real life politics, "the poor man's lobby".
Virtual nation in that perspective will be probably quickly flagged as terrorist nation by the old world media, but well,
one man's terrorist is another man's freedom fighter.

7
doc/sq_inv.puml Normal file
View File

@ -0,0 +1,7 @@
@startuml Server Invitation Step 01
Bob -> MeowBob: Create invitation for alice
Bob -> Alice: Send invitation
Alice -> MeowAlice: Accept Invitation and create answer
Alice -> Bob: Send answer
Bob -> MeowBob: Review Answer, invitation finalize
@enduml

12
doc/sq_srvinv01.puml Normal file
View File

@ -0,0 +1,12 @@
@startuml Server Invitation Step 01
User -> Bastet : fill invitation
User -> Bastet : select servers
Bastet -> NativeLib : get server cards for selected uids
NativeLib -> Bastet : server cards
Bastet -> NativeLib : invitationCreateMessage
NativeLib -> Bastet : invitationMessage
Bastet -> Server : send invitation
Server -> Redis : Store invitation
Server -> Bastet : invitation URL
Bastet -> User : invitation URL
@enduml

11
doc/sq_srvinv02.puml Normal file
View File

@ -0,0 +1,11 @@
@startuml Server Invitation Step 02
User -> Bastet : paste URL
Bastet -> NativeLib : build invitationGetMessage
NativeLib -> Bastet : invitationGetMessage
Bastet -> Server : send invitationGetMessage
Redis -> Server : retrieve invitation
Server -> Bastet : invitation message
Bastet -> NativeLib : decode invitation message
NativeLib -> Bastet : invitation data
Bastet -> User : invitation data
@enduml

12
doc/sq_srvinv03.puml Normal file
View File

@ -0,0 +1,12 @@
@startuml Server Invitation Step 03
User -> Bastet : select servers
User -> Bastet : accept invitation
Bastet -> NativeLib : accept invitation
Bastet -> NativeLib : build accept message
NativeLib -> Bastet : invitationGetMessage
Bastet -> Server : send accept message
Server -> Redis : store accept message
Server -> Bastet : accept message ok
Bastet -> User : accept msg sent
@enduml

9
doc/sq_srvinv04.puml Normal file
View File

@ -0,0 +1,9 @@
@startuml Server Invitation Step 03
Bastet -> NativeLib : periodic message check
NativeLib -> Server : get new messages
Server -> NativeLib : send invitation message
Server -> Redis : store accept message
Server -> Bastet : accept message ok
Bastet -> User : invitation sent is accepted
@enduml

View File

@ -22,29 +22,34 @@ func TestEndToEnd(t *testing.T) {
///////////////////////////
// Creating New Identity //
///////////////////////////
Me = client.CreateIdentity("myname")
Me, err = client.CreateIdentity("myname")
// define my preferences (servers)
Me.MessageServers.Name = "Message Servers"
Me.MessageServers.AddUrls([]string{"http://127.0.0.1/meow/", "mqtt://127.0.0.1", "meow://127.0.0.1"})
srv := client.Server{Name: "MyServer", Url: "http://127.0.0.1/meow/"}
Me.MessageServers.StoreServer(&srv)
srv = client.Server{Name: "MyServer", Url: "mqtt://127.0.0.1"}
Me.MessageServers.StoreServer(&srv)
srv = client.Server{Name: "MyServer", Url: "meow://127.0.0.1"}
Me.MessageServers.StoreServer(&srv)
////////////////////////////////////////////////////////////////////////////
// Create an invitation for a friend, I want him/her to know me as Bender //
////////////////////////////////////////////////////////////////////////////
fmt.Println("Creating an invitation for the first friend...")
peer, myContactCard, err := Me.InvitePeer("Bender", "myfirstfriend", []int{1, 2})
peer, err := Me.InvitePeer("Bender", "myfirstfriend", []string{"http://127.0.0.1/meow/", "mqtt://127.0.0.1"}, "welcome, it's me!")
if err != nil {
println(err)
}
println(peer.Name)
// print my invitation
a, _ := json.Marshal(myContactCard)
a, _ := json.Marshal(peer.GetMyContact())
fmt.Println(string(a))
// TODO : Convert invitation to QR Code
myContactCard.WritePng("invitation.png")
data, err := myContactCard.Compress()
peer.GetMyContact().WritePng("invitation.png")
data, err := peer.GetMyContact().Compress()
if err != nil {
println(err)
}
myContactCard.WriteQr("qrcode.png")
peer.GetMyContact().WriteQr("qrcode.png")
println("Compressed contact card :", len(data))
///////////////////////////////////////
// Simulate peer invitation response //
@ -60,7 +65,7 @@ func TestEndToEnd(t *testing.T) {
ReceivedContact.ContactPublicKey = FirstFriendContactKp.Public
ReceivedContact.EncryptionPublicKey = FirstFriendEncryptionKp.Public
ReceivedContact.LookupPublicKey = FirstFriendLookupKp.Public
ReceivedContact.InvitationId = myContactCard.InvitationId
ReceivedContact.InvitationId = peer.GetMyContact().InvitationId
FriendServer1KP := meowlib.NewKeyPair()
FriendServer1 := meowlib.ServerCard{Name: "FriendServer1", Url: "http://myfriend.org/meow/", PublicKey: FriendServer1KP.Public, Description: "Fancy description"}
ReceivedContact.PullServers = append(ReceivedContact.PullServers, &FriendServer1)
@ -100,9 +105,10 @@ func TestEndToEnd(t *testing.T) {
// Packing it
packedMsg := MyFirstFriend.PackUserMessage(enc.Data, enc.Signature)
srv := MyFirstFriend.Contact.PullServers[0]
intS1 := client.CreateServerFromServerCard(srv)
intS1, err := Me.MessageServers.LoadServer("http://127.0.0.1/meow/")
if err != nil {
fmt.Println(err.Error())
}
// Creating Server message for transporting the user message
toServerMessage, err := intS1.BuildMessageSendingMessage(packedMsg)
if err != nil {
@ -130,9 +136,9 @@ func TestEndToEnd(t *testing.T) {
// Simulating server side processing //
///////////////////////////////////////
var server1 server.Identity
server1.ServerName = intS1.ServerData.Name
server1.ServerName = intS1.Name
server1.ServerKp = FriendServer1KP
server1.ServerDesc = intS1.ServerData.Description
server1.ServerDesc = intS1.Description
// Unpack
srv_from, srv_encmsg, srv_signature, err := server1.UnpackReceived(protoPackedServerMsg)
if err != nil {

17
go.mod
View File

@ -3,18 +3,21 @@ module forge.redroom.link/yves/meowlib
go 1.16
require (
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.3
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.5
github.com/cloudflare/circl v1.3.7 // indirect
github.com/dgraph-io/badger v1.6.2
github.com/go-redis/redis v6.15.9+incompatible
github.com/google/uuid v1.3.1
github.com/google/uuid v1.6.0
github.com/makiuchi-d/gozxing v0.1.1
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.16
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.22.1 // indirect
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/protobuf v1.31.0
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/protobuf v1.33.0
)

108
go.sum
View File

@ -1,20 +1,41 @@
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.3 h1:AJu1OI/1UWVYZl6QcCLKGu9OTngS2r52618uGlje84I=
github.com/ProtonMail/gopenpgp/v2 v2.7.3/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA=
github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -23,6 +44,7 @@ github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -30,6 +52,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -38,19 +61,30 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -71,28 +105,47 @@ github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeR
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -112,8 +165,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -121,8 +174,10 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -146,15 +201,19 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -163,8 +222,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -175,8 +234,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -186,10 +245,11 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

40
http.go Normal file
View File

@ -0,0 +1,40 @@
package meowlib
import (
"bytes"
"encoding/json"
"io"
"net/http"
)
func HttpGetId(url string) (response map[string]string, err error) {
srvId := make(map[string]string)
resp, err := http.Get(url + "/id")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(body, &srvId)
if err != nil {
return nil, err
}
return srvId, nil
}
func HttpPostMessage(url string, msg []byte) (response []byte, err error) {
resp, err := http.Post(url+"/msg",
"application/octet-stream", bytes.NewBuffer(msg))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}

View File

@ -3,6 +3,7 @@ package meowlib
import (
"errors"
"os"
"path/filepath"
"github.com/google/uuid"
)
@ -30,13 +31,13 @@ func (msg *UserMessage) AddFile(filename string, maxMessageSize int64) error {
}
var file File
file.Filename = filename
file.Filename = filepath.Base(filename)
file.Size = uint64(fi.Size())
file.Data = data
msg.Files = append(msg.Files, &file)
msg.Status = &ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
msg.Status.Uuid = uuid.New().String()
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -30,17 +30,23 @@ message Invitation {
string uuid = 6; // id that the friend gave you, that you should include to your reply to get recognized
int64 expiry = 7; // the server allowed expiry date, it may be samller than the requested timeout according to server policy
int32 step = 8; // progress in the inviattion process : 1=invite friend, 2=friend requests invitation, 3=friend's answer
string from=9; // used in step 3 the answer public key to check the signature in user message
}
// structure for requesting incoming messages
message ConversationRequest {
string lookupKey = 1; // lookup key for a conversation
string lastServerUuidOK = 2; // Last Server message UUID received (send me all after that one)
bool publishOnline = 3; // ?? Publish my online status for that contact ?
string lookupSignature = 4; // prove that I own the private key by signing that block
string lookup_key = 1; // lookup key for a conversation
// removed string lastServerUuidOK = 2; // Last Server message UUID received (send me all after that one)
int64 send_timestamp = 2;
string lookup_signature = 3; // prove that I own the private key by signing that block
}
message Meet {
string public_status = 1; // Publish my online status, if the server is a meeting server
ContactCard contact_card = 2; // mine or the requester
string message = 3; // short description
}
// structure defining a message for a server, that will be encrypted, then sent in a "packedmessage" payload
message ToServerMessage {
@ -48,18 +54,19 @@ message ToServerMessage {
string from = 2 ; // My pub key for the server to send me an encrypter answer
bytes payload = 3 ; // optional payload for server
repeated ConversationRequest pullRequest = 4;
repeated ConversationRequest pull_request = 4;
repeated PackedUserMessage messages = 5;
repeated ServerCard knownServers = 6;
repeated ServerCard known_servers = 6;
Matriochka matriochkaMessage = 7;
Matriochka matriochka_message = 7;
string uuid = 8;
Invitation invitation = 9; // invitation for the 2 first steps of a "through server" invitation process
repeated PackedUserMessage device_messages = 10; // messages to another device belonging to the same user
}
message ConversationResponse {
@ -69,17 +76,20 @@ message ToServerMessage {
// structure defining a from server receiver message decrypted from a "packedmessage" payload
message FromServerMessage {
string type = 1; // Type
string serverPublicKey = 2 ; // Pub key from the server
string server_public_key = 2 ; // Pub key from the server
bytes payload = 3 ; //
string uuidAck = 4 ; // Ack for the last received ToServerMessage Uuid
string serverUuid = 5 ; // Provides the server uuid that replaced the client uuid
string uuid_ack = 4 ; // Ack for the last received ToServerMessage Uuid
string server_uuid = 5 ; // Provides the server uuid that replaced the client uuid
repeated PackedUserMessage chat = 6;
repeated ServerCard knownServers = 7;
repeated ServerCard known_servers = 7;
Invitation invitation = 8; // invitation answer, for the third steps of any invitation
repeated PackedUserMessage device_messages = 9; // messages from other devices belonging to the same user
}
message MatriochkaServer {
@ -100,7 +110,7 @@ message Matriochka {
message ServerCard {
string name = 1; // friendly server name
string description=2; // description : owner type (company/private/university...),
string publicKey = 3; // public key you must use to send encrypted messages to that server
string public_key = 3; // public key you must use to send encrypted messages to that server
string url = 4; // meow server url
string login = 5; // required login to access the server
string password = 6; // password associated to the login
@ -110,12 +120,13 @@ message ServerCard {
// structure describing a user contact card ie the minimum set of attributes for exchanging identities
message ContactCard {
string name=1; // contact nickname
string contactPublicKey =2; // contact public key, will be used to authenticate her/his messages
string encryptionPublicKey= 3; // public key you must use to to write encrypted messages to that contact
string lookupPublicKey =4; // public key you will use as "destination identifier" for her/him to lookup for your messages on the servers
repeated ServerCard pullServers =5; // list the servers where the contact will look for messages from you
string contact_public_key =2; // contact public key, will be used to authenticate her/his messages
string encryption_public_key= 3; // public key you must use to to write encrypted messages to that contact
string lookup_public_key =4; // public key you will use as "destination identifier" for her/him to lookup for your messages on the servers
repeated ServerCard pull_servers =5; // list the servers where the contact will look for messages from you
uint32 version = 6;
string invitationId=7;
string invitation_id=7;
string invitation_message=8;
}
// structure for sending a message to be forwarded to another user in protobuf format
@ -127,13 +138,14 @@ message PackedUserMessage {
}
message ConversationStatus {
string localUuid = 1;
uint64 localSequence = 2 ;
uint64 sent = 3 ;
uint64 received = 4;
uint64 processed = 5;
ContactCard myNextIdentity = 6;
int32 peerNextIdentityAck = 7; // version of the new peer accepted id
string uuid = 1;
string answer_to_uuid=2; // message is an answer to another one, specify uuid here
uint64 localSequence = 3 ;
uint64 sent = 4 ;
uint64 received = 5;
uint64 processed = 6;
ContactCard my_next_identity = 7;
int32 peer_next_identityAck = 8; // version of the new peer accepted id
}
message Group{
@ -147,22 +159,15 @@ message UserMessage {
string from = 2; // My public key for that contact
string type = 3;
bytes data = 4;
ConversationStatus Status = 5;
ConversationStatus status = 5;
ContactCard contact = 6;
ServerCard knownServers = 7;
Group group = 8;
repeated File files = 9;
Location currentLocation = 10;
bytes appdata = 11;
Invitation invitation = 12;
}
message File {
@ -177,4 +182,17 @@ message Location {
float latitude=2;
float longitude=3;
int32 altitude=4;
}
}
message DbMessage {
bool outbound = 1; // direction of the message
string type = 2;
bytes data = 3; // text data
ConversationStatus status = 4;
ContactCard contact = 5;
Group group = 6;
repeated string file_paths = 7;
Location current_location = 8;
bytes appdata = 9;
Invitation invitation = 10;
}

View File

@ -1,12 +1,13 @@
#!/bin/bash
echo Generating Golang
protoc -I=. --go_out=.. messages.proto
mv ../forge.redroom.link/yves/meowlib/messages.pb.go ../
rm -rf ../forge.redroom.link
echo Generating HTML doc
protoc --plugin=protoc-gen-doc=/usr/bin/protoc-gen-doc \
--doc_out=../doc/generated \
--doc_opt=html,index.html \
*.proto
echo Generating UML
protoc --plugin=protoc-gen-uml=/usr/bin/protoc-gen-uml \
--uml_out=../doc/generated -I=. *.proto

View File

@ -52,7 +52,7 @@ func (id *Identity) Save(file string) error {
if err != nil {
return err
}
err = os.WriteFile(file, []byte(armor), 0644)
err = os.WriteFile(file, []byte(armor), 0600)
return err
}

View File

@ -8,14 +8,14 @@ import (
"github.com/go-redis/redis"
)
func (r *RedisRouter) CreateInvitation(invitation []byte, timeout int, password string, serverTimeout int, urlLen int) (string, time.Time) {
func (r *RedisRouter) StoreInvitation(invitation []byte, timeout int, password string, serverTimeout int, urlLen int) (string, time.Time) {
id := r.createShortId(urlLen)
if timeout > serverTimeout {
timeout = serverTimeout
}
r.Client.Set("mwiv:"+id, invitation, time.Duration(timeout*1000000))
r.Client.Set("mwiv:"+id, invitation, 0) //, time.Duration(timeout*1000000))
if len(password) > 0 {
r.Client.Set("mwpw:"+id, password, time.Duration(timeout*1000000))
r.Client.Set("mwpw:"+id, password, 0) //, time.Duration(timeout*1000000))
}
return id, time.Now().Add(time.Duration(timeout * 1000000)).UTC()
}
@ -38,7 +38,7 @@ func (r *RedisRouter) GetInvitation(id string, password string) ([]byte, error)
return []byte(mwiv), nil
}
func (r *RedisRouter) AnswerInvitation(id string, timeout int, invitation []byte, serverTimeout int) time.Time {
func (r *RedisRouter) StoreAnswerToInvitation(id string, timeout int, invitation []byte, serverTimeout int) time.Time {
if timeout > serverTimeout {
timeout = serverTimeout
}
@ -46,8 +46,8 @@ func (r *RedisRouter) AnswerInvitation(id string, timeout int, invitation []byte
return time.Now().Add(time.Duration(timeout * 1000000)).UTC()
}
func (r *RedisRouter) GetInvitationAnswer(id string) ([]byte, error) {
mwan, err := r.Client.Get("mwiv:" + id).Result()
func (r *RedisRouter) GetAnswerToInvitation(id string) ([]byte, error) {
mwan, err := r.Client.Get("mwan:" + id).Result()
if err != nil {
return nil, err
}

View File

@ -37,78 +37,33 @@ func NewRedisRouter(server *Identity, redisUrl string, password string, db int,
}
func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
var from_server *meowlib.FromServerMessage
// update messages counter
err := r.Client.Incr("statistics:messages:total").Err()
if err != nil {
panic(err)
}
// user message
// user message => store
if len(msg.Messages) > 0 {
// update messages counter
err := r.Client.Incr("statistics:messages:usermessages").Err()
if err != nil {
panic(err)
}
for _, usrmsg := range msg.Messages {
// serialize the message to store it as byte array into redis
out, err := proto.Marshal(usrmsg)
if err != nil {
return nil, err
}
r.Client.ZAdd(usrmsg.Destination, redis.Z{Score: float64(time.Now().Unix()), Member: out})
}
from_server.UuidAck = msg.Uuid
}
// check for messages
if len(msg.PullRequest) > 0 {
// update messages counter
err := r.Client.Incr("statistics:messages:messagelookups").Err()
if err != nil {
panic(err)
}
for _, rq := range msg.PullRequest {
msgcnt, err := r.Client.ZCount(rq.LookupKey, "-inf", "+inf").Result()
if err != nil {
return nil, err
}
res, err := r.Client.ZPopMin(rq.LookupKey, msgcnt).Result()
if err != nil {
return nil, err
}
for _, redismsg := range res {
//println(redismsg.Score)
val := redismsg.Member
test := val.(string)
var usrmsg meowlib.PackedUserMessage
err := proto.Unmarshal([]byte(test), &usrmsg)
if err != nil {
return nil, err
}
// add server timestamp
usrmsg.ServerTimestamp = append(usrmsg.ServerTimestamp, int64(redismsg.Score))
from_server.Chat = append(from_server.Chat, &usrmsg)
}
}
}
// manage Matriochka
if msg.MatriochkaMessage != nil {
// update messages counter
err := r.Client.Incr("statistics:messages:matriochka").Err()
if err != nil {
panic(err)
}
out, err := proto.Marshal(msg)
from_server, err = r.storeMessage(msg)
if err != nil {
return nil, err
}
r.Client.ZAdd("mtk", redis.Z{Score: float64(time.Now().Unix()), Member: out})
if msg.MatriochkaMessage.LookupKey != "" {
//r.Client.ZAdd("trk:" + msg.MatriochkaMessage.Next.Uuid,{})
}
// check for messages
if len(msg.PullRequest) > 0 {
from_server, err = r.checkForMessage(msg)
if err != nil {
return nil, err
}
}
// manage Matriochka
if msg.MatriochkaMessage != nil {
from_server, err = r.handleMatriochka(msg)
if err != nil {
return nil, err
}
from_server.UuidAck = msg.Uuid
}
// Server list exchange
if len(msg.KnownServers) > 0 {
@ -116,40 +71,9 @@ func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMe
}
// Through server invitation process
if msg.Invitation != nil {
// update messages counter
err := r.Client.Incr("statistics:messages:invitation").Err()
from_server, err = r.handleInvitation(msg)
if err != nil {
panic(err)
}
switch msg.Invitation.Step {
case 1: // create invitation
url, expiry := r.CreateInvitation(msg.Invitation.Payload, int(msg.Invitation.Timeout), msg.Invitation.Password, r.InvitationTimeout, int(msg.Invitation.ShortcodeLen))
from_server.Invitation.Shortcode = url
from_server.Invitation.Expiry = expiry.UTC().Unix()
case 2: // get invitation
invitation, err := r.GetInvitation(msg.Invitation.Shortcode, msg.Invitation.Password)
if err != nil {
if err.Error() == "auth failed" {
from_server.Invitation.Payload = []byte("authentication failure")
} else {
from_server.Invitation.Payload = []byte("invitation expired")
}
} else {
from_server.Invitation.Payload = invitation
}
/* should not happen
case 3: // answer invitation
expiry := r.AnswerInvitation(msg.Invitation.Id, int(msg.Invitation.Timeout), msg.Invitation.Payload, r.InvitationTimeout)
from_server.Invitation.Expiry = expiry.UTC().Unix()
case 4: // get answer
answer, err := r.GetInvitationAnswer(msg.Invitation.Id)
if err != nil {
from_server.Invitation.Payload = []byte("invitation expired")
} else {
from_server.Invitation.Payload = answer
}
*/
return nil, err
}
}
/*
@ -165,5 +89,144 @@ func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMe
break
}
*/
return from_server, nil
}
func (r *RedisRouter) storeMessage(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
// update messages counter
err := r.Client.Incr("statistics:messages:usermessages").Err()
if err != nil {
return nil, err
}
for _, usrmsg := range msg.Messages {
// serialize the message to store it as byte array into redis
out, err := proto.Marshal(usrmsg)
if err != nil {
return nil, err
}
r.Client.ZAdd(usrmsg.Destination, redis.Z{Score: float64(time.Now().Unix()), Member: out})
}
from_server.UuidAck = msg.Uuid
return &from_server, nil
}
func (r *RedisRouter) checkForMessage(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
//dataFound := false
// update messages counter
err := r.Client.Incr("statistics:messages:messagelookups").Err()
if err != nil {
return nil, err
}
for _, rq := range msg.PullRequest {
// get messages from redis
msgcnt, err := r.Client.ZCount(rq.LookupKey, "-inf", "+inf").Result()
if err != nil {
return nil, err
}
res, err := r.Client.ZPopMin(rq.LookupKey, msgcnt).Result()
if err != nil {
return nil, err
}
// iterate over messages
for _, redismsg := range res {
//println(redismsg.Score)
val := redismsg.Member
test := val.(string)
var usrmsg meowlib.PackedUserMessage
err := proto.Unmarshal([]byte(test), &usrmsg)
if err != nil {
return nil, err
}
// add server timestamp
usrmsg.ServerTimestamp = append(usrmsg.ServerTimestamp, int64(redismsg.Score))
from_server.Chat = append(from_server.Chat, &usrmsg)
}
// if no messages check for invitationanswer payload
if msgcnt == 0 {
// get invitation answer
var answer meowlib.Invitation
storedAnswer, _ := r.GetAnswerToInvitation(rq.LookupKey)
if storedAnswer != nil {
err := proto.Unmarshal(storedAnswer, &answer)
if err != nil {
from_server.Invitation.Payload = []byte("invitation answer corrupted")
}
from_server.Invitation = &answer
// exit loop if invitation found, cannot store several in a message
return &from_server, nil
}
// add invitation answer to the response
}
}
return &from_server, nil
}
func (r *RedisRouter) handleInvitation(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
// update messages counter
err := r.Client.Incr("statistics:messages:invitation").Err()
if err != nil {
return nil, err
}
switch msg.Invitation.Step {
// create invitation => provide shortcode and expiry
case 1:
url, expiry := r.StoreInvitation(msg.Invitation.Payload, int(msg.Invitation.Timeout), msg.Invitation.Password, r.InvitationTimeout, int(msg.Invitation.ShortcodeLen))
from_server.Invitation = &meowlib.Invitation{}
from_server.Invitation.Shortcode = url
from_server.Invitation.Expiry = expiry.UTC().Unix()
// get invitation => retrieve invitation from redis and send
case 2:
from_server.Invitation = &meowlib.Invitation{}
invitation, err := r.GetInvitation(msg.Invitation.Shortcode, msg.Invitation.Password)
if err != nil {
if err.Error() == "auth failed" {
from_server.Invitation.Payload = []byte("authentication failure")
} else {
from_server.Invitation.Payload = []byte("invitation expired")
}
} else {
from_server.Invitation.Payload = invitation // protobuf invitation
}
// accept invitation => store accepted invitation for initiator
case 3:
var usermsg meowlib.PackedUserMessage
err := proto.Unmarshal(msg.Invitation.Payload, &usermsg)
if err != nil {
return nil, err
}
data, err := proto.Marshal(msg.Invitation)
if err != nil {
return nil, err
}
expiry := r.StoreAnswerToInvitation(usermsg.Destination, int(msg.Invitation.Timeout), data, r.InvitationTimeout)
from_server.Invitation = &meowlib.Invitation{}
from_server.Invitation.Expiry = expiry.UTC().Unix()
}
return &from_server, nil
}
func (r *RedisRouter) handleMatriochka(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
// update messages counter
err := r.Client.Incr("statistics:messages:matriochka").Err()
if err != nil {
return nil, err
}
out, err := proto.Marshal(msg)
if err != nil {
return nil, err
}
r.Client.ZAdd("mtk", redis.Z{Score: float64(time.Now().Unix()), Member: out})
if msg.MatriochkaMessage.LookupKey != "" {
//r.Client.ZAdd("trk:" + msg.MatriochkaMessage.Next.Uuid,{})
}
from_server.UuidAck = msg.Uuid
return &from_server, nil
}

12
servercard.go Normal file
View File

@ -0,0 +1,12 @@
package meowlib
func (sc *ServerCard) GetUid() string {
if len(sc.Login) > 0 || len(sc.Password) > 0 {
return sc.Login + ":" + sc.Password + "@" + sc.Url
}
return sc.Url
}
func (sc *ServerCard) IsSame(sc1 *ServerCard) bool {
return sc.GetUid() == sc1.GetUid()
}