gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnunet-go-plugins] 03/04: eample-go: initial revision.


From: gnunet
Subject: [gnunet-go-plugins] 03/04: eample-go: initial revision.
Date: Sun, 30 Oct 2022 18:22:43 +0100

This is an automated email from the git hooks/post-receive script.

bernd-fix pushed a commit to branch master
in repository gnunet-go-plugins.

commit e69bd785b5b04e22bab59b73e030e98523818233
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Sun Oct 30 18:15:07 2022 +0100

    eample-go: initial revision.
---
 example-go/Makefile       |  30 ++++++++
 example-go/gui.htpl       | 111 +++++++++++++++++++++++++++
 example-go/main.go        | 189 ++++++++++++++++++++++++++++++++++++++++++++++
 example-go/plugin_test.go | 109 ++++++++++++++++++++++++++
 example-go/records.go     |  96 +++++++++++++++++++++++
 5 files changed, 535 insertions(+)

diff --git a/example-go/Makefile b/example-go/Makefile
new file mode 100644
index 0000000..f4fa800
--- /dev/null
+++ b/example-go/Makefile
@@ -0,0 +1,30 @@
+# This file is part of gnunet-go-plugins, a collection of plugins for
+# the GNUnet-implementation in Golang (gnunet.go). Plugins are used to
+# implement type-specific processing of resource records (e.g. GUI).
+# Copyright (C) 2022 by the authors:
+#
+# * Bernd Fix <brf@hoi-polloi.org>  >Y<
+#
+# gnunet-go-plugins 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
+
+
+all: ${GOPATH}/bin/plugin_example_go.so
+
+clean: ${GOPATH}/bin/plugin_example_go.so
+       rm -f $^
+
+${GOPATH}/bin/plugin_example_go.so: main.go gui.htpl
+       go build -buildmode=plugin -trimpath -gcflags="-N -l" -o $@ ./...
diff --git a/example-go/gui.htpl b/example-go/gui.htpl
new file mode 100644
index 0000000..8317a72
--- /dev/null
+++ b/example-go/gui.htpl
@@ -0,0 +1,111 @@
+{{define "new_myrecords"}}
+    <!-- DEBUG: show template parameters -->
+    <div>
+        <h3>Template data:</h3>
+       <p>Ref = {{.Ref}}</p>
+           <p>Action = {{.Action}}</p>
+           <p>Button = {{.Button}}</p>
+        <p>Names: {{range $i,$v := .Names}}'{{$v}}',{{end}}</p>
+        <p>RRSpecs:</p>
+        <ul>
+            {{range $i,$s := .RRspecs}}
+            <li>Type = {{$s.Type}}, Flags = {{$s.Flags}}</li>
+            {{end}}
+        </ul>
+        <p>Params:</p>
+        <ul>
+        {{range $k,$v := .Params}}
+            <li>'{{$k}}' = '{{$v}}'</li>
+        {{end}}
+        </ul>
+    </div>
+
+    {{$data := .}}
+    <div>
+        <h3>Creating a new custom 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 "MYRECTYPE1"}}{{template "MYRECTYPE1" 
$data}}{{end}}
+                    {{if eq $t "MYRECTYPE2"}}{{template "MYRECTYPE2" 
$data}}{{end}}
+                </section>
+                {{end}}
+            </div>
+        </div>
+        <a href="/"><button>Leave</button></a>
+    </div>
+{{end}}
+
+{{define "edit_myrecords"}}
+    <!-- DEBUG: show template parameters -->
+    <div>
+        <h3>Template data:</h3>
+       <p>Ref = {{.Ref}}</p>
+           <p>Action = {{.Action}}</p>
+           <p>Button = {{.Button}}</p>
+        <p>Names: {{range $i,$v := .Names}}'{{$v}}',{{end}}</p>
+        <ul>
+        {{range $k,$v := .Params}}
+            <li>'{{$k}}' = '{{$v}}'</li>
+        {{end}}
+        </ul>
+    </div>
+
+    <div>
+        <h3>Edit a resource record for label {{index .Params "label"}}:</h3>
+        <p><small>(Created: {{index .Params "created"}}, Last edited: {{index 
.Params "modified"}})</small></p>
+        {{$t := rritype (index .Params "type")}}
+        {{if eq $t "MYRECTYPE1"}}{{template "MYRECTYPE1" .}}{{end}}
+        {{if eq $t "MYRECTYPE2"}}{{template "MYRECTYPE2" .}}{{end}}
+    </div>
+    <a href="/"><button>Leave</button></a>
+{{end}}
+
+{{define "MYRECTYPE1"}}
+    <h3>RECTYPE1</h3>
+    <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action 
"upd"}}method="post"{{end}}>
+        <input type="hidden" name="type" value="{{index .Params "type"}}">
+        <table>
+            <tr>
+                <td align="right"><b>Attribute1:</b></td>
+                <td>
+                    <input type="text" name="myrec1_text"
+                        maxlength="63" size="63"
+                        autofocus required
+                        value="{{index .Params "myrec1_text"}}"
+                    >
+                </td>
+            </tr>
+            {{template "RRCommon" .}}
+            <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
+        </table>
+    </form>
+{{end}}
+
+{{define "MYRECTYPE2"}}
+    <h3>RECTYPE2</h3>
+    <form action="/action/{{.Action}}/rr/{{.Ref}}" {{if eq .Action 
"upd"}}method="post"{{end}}>
+        <input type="hidden" name="type" value="{{index .Params "type"}}">
+        <table>
+            <tr>
+                <td align="right"><b>Attribute1:</b></td>
+                <td>
+                    <input type="text" name="myrec2_text"
+                        maxlength="63" size="63"
+                        autofocus required
+                        value="{{index .Params "myrec2_text"}}"
+                    >
+                </td>
+            </tr>
+            {{template "RRCommon" .}}
+            <tr><td/><td><button id="submit">{{.Button}} 
record</button></td></tr>
+        </table>
+    </form>
+{{end}}
diff --git a/example-go/main.go b/example-go/main.go
new file mode 100644
index 0000000..7a6f66a
--- /dev/null
+++ b/example-go/main.go
@@ -0,0 +1,189 @@
+// This file is part of gnunet-go-plugins, a collection of plugins for
+// the GNUnet-implementation in Golang (gnunet.go). Plugins are used to
+// implement type-specific processing of resource records (e.g. GUI).
+// Copyright (C) 2022 by the authors:
+//
+// * Bernd Fix <brf@hoi-polloi.org>  >Y<
+//
+// gnunet-go-plugins is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+       _ "embed"
+       "fmt"
+       "strconv"
+
+       "github.com/bfix/gospel/data"
+)
+
+//======================================================================
+//   C U S T O M I Z A T I O N
+//======================================================================
+
+// list of custom record types
+//
+// TODO: list record types handled by the plugin
+const (
+       RECTYPE1 = 90666
+       RECTYPE2 = 90667
+)
+
+//----------------------------------------------------------------------
+// Interface implementation
+//----------------------------------------------------------------------
+
+// Name returns the plugin name (sub-system name)
+//
+// TODO: customize name of the plugin
+func (p *CustomPlugin) Name() string {
+       return "Example-Go"
+}
+
+// CanHandle returns a list of resource record types that are handled
+// by the plugin.
+//
+// TODO: customize record type list
+func (p *CustomPlugin) CanHandle() (list []uint32) {
+       return []uint32{
+               RECTYPE1,
+               RECTYPE2,
+       }
+}
+
+// Compute a set of record specs allowed under a label with existing records
+func (p *CustomPlugin) Compatible(label string, rrSpecs [][2]uint32) 
[][2]uint32 {
+       // simple case: no restrictions (all types, all flags)
+       list := [][2]uint32{
+               {RECTYPE1, 0},
+               {RECTYPE2, 0},
+       }
+       return list
+}
+
+// TemplateNames returns the names of the new / edit template
+// TODO: customize template names (see gui.htpl)
+func (p *CustomPlugin) TemplateNames() (string, string) {
+       return "new_myrecords", "edit_myrecords"
+}
+
+// Prefix returns the prefix for record attributes in map
+func (p *CustomPlugin) Prefix(t uint32) string {
+       switch t {
+       case RECTYPE1:
+               return "rectype1"
+       case RECTYPE2:
+               return "rectype2"
+       }
+       return ""
+}
+
+//======================================================================
+// No need to customize code beond this point...
+//======================================================================
+
+// Plugin is the instance of a custom implementation accessed by the
+// gnunet-go framework
+var Plugin = NewCustomPlugin()
+
+// ExamplePlugin is an example plugin for custom records that implements
+// the zonemaster.Plugin interface.
+type CustomPlugin struct{}
+
+// NewCustomPlugin creates an initialized plugin instance
+func NewCustomPlugin() CustomPlugin {
+       return CustomPlugin{}
+}
+
+//go:embed gui.htpl
+var tpl []byte
+
+// Template returns the new / edit template for resource record types
+// handled by the plugin.
+func (p *CustomPlugin) Template() string {
+       return string(tpl)
+}
+
+// Value returns a human-readable description of RR data
+func (p *CustomPlugin) Value(t uint32, rr []byte) (string, error) {
+       rec, err := p.GetInstance(t, rr)
+       if err != nil {
+               return "", err
+       }
+       return rec.Value()
+}
+
+// ToMap converts resource record data into GUI template variables
+func (p *CustomPlugin) ToMap(t uint32, rr []byte) (map[string]string, error) {
+       rec, err := p.GetInstance(t, rr)
+       if err != nil {
+               return nil, err
+       }
+       return rec.ToMap()
+}
+
+// FromMap converts a GUI template variables into resource record data
+func (p *CustomPlugin) FromMap(t uint32, vars map[string]string) ([]byte, 
error) {
+       rec, err := p.GetInstance(t, nil)
+       if err != nil {
+               return nil, err
+       }
+       if err = rec.FromMap(vars); err != nil {
+               return nil, err
+       }
+       return data.Marshal(rec)
+}
+
+// CustomRecord interface impemented by custom types.
+type CustomRecord interface {
+       Value() (string, error)
+       ToMap() (map[string]string, error)
+       FromMap(map[string]string) error
+}
+
+// Get record instance for given type and data
+// TODO: customize to own needs
+func (p *CustomPlugin) GetInstance(t uint32, buf []byte) (rec CustomRecord, 
err error) {
+       switch t {
+       case RECTYPE1:
+               rec = new(MyRecord1)
+       case RECTYPE2:
+               rec = new(MyRecord2)
+       }
+       if buf != nil {
+               err = data.Unmarshal(rec, buf)
+       }
+       return
+}
+
+// Numbers convertable from parameter value (string)
+type Number interface {
+       uint16 | uint32 | int | int16 | int32 | float32 | float64
+}
+
+// convert parameter value (string) to given number type
+func asInt[T Number](params map[string]string, key string) (val T, err error) {
+       vs, ok := params[key]
+       if !ok {
+               err = fmt.Errorf("%s missing", key)
+               return
+       }
+       var v int
+       if v, err = strconv.Atoi(vs); err != nil {
+               return
+       }
+       return T(v), nil
+}
diff --git a/example-go/plugin_test.go b/example-go/plugin_test.go
new file mode 100644
index 0000000..d002789
--- /dev/null
+++ b/example-go/plugin_test.go
@@ -0,0 +1,109 @@
+// This file is part of gnunet-go-plugins, a collection of plugins for
+// the GNUnet-implementation in Golang (gnunet.go). Plugins are used to
+// implement type-specific processing of resource records (e.g. GUI).
+// Copyright (C) 2022 by the authors:
+//
+// * Bernd Fix <brf@hoi-polloi.org>  >Y<
+//
+// gnunet-go-plugins is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+       "bytes"
+       "encoding/hex"
+       "testing"
+)
+
+func TestPluginCanHandle(t *testing.T) {
+       list := Plugin.CanHandle()
+       if len(list) == 0 {
+               t.Fatal("no type list received from plugin")
+       }
+       for _, rr := range list {
+               t.Logf("Can handle %d\n", rr)
+       }
+}
+
+func TestPluginCompatible(t *testing.T) {
+       in := make([][2]uint32, 0)
+       out := Plugin.Compatible("@", in)
+       for _, s := range out {
+               t.Logf("Compatible: type=%d, flags=%d\n", s[0], s[1])
+       }
+}
+
+func TestPluginPrefix(t *testing.T) {
+       t.Logf("Prefix: '%s' (%d)\n", Plugin.Prefix(90666), 90666)
+       t.Logf("Prefix: '%s' (%d)\n", Plugin.Prefix(90667), 90667)
+}
+
+var (
+       rec1 = []byte{0x00, 0x03, 0x66, 0x6f, 0x6f}
+       rec2 = []byte{0x00, 0x00, 0x00, 0x23, 0x00, 0x42}
+)
+
+func TestPluginValue(t *testing.T) {
+       s1, err := Plugin.Value(90666, rec1)
+       if err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("rec1: text='%s'\n", s1)
+
+       s2, err := Plugin.Value(90667, rec2)
+       if err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("rec2: '%s'\n", s2)
+}
+
+func TestPluginToMap(t *testing.T) {
+       params, err := Plugin.ToMap(90666, rec1)
+       if err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("ToMap: rec1 -> %v\n", params)
+
+       if params, err = Plugin.ToMap(90667, rec2); err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("ToMap: rec2 -> %v\n", params)
+}
+
+func TestPluginFromMap(t *testing.T) {
+       params := map[string]string{
+               "rectype1_text": "foo",
+       }
+       rr, err := Plugin.FromMap(90666, params)
+       if err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("FromMap: rr=%s\n", hex.EncodeToString(rr))
+       if !bytes.Equal(rr, rec1) {
+               t.Fatal("FromMap for rectype1 failed")
+       }
+       params = map[string]string{
+               "rectype2_var1": "35",
+               "rectype2_var2": "66",
+       }
+       if rr, err = Plugin.FromMap(90667, params); err != nil {
+               t.Fatal(err)
+       }
+       t.Logf("FromMap: rr=%s\n", hex.EncodeToString(rr))
+       if !bytes.Equal(rr, rec2) {
+               t.Fatal("FromMap for rectype2 failed")
+       }
+}
diff --git a/example-go/records.go b/example-go/records.go
new file mode 100644
index 0000000..9053a4c
--- /dev/null
+++ b/example-go/records.go
@@ -0,0 +1,96 @@
+// This file is part of gnunet-go-plugins, a collection of plugins for
+// the GNUnet-implementation in Golang (gnunet.go). Plugins are used to
+// implement type-specific processing of resource records (e.g. GUI).
+// Copyright (C) 2022 by the authors:
+//
+// * Bernd Fix <brf@hoi-polloi.org>  >Y<
+//
+// gnunet-go-plugins is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+       "errors"
+       "fmt"
+       "strconv"
+)
+
+//----------------------------------------------------------------------
+// MyRecord1 (example custom record #1)
+//----------------------------------------------------------------------
+
+// MyRecord1 is an example record, where the record data is a simple
+// text string.
+type MyRecord1 struct {
+       TxtLen uint16 `order:"big"`   // length of text in NBO
+       Text   []byte `size:"TxtLen"` // text data (not terminated by \0)
+}
+
+// Value returns a human-readable representation of the record
+func (rec *MyRecord1) Value() (string, error) {
+       return string(rec.Text), nil
+}
+
+// ToMap returns the parameter set for the record
+func (rec *MyRecord1) ToMap() (params map[string]string, err error) {
+       params = make(map[string]string)
+       params["rectype1_text"] = string(rec.Text)
+       return
+}
+
+// FromMap reconstructs the record attributes from parameter list
+func (rec *MyRecord1) FromMap(params map[string]string) error {
+       text, ok := params["rectype1_text"]
+       if !ok {
+               return errors.New("rectype1_text missing")
+       }
+       rec.TxtLen = uint16(len(text))
+       rec.Text = []byte(text)
+       return nil
+}
+
+//----------------------------------------------------------------------
+// MyRecord2 (example custom record #2)
+//----------------------------------------------------------------------
+
+// MyRecord2 is an example record, where the record data contains two
+// integer values
+type MyRecord2 struct {
+       Var1 uint32 `order:"big"` // variable #1 in NBO
+       Var2 uint16 `order:"big"` // variable #2 in NBO
+}
+
+// Value returns a human-readable representation of the record
+func (rec *MyRecord2) Value() (string, error) {
+       return fmt.Sprintf("var1=%d,<br>var2=%d", rec.Var1, rec.Var2), nil
+}
+
+// ToMap returns the parameter set for the record
+func (rec *MyRecord2) ToMap() (params map[string]string, err error) {
+       params = make(map[string]string)
+       params["rectype2_var1"] = strconv.Itoa(int(rec.Var1))
+       params["rectype2_var2"] = strconv.Itoa(int(rec.Var2))
+       return
+}
+
+// FromMap reconstructs the record attributes from parameter list
+func (rec *MyRecord2) FromMap(params map[string]string) (err error) {
+       if rec.Var1, err = asInt[uint32](params, "rectype2_var1"); err != nil {
+               return
+       }
+       rec.Var2, err = asInt[uint16](params, "rectype2_var2")
+       return
+}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]