Skip to content

Commit c535b27

Browse files
nolashzelig
authored andcommitted
network: Add adaptive capabilities message (ethersphere#1619)
* network: Add Capability and Capabilities types * network: Tests pass (but cleanup needed) * network: Remove useless pointers * network: Add missing file, extend cap's test, reset altered tests * network: Add check and test for 'invalid' capabilities * network: Move capabilities to BzzAddr * network: Remove redundant Capabilities member on BzzPeer * network: Add API and test, move adaptive tests to sep file * network: Conceal capability type * network: Change Capabilities to struct, intro change chan * network: Add event channel subscribe * network: Add cleanup on service stop * network: Add test for notify in API test * network: Add rpc module Subscribe * network: Reinstate internal channel notify test * network: Extract API to separate file * network: Remove API files (will be PRd separately) * network: Add comments to code * pss: Remove stray alteration in pss * network: Speling * network: Bump protocol version, as rebase updates crossed last one * network: Delint * network: Make Capabilities in HandshakeMsg serializable * network: CapabilitiesMsg add get() instead of fromMsg() * network: WIP Simplify capabilities objects * network: WIP Rehabilitate tests after simplification * network: WIP revert to rlp encode and export cap/caps fields * network: Fixed full and light comparisons * network: WIP custom RLP decoder for Capabilities collection * network: Capabilities RLP Decoder now re-populates id to array idx map * network: Tidy up a bit * network: Add missing tests * network: Rename adaptive.go * network: name correction * network: Remove unused variable * network: Replace duplicate ops in rlp decode, simplify names, typos * network: Add comment on cap id index + remove leftover debug * network: Remove unused import * network: Remove comment * network: Remove really annoying stdout line * network: Add pointer receivers due to allow mutex reference passing * network: Rename Id->ID, pointer rcv in String()
1 parent 3a13b0d commit c535b27

File tree

4 files changed

+471
-31
lines changed

4 files changed

+471
-31
lines changed

network/adaptive.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package network
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
"github.com/ethereum/go-ethereum/rlp"
8+
)
9+
10+
// CapabilityID defines a unique type of capability
11+
type CapabilityID uint64
12+
13+
// Capability contains a bit vector of flags that define what capability a node has in a specific module
14+
// The module is defined by the Id.
15+
type Capability struct {
16+
Id CapabilityID
17+
Cap []bool
18+
}
19+
20+
// NewCapability initializes a new Capability with the given id and specified number of bits in the vector
21+
func NewCapability(id CapabilityID, bitCount int) *Capability {
22+
return &Capability{
23+
Id: id,
24+
Cap: make([]bool, bitCount),
25+
}
26+
}
27+
28+
// Set switches the bit at the specified index on
29+
func (c *Capability) Set(idx int) error {
30+
l := len(c.Cap)
31+
if idx > l-1 {
32+
return fmt.Errorf("index %d out of bounds (len=%d)", idx, l)
33+
}
34+
c.Cap[idx] = true
35+
return nil
36+
}
37+
38+
// Unset switches the bit at the specified index off
39+
func (c *Capability) Unset(idx int) error {
40+
l := len(c.Cap)
41+
if idx > l-1 {
42+
return fmt.Errorf("index %d out of bounds (len=%d)", idx, l)
43+
}
44+
c.Cap[idx] = false
45+
return nil
46+
}
47+
48+
// String implements Stringer interface
49+
func (c *Capability) String() (s string) {
50+
s = fmt.Sprintf("%d:", c.Id)
51+
for _, b := range c.Cap {
52+
if b {
53+
s += "1"
54+
} else {
55+
s += "0"
56+
}
57+
}
58+
return s
59+
}
60+
61+
// IsSameAs returns true if the given Capability object has the identical bit settings as the receiver
62+
func (c *Capability) IsSameAs(cp *Capability) bool {
63+
if cp == nil {
64+
return false
65+
}
66+
return isSameBools(c.Cap, cp.Cap)
67+
}
68+
69+
func isSameBools(left []bool, right []bool) bool {
70+
if len(left) != len(right) {
71+
return false
72+
}
73+
for i, b := range left {
74+
if b != right[i] {
75+
return false
76+
}
77+
}
78+
return true
79+
}
80+
81+
// Capabilities is the collection of capabilities for a Swarm node
82+
// It is used both to store the capabilities in the node, and
83+
// to communicate the node capabilities to its peers
84+
type Capabilities struct {
85+
idx map[CapabilityID]int // maps the CapabilityIDs to their position in the Caps vector
86+
Caps []*Capability
87+
mu sync.Mutex
88+
}
89+
90+
// NewCapabilities initializes a new Capabilities object
91+
func NewCapabilities() *Capabilities {
92+
return &Capabilities{
93+
idx: make(map[CapabilityID]int),
94+
}
95+
}
96+
97+
// adds a capability to the Capabilities collection
98+
func (c *Capabilities) add(cp *Capability) error {
99+
if _, ok := c.idx[cp.Id]; ok {
100+
return fmt.Errorf("Capability id %d already registered", cp.Id)
101+
}
102+
c.mu.Lock()
103+
defer c.mu.Unlock()
104+
c.Caps = append(c.Caps, cp)
105+
c.idx[cp.Id] = len(c.Caps) - 1
106+
return nil
107+
}
108+
109+
// gets the capability with the specified module id
110+
// returns nil if the id doesn't exist
111+
func (c *Capabilities) get(id CapabilityID) *Capability {
112+
idx, ok := c.idx[id]
113+
if !ok {
114+
return nil
115+
}
116+
return c.Caps[idx]
117+
}
118+
119+
// String Implements Stringer interface
120+
func (c *Capabilities) String() (s string) {
121+
for _, cp := range c.Caps {
122+
if s != "" {
123+
s += ","
124+
}
125+
s += cp.String()
126+
}
127+
return s
128+
}
129+
130+
// DecodeRLP implements rlp.RLPDecoder
131+
// this custom deserializer builds the module id to array index map
132+
// state of receiver is undefined on error
133+
func (c *Capabilities) DecodeRLP(s *rlp.Stream) error {
134+
135+
// make sure we have a pristine receiver
136+
c.idx = make(map[CapabilityID]int)
137+
c.Caps = []*Capability{}
138+
139+
// discard the Capabilities struct list item
140+
_, err := s.List()
141+
if err != nil {
142+
return err
143+
}
144+
145+
// discard the Capabilities Caps array list item
146+
_, err = s.List()
147+
if err != nil {
148+
return err
149+
}
150+
151+
// All elements in array should be Capability type
152+
for {
153+
var cap Capability
154+
155+
// Decode the Capability from the list item
156+
// if error means the end of the list we're done
157+
// if not then oh-oh spaghettio's
158+
err := s.Decode(&cap)
159+
if err != nil {
160+
if err == rlp.EOL {
161+
break
162+
}
163+
return err
164+
}
165+
166+
// Add the entry to the Capabilities array
167+
c.add(&cap)
168+
}
169+
170+
return nil
171+
}

