Skip to content

Commit

Permalink
brclient: invite new user to gc on kx
Browse files Browse the repository at this point in the history
  • Loading branch information
dajohi committed Mar 21, 2023
1 parent 815f933 commit 8b9b903
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 15 deletions.
10 changes: 8 additions & 2 deletions brclient/appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,20 +1251,26 @@ func (as *appState) handleRcvdText(s string, nick string) string {

// writeInvite writes a new invite to the given filename. This blocks until the
// invite is written.
func (as *appState) writeInvite(filename string) {
func (as *appState) writeInvite(filename string, gcID zkidentity.ShortID) {
as.cwHelpMsg("Attempting to create and subscribe to new invite")
w := new(bytes.Buffer)
pii, err := as.c.WriteNewInvite(w)
if err != nil {
as.cwHelpMsg("Unable to create invite: %v", err)
return
}

err = os.WriteFile(filename, w.Bytes(), 0o600)
if err != nil {
as.cwHelpMsg("Unable to write invite file: %v", err)
return
}
if !gcID.IsEmpty() {
err = as.c.AddInviteOnKX(pii.InitialRendezvous, gcID)
if err != nil {
as.cwHelpMsg("Unable to add KX action: %v", err)
return
}
}

as.cwHelpMsg("Created invite at RV %s", pii.InitialRendezvous)
as.cwHelpMsg("Send file %q to other client and type /add %s",
Expand Down
19 changes: 16 additions & 3 deletions brclient/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2453,8 +2453,8 @@ var commands = []tuicmd{
},
{
cmd: "invite",
usage: "<filename>",
descr: "Create invitation file to send OOB to another user",
usage: "<filename> [gcname]",
descr: "Create invitation file with optional GC to send OOB to another user",
handler: func(args []string, as *appState) error {
if len(args) < 1 {
return usageError{msg: "filename must be specified"}
Expand All @@ -2464,7 +2464,20 @@ var commands = []tuicmd{
if err != nil {
return err
}
go as.writeInvite(filename)

var gcID zkidentity.ShortID
if len(args) == 2 {
gcName := args[1]
gcID, err = as.c.GCIDByName(gcName)
if err != nil {
return err
}
if _, err := as.c.GetGC(gcID); err != nil {
return err
}
}

go as.writeInvite(filename, gcID)
return nil
},
completer: func(args []string, arg string, as *appState) []string {
Expand Down
1 change: 1 addition & 0 deletions bruig/golib/command_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ func handleClientCmd(cc *clientCtx, cmd *cmd) (interface{}, error) {
switch cmd.Type {
case CTInvite:
b := &bytes.Buffer{}
// TODO - Set an "invite_gc" post kx action.
_, err := c.WriteNewInvite(b)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ func (c *Client) loadAddressBook(ctx context.Context) error {

for _, entry := range ab {
_, err := c.initRemoteUser(entry.ID, entry.R, false,
entry.MyResetRV, entry.TheirResetRV, entry.Ignored)
clientdb.RawRVID{}, entry.MyResetRV, entry.TheirResetRV, entry.Ignored)
if err != nil {
c.log.Errorf("Unable to init remote user %s: %v",
entry.ID.Identity, err)
Expand Down
40 changes: 36 additions & 4 deletions client/client_kx.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ func (c *Client) takePostKXAction(ru *RemoteUser, act clientdb.PostKXAction) err

return c.subscribeToPosts(ru.ID(), &pid, true)

case clientdb.PKXActionInviteGC:
// Invite user to GC
gcname := act.Data
gcID, err := c.GCIDByName(gcname)
if err != nil {
return err
}
if _, err := c.GetGC(gcID); err != nil {
return err
}

return c.InviteToGroupChat(gcID, ru.ID())
default:
return fmt.Errorf("unknown post-kx action type")
}
Expand Down Expand Up @@ -105,7 +117,7 @@ func (c *Client) takePostKXActions(ru *RemoteUser, actions []clientdb.PostKXActi

// initRemoteUser inserts the given ratchet as a new remote user.
func (c *Client) initRemoteUser(id *zkidentity.PublicIdentity, r *ratchet.Ratchet,
updateAB bool, myResetRV, theirResetRV clientdb.RawRVID, ignored bool) (*RemoteUser, error) {
updateAB bool, initialRV, myResetRV, theirResetRV clientdb.RawRVID, ignored bool) (*RemoteUser, error) {

var postKXActions []clientdb.PostKXAction

Expand Down Expand Up @@ -166,12 +178,19 @@ func (c *Client) initRemoteUser(id *zkidentity.PublicIdentity, r *ratchet.Ratche
}
}

// Convert initial actions to post actions.
if !initialRV.IsEmpty() {
err = c.db.InitialToPostKXActions(tx, initialRV, id.Identity)
if err != nil {
return err
}
}

// See if there are any actions to be taken after completing KX.
postKXActions, err = c.db.ListPostKXActions(tx, id.Identity)
if err != nil {
return err
}

// Remove KX search if it exists.
if _, err := c.db.GetKXSearch(tx, id.Identity); err == nil {
hadKXSearch = true
Expand Down Expand Up @@ -225,16 +244,29 @@ func (c *Client) initRemoteUser(id *zkidentity.PublicIdentity, r *ratchet.Ratche
}

func (c *Client) kxCompleted(public *zkidentity.PublicIdentity, r *ratchet.Ratchet,
myResetRV, theirResetRV clientdb.RawRVID) {
initialRV, myResetRV, theirResetRV clientdb.RawRVID) {

ru, err := c.initRemoteUser(public, r, true, myResetRV, theirResetRV, false)
ru, err := c.initRemoteUser(public, r, true, initialRV, myResetRV, theirResetRV, false)
if err != nil && !errors.Is(err, clientintf.ErrSubsysExiting) {
c.log.Errorf("unable to init user for completed kx: %v", err)
}

c.ntfns.notifyOnKXCompleted(ru)
}

func (c *Client) AddInviteOnKX(initialRV, gcID zkidentity.ShortID) error {
// TODO find gcName

action := clientdb.PostKXAction{
Type: clientdb.PKXActionInviteGC,
DateAdded: time.Now(),
Data: gcID.String(),
}
return c.dbUpdate(func(tx clientdb.ReadWriteTx) error {
return c.db.AddInitialKXAction(tx, initialRV, action)
})
}

// WriteNewInvite creates a new invite and writes it to the given writer.
func (c *Client) WriteNewInvite(w io.Writer) (rpc.OOBPublicIdentityInvite, error) {
return c.kxl.createInvite(w, nil, nil, false)
Expand Down
1 change: 1 addition & 0 deletions client/clientdb/fscdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
kxSearches = "kxsearches"
miRequestsDir = "mirequests"
postKXActionsDir = "postkxactions"
initKXActionsDir = "initkxactions"
payStatsFile = "paystats.json"
unackedRMsDir = "unackedrms"
lastConnDateFile = "lastconndate.json"
Expand Down
1 change: 1 addition & 0 deletions client/clientdb/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ type PostKXActionType string
const (
PKXActionKXSearch PostKXActionType = "kx_search"
PKXActionFetchPost PostKXActionType = "fetch_post"
PKXActionInviteGC PostKXActionType = "invite_gc"
)

type PostKXAction struct {
Expand Down
45 changes: 45 additions & 0 deletions client/clientdb/kx.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/companyzero/bisonrelay/rpc"
"github.com/companyzero/bisonrelay/zkidentity"
)

func (db *DB) SaveKX(tx ReadWriteTx, kx KXData) error {
Expand Down Expand Up @@ -260,6 +261,50 @@ func (db *DB) ListKXSearches(tx ReadTx) ([]UserID, error) {
return res, nil
}

// AddInitialKXAction adds an action to be taken after kx completes with the given
// initial rendezvous.
func (db *DB) AddInitialKXAction(tx ReadWriteTx, initialRV zkidentity.ShortID, action PostKXAction) error {
filename := filepath.Join(db.root, initKXActionsDir, initialRV.String())
var actions []PostKXAction
err := db.readJsonFile(filename, &actions)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
actions = append(actions, action)
return db.saveJsonFile(filename, actions)
}

// RemoveInitialKXActions removes the initial-kx actions registered for the given
// initial rendezvous.
func (db *DB) RemoveInitialKXActions(tx ReadWriteTx, initialRV zkidentity.ShortID) error {
filename := filepath.Join(db.root, initKXActionsDir, initialRV.String())
err := os.Remove(filename)
if os.IsNotExist(err) {
return nil
}
return err
}

// InitialToPostKXAction converts an action based on initial rendezvous to a known user id.
func (db *DB) InitialToPostKXActions(tx ReadWriteTx, initialRV, target zkidentity.ShortID) error {
filename := filepath.Join(db.root, initKXActionsDir, initialRV.String())
var actions []PostKXAction
err := db.readJsonFile(filename, &actions)
switch {
case errors.Is(err, ErrNotFound):
return nil
case err != nil:
return err
default:
}
for _, action := range actions {
if err = db.AddUniquePostKXAction(tx, target, action); err != nil {
return err
}
}
return db.RemoveInitialKXActions(tx, initialRV)
}

// AddPostKXAction adds an action to be taken after kx completes with the given
// target user.
func (db *DB) AddPostKXAction(tx ReadWriteTx, target UserID, action PostKXAction) error {
Expand Down
6 changes: 3 additions & 3 deletions client/kx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type kxList struct {
compressLevel int

kxCompleted func(*zkidentity.PublicIdentity, *ratchet.Ratchet,
clientdb.RawRVID, clientdb.RawRVID)
clientdb.RawRVID, clientdb.RawRVID, clientdb.RawRVID)

log slog.Logger
}
Expand Down Expand Up @@ -280,7 +280,7 @@ func (kx *kxList) handleStep2IDKX(kxid clientdb.RawRVID, blob lowlevel.RVBlob) e

// Alert client of completed kx.
if kx.kxCompleted != nil {
kx.kxCompleted(&rmohk.Public, r, kxd.MyResetRV, rmohk.ResetRendezvous)
kx.kxCompleted(&rmohk.Public, r, kxd.InitialRV, kxd.MyResetRV, rmohk.ResetRendezvous)
}
return nil
}
Expand Down Expand Up @@ -345,7 +345,7 @@ func (kx *kxList) handleStep3IDKX(kxid clientdb.RawRVID, blob lowlevel.RVBlob) e

// Alert client of completed kx.
if kx.kxCompleted != nil {
kx.kxCompleted(&public, r, kxd.MyResetRV, kxd.TheirResetRV)
kx.kxCompleted(&public, r, kxid, kxd.MyResetRV, kxd.TheirResetRV)
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions client/kx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ func TestKXSucceeds(t *testing.T) {

// Ensure we're tracking the success of kx.
aliceRChan, bobRChan := make(chan *ratchet.Ratchet), make(chan *ratchet.Ratchet)
alice.kxCompleted = func(id *zkidentity.PublicIdentity, r *ratchet.Ratchet, mrrv clientdb.RawRVID, trrv clientdb.RawRVID) {
alice.kxCompleted = func(id *zkidentity.PublicIdentity, r *ratchet.Ratchet, irrv, mrrv, trrv clientdb.RawRVID) {
aliceRChan <- r
}
bob.kxCompleted = func(id *zkidentity.PublicIdentity, r *ratchet.Ratchet, mrrv clientdb.RawRVID, trrv clientdb.RawRVID) {
bob.kxCompleted = func(id *zkidentity.PublicIdentity, r *ratchet.Ratchet, irrv, mrrv, trrv clientdb.RawRVID) {
bobRChan <- r
}

Expand Down
14 changes: 14 additions & 0 deletions internal/e2etests/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,28 @@ func (ts *testScaffold) recreateClient(tc *testClient) *testClient {
return ts.newClientWithOpts(tc.name, tc.rootDir, tc.id)
}

// kxUsers performs a kx between the two users with an additional gc invite.
// This MUST be called only from the main test goroutine.
func (ts *testScaffold) kxUsersWithInvite(inviter, invitee *testClient, gcID zkidentity.ShortID) {
ts.t.Helper()
invite, err := inviter.WriteNewInvite(io.Discard)
assert.NilErr(ts.t, err)
ts.t.Logf("%s alice:%s bob:%s\n", invite.InitialRendezvous.String(), inviter.id.Public.String(), invitee.id.Public.String())
assert.NilErr(ts.t, inviter.AddInviteOnKX(invite.InitialRendezvous, gcID))
assert.NilErr(ts.t, invitee.AcceptInvite(invite))
assertClientsKXd(ts.t, inviter, invitee)
}

// kxUsers performs a kx between the two users, so that they can communicate
// with each other. This MUST be called only from the main test goroutine.
func (ts *testScaffold) kxUsers(inviter, invitee *testClient) {
ts.t.Helper()
invite, err := inviter.WriteNewInvite(io.Discard)
assert.NilErr(ts.t, err)

assert.NilErr(ts.t, invitee.AcceptInvite(invite))
assertClientsKXd(ts.t, inviter, invitee)

}

func (ts *testScaffold) run() {
Expand Down
38 changes: 38 additions & 0 deletions internal/e2etests/gc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,44 @@ import (
"github.com/companyzero/bisonrelay/zkidentity"
)

// TestInviteGCOnKX performs tests for inviting new users to GC's
func TestInviteGCOnKX(t *testing.T) {
// Setup Alice and Bob.
tcfg := testScaffoldCfg{}
ts := newTestScaffold(t, tcfg)
alice := ts.newClient("alice")
bob := ts.newClient("bob")

aliceAcceptedInvitesChan := make(chan clientintf.UserID, 1)
alice.handle(client.OnGCInviteAcceptedNtfn(func(user *client.RemoteUser, _ rpc.RMGroupList) {
t.Logf("alice accepted")
aliceAcceptedInvitesChan <- user.ID()
}))
bobJoinedGCChan := make(chan struct{}, 1)
bob.handle(client.OnJoinedGCNtfn(func(gc rpc.RMGroupList) {
t.Logf("bob joined")
bobJoinedGCChan <- struct{}{}
}))

// Alice creates a GC.
gcName := "testGC"
gcID, err := alice.NewGroupChat(gcName)
assert.NilErr(t, err)

// Setup Bob to accept Alice's invite and send the invite.
bobAcceptedChan := bob.acceptNextGCInvite(gcID)

ts.kxUsersWithInvite(alice, bob, gcID)

// Bob correctly accepted to join and Alice was notified Bob joined.
assert.NilErrFromChan(t, bobAcceptedChan)
assert.ChanWrittenWithVal(t, aliceAcceptedInvitesChan, bob.PublicID())
assert.ChanWritten(t, bobJoinedGCChan)

// Double check bob is in the GC.
assertClientInGC(t, bob, gcID)
}

// TestBasicGCFeatures performs tests for the basic GC features.
func TestBasicGCFeatures(t *testing.T) {
// Setup Alice and Bob.
Expand Down

0 comments on commit 8b9b903

Please sign in to comment.