[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnunet-go] branch master updated: Improved handling of pending DHT resu
From: |
gnunet |
Subject: |
[gnunet-go] branch master updated: Improved handling of pending DHT results. |
Date: |
Mon, 18 Jul 2022 11:18:25 +0200 |
This is an automated email from the git hooks/post-receive script.
bernd-fix pushed a commit to branch master
in repository gnunet-go.
The following commit(s) were added to refs/heads/master by this push:
new f425c2a Improved handling of pending DHT results.
f425c2a is described below
commit f425c2aeef06d1a6105678c8b058bdde65a26e78
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Mon Jul 18 11:16:34 2022 +0200
Improved handling of pending DHT results.
---
README.md | 52 ++-
src/gnunet/build.sh | 2 +-
src/gnunet/cmd/gnunet-service-dht-go/main.go | 33 +-
src/gnunet/cmd/gnunet-service-gns-go/main.go | 9 +-
.../cmd/gnunet-service-revocation-go/main.go | 9 +-
src/gnunet/cmd/peer_mockup/main.go | 40 +-
src/gnunet/cmd/revoke-zonekey/main.go | 80 ++--
src/gnunet/cmd/vanityid/main.go | 10 +-
src/gnunet/config/config.go | 56 +--
src/gnunet/config/config_test.go | 2 +-
src/gnunet/config/gnunet-config.json | 14 +-
src/gnunet/core/core.go | 186 +++-----
src/gnunet/core/core_test.go | 32 +-
src/gnunet/core/event.go | 17 +
src/gnunet/core/hello_test.go | 1 -
src/gnunet/core/peer.go | 13 +-
src/gnunet/core/peer_test.go | 8 +-
src/gnunet/crypto/gns.go | 109 +++--
src/gnunet/crypto/gns_edkey.go | 46 +-
src/gnunet/crypto/gns_pkey.go | 45 +-
src/gnunet/crypto/gns_test.go | 18 +-
src/gnunet/crypto/hash.go | 13 +-
src/gnunet/crypto/key_exchange_test.go | 52 +--
src/gnunet/crypto/keys_test.go | 2 +-
src/gnunet/enums/blocktype_string.go | 2 +-
src/gnunet/enums/dht.go | 14 +-
src/gnunet/enums/dht_block_type.go | 3 +-
src/gnunet/enums/gns.go | 3 +
src/gnunet/enums/gns_type.go | 1 +
src/gnunet/enums/gnunet-dht.tpl | 3 +-
src/gnunet/enums/gnunet-gns.tpl | 1 +
src/gnunet/enums/gnunet-signature.tpl | 1 +
src/gnunet/enums/signature_purpose.go | 1 +
src/gnunet/go.mod | 3 +-
src/gnunet/go.sum | 6 +-
src/gnunet/message/factory.go | 12 +-
src/gnunet/message/msg_core.go | 30 +-
src/gnunet/message/msg_dht_p2p.go | 473 +++++++++++++++++++
src/gnunet/message/msg_hello.go | 20 +-
src/gnunet/message/msg_hello_dht.go | 167 -------
src/gnunet/message/msg_namecache.go | 2 +-
src/gnunet/message/msg_transport.go | 13 +-
src/gnunet/message/types.go | 5 +-
src/gnunet/service/client.go | 1 -
src/gnunet/service/connection.go | 42 +-
src/gnunet/service/dht/blocks/filters.go | 296 ++++++++++++
src/gnunet/service/dht/blocks/filters_test.go | 110 +++++
src/gnunet/service/dht/blocks/generic.go | 64 ++-
src/gnunet/service/dht/blocks/generic_test.go | 67 ---
src/gnunet/service/dht/blocks/gns.go | 31 +-
src/gnunet/service/dht/blocks/handlers.go | 80 ++++
src/gnunet/service/dht/blocks/hello.go | 253 +++++++++--
src/gnunet/service/dht/blocks/types.go | 26 --
src/gnunet/service/dht/bloomfilter.go | 123 -----
src/gnunet/service/dht/messages.go | 380 ++++++++++++++++
src/gnunet/service/dht/module.go | 155 +++++--
src/gnunet/service/dht/resulthandler.go | 351 ++++++++++++++
src/gnunet/service/dht/routingtable.go | 305 ++++++++-----
src/gnunet/service/dht/routingtable_test.go | 42 +-
src/gnunet/service/dht/rpc.go | 50 +-
src/gnunet/service/dht/service.go | 56 +--
src/gnunet/service/gns/block_handler.go | 6 +-
src/gnunet/service/gns/dns.go | 4 +-
src/gnunet/service/gns/module.go | 30 +-
src/gnunet/service/gns/rpc.go | 4 +-
src/gnunet/service/gns/service.go | 22 +-
src/gnunet/service/module.go | 20 +-
src/gnunet/service/namecache/module.go | 9 +-
src/gnunet/service/revocation/module.go | 7 +-
src/gnunet/service/revocation/pow.go | 27 +-
src/gnunet/service/revocation/pow_test.go | 1 -
src/gnunet/service/revocation/rpc.go | 4 +-
src/gnunet/service/revocation/service.go | 9 +-
src/gnunet/service/rpc.go | 29 +-
src/gnunet/service/service.go | 3 +-
src/gnunet/service/store.go | 502 ---------------------
src/gnunet/{util => service/store}/database.go | 63 +--
src/gnunet/service/{dht => store}/dhtstore_test.go | 15 +-
src/gnunet/service/store/store.go | 278 ++++++++++++
src/gnunet/service/store/store_fs.go | 287 ++++++++++++
src/gnunet/service/store/store_fs_meta.go | 174 +++++++
src/gnunet/service/store/store_fs_meta.sql | 29 ++
src/gnunet/test/gnunet-dhtu/main.go | 206 ---------
src/gnunet/transport/endpoint.go | 31 +-
src/gnunet/transport/reader_writer.go | 41 +-
src/gnunet/transport/responder.go | 8 +
src/gnunet/transport/transport.go | 32 +-
src/gnunet/util/address.go | 56 +--
src/gnunet/util/address_test.go | 13 +-
src/gnunet/util/array.go | 5 +
src/gnunet/util/base32.go | 4 +-
src/gnunet/util/base32_test.go | 2 +-
src/gnunet/util/fs.go | 2 +-
src/gnunet/util/{misc.go => map.go} | 78 ++--
src/gnunet/util/misc.go | 105 +----
src/gnunet/util/peer.go | 94 ++++
src/gnunet/util/peer_id.go | 59 ---
src/gnunet/util/rnd.go | 12 +-
src/gnunet/util/time.go | 17 +-
99 files changed, 4063 insertions(+), 2266 deletions(-)
diff --git a/README.md b/README.md
index 3373608..d4575e5 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,56 @@ chown gnunet:gnunet
/var/lib/gnunet/.local/share/gnunet/private_key.ecc
chmod 600 /var/lib/gnunet/.local/share/gnunet/private_key.ecc
```
-For gnunet-go configuration files you need to paste the result of
+For `gnunet-go` configuration files you need to paste the result of
`echo "<hex.seed>" | xxd -r -p | base64` into the `PrivateSeed` field in the
`NodeConfig` section.
+
+## Using gnunet-go in your own projects
+
+`gnunet-go` is not a standard Go module for direct use (via go.mod) in other
+packages, but designed as a stand-alone application. The rationale behind was
+to **not** hard link the code to a single Git provider.
+
+If you are interested in using (parts of) `gnunet-go` in your own projects, the
+following step-by-step instructions show the easiest route.
+
+* `git clone https://github.com/bfix/gnunet-go` into folder
+`/home/user/gnunet-go` (or any other folder if you adjust the instructions
+accordingly).
+
+* create project folder and change into it
+* run `go mod init test` (replace test with the name of your project)
+* create a simple test `main.go`
+
+```go
+package main
+
+import (
+ "crypto/rand"
+ "fmt"
+ "gnunet/util"
+)
+
+func main() {
+ a := make([]byte, 32)
+ rand.Read(a)
+ fmt.Println(util.EncodeBinaryToString(a))
+}
+```
+
+* edit `go.mod` and add at end of file:
+
+```bash
+require gnunet v0.1.27
+
+replace gnunet v0.1.27 => /home/user/gnunet-go/src/gnunet
+```
+
+* run `go mod tidy`
+* build test program: `go build`
+* run test program: `./test`
+
+The only disadvantage of this approach is that you have to update the source
+code for `gnunet-go` yourself. From time to time or on demand, do a `git pull`
+followed by a `go mod tidy` described above. No version control is supported
+either because the dependency for `gnunet-go` is redirected to a local folder.
diff --git a/src/gnunet/build.sh b/src/gnunet/build.sh
index 39ecbaf..20f6933 100755
--- a/src/gnunet/build.sh
+++ b/src/gnunet/build.sh
@@ -1,4 +1,4 @@
#!/bin/bash
-go generate ./...
+[ "$1" = "withgen" ] && go generate ./...
go install -v -gcflags "-N -l" ./...
diff --git a/src/gnunet/cmd/gnunet-service-dht-go/main.go
b/src/gnunet/cmd/gnunet-service-dht-go/main.go
index ef6da91..8e23ac2 100644
--- a/src/gnunet/cmd/gnunet-service-dht-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-dht-go/main.go
@@ -21,7 +21,6 @@ package main
import (
"context"
"flag"
- "net/rpc"
"os"
"os/signal"
"strings"
@@ -58,7 +57,7 @@ func main() {
flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet
configuration file")
flag.StringVar(&socket, "s", "", "GNS service socket")
flag.StringVar(¶m, "p", "", "socket parameters (<key>=<value>,...)")
- flag.IntVar(&logLevel, "L", logger.INFO, "DHT log level (default:
INFO)")
+ flag.IntVar(&logLevel, "L", logger.DBG, "DHT log level (default: DBG)")
flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
flag.Parse()
@@ -71,7 +70,7 @@ func main() {
// apply configuration
logger.SetLogLevel(logLevel)
if len(socket) == 0 {
- socket = config.Cfg.GNS.Service.Socket
+ socket = config.Cfg.DHT.Service.Socket
}
params := make(map[string]string)
if len(param) > 0 {
@@ -80,7 +79,7 @@ func main() {
params[kv[0]] = kv[1]
}
} else {
- params = config.Cfg.GNS.Service.Params
+ params = config.Cfg.DHT.Service.Params
}
// instantiate core service
@@ -93,8 +92,8 @@ func main() {
defer c.Shutdown()
// start a new DHT service
- var dhtSrv service.Service
- if dhtSrv, err = dht.NewService(ctx, c); err != nil {
+ var dhtSrv *dht.Service
+ if dhtSrv, err = dht.NewService(ctx, c, config.Cfg.DHT); err != nil {
logger.Printf(logger.ERROR, "[dht] failed to create DHT
service: %s\n", err.Error())
return
}
@@ -104,6 +103,14 @@ func main() {
return
}
+ // hande network size estimation: if a fixed number of peers are present
+ // in the network config, use that value; otherwise utilize the NSE
+ // algorithm (not implemented yet)
+ numPeers := config.Cfg.Network.NumPeers
+ if numPeers != 0 {
+ dhtSrv.SetNetworkSize(numPeers)
+ }
+
// handle command-line arguments for RPC
if len(rpcEndp) > 0 {
parts := strings.Split(rpcEndp, ":")
@@ -115,8 +122,8 @@ func main() {
}
// start JSON-RPC server on request
if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
- var rpc *rpc.Server
- if rpc, err = service.StartRPC(ctx, ep); err != nil {
+ var rpc *service.JRPCServer
+ if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
logger.Printf(logger.ERROR, "[dht] RPC failed to start:
%s", err.Error())
return
}
@@ -125,7 +132,7 @@ func main() {
// handle bootstrap: collect known addresses
bsList := make([]*util.Address, 0)
- for _, bs := range config.Cfg.Bootstrap.Nodes {
+ for _, bs := range config.Cfg.Network.Bootstrap {
// check for HELLO URL
if strings.HasPrefix(bs, "gnunet://hello/") {
var hb *blocks.HelloBlock
@@ -147,7 +154,9 @@ func main() {
}
// send HELLO to all bootstrap addresses
for _, addr := range bsList {
- c.SendHello(ctx, addr)
+ if err := dhtSrv.SendHello(ctx, addr); err != nil {
+ logger.Printf(logger.ERROR, "[dht] send HELLO failed:
%s", err.Error())
+ }
}
// handle OS signals
@@ -181,5 +190,7 @@ loop:
// terminating service
cancel()
- srv.Stop()
+ if err := srv.Stop(); err != nil {
+ logger.Printf(logger.ERROR, "[dht] Failed to stop service: %s",
err.Error())
+ }
}
diff --git a/src/gnunet/cmd/gnunet-service-gns-go/main.go
b/src/gnunet/cmd/gnunet-service-gns-go/main.go
index 72f7de6..ebf2151 100644
--- a/src/gnunet/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-gns-go/main.go
@@ -21,7 +21,6 @@ package main
import (
"context"
"flag"
- "net/rpc"
"os"
"os/signal"
"strings"
@@ -109,8 +108,8 @@ func main() {
}
// start JSON-RPC server on request
if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
- var rpc *rpc.Server
- if rpc, err = service.StartRPC(ctx, ep); err != nil {
+ var rpc *service.JRPCServer
+ if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
logger.Printf(logger.ERROR, "[gns] RPC failed to start:
%s", err.Error())
return
}
@@ -148,5 +147,7 @@ loop:
// terminating service
cancel()
- srv.Stop()
+ if err = srv.Stop(); err != nil {
+ logger.Printf(logger.ERROR, "[gns] Failed to stop service: %s",
err.Error())
+ }
}
diff --git a/src/gnunet/cmd/gnunet-service-revocation-go/main.go
b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
index 564f430..93c5432 100644
--- a/src/gnunet/cmd/gnunet-service-revocation-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
@@ -21,7 +21,6 @@ package main
import (
"context"
"flag"
- "net/rpc"
"os"
"os/signal"
"strings"
@@ -109,8 +108,8 @@ func main() {
}
// start JSON-RPC server on request
if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
- var rpc *rpc.Server
- if rpc, err = service.StartRPC(ctx, ep); err != nil {
+ var rpc *service.JRPCServer
+ if rpc, err = service.RunRPCServer(ctx, ep); err != nil {
logger.Printf(logger.ERROR, "[revocation] RPC failed to
start: %s", err.Error())
return
}
@@ -148,5 +147,7 @@ loop:
// terminating service
cancel()
- srv.Stop()
+ if err := srv.Stop(); err != nil {
+ logger.Printf(logger.ERROR, "[revocation] Failed to stop
service: %s", err.Error())
+ }
}
diff --git a/src/gnunet/cmd/peer_mockup/main.go
b/src/gnunet/cmd/peer_mockup/main.go
index 96f62da..4d29927 100644
--- a/src/gnunet/cmd/peer_mockup/main.go
+++ b/src/gnunet/cmd/peer_mockup/main.go
@@ -15,6 +15,7 @@ import (
"gnunet/message"
"gnunet/service"
+ "github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/logger"
)
@@ -33,10 +34,9 @@ var (
},
}
// configuration for remote node
- remoteCfg = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
- remoteAddr = "udp://172.17.0.5:2086"
+ remoteCfg = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
- // top-level variables used accross functions
+ // top-level variables used across functions
local *core.Peer // local peer (with private key)
remote *core.Peer // remote peer
c *core.Core
@@ -79,7 +79,9 @@ func main() {
if !asServer {
// we start the message exchange
- c.Send(ctx, remote.GetID(),
message.NewTransportTCPWelcomeMsg(c.PeerID()))
+ if err := c.Send(ctx, remote.GetID(),
message.NewTransportTCPWelcomeMsg(c.PeerID())); err != nil {
+ fmt.Printf("send message failed: %s", err.Error())
+ }
}
// handle OS signals
@@ -117,23 +119,27 @@ loop:
// process incoming messages and send responses; it is used for protocol
exploration only.
// it tries to mimick the message flow between "real" GNUnet peers.
func process(ctx context.Context, ev *core.Event) {
-
logger.Printf(logger.DBG, "<<< %s", ev.Msg.String())
switch msg := ev.Msg.(type) {
-
case *message.TransportTCPWelcomeMsg:
- c.Send(ctx, ev.Peer, message.NewTransportPingMsg(ev.Peer, nil))
+ if err := c.Send(ctx, ev.Peer,
message.NewTransportPingMsg(ev.Peer, nil)); err != nil {
+ logger.Printf(logger.ERROR, "TransportTCPWelcomeMsg
send failed: %s", err.Error())
+ return
+ }
case *message.HelloMsg:
case *message.TransportPingMsg:
mOut := message.NewTransportPongMsg(msg.Challenge, nil)
if err := mOut.Sign(local.PrvKey()); err != nil {
- logger.Println(logger.ERROR, "PONG: signing failed")
+ logger.Printf(logger.ERROR, "PONG signing failed: %s",
err.Error())
+ return
+ }
+ if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+ logger.Printf(logger.ERROR, "TransportPongMsg send
failed: %s", err.Error())
return
}
- c.Send(ctx, ev.Peer, mOut)
logger.Printf(logger.DBG, ">>> %s", mOut)
case *message.TransportPongMsg:
@@ -148,7 +154,9 @@ func process(ctx context.Context, ev *core.Event) {
case *message.SessionSynMsg:
mOut := message.NewSessionSynAckMsg()
mOut.Timestamp = msg.Timestamp
- c.Send(ctx, ev.Peer, mOut)
+ if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+ logger.Printf(logger.ERROR, "SessionSynAckMsg send
failed: %s", err.Error())
+ }
logger.Printf(logger.DBG, ">>> %s", mOut)
case *message.SessionQuotaMsg:
@@ -157,7 +165,9 @@ func process(ctx context.Context, ev *core.Event) {
case *message.SessionKeepAliveMsg:
mOut := message.NewSessionKeepAliveRespMsg(msg.Nonce)
- c.Send(ctx, ev.Peer, mOut)
+ if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+ logger.Printf(logger.ERROR, "SessionKeepAliveRespMsg
send failed: %s", err.Error())
+ }
logger.Printf(logger.DBG, ">>> %s", mOut)
case *message.EphemeralKeyMsg:
@@ -171,9 +181,13 @@ func process(ctx context.Context, ev *core.Event) {
}
remote.SetEphKeyMsg(msg)
mOut := local.EphKeyMsg()
- c.Send(ctx, ev.Peer, mOut)
+ if err := c.Send(ctx, ev.Peer, mOut); err != nil {
+ logger.Printf(logger.ERROR, "EphKeyMsg send failed:
%s", err.Error())
+ }
logger.Printf(logger.DBG, ">>> %s", mOut)
- secret = crypto.SharedSecret(local.EphPrvKey(),
remote.EphKeyMsg().Public())
+ pk :=
ed25519.NewPublicKeyFromBytes(remote.EphKeyMsg().Public().Data)
+ secret = crypto.SharedSecret(local.EphPrvKey(), pk)
+ fmt.Printf("Shared secret: %s\n", secret.String())
default:
fmt.Printf("!!! %v\n", msg)
diff --git a/src/gnunet/cmd/revoke-zonekey/main.go
b/src/gnunet/cmd/revoke-zonekey/main.go
index 0713582..fe56946 100644
--- a/src/gnunet/cmd/revoke-zonekey/main.go
+++ b/src/gnunet/cmd/revoke-zonekey/main.go
@@ -43,10 +43,10 @@ import (
// State of RevData calculation
const (
- S_NEW = iota // start new PoW calculation
- S_CONT // continue PoW calculation
- S_DONE // PoW calculation done
- S_SIGNED // revocation data signed
+ StateNew = iota // start new PoW calculation
+ StateCont // continue PoW calculation
+ StateDone // PoW calculation done
+ StateSigned // revocation data signed
)
// RevData is the storage layout for persistent data used by this program.
@@ -67,7 +67,7 @@ func ReadRevData(filename string, bits int, zk
*crypto.ZoneKey) (rd *RevData, er
Rd: revocation.NewRevDataCalc(zk),
Numbits: uint8(bits),
T: util.NewRelativeTime(0),
- State: S_NEW,
+ State: StateNew,
}
// read revocation object from file. If the file does not exist, a new
@@ -80,24 +80,23 @@ func ReadRevData(filename string, bits int, zk
*crypto.ZoneKey) (rd *RevData, er
dataBuf := make([]byte, rd.size())
var n int
if n, err = file.Read(dataBuf); err != nil {
- err = fmt.Errorf("Error reading file: " + err.Error())
+ err = fmt.Errorf("error reading file: " + err.Error())
return
}
if n != len(dataBuf) {
- err = fmt.Errorf("File size mismatch")
+ err = fmt.Errorf("file size mismatch")
return
}
if err = data.Unmarshal(&rd, dataBuf); err != nil {
- err = fmt.Errorf("File corrupted: " + err.Error())
+ err = fmt.Errorf("file corrupted: " + err.Error())
return
}
if !zk.Equal(&rd.Rd.RevData.ZoneKeySig.ZoneKey) {
- err = fmt.Errorf("Zone key mismatch")
+ err = fmt.Errorf("zone key mismatch")
return
}
- bits = int(rd.Numbits)
if err = file.Close(); err != nil {
- err = fmt.Errorf("Error closing file: " + err.Error())
+ err = fmt.Errorf("error closing file: " + err.Error())
}
return
}
@@ -106,24 +105,24 @@ func ReadRevData(filename string, bits int, zk
*crypto.ZoneKey) (rd *RevData, er
func (r *RevData) Write(filename string) (err error) {
var file *os.File
if file, err = os.Create(filename); err != nil {
- return fmt.Errorf("Can't write to output file: " + err.Error())
+ return fmt.Errorf("can't write to output file: " + err.Error())
}
var buf []byte
if buf, err = data.Marshal(r); err != nil {
- return fmt.Errorf("Internal error: " + err.Error())
+ return fmt.Errorf("internal error: " + err.Error())
}
if len(buf) != r.size() {
- return fmt.Errorf("Internal error: Buffer mismatch %d != %d",
len(buf), r.size())
+ return fmt.Errorf("internal error: Buffer mismatch %d != %d",
len(buf), r.size())
}
var n int
if n, err = file.Write(buf); err != nil {
- return fmt.Errorf("Can't write to output file: " + err.Error())
+ return fmt.Errorf("can't write to output file: " + err.Error())
}
if n != len(buf) {
- return fmt.Errorf("Can't write data to output file!")
+ return fmt.Errorf("can't write data to output file")
}
if err = file.Close(); err != nil {
- return fmt.Errorf("Error closing file: " + err.Error())
+ return fmt.Errorf("error closing file: " + err.Error())
}
return
}
@@ -138,7 +137,7 @@ func (r *RevData) size() int {
//
// (1) Generate the desired PoWs for the public zone key:
// This process can be started, stopped and resumed, so the long
-// calculation time (usually days or even weeks) can be interruped if
+// calculation time (usually days or even weeks) can be interrupted if
// desired. For security reasons you should only pass the "-z" argument to
// this step but not the "-k" argument (private key) as it is not required
// to calculate the PoWs.
@@ -164,7 +163,7 @@ func main() {
zonekey string // zonekey to be revoked
prvkey string // private zonekey (base64-encoded key data)
testing bool // test mode (no minimum difficulty)
- filename string // name of file for persistance
+ filename string // name of file for persistence
)
minDiff := revocation.MinDifficulty
flag.IntVar(&bits, "b", minDiff+1, "Number of leading zero bits")
@@ -227,27 +226,27 @@ func main() {
// handle revocation data state
switch rd.State {
- case S_NEW:
+ case StateNew:
log.Println("Starting new revocation calculation...")
- rd.State = S_CONT
+ rd.State = StateCont
- case S_CONT:
+ case StateCont:
log.Printf("Revocation calculation started at %s\n",
rd.Rd.Timestamp.String())
log.Printf("Time spent on calculation: %s\n", rd.T.String())
log.Printf("Last tested PoW value: %d\n", rd.Last)
log.Println("Continuing...")
- case S_DONE:
+ case StateDone:
// calculation complete: sign with private key
if sk == nil {
log.Fatal("Need to sign revocation: private key is
missing.")
}
log.Println("Signing revocation with private key")
- if err := rd.Rd.Sign(sk); err != nil {
+ if err = rd.Rd.Sign(sk); err != nil {
log.Fatal("Failed to sign revocation: " + err.Error())
}
// write final revocation
- rd.State = S_SIGNED
+ rd.State = StateSigned
if err = rd.Write(filename); err != nil {
log.Fatal("Failed to write revocation: " + err.Error())
}
@@ -278,10 +277,10 @@ func main() {
// The calculation was interrupted; we still need to
compute
// more and better PoWs...
log.Printf("Incomplete revocation: Only %f zero bits on
average!\n", average)
- rd.State = S_CONT
+ rd.State = StateCont
} else {
// we have reached the required PoW difficulty
- rd.State = S_DONE
+ rd.State = StateDone
// check if we have a valid revocation.
log.Println("Revocation calculation complete:")
diff, rc := rd.Rd.Verify(false)
@@ -313,22 +312,19 @@ func main() {
sigCh := make(chan os.Signal, 5)
signal.Notify(sigCh)
loop:
- for {
- select {
+ for sig := range sigCh {
// handle OS signals
- case sig := <-sigCh:
- switch sig {
- case syscall.SIGKILL, syscall.SIGINT,
syscall.SIGTERM:
- log.Printf("Terminating (on signal
'%s')\n", sig)
- cancelFcn()
- break loop
- case syscall.SIGHUP:
- log.Println("SIGHUP")
- case syscall.SIGURG:
- // TODO:
https://github.com/golang/go/issues/37942
- default:
- log.Println("Unhandled signal: " +
sig.String())
- }
+ switch sig {
+ case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
+ log.Printf("Terminating (on signal '%s')\n",
sig)
+ cancelFcn()
+ break loop
+ case syscall.SIGHUP:
+ log.Println("SIGHUP")
+ case syscall.SIGURG:
+ // TODO:
https://github.com/golang/go/issues/37942
+ default:
+ log.Println("Unhandled signal: " + sig.String())
}
}
}()
diff --git a/src/gnunet/cmd/vanityid/main.go b/src/gnunet/cmd/vanityid/main.go
index 938df61..1c0bf24 100644
--- a/src/gnunet/cmd/vanityid/main.go
+++ b/src/gnunet/cmd/vanityid/main.go
@@ -8,8 +8,9 @@ import (
"regexp"
"time"
- "github.com/bfix/gospel/crypto/ed25519"
"gnunet/util"
+
+ "github.com/bfix/gospel/crypto/ed25519"
)
func main() {
@@ -32,16 +33,13 @@ func main() {
seed := make([]byte, 32)
start := time.Now()
for i := 0; ; i++ {
- n, err := rand.Read(seed)
- if err != nil || n != 32 {
- panic(err)
- }
+ _, _ = rand.Read(seed)
prv := ed25519.NewPrivateKeyFromSeed(seed)
pub := prv.Public().Bytes()
id := util.EncodeBinaryToString(pub)
for _, r := range reg {
if r.MatchString(id) {
- elapsed := time.Now().Sub(start)
+ elapsed := time.Since(start)
s1 := hex.EncodeToString(seed)
s2 := hex.EncodeToString(prv.D.Bytes())
fmt.Printf("%s [%s][%s] (%d tries, %s
elapsed)\n", id, s1, s2, i, elapsed)
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index b4d2840..c4d3722 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -21,6 +21,7 @@ package config
import (
"encoding/json"
"fmt"
+ "gnunet/util"
"io/ioutil"
"reflect"
"regexp"
@@ -56,12 +57,13 @@ type NodeConfig struct {
}
//----------------------------------------------------------------------
-// Bootstrap configuration
+// Network configuration
//----------------------------------------------------------------------
-// BootstrapConfig holds parameters for the initial connection to the network.
-type BootstrapConfig struct {
- Nodes []string `json:"nodes"` // bootstrap nodes
+// NetworkConfig holds parameters for the initial connection to the network.
+type NetworkConfig struct {
+ Bootstrap []string `json:"bootstrap"` // bootstrap nodes
+ NumPeers int `json:"numPeers"` // estimated number of peers (0 =
use NSE)
}
//----------------------------------------------------------------------
@@ -93,34 +95,21 @@ type GNSConfig struct {
MaxDepth int `json:"maxDepth"` // maximum recursion
depth in resolution
}
-//----------------------------------------------------------------------
-// Generic parameter configuration (handle any key/value settings)
-//----------------------------------------------------------------------
-
-// ParameterConfig handle arbitrary values for a key strings. This necessary
-// e.g. in the 'Storage' configuration, as custom storage implementations
-// require different sets of parameters.
-type ParameterConfig map[string]any
-
-// Get a parameter value with given type 'V'
-func GetParam[V any](params ParameterConfig, key string) (i V, ok bool) {
- var v any
- if v, ok = params[key]; ok {
- if i, ok = v.(V); ok {
- return
- }
- }
- return
-}
-
//----------------------------------------------------------------------
// DHT configuration
//----------------------------------------------------------------------
// DHTConfig contains parameters for the distributed hash table (DHT)
type DHTConfig struct {
- Service *ServiceConfig `json:"service"` // socket for DHT service
- Storage ParameterConfig `json:"storage"` // filesystem storage location
+ Service *ServiceConfig `json:"service"` // socket for DHT service
+ Storage util.ParameterSet `json:"storage"` // filesystem storage
location
+ Routing *RoutingConfig `json:"routing"` // routing table
configuration
+ Heartbeat int `json:"heartbeat"` // heartbeat intervall
+}
+
+// RoutingConfig holds parameters for routing tables
+type RoutingConfig struct {
+ PeerTTL int `json:"peerTTL"` // time-out for peers in table
}
//----------------------------------------------------------------------
@@ -129,8 +118,8 @@ type DHTConfig struct {
// NamecacheConfig contains parameters for the local name cache
type NamecacheConfig struct {
- Service *ServiceConfig `json:"service"` // socket for Namecache service
- Storage ParameterConfig `json:"storage"` // key/value cache
+ Service *ServiceConfig `json:"service"` // socket for Namecache
service
+ Storage util.ParameterSet `json:"storage"` // key/value cache
}
//----------------------------------------------------------------------
@@ -139,8 +128,8 @@ type NamecacheConfig struct {
// RevocationConfig contains parameters for the key revocation service
type RevocationConfig struct {
- Service *ServiceConfig `json:"service"` // socket for Revocation
service
- Storage ParameterConfig `json:"storage"` // persistance mechanism for
revocation data
+ Service *ServiceConfig `json:"service"` // socket for Revocation
service
+ Storage util.ParameterSet `json:"storage"` // persistence mechanism for
revocation data
}
//----------------------------------------------------------------------
@@ -153,7 +142,7 @@ type Environment map[string]string
// Config is the aggregated configuration for GNUnet.
type Config struct {
Local *NodeConfig `json:"local"`
- Bootstrap *BootstrapConfig `json:"bootstrap"`
+ Network *NetworkConfig `json:"network"`
Env Environment `json:"environ"`
RPC *RPCConfig `json:"rpc"`
DHT *DHTConfig `json:"dht"`
@@ -193,7 +182,7 @@ func ParseConfigBytes(data []byte, subst bool) (err error) {
}
var (
- rx = regexp.MustCompile("\\$\\{([^\\}]*)\\}")
+ rx = regexp.MustCompile(`\$\{([^\}]*)\}`)
)
// substString is a helper function to substitute environment variables
@@ -215,7 +204,6 @@ func substString(s string, env map[string]string) string {
// applySubstitutions traverses the configuration data structure
// and applies string substitutions to all string values.
func applySubstitutions(x interface{}, env map[string]string) {
-
var process func(v reflect.Value)
process = func(v reflect.Value) {
for i := 0; i < v.NumField(); i++ {
@@ -224,7 +212,7 @@ func applySubstitutions(x interface{}, env
map[string]string) {
switch fld.Kind() {
case reflect.String:
// check for substitution
- s := fld.Interface().(string)
+ s, _ := fld.Interface().(string)
for {
s1 := substString(s, env)
if s1 == s {
diff --git a/src/gnunet/config/config_test.go b/src/gnunet/config/config_test.go
index d82019e..b61cb67 100644
--- a/src/gnunet/config/config_test.go
+++ b/src/gnunet/config/config_test.go
@@ -35,7 +35,7 @@ func TestConfigRead(t *testing.T) {
t.Fatal(err)
}
// parse configuration
- if err := ParseConfigBytes(data, false); err != nil {
+ if err = ParseConfigBytes(data, false); err != nil {
t.Fatal(err)
}
// write configuration
diff --git a/src/gnunet/config/gnunet-config.json
b/src/gnunet/config/gnunet-config.json
index 167bfa0..27678ab 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -1,8 +1,10 @@
{
- "bootstrap": {
- "nodes": [
+ "network": {
+ "bootstrap": [
+ "ip+udp://172.17.0.5:10000",
"gnunet://hello/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHNBJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R/1653499308?r5n+ip+udp=127.0.0.1%3A7654"
- ]
+ ],
+ "numPeers": 10
},
"local": {
"name": "ygng",
@@ -33,7 +35,11 @@
"cache": false,
"path": "/var/lib/gnunet/dht/store",
"maxGB": 10
- }
+ },
+ "routing": {
+ "peerTTL": 10800
+ },
+ "heartbeat": 900
},
"gns": {
"service": {
diff --git a/src/gnunet/core/core.go b/src/gnunet/core/core.go
index 83da8b1..128d154 100644
--- a/src/gnunet/core/core.go
+++ b/src/gnunet/core/core.go
@@ -20,10 +20,10 @@ package core
import (
"context"
+ "encoding/hex"
"errors"
"gnunet/config"
"gnunet/message"
- "gnunet/service/dht/blocks"
"gnunet/transport"
"gnunet/util"
"net"
@@ -47,7 +47,7 @@ type EndpointRef struct {
id string // endpoint identifier in configuration
ep transport.Endpoint // reference to endpoint
addr *util.Address // public endpoint address
- upnpId string // UPNP identifier (empty if unused)
+ upnpID string // UPNP identifier (empty if unused)
}
//----------------------------------------------------------------------
@@ -57,7 +57,7 @@ type Core struct {
local *Peer
// incoming messages from transport
- incoming chan *transport.TransportMessage
+ incoming chan *transport.Message
// reference to transport implementation
trans *transport.Transport
@@ -68,37 +68,39 @@ type Core struct {
// list of known peers with addresses
peers *util.PeerAddrList
+ // list of connected peers
+ connected *util.Map[string, bool]
+
// List of registered endpoints
endpoints map[string]*EndpointRef
-
- // last HELLO message used; re-create if expired
- lastHello *message.HelloDHTMsg
}
//----------------------------------------------------------------------
// NewCore creates and runs a new core instance.
func NewCore(ctx context.Context, node *config.NodeConfig) (c *Core, err
error) {
-
// instantiate peer
var peer *Peer
if peer, err = NewLocalPeer(node); err != nil {
return
}
+ logger.Printf(logger.DBG, "[core] Local node is %s",
peer.GetID().String())
+
// create new core instance
- incoming := make(chan *transport.TransportMessage)
+ incoming := make(chan *transport.Message)
c = &Core{
local: peer,
incoming: incoming,
listeners: make(map[string]*Listener),
trans: transport.NewTransport(ctx, node.Name, incoming),
peers: util.NewPeerAddrList(),
+ connected: util.NewMap[string, bool](),
endpoints: make(map[string]*EndpointRef),
}
// add all local peer endpoints to transport.
for _, epCfg := range node.Endpoints {
var (
- upnpId string // upnp identifier
+ upnpID string // upnp identifier
local *util.Address // local address
remote *util.Address // remote address
ep transport.Endpoint // endpoint reference
@@ -113,7 +115,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig)
(c *Core, err error)
// handle UPNP port forwarding
protocol := transport.EpProtocol(epCfg.Network)
var localA, remoteA string
- if upnpId, remoteA, localA, err =
c.trans.ForwardOpen(protocol, epCfg.Address[5:], epCfg.Port); err != nil {
+ if upnpID, remoteA, localA, err =
c.trans.ForwardOpen(protocol, epCfg.Address[5:], epCfg.Port); err != nil {
return
}
// parse local and remote addresses
@@ -129,7 +131,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig)
(c *Core, err error)
return
}
remote = local
- upnpId = ""
+ upnpID = ""
}
// add endpoint for address
if ep, err = c.trans.AddEndpoint(ctx, local); err != nil {
@@ -148,7 +150,7 @@ func NewCore(ctx context.Context, node *config.NodeConfig)
(c *Core, err error)
id: epCfg.ID,
ep: ep,
addr: remote,
- upnpId: upnpId,
+ upnpID: upnpID,
}
}
// run message pump
@@ -165,35 +167,20 @@ func (c *Core) pump(ctx context.Context) {
case tm := <-c.incoming:
logger.Printf(logger.DBG, "[core] Message received from
%s: %s", tm.Peer, transport.Dump(tm.Msg, "json"))
- // inspect message for peer state events
- var ev *Event
- switch msg := tm.Msg.(type) {
- case *message.HelloDHTMsg:
-
- // verify integrity of message
- if ok, err := msg.Verify(tm.Peer); !ok || err
!= nil {
- logger.Println(logger.WARN, "[core]
Received invalid DHT_P2P_HELLO message")
- break
- }
- // keep peer addresses
- aList, err := msg.Addresses()
- if err != nil {
- logger.Println(logger.WARN, "[core]
Failed to parse addresses from DHT_P2P_HELLO message")
- break
- }
- if err := c.Learn(ctx, tm.Peer, aList); err !=
nil {
- logger.Println(logger.WARN, "[core]
Failed to learn addresses from DHT_P2P_HELLO message: "+err.Error())
- break
- }
-
+ // check if peer is already connected (has an entry in
PeerAddrist)
+ _, connected := c.connected.Get(tm.Peer.String())
+ if !connected {
+ // no: mark connected
+ c.connected.Put(tm.Peer.String(), true)
// generate EV_CONNECT event
- ev = &Event{
+ c.dispatch(&Event{
ID: EV_CONNECT,
Peer: tm.Peer,
- Msg: msg,
- }
- c.dispatch(ev)
+ })
+ // grace period for connection signal
+ time.Sleep(time.Second)
}
+
// set default responder (core) if no custom responder
// is defined by the receiving endpoint.
resp := tm.Resp
@@ -204,13 +191,12 @@ func (c *Core) pump(ctx context.Context) {
}
}
// generate EV_MESSAGE event
- ev = &Event{
+ c.dispatch(&Event{
ID: EV_MESSAGE,
Peer: tm.Peer,
Msg: tm.Msg,
- Resp: tm.Resp,
- }
- c.dispatch(ev)
+ Resp: resp,
+ })
// wait for termination
case <-ctx.Done():
@@ -234,12 +220,12 @@ func (c *Core) Send(ctx context.Context, peer
*util.PeerID, msg message.Message)
netw := "ip+udp"
// try all addresses for peer
- aList := c.peers.Get(peer.String(), netw)
+ aList := c.peers.Get(peer, netw)
maybe := false // message may be sent...
for _, addr := range aList {
- logger.Printf(logger.WARN, "[core] Trying to send to %s",
addr.URI())
+ logger.Printf(logger.INFO, "[core] Trying to send to %s",
addr.URI())
// send message to address
- if err = c.send(ctx, addr, msg); err != nil {
+ if err = c.SendToAddr(ctx, addr, msg); err != nil {
// if it is possible that the message was not sent, try
next address
if err != transport.ErrEndpMaybeSent {
logger.Printf(logger.WARN, "[core] Failed to
send to %s: %s", addr.URI(), err.Error())
@@ -252,15 +238,16 @@ func (c *Core) Send(ctx context.Context, peer
*util.PeerID, msg message.Message)
return
}
if maybe {
- err = transport.ErrEndpMaybeSent
+ logger.Printf(logger.WARN, "[core] %s",
transport.ErrEndpMaybeSent.Error())
+ err = nil
} else {
err = ErrCoreNotSent
}
return
}
-// send message directly to address
-func (c *Core) send(ctx context.Context, addr *util.Address, msg
message.Message) error {
+// SendToAddr message directly to address
+func (c *Core) SendToAddr(ctx context.Context, addr *util.Address, msg
message.Message) error {
// assemble transport message
tm := transport.NewTransportMessage(c.PeerID(), msg)
// send on transport
@@ -268,81 +255,16 @@ func (c *Core) send(ctx context.Context, addr
*util.Address, msg message.Message
}
// Learn (new) addresses for peer
-func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addrs
[]*util.Address) (err error) {
+func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addrs
[]*util.Address) (newPeer bool) {
// learn all addresses for peer
- newPeer := false
+ newPeer = false
for _, addr := range addrs {
logger.Printf(logger.INFO, "[core] Learning %s for %s (expires
%s)", addr.URI(), peer, addr.Expires)
- newPeer = (c.peers.Add(peer.String(), addr) == 1) || newPeer
- }
- // new peer detected?
- if newPeer {
- // we added a previously unknown peer: send a HELLO
- var msg *message.HelloDHTMsg
- if msg, err = c.getHello(); err != nil {
- return
- }
- logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s",
peer, msg)
- err = c.Send(ctx, peer, msg)
- // no error if the message might have been sent
- if err == transport.ErrEndpMaybeSent {
- err = nil
- }
+ newPeer = (c.peers.Add(peer, addr) == 1) || newPeer
}
return
}
-// Send the currently active HELLO to given network address
-func (c *Core) SendHello(ctx context.Context, addr *util.Address) (err error) {
- // get (buffered) HELLO
- var msg *message.HelloDHTMsg
- if msg, err = c.getHello(); err != nil {
- return
- }
- logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s",
addr.URI(), msg)
- return c.send(ctx, addr, msg)
-}
-
-// get the recent HELLO if it is defined and not expired;
-// create a new HELLO otherwise.
-func (c *Core) getHello() (msg *message.HelloDHTMsg, err error) {
- if c.lastHello == nil || c.lastHello.Expires.Expired() {
- // assemble new HELLO message
- addrList := make([]*util.Address, 0)
- for _, epRef := range c.endpoints {
- addrList = append(addrList, epRef.addr)
- }
- node := c.local
- var hello *blocks.HelloBlock
- hello, err = node.HelloData(time.Hour, addrList)
- if err != nil {
- return
- }
- msg = message.NewHelloDHTMsg()
- msg.NumAddr = uint16(len(hello.Addresses()))
- msg.SetAddresses(hello.Addresses())
- if err = msg.Sign(c.local.prv); err != nil {
- return
- }
- // save for later use
- c.lastHello = msg
-
- // DEBUG
- var ok bool
- if ok, err = msg.Verify(c.PeerID()); !ok || err != nil {
- if !ok {
- err = errors.New("[core] failed to verify own
HELLO")
- }
- logger.Println(logger.ERROR, err.Error())
- return
- }
- logger.Println(logger.DBG, "[core] New HELLO:
"+transport.Dump(msg, "json"))
- return
- }
- // we have a valid HELLO for re-use.
- return c.lastHello, nil
-}
-
// Addresses returns the list of listening endpoint addresses
func (c *Core) Addresses() (list []*util.Address, err error) {
for _, epRef := range c.endpoints {
@@ -365,6 +287,29 @@ func (c *Core) PeerID() *util.PeerID {
//----------------------------------------------------------------------
+// Signable interface for objects that can get signed by peer
+type Signable interface {
+ // SignedData returns the byte array to be signed
+ SignedData() []byte
+
+ // SetSignature returns the signature to the signable object
+ SetSignature(*util.PeerSignature) error
+}
+
+// Sign a signable onject with private peer key
+func (c *Core) Sign(obj Signable) error {
+ sd := obj.SignedData()
+ logger.Printf(logger.DBG, "[core] Signing data '%s'",
hex.EncodeToString(sd))
+ sig, err := c.local.prv.EdSign(sd)
+ if err != nil {
+ return err
+ }
+ logger.Printf(logger.DBG, "[core] --> signature '%s'",
hex.EncodeToString(sig.Bytes()))
+ return obj.SetSignature(util.NewPeerSignature(sig.Bytes()))
+}
+
+//----------------------------------------------------------------------
+
// TryConnect is a function which allows the local peer to attempt the
// establishment of a connection to another peer using an address.
// When the connection attempt is successful, information on the new
@@ -390,14 +335,6 @@ func (c *Core) Hold(peer *util.PeerID) {}
// established by HOLD().
func (c *Core) Drop(peer *util.PeerID) {}
-// L2NSE is ESTIMATE_NETWORK_SIZE(), a procedure that provides estimates
-// on the base-2 logarithm of the network size L2NSE, that is the base-2
-// logarithm number of peers in the network, for use by the routing
-// algorithm.
-func (c *Core) L2NSE() float64 {
- return 0.
-}
-
//----------------------------------------------------------------------
// Event listener and event dispatch.
//----------------------------------------------------------------------
@@ -418,11 +355,12 @@ func (c *Core) Unregister(name string) *Listener {
// internal: dispatch event to listeners
func (c *Core) dispatch(ev *Event) {
+ logger.Printf(logger.DBG, "[core] Dispatching %v...", ev)
// dispatch event to listeners
for _, l := range c.listeners {
if l.filter.CheckEvent(ev.ID) {
- mt := ev.Msg.Header().MsgType
if ev.ID == EV_MESSAGE {
+ mt := ev.Msg.Header().MsgType
if mt != 0 && !l.filter.CheckMsgType(mt) {
// skip event
return
diff --git a/src/gnunet/core/core_test.go b/src/gnunet/core/core_test.go
index 29f740b..f87dfef 100644
--- a/src/gnunet/core/core_test.go
+++ b/src/gnunet/core/core_test.go
@@ -36,7 +36,6 @@ import (
// TestCoreSimple test a two node network
func TestCoreSimple(t *testing.T) {
-
var (
peer1Cfg = &config.NodeConfig{
Name: "p1",
@@ -75,11 +74,11 @@ func TestCoreSimple(t *testing.T) {
}()
// create and run nodes
- node1, err := NewTestNode(t, ctx, peer1Cfg)
+ node1, err := NewTestNode(ctx, t, peer1Cfg)
if err != nil {
t.Fatal(err)
}
- node2, err := NewTestNode(t, ctx, peer2Cfg)
+ node2, err := NewTestNode(ctx, t, peer2Cfg)
if err != nil {
t.Fatal(err)
}
@@ -104,7 +103,6 @@ func TestCoreSimple(t *testing.T) {
// TestCoreSimple test a two node network
func TestCoreUPNP(t *testing.T) {
-
// configuration data
var (
peer1Cfg = &config.NodeConfig{
@@ -143,12 +141,16 @@ func TestCoreUPNP(t *testing.T) {
}()
// create and run nodes
- node1, err := NewTestNode(t, ctx, peer1Cfg)
+ node1, err := NewTestNode(ctx, t, peer1Cfg)
if err != nil {
+ if err == transport.ErrTransNoUPNP {
+ t.Log("No UPnP available -- skipping test")
+ return
+ }
t.Fatal(err)
}
defer node1.Shutdown()
- node2, err := NewTestNode(t, ctx, peer2Cfg)
+ node2, err := NewTestNode(ctx, t, peer2Cfg)
if err != nil {
t.Fatal(err)
}
@@ -180,7 +182,7 @@ func TestDHTU(t *testing.T) {
}
// convert arguments
var (
- rId *util.PeerID
+ rID *util.PeerID
rAddr *util.Address
err error
)
@@ -211,14 +213,14 @@ func TestDHTU(t *testing.T) {
}()
// create and run node
- node, err := NewTestNode(t, ctx, peerCfg)
+ node, err := NewTestNode(ctx, t, peerCfg)
if err != nil {
log.Fatal(err)
}
defer node.Shutdown()
// learn bootstrap address (triggers HELLO)
- node.Learn(ctx, rId, rAddr)
+ node.Learn(ctx, rID, rAddr)
// run forever
var ch chan struct{}
@@ -246,14 +248,12 @@ func (n *TestNode) Learn(ctx context.Context, peer
*util.PeerID, addr *util.Addr
if peer != nil {
label = peer.String()
}
- n.t.Logf("[%d] Learning %s for %s", n.id, addr.StringAll(), label)
- if err := n.core.Learn(ctx, peer, []*util.Address{addr}); err != nil {
- n.t.Log("Learn: " + err.Error())
- }
+ n.t.Logf("[%d] Learning %s for %s", n.id, addr.URI(), label)
+ n.core.Learn(ctx, peer, []*util.Address{addr})
}
-func NewTestNode(t *testing.T, ctx context.Context, cfg *config.NodeConfig)
(node *TestNode, err error) {
-
+func NewTestNode(ctx context.Context, t *testing.T, cfg *config.NodeConfig)
(node *TestNode, err error) {
+ t.Helper()
// create test node
node = new(TestNode)
node.t = t
@@ -265,7 +265,7 @@ func NewTestNode(t *testing.T, ctx context.Context, cfg
*config.NodeConfig) (nod
}
node.peer = node.core.Peer()
t.Logf("[%d] Node %s starting", node.id, node.peer.GetID())
- t.Logf("[%d] --> %s", node.id,
hex.EncodeToString(node.peer.GetID().Key))
+ t.Logf("[%d] --> %s", node.id,
hex.EncodeToString(node.peer.GetID().Data))
list, err := node.core.Addresses()
if err != nil {
diff --git a/src/gnunet/core/event.go b/src/gnunet/core/event.go
index 8d05b0d..7d7c7e1 100644
--- a/src/gnunet/core/event.go
+++ b/src/gnunet/core/event.go
@@ -19,6 +19,7 @@
package core
import (
+ "fmt"
"gnunet/message"
"gnunet/transport"
"gnunet/util"
@@ -29,6 +30,7 @@ import (
//----------------------------------------------------------------------
// Event types
+//nolint:stylecheck // allow non-camel-case in constants
const (
EV_CONNECT = iota // peer connected
EV_DISCONNECT // peer disconnected
@@ -82,6 +84,8 @@ func (f *EventFilter) CheckMsgType(mt uint16) bool {
return ok
}
+//----------------------------------------------------------------------
+
// Event sent to listeners
type Event struct {
ID int // event type
@@ -91,6 +95,19 @@ type Event struct {
Label string // event label (can be empty)
}
+// String returns a human-readable representation of an event.
+func (e *Event) String() string {
+ s := "Event{"
+ if len(e.Label) > 0 {
+ s += "label=" + e.Label + ","
+ }
+ s += fmt.Sprintf("id=%d,peer=%s", e.ID, e.Peer)
+ if e.Msg != nil {
+ s += fmt.Sprintf(",msg=%d", e.Msg.Header().MsgType)
+ }
+ return s + "}"
+}
+
//----------------------------------------------------------------------
// Listener for network events
diff --git a/src/gnunet/core/hello_test.go b/src/gnunet/core/hello_test.go
index 4abad04..0590c90 100644
--- a/src/gnunet/core/hello_test.go
+++ b/src/gnunet/core/hello_test.go
@@ -71,7 +71,6 @@ func TestHelloURLDirect(t *testing.T) {
}
func TestHelloURL(t *testing.T) {
-
// prepare peer and HELLO data
peer, err := NewLocalPeer(peerCfg)
if err != nil {
diff --git a/src/gnunet/core/peer.go b/src/gnunet/core/peer.go
index 86ed43a..5b0c3d0 100644
--- a/src/gnunet/core/peer.go
+++ b/src/gnunet/core/peer.go
@@ -103,11 +103,16 @@ func (p *Peer) HelloData(ttl time.Duration, a
[]*util.Address) (h *blocks.HelloB
// assemble HELLO data
h = new(blocks.HelloBlock)
h.PeerID = p.GetID()
- h.Expire = util.NewAbsoluteTime(time.Now().Add(ttl))
+ h.Expires = util.NewAbsoluteTime(time.Now().Add(ttl))
h.SetAddresses(a)
// sign data
- err = h.Sign(p.prv)
+ sd := h.SignedData()
+ var sig *ed25519.EdSignature
+ if sig, err = p.prv.EdSign(sd); err != nil {
+ return
+ }
+ err = h.SetSignature(util.NewPeerSignature(sig.Bytes()))
return
}
@@ -139,7 +144,7 @@ func (p *Peer) PubKey() *ed25519.PublicKey {
// GetID returns the node ID (public key) in binary format
func (p *Peer) GetID() *util.PeerID {
return &util.PeerID{
- Key: util.Clone(p.pub.Bytes()),
+ Data: util.Clone(p.pub.Bytes()),
}
}
@@ -151,7 +156,7 @@ func (p *Peer) GetIDString() string {
// Sign a message with the (long-term) private key.
func (p *Peer) Sign(msg []byte) (*ed25519.EdSignature, error) {
if p.prv == nil {
- return nil, fmt.Errorf("No private key")
+ return nil, fmt.Errorf("no private key")
}
return p.prv.EdSign(msg)
}
diff --git a/src/gnunet/core/peer_test.go b/src/gnunet/core/peer_test.go
index f546a52..1be4d42 100644
--- a/src/gnunet/core/peer_test.go
+++ b/src/gnunet/core/peer_test.go
@@ -43,7 +43,6 @@ var (
)
func TestPeerHello(t *testing.T) {
-
// generate new local node
node, err := NewLocalPeer(cfg)
if err != nil {
@@ -54,13 +53,16 @@ func TestPeerHello(t *testing.T) {
// This hack will only work for direct listening addresses
addrList := make([]*util.Address, 0)
for _, epRef := range cfg.Endpoints {
- addr, err := util.ParseAddress(epRef.Addr())
- if err != nil {
+ var addr *util.Address
+ if addr, err = util.ParseAddress(epRef.Addr()); err != nil {
t.Fatal(err)
}
addrList = append(addrList, addr)
}
h, err := node.HelloData(TTL, addrList)
+ if err != nil {
+ t.Fatal(err)
+ }
// convert to URL and back
u := h.URL()
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index d62a047..27a52d3 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -28,6 +28,7 @@ import (
"gnunet/util"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
"github.com/bfix/gospel/math"
"golang.org/x/crypto/hkdf"
)
@@ -105,7 +106,7 @@ type ZoneKeyImpl interface {
// Derive a zone key from this zone key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
- Derive(h *math.Int) (ZoneKeyImpl, *math.Int)
+ Derive(h *math.Int) (ZoneKeyImpl, *math.Int, error)
// BlockKey returns the key for block en-/decryption
BlockKey(label string, expires util.AbsoluteTime) (skey []byte)
@@ -126,7 +127,7 @@ type ZonePrivateImpl interface {
// Derive a private key from this zone key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
- Derive(h *math.Int) (ZonePrivateImpl, *math.Int)
+ Derive(h *math.Int) (ZonePrivateImpl, *math.Int, error)
// Sign binary data and return the signature
Sign(data []byte) (*ZoneSignature, error)
@@ -143,10 +144,14 @@ type ZoneSigImpl interface {
//----------------------------------------------------------------------
// Zone types
//----------------------------------------------------------------------
+
+//nolint:stylecheck // allow non-camel-case in constants
var (
ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY)
ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
+)
+var (
// register available zone types for BlockHandler
ZoneTypes = []enums.GNSType{
enums.GNS_TYPE_PKEY,
@@ -177,6 +182,7 @@ var (
// Error codes
var (
ErrNoImplementation = errors.New("unknown zone implementation")
+ ErrUnknownZoneType = errors.New("unknown zone type")
)
// GetImplementation return the factory for a given zone type.
@@ -204,14 +210,14 @@ type ZonePrivate struct {
}
// NewZonePrivate returns a new initialized ZonePrivate instance
-func NewZonePrivate(ztype uint32, d []byte) (*ZonePrivate, error) {
+func NewZonePrivate(ztype uint32, d []byte) (zp *ZonePrivate, err error) {
// get factory for given zone type
impl, ok := zoneImpl[ztype]
if !ok {
return nil, ErrNoImplementation
}
// assemble private zone key
- zp := &ZonePrivate{
+ zp = &ZonePrivate{
ZoneKey{
ztype,
nil,
@@ -220,11 +226,13 @@ func NewZonePrivate(ztype uint32, d []byte)
(*ZonePrivate, error) {
nil,
}
zp.impl = impl.NewPrivate()
- zp.impl.Init(d)
+ if err = zp.impl.Init(d); err != nil {
+ return
+ }
zp.ZoneKey.KeyData = zp.impl.Public().Bytes()
zp.ZoneKey.impl = impl.NewPublic()
- zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
- return zp, nil
+ err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+ return
}
// KeySize returns the number of bytes of a key representation.
@@ -237,17 +245,18 @@ func (zp *ZonePrivate) KeySize() uint {
}
// Derive key (key blinding)
-func (zp *ZonePrivate) Derive(label, context string) (*ZonePrivate, *math.Int)
{
+func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h
*math.Int, err error) {
// get factory for given zone type
impl := zoneImpl[zp.Type]
- // caclulate derived key
- h := deriveH(zp.impl.Bytes(), label, context)
+ // calculate derived key
+ h = deriveH(zp.impl.Bytes(), label, context)
var derived ZonePrivateImpl
- derived, h = zp.impl.Derive(h)
-
+ if derived, h, err = zp.impl.Derive(h); err != nil {
+ return
+ }
// assemble derived pivate key
- dzp := &ZonePrivate{
+ dzp = &ZonePrivate{
ZoneKey{
zp.Type,
nil,
@@ -257,8 +266,8 @@ func (zp *ZonePrivate) Derive(label, context string)
(*ZonePrivate, *math.Int) {
}
zp.ZoneKey.KeyData = derived.Public().Bytes()
zp.ZoneKey.impl = impl.NewPublic()
- zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
- return dzp, h
+ err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+ return
}
// ZoneSign data with a private key
@@ -284,20 +293,21 @@ type ZoneKey struct {
}
// NewZoneKey returns a new initialized ZoneKey instance
-func NewZoneKey(d []byte) (*ZoneKey, error) {
+func NewZoneKey(d []byte) (zk *ZoneKey, err error) {
// read zone key from data
- zk := new(ZoneKey)
- if err := data.Unmarshal(zk, d); err != nil {
- return nil, err
+ zk = new(ZoneKey)
+ if err = data.Unmarshal(zk, d); err != nil {
+ return
}
// initialize implementation
impl, ok := zoneImpl[zk.Type]
if !ok {
- return nil, errors.New("unknown zone type")
+ err = ErrUnknownZoneType
+ return
}
zk.impl = impl.NewPublic()
- zk.impl.Init(zk.KeyData)
- return zk, nil
+ err = zk.impl.Init(zk.KeyData)
+ return
}
// KeySize returns the number of bytes of a key representation.
@@ -310,15 +320,18 @@ func (zk *ZoneKey) KeySize() uint {
}
// Derive key (key blinding)
-func (zk *ZoneKey) Derive(label, context string) (*ZoneKey, *math.Int) {
- h := deriveH(zk.KeyData, label, context)
+func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int,
err error) {
+ h = deriveH(zk.KeyData, label, context)
var derived ZoneKeyImpl
- derived, h = zk.impl.Derive(h)
- return &ZoneKey{
+ if derived, h, err = zk.impl.Derive(h); err != nil {
+ return
+ }
+ dzk = &ZoneKey{
Type: zk.Type,
KeyData: derived.Bytes(),
impl: derived,
- }, h
+ }
+ return
}
// BlockKey returns the key for block en-/decryption
@@ -338,15 +351,22 @@ func (zk *ZoneKey) Decrypt(data []byte, label string,
expire util.AbsoluteTime)
// Verify a zone signature
func (zk *ZoneKey) Verify(data []byte, zs *ZoneSignature) (ok bool, err error)
{
- zk.withImpl()
+ if err = zk.withImpl(); err != nil {
+ return
+ }
return zk.impl.Verify(data, zs)
}
// ID returns the human-readable zone identifier.
func (zk *ZoneKey) ID() string {
buf := new(bytes.Buffer)
- binary.Write(buf, binary.BigEndian, zk.Type)
- buf.Write(zk.KeyData)
+ err := binary.Write(buf, binary.BigEndian, zk.Type)
+ if err == nil {
+ _, err = buf.Write(zk.KeyData)
+ }
+ if err != nil {
+ logger.Printf(logger.ERROR, "[ZoneKey.ID] failed: %s",
err.Error())
+ }
return util.EncodeBinaryToString(buf.Bytes())
}
@@ -362,12 +382,13 @@ func (zk *ZoneKey) Equal(k *ZoneKey) bool {
}
// withImpl ensure that an implementation reference is available
-func (zk *ZoneKey) withImpl() {
+func (zk *ZoneKey) withImpl() (err error) {
if zk.impl == nil {
factory := zoneImpl[zk.Type]
zk.impl = factory.NewPublic()
- zk.impl.Init(zk.KeyData)
+ err = zk.impl.Init(zk.KeyData)
}
+ return
}
//----------------------------------------------------------------------
@@ -382,27 +403,29 @@ type ZoneSignature struct {
}
// NewZoneSignature returns a new initialized ZoneSignature instance
-func NewZoneSignature(d []byte) (*ZoneSignature, error) {
+func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
// read signature
- sig := new(ZoneSignature)
- if err := data.Unmarshal(sig, d); err != nil {
- return nil, err
+ sig = new(ZoneSignature)
+ if err = data.Unmarshal(sig, d); err != nil {
+ return
}
// initialize implementations
impl, ok := zoneImpl[sig.Type]
if !ok {
- return nil, errors.New("unknown zone type")
+ err = ErrUnknownZoneType
+ return
}
// set signature implementation
zs := impl.NewSignature()
- zs.Init(sig.Signature)
+ err = zs.Init(sig.Signature)
sig.impl = zs
// set public key implementation
zk := impl.NewPublic()
- zk.Init(sig.KeyData)
+ if err = zk.Init(sig.KeyData); err != nil {
+ return
+ }
sig.ZoneKey.impl = zk
-
- return sig, nil
+ return
}
// SigSize returns the number of bytes of a signature that can be
@@ -435,6 +458,8 @@ func deriveH(key []byte, label, context string) *math.Int {
data := append([]byte(label), []byte(context)...)
rdr := hkdf.Expand(sha256.New, prk, data)
b := make([]byte, 64)
- rdr.Read(b)
+ if _, err := rdr.Read(b); err != nil {
+ logger.Printf(logger.ERROR, "[deriveH] failed: %s", err.Error())
+ }
return math.NewIntFromBytes(b)
}
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
index 3153628..68a6444 100644
--- a/src/gnunet/crypto/gns_edkey.go
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -26,6 +26,7 @@ import (
"github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
"github.com/bfix/gospel/math"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/nacl/secretbox"
@@ -76,15 +77,15 @@ func (pk *EDKEYPublicImpl) Bytes() []byte {
// Derive a public key from this key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
-func (pk *EDKEYPublicImpl) Derive(h *math.Int) (ZoneKeyImpl, *math.Int) {
+func (pk *EDKEYPublicImpl) Derive(h *math.Int) (dPk ZoneKeyImpl, hOut
*math.Int, err error) {
// limit to allowed value range
- h = h.Mod(ed25519.GetCurve().N)
- derived := pk.pub.Mult(h)
- dPk := &EDKEYPublicImpl{
+ hOut = h.Mod(ed25519.GetCurve().N)
+ derived := pk.pub.Mult(hOut)
+ dPk = &EDKEYPublicImpl{
pk.ztype,
derived,
}
- return dPk, h
+ return
}
// Encrypt binary data (of any size). Output can be larger than input
@@ -137,8 +138,9 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expires
util.AbsoluteTime) (sk
kd := pk.Bytes()
prk := hkdf.Extract(sha512.New, kd, []byte("gns-xsalsa-ctx-key"))
rdr := hkdf.Expand(sha256.New, prk, []byte(label))
- rdr.Read(skey[:32])
-
+ if _, err := rdr.Read(skey[:32]); err != nil {
+ logger.Printf(logger.ERROR, "[EDKEYPublicImpl.BlockKey] failed:
%s", err.Error())
+ }
// assemble initialization vector
iv := &struct {
Nonce []byte `size:"16"` // Nonce
@@ -149,7 +151,9 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expires
util.AbsoluteTime) (sk
}
prk = hkdf.Extract(sha512.New, kd, []byte("gns-xsalsa-ctx-iv"))
rdr = hkdf.Expand(sha256.New, prk, []byte(label))
- rdr.Read(iv.Nonce)
+ if _, err := rdr.Read(iv.Nonce); err != nil {
+ logger.Printf(logger.ERROR, "[EDKEYPublicImpl.BlockKey] failed:
%s", err.Error())
+ }
buf, _ := data.Marshal(iv)
copy(skey[32:], buf)
return
@@ -188,30 +192,32 @@ func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
// Derive a public key from this key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
-func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (ZonePrivateImpl, *math.Int) {
+func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut
*math.Int, err error) {
// limit to allowed value range
- h = h.Mod(ed25519.GetCurve().N)
- derived := pk.prv.Mult(h)
- dPk := &EDKEYPrivateImpl{
+ hOut = h.Mod(ed25519.GetCurve().N)
+ derived := pk.prv.Mult(hOut)
+ dPk = &EDKEYPrivateImpl{
EDKEYPublicImpl{
pk.ztype,
derived.Public(),
},
derived,
}
- return dPk, h
+ return
}
// Sign binary data
-func (pk *EDKEYPrivateImpl) Sign(data []byte) (*ZoneSignature, error) {
- s, err := pk.prv.EdSign(data)
- if err != nil {
- return nil, err
+func (pk *EDKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
+ var s *ed25519.EdSignature
+ if s, err = pk.prv.EdSign(data); err != nil {
+ return
}
sd := s.Bytes()
sigImpl := new(EDKEYSigImpl)
- sigImpl.Init(sd)
- sig := &ZoneSignature{
+ if err = sigImpl.Init(sd); err != nil {
+ return
+ }
+ sig = &ZoneSignature{
ZoneKey{
Type: pk.ztype,
KeyData: pk.pub.Bytes(),
@@ -219,7 +225,7 @@ func (pk *EDKEYPrivateImpl) Sign(data []byte)
(*ZoneSignature, error) {
sd,
sigImpl,
}
- return sig, nil
+ return
}
//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
index fa319a4..e9fdfb5 100644
--- a/src/gnunet/crypto/gns_pkey.go
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -27,6 +27,7 @@ import (
"github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
"github.com/bfix/gospel/math"
"golang.org/x/crypto/hkdf"
)
@@ -76,15 +77,15 @@ func (pk *PKEYPublicImpl) Bytes() []byte {
// Derive a public key from this key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
-func (pk *PKEYPublicImpl) Derive(h *math.Int) (ZoneKeyImpl, *math.Int) {
+func (pk *PKEYPublicImpl) Derive(h *math.Int) (dPk ZoneKeyImpl, hOut
*math.Int, err error) {
// limit to allowed value range
- h = h.Mod(ed25519.GetCurve().N)
- derived := pk.pub.Mult(h)
- dPk := &PKEYPublicImpl{
+ hOut = h.Mod(ed25519.GetCurve().N)
+ derived := pk.pub.Mult(hOut)
+ dPk = &PKEYPublicImpl{
pk.ztype,
derived,
}
- return dPk, h
+ return
}
// Encrypt binary data (of any size). Output can be larger than input
@@ -114,7 +115,9 @@ func (pk *PKEYPublicImpl) BlockKey(label string, expires
util.AbsoluteTime) (ske
kd := pk.pub.Bytes()
prk := hkdf.Extract(sha512.New, kd, []byte("gns-aes-ctx-key"))
rdr := hkdf.Expand(sha256.New, prk, []byte(label))
- rdr.Read(skey[:32])
+ if _, err := rdr.Read(skey[:32]); err != nil {
+ logger.Printf(logger.ERROR, "[PKEYPublicImpl.BlockKey] failed:
%s", err.Error())
+ }
// assemble initialization vector
iv := &struct {
@@ -128,7 +131,9 @@ func (pk *PKEYPublicImpl) BlockKey(label string, expires
util.AbsoluteTime) (ske
}
prk = hkdf.Extract(sha512.New, kd, []byte("gns-aes-ctx-iv"))
rdr = hkdf.Expand(sha256.New, prk, []byte(label))
- rdr.Read(iv.Nonce)
+ if _, err := rdr.Read(iv.Nonce); err != nil {
+ logger.Printf(logger.ERROR, "[PKEYPublicImpl.BlockKey] failed:
%s", err.Error())
+ }
buf, _ := data.Marshal(iv)
copy(skey[32:], buf)
return
@@ -184,30 +189,32 @@ func (pk *PKEYPrivateImpl) Public() ZoneKeyImpl {
// Derive a public key from this key based on a big integer
// (key blinding). Returns the derived key and the blinding value.
-func (pk *PKEYPrivateImpl) Derive(h *math.Int) (ZonePrivateImpl, *math.Int) {
+func (pk *PKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut
*math.Int, err error) {
// limit to allowed value range
- h = h.Mod(ed25519.GetCurve().N)
- derived := pk.prv.Mult(h)
- dPk := &PKEYPrivateImpl{
+ hOut = h.Mod(ed25519.GetCurve().N)
+ derived := pk.prv.Mult(hOut)
+ dPk = &PKEYPrivateImpl{
PKEYPublicImpl{
pk.ztype,
derived.Public(),
},
derived,
}
- return dPk, h
+ return
}
// Verify a signature for binary data
-func (pk *PKEYPrivateImpl) Sign(data []byte) (*ZoneSignature, error) {
- s, err := pk.prv.EcSign(data)
- if err != nil {
- return nil, err
+func (pk *PKEYPrivateImpl) Sign(data []byte) (sig *ZoneSignature, err error) {
+ var s *ed25519.EcSignature
+ if s, err = pk.prv.EcSign(data); err != nil {
+ return
}
sd := s.Bytes()
sigImpl := new(PKEYSigImpl)
- sigImpl.Init(sd)
- sig := &ZoneSignature{
+ if err = sigImpl.Init(sd); err != nil {
+ return
+ }
+ sig = &ZoneSignature{
ZoneKey{
Type: pk.ztype,
KeyData: pk.pub.Bytes(),
@@ -215,7 +222,7 @@ func (pk *PKEYPrivateImpl) Sign(data []byte)
(*ZoneSignature, error) {
sd,
sigImpl,
}
- return sig, nil
+ return
}
//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index 4ef64d4..12ab603 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -55,7 +55,9 @@ func TestDeriveBlockKey(t *testing.T) {
// create and initialize new public zone key (PKEY)
zkey := new(PKEYPublicImpl)
- zkey.Init(PUB)
+ if err := zkey.Init(PUB); err != nil {
+ t.Fatal(err)
+ }
// derive and check a key for symmetric cipher
skey := zkey.BlockKey(LABEL, EXPIRE)
@@ -267,7 +269,10 @@ func TestDeriveH(t *testing.T) {
}
// test key derivation
- dpub, h := pub.Derive(LABEL, CONTEXT)
+ dpub, h, err := pub.Derive(LABEL, CONTEXT)
+ if err != nil {
+ t.Fatal(err)
+ }
if !bytes.Equal(h.Bytes(), H) {
if testing.Verbose() {
t.Logf("H(computed) = %s\n",
hex.EncodeToString(h.Bytes()))
@@ -297,7 +302,6 @@ func TestDeriveH(t *testing.T) {
}
func TestHKDF_gnunet(t *testing.T) {
-
var (
// SALT as defined in GNUnet
salt = []byte("key-derivation")
@@ -332,7 +336,9 @@ func TestHKDF_gnunet(t *testing.T) {
rdr := hkdf.Expand(sha256.New, prk, info)
okm := make([]byte, len(OKM))
- rdr.Read(okm)
+ if _, err := rdr.Read(okm); err != nil {
+ t.Fatal(err)
+ }
if testing.Verbose() {
t.Log("OKM(computed) = " + hex.EncodeToString(okm))
}
@@ -387,7 +393,9 @@ func TestHDKF(t *testing.T) {
rdr := hkdf.Expand(sha512.New, prk, info)
okm := make([]byte, len(OKM))
- rdr.Read(okm)
+ if _, err := rdr.Read(okm); err != nil {
+ t.Fatal(err)
+ }
if testing.Verbose() {
t.Log("OKM(computed) = " + hex.EncodeToString(okm))
}
diff --git a/src/gnunet/crypto/hash.go b/src/gnunet/crypto/hash.go
index ed6edc7..437dcb2 100644
--- a/src/gnunet/crypto/hash.go
+++ b/src/gnunet/crypto/hash.go
@@ -21,6 +21,7 @@ package crypto
import (
"bytes"
"crypto/sha512"
+ "encoding/hex"
"gnunet/util"
)
@@ -35,7 +36,17 @@ func (hc *HashCode) Equals(n *HashCode) bool {
return bytes.Equal(hc.Bits, n.Bits)
}
-// NewHashCode creates a new (initalized) hash value
+// Clone the hash code
+func (hc *HashCode) Clone() *HashCode {
+ return NewHashCode(hc.Bits)
+}
+
+// String returns a hex-representation of the hash code
+func (hc *HashCode) String() string {
+ return hex.EncodeToString(hc.Bits)
+}
+
+// NewHashCode creates a new (initialized) hash value
func NewHashCode(buf []byte) *HashCode {
hc := &HashCode{
Bits: make([]byte, 64),
diff --git a/src/gnunet/crypto/key_exchange_test.go
b/src/gnunet/crypto/key_exchange_test.go
index 1e4f0dc..dbd1792 100644
--- a/src/gnunet/crypto/key_exchange_test.go
+++ b/src/gnunet/crypto/key_exchange_test.go
@@ -29,14 +29,14 @@ import (
)
var (
- d_1 = []byte{
+ d1 = []byte{
0x7F, 0xDE, 0x7A, 0xAA, 0xEA, 0x0D, 0xA1, 0x7A,
0x7B, 0xCB, 0x4F, 0x57, 0x49, 0xCC, 0xA9, 0xBE,
0xA7, 0xFB, 0x2B, 0x85, 0x77, 0xAD, 0xC9, 0x55,
0xDA, 0xB2, 0x68, 0xB2, 0xB4, 0xCC, 0x24, 0x78,
}
- d_2 = []byte{
+ d2 = []byte{
0x20, 0x3f, 0x2f, 0x8c, 0x54, 0xf4, 0x1a, 0xd3,
0x01, 0x9a, 0x56, 0x92, 0x19, 0xda, 0xee, 0x4f,
0xd2, 0x53, 0x55, 0xa6, 0x3c, 0xfc, 0x57, 0x40,
@@ -54,11 +54,11 @@ var (
0x05, 0xbd, 0x1b, 0x85, 0xd5, 0xfd, 0x63, 0x60,
}
- prv_1, prv_2 *ed25519.PrivateKey
- pub_1, pub_2 *ed25519.PublicKey
- ss_1, ss_2 []byte
+ prv1, prv2 *ed25519.PrivateKey
+ pub1, pub2 *ed25519.PublicKey
+ ss1, ss2 []byte
- ED25519_N = ed25519.GetCurve().N
+ ed25519N = ed25519.GetCurve().N
)
func calcSharedSecret() bool {
@@ -67,29 +67,29 @@ func calcSharedSecret() bool {
return x[:]
}
// compute shared secret
- ss_1 = calc(prv_1, pub_2)
- ss_2 = calc(prv_2, pub_1)
- return bytes.Equal(ss_1, ss_2)
+ ss1 = calc(prv1, pub2)
+ ss2 = calc(prv2, pub1)
+ return bytes.Equal(ss1, ss2)
}
func TestDHE(t *testing.T) {
// generate two key pairs
- prv_1 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d_1))
- pub_1 = prv_1.Public()
- prv_2 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d_2))
- pub_2 = prv_2.Public()
+ prv1 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d1))
+ pub1 = prv1.Public()
+ prv2 = ed25519.NewPrivateKeyFromD(math.NewIntFromBytes(d2))
+ pub2 = prv2.Public()
if !calcSharedSecret() {
t.Fatal("Shared secret mismatch")
}
if testing.Verbose() {
- t.Logf("SS_1 = %s\n", hex.EncodeToString(ss_1))
- t.Logf("SS_2 = %s\n", hex.EncodeToString(ss_2))
+ t.Logf("SS_1 = %s\n", hex.EncodeToString(ss1))
+ t.Logf("SS_2 = %s\n", hex.EncodeToString(ss2))
}
- if !bytes.Equal(ss_1[:], ss) {
+ if !bytes.Equal(ss1[:], ss) {
t.Logf("SS(expected) = %s\n", hex.EncodeToString(ss))
- t.Logf("SS(computed) = %s\n", hex.EncodeToString(ss_1[:]))
+ t.Logf("SS(computed) = %s\n", hex.EncodeToString(ss1[:]))
t.Fatal("Wrong shared secret:")
}
}
@@ -98,19 +98,19 @@ func TestDHERandom(t *testing.T) {
failed := 0
once := false
for i := 0; i < 100; i++ {
- prv_1 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ED25519_N))
- pub_1 = prv_1.Public()
- prv_2 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ED25519_N))
- pub_2 = prv_2.Public()
+ prv1 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ed25519N))
+ pub1 = prv1.Public()
+ prv2 = ed25519.NewPrivateKeyFromD(math.NewIntRnd(ed25519N))
+ pub2 = prv2.Public()
if !calcSharedSecret() {
if !once {
once = true
- t.Logf("d1=%s\n",
hex.EncodeToString(prv_1.D.Bytes()))
- t.Logf("d2=%s\n",
hex.EncodeToString(prv_2.D.Bytes()))
- t.Logf("ss1=%s\n", hex.EncodeToString(ss_1))
- t.Logf("ss2=%s\n", hex.EncodeToString(ss_2))
- dd := prv_1.D.Mul(prv_2.D).Mod(ED25519_N)
+ t.Logf("d1=%s\n",
hex.EncodeToString(prv1.D.Bytes()))
+ t.Logf("d2=%s\n",
hex.EncodeToString(prv2.D.Bytes()))
+ t.Logf("ss1=%s\n", hex.EncodeToString(ss1))
+ t.Logf("ss2=%s\n", hex.EncodeToString(ss2))
+ dd := prv1.D.Mul(prv2.D).Mod(ed25519N)
pk :=
sha512.Sum512(ed25519.NewPrivateKeyFromD(dd).Public().Q.X().Bytes())
t.Logf("ss0=%s\n", hex.EncodeToString(pk[:]))
}
diff --git a/src/gnunet/crypto/keys_test.go b/src/gnunet/crypto/keys_test.go
index d8ffe96..ca5c7b7 100644
--- a/src/gnunet/crypto/keys_test.go
+++ b/src/gnunet/crypto/keys_test.go
@@ -58,7 +58,7 @@ var (
func TestPrvKey(t *testing.T) {
if testing.Verbose() {
t.Logf("PRIVATE (seed=%s)\n", hex.EncodeToString(seed))
- t.Logf(" d = %s\n", hex.EncodeToString(prv_1.D.Bytes()))
+ t.Logf(" d = %s\n", hex.EncodeToString(prv1.D.Bytes()))
t.Logf(" ID = '%s'\n", util.EncodeBinaryToString(seed))
}
diff --git a/src/gnunet/enums/blocktype_string.go
b/src/gnunet/enums/blocktype_string.go
index 639501f..cc14d79 100644
--- a/src/gnunet/enums/blocktype_string.go
+++ b/src/gnunet/enums/blocktype_string.go
@@ -40,7 +40,7 @@ var (
func (i BlockType) String() string {
switch {
- case 0 <= i && i <= 2:
+ case i <= 2:
return
_BlockType_name_0[_BlockType_index_0[i]:_BlockType_index_0[i+1]]
case 6 <= i && i <= 13:
i -= 6
diff --git a/src/gnunet/enums/dht.go b/src/gnunet/enums/dht.go
index 36c3d8a..040e72f 100644
--- a/src/gnunet/enums/dht.go
+++ b/src/gnunet/enums/dht.go
@@ -16,18 +16,16 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
+//nolint:stylecheck // allow non-camel-case for constants
package enums
// DHT flags and settings
const (
- DHT_RO_NONE = 0 // Default. Do nothing special.
- DHT_RO_DEMULTIPLEX_EVERYWHERE = 1 // Each peer along the way should
look at 'enc'
- DHT_RO_RECORD_ROUTE = 2 // keep track of the route that the
message took in the P2P network.
- DHT_RO_FIND_PEER = 3 // This is a 'FIND-PEER' request, so
approximate results are fine.
- DHT_RO_BART = 4 // Possible message option for query
key randomization.
- DHT_RO_LAST_HOP = 16 // Flag given to monitors if this
was the last hop for a GET/PUT.
-
- DHT_GNS_REPLICATION_LEVEL = 10
+ DHT_RO_NONE = 0 // Default. Do nothing special.
+ DHT_RO_DEMULTIPLEX_EVERYWHERE = 1 // Each peer along the way should
look at 'enc'
+ DHT_RO_RECORD_ROUTE = 2 // keep track of the route that the
message took in the P2P network.
+ DHT_RO_FIND_APPROXIMATE = 4 // Approximate results are fine.
+ DHT_RO_TRUNCATED = 8 // Flag if path is truncated
)
//go:generate go run generate.go gnunet-dht.rec gnunet-dht.tpl
dht_block_type.go
diff --git a/src/gnunet/enums/dht_block_type.go
b/src/gnunet/enums/dht_block_type.go
index e435419..beb52dc 100644
--- a/src/gnunet/enums/dht_block_type.go
+++ b/src/gnunet/enums/dht_block_type.go
@@ -1,8 +1,9 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
-type BlockType int
+type BlockType uint16
// DHT block types
const (
diff --git a/src/gnunet/enums/gns.go b/src/gnunet/enums/gns.go
index 5ab28ad..f6e58a2 100644
--- a/src/gnunet/enums/gns.go
+++ b/src/gnunet/enums/gns.go
@@ -16,6 +16,7 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
+//nolint:stylecheck // allow non-camel-case for constants
package enums
const (
@@ -31,6 +32,8 @@ const (
GNS_LO_LOCAL_MASTER = 2 // For the rightmost label, only look in the
cache.
GNS_MAX_BLOCK_SIZE = (63 * 1024) // Maximum size of a value that can be
stored in a GNS block.
+
+ GNS_REPLICATION_LEVEL = 10
)
//go:generate go run generate.go gnunet-gns.rec gnunet-gns.tpl gns_type.go
diff --git a/src/gnunet/enums/gns_type.go b/src/gnunet/enums/gns_type.go
index 2b55817..5a13d67 100644
--- a/src/gnunet/enums/gns_type.go
+++ b/src/gnunet/enums/gns_type.go
@@ -1,5 +1,6 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
type GNSType int
diff --git a/src/gnunet/enums/gnunet-dht.tpl b/src/gnunet/enums/gnunet-dht.tpl
index 0010897..ed00e57 100644
--- a/src/gnunet/enums/gnunet-dht.tpl
+++ b/src/gnunet/enums/gnunet-dht.tpl
@@ -1,8 +1,9 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
-type BlockType int
+type BlockType uint16
// DHT block types
const (
diff --git a/src/gnunet/enums/gnunet-gns.tpl b/src/gnunet/enums/gnunet-gns.tpl
index 075fe73..3249569 100644
--- a/src/gnunet/enums/gnunet-gns.tpl
+++ b/src/gnunet/enums/gnunet-gns.tpl
@@ -1,5 +1,6 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
type GNSType int
diff --git a/src/gnunet/enums/gnunet-signature.tpl
b/src/gnunet/enums/gnunet-signature.tpl
index 009c086..5af6c28 100644
--- a/src/gnunet/enums/gnunet-signature.tpl
+++ b/src/gnunet/enums/gnunet-signature.tpl
@@ -1,5 +1,6 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
type SigPurpose int
diff --git a/src/gnunet/enums/signature_purpose.go
b/src/gnunet/enums/signature_purpose.go
index ad4bf0a..0c1901e 100644
--- a/src/gnunet/enums/signature_purpose.go
+++ b/src/gnunet/enums/signature_purpose.go
@@ -1,5 +1,6 @@
// Code generated by enum generator; DO NOT EDIT.
+//nolint:stylecheck // allow non-camel-case for constants
package enums
type SigPurpose int
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index bb2e58f..5109da4 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -3,10 +3,11 @@ module gnunet
go 1.18
require (
- github.com/bfix/gospel v1.2.14
+ github.com/bfix/gospel v1.2.15
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/mux v1.8.0
+ github.com/gorilla/rpc v1.2.0
github.com/mattn/go-sqlite3 v1.14.13
github.com/miekg/dns v1.1.49
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index 2451a41..5a08cee 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,5 +1,5 @@
-github.com/bfix/gospel v1.2.14 h1:lIdagJvkebG+uYbVdfK6XbT1udnq/ezd/Gi54EaMtV0=
-github.com/bfix/gospel v1.2.14/go.mod
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/bfix/gospel v1.2.15 h1:f0t8dvihSXWvhnXDI2q7FCtG7LHg5qImjEWdzIN/luY=
+github.com/bfix/gospel v1.2.15/go.mod
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
github.com/cespare/xxhash/v2 v2.1.2
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -11,6 +11,8 @@ github.com/go-sql-driver/mysql v1.6.0
h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
github.com/go-sql-driver/mysql v1.6.0/go.mod
h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod
h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
+github.com/gorilla/rpc v1.2.0/go.mod
h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
github.com/huin/goupnp v1.0.0/go.mod
h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod
h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 25506dc..23da078 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -66,8 +66,18 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
return NewDHTClientResultMsg(nil), nil
case DHT_CLIENT_GET_RESULTS_KNOWN:
return NewDHTClientGetResultsKnownMsg(nil), nil
+
+ //------------------------------------------------------------------
+ // DHT-P2P
+ //------------------------------------------------------------------
case DHT_P2P_HELLO:
- return NewHelloDHTMsg(), nil
+ return NewDHTP2PHelloMsg(), nil
+ case DHT_P2P_GET:
+ return NewDHTP2PGetMsg(), nil
+ case DHT_P2P_PUT:
+ return NewDHTP2PPutMsg(), nil
+ case DHT_P2P_RESULT:
+ return NewDHTP2PResultMsg(), nil
//------------------------------------------------------------------
// GNS
diff --git a/src/gnunet/message/msg_core.go b/src/gnunet/message/msg_core.go
index 245c61d..208b438 100644
--- a/src/gnunet/message/msg_core.go
+++ b/src/gnunet/message/msg_core.go
@@ -36,17 +36,17 @@ type EphKeyBlock struct {
Purpose *crypto.SignaturePurpose // signature purpose: SIG_ECC_KEY
CreateTime util.AbsoluteTime // Time of key creation
ExpireTime util.RelativeTime // Time to live for key
- EphemeralKey []byte `size:"32"` // Ephemeral EdDSA
public key
+ EphemeralKey *util.PeerPublicKey // Ephemeral EdDSA public key
PeerID *util.PeerID // Peer identity (EdDSA public
key)
}
// EphemeralKeyMsg announces a new transient key for a peer. The key is signed
// by the issuing peer.
type EphemeralKeyMsg struct {
- MsgSize uint16 `order:"big"` // total size of message
- MsgType uint16 `order:"big"` // CORE_EPHEMERAL_KEY (88)
- SenderStatus uint32 `order:"big"` // enum PeerStateMachine
- Signature []byte `size:"64"` // EdDSA signature
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // CORE_EPHEMERAL_KEY
(88)
+ SenderStatus uint32 `order:"big"` // enum PeerStateMachine
+ Signature *util.PeerSignature `` // EdDSA signature
SignedBlock *EphKeyBlock
}
@@ -56,7 +56,7 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
MsgSize: 160,
MsgType: CORE_EPHEMERAL_KEY,
SenderStatus: 1,
- Signature: make([]byte, 64),
+ Signature: util.NewPeerSignature(nil),
SignedBlock: &EphKeyBlock{
Purpose: &crypto.SignaturePurpose{
Size: 88,
@@ -64,7 +64,7 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
},
CreateTime: util.AbsoluteTimeNow(),
ExpireTime: util.NewRelativeTime(12 * time.Hour),
- EphemeralKey: make([]byte, 32),
+ EphemeralKey: util.NewPeerPublicKey(nil),
PeerID: util.NewPeerID(nil),
},
}
@@ -73,8 +73,8 @@ func NewEphemeralKeyMsg() *EphemeralKeyMsg {
// String returns a human-readable representation of the message.
func (m *EphemeralKeyMsg) String() string {
return
fmt.Sprintf("EphKeyMsg{peer=%s,ephkey=%s,create=%s,expire=%s,status=%d}",
- util.EncodeBinaryToString(m.SignedBlock.PeerID.Key),
- util.EncodeBinaryToString(m.SignedBlock.EphemeralKey),
+ util.EncodeBinaryToString(m.SignedBlock.PeerID.Data),
+ util.EncodeBinaryToString(m.SignedBlock.EphemeralKey.Data),
m.SignedBlock.CreateTime, m.SignedBlock.ExpireTime,
m.SenderStatus)
}
@@ -85,8 +85,8 @@ func (m *EphemeralKeyMsg) Header() *Header {
}
// Public extracts the public key of an announcing peer.
-func (m *EphemeralKeyMsg) Public() *ed25519.PublicKey {
- return m.SignedBlock.PeerID.PublicKey()
+func (m *EphemeralKeyMsg) Public() *util.PeerPublicKey {
+ return util.NewPeerPublicKey(m.SignedBlock.PeerID.Data)
}
// Verify the integrity of the message data using the public key of the
@@ -96,7 +96,7 @@ func (m *EphemeralKeyMsg) Verify(pub *ed25519.PublicKey)
(bool, error) {
if err != nil {
return false, err
}
- sig, err := ed25519.NewEdSignatureFromBytes(m.Signature)
+ sig, err := ed25519.NewEdSignatureFromBytes(m.Signature.Data)
if err != nil {
return false, err
}
@@ -107,10 +107,10 @@ func (m *EphemeralKeyMsg) Verify(pub *ed25519.PublicKey)
(bool, error) {
// key and the corresponding GNUnet message to announce the new key.
func NewEphemeralKey(peerID []byte, ltPrv *ed25519.PrivateKey)
(*ed25519.PrivateKey, *EphemeralKeyMsg, error) {
msg := NewEphemeralKeyMsg()
- copy(msg.SignedBlock.PeerID.Key, peerID)
+ copy(msg.SignedBlock.PeerID.Data, peerID)
seed := util.NewRndArray(32)
prv := ed25519.NewPrivateKeyFromSeed(seed)
- copy(msg.SignedBlock.EphemeralKey, prv.Public().Bytes())
+ copy(msg.SignedBlock.EphemeralKey.Data, prv.Public().Bytes())
data, err := data.Marshal(msg.SignedBlock)
if err != nil {
@@ -120,7 +120,7 @@ func NewEphemeralKey(peerID []byte, ltPrv
*ed25519.PrivateKey) (*ed25519.Private
if err != nil {
return nil, nil, err
}
- copy(msg.Signature, sig.Bytes())
+ copy(msg.Signature.Data, sig.Bytes())
return prv, msg, nil
}
diff --git a/src/gnunet/message/msg_dht_p2p.go
b/src/gnunet/message/msg_dht_p2p.go
new file mode 100644
index 0000000..55bb71d
--- /dev/null
+++ b/src/gnunet/message/msg_dht_p2p.go
@@ -0,0 +1,473 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package message
+
+import (
+ "bytes"
+ "crypto/sha512"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+ "time"
+
+ "github.com/bfix/gospel/crypto/ed25519"
+ "github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// DHT-P2P is a next-generation implementation of the R5N DHT.
+//======================================================================
+
+// shared path element data across types
+type pathElementData struct {
+ Expiration util.AbsoluteTime // expiration date
+ BlockHash *crypto.HashCode // block hash
+ PeerPredecessor *util.PeerID // predecessor peer
+ PeerSuccessor *util.PeerID // successor peer
+}
+
+// helper type for signature creation/verification
+type pathElementSignedData struct {
+ Size uint16 `order:"big"` // size of signed data
+ Purpose uint16 `order:"big"` // signature purpose
(SIG_DHT_HOP)
+ Elem *pathElementData `` // path element data
+}
+
+// PathElement is the full-fledged data assembly for a path element in
+// PUT/GET pathes. It is assembled programatically (on generation[1] and
+// verification[2]) and not transferred in messages directly.
+//
+// [1] spe = &PathElement{...}
+// core.Sign(spe)
+// msg.putpath[i] = spe.Wire()
+//
+// [2] pe = &PathElement{...,Signature: wire.sig}
+// if !pe.Verify(peerId) { ... }
+//
+type PathElement struct {
+ pathElementData
+ Signature *util.PeerSignature // signature
+}
+
+// NewPathElement creates a new path element from data
+func NewPathElement(key *crypto.HashCode, pred, succ *util.PeerID)
*PathElement {
+ return &PathElement{
+ pathElementData: pathElementData{
+ Expiration: util.AbsoluteTimeNow().Add(12 *
time.Hour),
+ BlockHash: key,
+ PeerPredecessor: pred,
+ PeerSuccessor: succ,
+ },
+ Signature: nil,
+ }
+}
+
+// PathElementWire is the data stored and retrieved from messages
+type PathElementWire struct {
+ Predecessor *util.PeerID // peer id of predecessor
+ Signature *util.PeerSignature // path signature
+}
+
+// Size returns the size of a path element in wire format
+func (pew *PathElementWire) Size() uint16 {
+ return 96
+}
+
+// SignedData gets the data to be signed by peer ('Signable' interface)
+func (pe *PathElement) SignedData() []byte {
+ sd := &pathElementSignedData{
+ Size: 80,
+ Purpose: uint16(enums.SIG_DHT_HOP),
+ Elem: &(pe.pathElementData),
+ }
+ buf, err := data.Marshal(sd)
+ if err != nil {
+ logger.Println(logger.ERROR, "can't serialize path element for
signature")
+ return nil
+ }
+ return buf
+}
+
+// SetSignature stores the generated signature.
+func (pe *PathElement) SetSignature(sig *util.PeerSignature) error {
+ pe.Signature = sig
+ return nil
+}
+
+// Wire returns the path element suitable for inclusion into messages
+func (pe *PathElement) Wire() *PathElementWire {
+ return &PathElementWire{
+ Predecessor: pe.PeerPredecessor,
+ Signature: pe.Signature,
+ }
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-GET messages are used to request information from other
+// peers in the DHT.
+//----------------------------------------------------------------------
+
+// DHTP2PGetMsg wire layout
+type DHTP2PGetMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // DHT_P2P_GET (147)
+ BType uint32 `order:"big"` // content type of the
payload
+ Flags uint16 `order:"big"` // processing flags
+ HopCount uint16 `order:"big"` // number of hops so far
+ ReplLevel uint16 `order:"big"` // Replication level
+ RfSize uint16 `order:"big"` // size of result filter
+ PeerFilter *blocks.PeerFilter `` // peer filter to prevent
loops
+ Query *crypto.HashCode `` // query hash
+ ResFilter []byte `size:"RfSize"` // result filter
+ XQuery []byte `size:"*"` // extended query
+}
+
+// NewDHTP2PGetMsg creates an empty DHT-P2P-Get message
+func NewDHTP2PGetMsg() *DHTP2PGetMsg {
+ return &DHTP2PGetMsg{
+ MsgSize: 208, // message size without
ResFiter and XQuery
+ MsgType: DHT_P2P_GET, // DHT_P2P_GET (147)
+ BType: 0, // no block type defined
+ Flags: 0, // no flags defined
+ HopCount: 0, // no hops
+ ReplLevel: 0, // no replication level
defined
+ RfSize: 0, // no result filter
+ PeerFilter: blocks.NewPeerFilter(), // allocate bloom filter
+ Query: crypto.NewHashCode(nil), // empty Query hash
+ ResFilter: nil, // empty result filter
+ XQuery: nil, // empty XQuery
+ }
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PGetMsg) String() string {
+ return fmt.Sprintf("DHTP2PGetMsg{btype=%s,hops=%d,flags=%d}",
+ enums.BlockType(m.BType).String(), m.HopCount, m.Flags)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PGetMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
+
+// Clone message
+func (m *DHTP2PGetMsg) Update(pf *blocks.PeerFilter, rf blocks.ResultFilter,
hop uint16) *DHTP2PGetMsg {
+ buf := rf.Bytes()
+ ns := uint16(len(buf))
+ return &DHTP2PGetMsg{
+ MsgSize: m.MsgSize - m.RfSize + ns,
+ MsgType: DHT_P2P_GET,
+ BType: m.BType,
+ Flags: m.Flags,
+ HopCount: hop,
+ ReplLevel: m.ReplLevel,
+ RfSize: ns,
+ PeerFilter: pf.Clone(),
+ Query: m.Query,
+ ResFilter: buf,
+ XQuery: util.Clone(m.XQuery),
+ }
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-PUT messages are used by other peers in the DHT to
+// request block storage.
+//----------------------------------------------------------------------
+
+// DHTP2PPutMsg wire layout
+type DHTP2PPutMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // DHT_P2P_PUT (146)
+ BType uint32 `order:"big"` // block type
+ Flags uint16 `order:"big"` // processing flags
+ HopCount uint16 `order:"big"` // message hops
+ ReplLvl uint16 `order:"big"` // replication level
+ PathL uint16 `order:"big"` // path length
+ Expiration util.AbsoluteTime `` // expiration date
+ PeerFilter *blocks.PeerFilter `` // peer bloomfilter
+ Key *crypto.HashCode `` // query key to block
+ Origin []byte `size:"(PESize)"` // truncated origin (if
TRUNCATED flag set)
+ PutPath []*PathElementWire `size:"PathL"` // PUT path
+ LastSig []byte `size:"(PESize)"` // signature of last
hop (if RECORD_ROUTE flag is set)
+ Block []byte `size:"*"` // block data
+}
+
+// NewDHTP2PPutMsg creates an empty new DHTP2PPutMsg
+func NewDHTP2PPutMsg() *DHTP2PPutMsg {
+ return &DHTP2PPutMsg{
+ MsgSize: 218, // total size without
path and block data
+ MsgType: DHT_P2P_PUT, // DHT_P2P_PUT (146)
+ BType: 0, // block type
+ Flags: 0, // processing flags
+ HopCount: 0, // message hops
+ ReplLvl: 0, // replication level
+ PathL: 0, // no PUT path
+ Expiration: util.AbsoluteTimeNever(), // expiration date
+ PeerFilter: blocks.NewPeerFilter(), // peer bloom filter
+ Key: crypto.NewHashCode(nil), // query key
+ Origin: nil, // no truncated path
+ PutPath: make([]*PathElementWire, 0), // empty PUT path
+ LastSig: nil, // no signature from
last hop
+ Block: nil, // no block data
+ }
+}
+
+// PESize calculates field sizes based on flags and attributes
+func (m *DHTP2PPutMsg) PESize(field string) uint {
+ switch field {
+ case "Origin":
+ if m.Flags&enums.DHT_RO_TRUNCATED != 0 {
+ return 32
+ }
+ case "LastSig":
+ if m.Flags&enums.DHT_RO_RECORD_ROUTE != 0 {
+ return 64
+ }
+ }
+ return 0
+}
+
+// AddPutPath adds an element to the PUT path
+func (m *DHTP2PPutMsg) AppendPutPath(pe *PathElement) {
+ pew := pe.Wire()
+ m.PutPath = append(m.PutPath, pew)
+ m.PathL++
+ m.MsgSize += pew.Size()
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PPutMsg) String() string {
+ return fmt.Sprintf("DHTP2PPutMsg{btype=%s,hops=%d,flags=%d}",
+ enums.BlockType(m.BType).String(), m.HopCount, m.Flags)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PPutMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-RESULT messages are used to answer peer requests for
+// bock retrieval.
+//----------------------------------------------------------------------
+
+// DHTP2PResultMsg wire layout
+type DHTP2PResultMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // DHT_P2P_RESULT (148)
+ BType uint32 `order:"big"` // Block type of result
+ Reserved uint32 `order:"big"` // Reserved for further
use
+ PutPathL uint16 `order:"big"` // size of PUTPATH field
+ GetPathL uint16 `order:"big"` // size of GETPATH field
+ Expires util.AbsoluteTime `` // expiration date
+ Query *crypto.HashCode `` // Query key for block
+ Origin []byte `size:"(PESize)"` // truncated origin (if
TRUNCATED flag set)
+ PutPath []*PathElementWire `size:"PutPathL"` // PUTPATH
+ GetPath []*PathElementWire `size:"GetPathL"` // GETPATH
+ LastSig []byte `size:"(PESize)"` // signature of last hop
(if RECORD_ROUTE flag is set)
+ Block []byte `size:"*"` // block data
+}
+
+// NewDHTP2PResultMsg creates a new empty DHTP2PResultMsg
+func NewDHTP2PResultMsg() *DHTP2PResultMsg {
+ return &DHTP2PResultMsg{
+ MsgSize: 88, // size of empty message
+ MsgType: DHT_P2P_RESULT, // DHT_P2P_RESULT (148)
+ BType: uint32(enums.BLOCK_TYPE_ANY), // type of returned
block
+ Origin: nil, // no truncated origin
+ PutPathL: 0, // empty putpath
+ PutPath: nil, // -"-
+ GetPathL: 0, // empty getpath
+ GetPath: nil, // -"-
+ LastSig: nil, // no recorded route
+ Block: nil, // empty block
+ }
+}
+
+// PESize calculates field sizes based on flags and attributes
+func (m *DHTP2PResultMsg) PESize(field string) uint {
+ switch field {
+ case "Origin":
+ //if m.Flags&enums.DHT_RO_TRUNCATED != 0 {
+ return 32
+ //}
+ case "LastSig":
+ //if m.Flags&enums.DHT_RO_RECORD_ROUTE != 0 {
+ return 64
+ //}
+ }
+ return 0
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PResultMsg) String() string {
+ return fmt.Sprintf("DHTP2PResultMsg{btype=%s,putl=%d,getl=%d}",
+ enums.BlockType(m.BType).String(), m.PutPathL, m.GetPathL)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PResultMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
+
+//----------------------------------------------------------------------
+// DHT-P2P-HELLO
+//
+// A DHT-P2P-HELLO message is used to exchange information about transports
+// with other DHT nodes. This struct is always followed by the actual
+// network addresses of type "HelloAddress"
+//----------------------------------------------------------------------
+
+// DHTP2PHelloMsg is a message send by peers to announce their presence
+type DHTP2PHelloMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // DHT_P2P_HELLO (157)
+ Reserved uint16 `order:"big"` // Reserved for further use
+ NumAddr uint16 `order:"big"` // Number of addresses in
list
+ Signature *util.PeerSignature `` // Signature
+ Expires util.AbsoluteTime `` // expiration time
+ AddrList []byte `size:"*"` // List of end-point
addresses (HelloAddress)
+}
+
+// NewHelloMsgDHT creates an empty DHT_P2P_HELLO message.
+func NewDHTP2PHelloMsg() *DHTP2PHelloMsg {
+ // return empty HelloMessage
+ exp := time.Now().Add(HelloAddressExpiration)
+ return &DHTP2PHelloMsg{
+ MsgSize: 80, // size without
'AddrList'
+ MsgType: DHT_P2P_HELLO, // DHT_P2P_HELLO (157)
+ Reserved: 0, // not used here
+ NumAddr: 0, // start with empty
address list
+ Signature: util.NewPeerSignature(nil), // signature
+ Expires: util.NewAbsoluteTime(exp), // default expiration
+ AddrList: make([]byte, 0), // list of addresses
+ }
+}
+
+// Addresses returns the list of HelloAddress
+func (m *DHTP2PHelloMsg) Addresses() (list []*util.Address, err error) {
+ var addr *util.Address
+ var as string
+ num, pos := 0, 0
+ for {
+ // parse address string from stream
+ if as, pos = util.ReadCString(m.AddrList, pos); pos == -1 {
+ break
+ }
+ if addr, err = util.ParseAddress(as); err != nil {
+ return
+ }
+ addr.Expires = m.Expires
+ list = append(list, addr)
+ num++
+ }
+ // check numbers
+ if num != int(m.NumAddr) {
+ logger.Printf(logger.WARN, "[DHTP2PHelloMsg] Number of
addresses does not match (got %d, expected %d)", num, m.NumAddr)
+ }
+ return
+}
+
+// SetAddresses adds addresses to the HELLO message.
+func (m *DHTP2PHelloMsg) SetAddresses(list []*util.Address) {
+ // write addresses as blob and track earliest expiration
+ exp := util.NewAbsoluteTime(time.Now().Add(HelloAddressExpiration))
+ wrt := new(bytes.Buffer)
+ for _, addr := range list {
+ // check if address expires before current expire
+ if exp.Compare(addr.Expires) > 0 {
+ exp = addr.Expires
+ }
+ n, _ := wrt.Write([]byte(addr.URI()))
+ wrt.WriteByte(0)
+ m.MsgSize += uint16(n + 1)
+ }
+ m.AddrList = wrt.Bytes()
+ m.Expires = exp
+ m.NumAddr = uint16(len(list))
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTP2PHelloMsg) String() string {
+ addrs, _ := m.Addresses()
+ aList := ""
+ for i, a := range addrs {
+ if i > 0 {
+ aList += ","
+ }
+ aList += a.URI()
+ }
+ return fmt.Sprintf("DHTP2PHelloMsg{expire:%s,addrs=%d:[%s]}",
m.Expires, m.NumAddr, aList)
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTP2PHelloMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
+
+// Verify the message signature
+func (m *DHTP2PHelloMsg) Verify(peer *util.PeerID) (bool, error) {
+ // assemble signed data and public key
+ sd := m.SignedData()
+ pub := ed25519.NewPublicKeyFromBytes(peer.Data)
+ sig, err := ed25519.NewEdSignatureFromBytes(m.Signature.Data)
+ if err != nil {
+ return false, err
+ }
+ return pub.EdVerify(sd, sig)
+}
+
+// SetSignature stores a signature in the the HELLO block
+func (m *DHTP2PHelloMsg) SetSignature(sig *util.PeerSignature) error {
+ m.Signature = sig
+ return nil
+}
+
+// SignedData assembles a data block for sign and verify operations.
+func (m *DHTP2PHelloMsg) SignedData() []byte {
+ // hash address block
+ hAddr := sha512.Sum512(m.AddrList)
+ var size uint32 = 80
+ purpose := uint32(enums.SIG_HELLO)
+
+ // assemble signed data
+ buf := new(bytes.Buffer)
+ var n int
+ err := binary.Write(buf, binary.BigEndian, size)
+ if err == nil {
+ if err = binary.Write(buf, binary.BigEndian, purpose); err ==
nil {
+ if err = binary.Write(buf, binary.BigEndian,
m.Expires.Epoch()*1000000); err == nil {
+ if n, err = buf.Write(hAddr[:]); err == nil {
+ if n != len(hAddr[:]) {
+ err = errors.New("write failed")
+ }
+ }
+ }
+ }
+ }
+ if err != nil {
+ logger.Printf(logger.ERROR, "[DHTP2PHelloMsg.SignedData]
failed: %s", err.Error())
+ }
+ return buf.Bytes()
+}
diff --git a/src/gnunet/message/msg_hello.go b/src/gnunet/message/msg_hello.go
index ae4c0e2..fdd9696 100644
--- a/src/gnunet/message/msg_hello.go
+++ b/src/gnunet/message/msg_hello.go
@@ -25,6 +25,8 @@ import (
"gnunet/util"
"io"
"time"
+
+ "github.com/bfix/gospel/logger"
)
//----------------------------------------------------------------------
@@ -112,11 +114,19 @@ func (a *HelloAddress) String() string {
// Bytes returns the binary representation of a HelloAddress
func (a *HelloAddress) Bytes() []byte {
buf := new(bytes.Buffer)
- buf.Write([]byte(a.transport))
- buf.WriteByte(0)
- binary.Write(buf, binary.BigEndian, a.addrSize)
- binary.Write(buf, binary.BigEndian, a.expires.Val)
- buf.Write(a.address)
+ _, err := buf.Write([]byte(a.transport))
+ if err == nil {
+ if err = buf.WriteByte(0); err == nil {
+ if err = binary.Write(buf, binary.BigEndian,
a.addrSize); err == nil {
+ if err = binary.Write(buf, binary.BigEndian,
a.expires.Val); err != nil {
+ _, err = buf.Write(a.address)
+ }
+ }
+ }
+ }
+ if err != nil {
+ logger.Printf(logger.ERROR, "[HelloAddress] failed: %s",
err.Error())
+ }
return buf.Bytes()
}
diff --git a/src/gnunet/message/msg_hello_dht.go
b/src/gnunet/message/msg_hello_dht.go
deleted file mode 100644
index f51757c..0000000
--- a/src/gnunet/message/msg_hello_dht.go
+++ /dev/null
@@ -1,167 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package message
-
-import (
- "bytes"
- "crypto/sha512"
- "encoding/binary"
- "fmt"
- "gnunet/enums"
- "gnunet/util"
- "time"
-
- "github.com/bfix/gospel/crypto/ed25519"
- "github.com/bfix/gospel/logger"
-)
-
-//----------------------------------------------------------------------
-// HELLO-DHT
-//
-// A HELLO message is used to exchange information about transports with
-// other DHT nodes. This struct is always followed by the actual network
-// addresses of type "HelloAddress"
-//----------------------------------------------------------------------
-
-// HelloDHTMsg is a message send by peers to announce their presence
-type HelloDHTMsg struct {
- MsgSize uint16 `order:"big"` // total size of message
- MsgType uint16 `order:"big"` // DHT_P2P_HELLO (157)
- Reserved uint16 `order:"big"` // Reserved for further use
- NumAddr uint16 `order:"big"` // Number of addresses in list
- Signature []byte `size:"64"` // Signature
- Expires util.AbsoluteTime `` // expiration time
- AddrList []byte `size:"*"` // List of end-point
addresses (HelloAddress)
-}
-
-// NewHelloMsgDHT creates an empty DHT_P2P_HELLO message.
-func NewHelloDHTMsg() *HelloDHTMsg {
- // return empty HelloMessage
- exp := time.Now().Add(HelloAddressExpiration)
- return &HelloDHTMsg{
- MsgSize: 80, // size without 'AddrList'
- MsgType: DHT_P2P_HELLO, // DHT_P2P_HELLO (157)
- Reserved: 0, // not used here
- NumAddr: 0, // start with empty
address list
- Signature: make([]byte, 64), // signature
- Expires: util.NewAbsoluteTime(exp), // default expiration
- AddrList: make([]byte, 0), // list of addresses
- }
-}
-
-// Addresses returns the list of HelloAddress
-func (m *HelloDHTMsg) Addresses() (list []*util.Address, err error) {
- var addr *util.Address
- var as string
- num, pos := 0, 0
- for {
- // parse address string from stream
- if as, pos = util.ReadCString(m.AddrList, pos); pos == -1 {
- break
- }
- if addr, err = util.ParseAddress(as); err != nil {
- return
- }
- addr.Expires = m.Expires
- list = append(list, addr)
- num++
- }
- // check numbers
- if num != int(m.NumAddr) {
- logger.Printf(logger.WARN, "[HelloDHTMsg] Number of addresses
does not match (got %d, expected %d)", num, m.NumAddr)
- }
- return
-}
-
-// SetAddresses adds addresses to the HELLO message.
-func (m *HelloDHTMsg) SetAddresses(list []*util.Address) {
- // write addresses as blob and track earliest expiration
- exp := util.NewAbsoluteTime(time.Now().Add(HelloAddressExpiration))
- wrt := new(bytes.Buffer)
- for _, addr := range list {
- // check if address expires before current expire
- if exp.Compare(addr.Expires) > 0 {
- exp = addr.Expires
- }
- n, _ := wrt.Write([]byte(addr.URI()))
- wrt.WriteByte(0)
- m.MsgSize += uint16(n + 1)
- }
- m.AddrList = wrt.Bytes()
- m.Expires = exp
- m.NumAddr = uint16(len(list))
-}
-
-// String returns a human-readable representation of the message.
-func (m *HelloDHTMsg) String() string {
- addrs, _ := m.Addresses()
- aList := ""
- for i, a := range addrs {
- if i > 0 {
- aList += ","
- }
- aList += a.URI()
- }
- return fmt.Sprintf("HelloDHTMsg{expire:%s,addrs=%d:[%s]}", m.Expires,
m.NumAddr, aList)
-}
-
-// Header returns the message header in a separate instance.
-func (m *HelloDHTMsg) Header() *Header {
- return &Header{m.MsgSize, m.MsgType}
-}
-
-// Verify the message signature
-func (m *HelloDHTMsg) Verify(peer *util.PeerID) (bool, error) {
- // assemble signed data and public key
- sd := m.signedData()
- pub := peer.PublicKey()
- sig, err := ed25519.NewEdSignatureFromBytes(m.Signature)
- if err != nil {
- return false, err
- }
- return pub.EdVerify(sd, sig)
-}
-
-// Sign the HELLO data with private key
-func (m *HelloDHTMsg) Sign(prv *ed25519.PrivateKey) error {
- // assemble signed data
- sd := m.signedData()
- sig, err := prv.EdSign(sd)
- if err != nil {
- return err
- }
- m.Signature = sig.Bytes()
- return nil
-}
-
-// signedData assembles a data block for sign and verify operations.
-func (m *HelloDHTMsg) signedData() []byte {
- // hash address block
- hAddr := sha512.Sum512(m.AddrList)
- var size uint32 = 80
- purpose := uint32(enums.SIG_HELLO)
-
- // assemble signed data
- buf := new(bytes.Buffer)
- binary.Write(buf, binary.BigEndian, size)
- binary.Write(buf, binary.BigEndian, purpose)
- binary.Write(buf, binary.BigEndian, m.Expires.Epoch()*1000000)
- buf.Write(hAddr[:])
- return buf.Bytes()
-}
diff --git a/src/gnunet/message/msg_namecache.go
b/src/gnunet/message/msg_namecache.go
index 517f11b..c36ec7c 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -149,7 +149,7 @@ func (m *NamecacheCacheMsg) Header() *Header {
// NAMECACHE_BLOCK_CACHE_RESPONSE
//----------------------------------------------------------------------
-// NamecacheCacheResponseMsg is the reponse message for a put request
+// NamecacheCacheResponseMsg is the response message for a put request
type NamecacheCacheResponseMsg struct {
MsgSize uint16 `order:"big"` // total size of message
MsgType uint16 `order:"big"` // NAMECACHE_LOOKUP_BLOCK_RESPONSE (432)
diff --git a/src/gnunet/message/msg_transport.go
b/src/gnunet/message/msg_transport.go
index 18d8ceb..fca651f 100644
--- a/src/gnunet/message/msg_transport.go
+++ b/src/gnunet/message/msg_transport.go
@@ -28,6 +28,7 @@ import (
"github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
)
//----------------------------------------------------------------------
@@ -106,7 +107,9 @@ func NewTransportPingMsg(target *util.PeerID, a
*util.Address) *TransportPingMsg
// String returns a human-readable representation of the message.
func (m *TransportPingMsg) String() string {
a := new(util.Address)
- data.Unmarshal(a, m.Address)
+ if err := data.Unmarshal(a, m.Address); err != nil {
+ logger.Printf(logger.ERROR, "[TransportPingMsg.String] failed:
%s", err.Error())
+ }
return fmt.Sprintf("TransportPingMsg{target=%s,addr=%s,challenge=%d}",
m.Target, a, m.Challenge)
}
@@ -155,7 +158,7 @@ func NewSignedAddress(a *util.Address) *SignedAddress {
return addr
}
-// TransportPongMsg is a reponse message for a PING request
+// TransportPongMsg is a response message for a PING request
type TransportPongMsg struct {
MsgSize uint16 `order:"big"` // total size of message
MsgType uint16 `order:"big"` // TRANSPORT_PING (372)
@@ -164,7 +167,7 @@ type TransportPongMsg struct {
SignedBlock *SignedAddress // signed block of data
}
-// NewTransportPongMsg creates a reponse message with an address the replying
+// NewTransportPongMsg creates a response message with an address the replying
// peer wants to be reached.
func NewTransportPongMsg(challenge uint32, a *util.Address) *TransportPongMsg {
m := &TransportPongMsg{
@@ -189,7 +192,7 @@ func (m *TransportPongMsg) String() string {
return fmt.Sprintf("TransportPongMsg{addr=%s,challenge=%d}",
a, m.Challenge)
}
- return fmt.Sprintf("TransportPongMsg{addr=<unkown>,%d}", m.Challenge)
+ return fmt.Sprintf("TransportPongMsg{addr=<unknown>,%d}", m.Challenge)
}
// Header returns the message header in a separate instance.
@@ -228,7 +231,7 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey)
(bool, error) {
// TRANSPORT_SESSION_ACK
//----------------------------------------------------------------------
-// SessionAckMsg is a message to acknowlege a session request
+// SessionAckMsg is a message to acknowledge a session request
type SessionAckMsg struct {
MsgSize uint16 `order:"big"` // total size of message
MsgType uint16 `order:"big"` // TRANSPORT_SESSION_ACK (377)
diff --git a/src/gnunet/message/types.go b/src/gnunet/message/types.go
index 3b1cf7a..6fa4d3a 100644
--- a/src/gnunet/message/types.go
+++ b/src/gnunet/message/types.go
@@ -16,6 +16,7 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
+//nolint:stylecheck // allow non-camel-case in constants
package message
// GNUnet message types
@@ -370,7 +371,7 @@ const (
NAMESTORE_MONITOR_START = 441 // Client to service: start
monitoring (yields sequence of "ZONE_ITERATION_RESPONSES" --- forever).
NAMESTORE_MONITOR_SYNC = 442 // Service to client: you're now
in sync.
NAMESTORE_RECORD_RESULT = 443 // Service to client: here is a
(plaintext) record you requested.
- NAMESTORE_MONITOR_NEXT = 444 // Client to service: I am now
ready for the next (set of) monitor events. Monitoring equivlaent of
#NAMESTORE_ZONE_ITERATION_NEXT.
+ NAMESTORE_MONITOR_NEXT = 444 // Client to service: I am now
ready for the next (set of) monitor events. Monitoring equivalent of
#NAMESTORE_ZONE_ITERATION_NEXT.
NAMESTORE_ZONE_ITERATION_START = 445 // Client to service: please
start iteration; receives "NAMESTORE_LOOKUP_NAME_RESPONSE" messages in return.
NAMESTORE_ZONE_ITERATION_NEXT = 447 // Client to service: next
record(s) in iteration please.
NAMESTORE_ZONE_ITERATION_STOP = 448 // Client to service: stop
iterating.
@@ -445,7 +446,7 @@ const (
CONSENSUS_P2P_ELEMENTS_REQUEST = 544 // Elements, and requests for
further elements
CONSENSUS_P2P_ELEMENTS_REPORT = 545 // Elements that a peer reports
to be missing at the remote peer.
CONSENSUS_P2P_HELLO = 546 // Initialization message for
consensus p2p communication.
- CONSENSUS_P2P_SYNCED = 547 // Report that the peer is
synced with the partner after successfuly decoding the invertible bloom filter.
+ CONSENSUS_P2P_SYNCED = 547 // Report that the peer is
synced with the partner after successfully decoding the invertible bloom filter.
CONSENSUS_P2P_FIN = 548 // Interaction os over, got
synched and reported all elements
CONSENSUS_P2P_ABORT = 548 // Abort a round, don't send
requested elements anymore
CONSENSUS_P2P_ROUND_CONTEXT = 547 // Abort a round, don't send
requested elements anymore
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index 81a9f01..5f7f8f0 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -67,7 +67,6 @@ func RequestResponse(
callee string,
path string,
req message.Message) (message.Message, error) {
-
// client-connect to the service
logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller,
callee)
cl, err := NewClient(ctx, path)
diff --git a/src/gnunet/service/connection.go b/src/gnunet/service/connection.go
index 1c690c5..d443160 100644
--- a/src/gnunet/service/connection.go
+++ b/src/gnunet/service/connection.go
@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"gnunet/message"
+ "gnunet/util"
"net"
"os"
"strconv"
@@ -43,6 +44,7 @@ var (
// based on Unix domain sockets. It is used locally by services and
// clients in the standard GNUnet environment.
type Connection struct {
+ id int // connection identifier
path string // file name of Unix socket
conn net.Conn // associated connection
buf []byte // read/write buffer
@@ -53,6 +55,7 @@ type Connection struct {
func NewConnection(ctx context.Context, path string) (s *Connection, err
error) {
var d net.Dialer
s = new(Connection)
+ s.id = util.NextID()
s.path = path
s.buf = make([]byte, 65536)
s.conn, err = d.DialContext(ctx, "unix", path)
@@ -118,11 +121,11 @@ func (s *Connection) Receive(ctx context.Context)
(message.Message, error) {
return nil, err
}
// get rest of message
- if err := get(4, int(mh.MsgSize)-4); err != nil {
+ if err = get(4, int(mh.MsgSize)-4); err != nil {
return nil, err
}
- msg, err := message.NewEmptyMessage(mh.MsgType)
- if err != nil {
+ var msg message.Message
+ if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
return nil, err
}
if msg == nil {
@@ -134,6 +137,11 @@ func (s *Connection) Receive(ctx context.Context)
(message.Message, error) {
return msg, nil
}
+// Receiver returns the receiving client (string representation)
+func (s *Connection) Receiver() string {
+ return fmt.Sprintf("uds:%d", s.id)
+}
+
//----------------------------------------------------------------------
// internal methods
//----------------------------------------------------------------------
@@ -212,7 +220,6 @@ func NewConnectionManager(
params map[string]string, // connection parameters
hdlr chan *Connection, // handler for incoming connections
) (cs *ConnectionManager, err error) {
-
// instantiate channel server
cs = &ConnectionManager{
listener: nil,
@@ -224,24 +231,21 @@ func NewConnectionManager(
return
}
// handle additional parameters
- if params != nil {
- for key, value := range params {
- switch key {
- case "perm": // set permissions on 'unix'
- if perm, err := strconv.ParseInt(value, 8, 32);
err == nil {
- if err := os.Chmod(path,
os.FileMode(perm)); err != nil {
- logger.Printf(
- logger.ERROR,
- "MsgChannelServer:
Failed to set permissions %s on %s: %s\n",
- path, value,
err.Error())
-
- }
- } else {
+ for key, value := range params {
+ switch key {
+ case "perm": // set permissions on 'unix'
+ if perm, err := strconv.ParseInt(value, 8, 32); err ==
nil {
+ if err := os.Chmod(path, os.FileMode(perm));
err != nil {
logger.Printf(
logger.ERROR,
- "MsgChannelServer: Invalid
permissions '%s'\n",
- value)
+ "MsgChannelServer: Failed to
set permissions %s on %s: %s\n",
+ path, value, err.Error())
}
+ } else {
+ logger.Printf(
+ logger.ERROR,
+ "MsgChannelServer: Invalid permissions
'%s'\n",
+ value)
}
}
}
diff --git a/src/gnunet/service/dht/blocks/filters.go
b/src/gnunet/service/dht/blocks/filters.go
new file mode 100644
index 0000000..0d194cc
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/filters.go
@@ -0,0 +1,296 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "bytes"
+ "crypto/sha512"
+ "encoding/binary"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// Peer filter
+//======================================================================
+
+// PeerFilter is a bloom filter without mutator
+type PeerFilter struct {
+ BF *BloomFilter
+}
+
+// NewPeerFilter creates an empty peer filter instance.
+func NewPeerFilter() *PeerFilter {
+ return &PeerFilter{
+ BF: NewBloomFilter(128),
+ }
+}
+
+// Add peer id to the filter
+func (pf *PeerFilter) Add(p *util.PeerID) {
+ pf.BF.Add(p.Data)
+}
+
+// Contains returns true if the peer id is filtered (in the filter)
+func (pf *PeerFilter) Contains(p *util.PeerID) bool {
+ return pf.BF.Contains(p.Data)
+}
+
+// Cloone peer filter instance
+func (pf *PeerFilter) Clone() *PeerFilter {
+ return &PeerFilter{
+ BF: pf.BF.Clone(),
+ }
+}
+
+//======================================================================
+// Result filter
+//======================================================================
+
+// ResultFilter return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+ RF_MORE = iota // Valid result, and there may be more.
+ RF_LAST // Last possible valid result.
+ RF_DUPLICATE // Valid result, but duplicate (was filtered by
the result filter).
+ RF_IRRELEVANT // Block does not satisfy the constraints imposed
by the XQuery.
+)
+
+// Compare return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+ CMP_SAME = iota // the two result filter are the same
+ CMP_MERGE // the two result filter can be merged
+ CMP_DIFFER // the two result filter are different
+ CMP_1 // used as state by derived/complex compare functions
+ CMP_2
+ CMP_3
+)
+
+//----------------------------------------------------------------------
+
+// ResultFilter is used to indicate to other peers which results are not of
+// interest when processing a GetMessage. Any peer which is processing
+// GetMessages and has a result which matches the query key MUST check the
+// result filter and only send a reply message if the result does not test
+// positive under the result filter. Before forwarding the GetMessage, the
+// result filter MUST be updated to filter out all results already returned
+// by the local peer.
+type ResultFilter interface {
+
+ // Add entry to filter
+ Add(Block)
+
+ // Contains returns true if entry is filtered
+ Contains(Block) bool
+
+ // Bytes returns the binary representation of a result filter
+ Bytes() []byte
+
+ // Compare two result filters
+ Compare(ResultFilter) int
+
+ // Merge two result filters
+ Merge(ResultFilter) bool
+}
+
+//----------------------------------------------------------------------
+// Dummy result filter
+// [Additional filters (per block type) are defined in corresponding files]
+//----------------------------------------------------------------------
+
+// PassResultFilter is a dummy result filter with no state.
+type PassResultFilter struct{}
+
+// Add a block to the result filter.
+func (rf *PassResultFilter) Add(Block) {
+}
+
+// Contains returns true if entry (binary representation) is filtered
+func (rf *PassResultFilter) Contains(Block) bool {
+ return false
+}
+
+// Bytes returns the binary representation of a result filter
+func (rf *PassResultFilter) Bytes() (buf []byte) {
+ return
+}
+
+// Merge two result filters
+func (rf *PassResultFilter) Merge(ResultFilter) bool {
+ return true
+}
+
+// Compare two result filters
+func (rf *PassResultFilter) Compare(t ResultFilter) int {
+ if _, ok := t.(*PassResultFilter); ok {
+ return CMP_SAME
+ }
+ return CMP_DIFFER
+}
+
+//======================================================================
+// Generic bllom filter with mutator
+//======================================================================
+
+// BloomFilter is a space-efficient probabilistic datastructure to test if
+// an element is part of a set of elementsis defined as a string of bits
+// always initially empty. An optional mutator can be used to additionally
+// "randomize" the computation of the bloomfilter while remaining
deterministic.
+type BloomFilter struct {
+ Bits []byte // filter bits
+
+ // transient attributes
+ mInput []byte // mutator input
+ mData []byte // mutator data
+}
+
+// NewBloomFilter creates a new empty filter of given size (8*n bits).
+func NewBloomFilter(n int) *BloomFilter {
+ return &BloomFilter{
+ Bits: make([]byte, n),
+ mInput: nil,
+ mData: nil,
+ }
+}
+
+// SetMutator to define a mutator for randomization. If 'm' is nil,
+// the mutator is removed from the filter (use with care!)
+func (bf *BloomFilter) SetMutator(m any) {
+ // handle mutator input
+ switch v := m.(type) {
+ case uint32:
+ buf := new(bytes.Buffer)
+ if err := binary.Write(buf, binary.BigEndian, v); err != nil {
+ logger.Printf(logger.ERROR, "[BloomFilter.SetMutator]
failed: %s", err.Error())
+ }
+ bf.mInput = buf.Bytes()
+ case []byte:
+ bf.mInput = make([]byte, 4)
+ util.CopyAlignedBlock(bf.mInput, v)
+ case nil:
+ bf.mInput = nil
+ bf.mData = nil
+ return
+ }
+ // generate mutator bytes
+ h := sha512.New()
+ if _, err := h.Write(bf.mInput); err != nil {
+ logger.Printf(logger.ERROR, "[BloomFilter.SetMutator] failed:
%s", err.Error())
+ }
+ bf.mData = h.Sum(nil)
+
+ //logger.Printf(logger.DBG, "[filter] Mutator %s -> %s",
hex.EncodeToString(bf.mInput), hex.EncodeToString(bf.mData))
+}
+
+// Mutator returns the mutator input as a 4-byte array
+func (bf *BloomFilter) Mutator() []byte {
+ return bf.mInput
+}
+
+// Bytes returns the binary representation of a bloom filter
+func (bf *BloomFilter) Bytes() []byte {
+ var buf []byte
+ if bf.mInput != nil {
+ buf = append(buf, bf.mInput...)
+ }
+ buf = append(buf, bf.Bits...)
+ return buf
+}
+
+// Compare two bloom filters
+func (bf *BloomFilter) Compare(a *BloomFilter) int {
+ if len(bf.Bits) != len(a.Bits) || !bytes.Equal(bf.mInput, a.mInput) {
+ return CMP_DIFFER
+ }
+ if bytes.Equal(bf.Bits, a.Bits) {
+ return CMP_SAME
+ }
+ return CMP_MERGE
+}
+
+// Merge two bloom filters
+func (bf *BloomFilter) Merge(a *BloomFilter) bool {
+ if len(bf.Bits) != len(a.Bits) || !bytes.Equal(bf.mInput, a.mInput) {
+ return false
+ }
+ for i := range bf.Bits {
+ bf.Bits[i] |= a.Bits[i]
+ }
+ return true
+}
+
+// Clone a bloom filter instance
+func (bf *BloomFilter) Clone() *BloomFilter {
+ return &BloomFilter{
+ Bits: util.Clone(bf.Bits),
+ mInput: util.Clone(bf.mInput),
+ mData: util.Clone(bf.mData),
+ }
+}
+
+// Add entry (binary representation):
+// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
+// integer n of the mapping M(e) is interpreted as a bit offset n mod L
+// within bf and set to 1.
+func (bf *BloomFilter) Add(e []byte) {
+ for _, idx := range bf.indices(e) {
+ bf.Bits[idx/8] |= (1 << (idx % 8))
+ }
+}
+
+// Contains returns true if the entry is most likely to be included:
+// When testing if an element may be in the Bloom filter bf using
+// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
+// Otherwise, the element is not considered to be in the Bloom filter.
+func (bf *BloomFilter) Contains(e []byte) bool {
+ for _, idx := range bf.indices(e) {
+ if bf.Bits[idx/8]&(1<<(idx%8)) == 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// indices returns the list of bit indices for antry e:
+// The element e is hashed using SHA-512. If a mutator is present, the
+// hash values are XOR-ed. The resulting value is interpreted as a list
+// of 16 32-bit integers in network byte order.
+func (bf *BloomFilter) indices(e []byte) []uint32 {
+ // hash the entry
+ h := sha512.Sum512(e)
+ // apply mutator if available
+ if bf.mData != nil {
+ for i := range h {
+ h[i] ^= bf.mData[i]
+ }
+ }
+ // compute the indices for the entry
+ size := uint32(8 * len(bf.Bits))
+ idx := make([]uint32, 16)
+ buf := bytes.NewReader(h[:])
+ for i := range idx {
+ if err := binary.Read(buf, binary.BigEndian, &idx[i]); err !=
nil {
+ logger.Printf(logger.ERROR, "[BloomFilter.indices]
failed: %s", err.Error())
+ }
+ idx[i] %= size
+ }
+ return idx
+}
diff --git a/src/gnunet/service/dht/blocks/filters_test.go
b/src/gnunet/service/dht/blocks/filters_test.go
new file mode 100644
index 0000000..ef1331c
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/filters_test.go
@@ -0,0 +1,110 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "bytes"
+ "crypto/rand"
+ "sort"
+ "testing"
+)
+
+type Entry []byte
+
+type EntryList []Entry
+
+func (list EntryList) Len() int { return len(list) }
+func (list EntryList) Swap(i, j int) { list[i], list[j] = list[j],
list[i] }
+func (list EntryList) Less(i, j int) bool { return bytes.Compare(list[i],
list[j]) < 0 }
+
+func (list EntryList) Contains(e Entry) bool {
+ size := len(list)
+ i := sort.Search(size, func(i int) bool { return bytes.Compare(list[i],
e) >= 0 })
+ return i != size
+}
+
+func TestBloomfilter(t *testing.T) {
+ F := 500 // number of expected entries
+
+ // The K-value for the HELLO_BF Bloom filter is always 16. The size S of
+ // the Bloom filter in bytes depends on the number of elements F known
to
+ // be filtered at the initiator. If F is zero, the size S is just 8
(bytes).
+ // Otherwise, S is set to the minimum of 2^15 and the lowest power of 2
that
+ // is strictly larger than K*F/4 (in bytes). The wire format of
HELLO_BF is
+ // the resulting byte array. In particular, K is never transmitted.
+ S := 1
+ for S < 4*F && S < 32768 {
+ S <<= 1
+ }
+ t.Logf("BloomFilter size in bytes: %d\n", S)
+
+ // generate positives (entries in the set)
+ positives := make(EntryList, F)
+ for i := 0; i < F; i++ {
+ data := make(Entry, 32)
+ if _, err := rand.Read(data); err != nil {
+ t.Fatal(err)
+ }
+ positives[i] = data
+ }
+ sort.Sort(positives)
+
+ // generate negatives (entries outside the set)
+ negatives := make(EntryList, F)
+ for i := 0; i < F; {
+ data := make(Entry, 32)
+ if _, err := rand.Read(data); err != nil {
+ t.Fatal(err)
+ }
+ if !positives.Contains(data) {
+ negatives[i] = data
+ i++
+ }
+ }
+
+ // create BloomFilter
+ bf := NewBloomFilter(S)
+
+ // add positives to bloomfilter
+ for _, e := range positives {
+ bf.Add(e)
+ }
+
+ // check lookup of positives
+ count := 0
+ for _, e := range positives {
+ if !bf.Contains(e) {
+ count++
+ }
+ }
+ if count > 0 {
+ t.Logf("FAILED with %d false-negatives", count)
+ }
+
+ // check lookup of negatives
+ count = 0
+ for _, e := range negatives {
+ if bf.Contains(e) {
+ count++
+ }
+ }
+ if count > 0 {
+ t.Logf("FAILED with %d false-positives", count)
+ }
+}
diff --git a/src/gnunet/service/dht/blocks/generic.go
b/src/gnunet/service/dht/blocks/generic.go
index 6301e3b..2962025 100644
--- a/src/gnunet/service/dht/blocks/generic.go
+++ b/src/gnunet/service/dht/blocks/generic.go
@@ -19,11 +19,10 @@
package blocks
import (
- "bytes"
- "encoding/gob"
"encoding/hex"
"fmt"
"gnunet/crypto"
+ "gnunet/enums"
"gnunet/util"
"github.com/bfix/gospel/data"
@@ -39,13 +38,11 @@ type Query interface {
// Key returns the DHT key for a block
Key() *crypto.HashCode
- // Get retrieves the value of a named query parameter. The value is
- // unchanged if the key is not in the map or if the value in the map
- // has an incompatible type.
- Get(key string, value any) bool
+ // Type returns the requested block type
+ Type() uint16
- // Set stores the value of a named query parameter
- Set(key string, value any)
+ // Flags returns the query flags
+ Flags() uint16
// Verify the integrity of a retrieved block (optional). Override in
// custom query types to implement block-specific integrity checks
@@ -77,7 +74,7 @@ type Block interface {
// types to implement block-specific integrity checks (see GNSBlock for
// example). This verification is usually weaker than the verification
// method from a Query (see GNSBlock.Verify for explanation).
- Verify() error
+ Verify() (bool, error)
// String returns the human-readable representation of a block
String() string
@@ -97,8 +94,14 @@ type GenericQuery struct {
// Key for repository queries (local/remote)
key *crypto.HashCode
- // query parameters (binary value representation)
- params map[string][]byte
+ // block type requested
+ btype uint16
+
+ // query flags
+ flags uint16
+
+ // Params holds additional query parameters
+ Params util.ParameterSet
}
// Key interface method implementation
@@ -106,23 +109,14 @@ func (q *GenericQuery) Key() *crypto.HashCode {
return q.key
}
-// Get retrieves the value of a named query parameter
-func (q *GenericQuery) Get(key string, value any) bool {
- data, ok := q.params[key]
- if !ok {
- return false
- }
- dec := gob.NewDecoder(bytes.NewReader(data))
- return dec.Decode(value) != nil
+// Type returns the requested block type
+func (q *GenericQuery) Type() uint16 {
+ return q.btype
}
-// Set stores the value of a named query parameter
-func (q *GenericQuery) Set(key string, value any) {
- wrt := new(bytes.Buffer)
- enc := gob.NewEncoder(wrt)
- if enc.Encode(value) == nil {
- q.params[key] = wrt.Bytes()
- }
+// Flags returns the query flags
+func (q *GenericQuery) Flags() uint16 {
+ return q.flags
}
// Verify interface method implementation
@@ -139,14 +133,16 @@ func (q *GenericQuery) Decrypt(b Block) error {
// String returns the human-readable representation of a block
func (q *GenericQuery) String() string {
- return fmt.Sprintf("GenericQuery{key=%s}",
hex.EncodeToString(q.Key().Bits))
+ return fmt.Sprintf("GenericQuery{btype=%d,key=%s}", q.btype,
hex.EncodeToString(q.Key().Bits))
}
// NewGenericQuery creates a simple Query from hash code.
-func NewGenericQuery(buf []byte) *GenericQuery {
+func NewGenericQuery(key []byte, btype enums.BlockType, flags uint16)
*GenericQuery {
return &GenericQuery{
- key: crypto.NewHashCode(buf),
- params: make(map[string][]byte),
+ key: crypto.NewHashCode(key),
+ btype: uint16(btype),
+ flags: flags,
+ Params: make(util.ParameterSet),
}
}
@@ -181,16 +177,16 @@ func (b *GenericBlock) String() string {
}
// Verify interface method implementation
-func (b *GenericBlock) Verify() error {
+func (b *GenericBlock) Verify() (bool, error) {
// no verification, no errors ;)
- return nil
+ return true, nil
}
// NewGenericBlock creates a Block from binary data.
func NewGenericBlock(buf []byte) *GenericBlock {
return &GenericBlock{
block: util.Clone(buf),
- btype: DHT_BLOCK_ANY, // unknown block type
- expire: util.AbsoluteTimeNever(), // never expires
+ btype: uint16(enums.BLOCK_TYPE_ANY), // unknown block type
+ expire: util.AbsoluteTimeNever(), // never expires
}
}
diff --git a/src/gnunet/service/dht/blocks/generic_test.go
b/src/gnunet/service/dht/blocks/generic_test.go
deleted file mode 100644
index 51ee5a1..0000000
--- a/src/gnunet/service/dht/blocks/generic_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package blocks
-
-import (
- "bytes"
- "testing"
-)
-
-// Test parameter handling for queries
-func TestQueryParams(t *testing.T) {
- q := NewGenericQuery(nil)
-
- // set parameters
- var (
- btype uint16 = DHT_BLOCK_ANY
- flags uint32 = 0
- name string = "Test"
- data = make([]byte, 8)
- )
- q.Set("btype", btype)
- q.Set("flags", flags)
- q.Set("name", name)
- q.Set("data", data)
-
- // get parameters
- var (
- t_btype uint16
- t_flags uint32
- t_name string
- t_data []byte
- )
- q.Get("btype", &t_btype)
- q.Get("flags", &t_flags)
- q.Get("name", &t_name)
- q.Get("data", &t_data)
-
- // check for unchanged data
- if btype != t_btype {
- t.Fatal("btype mismatch")
- }
- if flags != t_flags {
- t.Fatal("flags mismatch")
- }
- if name != t_name {
- t.Fatal("name mismatch")
- }
- if !bytes.Equal(data, t_data) {
- t.Fatal("data mismatch")
- }
-}
diff --git a/src/gnunet/service/dht/blocks/gns.go
b/src/gnunet/service/dht/blocks/gns.go
index 2085677..0225003 100644
--- a/src/gnunet/service/dht/blocks/gns.go
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -22,14 +22,19 @@ import (
"errors"
"fmt"
"gnunet/crypto"
+ "gnunet/enums"
"gnunet/util"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
)
// Error messages
var (
- ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
+ ErrBlockNotDecrypted = errors.New("GNS block not decrypted")
+ ErrBlockInvalidSig = errors.New("invalid signature key for GNS
Block")
+ ErrBlockTypeNotVerified = errors.New("can't verify block type")
+ ErrBlockCantDecrypt = errors.New("can't decrypt block type")
)
//----------------------------------------------------------------------
@@ -54,9 +59,13 @@ func (q *GNSQuery) Verify(b Block) (err error) {
// verify derived key
dkey := blk.DerivedKeySig.ZoneKey
- dkey2, _ := q.Zone.Derive(q.Label, "gns")
+ var dkey2 *crypto.ZoneKey
+ if dkey2, _, err = q.Zone.Derive(q.Label, "gns"); err != nil {
+ return
+ }
if !dkey.Equal(dkey2) {
- return fmt.Errorf("invalid signature key for GNS Block")
+ err = ErrBlockInvalidSig
+ return
}
// verify signature
var buf []byte
@@ -66,7 +75,7 @@ func (q *GNSQuery) Verify(b Block) (err error) {
blk.verified, err = blk.DerivedKeySig.Verify(buf)
default:
- err = errors.New("can't verify block type")
+ err = ErrBlockTypeNotVerified
}
return
}
@@ -81,7 +90,7 @@ func (q *GNSQuery) Decrypt(b Block) (err error) {
return
default:
- err = errors.New("can't decrypt block type")
+ err = ErrBlockCantDecrypt
}
return
}
@@ -91,10 +100,13 @@ func NewGNSQuery(zkey *crypto.ZoneKey, label string)
*GNSQuery {
// derive a public key from (pkey,label) and set the repository
// key as the SHA512 hash of the binary key representation.
// (key blinding)
- pd, _ := zkey.Derive(label, "gns")
+ pd, _, err := zkey.Derive(label, "gns")
+ if err != nil {
+ logger.Printf(logger.ERROR, "[NewGNSQuery] failed: %s",
err.Error())
+ }
gq := crypto.Hash(pd.Bytes()).Bits
return &GNSQuery{
- GenericQuery: *NewGenericQuery(gq),
+ GenericQuery: *NewGenericQuery(gq,
enums.BLOCK_TYPE_GNS_NAMERECORD, 0),
Zone: zkey,
Label: label,
derived: pd,
@@ -161,12 +173,11 @@ func NewBlock() *GNSBlock {
// Only the cryptographic signature is verified; the formal correctness of
// the association between the block and a GNS label in a GNS zone can't
// be verified. This is only possible in Query.Verify().
-func (b *GNSBlock) Verify() (err error) {
+func (b *GNSBlock) Verify() (ok bool, err error) {
// verify signature
var buf []byte
if buf, err = data.Marshal(b.Body); err != nil {
return
}
- _, err = b.DerivedKeySig.Verify(buf)
- return
+ return b.DerivedKeySig.Verify(buf)
}
diff --git a/src/gnunet/service/dht/blocks/handlers.go
b/src/gnunet/service/dht/blocks/handlers.go
new file mode 100644
index 0000000..9df3867
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/handlers.go
@@ -0,0 +1,80 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "gnunet/crypto"
+ "gnunet/enums"
+)
+
+// BlockHandler interface defines methods specific to block types.
+type BlockHandler interface {
+
+ // Parse a block instance from binary data
+ ParseBlock(buf []byte) (Block, error)
+
+ // ValidateBlockQuery is used to evaluate the request for a block as
part of
+ // DHT-P2P-GET processing. Here, the block payload is unknown, but if
possible
+ // the XQuery and Key SHOULD be verified.
+ ValidateBlockQuery(key *crypto.HashCode, xquery []byte) bool
+
+ // ValidateBlockKey returns true if the block key is the same as the
+ // query key used to access the block.
+ ValidateBlockKey(b Block, key *crypto.HashCode) bool
+
+ // ValidateBlockStoreRequest is used to evaluate a block payload as
part of
+ // PutMessage and ResultMessage processing.
+ ValidateBlockStoreRequest(b Block) bool
+
+ // SetupResultFilter is used to setup an empty result filter. The
arguments
+ // are the set of results that must be filtered at the initiator, and a
+ // MUTATOR value which MAY be used to deterministically re-randomize
+ // probabilistic data structures.
+ SetupResultFilter(filterSize int, mutator uint32) ResultFilter
+
+ // ParseResultFilter from binary data
+ ParseResultFilter(data []byte) ResultFilter
+
+ // FilterResult is used to filter results against specific queries. This
+ // function does not check the validity of the block itself or that it
+ // matches the given key, as this must have been checked earlier. Thus,
+ // locally stored blocks from previously observed ResultMessages and
+ // PutMessages use this function to perform filtering based on the
request
+ // parameters of a particular GET operation. Possible values for the
+ // FilterEvaluationResult are defined above. If the main evaluation
result
+ // is RF_MORE, the function also returns and updated result filter where
+ // the block is added to the set of filtered replies. An implementation
is
+ // not expected to actually differentiate between the RF_DUPLICATE and
+ // RF_IRRELEVANT return values: in both cases the block is ignored for
+ // this query.
+ FilterResult(b Block, key *crypto.HashCode, rf ResultFilter, xQuery
[]byte) int
+}
+
+// BlockHandlers is a map of block query validation implementations
+// for supported block types.
+var BlockHandlers map[enums.BlockType]BlockHandler
+
+// initializer function
+func init() {
+ // create map instance
+ BlockHandlers = make(map[enums.BlockType]BlockHandler)
+
+ // add validation functions
+ BlockHandlers[enums.BLOCK_TYPE_DHT_URL_HELLO] = new(HelloBlockHandler)
+}
diff --git a/src/gnunet/service/dht/blocks/hello.go
b/src/gnunet/service/dht/blocks/hello.go
index a5ccf8c..c884fd2 100644
--- a/src/gnunet/service/dht/blocks/hello.go
+++ b/src/gnunet/service/dht/blocks/hello.go
@@ -24,6 +24,7 @@ import (
"encoding/binary"
"errors"
"fmt"
+ "gnunet/crypto"
"gnunet/enums"
"gnunet/util"
"net/url"
@@ -32,6 +33,7 @@ import (
"github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
)
// HELLO-related errors
@@ -50,12 +52,12 @@ const helloPrefix = "gnunet://hello/"
// HelloBlock is the DHT-managed block type for HELLO information.
// It is used to create and parse HELLO URLs.
// All addresses expire at the same time /this different from HELLO
-// messages (see message.HeeloMsg).
+// messages (see message.HelloMsg).
type HelloBlock struct {
- PeerID *util.PeerID `` // peer identifier
- Signature []byte `size:"64"` // signature
- Expire util.AbsoluteTime `` // Expiration date
- AddrBin []byte `size:"*"` // raw address data
+ PeerID *util.PeerID `` // peer identifier
+ Signature *util.PeerSignature `` // signature
+ Expires util.AbsoluteTime `` // Expiration date
+ AddrBin []byte `size:"*"` // raw address data
// transient attributes
addrs []*util.Address // cooked address data
@@ -64,7 +66,9 @@ type HelloBlock struct {
// SetAddresses adds a bulk of addresses for this HELLO block.
func (h *HelloBlock) SetAddresses(a []*util.Address) {
h.addrs = util.Clone(a)
- h.finalize()
+ if err := h.finalize(); err != nil {
+ logger.Printf(logger.ERROR, "[HelloBlock.SetAddresses] failed:
%s", err.Error())
+ }
}
// Addresses returns the list of addresses
@@ -101,9 +105,10 @@ func ParseHelloURL(u string, checkExpiry bool) (h
*HelloBlock, err error) {
h.PeerID = util.NewPeerID(buf)
// (2) parse signature
- if h.Signature, err = util.DecodeStringToBinary(p[1], 64); err != nil {
+ if buf, err = util.DecodeStringToBinary(p[1], 64); err != nil {
return
}
+ h.Signature = util.NewPeerSignature(buf)
// (3) split last element into parts
q := strings.SplitN(p[2], "?", 2)
@@ -113,8 +118,8 @@ func ParseHelloURL(u string, checkExpiry bool) (h
*HelloBlock, err error) {
if exp, err = strconv.ParseUint(q[0], 10, 64); err != nil {
return
}
- h.Expire = util.NewAbsoluteTimeEpoch(exp)
- if checkExpiry && h.Expire.Expired() {
+ h.Expires = util.NewAbsoluteTimeEpoch(exp)
+ if checkExpiry && h.Expires.Expired() {
err = ErrHelloExpired
return
}
@@ -138,7 +143,9 @@ func ParseHelloURL(u string, checkExpiry bool) (h
*HelloBlock, err error) {
}
// (6) generate raw address data so block is complete
- h.finalize()
+ if err = h.finalize(); err != nil {
+ return
+ }
// check signature
var ok bool
@@ -190,24 +197,39 @@ func (h *HelloBlock) finalize() (err error) {
return
}
-/*
-// Message returns the corresponding HELLO message to be sent to peers.
-func (h *HelloBlock) Message() *message.HelloMsg {
- msg := message.NewHelloMsg(h.PeerID)
- for _, a := range h.addrs {
- msg.AddAddress(message.NewHelloAddress(a, h.Expire))
+// Return the block type
+func (h *HelloBlock) Type() uint16 {
+ return uint16(enums.BLOCK_TYPE_DHT_URL_HELLO)
+}
+
+// Data returns the raw block data
+func (h *HelloBlock) Data() []byte {
+ buf, err := data.Marshal(h)
+ if err != nil {
+ logger.Println(logger.ERROR, "[hello] Failed to serialize HELLO
block: "+err.Error())
+ buf = nil
}
- return msg
+ return buf
+}
+
+// Expire returns the block expiration
+func (h *HelloBlock) Expire() util.AbsoluteTime {
+ return h.Expires
+}
+
+// String returns the human-readable representation of a block
+func (h *HelloBlock) String() string {
+ return fmt.Sprintf("HelloBlock{peer=%s,expires=%s,addrs=[%d]}",
+ h.PeerID, h.Expires, len(h.Addresses()))
}
-*/
// URL returns the HELLO URL for the data.
func (h *HelloBlock) URL() string {
u := fmt.Sprintf("%s%s/%s/%d?",
helloPrefix,
h.PeerID.String(),
- util.EncodeBinaryToString(h.Signature),
- h.Expire.Epoch(),
+ util.EncodeBinaryToString(h.Signature.Data),
+ h.Expires.Epoch(),
)
for i, a := range h.addrs {
if i > 0 {
@@ -221,10 +243,10 @@ func (h *HelloBlock) URL() string {
}
// Equals returns true if two HELLOs are the same. The expiration
-// timestamp is ignored in the comparision.
+// timestamp is ignored in the comparison.
func (h *HelloBlock) Equals(g *HelloBlock) bool {
if !h.PeerID.Equals(g.PeerID) ||
- !util.Equals(h.Signature, g.Signature) ||
+ !util.Equals(h.Signature.Data, g.Signature.Data) ||
len(h.addrs) != len(g.addrs) {
return false
}
@@ -239,29 +261,23 @@ func (h *HelloBlock) Equals(g *HelloBlock) bool {
// Verify the integrity of the HELLO data
func (h *HelloBlock) Verify() (bool, error) {
// assemble signed data and public key
- sd := h.signedData()
- pub := h.PeerID.PublicKey()
- sig, err := ed25519.NewEdSignatureFromBytes(h.Signature)
+ sd := h.SignedData()
+ pub := ed25519.NewPublicKeyFromBytes(h.PeerID.Data)
+ sig, err := ed25519.NewEdSignatureFromBytes(h.Signature.Data)
if err != nil {
return false, err
}
return pub.EdVerify(sd, sig)
}
-// Sign the HELLO data with private key
-func (h *HelloBlock) Sign(prv *ed25519.PrivateKey) error {
- // assemble signed data
- sd := h.signedData()
- sig, err := prv.EdSign(sd)
- if err != nil {
- return err
- }
- h.Signature = sig.Bytes()
+// SetSignature stores a signature in the the HELLO block
+func (h *HelloBlock) SetSignature(sig *util.PeerSignature) error {
+ h.Signature = sig
return nil
}
-// signedData assembles a data block for sign and verify operations.
-func (h *HelloBlock) signedData() []byte {
+// SignedData assembles a data block for sign and verify operations.
+func (h *HelloBlock) SignedData() []byte {
// hash address block
hAddr := sha512.Sum512(h.AddrBin)
var size uint32 = 80
@@ -269,9 +285,166 @@ func (h *HelloBlock) signedData() []byte {
// assemble signed data
buf := new(bytes.Buffer)
- binary.Write(buf, binary.BigEndian, size)
- binary.Write(buf, binary.BigEndian, purpose)
- binary.Write(buf, binary.BigEndian, h.Expire.Epoch()*1000000)
- buf.Write(hAddr[:])
+ var n int
+ err := binary.Write(buf, binary.BigEndian, size)
+ if err == nil {
+ if err = binary.Write(buf, binary.BigEndian, purpose); err ==
nil {
+ if err = binary.Write(buf, binary.BigEndian,
h.Expires.Epoch()*1000000); err == nil {
+ if n, err = buf.Write(hAddr[:]); err == nil {
+ if n != len(hAddr[:]) {
+ err = errors.New("signed data
size mismatch")
+ }
+ }
+ }
+ }
+ }
+ if err != nil {
+ logger.Printf(logger.ERROR, "[HelloBlock.SignedData] failed:
%s", err.Error())
+ }
return buf.Bytes()
}
+
+//----------------------------------------------------------------------
+// HELLO block handler
+//----------------------------------------------------------------------
+
+// HelloBlockHandler methods related to HELLO blocks
+type HelloBlockHandler struct{}
+
+// Parse a block instance from binary data
+func (bh *HelloBlockHandler) ParseBlock(buf []byte) (Block, error) {
+ return ParseHelloFromBytes(buf)
+}
+
+// ValidateHelloBlockQuery validates query parameters for a
+// DHT-GET request for HELLO blocks.
+func (bh *HelloBlockHandler) ValidateBlockQuery(key *crypto.HashCode, xquery
[]byte) bool {
+ // no xquery parameters allowed.
+ return len(xquery) == 0
+}
+
+// ValidateBlockKey returns true if the block key is the same as the
+// query key used to access the block.
+func (bh *HelloBlockHandler) ValidateBlockKey(b Block, key *crypto.HashCode)
bool {
+ hb, ok := b.(*HelloBlock)
+ if !ok {
+ return false
+ }
+ // key must be the hash of the peer id
+ bkey := crypto.Hash(hb.PeerID.Bytes())
+ return key.Equals(bkey)
+}
+
+// ValidateBlockStoreRequest is used to evaluate a block payload as part of
+// PutMessage and ResultMessage processing.
+func (bh *HelloBlockHandler) ValidateBlockStoreRequest(b Block) bool {
+ // TODO: verify block payload
+ return true
+}
+
+// SetupResultFilter is used to setup an empty result filter. The arguments
+// are the set of results that must be filtered at the initiator, and a
+// MUTATOR value which MAY be used to deterministically re-randomize
+// probabilistic data structures.
+func (bh *HelloBlockHandler) SetupResultFilter(filterSize int, mutator uint32)
ResultFilter {
+ return NewHelloResultFilter(filterSize, mutator)
+}
+
+// ParseResultFilter from binary data
+func (bh *HelloBlockHandler) ParseResultFilter(data []byte) ResultFilter {
+ return NewHelloResultFilterFromBytes(data)
+}
+
+// FilterResult is used to filter results against specific queries. This
+// function does not check the validity of the block itself or that it
+// matches the given key, as this must have been checked earlier. Thus,
+// locally stored blocks from previously observed ResultMessages and
+// PutMessages use this function to perform filtering based on the request
+// parameters of a particular GET operation. Possible values for the
+// FilterEvaluationResult are defined above. If the main evaluation result
+// is RF_MORE, the function also returns and updated result filter where
+// the block is added to the set of filtered replies. An implementation is
+// not expected to actually differentiate between the RF_DUPLICATE and
+// RF_IRRELEVANT return values: in both cases the block is ignored for
+// this query.
+func (bh *HelloBlockHandler) FilterResult(b Block, key *crypto.HashCode, rf
ResultFilter, xQuery []byte) int {
+ if rf.Contains(b) {
+ return RF_DUPLICATE
+ }
+ rf.Add(b)
+ return RF_LAST
+}
+
+//----------------------------------------------------------------------
+
+// HelloResultFilter is a result filter implementation for HELLO blocks
+type HelloResultFilter struct {
+ bf *BloomFilter
+}
+
+// NewHelloResultFilter initializes an empty resut filter
+func NewHelloResultFilter(filterSize int, mutator uint32) *HelloResultFilter {
+ // HELLO result filters are BloomFilters with a mutator
+ rf := new(HelloResultFilter)
+ rf.bf = NewBloomFilter(filterSize)
+ rf.bf.SetMutator(mutator)
+ return rf
+}
+
+// NewHelloResultFilterFromBytes creates a new result filter from a binary
+// representation: 'data' is the concatenaion 'mutator|bloomfilter'.
+// If 'withMutator' is false, no mutator is used.
+func NewHelloResultFilterFromBytes(data []byte) *HelloResultFilter {
+ //logger.Printf(logger.DBG, "[filter] FromBytes = %d:%s (mutator:
%v)",len(data), hex.EncodeToString(data), withMutator)
+
+ // handle mutator input
+ mSize := 4
+ rf := new(HelloResultFilter)
+ rf.bf = &BloomFilter{
+ Bits: util.Clone(data[mSize:]),
+ }
+ if mSize > 0 {
+ rf.bf.SetMutator(data[:mSize])
+ }
+ return rf
+}
+
+// Add a HELLO block to th result filter
+func (rf *HelloResultFilter) Add(b Block) {
+ if hb, ok := b.(*HelloBlock); ok {
+ hAddr := sha512.Sum512(hb.AddrBin)
+ rf.bf.Add(hAddr[:])
+ }
+}
+
+// Contains checks if a block is contained in the result filter
+func (rf *HelloResultFilter) Contains(b Block) bool {
+ if hb, ok := b.(*HelloBlock); ok {
+ hAddr := sha512.Sum512(hb.AddrBin)
+ return rf.bf.Contains(hAddr[:])
+ }
+ return false
+}
+
+// Bytes returns a binary representation of a HELLO result filter
+func (rf *HelloResultFilter) Bytes() []byte {
+ return rf.bf.Bytes()
+}
+
+// Compare two HELLO result filters
+func (rf *HelloResultFilter) Compare(t ResultFilter) int {
+ trf, ok := t.(*HelloResultFilter)
+ if !ok {
+ return CMP_DIFFER
+ }
+ return rf.bf.Compare(trf.bf)
+}
+
+// Merge two HELLO result filters
+func (rf *HelloResultFilter) Merge(t ResultFilter) bool {
+ trf, ok := t.(*HelloResultFilter)
+ if !ok {
+ return false
+ }
+ return rf.bf.Merge(trf.bf)
+}
diff --git a/src/gnunet/service/dht/blocks/types.go
b/src/gnunet/service/dht/blocks/types.go
deleted file mode 100644
index 04edb6e..0000000
--- a/src/gnunet/service/dht/blocks/types.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package blocks
-
-// DHT Block types
-const (
- DHT_BLOCK_ANY = 0
- DHT_BLOCK_HELLO = 7 // Type of a block that contains a HELLO for a peer
- DHT_BLOCK_GNS = 11 // Block for storing record data
-)
diff --git a/src/gnunet/service/dht/bloomfilter.go
b/src/gnunet/service/dht/bloomfilter.go
deleted file mode 100644
index dcfd935..0000000
--- a/src/gnunet/service/dht/bloomfilter.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package dht
-
-import (
- "bytes"
- "crypto/sha512"
- "encoding/binary"
-)
-
-//======================================================================
-// Generic BloomFilter
-//======================================================================
-
-// BloomFilter parameter
-var (
- bfNumBits = 128
- bfHash = sha512.New
-)
-
-// BloomFilter is a space-efficient probabilistic datastructure to test if
-// an element is part of a set of elementsis defined as a string of bits
-// always initially empty.
-type BloomFilter struct {
- data []byte // filter bits
- salt []byte // salt for hashing
-}
-
-// NewBloomFilter cretes a new filter using the specified salt. An unused
-// salt is set to nil.
-func NewBloomFilter(salt []byte) *BloomFilter {
- return &BloomFilter{
- data: make([]byte, (bfNumBits+7)/8),
- salt: salt,
- }
-}
-
-// Add entry (binary representation):
-// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
-// integer n of the mapping M(e) is interpreted as a bit offset n mod L
-// within bf and set to 1.
-func (bf *BloomFilter) Add(e []byte) {
- for _, idx := range bf.indices(e) {
- bf.data[idx/8] |= (1 << (idx % 7))
- }
-}
-
-// Contains returns true if the entry is most likely to be included:
-// When testing if an element may be in the Bloom filter bf using
-// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
-// Otherwise, the element is not considered to be in the Bloom filter.
-func (bf *BloomFilter) Contains(e []byte) bool {
- for _, idx := range bf.indices(e) {
- if bf.data[idx/8]&(1<<(idx%7)) == 0 {
- return false
- }
- }
- return true
-}
-
-// indices returns the list of bit indices for antry e:
-// The element e is prepended with a salt (pütional) and hashed using SHA-512.
-// The resulting byte string is interpreted as a list of 16 32-bit integers
-// in network byte order.
-func (bf *BloomFilter) indices(e []byte) []int {
- // hash the entry (with optional salt prepended)
- hsh := bfHash()
- if bf.salt != nil {
- hsh.Write(bf.salt)
- }
- hsh.Write(e)
- h := hsh.Sum(nil)
-
- // compute the indices for the entry
- idx := make([]int, len(h)/2)
- buf := bytes.NewReader(h)
- for i := range idx {
- binary.Read(buf, binary.BigEndian, &idx[i])
- }
- return idx
-}
-
-//======================================================================
-// BloomFilter for peer addresses
-//======================================================================
-
-// PeerBloomFilter implements specific Add/Contains functions.
-type PeerBloomFilter struct {
- BloomFilter
-}
-
-// NewPeerBloomFilter creates a new filter for peer addresses.
-func NewPeerBloomFilter() *PeerBloomFilter {
- return &PeerBloomFilter{
- BloomFilter: *NewBloomFilter(nil),
- }
-}
-
-// Add peer address to the filter.
-func (bf *PeerBloomFilter) Add(p *PeerAddress) {
- bf.BloomFilter.Add(p.addr[:])
-}
-
-// Contains returns true if the peer address is most likely to be included.
-func (bf *PeerBloomFilter) Contains(p *PeerAddress) bool {
- return bf.BloomFilter.Contains(p.addr[:])
-}
diff --git a/src/gnunet/service/dht/messages.go
b/src/gnunet/service/dht/messages.go
new file mode 100644
index 0000000..38f0753
--- /dev/null
+++ b/src/gnunet/service/dht/messages.go
@@ -0,0 +1,380 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "context"
+ "gnunet/enums"
+ "gnunet/message"
+ "gnunet/service/dht/blocks"
+ "gnunet/transport"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/logger"
+ "github.com/bfix/gospel/math"
+)
+
+//----------------------------------------------------------------------
+// Handle DHT messages from the network
+//----------------------------------------------------------------------
+
+// HandleMessage handles a DHT request/response message. Responses are sent
+// to the specified responder.
+func (m *Module) HandleMessage(ctx context.Context, sender *util.PeerID, msgIn
message.Message, back transport.Responder) bool {
+ // assemble log label
+ label := "dht"
+ if v := ctx.Value("label"); v != nil {
+ if s, _ := v.(string); len(s) > 0 {
+ label = "dht-" + s
+ }
+ }
+ logger.Printf(logger.INFO, "[%s] message received from %s", label,
sender)
+
+ // process message
+ switch msg := msgIn.(type) {
+
+ case *message.DHTP2PGetMsg:
+ //--------------------------------------------------------------
+ // DHT-P2P GET
+ //--------------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-GET message",
label)
+ query := blocks.NewGenericQuery(msg.Query.Bits,
enums.BlockType(msg.BType), msg.Flags)
+
+ var block blocks.Block
+ var dist *math.Int
+ var err error
+
+ //--------------------------------------------------------------
+ // validate query (based on block type requested) (9.4.3.1)
+ btype := enums.BlockType(msg.BType)
+ blockHdlr, ok := blocks.BlockHandlers[btype]
+ if ok {
+ // validate block query
+ if !blockHdlr.ValidateBlockQuery(msg.Query, msg.XQuery)
{
+ logger.Printf(logger.WARN, "[%s] DHT-P2P-GET
invalid query -- discarded", label)
+ return false
+ }
+ } else {
+ logger.Printf(logger.INFO, "[%s] no handler defined for
block type %s", label, btype.String())
+ blockHdlr = nil
+ }
+ //----------------------------------------------------------
+ // check if sender is in peer filter (9.4.3.2)
+ if !msg.PeerFilter.Contains(sender) {
+ logger.Printf(logger.WARN, "[%s] sender not in peer
filter", label)
+ }
+ // parse result filter
+ var rf blocks.ResultFilter = new(blocks.PassResultFilter)
+ if msg.ResFilter != nil && len(msg.ResFilter) > 0 {
+ if blockHdlr != nil {
+ rf = blockHdlr.ParseResultFilter(msg.ResFilter)
+ } else {
+ logger.Printf(logger.WARN, "[%s] unknown result
filter implementation -- skipped", label)
+ }
+ }
+ // clone peer filter
+ pf := msg.PeerFilter.Clone()
+
+ //----------------------------------------------------------
+ // check if we need to respond (and how) (9.4.3.3)
+ addr := NewQueryAddress(msg.Query)
+ closest := m.rtable.IsClosestPeer(nil, addr, msg.PeerFilter)
+ demux := int(msg.Flags)&enums.DHT_RO_DEMULTIPLEX_EVERYWHERE != 0
+ approx := int(msg.Flags)&enums.DHT_RO_FIND_APPROXIMATE != 0
+ // actions
+ doResult := closest || (demux && approx)
+ doForward := !closest || (demux && !approx)
+ logger.Printf(logger.DBG, "[dht] GET message: closest=%v,
demux=%v, approx=%v --> result=%v, forward=%v",
+ closest, demux, approx, doResult, doForward)
+
+ //------------------------------------------------------
+ // query for a HELLO? (9.4.3.3a)
+ if msg.BType == uint32(enums.BLOCK_TYPE_DHT_URL_HELLO) {
+ logger.Println(logger.DBG, "[dht] GET message for
HELLO: check cache")
+ // find best cached HELLO
+ block, dist = m.rtable.BestHello(addr, rf)
+ }
+ //--------------------------------------------------------------
+ // find the closest block that has that is not filtered/ by the
result
+ // filter (in case we did not find an appropriate block in
cache).
+ if doResult {
+ // save best-match values from cache
+ blockCache := block
+ distCache := dist
+
+ // query DHT store for exact match (9.4.3.3c)
+ if block, err = m.Get(ctx, query); err != nil {
+ logger.Printf(logger.ERROR, "[%s] Failed to get
DHT block from storage: %s", label, err.Error())
+ return true
+ }
+ // if block is filtered, skip it
+ if rf.Contains(block) {
+ logger.Println(logger.DBG, "[dht] GET message
for HELLO: matching DHT block is filtered")
+ block = nil
+ }
+ // if we have no exact match, find approximate block if
requested
+ if block == nil || approx {
+ // no exact match: find approximate (9.4.3.3b)
+ match := func(b blocks.Block) bool {
+ return rf.Contains(b)
+ }
+ block, dist, err = m.GetApprox(ctx, query,
match)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[%s]
Failed to get (approx.) DHT block from storage: %s", label, err.Error())
+ return true
+ }
+ }
+ // if we have a block from cache, check if it is better
than the
+ // block found in the DHT
+ if blockCache != nil && distCache.Cmp(dist) < 0 {
+ block = blockCache
+ }
+ // if we have a block, send it as response
+ if block != nil {
+ logger.Printf(logger.INFO, "[%s] sending DHT
result message to caller", label)
+ if err := m.sendResult(ctx, query, block,
back); err != nil {
+ logger.Printf(logger.ERROR, "[%s]
Failed to send DHT result message: %s", label, err.Error())
+ }
+ }
+ }
+ // check if we need to forward message based on filter result
+ if block != nil && blockHdlr != nil {
+ switch blockHdlr.FilterResult(block, query.Key(), rf,
msg.XQuery) {
+ case blocks.RF_LAST:
+ // no need for further results
+ case blocks.RF_MORE:
+ // possibly more results
+ doForward = true
+ case blocks.RF_DUPLICATE, blocks.RF_IRRELEVANT:
+ // do not forward
+ }
+ }
+ if doForward {
+ // build updated GET message
+ pf.Add(m.core.PeerID())
+ msgOut := msg.Update(pf, rf, msg.HopCount+1)
+
+ // forward to number of peers
+ numForward := m.rtable.ComputeOutDegree(msg.ReplLevel,
msg.HopCount)
+ key := NewQueryAddress(query.Key())
+ for n := 0; n < numForward; n++ {
+ if p := m.rtable.SelectClosestPeer(key, pf); p
!= nil {
+ // forward message to peer
+ logger.Printf(logger.INFO, "[%s]
forward DHT get message to %s", label, p.String())
+ if err := back.Send(ctx, msgOut); err
!= nil {
+ logger.Printf(logger.ERROR,
"[%s] Failed to forward DHT get message: %s", label, err.Error())
+ }
+ pf.Add(p.Peer)
+ // create open get-forward result
handler
+ rh := NewForwardResultHandler(msg, rf,
back)
+ logger.Printf(logger.INFO, "[%s]
DHT-P2P-GET task #%d started", label, rh.ID())
+ m.reshdlrs.Add(rh)
+ } else {
+ break
+ }
+ }
+ }
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-GET message
done", label)
+
+ case *message.DHTP2PPutMsg:
+ //----------------------------------------------------------
+ // DHT-P2P PUT
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-PUT message",
label)
+
+ //--------------------------------------------------------------
+ // check if request is expired (9.3.2.1)
+ if msg.Expiration.Expired() {
+ logger.Printf(logger.WARN, "[%s] DHT-P2P-PUT message
expired (%s)", label, msg.Expiration.String())
+ return false
+ }
+ btype := enums.BlockType(msg.BType)
+ blockHdlr, ok := blocks.BlockHandlers[btype]
+ if ok { // (9.3.2.2)
+ // reconstruct block instance
+ if block, err := blockHdlr.ParseBlock(msg.Block); err
== nil {
+
+ // validate block key (9.3.2.3)
+ if !blockHdlr.ValidateBlockKey(block, msg.Key) {
+ logger.Printf(logger.WARN, "[%s]
DHT-P2P-PUT invalid key -- discarded", label)
+ return false
+ }
+
+ // validate block payload (9.3.2.4)
+ if !blockHdlr.ValidateBlockStoreRequest(block) {
+ logger.Printf(logger.WARN, "[%s]
DHT-P2P-PUT invalid payload -- discarded", label)
+ return false
+ }
+ }
+ } else {
+ logger.Printf(logger.INFO, "[%s] No validator defined
for block type %s", label, btype.String())
+ blockHdlr = nil
+ }
+ //--------------------------------------------------------------
+ // check if sender is in peer filter (9.3.2.5)
+ if !msg.PeerFilter.Contains(sender) {
+ logger.Printf(logger.WARN, "[%s] Sender not in peer
filter", label)
+ }
+ //--------------------------------------------------------------
+ // check if route is recorded (9.3.2.6)
+ /*
+ withPath := msg.Flags&enums.DHT_RO_RECORD_ROUTE != 0
+ if withPath {
+ spe := message.NewPathElement(msg.Key, sender,
p)
+ m.core.Sign(spe)
+ msg.AppendPutPath(spe)
+ }
+ */
+ //--------------------------------------------------------------
+ // verify PUT path (9.3.2.7)
+ if msg.PathL > 0 {
+
+ }
+
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-PUT message
done", label)
+
+ case *message.DHTP2PResultMsg:
+ //----------------------------------------------------------
+ // DHT-P2P RESULT
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-RESULT
message", label)
+
+ // check task list for handler
+ key := msg.Query.String()
+ handled := false
+ if list, ok := m.reshdlrs.Get(key); ok {
+ for _, rh := range list {
+ logger.Printf(logger.DBG, "[%s] Task #%d for
DHT-P2P-RESULT found", label, rh.ID())
+ // handle the message
+ go rh.Handle(ctx, msg)
+ }
+ return true
+ }
+ if !handled {
+ logger.Printf(logger.WARN, "[%s] DHT-P2P-RESULT not
processed (no handler)", label)
+ }
+ return handled
+
+ case *message.DHTP2PHelloMsg:
+ //----------------------------------------------------------
+ // DHT-P2P HELLO
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHT-P2P-HELLO
message", label)
+
+ // verify integrity of message
+ if ok, err := msg.Verify(sender); !ok || err != nil {
+ logger.Println(logger.WARN, "[dht] Received invalid
DHT_P2P_HELLO message")
+ if err != nil {
+ logger.Println(logger.ERROR, "[dht] -->
"+err.Error())
+ }
+ return false
+ }
+ // keep peer addresses in core for transport
+ aList, err := msg.Addresses()
+ if err != nil {
+ logger.Println(logger.ERROR, "[dht] Failed to parse
addresses from DHT_P2P_HELLO message")
+ return false
+ }
+ if newPeer := m.core.Learn(ctx, sender, aList); newPeer {
+ // we added a previously unknown peer: send a HELLO
+ var msgOut *message.DHTP2PHelloMsg
+ if msgOut, err = m.getHello(); err != nil {
+ return false
+ }
+ logger.Printf(logger.INFO, "[dht] Sending HELLO to %s:
%s", sender, msgOut)
+ err = m.core.Send(ctx, sender, msgOut)
+ // no error if the message might have been sent
+ if err == transport.ErrEndpMaybeSent {
+ err = nil
+ }
+ }
+
+ // cache HELLO block if applicable
+ k := sender.String()
+ isNew := true
+ if hb, ok := m.rtable.GetHello(k); ok {
+ // cache entry exists: is the HELLO message more recent?
+ _, isNew = hb.Expires.Diff(msg.Expires)
+ }
+ // we need to cache a new(er) HELLO
+ if isNew {
+ m.rtable.CacheHello(&blocks.HelloBlock{
+ PeerID: sender,
+ Signature: msg.Signature,
+ Expires: msg.Expires,
+ AddrBin: util.Clone(msg.AddrList),
+ })
+ }
+
+ //--------------------------------------------------------------
+ // Legacy message types (not implemented)
+ //--------------------------------------------------------------
+
+ case *message.DHTClientPutMsg:
+ //----------------------------------------------------------
+ // DHT PUT
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHTClientPut
message", label)
+
+ case *message.DHTClientGetMsg:
+ //----------------------------------------------------------
+ // DHT GET
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHTClientGet
message", label)
+
+ case *message.DHTClientGetResultsKnownMsg:
+ //----------------------------------------------------------
+ // DHT GET-RESULTS-KNOWN
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling
DHTClientGetResultsKnown message", label)
+
+ case *message.DHTClientGetStopMsg:
+ //----------------------------------------------------------
+ // DHT GET-STOP
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHTClientGetStop
message", label)
+
+ case *message.DHTClientResultMsg:
+ //----------------------------------------------------------
+ // DHT RESULT
+ //----------------------------------------------------------
+ logger.Printf(logger.INFO, "[%s] Handling DHTClientResult
message", label)
+
+ default:
+ //----------------------------------------------------------
+ // UNKNOWN message type received
+ //----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[%s] Unhandled message of type
(%d)\n", label, msgIn.Header().MsgType)
+ return false
+ }
+ return true
+}
+
+// send a result back to caller
+func (m *Module) sendResult(ctx context.Context, query blocks.Query, blk
blocks.Block, back transport.Responder) error {
+ // assemble result message
+ out := message.NewDHTP2PResultMsg()
+ out.BType = uint32(query.Type())
+ out.Expires = blk.Expire()
+ out.Query = query.Key()
+ out.Block = blk.Data()
+ out.MsgSize += uint16(len(out.Block))
+ // send message
+ return back.Send(ctx, out)
+}
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 612d0ec..ed28827 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -20,14 +20,20 @@ package dht
import (
"context"
+ "errors"
"gnunet/config"
"gnunet/core"
"gnunet/message"
"gnunet/service"
"gnunet/service/dht/blocks"
+ "gnunet/service/store"
+ "gnunet/transport"
+ "gnunet/util"
+ gmath "math"
"time"
"github.com/bfix/gospel/logger"
+ "github.com/bfix/gospel/math"
)
//======================================================================
@@ -42,34 +48,36 @@ import (
type Module struct {
service.ModuleImpl
- store service.DHTStore // reference to the block storage mechanism
- cache service.DHTStore // transient block cache
- core *core.Core // reference to core services
+ store store.DHTStore // reference to the block storage mechanism
+ core *core.Core // reference to core services
- rtable *RoutingTable // routing table
+ rtable *RoutingTable // routing table
+ lastHello *message.DHTP2PHelloMsg // last own HELLO message used;
re-create if expired
+ reshdlrs *ResultHandlerList // list of open tasks
}
// NewModule returns a new module instance. It initializes the storage
// mechanism for persistence.
-func NewModule(ctx context.Context, c *core.Core) (m *Module, err error) {
+func NewModule(ctx context.Context, c *core.Core, cfg *config.DHTConfig) (m
*Module, err error) {
// create permanent storage handler
- var store, cache service.DHTStore
- if store, err = service.NewDHTStore(config.Cfg.DHT.Storage); err != nil
{
+ var storage store.DHTStore
+ if storage, err = store.NewDHTStore(cfg.Storage); err != nil {
return
}
// create routing table
- rt := NewRoutingTable(NewPeerAddress(c.PeerID()))
+ rt := NewRoutingTable(NewPeerAddress(c.PeerID()), cfg.Routing)
// return module instance
m = &Module{
ModuleImpl: *service.NewModuleImpl(),
- store: store,
- cache: cache,
+ store: storage,
core: c,
rtable: rt,
+ reshdlrs: NewResultHandlerList(),
}
// register as listener for core events
- listener := m.Run(ctx, m.event, m.Filter(), 15*time.Minute, m.heartbeat)
+ pulse := time.Duration(cfg.Heartbeat) * time.Second
+ listener := m.Run(ctx, m.event, m.Filter(), pulse, m.heartbeat)
c.Register("dht", listener)
return
}
@@ -78,26 +86,20 @@ func NewModule(ctx context.Context, c *core.Core) (m
*Module, err error) {
// Get a block from the DHT ["dht:get"]
func (m *Module) Get(ctx context.Context, query blocks.Query) (block
blocks.Block, err error) {
+ return m.store.Get(query)
+}
- // check if we have the requested block in cache or permanent storage.
- block, err = m.cache.Get(query)
- if err == nil {
- // yes: we are done
- return
- }
- block, err = m.store.Get(query)
- if err == nil {
- // yes: we are done
- return
- }
- // retrieve the block from the DHT
-
- return nil, nil
+// GetApprox returns the first block not excluded ["dht:getapprox"]
+func (m *Module) GetApprox(ctx context.Context, query blocks.Query, excl
func(blocks.Block) bool) (block blocks.Block, dist *math.Int, err error) {
+ var d any
+ block, d, err = m.store.GetApprox(query, excl)
+ dist, _ = d.(*math.Int)
+ return
}
// Put a block into the DHT ["dht:put"]
func (m *Module) Put(ctx context.Context, key blocks.Query, block
blocks.Block) error {
- return nil
+ return m.store.Put(key, block)
}
//----------------------------------------------------------------------
@@ -110,17 +112,17 @@ func (m *Module) Filter() *core.EventFilter {
f.AddEvent(core.EV_DISCONNECT)
// messages we are interested in:
- // (1) DHT messages
+ // (1) DHT_P2P messages
+ f.AddMsgType(message.DHT_P2P_PUT)
+ f.AddMsgType(message.DHT_P2P_GET)
+ f.AddMsgType(message.DHT_P2P_RESULT)
+ f.AddMsgType(message.DHT_P2P_HELLO)
+ // (2) DHT messages (legacy, not implemented)
f.AddMsgType(message.DHT_CLIENT_GET)
f.AddMsgType(message.DHT_CLIENT_GET_RESULTS_KNOWN)
f.AddMsgType(message.DHT_CLIENT_GET_STOP)
f.AddMsgType(message.DHT_CLIENT_PUT)
f.AddMsgType(message.DHT_CLIENT_RESULT)
- // (2) DHT_P2P messages
- f.AddMsgType(message.DHT_P2P_PUT)
- f.AddMsgType(message.DHT_P2P_GET)
- f.AddMsgType(message.DHT_P2P_RESULT)
- f.AddMsgType(message.DHT_P2P_HELLO)
return f
}
@@ -143,32 +145,105 @@ func (m *Module) event(ctx context.Context, ev
*core.Event) {
// Message received.
case core.EV_MESSAGE:
logger.Printf(logger.INFO, "[dht] Message received: %s",
ev.Msg.String())
- // process message (if applicable)
- if m.ProcessFcn != nil {
- m.ProcessFcn(ctx, ev.Msg, ev.Resp)
+
+ // check if peer is in routing table (connected peer)
+ if !m.rtable.Contains(NewPeerAddress(ev.Peer)) {
+ logger.Printf(logger.WARN, "[dht] message %d from
unregistered peer -- discarded", ev.Msg.Header().MsgType)
+ return
+ }
+ // process message
+ if !m.HandleMessage(ctx, ev.Peer, ev.Msg, ev.Resp) {
+ logger.Println(logger.WARN, "[dht] Message NOT
handled!")
}
}
}
// Heartbeat handler for periodic tasks
func (m *Module) heartbeat(ctx context.Context) {
- // update the estimated network size
- m.rtable.l2nse = m.core.L2NSE()
-
// run heartbeat for routing table
m.rtable.heartbeat(ctx)
+
+ // clean-up task list
+ m.reshdlrs.Cleanup()
}
+// Send the currently active HELLO to given network address
+func (m *Module) SendHello(ctx context.Context, addr *util.Address) (err
error) {
+ // get (buffered) HELLO
+ var msg *message.DHTP2PHelloMsg
+ if msg, err = m.getHello(); err != nil {
+ return
+ }
+ logger.Printf(logger.INFO, "[core] Sending HELLO to %s: %s",
addr.URI(), msg)
+ return m.core.SendToAddr(ctx, addr, msg)
+}
+
+// get the recent HELLO if it is defined and not expired;
+// create a new HELLO otherwise.
+func (m *Module) getHello() (msg *message.DHTP2PHelloMsg, err error) {
+ if m.lastHello == nil || m.lastHello.Expires.Expired() {
+ // assemble new (signed) HELLO block
+ var addrList []*util.Address
+ if addrList, err = m.core.Addresses(); err != nil {
+ return
+ }
+ // assemble HELLO data
+ hb := new(blocks.HelloBlock)
+ hb.PeerID = m.core.PeerID()
+ hb.Expires =
util.NewAbsoluteTime(time.Now().Add(message.HelloAddressExpiration))
+ hb.SetAddresses(addrList)
+
+ // sign HELLO block
+ if err = m.core.Sign(hb); err != nil {
+ return
+ }
+ // assemble HELLO message
+ msg = message.NewDHTP2PHelloMsg()
+ msg.Expires = hb.Expires
+ msg.SetAddresses(hb.Addresses())
+ if err = m.core.Sign(msg); err != nil {
+ return
+ }
+
+ // save for later use
+ m.lastHello = msg
+
+ // DEBUG
+ var ok bool
+ if ok, err = msg.Verify(m.core.PeerID()); !ok || err != nil {
+ if !ok {
+ err = errors.New("failed to verify own HELLO")
+ }
+ logger.Println(logger.ERROR, err.Error())
+ return
+ }
+ logger.Println(logger.DBG, "[dht] New HELLO:
"+transport.Dump(msg, "hex"))
+ return
+ }
+ // we have a valid HELLO for re-use.
+ return m.lastHello, nil
+}
+
+//----------------------------------------------------------------------
+// Inter-module linkage helpers
//----------------------------------------------------------------------
// Export functions
func (m *Module) Export(fcn map[string]any) {
// add exported functions from module
fcn["dht:get"] = m.Get
+ fcn["dht:getapprox"] = m.GetApprox
fcn["dht:put"] = m.Put
}
// Import functions
-func (m *Module) Import(fcm map[string]any) {
- // nothing to import now.
+func (m *Module) Import(fcn map[string]any) {
+ // nothing to import for now.
+}
+
+//----------------------------------------------------------------------
+
+// SetNetworkSize sets a fixed number of peers in the network
+func (m *Module) SetNetworkSize(numPeers int) {
+ m.rtable.l2nse = gmath.Log2(float64(numPeers))
}
diff --git a/src/gnunet/service/dht/resulthandler.go
b/src/gnunet/service/dht/resulthandler.go
new file mode 100644
index 0000000..6564abd
--- /dev/null
+++ b/src/gnunet/service/dht/resulthandler.go
@@ -0,0 +1,351 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "bytes"
+ "context"
+ "gnunet/crypto"
+ "gnunet/message"
+ "gnunet/service/dht/blocks"
+ "gnunet/transport"
+ "gnunet/util"
+ "time"
+
+ "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// DHT GET requests send to neighbours result in DHT RESULT messages
+// being returned that need to be handled. The sequence of incoming
+// results is undetermined and usually not terminated (that is, there
+// is no mechanism to determine the end of results).
+// ResultHandlers handle DHT RESULT messages. The appropriate handler
+// is selected by the DHT query/store key associated with a GET/RESULT
+// message; there can be multiple handlers for the same key (serving
+// different GET requests and/or differnent originators).
+//======================================================================
+
+// ResultHandler interface
+type ResultHandler interface {
+
+ // ID returna the handler id
+ ID() int
+
+ // Done returns true if handler can be removed
+ Done() bool
+
+ // Key returns the query/store key as string
+ Key() string
+
+ // Compare two result handlers
+ Compare(ResultHandler) int
+
+ // Merge two result handlers that are the same except for result filter
+ Merge(ResultHandler) bool
+
+ // Handle result message
+ Handle(context.Context, *message.DHTP2PResultMsg) bool
+}
+
+// Compare return values
+//nolint:stylecheck // allow non-camel-case in constants
+const (
+ RHC_SAME = blocks.CMP_SAME // the two result handlers are the same
+ RHC_MERGE = blocks.CMP_MERGE // the two result handlers can be merged
+ RHC_DIFFER = blocks.CMP_DIFFER // the two result handlers are different
+ RHC_SIBL = blocks.CMP_1 // the two result handlers are siblings
+)
+
+//----------------------------------------------------------------------
+
+// Generic (shared) result handler data structure
+type GenericResultHandler struct {
+ id int // task identifier
+ key *crypto.HashCode // GET query key
+ btype uint32 // content type of the payload
+ flags uint16 // processing flags
+ resFilter blocks.ResultFilter // result filter
+ xQuery []byte // extended query
+ started util.AbsoluteTime // Timestamp of session start
+ active bool // is the task active?
+}
+
+// NewGenericResultHandler creates an instance from a DHT-GET message and a
+// result filter instance.
+func NewGenericResultHandler(msg *message.DHTP2PGetMsg, rf
blocks.ResultFilter) *GenericResultHandler {
+ return &GenericResultHandler{
+ id: util.NextID(),
+ key: msg.Query.Clone(),
+ btype: msg.BType,
+ flags: msg.Flags,
+ resFilter: rf,
+ xQuery: util.Clone(msg.XQuery),
+ started: util.AbsoluteTimeNow(),
+ active: true,
+ }
+}
+
+// ID returns the result handler identifier
+func (t *GenericResultHandler) ID() int {
+ return t.id
+}
+
+// Key returns the key string
+func (t *GenericResultHandler) Key() string {
+ return t.key.String()
+}
+
+// Done returns true if the result handler is no longer active.
+func (t *GenericResultHandler) Done() bool {
+ return !t.active || t.started.Add(time.Hour).Expired()
+}
+
+// Compare two handlers
+func (t *GenericResultHandler) Compare(h *GenericResultHandler) int {
+ if t.key.Equals(h.key) ||
+ t.btype != h.btype ||
+ t.flags != h.flags ||
+ !bytes.Equal(t.xQuery, h.xQuery) {
+ return RHC_DIFFER
+ }
+ return t.resFilter.Compare(h.resFilter)
+}
+
+// Merge two result handlers that are the same except for result filter
+func (t *GenericResultHandler) Merge(a *GenericResultHandler) bool {
+ return t.resFilter.Merge(a.resFilter)
+}
+
+//----------------------------------------------------------------------
+// Result handler for forwarded GET requests
+//----------------------------------------------------------------------
+
+// ForwardResultHandler data structure
+type ForwardResultHandler struct {
+ GenericResultHandler
+
+ resp transport.Responder // responder for communicating back to
originator
+}
+
+// NewForwardResultHandler derived from DHT-GET message
+func NewForwardResultHandler(msgIn message.Message, rf blocks.ResultFilter,
back transport.Responder) *ForwardResultHandler {
+ // check for correct message type and handler function
+ msg, ok := msgIn.(*message.DHTP2PGetMsg)
+ if ok {
+ return &ForwardResultHandler{
+ GenericResultHandler: *NewGenericResultHandler(msg, rf),
+ resp: back,
+ }
+ }
+ return nil
+}
+
+// Handle incoming DHT-P2P-RESULT message
+func (t *ForwardResultHandler) Handle(ctx context.Context, msg
*message.DHTP2PResultMsg) bool {
+ // send result message back to originator (result forwarding).
+ logger.Printf(logger.INFO, "[dht-task-%d] sending result back to
originator", t.id)
+ if err := t.resp.Send(ctx, msg); err != nil && err !=
transport.ErrEndpMaybeSent {
+ logger.Printf(logger.ERROR, "[dht-task-%d] sending result back
to originator failed: %s", t.id, err.Error())
+ return false
+ }
+ return true
+}
+
+// Compare two forward result filters
+func (t *ForwardResultHandler) Compare(h ResultHandler) int {
+ // check for correct handler type
+ ht, ok := h.(*ForwardResultHandler)
+ if !ok {
+ return RHC_DIFFER
+ }
+ // check for same recipient
+ if ht.resp.Receiver() != t.resp.Receiver() {
+ return RHC_DIFFER
+ }
+ // check generic handler data
+ return t.GenericResultHandler.Compare(&ht.GenericResultHandler)
+}
+
+// Merge two forward result handlers
+func (t *ForwardResultHandler) Merge(h ResultHandler) bool {
+ // check for correct handler type
+ ht, ok := h.(*ForwardResultHandler)
+ if !ok {
+ return false
+ }
+ return t.GenericResultHandler.Merge(&ht.GenericResultHandler)
+}
+
+//----------------------------------------------------------------------
+// Result handler for locally-initiated GET requests:
+//
+// Before sending the GET request a handler is added for the request:
+//
+// rc := make(chan any)
+// myRH := NewDirectResultHandler(msg, rf, MyCustomHandler, rc)
+// m.reshdlrs.Add(myRH)
+//
+// If a matching response is received, the custom handler is executed
+// in a separate go-routine. A custom handler returns a result (or error) on
+// a back channel and should be context-sensitive (termination).
+//
+// If an asynchronous behaviour is required, use 'ret := <-rc' to wait for
+// completion; synchronous execution does not require 'rc' (which can be set
+// to nil).
+//----------------------------------------------------------------------
+
+// ResultHandlerFcn is the function prototype for custom handlers:
+type ResultHandlerFcn func(context.Context, *message.DHTP2PResultMsg, chan<-
any) bool
+
+// DirectResultHandler for local DHT-P2P-GET requests
+type DirectResultHandler struct {
+ GenericResultHandler
+
+ hdlr ResultHandlerFcn // Hdlr is a custom message handler
+ rc chan any // handler result channel
+}
+
+// NewDirectResultHandler create a new GET handler instance
+func NewDirectResultHandler(msgIn message.Message, rf blocks.ResultFilter,
hdlr ResultHandlerFcn, rc chan any) *DirectResultHandler {
+ // check for correct message type and handler function
+ msg, ok := msgIn.(*message.DHTP2PGetMsg)
+ if ok {
+ return &DirectResultHandler{
+ GenericResultHandler: *NewGenericResultHandler(msg, rf),
+ hdlr: hdlr,
+ rc: rc,
+ }
+ }
+ return nil
+}
+
+// Handle incoming DHT-P2P-RESULT message
+func (t *DirectResultHandler) Handle(ctx context.Context, msg
*message.DHTP2PResultMsg) bool {
+ // check for correct message type and handler function
+ if t.hdlr != nil {
+ logger.Printf(logger.INFO, "[dht-task-%d] handling result
message", t.id)
+ return t.hdlr(ctx, msg, t.rc)
+ }
+ return false
+}
+
+// Compare two direct result handlers
+func (t *DirectResultHandler) Compare(h ResultHandler) int {
+ // check for correct handler type
+ ht, ok := h.(*DirectResultHandler)
+ if !ok {
+ return RHC_DIFFER
+ }
+ // check generic handler data
+ return t.GenericResultHandler.Compare(&ht.GenericResultHandler)
+}
+
+// Merge two direct result handlers
+func (t *DirectResultHandler) Merge(h ResultHandler) bool {
+ // check for correct handler type
+ ht, ok := h.(*DirectResultHandler)
+ if !ok {
+ return false
+ }
+ // check generic handler data
+ return t.GenericResultHandler.Merge(&ht.GenericResultHandler)
+}
+
+//----------------------------------------------------------------------
+// Handler list for book-keeping:
+// * For each query/store key there can be multiple result handlers.
+//----------------------------------------------------------------------
+
+// ResultHandlerList holds the currently active tasks
+type ResultHandlerList struct {
+ list *util.Map[string, []ResultHandler] // map of handlers
+}
+
+// NewResultHandlerList creates a new task list
+func NewResultHandlerList() *ResultHandlerList {
+ return &ResultHandlerList{
+ list: util.NewMap[string, []ResultHandler](),
+ }
+}
+
+// Add handler to list
+func (t *ResultHandlerList) Add(hdlr ResultHandler) bool {
+ // get current list of handlers for key
+ key := hdlr.Key()
+ list, ok := t.list.Get(key)
+ modified := false
+ if !ok {
+ list = make([]ResultHandler, 0)
+ } else {
+ // check if handler is already available
+ loop:
+ for i, h := range list {
+ switch h.Compare(hdlr) {
+ case RHC_SAME:
+ // already in list; no need to add again
+ return false
+ case RHC_MERGE:
+ // merge the two result handlers
+ modified = h.Merge(hdlr) || modified
+ break loop
+ case RHC_SIBL:
+ // replace the old handler with the new one
+ list[i] = hdlr
+ modified = true
+ break loop
+ case RHC_DIFFER:
+ // try next
+ }
+ }
+ }
+ if !modified {
+ // append new handler to list
+ list = append(list, hdlr)
+ }
+ t.list.Put(key, list)
+ return true
+}
+
+// Get handler list for given key
+func (t *ResultHandlerList) Get(key string) ([]ResultHandler, bool) {
+ return t.list.Get(key)
+}
+
+// Cleanup removes expired tasks from list
+func (t *ResultHandlerList) Cleanup() {
+ err := t.list.ProcessRange(func(key string, list []ResultHandler) error
{
+ var newList []ResultHandler
+ changed := false
+ for _, rh := range list {
+ if !rh.Done() {
+ newList = append(newList, rh)
+ } else {
+ changed = true
+ }
+ }
+ if changed {
+ t.list.Put(key, newList)
+ }
+ return nil
+ }, false)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[ResultHandlerList] clean-up
error: %s", err.Error())
+ }
+}
diff --git a/src/gnunet/service/dht/routingtable.go
b/src/gnunet/service/dht/routingtable.go
index 2933e6a..fe9f956 100644
--- a/src/gnunet/service/dht/routingtable.go
+++ b/src/gnunet/service/dht/routingtable.go
@@ -21,10 +21,11 @@ package dht
import (
"bytes"
"context"
- "crypto/sha512"
"encoding/hex"
+ "gnunet/config"
+ "gnunet/crypto"
+ "gnunet/service/dht/blocks"
"gnunet/util"
- "math/rand"
"sync"
"time"
@@ -32,61 +33,63 @@ import (
"github.com/bfix/gospel/math"
)
-var (
- // routing table hash function: defines number of
- // buckets and size of peer addresses
- rtHash = sha512.New
-)
-
-// Routing table contants (adjust with changing hash function)
+// Routing table constants
const (
- numBuckets = 512 // number of bits of hash function result
- numK = 20 // number of entries per k-bucket
- sizeAddr = 64 // size of peer address in bytes
+ numK = 20 // number of entries per k-bucket
)
//======================================================================
+// Peer address
//======================================================================
// PeerAddress is the identifier for a peer in the DHT network.
// It is the SHA-512 hash of the PeerID (public Ed25519 key).
type PeerAddress struct {
- addr [sizeAddr]byte // hash value as bytes
- connected bool // is peer connected?
- lastSeen util.AbsoluteTime // time the peer was last seen
- lastUsed util.AbsoluteTime // time the peer was last used
+ Peer *util.PeerID // peer identifier
+ Key *crypto.HashCode // address key is a sha512 hash
+ lastSeen util.AbsoluteTime // time the peer was last seen
+ lastUsed util.AbsoluteTime // time the peer was last used
}
// NewPeerAddress returns the DHT address of a peer.
func NewPeerAddress(peer *util.PeerID) *PeerAddress {
- r := new(PeerAddress)
- h := rtHash()
- h.Write(peer.Key)
- copy(r.addr[:], h.Sum(nil))
- r.lastSeen = util.AbsoluteTimeNow()
- r.lastUsed = util.AbsoluteTimeNow()
- return r
+ return &PeerAddress{
+ Peer: peer,
+ Key: crypto.Hash(peer.Data),
+ lastSeen: util.AbsoluteTimeNow(),
+ lastUsed: util.AbsoluteTimeNow(),
+ }
+}
+
+// NewQueryAddress returns a wrapped peer address for a query key
+func NewQueryAddress(key *crypto.HashCode) *PeerAddress {
+ return &PeerAddress{
+ Peer: nil,
+ Key: crypto.NewHashCode(key.Bits),
+ lastSeen: util.AbsoluteTimeNow(),
+ lastUsed: util.AbsoluteTimeNow(),
+ }
}
// String returns a human-readble representation of an address.
func (addr *PeerAddress) String() string {
- return hex.EncodeToString(addr.addr[:])
+ return hex.EncodeToString(addr.Key.Bits)
}
// Equals returns true if two peer addresses are the same.
func (addr *PeerAddress) Equals(p *PeerAddress) bool {
- return bytes.Equal(addr.addr[:], p.addr[:])
+ return bytes.Equal(addr.Key.Bits, p.Key.Bits)
}
// Distance between two addresses: returns a distance value and a
// bucket index (smaller index = less distant).
func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
- var d PeerAddress
- for i := range d.addr {
- d.addr[i] = addr.addr[i] ^ p.addr[i]
+ d := make([]byte, 64)
+ for i := range d {
+ d[i] = addr.Key.Bits[i] ^ p.Key.Bits[i]
}
- r := math.NewIntFromBytes(d.addr[:])
- return r, numBuckets - r.BitLen()
+ r := math.NewIntFromBytes(d)
+ return r, 512 - r.BitLen()
}
//======================================================================
@@ -98,23 +101,28 @@ func (addr *PeerAddress) Distance(p *PeerAddress)
(*math.Int, int) {
// distance to the reference address, so smaller index means
// "nearer" to the reference address.
type RoutingTable struct {
- ref *PeerAddress // reference address for distance
- buckets []*Bucket // list of buckets
- list map[*PeerAddress]struct{} // keep list of peers
- mtx sync.RWMutex // lock for write operations
- l2nse float64 // log2 of estimated network size
- inProcess bool // flag if Process() is running
+ sync.RWMutex
+
+ ref *PeerAddress // reference address
for distance
+ buckets []*Bucket // list of buckets
+ list *util.Map[string, *PeerAddress] // keep list of peers
+ l2nse float64 // log2 of estimated
network size
+ inProcess bool // flag if Process()
is running
+ cfg *config.RoutingConfig // routing parameters
+ helloCache *util.Map[string, *blocks.HelloBlock] // HELLO block cache
}
// NewRoutingTable creates a new routing table for the reference address.
-func NewRoutingTable(ref *PeerAddress) *RoutingTable {
+func NewRoutingTable(ref *PeerAddress, cfg *config.RoutingConfig)
*RoutingTable {
// create routing table
rt := &RoutingTable{
- ref: ref,
- list: make(map[*PeerAddress]struct{}),
- buckets: make([]*Bucket, numBuckets),
- l2nse: 0.,
- inProcess: false,
+ ref: ref,
+ list: util.NewMap[string, *PeerAddress](),
+ buckets: make([]*Bucket, 512),
+ l2nse: -1,
+ inProcess: false,
+ cfg: cfg,
+ helloCache: util.NewMap[string, *blocks.HelloBlock](),
}
// fill buckets
for i := range rt.buckets {
@@ -130,41 +138,69 @@ func NewRoutingTable(ref *PeerAddress) *RoutingTable {
// Add new peer address to routing table.
// Returns true if the entry was added, false otherwise.
func (rt *RoutingTable) Add(p *PeerAddress) bool {
- // ensure one write and no readers
- rt.lock(false)
- defer rt.unlock(false)
+ k := p.String()
+ logger.Printf(logger.DBG, "[RT] Add(%s)", k)
// check if peer is already known
- if _, ok := rt.list[p]; ok {
+ if px, ok := rt.list.Get(k); ok {
+ logger.Println(logger.DBG, "[RT] --> already known")
+ px.lastSeen = util.AbsoluteTimeNow()
return false
}
// compute distance (bucket index) and insert address.
_, idx := p.Distance(rt.ref)
if rt.buckets[idx].Add(p) {
- rt.list[p] = struct{}{}
+ logger.Println(logger.DBG, "[RT] --> entry added")
+ p.lastUsed = util.AbsoluteTimeNow()
+ rt.list.Put(k, p)
return true
}
// Full bucket: we did not add the address to the routing table.
+ logger.Println(logger.DBG, "[RT] --> bucket full -- discarded")
return false
}
// Remove peer address from routing table.
// Returns true if the entry was removed, false otherwise.
func (rt *RoutingTable) Remove(p *PeerAddress) bool {
- // ensure one write and no readers
- rt.lock(false)
- defer rt.unlock(false)
+ k := p.String()
+ logger.Printf(logger.DBG, "[RT] Remove(%s)", k)
// compute distance (bucket index) and remove entry from bucket
+ rc := false
_, idx := p.Distance(rt.ref)
if rt.buckets[idx].Remove(p) {
- delete(rt.list, p)
- return true
+ logger.Println(logger.DBG, "[RT] --> entry removed from bucket
and internal lists")
+ rc = true
+ } else {
+ // remove from internal list
+ logger.Println(logger.DBG, "[RT] --> entry removed from
internal lists only")
}
- // remove from internal list
- delete(rt.list, p)
- return false
+ rt.list.Delete(k)
+ // delete from HELLO cache
+ rt.helloCache.Delete(p.Peer.String())
+ return rc
+}
+
+// Contains checks if a peer is available in the routing table
+func (rt *RoutingTable) Contains(p *PeerAddress) bool {
+ k := p.String()
+ logger.Printf(logger.DBG, "[RT] Contains(%s)?", k)
+
+ // check for peer in internal list
+ px, ok := rt.list.Get(k)
+ if !ok {
+ logger.Println(logger.DBG, "[RT] --> NOT found in current
list:")
+ _ = rt.list.ProcessRange(func(key string, val *PeerAddress)
error {
+ logger.Printf(logger.DBG, "[RT] * %s", val)
+ return nil
+ }, true)
+ } else {
+ logger.Println(logger.DBG, "[RT] --> found in current list")
+ px.lastSeen = util.AbsoluteTimeNow()
+ }
+ return ok
}
//----------------------------------------------------------------------
@@ -186,51 +222,53 @@ func (rt *RoutingTable) Process(f func() error, readonly
bool) error {
// Routing functions
//----------------------------------------------------------------------
-// SelectClosestPeer for a given peer address and bloomfilter.
-func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter)
(n *PeerAddress) {
+// SelectClosestPeer for a given peer address and peer filter.
+func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, pf
*blocks.PeerFilter) (n *PeerAddress) {
// no writer allowed
- rt.mtx.RLock()
- defer rt.mtx.RUnlock()
+ rt.RLock()
+ defer rt.RUnlock()
- // find closest address
+ // find closest peer in routing table
var dist *math.Int
for _, b := range rt.buckets {
- if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil &&
d.Cmp(dist) < 0) {
+ if k, d := b.SelectClosestPeer(p, pf); n == nil || (d != nil &&
d.Cmp(dist) < 0) {
dist = d
n = k
}
}
// mark peer as used
- n.lastUsed = util.AbsoluteTimeNow()
+ if n != nil {
+ n.lastUsed = util.AbsoluteTimeNow()
+ }
return
}
// SelectRandomPeer returns a random address from table (that is not
// included in the bloomfilter)
-func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress {
+func (rt *RoutingTable) SelectRandomPeer(pf *blocks.PeerFilter) (p
*PeerAddress) {
// no writer allowed
- rt.mtx.RLock()
- defer rt.mtx.RUnlock()
+ rt.RLock()
+ defer rt.RUnlock()
// select random entry from list
- if size := len(rt.list); size > 0 {
- idx := rand.Intn(size)
- for k := range rt.list {
- if idx == 0 {
- // mark peer as used
- k.lastUsed = util.AbsoluteTimeNow()
- return k
- }
- idx--
+ var ok bool
+ for {
+ if _, p, ok = rt.list.GetRandom(); !ok {
+ return nil
+ }
+ if !pf.Contains(p.Peer) {
+ break
}
}
- return nil
+ // mark peer as used
+ p.lastUsed = util.AbsoluteTimeNow()
+ return
}
// SelectPeer selects a neighbor depending on the number of hops parameter.
// If hops < NSE this function MUST return SelectRandomPeer() and
// SelectClosestpeer() otherwise.
-func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf
*PeerBloomFilter) *PeerAddress {
+func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf
*blocks.PeerFilter) *PeerAddress {
if float64(hops) < rt.l2nse {
return rt.SelectRandomPeer(bf)
}
@@ -238,9 +276,24 @@ func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops
int, bf *PeerBloomFilter
}
// IsClosestPeer returns true if p is the closest peer for k. Peers with a
-// positive test in the Bloom filter are not considered.
-func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter)
bool {
- n := rt.SelectClosestPeer(k, bf)
+// positive test in the Bloom filter are not considered. If p is nil, our
+// reference address is used.
+func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, pf
*blocks.PeerFilter) bool {
+ // get closest peer in routing table
+ n := rt.SelectClosestPeer(k, pf)
+ // check SELF?
+ if p == nil {
+ // if no peer in routing table found
+ if n == nil {
+ // local peer is closest
+ return true
+ }
+ // check if local distance is smaller than for best peer in
routing table
+ d0, _ := n.Distance(k)
+ d1, _ := rt.ref.Distance(k)
+ return d1.Cmp(d0) < 0
+ }
+ // check if p is closest peer
return n.Equals(p)
}
@@ -248,7 +301,7 @@ func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf
*PeerBloomFilter) bo
// The arguments are the desired replication level, the hop count of the
message so far,
// and the base-2 logarithm of the current network size estimate (L2NSE) as
provided by the
// underlay. The result is the non-negative number of next hops to select.
-func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
+func (rt *RoutingTable) ComputeOutDegree(repl, hop uint16) int {
hf := float64(hop)
if hf > 4*rt.l2nse {
return 0
@@ -271,22 +324,62 @@ func (rt *RoutingTable) ComputeOutDegree(repl, hop int)
int {
func (rt *RoutingTable) heartbeat(ctx context.Context) {
// check for dead or expired peers
- timeout := util.NewRelativeTime(3 * time.Hour)
+ logger.Println(logger.DBG, "[dht] RT heartbeat...")
+ timeout := util.NewRelativeTime(time.Duration(rt.cfg.PeerTTL) *
time.Second)
if err := rt.Process(func() error {
- for addr := range rt.list {
- if addr.connected {
- continue
- }
+ return rt.list.ProcessRange(func(k string, p *PeerAddress)
error {
// check if we can/need to drop a peer
- drop := timeout.Compare(addr.lastSeen.Elapsed()) < 0
- if drop || timeout.Compare(addr.lastUsed.Elapsed()) < 0
{
- rt.Remove(addr)
+ drop := timeout.Compare(p.lastSeen.Elapsed()) < 0
+ if drop || timeout.Compare(p.lastUsed.Elapsed()) < 0 {
+ logger.Printf(logger.DBG, "[RT] removing %v:
%v, %v", p, p.lastSeen.Elapsed(), p.lastUsed.Elapsed())
+ rt.Remove(p)
}
- }
- return nil
+ return nil
+ }, false)
}, false); err != nil {
- logger.Println(logger.ERROR, "[dht] RT heartbeat: "+err.Error())
+ logger.Println(logger.ERROR, "[dht] RT heartbeat failed:
"+err.Error())
}
+
+ // drop expired entries from the HELLO cache
+ _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock)
error {
+ if val.Expires.Expired() {
+ rt.helloCache.Delete(key)
+ }
+ return nil
+ }, false)
+
+ // update the estimated network size
+ // rt.l2nse = ...
+}
+
+//----------------------------------------------------------------------
+
+func (rt *RoutingTable) BestHello(addr *PeerAddress, rf blocks.ResultFilter)
(hb *blocks.HelloBlock, dist *math.Int) {
+ // iterate over cached HELLOs to find (best) match first
+ _ = rt.helloCache.ProcessRange(func(key string, val *blocks.HelloBlock)
error {
+ // check if block is excluded by result filter
+ if !rf.Contains(val) {
+ // check for better match
+ p := NewPeerAddress(val.PeerID)
+ d, _ := addr.Distance(p)
+ if hb == nil || d.Cmp(dist) < 0 {
+ hb = val
+ dist = d
+ }
+ }
+ return nil
+ }, true)
+ return
+}
+
+// CacheHello adds a HELLO block to the list of cached entries.
+func (rt *RoutingTable) CacheHello(hb *blocks.HelloBlock) {
+ rt.helloCache.Put(hb.PeerID.String(), hb)
+}
+
+// GetHello returns a HELLO block for key k (if available)
+func (rt *RoutingTable) GetHello(k string) (*blocks.HelloBlock, bool) {
+ return rt.helloCache.Get(k)
}
//----------------------------------------------------------------------
@@ -295,9 +388,9 @@ func (rt *RoutingTable) heartbeat(ctx context.Context) {
func (rt *RoutingTable) lock(readonly bool) {
if !rt.inProcess {
if readonly {
- rt.mtx.RLock()
+ rt.RLock()
} else {
- rt.mtx.Lock()
+ rt.Lock()
}
}
}
@@ -306,9 +399,9 @@ func (rt *RoutingTable) lock(readonly bool) {
func (rt *RoutingTable) unlock(readonly bool) {
if !rt.inProcess {
if readonly {
- rt.mtx.RUnlock()
+ rt.RUnlock()
} else {
- rt.mtx.Unlock()
+ rt.Unlock()
}
}
}
@@ -319,8 +412,9 @@ func (rt *RoutingTable) unlock(readonly bool) {
// Bucket holds peer entries with approx. same distance from node
type Bucket struct {
- list []*PeerAddress
- rwlock sync.RWMutex
+ sync.RWMutex
+
+ list []*PeerAddress // list of peer addresses in bucket.
}
// NewBucket creates a new entry list of given size
@@ -334,8 +428,8 @@ func NewBucket(n int) *Bucket {
// Returns true if entry is added, false otherwise.
func (b *Bucket) Add(p *PeerAddress) bool {
// only one writer and no readers
- b.rwlock.Lock()
- defer b.rwlock.Unlock()
+ b.Lock()
+ defer b.Unlock()
// check for free space in bucket
if len(b.list) < numK {
@@ -343,6 +437,7 @@ func (b *Bucket) Add(p *PeerAddress) bool {
b.list = append(b.list, p)
return true
}
+ // full bucket: no further additions
return false
}
@@ -350,8 +445,8 @@ func (b *Bucket) Add(p *PeerAddress) bool {
// Returns true if entry is removed (found), false otherwise.
func (b *Bucket) Remove(p *PeerAddress) bool {
// only one writer and no readers
- b.rwlock.Lock()
- defer b.rwlock.Unlock()
+ b.Lock()
+ defer b.Unlock()
for i, pe := range b.list {
if pe.Equals(p) {
@@ -365,14 +460,14 @@ func (b *Bucket) Remove(p *PeerAddress) bool {
// SelectClosestPeer returns the entry with minimal distance to the given
// peer address; entries included in the bloom flter are ignored.
-func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n
*PeerAddress, dist *math.Int) {
+func (b *Bucket) SelectClosestPeer(p *PeerAddress, pf *blocks.PeerFilter) (n
*PeerAddress, dist *math.Int) {
// no writer allowed
- b.rwlock.RLock()
- defer b.rwlock.RUnlock()
+ b.RLock()
+ defer b.RUnlock()
for _, addr := range b.list {
// skip addresses in bloomfilter
- if bf.Contains(addr) {
+ if pf.Contains(addr.Peer) {
continue
}
// check for shorter distance
diff --git a/src/gnunet/service/dht/routingtable_test.go
b/src/gnunet/service/dht/routingtable_test.go
index 33c4b7f..16f39de 100644
--- a/src/gnunet/service/dht/routingtable_test.go
+++ b/src/gnunet/service/dht/routingtable_test.go
@@ -19,8 +19,11 @@
package dht
import (
+ "crypto/sha512"
+ "encoding/hex"
"gnunet/config"
"gnunet/core"
+ "gnunet/service/dht/blocks"
"gnunet/util"
"math/rand"
"testing"
@@ -43,7 +46,7 @@ type Entry struct {
// test data
var (
- cfg = &config.NodeConfig{
+ nodeCfg = &config.NodeConfig{
PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
Endpoints: []*config.EndpointConfig{
{
@@ -53,6 +56,9 @@ var (
},
},
}
+ rtCfg = &config.RoutingConfig{
+ PeerTTL: 10800,
+ }
)
// TestRT connects and disconnects random peers to test the base
@@ -64,18 +70,16 @@ func TestRT(t *testing.T) {
// helper functions
genRemotePeer := func() *PeerAddress {
d := make([]byte, 32)
- if _, err := rand.Read(d); err != nil {
- panic(err)
- }
+ _, _ = rand.Read(d)
return NewPeerAddress(util.NewPeerID(d))
}
// create routing table and start command handler
- local, err := core.NewLocalPeer(cfg)
+ local, err := core.NewLocalPeer(nodeCfg)
if err != nil {
t.Fatal(err)
}
- rt := NewRoutingTable(NewPeerAddress(local.GetID()))
+ rt := NewRoutingTable(NewPeerAddress(local.GetID()), rtCfg)
// create a task list
tasks := make([]*Entry, NUMP)
@@ -91,7 +95,6 @@ func TestRT(t *testing.T) {
// actions:
connected := func(task *Entry, e int64, msg string) {
- task.addr.connected = true
rt.Add(task.addr)
task.online = true
task.last = e
@@ -136,10 +139,29 @@ func TestRT(t *testing.T) {
// execute some routing functions on remaining table
k := genRemotePeer()
- bf := NewPeerBloomFilter()
- n := rt.SelectClosestPeer(k, bf)
+ pf := blocks.NewPeerFilter()
+ n := rt.SelectClosestPeer(k, pf)
t.Logf("Closest: %s -> %s\n", k, n)
- n = rt.SelectRandomPeer(bf)
+ n = rt.SelectRandomPeer(pf)
t.Logf("Random: %s\n", n)
}
+
+func TestDistance(t *testing.T) {
+ pid1 := "4ER9C0GV4QC25GGQMXBBGXYFEB3ZVAYMXZVSRKDVEGCDTAS34E30"
+ pid2 := "V61ESQ96AFXZWDSA509HP11K5HJXXJ9ECM4NAMCQRX5YW4KN8XPG"
+
+ p1, _ := util.DecodeStringToBinary(pid1, 32)
+ p2, _ := util.DecodeStringToBinary(pid2, 32)
+
+ h1 := sha512.Sum512(p1)
+ h2 := sha512.Sum512(p2)
+ t.Logf("h1=%s\n", hex.EncodeToString(h1[:]))
+ t.Logf("h2=%s\n", hex.EncodeToString(h2[:]))
+
+ pa1 := NewPeerAddress(util.NewPeerID(p1))
+ pa2 := NewPeerAddress(util.NewPeerID(p2))
+
+ dist, idx := pa1.Distance(pa2)
+ t.Logf("dist=%v, idx=%d\n", dist, idx)
+}
diff --git a/src/gnunet/service/dht/rpc.go b/src/gnunet/service/dht/rpc.go
index 3ec2b73..96ae26b 100644
--- a/src/gnunet/service/dht/rpc.go
+++ b/src/gnunet/service/dht/rpc.go
@@ -19,21 +19,49 @@
package dht
import (
- "net/rpc"
- "time"
+ "gnunet/service"
+ "net/http"
+
+ "github.com/bfix/gospel/logger"
)
//----------------------------------------------------------------------
-type DHTCommand struct{}
+// RPCService is a type for DHT-related JSON-RPC requests
+type RPCService struct{}
+
+// local instance of service
+var dhtRPC = &RPCService{}
+
+//----------------------------------------------------------------------
+// Command "DHT.Status"
+//----------------------------------------------------------------------
-type DHTStats struct {
- Started time.Time
+// StatusRequest is a status request for specific information addressed
+// by topic(s)
+type StatusRequest struct {
+ Topics []string `json:"topics"`
}
-func (c *DHTCommand) Status(mode int, stats *DHTStats) error {
- *stats = DHTStats{
- Started: time.Now(),
+// StatusResponse is a response to a status request. It returns information
+// on each topic requested.
+type StatusResponse struct {
+ Messages map[string]string `json:"messages"`
+}
+
+// Status requests information by topic(s).
+func (s *RPCService) Status(r *http.Request, req *StatusRequest, reply
*StatusResponse) error {
+ // assemble information on topic(s)
+ out := make(map[string]string)
+ for _, topic := range req.Topics {
+ switch topic {
+ case "echo":
+ out[topic] = "echo test"
+ }
+ }
+ // set reply
+ *reply = StatusResponse{
+ Messages: out,
}
return nil
}
@@ -41,6 +69,8 @@ func (c *DHTCommand) Status(mode int, stats *DHTStats) error {
//----------------------------------------------------------------------
// InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
- srv.Register(new(DHTCommand))
+func (m *Module) InitRPC(srv *service.JRPCServer) {
+ if err := srv.RegisterService(dhtRPC, "DHT"); err != nil {
+ logger.Printf(logger.ERROR, "[dht] Failed to init RPC: %s",
err.Error())
+ }
}
diff --git a/src/gnunet/service/dht/service.go
b/src/gnunet/service/dht/service.go
index 3cf216f..29e3804 100644
--- a/src/gnunet/service/dht/service.go
+++ b/src/gnunet/service/dht/service.go
@@ -23,10 +23,9 @@ import (
"fmt"
"io"
+ "gnunet/config"
"gnunet/core"
- "gnunet/message"
"gnunet/service"
- "gnunet/transport"
"github.com/bfix/gospel/logger"
)
@@ -48,15 +47,14 @@ type Service struct {
}
// NewService creates a new DHT service instance
-func NewService(ctx context.Context, c *core.Core) (service.Service, error) {
- mod, err := NewModule(ctx, c)
+func NewService(ctx context.Context, c *core.Core, cfg *config.DHTConfig)
(*Service, error) {
+ mod, err := NewModule(ctx, c, cfg)
if err != nil {
return nil, err
}
srv := &Service{
Module: *mod,
}
- srv.ProcessFcn = srv.HandleMessage
return srv, nil
}
@@ -85,7 +83,8 @@ loop:
logger.Printf(logger.INFO, "[dht:%d:%d] Received request:
%v\n", id, reqID, msg)
// handle message
- s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+ valueCtx := context.WithValue(ctx, service.CtxKey("label"),
fmt.Sprintf(":%d:%d", id, reqID))
+ s.HandleMessage(valueCtx, nil, msg, mc)
}
// close client connection
mc.Close()
@@ -94,48 +93,3 @@ loop:
logger.Printf(logger.INFO, "[dht:%d] Start closing session...\n", id)
cancel()
}
-
-// HandleMessage handles a DHT request/response message. If the transport
channel
-// is nil, responses are send directly via the transport layer.
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
transport.Responder) bool {
- // assemble log label
- label := ""
- if v := ctx.Value("label"); v != nil {
- label = v.(string)
- }
- // process message
- switch msg.(type) {
- case *message.DHTClientPutMsg:
- //----------------------------------------------------------
- // DHT PUT
- //----------------------------------------------------------
-
- case *message.DHTClientGetMsg:
- //----------------------------------------------------------
- // DHT GET
- //----------------------------------------------------------
-
- case *message.DHTClientGetResultsKnownMsg:
- //----------------------------------------------------------
- // DHT GET-RESULTS-KNOWN
- //----------------------------------------------------------
-
- case *message.DHTClientGetStopMsg:
- //----------------------------------------------------------
- // DHT GET-STOP
- //----------------------------------------------------------
-
- case *message.DHTClientResultMsg:
- //----------------------------------------------------------
- // DHT RESULT
- //----------------------------------------------------------
-
- default:
- //----------------------------------------------------------
- // UNKNOWN message type received
- //----------------------------------------------------------
- logger.Printf(logger.ERROR, "[dht-%s] Unhandled message of type
(%d)\n", label, msg.Header().MsgType)
- return false
- }
- return true
-}
diff --git a/src/gnunet/service/gns/block_handler.go
b/src/gnunet/service/gns/block_handler.go
index 4c49c99..ee87065 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -30,7 +30,7 @@ import (
"github.com/bfix/gospel/logger"
)
-// HdlrInst is the type for functions that instanciate custom block handlers.
+// HdlrInst is the type for functions that instantiate custom block handlers.
type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error)
// Error codes
@@ -157,7 +157,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord,
labels []string) (*B
hl.counts.Add(rrType)
// check for custom handler type
- if creat, ok := customHandler[enums.GNSType(rrType)]; ok {
+ if creat, ok := customHandler[rrType]; ok {
// check if a handler for given type already exists
var (
hdlr BlockHandler
@@ -192,7 +192,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord,
labels []string) (*B
}
// GetHandler returns a BlockHandler for the given GNS block type.
-// If more than one type is given, the first matching hanlder is
+// If more than one type is given, the first matching handler is
// returned.
func (hl *BlockHandlerList) GetHandler(types ...enums.GNSType) BlockHandler {
for _, t := range types {
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 4422c44..7996460 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -204,7 +204,7 @@ func QueryDNS(id int, name string, server net.IP, kind
RRTypeList) *message.Reco
// ResolveDNS resolves a name in DNS. Multiple DNS servers are queried in
// parallel; the first result delivered by any of the servers is returned
// as the result list of matching resource records.
-func (gns *Module) ResolveDNS(
+func (m *Module) ResolveDNS(
ctx context.Context,
name string,
servers []string,
@@ -223,7 +223,7 @@ func (gns *Module) ResolveDNS(
if addr == nil {
// no, it is a name... try to resolve an IP address
from the name
query := NewRRTypeList(enums.GNS_TYPE_DNS_A,
enums.GNS_TYPE_DNS_AAAA)
- if set, err = gns.ResolveUnknown(ctx, srv, nil, zkey,
query, depth+1); err != nil {
+ if set, err = m.ResolveUnknown(ctx, srv, nil, zkey,
query, depth+1); err != nil {
logger.Printf(logger.ERROR, "[dns] Can't
resolve NS server '%s': %s\n", srv, err.Error())
continue
}
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index 1129273..c431d48 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -103,7 +103,7 @@ func NewModule(ctx context.Context, c *core.Core) (m
*Module) {
ModuleImpl: *service.NewModuleImpl(),
}
// register as listener for core events
- listener := m.Run(ctx, m.event, m.Filter(), 0, nil)
+ listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
c.Register("gns", listener)
return
}
@@ -135,11 +135,11 @@ func (m *Module) Export(fcn map[string]any) {
// Import functions
func (m *Module) Import(fcn map[string]any) {
// resolve imports from other modules
- m.LookupLocal = fcn["namecache:get"].(func(ctx context.Context, query
*blocks.GNSQuery) (*blocks.GNSBlock, error))
- m.StoreLocal = fcn["namecache:put"].(func(ctx context.Context, query
*blocks.GNSQuery, block *blocks.GNSBlock) error)
- m.LookupRemote = fcn["dht:get"].(func(ctx context.Context, query
blocks.Query) (blocks.Block, error))
- m.RevocationQuery = fcn["rev:query"].(func(ctx context.Context, zkey
*crypto.ZoneKey) (valid bool, err error))
- m.RevocationRevoke = fcn["rev:revoke"].(func(ctx context.Context, rd
*revocation.RevData) (success bool, err error))
+ m.LookupLocal, _ = fcn["namecache:get"].(func(ctx context.Context,
query *blocks.GNSQuery) (*blocks.GNSBlock, error))
+ m.StoreLocal, _ = fcn["namecache:put"].(func(ctx context.Context, query
*blocks.GNSQuery, block *blocks.GNSBlock) error)
+ m.LookupRemote, _ = fcn["dht:get"].(func(ctx context.Context, query
blocks.Query) (blocks.Block, error))
+ m.RevocationQuery, _ = fcn["rev:query"].(func(ctx context.Context, zkey
*crypto.ZoneKey) (valid bool, err error))
+ m.RevocationRevoke, _ = fcn["rev:revoke"].(func(ctx context.Context, rd
*revocation.RevData) (success bool, err error))
}
//----------------------------------------------------------------------
@@ -257,7 +257,7 @@ func (m *Module) ResolveRelative(
if hdlr := hdlrs.GetHandler(crypto.ZoneTypes...); hdlr != nil {
// (1) zone key record:
- inst := hdlr.(*ZoneKeyHandler)
+ inst, _ := hdlr.(*ZoneKeyHandler)
// if labels are pending, set new zone and continue
resolution;
// otherwise resolve "@" label for the zone if no zone
key record
// was requested.
@@ -265,14 +265,15 @@ func (m *Module) ResolveRelative(
labels = append(labels, "@")
}
// check if zone key has been revoked
- if valid, err := m.RevocationQuery(ctx, inst.zkey); err
!= nil || !valid {
+ var valid bool
+ if valid, err = m.RevocationQuery(ctx, inst.zkey); err
!= nil || !valid {
// revoked key -> no results!
records = make([]*message.ResourceRecord, 0)
break
}
} else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS);
hdlr != nil {
// (2) GNS2DNS records
- inst := hdlr.(*Gns2DnsHandler)
+ inst, _ := hdlr.(*Gns2DnsHandler)
// if we are at the end of the path and the requested
type
// includes GNS_TYPE_GNS2DNS, the GNS2DNS records are
returned...
if len(labels) == 1 &&
kind.HasType(enums.GNS_TYPE_GNS2DNS) && !kind.IsAny() {
@@ -308,7 +309,7 @@ func (m *Module) ResolveRelative(
break
} else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_BOX); hdlr !=
nil {
// (3) BOX records:
- inst := hdlr.(*BoxHandler)
+ inst, _ := hdlr.(*BoxHandler)
newRecords := inst.Records(kind).Records
if len(newRecords) > 0 {
records = newRecords
@@ -316,7 +317,7 @@ func (m *Module) ResolveRelative(
}
} else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_DNS_CNAME);
hdlr != nil {
// (4) CNAME records:
- inst := hdlr.(*CnameHandler)
+ inst, _ := hdlr.(*CnameHandler)
// if we are at the end of the path and the requested
type
// includes GNS_TYPE_DNS_CNAME, the records are
returned...
if len(labels) == 1 &&
kind.HasType(enums.GNS_TYPE_DNS_CNAME) && !kind.IsAny() {
@@ -352,7 +353,7 @@ func (m *Module) ResolveRelative(
// check for VPN record
if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_VPN); hdlr != nil {
// add VPN record to result set
- inst := hdlr.(*VpnHandler)
+ inst, _ := hdlr.(*VpnHandler)
set.AddRecord(inst.rec)
}
}
@@ -458,7 +459,10 @@ func (m *Module) Lookup(
return
}
// store RRs from remote locally.
- m.StoreLocal(ctx, query, block)
+ if err = m.StoreLocal(ctx, query, block); err != nil {
+ logger.Printf(logger.DBG, "[gns] store local
failed: %s", err.Error())
+ return
+ }
}
}
return
diff --git a/src/gnunet/service/gns/rpc.go b/src/gnunet/service/gns/rpc.go
index 33682d3..042d72a 100644
--- a/src/gnunet/service/gns/rpc.go
+++ b/src/gnunet/service/gns/rpc.go
@@ -18,10 +18,10 @@
package gns
-import "net/rpc"
+import "gnunet/service"
//----------------------------------------------------------------------
// InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
+func (m *Module) InitRPC(srv *service.JRPCServer) {
}
diff --git a/src/gnunet/service/gns/service.go
b/src/gnunet/service/gns/service.go
index 19ddc14..45eb700 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -62,8 +62,6 @@ func NewService(ctx context.Context, c *core.Core)
service.Service {
srv := &Service{
Module: *mod,
}
- srv.ProcessFcn = srv.HandleMessage
-
// set external function references (external services)
srv.LookupLocal = srv.LookupNamecache
srv.StoreLocal = srv.StoreNamecache
@@ -98,7 +96,8 @@ func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connectio
logger.Printf(logger.INFO, "[gns:%d:%d] Received request:
%v\n", id, reqID, msg)
// handle message
- s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+ valueCtx := context.WithValue(ctx, service.CtxKey("label"),
fmt.Sprintf(":%d:%d", id, reqID))
+ s.HandleMessage(valueCtx, nil, msg, mc)
}
// close client connection
mc.Close()
@@ -109,11 +108,11 @@ func (s *Service) ServeClient(ctx context.Context, id
int, mc *service.Connectio
}
// Handle a single incoming message
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
transport.Responder) bool {
+func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg
message.Message, back transport.Responder) bool {
// assemble log label
label := ""
if v := ctx.Value("label"); v != nil {
- label = v.(string)
+ label, _ = v.(string)
}
// perform lookup
switch m := msg.(type) {
@@ -123,7 +122,7 @@ func (s *Service) HandleMessage(ctx context.Context, msg
message.Message, back t
//----------------------------------------------------------
// perform lookup on block (locally and remote)
- go func(m *message.LookupMsg) {
+ go func(m *message.LookupMsg, label string) {
logger.Printf(logger.INFO, "[gns%s] Lookup request
received.\n", label)
resp := message.NewGNSLookupResultMsg(m.ID)
defer func() {
@@ -137,7 +136,6 @@ func (s *Service) HandleMessage(ctx context.Context, msg
message.Message, back t
logger.Printf(logger.DBG, "[gns%s] Lookup
request finished.\n", label)
}()
- label := m.GetName()
kind := NewRRTypeList(enums.GNSType(m.Type))
recset, err := s.Resolve(ctx, label, m.Zone, kind,
int(m.Options), 0)
if err != nil {
@@ -163,11 +161,13 @@ func (s *Service) HandleMessage(ctx context.Context, msg
message.Message, back t
// is this the record type we are
looking for?
if rec.Type == m.Type ||
enums.GNSType(m.Type) == enums.GNS_TYPE_ANY {
// add it to the response
message
- resp.AddRecord(rec)
+ if err := resp.AddRecord(rec);
err != nil {
+
logger.Printf(logger.ERROR, "[gns%s] failed: %sv", label, err.Error())
+ }
}
}
}
- }(m)
+ }(m, label)
default:
//----------------------------------------------------------
@@ -374,7 +374,7 @@ func (s *Service) LookupDHT(ctx context.Context, query
blocks.Query) (block bloc
// send DHT GET request and wait for response
reqGet := message.NewDHTClientGetMsg(query.Key())
reqGet.ID = uint64(util.NextID())
- reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
+ reqGet.ReplLevel = uint32(enums.GNS_REPLICATION_LEVEL)
reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
reqGet.Options = uint32(enums.DHT_RO_DEMULTIPLEX_EVERYWHERE)
@@ -419,7 +419,7 @@ func (s *Service) LookupDHT(ctx context.Context, query
blocks.Query) (block bloc
}
// get GNSBlock from message
- qGNS := query.(*blocks.GNSQuery)
+ qGNS, _ := query.(*blocks.GNSQuery)
block = new(blocks.GNSBlock)
if err = data.Unmarshal(block, m.Data); err != nil {
logger.Printf(logger.ERROR, "[gns] can't read GNS
block: %s\n", err.Error())
diff --git a/src/gnunet/service/module.go b/src/gnunet/service/module.go
index 65b49d8..7311c4f 100644
--- a/src/gnunet/service/module.go
+++ b/src/gnunet/service/module.go
@@ -21,9 +21,6 @@ package service
import (
"context"
"gnunet/core"
- "gnunet/message"
- "gnunet/transport"
- "net/rpc"
"time"
)
@@ -66,7 +63,7 @@ type Module interface {
Import(map[string]any)
// InitRPC registers RPC commands for the module
- InitRPC(*rpc.Server)
+ InitRPC(*JRPCServer)
// Filter returns the event filter for the module
Filter() *core.EventFilter
@@ -78,20 +75,19 @@ type EventHandler func(context.Context, *core.Event)
// Heartbeat is a function prototype for periodic tasks
type Heartbeat func(context.Context)
+// CtxKey is a value-context key
+type CtxKey string
+
// ModuleImpl is an event-handling type used by Module implementations.
type ModuleImpl struct {
// channel for core events.
ch chan *core.Event
-
- // ProcessFcn message: function reference (implemented by service)
- ProcessFcn func(ctx context.Context, msg message.Message, back
transport.Responder) bool
}
// NewModuleImplementation returns a new base module and starts
func NewModuleImpl() (m *ModuleImpl) {
return &ModuleImpl{
- ch: make(chan *core.Event),
- ProcessFcn: nil,
+ ch: make(chan *core.Event),
}
}
@@ -108,14 +104,14 @@ func (m *ModuleImpl) Run(
if heartbeat == nil {
pulse = 365 * 24 * time.Hour // once a year
}
- tick := time.Tick(pulse)
+ tick := time.NewTicker(pulse)
// run event loop
go func() {
for {
select {
// Handle events
case event := <-m.ch:
- hCtx := context.WithValue(ctx, "label",
event.Label)
+ hCtx := context.WithValue(ctx, CtxKey("label"),
event.Label)
hdlr(hCtx, event)
// wait for terminate signal
@@ -123,7 +119,7 @@ func (m *ModuleImpl) Run(
return
// handle heartbeat
- case <-tick:
+ case <-tick.C:
// check for defined heartbeat handler
if heartbeat != nil {
heartbeat(ctx)
diff --git a/src/gnunet/service/namecache/module.go
b/src/gnunet/service/namecache/module.go
index 9251a58..42d38a2 100644
--- a/src/gnunet/service/namecache/module.go
+++ b/src/gnunet/service/namecache/module.go
@@ -24,6 +24,7 @@ import (
"gnunet/core"
"gnunet/service"
"gnunet/service/dht/blocks"
+ "gnunet/service/store"
)
//======================================================================
@@ -38,7 +39,7 @@ import (
type Module struct {
service.ModuleImpl
- cache service.DHTStore // transient block cache
+ cache store.DHTStore // transient block cache
}
// NewModule creates a new module instance.
@@ -46,7 +47,7 @@ func NewModule(ctx context.Context, c *core.Core) (m *Module)
{
m = &Module{
ModuleImpl: *service.NewModuleImpl(),
}
- m.cache, _ = service.NewDHTStore(config.Cfg.Namecache.Storage)
+ m.cache, _ = store.NewDHTStore(config.Cfg.Namecache.Storage)
return
}
@@ -69,7 +70,9 @@ func (m *Module) Import(fcm map[string]any) {
// Get an entry from the cache if available.
func (m *Module) Get(ctx context.Context, query *blocks.GNSQuery) (block
*blocks.GNSBlock, err error) {
var b blocks.Block
- b, err = m.cache.Get(query)
+ if b, err = m.cache.Get(query); err != nil {
+ return
+ }
err = blocks.Unwrap(b, block)
return
}
diff --git a/src/gnunet/service/revocation/module.go
b/src/gnunet/service/revocation/module.go
index 6997060..e435dd4 100644
--- a/src/gnunet/service/revocation/module.go
+++ b/src/gnunet/service/revocation/module.go
@@ -25,6 +25,7 @@ import (
"gnunet/crypto"
"gnunet/message"
"gnunet/service"
+ "gnunet/service/store"
"gnunet/util"
"net/http"
@@ -44,7 +45,7 @@ type Module struct {
service.ModuleImpl
bloomf *data.BloomFilter // bloomfilter for fast revocation check
- kvs service.KVStore // storage for known revocations
+ kvs store.KVStore // storage for known revocations
}
// NewModule returns an initialized revocation module
@@ -55,7 +56,7 @@ func NewModule(ctx context.Context, c *core.Core) (m *Module)
{
}
init := func() (err error) {
// Initialize access to revocation data storage
- if m.kvs, err =
service.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
+ if m.kvs, err =
store.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
return
}
// traverse the storage and build bloomfilter for all keys
@@ -170,6 +171,6 @@ func (m *Module) Revoke(ctx context.Context, rd *RevData)
(success bool, err err
// RPC returns the route and handler function for a JSON-RPC request
func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
return "/revocation/", func(wrt http.ResponseWriter, req *http.Request)
{
- wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
+ _, _ = wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
}
}
diff --git a/src/gnunet/service/revocation/pow.go
b/src/gnunet/service/revocation/pow.go
index cb35532..5518749 100644
--- a/src/gnunet/service/revocation/pow.go
+++ b/src/gnunet/service/revocation/pow.go
@@ -22,7 +22,6 @@ import (
"bytes"
"context"
"encoding/binary"
- "fmt"
"sort"
"time"
@@ -62,27 +61,21 @@ func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey
*crypto.ZoneKey) *PoWD
Timestamp: ts,
ZoneKey: zoneKey,
}
- if rd.SetPoW(pow) != nil {
- return nil
- }
+ rd.SetPoW(pow)
return rd
}
// SetPoW sets a new PoW value in the data structure
-func (p *PoWData) SetPoW(pow uint64) error {
+func (p *PoWData) SetPoW(pow uint64) {
p.PoW = pow
p.blob = p.Blob()
- if p.blob == nil {
- return fmt.Errorf("invalid PoW work unit")
- }
- return nil
}
// GetPoW returns the last checked PoW value
func (p *PoWData) GetPoW() uint64 {
if p.blob != nil {
var val uint64
- binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian, &val)
+ _ = binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian,
&val)
p.PoW = val
}
return p.PoW
@@ -138,14 +131,11 @@ type SignedRevData struct {
// NewRevDataFromMsg initializes a new RevData instance from a GNUnet message
func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData {
- rd := &RevData{
+ return &RevData{
Timestamp: m.Timestamp,
ZoneKeySig: m.ZoneKeySig,
+ PoWs: util.Clone(m.PoWs),
}
- for i, pow := range m.PoWs {
- rd.PoWs[i] = pow
- }
- return rd
}
// Size of a serialized RevData object.
@@ -174,7 +164,6 @@ func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err
error) {
// in this revocation and a verification status (-1=failed signature, -2=
// expired revocation, -3="out-of-order" PoW sequence).
func (rd *RevData) Verify(withSig bool) (zbits float64, rc int) {
-
// (1) check signature
if withSig {
sigBlock := &SignedRevData{
@@ -196,7 +185,7 @@ func (rd *RevData) Verify(withSig bool) (zbits float64, rc
int) {
}
// (2) check PoWs
- var last uint64 = 0
+ var last uint64
for _, pow := range rd.PoWs {
// check sequence order
if pow <= last {
@@ -252,7 +241,7 @@ func (rdc *RevDataCalc) Size() int {
// Average number of leading zero-bits in current list
func (rdc *RevDataCalc) Average() float64 {
- var sum uint16 = 0
+ var sum uint16
for _, num := range rdc.Bits {
sum += num
}
@@ -290,7 +279,7 @@ func (rdc *RevDataCalc) sortBits() {
func (rdc *RevDataCalc) Compute(ctx context.Context, bits int, last uint64, cb
func(float64, uint64)) (float64, uint64) {
// find the largest PoW value in current work unit
work := NewPoWData(0, rdc.Timestamp, &rdc.ZoneKeySig.ZoneKey)
- var max uint64 = 0
+ var max uint64
for i, pow := range rdc.PoWs {
if pow == 0 {
max++
diff --git a/src/gnunet/service/revocation/pow_test.go
b/src/gnunet/service/revocation/pow_test.go
index 17eb695..5d31b2f 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -12,7 +12,6 @@ import (
// Test revocation with test vector defined in the RFC draft.
func TestRevocationRFC(t *testing.T) {
-
var (
D =
"6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
ZKEY =
"000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
diff --git a/src/gnunet/service/revocation/rpc.go
b/src/gnunet/service/revocation/rpc.go
index 1b8ea12..7473def 100644
--- a/src/gnunet/service/revocation/rpc.go
+++ b/src/gnunet/service/revocation/rpc.go
@@ -18,10 +18,10 @@
package revocation
-import "net/rpc"
+import "gnunet/service"
//----------------------------------------------------------------------
// InitRPC registers RPC commands for the module
-func (m *Module) InitRPC(srv *rpc.Server) {
+func (m *Module) InitRPC(srv *service.JRPCServer) {
}
diff --git a/src/gnunet/service/revocation/service.go
b/src/gnunet/service/revocation/service.go
index 3d579e8..99d9b4a 100644
--- a/src/gnunet/service/revocation/service.go
+++ b/src/gnunet/service/revocation/service.go
@@ -27,6 +27,7 @@ import (
"gnunet/message"
"gnunet/service"
"gnunet/transport"
+ "gnunet/util"
"github.com/bfix/gospel/logger"
)
@@ -47,7 +48,6 @@ func NewService(ctx context.Context, c *core.Core)
service.Service {
srv := &Service{
Module: *mod,
}
- srv.ProcessFcn = srv.HandleMessage
return srv
}
@@ -75,7 +75,8 @@ func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connectio
logger.Printf(logger.INFO, "[revocation:%d:%d] Received
request: %v\n", id, reqID, msg)
// handle message
- s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+ valueCtx := context.WithValue(ctx, service.CtxKey("label"),
fmt.Sprintf(":%d:%d", id, reqID))
+ s.HandleMessage(valueCtx, nil, msg, mc)
}
// close client connection
mc.Close()
@@ -86,11 +87,11 @@ func (s *Service) ServeClient(ctx context.Context, id int,
mc *service.Connectio
}
// Handle a single incoming message
-func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
transport.Responder) bool {
+func (s *Service) HandleMessage(ctx context.Context, sender *util.PeerID, msg
message.Message, back transport.Responder) bool {
// assemble log label
label := ""
if v := ctx.Value("label"); v != nil {
- label = v.(string)
+ label, _ = v.(string)
}
switch m := msg.(type) {
case *message.RevocationQueryMsg:
diff --git a/src/gnunet/service/rpc.go b/src/gnunet/service/rpc.go
index de3a2c1..d5740fb 100644
--- a/src/gnunet/service/rpc.go
+++ b/src/gnunet/service/rpc.go
@@ -21,24 +21,35 @@ package service
import (
"context"
"net/http"
- "net/rpc"
"time"
"github.com/bfix/gospel/logger"
"github.com/gorilla/mux"
+ "github.com/gorilla/rpc/v2"
+ "github.com/gorilla/rpc/v2/json2"
)
+//----------------------------------------------------------------------
+//----------------------------------------------------------------------
+
+// JRPCServer for JSON-RPC handling (wrapper to keep type in our package)
+type JRPCServer struct {
+ *rpc.Server
+}
+
//----------------------------------------------------------------------
// JSON-RPC interface for services to be used as the primary client API
// for perform, manage and monitor GNUnet activities.
//----------------------------------------------------------------------
-// StartRPC the JSON-RPC server. It can be terminated by context
-func StartRPC(ctx context.Context, endpoint string) (srvRPC *rpc.Server, err
error) {
+// RunRPCServer runs the JSON-RPC server. It can be terminated by context only.
+func RunRPCServer(ctx context.Context, endpoint string) (srvRPC *JRPCServer,
err error) {
+ // instantiate RPC service
+ srvRPC = &JRPCServer{rpc.NewServer()}
+ srvRPC.RegisterCodec(json2.NewCodec(), "application/json")
// setup RPC request handler
router := mux.NewRouter()
- srvRPC = rpc.NewServer()
router.HandleFunc("/", srvRPC.ServeHTTP)
// instantiate a server and run it
@@ -51,16 +62,14 @@ func StartRPC(ctx context.Context, endpoint string) (srvRPC
*rpc.Server, err err
// start listening
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
- logger.Printf(logger.WARN, "[RPC] Server listen failed:
%s", err.Error())
+ logger.Printf(logger.WARN, "[rpc] server listen failed:
%s", err.Error())
}
}()
// wait for shutdown
go func() {
- select {
- case <-ctx.Done():
- if err := srv.Shutdown(context.Background()); err !=
nil {
- logger.Printf(logger.WARN, "[RPC] Server
shutdownn failed: %s", err.Error())
- }
+ <-ctx.Done()
+ if err := srv.Shutdown(context.Background()); err != nil {
+ logger.Printf(logger.WARN, "[rpc] server shutdownn
failed: %s", err.Error())
}
}()
return
diff --git a/src/gnunet/service/service.go b/src/gnunet/service/service.go
index c47ff5c..dd08282 100644
--- a/src/gnunet/service/service.go
+++ b/src/gnunet/service/service.go
@@ -42,7 +42,7 @@ type Service interface {
// Handle a single incoming message (either locally from a socket
// connection or from Transport). Response messages can be send
// via a Responder. Returns true if message was processed.
- HandleMessage(ctx context.Context, msg message.Message, resp
transport.Responder) bool
+ HandleMessage(ctx context.Context, sender *util.PeerID, msg
message.Message, resp transport.Responder) bool
}
// SocketHandler handles incoming connections on the local service socket.
@@ -85,7 +85,6 @@ func (h *SocketHandler) Start(ctx context.Context, path
string, params map[strin
loop:
for {
select {
-
// handle incoming connection
case conn := <-h.hdlr:
// run a new session with context
diff --git a/src/gnunet/service/store.go b/src/gnunet/service/store.go
deleted file mode 100644
index 5de5415..0000000
--- a/src/gnunet/service/store.go
+++ /dev/null
@@ -1,502 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package service
-
-import (
- "context"
- "database/sql"
- "encoding/binary"
- "encoding/gob"
- "encoding/hex"
- "errors"
- "fmt"
- "gnunet/config"
- "gnunet/crypto"
- "gnunet/service/dht/blocks"
- "gnunet/util"
- "io"
- "io/ioutil"
- "os"
- "sort"
- "sync"
-
- "github.com/bfix/gospel/logger"
- redis "github.com/go-redis/redis/v8"
-)
-
-// Error messages related to the key/value-store implementations
-var (
- ErrStoreInvalidSpec = fmt.Errorf("Invalid Store specification")
- ErrStoreUnknown = fmt.Errorf("Unknown Store type")
- ErrStoreNotAvailable = fmt.Errorf("Store not available")
-)
-
-//------------------------------------------------------------
-// Generic storage interface. Can be used for persistent or
-// transient (caching) storage of key/value data.
-//------------------------------------------------------------
-
-// Store is a key/value storage where the type of the key is either
-// a SHA512 hash value or a string and the value is either a DHT
-// block or a string. It is possiblle to mix any key/value types,
-// but not used in this implementation.
-type Store[K, V any] interface {
- // Put value into storage under given key
- Put(key K, val V) error
-
- // Get value with given key from storage
- Get(key K) (V, error)
-
- // List all store keys
- List() ([]K, error)
-}
-
-//------------------------------------------------------------
-// Types for custom store requirements
-//------------------------------------------------------------
-
-// DHTStore for DHT queries and blocks
-type DHTStore Store[blocks.Query, blocks.Block]
-
-// KVStore for key/value string pairs
-type KVStore Store[string, string]
-
-//------------------------------------------------------------
-// NewDHTStore creates a new storage handler with given spec
-// for use with DHT queries and blocks
-func NewDHTStore(spec config.ParameterConfig) (DHTStore, error) {
- // get the mode parameter
- mode, ok := config.GetParam[string](spec, "mode")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
- switch mode {
- //------------------------------------------------------------------
- // File-base storage
- //------------------------------------------------------------------
- case "file":
- return NewFileStore(spec)
- }
- return nil, ErrStoreUnknown
-}
-
-//------------------------------------------------------------
-// NewKVStore creates a new storage handler with given spec
-// for use with key/value string pairs.
-func NewKVStore(spec config.ParameterConfig) (KVStore, error) {
- // get the mode parameter
- mode, ok := config.GetParam[string](spec, "mode")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
- switch mode {
- //--------------------------------------------------------------
- // Redis service
- //--------------------------------------------------------------
- case "redis":
- return NewRedisStore(spec)
-
- //--------------------------------------------------------------
- // SQL database service
- //--------------------------------------------------------------
- case "sql":
- return NewSQLStore(spec)
- }
- return nil, errors.New("unknown storage mechanism")
-}
-
-//------------------------------------------------------------
-// Filesystem-based storage
-//------------------------------------------------------------
-
-// FileHeader is the layout of a file managed by the storage handler.
-// On start-up the file store recreates the list of file entries from
-// traversing the actual filesystem. This is done in the background.
-type FileHeader struct {
- key string // storage key
- size uint64 // size of file
- btype uint16 // block type
- stored util.AbsoluteTime // time added to store
- expires util.AbsoluteTime // expiration time
- lastUsed util.AbsoluteTime // time last used
- usedCount uint64 // usage count
-}
-
-// FileStore implements a filesystem-based storage mechanism for
-// DHT queries and blocks.
-type FileStore struct {
- path string // storage path
- cache bool // storage works as cache
- args config.ParameterConfig // arguments / settings
-
- totalSize uint64 // total storage size (logical, not
physical)
- files map[string]*FileHeader // list of file headers
- wrPos int // write position in cyclic list
- mtx sync.Mutex // serialize operations (prune)
-}
-
-// NewFileStore instantiates a new file storage.
-func NewFileStore(spec config.ParameterConfig) (DHTStore, error) {
- // get path parameter
- path, ok := config.GetParam[string](spec, "path")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
- isCache, ok := config.GetParam[bool](spec, "cache")
- if !ok {
- isCache = false
- }
- // remove old cache content
- if isCache {
- os.RemoveAll(path)
- }
- // create file store handler
- fs := &FileStore{
- path: path,
- args: spec,
- cache: isCache,
- files: make(map[string]*FileHeader),
- }
- // load file header list
- if !isCache {
- if fp, err := os.Open(path + "/files.db"); err == nil {
- dec := gob.NewDecoder(fp)
- for {
- hdr := new(FileHeader)
- if dec.Decode(hdr) != nil {
- if err != io.EOF {
- return nil, err
- }
- break
- }
- fs.files[hdr.key] = hdr
- fs.totalSize += hdr.size
- }
- fp.Close()
- }
- }
- return fs, nil
-}
-
-// Close file storage. write metadata to file
-func (s *FileStore) Close() (err error) {
- if !s.cache {
- if fp, err := os.Create(s.path + "/files.db"); err == nil {
- defer fp.Close()
- enc := gob.NewEncoder(fp)
- for _, hdr := range s.files {
- if err = enc.Encode(hdr); err != nil {
- break
- }
- }
- }
- }
- return
-}
-
-// Put block into storage under given key
-func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
- // check for free space
- if s.cache {
- // caching is limited by explicit number of files
- num, ok := config.GetParam[int](s.args, "num")
- if !ok {
- num = 100
- }
- if len(s.files) >= num {
- // make space for at least one new entry
- s.prune(1)
- }
- } else {
- // normal storage is limited by quota (default: 10GB)
- max, ok := config.GetParam[int](s.args, "maxGB")
- if !ok {
- max = 10
- }
- if int(s.totalSize>>30) > max {
- // drop a significant number of blocks
- s.prune(20)
- }
- }
- // get query parameters for entry
- var btype uint16 // block type
- query.Get("blkType", &btype)
- var expire util.AbsoluteTime // block expiration
- query.Get("expire", &expire)
-
- // get path and filename from key
- path, fname := s.expandPath(query.Key())
- // make sure the path exists
- if err = os.MkdirAll(path, 0755); err != nil {
- return
- }
- // write to file for storage
- var fp *os.File
- var fpSize int
- if fp, err = os.Create(path + "/" + fname); err == nil {
- defer fp.Close()
- // write block data
- if err = binary.Write(fp, binary.BigEndian, btype); err == nil {
- if err = binary.Write(fp, binary.BigEndian, expire);
err == nil {
- _, err = fp.Write(block.Data())
- }
- }
- }
- // add header to internal list
- now := util.AbsoluteTimeNow()
- hdr := &FileHeader{
- key: hex.EncodeToString(query.Key().Bits),
- size: uint64(fpSize),
- btype: btype,
- expires: expire,
- stored: now,
- lastUsed: now,
- usedCount: 1,
- }
- s.files[hdr.key] = hdr
- return
-}
-
-// Get block with given key from storage
-func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
- // get requested block type
- var (
- btype uint16 = blocks.DHT_BLOCK_ANY
- blkt uint16 // actual block type
- expire util.AbsoluteTime // expiration date
- data []byte // block data
- )
- query.Get("blkType", &btype)
-
- // get path and filename from key
- path, fname := s.expandPath(query.Key())
- // read file content (block data)
- var file *os.File
- if file, err = os.Open(path + "/" + fname); err != nil {
- return
- }
- // read block data
- if err = binary.Read(file, binary.BigEndian, &blkt); err == nil {
- if btype != blocks.DHT_BLOCK_ANY && btype != blkt {
- // block types not matching
- return
- }
- if err = binary.Read(file, binary.BigEndian, &expire); err ==
nil {
- if data, err = ioutil.ReadAll(file); err == nil {
- block = blocks.NewGenericBlock(data)
- }
- }
- }
- return
-}
-
-// Get a list of all stored block keys (generic query).
-func (s *FileStore) List() ([]blocks.Query, error) {
- return make([]blocks.Query, 0), nil
-}
-
-// expandPath returns the full path to the file for given key.
-func (s *FileStore) expandPath(key *crypto.HashCode) (string, string) {
- h := hex.EncodeToString(key.Bits)
- return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
-}
-
-// Prune list of file headers so we drop at least n entries.
-// returns number of removed entries.
-func (s *FileStore) prune(n int) (del int) {
- // get list of headers; remove expired entries on the fly
- list := make([]*FileHeader, 0)
- for key, hdr := range s.files {
- // remove expired entry
- if hdr.expires.Expired() {
- s.dropFile(key)
- del++
- }
- // append to list
- list = append(list, hdr)
- }
- // check if we are already done.
- if del >= n {
- return
- }
- // sort list by decending rate "(lifetime * size) / usedCount"
- sort.Slice(list, func(i, j int) bool {
- ri := (list[i].stored.Elapsed().Val * list[i].size) /
list[i].usedCount
- rj := (list[j].stored.Elapsed().Val * list[j].size) /
list[j].usedCount
- return ri > rj
- })
- // remove from start of list until prune limit is reached
- for _, hdr := range list {
- s.dropFile(hdr.key)
- del++
- if del == n {
- break
- }
- }
- return
-}
-
-// drop file removes a file from the internal list and the physical storage.
-func (s *FileStore) dropFile(key string) {
- // remove for internal list
- delete(s.files, key)
- // remove from filesystem
- path := fmt.Sprintf("%s/%s/%s/%s", s.path, key[:2], key[2:4], key[4:])
- if err := os.Remove(path); err != nil {
- logger.Printf(logger.ERROR, "[store] can't remove file %s: %s",
path, err.Error())
- return
- }
-}
-
-//------------------------------------------------------------
-// Redis: only use for caching purposes on key/value strings
-//------------------------------------------------------------
-
-// RedisStore uses a (local) Redis server for key/value storage
-type RedisStore struct {
- client *redis.Client // client connection
- db int // index to database
-}
-
-// NewRedisStore creates a Redis service client instance.
-func NewRedisStore(spec config.ParameterConfig) (s KVStore, err error) {
- // get connection parameters
- addr, ok := config.GetParam[string](spec, "addr")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
- passwd, ok := config.GetParam[string](spec, "passwd")
- if !ok {
- passwd = ""
- }
- db, ok := config.GetParam[int](spec, "db")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
-
- // create new Redis store
- kvs := new(RedisStore)
- kvs.db = db
- kvs.client = redis.NewClient(&redis.Options{
- Addr: addr,
- Password: passwd,
- DB: db,
- })
- if kvs.client == nil {
- err = ErrStoreNotAvailable
- }
- s = kvs
- return
-}
-
-// Put block into storage under given key
-func (s *RedisStore) Put(key string, value string) (err error) {
- return s.client.Set(context.TODO(), key, value, 0).Err()
-}
-
-// Get block with given key from storage
-func (s *RedisStore) Get(key string) (value string, err error) {
- return s.client.Get(context.TODO(), key).Result()
-}
-
-// List all keys in store
-func (s *RedisStore) List() (keys []string, err error) {
- var (
- crs uint64
- segm []string
- ctx = context.TODO()
- )
- keys = make([]string, 0)
- for {
- segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
- if err != nil {
- return
- }
- if crs == 0 {
- break
- }
- keys = append(keys, segm...)
- }
- return
-}
-
-//------------------------------------------------------------
-// SQL-based key-value-store
-//------------------------------------------------------------
-
-// SQLStore for generic SQL database handling
-type SQLStore struct {
- db *util.DbConn
-}
-
-// NewSQLStore creates a new SQL-based key/value store.
-func NewSQLStore(spec config.ParameterConfig) (s KVStore, err error) {
- // get connection parameters
- connect, ok := config.GetParam[string](spec, "connect")
- if !ok {
- return nil, ErrStoreInvalidSpec
- }
- // create SQL store
- kvs := new(SQLStore)
-
- // connect to SQL database
- kvs.db, err = util.DbPool.Connect(connect)
- if err != nil {
- return nil, err
- }
- // get number of key/value pairs (as a check for existing table)
- row := kvs.db.QueryRow("select count(*) from store")
- var num int
- if row.Scan(&num) != nil {
- return nil, ErrStoreNotAvailable
- }
- return kvs, nil
-}
-
-// Put a key/value pair into the store
-func (s *SQLStore) Put(key string, value string) error {
- _, err := s.db.Exec("insert into store(key,value) values(?,?)", key,
value)
- return err
-}
-
-// Get a value for a given key from store
-func (s *SQLStore) Get(key string) (value string, err error) {
- row := s.db.QueryRow("select value from store where key=?", key)
- err = row.Scan(&value)
- return
-}
-
-// List all keys in store
-func (s *SQLStore) List() (keys []string, err error) {
- var (
- rows *sql.Rows
- key string
- )
- keys = make([]string, 0)
- rows, err = s.db.Query("select key from store")
- if err == nil {
- for rows.Next() {
- if err = rows.Scan(&key); err != nil {
- break
- }
- keys = append(keys, key)
- }
- }
- return
-}
diff --git a/src/gnunet/util/database.go b/src/gnunet/service/store/database.go
similarity index 75%
rename from src/gnunet/util/database.go
rename to src/gnunet/service/store/database.go
index 852862b..2b94122 100644
--- a/src/gnunet/util/database.go
+++ b/src/gnunet/service/store/database.go
@@ -16,12 +16,13 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
-package util
+package store
import (
"context"
"database/sql"
"fmt"
+ "gnunet/util"
"os"
"strings"
@@ -31,8 +32,8 @@ import (
// Error messages related to databases
var (
- ErrSQLInvalidDatabaseSpec = fmt.Errorf("Invalid database specification")
- ErrSQLNoDatabase = fmt.Errorf("Database not found")
+ ErrSQLInvalidDatabaseSpec = fmt.Errorf("invalid database specification")
+ ErrSQLNoDatabase = fmt.Errorf("database not found")
)
//----------------------------------------------------------------------
@@ -40,35 +41,35 @@ var (
// on the same instance, managed by the database pool.
//----------------------------------------------------------------------
-// DbConn is a database connection suitable for executing SQL commands.
-type DbConn struct {
- conn *sql.Conn // connection to database instance
- pool *dbPool // reference to managng pool
- key string // database identifier (connect string)
+// DBConn is a database connection suitable for executing SQL commands.
+type DBConn struct {
+ conn *sql.Conn // connection to database instance
+ key string // database connect string (identifier for pool)
+ engine string // database engine
}
// Close database connection.
-func (db *DbConn) Close() (err error) {
+func (db *DBConn) Close() (err error) {
if err = db.conn.Close(); err != nil {
return
}
- err = db.pool.remove(db.key)
+ err = DBPool.remove(db.key)
return
}
// QueryRow returns a single record for a query
-func (db *DbConn) QueryRow(query string, args ...any) *sql.Row {
- return db.conn.QueryRowContext(db.pool.ctx, query, args...)
+func (db *DBConn) QueryRow(query string, args ...any) *sql.Row {
+ return db.conn.QueryRowContext(DBPool.ctx, query, args...)
}
// Query returns all matching records for a query
-func (db *DbConn) Query(query string, args ...any) (*sql.Rows, error) {
- return db.conn.QueryContext(db.pool.ctx, query, args...)
+func (db *DBConn) Query(query string, args ...any) (*sql.Rows, error) {
+ return db.conn.QueryContext(DBPool.ctx, query, args...)
}
// Exec a SQL statement
-func (db *DbConn) Exec(query string, args ...any) (sql.Result, error) {
- return db.conn.ExecContext(db.pool.ctx, query, args...)
+func (db *DBConn) Exec(query string, args ...any) (sql.Result, error) {
+ return db.conn.ExecContext(DBPool.ctx, query, args...)
}
// TODO: add more SQL methods
@@ -80,11 +81,11 @@ func (db *DbConn) Exec(query string, args ...any)
(sql.Result, error) {
// global instance for the database pool (singleton)
var (
- DbPool *dbPool
+ DBPool *dbPool
)
-// DbPoolEntry holds information about a database instance.
-type DbPoolEntry struct {
+// DBPoolEntry holds information about a database instance.
+type DBPoolEntry struct {
db *sql.DB // reference to the database engine
refs int // number of open connections (reference count)
connect string // SQL connect string
@@ -93,16 +94,16 @@ type DbPoolEntry struct {
// package initialization
func init() {
// construct database pool
- DbPool = new(dbPool)
- DbPool.insts = NewMap[string, *DbPoolEntry]()
- DbPool.ctx, DbPool.cancel = context.WithCancel(context.Background())
+ DBPool = new(dbPool)
+ DBPool.insts = util.NewMap[string, *DBPoolEntry]()
+ DBPool.ctx, DBPool.cancel = context.WithCancel(context.Background())
}
// dbPool keeps a mapping between connect string and database instance
type dbPool struct {
- ctx context.Context // connection context
- cancel context.CancelFunc // cancel function
- insts *Map[string, *DbPoolEntry] // map of database instances
+ ctx context.Context // connection context
+ cancel context.CancelFunc // cancel function
+ insts *util.Map[string, *DBPoolEntry] // map of database instances
}
// remove a database instance from the pool based on its connect string.
@@ -127,7 +128,7 @@ func (p *dbPool) remove(key string) error {
// Connect to a SQL database (various types and flavors):
// The 'spec' option defines the arguments required to connect to a database;
// the meaning and format of the arguments depends on the specific SQL
database.
-// The arguments are seperated by the '+' character; the first (and mandatory)
+// The arguments are separated by the '+' character; the first (and mandatory)
// argument defines the SQL database type. Other arguments depend on the value
// of this first argument.
// The following SQL types are implemented:
@@ -136,12 +137,13 @@ func (p *dbPool) remove(key string) error {
// * 'mysql': A MySQL-compatible database; the second argument specifies the
// information required to log into the database (e.g.
// "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]").
-func (p *dbPool) Connect(spec string) (db *DbConn, err error) {
+func (p *dbPool) Connect(spec string) (db *DBConn, err error) {
err = p.insts.Process(func() error {
// check if we have a connection to this database.
+ db = new(DBConn)
inst, ok := p.insts.Get(spec)
if !ok {
- inst = new(DbPoolEntry)
+ inst = new(DBPoolEntry)
inst.refs = 0
inst.connect = spec
@@ -152,7 +154,8 @@ func (p *dbPool) Connect(spec string) (db *DbConn, err
error) {
return ErrSQLInvalidDatabaseSpec
}
// create database object
- switch specs[0] {
+ db.engine = specs[0]
+ switch db.engine {
case "sqlite3":
// check if the database file exists
var fi os.FileInfo
@@ -172,12 +175,10 @@ func (p *dbPool) Connect(spec string) (db *DbConn, err
error) {
}
// save database in pool
p.insts.Put(spec, inst)
- ok = true
}
// increment reference count
inst.refs++
// return a new connection to the database.
- db = new(DbConn)
db.conn, err = inst.db.Conn(p.ctx)
return err
}, false)
diff --git a/src/gnunet/service/dht/dhtstore_test.go
b/src/gnunet/service/store/dhtstore_test.go
similarity index 92%
rename from src/gnunet/service/dht/dhtstore_test.go
rename to src/gnunet/service/store/dhtstore_test.go
index d9fc1d0..14da6ff 100644
--- a/src/gnunet/service/dht/dhtstore_test.go
+++ b/src/gnunet/service/store/dhtstore_test.go
@@ -16,37 +16,36 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
-package dht
+package store
import (
"encoding/hex"
- "gnunet/config"
"gnunet/crypto"
- "gnunet/service"
+ "gnunet/enums"
"gnunet/service/dht/blocks"
+ "gnunet/util"
"math/rand"
"testing"
)
// test constants
const (
- fsNumBlocks = 5
+ fsNumBlocks = 10
)
// TestDHTFileStore generates 'fsNumBlocks' fully-random blocks
// and stores them under their SHA512 key. It than retrieves
// each block from storage and checks for matching hash.
func TestDHTFilesStore(t *testing.T) {
-
// test configuration
- cfg := make(config.ParameterConfig)
+ cfg := make(util.ParameterSet)
cfg["mode"] = "file"
cfg["cache"] = false
cfg["path"] = "/var/lib/gnunet/dht/store"
cfg["maxGB"] = 10
// create file store
- fs, err := service.NewFileStore(cfg)
+ fs, err := NewFileStore(cfg)
if err != nil {
t.Fatal(err)
}
@@ -62,7 +61,7 @@ func TestDHTFilesStore(t *testing.T) {
val := blocks.NewGenericBlock(buf)
// generate associated key
k := crypto.Hash(buf).Bits
- key := blocks.NewGenericQuery(k)
+ key := blocks.NewGenericQuery(k, enums.BLOCK_TYPE_ANY, 0)
// store block
if err := fs.Put(key, val); err != nil {
diff --git a/src/gnunet/service/store/store.go
b/src/gnunet/service/store/store.go
new file mode 100644
index 0000000..d5ef05d
--- /dev/null
+++ b/src/gnunet/service/store/store.go
@@ -0,0 +1,278 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+ "context"
+ "database/sql"
+ _ "embed" // use embedded filesystem
+ "errors"
+ "fmt"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+
+ redis "github.com/go-redis/redis/v8"
+)
+
+// Error messages related to the key/value-store implementations
+var (
+ ErrStoreInvalidSpec = fmt.Errorf("invalid Store specification")
+ ErrStoreUnknown = fmt.Errorf("unknown Store type")
+ ErrStoreNotAvailable = fmt.Errorf("store not available")
+ ErrStoreNoApprox = fmt.Errorf("no approx search for store defined")
+ ErrStoreNoList = fmt.Errorf("no key listing for store defined")
+)
+
+//------------------------------------------------------------
+// Generic storage interface. Can be used for persistent or
+// transient (caching) storage of key/value data.
+//------------------------------------------------------------
+
+// Store is a key/value storage where the type of the key is either
+// a SHA512 hash value or a string and the value is either a DHT
+// block or a string. It is possiblle to mix any key/value types,
+// but not used in this implementation.
+type Store[K, V any] interface {
+ // Put value into storage under given key
+ Put(key K, val V) error
+
+ // Get value with given key from storage
+ Get(key K) (V, error)
+
+ // GetApprox returns the best-matching value with given key from storage
+ // that is not excluded.
+ GetApprox(key K, excl func(V) bool) (V, any, error)
+
+ // List all store keys
+ List() ([]K, error)
+
+ // Close store
+ Close() error
+}
+
+//------------------------------------------------------------
+// Types for custom store requirements
+//------------------------------------------------------------
+
+// DHTStore for DHT queries and blocks
+type DHTStore Store[blocks.Query, blocks.Block]
+
+// KVStore for key/value string pairs
+type KVStore Store[string, string]
+
+//------------------------------------------------------------
+// NewDHTStore creates a new storage handler with given spec
+// for use with DHT queries and blocks
+func NewDHTStore(spec util.ParameterSet) (DHTStore, error) {
+ // get the mode parameter
+ mode, ok := util.GetParam[string](spec, "mode")
+ if !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+ switch mode {
+ //------------------------------------------------------------------
+ // File-base storage
+ //------------------------------------------------------------------
+ case "file":
+ return NewFileStore(spec)
+ }
+ return nil, ErrStoreUnknown
+}
+
+//------------------------------------------------------------
+// NewKVStore creates a new storage handler with given spec
+// for use with key/value string pairs.
+func NewKVStore(spec util.ParameterSet) (KVStore, error) {
+ // get the mode parameter
+ mode, ok := util.GetParam[string](spec, "mode")
+ if !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+ switch mode {
+ //--------------------------------------------------------------
+ // Redis service
+ //--------------------------------------------------------------
+ case "redis":
+ return NewRedisStore(spec)
+
+ //--------------------------------------------------------------
+ // SQL database service
+ //--------------------------------------------------------------
+ case "sql":
+ return NewSQLStore(spec)
+ }
+ return nil, errors.New("unknown storage mechanism")
+}
+
+//------------------------------------------------------------
+// Redis: only use for caching purposes on key/value strings
+//------------------------------------------------------------
+
+// RedisStore uses a (local) Redis server for key/value storage
+type RedisStore struct {
+ client *redis.Client // client connection
+ db int // index to database
+}
+
+// NewRedisStore creates a Redis service client instance.
+func NewRedisStore(spec util.ParameterSet) (s KVStore, err error) {
+ // get connection parameters
+ addr, ok := util.GetParam[string](spec, "addr")
+ if !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+ passwd, ok := util.GetParam[string](spec, "passwd")
+ if !ok {
+ passwd = ""
+ }
+ db, ok := util.GetParam[int](spec, "db")
+ if !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+
+ // create new Redis store
+ kvs := new(RedisStore)
+ kvs.db = db
+ kvs.client = redis.NewClient(&redis.Options{
+ Addr: addr,
+ Password: passwd,
+ DB: db,
+ })
+ if kvs.client == nil {
+ err = ErrStoreNotAvailable
+ }
+ s = kvs
+ return
+}
+
+// Put value into storage under given key
+func (s *RedisStore) Put(key string, value string) (err error) {
+ return s.client.Set(context.TODO(), key, value, 0).Err()
+}
+
+// Get value with given key from storage
+func (s *RedisStore) Get(key string) (value string, err error) {
+ return s.client.Get(context.TODO(), key).Result()
+}
+
+// GetApprox returns the best-matching value for given key from storage
+func (s *RedisStore) GetApprox(key string, crit func(string) bool) (value
string, vkey any, err error) {
+ return "", "", ErrStoreNoApprox
+}
+
+// List all keys in store
+func (s *RedisStore) List() (keys []string, err error) {
+ var (
+ crs uint64
+ segm []string
+ ctx = context.TODO()
+ )
+ keys = make([]string, 0)
+ for {
+ segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
+ if err != nil {
+ return
+ }
+ if crs == 0 {
+ break
+ }
+ keys = append(keys, segm...)
+ }
+ return
+}
+
+// Close redis connection
+func (s *RedisStore) Close() error {
+ return s.client.Close()
+}
+
+//------------------------------------------------------------
+// SQL-based key-value-store
+//------------------------------------------------------------
+
+// SQLStore for generic SQL database handling
+type SQLStore struct {
+ db *DBConn
+}
+
+// NewSQLStore creates a new SQL-based key/value store.
+func NewSQLStore(spec util.ParameterSet) (s KVStore, err error) {
+ // get connection parameters
+ connect, ok := util.GetParam[string](spec, "connect")
+ if !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+ // create SQL store
+ kvs := new(SQLStore)
+
+ // connect to SQL database
+ kvs.db, err = DBPool.Connect(connect)
+ if err != nil {
+ return nil, err
+ }
+ // get number of key/value pairs (as a check for existing table)
+ row := kvs.db.QueryRow("select count(*) from store")
+ var num int
+ if row.Scan(&num) != nil {
+ return nil, ErrStoreNotAvailable
+ }
+ return kvs, nil
+}
+
+// Put a key/value pair into the store
+func (s *SQLStore) Put(key string, value string) error {
+ _, err := s.db.Exec("insert into store(key,value) values(?,?)", key,
value)
+ return err
+}
+
+// Get a value for a given key from store
+func (s *SQLStore) Get(key string) (value string, err error) {
+ row := s.db.QueryRow("select value from store where key=?", key)
+ err = row.Scan(&value)
+ return
+}
+
+// GetApprox returns the best-matching value for given key from storage
+func (s *SQLStore) GetApprox(key string, crit func(string) bool) (value
string, vkey any, err error) {
+ return "", "", ErrStoreNoApprox
+}
+
+// List all keys in store
+func (s *SQLStore) List() (keys []string, err error) {
+ var (
+ rows *sql.Rows
+ key string
+ )
+ keys = make([]string, 0)
+ rows, err = s.db.Query("select key from store")
+ if err == nil {
+ for rows.Next() {
+ if err = rows.Scan(&key); err != nil {
+ break
+ }
+ keys = append(keys, key)
+ }
+ }
+ return
+}
+
+// Close redis connection
+func (s *SQLStore) Close() error {
+ return s.db.Close()
+}
diff --git a/src/gnunet/service/store/store_fs.go
b/src/gnunet/service/store/store_fs.go
new file mode 100644
index 0000000..a33c317
--- /dev/null
+++ b/src/gnunet/service/store/store_fs.go
@@ -0,0 +1,287 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+ "encoding/hex"
+ "fmt"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+ "io/ioutil"
+ "os"
+
+ "github.com/bfix/gospel/logger"
+ "github.com/bfix/gospel/math"
+)
+
+//============================================================
+// Filesystem-based storage
+//============================================================
+
+// FileStore implements a filesystem-based storage mechanism for
+// DHT queries and blocks.
+type FileStore struct {
+ path string // storage path
+ cache bool // storage works as cache
+ args util.ParameterSet // arguments / settings
+ totalSize uint64 // total storage size (logical, not
physical)
+
+ // storage-mode metadata
+ meta *FileMetaDB // database for metadata
+ maxSpace int // max. storage space in GB
+
+ // cache-mode metadata
+ cacheMeta []*FileMetadata // cached metadata
+ wrPos int // write position in cyclic list
+ size int // size of cache (number of entries)
+}
+
+// NewFileStore instantiates a new file storage.
+func NewFileStore(spec util.ParameterSet) (DHTStore, error) {
+ // create file store handler
+ fs := new(FileStore)
+ fs.args = spec
+
+ // get parameter
+ var ok bool
+ if fs.path, ok = util.GetParam[string](spec, "path"); !ok {
+ return nil, ErrStoreInvalidSpec
+ }
+ if fs.cache, ok = util.GetParam[bool](spec, "cache"); !ok {
+ fs.cache = false
+ }
+
+ // setup file store depending on mode (storage/cache)
+ if fs.cache {
+ // remove old cache content
+ os.RemoveAll(fs.path)
+ // get number of cache entries
+ if fs.size, ok = util.GetParam[int](spec, "num"); !ok {
+ // defaults to 1000 entries
+ fs.size = 1000
+ }
+ fs.cacheMeta = make([]*FileMetadata, fs.size)
+ } else {
+ // connect to metadata database
+ var err error
+ if fs.meta, err = OpenMetaDB(fs.path); err != nil {
+ return nil, err
+ }
+ // normal storage is limited by quota (default: 10GB)
+ if fs.maxSpace, ok = util.GetParam[int](spec, "maxGB"); !ok {
+ fs.maxSpace = 10
+ }
+ }
+ return fs, nil
+}
+
+// Close file storage.
+func (s *FileStore) Close() (err error) {
+ if !s.cache {
+ // close database connection
+ err = s.meta.Close()
+ }
+ return
+}
+
+// Put block into storage under given key
+func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
+ // check for free space
+ if !s.cache {
+ if int(s.totalSize>>30) > s.maxSpace {
+ // drop a significant number of blocks
+ s.prune(20)
+ }
+ }
+ // get parameters
+ btype := query.Type()
+ expire := block.Expire()
+
+ // get path and filename from key
+ path, fname := s.expandPath(query.Key().Bits)
+ // make sure the path exists
+ if err = os.MkdirAll(path, 0755); err != nil {
+ return
+ }
+ // write to file for storage
+ var fp *os.File
+ bd := block.Data()
+ if fp, err = os.Create(path + "/" + fname); err == nil {
+ defer fp.Close()
+ // write block data
+ if _, err = fp.Write(bd); err != nil {
+ return
+ }
+ }
+ // compile metadata
+ now := util.AbsoluteTimeNow()
+ meta := &FileMetadata{
+ key: query.Key().Bits,
+ size: uint64(len(bd)),
+ btype: btype,
+ expires: expire,
+ stored: now,
+ lastUsed: now,
+ usedCount: 1,
+ }
+ if s.cache {
+ // store in cyclic list
+ s.cacheMeta[s.wrPos] = meta
+ s.wrPos = (s.wrPos + 1) % s.size
+ } else {
+ // store metadata in database
+ if err = s.meta.Store(meta); err != nil {
+ return
+ }
+ // add to total storage size
+ s.totalSize += meta.size
+ }
+ return
+}
+
+// Get block with given key from storage
+func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
+ // check if we have metadata for the query
+ key := query.Key().Bits
+ btype := query.Type()
+ var md *FileMetadata
+ if md, err = s.meta.Get(key, btype); err != nil || md == nil {
+ return
+ }
+ // check for expired entry
+ if md.expires.Expired() {
+ err = s.dropFile(md)
+ return
+ }
+ // mark the block as newly used
+ if err = s.meta.Used(key, btype); err != nil {
+ return
+ }
+ return s.readBlock(query.Key().Bits)
+}
+
+// GetApprox returns the best-matching value with given key from storage
+// that is not excluded
+func (s *FileStore) GetApprox(query blocks.Query, excl func(blocks.Block)
bool) (block blocks.Block, key any, err error) {
+ var bestKey []byte
+ var bestBlk blocks.Block
+ var bestDist *math.Int
+ // distance function
+ dist := func(a, b []byte) *math.Int {
+ c := make([]byte, len(a))
+ for i := range a {
+ c[i] = a[i] ^ b[i]
+ }
+ return math.NewIntFromBytes(c)
+ }
+ // iterate over all keys
+ check := func(md *FileMetadata) {
+ // check for better match
+ d := dist(md.key, query.Key().Bits)
+ if bestKey == nil || d.Cmp(bestDist) < 0 {
+ // we might have a match. check block for exclusion
+ block, err = s.readBlock(md.key)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[dhtstore] failed
to retrieve blok for %s", hex.EncodeToString(md.key))
+ return
+ }
+ if excl(block) {
+ return
+ }
+ // remember best match
+ bestKey = md.key
+ bestBlk = block
+ bestDist = d
+ }
+ }
+ if err = s.meta.Traverse(check); err != nil {
+ return
+ }
+ if bestBlk != nil {
+ // mark the block as newly used
+ if err = s.meta.Used(bestKey, bestBlk.Type()); err != nil {
+ return
+ }
+ }
+ return bestBlk, bestDist, nil
+}
+
+// Get a list of all stored block keys (generic query).
+func (s *FileStore) List() ([]blocks.Query, error) {
+ return nil, ErrStoreNoList
+}
+
+// read block from storage for given key
+func (s *FileStore) readBlock(key []byte) (block blocks.Block, err error) {
+ // get path and filename from key
+ path, fname := s.expandPath(key)
+ // read file content (block data)
+ var file *os.File
+ if file, err = os.Open(path + "/" + fname); err != nil {
+ return
+ }
+ defer file.Close()
+ // read block data
+ var data []byte
+ if data, err = ioutil.ReadAll(file); err == nil {
+ block = blocks.NewGenericBlock(data)
+ }
+ return
+}
+
+// expandPath returns the full path to the file for given key.
+func (s *FileStore) expandPath(key []byte) (string, string) {
+ h := hex.EncodeToString(key)
+ return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
+}
+
+// Prune list of file headers so we drop at least n entries.
+// returns number of removed entries.
+func (s *FileStore) prune(n int) (del int) {
+ // collect obsolete records
+ obsolete, err := s.meta.Obsolete(n)
+ if err != nil {
+ logger.Println(logger.ERROR, "[FileStore] failed to collect
obsolete records: "+err.Error())
+ return
+ }
+ for _, md := range obsolete {
+ if err := s.dropFile(md); err != nil {
+ return
+ }
+ del++
+ }
+ return
+}
+
+// drop file removes a file from metadatabase and the physical storage.
+func (s *FileStore) dropFile(md *FileMetadata) (err error) {
+ // adjust total size
+ s.totalSize -= md.size
+ // remove from database
+ if err = s.meta.Drop(md.key, md.btype); err != nil {
+ logger.Printf(logger.ERROR, "[store] can't remove metadata
(%s,%d): %s", md.key, md.btype, err.Error())
+ return
+ }
+ // remove from filesystem
+ path := fmt.Sprintf("%s/%s/%s/%s", s.path, md.key[:2], md.key[2:4],
md.key[4:])
+ if err = os.Remove(path); err != nil {
+ logger.Printf(logger.ERROR, "[store] can't remove file %s: %s",
path, err.Error())
+ }
+ return
+}
diff --git a/src/gnunet/service/store/store_fs_meta.go
b/src/gnunet/service/store/store_fs_meta.go
new file mode 100644
index 0000000..414921c
--- /dev/null
+++ b/src/gnunet/service/store/store_fs_meta.go
@@ -0,0 +1,174 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package store
+
+import (
+ "database/sql"
+ _ "embed"
+ "gnunet/util"
+ "os"
+)
+
+//============================================================
+// Metadata handling for file storage
+//============================================================
+
+// FileMetadata holds information about a file (raw block data)
+// and is stored in a SQL database for faster access.
+type FileMetadata struct {
+ key []byte // storage key
+ size uint64 // size of file
+ btype uint16 // block type
+ stored util.AbsoluteTime // time added to store
+ expires util.AbsoluteTime // expiration time
+ lastUsed util.AbsoluteTime // time last used
+ usedCount uint64 // usage count
+}
+
+//------------------------------------------------------------
+// Metadata database: A SQLite3 database to hold metadata about
+// blocks in file storage
+//------------------------------------------------------------
+
+//go:embed store_fs_meta.sql
+var initScript []byte
+
+// FileMetaDB is a SQLite3 database for block metadata
+type FileMetaDB struct {
+ conn *DBConn // database connection
+}
+
+// OpenMetaDB opens a metadata database in the given path. The name of the
+// database is "access.db".
+func OpenMetaDB(path string) (db *FileMetaDB, err error) {
+ // connect to database
+ dbFile := path + "/acccess.db"
+ if _, err = os.Stat(path + "/acccess.db"); err != nil {
+ var file *os.File
+ if file, err = os.Create(dbFile); err != nil {
+ return
+ }
+ file.Close()
+ }
+ db = new(FileMetaDB)
+ if db.conn, err = DBPool.Connect("sqlite3:" + dbFile); err != nil {
+ return
+ }
+ // check for initialized database
+ res := db.conn.QueryRow("select name from sqlite_master where
type='table' and name='meta'")
+ var s string
+ if res.Scan(&s) != nil {
+ // initialize database
+ if _, err = db.conn.Exec(string(initScript)); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// Store metadata in database: creates or updates a record for the metadata
+// in the database; primary key is the query key
+func (db *FileMetaDB) Store(md *FileMetadata) (err error) {
+ sql := "replace into
meta(qkey,btype,size,stored,expires,lastUsed,usedCount) values(?,?,?,?,?,?,?)"
+ _, err = db.conn.Exec(sql, md.key, md.btype, md.size,
md.stored.Epoch(), md.expires.Epoch(), md.lastUsed.Epoch(), md.usedCount)
+ return
+}
+
+// Get block metadata from database
+func (db *FileMetaDB) Get(key []byte, btype uint16) (md *FileMetadata, err
error) {
+ md = new(FileMetadata)
+ md.key = util.Clone(key)
+ md.btype = btype
+ stmt := "select size,stored,expires,lastUsed,usedCount from meta where
qkey=? and btype=?"
+ row := db.conn.QueryRow(stmt, key, btype)
+ var st, exp, lu uint64
+ if err = row.Scan(&md.size, &st, &exp, &lu, &md.usedCount); err ==
sql.ErrNoRows {
+ md = nil
+ err = nil
+ } else {
+ md.stored.Val = st * 1000000
+ md.expires.Val = exp * 1000000
+ md.lastUsed.Val = lu * 1000000
+ }
+ return
+}
+
+// Drop metadata for block from database
+func (db *FileMetaDB) Drop(key []byte, btype uint16) error {
+ _, err := db.conn.Exec("delete from meta where qkey=? and btype=?",
key, btype)
+ return err
+}
+
+// Used a block from store: increment usage count and lastUsed time.
+func (db *FileMetaDB) Used(key []byte, btype uint16) error {
+ _, err := db.conn.Exec("update meta set
usedCount=usedCount+1,lastUsed=unixepoch() where qkey=? and btype=?", key,
btype)
+ return err
+}
+
+// Obsolete collects records from the meta database that are considered
+// "removable". Entries are rated by the value of "(lifetime * size) /
usedCount"
+func (db *FileMetaDB) Obsolete(n int) (removable []*FileMetadata, err error) {
+ // get obsolete records from database
+ rate := "(unixepoch()-unixepoch(stored))*size/usedCount"
+ stmt := "select qkey,btype from meta order by " + rate + " limit ?"
+ var rows *sql.Rows
+ if rows, err = db.conn.Query(stmt, n); err != nil {
+ return
+ }
+ var md *FileMetadata
+ for rows.Next() {
+ var st, exp, lu uint64
+ if err = rows.Scan(&md.key, &md.btype, &md.size, &st, &exp,
&lu, &md.usedCount); err != nil {
+ return
+ }
+ md.stored.Val = st * 1000000
+ md.expires.Val = exp * 1000000
+ md.lastUsed.Val = lu * 1000000
+ removable = append(removable, md)
+ }
+ return
+}
+
+// Traverse metadata records and call function on each record
+func (db *FileMetaDB) Traverse(f func(*FileMetadata)) error {
+ sql := "select qkey,btype,size,stored,expires,lastUsed,usedCount from
meta"
+ rows, err := db.conn.Query(sql)
+ if err != nil {
+ return err
+ }
+ md := new(FileMetadata)
+ for rows.Next() {
+ var st, exp, lu uint64
+ err = rows.Scan(&md.key, &md.btype, &md.size, &st, &exp, &lu,
&md.usedCount)
+ if err != nil {
+ return err
+ }
+ md.stored.Val = st * 1000000
+ md.expires.Val = exp * 1000000
+ md.lastUsed.Val = lu * 1000000
+ // call process function
+ f(md)
+ }
+ return nil
+}
+
+// Close metadata database
+func (db *FileMetaDB) Close() error {
+ return db.conn.Close()
+}
diff --git a/src/gnunet/service/store/store_fs_meta.sql
b/src/gnunet/service/store/store_fs_meta.sql
new file mode 100644
index 0000000..a2692ab
--- /dev/null
+++ b/src/gnunet/service/store/store_fs_meta.sql
@@ -0,0 +1,29 @@
+-- This file is part of gnunet-go, a GNUnet-implementation in Golang.
+-- Copyright (C) 2019-2022 Bernd Fix >Y<
+--
+-- gnunet-go is free software: you can redistribute it and/or modify it
+-- under the terms of the GNU Affero General Public License as published
+-- by the Free Software Foundation, either version 3 of the License,
+-- or (at your option) any later version.
+--
+-- gnunet-go is distributed in the hope that it will be useful, but
+-- WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-- Affero General Public License for more details.
+--
+-- You should have received a copy of the GNU Affero General Public License
+-- along with this program. If not, see <http://www.gnu.org/licenses/>.
+--
+-- SPDX-License-Identifier: AGPL3.0-or-later
+
+create table meta (
+ qkey blob, -- key (SHA512 hash)
+ btype integer, -- block type
+ size integer, -- size of file
+ stored integer, -- time added to store
+ expires integer, -- expiration time
+ lastUsed integer, -- time last used
+ usedCount integer, -- usage count
+
+ unique(qkey,btype) -- unique key in database
+);
diff --git a/src/gnunet/test/gnunet-dhtu/main.go
b/src/gnunet/test/gnunet-dhtu/main.go
deleted file mode 100644
index 2a49b9c..0000000
--- a/src/gnunet/test/gnunet-dhtu/main.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "gnunet/config"
- "gnunet/core"
- "gnunet/message"
- "gnunet/service"
- "gnunet/service/dht"
- "gnunet/transport"
- "gnunet/util"
- "log"
- "net/rpc"
- "time"
-
- "github.com/bfix/gospel/logger"
-)
-
-//----------------------------------------------------------------------
-// Test Go node with DHTU GNUnet nodes
-//
-// N.B.: THIS TEST ONLY COVERS THE BASIC MESSAGE EXCHANGE LEVEL; NO
-// MESSAGE PROCESSING EXCEPT FOR HELLO MESSAGES WILL TAKE PLACE.
-//----------------------------------------------------------------------
-
-func main() {
- // handle command-line arguments
- var remoteAddr string
- var cfgFile string
- flag.StringVar(&cfgFile, "c", "gnunet-config.json", "configuration
file")
- flag.StringVar(&remoteAddr, "a", "", "address of remote node")
- flag.Parse()
-
- // read configuration file and set missing arguments.
- if err := config.ParseConfig(cfgFile); err != nil {
- logger.Printf(logger.ERROR, "[node] Invalid configuration file:
%s\n", err.Error())
- return
- }
-
- // convert arguments
- var rAddr *util.Address
- var err error
- if rAddr, err = util.ParseAddress(remoteAddr); err != nil {
- logger.Println(logger.ERROR, err.Error())
- return
- }
-
- // setup execution context
- ctx, cancel := context.WithCancel(context.Background())
- defer func() {
- cancel()
- time.Sleep(time.Second)
- }()
-
- // create and run node
- node, err := NewTestNode(ctx)
- if err != nil {
- logger.Println(logger.ERROR, err.Error())
- return
- }
- defer node.Shutdown()
-
- // show our HELLO URL
- ep := config.Cfg.Local.Endpoints[0]
- as := fmt.Sprintf("%s://%s:%d", ep.Network, ep.Address, ep.Port)
- listen, err := util.ParseAddress(as)
- if err != nil {
- logger.Println(logger.ERROR, err.Error())
- return
- }
- aList := []*util.Address{listen}
- logger.Println(logger.INFO, "[node] --> "+node.HelloURL(aList))
-
- // send HELLO to bootstrap address
- if err = node.SendHello(ctx, rAddr); err != nil && err !=
transport.ErrEndpMaybeSent {
- logger.Println(logger.ERROR, "[node] failed to send HELLO:
"+err.Error())
- return
- }
-
- // run forever
- var ch chan struct{}
- <-ch
-}
-
-//----------------------------------------------------------------------
-// create and run a node with given spec
-//----------------------------------------------------------------------
-
-type TestNode struct {
- id int
- peer *core.Peer
- core *core.Core
- addr *util.Address
-}
-
-func (n *TestNode) Shutdown() {
- n.core.Shutdown()
-}
-func (n *TestNode) HelloURL(a []*util.Address) string {
- hd, err := n.peer.HelloData(message.HelloAddressExpiration, a)
- if err != nil {
- return ""
- }
- return hd.URL()
-}
-
-func (n *TestNode) SendHello(ctx context.Context, addr *util.Address) error {
- return n.core.SendHello(ctx, addr)
-}
-
-func NewTestNode(ctx context.Context) (node *TestNode, err error) {
-
- // create test node
- node = new(TestNode)
- node.id = util.NextID()
-
- // create core service
- if node.core, err = core.NewCore(ctx, config.Cfg.Local); err != nil {
- return
- }
- node.peer = node.core.Peer()
- logger.Printf(logger.INFO, "[node] Node %s starting", node.peer.GetID())
-
- // start a new DHT service
- dht, err := dht.NewService(ctx, node.core)
- if err != nil {
- log.Fatal(err)
- }
-
- // start JSON-RPC server on request
- var rpc *rpc.Server
- if rpc, err = service.StartRPC(ctx, config.Cfg.RPC.Endpoint); err !=
nil {
- logger.Printf(logger.ERROR, "[node] RPC failed to start: %s",
err.Error())
- return
- }
- dht.InitRPC(rpc)
-
- // start listening on the network
- list, err := node.core.Addresses()
- if err != nil {
- log.Fatal(err)
- }
- for _, addr := range list {
- s := addr.Network() + "://" + addr.String()
- if node.addr, err = util.ParseAddress(s); err != nil {
- continue
- }
- logger.Printf(logger.INFO, "[node] Listening on %s", s)
- }
-
- // register as event listener
- incoming := make(chan *core.Event)
- node.core.Register(config.Cfg.Local.Name, core.NewListener(incoming,
nil))
-
- // heart beat
- tick := time.NewTicker(5 * time.Minute)
-
- // run event handler
- go func() {
- for {
- select {
- // show incoming event
- case ev := <-incoming:
- switch ev.ID {
- case core.EV_CONNECT:
- logger.Printf(logger.INFO, "[node] <<<
Peer %s connected", ev.Peer)
- case core.EV_DISCONNECT:
- logger.Printf(logger.INFO, "[node] <<<
Peer %s diconnected", ev.Peer)
- case core.EV_MESSAGE:
- logger.Printf(logger.INFO, "[node] <<<
Msg from %s of type %d", ev.Peer, ev.Msg.Header().MsgType)
- logger.Printf(logger.INFO, "[node] <<<
--> %s", ev.Msg.String())
- }
-
- // handle termination signal
- case <-ctx.Done():
- logger.Println(logger.INFO, "[node] Shutting
down node")
- return
-
- // handle heart beat
- case now := <-tick.C:
- logger.Printf(logger.INFO, "[node] Heart beat
at %s", now.String())
- }
- }
- }()
- return
-}
diff --git a/src/gnunet/transport/endpoint.go b/src/gnunet/transport/endpoint.go
index d98776a..a2f54d7 100644
--- a/src/gnunet/transport/endpoint.go
+++ b/src/gnunet/transport/endpoint.go
@@ -39,7 +39,7 @@ var (
ErrEndpExists = errors.New("endpoint exists")
ErrEndpNoAddress = errors.New("no address for endpoint")
ErrEndpNoConnection = errors.New("no connection on endpoint")
- ErrEndpMaybeSent = errors.New("message may have been sent - cant
know")
+ ErrEndpMaybeSent = errors.New("message may have been sent -
can't know")
ErrEndpWriteShort = errors.New("write too short")
)
@@ -48,10 +48,10 @@ var (
// remote endpoints for TCP and UDP traffic.
type Endpoint interface {
// Run the endpoint and send received messages to channel
- Run(context.Context, chan *TransportMessage) error
+ Run(context.Context, chan *Message) error
// Send message on endpoint
- Send(context.Context, net.Addr, *TransportMessage) error
+ Send(context.Context, net.Addr, *Message) error
// Address returns the listening address for the endpoint
Address() net.Addr
@@ -84,16 +84,17 @@ func NewEndpoint(addr net.Addr) (ep Endpoint, err error) {
// PacketEndpoint for packet-oriented network protocols
type PaketEndpoint struct {
+ sync.Mutex
+
id int // endpoint identifier
netw string // network identifier ("udp", "udp4", "udp6", ...)
addr net.Addr // endpoint address
conn net.PacketConn // packet connection
buf []byte // buffer for read/write operations
- mtx sync.Mutex // mutex for send operations
}
// Run packet endpoint: send incoming messages to the handler.
-func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *TransportMessage)
(err error) {
+func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *Message) (err
error) {
// create listener
var lc net.ListenConfig
xproto := ep.addr.Network()
@@ -144,7 +145,7 @@ func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan
*TransportMessage) (
}
// Read a transport message from endpoint based on extended protocol
-func (ep *PaketEndpoint) read() (tm *TransportMessage, err error) {
+func (ep *PaketEndpoint) read() (tm *Message, err error) {
// read next packet (assuming that it contains one complete message)
var n int
if n, _, err = ep.conn.ReadFrom(ep.buf); err != nil {
@@ -167,7 +168,7 @@ func (ep *PaketEndpoint) read() (tm *TransportMessage, err
error) {
panic(ErrEndpProtocolUnknown)
}
// return transport message
- return &TransportMessage{
+ return &Message{
Peer: peer,
Msg: msg,
Resp: nil,
@@ -176,10 +177,10 @@ func (ep *PaketEndpoint) read() (tm *TransportMessage,
err error) {
}
// Send message to address from endpoint
-func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) (err error) {
+func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg
*Message) (err error) {
// only one sender at a time
- ep.mtx.Lock()
- defer ep.mtx.Unlock()
+ ep.Lock()
+ defer ep.Unlock()
// check for valid connection
if ep.conn == nil {
@@ -284,7 +285,7 @@ type StreamEndpoint struct {
}
// Run packet endpoint: send incoming messages to the handler.
-func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan
*TransportMessage) (err error) {
+func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan *Message) (err
error) {
// create listener
var lc net.ListenConfig
xproto := ep.addr.Network()
@@ -331,7 +332,7 @@ func (ep *StreamEndpoint) Run(ctx context.Context, hdlr
chan *TransportMessage)
}
// Read a transport message from endpoint based on extended protocol
-func (ep *StreamEndpoint) read(ctx context.Context, conn net.Conn) (tm
*TransportMessage, err error) {
+func (ep *StreamEndpoint) read(ctx context.Context, conn net.Conn) (tm
*Message, err error) {
// parse transport message based on extended protocol
var (
peer *util.PeerID
@@ -341,7 +342,7 @@ func (ep *StreamEndpoint) read(ctx context.Context, conn
net.Conn) (tm *Transpor
case "ip+udp":
// parse peer id
peer = util.NewPeerID(nil)
- if _, err = conn.Read(peer.Key); err != nil {
+ if _, err = conn.Read(peer.Data); err != nil {
return
}
// read next message from connection
@@ -352,14 +353,14 @@ func (ep *StreamEndpoint) read(ctx context.Context, conn
net.Conn) (tm *Transpor
panic(ErrEndpProtocolUnknown)
}
// return transport message
- return &TransportMessage{
+ return &Message{
Peer: peer,
Msg: msg,
}, nil
}
// Send message to address from endpoint
-func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) error {
+func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg
*Message) error {
return nil
}
diff --git a/src/gnunet/transport/reader_writer.go
b/src/gnunet/transport/reader_writer.go
index e99b3df..13028fe 100644
--- a/src/gnunet/transport/reader_writer.go
+++ b/src/gnunet/transport/reader_writer.go
@@ -54,12 +54,8 @@ func WriteMessage(ctx context.Context, wrt io.WriteCloser,
msg message.Message)
}
// watch dog for write operation
go func() {
- for {
- select {
- case <-ctx.Done():
- wrt.Close()
- }
- }
+ <-ctx.Done()
+ wrt.Close()
}()
// perform write operation
var n int
@@ -86,46 +82,41 @@ func ReadMessageDirect(rdr io.Reader, buf []byte) (msg
message.Message, err erro
func ReadMessage(ctx context.Context, rdr io.ReadCloser, buf []byte) (msg
message.Message, err error) {
// watch dog for write operation
go func() {
- for {
- select {
- case <-ctx.Done():
- rdr.Close()
- }
- }
+ <-ctx.Done()
+ rdr.Close()
}()
// get bytes from reader
if buf == nil {
buf = make([]byte, 65536)
}
- get := func(pos, count int) error {
- n, err := rdr.Read(buf[pos : pos+count])
- if err == nil && n != count {
+ get := func(pos, count int) (err error) {
+ var n int
+ if n, err = rdr.Read(buf[pos : pos+count]); err == nil && n !=
count {
err = fmt.Errorf("not enough bytes on reader (%d of
%d)", n, count)
}
return err
}
// read header first
- if err := get(0, 4); err != nil {
- return nil, err
+ if err = get(0, 4); err != nil {
+ return
}
var mh *message.Header
if mh, err = message.GetMsgHeader(buf[:4]); err != nil {
- return nil, err
+ return
}
// get rest of message
if err = get(4, int(mh.MsgSize)-4); err != nil {
- return nil, err
+ return
}
if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
- return nil, err
+ return
}
if msg == nil {
- return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
- }
- if err = data.Unmarshal(msg, buf[:mh.MsgSize]); err != nil {
- return nil, err
+ err = fmt.Errorf("message{%d} is nil", mh.MsgType)
+ return
}
- return msg, nil
+ err = data.Unmarshal(msg, buf[:mh.MsgSize])
+ return
}
//----------------------------------------------------------------------
diff --git a/src/gnunet/transport/responder.go
b/src/gnunet/transport/responder.go
index f0d9d66..7032c78 100644
--- a/src/gnunet/transport/responder.go
+++ b/src/gnunet/transport/responder.go
@@ -32,6 +32,9 @@ import (
type Responder interface {
// Handle outgoing message
Send(ctx context.Context, msg message.Message) error
+
+ // Receiver returns the receiving peer (string representation)
+ Receiver() string
}
//----------------------------------------------------------------------
@@ -50,3 +53,8 @@ func (r *TransportResponder) Send(ctx context.Context, msg
message.Message) erro
}
return r.SendFcn(ctx, r.Peer, msg)
}
+
+// Receiver returns the receiving peer id
+func (r *TransportResponder) Receiver() string {
+ return r.Peer.String()
+}
diff --git a/src/gnunet/transport/transport.go
b/src/gnunet/transport/transport.go
index 2101849..bc4b632 100644
--- a/src/gnunet/transport/transport.go
+++ b/src/gnunet/transport/transport.go
@@ -32,17 +32,18 @@ import (
// Trnsport layer error codes
var (
ErrTransNoEndpoint = errors.New("no matching endpoint found")
+ ErrTransNoUPNP = errors.New("no UPnP available")
)
//======================================================================
// Network-oriented transport implementation
//======================================================================
-// TransportMessage is the unit processed by the transport mechanism.
+// Message is the unit processed by the transport mechanism.
// Peer refers to the remote endpoint (sender/receiver) and
// Msg is the exchanged GNUnet message. The packet itself satisfies the
// message.Message interface.
-type TransportMessage struct {
+type Message struct {
// Peer is a identifier for a remote peer
Peer *util.PeerID
@@ -62,10 +63,10 @@ type TransportMessage struct {
}
// Bytes returns the binary representation of a transport message
-func (msg *TransportMessage) Bytes() ([]byte, error) {
+func (msg *Message) Bytes() ([]byte, error) {
buf := new(bytes.Buffer)
// serialize peer id
- if _, err := buf.Write(msg.Peer.Key); err != nil {
+ if _, err := buf.Write(msg.Peer.Bytes()); err != nil {
return nil, err
}
// serialize message
@@ -74,16 +75,16 @@ func (msg *TransportMessage) Bytes() ([]byte, error) {
}
// String returns the message in human-readable form
-func (msg *TransportMessage) String() string {
+func (msg *Message) String() string {
return "TransportMessage{...}"
}
// NewTransportMessage creates a message suitable for transfer
-func NewTransportMessage(peer *util.PeerID, msg message.Message) (tm
*TransportMessage) {
+func NewTransportMessage(peer *util.PeerID, msg message.Message) (tm *Message)
{
if peer == nil {
peer = util.NewPeerID(nil)
}
- tm = &TransportMessage{
+ tm = &Message{
Peer: peer,
Msg: msg,
Resp: nil,
@@ -97,13 +98,13 @@ func NewTransportMessage(peer *util.PeerID, msg
message.Message) (tm *TransportM
// Transport enables network-oriented (like IP, UDP, TCP or UDS)
// message exchange on multiple endpoints.
type Transport struct {
- incoming chan *TransportMessage // messages as received from the
network
+ incoming chan *Message // messages as received from the
network
endpoints *util.Map[int, Endpoint] // list of available endpoints
upnp *network.PortMapper // UPnP mapper (optional)
}
// NewTransport creates and runs a new transport layer implementation.
-func NewTransport(ctx context.Context, tag string, ch chan *TransportMessage)
(t *Transport) {
+func NewTransport(ctx context.Context, tag string, ch chan *Message) (t
*Transport) {
// create transport instance
mngr, err := network.NewPortMapper(tag)
if err != nil {
@@ -124,7 +125,7 @@ func (t *Transport) Shutdown() {
}
// Send a message over suitable endpoint
-func (t *Transport) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) (err error) {
+func (t *Transport) Send(ctx context.Context, addr net.Addr, msg *Message)
(err error) {
// select best endpoint able to handle address
var bestEp Endpoint
err = t.endpoints.ProcessRange(func(_ int, ep Endpoint) error {
@@ -174,7 +175,7 @@ func (t *Transport) AddEndpoint(ctx context.Context, addr
*util.Address) (ep End
}
// add endpoint to list and run it
t.endpoints.Put(ep.ID(), ep)
- ep.Run(ctx, t.incoming)
+ err = ep.Run(ctx, t.incoming)
return
}
@@ -185,11 +186,20 @@ func (t *Transport) AddEndpoint(ctx context.Context, addr
*util.Address) (ep End
// ForwardOpen returns a local address for listening that will receive traffic
// from a port forward handled by UPnP on the router.
func (t *Transport) ForwardOpen(protocol, param string, port int) (id, local,
remote string, err error) {
+ // check for available UPnP
+ if t.upnp == nil {
+ err = ErrTransNoUPNP
+ return
+ }
// no parameters currently defined, so just do the assignment.
return t.upnp.Assign(protocol, port)
}
// ForwardClose closes a specific port forwarding
func (t *Transport) ForwardClose(id string) error {
+ // check for available UPnP
+ if t.upnp == nil {
+ return ErrTransNoUPNP
+ }
return t.upnp.Unassign(id)
}
diff --git a/src/gnunet/util/address.go b/src/gnunet/util/address.go
index 4cd07da..35e0030 100644
--- a/src/gnunet/util/address.go
+++ b/src/gnunet/util/address.go
@@ -27,10 +27,10 @@ import (
// Address specifies how a peer is reachable on the network.
type Address struct {
- Netw string `` // network protocol
- Options uint32 `order:"big"` // address options
- Expires AbsoluteTime `` // expiration date for address
- Address []byte `size:"*"` // address data (protocol-dependent)
+ Netw string // network protocol
+ Options uint32 // address options
+ Expires AbsoluteTime // expiration date for address
+ Address []byte // address data (protocol-dependent)
}
// NewAddress returns a new Address for the given transport and specs
@@ -43,6 +43,8 @@ func NewAddress(transport string, addr string) *Address {
}
}
+// NewAddressWrap returns new address from net.Addr with no options
+// or expiry date.
func NewAddressWrap(addr net.Addr) *Address {
return &Address{
Netw: addr.Network(),
@@ -53,7 +55,7 @@ func NewAddressWrap(addr net.Addr) *Address {
}
// ParseAddress translates a GNUnet address string like
-// "r5n+ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
+// "ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
// It can also handle standard strings like "udp:127.0.0.1:6735".
func ParseAddress(s string) (addr *Address, err error) {
p := strings.SplitN(s, ":", 2)
@@ -72,11 +74,6 @@ func (a *Address) Equals(b *Address) bool {
bytes.Equal(a.Address, b.Address)
}
-// StringAll returns a human-readable representation of an address.
-func (a *Address) StringAll() string {
- return a.Netw + "://" + string(a.Address)
-}
-
// implement net.Addr interface methods:
// String returns a human-readable representation of an address.
@@ -91,7 +88,7 @@ func (a *Address) Network() string {
//----------------------------------------------------------------------
-// URI returns a string representaion of an address.
+// URI returns a string representation of an address.
func (a *Address) URI() string {
return URI(a.Netw, a.Address)
}
@@ -101,24 +98,6 @@ func URI(network string, addr []byte) string {
//----------------------------------------------------------------------
-// IPAddress (can be IPv4 or IPv6 or a DNS name)
-type IPAddress struct {
- Host []byte `size:"*-2"`
- Port uint16 `order:"big"`
-}
-
-// NewIPAddress creates a new instance for a given host and port.
-func NewIPAddress(host []byte, port uint16) *IPAddress {
- ip := &IPAddress{
- Host: make([]byte, len(host)),
- Port: port,
- }
- copy(ip.Host, host)
- return ip
-}
-
-//----------------------------------------------------------------------
-
// PeerAddrList is a list of addresses per peer ID.
type PeerAddrList struct {
list *Map[string, []*Address]
@@ -133,12 +112,13 @@ func NewPeerAddrList() *PeerAddrList {
// Add address for peer. The returned mode is 0=not added, 1=new peer,
// 2=new address
-func (a *PeerAddrList) Add(id string, addr *Address) (mode int) {
+func (a *PeerAddrList) Add(peer *PeerID, addr *Address) (mode int) {
// check for expired address.
mode = 0
if !addr.Expires.Expired() {
// run add operation
- a.list.Process(func() error {
+ _ = a.list.Process(func() error {
+ id := peer.String()
list, ok := a.list.Get(id)
if !ok {
list = make([]*Address, 0)
@@ -160,7 +140,8 @@ func (a *PeerAddrList) Add(id string, addr *Address) (mode
int) {
}
// Get address for peer
-func (a *PeerAddrList) Get(id string, transport string) (res []*Address) {
+func (a *PeerAddrList) Get(peer *PeerID, transport string) (res []*Address) {
+ id := peer.String()
list, ok := a.list.Get(id)
if ok {
for _, addr := range list {
@@ -181,6 +162,13 @@ func (a *PeerAddrList) Get(id string, transport string)
(res []*Address) {
}
// Delete a list entry by key.
-func (a *PeerAddrList) Delete(id string) {
- a.list.Delete(id)
+func (a *PeerAddrList) Delete(peer *PeerID) {
+ a.list.Delete(peer.String())
+}
+
+// Contains checks if a peer is contained in the list. Does not check
+// for expired entries.
+func (a *PeerAddrList) Contains(peer *PeerID) (ok bool) {
+ _, ok = a.list.Get(peer.String())
+ return
}
diff --git a/src/gnunet/util/address_test.go b/src/gnunet/util/address_test.go
index d4936e8..1222124 100644
--- a/src/gnunet/util/address_test.go
+++ b/src/gnunet/util/address_test.go
@@ -37,17 +37,24 @@ func TestAddrList(t *testing.T) {
t.Fatal(err)
}
}
+ // test peer
+ peer := NewPeerID(nil)
// allocate AddrList
addrL := NewPeerAddrList()
for _, addr := range addrA {
- rc :=
addrL.Add("2BHV4BN8736W5W3CJNXY2S9WABWTGH35QMFG4BPCWBH7DNBCFC60", addr)
+ rc := addrL.Add(peer, addr)
t.Logf("added %s (%d)", addr.URI(), rc)
}
// check list
t.Log("checking list...")
- list :=
addrL.Get("2BHV4BN8736W5W3CJNXY2S9WABWTGH35QMFG4BPCWBH7DNBCFC60", "ip+udp")
- t.Logf("got: %v", list)
+ list := addrL.Get(peer, "ip+udp")
+ for i, addr := range list {
+ t.Logf("got: %s", addr.URI())
+ if addr != addrA[i] {
+ t.Errorf("address mismatch at index %d", i)
+ }
+ }
if len(list) != len(addrS) {
t.Fatal("list size not matching")
}
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 99c74d9..954521c 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -33,6 +33,11 @@ var (
// Clone creates a new array of same content as the argument.
func Clone[T []E, E any](d T) T {
+ // handle nil slices
+ if d == nil {
+ return nil
+ }
+ // create copy
r := make(T, len(d))
copy(r, d)
return r
diff --git a/src/gnunet/util/base32.go b/src/gnunet/util/base32.go
index f0b149a..e9ca494 100644
--- a/src/gnunet/util/base32.go
+++ b/src/gnunet/util/base32.go
@@ -46,9 +46,9 @@ const xlate = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
var (
// ErrInvalidEncoding signals an invalid encoding
- ErrInvalidEncoding = errors.New("Invalid encoding")
+ ErrInvalidEncoding = errors.New("invalid encoding")
// ErrBufferTooSmall signalsa too small buffer for decoding
- ErrBufferTooSmall = errors.New("Buffer to small")
+ ErrBufferTooSmall = errors.New("buffer to small")
)
// EncodeBinaryToString encodes a byte array into a string.
diff --git a/src/gnunet/util/base32_test.go b/src/gnunet/util/base32_test.go
index 2ec7231..32ea2de 100644
--- a/src/gnunet/util/base32_test.go
+++ b/src/gnunet/util/base32_test.go
@@ -73,7 +73,7 @@ func TestBase32Preset(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if bytes.Compare(x.bin, e) != 0 {
+ if !bytes.Equal(x.bin, e) {
t.Fatalf("Decoding mismatch: '%s' != '%s' for '%s'\n",
hex.EncodeToString(e), hex.EncodeToString(x.bin), x.str)
}
}
diff --git a/src/gnunet/util/fs.go b/src/gnunet/util/fs.go
index 3df641c..00ffa28 100644
--- a/src/gnunet/util/fs.go
+++ b/src/gnunet/util/fs.go
@@ -37,7 +37,7 @@ func EnforceDirExists(path string) error {
return err
}
if !fi.IsDir() {
- return fmt.Errorf("Not a directory (%s)", path)
+ return fmt.Errorf("not a directory (%s)", path)
}
return nil
}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/map.go
similarity index 73%
copy from src/gnunet/util/misc.go
copy to src/gnunet/util/map.go
index 2ee8f1d..adfcc0e 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/map.go
@@ -19,46 +19,19 @@
package util
import (
- "strings"
+ "math/rand"
"sync"
)
-//----------------------------------------------------------------------
-// Count occurence of multiple instance at the same time.
-//----------------------------------------------------------------------
-
-// Counter is a metric with single key
-type Counter[T comparable] map[T]int
-
-// Add one to themetric for a given key and return current value
-func (cm Counter[T]) Add(i T) int {
- count, ok := cm[i]
- if !ok {
- count = 1
- } else {
- count++
- }
- cm[i] = count
- return count
-}
-
-// Num returns the metric for a given key
-func (cm Counter[T]) Num(i T) int {
- count, ok := cm[i]
- if !ok {
- count = 0
- }
- return count
-}
-
//----------------------------------------------------------------------
// Thread-safe map implementation
//----------------------------------------------------------------------
// Map keys to values
type Map[K comparable, V any] struct {
+ sync.RWMutex
+
list map[K]V
- mtx sync.RWMutex
inProcess bool
}
@@ -107,6 +80,11 @@ func (m *Map[K, V]) ProcessRange(f func(key K, value V)
error, readonly bool) er
//----------------------------------------------------------------------
+// Size returns the number of entries in the map.
+func (m *Map[K, V]) Size() int {
+ return len(m.list)
+}
+
// Put value into map under given key.
func (m *Map[K, V]) Put(key K, value V) {
m.lock(false)
@@ -122,6 +100,25 @@ func (m *Map[K, V]) Get(key K) (value V, ok bool) {
return
}
+// GetRandom returns a random map entry.
+func (m *Map[K, V]) GetRandom() (key K, value V, ok bool) {
+ m.lock(true)
+ defer m.unlock(true)
+
+ ok = false
+ if size := m.Size(); size > 0 {
+ idx := rand.Intn(size)
+ for key, value = range m.list {
+ if idx == 0 {
+ ok = true
+ return
+ }
+ idx--
+ }
+ }
+ return
+}
+
// Delete key/value pair from map.
func (m *Map[K, V]) Delete(key K) {
m.lock(false)
@@ -135,9 +132,9 @@ func (m *Map[K, V]) Delete(key K) {
func (m *Map[K, V]) lock(readonly bool) {
if !m.inProcess {
if readonly {
- m.mtx.RLock()
+ m.RLock()
} else {
- m.mtx.Lock()
+ m.Lock()
}
}
}
@@ -146,22 +143,9 @@ func (m *Map[K, V]) lock(readonly bool) {
func (m *Map[K, V]) unlock(readonly bool) {
if !m.inProcess {
if readonly {
- m.mtx.RUnlock()
+ m.RUnlock()
} else {
- m.mtx.Unlock()
+ m.Unlock()
}
}
}
-
-//----------------------------------------------------------------------
-// additional helpers
-//----------------------------------------------------------------------
-
-// StripPathRight returns a dot-separated path without
-// its last (right-most) element.
-func StripPathRight(s string) string {
- if idx := strings.LastIndex(s, "."); idx != -1 {
- return s[:idx]
- }
- return s
-}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index 2ee8f1d..1768a96 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -20,11 +20,10 @@ package util
import (
"strings"
- "sync"
)
//----------------------------------------------------------------------
-// Count occurence of multiple instance at the same time.
+// Count occurrence of multiple instance at the same time.
//----------------------------------------------------------------------
// Counter is a metric with single key
@@ -52,107 +51,23 @@ func (cm Counter[T]) Num(i T) int {
}
//----------------------------------------------------------------------
-// Thread-safe map implementation
+// Parameter set with string keys and variable value types
//----------------------------------------------------------------------
-// Map keys to values
-type Map[K comparable, V any] struct {
- list map[K]V
- mtx sync.RWMutex
- inProcess bool
-}
-
-// NewMap allocates a new mapping.
-func NewMap[K comparable, V any]() *Map[K, V] {
- return &Map[K, V]{
- list: make(map[K]V),
- inProcess: false,
- }
-}
+// ParameterSet with string keys and variable value types
+type ParameterSet map[string]any
-//----------------------------------------------------------------------
-
-// Process a function in the locked map context. Calls
-// to other map functions in 'f' will skip their locks.
-func (m *Map[K, V]) Process(f func() error, readonly bool) error {
- // handle locking
- m.lock(readonly)
- m.inProcess = true
- defer func() {
- m.inProcess = false
- m.unlock(readonly)
- }()
- // function call in unlocked environment
- return f()
-}
-
-// Process a ranged function in the locked map context. Calls
-// to other map functions in 'f' will skip their locks.
-func (m *Map[K, V]) ProcessRange(f func(key K, value V) error, readonly bool)
error {
- // handle locking
- m.lock(readonly)
- m.inProcess = true
- defer func() {
- m.inProcess = false
- m.unlock(readonly)
- }()
- // range over map and call function.
- for key, value := range m.list {
- if err := f(key, value); err != nil {
- return err
+// Get a parameter value with given type 'V'
+func GetParam[V any](params ParameterSet, key string) (i V, ok bool) {
+ var v any
+ if v, ok = params[key]; ok {
+ if i, ok = v.(V); ok {
+ return
}
}
- return nil
-}
-
-//----------------------------------------------------------------------
-
-// Put value into map under given key.
-func (m *Map[K, V]) Put(key K, value V) {
- m.lock(false)
- defer m.unlock(false)
- m.list[key] = value
-}
-
-// Get value with iven key from map.
-func (m *Map[K, V]) Get(key K) (value V, ok bool) {
- m.lock(true)
- defer m.unlock(true)
- value, ok = m.list[key]
return
}
-// Delete key/value pair from map.
-func (m *Map[K, V]) Delete(key K) {
- m.lock(false)
- defer m.unlock(false)
- delete(m.list, key)
-}
-
-//----------------------------------------------------------------------
-
-// lock with given mode (if not in processing function)
-func (m *Map[K, V]) lock(readonly bool) {
- if !m.inProcess {
- if readonly {
- m.mtx.RLock()
- } else {
- m.mtx.Lock()
- }
- }
-}
-
-// lock with given mode (if not in processing function)
-func (m *Map[K, V]) unlock(readonly bool) {
- if !m.inProcess {
- if readonly {
- m.mtx.RUnlock()
- } else {
- m.mtx.Unlock()
- }
- }
-}
-
//----------------------------------------------------------------------
// additional helpers
//----------------------------------------------------------------------
diff --git a/src/gnunet/util/peer.go b/src/gnunet/util/peer.go
new file mode 100644
index 0000000..f880040
--- /dev/null
+++ b/src/gnunet/util/peer.go
@@ -0,0 +1,94 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package util
+
+import (
+ "bytes"
+)
+
+//----------------------------------------------------------------------
+// Peer public key (Ed25519 public key)
+//----------------------------------------------------------------------
+
+// PeerPublicKey is the binary representation of an Ed25519 public key
+type PeerPublicKey struct {
+ Data []byte `size:"32"` // Ed25519 public key data
+}
+
+// NewPeerPublicKey creates a key instance from binary data
+func NewPeerPublicKey(data []byte) *PeerPublicKey {
+ pk := &PeerPublicKey{
+ Data: make([]byte, 32),
+ }
+ if data != nil {
+ if len(data) < 32 {
+ CopyAlignedBlock(pk.Data, data)
+ } else {
+ copy(pk.Data, data[:32])
+ }
+ }
+ return pk
+}
+
+//----------------------------------------------------------------------
+// Peer identifier:
+//----------------------------------------------------------------------
+
+// PeerID is a wrpped PeerPublicKey
+type PeerID PeerPublicKey
+
+// NewPeerID creates a new peer id from data.
+func NewPeerID(data []byte) (p *PeerID) {
+ return (*PeerID)(NewPeerPublicKey(data))
+}
+
+// Equals returns true if two peer IDs match.
+func (p *PeerID) Equals(q *PeerID) bool {
+ return bytes.Equal(p.Data, q.Data)
+}
+
+// String returns a human-readable representation of a peer id.
+func (p *PeerID) String() string {
+ return EncodeBinaryToString(p.Data)
+}
+
+// Bytes returns the binary representation of a peer identifier.
+func (p *PeerID) Bytes() []byte {
+ return Clone(p.Data)
+}
+
+//----------------------------------------------------------------------
+
+// PeerSignature is a EdDSA signature from the peer
+type PeerSignature struct {
+ Data []byte `size:"64"`
+}
+
+// NewPeerSignature is a EdDSA signatre with the private peer key
+func NewPeerSignature(data []byte) *PeerSignature {
+ var v []byte
+ if data == nil {
+ v = make([]byte, 64)
+ } else {
+ v = Clone(data)
+ }
+ return &PeerSignature{
+ Data: v,
+ }
+}
diff --git a/src/gnunet/util/peer_id.go b/src/gnunet/util/peer_id.go
deleted file mode 100644
index 384f46c..0000000
--- a/src/gnunet/util/peer_id.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019-2022 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package util
-
-import (
- "bytes"
-
- "github.com/bfix/gospel/crypto/ed25519"
-)
-
-// PeerID is the 32-byte binary representation od a Ed25519 key
-type PeerID struct {
- Key []byte `size:"32"`
-}
-
-// NewPeerID creates a new peer id from data.
-func NewPeerID(data []byte) (p *PeerID) {
- p = &PeerID{
- Key: make([]byte, 32),
- }
- if data != nil {
- if len(data) < 32 {
- CopyAlignedBlock(p.Key, data)
- } else {
- copy(p.Key, data[:32])
- }
- }
- return
-}
-
-// Equals returns true if two peer IDs match.
-func (p *PeerID) Equals(q *PeerID) bool {
- return bytes.Equal(p.Key, q.Key)
-}
-
-// String returns a human-readable representation of a peer id.
-func (p *PeerID) String() string {
- return EncodeBinaryToString(p.Key)
-}
-
-func (p *PeerID) PublicKey() *ed25519.PublicKey {
- return ed25519.NewPublicKeyFromBytes(p.Key)
-}
diff --git a/src/gnunet/util/rnd.go b/src/gnunet/util/rnd.go
index c01f331..5bf6bd0 100644
--- a/src/gnunet/util/rnd.go
+++ b/src/gnunet/util/rnd.go
@@ -22,17 +22,21 @@ import (
"bytes"
"crypto/rand"
"encoding/binary"
+
+ "github.com/bfix/gospel/logger"
)
// RndArray fills a buffer with random content
func RndArray(b []byte) {
- rand.Read(b)
+ if _, err := rand.Read(b); err != nil {
+ logger.Printf(logger.ERROR, "[RndArray] failed: %s",
err.Error())
+ }
}
// NewRndArray creates a new buffer of given size; filled with random content.
func NewRndArray(size int) []byte {
b := make([]byte, size)
- rand.Read(b)
+ RndArray(b)
return b
}
@@ -42,7 +46,9 @@ func RndUInt64() uint64 {
RndArray(b)
var v uint64
c := bytes.NewBuffer(b)
- binary.Read(c, binary.BigEndian, &v)
+ if err := binary.Read(c, binary.BigEndian, &v); err != nil {
+ logger.Printf(logger.ERROR, "[RndUInt64] failed: %s",
err.Error())
+ }
return v
}
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index 9a9d365..6e98514 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -46,7 +46,7 @@ func NewAbsoluteTime(t time.Time) AbsoluteTime {
// NewAbsoluteTimeEpoch set the point in time to the given time value
func NewAbsoluteTimeEpoch(secs uint64) AbsoluteTime {
return AbsoluteTime{
- Val: uint64(secs * 1000000),
+ Val: secs * 1000000,
}
}
@@ -137,8 +137,9 @@ func (t AbsoluteTime) Compare(t2 AbsoluteTime) int {
// Relative time
//----------------------------------------------------------------------
-// RelativeTime is a timestamp defined relative to the current time.
-// It actually is more like a duration than a time...
+// RelativeTime is a timestamp defined relative to an AbsoluteTime.
+// It is measured in microseconds and is actually more like a duration
+// than a time...
type RelativeTime struct {
Val uint64 `order:"big"`
}
@@ -146,7 +147,7 @@ type RelativeTime struct {
// NewRelativeTime is initialized with a given duration.
func NewRelativeTime(d time.Duration) RelativeTime {
return RelativeTime{
- Val: uint64(d.Milliseconds()),
+ Val: uint64(d.Microseconds()),
}
}
@@ -155,12 +156,14 @@ func (t RelativeTime) String() string {
if t.Val == math.MaxUint64 {
return "Forever"
}
- return time.Duration(t.Val * 1000).String()
+ return time.Duration(t.Val * 1000000).String()
}
// Add two durations
-func (t RelativeTime) Add(t2 RelativeTime) {
- t.Val += t2.Val
+func (t RelativeTime) Add(t2 RelativeTime) RelativeTime {
+ return RelativeTime{
+ Val: t.Val + t2.Val,
+ }
}
// Compare two durations
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnunet-go] branch master updated: Improved handling of pending DHT results.,
gnunet <=