mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
lnwire: New API for feature vectors.
This is a rewrite of feature vectors in lnwire. This has a few benefits: 1) a simpler interface 2) separate structs for a plain set of feature bits and a feature vector with associated feature names their respective feature sets 3) loosened requirements that bits MUST be assigned in pairs 4) fix endianness of encoding/decoding
This commit is contained in:
parent
b4273d1eaa
commit
1633ab180f
@ -2,49 +2,24 @@ package lnwire
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// featureFlag represent the status of the feature optional/required and needed
|
// FeatureBit represents a feature that can be enabled in either a local or
|
||||||
// to allow future incompatible changes, or backward compatible changes.
|
// global feature vector at a specific bit position. Feature bits follow the
|
||||||
type featureFlag uint8
|
// "it's OK to be odd" rule, where features at even bit positions must be known
|
||||||
|
// to a node receiving them from a peer while odd bits do not. In accordance,
|
||||||
// String returns the string representation for the featureFlag.
|
// feature bits are usually assigned in pairs, first being assigned an odd bit
|
||||||
func (f featureFlag) String() string {
|
// position which may later be changed to the preceding even position once
|
||||||
switch f {
|
// knowledge of the feature becomes required on the network.
|
||||||
case OptionalFlag:
|
type FeatureBit uint16
|
||||||
return "optional"
|
|
||||||
case RequiredFlag:
|
|
||||||
return "required"
|
|
||||||
default:
|
|
||||||
return "<unknown>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// featureName represent the name of the feature and needed in order to have
|
|
||||||
// the compile errors if we specify wrong feature name.
|
|
||||||
type featureName string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// OptionalFlag represent the feature which we already have but it
|
// InitialRoutingSync is a local feature bit meaning that the receiving node
|
||||||
// isn't required yet, and if remote peer doesn't have this feature we
|
// should send a complete dump of routing information when a new connection
|
||||||
// may turn it off without disconnecting with peer.
|
// is established.
|
||||||
OptionalFlag featureFlag = 2 // 0b10
|
InitialRoutingSync FeatureBit = 3
|
||||||
|
|
||||||
// RequiredFlag represent the features which is required for proper
|
|
||||||
// peer interaction, we disconnect with peer if it doesn't have this
|
|
||||||
// particular feature.
|
|
||||||
RequiredFlag featureFlag = 1 // 0b01
|
|
||||||
|
|
||||||
// flagMask is a mask which is needed to extract feature flag value.
|
|
||||||
flagMask = 3 // 0b11
|
|
||||||
|
|
||||||
// flagBitsSize represent the size of the feature flag in bits. For
|
|
||||||
// more information read the init message specification.
|
|
||||||
flagBitsSize = 2
|
|
||||||
|
|
||||||
// maxAllowedSize is a maximum allowed size of feature vector.
|
// maxAllowedSize is a maximum allowed size of feature vector.
|
||||||
//
|
//
|
||||||
@ -61,61 +36,62 @@ const (
|
|||||||
maxAllowedSize = 32781
|
maxAllowedSize = 32781
|
||||||
)
|
)
|
||||||
|
|
||||||
// Feature represent the feature which is used on stage of initialization of
|
// LocalFeatures is a mapping of known connection-local feature bits to a
|
||||||
// feature vector. Initial feature flags might be changed dynamically later.
|
// descriptive name. All known local feature bits must be assigned a name in
|
||||||
type Feature struct {
|
// this mapping. Local features are those which are only sent to the peer and
|
||||||
Name featureName
|
// not advertised to the entire network. A full description of these feature
|
||||||
Flag featureFlag
|
// bits is provided in the BOLT-09 specification.
|
||||||
|
var LocalFeatures = map[FeatureBit]string{
|
||||||
|
InitialRoutingSync: "initial-routing-sync",
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeatureVector represents the global/local feature vector. With this
|
// GlobalFeatures is a mapping of known global feature bits to a descriptive
|
||||||
// structure you may set/get the feature by name and compare feature vector
|
// name. All known global feature bits must be assigned a name in this mapping.
|
||||||
// with remote one.
|
// Global features are those which are advertised to the entire network. A full
|
||||||
type FeatureVector struct {
|
// description of these feature bits is provided in the BOLT-09 specification.
|
||||||
// featuresMap is the map which stores the correspondence between
|
var GlobalFeatures map[FeatureBit]string
|
||||||
// feature name and its index within feature vector. Index within
|
|
||||||
// feature vector and actual binary position of feature are different
|
|
||||||
// things)
|
|
||||||
featuresMap map[featureName]int // name -> index
|
|
||||||
|
|
||||||
// flags is the map which stores the correspondence between feature
|
// RawFeatureVector represents a set of feature bits as defined in BOLT-09.
|
||||||
// index and its flag.
|
// A RawFeatureVector itself just stores a set of bit flags but can be used to
|
||||||
flags map[int]featureFlag // index -> flag
|
// construct a FeatureVector which binds meaning to each bit. Feature vectors
|
||||||
|
//can be serialized and deserialized to/from a byte representation that is
|
||||||
|
// transmitted in Lightning network messages.
|
||||||
|
type RawFeatureVector struct {
|
||||||
|
features map[FeatureBit]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeatureVector creates new instance of feature vector.
|
// NewRawFeatureVector creates a feature vector with all of the feature bits
|
||||||
func NewFeatureVector(features []Feature) *FeatureVector {
|
// given as arguments enabled.
|
||||||
featuresMap := make(map[featureName]int)
|
func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
|
||||||
flags := make(map[int]featureFlag)
|
fv := &RawFeatureVector{features: make(map[FeatureBit]bool)}
|
||||||
|
for _, bit := range bits {
|
||||||
for index, feature := range features {
|
fv.Set(bit)
|
||||||
featuresMap[feature.Name] = index
|
|
||||||
flags[index] = feature.Flag
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FeatureVector{
|
|
||||||
featuresMap: featuresMap,
|
|
||||||
flags: flags,
|
|
||||||
}
|
}
|
||||||
|
return fv
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFeatureFlag assign flag to the feature.
|
// IsSet returns whether a particular feature bit is enabled in the vector.
|
||||||
func (f *FeatureVector) SetFeatureFlag(name featureName, flag featureFlag) error {
|
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
|
||||||
position, ok := f.featuresMap[name]
|
return fv.features[feature]
|
||||||
if !ok {
|
|
||||||
return errors.Errorf("can't find feature with name: %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.flags[position] = flag
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serializedSize returns the number of bytes which is needed to represent
|
// Set marks a feature as enabled in the vector.
|
||||||
// feature vector in byte format.
|
func (fv *RawFeatureVector) Set(feature FeatureBit) {
|
||||||
func (f *FeatureVector) serializedSize() uint16 {
|
fv.features[feature] = true
|
||||||
// Find the largest index in f.flags
|
}
|
||||||
|
|
||||||
|
// Unset marks a feature as disabled in the vector.
|
||||||
|
func (fv *RawFeatureVector) Unset(feature FeatureBit) {
|
||||||
|
delete(fv.features, feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeSize returns the number of bytes needed to represent feature vector
|
||||||
|
// in byte format.
|
||||||
|
func (fv *RawFeatureVector) SerializeSize() int {
|
||||||
|
// Find the largest feature bit index
|
||||||
max := -1
|
max := -1
|
||||||
for index := range f.flags {
|
for feature := range fv.features {
|
||||||
|
index := int(feature)
|
||||||
if index > max {
|
if index > max {
|
||||||
max = index
|
max = index
|
||||||
}
|
}
|
||||||
@ -123,181 +99,134 @@ func (f *FeatureVector) serializedSize() uint16 {
|
|||||||
if max == -1 {
|
if max == -1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
// We calculate length via the largest index in f.flags so as to not
|
|
||||||
// get an index out of bounds in Encode's setFlag function.
|
// We calculate byte-length via the largest bit index
|
||||||
return uint16(math.Ceil(float64(flagBitsSize*(max+1)) / 8))
|
return max/8 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeatureVectorFromReader decodes the feature vector from binary
|
// Encode writes the feature vector in byte representation. Every feature
|
||||||
// representation and creates the instance of it. Every feature decoded as 2
|
// encoded as a bit, and the bit vector is serialized using the least number of
|
||||||
// bits where odd bit determine whether the feature is "optional" and even bit
|
// bytes. Since the bit vector length is variable, the first two bytes of the
|
||||||
// told us whether the feature is "required". The even/odd semantic allows
|
// serialization represent the length.
|
||||||
// future incompatible changes, or backward compatible changes. Bits generally
|
func (fv *RawFeatureVector) Encode(w io.Writer) error {
|
||||||
// assigned in pairs, so that optional features can later become compulsory.
|
|
||||||
func NewFeatureVectorFromReader(r io.Reader) (*FeatureVector, error) {
|
|
||||||
f := &FeatureVector{
|
|
||||||
flags: make(map[int]featureFlag),
|
|
||||||
}
|
|
||||||
|
|
||||||
getFlag := func(data []byte, position int) featureFlag {
|
|
||||||
byteNumber := uint(position / 8)
|
|
||||||
bitNumber := uint(position % 8)
|
|
||||||
|
|
||||||
return featureFlag((data[byteNumber] >> bitNumber) & flagMask)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the length of the feature vector.
|
|
||||||
var l [2]byte
|
|
||||||
if _, err := io.ReadFull(r, l[:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
length := binary.BigEndian.Uint16(l[:])
|
|
||||||
|
|
||||||
// Read the feature vector data.
|
|
||||||
data := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(r, data[:]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize feature vector.
|
|
||||||
bitsNumber := len(data) * 8
|
|
||||||
for position := 0; position <= bitsNumber-flagBitsSize; position += flagBitsSize {
|
|
||||||
flag := getFlag(data, position)
|
|
||||||
switch flag {
|
|
||||||
case OptionalFlag, RequiredFlag:
|
|
||||||
// Every feature/flag takes 2 bits, so in order to get
|
|
||||||
// the feature/flag index we should divide position
|
|
||||||
// on 2.
|
|
||||||
index := position / flagBitsSize
|
|
||||||
f.flags[index] = flag
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode encodes the features vector into bytes representation, every feature
|
|
||||||
// encoded as 2 bits where odd bit determine whether the feature is "optional"
|
|
||||||
// and even bit told us whether the feature is "required". The even/odd
|
|
||||||
// semantic allows future incompatible changes, or backward compatible changes.
|
|
||||||
// Bits generally assigned in pairs, so that optional features can later become
|
|
||||||
// compulsory.
|
|
||||||
func (f *FeatureVector) Encode(w io.Writer) error {
|
|
||||||
setFlag := func(data []byte, position int, flag featureFlag) {
|
|
||||||
byteNumber := uint(position / 8)
|
|
||||||
bitNumber := uint(position % 8)
|
|
||||||
|
|
||||||
data[byteNumber] |= (byte(flag) << bitNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write length of feature vector.
|
// Write length of feature vector.
|
||||||
var l [2]byte
|
var l [2]byte
|
||||||
length := f.serializedSize()
|
length := fv.SerializeSize()
|
||||||
binary.BigEndian.PutUint16(l[:], length)
|
binary.BigEndian.PutUint16(l[:], uint16(length))
|
||||||
if _, err := w.Write(l[:]); err != nil {
|
if _, err := w.Write(l[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the data and write it.
|
// Generate the data and write it.
|
||||||
data := make([]byte, length)
|
data := make([]byte, length)
|
||||||
for index, flag := range f.flags {
|
for feature := range fv.features {
|
||||||
// Every feature takes 2 bits, so in order to get the feature
|
byteIndex := int(feature / 8)
|
||||||
// bits position we should multiply index by 2.
|
bitIndex := feature % 8
|
||||||
position := index * flagBitsSize
|
data[length-byteIndex-1] |= 1 << bitIndex
|
||||||
setFlag(data, position, flag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := w.Write(data)
|
_, err := w.Write(data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare checks that features are compatible and returns the features which
|
// Decode reads the feature vector from its byte representation. Every feature
|
||||||
// were present in both remote and local feature vectors. If remote/local node
|
// encoded as a bit, and the bit vector is serialized using the least number of
|
||||||
// doesn't have the feature and local/remote node require it than such vectors
|
// bytes. Since the bit vector length is variable, the first two bytes of the
|
||||||
// are incompatible.
|
// serialization represent the length.
|
||||||
func (f *FeatureVector) Compare(f2 *FeatureVector) (*SharedFeatures, error) {
|
func (fv *RawFeatureVector) Decode(r io.Reader) error {
|
||||||
shared := newSharedFeatures(f.Copy())
|
// Read the length of the feature vector.
|
||||||
|
var l [2]byte
|
||||||
|
if _, err := io.ReadFull(r, l[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint16(l[:])
|
||||||
|
|
||||||
for index, flag := range f.flags {
|
// Read the feature vector data.
|
||||||
if _, exist := f2.flags[index]; !exist {
|
data := make([]byte, length)
|
||||||
switch flag {
|
if _, err := io.ReadFull(r, data); err != nil {
|
||||||
case RequiredFlag:
|
return err
|
||||||
return nil, errors.New("Remote node hasn't " +
|
|
||||||
"locally required feature")
|
|
||||||
case OptionalFlag:
|
|
||||||
// If feature is optional and remote side
|
|
||||||
// haven't it than it might be safely disabled.
|
|
||||||
delete(shared.flags, index)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If feature exists on both sides than such feature might be
|
|
||||||
// considered as active.
|
|
||||||
shared.flags[index] = flag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, flag := range f2.flags {
|
// Set feature bits from parsed data.
|
||||||
if _, exist := f.flags[index]; !exist {
|
bitsNumber := len(data) * 8
|
||||||
switch flag {
|
for i := 0; i < bitsNumber; i++ {
|
||||||
case RequiredFlag:
|
byteIndex := uint16(i / 8)
|
||||||
return nil, errors.New("Local node hasn't " +
|
bitIndex := uint(i % 8)
|
||||||
"locally required feature")
|
if (data[length-byteIndex-1]>>bitIndex)&1 == 1 {
|
||||||
case OptionalFlag:
|
fv.Set(FeatureBit(i))
|
||||||
// If feature is optional and local side
|
|
||||||
// haven't it than it might be safely disabled.
|
|
||||||
delete(shared.flags, index)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If feature exists on both sides than such feature might be
|
|
||||||
// considered as active.
|
|
||||||
shared.flags[index] = flag
|
|
||||||
}
|
|
||||||
|
|
||||||
return shared, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy generate new distinct instance of the feature vector.
|
|
||||||
func (f *FeatureVector) Copy() *FeatureVector {
|
|
||||||
features := make([]Feature, len(f.featuresMap))
|
|
||||||
|
|
||||||
for name, index := range f.featuresMap {
|
|
||||||
features[index] = Feature{
|
|
||||||
Name: name,
|
|
||||||
Flag: f.flags[index],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewFeatureVector(features)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SharedFeatures is a product of comparison of two features vector which
|
// FeatureVector represents a set of enabled features. The set stores
|
||||||
// consist of features which are present in both local and remote features
|
// information on enabled flags and metadata about the feature names. A feature
|
||||||
// vectors.
|
// vector is serializable to a compact byte representation that is included in
|
||||||
type SharedFeatures struct {
|
// Lightning network messages.
|
||||||
*FeatureVector
|
type FeatureVector struct {
|
||||||
|
*RawFeatureVector
|
||||||
|
featureNames map[FeatureBit]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSharedFeatures creates new shared features instance.
|
// NewFeatureVector constructs a new FeatureVector from a raw feature vector and
|
||||||
func newSharedFeatures(f *FeatureVector) *SharedFeatures {
|
// mapping of feature definitions.
|
||||||
return &SharedFeatures{f}
|
func NewFeatureVector(featureVector *RawFeatureVector,
|
||||||
}
|
featureNames map[FeatureBit]string) *FeatureVector {
|
||||||
|
|
||||||
// IsActive checks is feature active or not, it might be disabled during
|
return &FeatureVector{
|
||||||
// comparision with remote feature vector if it was optional and remote peer
|
RawFeatureVector: featureVector,
|
||||||
// doesn't support it.
|
featureNames: featureNames,
|
||||||
func (f *SharedFeatures) IsActive(name featureName) bool {
|
|
||||||
index, ok := f.featuresMap[name]
|
|
||||||
if !ok {
|
|
||||||
// If we even have no such feature in feature map, than it
|
|
||||||
// can't be active in any circumstances.
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_, exist := f.flags[index]
|
|
||||||
return exist
|
// HasFeature returns whether a particular feature is included in the set. The
|
||||||
|
// feature can be seen as set either if the bit is set directly OR the queried
|
||||||
|
// bit has the same meaning as its corresponding even/odd bit, which is set
|
||||||
|
// instead. The second case is because feature bits are generally assigned in
|
||||||
|
// pairs where both the even and odd position represent the same feature.
|
||||||
|
func (fv *FeatureVector) HasFeature(feature FeatureBit) bool {
|
||||||
|
return fv.IsSet(feature) ||
|
||||||
|
(fv.isFeatureBitPair(feature) && fv.IsSet(feature^1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownRequiredFeatures returns a list of feature bits set in the vector that
|
||||||
|
// are unknown and in an even bit position. Feature bits with an even index must
|
||||||
|
// be known to a node receiving the feature vector in a message.
|
||||||
|
func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
|
||||||
|
var unknown []FeatureBit
|
||||||
|
for feature := range fv.features {
|
||||||
|
if feature%2 == 0 && !fv.IsKnown(feature) {
|
||||||
|
unknown = append(unknown, feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns a string identifier for the feature represented by this bit. If
|
||||||
|
// the bit does not represent a known feature, this returns a string indicating
|
||||||
|
// as much.
|
||||||
|
func (fv *FeatureVector) Name(bit FeatureBit) string {
|
||||||
|
name, known := fv.featureNames[bit]
|
||||||
|
if !known {
|
||||||
|
name = "unknown"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s(%d)", name, bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsKnown returns whether this feature bit represents a known feature.
|
||||||
|
func (fv *FeatureVector) IsKnown(bit FeatureBit) bool {
|
||||||
|
_, known := fv.featureNames[bit]
|
||||||
|
return known
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFeatureBitPair returns whether this feature bit and its corresponding
|
||||||
|
// even/odd bit both represent the same feature. This may often be the case as
|
||||||
|
// bits are generally assigned in pairs, first being assigned an odd bit
|
||||||
|
// position then being promoted to an even bit position once the network is
|
||||||
|
// ready.
|
||||||
|
func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
|
||||||
|
name1, known1 := fv.featureNames[bit]
|
||||||
|
name2, known2 := fv.featureNames[bit^1]
|
||||||
|
return known1 && known2 && name1 == name2
|
||||||
}
|
}
|
||||||
|
@ -3,152 +3,260 @@ package lnwire
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestFeaturesRemoteRequireError checks that we throw an error if remote peer
|
var testFeatureNames = map[FeatureBit]string{
|
||||||
// has required feature which we don't support.
|
0: "feature1",
|
||||||
func TestFeaturesRemoteRequireError(t *testing.T) {
|
3: "feature2",
|
||||||
|
4: "feature3",
|
||||||
|
5: "feature3",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFeatureVectorSetUnset(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
tests := []struct {
|
||||||
first = "first"
|
bits []FeatureBit
|
||||||
second = "second"
|
expectedFeatures []bool
|
||||||
)
|
}{
|
||||||
|
// No features are enabled if no bits are set.
|
||||||
|
{
|
||||||
|
bits: nil,
|
||||||
|
expectedFeatures: []bool{false, false, false, false, false, false, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an even bit for an even-only bit feature. The
|
||||||
|
// corresponding odd bit should not be seen as set.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{0},
|
||||||
|
expectedFeatures: []bool{true, false, false, false, false, false, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an odd bit for an even-only bit feature. The
|
||||||
|
// corresponding even bit should not be seen as set.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{1},
|
||||||
|
expectedFeatures: []bool{false, true, false, false, false, false, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an even bit for an odd-only bit feature. The bit should
|
||||||
|
// be seen as set and the odd bit should not.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{2},
|
||||||
|
expectedFeatures: []bool{false, false, true, false, false, false, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an odd bit for an odd-only bit feature. The bit should
|
||||||
|
// be seen as set and the even bit should not.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{3},
|
||||||
|
expectedFeatures: []bool{false, false, false, true, false, false, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an even bit for even-odd pair feature. Both bits in the
|
||||||
|
// pair should be seen as set.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{4},
|
||||||
|
expectedFeatures: []bool{false, false, false, false, true, true, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an odd bit for even-odd pair feature. Both bits in the
|
||||||
|
// pair should be seen as set.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{5},
|
||||||
|
expectedFeatures: []bool{false, false, false, false, true, true, false, false},
|
||||||
|
},
|
||||||
|
// Test setting an even bit for an unknown feature. The bit should be
|
||||||
|
// seen as set and the odd bit should not.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{6},
|
||||||
|
expectedFeatures: []bool{false, false, false, false, false, false, true, false},
|
||||||
|
},
|
||||||
|
// Test setting an odd bit for an unknown feature. The bit should be
|
||||||
|
// seen as set and the odd bit should not.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{7},
|
||||||
|
expectedFeatures: []bool{false, false, false, false, false, false, false, true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
localFeatures := NewFeatureVector([]Feature{
|
fv := NewFeatureVector(nil, testFeatureNames)
|
||||||
{first, OptionalFlag},
|
for i, test := range tests {
|
||||||
})
|
for _, bit := range test.bits {
|
||||||
|
fv.Set(bit)
|
||||||
|
}
|
||||||
|
|
||||||
remoteFeatures := NewFeatureVector([]Feature{
|
for j, expectedSet := range test.expectedFeatures {
|
||||||
{first, OptionalFlag},
|
if fv.HasFeature(FeatureBit(j)) != expectedSet {
|
||||||
{second, RequiredFlag},
|
t.Errorf("Expection failed in case %d, bit %d", i, j)
|
||||||
})
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := localFeatures.Compare(remoteFeatures); err == nil {
|
for _, bit := range test.bits {
|
||||||
t.Fatal("error wasn't received")
|
fv.Unset(bit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFeaturesLocalRequireError checks that we throw an error if local peer has
|
func TestFeatureVectorEncodeDecode(t *testing.T) {
|
||||||
// required feature which remote peer don't support.
|
|
||||||
func TestFeaturesLocalRequireError(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
tests := []struct {
|
||||||
first = "first"
|
bits []FeatureBit
|
||||||
second = "second"
|
expectedEncoded []byte
|
||||||
)
|
}{
|
||||||
|
{
|
||||||
|
bits: nil,
|
||||||
|
expectedEncoded: []byte{0x00, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{2, 3, 7},
|
||||||
|
expectedEncoded: []byte{0x00, 0x01, 0x8C},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{2, 3, 8},
|
||||||
|
expectedEncoded: []byte{0x00, 0x02, 0x01, 0x0C},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
localFeatures := NewFeatureVector([]Feature{
|
for i, test := range tests {
|
||||||
{first, OptionalFlag},
|
fv := NewRawFeatureVector(test.bits...)
|
||||||
{second, RequiredFlag},
|
|
||||||
})
|
|
||||||
|
|
||||||
remoteFeatures := NewFeatureVector([]Feature{
|
// Test that Encode produces the correct serialization.
|
||||||
{first, OptionalFlag},
|
buffer := new(bytes.Buffer)
|
||||||
})
|
err := fv.Encode(buffer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to encode feature vector in case %d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := localFeatures.Compare(remoteFeatures); err == nil {
|
encoded := buffer.Bytes()
|
||||||
t.Fatal("error wasn't received")
|
if !bytes.Equal(encoded, test.expectedEncoded) {
|
||||||
|
t.Errorf("Wrong encoding in case %d: got %v, expected %v",
|
||||||
|
i, encoded, test.expectedEncoded)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that decoding then re-encoding produces the same result.
|
||||||
|
fv2 := NewRawFeatureVector()
|
||||||
|
err = fv2.Decode(bytes.NewReader(encoded))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to decode feature vector in case %d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer2 := new(bytes.Buffer)
|
||||||
|
err = fv2.Encode(buffer2)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to re-encode feature vector in case %d: %v",
|
||||||
|
i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reencoded := buffer2.Bytes()
|
||||||
|
if !bytes.Equal(reencoded, test.expectedEncoded) {
|
||||||
|
t.Errorf("Wrong re-encoding in case %d: got %v, expected %v",
|
||||||
|
i, reencoded, test.expectedEncoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOptionalFeature checks that if remote peer don't have the feature but
|
func TestFeatureVectorUnknownFeatures(t *testing.T) {
|
||||||
// on our side this feature is optional than we mark this feature as disabled.
|
|
||||||
func TestOptionalFeature(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const first = "first"
|
tests := []struct {
|
||||||
|
bits []FeatureBit
|
||||||
localFeatures := NewFeatureVector([]Feature{
|
expectedUnknown []FeatureBit
|
||||||
{first, OptionalFlag},
|
}{
|
||||||
})
|
{
|
||||||
|
bits: nil,
|
||||||
remoteFeatures := NewFeatureVector([]Feature{})
|
expectedUnknown: nil,
|
||||||
|
},
|
||||||
shared, err := localFeatures.Compare(remoteFeatures)
|
// Since bits {0, 3, 4, 5} are known, and only even bits are considered
|
||||||
if err != nil {
|
// required (according to the "it's OK to be odd rule"), that leaves
|
||||||
t.Fatalf("error while feature vector compare: %v", err)
|
// {2, 6} as both unknown and required.
|
||||||
|
{
|
||||||
|
bits: []FeatureBit{0, 1, 2, 3, 4, 5, 6, 7},
|
||||||
|
expectedUnknown: []FeatureBit{2, 6},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if shared.IsActive(first) {
|
for i, test := range tests {
|
||||||
t.Fatal("locally feature was set but remote peer notified us" +
|
rawVector := NewRawFeatureVector(test.bits...)
|
||||||
" that it don't have it")
|
fv := NewFeatureVector(rawVector, testFeatureNames)
|
||||||
}
|
|
||||||
|
|
||||||
// A feature with a non-existent name shouldn't be active.
|
unknown := fv.UnknownRequiredFeatures()
|
||||||
if shared.IsActive("nothere") {
|
|
||||||
t.Fatal("non-existent feature shouldn't be active")
|
// Sort to make comparison independent of order
|
||||||
|
sort.Slice(unknown, func(i, j int) bool {
|
||||||
|
return unknown[i] < unknown[j]
|
||||||
|
})
|
||||||
|
if !reflect.DeepEqual(unknown, test.expectedUnknown) {
|
||||||
|
t.Errorf("Wrong unknown features in case %d: got %v, expected %v",
|
||||||
|
i, unknown, test.expectedUnknown)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSetRequireAfterInit checks that we can change the feature flag after
|
func TestFeatureNames(t *testing.T) {
|
||||||
// initialization.
|
|
||||||
func TestSetRequireAfterInit(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const first = "first"
|
tests := []struct {
|
||||||
|
bit FeatureBit
|
||||||
|
expectedName string
|
||||||
|
expectedKnown bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
bit: 0,
|
||||||
|
expectedName: "feature1(0)",
|
||||||
|
expectedKnown: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 1,
|
||||||
|
expectedName: "unknown(1)",
|
||||||
|
expectedKnown: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 2,
|
||||||
|
expectedName: "unknown(2)",
|
||||||
|
expectedKnown: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 3,
|
||||||
|
expectedName: "feature2(3)",
|
||||||
|
expectedKnown: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 4,
|
||||||
|
expectedName: "feature3(4)",
|
||||||
|
expectedKnown: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 5,
|
||||||
|
expectedName: "feature3(5)",
|
||||||
|
expectedKnown: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 6,
|
||||||
|
expectedName: "unknown(6)",
|
||||||
|
expectedKnown: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bit: 7,
|
||||||
|
expectedName: "unknown(7)",
|
||||||
|
expectedKnown: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
localFeatures := NewFeatureVector([]Feature{
|
fv := NewFeatureVector(nil, testFeatureNames)
|
||||||
{first, OptionalFlag},
|
for _, test := range tests {
|
||||||
})
|
name := fv.Name(test.bit)
|
||||||
localFeatures.SetFeatureFlag(first, RequiredFlag)
|
if name != test.expectedName {
|
||||||
remoteFeatures := NewFeatureVector([]Feature{})
|
t.Errorf("Name for feature bit %d is incorrect: "+
|
||||||
|
"expected %s, got %s", test.bit, name, test.expectedName)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := localFeatures.Compare(remoteFeatures)
|
known := fv.IsKnown(test.bit)
|
||||||
if err == nil {
|
if known != test.expectedKnown {
|
||||||
t.Fatalf("feature was set as required but error wasn't "+
|
t.Errorf("IsKnown for feature bit %d is incorrect: "+
|
||||||
"returned: %v", err)
|
"expected %v, got %v", test.bit, known, test.expectedKnown)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TestDecodeEncodeFeaturesVector checks that feature vector might be
|
|
||||||
// successfully encoded and decoded.
|
|
||||||
func TestDecodeEncodeFeaturesVector(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const first = "first"
|
|
||||||
|
|
||||||
f := NewFeatureVector([]Feature{
|
|
||||||
{first, OptionalFlag},
|
|
||||||
})
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err := f.Encode(&b); err != nil {
|
|
||||||
t.Fatalf("error while encoding feature vector: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nf, err := NewFeatureVectorFromReader(&b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error while decoding feature vector: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert equality of the two instances.
|
|
||||||
if !reflect.DeepEqual(f.flags, nf.flags) {
|
|
||||||
t.Fatalf("encode/decode feature vector don't match %v vs "+
|
|
||||||
"%v", spew.Sdump(f), spew.Sdump(nf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeatureFlagString(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if OptionalFlag.String() != "optional" {
|
|
||||||
t.Fatalf("incorrect string, expected optional got %v",
|
|
||||||
OptionalFlag.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if RequiredFlag.String() != "required" {
|
|
||||||
t.Fatalf("incorrect string, expected required got %v",
|
|
||||||
OptionalFlag.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeFlag := featureFlag(9)
|
|
||||||
if fakeFlag.String() != "<unknown>" {
|
|
||||||
t.Fatalf("incorrect string, expected <unknown> got %v",
|
|
||||||
fakeFlag.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user