package client import ( "net/netip" "sync" "time" "github.com/cybriq/qu" "github.com/davecgh/go-spew/spew" "github.com/indra-labs/indra" "github.com/indra-labs/indra/pkg/ifc" "github.com/indra-labs/indra/pkg/key/cloak" "github.com/indra-labs/indra/pkg/key/prv" "github.com/indra-labs/indra/pkg/key/pub" "github.com/indra-labs/indra/pkg/key/signer" log2 "github.com/indra-labs/indra/pkg/log" "github.com/indra-labs/indra/pkg/node" "github.com/indra-labs/indra/pkg/nonce" "github.com/indra-labs/indra/pkg/slice" "github.com/indra-labs/indra/pkg/wire/confirm" "github.com/indra-labs/indra/pkg/wire/layer" "github.com/indra-labs/indra/pkg/wire/response" "github.com/indra-labs/indra/pkg/wire/reverse" "go.uber.org/atomic" ) const ( ReverseLayerLen = reverse.Len + layer.Len ReverseHeaderLen = 3 * ReverseLayerLen ) var ( log = log2.GetLogger(indra.PathBase) check = log.E.Chk ) type Client struct { *node.Node node.Nodes PendingSessions []nonce.ID *confirm.Confirms ExitHooks response.Hooks sync.Mutex *signer.KeySet ShuttingDown atomic.Bool qu.C } func New(tpt ifc.Transport, hdrPrv *prv.Key, no *node.Node, nodes node.Nodes) (c *Client, e error) { no.Transport = tpt no.IdentityPrv = hdrPrv no.IdentityPub = pub.Derive(hdrPrv) var ks *signer.KeySet if _, ks, e = signer.New(); check(e) { return } c = &Client{ Confirms: confirm.NewConfirms(), Node: no, Nodes: nodes, KeySet: ks, C: qu.T(), } return } // Start a single thread of the Client. func (cl *Client) Start() { for { if cl.runner() { break } } } func (cl *Client) RegisterConfirmation(hook confirm.Hook, cnf nonce.ID) { cl.Confirms.Add(&confirm.Callback{ ID: cnf, Time: time.Now(), Hook: hook, }) } // FindCloaked searches the client identity key and the Sessions for a match. It // returns the session as well, though not all users of this function will need // this. func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, pld *prv.Key, sess *node.Session) { var b cloak.Blinder copy(b[:], clk[:cloak.BlindLen]) hash := cloak.Cloak(b, cl.Node.IdentityBytes) if hash == clk { log.T.Ln("encrypted to identity key") hdr = cl.Node.IdentityPrv // there is no payload key for the node, only in sessions. return } for i := range cl.Sessions { hash = cloak.Cloak(b, cl.Sessions[i].HeaderBytes) if hash == clk { log.T.F("found cloaked key in session %d", i) hdr = cl.Sessions[i].HeaderPrv pld = cl.Sessions[i].PayloadPrv sess = cl.Sessions[i] return } } return } // Cleanup closes and flushes any resources the client opened that require sync // in order to reopen correctly. func (cl *Client) Cleanup() { // Do cleanup stuff before shutdown. } // Shutdown triggers the shutdown of the client and the Cleanup before // finishing. func (cl *Client) Shutdown() { if cl.ShuttingDown.Load() { return } log.T.C(func() string { return "shutting down client " + cl.Node.AddrPort.String() }) cl.ShuttingDown.Store(true) cl.C.Q() } // Send a message to a peer via their AddrPort. func (cl *Client) Send(addr *netip.AddrPort, b slice.Bytes) { // first search if we already have the node available with connection // open. as := addr.String() for i := range cl.Nodes { if as == cl.Nodes[i].Addr { log.T.C(func() string { return cl.AddrPort.String() + " sending to " + addr.String() + "\n" + spew.Sdump(b.ToBytes()) }) cl.Nodes[i].Transport.Send(b) return } } // If we got to here none of the addresses matched, and we need to // establish a new peer connection to them, if we know of them (this // would usually be the reason this happens). }