Merge branch 'main' into ind-bootstrap

# Conflicts:
#	version.go
This commit is contained in:
Colin Lyons
2023-01-14 23:27:35 +00:00
66 changed files with 638 additions and 1487 deletions

View File

@@ -1,4 +1,7 @@
// Package ciph manages encryption ciphers and encrypting blobs of data.
// Package ciph manages encryption ciphers and encrypting blobs of data. Keys
// are generated using ECDH from a public and private secp256k1 combined, as
// well as directly from a 32 byte secret in the form of a static array as used
// in most cryptographic hash function implementations in Go.
package ciph
import (
@@ -27,8 +30,9 @@ func GetBlock(from *prv.Key, to *pub.Key) (block cipher.Block) {
return
}
// BlockFromHash creates an AES block cipher from an sha256.Hash
// BlockFromHash creates an AES block cipher from an sha256.Hash.
func BlockFromHash(h sha256.Hash) (block cipher.Block) {
// We can ignore the error because sha256.Hash is a valid key size.
block, _ = aes.NewCipher(h[:])
return
}

View File

@@ -1 +0,0 @@
package ciph

View File

@@ -1,6 +1,8 @@
package client
import (
"fmt"
"math"
"net/netip"
"sync"
"time"
@@ -17,7 +19,6 @@ import (
"github.com/indra-labs/indra/pkg/nonce"
"github.com/indra-labs/indra/pkg/session"
"github.com/indra-labs/indra/pkg/slice"
"github.com/indra-labs/indra/pkg/wire"
"github.com/indra-labs/indra/pkg/wire/confirm"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/response"
@@ -39,8 +40,6 @@ var (
type Client struct {
*node.Node
node.Nodes
// *address.SendCache
// *address.ReceiveCache
session.Sessions
PendingSessions []nonce.ID
*confirm.Confirms
@@ -55,8 +54,8 @@ func New(tpt ifc.Transport, hdrPrv *prv.Key, no *node.Node,
nodes node.Nodes) (c *Client, e error) {
no.Transport = tpt
no.HeaderPrv = hdrPrv
no.HeaderPub = pub.Derive(hdrPrv)
no.IdentityPrv = hdrPrv
no.IdentityPub = pub.Derive(hdrPrv)
var ks *signer.KeySet
if _, ks, e = signer.New(); check(e) {
return
@@ -65,20 +64,21 @@ func New(tpt ifc.Transport, hdrPrv *prv.Key, no *node.Node,
Confirms: confirm.NewConfirms(),
Node: no,
Nodes: nodes,
// ReceiveCache: address.NewReceiveCache(),
KeySet: ks,
C: qu.T(),
KeySet: ks,
C: qu.T(),
}
// c.ReceiveCache.Add(address.NewReceiver(hdrPrv))
// A new client requires a Session for receiving responses. This session
// should have its keys changed periodically, or at least once on
// startup.
c.Sessions = c.Sessions.Add(session.New(no.ID, no, math.MaxUint64, 0, 0))
return
}
// Start a single thread of the Client.
func (cl *Client) Start() {
out:
for {
if cl.runner() {
break out
break
}
}
}
@@ -93,13 +93,17 @@ func (cl *Client) RegisterConfirmation(hook confirm.Hook,
})
}
// FindCloaked searches the client identity key and the Sessions for a match.
func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, pld *prv.Key) {
// 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 *session.Session) {
var b cloak.Blinder
copy(b[:], clk[:cloak.BlindLen])
hash := cloak.Cloak(b, cl.Node.HeaderBytes)
hash := cloak.Cloak(b, cl.Node.IdentityBytes)
if hash == clk {
hdr = cl.Node.HeaderPrv
hdr = cl.Node.IdentityPrv
// there is no payload key for the node, only in sessions.
return
}
@@ -108,44 +112,82 @@ func (cl *Client) FindCloaked(clk cloak.PubKey) (hdr *prv.Key, pld *prv.Key) {
if hash == clk {
hdr = cl.Sessions[i].HeaderPrv
pld = cl.Sessions[i].PayloadPrv
sess = cl.Sessions[i]
return
}
}
return
}
func (cl *Client) SendKeys(nodeID nonce.ID,
hook func(cf nonce.ID)) (confirmation nonce.ID, hdr, pld *prv.Key,
// SendKeys is the delivery of a ...
//
// todo: this function should be requiring input of keys, and a related prior
//
// function that generates the preimage for an LN AMP, from the hash of the
// keys.
func (cl *Client) SendKeys(nodeID []nonce.ID, hook confirm.Hook) (conf nonce.ID,
e error) {
// var hdrPub, pldPub *pub.Key
if hdr, e = prv.GenerateKey(); check(e) {
return
var n []*node.Node
for i := range nodeID {
no := cl.Nodes.FindByID(nodeID[i])
if no != nil {
n = append(n, no)
}
}
// hdrPub = pub.Derive(hdr)
if pld, e = prv.GenerateKey(); check(e) {
return
if len(n) > 5 {
e = fmt.Errorf("SendKeys maximum 5 keys %d given", len(nodeID))
}
ln := len(n)
hdrPrv, pldPrv := make([]*prv.Key, ln), make([]*prv.Key, ln)
hdrPub, pldPub := make([]*pub.Key, ln), make([]*pub.Key, ln)
for i := range n {
if hdrPrv[i], e = prv.GenerateKey(); check(e) {
return
}
hdrPub[i] = pub.Derive(hdrPrv[i])
if pldPrv[i], e = prv.GenerateKey(); check(e) {
return
}
pldPub[i] = pub.Derive(pldPrv[i])
}
// pldPub = pub.Derive(pld)
n := cl.Nodes.FindByID(nodeID)
selected := cl.Nodes.Select(SimpleSelector, n, 4)
var hop [5]*node.Node
hop[0], hop[1], hop[2], hop[3], hop[4] =
selected[0], selected[1], selected[2], selected[3], cl.Node
confirmation = nonce.NewID()
os := wire.SendKeys(confirmation, hdr, pld, cl.Node, hop, cl.KeySet)
cl.RegisterConfirmation(hook, os[len(os)-1].(*confirm.OnionSkin).ID)
o := os.Assemble()
b := wire.EncodeOnion(o)
cl.Send(hop[0].AddrPort, b)
// selected := cl.Nodes.Select(SimpleSelector, n, 4)
// if len(selected) < 4 {
// e = fmt.Errorf("not enough nodes known to form circuit")
// return
// }
// hop := [5]*node.Node{
// selected[0], selected[1], n, selected[2], selected[3],
// }
// conf = nonce.NewID()
// os := wire.SendKeys(conf, hdrPrv, pldPrv, cl.Node, hop, cl.KeySet)
// cl.RegisterConfirmation(hook, os[len(os)-1].(*confirm.OnionSkin).ID)
// cl.Sessions.Add(&session.Session{
// ID: n.ID,
// Remaining: 1 << 16,
// IdentityPub: hdrPub,
// IdentityBytes: hdrPub.ToBytes(),
// PayloadPub: pldPub,
// PayloadBytes: pldPub.ToBytes(),
// IdentityPrv: hdrPrv,
// PayloadPrv: pldPrv,
// Deadline: time.Now().Add(DefaultDeadline),
// })
// o := os.Assemble()
// b := wire.EncodeOnion(o)
// cl.Send(hop[0].AddrPort, b)
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.Bool.Load() {
return
@@ -155,6 +197,7 @@ func (cl *Client) Shutdown() {
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.

View File

@@ -18,7 +18,7 @@ import (
)
func TestPing(t *testing.T) {
const nTotal = 4
const nTotal = 6
clients := make([]*Client, nTotal)
var e error
if clients, e = CreateMockCircuitClients(nTotal); check(e) {
@@ -29,17 +29,18 @@ func TestPing(t *testing.T) {
for _, v := range clients {
go v.Start()
}
pn := nonce.NewID()
conf := nonce.NewID()
var ks *signer.KeySet
if _, ks, e = signer.New(); check(e) {
t.Error(e)
t.FailNow()
}
var hop [nTotal - 1]*node.Node
for i := range clients[0].Nodes {
hop[i] = clients[0].Nodes[i]
var sessions session.Sessions
for _, v := range clients[1:] {
sessions = append(sessions, v.Sessions[0])
}
os := wire.Ping(pn, clients[0].Node, hop, ks)
sessions = append(sessions, clients[0].Sessions[0])
os := wire.Ping(conf, sessions, ks)
quit := qu.T()
log.I.S("sending ping with ID", os[len(os)-1].(*confirm.OnionSkin))
clients[0].RegisterConfirmation(func(cf nonce.ID) {
@@ -48,85 +49,44 @@ func TestPing(t *testing.T) {
}, os[len(os)-1].(*confirm.OnionSkin).ID)
o := os.Assemble()
b := wire.EncodeOnion(o)
hop[0].Send(b)
// go func() {
// time.Sleep(time.Second)
// quit.Q()
// t.Error("ping got stuck")
// }()
<-quit.Wait()
for _, v := range clients {
v.Shutdown()
}
}
func TestSendKeys(t *testing.T) {
const nTotal = 6
clients := make([]*Client, nTotal)
var e error
if clients, e = CreateMockCircuitClients(nTotal); check(e) {
t.Error(e)
t.FailNow()
}
// Start up the clients.
for _, v := range clients {
go v.Start()
}
quit := qu.T()
clients[0].SendKeys(clients[0].Nodes[0].ID, func(cf nonce.ID) {
log.I.S("received sendkeys confirmation ID", cf)
clients[0].Send(clients[1].AddrPort, b)
go func() {
select {
case <-time.After(time.Second):
t.Error("ping got stuck")
case <-quit:
}
time.Sleep(time.Second)
quit.Q()
})
}()
<-quit.Wait()
for _, v := range clients {
v.Shutdown()
}
}
func TestSendPurchase(t *testing.T) {
const nTotal = 6
clients := make([]*Client, nTotal)
var e error
if clients, e = CreateMockCircuitClients(nTotal); check(e) {
t.Error(e)
t.FailNow()
}
var ks *signer.KeySet
if _, ks, e = signer.New(); check(e) {
t.Error(e)
t.FailNow()
}
var sess [3]*session.Session
for i := range sess {
sess[i] = session.NewSession(nonce.NewID(), 203230230, time.Hour)
}
// clients[4].ReceiveCache.Add(address.NewReceiver(sess[0].HeaderPrv))
// clients[5].ReceiveCache.Add(address.NewReceiver(sess[1].HeaderPrv))
// clients[0].ReceiveCache.Add(address.NewReceiver(sess[2].HeaderPrv))
clients[4].Sessions = clients[4].Sessions.Add(sess[0])
clients[5].Sessions = clients[5].Sessions.Add(sess[1])
clients[0].Sessions = clients[0].Sessions.Add(sess[2])
// Start up the clients.
for _, v := range clients {
go v.Start()
}
var hop [nTotal - 1]*node.Node
for i := range clients[0].Nodes {
hop[i] = clients[0].Nodes[i]
}
const nBytes = 2342342
id := nonce.NewID()
os := wire.SendPurchase(id, nBytes, clients[0].Node, hop, sess, ks)
clients[0].PendingSessions = append(clients[0].PendingSessions, id)
o := os.Assemble()
b := wire.EncodeOnion(o)
hop[0].Send(b)
<-clients[0].Wait()
for _, v := range clients {
v.Shutdown()
}
}
// func TestSendKeys(t *testing.T) {
// const nTotal = 6
// clients := make([]*Client, nTotal)
// var e error
// if clients, e = CreateMockCircuitClients(nTotal); check(e) {
// t.Error(e)
// t.FailNow()
// }
// // Start up the clients.
// for _, v := range clients {
// go v.Start()
// }
// quit := qu.T()
// clients[0].SendKeys(clients[0].Nodes[0].ID, func(cf nonce.ID) {
// log.I.S("received sendkeys confirmation ID", cf)
// quit.Q()
// })
// <-quit.Wait()
// for _, v := range clients {
// v.Shutdown()
// }
// }
func TestSendExit(t *testing.T) {
const nTotal = 6
@@ -142,12 +102,9 @@ func TestSendExit(t *testing.T) {
t.FailNow()
}
var sess [3]*session.Session
for i := range sess {
sess[i] = session.NewSession(nonce.NewID(), 203230230, time.Hour)
}
// clients[4].ReceiveCache.Add(address.NewReceiver(sess[0].HeaderPrv))
// clients[5].ReceiveCache.Add(address.NewReceiver(sess[1].HeaderPrv))
// clients[0].ReceiveCache.Add(address.NewReceiver(sess[2].HeaderPrv))
sess[0] = clients[4].Sessions.Find(clients[4].ID)
sess[1] = clients[5].Sessions.Find(clients[5].ID)
sess[2] = clients[0].Sessions.Find(clients[0].ID)
clients[4].Sessions = clients[4].Sessions.Add(sess[0])
clients[5].Sessions = clients[5].Sessions.Add(sess[1])
clients[0].Sessions = clients[0].Sessions.Add(sess[2])

View File

@@ -1,11 +1,7 @@
package client
import (
"sync"
"testing"
"github.com/indra-labs/indra/pkg/key/prv"
"github.com/indra-labs/indra/pkg/nonce"
)
func TestPurchaseFlow(t *testing.T) {
@@ -26,23 +22,22 @@ func TestPurchaseFlow(t *testing.T) {
// quit.Q()
// }()
// <-quit.Wait()
selected := clients[0].Nodes.Select(SimpleSelector, clients[1].Node, 4)
// next to send out keys for the return hops
returnNodes := selected[2:]
var rtnHdr, rtnPld [2]*prv.Key
var confirmation [2]nonce.ID
var wait sync.WaitGroup
for i := range returnNodes {
wait.Add(1)
confirmation[i], rtnHdr[i], rtnPld[i], e = clients[0].
SendKeys(returnNodes[i].ID, func(cf nonce.ID) {
log.I.S("confirmed", cf)
wait.Done()
})
}
log.I.S(confirmation)
wait.Wait()
// now to do the purchase
// selected := clients[0].Nodes.Select(SimpleSelector, clients[1].Node, 4)
// // next to send out keys for the return hops
// returnNodes := selected[2:]
// var confirmation [2]nonce.ID
// var wait sync.WaitGroup
// for i := range returnNodes {
// wait.Add(1)
// confirmation[i], e = clients[0].
// SendKeys(returnNodes[i].ID, func(cf nonce.ID) {
// log.I.S("confirmed", cf)
// wait.Done()
// })
// }
// log.I.S(confirmation)
// wait.Wait()
// // now to do the purchase
for _, v := range clients {
v.Shutdown()

View File

@@ -4,7 +4,6 @@ import (
"time"
"github.com/indra-labs/indra/pkg/ciph"
session2 "github.com/indra-labs/indra/pkg/session"
"github.com/indra-labs/indra/pkg/sha256"
"github.com/indra-labs/indra/pkg/slice"
"github.com/indra-labs/indra/pkg/types"
@@ -16,7 +15,6 @@ import (
"github.com/indra-labs/indra/pkg/wire/forward"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/noop"
"github.com/indra-labs/indra/pkg/wire/purchase"
"github.com/indra-labs/indra/pkg/wire/response"
"github.com/indra-labs/indra/pkg/wire/reverse"
"github.com/indra-labs/indra/pkg/wire/session"
@@ -52,8 +50,6 @@ func (cl *Client) runner() (out bool) {
cl.layer(on, b, c)
case *noop.OnionSkin:
cl.noop(on, b, c)
case *purchase.OnionSkin:
cl.purchase(on, b, c)
case *reverse.OnionSkin:
cl.reverse(on, b, c)
case *response.OnionSkin:
@@ -139,7 +135,7 @@ func (cl *Client) forward(on *forward.OnionSkin, b slice.Bytes,
func (cl *Client) layer(on *layer.OnionSkin, b slice.Bytes, c *slice.Cursor) {
// this is probably an encrypted layer for us.
hdr, _ := cl.FindCloaked(on.Cloak)
hdr, _, _ := cl.FindCloaked(on.Cloak)
if hdr == nil {
log.I.Ln("no matching key found from cloaked key")
return
@@ -153,31 +149,6 @@ func (cl *Client) noop(on *noop.OnionSkin, b slice.Bytes, c *slice.Cursor) {
// this won't happen normally
}
func (cl *Client) purchase(on *purchase.OnionSkin, b slice.Bytes, c *slice.Cursor) {
// Create a new Session.
s := session2.NewSession(on.ID, on.NBytes, DefaultDeadline)
se := &session.OnionSkin{
ID: s.ID,
HeaderKey: s.HeaderPub,
PayloadKey: s.PayloadPub,
Onion: &noop.OnionSkin{},
}
cl.Mutex.Lock()
cl.Sessions.Add(s)
cl.Mutex.Unlock()
header := b[*c:c.Inc(ReverseHeaderLen)]
rb := make(slice.Bytes, ReverseHeaderLen+session.Len)
cur := slice.NewCursor()
copy(rb[*cur:cur.Inc(ReverseHeaderLen)], header[:ReverseHeaderLen])
start := *cur
se.Encode(rb, cur)
for i := range on.Ciphers {
blk := ciph.BlockFromHash(on.Ciphers[i])
ciph.Encipher(blk, on.Nonces[2-i], rb[start:])
}
cl.Node.Send(rb)
}
func (cl *Client) reverse(on *reverse.OnionSkin, b slice.Bytes,
c *slice.Cursor) {
@@ -193,9 +164,8 @@ func (cl *Client) reverse(on *reverse.OnionSkin, b slice.Bytes,
first := *c
second := first + ReverseLayerLen
last := second + ReverseLayerLen
hdr, pld := cl.FindCloaked(on1.Cloak)
hdr, pld, _ := cl.FindCloaked(on1.Cloak)
// We need to find the PayloadPub to match.
// ses := cl.Sessions.FindPub(hdr.Pub)
hdrPrv := hdr
hdrPub := on1.FromPub
blk := ciph.GetBlock(hdrPrv, hdrPub)
@@ -230,7 +200,9 @@ func (cl *Client) response(on *response.OnionSkin, b slice.Bytes, cur *slice.Cur
cl.ExitHooks.Find(on.Hash)
}
func (cl *Client) session(s *session.OnionSkin, b slice.Bytes, cur *slice.Cursor) {
func (cl *Client) session(s *session.OnionSkin, b slice.Bytes,
cur *slice.Cursor) {
// Session is returned from a Purchase message in the reverse layers.
//
// Session has a nonce.ID that is given in the last layer of a LN sphinx

View File

@@ -1,38 +0,0 @@
package client
import (
"math/rand"
"github.com/indra-labs/indra/pkg/node"
"github.com/indra-labs/indra/pkg/slice"
)
// SimpleSelector is a pure random shuffle selection algorithm for producing
// a list of nodes for an onion.
//
// This function will return nil if there isn't enough
func SimpleSelector(n node.Nodes, exit *node.Node,
count int) (selected node.Nodes) {
// For the purposes of this simple selector algorithm we require unique
// nodes for each hop.
if len(n) < count+1 {
log.E.F("not enough nodes, have %d, need %d", len(n), count+1)
return
}
// Remove the exit from the list of options.
var nCandidates node.Nodes
if exit != nil {
for i := range n {
if n[i].ID != exit.ID {
nCandidates = append(nCandidates, n[i])
}
}
}
// Shuffle the list we made
rand.Seed(slice.GetCryptoRandSeed())
rand.Shuffle(len(nCandidates), func(i, j int) {
nCandidates[i], nCandidates[j] = nCandidates[j], nCandidates[i]
})
return nCandidates[:count]
}

View File

@@ -31,7 +31,7 @@ func CreateMockCircuitClients(nTotal int) (clients []*Client, e error) {
clients[i].AddrPort = nodes[i].AddrPort
}
// add each node to each other's Nodes except itself.
for i := range nodes {
for i := range clients {
for j := range nodes {
if i == j {
continue
@@ -39,5 +39,6 @@ func CreateMockCircuitClients(nTotal int) (clients []*Client, e error) {
clients[i].Nodes = append(clients[i].Nodes, nodes[j])
}
}
return
}

View File

@@ -16,28 +16,26 @@ var (
)
// ParseCLIArgs reads a command line argument slice (presumably from os.Args),
// identifies the command to run and
// identifies the command to run and sets options provided.
//
// Rules for constructing CLI args:
//
// - Commands are identified by name, and must appear in their hierarchic
// order to invoke subcommands. They are matched as normalised to lower
// case.
// Commands are identified by name, and must appear in their hierarchic order to
// invoke subcommands. They are matched as normalised to lower case.
//
// - Options can be preceded by "--" or "-", and the full name, or the
// alias, normalised to lower case for matching, and if there is an "=" after
// it, the value is after this, otherwise, the next element in the args is the
// value, except booleans, which default to true unless set to false. For these,
// the prefix "no" or similar indicates the semantics of the true value.
// Options can be preceded by "--" or "-", and the full name, or the alias,
// normalised to lower case for matching, and if there is an "=" after it, the
// value is after this, otherwise, the next element in the args is the value,
// except booleans, which default to true unless set to false. For these, the
// prefix "no" or similar indicates the semantics of the true value.
//
// - Options only match when preceded by their relevant Command, except for
// the root Command, and these options must precede any other command
// options.
// Options only match when preceded by their relevant Command, except for the
// root Command, and these options must precede any other Command.
//
// - If no command is selected, the root Command.Default is selected. This
// can optionally be used for subcommands as well, though it is unlikely
// needed, if found, the Default of the tip of the Command branch selected
// by the CLI if there is one, otherwise the Command itself.
// If no command is selected, the root Command.Default is selected. This can
// optionally be used for subcommands as well, though it is unlikely needed, if
// found, the Default of the tip of the Command branch selected by the CLI if
// there is one, otherwise the Command itself.
func (c *Command) ParseCLIArgs(a []string) (run *Command, runArgs []string,
e error) {

View File

@@ -187,7 +187,7 @@ func Init(c *Command, p path.Path) (cmd *Command, err error) {
if p == nil {
p = path.Path{c.Name}
}
c.Path = p // .Parent()
c.Path = p
for i := range c.Configs {
c.Configs[i].SetPath(p)
}
@@ -208,7 +208,7 @@ func Init(c *Command, p path.Path) (cmd *Command, err error) {
return c, err
}
// GetOpt returns the option at a requested path
// GetOpt returns the option at a requested path.
func (c *Command) GetOpt(path path.Path) (o config.Option) {
p := make([]string, len(path))
for i := range path {
@@ -239,24 +239,6 @@ func (c *Command) GetOpt(path path.Path) (o config.Option) {
return nil
}
func (c *Command) GetCommand(p string) (o *Command) {
pp := strings.Split(p, " ")
path := path.Path(pp)
// log.I.F("%v == %v", path, c.Path)
if path.Equal(c.Path) {
// log.I.Ln("found", c.Path)
return c
}
for i := range c.Commands {
// log.I.Ln(c.Commands[i].Path)
o = c.Commands[i].GetCommand(p)
if o != nil {
return
}
}
return
}
func (c *Command) GetValue(key string) config.Concrete {
return c.Configs[key].Value()
}
@@ -270,7 +252,7 @@ func (c *Command) GetTextValue(key string) string {
}
// ForEach runs a closure on every node in the Commands tree, stopping if the
// closure returns false
// closure returns false.
func (c *Command) ForEach(cl func(*Command, int) bool, hereDepth,
hereDist int, cmd *Command) (ocl func(*Command, int) bool, depth,
dist int, cm *Command) {

View File

@@ -6,13 +6,11 @@ import (
"strings"
"testing"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/path"
)
func TestCommand_Foreach(t *testing.T) {
log2.SetLogLevel(log2.Info)
cm, _ := Init(GetExampleCommands(), nil)
log.I.Ln("spewing only droptxindex")
cm.ForEach(func(cmd *Command, _ int) bool {
@@ -23,7 +21,6 @@ func TestCommand_Foreach(t *testing.T) {
}, 0, 0, cm)
log.I.Ln("printing name of all commands found on search")
cm.ForEach(func(cmd *Command, depth int) bool {
log.I.Ln()
log.I.F("%s%s #(%d)", path.GetIndent(depth), cmd.Path, depth)
for i := range cmd.Configs {
log.I.F("%s%s -%s %v #%v (%d)", path.GetIndent(depth),
@@ -34,10 +31,7 @@ func TestCommand_Foreach(t *testing.T) {
}
func TestCommand_MarshalText(t *testing.T) {
log2.SetLogLevel(log2.Info)
o, _ := Init(GetExampleCommands(), nil)
// log.I.S(o)
conf, err := o.MarshalText()
if log.E.Chk(err) {
t.FailNow()
@@ -46,7 +40,6 @@ func TestCommand_MarshalText(t *testing.T) {
}
func TestCommand_UnmarshalText(t *testing.T) {
log2.SetLogLevel(log2.Info)
o, _ := Init(GetExampleCommands(), nil)
var conf []byte
var err error
@@ -61,7 +54,6 @@ func TestCommand_UnmarshalText(t *testing.T) {
}
func TestCommand_GetEnvs(t *testing.T) {
log2.SetLogLevel(log2.Info)
o, _ := Init(GetExampleCommands(), nil)
envs := o.GetEnvs()
var out []string
@@ -80,7 +72,6 @@ func TestCommand_GetEnvs(t *testing.T) {
var testSeparator = fmt.Sprintf("%s\n", strings.Repeat("-", 72))
func TestCommand_Help(t *testing.T) {
log2.SetLogLevel(log2.Debug)
ex := GetExampleCommands()
ex.AddCommand(Help())
o, _ := Init(ex, nil)
@@ -207,7 +198,6 @@ func TestCommand_Help(t *testing.T) {
}
func TestCommand_LogToFile(t *testing.T) {
log2.SetLogLevel(log2.Trace)
ex := GetExampleCommands()
ex.AddCommand(Help())
ex, _ = Init(ex, nil)

View File

@@ -16,6 +16,8 @@ type Env struct {
type Envs []Env
// ForEach runs a closure on each element of an Env item in an Envs and halts if
// one returns an error.
func (e Envs) ForEach(fn func(env string, opt config.Option) (err error)) (err error) {
for i := range e {
var name []string
@@ -30,6 +32,8 @@ func (e Envs) ForEach(fn func(env string, opt config.Option) (err error)) (err e
return
}
// LoadFromEnvironment walks the Envs and reads the values found in the
// environment variables.
func (e Envs) LoadFromEnvironment() (err error) {
err = e.ForEach(func(env string, opt config.Option) (err error) {
v, exists := os.LookupEnv(env)
@@ -44,10 +48,12 @@ func (e Envs) LoadFromEnvironment() (err error) {
return
}
func (e Envs) Len() int {
return len(e)
}
// Len returns the number items in an Envs. This is a sort.Sorter interface
// implementation.
func (e Envs) Len() int { return len(e) }
// Less compares two items and returns whether the first is lesser than the
// second. This is a sort.Sorter interface implementation.
func (e Envs) Less(i, j int) (res bool) {
li, lj := len(e[i].Name), len(e[j].Name)
if li < lj {
@@ -68,9 +74,9 @@ func (e Envs) Less(i, j int) (res bool) {
return
}
func (e Envs) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
// Swap switches the position of two elements of an Envs. This is a sort.Sorter
// interface implementation.
func (e Envs) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
// GetEnvs walks a Command tree and returns a slice containing all environment
// variable names and the related config.Option.

View File

@@ -25,8 +25,8 @@ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.`
// GetExampleCommands returns available subcommands in hypothetical Parallelcoin
// Pod example for testing (derived from btcd and btcwallet plus
// parallelcoin kopach miner)
// Pod example for testing (derived from btcd and btcwallet plus parallelcoin
// GUI and kopach miner).
func GetExampleCommands() (c *Command) {
c = &Command{
Name: "pod123",

View File

@@ -13,23 +13,27 @@ import (
"github.com/indra-labs/indra/pkg/util"
)
// Help is a default top level command that subsequent
// Help is a default top level command that is injected into a Command at the
// top level to provide an interface to finding the documentation embedded in
// the options and commands definitions.
func Help() (h *Command) {
h = &Command{
Path: nil,
Name: "help",
Description: "Print help information, optionally multiple keywords" +
" can be given and will be searched to generate output",
Documentation: "Uses partial matching, if result is ambiguous, prints general top " +
Description: "Print help information, optionally multiple " +
"keywords can be given and will be searched to " +
"generate output",
Documentation: "Uses partial matching, if result is " +
"ambiguous, prints general top " +
"level\nhelp and the list of partial match terms.\n\n" +
"If single term exactly matches a command, the command help will" +
" be printed.\n\n" +
"If single term exactly matches a command, the command " +
"help will be printed.\n\n" +
"Otherwise, a list showing all items, their paths, and " +
"description are shown.\n\n" +
"Use their full path and the full " +
"documentation for the item will be shown.\n\n" +
"Note that in all cases, options are only recognised after their\n" +
"related subcommand.",
"Note that in all cases, options are only recognised " +
"after their\nrelated subcommand.",
Entrypoint: HelpEntrypoint,
Parent: nil,
Commands: nil,
@@ -40,8 +44,8 @@ func Help() (h *Command) {
return
}
// IndentTextBlock adds an arbitrary number of tabs to the front of a text
// block that is presumed to already be flowed to ~80 columns.
// IndentTextBlock adds an arbitrary number of tabs to the front of a text block
// that is presumed to already be flowed to ~80 columns.
func IndentTextBlock(s string, tabs int) (o string) {
s = strings.TrimSpace(s)
split := strings.Split(s, strings.Repeat("\n", tabs))
@@ -57,54 +61,64 @@ type CommandInfo struct {
type CommandInfos []*CommandInfo
// Sorter for CommandInfos.
func (c CommandInfos) Len() int { return len(c) }
func (c CommandInfos) Less(i, j int) bool { return c[i].name < c[j].name }
func (c CommandInfos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func HelpEntrypoint(c *Command, args []string) (err error) {
if args == nil {
// no args given, just print top level general help
// no args given, just print top level general help.
return
}
foundCommands := &[]*Command{}
foundCmds := &[]*Command{}
fops := make(map[string]config.Option)
foundOptions := &fops
foundCommandWhole := false
foundOptionWhole := false
foundOpts := &fops
var foundCmdWhole, foundOptWhole bool
c.ForEach(func(cm *Command, depth int) bool {
for i := range args {
// check for match of current command name
if strings.Contains(util.Norm(cm.Name), util.Norm(args[i])) {
if util.Norm(cm.Name) == util.Norm(args[i]) {
if len(args) == 1 {
foundCommandWhole = true
*foundCommands = append(*foundCommands, cm)
break
}
if util.Norm(cm.Name) == util.Norm(args[i]) {
if len(args) == 1 {
foundCmdWhole = true
*foundCmds = append(*foundCmds, cm)
break
}
*foundCommands = append(*foundCommands, cm)
}
// or partial match.
if strings.Contains(util.Norm(cm.Name),
util.Norm(args[i])) {
*foundCmds = append(*foundCmds, cm)
}
// check for matches on configs
for ops := range cm.Configs {
// log.I.Ln(ops, cm.Name, Norm(ops), Norm(args[i]))
if strings.Contains(util.Norm(ops), util.Norm(args[i])) {
// in the case of specifying a command and an option
// and the option is from the command, and there is
// only two args, and the option is fully named, not
// just partial matched, clear found options and
// break to return one command one option,
// which later is recognised to indicate show detail
if len(args) == 2 && len(*foundCommands) == 1 &&
util.Norm(ops) == util.Norm(args[i]) {
if cm.Configs[ops].Path().Equal(cm.Path) {
*foundOptions = make(map[string]config.Option)
(*foundOptions)[ops] = cm.Configs[ops]
foundOptionWhole = true
return false
}
if strings.Contains(util.Norm(ops),
util.Norm(args[i])) {
// in the case of specifying a command
// and an option and the option is from
// the command, and there is only two
// args, and the option is fully named,
// not just partial matched, clear found
// options and break to return one
// command one option, which later is
// recognised to indicate show detail
if len(args) == 2 &&
len(*foundCmds) == 1 &&
util.Norm(ops) ==
util.Norm(args[i]) &&
cm.Configs[ops].Path().
Equal(cm.Path) {
*foundOpts = make(map[string]config.Option)
(*foundOpts)[ops] = cm.Configs[ops]
foundOptWhole = true
return false
} else {
(*foundOptions)[ops] = cm.Configs[ops]
(*foundOpts)[ops] =
cm.Configs[ops]
}
}
}
@@ -117,10 +131,9 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
w := new(tabwriter.Writer)
// minwidth, tabwidth, padding, padchar, flags
w.Init(&b, 8, 8, 0, '\t', 0)
// log.I.S(bufWriter.String())
switch {
case foundCommandWhole && len(args) == 1:
cm := (*foundCommands)[0]
case foundCmdWhole && len(args) == 1:
cm := (*foundCmds)[0]
// Print command help information
var outs CommandInfos
for i := range cm.Commands {
@@ -131,7 +144,6 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
})
}
sort.Sort(outs)
// out += fmt.Sprintf("\n%s - %s\n\n", cm.Path, cm.Description)
out += fmt.Sprintf(
"Help information for command '%s':\n\n",
args[0])
@@ -150,7 +162,8 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
outs[i].name, outs[i].description,
def); e != nil {
_, _ = fmt.Fprintln(os.Stderr, "error printing columns")
_, _ = fmt.Fprintln(os.Stderr,
"error printing columns")
} else {
w.Flush()
out += b.String()
@@ -158,18 +171,23 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
}
}
if cm.Default != nil {
out += "\n\t\t* indicates default if no subcommand given\n\n"
out += "\n\t\t* indicates default if no " +
"subcommand given\n\n"
} else {
out += "\n"
}
out += fmt.Sprintf("for more information on subcommands:\n\n")
out += fmt.Sprintf("\t%s help <subcommand>\n\n", os.Args[0])
out += fmt.Sprintf(
"for more information on subcommands:\n\n")
out += fmt.Sprintf(
"\t%s help <subcommand>\n\n", os.Args[0])
}
if len(cm.Configs) > 0 {
out += "Available configuration options on this command:\n\n"
out += fmt.Sprintf("\t-%s %v - %s (default: '%s')\n",
"flag", "[alias1 alias2]", "description", "default")
out += "\t\t(prefix '-' can also be '--', value can follow after space or with '=' and no space)\n\n"
"flag", "[alias1 alias2]", "description",
"default")
out += "\t\t(prefix '-' can also be '--', value can " +
"follow after space or with '=' and no space)\n\n"
var opts []string
for i := range c.Configs {
opts = append(opts, i)
@@ -193,15 +211,15 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
"\nUse 'help %s <option>' to get details on option.\n",
cm.Name)
}
case len(*foundOptions) == 1 &&
(len(*foundCommands) == 0 ||
foundOptionWhole):
case len(*foundOpts) == 1 &&
(len(*foundCmds) == 0 ||
foundOptWhole):
// For this case there is only one option, and one match, so we
// will print the full details as it is unambiguous.
for i := range *foundOptions {
for i := range *foundOpts {
// there is only one but a range statement grabs it
op := (*foundOptions)[i]
op := (*foundOpts)[i]
om := op.Meta()
path := op.Path().TrimPrefix().String()
if len(path) > 0 {
@@ -210,13 +228,16 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
search := strings.Join(args, " ")
if len(args) > 1 {
out += fmt.Sprintf(
"Help information for search terms '%s':\n\n", search)
"Help information for search terms '%s':\n\n",
search)
} else {
out += fmt.Sprintf("Help information for option '%s'\n\n",
out += fmt.Sprintf(
"Help information for option '%s'\n\n",
i)
}
if len(path) > 1 {
out += fmt.Sprintf("Command Path:\n\n\t%s\n\n", path)
out += fmt.Sprintf("Command Path:\n\n\t%s\n\n",
path)
}
out += fmt.Sprintf("%s [-%s]\n\n", i, strings.ToLower(i))
out += fmt.Sprintf("\t%s\n\n", om.Description())
@@ -225,7 +246,7 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
out += fmt.Sprintf("Documentation:\n\n%s\n\n",
IndentTextBlock(om.Documentation(), 1))
}
case len(*foundCommands) > 0 || len(*foundOptions) > 0:
case len(*foundCmds) > 0 || len(*foundOpts) > 0:
// if the text was not a command and one of its options, just
// show all partial matches of both commands and options in
// summary with their relevant paths
@@ -237,26 +258,27 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
out += fmt.Sprintf(
"Help information for search term%s '%s':\n\n",
plural, search)
if len(*foundCommands)+len(*foundOptions) > 1 {
if len(*foundCmds)+len(*foundOpts) > 1 {
out += "Multiple matches found:\n\n"
}
if len(*foundCommands) > 0 {
if len(*foundCmds) > 0 {
plural := ""
if len(*foundCommands) > 1 {
if len(*foundCmds) > 1 {
plural = "s"
}
out += fmt.Sprintf("Command%s\n\n", plural)
for i := range *foundCommands {
cm := (*foundCommands)[i]
for i := range *foundCmds {
cm := (*foundCmds)[i]
fmt.Fprintf(w, "\t%s\t %s\n",
strings.ToLower(cm.Name), cm.Description)
strings.ToLower(cm.Name),
cm.Description)
}
out += "\n"
}
if len(*foundOptions) > 0 {
if len(*foundOpts) > 0 {
out += fmt.Sprintf("Options:\n\n")
for i := range *foundOptions {
op := (*foundOptions)[i]
for i := range *foundOpts {
op := (*foundOpts)[i]
om := op.Meta()
path := op.Path().TrimPrefix().String()
if len(path) > 0 {
@@ -288,7 +310,6 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
})
}
sort.Sort(outs)
// log.I.S(outs)
plural := ""
pluralVerb := "is"
if len(c.Commands) > 1 {
@@ -348,14 +369,16 @@ func HelpEntrypoint(c *Command, args []string) (err error) {
fmt.Fprint(w, "\n\tFormat of configuration items:\n\n")
fmt.Fprintf(w, "\t\t-%s\t%v\t\n",
"flag [alias1 alias2]", "description (default: )")
fmt.Fprint(w, "\t\t(prefix '-' can also be '--', value can follow after space or with '=' and no space)\n\n")
fmt.Fprint(w, "\t\t(prefix '-' can also be '--', value can "+
"follow after space or with '=' and no space)\n\n")
w.Flush()
out += b.String()
b.Reset()
out += fmt.Sprintf("For more information:\n\n")
out += fmt.Sprintf("\t%s help <subcommand>\n\n", c.Name)
out += "\tUse 'help <option>' to get details on option.\n"
out += "\tUse 'help help' to learn more about the help function.\n\n"
out += "\tUse 'help help' to learn more about the help " +
"function.\n\n"
}
fmt.Print(out)

View File

@@ -95,7 +95,8 @@ func (c *Command) MarshalText() (text []byte, err error) {
current = current.Parent
}
text = append(text,
[]byte("# "+cmdPath+cmd.Name+": "+cmd.Description+"\n")...)
[]byte("# "+cmdPath+cmd.Name+": "+
cmd.Description+"\n")...)
text = append(text,
[]byte("["+cmdPath+cmd.Name+"]"+"\n\n")...)
}
@@ -141,32 +142,32 @@ func (c *Command) UnmarshalText(t []byte) (err error) {
switch op.Type() {
case meta.Bool:
v := op.(*toggle.Opt)
nv, ok := oo[i].value.(bool)
if ok {
if nv, ok := oo[i].value.(bool); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
case meta.Duration:
v := op.(*duration.Opt)
nv, ok := oo[i].value.(time.Duration)
if ok {
if nv, ok := oo[i].value.(time.Duration); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
case meta.Float:
v := op.(*float.Opt)
nv, ok := oo[i].value.(float64)
if ok {
if nv, ok := oo[i].value.(float64); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
case meta.Integer:
v := op.(*integer.Opt)
nv, ok := oo[i].value.(int64)
if ok {
if nv, ok := oo[i].value.(int64); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
case meta.List:
v := op.(*list.Opt)
nv, ok := oo[i].value.([]interface{})
@@ -177,16 +178,18 @@ func (c *Command) UnmarshalText(t []byte) (err error) {
if ok {
v.FromValue(strings)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
log.T.Ln("setting value of", oo[i].path, "to",
nv)
case meta.Text:
v := op.(*text.Opt)
nv, ok := oo[i].value.(string)
if ok {
if nv, ok := oo[i].value.(string); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
log.T.Ln("setting value of", oo[i].path, "to", nv)
default:
log.E.Ln("option type unknown:", oo[i].path, op.Type())
log.E.Ln("option type unknown:",
oo[i].path, op.Type())
}
} else {
log.D.Ln("option not found:", oo[i].path)
@@ -199,7 +202,8 @@ func (c *Command) LoadConfig() (err error) {
var file io.Reader
if file, err = os.Open(cfgFile.Expanded()); err != nil {
log.T.F("creating config file at path: '%s'", cfgFile.Expanded())
// If no config found, create data dir and drop the default in place
// If no config found, create data dir and drop the default in
// place
return c.SaveConfig()
} else {
var all []byte
@@ -213,8 +217,8 @@ func (c *Command) LoadConfig() (err error) {
}
func (c *Command) SaveConfig() (err error) {
datadir := c.GetOpt(path2.Path{c.Name, "DataDir"})
if err = os.MkdirAll(datadir.Expanded(), 0700); log.E.Chk(err) {
dd := c.GetOpt(path2.Path{c.Name, "DataDir"})
if err = os.MkdirAll(dd.Expanded(), 0700); log.E.Chk(err) {
return err
}
var f *os.File

View File

@@ -4,11 +4,6 @@ import (
"github.com/indra-labs/indra/pkg/slice"
)
// var (
// log = log2.GetLogger(indra.PathBase)
// check = log.E.Chk
// )
type Transport interface {
Send(b slice.Bytes)
Receive() <-chan slice.Bytes

View File

@@ -9,18 +9,25 @@ import (
"strings"
"syscall"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"go.uber.org/atomic"
"github.com/kardianos/osext"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type HandlerWithSource struct {
Source string
Fn func()
}
var (
Restart bool // = true
Restart bool
requested atomic.Bool
// ch is used to receive SIGINT (Ctrl+C) signals.
ch chan os.Signal
@@ -132,7 +139,7 @@ out:
// AddHandler adds a handler to call when a SIGINT (Ctrl+C) is received.
func AddHandler(handler func()) {
// Create the channel and start the main interrupt handler which invokes
// Create the channel and start the main interrupt handler that invokes
// all other callbacks and exits if not already done.
_, loc, line, _ := runtime.Caller(1)
msg := fmt.Sprintf("%s:%d", loc, line)

View File

@@ -1,8 +0,0 @@
package interrupt
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -25,21 +25,21 @@ func (c PubKey) CopyBlinder() (blinder Blinder) {
return
}
// PubKey is the blinded hash of a public key used to conceal a message
// public key from attackers.
// PubKey is the blinded hash of a public key used to conceal a message public
// key from attackers.
type PubKey [Len]byte
type Blinder [BlindLen]byte
type Hash [HashLen]byte
// GetCloak returns a value which a receiver with the private key can
// identify the association of a message with the peer in order to retrieve the
// private key to generate the message cipher.
// GetCloak returns a value which a receiver with the private key can identify
// the association of a message with the peer in order to retrieve the private
// key to generate the message cipher.
//
// The three byte blinding factor concatenated in front of the public key
// generates the 5 bytes at the end of the PubKey code. In this way the
// source public key it relates to is hidden to any who don't have this public
// key, which only the parties know.
// generates the 5 bytes at the end of the PubKey code. In this way the source
// public key it relates to is hidden to any who don't have this public key,
// which only the parties know.
func GetCloak(s *pub.Key) (c PubKey) {
var blinder Blinder
var n int
@@ -58,54 +58,12 @@ func Cloak(b Blinder, key pub.Bytes) (c PubKey) {
return
}
// Match uses the cached public key and the provided blinding factor to
// match the source public key so the packet address field is only recognisable
// to the intended recipient.
// Match uses the cached public key and the provided blinding factor to match
// the source public key so the packet address field is only recognisable to the
// intended recipient.
func Match(r PubKey, k pub.Bytes) bool {
var b Blinder
copy(b[:], r[:BlindLen])
hash := Cloak(b, k)
return r == hash
}
// // Receiver wraps a private key with pre-generated public key used to recognise
// // and associate messages from a specific peer, the public key is sent in a
// // previous message inside the encrypted payload and this structure is cached to
// // identify the correct key to decrypt the message.
// type Receiver struct {
// *prv.Key
// Pub *pub.Key
// pub.Bytes
// }
//
// // NewReceiver takes a private key and generates a Receiver for the address
// // cache.
// func NewReceiver(k *prv.Key) (a *Receiver) {
// a = &Receiver{
// Key: k,
// Pub: pub.Derive(k),
// }
// a.Bytes = a.Pub.ToBytes()
// return
// }
//
// // Sender is the raw bytes of a public key received in the metadata of a
// // message.
// type Sender struct {
// *pub.Key
// }
//
// // FromPub creates a Sender from a public key.
// func FromPub(k *pub.Key) (s *Sender) {
// s = &Sender{Key: k}
// return
// }
//
// // FromBytes creates a Sender from a received public key bytes.
// func FromBytes(pkb pub.Bytes) (s *Sender, e error) {
// var pk *pub.Key
// pk, e = pub.FromBytes(pkb[:])
// s = &Sender{Key: pk}
// return
// }

View File

@@ -10,7 +10,7 @@ import (
"github.com/indra-labs/indra/pkg/sha256"
)
// Compute computes an elliptic curve diffie hellman shared secret that can be
// Compute computes an Elliptic Curve Diffie-Hellman shared secret that can be
// decrypted by the holder of the private key matching the public key provided.
func Compute(prv *prv.Key, pub *pub.Key) sha256.Hash {
return sha256.Single(

View File

@@ -5,8 +5,6 @@
package sig
import (
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/indra-labs/indra"
@@ -31,23 +29,6 @@ const Len = 65
// extra bytes to also specify the public key of the signer.
type Bytes [Len]byte
func New() Bytes { return Bytes{} }
// IsValid checks that the signature is the correct length. This avoids needing
// to copy into a static array. Static arrays save on this code because they
// automatically must be correct.
func (sig Bytes) IsValid() (e error) {
if len(sig) == Len {
return
}
return fmt.Errorf(
"signature incorrect length, expect %d, got %d",
Len, len(sig))
}
// FromBytes checks if signature bytes are the correct length to be a signature.
func FromBytes(sig Bytes) (e error) { return sig.IsValid() }
// Sign produces an ECDSA BIP62 compact signature.
func Sign(prv *prv.Key, hash sha256.Hash) (sig Bytes, e error) {
copy(sig[:],

View File

@@ -144,7 +144,7 @@ var (
// allSubsystems stores all package subsystem names found in the current
// application.
allSubsystems []string
codeLoc = false
codeLoc = true
)
func GetAllSubsystems() (o []string) {
@@ -287,52 +287,51 @@ func logPrint(
printFunc func() string,
) func() {
return func() {
if level > Off && level <= logLevel {
formatString := "%v%s%s%-6v %s\n"
loc := ""
tsf := timeStampFormat
if codeLoc {
formatString = "%-58v%s%s%-6v %s\n"
loc = GetLoc(3, subsystem)
tsf = LocTimeStampFormat
}
var app string
if len(App) > 0 {
fmt.Sprint(" [" + App + "]")
}
s := fmt.Sprintf(
formatString,
loc,
color.Gray.Sprint(getTimeText(tsf)),
app,
LevelSpecs[level].Colorizer(
" "+LevelSpecs[level].Name+" ",
),
printFunc(),
)
writerMx.Lock()
defer writerMx.Unlock()
fmt.Fprintf(writer, s)
if level <= Off && level > logLevel {
return
}
formatString := "%v%s%s%-6v %s\n"
loc := ""
tsf := timeStampFormat
if codeLoc {
formatString = "%-58v%s%s%-6v %s\n"
loc = GetLoc(3, subsystem)
tsf = LocTimeStampFormat
}
var app string
if len(App) > 0 {
fmt.Sprint(" [" + App + "]")
}
s := fmt.Sprintf(
formatString,
loc,
color.Gray.Sprint(getTimeText(tsf)),
app,
LevelSpecs[level].Colorizer(
" "+LevelSpecs[level].Name+" ",
),
printFunc(),
)
writerMx.Lock()
defer writerMx.Unlock()
fmt.Fprintf(writer, s)
}
}
// sortSubsystemsList sorts the list of subsystems, to keep the data read-only,
// call this function right at the top of the main, which runs after
// declarations and main/init. Really this is just here to alert the reader.
func sortSubsystemsList() {
sort.Strings(allSubsystems)
}
func sortSubsystemsList() { sort.Strings(allSubsystems) }
// Add adds a subsystem to the list of known subsystems and returns the
// string so it is nice and neat in the package logg.go file
func Add(pathBase string) (subsystem string) {
var ok bool
var file string
_, file, _, ok = runtime.Caller(2)
var fh string
_, fh, _, ok = runtime.Caller(2)
if ok {
r := strings.Split(file, pathBase)
fromRoot := filepath.Base(file)
r := strings.Split(fh, pathBase)
fromRoot := filepath.Base(fh)
if len(r) > 1 {
fromRoot = r[1]
}
@@ -349,7 +348,6 @@ func Add(pathBase string) (subsystem string) {
// GetLogger returns a set of LevelPrinter with their subsystem preloaded
func GetLogger(pathBase string) (l *Logger) {
ss := Add(pathBase)
// fmt.Println("subsystems:", allSubsystems)
return &Logger{
getOnePrinter(Fatal, ss),
getOnePrinter(Error, ss),

View File

@@ -5,6 +5,7 @@ package node
import (
"fmt"
"net/netip"
"time"
"github.com/indra-labs/indra"
"github.com/indra-labs/indra/pkg/ifc"
@@ -26,11 +27,13 @@ var (
// this except when the netip.AddrPort is known via the packet sender address.
type Node struct {
nonce.ID
Addr string
AddrPort *netip.AddrPort
HeaderPub *pub.Key
HeaderBytes pub.Bytes
HeaderPrv *prv.Key
Addr string
AddrPort *netip.AddrPort
IdentityPub *pub.Key
IdentityBytes pub.Bytes
IdentityPrv *prv.Key
PingCount int
LastSeen time.Time
Services
ifc.Transport
}
@@ -42,17 +45,18 @@ func New(addr *netip.AddrPort, hdr *pub.Key, hdrPrv *prv.Key,
id = nonce.NewID()
n = &Node{
ID: id,
Addr: addr.String(),
AddrPort: addr,
Transport: tpt,
HeaderPub: hdr,
HeaderBytes: hdr.ToBytes(),
HeaderPrv: hdrPrv,
ID: id,
Addr: addr.String(),
AddrPort: addr,
Transport: tpt,
IdentityPub: hdr,
IdentityBytes: hdr.ToBytes(),
IdentityPrv: hdrPrv,
}
return
}
// SendTo delivers a message to a service identified by its port.
func (n *Node) SendTo(port uint16, b slice.Bytes) (e error) {
e = fmt.Errorf("port not registered %d", port)
for i := range n.Services {
@@ -65,6 +69,7 @@ func (n *Node) SendTo(port uint16, b slice.Bytes) (e error) {
return
}
// ReceiveFrom returns the channel that receives messages for a given port.
func (n *Node) ReceiveFrom(port uint16) (b <-chan slice.Bytes) {
for i := range n.Services {
if n.Services[i].Port == port {
@@ -81,14 +86,11 @@ type Nodes []*Node
// NewNodes creates an empty Nodes
func NewNodes() (n Nodes) { return Nodes{} }
func (n Nodes) Len() int {
return len(n)
}
// Len returns the length of a Nodes.
func (n Nodes) Len() int { return len(n) }
// Add a Node to a Nodes.
func (n Nodes) Add(nn *Node) Nodes {
return append(n, nn)
}
func (n Nodes) Add(nn *Node) Nodes { return append(n, nn) }
// FindByID searches for a Node by ID.
func (n Nodes) FindByID(i nonce.ID) (no *Node) {
@@ -114,21 +116,18 @@ func (n Nodes) FindByAddrPort(id *netip.AddrPort) (no *Node) {
// DeleteByID deletes a node identified by an ID.
func (n Nodes) DeleteByID(ii nonce.ID) (nn Nodes, e error) {
e = fmt.Errorf("id %x not found", ii)
e, nn = fmt.Errorf("id %x not found", ii), n
for i := range n {
if n[i].ID == ii {
n = append(n[:i], n[i+1:]...)
e = nil
break
return append(n[:i], n[i+1:]...), nil
}
}
return n, e
return
}
// DeleteByAddrPort deletes a node identified by a netip.AddrPort.
func (n Nodes) DeleteByAddrPort(ip *netip.AddrPort) (nn Nodes, e error) {
e = fmt.Errorf("node with ip %v not found", ip)
nn = n
e, nn = fmt.Errorf("node with ip %v not found", ip), n
for i := range n {
if n[i].AddrPort.String() == ip.String() {
nn = append(n[:i], n[i+1:]...)
@@ -138,13 +137,3 @@ func (n Nodes) DeleteByAddrPort(ip *netip.AddrPort) (nn Nodes, e error) {
}
return
}
type Selector func(n Nodes, exit *Node, count int) (selected Nodes)
func (n Nodes) Select(selector Selector, exit *Node, count int) (selected Nodes) {
if selector == nil {
log.E.Ln("no selector function given")
return
}
return selector(n, exit, count)
}

View File

@@ -1 +0,0 @@
package node

View File

@@ -8,20 +8,20 @@ import (
const IDLen = 8
// ID is a value generated by a hash chain that renews at first use and every
// time it generates 65536 new ID's.
type ID [IDLen]byte
var seed sha256.Hash
var counter uint16
func reseed() {
var c int
var e error
if c, e = rand.Read(seed[:]); check(e) && c != IDLen {
panic(e)
if c, e := rand.Read(seed[:]); check(e) && c != IDLen {
}
counter++
}
// NewID returns a random 8 byte nonce to be used as identifiers.
func NewID() (t ID) {
if counter == 0 {
reseed()

View File

@@ -22,7 +22,7 @@ type IV [IVLen]byte
// New reads a nonce from a cryptographically secure random number source
func New() (n IV) {
if _, e := rand.Read(n[:]); check(e) {
if c, e := rand.Read(n[:]); check(e) && c != IDLen {
}
return
}

View File

@@ -1,8 +0,0 @@
package integer
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -4,12 +4,19 @@ import (
"strconv"
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
"github.com/indra-labs/indra/pkg/path"
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata
@@ -53,8 +60,7 @@ func (o *Opt) FromValue(v int64) *Opt {
func (o *Opt) FromString(s string) (e error) {
s = strings.TrimSpace(s)
var p int64
p, e = strconv.ParseInt(s, 10, 64)
if e != nil {
if p, e = strconv.ParseInt(s, 10, 64); check(e) {
return e
}
o.v.Store(p)
@@ -70,7 +76,7 @@ func (o *Opt) Expanded() (s string) { return o.String() }
func (o *Opt) SetExpanded(s string) {
err := o.FromString(s)
log.E.Chk(err)
check(err)
}
func (o *Opt) Value() (c config.Concrete) {

View File

@@ -1,8 +0,0 @@
package duration
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -5,12 +5,19 @@ import (
"strings"
"time"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
"github.com/indra-labs/indra/pkg/path"
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata

View File

@@ -1,8 +0,0 @@
package float
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -4,12 +4,19 @@ import (
"strconv"
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
"github.com/indra-labs/indra/pkg/path"
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata

View File

@@ -1,8 +0,0 @@
package list
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -3,6 +3,8 @@ package list
import (
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
normalize2 "github.com/indra-labs/indra/pkg/opts/normalize"
@@ -10,6 +12,11 @@ import (
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata
@@ -54,11 +61,9 @@ func (o *Opt) FromValue(v []string) *Opt {
func (o *Opt) FromString(s string) (e error) {
s = strings.TrimSpace(s)
split := strings.Split(s, ",")
if len(split) == 1 && split[0] == "" {
split = make([]string, 0)
}
o.v.Store(split)
e = o.RunHooks()
return

View File

@@ -3,6 +3,14 @@ package normalize
import (
"net"
"strconv"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
// Address returns addr with the passed default port appended if there is not

View File

@@ -1,8 +0,0 @@
package normalize
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -15,13 +15,13 @@ func ResolvePath(input, appName string, abs bool) (cleaned string, e error) {
input = strings.Replace(input, "~", homeDir, 1)
cleaned = filepath.Clean(input)
} else {
if abs {
if cleaned, e = filepath.Abs(cleaned); log.E.Chk(e) {
return
}
// if the path is relative, either ./ or not starting with a / then
// we assume the path is relative to the app data directory
// if the path is relative, either ./ or not starting
// with a / then we assume the path is relative to the
// app data directory
} else if !strings.HasPrefix(string(os.PathSeparator), cleaned) ||
strings.HasPrefix("."+string(os.PathSeparator), cleaned) {
@@ -37,9 +37,8 @@ func getHomeDir() (homeDir string) {
if usr, e = user.Current(); !log.E.Chk(e) {
homeDir = usr.HomeDir
}
// Fall back to standard HOME environment variable that
// works for most POSIX OSes if the directory from the
// Go standard lib failed.
// Fall back to standard HOME environment variable that works for most
// POSIX OSes if the directory from the Go standard lib failed.
if e != nil || homeDir == "" {
homeDir = os.Getenv("HOME")
}

View File

@@ -1,8 +0,0 @@
package text
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -3,6 +3,8 @@ package text
import (
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
normalize2 "github.com/indra-labs/indra/pkg/opts/normalize"
@@ -10,6 +12,11 @@ import (
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata

View File

@@ -1,8 +0,0 @@
package toggle
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -5,12 +5,19 @@ import (
"strconv"
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/opts/config"
"github.com/indra-labs/indra/pkg/opts/meta"
"github.com/indra-labs/indra/pkg/path"
"go.uber.org/atomic"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Opt struct {
p path.Path
m meta.Metadata

View File

@@ -105,7 +105,8 @@ func Encode(ep EP) (pkt []byte, e error) {
slice.EncodeUint32(Length, ep.Length)
Deadline := slice.NewUint64()
slice.EncodeUint64(Deadline, uint64(ep.Deadline.Unix()))
pkt = make([]byte, slice.SumLen(Seq, Length, Deadline, ep.Data)+1+Overhead)
pkt = make([]byte, slice.SumLen(Seq, Length, Deadline,
ep.Data)+1+Overhead)
// Append pubkey used for encryption key derivation.
k := pub.Derive(ep.From).ToBytes()
// Copy nonce, address and key over top of the header.
@@ -136,8 +137,8 @@ func Encode(ep EP) (pkt []byte, e error) {
func GetKeys(d []byte) (from *pub.Key, e error) {
pktLen := len(d)
if pktLen < Overhead {
// If this isn't checked the slice operations later can
// hit bounds errors.
// If this isn't checked the slice operations later can hit
// bounds errors.
e = fmt.Errorf("packet too small, min %d, got %d",
Overhead, pktLen)
log.E.Ln(e)
@@ -167,8 +168,8 @@ func GetKeys(d []byte) (from *pub.Key, e error) {
func Decode(d []byte, from *pub.Key, to *prv.Key) (f *Packet, e error) {
pktLen := len(d)
if pktLen < Overhead {
// If this isn't checked the slice operations later can
// hit bounds errors.
// If this isn't checked the slice operations later can hit
// bounds errors.
e = fmt.Errorf("packet too small, min %d, got %d",
Overhead, pktLen)
log.E.Ln(e)

View File

@@ -17,16 +17,14 @@ func TestSplitJoin(t *testing.T) {
var e error
var payload []byte
var pHash sha256.Hash
if payload, pHash, e = testutils.GenerateTestMessage(msgSize); check(e) {
t.FailNow()
}
var sp, rp, Rp *prv.Key
var sP, rP, RP *pub.Key
if sp, rp, sP, rP, e = testutils.GenerateTestKeyPairs(); check(e) {
var sp, rp *prv.Key
var rP *pub.Key
if sp, rp, _, rP, e = testutils.GenerateTestKeyPairs(); check(e) {
t.FailNow()
}
_, _, _, _ = sP, Rp, RP, rp
addr := rP
params := EP{
To: addr,
@@ -77,17 +75,14 @@ func BenchmarkSplit(b *testing.B) {
segSize := 1382
var e error
var payload []byte
var hash sha256.Hash
if payload, hash, e = testutils.GenerateTestMessage(msgSize); check(e) {
if payload, _, e = testutils.GenerateTestMessage(msgSize); check(e) {
b.Error(e)
}
_ = hash
var sp, rp, Rp *prv.Key
var sP, rP *pub.Key
if sp, rp, sP, rP, e = testutils.GenerateTestKeyPairs(); check(e) {
var sp *prv.Key
var rP *pub.Key
if sp, _, _, rP, e = testutils.GenerateTestKeyPairs(); check(e) {
b.FailNow()
}
_, _, _ = sP, Rp, rp
addr := rP
for n := 0; n < b.N; n++ {
params := EP{
@@ -103,6 +98,17 @@ func BenchmarkSplit(b *testing.B) {
}
_ = splitted
}
// Example benchmark results show about 10Mb/s/thread throughput
// handling 64Kb messages.
//
// goos: linux
// goarch: amd64
// pkg: github.com/indra-labs/indra/pkg/packet
// cpu: AMD Ryzen 7 5800H with Radeon Graphics
// BenchmarkSplit
// BenchmarkSplit-16 157 7670080 ns/op
// PASS
}
func TestRemovePacket(t *testing.T) {
@@ -144,7 +150,9 @@ func TestSplitJoinFEC(t *testing.T) {
var payload []byte
var pHash sha256.Hash
if payload, pHash, e = testutils.GenerateTestMessage(msgSize); check(e) {
if payload, pHash, e = testutils.GenerateTestMessage(
msgSize); check(e) {
t.FailNow()
}
var punctures []int
@@ -173,9 +181,11 @@ func TestSplitJoinFEC(t *testing.T) {
t.FailNow()
}
overhead := ep.GetOverhead()
segMap := NewSegments(len(ep.Data), segSize, overhead, ep.Parity)
segMap := NewSegments(len(ep.Data), segSize, overhead,
ep.Parity)
for segs := range segMap {
start, end := segMap[segs].DStart, segMap[segs].PEnd
start := segMap[segs].DStart
end := segMap[segs].PEnd
cnt := end - start
par := segMap[segs].PEnd - segMap[segs].DEnd
a := make([][]byte, cnt)

View File

@@ -1,8 +0,0 @@
package path
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
)
var log = log2.GetLogger(indra.PathBase)

View File

@@ -3,9 +3,16 @@ package path
import (
"strings"
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/util"
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
)
type Path []string
func (p Path) TrimPrefix() Path {
@@ -15,14 +22,9 @@ func (p Path) TrimPrefix() Path {
return p[:0]
}
func (p Path) String() string {
return strings.Join(p, " ")
}
func (p Path) String() string { return strings.Join(p, " ") }
func From(s string) (p Path) {
p = strings.Split(s, " ")
return
}
func From(s string) (p Path) { return strings.Split(s, " ") }
func (p Path) Parent() (p1 Path) {
if len(p) > 0 {
@@ -31,11 +33,7 @@ func (p Path) Parent() (p1 Path) {
return
}
func (p Path) Child(child string) (p1 Path) {
p1 = append(p, child)
// log.I.Ln(p, p1)
return
}
func (p Path) Child(child string) (p1 Path) { return append(p, child) }
func (p Path) Common(p2 Path) (o Path) {
for i := range p {
@@ -61,6 +59,4 @@ func (p Path) Equal(p2 Path) bool {
return false
}
func GetIndent(d int) string {
return strings.Repeat("\t", d)
}
func GetIndent(d int) string { return strings.Repeat("\t", d) }

View File

@@ -26,49 +26,16 @@ type Session struct {
HeaderPub, PayloadPub *pub.Key
HeaderBytes, PayloadBytes pub.Bytes
HeaderPrv, PayloadPrv *prv.Key
Depth int8
Deadline time.Time
}
type Sessions []*Session
func (s Sessions) Len() int { return len(s) }
func (s Sessions) Add(se *Session) Sessions { return append(s, se) }
func (s Sessions) Delete(se *Session) Sessions {
for i := range s {
if s[i] == se {
return append(s[:i], s[i:]...)
}
}
return s
}
func (s Sessions) Find(t nonce.ID) (se *Session) {
for i := range s {
if s[i].ID == t {
se = s[i]
return
}
}
return
}
func (s Sessions) FindPub(pubKey *pub.Key) (se *Session) {
for i := range s {
if s[i].HeaderPub.Equals(pubKey) {
se = s[i]
return
}
}
return
}
// NewSession creates a new Session.
// New creates a new Session.
//
// Purchasing a session the seller returns a token, based on a requested data
// allocation.
func NewSession(id nonce.ID, rem uint64, deadline time.Duration) (s *Session) {
func New(id nonce.ID, node *node.Node, rem uint64, deadline time.Duration,
depth int8) (s *Session) {
var e error
var hdrPrv, pldPrv *prv.Key
@@ -83,6 +50,7 @@ func NewSession(id nonce.ID, rem uint64, deadline time.Duration) (s *Session) {
s = &Session{
ID: id,
Node: node,
Remaining: rem,
HeaderPub: hdrPub,
HeaderBytes: hdrPub.ToBytes(),
@@ -91,6 +59,7 @@ func NewSession(id nonce.ID, rem uint64, deadline time.Duration) (s *Session) {
HeaderPrv: hdrPrv,
PayloadPrv: pldPrv,
Deadline: time.Now().Add(deadline),
Depth: depth,
}
return
}
@@ -109,3 +78,45 @@ func (s *Session) SubtractBytes(b uint64) bool {
s.Remaining -= b
return true
}
type Sessions []*Session
func (s Sessions) Len() int { return len(s) }
func (s Sessions) Add(se *Session) Sessions { return append(s, se) }
func (s Sessions) Delete(se *Session) Sessions {
for i := range s {
if s[i] == se {
return append(s[:i], s[i+1:]...)
}
}
return s
}
func (s Sessions) DeleteByID(id nonce.ID) Sessions {
for i := range s {
if s[i].ID == id {
return append(s[:i], s[i+1:]...)
}
}
return s
}
func (s Sessions) Find(t nonce.ID) (se *Session) {
for i := range s {
if s[i].ID == t {
return s[i]
}
}
return
}
func (s Sessions) FindPub(pubKey *pub.Key) (se *Session) {
for i := range s {
if s[i].HeaderPub.Equals(pubKey) {
return s[i]
}
}
return
}

View File

@@ -59,12 +59,14 @@ func Cat(chunks ...[]byte) (pkt []byte) {
return
}
var put64 = binary.LittleEndian.PutUint64
var get64 = binary.LittleEndian.Uint64
var put32 = binary.LittleEndian.PutUint32
var get32 = binary.LittleEndian.Uint32
var put16 = binary.LittleEndian.PutUint16
var get16 = binary.LittleEndian.Uint16
var (
put64 = binary.LittleEndian.PutUint64
get64 = binary.LittleEndian.Uint64
put32 = binary.LittleEndian.PutUint32
get32 = binary.LittleEndian.Uint32
put16 = binary.LittleEndian.PutUint16
get16 = binary.LittleEndian.Uint16
)
// DecodeUint64 returns an int containing the little endian encoded 64-bit value
// stored in a 4 byte long slice
@@ -102,10 +104,12 @@ func DecodeUint16(b []byte) int { return int(get16(b)) }
// EncodeUint16 puts an int into a uint32 and then into 2 byte long slice.
func EncodeUint16(b []byte, n int) { put16(b, uint16(n)) }
const Uint64Len = 8
const Uint32Len = 4
const Uint24Len = 3
const Uint16Len = 2
const (
Uint64Len = 8
Uint32Len = 4
Uint24Len = 3
Uint16Len = 2
)
func NewUint64() Bytes { return make(Bytes, Uint64Len) }
func NewUint32() Bytes { return make(Bytes, Uint32Len) }

View File

@@ -15,11 +15,8 @@ type Sim chan slice.Bytes
func NewSim(bufs int) Sim { return make(Sim, bufs) }
func (d Sim) Send(b slice.Bytes) {
// log.I.S("sending", b.ToBytes())
// log.I.S(runtime.Caller(1))
d <- b
}
func (d Sim) Receive() <-chan slice.Bytes {
// log.I.Ln("receiving")
return d
}

View File

@@ -22,8 +22,9 @@ var (
_ types.Onion = &OnionSkin{}
)
// OnionSkin cipher delivers a pair of private keys to be used in association with a
// reply.Type specifically in the situation of a node bootstrapping sessions.
// OnionSkin cipher delivers a pair of private keys to be used in association
// with a reply.Type specifically in the situation of a node bootstrapping
// sessions.
//
// After ~10 seconds these can be purged from the cache as they are otherwise a
// DoS vector buffer flooding.
@@ -35,7 +36,6 @@ type OnionSkin struct {
func (x *OnionSkin) Inner() types.Onion { return x.Onion }
func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int { return Len + x.Onion.Len() }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
hdr := x.Header.ToBytes()
@@ -44,8 +44,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(pub.KeyLen)], pld[:])
x.Onion.Encode(b, c)
}
// Decode unwraps a cipher.OnionSkin message.
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]),

View File

@@ -14,7 +14,6 @@ import (
"github.com/indra-labs/indra/pkg/wire/forward"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/magicbytes"
"github.com/indra-labs/indra/pkg/wire/purchase"
"github.com/indra-labs/indra/pkg/wire/response"
"github.com/indra-labs/indra/pkg/wire/reverse"
"github.com/indra-labs/indra/pkg/wire/session"
@@ -72,12 +71,6 @@ func PeelOnion(b slice.Bytes, c *slice.Cursor) (on types.Onion, e error) {
return
}
on = &o
case purchase.MagicString:
o := &purchase.OnionSkin{}
if e = o.Decode(b, c); check(e) {
return
}
on = o
case reverse.MagicString:
o := &reverse.OnionSkin{}
if e = o.Decode(b, c); check(e) {

View File

@@ -21,7 +21,6 @@ import (
"github.com/indra-labs/indra/pkg/wire/exit"
"github.com/indra-labs/indra/pkg/wire/forward"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/purchase"
"github.com/indra-labs/indra/pkg/wire/response"
"github.com/indra-labs/indra/pkg/wire/reverse"
"github.com/indra-labs/indra/pkg/wire/session"
@@ -243,46 +242,6 @@ func TestOnionSkins_Layer(t *testing.T) {
}
}
func TestOnionSkins_Purchase(t *testing.T) {
var e error
prvs, pubs := GetCipherSet(t)
ciphers := GenCiphers(prvs, pubs)
p := rand.Uint64()
n3 := Gen3Nonces()
n := nonce.NewID()
on := OnionSkins{}.
Purchase(n, p, prvs, pubs, n3).
Assemble()
onb := EncodeOnion(on)
c := slice.NewCursor()
var onex types.Onion
if onex, e = PeelOnion(onb, c); check(e) {
t.FailNow()
}
pr := &purchase.OnionSkin{}
var ok bool
if pr, ok = onex.(*purchase.OnionSkin); !ok {
t.Error("did not unwrap expected type")
t.FailNow()
}
if pr.NBytes != p {
t.Error("NBytes did not unwrap correctly")
t.FailNow()
}
if pr.ID != n {
t.Errorf("id %v did not unwrap correctly, expected %v",
pr.ID, n)
t.FailNow()
}
for i := range pr.Ciphers {
if pr.Ciphers[i] != ciphers[i] {
t.Errorf("cipher %d did not unwrap correctly", i)
t.FailNow()
}
}
}
func TestOnionSkins_Reply(t *testing.T) {
var e error

View File

@@ -47,13 +47,11 @@ func (x *OnionSkin) String() string {
func (x *OnionSkin) Inner() types.Onion { return nil }
func (x *OnionSkin) Insert(o types.Onion) {}
func (x *OnionSkin) Len() int { return Len }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
// Copy in the ID.
copy(b[*c:c.Inc(nonce.IDLen)], x.ID[:])
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]),

View File

@@ -13,7 +13,7 @@ type Callback struct {
nonce.ID
time.Time
Onion *OnionSkin
Hook Hook
Hook
}
type Confirms struct {
@@ -21,12 +21,7 @@ type Confirms struct {
Cnf []Callback
}
func NewConfirms() *Confirms {
cn := Confirms{
Cnf: make([]Callback, 0),
}
return &cn
}
func NewConfirms() *Confirms { return &Confirms{Cnf: make([]Callback, 0)} }
func (cn *Confirms) Add(cb *Callback) {
cn.Lock()

View File

@@ -38,7 +38,6 @@ func (x *OnionSkin) String() string {
func (x *OnionSkin) Inner() types.Onion { return x.Onion }
func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int { return Len + x.Onion.Len() }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
var ap []byte
@@ -50,7 +49,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[c.Inc(1):c.Inc(Len-magicbytes.Len-1)], ap)
x.Onion.Encode(b, c)
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), Len-magicbytes.Len, string(Magic))

View File

@@ -31,7 +31,7 @@ var (
// OnionSkin message is the generic top level wrapper for an OnionSkin. All
// following messages are wrapped inside this. This type provides the encryption
// for each layer, and a header which a relay uses to determine what cipher to
// use.
// use. Everything in a message after this message is encrypted as specified.
type OnionSkin struct {
To *pub.Key
From *prv.Key
@@ -58,7 +58,6 @@ func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int {
return Len + x.Onion.Len()
}
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
copy(b[*c:c.Inc(nonce.IVLen)], x.Nonce[:])
@@ -79,9 +78,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
}
ciph.Encipher(blk, x.Nonce, b[start:])
}
// Decode decodes a received OnionSkin. The entire remainder of the message is
// encrypted by this layer.
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), Len-magicbytes.Len, "message")

View File

@@ -4,7 +4,6 @@ import (
"net/netip"
"time"
"github.com/indra-labs/indra/pkg/key/ecdh"
"github.com/indra-labs/indra/pkg/key/prv"
"github.com/indra-labs/indra/pkg/key/pub"
"github.com/indra-labs/indra/pkg/nonce"
@@ -18,39 +17,22 @@ import (
"github.com/indra-labs/indra/pkg/wire/forward"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/noop"
"github.com/indra-labs/indra/pkg/wire/purchase"
"github.com/indra-labs/indra/pkg/wire/response"
"github.com/indra-labs/indra/pkg/wire/reverse"
"github.com/indra-labs/indra/pkg/wire/session"
"github.com/indra-labs/indra/pkg/wire/token"
)
func GenCiphers(prvs [3]*prv.Key, pubs [3]*pub.Key) (ciphers [3]sha256.Hash) {
for i := range prvs {
ciphers[2-i] = ecdh.Compute(prvs[i], pubs[i])
}
return
}
func Gen3Nonces() (n [3]nonce.IV) {
for i := range n {
n[i] = nonce.New()
}
return
}
func GenPingNonces() (n [4]nonce.IV) {
for i := range n {
n[i] = nonce.New()
}
return
}
type OnionSkins []types.Onion
var os = &noop.OnionSkin{}
func (o OnionSkins) Cipher(hdr, pld *prv.Key) OnionSkins {
// SendKeys can apply to from 1 to 5 nodes, if either key is nil then
// this layer just doesn't get added in the serialization process.
if hdr == nil || pld == nil {
return o
}
return append(o, &cipher.OnionSkin{
Header: hdr,
Payload: pld,
@@ -92,26 +74,16 @@ func (o OnionSkins) OnionSkin(to *pub.Key, from *prv.Key,
Onion: os,
})
}
func (o OnionSkins) Purchase(id nonce.ID, nBytes uint64, prvs [3]*prv.Key,
pubs [3]*pub.Key, n [3]nonce.IV) OnionSkins {
oo := append(o, &purchase.OnionSkin{
ID: id,
NBytes: nBytes,
Ciphers: GenCiphers(prvs, pubs),
Nonces: n,
Onion: os,
})
return oo
}
func (o OnionSkins) Reverse(ip *netip.AddrPort) OnionSkins {
return append(o, &reverse.OnionSkin{AddrPort: ip, Onion: os})
}
func (o OnionSkins) Response(hash sha256.Hash, res slice.Bytes) OnionSkins {
rs := response.OnionSkin{Hash: hash, Bytes: res}
return append(o, &rs)
}
func (o OnionSkins) Session(hdr, pld *pub.Key) OnionSkins {
return append(o, &session.OnionSkin{
HeaderKey: hdr,
@@ -119,6 +91,7 @@ func (o OnionSkins) Session(hdr, pld *pub.Key) OnionSkins {
Onion: os,
})
}
func (o OnionSkins) Token(tok sha256.Hash) OnionSkins {
return append(o, (*token.OnionSkin)(&tok))
}

View File

@@ -11,10 +11,10 @@ type OnionSkin struct {
func (x *OnionSkin) Inner() types.Onion { return nil }
func (x *OnionSkin) Insert(o types.Onion) {}
func (x *OnionSkin) Len() int { return 0 }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
func (x *OnionSkin) Encode(b slice.Bytes,
c *slice.Cursor) {
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
func (x *OnionSkin) Decode(b slice.Bytes,
c *slice.Cursor) (e error) {
return
}

View File

@@ -15,24 +15,30 @@ import (
// last hop as the key to narrow the number of elements to search through to
// find the matching cipher and reveal the contained ID inside it.
//
// The pending ping records keep the identifiers of the three nodes that were in
// The pending ping records keep the identifiers of the 5 nodes that were in
// a ping onion and when the Confirmation is correctly received these nodes get
// an increment of their liveness score. By using this scheme, when nodes are
// offline their scores will fall to zero after a time whereas live nodes will
// have steadily increasing scores from successful pings.
func Ping(id nonce.ID, client *node.Node, hop [3]*node.Node,
set *signer.KeySet) OnionSkins {
func Ping(id nonce.ID, s session.Sessions, ks *signer.KeySet) OnionSkins {
if len(s) != 6 {
log.E.F("Ping requires 6 sessions, received %d", len(s))
return nil
}
n := GenPingNonces()
return OnionSkins{}.
Forward(hop[0].AddrPort).
OnionSkin(hop[0].HeaderPub, set.Next(), n[0]).
Forward(hop[1].AddrPort).
OnionSkin(hop[1].HeaderPub, set.Next(), n[1]).
Forward(hop[2].AddrPort).
OnionSkin(hop[2].HeaderPub, set.Next(), n[2]).
Forward(client.AddrPort).
OnionSkin(client.HeaderPub, set.Next(), n[3]).
Forward(s[0].AddrPort).
OnionSkin(s[0].HeaderPub, ks.Next(), n[0]).
Forward(s[1].AddrPort).
OnionSkin(s[1].HeaderPub, ks.Next(), n[1]).
Forward(s[2].AddrPort).
OnionSkin(s[2].HeaderPub, ks.Next(), n[2]).
Forward(s[3].AddrPort).
OnionSkin(s[3].HeaderPub, ks.Next(), n[3]).
Forward(s[4].AddrPort).
OnionSkin(s[4].HeaderPub, ks.Next(), n[3]).
Forward(s[5].AddrPort).
OnionSkin(s[5].HeaderPub, ks.Next(), n[3]).
Confirmation(id)
}
@@ -47,73 +53,32 @@ func Ping(id nonce.ID, client *node.Node, hop [3]*node.Node,
// the Reverse relay.
//
// This message's last layer is a Confirmation, which allows the client to know
// that the key was successfully delivered to the Reverse relays that will be
// used in the Purchase.
func SendKeys(id nonce.ID, hdr, pld *prv.Key,
client *node.Node, hop [5]*node.Node, set *signer.KeySet) OnionSkins {
// that the keys were successfully delivered.
func SendKeys(id nonce.ID, hdr, pld []*prv.Key,
client *node.Node, hop []*node.Node, set *signer.KeySet) OnionSkins {
n0 := Gen3Nonces()
n1 := Gen3Nonces()
n := GenNonces(6)
return OnionSkins{}.
Forward(hop[0].AddrPort).
OnionSkin(hop[0].HeaderPub, set.Next(), n0[0]).
OnionSkin(hop[0].IdentityPub, set.Next(), n[0]).
Cipher(hdr[0], pld[0]).
Forward(hop[1].AddrPort).
OnionSkin(hop[1].HeaderPub, set.Next(), n0[1]).
OnionSkin(hop[1].IdentityPub, set.Next(), n[1]).
Cipher(hdr[1], pld[1]).
Forward(hop[2].AddrPort).
OnionSkin(hop[2].HeaderPub, set.Next(), n0[2]).
Cipher(hdr, pld).
OnionSkin(hop[2].IdentityPub, set.Next(), n[2]).
Cipher(hdr[2], pld[2]).
Forward(hop[3].AddrPort).
OnionSkin(hop[3].HeaderPub, set.Next(), n1[0]).
OnionSkin(hop[3].IdentityPub, set.Next(), n[3]).
Cipher(hdr[3], pld[3]).
Forward(hop[4].AddrPort).
OnionSkin(hop[4].HeaderPub, set.Next(), n1[1]).
OnionSkin(hop[4].IdentityPub, set.Next(), n[4]).
Cipher(hdr[4], pld[4]).
Forward(client.AddrPort).
OnionSkin(client.HeaderPub, set.Next(), n1[2]).
OnionSkin(client.IdentityPub, set.Next(), n[5]).
Confirmation(id)
}
// SendPurchase delivers a request for keys for a relaying session with a given
// router (in this case, hop 2). It is almost identical to an Exit except the
// payload is always just a 64-bit unsigned integer.
//
// The response, which will be two public keys that identify the session and
// form the basis of the cloaked "To" keys, is encrypted with the given layers,
// the ciphers are already given in reverse order, so they are decoded in given
// order to create the correct payload encryption to match the PayloadPub
// combined with the header's given public From key.
//
// The header remains a constant size and each node in the Reverse trims off
// their section at the top, moves the next layer header to the top and pads the
// remainder with noise, so it always looks like the first hop,
// indistinguishable.
func SendPurchase(n nonce.ID, nBytes uint64, client *node.Node,
hop [5]*node.Node, sess [3]*session.Session,
set *signer.KeySet) OnionSkins {
var prvs [3]*prv.Key
for i := range prvs {
prvs[i] = set.Next()
}
n0, n1 := Gen3Nonces(), Gen3Nonces()
var pubs [3]*pub.Key
pubs[0] = sess[0].PayloadPub
pubs[1] = sess[1].PayloadPub
pubs[2] = sess[2].PayloadPub
return OnionSkins{}.
Forward(hop[0].AddrPort).
OnionSkin(hop[0].HeaderPub, set.Next(), n0[0]).
Forward(hop[1].AddrPort).
OnionSkin(hop[1].HeaderPub, set.Next(), n0[1]).
Forward(hop[2].AddrPort).
OnionSkin(hop[2].HeaderPub, set.Next(), n0[2]).
Purchase(n, nBytes, prvs, pubs, n1).
Reverse(hop[3].AddrPort).
OnionSkin(sess[0].HeaderPub, prvs[0], n1[0]).
Reverse(hop[4].AddrPort).
OnionSkin(sess[1].HeaderPub, prvs[1], n1[1]).
Reverse(client.AddrPort).
OnionSkin(sess[2].HeaderPub, prvs[2], n1[2])
}
// SendExit constructs a message containing an arbitrary payload to a node (3rd
// hop) with a set of 3 ciphers derived from the hidden PayloadPub of the return
// hops that are layered progressively after the Exit message.
@@ -145,11 +110,11 @@ func SendExit(payload slice.Bytes, port uint16, client *node.Node,
pubs[2] = sess[2].PayloadPub
return OnionSkins{}.
Forward(hop[0].AddrPort).
OnionSkin(hop[0].HeaderPub, set.Next(), n0[0]).
OnionSkin(hop[0].IdentityPub, set.Next(), n0[0]).
Forward(hop[1].AddrPort).
OnionSkin(hop[1].HeaderPub, set.Next(), n0[1]).
OnionSkin(hop[1].IdentityPub, set.Next(), n0[1]).
Forward(hop[2].AddrPort).
OnionSkin(hop[2].HeaderPub, set.Next(), n0[2]).
OnionSkin(hop[2].IdentityPub, set.Next(), n0[2]).
Exit(port, prvs, pubs, n1, payload).
Reverse(hop[3].AddrPort).
OnionSkin(sess[0].HeaderPub, prvs[0], n1[0]).

View File

@@ -1,532 +0,0 @@
package wire
import (
"math/rand"
"reflect"
"testing"
"time"
"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/session"
"github.com/indra-labs/indra/pkg/sha256"
"github.com/indra-labs/indra/pkg/slice"
"github.com/indra-labs/indra/pkg/testutils"
"github.com/indra-labs/indra/pkg/types"
"github.com/indra-labs/indra/pkg/wire/cipher"
"github.com/indra-labs/indra/pkg/wire/confirm"
"github.com/indra-labs/indra/pkg/wire/exit"
"github.com/indra-labs/indra/pkg/wire/forward"
"github.com/indra-labs/indra/pkg/wire/layer"
"github.com/indra-labs/indra/pkg/wire/purchase"
"github.com/indra-labs/indra/pkg/wire/reverse"
)
func PeelForward(t *testing.T, b slice.Bytes,
c *slice.Cursor) (fwd *forward.OnionSkin) {
var ok bool
var on types.Onion
var e error
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
}
if fwd, ok = on.(*forward.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(fwd))
}
return
}
func PeelOnionSkin(t *testing.T, b slice.Bytes,
c *slice.Cursor) (l *layer.OnionSkin) {
var ok bool
var on types.Onion
var e error
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
}
if l, ok = on.(*layer.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(l))
}
return
}
func PeelConfirmation(t *testing.T, b slice.Bytes,
c *slice.Cursor) (cn *confirm.OnionSkin) {
var ok bool
var e error
var on types.Onion
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
}
if cn, ok = on.(*confirm.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(on))
}
return
}
func PeelPurchase(t *testing.T, b slice.Bytes,
c *slice.Cursor) (pr *purchase.OnionSkin) {
var ok bool
var e error
var on types.Onion
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
}
if pr, ok = on.(*purchase.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(on))
}
return
}
func PeelReverse(t *testing.T, b slice.Bytes,
c *slice.Cursor) (rp *reverse.OnionSkin) {
var ok bool
var e error
var on types.Onion
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
}
if rp, ok = on.(*reverse.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(on))
}
return
}
func PeelExit(t *testing.T, b slice.Bytes,
c *slice.Cursor) (ex *exit.OnionSkin) {
var ok bool
var e error
var on types.Onion
if on, e = PeelOnion(b, c); check(e) {
t.Error(e)
t.FailNow()
}
if ex, ok = on.(*exit.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(on))
t.FailNow()
}
return
}
func TestPing(t *testing.T) {
_, ks, e := signer.New()
if check(e) {
t.Error(e)
t.FailNow()
}
var hop [3]*node.Node
for i := range hop {
prv1, _ := GetTwoPrvKeys(t)
pub1 := pub.Derive(prv1)
var n nonce.ID
hop[i], n = node.New(slice.GenerateRandomAddrPortIPv4(),
pub1, prv1, nil)
_ = n
}
cprv1, _ := GetTwoPrvKeys(t)
cpub1 := pub.Derive(cprv1)
var n nonce.ID
var client *node.Node
client, n = node.New(slice.GenerateRandomAddrPortIPv4(),
cpub1, cprv1, nil)
on := Ping(n, client, hop, ks)
b := EncodeOnion(on.Assemble())
c := slice.NewCursor()
// Forward(hop[0].AddrPort).
f0 := PeelForward(t, b, c)
if hop[0].AddrPort.String() != f0.AddrPort.String() {
t.Errorf("failed to unwrap; expected: '%s', got: '%s'",
hop[0].AddrPort.String(), f0.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[0].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[0].HeaderPrv, b, c)
// Forward(hop[1].AddrPort).
f1 := PeelForward(t, b, c)
if hop[1].AddrPort.String() != f1.AddrPort.String() {
t.Errorf("failed to unwrap; expected: '%s', got: '%s'",
hop[1].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[1].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[1].HeaderPrv, b, c)
// Forward(hop[2].AddrPort).
f2 := PeelForward(t, b, c)
if hop[2].AddrPort.String() != f2.AddrPort.String() {
t.Errorf("failed to unwrap; expected: '%s', got: '%s'",
hop[2].AddrPort.String(), f2.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[2].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[2].HeaderPrv, b, c)
// Forward(client.AddrPort).
f3 := PeelForward(t, b, c)
if client.AddrPort.String() != f3.AddrPort.String() {
t.Errorf("failed to unwrap; expected: '%s', got: '%s'",
client.AddrPort.String(), f3.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(client.HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(client.HeaderPrv, b, c)
// Confirmation(id).
co := PeelConfirmation(t, b, c)
if co.ID != n {
t.Error("did not unwrap expected confirmation nonce")
t.FailNow()
}
}
func TestSendKeys(t *testing.T) {
_, ks, e := signer.New()
if check(e) {
t.Error(e)
t.FailNow()
}
var hop [5]*node.Node
for i := range hop {
prv1, _ := GetTwoPrvKeys(t)
pub1 := pub.Derive(prv1)
hop[i], _ = node.New(slice.GenerateRandomAddrPortIPv4(),
pub1, prv1, nil)
}
cprv1, _ := GetTwoPrvKeys(t)
cpub1 := pub.Derive(cprv1)
var n nonce.ID
var client *node.Node
client, n = node.New(slice.GenerateRandomAddrPortIPv4(),
cpub1, cprv1, nil)
ciprv1, ciprv2 := GetTwoPrvKeys(t)
// cipub1, cipub2 := pub.Derive(ciprv1), pub.Derive(ciprv2)
on := SendKeys(n, ciprv1, ciprv2, client, hop, ks)
b := EncodeOnion(on.Assemble())
c := slice.NewCursor()
var ok bool
// Forward(hop[0].AddrPort).
f0 := PeelForward(t, b, c)
if hop[0].AddrPort.String() != f0.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[0].AddrPort.String(), f0.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[0].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[0].HeaderPrv, b, c)
// Forward(hop[1].AddrPort).
f1 := PeelForward(t, b, c)
if hop[1].AddrPort.String() != f1.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[1].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[1].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[1].HeaderPrv, b, c)
// Forward(hop[2].AddrPort).
f2 := PeelForward(t, b, c)
if hop[2].AddrPort.String() != f2.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[2].AddrPort.String(), f2.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[2].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[2].HeaderPrv, b, c)
// Cipher(hdr, pld).
var onc types.Onion
if onc, e = PeelOnion(b, c); check(e) {
t.Error(e)
t.FailNow()
}
var ci *cipher.OnionSkin
if ci, ok = onc.(*cipher.OnionSkin); !ok {
t.Error("did not unwrap expected type", reflect.TypeOf(onc))
t.FailNow()
}
if !ci.Header.Key.Equals(&ciprv1.Key) {
t.Error("did not unwrap header key")
t.FailNow()
}
if !ci.Payload.Key.Equals(&ciprv2.Key) {
t.Error("did not unwrap payload key")
t.FailNow()
}
// Forward(hop[3].AddrPort).
f3 := PeelForward(t, b, c)
if hop[3].AddrPort.String() != f3.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[3].AddrPort.String(), f3.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[3].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[3].HeaderPrv, b, c)
// Forward(hop[4].AddrPort).
f4 := PeelForward(t, b, c)
if hop[4].AddrPort.String() != f4.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[3].AddrPort.String(), f4.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[4].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[4].HeaderPrv, b, c)
// Forward(client.AddrPort).
f5 := PeelForward(t, b, c)
if client.AddrPort.String() != f5.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
client.AddrPort.String(), f5.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(client.HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(client.HeaderPrv, b, c)
// Confirmation(id).
co := PeelConfirmation(t, b, c)
if co.ID != n {
t.Error("did not unwrap expected confirmation nonce")
t.FailNow()
}
}
func TestSendPurchase(t *testing.T) {
log2.SetLogLevel(log2.Trace)
_, ks, e := signer.New()
if check(e) {
t.Error(e)
t.FailNow()
}
var hop [5]*node.Node
for i := range hop {
prv1, _ := GetTwoPrvKeys(t)
pub1 := pub.Derive(prv1)
hop[i], _ = node.New(slice.GenerateRandomAddrPortIPv4(),
pub1, prv1, nil)
}
cprv1, _ := GetTwoPrvKeys(t)
cpub1 := pub.Derive(cprv1)
var client *node.Node
client, _ = node.New(slice.GenerateRandomAddrPortIPv4(),
cpub1, cprv1, nil)
var sess [3]*session.Session
for i := range sess {
sess[i] = session.NewSession(nonce.NewID(), 203230230, time.Hour)
}
nBytes := rand.Uint64()
n := nonce.NewID()
on := SendPurchase(n, nBytes, client, hop, sess, ks)
b := EncodeOnion(on.Assemble())
c := slice.NewCursor()
// Forward(hop[0].AddrPort).
f0 := PeelForward(t, b, c)
if hop[0].AddrPort.String() != f0.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[0].AddrPort.String(), f0.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[0].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[0].HeaderPrv, b, c)
// Forward(hop[1].AddrPort).
f1 := PeelForward(t, b, c)
if hop[1].AddrPort.String() != f1.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[0].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[1].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[1].HeaderPrv, b, c)
// Forward(hop[2].AddrPort).
f2 := PeelForward(t, b, c)
if hop[2].AddrPort.String() != f2.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[1].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[2].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[2].HeaderPrv, b, c)
// Purchase(nBytes, prvs, pubs).
pr := PeelPurchase(t, b, c)
if pr.NBytes != nBytes {
t.Errorf("failed to retrieve original purchase nBytes")
t.FailNow()
}
// Reverse(hop[3].AddrPort).
rp1 := PeelReverse(t, b, c)
if rp1.AddrPort.String() != hop[3].AddrPort.String() {
t.Errorf("failed to retrieve first reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[3].HeaderPub), replies[0]).
PeelOnionSkin(t, b, c).Decrypt(sess[0].HeaderPrv, b, c)
// Reverse(hop[4].AddrPort).
rp2 := PeelReverse(t, b, c)
if rp2.AddrPort.String() != hop[4].AddrPort.String() {
t.Errorf("failed to retrieve second reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[4].HeaderPub), replies[1]).
PeelOnionSkin(t, b, c).Decrypt(sess[1].HeaderPrv, b, c)
// Reverse(client.AddrPort).
rp3 := PeelReverse(t, b, c)
if rp3.AddrPort.String() != client.AddrPort.String() {
t.Errorf("failed to retrieve third reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(client.HeaderPub), replies[2]).
PeelOnionSkin(t, b, c).Decrypt(sess[2].HeaderPrv, b, c)
}
func TestSendExit(t *testing.T) {
_, ks, e := signer.New()
if check(e) {
t.Error(e)
t.FailNow()
}
var hop [5]*node.Node
for i := range hop {
prv1, _ := GetTwoPrvKeys(t)
pub1 := pub.Derive(prv1)
hop[i], _ = node.New(slice.GenerateRandomAddrPortIPv4(),
pub1, prv1, nil)
}
cprv1, _ := GetTwoPrvKeys(t)
cpub1 := pub.Derive(cprv1)
var client *node.Node
client, _ = node.New(slice.GenerateRandomAddrPortIPv4(),
cpub1, cprv1, nil)
port := uint16(rand.Uint32())
var message slice.Bytes
var hash sha256.Hash
message, hash, e = testutils.GenerateTestMessage(2502)
var sess [3]*session.Session
for i := range sess {
sess[i] = session.NewSession(nonce.NewID(), 203230230, time.Hour)
}
on := SendExit(message, port, client, hop, sess, ks)
b := EncodeOnion(on.Assemble())
c := slice.NewCursor()
// Forward(hop[0].AddrPort).
f0 := PeelForward(t, b, c)
if hop[0].AddrPort.String() != f0.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[0].AddrPort.String(), f0.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[0].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[0].HeaderPrv, b, c)
// Forward(hop[1].AddrPort).
f1 := PeelForward(t, b, c)
if hop[1].AddrPort.String() != f1.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[0].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[1].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[1].HeaderPrv, b, c)
// Forward(hop[2].AddrPort).
f2 := PeelForward(t, b, c)
if hop[2].AddrPort.String() != f2.AddrPort.String() {
t.Errorf("failed to unwrap expected: '%s', got '%s'",
hop[1].AddrPort.String(), f1.AddrPort.String())
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[2].HeaderPub), set.Next()).
PeelOnionSkin(t, b, c).Decrypt(hop[2].HeaderPrv, b, c)
// Exit(port, prvs, pubs, payload).
pr := PeelExit(t, b, c)
if pr.Port != port {
t.Errorf("failed to retrieve original purchase nBytes")
t.FailNow()
}
mh := sha256.Single(pr.Bytes)
if mh != hash {
t.Errorf("exit message not correctly decoded")
t.FailNow()
}
// Reverse(hop[3].AddrPort).
rp1 := PeelReverse(t, b, c)
if rp1.AddrPort.String() != hop[3].AddrPort.String() {
t.Errorf("failed to retrieve first reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[3].HeaderPub), replies[0]).
PeelOnionSkin(t, b, c).Decrypt(sess[0].HeaderPrv, b, c)
// Reverse(hop[4].AddrPort).
rp2 := PeelReverse(t, b, c)
if rp2.AddrPort.String() != hop[4].AddrPort.String() {
t.Errorf("failed to retrieve second reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(hop[4].HeaderPub), replies[1]).
PeelOnionSkin(t, b, c).Decrypt(sess[1].HeaderPrv, b, c)
// Reverse(client.AddrPort).
rp3 := PeelReverse(t, b, c)
if rp3.AddrPort.String() != client.AddrPort.String() {
t.Errorf("failed to retrieve third reply hop")
t.FailNow()
}
// OnionSkin(address.FromPubKey(client.HeaderPub), replies[2]).
PeelOnionSkin(t, b, c).Decrypt(sess[2].HeaderPrv, b, c)
}

View File

@@ -1,81 +0,0 @@
package purchase
import (
"github.com/indra-labs/indra"
log2 "github.com/indra-labs/indra/pkg/log"
"github.com/indra-labs/indra/pkg/nonce"
"github.com/indra-labs/indra/pkg/sha256"
"github.com/indra-labs/indra/pkg/slice"
"github.com/indra-labs/indra/pkg/types"
"github.com/indra-labs/indra/pkg/wire/magicbytes"
)
const (
MagicString = "pc"
Len = magicbytes.Len + slice.Uint64Len + sha256.Len*3 +
nonce.IVLen*3 + nonce.IDLen
)
var (
log = log2.GetLogger(indra.PathBase)
check = log.E.Chk
Magic = slice.Bytes(MagicString)
_ types.Onion = &OnionSkin{}
)
// OnionSkin purchase is a message that requests a session key, which will
// activate when a payment for it has been done, or it will time out after some
// period to allow unused codes to be flushed.
type OnionSkin struct {
nonce.ID
NBytes uint64
// Ciphers is a set of 3 symmetric ciphers that are to be used in their
// given order over the reply message from the service.
Ciphers [3]sha256.Hash
// Nonces are the nonces to use with the cipher when creating the
// encryption for the reply message.
Nonces [3]nonce.IV
types.Onion
}
func (x *OnionSkin) Inner() types.Onion { return x.Onion }
func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int {
return Len + x.Onion.Len()
}
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
copy(b[*c:c.Inc(nonce.IDLen)], x.ID[:])
value := slice.NewUint64()
slice.EncodeUint64(value, x.NBytes)
copy(b[*c:c.Inc(slice.Uint64Len)], value)
copy(b[*c:c.Inc(sha256.Len)], x.Ciphers[0][:])
copy(b[*c:c.Inc(sha256.Len)], x.Ciphers[1][:])
copy(b[*c:c.Inc(sha256.Len)], x.Ciphers[2][:])
copy(b[*c:c.Inc(nonce.IVLen)], x.Nonces[0][:])
copy(b[*c:c.Inc(nonce.IVLen)], x.Nonces[1][:])
copy(b[*c:c.Inc(nonce.IVLen)], x.Nonces[2][:])
x.Onion.Encode(b, c)
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]),
Len-magicbytes.Len, MagicString)
}
copy(x.ID[:], b[*c:c.Inc(nonce.IDLen)])
x.NBytes = slice.DecodeUint64(
b[*c:c.Inc(slice.Uint64Len)])
for i := range x.Ciphers {
bytes := b[*c:c.Inc(sha256.Len)]
copy(x.Ciphers[i][:], bytes)
bytes.Zero()
}
for i := range x.Nonces {
bytes := b[*c:c.Inc(nonce.IVLen)]
copy(x.Nonces[i][:], bytes)
bytes.Zero()
}
return
}

View File

@@ -1,18 +1,21 @@
package response
import (
"time"
"github.com/indra-labs/indra/pkg/sha256"
)
type Hook struct {
sha256.Hash
Callback func()
time.Time
}
type Hooks []Hook
func (h Hooks) Add(hash sha256.Hash, fn func()) (hh Hooks) {
return append(h, Hook{Hash: hash, Callback: fn})
return append(h, Hook{Hash: hash, Callback: fn, Time: time.Now()})
}
func (h Hooks) Find(hash sha256.Hash) (hh Hooks) {

View File

@@ -35,7 +35,6 @@ func New() *OnionSkin {
func (x *OnionSkin) Inner() types.Onion { return nil }
func (x *OnionSkin) Insert(_ types.Onion) {}
func (x *OnionSkin) Len() int { return Len + len(x.Bytes) }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
copy(b[*c:c.Inc(sha256.Len)], x.Hash[:])
@@ -44,7 +43,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(slice.Uint32Len)], bytesLen)
copy(b[*c:c.Inc(len(x.Bytes))], x.Bytes)
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]),

View File

@@ -39,7 +39,6 @@ type OnionSkin struct {
func (x *OnionSkin) Inner() types.Onion { return x.Onion }
func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int { return Len + x.Onion.Len() }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
var ap []byte
@@ -51,7 +50,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[c.Inc(1):c.Inc(Len-magicbytes.Len-1)], ap)
x.Onion.Encode(b, c)
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), Len-magicbytes.Len, string(Magic))

View File

@@ -43,7 +43,6 @@ type OnionSkin struct {
func (x *OnionSkin) Inner() types.Onion { return x.Onion }
func (x *OnionSkin) Insert(o types.Onion) { x.Onion = o }
func (x *OnionSkin) Len() int { return Len + x.Onion.Len() }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
hdr, pld := x.HeaderKey.ToBytes(), x.PayloadKey.ToBytes()
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
@@ -52,7 +51,6 @@ func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(pub.KeyLen)], pld[:])
x.Onion.Encode(b, c)
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < Len-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), Len-magicbytes.Len, string(Magic))

View File

@@ -29,12 +29,10 @@ func NewOnionSkin() *OnionSkin {
func (x *OnionSkin) Inner() types.Onion { return nil }
func (x *OnionSkin) Insert(_ types.Onion) {}
func (x *OnionSkin) Len() int { return MinLen }
func (x *OnionSkin) Encode(b slice.Bytes, c *slice.Cursor) {
copy(b[*c:c.Inc(magicbytes.Len)], Magic)
copy(b[*c:c.Inc(sha256.Len)], x[:sha256.Len])
}
func (x *OnionSkin) Decode(b slice.Bytes, c *slice.Cursor) (e error) {
if len(b[*c:]) < MinLen-magicbytes.Len {
return magicbytes.TooShort(len(b[*c:]), MinLen-magicbytes.Len,

38
pkg/wire/util.go Normal file
View File

@@ -0,0 +1,38 @@
package wire
import (
"github.com/indra-labs/indra/pkg/key/ecdh"
"github.com/indra-labs/indra/pkg/key/prv"
"github.com/indra-labs/indra/pkg/key/pub"
"github.com/indra-labs/indra/pkg/nonce"
"github.com/indra-labs/indra/pkg/sha256"
)
func GenCiphers(prvs [3]*prv.Key, pubs [3]*pub.Key) (ciphers [3]sha256.Hash) {
for i := range prvs {
ciphers[2-i] = ecdh.Compute(prvs[i], pubs[i])
}
return
}
func Gen3Nonces() (n [3]nonce.IV) {
for i := range n {
n[i] = nonce.New()
}
return
}
func GenNonces(count int) (n []nonce.IV) {
n = make([]nonce.IV, count)
for i := range n {
n[i] = nonce.New()
}
return
}
func GenPingNonces() (n [4]nonce.IV) {
for i := range n {
n[i] = nonce.New()
}
return
}