package helpers import ( "errors" "forge.redroom.link/yves/meowlib" "forge.redroom.link/yves/meowlib/client" "google.golang.org/protobuf/proto" ) // 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 if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil { return nil, nil, "InvitationStep3ProcessAnswer: Unmarshal PackedUserMessage", err } peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid) 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 }