Compare commits

..

32 Commits

Author SHA1 Message Date
ycc
6a42d261f1 doc update
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-12-12 23:44:30 +01:00
ycc
1dda1b27a8 Server lists simplify
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-09 20:09:55 +01:00
ycc
535181d669 Server management keys, and user Kp rename
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-09 20:01:19 +01:00
ycc
c3ad8d5d2d start time format fix
Some checks failed
continuous-integration/drone/push Build is failing
2023-12-08 17:08:01 +01:00
ycc
4cd928fdc6 add basic server statistics to redis db
All checks were successful
continuous-integration/drone/push Build is passing
2023-12-08 16:52:18 +01:00
ycc
420b7d0af0 Refactor and servercard ajustments
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-27 00:31:43 +01:00
ycc
432f449558 Server invitation process functions
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-14 16:32:50 +01:00
ycc
65f9ee2e07 out of bound errot detection
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-09 22:50:51 +01:00
ycc
eeb39338e2 Split server and serverlist + serverlist methods
Some checks failed
continuous-integration/drone/push Build is failing
2023-11-09 22:46:39 +01:00
ycc
c203431ede InternalServer to Server refactor
All checks were successful
continuous-integration/drone Build is passing
2023-11-08 22:01:44 +01:00
ycc
04a390d558 Refactor and comment 2023-11-08 21:52:09 +01:00
ycc
08ff9d58b8 Doc update 2023-11-06 22:44:21 +01:00
ycc
cd41a59518 config params 2023-09-27 22:17:44 +02:00
ycc
4750915b49 config update 2023-09-27 17:16:23 +02:00
ycc
e0faaf8cef switch url to id in invitation process 2023-08-31 23:51:20 +02:00
ycc
539e2c528e fix router constructor 2023-08-31 23:43:36 +02:00
ycc
d082724432 Integrate invitation process in router 2023-08-31 23:38:03 +02:00
ycc
5eb06ebcc5 typo 2023-08-30 21:16:13 +02:00
ycc
753cd30f38 start through server invitation process 2023-08-29 23:40:30 +02:00
ycc
4a009b69eb start through server invitation process 2023-08-29 23:40:19 +02:00
ycc
9d5ba42dfc loaction and appdata added 2023-08-01 22:47:18 +02:00
ycc
71df3a792b add my name and myavatar to my identity for a peer 2023-07-27 15:48:06 +02:00
ycc
d748735ded Added avatar identifiers and servers table 2023-07-27 10:44:09 +02:00
ycc
698740e20a Identity accessors
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-17 22:30:13 +01:00
ycc
940a8d395c add identity accessors to config 2023-02-17 22:29:21 +01:00
ycc
6f2a65dac9 storage get messages + config improve + tests
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-15 22:08:17 +01:00
ycc
d9155bac51 tests
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-11 22:43:08 +01:00
ycc
0f8bdf5f66 Store pass for db saving
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-01-11 22:29:31 +01:00
ycc
ce758c5bb1 hide peers, add drone ci, doc update 2023-01-11 21:42:14 +01:00
ycc
c4bb8c5693 hiddens peers unlock 2023-01-08 23:19:08 +01:00
ycc
61958593a1 identity in config to allow save from everywhere 2023-01-08 22:57:17 +01:00
ycc
5ac92ce3a8 storage start and identity in shared config 2023-01-07 00:39:05 +01:00
31 changed files with 2142 additions and 975 deletions

11
.drone.yml Normal file
View File

@ -0,0 +1,11 @@
type: docker
kind: pipeline
name: unit
steps:
- name: test
image: golang
commands:
- go test
- go build

13
.gitignore vendored
View File

@ -1,9 +1,10 @@
doc/protocol.aux
doc/protocol.fdb_latexmk
doc/protocol.fls
doc/protocol.log
doc/protocol.pdf
doc/protocol.synctex.gz
doc/*.aux
doc/*.fdb_latexmk
doc/*.fls
doc/*.log
doc/*.pdf
doc/*.synctex.gz
doc/generated
out/doc/general_deployment/general_deployment.png
out/doc/server_deployment/server_deployment.png
*.json

View File

@ -1,9 +1,21 @@
# MEOW lib
## Go Mobile setup
go install golang.org/x/mobile/cmd/gobind@latest
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
Replace "Get" by "get" in messages.pb.go
# Documentation generation
## required tools
* protoc
* protoc-gen-doc
* protoc-gen-uml
* go-plantuml
* plantuml (plantuml command is as shell script : `java -jar /<path-to-plantuml>/plantuml-mit-<version>.jar "$@"`)
## generation
run the shell scripts
cd pb
./protogen.sh
cd doc
./docgen.sh
# Tests
gomobile bind -target android -androidapi=19

View File

@ -4,7 +4,6 @@ import (
"encoding/base64"
"fmt"
"log"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -122,6 +121,7 @@ func TestFlutterCompat(t *testing.T) {
}
/*
func TestFlutterDecode(t *testing.T) {
pub, err := os.ReadFile("/home/yves/Documents/code/flutter/meowlib/pub.key")
if err != nil {
@ -179,3 +179,4 @@ func TestFlutterEncode(t *testing.T) {
println(base64.StdEncoding.EncodeToString(pub))
}
*/

View File

