From 1906431061fd4238148b1f50458c7add2f5b3a2e Mon Sep 17 00:00:00 2001 From: ycc Date: Thu, 2 Apr 2026 18:50:04 +0200 Subject: [PATCH] invitation process upgrade --- client/helpers/bgPollHelper.go | 48 ++- client/helpers/invitationAnswerHelper.go | 165 ++++------- client/helpers/invitationCheckHelper.go | 107 ++----- client/helpers/invitationCreateHelper.go | 133 +++------ client/helpers/invitationFinalizeHelper.go | 175 ++++++++--- client/identity.go | 242 ++++++++------- client/identity_test.go | 10 +- client/inv_test_init.id | 44 +++ client/invitation_test.go | 205 +++++++++++++ client/peer.go | 65 +++- client/peer_test.go | 42 ++- client/server.go | 8 +- client/server_test.go | 25 +- client/test.id | 44 +++ doc/invitation/sq_invitation.puml | 15 +- doc/invitation/sq_invitation_calls.puml | 9 - doc/invitation/sq_srvinv00.puml | 3 +- endtoend_test.go | 67 ++--- invitationinitpayload.go | 80 +++++ messages.pb.go | 326 +++++++++++++-------- pb/messages.proto | 10 +- 21 files changed, 1185 insertions(+), 638 deletions(-) create mode 100644 client/inv_test_init.id create mode 100644 client/invitation_test.go create mode 100644 client/test.id delete mode 100644 doc/invitation/sq_invitation_calls.puml create mode 100644 invitationinitpayload.go diff --git a/client/helpers/bgPollHelper.go b/client/helpers/bgPollHelper.go index 3ff2424..1ff0b88 100644 --- a/client/helpers/bgPollHelper.go +++ b/client/helpers/bgPollHelper.go @@ -129,9 +129,20 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error if err != nil { return nil, nil, "ReadMessage: Unmarshal FromServerMessage", err } - // check if invitation answer + // check if invitation answer (step-2 answer waiting for the initiator) if fromServerMessage.Invitation != nil { - invitationGetAnswerReadResponse(fromServerMessage.Invitation) + peer, _, _, invErr := InvitationStep3ProcessAnswer(fromServerMessage.Invitation) + if invErr == nil && peer != nil { + // Auto-send step-3 CC to invitee's servers. + msgs, _, sendErr := InvitationStep3Message(peer.InvitationId) + if sendErr == nil { + for i, bytemsg := range msgs { + if i < len(peer.ContactPullServers) { + meowlib.HttpPostMessage(peer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + } + } + } + } } // Chat messages if len(fromServerMessage.Chat) > 0 { @@ -142,12 +153,41 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error if peer == nil { return nil, nil, "ReadMessage: GetFromMyLookupKey", errors.New("no visible peer for that message") } - // Unpack the message - usermsg, err := peer.ProcessInboundUserMessage(packedUserMessage) + // Unpack the message — step-3 messages arrive before the initiator's identity + // key is known, so skip signature verification for pending peers. + var usermsg *meowlib.UserMessage + if peer.InvitationPending() { + usermsg, err = peer.ProcessInboundStep3UserMessage(packedUserMessage) + } else { + usermsg, err = peer.ProcessInboundUserMessage(packedUserMessage) + } if err != nil { return nil, nil, "ReadMessage: ProcessInboundUserMessage", err } + // Handle invitation step 3: initiator's full ContactCard arriving at the invitee. + if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 { + finalizedPeer, _, finalErr := InvitationStep4ProcessStep3(usermsg) + if finalErr == nil && finalizedPeer != nil { + // Auto-send step-4 confirmation to initiator's servers. + step4msgs, _, sendErr := InvitationStep4Message(finalizedPeer.InvitationId) + if sendErr == nil { + for i, bytemsg := range step4msgs { + if i < len(finalizedPeer.ContactPullServers) { + meowlib.HttpPostMessage(finalizedPeer.ContactPullServers[i], bytemsg, client.GetConfig().HttpTimeOut) + } + } + } + } + continue + } + + // Handle invitation step 4: invitee's confirmation arriving at the initiator. + if usermsg.Invitation != nil && usermsg.Invitation.Step == 4 { + // Contact is fully active — nothing more to do on the initiator side. + continue + } + // Check for received or processed already filled => it's an ack for one of our sent messages if len(usermsg.Data) == 0 && usermsg.Status != nil && usermsg.Status.Uuid != "" && (usermsg.Status.Received != 0 || usermsg.Status.Processed != 0) { diff --git a/client/helpers/invitationAnswerHelper.go b/client/helpers/invitationAnswerHelper.go index dd0a1f3..996ea62 100644 --- a/client/helpers/invitationAnswerHelper.go +++ b/client/helpers/invitationAnswerHelper.go @@ -1,161 +1,110 @@ package helpers import ( - "C" - "fmt" + "errors" "os" "strings" "forge.redroom.link/yves/meowlib" "forge.redroom.link/yves/meowlib/client" ) -import ( - "errors" -) - -// InvitationAnswer -func InvitationAnswer(cc *meowlib.ContactCard, nickname string, myNickname string, serverUids []string) (*client.Peer, string, error) { +// InvitationStep2Answer creates the invitee's peer from an InvitationInitPayload and returns +// the new peer (STEP_2, invitee side — in-memory, no server involved). +func InvitationStep2Answer(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*client.Peer, string, error) { mynick := myNickname - // my nickname for that contact - if myNickname == "" { mynick = client.GetConfig().GetIdentity().Nickname } - - // build my contact card for that friend - peer, err := client.GetConfig().GetIdentity().AnswerInvitation(mynick, nickname, serverUids, cc) + peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload) if err != nil { - return nil, "InvitationAnswer: AnswerInvitation", err + return nil, "InvitationStep2Answer: InvitationStep2", err } - - //peerstr, err := json.Marshal(peer) - //fmt.Println("InvitationAnswer: " + string(peerstr)) - c := client.GetConfig() - c.GetIdentity().Save() - + client.GetConfig().GetIdentity().Save() return peer, "", nil } -// InvitationAnswerFile -func InvitationAnswerFile(invitationFile string, nickname string, myNickname string, serverUids []string) (string, error) { - format := "qr" - var filename string = "" - var cc *meowlib.ContactCard - c := client.GetConfig() +// InvitationStep2AnswerFile reads an InvitationInitPayload from a .mwiv file and creates the +// invitee's peer. It also writes the invitee's ContactCard response to a file (STEP_2_SEND, file variant). +func InvitationStep2AnswerFile(invitationFile string, nickname string, myNickname string, serverUids []string) (string, error) { if _, err := os.Stat(invitationFile); os.IsNotExist(err) { - return "InvitationAnswerFile : os.Stat", err + return "InvitationStep2AnswerFile: os.Stat", err } - if strings.HasSuffix(invitationFile, ".mwiv") { - format = "mwiv" - data, err := os.ReadFile(invitationFile) - if err != nil { - return "InvitationAnswerFile : os.ReadFile", err - } - cc, err = meowlib.NewContactCardFromCompressed(data) - if err != nil { - return "InvitationAnswerFile : NewContactCardFromCompressed", err - } + if !strings.HasSuffix(invitationFile, ".mwiv") { + return "InvitationStep2AnswerFile: unsupported format", errors.New("only .mwiv files are supported") } - identity := client.GetConfig().GetIdentity() - if cc != nil { - isAnswer, proposed, received, _ := identity.CheckInvitation(cc) - if isAnswer { - fmt.Fprintln(os.Stdout, "This is already a response "+proposed+" to your invitation.") - fmt.Fprintln(os.Stdout, "You cannot answer again.") - fmt.Fprintln(os.Stdout, "You should finalize it by importing "+proposed+" contact card to your meow.") - fmt.Fprintln(os.Stdout, "Use : 'meow invitation finalize "+invitationFile+"' to do it.") - - } else { - mynick := myNickname - // my nickname for that contact - - if myNickname == "" { - mynick = client.GetConfig().GetIdentity().Nickname - } - - response, err := identity.AnswerInvitation(mynick, nickname, serverUids, cc) - if err != nil { - return "InvitationAnswerFile : AnswerInvitation", err - } - fmt.Fprintln(os.Stdout, "Invitation sent by "+received) - if format == "qr" { - filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".png" - response.GetMyContact().WriteQr(filename) - } else { - filename = c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv" - response.GetMyContact().WriteCompressed(filename) - } - client.GetConfig().GetIdentity().Save() - } - + data, err := os.ReadFile(invitationFile) + if err != nil { + return "InvitationStep2AnswerFile: os.ReadFile", err } + payload, err := meowlib.NewInvitationInitPayloadFromCompressed(data) + if err != nil { + return "InvitationStep2AnswerFile: NewInvitationInitPayloadFromCompressed", err + } + + mynick := myNickname + if myNickname == "" { + mynick = client.GetConfig().GetIdentity().Nickname + } + c := client.GetConfig() + response, err := c.GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload) + if err != nil { + return "InvitationStep2AnswerFile: InvitationStep2", err + } + + filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv" + if err := response.GetMyContact().WriteCompressed(filename); err != nil { + return "InvitationStep2AnswerFile: WriteCompressed", err + } + c.GetIdentity().Save() return "", nil } -// InvitationAnswerMessage -func InvitationAnswerMessage(invitationId string, invitationServerUid string, timeout int) ([]byte, string, error) { - - // find the peer with that invitation id - /*var peer *client.Peer - for i := len(client.GetConfig().GetIdentity().Peers) - 1; i >= 0; i-- { //! to allow self invitation : testing only, findinc the received peer before myself - // for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ { - if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId { - peer = client.GetConfig().GetIdentity().Peers[i] - break - } - }*/ +// InvitationStep2AnswerMessage builds and returns the packed server message that posts the +// invitee's ContactCard (encrypted with the initiator's temp key) to the invitation server +// (STEP_2_SEND, through-server variant). +func InvitationStep2AnswerMessage(invitationId string, invitationServerUid string, timeout int) ([]byte, string, error) { peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitationId) - if peer == nil { - // declare a custom go error for no peer found - return nil, "InvitationAnswerMessage: loop for peer", errors.New("no peer with that invitation id") + return nil, "InvitationStep2AnswerMessage: peer not found", errors.New("no peer with that invitation id") } - answermsg, err := peer.BuildInvitationAnswerMessage(peer.GetMyContact()) + + answermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact()) if err != nil { - return nil, "InvitationAnswerMessage: BuildInvitationAnswserMessage", err + return nil, "InvitationStep2AnswerMessage: BuildInvitationStep2Message", err } - // Server: get the invitation server + invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) if err != nil { - return nil, "InvitationAnswerMessage: LoadServer", err + return nil, "InvitationStep2AnswerMessage: LoadServer", err } - // this will be the invitation's payload packedMsg, err := peer.ProcessOutboundUserMessage(answermsg) if err != nil { - return nil, "InvitationAnswerMessage: ProcessOutboundUserMessage", err - } - // Creating Server message for transporting the user message - toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout) - if err != nil { - return nil, "InvitationAnswerMessage: BuildToServerMessageInvitationAnswer", err + return nil, "InvitationStep2AnswerMessage: ProcessOutboundUserMessage", err + } + + toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout) + if err != nil { + return nil, "InvitationStep2AnswerMessage: BuildToServerMessageInvitationAnswer", err } - // Server outbound processing bytemsg, err := invitationServer.ProcessOutboundMessage(toServerMessage) if err != nil { - return nil, "InvitationAnswerMessage: ProcessOutboundMessage", err + return nil, "InvitationStep2AnswerMessage: ProcessOutboundMessage", err } return bytemsg, "", nil } -// InvitationAnswerMessageReadResponse -// Called by the invitation receiver -// invitationData: the data received from the server -// invitationServerUid: the uid of the server holding the invitation -func InvitationAnswerMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.Invitation, string, error) { - +// InvitationStep2AnswerMessageReadResponse reads the server acknowledgement of a Step2 answer. +func InvitationStep2AnswerMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.Invitation, string, error) { server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) if err != nil { - return nil, "InvitationAnswerMessageReadResponse: LoadServer", err + return nil, "InvitationStep2AnswerMessageReadResponse: LoadServer", err } - // Server inbound processing : get the invitation server serverMsg, err := server.ProcessInboundServerResponse(invitationData) if err != nil { - return nil, "InvitationAnswerMessageReadResponse: ProcessInboundServerResponse", err + return nil, "InvitationStep2AnswerMessageReadResponse: ProcessInboundServerResponse", err } - return serverMsg.Invitation, "", nil - } diff --git a/client/helpers/invitationCheckHelper.go b/client/helpers/invitationCheckHelper.go index 344b797..7e47b0b 100644 --- a/client/helpers/invitationCheckHelper.go +++ b/client/helpers/invitationCheckHelper.go @@ -7,84 +7,31 @@ import ( "forge.redroom.link/yves/meowlib/client" ) -// InvitationCheck -// todo -/* -func InvitationCheck(invitationdata []byte) *C.char { - var jsoninv map[string]interface{} - err := json.Unmarshal([]byte(C.GoString(invitationdata)), &jsoninv) - if err != nil { - return C.CString(errorToJson(err, "InvitationCheck: ")) - } - var cc *meowlib.ContactCard - if _, err := os.Stat(jsoninv["filename"].(string)); os.IsNotExist(err) { - return C.CString(errorToJson(err, "InvitationCheck: ")) - } - if strings.HasSuffix(jsoninv["filename"].(string), ".mwiv") { - data, err := os.ReadFile(jsoninv["filename"].(string)) - if err != nil { - return C.CString(errorToJson(err, "InvitationCheck: ")) - } - cc, err = meowlib.NewContactCardFromCompressed(data) - if err != nil { - return C.CString(errorToJson(err, "InvitationCheck: ")) - } - } - identity := client.GetConfig().GetIdentity() - result := map[string]string{} - if cc != nil { - isAnswer, proposed, received, invitationMessage := identity.CheckInvitation(cc) - if isAnswer { // answer to infitation - result["type"] = "answer" - result["to"] = proposed - result["from"] = received - result["invitation_message"] = invitationMessage - //fmt.Fprintln(os.Stdout, "Invitation sent to "+proposed+" received with "+received+" as suggested nickname") - } else { // finalization message - result["type"] = "finalize" - result["from"] = received - //fmt.Fprintln(os.Stdout, "Invitation sent by "+received) - } - - } - val, err := json.Marshal(result) - if err != nil { - return C.CString(errorToJson(err, "InvitationCheck: ")) - } - return C.CString(string(val)) -}*/ - -// InvitationGetMessage -// Called by the invitation receiver -// invitationUrl: the url of server holding the invitation -// serverPublicKey: the public key of the server holding the invitation -// invitationPassword: the password of the invitation -func InvitationGetMessage(invitationUrl string, serverPublicKey string, invitationPassword string) ([]byte, string, error) { - +// InvitationStep2GetMessage builds and returns the packed server message that retrieves +// the InvitationInitPayload from the server using the shortcode URL (STEP_2, invitee side). +func InvitationStep2GetMessage(invitationUrl string, serverPublicKey string, invitationPassword string) ([]byte, string, error) { meowurl := strings.Split(invitationUrl, "?") - shortcode := meowurl[1] + srv, err := client.CreateServerFromMeowUrl(meowurl[0]) if err != nil { - return nil, "InvitationGetMessage: CreateServerFromMeowUrl", err + return nil, "InvitationStep2GetMessage: CreateServerFromMeowUrl", err } - // check if already in msg servers + + // Reuse the server entry if already known. dbsrv, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(srv.Url) if err != nil { - return nil, "InvitationGetMessage: LoadServer", err + return nil, "InvitationStep2GetMessage: LoadServer", err } if dbsrv == nil { - // create a server object with url & pubkey srv.PublicKey = serverPublicKey k, err := meowlib.NewKeyPair() if err != nil { - return nil, "InvitationGetMessage: NewKeyPair", err + return nil, "InvitationStep2GetMessage: NewKeyPair", err } srv.UserKp = k - // save it - err = client.GetConfig().GetIdentity().MessageServers.StoreServer(srv) - if err != nil { - return nil, "InvitationGetMessage: StoreServer", err + if err := client.GetConfig().GetIdentity().MessageServers.StoreServer(srv); err != nil { + return nil, "InvitationStep2GetMessage: StoreServer", err } } else { if dbsrv.PublicKey != serverPublicKey { @@ -92,42 +39,32 @@ func InvitationGetMessage(invitationUrl string, serverPublicKey string, invitati } srv = dbsrv } - // buildserver message + toSrvMsg, err := srv.BuildToServerMessageInvitationRequest(shortcode, invitationPassword) if err != nil { - return nil, "InvitationGetMessage: BuildToServerMessageInvitationRequest", err + return nil, "InvitationStep2GetMessage: BuildToServerMessageInvitationRequest", err } - // processoutbound bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg) if err != nil { - return nil, "InvitationGetMessage: ProcessOutboundMessage", err + return nil, "InvitationStep2GetMessage: ProcessOutboundMessage", err } - return bytemsg, "", nil } -// InvitationGetMessageReadResponse -// Called by the invitation receiver -// invitationData: the data received from the server -// invitationServerUid: the uid of the server holding the invitation -func InvitationGetMessageReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.ContactCard, string, error) { - +// InvitationStep2ReadResponse decodes the server response to a Step2 get-message and returns +// the InvitationInitPayload sent by the initiator. +func InvitationStep2ReadResponse(invitationData []byte, invitationServerUid string) (*meowlib.InvitationInitPayload, string, error) { server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) if err != nil { - return nil, "InvitationGetMessageReadResponse: LoadServer", err + return nil, "InvitationStep2ReadResponse: LoadServer", err } - // Server inbound processing : get the invitation server serverMsg, err := server.ProcessInboundServerResponse(invitationData) if err != nil { - return nil, "InvitationGetMessageReadResponse: ProcessInboundServerResponse", err + return nil, "InvitationStep2ReadResponse: ProcessInboundServerResponse", err } - // fmt.Println("Inbound OK, Invitation Step: ", serverMsg.Invitation.Step, len(serverMsg.Invitation.Payload)) - // fmt.Println("Invitation Check") - // fmt.Println(hex.EncodeToString(serverMsg.Invitation.Payload)) - // contactCard decode - cc, err := meowlib.NewContactCardFromCompressed(serverMsg.Invitation.Payload) + payload, err := meowlib.NewInvitationInitPayloadFromCompressed(serverMsg.Invitation.Payload) if err != nil { - return nil, "InvitationGetMessageReadResponse: NewContactCardFromCompressed", err + return nil, "InvitationStep2ReadResponse: NewInvitationInitPayloadFromCompressed", err } - return cc, "", nil + return payload, "", nil } diff --git a/client/helpers/invitationCreateHelper.go b/client/helpers/invitationCreateHelper.go index df81f31..ac4da6e 100644 --- a/client/helpers/invitationCreateHelper.go +++ b/client/helpers/invitationCreateHelper.go @@ -8,136 +8,95 @@ import ( "forge.redroom.link/yves/meowlib/client" ) -// InvitationCreatePeer creates a new peer and returns it -// Called by invitation initiator -// name: the name of the peer -// myNickname: my nickname for that peer -// invitationMessage: the message to send to the peer -// serverUids: the list of server uids -func InvitationCreatePeer(name string, myNickname string, invitationMessage string, serverUids []string) (*client.Peer, string, error) { - +// InvitationStep1CreatePeer creates a minimal pending peer and returns the InvitationInitPayload +// to be transmitted to the invitee (STEP_1). +func InvitationStep1CreatePeer(contactName string, myNickname string, invitationMessage string, serverUids []string) (*meowlib.InvitationInitPayload, *client.Peer, string, error) { mynick := myNickname if myNickname == "" { mynick = client.GetConfig().GetIdentity().Nickname } - - // build my contact card for that friend - peer, err := client.GetConfig().GetIdentity().InvitePeer(mynick, name, serverUids, invitationMessage) + payload, peer, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage) if err != nil { - return nil, "InvitationCreate: InvitePeer", err + return nil, nil, "InvitationStep1CreatePeer: InvitationStep1", err } client.GetConfig().GetIdentity().Save() - - return peer, "", nil + return payload, peer, "", nil } -// InvitationCreateFile creates a new peer and writes the invitation to a file -// Called by invitation initiator -// name: the name of the peer -// myNickname: my nickname for that peer -// invitationMessage: the message to send to the peer -// serverUids: the list of server uids -// format: the format of the file (qr or mwiv) -func InvitationCreateFile(name string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, string, error) { - - peer, errdata, err := InvitationCreatePeer(name, myNickname, invitationMessage, serverUids) +// InvitationStep1File creates a pending peer and writes the InvitationInitPayload to a file +// (format: "qr" for QR-code PNG, anything else for compressed binary .mwiv). +func InvitationStep1File(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, string, error) { + payload, peer, errdata, err := InvitationStep1CreatePeer(contactName, myNickname, invitationMessage, serverUids) if err != nil { return nil, errdata, err } c := client.GetConfig() - var filename string = "" if format == "qr" { - filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png" - err := peer.GetMyContact().WriteQr(filename) - if err != nil { - return nil, "InvitationCreateFile: WriteQr", err + filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png" + if err := payload.WriteQr(filename); err != nil { + return nil, "InvitationStep1File: WriteQr", err } } else { - filename = c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv" - err := peer.GetMyContact().WriteCompressed(filename) - if err != nil { - return nil, "InvitationCreateFile: WriteCompressed", err + filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv" + if err := payload.WriteCompressed(filename); err != nil { + return nil, "InvitationStep1File: WriteCompressed", err } } return peer, "", nil } -// InvitationCreateMessage creates a new invitation message for an invited peer -// Called by invitation initiator -// invitationId: the invitation id of the peer -// invitationServerUid: the uid of the server for sending the invitation -// timeOut: the timeout for the invitation -// urlLen: the length of the invitation url -// password: the password for the invitation -func InvitationCreateMessage(invitationId string, invitationServerUid string, timeOut int, urlLen int, password string) ([]byte, string, error) { - - // lookup for peer with "invitation_id" - var myContact *meowlib.ContactCard - /* for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ { - if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId { - myContact = client.GetConfig().GetIdentity().Peers[i].GetMyContact() - break - } - }*/ +// InvitationStep1Message builds and returns the packed server message that posts the +// InvitationInitPayload to the invitation server (STEP_1 through-server variant). +func InvitationStep1Message(invitationId string, invitationServerUid string, timeOut int, urlLen int, password string) ([]byte, string, error) { peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitationId) - myContact = peer.GetMyContact() - // todo handle not found !! - // lookup for message server with "invitation_server" - invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) //.GetServerByIdx(int(jsoninv["invitation_server"].(float64))) - if err != nil { - return nil, "InvitationCreateMessage: LoadServer", err + if peer == nil { + return nil, "InvitationStep1Message: peer not found", nil } - // call server.buildinviattion - msg, err := invitationServer.BuildToServerMessageInvitationCreation(myContact, password, timeOut, urlLen) - if err != nil { - return nil, "InvitationCreateMessage: BuildToServerMessageInvitationCreation", err + if peer.InvitationKp == nil { + return nil, "InvitationStep1Message: peer has no InvitationKp", nil + } + initPayload := &meowlib.InvitationInitPayload{ + Uuid: peer.InvitationId, + Name: peer.MyName, + PublicKey: peer.InvitationKp.Public, + InvitationMessage: peer.InvitationMessage, + } + invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) + if err != nil { + return nil, "InvitationStep1Message: LoadServer", err + } + msg, err := invitationServer.BuildToServerMessageInvitationStep1(initPayload, password, timeOut, urlLen) + if err != nil { + return nil, "InvitationStep1Message: BuildToServerMessageInvitationStep1", err } - // fmt.Println("Invitation Create") - // fmt.Println(hex.EncodeToString(msg.Invitation.Payload)) bytemsg, err := invitationServer.ProcessOutboundMessage(msg) if err != nil { - return nil, "InvitationCreateMessage: ProcessOutboundMessage", err + return nil, "InvitationStep1Message: ProcessOutboundMessage", err } return bytemsg, "", nil } -// InvitationCreateReadResponse reads the response of an invitation creation (url, expiry) -// Called by invitation initiator -// invitationServerUid: the uid of the server where we sent the invitation -// invitationResponse: the response we got from the server -func InvitationCreateReadResponse(invitationServerUid string, invitationResponse []byte) (*meowlib.Invitation, string, error) { - +// InvitationStep1ReadResponse reads the server response to a Step1 message (shortcode URL + expiry). +func InvitationStep1ReadResponse(invitationServerUid string, invitationResponse []byte) (*meowlib.Invitation, string, error) { server, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid) if err != nil { - return nil, "InvitationCreateReadResponse: LoadServer", err + return nil, "InvitationStep1ReadResponse: LoadServer", err } serverMsg, err := server.ProcessInboundServerResponse(invitationResponse) if err != nil { - return nil, "InvitationCreateReadResponse: ProcessInboundServerResponse", err + return nil, "InvitationStep1ReadResponse: ProcessInboundServerResponse", err } - return serverMsg.Invitation, "", nil } -// InvitationSetUrlInfo sets the url info for an invitation -// Called by invitation initiator -// invitationId: the invitation id of the peer -// url: the url of the invitation we got from the server +// InvitationSetUrlInfo stores the shortcode URL and expiry on the pending peer. func InvitationSetUrlInfo(invitationId string, url string, expiry int64) { id := client.GetConfig().GetIdentity() - // lookup for peer with "invitation_id" peer := id.Peers.GetFromInvitationId(invitationId) + if peer == nil { + return + } peer.InvitationUrl = url peer.InvitationExpiry = time.Unix(expiry, 0) id.Peers.StorePeer(peer) - - /* for i := 0; i < len(client.GetConfig().GetIdentity().Peers); i++ { - if client.GetConfig().GetIdentity().Peers[i].InvitationId == invitationId { - client.GetConfig().GetIdentity().Peers[i].InvitationUrl = url - client.GetConfig().GetIdentity().Peers[i].InvitationExpiry = time.Unix(expiry, 0) - break - } - } - client.GetConfig().GetIdentity().Save()*/ - } diff --git a/client/helpers/invitationFinalizeHelper.go b/client/helpers/invitationFinalizeHelper.go index f0bfb48..7ae821e 100644 --- a/client/helpers/invitationFinalizeHelper.go +++ b/client/helpers/invitationFinalizeHelper.go @@ -1,53 +1,150 @@ package helpers import ( + "errors" + "forge.redroom.link/yves/meowlib" "forge.redroom.link/yves/meowlib/client" "google.golang.org/protobuf/proto" ) -// Got it by the message background check -// => noInvitationGetAnswer - -// invitationGetAnswerReadResponse -// Called by the initiator's background service only -// invitationAnswerData: the data received from the server -// invitationServerUid: the uid of the server holding the invitation -func invitationGetAnswerReadResponse(invitation *meowlib.Invitation) (*client.Peer, string, error) { - - // decode the payload +// InvitationStep3ProcessAnswer is called by the initiator's background service when a +// step-2 answer (invitee's ContactCard) arrives via the invitation server poll. +// It decrypts the answer, calls InvitationStep3 to generate the initiator's full keypairs, +// and returns the peer and the initiator's ContactCard ready for STEP_3_SEND. +func InvitationStep3ProcessAnswer(invitation *meowlib.Invitation) (*client.Peer, *meowlib.ContactCard, string, error) { var invitationAnswer meowlib.PackedUserMessage - err := proto.Unmarshal(invitation.Payload, &invitationAnswer) - if err != nil { - return nil, "InvitationGetAnswerReadResponse: Unmarshal", err + if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil { + return nil, nil, "InvitationStep3ProcessAnswer: Unmarshal PackedUserMessage", err } - // retreive user public key to check usermessage signature - // contactPublikKey := serverMsg.Invitation.From + peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid) - peer.ContactPublicKey = invitation.From - if peer != nil { - - // process the packed user message - usermsg, err := peer.ProcessInboundUserMessage(&invitationAnswer) - if err != nil { - return nil, "InvitationGetAnswerReadResponse: ProcessInboundUserMessage", err - } - decodedInvitation := usermsg.Invitation - var cc meowlib.ContactCard - err = proto.Unmarshal(decodedInvitation.Payload, &cc) - if err != nil { - return nil, "InvitationGetAnswerReadResponse: Unmarshal", err - } - - // finalize the invitation - // id := client.GetConfig().GetIdentity() - peer.ContactLookupKey = cc.ContactPublicKey - peer.ContactEncryption = cc.EncryptionPublicKey - for _, server := range cc.PullServers { - peer.ContactPullServers = append(peer.ContactPullServers, server.GetUid()) - } - client.GetConfig().GetIdentity().Save() - + if peer == nil { + return nil, nil, "InvitationStep3ProcessAnswer: peer not found", errors.New("no peer for invitation uuid " + invitation.Uuid) } + // Guard against duplicate delivery (e.g., same answer from multiple servers). + if peer.InvitationKp == nil { + return nil, nil, "", nil + } + + // Decrypt invitee's ContactCard using the initiator's temporary InvitationKp. + usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From) + if err != nil { + return nil, nil, "InvitationStep3ProcessAnswer: ProcessInboundStep2UserMessage", err + } + + var inviteeCC meowlib.ContactCard + if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil { + return nil, nil, "InvitationStep3ProcessAnswer: Unmarshal ContactCard", err + } + + myCC, peer, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC) + if err != nil { + return nil, nil, "InvitationStep3ProcessAnswer: InvitationStep3", err + } + client.GetConfig().GetIdentity().Save() + return peer, myCC, "", nil +} + +// InvitationStep3Message builds and returns the packed server messages that send the +// initiator's full ContactCard to the invitee through the invitee's servers (STEP_3_SEND). +func InvitationStep3Message(invitationId string) ([][]byte, string, error) { + id := client.GetConfig().GetIdentity() + peer := id.Peers.GetFromInvitationId(invitationId) + if peer == nil { + return nil, "InvitationStep3Message: peer not found", errors.New("no peer for invitation id " + invitationId) + } + + step3msg, err := peer.BuildInvitationStep3Message(peer.GetMyContact()) + if err != nil { + return nil, "InvitationStep3Message: BuildInvitationStep3Message", err + } + // Step-3 must NOT use DR or sym layers: the invitee hasn't received those + // keys yet (they are carried inside this very message). Use plain asym only. + serialized, err := peer.SerializeUserMessage(step3msg) + if err != nil { + return nil, "InvitationStep3Message: SerializeUserMessage", err + } + enc, err := peer.AsymEncryptMessage(serialized) + if err != nil { + return nil, "InvitationStep3Message: AsymEncryptMessage", err + } + packedMsg := peer.PackUserMessage(enc.Data, enc.Signature) + + var results [][]byte + for _, srvUid := range peer.ContactPullServers { + srv, err := id.MessageServers.LoadServer(srvUid) + if err != nil { + continue + } + toSrvMsg := srv.BuildToServerMessageFromUserMessage(packedMsg) + bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg) + if err != nil { + continue + } + results = append(results, bytemsg) + } + if len(results) == 0 { + return nil, "InvitationStep3Message: no reachable invitee server", errors.New("could not build message for any invitee server") + } + return results, "", nil +} + +// InvitationStep4ProcessStep3 is called by the invitee's message processing when a UserMessage +// with invitation.step==3 is received. It finalizes the initiator's peer entry. +func InvitationStep4ProcessStep3(usermsg *meowlib.UserMessage) (*client.Peer, string, error) { + if usermsg.Invitation == nil || usermsg.Invitation.Step != 3 { + return nil, "InvitationStep4ProcessStep3: unexpected step", errors.New("expected invitation step 3") + } + var initiatorCC meowlib.ContactCard + if err := proto.Unmarshal(usermsg.Invitation.Payload, &initiatorCC); err != nil { + return nil, "InvitationStep4ProcessStep3: Unmarshal ContactCard", err + } + // Patch the invitation ID from the outer message in case it was not set in the CC. + if initiatorCC.InvitationId == "" { + initiatorCC.InvitationId = usermsg.Invitation.Uuid + } + if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil { + return nil, "InvitationStep4ProcessStep3: InvitationStep4", err + } + client.GetConfig().GetIdentity().Save() + peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(initiatorCC.InvitationId) return peer, "", nil } + +// InvitationStep4Message builds and returns the packed server messages that send the +// invitee's confirmation to the initiator through the initiator's servers (STEP_4). +func InvitationStep4Message(invitationId string) ([][]byte, string, error) { + id := client.GetConfig().GetIdentity() + peer := id.Peers.GetFromInvitationId(invitationId) + if peer == nil { + return nil, "InvitationStep4Message: peer not found", errors.New("no peer for invitation id " + invitationId) + } + + step4msg, err := peer.BuildInvitationStep4Message() + if err != nil { + return nil, "InvitationStep4Message: BuildInvitationStep4Message", err + } + packedMsg, err := peer.ProcessOutboundUserMessage(step4msg) + if err != nil { + return nil, "InvitationStep4Message: ProcessOutboundUserMessage", err + } + + var results [][]byte + for _, srvUid := range peer.ContactPullServers { + srv, err := id.MessageServers.LoadServer(srvUid) + if err != nil { + continue + } + toSrvMsg := srv.BuildToServerMessageFromUserMessage(packedMsg) + bytemsg, err := srv.ProcessOutboundMessage(toSrvMsg) + if err != nil { + continue + } + results = append(results, bytemsg) + } + if len(results) == 0 { + return nil, "InvitationStep4Message: no reachable initiator server", errors.New("could not build message for any initiator server") + } + return results, "", nil +} diff --git a/client/identity.go b/client/identity.go index 6f0dd71..42d9782 100644 --- a/client/identity.go +++ b/client/identity.go @@ -80,89 +80,44 @@ func (id *Identity) WipeFolder() error { return nil } -// Creates an invitation for a peer, returns the newly created peer including infos to provide a ContactCard -func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerUids []string, InvitationMessage string) (*Peer, error) { +// InvitationStep1 creates a minimal pending peer with only a temporary keypair and returns +// the InvitationInitPayload to be transmitted to the invitee (via file, QR code, or server). +// Full keypairs are only generated in InvitationStep3, after the invitee's answer is received. +func (id *Identity) InvitationStep1(MyName string, ContactName string, MessageServerUids []string, InvitationMessage string) (*meowlib.InvitationInitPayload, *Peer, error) { var peer Peer var err error peer.Uid = uuid.New().String() - peer.MyIdentity, err = meowlib.NewKeyPair() - if err != nil { - return nil, err - } - peer.MyEncryptionKp, err = meowlib.NewKeyPair() - if err != nil { - return nil, err - } - peer.MyLookupKp, err = meowlib.NewKeyPair() - if err != nil { - return nil, err - } peer.Name = ContactName - peer.InvitationId = uuid.New().String() // todo as param to identify then update url - symKeyBytes := make([]byte, 32) - if _, err = rand.Read(symKeyBytes); err != nil { - return nil, err - } - peer.MySymKey = base64.StdEncoding.EncodeToString(symKeyBytes) - /* if id.MessageServers.Servers == nil { - return 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") - } - } - for _, i := range MessageServerIdxs { - srv := id.MessageServers.Servers[i].GetServerCard() - peer.MyContact.PullServers = append(peer.MyContact.PullServers, srv) - }*/ - /* pullServers, err := id.MessageServers.LoadServerCardsFromUids(MessageServerUids) - if err != nil { - return nil, err - }*/ - peer.MyPullServers = MessageServerUids peer.MyName = MyName + peer.InvitationId = uuid.New().String() peer.InvitationMessage = InvitationMessage + peer.MyPullServers = MessageServerUids - // Generate DR keypair and root key for the initiator side - drKp, err := doubleratchet.DefaultCrypto{}.GenerateDH() + // Temporary keypair: public key is sent to invitee for step-2 encryption and as + // the server-side lookup key where the invitee will post their answer. + peer.InvitationKp, err = meowlib.NewKeyPair() if err != nil { - return nil, err + return nil, nil, err } - peer.DrKpPrivate = base64.StdEncoding.EncodeToString(drKp.PrivateKey()) - peer.DrKpPublic = base64.StdEncoding.EncodeToString(drKp.PublicKey()) - drRootKey := make([]byte, 32) - if _, err = rand.Read(drRootKey); err != nil { - return nil, err - } - peer.DrRootKey = base64.StdEncoding.EncodeToString(drRootKey) - peer.DrInitiator = true id.Peers.StorePeer(&peer) - return &peer, nil -} - -// Checks if the received contact card is an answer to an invitation, returns true if it is, and the proposed and received nicknames -func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string, invitationMessage string) { - // invitation Id found, this is an answer to an invitation - /*for _, p := range id.Peers { - if p.InvitationId == ReceivedContact.InvitationId { - return true, p.Name, ReceivedContact.Name, ReceivedContact.InvitationMessage - } + payload := &meowlib.InvitationInitPayload{ + Uuid: peer.InvitationId, + Name: MyName, + PublicKey: peer.InvitationKp.Public, + InvitationMessage: InvitationMessage, } - - // it's an invitation - return false, "", ReceivedContact.Name, ReceivedContact.InvitationMessage*/ - return id.Peers.CheckInvitation(ReceivedContact) + return payload, &peer, nil } -// Answers an invitation, returns the newly created peer including infos to provide a ContactCard -func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageServerIdxs []string, ReceivedContact *meowlib.ContactCard) (*Peer, error) { +// InvitationStep2 creates the invitee's peer entry from the received InvitationInitPayload +// and returns the peer. The invitee generates their full keypairs here. +// The initiator's temporary public key (payload.PublicKey) is used both as the encryption +// target for the step-2 answer and as the server-side lookup address. +func (id *Identity) InvitationStep2(MyName string, ContactName string, MessageServerUids []string, payload *meowlib.InvitationInitPayload) (*Peer, error) { var peer Peer var err error - var newsrv *Server - //var myContactCard meowlib.ContactCard peer.Uid = uuid.New().String() peer.MyIdentity, err = meowlib.NewKeyPair() if err != nil { @@ -179,66 +134,116 @@ func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageS if ContactName != "" { peer.Name = ContactName } else { - peer.Name = ReceivedContact.Name + peer.Name = payload.Name } - peer.ContactEncryption = ReceivedContact.EncryptionPublicKey - peer.ContactLookupKey = ReceivedContact.LookupPublicKey - peer.ContactPublicKey = ReceivedContact.ContactPublicKey - peer.MySymKey = ReceivedContact.SymetricKey - peer.InvitationId = ReceivedContact.InvitationId - peer.InvitationMessage = ReceivedContact.InvitationMessage - for srv := range ReceivedContact.PullServers { - peer.ContactPullServers = append(peer.ContactPullServers, ReceivedContact.PullServers[srv].GetUid()) - newsrv, err = CreateServerFromUid(ReceivedContact.PullServers[srv].GetUid()) - id.MessageServers.StoreServerIfNotExists(newsrv) - } - /* for _, i := range MessageServerIdxs { - srv := id.MessageServers.Servers[i].GetServerCard() - peer.MyContact.PullServers = append(peer.MyContact.PullServers, srv) - }*/ - /* srvCards, err := GetConfig().GetIdentity().MessageServers.LoadServerCardsFromUids(MessageServerIdxs) - if err != nil { - peer.MyContact.PullServers = srvCards - }*/ - peer.MyPullServers = MessageServerIdxs + // The initiator's temp key is used for both encrypting the answer and as destination. + peer.ContactEncryption = payload.PublicKey + peer.ContactLookupKey = payload.PublicKey + peer.InvitationId = payload.Uuid + peer.InvitationMessage = payload.InvitationMessage + peer.MyPullServers = MessageServerUids peer.MyName = MyName - peer.InvitationId = ReceivedContact.InvitationId - // Adopt DR material from the initiator's ContactCard - peer.DrRootKey = ReceivedContact.DrRootKey - peer.ContactDrPublicKey = ReceivedContact.DrPublicKey - peer.DrInitiator = false id.Peers.StorePeer(&peer) - return &peer, nil } -// Finalizes an invitation, returns nil if successful -func (id *Identity) FinalizeInvitation(ReceivedContact *meowlib.ContactCard) error { +// InvitationStep3 is called by the initiator after receiving and decrypting the invitee's +// ContactCard (step-2 answer). It generates the initiator's full keypairs and DR material, +// updates the pending peer with the invitee's contact info, and returns the initiator's +// full ContactCard to be sent to the invitee (STEP_3_SEND). +func (id *Identity) InvitationStep3(inviteeContact *meowlib.ContactCard) (*meowlib.ContactCard, *Peer, error) { var err error - var newsrv *Server - /*for i, p := range id.Peers { - if p.InvitationId == ReceivedContact.InvitationId { - //id.Peers[i].Name = ReceivedContact.Name - id.Peers[i].ContactEncryption = ReceivedContact.EncryptionPublicKey - id.Peers[i].ContactLookupKey = ReceivedContact.LookupPublicKey - id.Peers[i].ContactPublicKey = ReceivedContact.ContactPublicKey - srvs := []string{} - for srv := range ReceivedContact.PullServers { - srvs = append(srvs, ReceivedContact.PullServers[srv].GetUid()) - } - id.Peers[i].ContactPullServers = srvs - return nil + peer := id.Peers.GetFromInvitationId(inviteeContact.InvitationId) + if peer == nil { + return nil, nil, errors.New("no pending peer found for invitation id " + inviteeContact.InvitationId) + } + + // Generate full keypairs now that the invitee's identity is known. + peer.MyIdentity, err = meowlib.NewKeyPair() + if err != nil { + return nil, nil, err + } + peer.MyEncryptionKp, err = meowlib.NewKeyPair() + if err != nil { + return nil, nil, err + } + peer.MyLookupKp, err = meowlib.NewKeyPair() + if err != nil { + return nil, nil, err + } + symKeyBytes := make([]byte, 32) + if _, err = rand.Read(symKeyBytes); err != nil { + return nil, nil, err + } + peer.MySymKey = base64.StdEncoding.EncodeToString(symKeyBytes) + + // Generate DR keypair and root key. + drKp, err := doubleratchet.DefaultCrypto{}.GenerateDH() + if err != nil { + return nil, nil, err + } + peer.DrKpPrivate = base64.StdEncoding.EncodeToString(drKp.PrivateKey()) + peer.DrKpPublic = base64.StdEncoding.EncodeToString(drKp.PublicKey()) + drRootKey := make([]byte, 32) + if _, err = rand.Read(drRootKey); err != nil { + return nil, nil, err + } + peer.DrRootKey = base64.StdEncoding.EncodeToString(drRootKey) + peer.DrInitiator = true + + // Store invitee contact info. + peer.ContactPublicKey = inviteeContact.ContactPublicKey + peer.ContactEncryption = inviteeContact.EncryptionPublicKey + peer.ContactLookupKey = inviteeContact.LookupPublicKey + for _, srv := range inviteeContact.PullServers { + peer.ContactPullServers = append(peer.ContactPullServers, srv.GetUid()) + newsrv, err := CreateServerFromUid(srv.GetUid()) + if err == nil { + id.MessageServers.StoreServerIfNotExists(newsrv) } } - return errors.New("no matching contact found for invitationId " + ReceivedContact.InvitationId)*/ - for srv := range ReceivedContact.PullServers { - newsrv, err = CreateServerFromUid(ReceivedContact.PullServers[srv].GetUid()) + // Drop the temporary invitation keypair — no longer needed. + peer.InvitationKp = nil + + id.Peers.StorePeer(peer) + + return peer.GetMyContact(), peer, nil +} + +// InvitationStep4 is called by the invitee upon receiving the initiator's full ContactCard +// (carried as a regular UserMessage with invitation.step=3). It finalizes the peer entry. +func (id *Identity) InvitationStep4(initiatorContact *meowlib.ContactCard) error { + var err error + var newsrv *Server + for _, srv := range initiatorContact.PullServers { + newsrv, err = CreateServerFromUid(srv.GetUid()) if err != nil { return err } id.MessageServers.StoreServerIfNotExists(newsrv) } - return id.Peers.FinalizeInvitation(ReceivedContact) + peer := id.Peers.GetFromInvitationId(initiatorContact.InvitationId) + if peer == nil { + return errors.New("no pending peer found for invitation id " + initiatorContact.InvitationId) + } + peer.ContactPublicKey = initiatorContact.ContactPublicKey + peer.ContactEncryption = initiatorContact.EncryptionPublicKey + peer.ContactLookupKey = initiatorContact.LookupPublicKey + peer.MySymKey = initiatorContact.SymetricKey + peer.DrRootKey = initiatorContact.DrRootKey + peer.ContactDrPublicKey = initiatorContact.DrPublicKey + peer.DrInitiator = false + for _, srv := range initiatorContact.PullServers { + peer.ContactPullServers = append(peer.ContactPullServers, srv.GetUid()) + } + id.Peers.StorePeer(peer) + return nil +} + +// CheckInvitation checks if the received ContactCard is an answer to one of our pending +// invitations. Returns true when it is, with the proposed and received nicknames. +func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string, invitationMessage string) { + return id.Peers.CheckInvitation(ReceivedContact) } // LoadIdentity loads an identity from an encrypted file @@ -386,9 +391,18 @@ func (id *Identity) GetRequestJobs() []RequestsJob { return nil } for _, peer := range peers { - // check if peer inviation is accepted for _, server := range peer.MyPullServers { - srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp) + if srvs[server] == nil { + continue + } + if peer.MyLookupKp != nil { + // Active peer — use the permanent lookup key. + srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp) + } else if peer.InvitationKp != nil { + // Step-1 pending peer — poll using the temp invitation keypair so the + // server-stored step-2 answer can be retrieved. + srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.InvitationKp) + } } } // add hidden peers diff --git a/client/identity_test.go b/client/identity_test.go index ea47f5e..023b692 100644 --- a/client/identity_test.go +++ b/client/identity_test.go @@ -142,14 +142,12 @@ func TestGetRequestJobs(t *testing.T) { // Call GetRequestJobs jobs := id.GetRequestJobs() - // Check that the returned list is as expected - assert.Equal(t, 6, len(jobs), "Expected 6 jobs") + // All 10 test peers use server1 and server2, so exactly 2 jobs are expected. + assert.Equal(t, 2, len(jobs), "Expected 2 jobs (server1 and server2)") - // Check that each job has the correct server and lookup keys for _, job := range jobs { - //fmt.Println(job.Server.GetUid(), job.LookupKeys) - assert.Contains(t, []string{"server1", "server2", "server3", "server4", "server5", "server6"}, job.Server.GetUid(), "Unexpected server UID") - assert.Len(t, job.LookupKeys, 1, "Expected 1 lookup key per job") + assert.Contains(t, []string{"server1", "server2"}, job.Server.GetUid(), "Unexpected server UID") + assert.Len(t, job.LookupKeys, 10, "Expected 10 lookup keys (one per test peer) per job") } // Clean up diff --git a/client/inv_test_init.id b/client/inv_test_init.id new file mode 100644 index 0000000..842af96 --- /dev/null +++ b/client/inv_test_init.id @@ -0,0 +1,44 @@ +-----BEGIN PGP MESSAGE----- +Comment: https://gopenpgp.org +Version: GopenPGP 2.8.3 + +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 +=C05v +-----END PGP MESSAGE----- \ No newline at end of file diff --git a/client/invitation_test.go b/client/invitation_test.go new file mode 100644 index 0000000..fd58393 --- /dev/null +++ b/client/invitation_test.go @@ -0,0 +1,205 @@ +package client + +import ( + "os" + "testing" + + "forge.redroom.link/yves/meowlib" + "github.com/stretchr/testify/assert" +) + +// setupInvitationTest creates two independent identities with separate storage paths. +func setupInvitationTest(t *testing.T) (initiator *Identity, invitee *Identity, cleanup func()) { + t.Helper() + cfg := GetConfig() + cfg.IdentityFile = "inv_test_init.id" + cfg.SetMemPass("testpass") + + initiator, err := CreateIdentity("initiator") + if err != nil { + t.Fatal(err) + } + invitee, err = CreateIdentity("invitee") + if err != nil { + t.Fatal(err) + } + // Give each identity a pull server UID. + srvInit, _ := CreateServerFromUrl("http://init.server/meow/") + initiator.MessageServers.StoreServer(srvInit) + srvInvitee, _ := CreateServerFromUrl("http://invitee.server/meow/") + invitee.MessageServers.StoreServer(srvInvitee) + + cleanup = func() { + os.Remove("inv_test_init.id") + os.RemoveAll(cfg.StoragePath + "/" + initiator.Uuid) + os.RemoveAll(cfg.StoragePath + "/" + invitee.Uuid) + } + return initiator, invitee, cleanup +} + +// TestInvitationStep1 verifies that InvitationStep1 creates a minimal peer with only +// InvitationKp set (no full keypairs yet) and returns a valid InvitationInitPayload. +func TestInvitationStep1(t *testing.T) { + initiator, _, cleanup := setupInvitationTest(t) + defer cleanup() + + payload, peer, err := initiator.InvitationStep1("Alice", "Bob", []string{"http://init.server/meow/"}, "Hello Bob!") + assert.NoError(t, err) + assert.NotNil(t, payload) + assert.NotNil(t, peer) + + assert.NotEmpty(t, payload.Uuid) + assert.Equal(t, "Alice", payload.Name) + assert.NotEmpty(t, payload.PublicKey) + assert.Equal(t, "Hello Bob!", payload.InvitationMessage) + + // Full keypairs must NOT be set yet. + assert.Nil(t, peer.MyIdentity) + assert.Nil(t, peer.MyEncryptionKp) + assert.Nil(t, peer.MyLookupKp) + // Temp keypair must be set. + assert.NotNil(t, peer.InvitationKp) + assert.Equal(t, payload.PublicKey, peer.InvitationKp.Public) +} + +// TestInvitationStep1PayloadRoundTrip verifies Compress/Decompress of InvitationInitPayload. +func TestInvitationStep1PayloadRoundTrip(t *testing.T) { + initiator, _, cleanup := setupInvitationTest(t) + defer cleanup() + + payload, _, err := initiator.InvitationStep1("Alice", "Bob", nil, "test msg") + assert.NoError(t, err) + + compressed, err := payload.Compress() + assert.NoError(t, err) + assert.NotEmpty(t, compressed) + + restored, err := meowlib.NewInvitationInitPayloadFromCompressed(compressed) + assert.NoError(t, err) + assert.Equal(t, payload.Uuid, restored.Uuid) + assert.Equal(t, payload.Name, restored.Name) + assert.Equal(t, payload.PublicKey, restored.PublicKey) + assert.Equal(t, payload.InvitationMessage, restored.InvitationMessage) +} + +// TestInvitationStep2 verifies that InvitationStep2 creates a peer with full keypairs and +// sets the initiator's temp key as both ContactEncryption and ContactLookupKey. +func TestInvitationStep2(t *testing.T) { + initiator, invitee, cleanup := setupInvitationTest(t) + defer cleanup() + + payload, _, err := initiator.InvitationStep1("Alice", "Bob", nil, "Hi") + assert.NoError(t, err) + + peer, err := invitee.InvitationStep2("Bob", "Alice", []string{"http://invitee.server/meow/"}, payload) + assert.NoError(t, err) + assert.NotNil(t, peer) + + // Full keypairs must be set on invitee's peer. + assert.NotNil(t, peer.MyIdentity) + assert.NotNil(t, peer.MyEncryptionKp) + assert.NotNil(t, peer.MyLookupKp) + + // Contact fields must point to initiator's temp key. + assert.Equal(t, payload.PublicKey, peer.ContactEncryption) + assert.Equal(t, payload.PublicKey, peer.ContactLookupKey) + assert.Equal(t, payload.Uuid, peer.InvitationId) +} + +// TestInvitationFullFlow exercises the complete 4-step invitation handshake end-to-end, +// verifying that both peers end up with each other's full contact information. +func TestInvitationFullFlow(t *testing.T) { + initiator, invitee, cleanup := setupInvitationTest(t) + defer cleanup() + + // STEP_1: initiator creates init payload. + payload, initPeer, err := initiator.InvitationStep1("Alice", "Bob", []string{"http://init.server/meow/"}, "Hello!") + assert.NoError(t, err) + assert.NotNil(t, initPeer.InvitationKp) + assert.Nil(t, initPeer.MyIdentity) + + // STEP_2: invitee creates their peer from the payload. + srvCard := &meowlib.ServerCard{Name: "InviteeServer", Url: "http://invitee.server/meow/"} + inviteePeer, err := invitee.InvitationStep2("Bob", "Alice", []string{"http://invitee.server/meow/"}, payload) + assert.NoError(t, err) + inviteeCC := inviteePeer.GetMyContact() + inviteeCC.PullServers = append(inviteeCC.PullServers, srvCard) + + // STEP_3: initiator receives invitee's CC, generates full keypairs. + myCC, _, err := initiator.InvitationStep3(inviteeCC) + assert.NoError(t, err) + assert.NotNil(t, myCC) + assert.NotEmpty(t, myCC.ContactPublicKey) + assert.NotEmpty(t, myCC.EncryptionPublicKey) + assert.NotEmpty(t, myCC.LookupPublicKey) + assert.NotEmpty(t, myCC.DrRootKey) + assert.NotEmpty(t, myCC.DrPublicKey) + + // After step 3, initiator's peer must have full keypairs and invitee's contact info. + updatedInitPeer := initiator.Peers.GetFromInvitationId(payload.Uuid) + assert.NotNil(t, updatedInitPeer.MyIdentity) + assert.NotNil(t, updatedInitPeer.MyEncryptionKp) + assert.NotNil(t, updatedInitPeer.MyLookupKp) + assert.Equal(t, inviteePeer.MyIdentity.Public, updatedInitPeer.ContactPublicKey) + assert.Equal(t, inviteePeer.MyEncryptionKp.Public, updatedInitPeer.ContactEncryption) + assert.Nil(t, updatedInitPeer.InvitationKp) // temp key must be cleared + + // STEP_4: invitee finalizes from initiator's full CC. + srvCardInit := &meowlib.ServerCard{Name: "InitServer", Url: "http://init.server/meow/"} + myCC.PullServers = append(myCC.PullServers, srvCardInit) + err = invitee.InvitationStep4(myCC) + assert.NoError(t, err) + + // Both peers must now be fully finalized (ContactPublicKey set → not pending). + finalInitPeer := initiator.Peers.GetFromInvitationId(payload.Uuid) + assert.False(t, finalInitPeer.InvitationPending()) + + finalInviteePeer := invitee.Peers.GetFromInvitationId(payload.Uuid) + assert.False(t, finalInviteePeer.InvitationPending()) + assert.Equal(t, updatedInitPeer.MyIdentity.Public, finalInviteePeer.ContactPublicKey) + assert.Equal(t, updatedInitPeer.MyEncryptionKp.Public, finalInviteePeer.ContactEncryption) + assert.Equal(t, updatedInitPeer.MyLookupKp.Public, finalInviteePeer.ContactLookupKey) + assert.NotEmpty(t, finalInviteePeer.DrRootKey) +} + +// TestInvitationStep3NotFound verifies that InvitationStep3 returns an error when no +// pending peer exists for the given invitation ID. +func TestInvitationStep3NotFound(t *testing.T) { + initiator, _, cleanup := setupInvitationTest(t) + defer cleanup() + + cc := &meowlib.ContactCard{InvitationId: "nonexistent-uuid", ContactPublicKey: "pub"} + _, _, err := initiator.InvitationStep3(cc) + assert.Error(t, err) +} + +// TestGetRequestJobsPendingPeer verifies that pending (step-1 only) peers contribute +// their InvitationKp to GetRequestJobs instead of MyLookupKp. +func TestGetRequestJobsPendingPeer(t *testing.T) { + cfg := GetConfig() + cfg.SetMemPass("testpass") + id, err := CreateIdentity("testjobs") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(cfg.StoragePath + "/" + id.Uuid) + + cfg.SetIdentity(id) + id.MessageServers = ServerStorage{DbFile: "testjobs.db"} + defer os.RemoveAll("testjobs.db") + + srv, _ := CreateServerFromUrl("http://srv1.test/meow/") + id.MessageServers.StoreServer(srv) + + // Create a step-1 pending peer. + _, _, err = id.InvitationStep1("Me", "Friend", []string{"http://srv1.test/meow/"}, "Hi") + assert.NoError(t, err) + + jobs := id.GetRequestJobs() + // At least one job should have a lookup key (the InvitationKp). + total := 0 + for _, j := range jobs { + total += len(j.LookupKeys) + } + assert.Greater(t, total, 0) +} diff --git a/client/peer.go b/client/peer.go index dd1a203..72c1677 100644 --- a/client/peer.go +++ b/client/peer.go @@ -56,8 +56,10 @@ type Peer struct { DbIds []string `json:"db_ids,omitempty"` Type string `json:"type,omitempty"` PersonnaeDbId string `json:"personnae_db_id,omitempty"` + // Invitation temporary keypair (step 1 only — discarded after step 3) + InvitationKp *meowlib.KeyPair `json:"invitation_kp,omitempty"` // Double Ratchet state - DrKpPublic string `json:"dr_kp_public,omitempty"` + DrKpPublic string `json:"dr_kp_public,omitempty"` DrKpPrivate string `json:"dr_kp_private,omitempty"` DrRootKey string `json:"dr_root_key,omitempty"` DrInitiator bool `json:"dr_initiator,omitempty"` @@ -171,9 +173,28 @@ 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) BuildInvitationAnswerMessage(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) { +// BuildInvitationStep2Message builds the invitee's answer UserMessage (STEP_2_SEND). +// The ContactCard is encrypted with the initiator's temp public key via ProcessOutboundUserMessage. +func (p *Peer) BuildInvitationStep2Message(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) { + var msg meowlib.UserMessage + var invitation meowlib.Invitation + invitation.Step = 2 + out, err := proto.Marshal(myContactCard) + if err != nil { + return nil, err + } + invitation.Uuid = p.InvitationId + invitation.Payload = out + msg.Destination = p.ContactLookupKey + msg.Invitation = &invitation + msg.From = p.MyIdentity.Public + msg.Type = "1" + return &msg, nil +} + +// BuildInvitationStep3Message builds the initiator's full ContactCard UserMessage (STEP_3_SEND). +// Sent through the invitee's servers after the initiator has finalized their keypairs. +func (p *Peer) BuildInvitationStep3Message(myContactCard *meowlib.ContactCard) (*meowlib.UserMessage, error) { var msg meowlib.UserMessage var invitation meowlib.Invitation invitation.Step = 3 @@ -190,6 +211,42 @@ func (p *Peer) BuildInvitationAnswerMessage(myContactCard *meowlib.ContactCard) return &msg, nil } +// BuildInvitationStep4Message builds the invitee's confirmation UserMessage (STEP_4). +// Sent through the initiator's servers to signal the invitation is complete. +func (p *Peer) BuildInvitationStep4Message() (*meowlib.UserMessage, error) { + var msg meowlib.UserMessage + var invitation meowlib.Invitation + invitation.Step = 4 + invitation.Uuid = p.InvitationId + msg.Destination = p.ContactLookupKey + msg.Invitation = &invitation + msg.From = p.MyIdentity.Public + msg.Type = "1" + return &msg, nil +} + +// ProcessInboundStep2UserMessage decrypts the invitee's step-2 answer using the +// initiator's temporary InvitationKp private key. inviteePublicKey is the sender's +// identity public key (carried in Invitation.From by the server). +func (p *Peer) ProcessInboundStep2UserMessage(packed *meowlib.PackedUserMessage, inviteePublicKey string) (*meowlib.UserMessage, error) { + dec, err := meowlib.AsymDecryptAndCheck(p.InvitationKp.Private, inviteePublicKey, packed.Payload, packed.Signature) + if err != nil { + return nil, err + } + return p.DeserializeUserMessage(dec) +} + +// ProcessInboundStep3UserMessage decrypts the initiator's step-3 full ContactCard using +// the invitee's MyEncryptionKp. Signature verification is skipped because the +// initiator's identity key is not yet known — it is extracted from the decrypted payload. +func (p *Peer) ProcessInboundStep3UserMessage(packed *meowlib.PackedUserMessage) (*meowlib.UserMessage, error) { + dec, err := meowlib.AsymDecrypt(p.MyEncryptionKp.Private, packed.Payload) + if err != nil { + return nil, err + } + return p.DeserializeUserMessage(dec) +} + // // Messages encryption and packaging // diff --git a/client/peer_test.go b/client/peer_test.go index 3383553..a8fa157 100644 --- a/client/peer_test.go +++ b/client/peer_test.go @@ -228,7 +228,7 @@ func TestBuildSingleFileMessage_EmptyFile(t *testing.T) { // BuildInvitationAnswerMessage // --------------------------------------------------------------------------- -func TestBuildInvitationAnswerMessage(t *testing.T) { +func TestBuildInvitationStep2Message(t *testing.T) { p := &Peer{ ContactLookupKey: "dest-lookup", MyIdentity: &meowlib.KeyPair{Public: "my-pub"}, @@ -239,13 +239,13 @@ func TestBuildInvitationAnswerMessage(t *testing.T) { ContactPublicKey: "alice-pub", } - msg, err := p.BuildInvitationAnswerMessage(contactCard) + msg, err := p.BuildInvitationStep2Message(contactCard) assert.NoError(t, err) assert.Equal(t, "dest-lookup", msg.Destination) assert.Equal(t, "my-pub", msg.From) assert.Equal(t, "1", msg.Type) assert.NotNil(t, msg.Invitation) - assert.Equal(t, int32(3), msg.Invitation.Step) + assert.Equal(t, int32(2), msg.Invitation.Step) assert.Equal(t, "inv-uuid-123", msg.Invitation.Uuid) // Payload is the proto-serialized contact card @@ -256,6 +256,42 @@ func TestBuildInvitationAnswerMessage(t *testing.T) { assert.Equal(t, "alice-pub", decoded.ContactPublicKey) } +func TestBuildInvitationStep3Message(t *testing.T) { + p := &Peer{ + ContactLookupKey: "dest-lookup", + MyIdentity: &meowlib.KeyPair{Public: "my-pub"}, + InvitationId: "inv-uuid-456", + } + contactCard := &meowlib.ContactCard{ + Name: "Initiator", + ContactPublicKey: "init-pub", + } + + msg, err := p.BuildInvitationStep3Message(contactCard) + assert.NoError(t, err) + assert.Equal(t, int32(3), msg.Invitation.Step) + assert.Equal(t, "inv-uuid-456", msg.Invitation.Uuid) + + var decoded meowlib.ContactCard + err = proto.Unmarshal(msg.Invitation.Payload, &decoded) + assert.NoError(t, err) + assert.Equal(t, "Initiator", decoded.Name) +} + +func TestBuildInvitationStep4Message(t *testing.T) { + p := &Peer{ + ContactLookupKey: "dest-lookup", + MyIdentity: &meowlib.KeyPair{Public: "my-pub"}, + InvitationId: "inv-uuid-789", + } + + msg, err := p.BuildInvitationStep4Message() + assert.NoError(t, err) + assert.Equal(t, int32(4), msg.Invitation.Step) + assert.Equal(t, "inv-uuid-789", msg.Invitation.Uuid) + assert.Nil(t, msg.Invitation.Payload) +} + // --------------------------------------------------------------------------- // Serialize / Deserialize // --------------------------------------------------------------------------- diff --git a/client/server.go b/client/server.go index effcf10..182cba6 100644 --- a/client/server.go +++ b/client/server.go @@ -199,12 +199,12 @@ func (ints *Server) BuildVideoRoomRequestMessage(users []string, expiry uint64) return &msg, nil } -// 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, shortCodeLen int) (*meowlib.ToServerMessage, error) { +// BuildToServerMessageInvitationStep1 sends the InvitationInitPayload to the server (STEP_1). +// The server stores it and returns a shortcode URL. +func (ints *Server) BuildToServerMessageInvitationStep1(initPayload *meowlib.InvitationInitPayload, password string, timeout int, shortCodeLen int) (*meowlib.ToServerMessage, error) { var msg meowlib.ToServerMessage var inv meowlib.Invitation - payload, err := invitation.Compress() + payload, err := initPayload.Compress() if err != nil { return nil, err } diff --git a/client/server_test.go b/client/server_test.go index 4f20a8f..12160b5 100644 --- a/client/server_test.go +++ b/client/server_test.go @@ -454,16 +454,18 @@ func TestServer_BuildVideoRoomRequestMessage_SingleUser(t *testing.T) { // BuildToServerMessageInvitationCreation // --------------------------------------------------------------------------- -func TestServer_BuildToServerMessageInvitationCreation(t *testing.T) { +func TestServer_BuildToServerMessageInvitationStep1(t *testing.T) { srv, err := CreateServerFromUrl("https://example.com/meow") assert.NoError(t, err) - cc := &meowlib.ContactCard{ - Name: "Alice", - ContactPublicKey: "alice-pub", + initPayload := &meowlib.InvitationInitPayload{ + Uuid: "test-uuid", + Name: "Alice", + PublicKey: "alice-temp-pub", + InvitationMessage: "Hello!", } - msg, err := srv.BuildToServerMessageInvitationCreation(cc, "secret", 300, 8) + msg, err := srv.BuildToServerMessageInvitationStep1(initPayload, "secret", 300, 8) assert.NoError(t, err) assert.Equal(t, "1", msg.Type) assert.Equal(t, srv.UserKp.Public, msg.From) @@ -474,18 +476,19 @@ func TestServer_BuildToServerMessageInvitationCreation(t *testing.T) { assert.Equal(t, int32(8), msg.Invitation.ShortcodeLen) assert.NotEmpty(t, msg.Invitation.Payload) - // Payload is a compressed ContactCard — decompress and verify - restored, err := meowlib.NewContactCardFromCompressed(msg.Invitation.Payload) + // Payload is a compressed InvitationInitPayload — decompress and verify + restored, err := meowlib.NewInvitationInitPayloadFromCompressed(msg.Invitation.Payload) assert.NoError(t, err) assert.Equal(t, "Alice", restored.Name) - assert.Equal(t, "alice-pub", restored.ContactPublicKey) + assert.Equal(t, "alice-temp-pub", restored.PublicKey) + assert.Equal(t, "test-uuid", restored.Uuid) } -func TestServer_BuildToServerMessageInvitationCreation_NoPassword(t *testing.T) { +func TestServer_BuildToServerMessageInvitationStep1_NoPassword(t *testing.T) { srv, _ := CreateServerFromUrl("https://example.com/meow") - cc := &meowlib.ContactCard{Name: "Bob"} + initPayload := &meowlib.InvitationInitPayload{Name: "Bob", Uuid: "bob-uuid", PublicKey: "bob-pub"} - msg, err := srv.BuildToServerMessageInvitationCreation(cc, "", 60, 6) + msg, err := srv.BuildToServerMessageInvitationStep1(initPayload, "", 60, 6) assert.NoError(t, err) assert.Empty(t, msg.Invitation.Password) assert.Equal(t, int32(1), msg.Invitation.Step) diff --git a/client/test.id b/client/test.id new file mode 100644 index 0000000..83fc178 --- /dev/null +++ b/client/test.id @@ -0,0 +1,44 @@ +-----BEGIN PGP MESSAGE----- +Comment: https://gopenpgp.org +Version: GopenPGP 2.8.3 + +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 +=BI6k +-----END PGP MESSAGE----- \ No newline at end of file diff --git a/doc/invitation/sq_invitation.puml b/doc/invitation/sq_invitation.puml index c6ec40f..f896505 100644 --- a/doc/invitation/sq_invitation.puml +++ b/doc/invitation/sq_invitation.puml @@ -1,6 +1,13 @@ @startuml General Invitation Steps -InitiatingUser -> InvitedUser: STEP_1=InitiatingUser_TmpId generate a public key, invitation uid & message for InvitedUser optionnally password protected -InvitedUser -> InitiatingUser: STEP_2=InvitedUser_Id Create invitation for alice (Generate InvitedUser ContactCard and create InitiatingUser pending contact) -InitiatingUser -> InvitedUser: STEP_3=InitiatingUser_Id Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact) -InvitedUser -> InitiatingUser: STEP_4=InvitedUser_OK Review Answer, invitation finalize (Finalize InitiatingUser contact and notify InitiatingUser that communication is possible) +InitiatingUser -> InitiatingUser : STEP_1 = Create InivitedUser_Id generate a public key, invitation uid & message for InvitedUser optionnally password protected +InitiatingUser -> InvitedUser: STEP_1_SEND= transmit step 1 data (QR Code, Bluetooth, messaging, mail, ...) optional password being tranmitted through another channel + +InvitedUser -> InvitedUser :Create InitatingUser_Id & InvitedUser ContactCard +InvitedUser -> InitiatingUser: STEP_2_SEND=transmit InvitedUser ContactCard (QR Codes, Bluetooth, messaging, mail, ...) encrypted with initiating user pub key + +InitiatingUser -> InitiatingUser : STEP_3=InitiatingUser_Id Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact) +InitiatingUser -> InvitedUser: STEP_3_SEND=Send answer through invited user's message servers from contact card + +InvitedUser -> InvitedUser : Finalize InitiatingUser from its ContactCard +InvitedUser -> InitiatingUser: STEP_4= Send confirmation to InitiatingUser that communication is possible through initiating user's message servers from contact card @enduml \ No newline at end of file diff --git a/doc/invitation/sq_invitation_calls.puml b/doc/invitation/sq_invitation_calls.puml deleted file mode 100644 index fcefe5d..0000000 --- a/doc/invitation/sq_invitation_calls.puml +++ /dev/null @@ -1,9 +0,0 @@ -@startuml Detailled Invitation Steps -InitiatingUser -> MeowInitiatingUser: STEP 1 generate a public key & message for InvitedUser optionnally password protected -InitiatingUser -> InvitedUser: send public key -InvitedUser -> MeowInvitedUser: STEP 2 Create invitation for alice (Generate InvitedUser ContactCard and create InitiatingUser pending contact) -InvitedUser -> InitiatingUser: Send invitation (InvitedUser ContactCard encrypted with InitiatingUser public key) -InitiatingUser -> MeowInitiatingUser: STEP 3 Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact) -InitiatingUser -> InvitedUser: Send answer (InitiatingUser ContactCard) -InvitedUser -> MeowInvitedUser: STEP 4 Review Answer, invitation finalize (Finalize InitiatingUser contact and notify InitiatingUser that communication is possible) -@enduml \ No newline at end of file diff --git a/doc/invitation/sq_srvinv00.puml b/doc/invitation/sq_srvinv00.puml index 6b261e6..888bc48 100644 --- a/doc/invitation/sq_srvinv00.puml +++ b/doc/invitation/sq_srvinv00.puml @@ -2,7 +2,7 @@ InitiatingUser -> Bastet : contact name InitiatingUser -> Bastet : invitationMessage InitiatingUser -> Bastet : select invitation server -InitiatingUser -> Bastet : optional password +InitiatingUser -> Bastet : optional passwords ([payload], [url]) Bastet -> NativeLib : send invitation NativeLib -> NativeLib : create contact and invtation KP NativeLib -> Server : send invitation @@ -12,4 +12,5 @@ Server -> NativeLib : invitation URL NativeLib -> Bastet : invitation URL Bastet -> InitiatingUser : invitation URL InitiatingUser -> InvitedUser : invitation URL +InitiatingUser -> InvitedUser : [invitation passwords] through another channel @enduml \ No newline at end of file diff --git a/endtoend_test.go b/endtoend_test.go index 536a5c4..c48b4a2 100644 --- a/endtoend_test.go +++ b/endtoend_test.go @@ -32,58 +32,59 @@ func TestEndToEnd(t *testing.T) { srv = client.Server{Name: "MyServer", Url: "meow://127.0.0.1"} Me.MessageServers.StoreServer(&srv) //////////////////////////////////////////////////////////////////////////// - // Create an invitation for a friend, I want him/her to know me as Bender // + // STEP_1: Create an invitation — only a temp keypair, no full ContactCard // //////////////////////////////////////////////////////////////////////////// fmt.Println("Creating an invitation for the first friend...") - peer, err := Me.InvitePeer("Bender", "myfirstfriend", []string{"http://127.0.0.1/meow/", "mqtt://127.0.0.1"}, "welcome, it's me!") + initPayload, peer, err := Me.InvitationStep1("Bender", "myfirstfriend", []string{"http://127.0.0.1/meow/", "mqtt://127.0.0.1"}, "welcome, it's me!") if err != nil { println(err) } println(peer.Name) - // print my invitation - a, _ := json.Marshal(peer.GetMyContact()) - fmt.Println(string(a)) - // TODO : Convert invitation to QR Code - peer.GetMyContact().WritePng("invitation.png") - data, err := peer.GetMyContact().Compress() + a, _ := json.Marshal(initPayload) + fmt.Println("InvitationInitPayload:", string(a)) + data, err := initPayload.Compress() if err != nil { println(err) } - peer.GetMyContact().WriteQr("qrcode.png") - println("Compressed contact card :", len(data)) - /////////////////////////////////////// - // Simulate peer invitation response // - /////////////////////////////////////// - fmt.Println("Simulating first friend answer...") - var ReceivedContact meowlib.ContactCard + initPayload.WriteQr("qrcode.png") + println("Compressed init payload :", len(data)) - // Friend simulated invitation - FirstFriendContactKp, err := meowlib.NewKeyPair() + /////////////////////////////////////////////////////// + // STEP_2: Simulate friend receiving the payload and answering // + /////////////////////////////////////////////////////// + fmt.Println("Simulating first friend answer (STEP_2)...") + friendMe, err := client.CreateIdentity("friendname") if err != nil { t.Fatal(err) } - FirstFriendEncryptionKp, err := meowlib.NewKeyPair() + friendPeer, err := friendMe.InvitationStep2("FriendNick", "Bender", []string{}, initPayload) if err != nil { t.Fatal(err) } - FirstFriendLookupKp, err := meowlib.NewKeyPair() - if err != nil { - t.Fatal(err) - } - ReceivedContact.Name = "I'm the friend" - ReceivedContact.ContactPublicKey = FirstFriendContactKp.Public - ReceivedContact.EncryptionPublicKey = FirstFriendEncryptionKp.Public - ReceivedContact.LookupPublicKey = FirstFriendLookupKp.Public - ReceivedContact.InvitationId = peer.GetMyContact().InvitationId + friendCC := friendPeer.GetMyContact() FriendServer1KP, err := meowlib.NewKeyPair() - + if err != nil { + t.Fatal(err) + } FriendServer1 := meowlib.ServerCard{Name: "FriendServer1", Url: "http://myfriend.org/meow/", PublicKey: FriendServer1KP.Public, Description: "Fancy description"} - ReceivedContact.PullServers = append(ReceivedContact.PullServers, &FriendServer1) + friendCC.PullServers = append(friendCC.PullServers, &FriendServer1) - /////////////////////////////////////////////////////// - // Finalize the contact with the invitation response // - /////////////////////////////////////////////////////// - Me.FinalizeInvitation(&ReceivedContact) + ////////////////////////////////////////////////////////////////// + // STEP_3: Initiator receives friend's CC, generates full keypairs // + ////////////////////////////////////////////////////////////////// + myCC, _, err := Me.InvitationStep3(friendCC) + if err != nil { + t.Fatal(err) + } + fmt.Println("Initiator ContactCard ready:", myCC.ContactPublicKey != "") + + //////////////////////////////////////////////////// + // STEP_4: Friend finalizes initiator's ContactCard // + //////////////////////////////////////////////////// + err = friendMe.InvitationStep4(myCC) + if err != nil { + t.Fatal(err) + } err = Me.Save() if err != nil { t.Fatal(err) diff --git a/invitationinitpayload.go b/invitationinitpayload.go new file mode 100644 index 0000000..5c9fbd6 --- /dev/null +++ b/invitationinitpayload.go @@ -0,0 +1,80 @@ +package meowlib + +import ( + "bytes" + "compress/gzip" + "io" + "os" + + "github.com/makiuchi-d/gozxing" + "github.com/makiuchi-d/gozxing/qrcode" + "google.golang.org/protobuf/proto" + + "image/png" +) + +func (p *InvitationInitPayload) Serialize() ([]byte, error) { + return proto.Marshal(p) +} + +func (p *InvitationInitPayload) Compress() ([]byte, error) { + out, err := p.Serialize() + if err != nil { + return nil, err + } + var b bytes.Buffer + gz, err := gzip.NewWriterLevel(&b, gzip.BestCompression) + if err != nil { + return nil, err + } + if _, err := gz.Write(out); err != nil { + return nil, err + } + if err := gz.Close(); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +func NewInvitationInitPayloadFromCompressed(compressed []byte) (*InvitationInitPayload, error) { + p := &InvitationInitPayload{} + reader := bytes.NewReader(compressed) + gzreader, err := gzip.NewReader(reader) + if err != nil { + return nil, err + } + output, err := io.ReadAll(gzreader) + if err != nil { + return nil, err + } + if err := proto.Unmarshal(output, p); err != nil { + return nil, err + } + return p, nil +} + +func (p *InvitationInitPayload) WriteCompressed(filename string) error { + out, err := p.Compress() + if err != nil { + return err + } + return os.WriteFile(filename, out, 0600) +} + +func (p *InvitationInitPayload) WriteQr(filename string) error { + data, err := p.Compress() + if err != nil { + return err + } + qwriter := qrcode.NewQRCodeWriter() + code, err := qwriter.Encode(string(data), gozxing.BarcodeFormat_QR_CODE, 512, 512, nil) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + return png.Encode(file, code) +} diff --git a/messages.pb.go b/messages.pb.go index 64a12f1..86226e1 100644 --- a/messages.pb.go +++ b/messages.pb.go @@ -97,15 +97,15 @@ func (x *PackedServerMessage) GetSignature() []byte { // structure to hold an invitation through a server type Invitation struct { state protoimpl.MessageState `protogen:"open.v1"` - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` // invitation payload, encrypted after step 2 - Timeout int32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` // how long do I want the invitation to remain available on the server - ShortcodeLen int32 `protobuf:"varint,3,opt,name=shortcodeLen,proto3" json:"shortcodeLen,omitempty"` // len of the shortcode you wish for short url transmission - Shortcode string `protobuf:"bytes,4,opt,name=shortcode,proto3" json:"shortcode,omitempty"` // shortcode that the friend shall request to get the invitation - Password string `protobuf:"bytes,5,opt,name=password,proto3" json:"password,omitempty"` // password to set for accessing invitation (optional) - Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"` // id that the friend gave you, that you should include to your reply to get recognized - Expiry int64 `protobuf:"varint,7,opt,name=expiry,proto3" json:"expiry,omitempty"` // the server allowed expiry date, it may be samller than the requested timeout according to server policy - Step int32 `protobuf:"varint,8,opt,name=step,proto3" json:"step,omitempty"` // progress in the inviattion process : 1=invite friend, 2=friend requests invitation, 3=friend's answer - From string `protobuf:"bytes,9,opt,name=from,proto3" json:"from,omitempty"` // used in step 3 the answer public key to check the signature in user message + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` // invitation payload, optionaly encrypted with payload password(transmitted OOB) on step 1 + Timeout int32 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` // how long do I want the invitation to remain available on the server + ShortcodeLen int32 `protobuf:"varint,3,opt,name=shortcode_len,json=shortcodeLen,proto3" json:"shortcode_len,omitempty"` // len of the shortcode you wish for short url transmission + Shortcode string `protobuf:"bytes,4,opt,name=shortcode,proto3" json:"shortcode,omitempty"` // shortcode that the friend shall request to get the invitation + Password string `protobuf:"bytes,5,opt,name=password,proto3" json:"password,omitempty"` // optional password(transmitted OOB) to set for accessing invitation (server check) + Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"` // invitation uuid + Expiry int64 `protobuf:"varint,7,opt,name=expiry,proto3" json:"expiry,omitempty"` // the server allowed expiry date, it may be smaller than the requested timeout according to server policy + Step int32 `protobuf:"varint,8,opt,name=step,proto3" json:"step,omitempty"` // progress in the invitation process : 1=initiator pub key, 2=invited data enc with pub key, 3=initator data full encrypted, 4=invited All OK ! + From string `protobuf:"bytes,9,opt,name=from,proto3" json:"from,omitempty"` // still useful ? unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -203,6 +203,75 @@ func (x *Invitation) GetFrom() string { return "" } +// This payload migh be used for file serialization as well as Invitation/payload in case of through server invitation +type InvitationInitPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // uuid of the invitation, it is set here on init cause the payload might be encrypted + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // name of the initiator + PublicKey string `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // public key to be used for step 2 encryption + InvitationMessage string `protobuf:"bytes,4,opt,name=invitation_message,json=invitationMessage,proto3" json:"invitation_message,omitempty"` // message for the invited peer + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InvitationInitPayload) Reset() { + *x = InvitationInitPayload{} + mi := &file_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InvitationInitPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InvitationInitPayload) ProtoMessage() {} + +func (x *InvitationInitPayload) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InvitationInitPayload.ProtoReflect.Descriptor instead. +func (*InvitationInitPayload) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *InvitationInitPayload) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *InvitationInitPayload) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *InvitationInitPayload) GetPublicKey() string { + if x != nil { + return x.PublicKey + } + return "" +} + +func (x *InvitationInitPayload) GetInvitationMessage() string { + if x != nil { + return x.InvitationMessage + } + return "" +} + // structure for requesting incoming messages type ConversationRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -216,7 +285,7 @@ type ConversationRequest struct { func (x *ConversationRequest) Reset() { *x = ConversationRequest{} - mi := &file_messages_proto_msgTypes[2] + mi := &file_messages_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -228,7 +297,7 @@ func (x *ConversationRequest) String() string { func (*ConversationRequest) ProtoMessage() {} func (x *ConversationRequest) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[2] + mi := &file_messages_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -241,7 +310,7 @@ func (x *ConversationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConversationRequest.ProtoReflect.Descriptor instead. func (*ConversationRequest) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{2} + return file_messages_proto_rawDescGZIP(), []int{3} } func (x *ConversationRequest) GetLookupKey() string { @@ -283,7 +352,7 @@ type Meet struct { func (x *Meet) Reset() { *x = Meet{} - mi := &file_messages_proto_msgTypes[3] + mi := &file_messages_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -295,7 +364,7 @@ func (x *Meet) String() string { func (*Meet) ProtoMessage() {} func (x *Meet) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[3] + mi := &file_messages_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -308,7 +377,7 @@ func (x *Meet) ProtoReflect() protoreflect.Message { // Deprecated: Use Meet.ProtoReflect.Descriptor instead. func (*Meet) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{3} + return file_messages_proto_rawDescGZIP(), []int{4} } func (x *Meet) GetPublicStatus() string { @@ -344,7 +413,7 @@ type Credentials struct { func (x *Credentials) Reset() { *x = Credentials{} - mi := &file_messages_proto_msgTypes[4] + mi := &file_messages_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -356,7 +425,7 @@ func (x *Credentials) String() string { func (*Credentials) ProtoMessage() {} func (x *Credentials) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[4] + mi := &file_messages_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -369,7 +438,7 @@ func (x *Credentials) ProtoReflect() protoreflect.Message { // Deprecated: Use Credentials.ProtoReflect.Descriptor instead. func (*Credentials) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{4} + return file_messages_proto_rawDescGZIP(), []int{5} } func (x *Credentials) GetLogin() string { @@ -422,7 +491,7 @@ type ToServerMessage struct { func (x *ToServerMessage) Reset() { *x = ToServerMessage{} - mi := &file_messages_proto_msgTypes[5] + mi := &file_messages_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -434,7 +503,7 @@ func (x *ToServerMessage) String() string { func (*ToServerMessage) ProtoMessage() {} func (x *ToServerMessage) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[5] + mi := &file_messages_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -447,7 +516,7 @@ func (x *ToServerMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use ToServerMessage.ProtoReflect.Descriptor instead. func (*ToServerMessage) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{5} + return file_messages_proto_rawDescGZIP(), []int{6} } func (x *ToServerMessage) GetType() string { @@ -561,7 +630,7 @@ type FromServerMessage struct { func (x *FromServerMessage) Reset() { *x = FromServerMessage{} - mi := &file_messages_proto_msgTypes[6] + mi := &file_messages_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -573,7 +642,7 @@ func (x *FromServerMessage) String() string { func (*FromServerMessage) ProtoMessage() {} func (x *FromServerMessage) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[6] + mi := &file_messages_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -586,7 +655,7 @@ func (x *FromServerMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use FromServerMessage.ProtoReflect.Descriptor instead. func (*FromServerMessage) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{6} + return file_messages_proto_rawDescGZIP(), []int{7} } func (x *FromServerMessage) GetType() string { @@ -678,7 +747,7 @@ type MatriochkaServer struct { func (x *MatriochkaServer) Reset() { *x = MatriochkaServer{} - mi := &file_messages_proto_msgTypes[7] + mi := &file_messages_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -690,7 +759,7 @@ func (x *MatriochkaServer) String() string { func (*MatriochkaServer) ProtoMessage() {} func (x *MatriochkaServer) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[7] + mi := &file_messages_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -703,7 +772,7 @@ func (x *MatriochkaServer) ProtoReflect() protoreflect.Message { // Deprecated: Use MatriochkaServer.ProtoReflect.Descriptor instead. func (*MatriochkaServer) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{7} + return file_messages_proto_rawDescGZIP(), []int{8} } func (x *MatriochkaServer) GetUrl() string { @@ -746,7 +815,7 @@ type Matriochka struct { func (x *Matriochka) Reset() { *x = Matriochka{} - mi := &file_messages_proto_msgTypes[8] + mi := &file_messages_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -758,7 +827,7 @@ func (x *Matriochka) String() string { func (*Matriochka) ProtoMessage() {} func (x *Matriochka) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[8] + mi := &file_messages_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -771,7 +840,7 @@ func (x *Matriochka) ProtoReflect() protoreflect.Message { // Deprecated: Use Matriochka.ProtoReflect.Descriptor instead. func (*Matriochka) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{8} + return file_messages_proto_rawDescGZIP(), []int{9} } func (x *Matriochka) GetLookupKey() string { @@ -818,7 +887,7 @@ type ServerCard struct { func (x *ServerCard) Reset() { *x = ServerCard{} - mi := &file_messages_proto_msgTypes[9] + mi := &file_messages_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -830,7 +899,7 @@ func (x *ServerCard) String() string { func (*ServerCard) ProtoMessage() {} func (x *ServerCard) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[9] + mi := &file_messages_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -843,7 +912,7 @@ func (x *ServerCard) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerCard.ProtoReflect.Descriptor instead. func (*ServerCard) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{9} + return file_messages_proto_rawDescGZIP(), []int{10} } func (x *ServerCard) GetName() string { @@ -915,7 +984,7 @@ type ContactCard struct { func (x *ContactCard) Reset() { *x = ContactCard{} - mi := &file_messages_proto_msgTypes[10] + mi := &file_messages_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -927,7 +996,7 @@ func (x *ContactCard) String() string { func (*ContactCard) ProtoMessage() {} func (x *ContactCard) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[10] + mi := &file_messages_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -940,7 +1009,7 @@ func (x *ContactCard) ProtoReflect() protoreflect.Message { // Deprecated: Use ContactCard.ProtoReflect.Descriptor instead. func (*ContactCard) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{10} + return file_messages_proto_rawDescGZIP(), []int{11} } func (x *ContactCard) GetName() string { @@ -1035,7 +1104,7 @@ type PackedUserMessage struct { func (x *PackedUserMessage) Reset() { *x = PackedUserMessage{} - mi := &file_messages_proto_msgTypes[11] + mi := &file_messages_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1047,7 +1116,7 @@ func (x *PackedUserMessage) String() string { func (*PackedUserMessage) ProtoMessage() {} func (x *PackedUserMessage) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[11] + mi := &file_messages_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1060,7 +1129,7 @@ func (x *PackedUserMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use PackedUserMessage.ProtoReflect.Descriptor instead. func (*PackedUserMessage) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{11} + return file_messages_proto_rawDescGZIP(), []int{12} } func (x *PackedUserMessage) GetDestination() string { @@ -1108,7 +1177,7 @@ func (x *PackedUserMessage) GetDrHeader() []byte { type ConversationStatus struct { state protoimpl.MessageState `protogen:"open.v1"` Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // uuid of message, or uuid of related message if uuid_action is not empty - Reactions []*Reaction `protobuf:"bytes,2,rep,name=reactions,proto3" json:"reactions,omitempty"` // empty => normal message, 1: receivedack, 2: processedack, 3:reaction + Reactions []*Reaction `protobuf:"bytes,2,rep,name=reactions,proto3" json:"reactions,omitempty"` // reaction to the message per peer ReplyToUuid string `protobuf:"bytes,3,opt,name=reply_to_uuid,json=replyToUuid,proto3" json:"reply_to_uuid,omitempty"` // this message replies to the specified uuid LocalSequence uint64 `protobuf:"varint,4,opt,name=local_sequence,json=localSequence,proto3" json:"local_sequence,omitempty"` // seq number in local conversation for custom reordering Sent uint64 `protobuf:"varint,5,opt,name=sent,proto3" json:"sent,omitempty"` // timestamp of the message sent @@ -1122,7 +1191,7 @@ type ConversationStatus struct { func (x *ConversationStatus) Reset() { *x = ConversationStatus{} - mi := &file_messages_proto_msgTypes[12] + mi := &file_messages_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1134,7 +1203,7 @@ func (x *ConversationStatus) String() string { func (*ConversationStatus) ProtoMessage() {} func (x *ConversationStatus) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[12] + mi := &file_messages_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1147,7 +1216,7 @@ func (x *ConversationStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ConversationStatus.ProtoReflect.Descriptor instead. func (*ConversationStatus) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{12} + return file_messages_proto_rawDescGZIP(), []int{13} } func (x *ConversationStatus) GetUuid() string { @@ -1223,7 +1292,7 @@ type Reaction struct { func (x *Reaction) Reset() { *x = Reaction{} - mi := &file_messages_proto_msgTypes[13] + mi := &file_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1235,7 +1304,7 @@ func (x *Reaction) String() string { func (*Reaction) ProtoMessage() {} func (x *Reaction) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[13] + mi := &file_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1248,7 +1317,7 @@ func (x *Reaction) ProtoReflect() protoreflect.Message { // Deprecated: Use Reaction.ProtoReflect.Descriptor instead. func (*Reaction) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{13} + return file_messages_proto_rawDescGZIP(), []int{14} } func (x *Reaction) GetReaction() string { @@ -1275,7 +1344,7 @@ type Group struct { func (x *Group) Reset() { *x = Group{} - mi := &file_messages_proto_msgTypes[14] + mi := &file_messages_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1287,7 +1356,7 @@ func (x *Group) String() string { func (*Group) ProtoMessage() {} func (x *Group) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[14] + mi := &file_messages_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1300,7 +1369,7 @@ func (x *Group) ProtoReflect() protoreflect.Message { // Deprecated: Use Group.ProtoReflect.Descriptor instead. func (*Group) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{14} + return file_messages_proto_rawDescGZIP(), []int{15} } func (x *Group) GetName() string { @@ -1339,7 +1408,7 @@ type UserMessage struct { func (x *UserMessage) Reset() { *x = UserMessage{} - mi := &file_messages_proto_msgTypes[15] + mi := &file_messages_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1351,7 +1420,7 @@ func (x *UserMessage) String() string { func (*UserMessage) ProtoMessage() {} func (x *UserMessage) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[15] + mi := &file_messages_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1364,7 +1433,7 @@ func (x *UserMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use UserMessage.ProtoReflect.Descriptor instead. func (*UserMessage) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{15} + return file_messages_proto_rawDescGZIP(), []int{16} } func (x *UserMessage) GetDestination() string { @@ -1470,7 +1539,7 @@ type File struct { func (x *File) Reset() { *x = File{} - mi := &file_messages_proto_msgTypes[16] + mi := &file_messages_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1482,7 +1551,7 @@ func (x *File) String() string { func (*File) ProtoMessage() {} func (x *File) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[16] + mi := &file_messages_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1495,7 +1564,7 @@ func (x *File) ProtoReflect() protoreflect.Message { // Deprecated: Use File.ProtoReflect.Descriptor instead. func (*File) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{16} + return file_messages_proto_rawDescGZIP(), []int{17} } func (x *File) GetFilename() string { @@ -1538,7 +1607,7 @@ type Location struct { func (x *Location) Reset() { *x = Location{} - mi := &file_messages_proto_msgTypes[17] + mi := &file_messages_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1550,7 +1619,7 @@ func (x *Location) String() string { func (*Location) ProtoMessage() {} func (x *Location) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[17] + mi := &file_messages_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1563,7 +1632,7 @@ func (x *Location) ProtoReflect() protoreflect.Message { // Deprecated: Use Location.ProtoReflect.Descriptor instead. func (*Location) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{17} + return file_messages_proto_rawDescGZIP(), []int{18} } func (x *Location) GetTime() uint64 { @@ -1615,7 +1684,7 @@ type DbMessage struct { func (x *DbMessage) Reset() { *x = DbMessage{} - mi := &file_messages_proto_msgTypes[18] + mi := &file_messages_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1627,7 +1696,7 @@ func (x *DbMessage) String() string { func (*DbMessage) ProtoMessage() {} func (x *DbMessage) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[18] + mi := &file_messages_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1640,7 +1709,7 @@ func (x *DbMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use DbMessage.ProtoReflect.Descriptor instead. func (*DbMessage) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{18} + return file_messages_proto_rawDescGZIP(), []int{19} } func (x *DbMessage) GetOutbound() bool { @@ -1747,7 +1816,7 @@ type VideoData struct { func (x *VideoData) Reset() { *x = VideoData{} - mi := &file_messages_proto_msgTypes[19] + mi := &file_messages_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1759,7 +1828,7 @@ func (x *VideoData) String() string { func (*VideoData) ProtoMessage() {} func (x *VideoData) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[19] + mi := &file_messages_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1772,7 +1841,7 @@ func (x *VideoData) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoData.ProtoReflect.Descriptor instead. func (*VideoData) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{19} + return file_messages_proto_rawDescGZIP(), []int{20} } func (x *VideoData) GetUrl() string { @@ -1821,7 +1890,7 @@ type VideoCredential struct { func (x *VideoCredential) Reset() { *x = VideoCredential{} - mi := &file_messages_proto_msgTypes[20] + mi := &file_messages_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1833,7 +1902,7 @@ func (x *VideoCredential) String() string { func (*VideoCredential) ProtoMessage() {} func (x *VideoCredential) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[20] + mi := &file_messages_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1846,7 +1915,7 @@ func (x *VideoCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoCredential.ProtoReflect.Descriptor instead. func (*VideoCredential) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{20} + return file_messages_proto_rawDescGZIP(), []int{21} } func (x *VideoCredential) GetUsername() string { @@ -1878,18 +1947,24 @@ const file_messages_proto_rawDesc = "" + "\x13PackedServerMessage\x12\x12\n" + "\x04from\x18\x01 \x01(\tR\x04from\x12\x18\n" + "\apayload\x18\x02 \x01(\fR\apayload\x12\x1c\n" + - "\tsignature\x18\x03 \x01(\fR\tsignature\"\xf2\x01\n" + + "\tsignature\x18\x03 \x01(\fR\tsignature\"\xf3\x01\n" + "\n" + "Invitation\x12\x18\n" + "\apayload\x18\x01 \x01(\fR\apayload\x12\x18\n" + - "\atimeout\x18\x02 \x01(\x05R\atimeout\x12\"\n" + - "\fshortcodeLen\x18\x03 \x01(\x05R\fshortcodeLen\x12\x1c\n" + + "\atimeout\x18\x02 \x01(\x05R\atimeout\x12#\n" + + "\rshortcode_len\x18\x03 \x01(\x05R\fshortcodeLen\x12\x1c\n" + "\tshortcode\x18\x04 \x01(\tR\tshortcode\x12\x1a\n" + "\bpassword\x18\x05 \x01(\tR\bpassword\x12\x12\n" + "\x04uuid\x18\x06 \x01(\tR\x04uuid\x12\x16\n" + "\x06expiry\x18\a \x01(\x03R\x06expiry\x12\x12\n" + "\x04step\x18\b \x01(\x05R\x04step\x12\x12\n" + - "\x04from\x18\t \x01(\tR\x04from\"\xb1\x01\n" + + "\x04from\x18\t \x01(\tR\x04from\"\x8d\x01\n" + + "\x15InvitationInitPayload\x12\x12\n" + + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1d\n" + + "\n" + + "public_key\x18\x03 \x01(\tR\tpublicKey\x12-\n" + + "\x12invitation_message\x18\x04 \x01(\tR\x11invitationMessage\"\xb1\x01\n" + "\x13ConversationRequest\x12\x1d\n" + "\n" + "lookup_key\x18\x01 \x01(\tR\tlookupKey\x12)\n" + @@ -2072,66 +2147,67 @@ func file_messages_proto_rawDescGZIP() []byte { return file_messages_proto_rawDescData } -var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_messages_proto_goTypes = []any{ - (*PackedServerMessage)(nil), // 0: meowlib.PackedServerMessage - (*Invitation)(nil), // 1: meowlib.Invitation - (*ConversationRequest)(nil), // 2: meowlib.ConversationRequest - (*Meet)(nil), // 3: meowlib.Meet - (*Credentials)(nil), // 4: meowlib.Credentials - (*ToServerMessage)(nil), // 5: meowlib.ToServerMessage - (*FromServerMessage)(nil), // 6: meowlib.FromServerMessage - (*MatriochkaServer)(nil), // 7: meowlib.MatriochkaServer - (*Matriochka)(nil), // 8: meowlib.Matriochka - (*ServerCard)(nil), // 9: meowlib.ServerCard - (*ContactCard)(nil), // 10: meowlib.ContactCard - (*PackedUserMessage)(nil), // 11: meowlib.PackedUserMessage - (*ConversationStatus)(nil), // 12: meowlib.ConversationStatus - (*Reaction)(nil), // 13: meowlib.Reaction - (*Group)(nil), // 14: meowlib.Group - (*UserMessage)(nil), // 15: meowlib.UserMessage - (*File)(nil), // 16: meowlib.File - (*Location)(nil), // 17: meowlib.Location - (*DbMessage)(nil), // 18: meowlib.DbMessage - (*VideoData)(nil), // 19: meowlib.VideoData - (*VideoCredential)(nil), // 20: meowlib.VideoCredential + (*PackedServerMessage)(nil), // 0: meowlib.PackedServerMessage + (*Invitation)(nil), // 1: meowlib.Invitation + (*InvitationInitPayload)(nil), // 2: meowlib.InvitationInitPayload + (*ConversationRequest)(nil), // 3: meowlib.ConversationRequest + (*Meet)(nil), // 4: meowlib.Meet + (*Credentials)(nil), // 5: meowlib.Credentials + (*ToServerMessage)(nil), // 6: meowlib.ToServerMessage + (*FromServerMessage)(nil), // 7: meowlib.FromServerMessage + (*MatriochkaServer)(nil), // 8: meowlib.MatriochkaServer + (*Matriochka)(nil), // 9: meowlib.Matriochka + (*ServerCard)(nil), // 10: meowlib.ServerCard + (*ContactCard)(nil), // 11: meowlib.ContactCard + (*PackedUserMessage)(nil), // 12: meowlib.PackedUserMessage + (*ConversationStatus)(nil), // 13: meowlib.ConversationStatus + (*Reaction)(nil), // 14: meowlib.Reaction + (*Group)(nil), // 15: meowlib.Group + (*UserMessage)(nil), // 16: meowlib.UserMessage + (*File)(nil), // 17: meowlib.File + (*Location)(nil), // 18: meowlib.Location + (*DbMessage)(nil), // 19: meowlib.DbMessage + (*VideoData)(nil), // 20: meowlib.VideoData + (*VideoCredential)(nil), // 21: meowlib.VideoCredential } var file_messages_proto_depIdxs = []int32{ - 10, // 0: meowlib.Meet.contact_card:type_name -> meowlib.ContactCard - 2, // 1: meowlib.ToServerMessage.pull_request:type_name -> meowlib.ConversationRequest - 11, // 2: meowlib.ToServerMessage.messages:type_name -> meowlib.PackedUserMessage - 9, // 3: meowlib.ToServerMessage.known_servers:type_name -> meowlib.ServerCard - 8, // 4: meowlib.ToServerMessage.matriochka_message:type_name -> meowlib.Matriochka + 11, // 0: meowlib.Meet.contact_card:type_name -> meowlib.ContactCard + 3, // 1: meowlib.ToServerMessage.pull_request:type_name -> meowlib.ConversationRequest + 12, // 2: meowlib.ToServerMessage.messages:type_name -> meowlib.PackedUserMessage + 10, // 3: meowlib.ToServerMessage.known_servers:type_name -> meowlib.ServerCard + 9, // 4: meowlib.ToServerMessage.matriochka_message:type_name -> meowlib.Matriochka 1, // 5: meowlib.ToServerMessage.invitation:type_name -> meowlib.Invitation - 11, // 6: meowlib.ToServerMessage.device_messages:type_name -> meowlib.PackedUserMessage - 19, // 7: meowlib.ToServerMessage.video_data:type_name -> meowlib.VideoData - 4, // 8: meowlib.ToServerMessage.credentials:type_name -> meowlib.Credentials - 11, // 9: meowlib.FromServerMessage.chat:type_name -> meowlib.PackedUserMessage - 9, // 10: meowlib.FromServerMessage.known_servers:type_name -> meowlib.ServerCard + 12, // 6: meowlib.ToServerMessage.device_messages:type_name -> meowlib.PackedUserMessage + 20, // 7: meowlib.ToServerMessage.video_data:type_name -> meowlib.VideoData + 5, // 8: meowlib.ToServerMessage.credentials:type_name -> meowlib.Credentials + 12, // 9: meowlib.FromServerMessage.chat:type_name -> meowlib.PackedUserMessage + 10, // 10: meowlib.FromServerMessage.known_servers:type_name -> meowlib.ServerCard 1, // 11: meowlib.FromServerMessage.invitation:type_name -> meowlib.Invitation - 11, // 12: meowlib.FromServerMessage.device_messages:type_name -> meowlib.PackedUserMessage - 19, // 13: meowlib.FromServerMessage.video_data:type_name -> meowlib.VideoData - 10, // 14: meowlib.FromServerMessage.contact_card:type_name -> meowlib.ContactCard - 7, // 15: meowlib.Matriochka.prev:type_name -> meowlib.MatriochkaServer - 7, // 16: meowlib.Matriochka.next:type_name -> meowlib.MatriochkaServer - 9, // 17: meowlib.ContactCard.pull_servers:type_name -> meowlib.ServerCard - 13, // 18: meowlib.ConversationStatus.reactions:type_name -> meowlib.Reaction - 10, // 19: meowlib.ConversationStatus.my_next_identity:type_name -> meowlib.ContactCard - 10, // 20: meowlib.Group.members:type_name -> meowlib.ContactCard - 12, // 21: meowlib.UserMessage.status:type_name -> meowlib.ConversationStatus - 10, // 22: meowlib.UserMessage.contact:type_name -> meowlib.ContactCard - 9, // 23: meowlib.UserMessage.known_servers:type_name -> meowlib.ServerCard - 14, // 24: meowlib.UserMessage.group:type_name -> meowlib.Group - 16, // 25: meowlib.UserMessage.files:type_name -> meowlib.File - 17, // 26: meowlib.UserMessage.current_location:type_name -> meowlib.Location + 12, // 12: meowlib.FromServerMessage.device_messages:type_name -> meowlib.PackedUserMessage + 20, // 13: meowlib.FromServerMessage.video_data:type_name -> meowlib.VideoData + 11, // 14: meowlib.FromServerMessage.contact_card:type_name -> meowlib.ContactCard + 8, // 15: meowlib.Matriochka.prev:type_name -> meowlib.MatriochkaServer + 8, // 16: meowlib.Matriochka.next:type_name -> meowlib.MatriochkaServer + 10, // 17: meowlib.ContactCard.pull_servers:type_name -> meowlib.ServerCard + 14, // 18: meowlib.ConversationStatus.reactions:type_name -> meowlib.Reaction + 11, // 19: meowlib.ConversationStatus.my_next_identity:type_name -> meowlib.ContactCard + 11, // 20: meowlib.Group.members:type_name -> meowlib.ContactCard + 13, // 21: meowlib.UserMessage.status:type_name -> meowlib.ConversationStatus + 11, // 22: meowlib.UserMessage.contact:type_name -> meowlib.ContactCard + 10, // 23: meowlib.UserMessage.known_servers:type_name -> meowlib.ServerCard + 15, // 24: meowlib.UserMessage.group:type_name -> meowlib.Group + 17, // 25: meowlib.UserMessage.files:type_name -> meowlib.File + 18, // 26: meowlib.UserMessage.current_location:type_name -> meowlib.Location 1, // 27: meowlib.UserMessage.invitation:type_name -> meowlib.Invitation - 19, // 28: meowlib.UserMessage.video_data:type_name -> meowlib.VideoData - 12, // 29: meowlib.DbMessage.status:type_name -> meowlib.ConversationStatus - 10, // 30: meowlib.DbMessage.contact:type_name -> meowlib.ContactCard - 14, // 31: meowlib.DbMessage.group:type_name -> meowlib.Group - 17, // 32: meowlib.DbMessage.current_location:type_name -> meowlib.Location + 20, // 28: meowlib.UserMessage.video_data:type_name -> meowlib.VideoData + 13, // 29: meowlib.DbMessage.status:type_name -> meowlib.ConversationStatus + 11, // 30: meowlib.DbMessage.contact:type_name -> meowlib.ContactCard + 15, // 31: meowlib.DbMessage.group:type_name -> meowlib.Group + 18, // 32: meowlib.DbMessage.current_location:type_name -> meowlib.Location 1, // 33: meowlib.DbMessage.invitation:type_name -> meowlib.Invitation - 20, // 34: meowlib.VideoData.credentials:type_name -> meowlib.VideoCredential + 21, // 34: meowlib.VideoData.credentials:type_name -> meowlib.VideoCredential 35, // [35:35] is the sub-list for method output_type 35, // [35:35] is the sub-list for method input_type 35, // [35:35] is the sub-list for extension type_name @@ -2150,7 +2226,7 @@ func file_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)), NumEnums: 0, - NumMessages: 21, + NumMessages: 22, NumExtensions: 0, NumServices: 0, }, diff --git a/pb/messages.proto b/pb/messages.proto index 84b481c..5623c6e 100644 --- a/pb/messages.proto +++ b/pb/messages.proto @@ -30,7 +30,15 @@ message Invitation { string uuid = 6; // invitation uuid int64 expiry = 7; // the server allowed expiry date, it may be smaller than the requested timeout according to server policy int32 step = 8; // progress in the invitation process : 1=initiator pub key, 2=invited data enc with pub key, 3=initator data full encrypted, 4=invited All OK ! - string from = 9; // used in step 1 the public key to encrypt step 2 message + string from = 9; // still useful ? +} + +// This payload migh be used for file serialization as well as Invitation/payload in case of through server invitation +message InvitationInitPayload { + string uuid = 1; // uuid of the invitation, it is set here on init cause the payload might be encrypted + string name = 2; // name of the initiator + string public_key = 3; // public key to be used for step 2 encryption + string invitation_message = 4; // message for the invited peer } // structure for requesting incoming messages