[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnunet-go] branch master updated: Initial revision of Zonemaster implem
From: |
gnunet |
Subject: |
[gnunet-go] branch master updated: Initial revision of Zonemaster implementation. |
Date: |
Sat, 22 Oct 2022 16:59:03 +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 6ed1dfa Initial revision of Zonemaster implementation.
6ed1dfa is described below
commit 6ed1dfac9e0ac7a24114198ae8a364388cb55528
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Sat Oct 22 16:57:16 2022 +0200
Initial revision of Zonemaster implementation.
---
README.md | 11 +-
src/gnunet/build.sh | 7 +-
src/gnunet/cmd/gnunet-service-gns-go/main.go | 13 +-
.../main.go | 83 ++-
src/gnunet/config/config.go | 56 +-
src/gnunet/config/gnunet-config.json | 28 +-
src/gnunet/crypto/gns.go | 126 ++--
src/gnunet/crypto/gns_edkey.go | 28 +-
src/gnunet/crypto/gns_edkey_test.go | 56 ++
src/gnunet/crypto/gns_pkey.go | 15 +-
src/gnunet/crypto/gns_pkey_test.go | 48 ++
src/gnunet/crypto/gns_test.go | 35 +-
src/gnunet/enums/gns.go | 17 +-
src/gnunet/enums/gns_type.go | 2 +-
src/gnunet/enums/gnunet-gns.tpl | 2 +-
src/gnunet/go.mod | 4 +-
src/gnunet/go.sum | 4 +-
src/gnunet/message/factory.go | 22 +-
src/gnunet/message/msg_dht_p2p.go | 24 +-
src/gnunet/message/msg_gns.go | 81 +--
src/gnunet/message/msg_namecache.go | 97 +--
src/gnunet/message/msg_namestore.go | 199 ++++++
src/gnunet/service/client.go | 13 +-
src/gnunet/service/dht/blocks/gns.go | 108 +++-
.../dht/blocks/gns_test.go} | 99 ++-
src/gnunet/service/dht/module.go | 17 +-
src/gnunet/service/gns/block_handler.go | 134 +++--
src/gnunet/service/gns/box.go | 169 ------
src/gnunet/service/gns/dns.go | 16 +-
src/gnunet/service/gns/module.go | 45 +-
src/gnunet/service/gns/rr/coexist.go | 146 +++++
src/gnunet/service/gns/rr/dns.go | 119 ++++
src/gnunet/service/gns/rr/gns.go | 199 ++++++
src/gnunet/service/gns/rr/gns_box.go | 375 ++++++++++++
src/gnunet/service/gns/service.go | 12 +-
src/gnunet/service/revocation/pow_test.go | 2 +-
src/gnunet/service/store/store_dht_meta.go | 24 +-
src/gnunet/service/store/store_zonemaster.go | 548 +++++++++++++++++
src/gnunet/service/store/store_zonemaster.sql | 47 ++
src/gnunet/service/store/store_zonemaster_test.go | 105 ++++
src/gnunet/service/zonemaster/gui.go | 666 +++++++++++++++++++++
src/gnunet/service/zonemaster/gui.htpl | 133 ++++
src/gnunet/service/zonemaster/gui_css.htpl | 253 ++++++++
src/gnunet/service/zonemaster/gui_debug.htpl | 14 +
src/gnunet/service/zonemaster/gui_edit.htpl | 130 ++++
src/gnunet/service/zonemaster/gui_new.htpl | 114 ++++
src/gnunet/service/zonemaster/gui_rr.htpl | 420 +++++++++++++
src/gnunet/service/zonemaster/module.go | 84 +++
src/gnunet/service/zonemaster/records.go | 348 +++++++++++
src/gnunet/service/zonemaster/rpc.go | 24 +
src/gnunet/service/zonemaster/service.go | 150 +++++
src/gnunet/service/zonemaster/zonemaster.go | 179 ++++++
src/gnunet/util/array.go | 20 +-
src/gnunet/util/misc.go | 16 +
src/gnunet/util/time.go | 5 +
55 files changed, 5109 insertions(+), 583 deletions(-)
diff --git a/README.md b/README.md
index 79825da..bbaced7 100644
--- a/README.md
+++ b/README.md
@@ -213,13 +213,16 @@ The above configuration will expect a network of 10 nodes
and has a single
bootstrap node (the first DHTU node in the testbed). `gnunet-go` will listen
on port 2086.
+Check the configuration for path definitions (especially
`/var/libg/gnunet/...`)
+and make sure the folders exist and have R/W permissions for the user running
+the test.
+
#### Running the `gnunet-go`node
Run the following commands to start the `gnunet-go` node:
```bash
-rm -rf /tmp/gnunet-system-runtime
-mkdir -p /tmp/gnunet-system-runtime
+rm -f /tmp/gnunet-system-runtime/*-go.sock
${GOPATH}/bin/gnunet-service-dht-go -c dhtu-config.json 2>&1 | tee run.log
```
@@ -389,9 +392,9 @@ func main() {
* edit `go.mod` and add at end of file:
```bash
-require gnunet v0.1.27
+require gnunet v0.1.34
-replace gnunet v0.1.27 => /home/user/gnunet-go/src/gnunet
+replace gnunet v0.1.34 => /home/user/gnunet-go/src/gnunet
```
* run `go mod tidy`
diff --git a/src/gnunet/build.sh b/src/gnunet/build.sh
index 20f6933..03899f7 100755
--- a/src/gnunet/build.sh
+++ b/src/gnunet/build.sh
@@ -1,4 +1,7 @@
#!/bin/bash
-[ "$1" = "withgen" ] && go generate ./...
-go install -v -gcflags "-N -l" ./...
+if [ "$1" = "withgen" ]; then
+ go generate ./...
+ shift
+fi
+go install $* -gcflags "-N -l" ./...
diff --git a/src/gnunet/cmd/gnunet-service-gns-go/main.go
b/src/gnunet/cmd/gnunet-service-gns-go/main.go
index ebf2151..2a4cae8 100644
--- a/src/gnunet/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-gns-go/main.go
@@ -28,7 +28,6 @@ import (
"time"
"gnunet/config"
- "gnunet/core"
"gnunet/service"
"gnunet/service/gns"
@@ -80,17 +79,9 @@ func main() {
params = config.Cfg.GNS.Service.Params
}
- // instantiate core service
- ctx, cancel := context.WithCancel(context.Background())
- var c *core.Core
- if c, err = core.NewCore(ctx, config.Cfg.Local); err != nil {
- logger.Printf(logger.ERROR, "[gns] core failed: %s\n",
err.Error())
- return
- }
- defer c.Shutdown()
-
// start a new GNS service
- gns := gns.NewService(ctx, c)
+ ctx, cancel := context.WithCancel(context.Background())
+ gns := gns.NewService(ctx, nil)
srv := service.NewSocketHandler("gns", gns)
if err = srv.Start(ctx, socket, params); err != nil {
logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
diff --git a/src/gnunet/cmd/gnunet-service-gns-go/main.go
b/src/gnunet/cmd/zonemaster-go/main.go
similarity index 57%
copy from src/gnunet/cmd/gnunet-service-gns-go/main.go
copy to src/gnunet/cmd/zonemaster-go/main.go
index ebf2151..1e4b985 100644
--- a/src/gnunet/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/zonemaster-go/main.go
@@ -28,80 +28,76 @@ import (
"time"
"gnunet/config"
- "gnunet/core"
"gnunet/service"
- "gnunet/service/gns"
+ "gnunet/service/zonemaster"
"github.com/bfix/gospel/logger"
)
func main() {
defer func() {
- logger.Println(logger.INFO, "[gns] Bye.")
+ logger.Println(logger.INFO, "[zonemaster] Bye.")
// flush last messages
logger.Flush()
}()
- logger.Println(logger.INFO, "[gns] Starting service...")
+ // intro
+ logger.SetLogLevel(logger.DBG)
+ logger.Println(logger.INFO, "[zonemaster] Starting service...")
var (
cfgFile string
- socket string
- param string
+ gui string
err error
logLevel int
rpcEndp string
)
// handle command line arguments
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, "GNS log level (default:
INFO)")
+ flag.StringVar(&gui, "g", "", "GUI listen address")
+ flag.IntVar(&logLevel, "L", logger.INFO, "zonemaster log level
(default: INFO)")
flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
flag.Parse()
// read configuration file and set missing arguments.
if err = config.ParseConfig(cfgFile); err != nil {
- logger.Printf(logger.ERROR, "[gns] Invalid configuration file:
%s\n", err.Error())
+ logger.Printf(logger.ERROR, "[zonemaster] Invalid configuration
file: %s\n", err.Error())
return
}
- // apply configuration (from file and command-line)
- logger.SetLogLevel(logLevel)
- if len(socket) == 0 {
- socket = config.Cfg.GNS.Service.Socket
+ // apply configuration
+ if config.Cfg.Logging.Level > 0 {
+ logLevel = config.Cfg.Logging.Level
}
- params := make(map[string]string)
- if len(param) == 0 {
- for _, p := range strings.Split(param, ",") {
- kv := strings.SplitN(p, "=", 2)
- params[kv[0]] = kv[1]
- }
- } else {
- params = config.Cfg.GNS.Service.Params
+ logger.SetLogLevel(logLevel)
+ if len(gui) > 0 {
+ config.Cfg.ZoneMaster.GUI = gui
}
- // instantiate core service
+ // start a new namestore service under zonemaster umbrella
ctx, cancel := context.WithCancel(context.Background())
- var c *core.Core
- if c, err = core.NewCore(ctx, config.Cfg.Local); err != nil {
- logger.Printf(logger.ERROR, "[gns] core failed: %s\n",
err.Error())
+ srv, ok := zonemaster.NewService(ctx, nil).(*zonemaster.Service)
+ if !ok {
+ logger.Println(logger.ERROR, "[zonemaster] Failed to create
service")
return
}
- defer c.Shutdown()
-
- // start a new GNS service
- gns := gns.NewService(ctx, c)
- srv := service.NewSocketHandler("gns", gns)
- if err = srv.Start(ctx, socket, params); err != nil {
- logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
- return
+ // start UDS listener if service is specified
+ if config.Cfg.ZoneMaster.Service != nil {
+ sockHdlr := service.NewSocketHandler("zonemaster", srv)
+ if err = sockHdlr.Start(ctx,
config.Cfg.ZoneMaster.Service.Socket, config.Cfg.ZoneMaster.Service.Params);
err != nil {
+ logger.Printf(logger.ERROR, "[zonemaster] Error: '%s'",
err.Error())
+ return
+ }
}
+ // start a new ZONEMASTER (background service with HTTPS backend)
+ zm := zonemaster.NewZoneMaster(config.Cfg, srv)
+ go zm.Run(ctx)
+
// handle command-line arguments for RPC
if len(rpcEndp) > 0 {
parts := strings.Split(rpcEndp, ":")
if parts[0] != "tcp" {
- logger.Println(logger.ERROR, "[gns] RPC must have a
TCP/IP endpoint")
+ logger.Println(logger.ERROR, "[zonemaster] RPC must
have a TCP/IP endpoint")
return
}
config.Cfg.RPC.Endpoint = parts[1]
@@ -110,12 +106,11 @@ func main() {
if ep := config.Cfg.RPC.Endpoint; len(ep) > 0 {
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())
+ logger.Printf(logger.ERROR, "[zonemaster] RPC failed to
start: %s", err.Error())
return
}
- gns.InitRPC(rpc)
+ srv.InitRPC(rpc)
}
-
// handle OS signals
sigCh := make(chan os.Signal, 5)
signal.Notify(sigCh)
@@ -130,24 +125,20 @@ loop:
case sig := <-sigCh:
switch sig {
case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
- logger.Printf(logger.INFO, "[gns] Terminating
service (on signal '%s')\n", sig)
+ logger.Printf(logger.INFO, "[zonemaster]
Terminating service (on signal '%s')\n", sig)
break loop
case syscall.SIGHUP:
- logger.Println(logger.INFO, "[gns] SIGHUP")
+ logger.Println(logger.INFO, "[zonemaster]
SIGHUP")
case syscall.SIGURG:
// TODO:
https://github.com/golang/go/issues/37942
default:
- logger.Println(logger.INFO, "[gns] Unhandled
signal: "+sig.String())
+ logger.Println(logger.INFO, "[zonemaster]
Unhandled signal: "+sig.String())
}
// handle heart beat
case now := <-tick.C:
- logger.Println(logger.INFO, "[gns] Heart beat at
"+now.String())
+ logger.Println(logger.INFO, "[zonemaster] Heart beat at
"+now.String())
}
}
-
// terminating service
cancel()
- if err = srv.Stop(); err != nil {
- logger.Printf(logger.ERROR, "[gns] Failed to stop service: %s",
err.Error())
- }
}
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 526b7b8..05f8cc9 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -95,6 +95,14 @@ type GNSConfig struct {
MaxDepth int `json:"maxDepth"` // maximum recursion depth
in resolution
}
+// ZoneMasterConfig contains parameters for the GNS ZoneMaster process
+type ZoneMasterConfig struct {
+ Service *ServiceConfig `json:"service"` // socket for NameStore
service
+ Period int `json:"period"` // cycle period
+ Storage util.ParameterSet `json:"storage"` // persistence mechanism for
zone data
+ GUI string `json:"gui"` // listen address for HTTP
GUI
+}
+
//----------------------------------------------------------------------
// DHT configuration
//----------------------------------------------------------------------
@@ -159,6 +167,7 @@ type Config struct {
DHT *DHTConfig `json:"dht"`
GNS *GNSConfig `json:"gns"`
Namecache *NamecacheConfig `json:"namecache"`
+ ZoneMaster *ZoneMasterConfig `json:"zonemaster"`
Revocation *RevocationConfig `json:"revocation"`
Logging *LoggingConfig `json:"logging"`
}
@@ -200,14 +209,19 @@ var (
// substString is a helper function to substitute environment variables
// with actual values.
func substString(s string, env map[string]string) string {
- matches := rx.FindAllStringSubmatch(s, -1)
- for _, m := range matches {
- if len(m[1]) != 0 {
- subst, ok := env[m[1]]
- if !ok {
- continue
+ changed := true
+ for changed {
+ changed = false
+ matches := rx.FindAllStringSubmatch(s, -1)
+ for _, m := range matches {
+ if len(m[1]) != 0 {
+ subst, ok := env[m[1]]
+ if !ok {
+ continue
+ }
+ s = strings.Replace(s, "${"+m[1]+"}", subst, -1)
+ changed = true
}
- s = strings.Replace(s, "${"+m[1]+"}", subst, -1)
}
}
return s
@@ -225,14 +239,26 @@ func applySubstitutions(x interface{}, env
map[string]string) {
case reflect.String:
// check for substitution
if s, ok := fld.Interface().(string);
ok {
- for {
- s1 := substString(s,
env)
- if s1 == s {
- break
+ sOut := substString(s, env)
+ if sOut != s {
+
logger.Printf(logger.DBG, "[config] %s --> %s\n", s, sOut)
+ fld.SetString(sOut)
+ }
+ }
+
+ case reflect.Map:
+ // substitute values
+ if s, ok :=
fld.Interface().(util.ParameterSet); ok {
+ for k, v := range s {
+ v1, ok := v.(string)
+ if !ok {
+ continue
+ }
+ sOut := substString(v1,
env)
+ if sOut != v1 {
+
logger.Printf(logger.DBG, "[config] %s --> %s\n", v1, sOut)
+ s[k] = sOut
}
-
logger.Printf(logger.DBG, "[config] %s --> %s\n", s, s1)
- fld.SetString(s1)
- s = s1
}
}
@@ -245,8 +271,6 @@ func applySubstitutions(x interface{}, env
map[string]string) {
e := fld.Elem()
if e.IsValid() {
process(fld.Elem())
- } else {
- logger.Printf(logger.ERROR,
"[config] 'nil' pointer encountered")
}
}
}
diff --git a/src/gnunet/config/gnunet-config.json
b/src/gnunet/config/gnunet-config.json
index f6823d7..5e9ec99 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -21,11 +21,13 @@
},
"environ": {
"TMP": "/tmp",
- "RT_SYS": "${TMP}/gnunet-system-runtime"
+ "RT_SYS": "${TMP}/gnunet-system-runtime",
+ "RT_USER": "${TMP}/gnunet-user-runtime",
+ "VAR_LIB": "/var/lib/gnunet"
},
"dht": {
"service": {
- "socket": "${RT_SYS}/gnunet-service-dht.sock",
+ "socket": "${RT_SYS}/gnunet-service-dht-go.sock",
"params": {
"perm": "0770"
}
@@ -33,7 +35,7 @@
"storage": {
"mode": "file",
"cache": false,
- "path": "/var/lib/gnunet/dht/store",
+ "path": "${VAR_LIB}/dht/store",
"maxGB": 10
},
"routing": {
@@ -54,7 +56,7 @@
},
"namecache": {
"service": {
- "socket": "${RT_SYS}/gnunet-service-namecache.sock",
+ "socket": "${RT_SYS}/gnunet-service-namecache-go.sock",
"params": {
"perm": "0770"
}
@@ -62,7 +64,7 @@
"storage": {
"mode": "file",
"cache": true,
- "path": "/var/lib/gnunet/namecache",
+ "path": "${VAR_LIB}/namecache",
"num": 1000,
"expire": 43200
}
@@ -81,11 +83,25 @@
"id": 15
}
},
+ "zonemaster": {
+ "period": 300,
+ "storage": {
+ "mode": "sqlite3",
+ "file": "${VAR_LIB}/gns/zonemaster.sqlite3"
+ },
+ "gui": "127.0.0.1:8100",
+ "service": {
+ "socket": "${RT_USER}/gnunet-service-namestore-go.sock",
+ "params": {
+ "perm": "0770"
+ }
+ }
+ },
"rpc": {
"endpoint": "tcp:127.0.0.1:80"
},
"logging": {
"level": 4,
- "file": "/tmp/gnunet-go/run.log"
+ "file": "${TMP}/gnunet-go/run.log"
}
}
\ No newline at end of file
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index 39eb8c5..301acb1 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -20,6 +20,7 @@ package crypto
import (
"bytes"
+ "crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
@@ -119,6 +120,9 @@ type ZoneKeyImpl interface {
// Verify a signature for binary data
Verify(data []byte, sig *ZoneSignature) (bool, error)
+
+ // ID returns the GNUnet identifier for a public zone key
+ ID() string
}
// ZonePrivateImpl defines the methods for a private zone key.
@@ -134,6 +138,9 @@ type ZonePrivateImpl interface {
// Public returns the associated public key
Public() ZoneKeyImpl
+
+ // ID returns the GNUnet identifier for a private zone key
+ ID() string
}
// ZoneSigImpl defines the methods for a signature object.
@@ -147,8 +154,8 @@ type ZoneSigImpl interface {
//nolint:stylecheck // allow non-camel-case in constants
var (
- ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY)
- ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
+ ZONE_EDKEY = enums.GNS_TYPE_EDKEY
+ ZONE_PKEY = enums.GNS_TYPE_PKEY
)
var (
@@ -176,7 +183,7 @@ type ZoneImplementation struct {
// keep a mapping of available implementations
var (
- zoneImpl = make(map[uint32]*ZoneImplementation)
+ zoneImpl = make(map[enums.GNSType]*ZoneImplementation)
)
// Error codes
@@ -187,7 +194,7 @@ var (
// GetImplementation return the factory for a given zone type.
// If zje zone type is unregistered, nil is returned.
-func GetImplementation(ztype uint32) *ZoneImplementation {
+func GetImplementation(ztype enums.GNSType) *ZoneImplementation {
if impl, ok := zoneImpl[ztype]; ok {
return impl
}
@@ -209,13 +216,22 @@ type ZonePrivate struct {
impl ZonePrivateImpl // reference to implementation
}
-// NewZonePrivate returns a new initialized ZonePrivate instance
-func NewZonePrivate(ztype uint32, d []byte) (zp *ZonePrivate, err error) {
+// NewZonePrivate returns a new initialized ZonePrivate instance. If no data is
+// provided, a new random key is created
+func NewZonePrivate(ztype enums.GNSType, d []byte) (zp *ZonePrivate, err
error) {
// get factory for given zone type
impl, ok := zoneImpl[ztype]
if !ok {
return nil, ErrNoImplementation
}
+ // init data available?
+ if d == nil {
+ // no: create random seed
+ d = make([]byte, impl.PrivateSize)
+ if _, err = rand.Read(d); err != nil {
+ return
+ }
+ }
// assemble private zone key
zp = &ZonePrivate{
ZoneKey{
@@ -246,11 +262,9 @@ func (zp *ZonePrivate) KeySize() uint {
// Derive key (key blinding)
func (zp *ZonePrivate) Derive(label, context string) (dzp *ZonePrivate, h
*math.Int, err error) {
- // get factory for given zone type
- impl := zoneImpl[zp.Type]
-
// calculate derived key
- h = deriveH(zp.impl.Bytes(), label, context)
+ key := zp.Public().Bytes()
+ h = deriveH(key, label, context)
var derived ZonePrivateImpl
if derived, h, err = zp.impl.Derive(h); err != nil {
return
@@ -264,9 +278,8 @@ func (zp *ZonePrivate) Derive(label, context string) (dzp
*ZonePrivate, h *math.
},
derived,
}
- zp.ZoneKey.KeyData = derived.Public().Bytes()
- zp.ZoneKey.impl = impl.NewPublic()
- err = zp.ZoneKey.impl.Init(zp.ZoneKey.KeyData)
+ dzp.ZoneKey.KeyData = derived.Public().Bytes()
+ err = dzp.Init()
return
}
@@ -280,33 +293,45 @@ func (zp *ZonePrivate) Public() *ZoneKey {
return &zp.ZoneKey
}
+// ID returns the human-readable zone private key.
+func (zp *ZonePrivate) ID() string {
+ return zp.impl.ID()
+}
+
//----------------------------------------------------------------------
// Zone key (public)
//----------------------------------------------------------------------
// ZoneKey represents the possible types of zone keys (PKEY, EDKEY,...)
type ZoneKey struct {
- Type uint32 `json:"type" order:"big"`
- KeyData []byte `json:"key" size:"(KeySize)"`
+ Type enums.GNSType `json:"type" order:"big"`
+ KeyData []byte `json:"key" size:"(KeySize)"`
impl ZoneKeyImpl // reference to implementation
}
+// Init a zone key where only the attributes have been read/deserialized.
+func (zk *ZoneKey) Init() (err error) {
+ if zk.impl == nil {
+ // initialize implementation
+ impl, ok := zoneImpl[zk.Type]
+ if !ok {
+ err = ErrUnknownZoneType
+ return
+ }
+ zk.impl = impl.NewPublic()
+ err = zk.impl.Init(zk.KeyData)
+ }
+ return
+}
+
// NewZoneKey returns a new initialized ZoneKey instance
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
+ if err = data.Unmarshal(zk, d); err == nil {
+ err = zk.Init()
}
- // initialize implementation
- impl, ok := zoneImpl[zk.Type]
- if !ok {
- err = ErrUnknownZoneType
- return
- }
- zk.impl = impl.NewPublic()
- err = zk.impl.Init(zk.KeyData)
return
}
@@ -321,7 +346,8 @@ func (zk *ZoneKey) KeySize() uint {
// Derive key (key blinding)
func (zk *ZoneKey) Derive(label, context string) (dzk *ZoneKey, h *math.Int,
err error) {
- h = deriveH(zk.KeyData, label, context)
+ key := zk.Bytes()
+ h = deriveH(key, label, context)
var derived ZoneKeyImpl
if derived, h, err = zk.impl.Derive(h); err != nil {
return
@@ -359,15 +385,7 @@ func (zk *ZoneKey) Verify(data []byte, zs *ZoneSignature)
(ok bool, err error) {
// ID returns the human-readable zone identifier.
func (zk *ZoneKey) ID() string {
- buf := new(bytes.Buffer)
- 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())
+ return zk.impl.ID()
}
// Bytes returns all bytes of a zone key
@@ -402,31 +420,33 @@ type ZoneSignature struct {
impl ZoneSigImpl // reference to implementation
}
-// NewZoneSignature returns a new initialized ZoneSignature instance
-func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
- // read signature
- sig = new(ZoneSignature)
- if err = data.Unmarshal(sig, d); err != nil {
- return
- }
+func (zs *ZoneSignature) Init() (err error) {
// initialize implementations
- impl, ok := zoneImpl[sig.Type]
+ impl, ok := zoneImpl[zs.Type]
if !ok {
err = ErrUnknownZoneType
return
}
// set signature implementation
- zs := impl.NewSignature()
- if err = zs.Init(sig.Signature); err != nil {
+ sig := impl.NewSignature()
+ if err = sig.Init(zs.Signature); err != nil {
return
}
- sig.impl = zs
+ zs.impl = sig
// set public key implementation
zk := impl.NewPublic()
- if err = zk.Init(sig.KeyData); err != nil {
- return
+ err = zk.Init(zs.KeyData)
+ zs.ZoneKey.impl = zk
+ return
+}
+
+// NewZoneSignature returns a new initialized ZoneSignature instance
+func NewZoneSignature(d []byte) (sig *ZoneSignature, err error) {
+ // read signature
+ sig = new(ZoneSignature)
+ if err = data.Unmarshal(sig, d); err == nil {
+ err = sig.Init()
}
- sig.ZoneKey.impl = zk
return
}
@@ -465,3 +485,11 @@ func deriveH(key []byte, label, context string) *math.Int {
}
return math.NewIntFromBytes(b)
}
+
+// convert (type|data) to GNUnet identifier
+func asID(t enums.GNSType, data []byte) string {
+ buf := new(bytes.Buffer)
+ _ = binary.Write(buf, binary.BigEndian, t)
+ _, _ = buf.Write(data)
+ return util.EncodeBinaryToString(buf.Bytes())
+}
diff --git a/src/gnunet/crypto/gns_edkey.go b/src/gnunet/crypto/gns_edkey.go
index 7d4323a..08dbc55 100644
--- a/src/gnunet/crypto/gns_edkey.go
+++ b/src/gnunet/crypto/gns_edkey.go
@@ -22,6 +22,7 @@ import (
"crypto/sha256"
"crypto/sha512"
"errors"
+ "gnunet/enums"
"gnunet/util"
"github.com/bfix/gospel/crypto/ed25519"
@@ -57,7 +58,7 @@ func init() {
// EDKEYPublicImpl implements the public key scheme.
type EDKEYPublicImpl struct {
- ztype uint32
+ ztype enums.GNSType
pub *ed25519.PublicKey
}
@@ -159,6 +160,11 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expire
util.AbsoluteTime) (ske
return
}
+// ID returns the GNUnet identifier for a public zone key
+func (pk *EDKEYPublicImpl) ID() string {
+ return asID(enums.GNS_TYPE_EDKEY, pk.pub.Bytes())
+}
+
//----------------------------------------------------------------------
// Private key
//----------------------------------------------------------------------
@@ -167,12 +173,14 @@ func (pk *EDKEYPublicImpl) BlockKey(label string, expire
util.AbsoluteTime) (ske
type EDKEYPrivateImpl struct {
EDKEYPublicImpl
- prv *ed25519.PrivateKey
+ seed []byte // seed used to generate key
+ prv *ed25519.PrivateKey // private key
}
// Init instance from binary data. The data represents a big integer
// (in big-endian notation) for the private scalar d.
func (pk *EDKEYPrivateImpl) Init(data []byte) error {
+ pk.seed = util.Clone(data)
pk.prv = ed25519.NewPrivateKeyFromSeed(data)
pk.ztype = ZONE_EDKEY
pk.pub = pk.prv.Public()
@@ -194,14 +202,19 @@ func (pk *EDKEYPrivateImpl) Public() ZoneKeyImpl {
// (key blinding). Returns the derived key and the blinding value.
func (pk *EDKEYPrivateImpl) Derive(h *math.Int) (dPk ZonePrivateImpl, hOut
*math.Int, err error) {
// limit to allowed value range
- hOut = h.Mod(ed25519.GetCurve().N)
+ hOut = h.SetBit(255, 0)
+ // derive private key
derived := pk.prv.Mult(hOut)
+ // derive nonce
+ md := sha256.Sum256(append(pk.prv.Nonce, h.Bytes()...))
+ derived.Nonce = md[:]
+ // assemble EDKEY private key implementation
dPk = &EDKEYPrivateImpl{
- EDKEYPublicImpl{
+ EDKEYPublicImpl: EDKEYPublicImpl{
pk.ztype,
derived.Public(),
},
- derived,
+ prv: derived,
}
return
}
@@ -228,6 +241,11 @@ func (pk *EDKEYPrivateImpl) Sign(data []byte) (sig
*ZoneSignature, err error) {
return
}
+// ID returns the GNUnet identifier for a private zone key
+func (pk *EDKEYPrivateImpl) ID() string {
+ return asID(enums.GNS_TYPE_EDKEY, pk.seed)
+}
+
//----------------------------------------------------------------------
// Signature
//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_edkey_test.go
b/src/gnunet/crypto/gns_edkey_test.go
new file mode 100644
index 0000000..aa9728f
--- /dev/null
+++ b/src/gnunet/crypto/gns_edkey_test.go
@@ -0,0 +1,56 @@
+// 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 crypto
+
+import (
+ "bytes"
+ "gnunet/enums"
+ "testing"
+)
+
+func TestEdKeyCreate(t *testing.T) {
+ // create private key
+ zp, err := NewZonePrivate(enums.GNS_TYPE_EDKEY, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Log(zp.ID())
+}
+
+func TestDeriveEDKEY(t *testing.T) {
+ // create new key pair
+ zp, err := NewZonePrivate(enums.GNS_TYPE_EDKEY, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ zk := zp.Public()
+
+ // derive keys
+ dzp, _, err := zp.Derive("@", "gns")
+ if err != nil {
+ t.Fatal(err)
+ }
+ dzk, _, err := zk.Derive("@", "gns")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(dzp.Public().Bytes(), dzk.Bytes()) {
+ t.Fatal("derive mismatch")
+ }
+}
diff --git a/src/gnunet/crypto/gns_pkey.go b/src/gnunet/crypto/gns_pkey.go
index 13925d4..8184483 100644
--- a/src/gnunet/crypto/gns_pkey.go
+++ b/src/gnunet/crypto/gns_pkey.go
@@ -23,6 +23,7 @@ import (
"crypto/cipher"
"crypto/sha256"
"crypto/sha512"
+ "gnunet/enums"
"gnunet/util"
"github.com/bfix/gospel/crypto/ed25519"
@@ -57,7 +58,7 @@ func init() {
// PKEYPublicImpl implements the public key scheme.
type PKEYPublicImpl struct {
- ztype uint32
+ ztype enums.GNSType
pub *ed25519.PublicKey
}
@@ -155,6 +156,11 @@ func (pk *PKEYPublicImpl) cipher(encrypt bool, data
[]byte, label string, expire
return
}
+// ID returns the GNUnet identifier for a public zone key
+func (pk *PKEYPublicImpl) ID() string {
+ return asID(enums.GNS_TYPE_PKEY, pk.pub.Bytes())
+}
+
//----------------------------------------------------------------------
// Private key
//----------------------------------------------------------------------
@@ -171,7 +177,7 @@ type PKEYPrivateImpl struct {
func (pk *PKEYPrivateImpl) Init(data []byte) error {
d := math.NewIntFromBytes(data)
pk.prv = ed25519.NewPrivateKeyFromD(d)
- pk.ztype = ZONE_PKEY
+ pk.ztype = enums.GNS_TYPE_PKEY
pk.pub = pk.prv.Public()
return nil
}
@@ -225,6 +231,11 @@ func (pk *PKEYPrivateImpl) Sign(data []byte) (sig
*ZoneSignature, err error) {
return
}
+// ID returns the GNUnet identifier for a private zone key
+func (pk *PKEYPrivateImpl) ID() string {
+ return asID(enums.GNS_TYPE_PKEY, pk.prv.D.Bytes())
+}
+
//----------------------------------------------------------------------
// Signature
//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/gns_pkey_test.go
b/src/gnunet/crypto/gns_pkey_test.go
new file mode 100644
index 0000000..0982227
--- /dev/null
+++ b/src/gnunet/crypto/gns_pkey_test.go
@@ -0,0 +1,48 @@
+// 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 crypto
+
+import (
+ "bytes"
+ "gnunet/enums"
+ "testing"
+)
+
+func TestDerivePKEY(t *testing.T) {
+ // create new key pair
+ zp, err := NewZonePrivate(enums.GNS_TYPE_PKEY, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ zk := zp.Public()
+
+ // derive keys
+ dzp, _, err := zp.Derive("@", "gns")
+ if err != nil {
+ t.Fatal(err)
+ }
+ dzk, _, err := zk.Derive("@", "gns")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(dzp.Public().Bytes(), dzk.Bytes()) {
+ t.Fatal("derive mismatch")
+ }
+}
diff --git a/src/gnunet/crypto/gns_test.go b/src/gnunet/crypto/gns_test.go
index 12ab603..2cccb4d 100644
--- a/src/gnunet/crypto/gns_test.go
+++ b/src/gnunet/crypto/gns_test.go
@@ -23,6 +23,7 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
+ "gnunet/enums"
"gnunet/util"
"testing"
"time"
@@ -222,34 +223,34 @@ func TestDeriveH(t *testing.T) {
CONTEXT = "gns"
H = []byte{
- 0x07, 0x1e, 0xfc, 0xa7, 0xdb, 0x28, 0x50, 0xbd,
- 0x6f, 0x35, 0x4e, 0xbf, 0xe3, 0x8c, 0x5b, 0xbf,
- 0xd6, 0xba, 0x2f, 0x80, 0x5c, 0xd8, 0xd3, 0xb5,
- 0x4e, 0xdd, 0x7f, 0x3d, 0xd0, 0x73, 0x0d, 0x1a,
+ 0x06, 0x5b, 0xb7, 0x42, 0x12, 0xa1, 0xae, 0xc3,
+ 0x59, 0x68, 0xdd, 0xdb, 0xca, 0xa3, 0x48, 0xfc,
+ 0xb0, 0xcd, 0x89, 0xd4, 0xcf, 0x9a, 0xe0, 0xfe,
+ 0xd1, 0xf9, 0xab, 0x6b, 0xd4, 0x28, 0xf4, 0x95,
}
Q = []byte{
// zone type
0x00, 0x01, 0x00, 0x00,
// derived public key data
- 0x9f, 0x27, 0xad, 0x25, 0xb5, 0x95, 0x4a, 0x46,
- 0x7b, 0xc6, 0x5a, 0x67, 0x6b, 0x7a, 0x6d, 0x23,
- 0xb2, 0xef, 0x30, 0x0f, 0x7f, 0xc7, 0x00, 0x58,
- 0x05, 0x9e, 0x7f, 0x29, 0xe5, 0x94, 0xb5, 0xc1,
+ 0xb1, 0x0e, 0x88, 0xd5, 0x17, 0x02, 0xf3, 0x3d,
+ 0xc9, 0xcb, 0xa1, 0xe9, 0x16, 0x65, 0x9c, 0x44,
+ 0x47, 0x9c, 0xc8, 0xdb, 0x83, 0x32, 0xd1, 0xd1,
+ 0xc5, 0x03, 0xdb, 0x50, 0x0e, 0xbd, 0x2d, 0x67,
}
QUERY = []byte{
- 0xa9, 0x1a, 0x2c, 0x46, 0xf1, 0x98, 0x35, 0x50,
- 0x4f, 0x4e, 0x96, 0x78, 0x2d, 0x77, 0xd1, 0x3b,
- 0x9d, 0x4e, 0x61, 0xf3, 0x50, 0xe2, 0xe6, 0xa5,
- 0xc2, 0xd1, 0x36, 0xc1, 0xf1, 0x37, 0x94, 0x79,
- 0x19, 0xe9, 0xab, 0x2b, 0xae, 0xb5, 0xb9, 0x79,
- 0xe9, 0x1e, 0xf2, 0x6a, 0xaa, 0x54, 0x81, 0x65,
- 0xac, 0xb2, 0xec, 0xca, 0x8e, 0x30, 0x76, 0x1c,
- 0xc2, 0x1b, 0xbe, 0x89, 0x0b, 0x34, 0x6d, 0xa1,
+ 0xa9, 0x47, 0x81, 0x8a, 0xaf, 0x45, 0x94, 0xda,
+ 0x89, 0x41, 0xfa, 0x29, 0x77, 0x53, 0x94, 0x9d,
+ 0xcb, 0xc5, 0xfb, 0x41, 0xea, 0x77, 0xc6, 0x25,
+ 0x11, 0x3a, 0x59, 0x09, 0x32, 0xfe, 0xeb, 0xb4,
+ 0x59, 0x98, 0x69, 0xe2, 0x83, 0xe9, 0xdb, 0xd9,
+ 0xc7, 0x24, 0xeb, 0xf2, 0xd5, 0x30, 0x3b, 0x73,
+ 0xd7, 0xda, 0x9a, 0x2c, 0xd1, 0xd7, 0x95, 0x70,
+ 0xc5, 0x9d, 0x71, 0xb8, 0x32, 0x68, 0xc9, 0xd1,
}
)
// create private key from scalar
- prv, err := NewZonePrivate(ZONE_PKEY, D)
+ prv, err := NewZonePrivate(enums.GNS_TYPE_PKEY, D)
if err != nil {
t.Fatal(err)
}
diff --git a/src/gnunet/enums/gns.go b/src/gnunet/enums/gns.go
index f6e58a2..8f786f8 100644
--- a/src/gnunet/enums/gns.go
+++ b/src/gnunet/enums/gns.go
@@ -19,12 +19,15 @@
//nolint:stylecheck // allow non-camel-case for constants
package enums
+// GNSFlag type
+type GNSFlag uint32
+
const (
// GNS record flags
- GNS_FLAG_PRIVATE = 2 // Record is not shared on the DHT
- GNS_FLAG_SUPPL = 4 // Supplemental records (e.g. NICK) in a block
- GNS_FLAG_EXPREL = 8 // Expire time in record is in relative time.
- GNS_FLAG_SHADOW = 16 // Record is ignored if non-expired records of
same type exist in block
+ GNS_FLAG_PRIVATE GNSFlag = 2 // Record is not shared on the DHT
+ GNS_FLAG_SUPPL GNSFlag = 4 // Supplemental records (e.g. NICK) in a
block
+ GNS_FLAG_EXPREL GNSFlag = 8 // Expire time in record is in relative
time.
+ GNS_FLAG_SHADOW GNSFlag = 16 // Record is ignored if non-expired
records of same type exist in block
// GNS_LocalOptions
GNS_LO_DEFAULT = 0 // Defaults, look in cache, then in DHT.
@@ -39,3 +42,9 @@ const (
//go:generate go run generate.go gnunet-gns.rec gnunet-gns.tpl gns_type.go
//go:generate stringer -type=GNSType gns_type.go
+
+// GNSSpec is the combination of type and flags
+type GNSSpec struct {
+ Type GNSType
+ Flags GNSFlag
+}
diff --git a/src/gnunet/enums/gns_type.go b/src/gnunet/enums/gns_type.go
index 5a13d67..d4cf6a6 100644
--- a/src/gnunet/enums/gns_type.go
+++ b/src/gnunet/enums/gns_type.go
@@ -3,7 +3,7 @@
//nolint:stylecheck // allow non-camel-case for constants
package enums
-type GNSType int
+type GNSType uint32
// GNS constants
const (
diff --git a/src/gnunet/enums/gnunet-gns.tpl b/src/gnunet/enums/gnunet-gns.tpl
index 3249569..4f4f598 100644
--- a/src/gnunet/enums/gnunet-gns.tpl
+++ b/src/gnunet/enums/gnunet-gns.tpl
@@ -3,7 +3,7 @@
//nolint:stylecheck // allow non-camel-case for constants
package enums
-type GNSType int
+type GNSType uint32
// GNS constants
const (
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index 26f0e38..185eaad 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -3,7 +3,7 @@ module gnunet
go 1.18
require (
- github.com/bfix/gospel v1.2.19
+ github.com/bfix/gospel v1.2.20
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
@@ -24,4 +24,4 @@ require (
golang.org/x/tools v0.1.11 // indirect
)
-// replace github.com/bfix/gospel v1.2.19 => ../gospel
+// replace github.com/bfix/gospel v1.2.20 => ../gospel
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index a1c014b..a8e3d7e 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,5 +1,5 @@
-github.com/bfix/gospel v1.2.19 h1:B57L5CMjKPeRPtVxt1JcSx42AKwD+SpN32QaF0DxXFM=
-github.com/bfix/gospel v1.2.19/go.mod
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/bfix/gospel v1.2.20 h1:e/IxmTiC579jIQlIxpMzCX/MIKHNsBzJ1WdMKheCgBw=
+github.com/bfix/gospel v1.2.20/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=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 8f664b0..82af6e9 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -24,6 +24,8 @@ import (
)
// NewEmptyMessage creates a new empty message object for the given type.
+//
+//nolint:gocyclo // it's a long switch intentionally
func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
switch msgType {
//------------------------------------------------------------------
@@ -76,7 +78,7 @@ func NewEmptyMessage(msgType enums.MsgType) (Message, error) {
case enums.MSG_DHT_P2P_GET:
return NewDHTP2PGetMsg(), nil
case enums.MSG_DHT_P2P_PUT:
- return NewDHTP2PPutMsg(), nil
+ return NewDHTP2PPutMsg(nil), nil
case enums.MSG_DHT_P2P_RESULT:
return NewDHTP2PResultMsg(), nil
@@ -111,6 +113,24 @@ func NewEmptyMessage(msgType enums.MsgType) (Message,
error) {
return NewRevocationRevokeMsg(nil), nil
case enums.MSG_REVOCATION_REVOKE_RESPONSE:
return NewRevocationRevokeResponseMsg(false), nil
+
+ //------------------------------------------------------------------
+ // Namestore service
+ //------------------------------------------------------------------
+ case enums.MSG_NAMESTORE_ZONE_ITERATION_START:
+ return NewNamestoreZoneIterStartMsg(nil), nil
+ case enums.MSG_NAMESTORE_ZONE_ITERATION_NEXT:
+ case enums.MSG_NAMESTORE_ZONE_ITERATION_STOP:
+ case enums.MSG_NAMESTORE_RECORD_STORE:
+ case enums.MSG_NAMESTORE_RECORD_STORE_RESPONSE:
+ case enums.MSG_NAMESTORE_RECORD_LOOKUP:
+ case enums.MSG_NAMESTORE_RECORD_LOOKUP_RESPONSE:
+ case enums.MSG_NAMESTORE_ZONE_TO_NAME:
+ case enums.MSG_NAMESTORE_ZONE_TO_NAME_RESPONSE:
+ case enums.MSG_NAMESTORE_MONITOR_START:
+ case enums.MSG_NAMESTORE_MONITOR_SYNC:
+ case enums.MSG_NAMESTORE_RECORD_RESULT:
+ case enums.MSG_NAMESTORE_MONITOR_NEXT:
}
return nil, fmt.Errorf("unknown message type %d", msgType)
}
diff --git a/src/gnunet/message/msg_dht_p2p.go
b/src/gnunet/message/msg_dht_p2p.go
index 9d1d815..d4a474c 100644
--- a/src/gnunet/message/msg_dht_p2p.go
+++ b/src/gnunet/message/msg_dht_p2p.go
@@ -24,6 +24,7 @@ import (
"encoding/binary"
"errors"
"fmt"
+ "gnunet/config"
"gnunet/crypto"
"gnunet/enums"
"gnunet/service/dht/blocks"
@@ -122,9 +123,10 @@ type DHTP2PPutMsg struct {
}
// NewDHTP2PPutMsg creates an empty new DHTP2PPutMsg
-func NewDHTP2PPutMsg() *DHTP2PPutMsg {
- return &DHTP2PPutMsg{
- MsgHeader: MsgHeader{218, enums.MSG_DHT_P2P_PUT},
+func NewDHTP2PPutMsg(block blocks.Block) *DHTP2PPutMsg {
+ // create empty message
+ msg := &DHTP2PPutMsg{
+ MsgHeader: MsgHeader{216, enums.MSG_DHT_P2P_PUT},
BType: enums.BLOCK_TYPE_ANY, // block type
Flags: 0, // processing flags
HopCount: 0, // message hops
@@ -138,6 +140,20 @@ func NewDHTP2PPutMsg() *DHTP2PPutMsg {
LastSig: nil, // no signature from
last hop
Block: nil, // no block data
}
+ // initialize with block if available
+ if block != nil {
+ msg.BType = block.Type()
+ msg.HopCount = 0
+ msg.PeerFilter = blocks.NewPeerFilter()
+ msg.ReplLvl = uint16(config.Cfg.GNS.ReplLevel)
+ msg.Expire = block.Expire()
+ msg.Block = block.Bytes()
+ msg.TruncOrigin = nil
+ msg.PutPath = nil
+ msg.LastSig = nil
+ msg.MsgSize += uint16(len(msg.Block))
+ }
+ return msg
}
// IsUsed returns true if an optional field is used
@@ -155,7 +171,7 @@ func (m *DHTP2PPutMsg) IsUsed(field string) bool {
// Update message (forwarding)
func (m *DHTP2PPutMsg) Update(p *path.Path, pf *blocks.PeerFilter, hop uint16)
*DHTP2PPutMsg {
- msg := NewDHTP2PPutMsg()
+ msg := NewDHTP2PPutMsg(nil)
msg.Flags = m.Flags
msg.HopCount = hop
msg.PathL = p.NumList
diff --git a/src/gnunet/message/msg_gns.go b/src/gnunet/message/msg_gns.go
index 0b96e88..1c7f9af 100644
--- a/src/gnunet/message/msg_gns.go
+++ b/src/gnunet/message/msg_gns.go
@@ -23,6 +23,7 @@ import (
"gnunet/crypto"
"gnunet/enums"
+ "gnunet/service/dht/blocks"
"gnunet/util"
"github.com/bfix/gospel/logger"
@@ -39,7 +40,7 @@ type LookupMsg struct {
Zone *crypto.ZoneKey `` // Zone that is to be used for
lookup
Options uint16 `order:"big"` // Local options for where to
look for results
Reserved uint16 `order:"big"` // Always 0
- RType uint32 `order:"big"` // the type of record to look up
+ RType enums.GNSType `order:"big"` // the type of record to look up
Name []byte `size:"*"` // zero-terminated name to look
up
}
@@ -51,7 +52,7 @@ func NewGNSLookupMsg() *LookupMsg {
Zone: nil,
Options: uint16(enums.GNS_LO_DEFAULT),
Reserved: 0,
- RType: uint32(enums.GNS_TYPE_ANY),
+ RType: enums.GNS_TYPE_ANY,
Name: nil,
}
}
@@ -84,78 +85,12 @@ func (m *LookupMsg) String() string {
// GNS_LOOKUP_RESULT
//----------------------------------------------------------------------
-// RecordSet ist the GNUnet data structure for a list of resource records
-// in a GNSBlock. As part of GNUnet messages, the record set is padded so that
-// the binary size of (records||padding) is the smallest power of two.
-type RecordSet struct {
- Count uint32 `order:"big"` // number of resource records
- Records []*ResourceRecord `size:"Count"` // list of resource records
- Padding []byte `size:"*"` // padding
-}
-
-// NewRecordSet returns an empty resource record set.
-func NewRecordSet() *RecordSet {
- return &RecordSet{
- Count: 0,
- Records: make([]*ResourceRecord, 0),
- Padding: make([]byte, 0),
- }
-}
-
-// AddRecord to append a resource record to the set.
-func (rs *RecordSet) AddRecord(rec *ResourceRecord) {
- rs.Count++
- rs.Records = append(rs.Records, rec)
-}
-
-// SetPadding (re-)calculates and allocates the padding.
-func (rs *RecordSet) SetPadding() {
- size := 0
- for _, rr := range rs.Records {
- size += int(rr.Size) + 20
- }
- n := 1
- for n < size {
- n <<= 1
- }
- rs.Padding = make([]byte, n-size)
-}
-
-// Expire returns the earliest expiration timestamp for the records.
-func (rs *RecordSet) Expire() util.AbsoluteTime {
- var expires util.AbsoluteTime
- for i, rr := range rs.Records {
- if i == 0 {
- expires = rr.Expire
- } else if rr.Expire.Compare(expires) < 0 {
- expires = rr.Expire
- }
- }
- return expires
-}
-
-// ResourceRecord is the GNUnet-specific representation of resource
-// records (not to be confused with DNS resource records).
-type ResourceRecord struct {
- Expire util.AbsoluteTime // Expiration time for the record
- Size uint32 `order:"big"` // Number of bytes in 'Data'
- RType uint32 `order:"big"` // Type of the GNS/DNS record
- Flags uint32 `order:"big"` // Flags for the record
- Data []byte `size:"Size"` // Record data
-}
-
-// String returns a human-readable representation of the message.
-func (r *ResourceRecord) String() string {
- return
fmt.Sprintf("GNSResourceRecord{type=%s,expire=%s,flags=%d,size=%d}",
- enums.GNSType(r.RType).String(), r.Expire, r.Flags, r.Size)
-}
-
// LookupResultMsg is a response message for a GNS name lookup request
type LookupResultMsg struct {
MsgHeader
- ID uint32 `order:"big"` // Unique identifier for this
request (for key collisions).
- Count uint32 `order:"big"` // The number of records
contained in response
- Records []*ResourceRecord `size:"Count"` // GNS resource records
+ ID uint32 `order:"big"` // Unique identifier
for this request (for key collisions).
+ Count uint32 `order:"big"` // The number of
records contained in response
+ Records []*blocks.ResourceRecord `size:"Count"` // GNS resource records
}
// NewGNSLookupResultMsg returns a new lookup result message
@@ -164,12 +99,12 @@ func NewGNSLookupResultMsg(id uint32) *LookupResultMsg {
MsgHeader: MsgHeader{12, enums.MSG_GNS_LOOKUP_RESULT},
ID: id,
Count: 0,
- Records: make([]*ResourceRecord, 0),
+ Records: make([]*blocks.ResourceRecord, 0),
}
}
// AddRecord adds a GNS resource recordto the response message.
-func (m *LookupResultMsg) AddRecord(rec *ResourceRecord) error {
+func (m *LookupResultMsg) AddRecord(rec *blocks.ResourceRecord) error {
recSize := 20 + int(rec.Size)
if int(m.MsgSize)+recSize > enums.GNS_MAX_BLOCK_SIZE {
return fmt.Errorf("gns.AddRecord(): MAX_BLOCK_SIZE reached")
diff --git a/src/gnunet/message/msg_namecache.go
b/src/gnunet/message/msg_namecache.go
index ea413a7..53b2f4c 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -27,14 +27,32 @@ import (
"gnunet/util"
)
+//----------------------------------------------------------------------
+// Generic Namecache message header
+//----------------------------------------------------------------------
+
+// GenericNamecacheMsg is the common header for Namestore messages
+type GenericNamecacheMsg struct {
+ MsgHeader
+ ID uint32 `order:"big"` // unique reference ID
+}
+
+// return initialized common message header
+func newGenericNamecacheMsg(size uint16, mtype enums.MsgType)
GenericNamecacheMsg {
+ return GenericNamecacheMsg{
+ MsgHeader: MsgHeader{size, mtype},
+ ID: uint32(util.NextID()),
+ }
+}
+
//----------------------------------------------------------------------
// NAMECACHE_LOOKUP_BLOCK
//----------------------------------------------------------------------
// NamecacheLookupMsg is request message for lookups in local namecache
type NamecacheLookupMsg struct {
- MsgHeader
- ID uint32 `order:"big"` // Request Id
+ GenericNamecacheMsg
+
Query *crypto.HashCode // Query data
}
@@ -44,9 +62,8 @@ func NewNamecacheLookupMsg(query *crypto.HashCode)
*NamecacheLookupMsg {
query = crypto.NewHashCode(nil)
}
return &NamecacheLookupMsg{
- MsgHeader: MsgHeader{72, enums.MSG_NAMECACHE_LOOKUP_BLOCK},
- ID: 0,
- Query: query,
+ GenericNamecacheMsg: newGenericNamecacheMsg(72,
enums.MSG_NAMECACHE_LOOKUP_BLOCK),
+ Query: query,
}
}
@@ -62,21 +79,20 @@ func (m *NamecacheLookupMsg) String() string {
// NamecacheLookupResultMsg is the response message for namecache lookups.
type NamecacheLookupResultMsg struct {
- MsgHeader
- ID uint32 `order:"big"` // Request Id
- Expire util.AbsoluteTime `` // Expiration time
- DerivedKeySig *crypto.ZoneSignature `` // Derived public key
- EncData []byte `size:"*"` // Encrypted block
data
+ GenericNamecacheMsg
+
+ Expire util.AbsoluteTime `` // Expiration time
+ DerivedKeySig *crypto.ZoneSignature `` // Derived public key
+ EncData []byte `size:"*"` // Encrypted block data
}
// NewNamecacheLookupResultMsg creates a new default message.
func NewNamecacheLookupResultMsg() *NamecacheLookupResultMsg {
return &NamecacheLookupResultMsg{
- MsgHeader: MsgHeader{112,
enums.MSG_NAMECACHE_LOOKUP_BLOCK_RESPONSE},
- ID: 0,
- Expire: *new(util.AbsoluteTime),
- DerivedKeySig: nil,
- EncData: nil,
+ GenericNamecacheMsg: newGenericNamecacheMsg(112,
enums.MSG_NAMECACHE_LOOKUP_BLOCK_RESPONSE),
+ Expire: util.AbsoluteTimeNever(),
+ DerivedKeySig: nil,
+ EncData: nil,
}
}
@@ -92,24 +108,38 @@ func (m *NamecacheLookupResultMsg) String() string {
// NamecacheCacheMsg is the request message to put a name into the local cache.
type NamecacheCacheMsg struct {
- MsgHeader
- ID uint32 `order:"big"` // Request Id
- Expire util.AbsoluteTime `` // Expiration time
- DerivedKeySig *crypto.ZoneSignature `` // Derived public key
and signature
- EncData []byte `size:"*"` // Encrypted block
data
+ GenericNamecacheMsg
+
+ Expire util.AbsoluteTime `` // Expiration time
+ DerivedSig []byte `size:"(FldSize)"` // Derived signature
+ DerivedKey []byte `size:"(FldSize)"` // Derived public key
+ EncData []byte `size:"*"` // Encrypted block data
+}
+
+// Size returns buffer sizes for fields
+func (m *NamecacheCacheMsg) FldSize(field string) uint {
+ switch field {
+ case "DerivedSig":
+ return 64
+ case "DerivedKey":
+ return 36
+ }
+ // defaults to empty buffer
+ return 0
}
// NewNamecacheCacheMsg creates a new default message.
func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg {
msg := &NamecacheCacheMsg{
- MsgHeader: MsgHeader{108, enums.MSG_NAMECACHE_BLOCK_CACHE},
- ID: 0,
- Expire: *new(util.AbsoluteTime),
- DerivedKeySig: nil,
- EncData: make([]byte, 0),
+ GenericNamecacheMsg: newGenericNamecacheMsg(116,
enums.MSG_NAMECACHE_BLOCK_CACHE),
+ Expire: util.AbsoluteTimeNever(),
+ DerivedSig: nil,
+ DerivedKey: nil,
+ EncData: make([]byte, 0),
}
if block != nil {
- msg.DerivedKeySig = block.DerivedKeySig
+ msg.DerivedKey = util.Clone(block.DerivedKeySig.ZoneKey.Bytes())
+ msg.DerivedSig = util.Clone(block.DerivedKeySig.Signature)
msg.Expire = block.Body.Expire
size := len(block.Body.Data)
msg.EncData = make([]byte, size)
@@ -121,8 +151,8 @@ func NewNamecacheCacheMsg(block *blocks.GNSBlock)
*NamecacheCacheMsg {
// String returns a human-readable representation of the message.
func (m *NamecacheCacheMsg) String() string {
- return fmt.Sprintf("NewNamecacheCacheMsg{id=%d,expire=%s}",
- m.ID, m.Expire)
+ return fmt.Sprintf("NamecacheCacheMsg{size=%d,id=%d,expire=%s}",
+ m.Size(), m.ID, m.Expire)
}
//----------------------------------------------------------------------
@@ -131,17 +161,16 @@ func (m *NamecacheCacheMsg) String() string {
// NamecacheCacheResponseMsg is the response message for a put request
type NamecacheCacheResponseMsg struct {
- MsgHeader
- ID uint32 `order:"big"` // Request Id
- Result int32 `order:"big"` // Result code
+ GenericNamecacheMsg
+
+ Result int32 `order:"big"` // Result code
}
// NewNamecacheCacheResponseMsg creates a new default message.
func NewNamecacheCacheResponseMsg() *NamecacheCacheResponseMsg {
return &NamecacheCacheResponseMsg{
- MsgHeader: MsgHeader{12,
enums.MSG_NAMECACHE_BLOCK_CACHE_RESPONSE},
- ID: 0,
- Result: 0,
+ GenericNamecacheMsg: newGenericNamecacheMsg(12,
enums.MSG_NAMECACHE_BLOCK_CACHE_RESPONSE),
+ Result: 0,
}
}
diff --git a/src/gnunet/message/msg_namestore.go
b/src/gnunet/message/msg_namestore.go
new file mode 100644
index 0000000..a682ef1
--- /dev/null
+++ b/src/gnunet/message/msg_namestore.go
@@ -0,0 +1,199 @@
+// 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 (
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+)
+
+//======================================================================
+// NameStore service messages
+//======================================================================
+
+// GenericNamestoreMsg is the common header for Namestore messages
+type GenericNamestoreMsg struct {
+ MsgHeader
+ ID uint32 `order:"big"` // unique reference ID
+}
+
+// return initialized common message header
+func newGenericNamestoreMsg(size uint16, mtype enums.MsgType)
GenericNamestoreMsg {
+ return GenericNamestoreMsg{
+ MsgHeader: MsgHeader{size, mtype},
+ ID: uint32(util.NextID()),
+ }
+}
+
+//----------------------------------------------------------------------
+// MSG_NAMESTORE_ZONE_ITERATION_START
+//----------------------------------------------------------------------
+
+// NamestoreZoneIterStartMsg starts a new iteration over all zones
+type NamestoreZoneIterStartMsg struct {
+ GenericNamestoreMsg
+
+ ZoneKey *crypto.ZonePrivate // private zone key
+}
+
+// NewNamecacheCacheMsg creates a new default message.
+func NewNamestoreZoneIterStartMsg(zone *crypto.ZonePrivate)
*NamestoreZoneIterStartMsg {
+ return &NamestoreZoneIterStartMsg{
+ GenericNamestoreMsg: newGenericNamestoreMsg(100,
enums.MSG_NAMESTORE_ZONE_ITERATION_START),
+ ZoneKey: zone,
+ }
+}
+
+// String returns a human-readable representation of the message.
+func (m *NamestoreZoneIterStartMsg) String() string {
+ return fmt.Sprintf("NamestoreZoneIterStartMsg{id=%d,zone=%s}", m.ID,
m.ZoneKey.ID())
+}
+
+//----------------------------------------------------------------------
+// MSG_NAMESTORE_ZONE_ITERATION_NEXT
+//----------------------------------------------------------------------
+
+type NamestoreZoneIterNextMsg struct {
+ GenericNamestoreMsg
+
+ Limit uint64 `order:"big"` // max. number of records in one go
+}
+
+func NewNamestoreZoneIterNextMsg() *NamestoreZoneIterNextMsg {
+ return &NamestoreZoneIterNextMsg{}
+}
+
+// String returns a human-readable representation of the message.
+func (m *NamestoreZoneIterNextMsg) String() string {
+ return fmt.Sprintf("NamestoreZoneIterNextMsg{id=%d,limit=%d}", m.ID,
m.Limit)
+}
+
+//----------------------------------------------------------------------
+// MSG_NAMESTORE_ZONE_ITERATION_STOP
+//----------------------------------------------------------------------
+
+type NamestoreZoneIterStopMsg struct {
+ GenericNamestoreMsg
+}
+
+//----------------------------------------------------------------------
+//----------------------------------------------------------------------
+
+type NamestoreRecordStoreMsg struct {
+ GenericNamestoreMsg
+
+ ZoneKey *crypto.ZonePrivate // private zone key
+ Records *blocks.RecordSet // list of records
+}
+
+type NamestoreRecordStoreRespMsg struct {
+ GenericNamestoreMsg
+
+ Status int32 `order:"big"` // result status
+ ErrLen uint16 `order:"big"` // length of error message
+ Reserved uint16 `order:"big"` // alignment
+ Error []byte `size:"ErrLen"` // error message
+}
+
+type NamestoreLabelLookupMsg struct {
+ GenericNamestoreMsg
+
+ LblLen uint32 `order:"big"` // length of label
+ IsEdit uint32 `order:"big"` // lookup corresponds to edit
request
+ ZoneKey *crypto.ZonePrivate // private zone key
+ Label []byte `size:"LblLen"` // label string
+}
+
+type NamestoreLabelLookupRespMsg struct {
+ GenericNamestoreMsg
+
+ LblLen uint16 `order:"big"` // Length of label
+ RdLen uint16 `order:"big"` // size of record data
+ RdCount uint16 `order:"big"` // number of records
+ Found int16 `order:"big"` // label found?
+ ZoneKey *crypto.ZonePrivate // private zone key
+ Label []byte `size:"LblLen"` // label string
+ Records []byte `size:"RdLen"` // serialized record data
+}
+
+type NamestoreZoneToNameMsg struct {
+ GenericNamestoreMsg
+
+ ZoneKey *crypto.ZonePrivate // private zone key
+ ZonePublic *crypto.ZoneKey // public zone key
+}
+
+type NamestoreZoneToNameRespMsg struct {
+ GenericNamestoreMsg
+
+ NameLen uint16 `order:"big"` // length of name
+ RdLen uint16 `order:"big"` // size of record data
+ RdCount uint16 `order:"big"` // number of records
+ Status int16 `order:"big"` // result status
+ ZoneKey *crypto.ZonePrivate // private zone key
+ Name []byte `size:"NameLen"` // name string
+ Records []byte `size:"RdLen"` // serialized record data
+}
+
+type NamestoreRecordResultMsg struct {
+ GenericNamestoreMsg
+
+ Expire util.AbsoluteTime `` // expiration date
+ NameLen uint16 `order:"big"` // length of name
+ RdLen uint16 `order:"big"` // size of record data
+ RdCount uint16 `order:"big"` // number of records
+ Reserved uint16 `order:"big"` // alignment
+ ZoneKey *crypto.ZonePrivate // private zone key
+ Name []byte `size:"NameLen"` // name string
+ Records []byte `size:"RdLen"` // serialized record data
+}
+
+type NamestoreTxControlMsg struct {
+ GenericNamestoreMsg
+
+ Control uint16 `order:"big"` // type of control message
+ Reserved uint16 `order:"big"` // alignment
+}
+
+type NamestoreTxControlResultMsg struct {
+ GenericNamestoreMsg
+
+ Control uint16 `order:"big"` // type of control message
+ Status uint16 `order:"big"` // result status
+ Error []byte `size:"*"` // error message (on status != OK)
+}
+
+type NamestoreZoneMonStartMsg struct {
+ GenericNamestoreMsg
+
+ Iterate uint32 `order:"big"` // iterate over all records
+ Filter uint16 `order:"big"` // filter flags
+ Reserved uint16 `order:"big"` // alignment
+ ZoneKey *crypto.ZonePrivate // private zone key
+}
+
+type NamestoreZoneMonNextMsg struct {
+ GenericNamestoreMsg
+
+ Reserved uint32 `order:"big"` // alignment =0
+ Limit uint64 `order:"big"` // max. number of records in one go
+}
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index 5f7f8f0..7b5e29b 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -66,7 +66,8 @@ func RequestResponse(
caller string,
callee string,
path string,
- req message.Message) (message.Message, error) {
+ req message.Message,
+ withResponse bool) (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)
@@ -78,11 +79,13 @@ func RequestResponse(
if err = cl.SendRequest(ctx, req); err != nil {
return nil, err
}
- // wait for a single response, then close the connection
- logger.Printf(logger.DBG, "[%s] Waiting for response from %s
service\n", caller, callee)
var resp message.Message
- if resp, err = cl.ReceiveResponse(ctx); err != nil {
- return nil, err
+ if withResponse {
+ // wait for a single response, then close the connection
+ logger.Printf(logger.DBG, "[%s] Waiting for response from %s
service\n", caller, callee)
+ if resp, err = cl.ReceiveResponse(ctx); err != nil {
+ return nil, err
+ }
}
logger.Printf(logger.DBG, "[%s] Closing connection to %s service\n",
caller, callee)
cl.Close()
diff --git a/src/gnunet/service/dht/blocks/gns.go
b/src/gnunet/service/dht/blocks/gns.go
index 6b41b0b..0c32085 100644
--- a/src/gnunet/service/dht/blocks/gns.go
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -47,7 +47,7 @@ type GNSQuery struct {
GenericQuery
Zone *crypto.ZoneKey // Public zone key
Label string // Atomic label
- derived *crypto.ZoneKey // Derived zone key from (pkey,label)
+ derived *crypto.ZoneKey // Derived zone key from (zone,label)
}
// Verify the integrity of the block data from a signature.
@@ -103,6 +103,7 @@ func NewGNSQuery(zkey *crypto.ZoneKey, label string)
*GNSQuery {
pd, _, err := zkey.Derive(label, "gns")
if err != nil {
logger.Printf(logger.ERROR, "[NewGNSQuery] failed: %s",
err.Error())
+ return nil
}
gq := crypto.Hash(pd.Bytes())
return &GNSQuery{
@@ -140,6 +141,11 @@ type GNSBlock struct {
data []byte // decrypted data
}
+// Payload returns the decrypted block data (or nil)
+func (b *GNSBlock) Payload() []byte {
+ return util.Clone(b.data)
+}
+
// Bytes return th binary representation of block
func (b *GNSBlock) Bytes() []byte {
buf, _ := data.Marshal(b)
@@ -167,8 +173,12 @@ func NewGNSBlock() Block {
return &GNSBlock{
DerivedKeySig: nil,
Body: &SignedGNSBlockData{
- Purpose: new(crypto.SignaturePurpose),
- Data: nil,
+ Purpose: &crypto.SignaturePurpose{
+ Size: 16,
+ Purpose: enums.SIG_GNS_RECORD_SIGN,
+ },
+ Expire: util.AbsoluteTimeNever(),
+ Data: nil,
},
checked: false,
verified: false,
@@ -181,6 +191,23 @@ func NewGNSBlock() Block {
// Not required for GNS blocks
func (b *GNSBlock) Prepare(enums.BlockType, util.AbsoluteTime) {}
+// SetData sets the data for the GNS block
+func (b *GNSBlock) SetData(data []byte) {
+ b.Body.Data = data
+ b.Body.Purpose.Size = uint32(len(data) + 16)
+}
+
+// Sign the block with a derived private key
+func (b *GNSBlock) Sign(sk *crypto.ZonePrivate) error {
+ // get signed data
+ buf, err := data.Marshal(b.Body)
+ if err == nil {
+ // generate signature
+ b.DerivedKeySig, err = sk.Sign(buf)
+ }
+ return err
+}
+
// Verify the integrity of the block data from a signature.
// 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
@@ -193,3 +220,78 @@ func (b *GNSBlock) Verify() (ok bool, err error) {
}
return b.DerivedKeySig.Verify(buf)
}
+
+// RecordSet ist the GNUnet data structure for a list of resource records
+// in a GNSBlock. As part of GNUnet messages, the record set is padded so that
+// the binary size of (records||padding) is the smallest power of two.
+type RecordSet struct {
+ Count uint32 `order:"big"` // number of resource records
+ Records []*ResourceRecord `size:"Count"` // list of resource records
+ Padding []byte `size:"*"` // padding
+}
+
+// NewRecordSet returns an empty resource record set.
+func NewRecordSet() *RecordSet {
+ return &RecordSet{
+ Count: 0,
+ Records: make([]*ResourceRecord, 0),
+ Padding: make([]byte, 0),
+ }
+}
+
+// AddRecord to append a resource record to the set.
+func (rs *RecordSet) AddRecord(rec *ResourceRecord) {
+ rs.Count++
+ rs.Records = append(rs.Records, rec)
+}
+
+// SetPadding (re-)calculates and allocates the padding.
+func (rs *RecordSet) SetPadding() {
+ size := 0
+ for _, rr := range rs.Records {
+ size += int(rr.Size) + 20
+ }
+ n := 1
+ for n < size {
+ n <<= 1
+ }
+ rs.Padding = make([]byte, n-size)
+}
+
+// Expire returns the earliest expiration timestamp for the records.
+func (rs *RecordSet) Expire() util.AbsoluteTime {
+ var expires util.AbsoluteTime
+ for i, rr := range rs.Records {
+ if i == 0 {
+ expires = rr.Expire
+ } else if rr.Expire.Compare(expires) < 0 {
+ expires = rr.Expire
+ }
+ }
+ return expires
+}
+
+// Bytes returns the binary representation
+func (rs *RecordSet) Bytes() []byte {
+ buf, err := data.Marshal(rs)
+ if err != nil {
+ return nil
+ }
+ return buf
+}
+
+// ResourceRecord is the GNUnet-specific representation of resource
+// records (not to be confused with DNS resource records).
+type ResourceRecord struct {
+ Expire util.AbsoluteTime // Expiration time for the record
+ Size uint32 `order:"big"` // Number of bytes in 'Data'
+ RType enums.GNSType `order:"big"` // Type of the GNS/DNS record
+ Flags enums.GNSFlag `order:"big"` // Flags for the record
+ Data []byte `size:"Size"` // Record data
+}
+
+// String returns a human-readable representation of the message.
+func (r *ResourceRecord) String() string {
+ return
fmt.Sprintf("GNSResourceRecord{type=%s,expire=%s,flags=%d,size=%d}",
+ r.RType.String(), r.Expire, r.Flags, r.Size)
+}
diff --git a/src/gnunet/message/msg_gns_test.go
b/src/gnunet/service/dht/blocks/gns_test.go
similarity index 78%
rename from src/gnunet/message/msg_gns_test.go
rename to src/gnunet/service/dht/blocks/gns_test.go
index 034e1a8..260d83b 100644
--- a/src/gnunet/message/msg_gns_test.go
+++ b/src/gnunet/service/dht/blocks/gns_test.go
@@ -16,7 +16,7 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
-package message
+package blocks
import (
"bytes"
@@ -29,6 +29,97 @@ import (
"github.com/bfix/gospel/data"
)
+func TestGNSBlock(t *testing.T) {
+ var (
+ ZONEKEY =
"000G054G4G3HWZP2WFNVS1XJ4VXWY85G49AVYBZ7TV4EWP5J5V59H5QN40"
+ LABEL = "@"
+
+ QKEY = []byte{
+ 0xb6, 0x48, 0xfd, 0x0c, 0x4a, 0x6c, 0xaa, 0x87,
+ 0x33, 0x2f, 0xf5, 0x12, 0x90, 0xe4, 0xbd, 0x55,
+ 0x0f, 0x8c, 0xe7, 0x9b, 0xc9, 0x5b, 0x3a, 0xfb,
+ 0xbb, 0xe2, 0xd7, 0x33, 0xbc, 0x32, 0xc9, 0x7d,
+ 0xc5, 0x4a, 0x56, 0x22, 0xbf, 0xfa, 0x49, 0x1a,
+ 0x60, 0xd6, 0xdb, 0x77, 0x5d, 0x3d, 0x18, 0x99,
+ 0x5b, 0x4f, 0xc3, 0x7d, 0x86, 0x00, 0x15, 0x76,
+ 0x42, 0x03, 0x98, 0xcc, 0xdf, 0x83, 0x4d, 0x21,
+ }
+ BLK = []byte{
+ 0x00, 0x01, 0x00, 0x14, 0xe0, 0x6b, 0xea, 0x2b,
+ 0x1b, 0xd6, 0xc6, 0x9a, 0xd4, 0x30, 0xa5, 0x0f,
+ 0x81, 0x16, 0x89, 0xe1, 0x9f, 0xca, 0x1f, 0x86,
+ 0x3f, 0x83, 0x6e, 0xe6, 0xa7, 0x54, 0x97, 0xde,
+ 0xf2, 0xc4, 0x2a, 0x84, 0xb6, 0x89, 0xe6, 0x7e,
+ 0xff, 0x0c, 0xae, 0x84, 0xe6, 0xb1, 0x6c, 0x72,
+ 0x83, 0x09, 0x68, 0x5b, 0x2f, 0xa2, 0x9f, 0xbe,
+ 0xfa, 0xef, 0x43, 0x52, 0x20, 0x48, 0xe5, 0x57,
+ 0x1e, 0x65, 0x21, 0x86, 0xd4, 0x9f, 0x96, 0x51,
+ 0x4f, 0xa9, 0x6d, 0xa9, 0x98, 0xaa, 0x2d, 0xf6,
+ 0x92, 0xd7, 0x86, 0x36, 0xc0, 0x84, 0x90, 0x00,
+ 0x42, 0x2e, 0x4e, 0xc1, 0xaf, 0x6f, 0xe0, 0x7e,
+ 0x71, 0xe3, 0xc4, 0x0d, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x08, 0x00,
+ 0xb5, 0x99, 0x2a, 0x00, 0xd0, 0x7a, 0x2b, 0x9e,
+ 0x02, 0x45, 0x54, 0x0d, 0x65, 0x26, 0xa1, 0x05,
+ 0x80, 0x26, 0xce, 0xc2, 0x70, 0xd5, 0x22, 0x38,
+ 0x80, 0x9a, 0xed, 0x63, 0x2f, 0x96, 0x60, 0x4d,
+ 0x02, 0x59, 0xd0, 0x9a, 0x4e, 0x71, 0xfa, 0x30,
+ 0xd6, 0xf9, 0xf4, 0x84, 0x5d, 0xb8, 0x60, 0xa4,
+ 0xdf, 0xea, 0x34, 0x06, 0x3f, 0x6f, 0x76, 0x9e,
+ }
+ )
+ // unmarshal block
+ blk := new(GNSBlock)
+ if err := data.Unmarshal(blk, BLK); err != nil {
+ t.Fatal(err)
+ }
+ // Initialize signature
+ if err := blk.DerivedKeySig.Init(); err != nil {
+ t.Fatal(err)
+ }
+ // assemble query from public zone key and label
+ zkData, err := util.DecodeStringToBinary(ZONEKEY, 36)
+ if err != nil {
+ t.Fatal(err)
+ }
+ zk, err := crypto.NewZoneKey(zkData)
+ if err != nil {
+ t.Fatal(err)
+ }
+ query := NewGNSQuery(zk, LABEL)
+
+ // check query key
+ if !bytes.Equal(QKEY, query.Key().Data) {
+ t.Fatal("query key mismatch")
+ }
+
+ // check derived public key (form zone key and label)
+ dkey2, _, err := zk.Derive(LABEL, "gns")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(blk.DerivedKeySig.ZoneKey.Bytes(), dkey2.Bytes()) {
+ t.Logf("expected: %s\n",
hex.EncodeToString(blk.DerivedKeySig.ZoneKey.Bytes()))
+ t.Logf("got: %s\n", hex.EncodeToString(dkey2.Bytes()))
+ t.Fatal("key mismatch")
+ }
+
+ // verify signature
+ if err = query.Verify(blk); err != nil {
+ t.Fatal(err)
+ }
+ // decrypt payload
+ if err = query.Decrypt(blk); err != nil {
+ t.Fatal(err)
+ }
+ rrs := new(RecordSet)
+ if err = data.Unmarshal(rrs, blk.Payload()); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("RecordSet=%v\n", rrs)
+
+}
+
// TestRecordsetPKEY implements the test case as defined in the GNS draft
// (see section 13. Test vectors, case "PKEY")
func TestRecordsetPKEY(t *testing.T) {
@@ -69,7 +160,7 @@ func TestRecordsetPKEY(t *testing.T) {
Val: uint64(26147096139323793),
},
Size: 36,
- RType: crypto.ZONE_PKEY,
+ RType: enums.GNS_TYPE_PKEY,
Flags: 2,
Data: []byte{
0x00, 0x01, 0x00, 0x00,
@@ -134,7 +225,7 @@ func TestRecordsetPKEY(t *testing.T) {
)
// check zone key pair
- prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, D)
+ prv, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, D)
if err != nil {
t.Fatal(err)
}
@@ -229,7 +320,7 @@ func TestRecordsetEDKEY(t *testing.T) {
Val: uint64(49556645701000000),
},
Size: 36,
- RType: uint32(enums.GNS_TYPE_NICK),
+ RType: enums.GNS_TYPE_NICK,
Flags: 2,
Data: []byte{
0x4d, 0x79, 0x20, 0x4e, 0x69,
0x63, 0x6b, 0x00,
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 718b730..9f3aaa0 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -248,25 +248,10 @@ func (m *Module) Get(ctx context.Context, query
blocks.Query) <-chan blocks.Bloc
// Put a block into the DHT ["dht:put"]
func (m *Module) Put(ctx context.Context, query blocks.Query, block
blocks.Block) error {
- // get additional query parameters
- expire, ok := util.GetParam[util.AbsoluteTime](query.Params(), "expire")
- if !ok {
- expire = util.AbsoluteTimeNever()
- }
// assemble a new PUT message
- msg := message.NewDHTP2PPutMsg()
- msg.BType = query.Type()
+ msg := message.NewDHTP2PPutMsg(block)
msg.Flags = query.Flags()
- msg.HopCount = 0
- msg.PeerFilter = blocks.NewPeerFilter()
- msg.ReplLvl = uint16(m.cfg.Routing.ReplLevel)
- msg.Expire = expire
- msg.Block = block.Bytes()
msg.Key = query.Key().Clone()
- msg.TruncOrigin = nil
- msg.PutPath = nil
- msg.LastSig = nil
- msg.MsgSize += uint16(len(msg.Block))
// send message
self := m.core.PeerID()
diff --git a/src/gnunet/service/gns/block_handler.go
b/src/gnunet/service/gns/block_handler.go
index a0e7874..b00fac2 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -19,19 +19,21 @@
package gns
import (
+ "crypto/sha256"
"encoding/hex"
"fmt"
"gnunet/crypto"
"gnunet/enums"
- "gnunet/message"
+ "gnunet/service/dht/blocks"
+ "gnunet/service/gns/rr"
"gnunet/util"
"github.com/bfix/gospel/logger"
)
// HdlrInst is the type for functions that instantiate custom block handlers.
-type HdlrInst func(*message.ResourceRecord, []string) (BlockHandler, error)
+type HdlrInst func(*blocks.ResourceRecord, []string) (BlockHandler, error)
// Error codes
var (
@@ -70,7 +72,7 @@ type BlockHandler interface {
// processing. The handler can inspect the remaining labels in a path
// if required. The method returns an error if a record is not accepted
// by the block handler (RR not of required type).
- AddRecord(rr *message.ResourceRecord, labels []string) error
+ AddRecord(rr *blocks.ResourceRecord, labels []string) error
// Coexist checks if a custom block handler can co-exist with other
// resource records in the same block. 'cm' maps the resource type
@@ -80,7 +82,7 @@ type BlockHandler interface {
// Records returns a list of RR of the given types associated with
// the custom handler
- Records(kind RRTypeList) *message.RecordSet
+ Records(kind RRTypeList) *blocks.RecordSet
// Name returns the human-readable name of the handler
Name() string
@@ -108,7 +110,7 @@ type BlockHandlerList struct {
// NewBlockHandlerList instantiates an a list of active block handlers
// for a given set of records (GNS block).
-func NewBlockHandlerList(records []*message.ResourceRecord, labels []string)
(*BlockHandlerList, []*message.ResourceRecord, error) {
+func NewBlockHandlerList(records []*blocks.ResourceRecord, labels []string)
(*BlockHandlerList, []*blocks.ResourceRecord, error) {
// initialize block handler list
hl := &BlockHandlerList{
list: make(map[enums.GNSType]BlockHandler),
@@ -116,19 +118,19 @@ func NewBlockHandlerList(records
[]*message.ResourceRecord, labels []string) (*B
}
// first pass: build list of shadow records in this block
- shadows := make([]*message.ResourceRecord, 0)
+ shadows := make([]*blocks.ResourceRecord, 0)
for _, rec := range records {
// filter out shadow records...
- if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 {
+ if (rec.Flags & enums.GNS_FLAG_SHADOW) != 0 {
shadows = append(shadows, rec)
}
}
// second pass: normalize block by filtering out expired records (and
// replacing them with shadow records if available
- active := make([]*message.ResourceRecord, 0)
+ active := make([]*blocks.ResourceRecord, 0)
for _, rec := range records {
// don't process shadow records again
- if (int(rec.Flags) & enums.GNS_FLAG_SHADOW) != 0 {
+ if (rec.Flags & enums.GNS_FLAG_SHADOW) != 0 {
continue
}
// check for expired record
@@ -137,7 +139,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord,
labels []string) (*B
for _, shadow := range shadows {
if shadow.RType == rec.RType &&
!shadow.Expire.Expired() {
// deliver un-expired shadow record
instead.
- shadow.Flags &^=
uint32(enums.GNS_FLAG_SHADOW)
+ shadow.Flags &^= enums.GNS_FLAG_SHADOW
active = append(active, shadow)
}
}
@@ -149,11 +151,11 @@ func NewBlockHandlerList(records
[]*message.ResourceRecord, labels []string) (*B
// Third pass: Traverse active list and build list of handler instances.
for _, rec := range active {
// update counter map for non-supplemental records
- if (int(rec.Flags) & enums.GNS_FLAG_SUPPL) != 0 {
+ if (rec.Flags & enums.GNS_FLAG_SUPPL) != 0 {
logger.Printf(logger.DBG, "[gns] handler_list: skip
%v\n", rec)
continue
}
- rrType := enums.GNSType(rec.RType)
+ rrType := rec.RType
hl.counts.Add(rrType)
// check for custom handler type
@@ -205,7 +207,7 @@ func (hl *BlockHandlerList) GetHandler(types
...enums.GNSType) BlockHandler {
}
// FinalizeRecord post-processes records
-func (hl *BlockHandlerList) FinalizeRecord(rec *message.ResourceRecord)
*message.ResourceRecord {
+func (hl *BlockHandlerList) FinalizeRecord(rec *blocks.ResourceRecord)
*blocks.ResourceRecord {
// no implementation yet
return rec
}
@@ -216,13 +218,13 @@ func (hl *BlockHandlerList) FinalizeRecord(rec
*message.ResourceRecord) *message
// ZoneKeyHandler implementing the BlockHandler interface
type ZoneKeyHandler struct {
- ztype uint32 // zone type
- zkey *crypto.ZoneKey // Zone key
- rec *message.ResourceRecord // associated recource record
+ ztype enums.GNSType // zone type
+ zkey *crypto.ZoneKey // Zone key
+ rec *blocks.ResourceRecord // associated recource record
}
// NewZoneHandler returns a new BlockHandler instance
-func NewZoneHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
+func NewZoneHandler(rec *blocks.ResourceRecord, labels []string)
(BlockHandler, error) {
// check if we have an implementation for the zone type
if crypto.GetImplementation(rec.RType) == nil {
return nil, ErrInvalidRecordType
@@ -240,7 +242,7 @@ func NewZoneHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandler,
}
// AddRecord inserts a PKEY record into the handler.
-func (h *ZoneKeyHandler) AddRecord(rec *message.ResourceRecord, labels
[]string) (err error) {
+func (h *ZoneKeyHandler) AddRecord(rec *blocks.ResourceRecord, labels
[]string) (err error) {
// check record type
if rec.RType != h.ztype {
return ErrInvalidRecordType
@@ -266,8 +268,8 @@ func (h *ZoneKeyHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *ZoneKeyHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *ZoneKeyHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
if kind.HasType(enums.GNS_TYPE_PKEY) {
rs.AddRecord(h.rec)
}
@@ -285,20 +287,20 @@ func (h *ZoneKeyHandler) Name() string {
// Gns2DnsHandler implementing the BlockHandler interface
type Gns2DnsHandler struct {
- Query string // DNS query name
- Servers []string // DNS servers to ask
- recs []*message.ResourceRecord // list of rersource records
+ Query string // DNS query name
+ Servers []string // DNS servers to ask
+ recs []*blocks.ResourceRecord // list of rersource records
}
// NewGns2DnsHandler returns a new BlockHandler instance
-func NewGns2DnsHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_GNS2DNS {
+func NewGns2DnsHandler(rec *blocks.ResourceRecord, labels []string)
(BlockHandler, error) {
+ if rec.RType != enums.GNS_TYPE_GNS2DNS {
return nil, ErrInvalidRecordType
}
h := &Gns2DnsHandler{
Query: "",
Servers: make([]string, 0),
- recs: make([]*message.ResourceRecord, 0),
+ recs: make([]*blocks.ResourceRecord, 0),
}
if err := h.AddRecord(rec, labels); err != nil {
return nil, err
@@ -307,8 +309,8 @@ func NewGns2DnsHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandl
}
// AddRecord inserts a GNS2DNS record into the handler.
-func (h *Gns2DnsHandler) AddRecord(rec *message.ResourceRecord, labels
[]string) error {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_GNS2DNS {
+func (h *Gns2DnsHandler) AddRecord(rec *blocks.ResourceRecord, labels
[]string) error {
+ if rec.RType != enums.GNS_TYPE_GNS2DNS {
return ErrInvalidRecordType
}
logger.Printf(logger.DBG, "[gns] GNS2DNS data: %s\n",
hex.EncodeToString(rec.Data))
@@ -341,8 +343,8 @@ func (h *Gns2DnsHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *Gns2DnsHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *Gns2DnsHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
if kind.HasType(enums.GNS_TYPE_GNS2DNS) {
for _, rec := range h.recs {
rs.AddRecord(rec)
@@ -360,14 +362,21 @@ func (h *Gns2DnsHandler) Name() string {
// BOX handler
//----------------------------------------------------------------------
+// Box record for handler logic
+type Box struct {
+ rr.BOX
+ key string // map key for box instance
+ rec *blocks.ResourceRecord // originating RR
+}
+
// BoxHandler implementing the BlockHandler interface
type BoxHandler struct {
boxes map[string]*Box // map of found boxes
}
// NewBoxHandler returns a new BlockHandler instance
-func NewBoxHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_BOX {
+func NewBoxHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler,
error) {
+ if rec.RType != enums.GNS_TYPE_BOX {
return nil, ErrInvalidRecordType
}
h := &BoxHandler{
@@ -380,8 +389,8 @@ func NewBoxHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandler,
}
// AddRecord inserts a BOX record into the handler.
-func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string)
error {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_BOX {
+func (h *BoxHandler) AddRecord(rec *blocks.ResourceRecord, labels []string)
error {
+ if rec.RType != enums.GNS_TYPE_BOX {
return ErrInvalidRecordType
}
logger.Printf(logger.DBG, "[box-rr] for labels %v\n", labels)
@@ -395,7 +404,12 @@ func (h *BoxHandler) AddRecord(rec
*message.ResourceRecord, labels []string) err
return nil
}
// (3) check of "svc" and "proto" match values in the BOX
- box := NewBox(rec)
+ hsh := sha256.Sum256(rec.Data)
+ box := &Box{
+ BOX: *rr.NewBOX(rec.Data),
+ key: hex.EncodeToString(hsh[:8]),
+ rec: rec,
+ }
if box.Matches(labels) {
logger.Println(logger.DBG, "[box-rr] MATCH -- adding record")
h.boxes[box.key] = box
@@ -411,12 +425,12 @@ func (h *BoxHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *BoxHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *BoxHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
for _, box := range h.boxes {
- if kind.HasType(enums.GNSType(box.Type)) {
+ if kind.HasType(box.Type) {
// valid box found: assemble new resource record.
- rr := new(message.ResourceRecord)
+ rr := new(blocks.ResourceRecord)
rr.Expire = box.rec.Expire
rr.Flags = box.rec.Flags
rr.RType = box.Type
@@ -440,12 +454,12 @@ func (h *BoxHandler) Name() string {
// LehoHandler implementing the BlockHandler interface
type LehoHandler struct {
name string
- rec *message.ResourceRecord
+ rec *blocks.ResourceRecord
}
// NewLehoHandler returns a new BlockHandler instance
-func NewLehoHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_LEHO {
+func NewLehoHandler(rec *blocks.ResourceRecord, labels []string)
(BlockHandler, error) {
+ if rec.RType != enums.GNS_TYPE_LEHO {
return nil, ErrInvalidRecordType
}
h := &LehoHandler{
@@ -458,8 +472,8 @@ func NewLehoHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandler,
}
// AddRecord inserts a LEHO record into the handler.
-func (h *LehoHandler) AddRecord(rec *message.ResourceRecord, labels []string)
error {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_LEHO {
+func (h *LehoHandler) AddRecord(rec *blocks.ResourceRecord, labels []string)
error {
+ if rec.RType != enums.GNS_TYPE_LEHO {
return ErrInvalidRecordType
}
h.name = string(rec.Data)
@@ -475,8 +489,8 @@ func (h *LehoHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *LehoHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *LehoHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
if kind.HasType(enums.GNS_TYPE_LEHO) {
rs.AddRecord(h.rec)
}
@@ -495,12 +509,12 @@ func (h *LehoHandler) Name() string {
// CnameHandler implementing the BlockHandler interface
type CnameHandler struct {
name string
- rec *message.ResourceRecord
+ rec *blocks.ResourceRecord
}
// NewCnameHandler returns a new BlockHandler instance
-func NewCnameHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_DNS_CNAME {
+func NewCnameHandler(rec *blocks.ResourceRecord, labels []string)
(BlockHandler, error) {
+ if rec.RType != enums.GNS_TYPE_DNS_CNAME {
return nil, ErrInvalidRecordType
}
h := &CnameHandler{
@@ -513,8 +527,8 @@ func NewCnameHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandler
}
// AddRecord inserts a CNAME record into the handler.
-func (h *CnameHandler) AddRecord(rec *message.ResourceRecord, labels []string)
error {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_DNS_CNAME {
+func (h *CnameHandler) AddRecord(rec *blocks.ResourceRecord, labels []string)
error {
+ if rec.RType != enums.GNS_TYPE_DNS_CNAME {
return ErrInvalidRecordType
}
if h.rec != nil {
@@ -533,8 +547,8 @@ func (h *CnameHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *CnameHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *CnameHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
if kind.HasType(enums.GNS_TYPE_DNS_CNAME) {
rs.AddRecord(h.rec)
}
@@ -552,12 +566,12 @@ func (h *CnameHandler) Name() string {
// VpnHandler implementing the BlockHandler interface
type VpnHandler struct {
- rec *message.ResourceRecord
+ rec *blocks.ResourceRecord
}
// NewVpnHandler returns a new BlockHandler instance
-func NewVpnHandler(rec *message.ResourceRecord, labels []string)
(BlockHandler, error) {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_VPN {
+func NewVpnHandler(rec *blocks.ResourceRecord, labels []string) (BlockHandler,
error) {
+ if rec.RType != enums.GNS_TYPE_VPN {
return nil, ErrInvalidRecordType
}
h := &VpnHandler{}
@@ -568,8 +582,8 @@ func NewVpnHandler(rec *message.ResourceRecord, labels
[]string) (BlockHandler,
}
// AddRecord inserts a VPN record into the handler.
-func (h *VpnHandler) AddRecord(rec *message.ResourceRecord, labels []string)
error {
- if enums.GNSType(rec.RType) != enums.GNS_TYPE_VPN {
+func (h *VpnHandler) AddRecord(rec *blocks.ResourceRecord, labels []string)
error {
+ if rec.RType != enums.GNS_TYPE_VPN {
return ErrInvalidRecordType
}
if h.rec != nil {
@@ -587,8 +601,8 @@ func (h *VpnHandler) Coexist(cm
util.Counter[enums.GNSType]) bool {
}
// Records returns a list of RR of the given type associated with this handler
-func (h *VpnHandler) Records(kind RRTypeList) *message.RecordSet {
- rs := message.NewRecordSet()
+func (h *VpnHandler) Records(kind RRTypeList) *blocks.RecordSet {
+ rs := blocks.NewRecordSet()
if kind.HasType(enums.GNS_TYPE_VPN) {
rs.AddRecord(h.rec)
}
diff --git a/src/gnunet/service/gns/box.go b/src/gnunet/service/gns/box.go
deleted file mode 100644
index f97471e..0000000
--- a/src/gnunet/service/gns/box.go
+++ /dev/null
@@ -1,169 +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 gns
-
-import (
- "encoding/hex"
- "strconv"
- "strings"
-
- "gnunet/message"
-
- "github.com/bfix/gospel/data"
- "github.com/bfix/gospel/logger"
-)
-
-// Box is an encapsulated RR for special names
-type Box struct {
- Proto uint16 `order:"big"` // Protcol identifier
- Svc uint16 `order:"big"` // Service identifier
- Type uint32 `order:"big"` // Type of embedded RR
- RR []byte `size:"*"` // embedded RR
-
- // transient attributes (not serialized)
- key string // map key for box instance
- rec *message.ResourceRecord // originating RR
-}
-
-// NewBox creates a new box instance from a BOX resource record.
-func NewBox(rec *message.ResourceRecord) *Box {
- b := new(Box)
- if err := data.Unmarshal(b, rec.Data); err != nil {
- logger.Printf(logger.ERROR, "[gns] Can't unmarshal BOX")
- return nil
- }
- b.key = hex.EncodeToString(rec.Data[:8])
- b.rec = rec
- return b
-}
-
-// Matches verifies that the remaining labels comply with the values
-// in the BOX record.
-func (b *Box) Matches(labels []string) bool {
- // resolve protocol and service names
- proto, protoName := GetProtocol(labels[0])
- svc, _ := GetService(labels[1], protoName)
- // no match on invalid resolution
- if proto == 0 || svc == 0 {
- return false
- }
- // check for matching values in box
- return proto == b.Proto && svc == b.Svc
-}
-
-//----------------------------------------------------------------------
-// helper functions
-
-// list of handled protocols in BOX records
-var protocols = map[string]int{
- "icmp": 1,
- "igmp": 2,
- "tcp": 6,
- "udp": 17,
- "ipv6-icmp": 58,
-}
-
-// GetProtocol returns the protocol number and name for a given name. The
-// name can be an integer value (e.g. "_6" for "tcp") or a mnemonic name
-// (e.g. like "_tcp").
-func GetProtocol(name string) (uint16, string) {
- // check for required prefix
- if name[0] != '_' {
- return 0, ""
- }
- name = strings.ToLower(name[1:])
-
- // if label is an integer value it is the protocol number
- if val, err := strconv.Atoi(name); err == nil {
- // check for valid number (reverse protocol lookup)
- for label, id := range protocols {
- if id == val {
- // return found entry
- return uint16(val), label
- }
- }
- // number out of range
- return 0, ""
- }
- // try to resolve via protocol map
- if id, ok := protocols[name]; ok {
- return uint16(id), name
- }
- // resolution failed
- return 0, ""
-}
-
-// list of services (per protocol) handled in BOX records
-var services = map[string]map[string]int{
- "udp": {
- "domain": 53,
- },
- "tcp": {
- "ftp": 21,
- "ftps": 990,
- "gopher": 70,
- "http": 80,
- "https": 443,
- "imap2": 143,
- "imap3": 220,
- "imaps": 993,
- "pop3": 110,
- "pop3s": 995,
- "smtp": 25,
- "ssh": 22,
- "telnet": 23,
- },
-}
-
-// GetService returns the port number and the name of a service (with given
-// protocol). The name can be an integer value (e.g. "_443" for "https") or
-// a mnemonic name (e.g. like "_https").
-func GetService(name, proto string) (uint16, string) {
- // check for required prefix
- if name[0] != '_' {
- return 0, ""
- }
- name = strings.ToLower(name[1:])
-
- // get list of services for given protocol
- svcs, ok := services[proto]
- if !ok {
- // no services available for this protocol
- return 0, ""
- }
-
- // if label is an integer value it is the port number
- if val, err := strconv.Atoi(name); err == nil {
- // check for valid number (reverse service lookup)
- for label, id := range svcs {
- if id == val {
- // return found entry
- return uint16(val), label
- }
- }
- // number out of range
- return 0, ""
- }
- // try to resolve via services map
- if id, ok := svcs[name]; ok {
- return uint16(id), name
- }
- // resolution failed
- return 0, ""
-}
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 5942b94..32c71e9 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -27,7 +27,7 @@ import (
"gnunet/crypto"
"gnunet/enums"
- "gnunet/message"
+ "gnunet/service/dht/blocks"
"gnunet/util"
"github.com/bfix/gospel/logger"
@@ -116,7 +116,7 @@ func DNSNameFromBytes(b []byte, offset int) (int, string) {
}
// QueryDNS queries the specified DNS server for a given name and expected
result types.
-func QueryDNS(id int, name string, server net.IP, kind RRTypeList)
*message.RecordSet {
+func QueryDNS(id int, name string, server net.IP, kind RRTypeList)
*blocks.RecordSet {
// get default nameserver if not defined.
if server == nil {
server = net.IPv4(8, 8, 8, 8)
@@ -161,7 +161,7 @@ func QueryDNS(id int, name string, server net.IP, kind
RRTypeList) *message.Reco
logger.Printf(logger.ERROR, "[dns][%d] No results\n",
id)
return nil
}
- set := message.NewRecordSet()
+ set := blocks.NewRecordSet()
for _, record := range in.Answer {
// check if answer record is of requested type
if kind.HasType(enums.GNSType(record.Header().Rrtype)) {
@@ -174,11 +174,11 @@ func QueryDNS(id int, name string, server net.IP, kind
RRTypeList) *message.Reco
}
// create a new GNS resource record
- rr := new(message.ResourceRecord)
+ rr := new(blocks.ResourceRecord)
expires :=
time.Now().Add(time.Duration(record.Header().Ttl) * time.Second)
rr.Expire = util.NewAbsoluteTime(expires)
rr.Flags = 0
- rr.RType = uint32(record.Header().Rrtype)
+ rr.RType = enums.GNSType(record.Header().Rrtype)
rr.Size = uint32(record.Header().Rdlength)
rr.Data = make([]byte, rr.Size)
@@ -210,11 +210,11 @@ func (m *Module) ResolveDNS(
servers []string,
kind RRTypeList,
zkey *crypto.ZoneKey,
- depth int) (set *message.RecordSet, err error) {
+ depth int) (set *blocks.RecordSet, err error) {
// start DNS queries concurrently
logger.Printf(logger.DBG, "[dns] Resolution of '%s' starting...\n",
name)
- res := make(chan *message.RecordSet)
+ res := make(chan *blocks.RecordSet)
running := 0
for _, srv := range servers {
// check if srv is an IPv4/IPv6 address
@@ -230,7 +230,7 @@ func (m *Module) ResolveDNS(
// traverse resource records for 'A' and 'AAAA' records.
rec_loop:
for _, rec := range set.Records {
- switch enums.GNSType(rec.RType) {
+ switch rec.RType {
case enums.GNS_TYPE_DNS_AAAA:
addr = net.IP(rec.Data)
// we prefer IPv6
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index f12a89a..37bbfc5 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -27,7 +27,6 @@ import (
"gnunet/core"
"gnunet/crypto"
"gnunet/enums"
- "gnunet/message"
"gnunet/service"
"gnunet/service/dht/blocks"
"gnunet/service/revocation"
@@ -102,9 +101,11 @@ func NewModule(ctx context.Context, c *core.Core) (m
*Module) {
m = &Module{
ModuleImpl: *service.NewModuleImpl(),
}
- // register as listener for core events
- listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
- c.Register("gns", listener)
+ if c != nil {
+ // register as listener for core events
+ listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
+ c.Register("gns", listener)
+ }
return
}
@@ -152,7 +153,7 @@ func (m *Module) Resolve(
zkey *crypto.ZoneKey,
kind RRTypeList,
mode int,
- depth int) (set *message.RecordSet, err error) {
+ depth int) (set *blocks.RecordSet, err error) {
// check for recursion depth
if depth > config.Cfg.GNS.MaxDepth {
@@ -178,7 +179,7 @@ func (m *Module) ResolveAbsolute(
labels []string,
kind RRTypeList,
mode int,
- depth int) (set *message.RecordSet, err error) {
+ depth int) (set *blocks.RecordSet, err error) {
// get the zone key for the TLD
zkey := m.GetZoneKey(labels[0])
@@ -189,7 +190,7 @@ func (m *Module) ResolveAbsolute(
}
// check if zone key has been revoked
var valid bool
- set = message.NewRecordSet()
+ set = blocks.NewRecordSet()
if valid, err = m.RevocationQuery(ctx, zkey); err != nil || !valid {
return
}
@@ -208,12 +209,12 @@ func (m *Module) ResolveRelative(
zkey *crypto.ZoneKey,
kind RRTypeList,
mode int,
- depth int) (set *message.RecordSet, err error) {
+ depth int) (set *blocks.RecordSet, err error) {
// Process all names in sequence
var (
- records []*message.ResourceRecord // final resource records
from resolution
- hdlrs *BlockHandlerList // list of block handlers in
final step
+ records []*blocks.ResourceRecord // final resource records from
resolution
+ hdlrs *BlockHandlerList // list of block handlers in
final step
)
for ; len(labels) > 0; labels = labels[1:] {
logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in
'%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes()))
@@ -229,7 +230,7 @@ func (m *Module) ResolveRelative(
// if we have no results at this point, return NXDOMAIN
if block == nil {
// return record set with no entries as signal
for NXDOMAIN
- set = message.NewRecordSet()
+ set = blocks.NewRecordSet()
return
}
mode = enums.GNS_LO_DEFAULT
@@ -270,7 +271,7 @@ func (m *Module) ResolveRelative(
var valid bool
if valid, err = m.RevocationQuery(ctx, inst.zkey); err
!= nil || !valid {
// revoked key -> no results!
- records = make([]*message.ResourceRecord, 0)
+ records = make([]*blocks.ResourceRecord, 0)
break
}
} else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS);
hdlr != nil {
@@ -338,10 +339,10 @@ func (m *Module) ResolveRelative(
}
// Assemble resulting resource record set by filtering for requested
types.
// Records might get transformed by active block handlers.
- set = message.NewRecordSet()
+ set = blocks.NewRecordSet()
for _, rec := range records {
// is this the record type we are looking for?
- if kind.HasType(enums.GNSType(rec.RType)) {
+ if kind.HasType(rec.RType) {
// add it to the result
if rec = hdlrs.FinalizeRecord(rec); rec != nil {
set.AddRecord(rec)
@@ -364,7 +365,7 @@ func (m *Module) ResolveRelative(
// asking for explicitly.
if set.Count > 0 {
for _, rec := range records {
- if !kind.HasType(enums.GNSType(rec.RType)) &&
(int(rec.Flags)&enums.GNS_FLAG_SUPPL) != 0 {
+ if !kind.HasType(rec.RType) &&
(rec.Flags&enums.GNS_FLAG_SUPPL) != 0 {
set.AddRecord(rec)
}
}
@@ -383,7 +384,7 @@ func (m *Module) ResolveUnknown(
labels []string,
zkey *crypto.ZoneKey,
kind RRTypeList,
- depth int) (set *message.RecordSet, err error) {
+ depth int) (set *blocks.RecordSet, err error) {
// relative GNS-based server name?
if strings.HasSuffix(name, ".+") {
@@ -471,11 +472,11 @@ func (m *Module) Lookup(
}
// newLEHORecord creates a new supplemental GNS record of type LEHO.
-func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime)
*message.ResourceRecord {
- rr := new(message.ResourceRecord)
+func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime)
*blocks.ResourceRecord {
+ rr := new(blocks.ResourceRecord)
rr.Expire = expires
- rr.Flags = uint32(enums.GNS_FLAG_SUPPL)
- rr.RType = uint32(enums.GNS_TYPE_LEHO)
+ rr.Flags = enums.GNS_FLAG_SUPPL
+ rr.RType = enums.GNS_TYPE_LEHO
rr.Size = uint32(len(name) + 1)
rr.Data = make([]byte, rr.Size)
copy(rr.Data, []byte(name))
@@ -484,9 +485,9 @@ func (m *Module) newLEHORecord(name string, expires
util.AbsoluteTime) *message.
}
// Records returns the list of resource records from binary data.
-func (m *Module) records(buf []byte) ([]*message.ResourceRecord, error) {
+func (m *Module) records(buf []byte) ([]*blocks.ResourceRecord, error) {
// parse data into record set
- rs := message.NewRecordSet()
+ rs := blocks.NewRecordSet()
if err := data.Unmarshal(rs, buf); err != nil {
return nil, err
}
diff --git a/src/gnunet/service/gns/rr/coexist.go
b/src/gnunet/service/gns/rr/coexist.go
new file mode 100644
index 0000000..1037b11
--- /dev/null
+++ b/src/gnunet/service/gns/rr/coexist.go
@@ -0,0 +1,146 @@
+// 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 rr
+
+import (
+ "errors"
+ "gnunet/enums"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/data"
+)
+
+// RR interface for resource records
+type RR interface {
+ // Coexist checks if a new resource record could coexist with given set
+ // of records under a label (can be called with a nil receiver)
+ Coexist(list []*enums.GNSSpec, label string) (bool, enums.GNSFlag)
+
+ // ToMap adds the RR attributes to a stringed map
+ ToMap(map[string]string, string)
+}
+
+// CanCoexist checks if a (new) resource record of type 't' can coexist
+// with a given set of resource records. If ok is true, it can enforce
+// flags for the new record.
+func CanCoexist(t enums.GNSType, list []*enums.GNSSpec, label string) (ok
bool, forced enums.GNSFlag) {
+ rr := NilRR(t)
+ if rr == nil {
+ return true, 0
+ }
+ // check if new record against list
+ if ok, forced = rr.Coexist(list, label); !ok {
+ return
+ }
+ // now check if each existing record can coexists with a modified list
+ // swpping new record and tested record.
+ testList := util.Clone(list)
+ eNew := &enums.GNSSpec{
+ Type: t,
+ Flags: forced,
+ }
+ for i, e := range testList {
+ testList[i] = eNew
+ ok, forced = NilRR(e.Type).Coexist(testList, label)
+ if !ok {
+ return
+ }
+ eNew.Flags |= forced
+ testList[i] = e
+ }
+ // all checks passed
+ forced = eNew.Flags
+ return
+}
+
+// ParseRR returns a RR instance from data for given type
+func ParseRR(t enums.GNSType, buf []byte) (rr RR, err error) {
+ // get record instance
+ if rr = NewRR(t); rr == nil {
+ err = errors.New("parse RR failed")
+ return
+ }
+ // reconstruct record
+ err = data.Unmarshal(rr, buf)
+ return
+}
+
+// NewRR returns a new RR instance of given type
+func NewRR(t enums.GNSType) RR {
+ switch t {
+ case enums.GNS_TYPE_PKEY:
+ return new(PKEY)
+ case enums.GNS_TYPE_EDKEY:
+ return new(EDKEY)
+ case enums.GNS_TYPE_REDIRECT:
+ return (*REDIRECT)(nil)
+ case enums.GNS_TYPE_NICK:
+ return new(NICK)
+ case enums.GNS_TYPE_LEHO:
+ return new(LEHO)
+ case enums.GNS_TYPE_GNS2DNS:
+ return new(GNS2DNS)
+ case enums.GNS_TYPE_BOX:
+ return new(BOX)
+ case enums.GNS_TYPE_DNS_CNAME:
+ return new(CNAME)
+ case enums.GNS_TYPE_DNS_A:
+ return new(DNSA)
+ case enums.GNS_TYPE_DNS_AAAA:
+ return new(DNSAAAA)
+ case enums.GNS_TYPE_DNS_MX:
+ return new(MX)
+ case enums.GNS_TYPE_DNS_TXT:
+ return new(TXT)
+ }
+ return nil
+}
+
+// NilRR returns a typed nil reference to a RR that can be used to
+// call type methods that allow a nil receiver.
+func NilRR(t enums.GNSType) RR {
+ switch t {
+ case enums.GNS_TYPE_PKEY:
+ return (*PKEY)(nil)
+ case enums.GNS_TYPE_EDKEY:
+ return (*EDKEY)(nil)
+ case enums.GNS_TYPE_REDIRECT:
+ return (*REDIRECT)(nil)
+ case enums.GNS_TYPE_NICK:
+ return (*NICK)(nil)
+ case enums.GNS_TYPE_LEHO:
+ return (*LEHO)(nil)
+ case enums.GNS_TYPE_GNS2DNS:
+ return (*GNS2DNS)(nil)
+ case enums.GNS_TYPE_BOX:
+ return (*BOX)(nil)
+ case enums.GNS_TYPE_DNS_CNAME:
+ return (*CNAME)(nil)
+ case enums.GNS_TYPE_DNS_A:
+ return (*DNSA)(nil)
+ case enums.GNS_TYPE_DNS_AAAA:
+ return (*DNSAAAA)(nil)
+ case enums.GNS_TYPE_DNS_MX:
+ return (*MX)(nil)
+ case enums.GNS_TYPE_DNS_TXT:
+ return (*TXT)(nil)
+ }
+ // return untyped nil
+ return nil
+}
diff --git a/src/gnunet/service/gns/rr/dns.go b/src/gnunet/service/gns/rr/dns.go
new file mode 100644
index 0000000..a98b529
--- /dev/null
+++ b/src/gnunet/service/gns/rr/dns.go
@@ -0,0 +1,119 @@
+// 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 rr
+
+import (
+ "fmt"
+ "gnunet/enums"
+ "net"
+)
+
+//----------------------------------------------------------------------
+// DNS-related resource records
+//----------------------------------------------------------------------
+
+// DNS CNAME record
+type CNAME struct {
+ Name string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *CNAME) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *CNAME) ToMap(params map[string]string, prefix string) {
+ params[prefix+"name"] = rr.Name
+}
+
+//----------------------------------------------------------------------
+
+// DNS TXT record
+type TXT struct {
+ Text string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *TXT) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *TXT) ToMap(params map[string]string, prefix string) {
+ params[prefix+"text"] = rr.Text
+}
+
+//----------------------------------------------------------------------
+
+// DNS IPv4 address
+type DNSA struct {
+ Addr net.IP `size:"16"`
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *DNSA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *DNSA) ToMap(params map[string]string, prefix string) {
+ params[prefix+"addr"] = rr.Addr.String()
+}
+
+//----------------------------------------------------------------------
+
+// DNS IPv6 address
+type DNSAAAA struct {
+ Addr net.IP `size:"16"`
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *DNSAAAA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *DNSAAAA) ToMap(params map[string]string, prefix string) {
+ params[prefix+"addr"] = rr.Addr.String()
+}
+
+//----------------------------------------------------------------------
+
+// MX is a DNS MX record
+type MX struct {
+ Prio uint16 `order:"big"`
+ Server string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *MX) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *MX) ToMap(params map[string]string, prefix string) {
+ params[prefix+"prio"] = fmt.Sprintf("%d", rr.Prio)
+ params[prefix+"host"] = rr.Server
+}
diff --git a/src/gnunet/service/gns/rr/gns.go b/src/gnunet/service/gns/rr/gns.go
new file mode 100644
index 0000000..1926a4a
--- /dev/null
+++ b/src/gnunet/service/gns/rr/gns.go
@@ -0,0 +1,199 @@
+// 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 rr
+
+import (
+ "gnunet/crypto"
+ "gnunet/enums"
+)
+
+//----------------------------------------------------------------------
+// GNS resource records
+//----------------------------------------------------------------------
+
+// PKEY (Ed25519+EcDSA) zone key
+type PKEY struct {
+ *crypto.ZoneKey
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *PKEY) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced
enums.GNSFlag) {
+ // can't add PKEY to apex label
+ if label == "@" {
+ return
+ }
+ // make sure all existing records are PKEYs too
+ for _, e := range list {
+ if e.Type != enums.GNS_TYPE_PKEY && e.Type !=
enums.GNS_TYPE_EDKEY {
+ // check failed on non-PKEY
+ return
+ }
+ // check for active PKEY
+ if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
+ // only additional shaow records allowed
+ forced = enums.GNS_FLAG_SHADOW
+ }
+ }
+ ok = true
+ return
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *PKEY) ToMap(params map[string]string, prefix string) {
+ params[prefix+"data"] = rr.ID()
+}
+
+//----------------------------------------------------------------------
+
+// EDKEY (EdDSA) zone key
+type EDKEY struct {
+ *crypto.ZoneKey
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *EDKEY) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced
enums.GNSFlag) {
+ // can't add EDKEY to apex label
+ if label == "@" {
+ return
+ }
+ // make sure all existing records are EDKEYs too
+ for _, e := range list {
+ if e.Type != enums.GNS_TYPE_EDKEY && e.Type !=
enums.GNS_TYPE_PKEY {
+ // check failed on non-EDKEY
+ return
+ }
+ // check for active PKEY
+ if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
+ // only additional shaow records allowed
+ forced = enums.GNS_FLAG_SHADOW
+ }
+ }
+ ok = true
+ return
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *EDKEY) ToMap(params map[string]string, prefix string) {
+ params[prefix+"data"] = rr.ID()
+}
+
+//----------------------------------------------------------------------
+
+// REDIRECT to name
+type REDIRECT struct {
+ Name string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *REDIRECT) Coexist(list []*enums.GNSSpec, label string) (ok bool,
forced enums.GNSFlag) {
+ // no REDIRECT in apex zone
+ if label == "@" {
+ return
+ }
+ // make sure all existing records are supplemental EDKEYs too
+ for _, e := range list {
+ if e.Type != enums.GNS_TYPE_REDIRECT &&
e.Flags&enums.GNS_FLAG_SUPPL == 0 {
+ // check failed on non-supplemental non-REDIRECT record
+ return
+ }
+ // check for active REDIRECT
+ if e.Flags&enums.GNS_FLAG_SHADOW == 0 {
+ // only additional shaow records allowed
+ forced = enums.GNS_FLAG_SHADOW
+ }
+ }
+ ok = true
+ return
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *REDIRECT) ToMap(params map[string]string, prefix string) {
+ params[prefix+"name"] = rr.Name
+}
+
+//----------------------------------------------------------------------
+
+// GNS NICK record
+type NICK struct {
+ Name string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *NICK) Coexist(list []*enums.GNSSpec, label string) (ok bool, forced
enums.GNSFlag) {
+ // can only be added to the apex label
+ if label != "@" {
+ return
+ }
+ // only one un-shadowed NICK allowed
+ for _, e := range list {
+ if e.Type == enums.GNS_TYPE_NICK &&
e.Flags&enums.GNS_FLAG_SHADOW == 0 {
+ // only additional shadow records allowed
+ forced = enums.GNS_FLAG_SHADOW
+ }
+ }
+ ok = true
+ return
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *NICK) ToMap(params map[string]string, prefix string) {
+ params[prefix+"name"] = rr.Name
+}
+
+//----------------------------------------------------------------------
+
+// LEHO record
+type LEHO struct {
+ Name string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *LEHO) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *LEHO) ToMap(params map[string]string, prefix string) {
+ params[prefix+"name"] = rr.Name
+}
+
+//----------------------------------------------------------------------
+
+// GNS2DNS delegation
+type GNS2DNS struct {
+ Name string
+ Server string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *GNS2DNS) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *GNS2DNS) ToMap(params map[string]string, prefix string) {
+ params[prefix+"name"] = rr.Name
+ params[prefix+"server"] = rr.Server
+}
diff --git a/src/gnunet/service/gns/rr/gns_box.go
b/src/gnunet/service/gns/rr/gns_box.go
new file mode 100644
index 0000000..5123beb
--- /dev/null
+++ b/src/gnunet/service/gns/rr/gns_box.go
@@ -0,0 +1,375 @@
+// 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 rr
+
+import (
+ "encoding/hex"
+ "strconv"
+ "strings"
+
+ "gnunet/enums"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
+)
+
+//----------------------------------------------------------------------
+// GNS box record that embeds either a TLSA or SRV record
+//----------------------------------------------------------------------
+
+// BOX is an encapsulated RR for special names
+type BOX struct {
+ Proto uint16 `order:"big"` // Protcol identifier
+ Svc uint16 `order:"big"` // Service identifier
+ Type enums.GNSType `order:"big"` // Type of embedded RR
+ RR []byte `size:"*"` // embedded RR
+}
+
+// NewBOX creates a new box instance from a BOX resource record data.
+func NewBOX(buf []byte) *BOX {
+ b := new(BOX)
+ if err := data.Unmarshal(b, buf); err != nil {
+ logger.Printf(logger.ERROR, "[gns] Can't unmarshal BOX")
+ return nil
+ }
+ return b
+}
+
+// Matches verifies that the remaining labels comply with the values
+// in the BOX record.
+func (b *BOX) Matches(labels []string) bool {
+ // resolve protocol and service names
+ proto, protoName := GetProtocol(labels[0])
+ svc, _ := GetService(labels[1], protoName)
+ // no match on invalid resolution
+ if proto == 0 || svc == 0 {
+ return false
+ }
+ // check for matching values in box
+ return proto == b.Proto && svc == b.Svc
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (b *BOX) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (b *BOX) ToMap(params map[string]string, prefix string) {
+ // shared attributes
+ protoS := util.CastToString(b.Proto)
+ if pn := GetProtocolName(b.Proto); len(pn) > 0 {
+ protoS += " (" + pn + ")"
+ }
+ params[prefix+"proto"] = protoS
+ svcS := util.CastToString(b.Svc)
+ if sn := GetServiceName(b.Svc, b.Proto); len(sn) > 0 {
+ svcS += " " + sn
+ }
+ params[prefix+"svc"] = svcS
+ params[prefix+"type"] = util.CastToString(int(b.Type))
+ // attributes of embedded record
+ if rr, err := b.EmbeddedRR(); err == nil && rr != nil {
+ rr.ToMap(params, prefix)
+ }
+}
+
+// EmbeddedRR returns the embedded RR as an instance
+func (b *BOX) EmbeddedRR() (rr RR, err error) {
+ switch b.Type {
+ case enums.GNS_TYPE_DNS_TLSA:
+ rr = new(TLSA)
+ case enums.GNS_TYPE_DNS_SRV:
+ rr = new(SRV)
+ }
+ err = data.Unmarshal(rr, b.RR)
+ return
+}
+
+//----------------------------------------------------------------------
+// embedded resource records
+//----------------------------------------------------------------------
+
+var (
+ // TLSAUsage for defined usage values
+ TLSAUsage = map[uint8]string{
+ 0: "CA certificate",
+ 1: "Service certificate constraint",
+ 2: "Trust anchor assertion",
+ 3: "Domain-issued certificate",
+ 255: "Private use",
+ }
+ // TLSASelector for defined selector values
+ TLSASelector = map[uint8]string{
+ 0: "Full certificate",
+ 1: "SubjectPublicKeyInfo",
+ 255: "Private use",
+ }
+ // TLSAMatch for defined match values
+ TLSAMatch = map[uint8]string{
+ 0: "No hash",
+ 1: "SHA-256",
+ 2: "SHA-512",
+ 255: "Private use",
+ }
+)
+
+// TLSA is a DNSSEC TLS asscoication
+type TLSA struct {
+ Usage uint8
+ Selector uint8
+ Match uint8
+ Cert []byte `size:"*"`
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *TLSA) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *TLSA) ToMap(params map[string]string, prefix string) {
+ params[prefix+"tlsa_usage"] = strconv.Itoa(int(rr.Usage))
+ params[prefix+"tlsa_selector"] = strconv.Itoa(int(rr.Selector))
+ params[prefix+"tlsa_match"] = strconv.Itoa(int(rr.Match))
+ params[prefix+"tlsa_cert"] = hex.EncodeToString(rr.Cert)
+}
+
+//----------------------------------------------------------------------
+
+// SRV for service definitions
+type SRV struct {
+ Host string
+}
+
+// Coexist checks if a new resource record could coexist with given set
+// of records under a label (can be called with a nil receiver)
+func (rr *SRV) Coexist([]*enums.GNSSpec, string) (bool, enums.GNSFlag) {
+ return true, 0
+}
+
+// ToMap adds the RR attributes to a stringed map
+func (rr *SRV) ToMap(params map[string]string, prefix string) {
+ params[prefix+"srv_host"] = rr.Host
+}
+
+//----------------------------------------------------------------------
+// BOX protocols
+//----------------------------------------------------------------------
+
+// list of handled protocols in BOX records
+var protocols = map[string]uint16{
+ "icmp": 1,
+ "igmp": 2,
+ "tcp": 6,
+ "udp": 17,
+ "ipv6-icmp": 58,
+}
+
+// GetProtocolName returns the name of a protocol for given nu,ber
+func GetProtocolName(proto uint16) string {
+ // check for valid number (reverse protocol lookup)
+ for label, id := range protocols {
+ if id == proto {
+ // return found entry
+ return label
+ }
+ }
+ return util.CastToString(proto)
+}
+
+// GetProtocols returns a list of supported protocols for use
+// by caller (e.g. UI handling)
+func GetProtocols() (protos map[uint16]string) {
+ protos = make(map[uint16]string)
+ for name, id := range protocols {
+ protos[id] = name
+ }
+ return
+}
+
+// GetProtocol returns the protocol number and name for a given name. The
+// name can be an integer value (e.g. "_6" for "tcp") or a mnemonic name
+// (e.g. like "_tcp").
+func GetProtocol(name string) (uint16, string) {
+ // check for required prefix
+ if name[0] != '_' {
+ return 0, ""
+ }
+ name = strings.ToLower(name[1:])
+
+ // if label is an integer value it is the protocol number
+ if val, err := strconv.Atoi(name); err == nil {
+ proto := uint16(val)
+ label := GetProtocolName(proto)
+ if len(label) == 0 {
+ proto = 0
+ }
+ return proto, label
+ }
+ // try to resolve via protocol map
+ if id, ok := protocols[name]; ok {
+ return id, name
+ }
+ // resolution failed
+ return 0, ""
+}
+
+//----------------------------------------------------------------------
+// BOX services
+//----------------------------------------------------------------------
+
+// list of services (per protocol) handled in BOX records
+var services = map[string]map[string]uint16{
+ "udp": {
+ "bootpc": 68,
+ "bootps": 67,
+ "domain": 53,
+ "gnunet": 2086,
+ "https": 443,
+ "isakmp": 500,
+ "kerberos4": 750,
+ "kerberos": 88,
+ "ldap": 389,
+ "ldaps": 636,
+ "ntp": 123,
+ "openvpn": 1194,
+ "radius": 1812,
+ "rtsp": 554,
+ "sip": 5060,
+ "sip-tls": 5061,
+ "snmp": 161,
+ "syslog": 514,
+ "tftp": 69,
+ "who": 513,
+ },
+ "tcp": {
+ "domain": 53,
+ "finger": 79,
+ "ftp": 21,
+ "ftp-data": 20,
+ "ftps": 990,
+ "ftps-data": 989,
+ "git": 9418,
+ "gnunet": 2086,
+ "gopher": 70,
+ "http": 80,
+ "https": 443,
+ "imap2": 143,
+ "imaps": 993,
+ "kerberos4": 750,
+ "kerberos": 88,
+ "kermit": 1649,
+ "ldap": 389,
+ "ldaps": 636,
+ "login": 513,
+ "mysql": 3306,
+ "openvpn": 1194,
+ "pop3": 110,
+ "pop3s": 995,
+ "printer": 515,
+ "radius": 1812,
+ "redis": 6379,
+ "rsync": 873,
+ "rtsp": 554,
+ "shell": 514,
+ "sip": 5060,
+ "sip-tls": 5061,
+ "smtp": 25,
+ "snmp": 161,
+ "ssh": 22,
+ "telnet": 23,
+ "telnets": 992,
+ "uucp": 540,
+ "webmin": 10000,
+ "x11": 6000,
+ },
+}
+
+// GetServiceName returns the service spec on given port
+func GetServiceName(svc, proto uint16) string {
+ for n, id := range services[GetProtocolName(proto)] {
+ if id == svc {
+ return n
+ }
+ }
+ return util.CastToString(svc)
+}
+
+// GetServices returns a list of supported services for use
+// by caller (e.g. UI handling)
+func GetServices() (svcs map[uint16]string) {
+ svcs = make(map[uint16]string)
+ for n, id := range services["tcp"] {
+ svcs[id] = n + " (tcp"
+ }
+ for n, id := range services["udp"] {
+ nn, ok := svcs[id]
+ if ok {
+ svcs[id] = nn + "/udp"
+ } else {
+ svcs[id] = n + " (udp"
+ }
+ }
+ for id, n := range svcs {
+ svcs[id] = n + ")"
+ }
+ return
+}
+
+// GetService returns the port number and the name of a service (with given
+// protocol). The name can be an integer value (e.g. "_443" for "https") or
+// a mnemonic name (e.g. like "_https").
+func GetService(name, proto string) (uint16, string) {
+ // check for required prefix
+ if name[0] != '_' {
+ return 0, ""
+ }
+ name = strings.ToLower(name[1:])
+
+ // get list of services for given protocol
+ svcs, ok := services[proto]
+ if !ok {
+ // no services available for this protocol
+ return 0, ""
+ }
+
+ // if label is an integer value it is the port number
+ if val, err := strconv.Atoi(name); err == nil {
+ svc := uint16(val)
+ // check for valid number (reverse service lookup)
+ for label, id := range svcs {
+ if id == svc {
+ // return found entry
+ return svc, label
+ }
+ }
+ // number out of range
+ return 0, ""
+ }
+ // try to resolve via services map
+ if id, ok := svcs[name]; ok {
+ return id, name
+ }
+ // resolution failed
+ return 0, ""
+}
diff --git a/src/gnunet/service/gns/service.go
b/src/gnunet/service/gns/service.go
index 010c460..dbbd425 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -136,7 +136,7 @@ func (s *Service) HandleMessage(ctx context.Context, sender
*util.PeerID, msg me
logger.Printf(logger.DBG, "[gns%s] Lookup
request finished.\n", label)
}()
- kind := NewRRTypeList(enums.GNSType(m.RType))
+ kind := NewRRTypeList(m.RType)
recset, err := s.Resolve(ctx, label, m.Zone, kind,
int(m.Options), 0)
if err != nil {
logger.Printf(logger.ERROR, "[gns%s] Failed to
lookup block: %s\n", label, err.Error())
@@ -159,7 +159,7 @@ func (s *Service) HandleMessage(ctx context.Context, sender
*util.PeerID, msg me
logger.Printf(logger.DBG, "[gns%s]
Record #%d: %v\n", label, i, rec)
// is this the record type we are
looking for?
- if rec.RType == m.RType ||
enums.GNSType(m.RType) == enums.GNS_TYPE_ANY {
+ if rec.RType == m.RType || m.RType ==
enums.GNS_TYPE_ANY {
// add it to the response
message
if err := resp.AddRecord(rec);
err != nil {
logger.Printf(logger.ERROR, "[gns%s] failed: %sv", label, err.Error())
@@ -192,7 +192,7 @@ func (s *Service) QueryKeyRevocation(ctx context.Context,
zkey *crypto.ZoneKey)
// get response from Revocation service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req, true); err != nil {
return
}
@@ -218,7 +218,7 @@ func (s *Service) RevokeKey(ctx context.Context, rd
*revocation.RevData) (succes
// get response from Revocation service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req, true); err != nil {
return
}
@@ -247,7 +247,7 @@ func (s *Service) LookupNamecache(ctx context.Context,
query *blocks.GNSQuery) (
// get response from Namecache service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req, true); err != nil {
return
}
@@ -308,7 +308,7 @@ func (s *Service) StoreNamecache(ctx context.Context, query
*blocks.GNSQuery, bl
// get response from Namecache service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req, true); err != nil {
return
}
diff --git a/src/gnunet/service/revocation/pow_test.go
b/src/gnunet/service/revocation/pow_test.go
index 0747d72..41c17b4 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -60,7 +60,7 @@ func TestRevocationRFC(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- prv, err := crypto.NewZonePrivate(crypto.ZONE_PKEY, d)
+ prv, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, d)
if err != nil {
t.Fatal(err)
}
diff --git a/src/gnunet/service/store/store_dht_meta.go
b/src/gnunet/service/store/store_dht_meta.go
index 64f658b..d1c4cb7 100644
--- a/src/gnunet/service/store/store_dht_meta.go
+++ b/src/gnunet/service/store/store_dht_meta.go
@@ -97,10 +97,16 @@ func OpenMetaDB(path string) (db *FileMetaDB, err error) {
// 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) {
+ // work around a SQLite3 bug when storing uint64 with high bit set
+ var exp *uint64
+ if !md.expires.IsNever() {
+ exp = new(uint64)
+ *exp = md.expires.Val
+ }
sql := "replace into
meta(qkey,btype,bhash,size,stored,expires,lastUsed,usedCount)
values(?,?,?,?,?,?,?,?)"
_, err = db.conn.Exec(sql,
md.key.Data, md.btype, md.bhash.Data, md.size,
md.stored.Epoch(),
- md.expires.Val, md.lastUsed.Epoch(), md.usedCount)
+ exp, md.lastUsed.Epoch(), md.usedCount)
return
}
@@ -124,13 +130,19 @@ func (db *FileMetaDB) Get(query blocks.Query) (mds
[]*FileMetadata, err error) {
md.key = query.Key()
md.btype = btype
var st, lu uint64
- if err = rows.Scan(&md.size, &md.bhash.Data, &st,
&md.expires.Val, &lu, &md.usedCount); err != nil {
+ var exp *uint64
+ if err = rows.Scan(&md.size, &md.bhash.Data, &st, &exp, &lu,
&md.usedCount); err != nil {
if err == sql.ErrNoRows {
md = nil
err = nil
}
return
}
+ if exp != nil {
+ md.expires.Val = *exp
+ } else {
+ md.expires = util.AbsoluteTimeNever()
+ }
md.stored.Val = st * 1000000
md.lastUsed.Val = lu * 1000000
mds = append(mds, md)
@@ -192,10 +204,16 @@ func (db *FileMetaDB) Traverse(f func(*FileMetadata))
error {
md := NewFileMetadata()
for rows.Next() {
var st, lu uint64
- err = rows.Scan(&md.key.Data, &md.btype, &md.bhash.Data,
&md.size, &st, &md.expires.Val, &lu, &md.usedCount)
+ var exp *uint64
+ err = rows.Scan(&md.key.Data, &md.btype, &md.bhash.Data,
&md.size, &st, &exp, &lu, &md.usedCount)
if err != nil {
return err
}
+ if exp != nil {
+ md.expires.Val = *exp
+ } else {
+ md.expires = util.AbsoluteTimeNever()
+ }
md.stored.Val = st * 1000000
md.lastUsed.Val = lu * 1000000
// call process function
diff --git a/src/gnunet/service/store/store_zonemaster.go
b/src/gnunet/service/store/store_zonemaster.go
new file mode 100644
index 0000000..44b1a68
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster.go
@@ -0,0 +1,548 @@
+// 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"
+ "errors"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+ "os"
+ // "https://github.com/go-zeromq/zmq4"
+)
+
+//============================================================
+// Local zone records stored in SQLite3 database
+//============================================================
+
+// Zone is the definition of a local GNS zone
+// and is stored in a SQL database for faster access.
+type Zone struct {
+ ID int64 // database identifier
+ Name string // zone name
+ Created util.AbsoluteTime // date of creation
+ Modified util.AbsoluteTime // date of last modification
+ Key *crypto.ZonePrivate // private zone key (ztype|zdata)
+}
+
+// NewZone creates a new zone for the given private key. The zone is not stored
+// in the database automatically.
+func NewZone(name string, sk *crypto.ZonePrivate) *Zone {
+ // create zone instance
+ return &Zone{
+ Name: name,
+ Created: util.AbsoluteTimeNow(),
+ Modified: util.AbsoluteTimeNow(),
+ Key: sk,
+ }
+}
+
+//----------------------------------------------------------------------
+
+type Label struct {
+ ID int64 // database id of label
+ Zone int64 // database ID of parent zone
+ Name string // label name
+ Created util.AbsoluteTime // date of creation
+ Modified util.AbsoluteTime // date of last modification
+}
+
+func NewLabel(label string) *Label {
+ lbl := new(Label)
+ lbl.ID = 0
+ lbl.Zone = 0
+ lbl.Name = label
+ lbl.Created = util.AbsoluteTimeNow()
+ lbl.Modified = util.AbsoluteTimeNow()
+ return lbl
+}
+
+//----------------------------------------------------------------------
+
+// Record for GNS resource in a zone (generic). It is the responsibility
+// of the caller to provide valid resource data in binary form.
+type Record struct {
+ ID int64 // database id of record
+ Label int64 // database ID of parent label
+ Created util.AbsoluteTime // date of creation
+ Modified util.AbsoluteTime // date of last modification
+
+ blocks.ResourceRecord
+}
+
+// NewRecord creates a new record for given data. The record is not
+// automatically added to the database.
+func NewRecord(expire util.AbsoluteTime, rtype enums.GNSType, flags
enums.GNSFlag, data []byte) *Record {
+ rec := new(Record)
+ rec.ID = 0
+ rec.Label = 0
+ rec.Expire = expire
+ rec.RType = rtype
+ rec.Flags = flags
+ rec.Data = data
+ rec.Size = uint32(len(rec.Data))
+ rec.Created = util.AbsoluteTimeNow()
+ rec.Modified = util.AbsoluteTimeNow()
+ return rec
+}
+
+//======================================================================
+// Zone database: A SQLite3 database to hold metadata about
+// managed local zones (see "namestore" in gnunet).
+//======================================================================
+
+//go:embed store_zonemaster.sql
+var initScriptZM []byte
+
+// ZoneDB is a SQLite3 database for locally managed zones
+type ZoneDB struct {
+ conn *DBConn // database connection
+}
+
+// OpenZoneDB opens a zone database in the given filename (including
+// path). If the database file does not exist, it is created and
+// set up with empty tables.
+func OpenZoneDB(fname string) (db *ZoneDB, err error) {
+ // connect to database
+ if _, err = os.Stat(fname); err != nil {
+ var file *os.File
+ if file, err = os.Create(fname); err != nil {
+ return
+ }
+ file.Close()
+ }
+ db = new(ZoneDB)
+ if db.conn, err = DBPool.Connect("sqlite3:" + fname); err != nil {
+ return
+ }
+ // check for initialized database
+ res := db.conn.QueryRow("select name from sqlite_master where
type='table' and name='zones'")
+ var s string
+ if res.Scan(&s) != nil {
+ // initialize database
+ if _, err = db.conn.Exec(string(initScriptZM)); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// Close zone database
+func (db *ZoneDB) Close() error {
+ return db.conn.Close()
+}
+
+//----------------------------------------------------------------------
+// Zone handling
+//----------------------------------------------------------------------
+
+// SetZone inserts, updates or deletes a zone in the database.
+// The function does not change timestamps which are in the
+// responsibility of the caller.
+// - insert: Zone.ID is nil (0)
+// - update: Zone.Name is set
+// - remove: otherwise
+func (db *ZoneDB) SetZone(z *Zone) error {
+ // check for zone insert
+ if z.ID == 0 {
+ stmt := "insert into zones(name,created,modified,ztype,zdata)
values(?,?,?,?,?)"
+ result, err := db.conn.Exec(stmt, z.Name, z.Created.Val,
z.Modified.Val, z.Key.Type, z.Key.KeyData)
+ if err != nil {
+ return err
+ }
+ z.ID, err = result.LastInsertId()
+ return err
+ }
+ // check for zone update (name only)
+ if len(z.Name) > 0 {
+ stmt := "update zones set name=?,modified=? where id=?"
+ result, err := db.conn.Exec(stmt, z.Name, z.Modified.Val, z.ID)
+ if err != nil {
+ return err
+ }
+ var num int64
+ if num, err = result.RowsAffected(); err == nil {
+ if num != 1 {
+ err = errors.New("update zone failed")
+ }
+ }
+ return err
+ }
+ // remove zone from database: also move all dependent labels to "trash
bin"
+ // (parent zone reference is nil)
+ if _, err := db.conn.Exec("update labels set zid=null where zid=?",
z.ID); err != nil {
+ return err
+ }
+ _, err := db.conn.Exec("delete from zones where id=?", z.ID)
+ return err
+}
+
+// GetZone gets a zone with given identifier
+func (db *ZoneDB) GetZone(id int64) (zone *Zone, err error) {
+ // assemble zone from database row
+ stmt := "select name,created,modified,ztype,zdata from zones where id=?"
+ zone = new(Zone)
+ var ztype enums.GNSType
+ var zdata []byte
+ row := db.conn.QueryRow(stmt, id)
+ if err = row.Scan(&zone.Name, &zone.Created.Val, &zone.Modified.Val,
&ztype, &zdata); err == nil {
+ // reconstruct private zone key
+ zone.Key, err = crypto.NewZonePrivate(ztype, zdata)
+ }
+ return
+}
+
+// GetZones retrieves zone instances from database matching a filter
+// ("where" clause)
+func (db *ZoneDB) GetZones(filter string, args ...any) (list []*Zone, err
error) {
+ // assemble query
+ stmt := "select id,name,created,modified,ztype,zdata from zones"
+ if len(filter) > 0 {
+ stmt += " where " + fmt.Sprintf(filter, args...)
+ }
+ // select zones
+ var rows *sql.Rows
+ if rows, err = db.conn.Query(stmt); err != nil {
+ return
+ }
+ // process zones
+ defer rows.Close()
+ for rows.Next() {
+ // assemble zone from database row
+ zone := new(Zone)
+ var ztype enums.GNSType
+ var zdata []byte
+ if err = rows.Scan(&zone.ID, &zone.Name, &zone.Created.Val,
&zone.Modified.Val, &ztype, &zdata); err != nil {
+ // terminate on error; return list so far
+ return
+ }
+ // reconstruct private zone key
+ if zone.Key, err = crypto.NewZonePrivate(ztype, zdata); err !=
nil {
+ return
+ }
+ // append to result list
+ list = append(list, zone)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Label handling
+//----------------------------------------------------------------------
+
+// SetLabel inserts, updates or deletes a zone label in the database.
+// The function does not change timestamps which are in the
+// responsibility of the caller.
+// - insert: Label.ID is nil (0)
+// - update: Label.Name is set (eventually modified)
+// - remove: otherwise
+func (db *ZoneDB) SetLabel(l *Label) error {
+ // check for label insert
+ if l.ID == 0 {
+ stmt := "insert into labels(zid,name,created,modified)
values(?,?,?,?)"
+ result, err := db.conn.Exec(stmt, l.Zone, l.Name,
l.Created.Val, l.Modified.Val)
+ if err != nil {
+ return err
+ }
+ l.ID, err = result.LastInsertId()
+ return err
+ }
+ // check for label update (name only)
+ if len(l.Name) > 0 {
+ stmt := "update labels set name=?,modified=? where id=?"
+ result, err := db.conn.Exec(stmt, l.Name, l.Modified.Val, l.ID)
+ if err != nil {
+ return err
+ }
+ var num int64
+ if num, err = result.RowsAffected(); err == nil {
+ if num != 1 {
+ err = errors.New("update label failed")
+ }
+ }
+ return err
+ }
+ // remove label from database; move dependent records to trash bin
+ // (label id set to nil)
+ if _, err := db.conn.Exec("update records set lid=null where lid=?",
l.ID); err != nil {
+ return err
+ }
+ _, err := db.conn.Exec("delete from labels where id=?", l.ID)
+ return err
+}
+
+// GetLabel gets a label with given identifier
+func (db *ZoneDB) GetLabel(id int64) (label *Label, err error) {
+ // assemble label from database row
+ stmt := "select zid,name,created,modified from labels where id=?"
+ label = new(Label)
+ row := db.conn.QueryRow(stmt, id)
+ err = row.Scan(&label.Zone, &label.Name, &label.Created.Val,
&label.Modified.Val)
+ return
+}
+
+// GetLabels retrieves record instances from database matching a filter
+// ("where" clause)
+func (db *ZoneDB) GetLabels(filter string, args ...any) (list []*Label, err
error) {
+ // assemble querey
+ stmt := "select id,zid,name,created,modified from labels"
+ if len(filter) > 0 {
+ stmt += " where " + fmt.Sprintf(filter, args...)
+ }
+ // select labels
+ var rows *sql.Rows
+ if rows, err = db.conn.Query(stmt); err != nil {
+ return
+ }
+ // process labels
+ defer rows.Close()
+ for rows.Next() {
+ // assemble label from database row
+ lbl := new(Label)
+ if err = rows.Scan(&lbl.ID, &lbl.Zone, &lbl.Name,
&lbl.Created.Val, &lbl.Modified.Val); err != nil {
+ // terminate on error; return list so far
+ return
+ }
+ // append to result list
+ list = append(list, lbl)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Record handling
+//----------------------------------------------------------------------
+
+// SetRecord inserts, updates or deletes a record in the database.
+// The function does not change timestamps which are in the
+// responsibility of the caller.
+// - insert: Record.ID is nil (0)
+// - update: Record.ZID is set (eventually modified)
+// - remove: otherwise
+func (db *ZoneDB) SetRecord(r *Record) error {
+ // work around a SQLite3 bug when storing uint64 with high bit set
+ var exp *uint64
+ if !r.Expire.IsNever() {
+ exp = new(uint64)
+ *exp = r.Expire.Val
+ }
+ // check for record insert
+ if r.ID == 0 {
+ stmt := "insert into
records(lid,expire,created,modified,flags,rtype,rdata) values(?,?,?,?,?,?,?)"
+ result, err := db.conn.Exec(stmt, r.Label, exp, r.Created.Val,
r.Modified.Val, r.Flags, r.RType, r.Data)
+ if err != nil {
+ return err
+ }
+ r.ID, err = result.LastInsertId()
+ return err
+ }
+ // check for record update
+ if r.Label != 0 {
+ stmt := "update records set
lid=?,expire=?,modified=?,flags=?,rtype=?,rdata=? where id=?"
+ result, err := db.conn.Exec(stmt, r.Label, exp, r.Modified.Val,
r.Flags, r.RType, r.Data, r.ID)
+ if err != nil {
+ return err
+ }
+ var num int64
+ if num, err = result.RowsAffected(); err == nil {
+ if num != 1 {
+ err = errors.New("update record failed")
+ }
+ }
+ return err
+ }
+ // remove record from database
+ _, err := db.conn.Exec("delete from records where id=?", r.ID)
+ return err
+}
+
+// GetRecord gets a resource record with given identifier
+func (db *ZoneDB) GetRecord(id int64) (rec *Record, err error) {
+ // assemble resource record from database row
+ stmt := "select lid,expire,created,modified,flags,rtype,rdata from
records where id=?"
+ rec = new(Record)
+ row := db.conn.QueryRow(stmt, id)
+ var exp *uint64
+ if err = row.Scan(&rec.Label, &exp, &rec.Created.Val,
&rec.Modified.Val, &rec.Flags, &rec.RType, &rec.Data); err != nil {
+ // terminate on error
+ return
+ }
+ // setup missing fields
+ rec.Size = uint32(len(rec.Data))
+ if exp != nil {
+ rec.Expire.Val = *exp
+ } else {
+ rec.Expire = util.AbsoluteTimeNever()
+ }
+ return
+}
+
+// GetRecords retrieves record instances from database matching a filter
+// ("where" clause)
+func (db *ZoneDB) GetRecords(filter string, args ...any) (list []*Record, err
error) {
+ // assemble querey
+ stmt := "select id,lid,expire,created,modified,flags,rtype,rdata from
records"
+ if len(filter) > 0 {
+ stmt += " where " + fmt.Sprintf(filter, args...)
+ }
+ // select records
+ var rows *sql.Rows
+ if rows, err = db.conn.Query(stmt); err != nil {
+ return
+ }
+ // process records
+ defer rows.Close()
+ for rows.Next() {
+ // assemble record from database row
+ rec := new(Record)
+ var exp *uint64
+ if err = rows.Scan(&rec.ID, &rec.Label, &exp, &rec.Created.Val,
&rec.Modified.Val, &rec.Flags, &rec.RType, &rec.Data); err != nil {
+ // terminate on error; return list so far
+ return
+ }
+ rec.Size = uint32(len(rec.Data))
+ if exp != nil {
+ rec.Expire.Val = *exp
+ } else {
+ rec.Expire = util.AbsoluteTimeNever()
+ }
+ // append to result list
+ list = append(list, rec)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Retrieve database content as a nested struct
+//----------------------------------------------------------------------
+
+// LabelGroup is a nested label entry (with records)
+type LabelGroup struct {
+ Label *Label
+ Records []*Record
+}
+
+// ZoneGroup is a nested zone entry (with labels)
+type ZoneGroup struct {
+ Zone *Zone
+ PubID string
+ Labels []*LabelGroup
+}
+
+// GetContent returns the database content as a nested list of zones, labels
+// and records. Since the use-case for the ZoneManager is the management of
+// local zones, the number of entries is limited.
+func (db *ZoneDB) GetContent() (zg []*ZoneGroup, err error) {
+ // get all zones
+ var zones []*Zone
+ if zones, err = db.GetZones(""); err != nil {
+ return
+ }
+ for _, z := range zones {
+ // create group instance for zone
+ zGroup := &ZoneGroup{
+ Zone: z,
+ PubID: z.Key.Public().ID(),
+ Labels: make([]*LabelGroup, 0),
+ }
+ zg = append(zg, zGroup)
+
+ // get all labels for zone
+ var labels []*Label
+ if labels, err = db.GetLabels("zid=%d", z.ID); err != nil {
+ return
+ }
+ for _, l := range labels {
+ // create group instance for label
+ lGroup := &LabelGroup{
+ Label: l,
+ Records: make([]*Record, 0),
+ }
+ // link to zone group
+ zGroup.Labels = append(zGroup.Labels, lGroup)
+
+ // get all records for label
+ lGroup.Records, err = db.GetRecords("lid=%d", l.ID)
+ }
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Retrieve list of used names (Zone,Label) or RR types (Record)
+//----------------------------------------------------------------------
+
+// GetName returns an object name (zone,label) for given id
+func (db *ZoneDB) GetName(tbl string, id int64) (name string, err error) {
+ row := db.conn.QueryRow("select name from "+tbl+" where id=?", id)
+ err = row.Scan(&name)
+ return
+}
+
+// GetNames returns a list of used names (table "zones" and "labels")
+func (db *ZoneDB) GetNames(tbl string) (names []string, err error) {
+ // select all table names
+ var rows *sql.Rows
+ if rows, err = db.conn.Query("select name from " + tbl); err != nil {
+ return
+ }
+ // process names
+ defer rows.Close()
+ var name string
+ for rows.Next() {
+ if err = rows.Scan(&name); err != nil {
+ // terminate on error; return list so far
+ return
+ }
+ // append to result list
+ names = append(names, name)
+ }
+ return
+}
+
+// GetRRTypes returns a list record types stored under a label
+func (db *ZoneDB) GetRRTypes(lid int64) (rrtypes []*enums.GNSSpec, label
string, err error) {
+ // select label name
+ row := db.conn.QueryRow("select name from labels where id=?", lid)
+ if err = row.Scan(&label); err != nil {
+ return
+ }
+ // select all record types under label
+ stmt := "select rtype,flags from records where lid=?"
+ var rows *sql.Rows
+ if rows, err = db.conn.Query(stmt, lid); err != nil {
+ return
+ }
+ // process records
+ defer rows.Close()
+ for rows.Next() {
+ e := new(enums.GNSSpec)
+ if err = rows.Scan(&e.Type, &e.Flags); err != nil {
+ // terminate on error; return list so far
+ return
+ }
+ // append to result list
+ rrtypes = append(rrtypes, e)
+ }
+ return
+}
diff --git a/src/gnunet/service/store/store_zonemaster.sql
b/src/gnunet/service/store/store_zonemaster.sql
new file mode 100644
index 0000000..169fefd
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster.sql
@@ -0,0 +1,47 @@
+-- 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 zones (
+ id integer primary key autoincrement,
+ name text unique,
+ created integer,
+ modified integer,
+ ztype integer,
+ zdata blob
+);
+
+
+create table labels (
+ id integer primary key autoincrement,
+ zid integer references zones(id),
+ name text,
+ created integer,
+ modified integer,
+ unique (zid,name)
+);
+
+create table records (
+ id integer primary key autoincrement,
+ lid integer references labels(id),
+ expire integer,
+ created integer,
+ modified integer,
+ flags integer,
+ rtype integer,
+ rdata blob
+);
diff --git a/src/gnunet/service/store/store_zonemaster_test.go
b/src/gnunet/service/store/store_zonemaster_test.go
new file mode 100644
index 0000000..6f416b1
--- /dev/null
+++ b/src/gnunet/service/store/store_zonemaster_test.go
@@ -0,0 +1,105 @@
+// 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 (
+ "crypto/rand"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/util"
+ "os"
+ "testing"
+ "time"
+)
+
+func TestZoneMaster(t *testing.T) {
+
+ //------------------------------------------------------------------
+ // create database
+ _ = os.Remove("/tmp/zonemaster.db")
+ zdb, err := OpenZoneDB("/tmp/zonemaster.db")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ //------------------------------------------------------------------
+ // create zone and add zone to database
+ seed := make([]byte, 32)
+ if _, err = rand.Read(seed); err != nil {
+ t.Fatal(err)
+ }
+ zp, err := crypto.NewZonePrivate(enums.GNS_TYPE_PKEY, seed)
+ if err != nil {
+ t.Fatal(err)
+ }
+ zone := NewZone("foo", zp)
+ if err = zdb.SetZone(zone); err != nil {
+ t.Fatal(err)
+ }
+
+ //------------------------------------------------------------------
+ // create label and add to zone and database
+ label := NewLabel("bar")
+ label.Zone = zone.ID
+ if err = zdb.SetLabel(label); err != nil {
+ t.Fatal(err)
+ }
+
+ //------------------------------------------------------------------
+ // add record to label and database
+ rec := NewRecord(util.AbsoluteTimeNever().Add(time.Hour),
enums.GNS_TYPE_DNS_TXT, 0, []byte("test entry"))
+ rec.Label = label.ID
+ if err = zdb.SetRecord(rec); err != nil {
+ t.Fatal(err)
+ }
+
+ //------------------------------------------------------------------
+ // search record in database
+ recs, err := zdb.GetRecords("rtype=%d", enums.GNS_TYPE_DNS_TXT)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(recs) != 1 {
+ t.Fatalf("record: got %d records, expected 1", len(recs))
+ }
+
+ //------------------------------------------------------------------
+ // rename zone
+ zone.Name = "MyZone"
+ zone.Modified = util.AbsoluteTimeNow()
+ if err = zdb.SetZone(zone); err != nil {
+ t.Fatal(err)
+ }
+
+ //------------------------------------------------------------------
+ // search zone in database
+ zones, err := zdb.GetZones("name like 'My%%'")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(zones) != 1 {
+ t.Fatalf("zone: got %d records, expected 1", len(zones))
+ }
+
+ //------------------------------------------------------------------
+ // close database
+ if err = zdb.Close(); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/src/gnunet/service/zonemaster/gui.go
b/src/gnunet/service/zonemaster/gui.go
new file mode 100644
index 0000000..3885d01
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui.go
@@ -0,0 +1,666 @@
+// 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 zonemaster
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "embed"
+ "errors"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/service/gns/rr"
+ "gnunet/service/store"
+ "gnunet/util"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/bfix/gospel/logger"
+ "github.com/gorilla/mux"
+)
+
+//======================================================================
+// HTTP service
+//======================================================================
+
+//go:embed gui.htpl gui_css.htpl gui_rr.htpl gui_debug.htpl gui_edit.htpl
gui_new.htpl
+var fsys embed.FS
+
+var (
+ tpl *template.Template // HTML templates
+ timeHTML = "2006-01-02T15:04" // time format (defined by HTML, don't
change!)
+ timeGUI = "02.01.06 15:04" // time format for GUI
+)
+
+// state-change constants
+const (
+ ChangeNew = iota
+ ChangeUpdate
+ ChangeDelete
+)
+
+//----------------------------------------------------------------------
+
+// Start HTTP server to provide GUI
+func (zm *ZoneMaster) startGUI(ctx context.Context) {
+ logger.Println(logger.INFO, "[zonemaster] Starting HTTP GUI backend...")
+
+ // read and prepare templates
+ tpl = template.New("gui")
+ tpl.Funcs(template.FuncMap{
+ "date": func(ts util.AbsoluteTime) string {
+ return guiTime(ts)
+ },
+ "keytype": func(t enums.GNSType) string {
+ return guiKeyType(t)
+ },
+ "setspecs": func(data map[string]string, spec enums.GNSSpec)
string {
+ pf := guiPrefix(spec.Type)
+ data["prefix"] = pf
+ if spec.Flags&enums.GNS_FLAG_PRIVATE != 0 {
+ data[pf+"private"] = "on"
+ }
+ if spec.Flags&enums.GNS_FLAG_SHADOW != 0 {
+ data[pf+"shadow"] = "on"
+ }
+ if spec.Flags&enums.GNS_FLAG_SUPPL != 0 {
+ data[pf+"suppl"] = "on"
+ }
+ return pf
+ },
+ "boxprotos": func() map[uint16]string {
+ return rr.GetProtocols()
+ },
+ "boxsvcs": func() map[uint16]string {
+ return rr.GetServices()
+ },
+ "rrtype": func(t enums.GNSType) string {
+ return strings.Replace(t.String(), "GNS_TYPE_", "", -1)
+ },
+ "rritype": func(ts string) string {
+ t, _ := util.CastFromString[enums.GNSType](ts)
+ return strings.Replace(t.String(), "GNS_TYPE_", "", -1)
+ },
+ "rrflags": func(f enums.GNSFlag) string {
+ flags := make([]string, 0)
+ if f&enums.GNS_FLAG_PRIVATE != 0 {
+ flags = append(flags, "Private")
+ }
+ if f&enums.GNS_FLAG_SHADOW != 0 {
+ flags = append(flags, "Shadow")
+ }
+ if f&enums.GNS_FLAG_SUPPL != 0 {
+ flags = append(flags, "Suppl")
+ }
+ if len(flags) == 0 {
+ return "None"
+ }
+ return strings.Join(flags, ",<br>")
+ },
+ "rrdata": func(t enums.GNSType, buf []byte) string {
+ return guiRRdata(t, buf)
+ },
+ })
+ if _, err := tpl.ParseFS(fsys, "*.htpl"); err != nil {
+ logger.Println(logger.ERROR, "[zonemaster] GUI templates
failed: "+err.Error())
+ return
+ }
+
+ // start HTTP server
+ router := mux.NewRouter()
+ router.HandleFunc("/new/{mode}/{id}", zm.new)
+ router.HandleFunc("/edit/{mode}/{id}", zm.edit)
+ router.HandleFunc("/del/{mode}/{id}", zm.remove)
+ router.HandleFunc("/action/{cmd}/{mode}/{id}", zm.action)
+ router.HandleFunc("/", zm.dashboard)
+ srv := &http.Server{
+ Addr: zm.cfg.ZoneMaster.GUI,
+ ReadTimeout: 10 * time.Second,
+ ReadHeaderTimeout: 5 * time.Second,
+ Handler: router,
+ BaseContext: func(l net.Listener) context.Context {
+ return ctx
+ },
+ }
+ go func() {
+ if err := srv.ListenAndServe(); err != http.ErrServerClosed {
+ logger.Printf(logger.ERROR, "[zonemaster] Failed to
start GUI: "+err.Error())
+ }
+ }()
+}
+
+//----------------------------------------------------------------------
+
+// dashboard is the main entry point for the GUI
+func (zm *ZoneMaster) dashboard(w http.ResponseWriter, r *http.Request) {
+ // collect information for the GUI
+ zg, err := zm.zdb.GetContent()
+ if err != nil {
+ _, _ = io.WriteString(w, "ERROR: "+err.Error())
+ return
+ }
+ // show dashboard
+ renderPage(w, zg, "dashboard")
+}
+
+//======================================================================
+// Handle GUI actions (add, edit and remove)
+//======================================================================
+
+// action dispatcher
+func (zm *ZoneMaster) action(w http.ResponseWriter, r *http.Request) {
+ // prepare variables and form values
+ vars := mux.Vars(r)
+ mode := vars["mode"]
+ id, ok := util.CastFromString[int64](vars["id"])
+ _ = r.ParseForm()
+
+ var err error
+ if ok {
+ switch vars["cmd"] {
+ case "new":
+ err = zm.actionNew(w, r, mode, id)
+ case "upd":
+ err = zm.actionUpd(w, r, mode, id)
+ }
+ } else {
+ err = errors.New("action: missing object id")
+ }
+ if err != nil {
+ _, _ = io.WriteString(w, "ERROR: "+err.Error())
+ return
+ }
+ // redirect back to dashboard
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+}
+
+//----------------------------------------------------------------------
+// NEW: create zone, label or resource record
+//----------------------------------------------------------------------
+
+func (zm *ZoneMaster) actionNew(w http.ResponseWriter, r *http.Request, mode
string, id int64) (err error) {
+ switch mode {
+ // new zone
+ case "zone":
+ name := r.FormValue("name")
+ // create private key
+ seed := make([]byte, 32)
+ if _, err = rand.Read(seed); err != nil {
+ return
+ }
+ var zp *crypto.ZonePrivate
+ kt := enums.GNS_TYPE_PKEY
+ if r.FormValue("keytype") == "EDKEY" {
+ kt = enums.GNS_TYPE_EDKEY
+ }
+ zp, err = crypto.NewZonePrivate(kt, seed)
+ if err != nil {
+ return
+ }
+ // add zone to database
+ zone := store.NewZone(name, zp)
+ err = zm.zdb.SetZone(zone)
+
+ // notify listeners
+ zm.OnChange("zones", zone.ID, ChangeNew)
+
+ // new label
+ case "label":
+ name := r.FormValue("name")
+ // add label to database
+ label := store.NewLabel(name)
+ label.Zone = id
+ err = zm.zdb.SetLabel(label)
+
+ // notify listeners
+ zm.OnChange("labels", label.ID, ChangeNew)
+
+ // new resource record
+ case "rr":
+ err = zm.newRec(w, r, id)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+
+// create new resource record from dialog data
+func (zm *ZoneMaster) newRec(w http.ResponseWriter, r *http.Request, label
int64) error {
+ // get list of parameters from resource record dialog
+ params := make(map[string]string)
+ for key, val := range r.Form {
+ params[key] = strings.Join(val, ",")
+ }
+ // parse RR type (and set prefix for map keys)
+ t, ok := util.CastFromString[enums.GNSType](params["type"])
+ if !ok {
+ return errors.New("new: missing resource record type")
+ }
+ pf := dlgPrefix[t]
+
+ // construct RR data
+ exp, flags := guiParse(params, pf)
+ rrdata, err := Map2RRData(t, params)
+ if err == nil {
+ // assemble record and store in database
+ rr := store.NewRecord(exp, t, flags, rrdata)
+ rr.Label = label
+ err = zm.zdb.SetRecord(rr)
+
+ // notify listeners
+ zm.OnChange("records", rr.ID, ChangeNew)
+ }
+ return err
+}
+
+//----------------------------------------------------------------------
+// UPD: update zone, label or resource record
+//----------------------------------------------------------------------
+
+func (zm *ZoneMaster) actionUpd(w http.ResponseWriter, r *http.Request, mode
string, id int64) (err error) {
+ // handle type
+ switch mode {
+ case "zone":
+ // update zone name in database
+ var zone *store.Zone
+ if zone, err = zm.zdb.GetZone(id); err != nil {
+ return
+ }
+ zone.Name = r.FormValue("name")
+ zone.Modified = util.AbsoluteTimeNow()
+ err = zm.zdb.SetZone(zone)
+
+ // notify listeners
+ zm.OnChange("zones", zone.ID, ChangeUpdate)
+
+ case "label":
+ // update label name
+ label := store.NewLabel(r.FormValue("name"))
+ label.ID = id
+ label.Modified = util.AbsoluteTimeNow()
+ err = zm.zdb.SetLabel(label)
+
+ // notify listeners
+ zm.OnChange("labels", label.ID, ChangeUpdate)
+
+ case "rr":
+ // update record
+ err = zm.updRec(w, r, id)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+
+// update resource record
+func (zm *ZoneMaster) updRec(w http.ResponseWriter, r *http.Request, id int64)
error {
+ // get list of parameters from resource record dialog
+ oldParams := make(map[string]string)
+ newParams := make(map[string]string)
+ for key, val := range r.Form {
+ v := strings.Join(val, ",")
+ if strings.HasPrefix(key, "old_") {
+ oldParams[key[4:]] = v
+ } else {
+ newParams[key] = v
+ }
+ }
+ // parse RR type (and set prefix for map keys)
+ t, ok := util.CastFromString[enums.GNSType](oldParams["type"])
+ if !ok {
+ return errors.New("new: missing resource record type")
+ }
+ pf := guiPrefix(t)
+
+ // check for changed resource record
+ changed := false
+ for key, val := range newParams {
+ old, ok := oldParams[key]
+ if ok && old != val {
+ changed = true
+ break
+ }
+ }
+ if changed {
+ // reconstruct record from GUI parameters
+ rrData, err := Map2RRData(t, newParams)
+ if err != nil {
+ return err
+ }
+ exp, flags := guiParse(newParams, pf)
+ rec := store.NewRecord(exp, t, flags, rrData)
+ rec.ID = id
+ rec.Label, _ = util.CastFromString[int64](newParams["lid"])
+
+ // update in database
+ if err := zm.zdb.SetRecord(rec); err != nil {
+ return err
+ }
+
+ // notify listeners
+ zm.OnChange("records", rec.ID, ChangeUpdate)
+ }
+ return nil
+}
+
+//----------------------------------------------------------------------
+// Create new zone. label or resource record
+//----------------------------------------------------------------------
+
+type NewEditData struct {
+ Ref int64 // database id of reference object
+ Action string // "new" or "upd" action
+ Button string // "Add new" or "Update"
+ Names []string // list of names in use (ZONE,LABEL)
+ RRspecs []*enums.GNSSpec // list of allowed record types and flags
(REC)
+ Params map[string]string // list of current values
+}
+
+func (zm *ZoneMaster) new(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ var err error
+ data := new(NewEditData)
+ data.Action = "new"
+ data.Button = "Add new"
+ data.Params = make(map[string]string)
+ switch vars["mode"] {
+
+ // new zone dialog
+ case "zone":
+ if data.Names, err = zm.zdb.GetNames("zones"); err != nil {
+ break
+ }
+ renderPage(w, data, "new_zone")
+ return
+
+ // new label dialog
+ case "label":
+ // get reference id
+ id, ok := util.CastFromString[int64](vars["id"])
+ if !ok {
+ err = errors.New("new label: missing zone id")
+ break
+ }
+ // get all existing label names for zone
+ stmt := fmt.Sprintf("labels where zid=%d", id)
+ if data.Names, err = zm.zdb.GetNames(stmt); err == nil {
+ data.Ref = id
+ data.Params["zone"], _ = zm.zdb.GetName("zones", id)
+ data.Params["zid"] = util.CastToString(id)
+ renderPage(w, data, "new_label")
+ return
+ }
+
+ // new resource record dialog
+ case "rr":
+ // get reference id
+ id, ok := util.CastFromString[int64](vars["id"])
+ if !ok {
+ err = errors.New("new record: missing label id")
+ break
+ }
+ // get all rrtypes used under given label
+ var rrs []*enums.GNSSpec
+ var label string
+ if rrs, label, err = zm.zdb.GetRRTypes(id); err == nil {
+ // compile a list of acceptable types for new records
+ data.RRspecs = compatibleRR(rrs, label)
+ data.Ref = id
+ data.Params["label"] = label
+ data.Params["lid"] = util.CastToString(id)
+ renderPage(w, data, "new_record")
+ return
+ }
+ }
+ // handle error
+ if err != nil {
+ _, _ = io.WriteString(w, "ERROR: "+err.Error())
+ return
+ }
+ // redirect back to dashboard
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+}
+
+//----------------------------------------------------------------------
+// Edit zone, label or resource record
+//----------------------------------------------------------------------
+
+func (zm *ZoneMaster) edit(w http.ResponseWriter, r *http.Request) {
+ // get database id of edited object
+ vars := mux.Vars(r)
+ var err error
+ id, ok := util.CastFromString[int64](vars["id"])
+ if !ok {
+ err = errors.New("missing edit id")
+ } else {
+ // create edit data instance
+ data := new(NewEditData)
+ data.Ref = id
+ data.Action = "upd"
+ data.Button = "Update"
+ data.Params = make(map[string]string)
+
+ switch vars["mode"] {
+
+ // edit zone name (type can't be changed)
+ case "zone":
+ // get all existing zone names (including the edited
one!)
+ if data.Names, err = zm.zdb.GetNames("zones"); err !=
nil {
+ break
+ }
+ // get edited zone
+ var zone *store.Zone
+ if zone, err = zm.zdb.GetZone(id); err != nil {
+ break
+ }
+ // set edit attributes
+ data.Params["name"] = zone.Name
+ data.Params["keytype"] = guiKeyType(zone.Key.Type)
+ data.Params["keydata"] = zone.Key.Public().ID()
+ data.Params["prvdata"] = zone.Key.ID()
+ data.Params["created"] = guiTime(zone.Created)
+ data.Params["modified"] = guiTime(zone.Modified)
+
+ // show dialog
+ renderPage(w, data, "edit_zone")
+ return
+
+ // edit label name
+ case "label":
+ // get existing label names (including the edited
label!)
+ stmt := fmt.Sprintf("labels where zid=%d", id)
+ if data.Names, err = zm.zdb.GetNames(stmt); err != nil {
+ break
+ }
+ // get edited label
+ var label *store.Label
+ if label, err = zm.zdb.GetLabel(id); err != nil {
+ return
+ }
+ // set edit parameters
+ data.Params["zone"], _ = zm.zdb.GetName("zones", id)
+ data.Params["zid"] = util.CastToString(label.Zone)
+ data.Params["name"] = label.Name
+ data.Params["created"] = guiTime(label.Created)
+ data.Params["modified"] = guiTime(label.Modified)
+
+ // show dialog
+ renderPage(w, data, "edit_label")
+ return
+
+ // edit resource record
+ case "rr":
+ if err = zm.editRec(w, r, data); err == nil {
+ return
+ }
+ }
+ }
+ // handle error
+ if err != nil {
+ _, _ = io.WriteString(w, "ERROR: "+err.Error())
+ return
+ }
+ // redirect back to dashboard
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+}
+
+//----------------------------------------------------------------------
+
+func (zm *ZoneMaster) editRec(w http.ResponseWriter, r *http.Request, data
*NewEditData) (err error) {
+ // get edited resource record
+ var rec *store.Record
+ if rec, err = zm.zdb.GetRecord(data.Ref); err != nil {
+ return
+ }
+ // build map of attribute values
+ pf := dlgPrefix[rec.RType]
+
+ // save shared attributes
+ data.Params["prefix"] = pf
+ data.Params["type"] = util.CastToString(int(rec.RType))
+ data.Params["created"] = guiTime(rec.Created)
+ data.Params["modified"] = guiTime(rec.Modified)
+ data.Params["label"], _ = zm.zdb.GetName("labels", rec.Label)
+ data.Params["lid"] = util.CastToString(rec.Label)
+ if rec.Expire.IsNever() {
+ data.Params[pf+"never"] = "on"
+ } else {
+ data.Params[pf+"expires"] = htmlTime(rec.Expire)
+ }
+ if rec.Flags&enums.GNS_FLAG_PRIVATE != 0 {
+ data.Params[pf+"private"] = "on"
+ }
+ if rec.Flags&enums.GNS_FLAG_SHADOW != 0 {
+ data.Params[pf+"shadow"] = "on"
+ }
+ if rec.Flags&enums.GNS_FLAG_SUPPL != 0 {
+ data.Params[pf+"suppl"] = "on"
+ }
+ // get record instance
+ var inst rr.RR
+ if inst, err = rr.ParseRR(rec.RType, rec.Data); err == nil {
+ // add RR attributes to list
+ inst.ToMap(data.Params, pf)
+ }
+ // show dialog
+ renderPage(w, data, "edit_rec")
+ return
+}
+
+//----------------------------------------------------------------------
+// Remove zone. label or resource record
+//----------------------------------------------------------------------
+
+func (zm *ZoneMaster) remove(w http.ResponseWriter, r *http.Request) {
+ // get database id of edited object
+ vars := mux.Vars(r)
+ var err error
+ id, ok := util.CastFromString[int64](vars["id"])
+ if !ok {
+ err = errors.New("missing remove id")
+ } else {
+ switch vars["mode"] {
+
+ // remove zone
+ case "zone":
+ // get zone from database
+ var zone *store.Zone
+ if zone, err = zm.zdb.GetZone(id); err != nil {
+ return
+ }
+ // remove zone in database
+ zone.Name = ""
+ if err = zm.zdb.SetZone(zone); err != nil {
+ return
+ }
+ zm.OnChange("zones", id, ChangeDelete)
+
+ // remove label
+ case "label":
+ label := store.NewLabel("")
+ label.ID = id
+ if err = zm.zdb.SetLabel(label); err != nil {
+ return
+ }
+ zm.OnChange("labels", id, ChangeDelete)
+
+ // remove resource record
+ case "rr":
+ rec := new(store.Record)
+ rec.ID = id
+ rec.Label = 0
+ if err = zm.zdb.SetRecord(rec); err != nil {
+ return
+ }
+ zm.OnChange("records", id, ChangeDelete)
+ }
+ }
+ // handle error
+ if err != nil {
+ _, _ = io.WriteString(w, "ERROR: "+err.Error())
+ return
+ }
+ // redirect back to dashboard
+ http.Redirect(w, r, "/", http.StatusMovedPermanently)
+}
+
+//======================================================================
+// Helper methods
+//======================================================================
+
+// render a webpage with given data and template reference
+func renderPage(w io.Writer, data interface{}, page string) {
+ // create content section
+ t := tpl.Lookup(page)
+ if t == nil {
+ _, _ = io.WriteString(w, "No template '"+page+"' found")
+ return
+ }
+ content := new(bytes.Buffer)
+ if err := t.Execute(content, data); err != nil {
+ _, _ = io.WriteString(w, err.Error())
+ return
+ }
+ // emit final page
+ t = tpl.Lookup("main")
+ if t == nil {
+ _, _ = io.WriteString(w, "No main template found")
+ return
+ }
+ if err := t.Execute(w, content.String()); err != nil {
+ _, _ = io.WriteString(w, err.Error())
+ }
+}
+
+//----------------------------------------------------------------------
+// Debug rendering
+//----------------------------------------------------------------------
+
+// DebugData for error page
+type DebugData struct {
+ Params map[string]string
+ RR string
+ Err error
+}
diff --git a/src/gnunet/service/zonemaster/gui.htpl
b/src/gnunet/service/zonemaster/gui.htpl
new file mode 100644
index 0000000..ffa9720
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui.htpl
@@ -0,0 +1,133 @@
+{{define "main"}}
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1,
shrink-to-fit=no">
+ {{template "css"}}
+ </head>
+ <body>
+ <h1>GNUnet Zone Master</h1>
+ <hr/>
+ {{.}}
+ <script>
+ function notify(msg) {
+ if ('Notification' in window) {
+ if (Notification.permission !== 'denied') {
+ Notification.requestPermission(function (permission) {
+ if (permission === 'granted') {
+ var note = new Notification('GNUnet Zone
Master', {
+ body: msg,
+ actions: []
+ });
+ }
+ });
+ }
+ }
+ }
+ </script>
+ </body>
+</html>
+{{end}}
+
+{{define "dashboard"}}
+<div>
+ <ul id="dashboard">
+ {{if .}}
+ {{range $zi, $zone := .}}
+ <li>
+ {{$z := $zone.Zone}}
+ <span class="caret"><b>{{$z.Name}}</b></span> [{{keytype
$z.Key.Type}}: {{$zone.PubID}}]
+ <a href="/edit/zone/{{$z.ID}}" title="Edit zone"><button
class="icon blue">✎</button></a>
+ <a href="/del/zone/{{$z.ID}}" title="Remove zone"><button
class="icon red">✖</button></a>
+ (Created: {{date $z.Created}}, Modified: {{date $z.Modified}})
+ <ul class="nested">
+ {{if $zone.Labels}}
+ {{range $li, $label := $zone.Labels}}
+ <li>
+ {{$l := $label.Label}}
+ <span class="caret"><b>{{$l.Name}}</b></span>
+ <a href="/edit/label/{{$l.ID}}" title="Edit label"><button
class="icon blue">✎</button></a>
+ <a href="/del/label/{{$l.ID}}" title="Remove
label"><button class="icon red">✖</button></a>
+ (Created: {{date $l.Created}}, Modified: {{date
$l.Modified}})
+ <ul class="nested">
+ {{if $label.Records}}
+ <li>
+ <table class="rowed">
+ <tr class="header">
+ <th>Type</th>
+ <th>Value</th>
+ <th>Flags</th>
+ <th>Expires</th>
+ <th>Created</th>
+ <th>Modified</th>
+ <th>Actions</th>
+ </tr>
+ {{range $ri, $rec := $label.Records}}
+ <tr class="row">
+ <td>{{rrtype $rec.RType}}</td>
+ <td>{{rrdata $rec.RType $rec.Data}}</td>
+ <td>{{rrflags $rec.Flags}}</td>
+ <td>{{date $rec.Expire}}</td>
+ <td>{{date $rec.Created}}</td>
+ <td>{{date $rec.Modified}}</td>
+ <td>
+ <a href="/edit/rr/{{$rec.ID}}"
title="Edit record"><button class="icon blue">✎</button></a>
+ <a href="/del/rr/{{$rec.ID}}"
title="Remove record"><button class="icon red">✖</button></a>
+ </td>
+ </tr>
+ {{end}}
+ </table>
+ </li>
+ {{else}}
+ <li><h3>No resource records for label defined
yet.</h3></li>
+ {{end}}
+ <li>
+ <a href="/new/rr/{{$l.ID}}" title="Add new
record..."><button class="icon blue">✚</button></a>
+ </li>
+ </ul>
+ </li>
+ {{end}}
+ {{else}}
+ <li><h3>No labels for zone defined yet.</h3></li>
+ {{end}}
+ <li>
+ <a href="/new/label/{{$z.ID}}" title="Add new
label..."><button class="icon blue">✚</button></a>
+ </li>
+ </ul>
+ </li>
+ {{end}}
+ {{else}}
+ <li>
+ <h3>No zones defined yet.</h3>
+ </li>
+ {{end}}
+ <li>
+ <a href="/new/zone/0" title="Add new zone..."><button class="icon
blue">✚</button></a>
+ </li>
+ </ul>
+</div>
+<script>
+ var toggler = document.getElementsByClassName("caret");
+ for (var i = 0; i < toggler.length; i++) {
+ toggler[i].addEventListener("click", function() {
+
this.parentElement.querySelector(".nested").classList.toggle("active");
+ this.classList.toggle("caret-down");
+ });
+ }
+
+ for (var i = 0; i < toggler.length; i++) {
+ if (localStorage.getItem("t"+i) == "true") {
+
toggler[i].parentElement.querySelector(".nested").classList.toggle("active");
+ toggler[i].classList.toggle("caret-down");
+ }
+ }
+ document.documentElement.scrollTop = document.body.scrollTop =
localStorage.getItem("top");
+
+ window.addEventListener('beforeunload', function (e) {
+ for (var i = 0; i < toggler.length; i++) {
+ localStorage.setItem("t"+i,
toggler[i].classList.contains("caret-down"));
+ }
+ localStorage.setItem("top", window.pageYOffset ||
document.documentElement.scrollTop);
+ });
+</script>
+{{end}}
\ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_css.htpl
b/src/gnunet/service/zonemaster/gui_css.htpl
new file mode 100644
index 0000000..b31e714
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_css.htpl
@@ -0,0 +1,253 @@
+{{define "css"}}
+<style>
+ * {
+ box-sizing: border-box;
+ }
+ body {
+ margin: 2em 9em 2em 9em;
+ }
+ input[type=text] {
+ font-size: 1.2em;
+ padding: 5px;
+ border: 2px solid #ddd;
+ border-radius: 7px;
+ }
+ input[type=text]:focus {
+ outline: none;
+ border-color: #ace;
+ box-shadow: 0 0 10px #ace;
+ }
+ div.row::after {
+ content: "";
+ clear: both;
+ display: table;
+ }
+ div.cell {
+ display: inline;
+ float: left;
+ }
+ div.box {
+ border: 2px solid black;
+ margin: 0.5em;
+ padding: 0.5em;
+ }
+ div.block {
+ margin: 0.5em;
+ padding: 0.5em;
+ }
+ div.heading {
+ color: white;
+ background-color: orange;
+ font-size: 200%;
+ font-weight: bold;
+ padding: 0.3em;
+ margin: 1em 0 1em 0;
+ }
+ button.icon {
+ border: none;
+ color: black;
+ background-color: transparent;
+ padding: 0 0;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 100%;
+ margin: 4px 2px;
+ cursor: pointer;
+ }
+ .label {
+ text-align: right;
+ vertical-align: top;
+ font-weight: bold;
+ }
+ .title {
+ font-size: 120%;
+ font-weight: bold;
+ margin-bottom: 0.5em;
+ }
+ .large {
+ font-size: 200%;
+ font-weight: bold;
+ }
+ .small {
+ font-size: 75%;
+ }
+ .blue {
+ color: blue !important;
+ }
+ .red {
+ color: red !important;
+ }
+ .disabled {
+ pointer-events:none;
+ }
+ .headline {
+ color: white;
+ padding: 0.3em;
+ }
+ .status-0 {
+ background-color: green;
+ }
+ .status-1 {
+ background-color: orange;
+ }
+ .status-2 {
+ background-color: red;
+ }
+ .spacer-right {
+ margin-right: 2em;
+ }
+ .changed {
+ background-color: #fee;
+ }
+ table.rowed {
+ border-collapse: separate;
+ }
+ table.rowed > tbody > tr {
+ border: solid;
+ border-width: 1px 0;
+ border-color: #ccc;
+ }
+ tr.row:nth-child(even) {
+ background: #fff;
+ }
+ tr.row:nth-child(odd) {
+ background: #eee;
+ }
+ tr.header {
+ background: #eef;
+ color: black;
+ font-weight: bold;
+ }
+ td {
+ padding: 0.5em;
+ }
+ th {
+ padding: 0.5em;
+ text-align: center;
+ }
+ label[for=toggle] {
+ cursor: pointer;
+ border: 1px solid black;
+ border-radius: 0.2em;
+ background-color: #eeeeee;
+ padding: 0.1em;
+ }
+ #toggle {
+ display: none;
+ }
+ #toggle:not(:checked) ~ #toggled {
+ display: none;
+ }
+ ul, #dashboard {
+ list-style-type: none;
+ }
+ #dashboard {
+ margin: 0;
+ padding: 0;
+ }
+ li {
+ margin: 0.5em;
+ }
+ .caret {
+ cursor: pointer;
+ user-select: none;
+ }
+ .caret::before {
+ content: "\25B6";
+ color: black;
+ display: inline-block;
+ margin-right: 6px;
+ }
+ .caret-down::before {
+ transform: rotate(90deg);
+ }
+ .nested {
+ display: none;
+ }
+ .active {
+ display: block;
+ }
+ .tabset > input[type="radio"] {
+ position: absolute;
+ left: -200vw;
+ }
+ .tabset .tab-panel {
+ display: none;
+ }
+ .tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child,
+ .tabset > input:nth-child(3):checked ~ .tab-panels >
.tab-panel:nth-child(2),
+ .tabset > input:nth-child(5):checked ~ .tab-panels >
.tab-panel:nth-child(3),
+ .tabset > input:nth-child(7):checked ~ .tab-panels >
.tab-panel:nth-child(4),
+ .tabset > input:nth-child(9):checked ~ .tab-panels >
.tab-panel:nth-child(5),
+ .tabset > input:nth-child(11):checked ~ .tab-panels >
.tab-panel:nth-child(6),
+ .tabset > input:nth-child(13):checked ~ .tab-panels >
.tab-panel:nth-child(7),
+ .tabset > input:nth-child(15):checked ~ .tab-panels >
.tab-panel:nth-child(8),
+ .tabset > input:nth-child(17):checked ~ .tab-panels >
.tab-panel:nth-child(9),
+ .tabset > input:nth-child(19):checked ~ .tab-panels >
.tab-panel:nth-child(10),
+ .tabset > input:nth-child(21):checked ~ .tab-panels >
.tab-panel:nth-child(11),
+ .tabset > input:nth-child(23):checked ~ .tab-panels >
.tab-panel:nth-child(12),
+ .tabset > input:nth-child(25):checked ~ .tab-panels >
.tab-panel:nth-child(13),
+ .tabset > input:nth-child(27):checked ~ .tab-panels >
.tab-panel:nth-child(14),
+ .tabset > input:nth-child(29):checked ~ .tab-panels >
.tab-panel:nth-child(15),
+ .tabset > input:nth-child(31):checked ~ .tab-panels >
.tab-panel:nth-child(16),
+ .tabset > input:nth-child(33):checked ~ .tab-panels >
.tab-panel:nth-child(17),
+ .tabset > input:nth-child(35):checked ~ .tab-panels >
.tab-panel:nth-child(18),
+ .tabset > input:nth-child(37):checked ~ .tab-panels >
.tab-panel:nth-child(19),
+ .tabset > input:nth-child(39):checked ~ .tab-panels >
.tab-panel:nth-child(20) {
+ display: block;
+ }
+ .tabset > label {
+ position: relative;
+ display: inline-block;
+ padding: 15px 15px 25px;
+ border: 1px solid transparent;
+ border-bottom: 0;
+ cursor: pointer;
+ font-weight: 600;
+ }
+ .tabset > label::after {
+ content: "";
+ position: absolute;
+ left: 15px;
+ bottom: 10px;
+ width: 22px;
+ height: 4px;
+ background: #8d8d8d;
+ }
+ .tabset > label:hover {
+ color: #f90;
+ }
+ .tabset > input:focus + label {
+ color: #06c;
+ }
+ .tabset > label:hover::after {
+ background: #f90;
+ }
+ .tabset > input:focus + label::after,
+ .tabset > input:checked + label::after {
+ background: #06c;
+ }
+ .tabset > input:checked + label {
+ border-color: #ccc;
+ border-bottom: 1px solid #fff;
+ margin-bottom: -1px;
+ }
+ .tab-panel {
+ padding: 30px 0;
+ border-top: 1px solid #ccc;
+ }
+ div.switch {
+ display: none;
+ }
+ input.switch:checked ~ div.switch {
+ display: block;
+ }
+ div.alternate {
+ display: block;
+ }
+ input.alternate:checked ~ div.alternate {
+ display: none;
+ }
+</style>
+{{end}}
diff --git a/src/gnunet/service/zonemaster/gui_debug.htpl
b/src/gnunet/service/zonemaster/gui_debug.htpl
new file mode 100644
index 0000000..3112600
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_debug.htpl
@@ -0,0 +1,14 @@
+{{define "debug"}}
+ <h1>Debug</h1>
+ <h3>Parameters:</h3>
+ <ul>
+ {{range $k,$v := .Params}}
+ <li><b>{{$k}}</b> = {{$v}}</li>
+ {{end}}
+ </ul>
+ <h3>RR data:</h3>
+ <p>{{.RR}}</p>
+ {{if .Err}}
+ <p>Error: <b>{{.Err}}</b></p>
+ {{end}}
+{{end}}
\ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_edit.htpl
b/src/gnunet/service/zonemaster/gui_edit.htpl
new file mode 100644
index 0000000..a4673b0
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_edit.htpl
@@ -0,0 +1,130 @@
+{{define "edit_zone"}}
+ {{$type := index .Params "keytype"}}
+ {{$name := index .Params "name"}}
+ <div>
+ <h3>Edit a [{{$type}}] GNS zone:</h3>
+ <p><small>(Created: {{index .Params "created"}}, Last edited: {{index
.Params "modified"}})</small></p>
+ <form action="/action/upd/zone/{{.Ref}}" method="post"
onsubmit="return(zone_validate());">
+ <input type="hidden" name="old_name" value="{{$name}}">
+ <table>
+ <tr>
+ <td align="right"><b>Zone name:</b></td>
+ <td><input type="text" id="name" name="name"
value="{{$name}}"></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <p>The type of the zone key cannot be changed. It is
currently set to
+ <b>{{if eq $type "PKEY"}}PKEY
(Ed25519+EcDSA){{else}}EDKEY (EdDSA){{end}}</b>:</p>
+ <table>
+ <tr>
+ <td align="right"><b>Public key:</b></td>
+ <td>{{index .Params "keydata"}}</td>
+ </tr>
+ <tr>
+ <td align="right"><b>Private key:</b></td>
+ <td>{{index .Params "prvdata"}}</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <button id="submit">Change zone name</button>
+ </form>
+ <p><a href="/"><button>Leave</button></a></p>
+ </div>
+ <script>
+ const old_zone = "{{$name}}";
+ const zone_names = [
+ {{range $i, $n := .Names}}
+ "{{$n}}",
+ {{end}}
+ ];
+ function zone_validate() {
+ const name = document.getElementById("name").value;
+ if (!name) {
+ alert("Empty zone name not allowed");
+ return false;
+ }
+ if (name == old_zone) {
+ alert("Zone name not changed");
+ return false;
+ }
+ for (var i = 0; i < names.length; i++) {
+ if (zone_names[i] == name) {
+ alert("Zone name already in-use");
+ return false;
+ }
+ }
+ return(true);
+ }
+ </script>
+{{end}}
+
+{{define "edit_label"}}
+ {{$name := index .Params "name"}}
+ {{$zone := index .Params "zone"}}
+ <div>
+ <h3>Edit a GNS label for zone "{{$zone}}":</h3>
+ <p><small>(Created: {{index .Params "created"}}, Last edited: {{index
.Params "modified"}})</small></p>
+ <form action="/action/upd/label/{{.Ref}}" method="post"
onsubmit="return(label_validate());">
+ <input type="hidden" name="old_name" value="{{$name}}">
+ <input type="hidden" name="zid" value="{{index .Params "zid"}}">
+ <table>
+ <tr>
+ <td align="right">Name:</td>
+ <td><input type="text" id="name" name="name"
value="{{$name}}"></td>
+ </tr>
+ </table>
+ <button id="submit">Change label name</button>
+ </form>
+ <p><a href="/"><button>Leave</button></a></p>
+ </div>
+ <script>
+ const old_label = "{{$name}}";
+ const label_names = [
+ {{range $i, $n := .Names}}
+ '{{$n}}',
+ {{end}}
+ ];
+ function label_validate() {
+ const name = document.getElementById("name").value;
+ if (!name) {
+ alert("Empty labels not allowed");
+ return false;
+ }
+ if (name == old_label) {
+ alert("Label name not changed");
+ return false;
+ }
+ for (var i = 0; i < names.length; i++) {
+ if (label_names[i] == name) {
+ alert("Label name already in-use");
+ return false;
+ }
+ }
+ return(true);
+ }
+ </script>
+{{end}}
+
+{{define "edit_rec"}}
+ {{$label := index .Params "label"}}
+ <div>
+ <h3>Edit a resource record for label "{{$label}}":</h3>
+ <p><small>(Created: {{index .Params "created"}}, Last edited: {{index
.Params "modified"}})</small></p>
+ {{$t := rritype (index .Params "type")}}
+ {{if eq $t "PKEY"}}{{template "PKEY" .}}{{end}}
+ {{if eq $t "EDKEY"}}{{template "EDKEY" .}}{{end}}
+ {{if eq $t "NICK"}}{{template "NICK" .}}{{end}}
+ {{if eq $t "LEHO"}}{{template "LEHO" .}}{{end}}
+ {{if eq $t "REDIRECT"}}{{template "REDIRECT" .}}{{end}}
+ {{if eq $t "GNS2DNS"}}{{template "GNS2DNS" .}}{{end}}
+ {{if eq $t "BOX"}}{{template "BOX" .}}{{end}}
+ {{if eq $t "DNS_CNAME"}}{{template "DNS_CNAME" .}}{{end}}
+ {{if eq $t "DNS_A"}}{{template "DNS_A" .}}{{end}}
+ {{if eq $t "DNS_AAAA"}}{{template "DNS_AAAA" .}}{{end}}
+ {{if eq $t "DNS_MX"}}{{template "DNS_MX" .}}{{end}}
+ {{if eq $t "DNS_TXT"}}{{template "DNS_TXT" .}}{{end}}
+ </div>
+ <a href="/"><button>Leave</button></a>
+{{end}}
\ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/gui_new.htpl
b/src/gnunet/service/zonemaster/gui_new.htpl
new file mode 100644
index 0000000..f470de9
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_new.htpl
@@ -0,0 +1,114 @@
+{{define "new_zone"}}
+<div>
+ <h3>Creating a new GNS zone:</h3>
+ <form action="/action/new/zone/0" method="post"
onsubmit="return(zone_validate());">
+ <table>
+ <tr>
+ <td align="right"><b>Zone name:</b></td>
+ <td><input type="text" id="name" name="name"></td>
+ </tr>
+ <tr>
+ <td align="right" valign="top"><b>Key type:</b></td>
+ <td>
+ <input type="radio" id="pkey" name="keytype" value="PKEY"
checked="checked"> PKEY (Ed25519+EcDSA)<br>
+ <input type="radio" id="edkey" name="keytype"
value="EDKEY"> EDKEY (EdDSA)
+ </td>
+ </tr>
+ </table>
+ <button id="submit">Add zone</button>
+ </form>
+ <a href="/"><button id="leave">Leave</button></a>
+</div>
+<script>
+ const zone_names = [
+ {{range $i, $n := .Names}}
+ '{{$n}}',
+ {{end}}
+ ];
+ function zone_validate() {
+ const name = document.getElementById("name").value;
+ if (!name) {
+ alert("Empty zone name not allowed");
+ return false;
+ }
+ for (var i = 0; i < names.length; i++) {
+ if (zone_names[i] == name) {
+ alert("Zone name already used");
+ return false;
+ }
+ }
+ return(true);
+ }
+</script>
+{{end}}
+
+{{define "new_label"}}
+<div>
+ <h3>Creating a new GNS label for zone "{{index .Params "zone"}}":</h3>
+ <form action="/action/new/label/{{.Ref}}"
onsubmit="return(label_validate());">
+ <table>
+ <tr>
+ <td align="right">Name:</td>
+ <td><input type="text" id="name" name="name"></td>
+ </tr>
+ </table>
+ <button id="submit">Add label</button>
+ </form>
+ <a href="/"><button>Leave</button></a>
+</div>
+<script>
+ const label_names = [
+ {{range $i, $n := .Names}}
+ '{{$n}}',
+ {{end}}
+ ];
+ function label_validate() {
+ const name = document.getElementById("name").value;
+ if (!name) {
+ alert("Empty labels not allowed");
+ return false;
+ }
+ for (var i = 0; i < names.length; i++) {
+ if (label_names[i] == name) {
+ alert("Label already used");
+ return false;
+ }
+ }
+ return(true);
+ }
+</script>
+{{end}}
+
+{{define "new_record"}}
+{{$data := .}}
+<div>
+ <h3>Creating a new GNS resource record for label "{{index .Params
"label"}}":</h3>
+ <div class="tabset">
+ {{range $i, $type := .RRspecs}}
+ <input type="radio" name="tabset" id="tab{{$i}}"
aria-controls="tab{{$i}}" {{if eq $i 0}}checked{{end}}>
+ <label for="tab{{$i}}">{{rrtype $type.Type}}</label>
+ {{end}}
+ <div class="tab-panels">
+ {{range $i, $spec := .RRspecs}}
+ <section id="tab{{$i}}" class="tab-panel">
+ {{$t := rrtype $spec.Type}}
+ {{$pf := setspecs $data.Params $spec}}
+ {{if eq $t "PKEY"}}{{template "PKEY" $data}}{{end}}
+ {{if eq $t "EDKEY"}}{{template "EDKEY" $data}}{{end}}
+ {{if eq $t "NICK"}}{{template "NICK" $data}}{{end}}
+ {{if eq $t "LEHO"}}{{template "LEHO" $data}}{{end}}
+ {{if eq $t "REDIRECT"}}{{template "REDIRECT" $data}}{{end}}
+ {{if eq $t "GNS2DNS"}}{{template "GNS2DNS" $data}}{{end}}
+ {{if eq $t "BOX"}}{{template "BOX" $data}}{{end}}
+ {{if eq $t "DNS_CNAME"}}{{template "DNS_CNAME" $data}}{{end}}
+ {{if eq $t "DNS_A"}}{{template "DNS_A" $data}}{{end}}
+ {{if eq $t "DNS_AAAA"}}{{template "DNS_AAAA" $data}}{{end}}
+ {{if eq $t "DNS_MX"}}{{template "DNS_MX" $data}}{{end}}
+ {{if eq $t "DNS_TXT"}}{{template "DNS_TXT" $data}}{{end}}
+ </section>
+ {{end}}
+ </div>
+ </div>
+ <a href="/"><button>Leave</button></a>
+</div>
+{{end}}
diff --git a/src/gnunet/service/zonemaster/gui_rr.htpl
b/src/gnunet/service/zonemaster/gui_rr.htpl
new file mode 100644
index 0000000..360a734
--- /dev/null
+++ b/src/gnunet/service/zonemaster/gui_rr.htpl
@@ -0,0 +1,420 @@
+{{define "RRCommon"}}
+ <input type="hidden" name="lid" value="{{index . "lid"}}">
+ {{range $k, $v := .}}
+ <input type="hidden" name="old_{{$k}}" value="{{$v}}">
+ {{end}}
+ {{$pf := index . "prefix"}}
+ <tr>
+ <td align="right" valign="top"><b>Expires:</b></td>
+ <td>
+ Never <input type="checkbox" class="alternate" name="{{$pf}}never"
+ {{if eq "on" (index . (print $pf
"never"))}}checked="checked"{{end}}
+ >
+ <div class="alternate">
+ At given date and time:
+ <input type="datetime-local" id="{{$pf}}expires"
name="{{$pf}}expires" required
+ value="{{index . (print $pf "expires")}}"
+ >
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="top"><b>Flags:</b></td>
+ <td>
+ <input type="checkbox" name="{{$pf}}private"
+ {{if eq "on" (index . (print $pf
"private"))}}checked="checked" class="disabled"{{end}}
+ > Private<br>
+ <input type="checkbox" name="{{$pf}}shadow"
+ {{if eq "on" (index . (print $pf "shadow"))}}checked="checked"
class="disabled"{{end}}
+ > Shadow<br>
+ <input type="checkbox" name="{{$pf}}suppl"
+ {{if eq "on" (index . (print $pf "suppl"))}}checked="checked"
class="disabled"{{end}}
+ > Supplemental<br>
+ </td>
+ </tr>
+ {{if eq .Action "new"}}
+ <script>
+ var dt = document.getElementById("{{$pf}}expires");
+ if (!dt.value) {
+ var exp = new Date(new Date().getTime() + 31536000000);
+ dt.value = exp.toISOString().slice(0, 16);
+ }
+ </script>
+ {{end}}
+{{end}}
+
+{{define "PKEY"}}
+ <h3>PKEY delegation</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65536">
+ <table>
+ <tr><td/>
+ <td>
+ Enter the public zone key (type
+ <a href="https://lsd.gnunet.org/lsd0001/#name-pkey"
target="_blank">PKEY</a>
+ ) in
+ <a href="https://lsd.gnunet.org/lsd0001/#name-base32gns"
target="_blank">Base32GNS</a>
+ encoding:
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>Key:</b></td>
+ <td>
+ <input type="text" name="pkey_data"
+ maxlength="58" minlength="58" size="64"
+ pattern="[0-9A-HJKMNP-TV-Z]{58}"
+ autofocus required
+ value="{{index .Params "pkey_data"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "EDKEY"}}
+ <h3>EDKEY delegation</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65556">
+ <table>
+ <tr><td/>
+ <td>
+ Enter the public zone key (type
+ <a href="https://lsd.gnunet.org/lsd0001/#name-edkey"
target="_blank">EDKEY</a>
+ ) in
+ <a href="https://lsd.gnunet.org/lsd0001/#name-base32gns"
target="_blank">Base32GNS</a>
+ encoding:
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>Key:</b></td>
+ <td>
+ <input type="text" name="edkey_data"
+ maxlength="58" minlength="58" size="64"
+ pattern="[0-9A-HJKMNP-TV-Z]{58}"
+ autofocus required
+ value="{{index .Params "edkey_data"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "REDIRECT"}}
+ <h3>REDIRECT (GNS delegation)</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65551">
+ <table>
+ <tr><td/>
+ <td>
+ Enter the redirected GNS name (see
+ <a href="https://lsd.gnunet.org/lsd0001/#name-redirect"
target="_blank">specification</a>
+ ):
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>Name:</b></td>
+ <td>
+ <input type="text" name="redirect_name"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "redirect_name"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "LEHO"}}
+ <h3>LEHO (legacy hostname)</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65538">
+ <table>
+ <tr>
+ <td align="right"><b>Legacy hostname:</b></td>
+ <td>
+ <input type="text" name="leho_name"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "leho_name"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "NICK"}}
+ <h3>NICK</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65537">
+ <table>
+ <tr>
+ <td align="right"><b>Nick name:</b></td>
+ <td>
+ <input type="text" name="nick_name"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "nick_name"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "GNS2DNS"}}
+ <h3>GNS2DNS delegation</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65540">
+ <table>
+ <tr><td/>
+ <td>
+ Enter DNS name and server as
+ <a href="https://lsd.gnunet.org/lsd0001/#name-gns2dns"
target="_blank">specified</a>.
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>DNS name:</b></td>
+ <td>
+ <input type="text" name="gns2dns_name"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "gns2dns_name"}}"
+ >
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>DNS server:</b></td>
+ <td>
+ <input type="text" name="gns2dns_server"
+ maxlength="63" size="63" required
+ value="{{index .Params "gns2dns_server"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "BOX"}}
+ <h3>BOX</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="65541">
+ <table>
+ <tr><td/>
+ <td>
+ Enter protocol, service (port) and type of the boxed
resource type as
+ <a href="https://lsd.gnunet.org/lsd0001/#name-box"
target="_blank">specified</a>:
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>Protocol:</b></td>
+ <td>
+ <input type="text" name="box_proto" size="16"
list="protocols" required
+ value="{{index .Params "box_proto"}}"
+ >
+ <datalist id="protocols">
+ {{range $id,$name := boxprotos}}
+ <option value="{{$id}} ({{$name}})">
+ {{end}}
+ </datalist>
+ </td>
+ </tr>
+ <tr>
+ <td align="right"><b>Service:</b></td>
+ <td>
+ <input type="text" name="box_svc" size="16"
list="services" required
+ value="{{index .Params "box_svc"}}"
+ >
+ <datalist id="services">
+ {{range $id,$name := boxsvcs}}
+ <option value='{{$id}} {{$name}}'>
+ {{end}}
+ </datalist>
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="top"><b>Type:</b></td>
+ <td>
+ <input type="radio" class="switch" name="box_type"
value="33"
+ {{if eq (index .Params "box_type") "33"}}checked{{end}}
+ > SRV (Service description)
+ <div class="switch">
+ <div class="block">
+ <label for="box_srv_host">Host:</label>
+ <input type="text" name="box_srv_host"
maxlength="63" size="63"
+ value="{{index .Params "box_srv_host"}}"
+ >
+ </div>
+ </div>
+ </td>
+ </tr>
+ </tr>
+ <tr>
+ <td/><td>
+ <input type="radio" class="switch" name="box_type"
value="52"
+ {{if eq (index .Params "box_type") "52"}}checked{{end}}
+ > TLSA (TLS Association)
+ <div class="switch">
+ <div class="block">
+ <label for="box_tlsa_usage">Usage:</label>
+ {{$x := index .Params "box_tlsa_usage"}}
+ <select size="1" name="box_tlsa_usage">
+ <option value="0" {{if eq $x
"0"}}selected{{end}}>CA certificate</option>
+ <option value="1" {{if eq $x
"1"}}selected{{end}}>Service certificate constraint</option>
+ <option value="2" {{if eq $x
"2"}}selected{{end}}>Trust anchor assertion</option>
+ <option value="3" {{if eq $x
"3"}}selected{{end}}>Domain-issued certificate</option>
+ <option value="255" {{if eq $x
"255"}}selected{{end}}>Private use</option>
+ </select>
+ </div>
+ <div class="block">
+ <label for="box_tlsa_selector">Selector:</label>
+ {{$x = index .Params "box_tlsa_selector"}}
+ <select size="1" name="box_tlsa_selector">
+ <option value="0" {{if eq $x
"0"}}selected{{end}}>Full certificate</option>
+ <option value="1" {{if eq $x
"1"}}selected{{end}}>SubjectPublicKeyInfo</option>
+ <option value="255" {{if eq $x
"255"}}selected{{end}}>Private use</option>
+ </select>
+ </div>
+ <div class="block">
+ <label for="box_tlsa_match">Match:</label>
+ {{$x = index .Params "box_tlsa_match"}}
+ <select size="1" name="box_tlsa_match">
+ <option value="0" {{if eq $x
"0"}}selected{{end}}>No hash</option>
+ <option value="1" {{if eq $x
"1"}}selected{{end}}>SHA-256</option>
+ <option value="2" {{if eq $x
"2"}}selected{{end}}>SHA-512</option>
+ <option value="255" {{if eq $x
"255"}}selected{{end}}>Private use</option>
+ </select>
+ </div>
+ <div class="block">
+ <label for="box_tlsa_cert">Certificate information
(hex):</label><br>
+ <textarea name="box_tlsa_cert" rows="10"
cols="50">{{index .Params "box_tlsa_cert"}}</textarea>
+ </div>
+ </div>
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "DNS_A"}}
+ <h3>DNS A (IPv4 address)</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="1">
+ <table>
+ <tr>
+ <td align="right"><b>Address:</b></td>
+ <td>
+ <input type="text" name="dnsa_addr"
+ maxlength="15" size="15"
+
pattern="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
+ autofocus required
+ value="{{index .Params "dnsa_addr"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "DNS_AAAA"}}
+ <h3>DNS AAAA (IPv6 address)</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="28">
+ <table>
+ <tr>
+ <td align="right"><b>Address:</b></td>
+ <td>
+ <input type="text" name="dnsaaaa_addr"
+ maxlength="15" size="15"
+
pattern="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[
[...]
+ autofocus required
+ value="{{index .Params "dnsaaaa_addr"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "DNS_CNAME"}}
+ <h3>DNS CNAME delegation</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="5">
+ <table>
+ <tr>
+ <td align="right"><b>Name:</b></td>
+ <td>
+ <input type="text" name="dnscname_name"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "dnscname_name"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "DNS_TXT"}}
+ <h3>DNS TXT</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="16">
+ <table>
+ <tr>
+ <td align="right"><b>Text:</b></td>
+ <td>
+ <input type="text" name="dnstxt_text"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "dnstxt_text"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
+{{define "DNS_MX"}}
+ <h3>DNS MX (Mailbox)</h3>
+ <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action
"upd"}}method="post"{{end}}>
+ <input type="hidden" name="type" value="15">
+ <table>
+ <tr>
+ <td align="right" valign="top"><b>Priority:</b></td>
+ <td>
+ {{$v := index .Params "dnsmx_prio"}}
+ <input type="number" name="dnsmx_prio" min="1" max="100"
+ value="{{if $v}}{{$v}}{{else}}10{{end}}"
+ >
+ </td>
+ </tr>
+ <tr>
+ <td align="right" valign="top"><b>Mailserver:</b></td>
+ <td>
+ <input type="text" name="dnsmx_host"
+ maxlength="63" size="63"
+ autofocus required
+ value="{{index .Params "dnsmx_host"}}"
+ >
+ </td>
+ </tr>
+ {{template "RRCommon" .Params}}
+ <tr><td/><td><button id="submit">{{.Button}}
record</button></td></tr>
+ </table>
+ </form>
+{{end}}
\ No newline at end of file
diff --git a/src/gnunet/service/zonemaster/module.go
b/src/gnunet/service/zonemaster/module.go
new file mode 100644
index 0000000..8f7b000
--- /dev/null
+++ b/src/gnunet/service/zonemaster/module.go
@@ -0,0 +1,84 @@
+// 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 zonemaster
+
+import (
+ "context"
+
+ "gnunet/core"
+ "gnunet/enums"
+ "gnunet/service"
+ "gnunet/service/dht/blocks"
+)
+
+//======================================================================
+// "GNUnet Zonemaster" implementation
+//======================================================================
+
+// Module handles namestore and identity requests.
+type Module struct {
+ service.ModuleImpl
+
+ // Use function references for calls to methods in other modules:
+ StoreLocal func(ctx context.Context, query *blocks.GNSQuery, block
*blocks.GNSBlock) error
+ StoreRemote func(ctx context.Context, query blocks.Query, block
blocks.Block) error
+}
+
+// NewModule instantiates a new GNS module.
+func NewModule(ctx context.Context, c *core.Core) (m *Module) {
+ m = &Module{
+ ModuleImpl: *service.NewModuleImpl(),
+ }
+ if c != nil {
+ // register as listener for core events
+ listener := m.ModuleImpl.Run(ctx, m.event, m.Filter(), 0, nil)
+ c.Register("zonemaster", listener)
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+
+// Filter returns the event filter for the service
+func (m *Module) Filter() *core.EventFilter {
+ f := core.NewEventFilter()
+ f.AddMsgType(enums.MSG_NAMESTORE_ZONE_ITERATION_START)
+ return f
+}
+
+// Event handler
+func (m *Module) event(ctx context.Context, ev *core.Event) {
+
+}
+
+//----------------------------------------------------------------------
+
+// Export functions
+func (m *Module) Export(fcn map[string]any) {
+ // add exported functions from module
+}
+
+// Import functions
+func (m *Module) Import(fcn map[string]any) {
+ // resolve imports from other modules
+ m.StoreLocal, _ = fcn["namecache:put"].(func(ctx context.Context, query
*blocks.GNSQuery, block *blocks.GNSBlock) error)
+ m.StoreRemote, _ = fcn["dht:put"].(func(ctx context.Context, query
blocks.Query, block blocks.Block) error)
+}
+
+//----------------------------------------------------------------------
diff --git a/src/gnunet/service/zonemaster/records.go
b/src/gnunet/service/zonemaster/records.go
new file mode 100644
index 0000000..3015893
--- /dev/null
+++ b/src/gnunet/service/zonemaster/records.go
@@ -0,0 +1,348 @@
+// 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 zonemaster
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "gnunet/enums"
+ "gnunet/service/gns/rr"
+ "gnunet/util"
+ "net"
+ "time"
+
+ "github.com/bfix/gospel/data"
+)
+
+var (
+ // list of managed RR types
+ rrtypes = []enums.GNSType{
+ enums.GNS_TYPE_PKEY, // PKEY zone delegation
+ enums.GNS_TYPE_EDKEY, // EDKEY zone delegation
+ enums.GNS_TYPE_REDIRECT, // GNS delegation by name
+ enums.GNS_TYPE_GNS2DNS, // DNS delegation by name
+ enums.GNS_TYPE_NICK, // Nick name
+ enums.GNS_TYPE_LEHO, // Legacy hostname
+ enums.GNS_TYPE_BOX, // Boxed resource record
+ enums.GNS_TYPE_DNS_A, // IPv4 address
+ enums.GNS_TYPE_DNS_AAAA, // IPv6 address
+ enums.GNS_TYPE_DNS_CNAME, // CNAME in DNS
+ enums.GNS_TYPE_DNS_TXT, // DNS TXT
+ enums.GNS_TYPE_DNS_MX, // Mailbox
+ }
+)
+
+//======================================================================
+// Convert binary resource records to ParameterSet and vice-versa.
+// The map keys must match the HTML names of dialog fields.
+//======================================================================
+
+//----------------------------------------------------------------------
+// GUI rendering hepers
+//----------------------------------------------------------------------
+
+var (
+ // List of key prefixes based on RR type
+ dlgPrefix = map[enums.GNSType]string{
+ enums.GNS_TYPE_PKEY: "pkey_",
+ enums.GNS_TYPE_EDKEY: "edkey_",
+ enums.GNS_TYPE_REDIRECT: "redirect_",
+ enums.GNS_TYPE_LEHO: "leho_",
+ enums.GNS_TYPE_NICK: "nick_",
+ enums.GNS_TYPE_GNS2DNS: "gns2dns_",
+ enums.GNS_TYPE_BOX: "box_",
+ enums.GNS_TYPE_DNS_A: "dnsa_",
+ enums.GNS_TYPE_DNS_AAAA: "dnsaaaa_",
+ enums.GNS_TYPE_DNS_CNAME: "dnscname_",
+ enums.GNS_TYPE_DNS_TXT: "dnstxt_",
+ enums.GNS_TYPE_DNS_MX: "dnsmx_",
+ }
+)
+
+// convert GNUnet time to string for HTML
+func htmlTime(ts util.AbsoluteTime) string {
+ if ts.IsNever() {
+ return ""
+ }
+ return time.UnixMicro(int64(ts.Val)).Format(timeHTML)
+}
+
+func guiTime(ts util.AbsoluteTime) string {
+ if ts.IsNever() {
+ return "Never"
+ }
+ return time.UnixMicro(int64(ts.Val)).Format(timeGUI)
+}
+
+// convert zone key type to string
+func guiKeyType(t enums.GNSType) string {
+ switch t {
+ case enums.GNS_TYPE_PKEY:
+ return "PKEY"
+ case enums.GNS_TYPE_EDKEY:
+ return "EDKEY"
+ }
+ return "???"
+}
+
+func guiRRdata(t enums.GNSType, buf []byte) string {
+ // get record instance
+ inst, err := rr.ParseRR(t, buf)
+ if err != nil {
+ return "<unknown>"
+ }
+ // type-dependent rendering
+ switch rec := inst.(type) {
+ case *rr.PKEY:
+ return fmt.Sprintf("<span title='public zone key'>%s</span>",
rec.ZoneKey.ID())
+ case *rr.EDKEY:
+ return fmt.Sprintf("<span title='public zone key'>%s</span>",
rec.ZoneKey.ID())
+ case *rr.REDIRECT:
+ return fmt.Sprintf("<span title='redirect target'>%s</span>",
rec.Name)
+ case *rr.NICK:
+ return fmt.Sprintf("<span title='nick name'>%s</span>",
rec.Name)
+ case *rr.LEHO:
+ return fmt.Sprintf("<span title='legacy hostname'>%s</span>",
rec.Name)
+ case *rr.CNAME:
+ return fmt.Sprintf("<span title='canonical name'>%s</span>",
rec.Name)
+ case *rr.TXT:
+ return fmt.Sprintf("<span title='text'>%s</span>", rec.Text)
+ case *rr.DNSA:
+ return fmt.Sprintf("<span title='IPv4 address'>%s</span>",
rec.Addr.String())
+ case *rr.DNSAAAA:
+ return fmt.Sprintf("<span title='IPv6 address'>%s</span>",
rec.Addr.String())
+ case *rr.MX:
+ s := fmt.Sprintf("<span title='priority'>[%d]</span> ",
rec.Prio)
+ return s + fmt.Sprintf("<span title='server'>%s</span>",
rec.Server)
+ case *rr.BOX:
+ s := fmt.Sprintf("<span title='service'>%s</span>/",
rr.GetServiceName(rec.Svc, rec.Proto))
+ s += fmt.Sprintf("<span title='protocol'>%s</span> ",
rr.GetProtocolName(rec.Proto))
+ switch rec.Type {
+ case enums.GNS_TYPE_DNS_TLSA:
+ tlsa := new(rr.TLSA)
+ _ = data.Unmarshal(tlsa, rec.RR)
+ s += "TLSA[<br>"
+ s += fmt.Sprintf("∙ Usage: %s<br>",
rr.TLSAUsage[tlsa.Usage])
+ s += fmt.Sprintf("∙ Selector: %s<br>",
rr.TLSASelector[tlsa.Selector])
+ s += fmt.Sprintf("∙ Match: %s<br>",
rr.TLSAMatch[tlsa.Match])
+ s += "∙ CertData:<br>"
+ cert := hex.EncodeToString(tlsa.Cert)
+ for len(cert) > 32 {
+ s += " " + cert[:32] + "<br>"
+ cert = cert[32:]
+ }
+ s += " " + cert + "<br>]"
+ return s
+ case enums.GNS_TYPE_DNS_SRV:
+ srv, _ := util.ReadCString(rec.RR, 0)
+ s += fmt.Sprintf("SRV[ %s ]", srv)
+ return s
+ }
+ case *rr.GNS2DNS:
+ s := fmt.Sprintf("<span title='name'>%s</span> (Resolver: ",
rec.Name)
+ return s + fmt.Sprintf("<span title='server'>%s</span>)",
rec.Server)
+ }
+ return "(unknown)"
+}
+
+// get prefix for GUI fields for given RR type
+func guiPrefix(t enums.GNSType) string {
+ pf, ok := dlgPrefix[t]
+ if !ok {
+ return ""
+ }
+ return pf
+}
+
+// parse expiration time and flags from GUI parameters
+func guiParse(params map[string]string, pf string) (exp util.AbsoluteTime,
flags enums.GNSFlag) {
+ // parse expiration time
+ exp = util.AbsoluteTimeNever()
+ if _, ok := params[pf+"never"]; !ok {
+ ts, _ := time.Parse(timeHTML, params[pf+"expires"])
+ exp.Val = uint64(ts.UnixMicro())
+ }
+ // parse flags
+ flags = 0
+ if _, ok := params[pf+"private"]; ok {
+ flags |= enums.GNS_FLAG_PRIVATE
+ }
+ if _, ok := params[pf+"shadow"]; ok {
+ flags |= enums.GNS_FLAG_SHADOW
+ }
+ if _, ok := params[pf+"suppl"]; ok {
+ flags |= enums.GNS_FLAG_SUPPL
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Convert RR to string-keyed map and vice-versa.
+//----------------------------------------------------------------------
+
+// RRData2Map converts resource record data in to a map
+func RRData2Map(t enums.GNSType, buf []byte) (set map[string]string) {
+ pf := dlgPrefix[t]
+ set = make(map[string]string)
+ switch t {
+ // Ed25519 public key
+ case enums.GNS_TYPE_PKEY,
+ enums.GNS_TYPE_EDKEY:
+ set[pf+"data"] = util.EncodeBinaryToString(buf)
+
+ // Name string data
+ case enums.GNS_TYPE_REDIRECT,
+ enums.GNS_TYPE_NICK,
+ enums.GNS_TYPE_LEHO,
+ enums.GNS_TYPE_DNS_CNAME:
+ set[pf+"name"], _ = util.ReadCString(buf, 0)
+
+ // DNS TXT
+ case enums.GNS_TYPE_DNS_TXT:
+ set[pf+"text"], _ = util.ReadCString(buf, 0)
+
+ // IPv4/IPv6 address
+ case enums.GNS_TYPE_DNS_A,
+ enums.GNS_TYPE_DNS_AAAA:
+ addr := net.IP(buf)
+ set[pf+"addr"] = addr.String()
+
+ // DNS MX
+ case enums.GNS_TYPE_DNS_MX:
+ mx := new(rr.MX)
+ _ = data.Unmarshal(mx, buf)
+ set[pf+"prio"] = util.CastToString(mx.Prio)
+ set[pf+"host"] = mx.Server
+
+ // BOX
+ case enums.GNS_TYPE_BOX:
+ // get BOX from data
+ box := rr.NewBOX(buf)
+ set[pf+"proto"] = util.CastToString(box.Proto)
+ set[pf+"svc"] = util.CastToString(box.Svc)
+ set[pf+"type"] = util.CastToString(box.Type)
+
+ // handle TLSA and SRV cases
+ switch box.Type {
+ case enums.GNS_TYPE_DNS_TLSA:
+ tlsa := new(rr.TLSA)
+ _ = data.Unmarshal(tlsa, box.RR)
+ set[pf+"tlsa_usage"] = util.CastToString(tlsa.Usage)
+ set[pf+"tlsa_selector"] =
util.CastToString(tlsa.Selector)
+ set[pf+"tlsa_match"] = util.CastToString(tlsa.Match)
+ set[pf+"tlsa_cert"] = hex.EncodeToString(tlsa.Cert)
+
+ case enums.GNS_TYPE_DNS_SRV:
+ set[pf+"srv_host"], _ = util.ReadCString(box.RR, 0)
+ }
+
+ // GNS2DNS
+ case enums.GNS_TYPE_GNS2DNS:
+ list := util.StringList(buf)
+ set[pf+"name"] = list[0]
+ set[pf+"server"] = list[1]
+ }
+ return
+}
+
+// Map2RRData converts a map to resource record data
+func Map2RRData(t enums.GNSType, set map[string]string) (buf []byte, err
error) {
+ pf := dlgPrefix[t]
+ switch t {
+ // Ed25519 public key
+ case enums.GNS_TYPE_PKEY,
+ enums.GNS_TYPE_EDKEY:
+ return util.DecodeStringToBinary(set[pf+"data"], 36)
+
+ // Name string data
+ case enums.GNS_TYPE_REDIRECT,
+ enums.GNS_TYPE_NICK,
+ enums.GNS_TYPE_LEHO,
+ enums.GNS_TYPE_DNS_CNAME:
+ return util.WriteCString(set[pf+"name"]), nil
+
+ // DNS TXT
+ case enums.GNS_TYPE_DNS_TXT:
+ return util.WriteCString(set[pf+"text"]), nil
+
+ // IPv4/IPv6 address
+ case enums.GNS_TYPE_DNS_A,
+ enums.GNS_TYPE_DNS_AAAA:
+ buf := net.ParseIP(set[pf+"addr"])
+ if buf == nil {
+ return nil, errors.New("ParseIP failed")
+ }
+ return buf, nil
+
+ // DNS MX
+ case enums.GNS_TYPE_DNS_MX:
+ mx := new(rr.MX)
+ mx.Prio, _ = util.CastFromString[uint16](set[pf+"prio"])
+ mx.Server = set[pf+"host"]
+ return data.Marshal(mx)
+
+ // BOX
+ case enums.GNS_TYPE_BOX:
+ // assemble box
+ box := new(rr.BOX)
+ box.Proto, _ = util.CastFromString[uint16](set[pf+"proto"])
+ box.Svc, _ = util.CastFromString[uint16](set[pf+"svc"])
+ box.Type, _ = util.CastFromString[enums.GNSType](set[pf+"type"])
+
+ // handle TLSA and SRV cases
+ switch box.Type {
+ case enums.GNS_TYPE_DNS_TLSA:
+ tlsa := new(rr.TLSA)
+ tlsa.Usage, _ =
util.CastFromString[uint8](set[pf+"tlsa_usage"])
+ tlsa.Selector, _ =
util.CastFromString[uint8](set[pf+"tlsa_selector"])
+ tlsa.Match, _ =
util.CastFromString[uint8](set[pf+"tlsa_match"])
+ tlsa.Cert, _ = hex.DecodeString(set[pf+"tlsa_cert"])
+ box.RR, _ = data.Marshal(tlsa)
+
+ case enums.GNS_TYPE_DNS_SRV:
+ box.RR = util.WriteCString(set[pf+"srv_host"])
+ }
+ return data.Marshal(box)
+
+ // GNS2DNS
+ case enums.GNS_TYPE_GNS2DNS:
+ buf := util.WriteCString(set[pf+"name"])
+ return append(buf, util.WriteCString(set[pf+"server"])...), nil
+ }
+ return nil, errors.New("unknown RR type")
+}
+
+//======================================================================
+// Get list of allowed new RRs given a set of existing RRs.
+//======================================================================
+
+// Create a list of compatible record types from list of
+// existing record types.
+func compatibleRR(in []*enums.GNSSpec, label string) (out []*enums.GNSSpec) {
+ for _, t := range rrtypes {
+ if ok, forced := rr.CanCoexist(t, in, label); ok {
+ out = append(out, &enums.GNSSpec{
+ Type: t,
+ Flags: forced,
+ })
+ }
+ }
+ return
+}
diff --git a/src/gnunet/service/zonemaster/rpc.go
b/src/gnunet/service/zonemaster/rpc.go
new file mode 100644
index 0000000..2060e56
--- /dev/null
+++ b/src/gnunet/service/zonemaster/rpc.go
@@ -0,0 +1,24 @@
+// 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 zonemaster
+
+import "gnunet/service"
+
+func (s *Service) InitRPC(rpc *service.JRPCServer) {
+}
diff --git a/src/gnunet/service/zonemaster/service.go
b/src/gnunet/service/zonemaster/service.go
new file mode 100644
index 0000000..c73857f
--- /dev/null
+++ b/src/gnunet/service/zonemaster/service.go
@@ -0,0 +1,150 @@
+// 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 zonemaster
+
+import (
+ "context"
+ "fmt"
+ "io"
+
+ "gnunet/config"
+ "gnunet/core"
+ "gnunet/crypto"
+ "gnunet/message"
+ "gnunet/service"
+ "gnunet/service/dht/blocks"
+ "gnunet/transport"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/logger"
+)
+
+type ZoneIterator struct {
+ zk *crypto.ZonePrivate
+}
+
+//----------------------------------------------------------------------
+// "GNUnet Zonemaster" service implementation:
+// The zonemaster service handles Namestore messages
+//----------------------------------------------------------------------
+
+// Service implements a GNS service
+type Service struct {
+ Module
+
+ ZoneIters *util.Map[uint32, *ZoneIterator]
+}
+
+// NewService creates a new GNS service instance
+func NewService(ctx context.Context, c *core.Core) service.Service {
+ // instantiate service
+ mod := NewModule(ctx, c)
+ srv := &Service{
+ Module: *mod,
+ ZoneIters: util.NewMap[uint32, *ZoneIterator](),
+ }
+ // set external function references (external services)
+ srv.StoreLocal = srv.StoreNamecache
+ srv.StoreRemote = srv.StoreDHT
+
+ return srv
+}
+
+// ServeClient processes a client channel.
+func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connection) {
+ reqID := 0
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithCancel(ctx)
+
+ for {
+ // receive next message from client
+ reqID++
+ logger.Printf(logger.DBG, "[zonemaster:%d:%d] Waiting for
client request...\n", id, reqID)
+ msg, err := mc.Receive(ctx)
+ if err != nil {
+ if err == io.EOF {
+ logger.Printf(logger.INFO, "[zonemaster:%d:%d]
Client channel closed.\n", id, reqID)
+ } else if err == service.ErrConnectionInterrupted {
+ logger.Printf(logger.INFO, "[zonemaster:%d:%d]
Service operation interrupted.\n", id, reqID)
+ } else {
+ logger.Printf(logger.ERROR, "[zonemaster:%d:%d]
Message-receive failed: %s\n", id, reqID, err.Error())
+ }
+ break
+ }
+ logger.Printf(logger.INFO, "[zonemaster:%d:%d] Received
request: %v\n", id, reqID, msg)
+
+ // handle message
+ valueCtx := context.WithValue(ctx, core.CtxKey("label"),
fmt.Sprintf(":%d:%d", id, reqID))
+ s.HandleMessage(valueCtx, nil, msg, mc)
+ }
+ // close client connection
+ mc.Close()
+
+ // cancel all tasks running for this session/connection
+ logger.Printf(logger.INFO, "[zonemaster:%d] Start closing
session...\n", id)
+ cancel()
+}
+
+// Handle a single incoming message
+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)
+ }
+ // perform lookup
+ switch m := msg.(type) {
+
+ // start new zone iteration
+ case *message.NamestoreZoneIterStartMsg:
+ zi := new(ZoneIterator)
+ zi.zk = m.ZoneKey
+ s.ZoneIters.Put(m.ID, zi, 0)
+
+ default:
+ //----------------------------------------------------------
+ // UNKNOWN message type received
+ //----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[zonemaster%s] Unhandled message
of type (%s)\n", label, msg.Type())
+ return false
+ }
+ return true
+}
+
+// storeDHT stores a GNS block in the DHT.
+func (s *Service) StoreDHT(ctx context.Context, query blocks.Query, block
blocks.Block) (err error) {
+ // assemble DHT request
+ req := message.NewDHTP2PPutMsg(block)
+ req.Flags = query.Flags()
+ req.Key = query.Key().Clone()
+
+ // store block
+ _, err = service.RequestResponse(ctx, "zonemaster", "dht",
config.Cfg.DHT.Service.Socket, req, false)
+ return
+}
+
+// storeNamecache stores a GNS block in the local namecache.
+func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery,
block *blocks.GNSBlock) (err error) {
+ // assemble Namecache request
+ req := message.NewNamecacheCacheMsg(block)
+
+ // get response from Namecache service
+ _, err = service.RequestResponse(ctx, "zonemaster", "namecache",
config.Cfg.Namecache.Service.Socket, req, false)
+ return
+}
diff --git a/src/gnunet/service/zonemaster/zonemaster.go
b/src/gnunet/service/zonemaster/zonemaster.go
new file mode 100644
index 0000000..7c2a13c
--- /dev/null
+++ b/src/gnunet/service/zonemaster/zonemaster.go
@@ -0,0 +1,179 @@
+// 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 zonemaster
+
+import (
+ "context"
+ "gnunet/config"
+ "gnunet/enums"
+ "gnunet/service/dht/blocks"
+ "gnunet/service/store"
+ "gnunet/util"
+ "time"
+
+ "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// "GNS ZoneMaster" implementation:
+// Manage and publish local zone records
+//======================================================================
+
+// ZoneMaster instance
+type ZoneMaster struct {
+ cfg *config.Config // Zonemaster configuration
+ zdb *store.ZoneDB // ZoneDB connection
+ srv *Service // NameStore service
+}
+
+// NewZoneMaster initializes a new zone master instance.
+func NewZoneMaster(cfg *config.Config, srv *Service) *ZoneMaster {
+ zm := new(ZoneMaster)
+ zm.cfg = cfg
+ return zm
+}
+
+// Run zone master: connect to zone database and start the RPC/HTTP
+// services as background processes. Periodically publish GNS blocks
+// into the DHT.
+func (zm *ZoneMaster) Run(ctx context.Context) {
+ // connect to database
+ logger.Println(logger.INFO, "[zonemaster] Connecting to zone
database...")
+ dbFile, ok := util.GetParam[string](zm.cfg.ZoneMaster.Storage, "file")
+ if !ok {
+ logger.Printf(logger.ERROR, "[zonemaster] missing database file
specification")
+ return
+ }
+ var err error
+ if zm.zdb, err = store.OpenZoneDB(dbFile); err != nil {
+ logger.Printf(logger.ERROR, "[zonemaster] open database: %v",
err)
+ return
+ }
+ defer zm.zdb.Close()
+
+ // start HTTP GUI
+ zm.startGUI(ctx)
+ /*
+ // publish on start-up
+ if err = zm.Publish(ctx); err != nil {
+ logger.Printf(logger.ERROR, "[zonemaster] initial
publish failed: %s", err.Error())
+ return
+ }
+ */
+ // periodically publish GNS blocks to the DHT
+ tick := time.NewTicker(time.Duration(zm.cfg.ZoneMaster.Period) *
time.Second)
+loop:
+ for {
+ select {
+ case <-tick.C:
+ if err := zm.Publish(ctx); err != nil {
+ logger.Printf(logger.ERROR, "[zonemaster]
periodic publish failed: %s", err.Error())
+ }
+
+ // check for termination
+ case <-ctx.Done():
+ break loop
+ }
+ }
+}
+
+// OnChange is called if a zone or record has changed or was inserted
+func (zm *ZoneMaster) OnChange(table string, id int64, mode int) {
+}
+
+// Publish all zone labels to the DHT
+func (zm *ZoneMaster) Publish(ctx context.Context) error {
+ // collect all zones
+ zones, err := zm.zdb.GetZones("")
+ if err != nil {
+ return err
+ }
+ for _, z := range zones {
+ // collect labels for zone
+ var labels []*store.Label
+ if labels, err = zm.zdb.GetLabels("zid=%d", z.ID); err != nil {
+ return err
+ }
+ for _, l := range labels {
+ // publish label
+ if err = zm.PublishZoneLabel(ctx, z, l); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// PublishZoneLabel with public records
+func (zm *ZoneMaster) PublishZoneLabel(ctx context.Context, zone *store.Zone,
label *store.Label) error {
+ zk := zone.Key.Public()
+ logger.Printf(logger.INFO, "[zonemaster] Publishing label '%s' of zone
%s", label.Name, zk.ID())
+
+ // collect public records for zone label
+ recs, err := zm.zdb.GetRecords("lid=%d and flags&%d = 0", label.ID,
enums.GNS_FLAG_PRIVATE)
+ if err != nil {
+ return err
+ }
+ // assemble record set and find earliest expiration
+ expire := util.AbsoluteTimeNever()
+ rrSet := blocks.NewRecordSet()
+ for _, r := range recs {
+ if r.Expire.Compare(expire) < 0 {
+ expire = r.Expire
+ }
+ rrSet.AddRecord(&r.ResourceRecord)
+ }
+ rrSet.SetPadding()
+ if rrSet.Count == 0 {
+ logger.Println(logger.INFO, "[zonemaster] No resource records
-- skipped")
+ return nil
+ }
+
+ // assemble GNS query
+ query := blocks.NewGNSQuery(zk, label.Name)
+
+ // assemble, encrypt and sign GNS block
+ blk, _ := blocks.NewGNSBlock().(*blocks.GNSBlock)
+
+ blk.Body.Expire = expire
+ blk.Body.Data, err = zk.Encrypt(rrSet.Bytes(), label.Name, expire)
+ if err != nil {
+ return err
+ }
+ dzk, _, err := zone.Key.Derive(label.Name, "gns")
+ if err != nil {
+ return err
+ }
+ if err = blk.Sign(dzk); err != nil {
+ return err
+ }
+
+ // DEBUG:
+ // logger.Printf(logger.DBG, "[zonemaster] Query key = %s",
hex.EncodeToString(query.Key().Data))
+ // logger.Printf(logger.DBG, "[zonemaster] Block data = %s",
hex.EncodeToString(blk.Bytes()))
+
+ // publish GNS block to DHT and Namecache
+ if err = zm.srv.StoreDHT(ctx, query, blk); err != nil {
+ return err
+ }
+ if err = zm.srv.StoreNamecache(ctx, query, blk); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 97d884a..b4a1776 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -19,6 +19,7 @@
package util
import (
+ "bytes"
"fmt"
)
@@ -114,14 +115,13 @@ func CopyAlignedBlock(out, in []byte) {
// not terminated, it is skipped.
func StringList(b []byte) []string {
res := make([]string, 0)
- str := ""
- for _, ch := range b {
- if ch == 0 {
+ pos := 0
+ var str string
+ for pos != -1 {
+ str, pos = ReadCString(b, pos)
+ if len(str) > 0 {
res = append(res, str)
- str = ""
- continue
}
- str += string(ch)
}
return res
}
@@ -137,3 +137,11 @@ func ReadCString(buf []byte, pos int) (string, int) {
}
return "", -1
}
+
+// WriteCString returns the binary C-representation of a string
+func WriteCString(s string) []byte {
+ buf := new(bytes.Buffer)
+ _, _ = buf.WriteString(s)
+ _ = buf.WriteByte(0)
+ return buf.Bytes()
+}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index c5fd308..aeda678 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -21,6 +21,7 @@ package util
import (
"encoding/hex"
"encoding/json"
+ "fmt"
"strings"
"github.com/bfix/gospel/data"
@@ -73,6 +74,21 @@ func GetParam[V any](params ParameterSet, key string) (i V,
ok bool) {
return
}
+// CastFromString a string to given type (only works for intrinsic type)
+func CastFromString[V any](s string) (i V, ok bool) {
+ num, err := fmt.Sscanf(s, "%v", &i)
+ ok = true
+ if err != nil || num != 1 {
+ ok = false
+ }
+ return
+}
+
+// CastToString returns a string representation (for intrinsic types)
+func CastToString(v any) string {
+ return fmt.Sprintf("%v", v)
+}
+
//----------------------------------------------------------------------
// additional helpers
//----------------------------------------------------------------------
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index cf3739a..c49755c 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -60,6 +60,11 @@ func AbsoluteTimeNever() AbsoluteTime {
return AbsoluteTime{math.MaxUint64}
}
+// IsNever returns true if time is "never"
+func (t AbsoluteTime) IsNever() bool {
+ return t.Val == math.MaxUint64
+}
+
// Epoch returns the seconds since Unix epoch.
func (t AbsoluteTime) Epoch() uint64 {
return t.Val / 1000000
--
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: Initial revision of Zonemaster implementation.,
gnunet <=