@ -11,27 +11,40 @@ type Config struct {
SavePassword bool `json:"save_password,omitempty"`
SavedPassword string `json:"saved_password,omitempty"`
// Technical
IdentityFile string `json:"identity_file,omitempty"`
StoragePath string `json:"storage_path,omitempty"`
MaxIdsPerUser int `json:"max_ids_per_user,omitempty"`
MsgDbRollingPeriod int `json:"msg_db_rolling_period,omitempty"`
Chunksize int64 `json:"chunksize,omitempty"`
ServerPollInterval int `json:"server_poll_interval,omitempty"`
DbSize int `json:"db_size,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
// GUI
LastOpenChat string `json:"last_open_chat,omitempty"`
SoundNotification bool `json:"sound_notification,omitempty"`
DefaultNotificationSound string `json:"default_notification_sound,omitempty"`
NotificationVibe string `json:"notification_vibe,omitempty"`
DefaultNotificationVibe string `json:"default_notification_vibe,omitempty"`
NotificationLED string `json:"notification_led,omitempty"`
DefaultNotificationLEDColor string `json:"default_notification_led_color,omitempty"`
VisualNotification bool `json:"visual_notification,omitempty"`
VisualNotifiactionModes string `json:"visual_notifiaction_modes,omitempty"`
PrivateChatNotifiactions bool `json:"private_chat_notifiactions,omitempty"`
GroupChatNotifiactions bool `json:"group_chat_notifiactions,omitempty"`
ChannelNotifications bool `json:"channel_notifications,omitempty"`
LastOpenChat string `json:"last_open_chat,omitempty"`
SoundNotificationEnable bool `json:"sound_notification_enable,omitempty"`
DefaultNotificationSound string `json:"default_notification_sound,omitempty"`
NotificationVibeEnable bool `json:"notification_vibe_enable,omitempty"`
DefaultNotificationVibe string `json:"default_notification_vibe,omitempty"`
NotificationLEDEnable bool `json:"notification_led_enable,omitempty"`
DefaultNotificationLEDColor string `json:"default_notification_led_color,omitempty"`
VisualNotificationEnable bool `json:"visual_notification_enable,omitempty"`
VisualNotificationModes string `json:"visual_notifiaction_modes,omitempty"`
PrivateChatNotificationsEnable bool `json:"private_chat_notifiactions_enable,omitempty"`
GroupChatNotificationsEnable bool `json:"group_chat_notifiactions_enable,omitempty"`
ChannelNotificationsEnable bool `json:"channel_notifications_enable,omitempty"`
Language string `json:"language,omitempty"`
Theme string `json:"theme,omitempty"`
FingerprintEnable bool `json:"fingerprint_enable,omitempty"`
ShowFavoriteContacts bool `json:"show_favorite_contacts,omitempty"`
NightModeEnable bool `json:"night_mode_enable,omitempty"`
// Debug
DbSuffix string `json:"db_suffix,omitempty"`
// Inner
memoryPassword string
identityFile string
memoryPassword string `json:"memory_password,omitempty"`
additionalPasswords []string `json:"additional_passwords,omitempty"`
me *Identity `json:"me,omitempty"`
}
var instance *Config
@ -75,3 +88,19 @@ func (c *Config) SetMemPass(pass string) {
func (c *Config) GetMemPass() string {
return c.memoryPassword
}
func (c *Config) GetIdentity() *Identity {
return c.me
}
func (c *Config) SetIdentity(id *Identity) {
c.me = id
}
func (c *Config) SaveIdentity() error {
return c.me.Save()
}
func (c *Config) Clean() {
c.additionalPasswords = []string{}
}

View File

@ -7,6 +7,7 @@ import (
func TestConfigSave(t *testing.T) {
c := GetConfig()
c.memoryPassword = "hideme"
c.IdentityFile = "test.id"
c.Chunksize = 10000000
c.SavePassword = true
c.SavedPassword = "stupid"

View File

@ -3,39 +3,47 @@ package client
import (
"encoding/json"
"errors"
"math/rand"
"os"
"time"
"forge.redroom.link/yves/meowlib"
"github.com/ProtonMail/gopenpgp/v2/helper"
"github.com/google/uuid"
)
const maxHiddenCount = 30
type Identity struct {
Nickname string `json:"nickname,omitempty"`
RootKp meowlib.KeyPair `json:"id_kp,omitempty"`
Status string `json:"status,omitempty"`
Peers PeerList `json:"peers,omitempty"`
HiddenPeers [][]byte `json:"hiddend_peers,omitempty"`
Device meowlib.KeyPair `json:"device,omitempty"`
KnownServers InternalServerList `json:"known_servers,omitempty"`
MessageServers InternalServerList `json:"message_servers,omitempty"`
ArchiveServers InternalServerList `json:"archive_servers,omitempty"`
OwnedServers InternalServerList `json:"owned_servers,omitempty"`
DefaultDbPassword string `json:"default_db_password,omitempty"`
DbPasswordStore bool `json:"db_password_store,omitempty"`
OwnedDevices PeerList `json:"owned_devices,omitempty"`
StaticMtkServerPaths []InternalServerList `json:"static_mtk_server_paths,omitempty"`
DynamicMtkServeRules []string `json:"dynamic_mtk_serve_rules,omitempty"`
Nickname string `json:"nickname,omitempty"`
DefaultAvatarUuid string `json:"default_avatar_uuid,omitempty"`
RootKp meowlib.KeyPair `json:"id_kp,omitempty"`
Status string `json:"status,omitempty"`
Peers PeerList `json:"peers,omitempty"`
HiddenPeers [][]byte `json:"hiddend_peers,omitempty"`
Device meowlib.KeyPair `json:"device,omitempty"`
KnownServers ServerList `json:"known_servers,omitempty"`
MessageServers ServerList `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"`
unlockedHiddenPeers PeerList
}
func CreateIdentity(nickname string) *Identity {
var id Identity
id.Nickname = nickname
id.RootKp = meowlib.NewKeyPair()
GetConfig().me = &id
id.generateRandomHiddenStuff()
return &id
}
func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerIdxs []int) (*meowlib.ContactCard, error) {
// Creates an invitation for a peer, returns the peer containing
func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerIdxs []int) (*Peer, *meowlib.ContactCard, error) {
var peer Peer
var myContactCard meowlib.ContactCard
peer.MyIdentity = meowlib.NewKeyPair()
@ -44,11 +52,11 @@ func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerI
peer.Name = ContactName
peer.InvitationId = uuid.New().String()
if id.MessageServers.Servers == nil {
return nil, errors.New("no message servers defined in your identity")
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, errors.New("requested server out of range of defined message servers")
return nil, nil, errors.New("requested server out of range of defined message servers")
}
}
for _, i := range MessageServerIdxs {
@ -62,7 +70,7 @@ func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerI
myContactCard.InvitationId = peer.InvitationId
id.Peers = append(id.Peers, peer)
return &myContactCard, nil
return &peer, &myContactCard, nil
}
func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string) {
@ -114,7 +122,7 @@ func (id *Identity) FinalizeInvitation(ReceivedContact *meowlib.ContactCard) err
func LoadIdentity(filename string, password string) (*Identity, error) {
var id Identity
GetConfig().memoryPassword = password
GetConfig().identityFile = filename
GetConfig().IdentityFile = filename
indata, err := os.ReadFile(filename)
if err != nil {
return nil, err
@ -124,15 +132,91 @@ func LoadIdentity(filename string, password string) (*Identity, error) {
return nil, err
}
err = json.Unmarshal([]byte(pass), &id)
GetConfig().me = &id
return &id, err
}
func (id *Identity) Save() error {
if GetConfig().IdentityFile == "" {
return errors.New("identity filename empty")
}
b, _ := json.Marshal(id)
armor, err := helper.EncryptMessageWithPassword([]byte(GetConfig().memoryPassword), string(b))
if err != nil {
return err
}
err = os.WriteFile(GetConfig().identityFile, []byte(armor), 0600)
err = os.WriteFile(GetConfig().IdentityFile, []byte(armor), 0600)
return err
}
func (id *Identity) TryUnlockHidden(password string) error {
found := false
for _, encPeer := range id.HiddenPeers {
p := Peer{}
jsonPeer, err := meowlib.SymDecrypt(password, encPeer)
if err == nil {
err = json.Unmarshal(jsonPeer, &p)
if err != nil {
return err
}
p.dbPassword = password
id.unlockedHiddenPeers = append(id.unlockedHiddenPeers, p)
found = true
}
}
if found {
return nil
}
return errors.New("no peer found")
}
func (id *Identity) HidePeer(peerIdx int, password string) error {
serializedPeer, err := json.Marshal(id.Peers[peerIdx])
if err != nil {
return err
}
encrypted, err := meowlib.SymEncrypt(password, serializedPeer)
if err != nil {
return err
}
// add encrypted peer data
id.HiddenPeers = append(id.HiddenPeers, encrypted)
// remove clear text peer
id.Peers = append(id.Peers[:peerIdx], id.Peers[peerIdx+1:]...)
return nil
}
func (id *Identity) generateRandomHiddenStuff() {
rand.Seed(time.Now().UnixNano())
count := rand.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)
id.HidePeer(0, randomLenString(8, 14))
// TODO Add conversations
}
}
func randomLenString(min int, max int) string {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(max-min) + min
return randomString(n)
}
func randomString(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
s := make([]rune, n)
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}

View File