network/adaptive_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package network
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/ethereum/go-ethereum/rlp"
8+
)
9+
10+
// TestCapabilitySetUnset tests that setting and unsetting bits yield expected results
11+
func TestCapabilitySetUnset(t *testing.T) {
12+
firstSet := []bool{
13+
true, false, false, false, false, false, true, true, false,
14+
} // 1000 0011 0
15+
firstResult := firstSet
16+
secondSet := []bool{
17+
false, true, false, true, false, false, true, false, true,
18+
} // 0101 0010 1
19+
secondResult := []bool{
20+
true, true, false, true, false, false, true, true, true,
21+
} // 1101 0011 1
22+
thirdUnset := []bool{
23+
true, false, true, true, false, false, true, false, true,
24+
} // 1011 0010 1
25+
thirdResult := []bool{
26+
false, true, false, false, false, false, false, true, false,
27+
} // 0100 0001 0
28+
29+
c := NewCapability(42, 9)
30+
for i, b := range firstSet {
31+
if b {
32+
c.Set(i)
33+
}
34+
}
35+
if !isSameBools(c.Cap, firstResult) {
36+
t.Fatalf("first set result mismatch, expected %v, got %v", firstResult, c.Cap)
37+
}
38+
39+
for i, b := range secondSet {
40+
if b {
41+
c.Set(i)
42+
}
43+
}
44+
if !isSameBools(c.Cap, secondResult) {
45+
t.Fatalf("second set result mismatch, expected %v, got %v", secondResult, c.Cap)
46+
}
47+
48+
for i, b := range thirdUnset {
49+
if b {
50+
c.Unset(i)
51+
}
52+
}
53+
if !isSameBools(c.Cap, thirdResult) {
54+
t.Fatalf("second set result mismatch, expected %v, got %v", thirdResult, c.Cap)
55+
}
56+
}
57+
58+
// TestCapabilitiesControl tests that the methods for manipulating the capabilities bitvectors set values correctly and return errors when they should
59+
func TestCapabilitiesControl(t *testing.T) {
60+
61+
// Initialize capability
62+
caps := NewCapabilities()
63+
64+
// Register module. Should succeed
65+
c1 := NewCapability(1, 16)
66+
err := caps.add(c1)
67+
if err != nil {
68+
t.Fatalf("RegisterCapabilityModule fail: %v", err)
69+
}
70+
71+
// Fail if capability id already exists
72+
c2 := NewCapability(1, 1)
73+
err = caps.add(c2)
74+
if err == nil {
75+
t.Fatalf("Expected RegisterCapabilityModule call with existing id to fail")
76+
}
77+
78+
// More than one capabilities flag vector should be possible
79+
c3 := NewCapability(2, 1)
80+
err = caps.add(c3)
81+
if err != nil {
82+
t.Fatalf("RegisterCapabilityModule (second) fail: %v", err)
83+
}
84+
}
85+
86+
// TestCapabilitiesString checks that the string representation of the capabilities is correct
87+
func TestCapabilitiesString(t *testing.T) {
88+
sets1 := []bool{
89+
false, false, true,
90+
}
91+
c1 := NewCapability(42, len(sets1))
92+
for i, b := range sets1 {
93+
if b {
94+
c1.Set(i)
95+
}
96+
}
97+
sets2 := []bool{
98+
true, false, false, false, true, false, true, false, true,
99+
}
100+
c2 := NewCapability(666, len(sets2))
101+
for i, b := range sets2 {
102+
if b {
103+
c2.Set(i)
104+
}
105+
}
106+
107+
caps := NewCapabilities()
108+
caps.add(c1)
109+
caps.add(c2)
110+
111+
correctString := "42:001,666:100010101"
112+
if correctString != caps.String() {
113+
t.Fatalf("Capabilities string mismatch; expected %s, got %s", correctString, caps)
114+
}
115+
}
116+
117+
// TestCapabilitiesRLP ensures that a round of serialization and deserialization of Capabilities object
118+
// results in the correct data
119+
func TestCapabilitiesRLP(t *testing.T) {
120+
c := NewCapabilities()
121+
cap1 := &Capability{
122+
Id: 42,
123+
Cap: []bool{true, false, true},
124+
}
125+
c.add(cap1)
126+
cap2 := &Capability{
127+
Id: 666,
128+
Cap: []bool{true, false, true, false, true, true, false, false, true},
129+
}
130+
c.add(cap2)
131+
buf := bytes.NewBuffer(nil)
132+
err := rlp.Encode(buf, &c)
133+
if err != nil {
134+
t.Fatal(err)
135+
}
136+
137+
cRestored := NewCapabilities()
138+
err = rlp.Decode(buf, &cRestored)
139+
if err != nil {
140+
t.Fatal(err)
141+
}
142+
143+
cap1Restored := cRestored.get(cap1.Id)
144+
if cap1Restored.Id != cap1.Id {
145+
t.Fatalf("cap 1 id not correct, expected %d, got %d", cap1.Id, cap1Restored.Id)
146+
}
147+
if !cap1.IsSameAs(cap1Restored) {
148+
t.Fatalf("cap 1 caps not correct, expected %v, got %v", cap1.Cap, cap1Restored.Cap)
149+
}
150+
151+
cap2Restored := cRestored.get(cap2.Id)
152+
if cap2Restored.Id != cap2.Id {
153+
t.Fatalf("cap 1 id not correct, expected %d, got %d", cap2.Id, cap2Restored.Id)
154+
}
155+
if !cap2.IsSameAs(cap2Restored) {
156+
t.Fatalf("cap 1 caps not correct, expected %v, got %v", cap2.Cap, cap2Restored.Cap)
157+
}
158+
}

0 commit comments

Comments
 (0)