@ -5,6 +5,7 @@ import (
"os"
"testing"
"forge.redroom.link/yves/meowlib"
"github.com/stretchr/testify/assert"
)
@ -16,6 +17,31 @@ func exists(filename string) bool {
}
}
func createId() *Identity {
config := GetConfig()
config.IdentityFile = "test.id"
config.memoryPassword = "generalPassword"
// ! Extension to quickly open db : Debug only !
config.DbSuffix = ".sqlite"
id := CreateIdentity("myname")
err := id.Save()
if err != nil {
log.Fatal("Save failed")
}
var p Peer
p.Name = "testName"
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)
return id
}
func TestLoad(t *testing.T) {
if exists("test.id") {
os.Remove("test.id")
@ -24,6 +50,8 @@ func TestLoad(t *testing.T) {
if err != nil {
id := CreateIdentity("myname")
id.Save()
} else {
log.Println(id.Nickname)
}
id, err = LoadIdentity("test.id", "toto")
if err != nil {
@ -36,6 +64,18 @@ func TestLoad(t *testing.T) {
}
}
func TestCreate(t *testing.T) {
func TestHidePeer(t *testing.T) {
id := createId()
name := id.Peers[0].Name
assert.Equal(t, len(id.Peers), 1)
h := len(id.HiddenPeers)
id.HidePeer(0, "mypassword")
assert.Equal(t, len(id.Peers), 0)
assert.Equal(t, len(id.HiddenPeers), h+1)
id.TryUnlockHidden("mypassword")
assert.Equal(t, len(id.unlockedHiddenPeers), 1)
assert.Equal(t, id.unlockedHiddenPeers[0].Name, name)
if exists("test.id") {
os.Remove("test.id")
}
}

View File

@ -5,7 +5,7 @@ import (
"github.com/google/uuid"
)
func ProcessForOutput(usermessage *meowlib.UserMessage, peer *Peer, servers *InternalServerList, trackingLookupKey string) ([]byte, error) {
func ProcessForOutput(usermessage *meowlib.UserMessage, peer *Peer, servers *ServerList, trackingLookupKey string) ([]byte, error) {
lastIdx := len(servers.Servers) - 1
// LAST SERVER : Message delivery as usual
srv := &servers.Servers[lastIdx]

View File

@ -1,6 +1,13 @@
package client
func ProcessOutboundTextMessage(peer *Peer, text string, srv *InternalServer) ([]byte, error) {
import "forge.redroom.link/yves/meowlib"
type InternalUserMessage struct {
message *meowlib.UserMessage
dbid int64
}
func ProcessOutboundTextMessage(peer *Peer, text string, srv *Server) ([]byte, error) {
// Creating User message
usermessage, err := peer.BuildSimpleUserMessage([]byte(text))
if err != nil {

View File

@ -11,14 +11,19 @@ import (
"google.golang.org/protobuf/proto"
)
// Peer manages the peer messaging functions
// - Building simple user messages
// - Utility functions for packing/unpacking, encrypting/decrypting messages for peer communication
type Peer struct {
Name string `json:"name,omitempty"`
Name string `json:"name,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.Server `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 []meowlib.ServerCard `json:"my_pull_servers,omitempty"`
// Peer keys and infos
Contact meowlib.ContactCard `json:"contact,omitempty"`
InvitationId string `json:"invitation_id,omitempty"`
@ -31,53 +36,13 @@ type Peer struct {
OnionMode bool `json:"onion_mode,omitempty"`
LastMessage time.Time `json:"last_message,omitempty"`
DbIds []string `json:"db_ids,omitempty"`
DbPassword string `json:"db_password,omitempty"`
AvatarUuid string `json:"avatar_uid,omitempty"`
dbPassword string
}
type PeerList []Peer
type Group struct {
Name string `json:"name,omitempty"`
Members []Peer `json:"members,omitempty"`
}
func (pl *PeerList) GetFromPublicKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.Contact.ContactPublicKey == publickey {
return &peer
}
}
return nil
}
func (pl *PeerList) GetFromMyLookupKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.MyLookupKp.Public == publickey {
return &peer
}
}
return nil
}
func (pl *PeerList) GetFromName(name string) *Peer {
for _, peer := range *pl {
if peer.Contact.Name == name {
return &peer
}
}
return nil
}
func (pl *PeerList) GetConversationRequests() []*meowlib.ToServerMessage_ConversationRequest {
var list []*meowlib.ToServerMessage_ConversationRequest
for _, peer := range *pl {
var cr meowlib.ToServerMessage_ConversationRequest
cr.LookupKey = peer.MyLookupKp.Public
// TODO Add key signature
list = append(list, &cr)
}
return list
}
//
// Messages building
//
func (p *Peer) BuildSimpleUserMessage(message []byte) (*meowlib.UserMessage, error) {
var msg meowlib.UserMessage
@ -85,7 +50,7 @@ func (p *Peer) BuildSimpleUserMessage(message []byte) (*meowlib.UserMessage, err
msg.From = p.MyIdentity.Public
msg.Data = message
msg.Type = "1"
msg.Status = &meowlib.UserMessage_ConversationStatus{}
msg.Status = &meowlib.ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
return &msg, nil
}
@ -96,16 +61,13 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
if err != nil {
return nil, err
}
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
// declare chunk size
maxSz := GetConfig().Chunksize
// create buffer
b := make([]byte, maxSz)
chunk := 0
@ -129,7 +91,7 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
msg.Files = append(msg.Files, &file)
msg.Type = "2"
if chunk == 0 {
msg.Status = &meowlib.UserMessage_ConversationStatus{}
msg.Status = &meowlib.ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
}
msgs = append(msgs, msg)
@ -138,6 +100,27 @@ func (p *Peer) BuildSingleFileMessage(filename string, message []byte) ([]meowli
return msgs, nil
}
// 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) {
var msg meowlib.UserMessage
var invitation meowlib.Invitation
invitation.Step = 3
out, err := proto.Marshal(myContactCard)
if err != nil {
return nil, err
}
invitation.Payload = out
msg.Destination = p.Contact.LookupPublicKey
msg.From = p.MyIdentity.Public
msg.Type = "1"
return &msg, nil
}
//
// Messages encryption and packaging
//
func (p *Peer) SerializeUserMessage(msg *meowlib.UserMessage) ([]byte, error) {
out, err := proto.Marshal(msg)
if err != nil {
@ -176,7 +159,7 @@ func (p *Peer) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMe
return DecryptedMessage, err
}
// PackUserMessage will package the previously encrypted message for sending it to the peer in protobuff format
// 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
@ -185,6 +168,7 @@ func (p *Peer) PackUserMessage(message []byte, signature []byte) *meowlib.Packed
return &msg
}
// UnPackUserMessage unpacks a user message
func (p *Peer) UnPackUserMessage(protoPackedMessage []byte) (payload []byte, signature []byte, err error) {
msg := &meowlib.PackedServerMessage{}
if err := proto.Unmarshal(protoPackedMessage, msg); err != nil {
@ -193,11 +177,12 @@ func (p *Peer) UnPackUserMessage(protoPackedMessage []byte) (payload []byte, sig
return msg.Payload, msg.Signature, nil
}
func (p *Peer) GetConversationRequest() meowlib.ToServerMessage_ConversationRequest {
var cr meowlib.ToServerMessage_ConversationRequest
return cr
func (p *Peer) GetConversationRequest() *meowlib.ConversationRequest {
var cr meowlib.ConversationRequest
return &cr
}
// ProcessOutboundUserMessage is a helper function that serializes, encrypts and packs a user message
func (p *Peer) ProcessOutboundUserMessage(usermessage *meowlib.UserMessage) (*meowlib.PackedUserMessage, error) {
serializedMessage, err := p.SerializeUserMessage(usermessage)
if err != nil {
@ -210,10 +195,10 @@ func (p *Peer) ProcessOutboundUserMessage(usermessage *meowlib.UserMessage) (*me
}
// Packing it
packedMsg := p.PackUserMessage(enc.Data, enc.Signature)
return packedMsg, nil
}
// ProcessInboundUserMessage is a helper function that decrypts and deserializes a user message
func (p *Peer) ProcessInboundUserMessage(message []byte, signature []byte) (*meowlib.UserMessage, error) {
dec, err := p.AsymDecryptMessage(message, signature)
if err != nil {
@ -226,6 +211,19 @@ func (p *Peer) ProcessInboundUserMessage(message []byte, signature []byte) (*meo
return msg, nil
}
//
// Messages database
//
// SetDbPassword sets a specific password for a hidden user, it won't be saved, but kept in memory only for the next operations
func (p *Peer) SetDbPassword(password string) {
p.dbPassword = password
}
func (p *Peer) GetDbPassword() string {
return p.dbPassword
}
func (p *Peer) StoreMessage(msg []byte) {
}

View File

@ -1,10 +1,21 @@
package client
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetFromPublicKey(t *testing.T) {
id := CreateIdentity("test")
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)
}
p5 := id.Peers.GetFromPublicKey("stringToFind5")
assert.Equal(t, p5.Name, "test5")
}

45
client/peerlist.go Normal file
View File

@ -0,0 +1,45 @@
package client
import (
"forge.redroom.link/yves/meowlib"
)
type PeerList []Peer
func (pl *PeerList) GetFromPublicKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.Contact.ContactPublicKey == publickey {
return &peer
}
}
return nil
}
func (pl *PeerList) GetFromMyLookupKey(publickey string) *Peer {
for _, peer := range *pl {
if peer.MyLookupKp.Public == publickey {
return &peer
}
}
return nil
}
func (pl *PeerList) GetFromName(name string) *Peer {
for _, peer := range *pl {
if peer.Contact.Name == name {
return &peer
}
}
return nil
}
func (pl *PeerList) GetConversationRequests() []*meowlib.ConversationRequest {
var list []*meowlib.ConversationRequest
for _, peer := range *pl {
var cr meowlib.ConversationRequest
cr.LookupKey = peer.MyLookupKp.Public
// TODO Add key signature
list = append(list, &cr)
}
return list
}

View File

@ -9,46 +9,46 @@ import (
"google.golang.org/protobuf/proto"
)
type InternalServer struct {
ServerData meowlib.Server `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"`
Me meowlib.KeyPair `json:"me,omitempty"`
Country string `json:"country,omitempty"`
AllowedDelay int `json:"allowed_delay,omitempty"`
// Server manages server related operations
// - Sending messages for server usage
// - Two first steps of an invitation
// - User message sending with UserKp identification
// - Messages lookup requests
// - 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"`
}
type InternalServerList struct {
Name string
Servers []InternalServer
}
func InternalServerFromUrl(url string) *InternalServer {
var is InternalServer
// 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
return &is
}
func InternalServerFromServer(server *meowlib.Server) *InternalServer {
var is InternalServer
// Create a server from a server card
func CreateServerFromServerCard(server *meowlib.ServerCard) *Server {
var is Server
is.ServerData = *server
is.Me = meowlib.NewKeyPair()
is.UserKp = meowlib.NewKeyPair()
return &is
}
func (sl *InternalServerList) AddUrls(urls []string) {
for _, url := range urls {
sl.Servers = append(sl.Servers, *InternalServerFromUrl(url))
}
}
// AsymEncryptMessage prepares a message to send to a specific internal server
func (ints *InternalServer) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, error) {
func (ints *Server) AsymEncryptMessage(Message []byte) (*meowlib.EncryptedMessage, error) {
var enc *meowlib.EncryptedMessage
enc, err := meowlib.AsymEncryptAndSign(ints.ServerData.PublicKey, ints.Me.Private, Message)
enc, err := meowlib.AsymEncryptAndSign(ints.ServerData.PublicKey, ints.UserKp.Private, Message)
if err != nil {
fmt.Println(err.Error())
return nil, err
@ -57,8 +57,8 @@ func (ints *InternalServer) AsymEncryptMessage(Message []byte) (*meowlib.Encrypt
}
// AsymDecryptMessage reads a message from a specific internal server
func (ints *InternalServer) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMessage []byte, err error) {
DecryptedMessage, err = meowlib.AsymDecryptAndCheck(ints.Me.Private, ints.ServerData.PublicKey, Message, Signature)
func (ints *Server) AsymDecryptMessage(Message []byte, Signature []byte) (DecryptedMessage []byte, err error) {
DecryptedMessage, err = meowlib.AsymDecryptAndCheck(ints.UserKp.Private, ints.ServerData.PublicKey, Message, Signature)
if err != nil {
fmt.Println(err.Error())
return nil, err
@ -66,18 +66,18 @@ func (ints *InternalServer) AsymDecryptMessage(Message []byte, Signature []byte)
return DecryptedMessage, err
}
// Creates a basic message to server from a single packed user message and returns it as protobuf serialized byte array
func (ints *InternalServer) BuildToServerMessageFromUserMessage(usermsg *meowlib.PackedUserMessage) *meowlib.ToServerMessage {
// BuildToServerMessageFromUserMessage creates a basic message to server from a single packed user message and returns a meowlib.ToServerMessage
func (ints *Server) BuildToServerMessageFromUserMessage(usermsg *meowlib.PackedUserMessage) *meowlib.ToServerMessage {
var msg meowlib.ToServerMessage
msg.Uuid = uuid.New().String()
msg.Type = "1"
msg.From = ints.Me.Public
msg.From = ints.UserKp.Public
msg.Messages = append(msg.Messages, usermsg)
return &msg
}
// Creates a basic message to server from a single packed user message and returns it as protobuf serialized byte array
func (ints *InternalServer) BuildMessageSendingMessage(usermsg *meowlib.PackedUserMessage) ([]byte, error) {
// BuildMessageSendingMessage creates a basic message to server from a single packed user message and returns it as protobuf serialized byte array
func (ints *Server) BuildMessageSendingMessage(usermsg *meowlib.PackedUserMessage) ([]byte, error) {
msg := ints.BuildToServerMessageFromUserMessage(usermsg)
out, err := proto.Marshal(msg)
if err != nil {
@ -86,13 +86,12 @@ func (ints *InternalServer) BuildMessageSendingMessage(usermsg *meowlib.PackedUs
return out, nil
}
// Creates a basic message to server from a single packed user message and returns it as protobuf serialized byte array
func (ints *InternalServer) BuildMessageRequestMessage(lookupKeys []string) ([]byte, error) {
// BuildMessageRequestMessage creates a message lookup message to server and returns it as protobuf serialized byte array
func (ints *Server) BuildMessageRequestMessage(lookupKeys []string) ([]byte, error) {
var msg meowlib.ToServerMessage
msg.Uuid = uuid.New().String()
msg.Type = "1"
msg.From = ints.Me.Public
msg.From = ints.UserKp.Public
out, err := proto.Marshal(&msg)
if err != nil {
return nil, err
@ -100,9 +99,43 @@ func (ints *InternalServer) BuildMessageRequestMessage(lookupKeys []string) ([]b
return out, nil
}
func (ints *InternalServer) PackServerMessage(payload []byte, signature []byte) (protoPackedMessage []byte, err error) {
// 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) {
var msg meowlib.ToServerMessage
var inv meowlib.Invitation
payload, err := invitation.Compress()
if err != nil {
return nil, err
}
msg.Type = "1"
msg.From = ints.UserKp.Public
inv.Step = 1
inv.Password = password
inv.Timeout = int32(timeout)
inv.ShortcodeLen = int32(invitationIdLen)
inv.Payload = payload
msg.Invitation = &inv
return &msg, nil
}
// BuildToServerMessageInvitationRequest requests invitation with provided id from server and returns it as a meowlib.ToServerMessage
func (ints *Server) BuildToServerMessageInvitationRequest(shortcode string, password string) (*meowlib.ToServerMessage, error) {
var msg meowlib.ToServerMessage
var inv meowlib.Invitation
msg.Type = "1"
msg.From = ints.UserKp.Public
inv.Step = 2
inv.Password = password
inv.Shortcode = shortcode
msg.Invitation = &inv
return &msg, nil
}
// PackServerMessage
func (ints *Server) PackServerMessage(payload []byte, signature []byte) (protoPackedMessage []byte, err error) {
var msg meowlib.PackedServerMessage
msg.From = ints.Me.Public
msg.From = ints.UserKp.Public
msg.Payload = payload
msg.Signature = signature
out, err := proto.Marshal(&msg)
@ -112,7 +145,7 @@ func (ints *InternalServer) PackServerMessage(payload []byte, signature []byte)
return out, nil
}
func (ints *InternalServer) UnPackServerMessage(protoPackedMessage []byte) (payload []byte, signature []byte, err error) {
func (ints *Server) UnPackServerMessage(protoPackedMessage []byte) (payload []byte, signature []byte, err error) {
msg := &meowlib.PackedServerMessage{}
if err := proto.Unmarshal(protoPackedMessage, msg); err != nil {
return nil, nil, err
@ -120,7 +153,7 @@ func (ints *InternalServer) UnPackServerMessage(protoPackedMessage []byte) (payl
return msg.Payload, msg.Signature, nil
}
func (srv *InternalServer) ProcessOutboundMessage(toServerMessage *meowlib.ToServerMessage) ([]byte, error) {
func (srv *Server) ProcessOutboundMessage(toServerMessage *meowlib.ToServerMessage) ([]byte, error) {
byteToServerMessage, err := proto.Marshal(toServerMessage)
if err != nil {
return nil, err
@ -138,7 +171,7 @@ func (srv *InternalServer) ProcessOutboundMessage(toServerMessage *meowlib.ToSer
return protoPackedServerMsg, nil
}
func (srv *InternalServer) ProcessInboundServerResponse(msg []byte) (*meowlib.FromServerMessage, error) {
func (srv *Server) ProcessInboundServerResponse(msg []byte) (*meowlib.FromServerMessage, error) {
fsmsg := &meowlib.FromServerMessage{}
payload, signature, err := srv.UnPackServerMessage(msg)
if err != nil {

52
client/serverlist.go Normal file
View File

@ -0,0 +1,52 @@
package client
import "errors"
// ServerList manages lists that are used in Identity
// - Message servers list
// - ArchiveServers lists
// - Owned servers lists
// - Matriochka paths
type ServerList struct {
Name string `json:"name,omitempty"`
Servers []Server `json:"servers,omitempty"`
}
// FilterByIdxs returns a filtered server list filtered according to an index list
func (sl *ServerList) FilterByIdxs(MessageServerIdxs []int) (filtered *ServerList, err error) {
filtered.Servers = []Server{}
for _, i := range MessageServerIdxs {
if i > len(sl.Servers)-1 {
return nil, errors.New("requested server out of range of defined message servers")
}
}
for _, i := range MessageServerIdxs {
filtered.Servers = append(filtered.Servers, sl.Servers[i])
}
return filtered, nil
}
// GetServerByIdx returns a server from it's index
func (sl *ServerList) GetServerByIdx(idx int) (server *Server, err error) {
if idx > len(sl.Servers)-1 {
return nil, errors.New("requested server out of range of defined message servers")
}
return &sl.Servers[idx], nil
}
// 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 {
return &srv
}
}
return nil
}
// AddUrls is a simple utility functon used mainly as a shortcut for testing purposes
func (sl *ServerList) AddUrls(urls []string) {
for _, url := range urls {
sl.Servers = append(sl.Servers, *CreateServerFromUrl(url))
}
}

184
client/storage.go Normal file
View File

@ -0,0 +1,184 @@
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
}

72
client/storage_test.go Normal file
View File

@ -0,0 +1,72 @@
package client
import (
"log"
"os"
"path/filepath"
"testing"
"forge.redroom.link/yves/meowlib"
"github.com/stretchr/testify/assert"
)
func TestStoreMessage(t *testing.T) {
id := createId()
var um meowlib.UserMessage
um.Data = []byte("blabla")
err := StoreMessage(&id.Peers[0], &um, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
messages, err := GetLastMessages(&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")
// Cleanup
if exists("test.id") {
os.Remove("test.id")
}
files, err := filepath.Glob("*.sqlite")
if err != nil {
panic(err)
}
for _, f := range files {
if err := os.Remove(f); err != nil {
panic(err)
}
}
}
func TestManyStoreMessage(t *testing.T) {
id := createId()
for i := 1; i < 100; i++ {
var um meowlib.UserMessage
um.Data = []byte(randomLenString(20, 200))
err := StoreMessage(&id.Peers[0], &um, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
}
messages, err := GetLastMessages(&id.Peers[0], 0, 0, 10, GetConfig().memoryPassword)
if err != nil {
log.Fatal(err)
}
// Checks
assert.Equal(t, len(messages), 10, "not 10 message")
// Cleanup
if exists("test.id") {
os.Remove("test.id")
}
files, err := filepath.Glob("*.sqlite")
if err != nil {
panic(err)
}
for _, f := range files {
if err := os.Remove(f); err != nil {
panic(err)
}
}
}

View File

@ -17,8 +17,8 @@ import (
"google.golang.org/protobuf/proto"
)
func ServerFromUrl(url string) *Server {
var s Server
func ServerFromUrl(url string) *ServerCard {
var s ServerCard
s.Url = url
return &s
}

12
doc/docgen.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
go-plantuml generate -o generated/meowlib.puml -d ..
sed -i 's/\.\./meowlib\ /g' generated/meowlib.puml
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
cd generated
plantuml .
plantuml -latex .

218
doc/meow.tex Normal file
View File

@ -0,0 +1,218 @@
\documentclass{article}
\usepackage{fetamont}
\begin{document}
\title{
\textffm{Meow} messaging protocol}
\author{Author
\texttt{meow@redroom.link}}
\date{\today}
\maketitle
\begin{abstract}
The \textffm{Meow} protocol is a privacy driven instant messaging protocol.
That protocol might be used for creating secure and distributed chat services or allowing machine to machine communication.
This document describes the services provided by the protocol.
\begin{quote}
\centering
\emph{"Nous ne vivrons pas d'utopie collective, nous arrivons trop tard, le grand marché est déjà là.
Nous devons élaborer des stratégies de survie et de contamination, par la prolifération d'utopies privées, cryptées, qui se substitueront à l'ancien ordre social.
Tout ce que je sais, c'est que nous vivons dans un monde dont on ne s'évade pas"}\\
\footnotesize{Maurice G. Dantec for NOII (1997)}
\end{quote}
\end{abstract}
\section{Services}
\subsection{Unregulated identities}
The only requirement to get a valid \textffm{Meow} identity is to generate a user key pair.
No phone number or email check will be performed, unlike main instant messaging protocols, there is no central administration.
\subsection{Fine grained privacy control}
\subsubsection{Trustable server based communication}
Like most widely available messaging softwares, (Whatsapp, Signal, Viber, Telegram...), \textffm{Meow} provides a simple server based messaging.
The main difference is that \textffm{Meow} allows you to explicitly choose which server you want to use.
The server code being open source, we strongly encourage you to run your own server at home or in your company.
The server requires very few ressources and will run on any low cost single board computer.
\subsubsection{Anonymized message transfer}
\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.
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}
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.
\subsubsection{Peer based privacy settings}
You might define specific communication privacy preferences for each of your contacts:
\begin{itemize}
\item simple server based communication allowed for Sarah,
\item preferred direct communication with Julian, fallback to my own server,
\item required matriochka protocol for Edward, first node is one of my trusted servers, my message node is my own server, randomly switch from trusted server lists for others.
\item ...
\end{itemize}
\subsubsection{Resistance to device requisition / forensic}
All your contact information and discussion are encrypted on the device and password protected.
Password shall be asked on application startup and allows your identity file and contact decrytion.
That password is not recoverable, so you can't forget it, or you'll loose your whole configuration and identity.
Real security implies some constraints.
You might configure the app to save your password, but that is a security flaw.
In many authoritarian countries, you are required by law to provide your device passwords to authorities.
In a \textffm{Meow} device, you might set a specific password for some contacts.
Those contacts won't be visible when entering your main identity password.
You'll have to type their specific password in order to make them visible.
The \textffm{Meow} application will by default create a random set of fake hidden contacts and conversations.
Even in case of device storage analysis, authorities won't be able to differentiate a real hidden contact from an normal fake generated one.
It could be argued that this feature puts every user at risk, because authorities might think you're hiding something, even if you're not.
As every \textffm{Meow} user has the same constraint, users are not responsible for that. Moreover solidarity is also a requirement for real security.
\subsection{Multiple devices support}
\textffm{Meow} allows you to be connected from multiple devices and offers chat synchronization capability.
A device might be revoqued anytime from any other one. Proof of your identity (password or other) shall be provided in order to grant device revocation.
\subsection{Adding contacts}
If you want to add a new contact, keys will be generated, then a contact card will be created.
That contact card might be sent by any trustable communication means, or preferably from hand to hand, as a file on a flash disk or a QR code.\\
In return your contact will provide a similar contact card as an answer to your invitation.
\subsection{Contacts forwarding}
By using the \textffm{Meow} protocol a user won't be able to forward your contact information without your consent.
Each user knows you as a different identity, thus forwarding a known identity to another user is meaningless. Any message to that identity signed by another user than you would be discarded.
\subsection{Group conversation}
A very basic group messaging service is available. It allows to exchange group information between users. After that, a message to a group will send a copy of the message to each member.
\subsection{Emergency broadcast}
A local (server based) emergency broadcast service will be provided. It will provide the ability to send/receive broadcast messages to all users connected to the current server.
\subsection{Public networks shortage resilience}
\textffm{Meow} may run without Internet connection, either on an isolated wifi access point, or on a meshed network of wifi routers or even via serial IOT transport layers (LoRa,...)
\subsection{User directory service}
This service allows restoring a lost functionality of Internet historic chat services (like ICQ). You could simply set a "Free for chat" status that would allow other people to contact you, either randomly or based on a short description that you might provide.
Why providing that service while the internet is suffocating due to the abundance of social networks ?\\
Well, that option offers a few advantages :
\begin{itemize}
\item you are still an anonymous user chatting with other anonymous users;
\item no social network algorithm will select people that think/behave/vote/eat... just like you. Diversity makes a better world;
\item a smaller community of users, skilled enough to operate a \textffm{Meow} chat app... that might provide a first filter;
It's a bit like in the early ages, when people had to be able to start a win98 computer, connect it to internet, then download and install ICQ...
If you lost some time in social networks today, and experienced ICQ in the 2000's, you'll understand what we'd like to revive.
\end{itemize}
\section{Identities and keys}
\subsection{User identity}
Each \textffm{Meow} user has a unique identity. That identity is strictly private, only used to manage your own data (local encryption, devices, ...)
Let's call that one the User Key Pair (Ukp)
\subsection{Contact identity}
Each of your contacts will know you under a different identity, we'll call that one the Contact Key Pair (Ckp)
That contact Key Pair will not change once it's agreed between both peers: an initial key will be exchanged as part of the peer invitation process.
As other people might have seen your key, this means that :
\begin{itemize}
\item none of your contacts will be able to forward your id to another person without your consent;
\item any message to that Ckp, not signed by its associated user, will be discarded.
\end{itemize}
\subsection{Conversation encryption}
Each conversation with one of your contacts will be encrypted using an encryption keypair (Ekp) allowing cyphering your conversation.
The Ekp might be changed anytime by its owner and the new public key will be sent along the last message.
\subsection{Conversation lookup}
A contact conversation Lookup Key Pair(Lkp) is also associated with your conversation. The Lkp public key is used to identify your conversation on a server.
The private key allows you to sign your request and prove the server that you are the legitimate recipient for a message.
This Lkp can be changed anytime by its owner and the new public key will be sent along the last message.
The Lkp and the Ekp are only changed once the change has been acknowledged by your contact.
\subsection{Server identity}
Each server has a Server key (Skp). That key allows you to cypher the messages that you're sending to the server.
\subsection{Device identity}
Each device is identified by a device key (Dkp) that allows you to perform secured exchanges between your devices for synchronization/revocation purposes.
Communication between devices is achieved using the same principle as the user to user communication. A device might be considered as any another user. The messages content is based on a synchronization protocol.
\section{Contact management}
\subsection{Adding a contact}
Rendez-vous card, containing :
\begin{itemize}
\item Your public key for that contact;
\item An initial conversation public key for getting encrypted messages from that contact;
\item An initial conversation uuid that you'll use to lookup for incoming messages on the servers;
\item A list of your preferred message servers;
\item A signature to prevent transmission of tampered data.
\end{itemize}
\subsection{Sharing a contact}
If a user wants to forward one of his contacts to you, it will be handled as a double request:
\begin{enumerate}
\item I'm receiving a contact name, without any key
\item
\end{enumerate}
\section{Messaging}
\subsection{User messages}
TODO
\subsection{Server stored message}
TODO
\subsection{Matriochka message packing}
TODO
\subsection{Synchronization messages}
TODO
\section{Server Features}
\subsection{Server catalog}
Each server will cache a list of all the servers that it is aware of.
This server list will be shared between servers in a lazy exchange mode.
\subsection{Antispam}
\subsection{Self defense}
The servers do integrate self defense mechanisms. Any threat to the \textffm{Meow} network by any computer,
computer group or organization, might result in a distributed response from volunteering \textffm{Meow} servers and clients.
An information about threat, desired defense action and request for assitance, might be submitted by any server or group of servers.
Server owners and client users might accept or refuse to participate to the response action.
TODO : Request and actions definition consensus mechanism
\section{Backup}
\section{Recovery}
\section{Very secure devices}
You don't trust your phone ?
We're planning to provide very secured minimal devices dedicated to very sensitive \textffm{Meow} communication.
\section{Roadmap}
\subsection{Nations}
Beyond the scope of user directories, we plan to implement the concept of virtual Nations.
Nation will allow people to regroup around common political funding values.
They're not exclusive, you might be a citizen of several virtual nations.
Today still, most people don't really choose the nation they live in.
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".
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.
If requiring more democracy, using the same technique that is preventing it from happening, has to qualified that way, so be it.
\end{document}

View File

@ -1,8 +1,11 @@
\documentclass{article}
\usepackage{fetamont}
\usepackage{listings}
\usepackage{protobuf/lang} % include language definition for protobuf
\usepackage{protobuf/style} % include custom style for proto declarations.
\begin{document}
\title{
\textffm{Meow} messaging protocol}
\textffm{Meow} messaging protocol description}
\author{Author
\texttt{meow@redroom.link}}
\date{\today}
@ -25,155 +28,13 @@ Tout ce que je sais, c'est que nous vivons dans un monde dont on ne s'évade pas
\end{quote}
\end{abstract}
\section{Identity creation}
\section{Services}
\subsection{Unregulated identities}
The only requirement to get a valid \textffm{Meow} identity is to generate a user key pair.
No phone number or email check will be performed, unlike main instant messaging protocols, there is no central administration.
\section{Messages structure}
\lstinputlisting[language=protobuf2,style=protobuf]{../pb/messages.proto}
\subsection{Fine grained privacy control}
\section{Invitation process}
\subsubsection{Trustable server based communication}
Like most widely available messaging softwares, (Whatsapp, Signal, Viber, Telegram...), \textffm{Meow} provides a simple server based messaging.
The main difference is that \textffm{Meow} allows you to explicitly choose which server you want to use.
The server code being open source, we strongly encourage you to run your own server at home or in your company.
The server requires very few ressources and will run on any low cost single board computer.
\subsubsection{Anonymized message transfer}
\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 might be set for each forwarding step, making the overall message tracking much more difficult, even with a global network audit.
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}
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.
\subsubsection{Peer based privacy settings}
You might define specific communication privacy preferences for each of your contacts:
\begin{itemize}
\item simple server based communication allowed for Joe,
\item preferred direct communication with Julian, fallback to my own server,
\item required matriochka protocol for Edward, first node is one of my trusted servers, my message node is my own server, randomly switch from trusted server lists for others.
\item ...
\end{itemize}
\subsubsection{Resistance to device requisition}
All your contact information and discussion are encrypted on the device and password protected.
Password shall be asked on application startup and allows your identity file and contact decrytion.
That password is not recoverable, so you can't forget it, or you'll loose your whole configuration and identity.
Real security implies some constraints.
You might configure the app to save your password, but that is a security flaw.
In many authoritarian countries, you are required by law to provide your device passwords to authorities.
In a \textffm{Meow} device, you might set a special password for specific contacts.
Those contacts won't be visible when entering your main identity password.
You'll have to type their specific password in order to make them visible.
The \textffm{Meow} application will by default create a random set of fake hidden contacts and conversations.
Even in case of device storage analysis, authorities won't be able to differentiate a real hidden contact from an normal fake generated one.
It could be argued that this feature puts every user at risk, because authorities might think you're hiding something, even if you're not.
As every \textffm{Meow} user has the same constraint, users are not responsible for that. Moreover solidarity is also a requirement for real security.
\subsection{Multiple devices support}
\textffm{Meow} allows you to be connected from multiple devices and offers chat synchronization capability.
A device might be revoqued anytime from any other one. Proof of your identity (password or other) shall be provided in order to grant device revocation.
\subsection{Adding contacts}
If you want to add a new contact, keys and uuids will be generated, then a rendez-vous card will be created.
That rendez-vous card might be sent by any trustable communication means, or preferably from hand to hand, as a file on a flash disk or a QR code.\\
In return your contact will provide the exact same data, encrypted with your public key and delivered to the address specified in the initial rendez-vous card.
\subsection{Contacts forwarding}
By using the \textffm{Meow} protocol a user won't be able to forward your contact information without your consent.
Each user knows you as a different identity, thus forwarding a known identity to another user is meaningless. Any message to that identity signed by another user than you would be discarded.
\subsection{Group conversation}
A very basic group messaging service is available. It allows to exchange group information between users. After that, a message to a group will send a copy of the message to each member.
\subsection{Emergency broadcast}
A local (server based) emergency broadcast service will be provided. It will provide the ability to send/receive broadcast messages to all users connected to the current server.
\subsection{Public networks shortage resilience}
\textffm{Meow} may run without Internet connection, either on an isolated wifi access point, or on a meshed network of wifi routers or even via serial IOT transport layers (LoRa,...)
\subsection{User directory service}
This service allows to restore a lost functionality of Internet historic chat services (like ICQ). You could simply set a "Free for chat" status that would allow other people to contact you, either randomly or based on a short description that you might provide.
Why providing that service while the internet is suffocating due to the abundance of social networks ?\\
Well, that option offers a few advantages :
\begin{itemize}
\item you are still an anonymous user chatting with other anonymous users;
\item no social network algorithm will select people that think/behave/vote/eat... just like you. Diversity makes a better world;
\item a smaller community of users, skilled enough to operate a \textffm{Meow} chat app... that might provide a first filter;
It's a bit like in the old times, when people had to be able to start a win98 computer, connect it to internet, then download and install ICQ...
If you lost some time in social networks, and experienced ICQ in the 2000's, you'll understand.
\end{itemize}
\section{Identities and keys}
\subsection{User identity}
Each \textffm{Meow} user has a unique identity. That identity is strictly private, only used to manage your own data (local encryption, devices, ...)
Let's call that one the User Key Pair (Ukp)
\subsection{Contact identity}
Each of your contacts will know you under a different identity, we'll call that one the Contact Key Pair (Ckp)
That contact Key Pair will not change once it's agreed between both peers: an initial key will be exchanged as part of the peer invitation process.
As other people might have seen your key, this means that :
\begin{itemize}
\item none of your contacts will be able to forward your id to another person without your consent;
\item any message to that Ckp, not signed by its associated user, will be discarded.
\end{itemize}
\subsection{Conversation encryption}
Each conversation with one of your contacts will be encrypted using an encryption keypair (Ekp) allowing cyphering your conversation.
The Ekp might be changed anytime by its owner and the new public key will be sent along the last message.
\subsection{Conversation lookup}
A contact conversation Lookup Key Pair(Lkp) is also associated with your conversation. The Lkp public key is used to identify your conversation on a server.
The private key allows you to sign your request and prove the server that you are the legitimate recipient for a message.
This Lkp can be changed anytime by its owner and the new public key will be sent along the last message.
The Lkp and the Ekp are only changed once the change has beeen acknowledged by your contact.
\subsection{Server identity}
Each server has a Server key (Skp). That key allows you to cypher the messages that you're sending to the server.
\subsection{Device identity}
Each device is identified by a device key (Dkp) that allows you to perform secured exchanges between your devices for synchronization/revocation purposes.
Communication between devices is achieved using the same principle as the user to user communication. A device might be considered as any another user. The messages content is based on a synchronization protocol.
\section{Contact management}
\subsection{Adding a contact}
Rendez-vous card, containing :
\begin{itemize}
\item Your public key for that contact;
\item An initial conversation public key for getting encrypted messages from that contact;
\item An initial conversation uuid that you'll use to lookup for incoming messages on the servers;
\item A list of your preferred message servers;
\item A signature to prevent transmission of tampered data.
\end{itemize}
\subsection{Sharing a contact}
If a user wants to forward one of his contacts to you, it will be handled as a double request:
\begin{enumerate}
\item I'm receiving a contact name, without any key
\item
\end{enumerate}
\section{Messaging}
\subsection{User messages}
TODO
\subsection{Server stored message}
TODO
\subsection{Matriochka message packing}
TODO
\subsection{Synchronization messages}
TODO
\section{Transport protocols}
\subsection{URLs}

View File

@ -30,20 +30,21 @@ func TestEndToEnd(t *testing.T) {
// Create an invitation for a friend, I want him/her to know me as Bender //
////////////////////////////////////////////////////////////////////////////
fmt.Println("Creating an invitation for the first friend...")
invitation, err := Me.InvitePeer("Bender", "myfirstfriend", []int{1, 2})
peer, myContactCard, err := Me.InvitePeer("Bender", "myfirstfriend", []int{1, 2})
if err != nil {
println(err)
}
println(peer.Name)
// print my invitation
a, _ := json.Marshal(invitation)
a, _ := json.Marshal(myContactCard)
fmt.Println(string(a))
// TODO : Convert invitation to QR Code
invitation.WritePng("invitation.png")
data, err := invitation.Compress()
myContactCard.WritePng("invitation.png")
data, err := myContactCard.Compress()
if err != nil {
println(err)
}
invitation.WriteQr("qrcode.png")
myContactCard.WriteQr("qrcode.png")
println("Compressed contact card :", len(data))
///////////////////////////////////////
// Simulate peer invitation response //
@ -59,9 +60,9 @@ func TestEndToEnd(t *testing.T) {
ReceivedContact.ContactPublicKey = FirstFriendContactKp.Public
ReceivedContact.EncryptionPublicKey = FirstFriendEncryptionKp.Public
ReceivedContact.LookupPublicKey = FirstFriendLookupKp.Public
ReceivedContact.InvitationId = invitation.InvitationId
ReceivedContact.InvitationId = myContactCard.InvitationId
FriendServer1KP := meowlib.NewKeyPair()
FriendServer1 := meowlib.Server{Name: "FriendServer1", Url: "http://myfriend.org/meow/", PublicKey: FriendServer1KP.Public, Description: "Fancy description", ConfidenceLevel: 1}
FriendServer1 := meowlib.ServerCard{Name: "FriendServer1", Url: "http://myfriend.org/meow/", PublicKey: FriendServer1KP.Public, Description: "Fancy description"}
ReceivedContact.PullServers = append(ReceivedContact.PullServers, &FriendServer1)
///////////////////////////////////////////////////////
@ -100,7 +101,7 @@ func TestEndToEnd(t *testing.T) {
packedMsg := MyFirstFriend.PackUserMessage(enc.Data, enc.Signature)
srv := MyFirstFriend.Contact.PullServers[0]
intS1 := client.InternalServerFromServer(srv)
intS1 := client.CreateServerFromServerCard(srv)
// Creating Server message for transporting the user message
toServerMessage, err := intS1.BuildMessageSendingMessage(packedMsg)
@ -178,11 +179,11 @@ func TestEndToEnd(t *testing.T) {
// user unpack
// user decrypt
decMess, err2 := MyFirstFriend.AsymDecryptMessage([]byte(enc.Data), enc.Signature)
/*decMess, err2 := MyFirstFriend.AsymDecryptMessage([]byte(enc.Data), enc.Signature)
if err2 != nil {
fmt.Println(err2.Error())
}
fmt.Println(decMess)
fmt.Println(decMess)*/
// user decode protobuf
}

16
go.mod
View File

@ -3,16 +3,18 @@ module forge.redroom.link/yves/meowlib
go 1.16
require (
github.com/ProtonMail/gopenpgp/v2 v2.2.4
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.3
github.com/go-redis/redis v6.15.9+incompatible
github.com/google/uuid v1.3.0
github.com/google/uuid v1.3.1
github.com/makiuchi-d/gozxing v0.1.1
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.25.0
github.com/stretchr/testify v1.5.1
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect
golang.org/x/net v0.3.0 // indirect
google.golang.org/protobuf v1.28.1
github.com/rs/zerolog v1.31.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
)

120
go.sum
View File

@ -1,14 +1,17 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7 h1:DSqTh6nEes/uO8BlNcGk8PzZsxY2sN9ZL//veWBdTRI=
github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/gopenpgp/v2 v2.2.4 h1:PEke+LAMLH9CplflEl8WqGyz2IiDoiiipKkB+3cEWFQ=
github.com/ProtonMail/gopenpgp/v2 v2.2.4/go.mod h1:ygdaHbrbWFPhKjmXii0zOs3/xlSR/01GaVePKqv19Hc=
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-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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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=
@ -35,14 +38,19 @@ 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.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
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=
@ -67,68 +75,54 @@ 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.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/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 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
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/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=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/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-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-20190422165155-953cdadca894/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=
@ -137,9 +131,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -147,32 +139,44 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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=
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=
@ -182,8 +186,8 @@ 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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -192,7 +196,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -13,7 +13,7 @@ func (msg *UserMessage) AddFile(filename string, maxMessageSize int64) error {
return err
}
if fi.Size() > maxMessageSize {
return errors.New("cannot add file, file bigger than messge size")
return errors.New("cannot add file, file bigger than message size")
}
var totalsize int64
totalsize = 0
@ -35,7 +35,7 @@ func (msg *UserMessage) AddFile(filename string, maxMessageSize int64) error {
file.Data = data
msg.Files = append(msg.Files, &file)
msg.Status = &UserMessage_ConversationStatus{}
msg.Status = &ConversationStatus{}
msg.Status.LocalUuid = uuid.New().String()
return nil

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,14 @@
// You should use the field numbers 1 through 15
// for the most-frequently-set fields.
// Lower field number values take less space in the wire format.
// For example, field numbers in the range 1 through 15 take one byte to encode.
// Field numbers in the range 16 through 2047 take two bytes.
/**
* Meow messages
*
* This is the Meow protocol protobuf messages description.
*
*/
syntax = "proto3";
package meowlib;
option go_package = "forge.redroom.link/yves/meowlib";
@ -9,32 +20,52 @@ message PackedServerMessage {
bytes signature = 3; // The message signature with the client public key |eo| the reference to teh symetrical key used
}
// structure to hold an invitation through a server
message Invitation {
bytes payload = 1; // invitation payload, encrypted after step 2
int32 timeout = 2; // how long do I want the invitation to remain available on the server
int32 shortcodeLen = 3; // len of the shortcode you wish for short url transmission
string shortcode = 4; // shortcode that the friend shall request to get the invitation
string password = 5; // password tou set for accessin invitation (optional)
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
}
// 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
}
// structure defining a message for a server, that will be encrypted, then sent in a "packedmessage" payload
message ToServerMessage {
string type = 1; // Type 1 : final destination / 2 : forward
string from = 2 ; // My pub key for the server to send me an encrypter answer
bytes payload = 3 ; // optional payload for server
// 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
}
repeated ConversationRequest pullRequest = 4;
repeated PackedUserMessage messages = 5;
repeated Server knownServers = 6;
repeated ServerCard knownServers = 6;
Matriochka matriochkaMessage = 7;
string uuid = 8;
Invitation invitation = 9; // invitation for the 2 first steps of a "through server" invitation process
}
message ConversationResponse {
repeated string messageUuids = 1;
}
// structure defining a from server receiver message decrypted from a "packedmessage" payload
message FromServerMessage {
string type = 1; // Type
@ -43,13 +74,11 @@ message FromServerMessage {
string uuidAck = 4 ; // Ack for the last received ToServerMessage Uuid
string serverUuid = 5 ; // Provides the server uuid that replaced the client uuid
message ConversationResponse {
repeated string messageUuids = 1;
}
repeated PackedUserMessage chat = 6;
repeated Server knownServers = 7;
repeated ServerCard knownServers = 7;
Invitation invitation = 8; // invitation answer, for the third steps of any invitation
}
@ -68,23 +97,23 @@ message Matriochka {
}
// structure describing required server attributes
message Server {
string name = 1;
string description=2;
string publicKey = 3;
string url = 4;
bool publish = 5; // publish this server when asked for a list by server
bytes signature = 6; // signature of all previous fields by the server itself
int32 confidenceLevel = 7; // additional info from the user
}
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 url = 4; // meow server url
string login = 5; // required login to access the server
string password = 6; // password associated to the login
string signature = 7; // signature of all previous fields by the server itself
}
// structure describing a user contact card ie the minimum set of attributes for exchanging identities
message ContactCard {
string name=1;
string contactPublicKey =2;
string encryptionPublicKey= 3;
string lookupPublicKey =4;
repeated Server pullServers =5;
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
uint32 version = 6;
string invitationId=7;
}
@ -97,6 +126,20 @@ message PackedUserMessage {
repeated int64 serverTimestamp=4; // server time stamp, might be several in matriochka mode
}
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
}
message Group{
string name=1;
repeated ContactCard members = 2;
}
// structure defining information that might be exchanged between two peers.
message UserMessage {
@ -104,33 +147,34 @@ message UserMessage {
string from = 2; // My public key for that contact
string type = 3;
bytes data = 4;
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
}
ConversationStatus Status = 5;
ContactCard contact = 6;
Server knownServers = 7;
ServerCard knownServers = 7;
message Group{
string name=1;
repeated ContactCard members = 2;
}
Group group = 8;
repeated File files = 9;
Location currentLocation = 10;
bytes appdata = 11;
Invitation invitation = 12;
}
message File {
string filename=1;
uint64 size=2;
uint32 chunk=3;
bytes data=4;
string filename=1; // the proposed filename
uint64 size=2; // the file size
uint32 chunk=3; // the chunk counter if file is sent by chunks
bytes data=4; // the file/chunk content
}
message Location {
uint64 time=1;
float latitude=2;
float longitude=3;
int32 altitude=4;
}

View File

@ -3,4 +3,10 @@ protoc -I=. --go_out=.. messages.proto
mv ../forge.redroom.link/yves/meowlib/messages.pb.go ../
rm -rf ../forge.redroom.link
protoc -I=. --dart_out=../../../flutter/meowlib/lib/ messages.proto
protoc --plugin=protoc-gen-doc=/usr/bin/protoc-gen-doc \
--doc_out=../doc/generated \
--doc_opt=html,index.html \
*.proto
protoc --plugin=protoc-gen-uml=/usr/bin/protoc-gen-uml \
--uml_out=../doc/generated -I=. *.proto

View File

@ -14,14 +14,14 @@ import (
const key = "3pw0c8#6ZG8{75b5;3?fe80$2"
type Identity struct {
ServerName string `json:"servername,omitempty"`
ServerDesc string `json:"serverdesc,omitempty"`
ServerKp meowlib.KeyPair `json:"server_kp,omitempty"`
Status string `json:"status,omitempty"`
OwnerName string `json:"owner_name,omitempty"`
OwnerPublicKey string `json:"owner_public_key,omitempty"`
ArchiveClients []string `json:"archive_clients,omitempty"`
KnownServers []meowlib.Server `json:"known_servers,omitempty"`
ServerName string `json:"servername,omitempty"`
ServerDesc string `json:"serverdesc,omitempty"`
ServerKp meowlib.KeyPair `json:"server_kp,omitempty"`
Status string `json:"status,omitempty"`
OwnerName string `json:"owner_name,omitempty"`
OwnerPublicKey string `json:"owner_public_key,omitempty"`
ArchiveClients []string `json:"archive_clients,omitempty"`
KnownServers []meowlib.ServerCard `json:"known_servers,omitempty"`
}
func CreateIdentity(ServerName string, ServerDesc string) *Identity {

70
server/invitation.go Normal file
View File

@ -0,0 +1,70 @@
package server
import (
"errors"
"math/rand"
"time"
"github.com/go-redis/redis"
)
func (r *RedisRouter) CreateInvitation(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))
if len(password) > 0 {
r.Client.Set("mwpw:"+id, password, time.Duration(timeout*1000000))
}
return id, time.Now().Add(time.Duration(timeout * 1000000)).UTC()
}
func (r *RedisRouter) GetInvitation(id string, password string) ([]byte, error) {
passRequired := false
expectedpass, err := r.Client.Get("mwpw:" + id).Result()
if err != nil {
passRequired = false
} else {
passRequired = true
}
if passRequired && password != expectedpass {
return nil, errors.New("auth failed")
}
mwiv, err := r.Client.Get("mwiv:" + id).Result()
if err != nil {
return nil, err
}
return []byte(mwiv), nil
}
func (r *RedisRouter) AnswerInvitation(id string, timeout int, invitation []byte, serverTimeout int) time.Time {
if timeout > serverTimeout {
timeout = serverTimeout
}
r.Client.Set("mwan:"+id, invitation, time.Duration(timeout*1000000))
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()
if err != nil {
return nil, err
}
return []byte(mwan), nil
}
func (r *RedisRouter) createShortId(length int) string {
id := ""
alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// for not in redis
for {
for i := 1; i <= length; i++ {
id += string(alphabet[rand.Intn(61)])
}
if r.Client.Get("mwiv:"+id).Err() == redis.Nil {
break
}
}
return id
}

View File

@ -10,28 +10,46 @@ import (
)
type RedisRouter struct {
Name string
ServerIdentity *Identity
Client *redis.Client
Context context.Context
Name string
ServerIdentity *Identity
Client *redis.Client
InvitationTimeout int
Context context.Context
}
func NewRedisRouter(server *Identity, url string, password string, db int) *RedisRouter {
func NewRedisRouter(server *Identity, redisUrl string, password string, db int, invitationTimeout int) *RedisRouter {
var r RedisRouter
r.ServerIdentity = server
r.Name = "Redis"
r.Client = redis.NewClient(&redis.Options{
Addr: url,
Addr: redisUrl,
Password: password,
DB: db,
})
r.InvitationTimeout = invitationTimeout
r.Context = context.Background()
// set start for uptime
err := r.Client.Set("statistics:start", time.Now().UTC().Format(time.RFC3339), 0).Err()
if err != nil {
panic(err)
}
return &r
}
func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMessage, error) {
var from_server meowlib.FromServerMessage
if len(msg.Messages) > 0 { // user message
// update messages counter
err := r.Client.Incr("statistics:messages:total").Err()
if err != nil {
panic(err)
}
// user message
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)
@ -42,7 +60,13 @@ func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMe
}
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 {
@ -69,8 +93,13 @@ func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMe
}
}
// 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)
if err != nil {
return nil, err
@ -81,9 +110,47 @@ func (r *RedisRouter) Route(msg *meowlib.ToServerMessage) (*meowlib.FromServerMe
}
from_server.UuidAck = msg.Uuid
}
// Server list exchange
if len(msg.KnownServers) > 0 {
}
// Through server invitation process
if msg.Invitation != nil {
// update messages counter
err := r.Client.Incr("statistics:messages:invitation").Err()
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
}
*/
}
}
/*