mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-08 22:31:04 +00:00
Refactor node ID handling, use check digits (fixes #269)
New node ID:s contain four Luhn check digits and are grouped differently. Code uses NodeID type instead of string, so it's formatted homogenously everywhere.
This commit is contained in:
parent
fee8289c0a
commit
8f3effed32
File diff suppressed because one or more lines are too long
35
cid/cid.go
35
cid/cid.go
@ -5,27 +5,32 @@
|
|||||||
// Package cid provides a manager for mappings between node ID:s and connection ID:s.
|
// Package cid provides a manager for mappings between node ID:s and connection ID:s.
|
||||||
package cid
|
package cid
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
type Map struct {
|
type Map struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
toCid map[string]uint
|
toCid map[protocol.NodeID]uint
|
||||||
toName []string
|
toName []protocol.NodeID
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LocalName = "<local>"
|
LocalNodeID = protocol.NodeID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
LocalID uint = 0
|
LocalID uint = 0
|
||||||
|
emptyNodeID protocol.NodeID
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMap() *Map {
|
func NewMap() *Map {
|
||||||
return &Map{
|
return &Map{
|
||||||
toCid: map[string]uint{"<local>": 0},
|
toCid: map[protocol.NodeID]uint{LocalNodeID: LocalID},
|
||||||
toName: []string{"<local>"},
|
toName: []protocol.NodeID{LocalNodeID},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) Get(name string) uint {
|
func (m *Map) Get(name protocol.NodeID) uint {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
@ -36,7 +41,7 @@ func (m *Map) Get(name string) uint {
|
|||||||
|
|
||||||
// Find a free slot to get a new ID
|
// Find a free slot to get a new ID
|
||||||
for i, n := range m.toName {
|
for i, n := range m.toName {
|
||||||
if n == "" {
|
if n == emptyNodeID {
|
||||||
m.toName[i] = name
|
m.toName[i] = name
|
||||||
m.toCid[name] = uint(i)
|
m.toCid[name] = uint(i)
|
||||||
return uint(i)
|
return uint(i)
|
||||||
@ -50,19 +55,19 @@ func (m *Map) Get(name string) uint {
|
|||||||
return cid
|
return cid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) Name(cid uint) string {
|
func (m *Map) Name(cid uint) protocol.NodeID {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
return m.toName[cid]
|
return m.toName[cid]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) Names() []string {
|
func (m *Map) Names() []protocol.NodeID {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
|
||||||
var names []string
|
var names []protocol.NodeID
|
||||||
for _, name := range m.toName {
|
for _, name := range m.toName {
|
||||||
if name != "" {
|
if name != emptyNodeID {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,11 +76,11 @@ func (m *Map) Names() []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Map) Clear(name string) {
|
func (m *Map) Clear(name protocol.NodeID) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
cid, ok := m.toCid[name]
|
cid, ok := m.toCid[name]
|
||||||
if ok {
|
if ok {
|
||||||
m.toName[cid] = ""
|
m.toName[cid] = emptyNodeID
|
||||||
delete(m.toCid, name)
|
delete(m.toCid, name)
|
||||||
}
|
}
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
|
@ -4,28 +4,35 @@
|
|||||||
|
|
||||||
package cid
|
package cid
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
m := NewMap()
|
m := NewMap()
|
||||||
|
|
||||||
if i := m.Get("foo"); i != 1 {
|
fooID := protocol.NewNodeID([]byte("foo"))
|
||||||
|
barID := protocol.NewNodeID([]byte("bar"))
|
||||||
|
|
||||||
|
if i := m.Get(fooID); i != 1 {
|
||||||
t.Errorf("Unexpected id %d != 1", i)
|
t.Errorf("Unexpected id %d != 1", i)
|
||||||
}
|
}
|
||||||
if i := m.Get("bar"); i != 2 {
|
if i := m.Get(barID); i != 2 {
|
||||||
t.Errorf("Unexpected id %d != 2", i)
|
t.Errorf("Unexpected id %d != 2", i)
|
||||||
}
|
}
|
||||||
if i := m.Get("foo"); i != 1 {
|
if i := m.Get(fooID); i != 1 {
|
||||||
t.Errorf("Unexpected id %d != 1", i)
|
t.Errorf("Unexpected id %d != 1", i)
|
||||||
}
|
}
|
||||||
if i := m.Get("bar"); i != 2 {
|
if i := m.Get(barID); i != 2 {
|
||||||
t.Errorf("Unexpected id %d != 2", i)
|
t.Errorf("Unexpected id %d != 2", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if LocalID != 0 {
|
if LocalID != 0 {
|
||||||
t.Error("LocalID should be 0")
|
t.Error("LocalID should be 0")
|
||||||
}
|
}
|
||||||
if i := m.Get(LocalName); i != LocalID {
|
if i := m.Get(LocalNodeID); i != LocalID {
|
||||||
t.Errorf("Unexpected id %d != %c", i, LocalID)
|
t.Errorf("Unexpected id %d != %d", i, LocalID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,12 @@ func connect(target string) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
myID := string(certID(cert.Certificate[0]))
|
myID := protocol.NewNodeID(cert.Certificate[0])
|
||||||
|
|
||||||
tlsCfg := &tls.Config{
|
tlsCfg := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
NextProtos: []string{"bep/1.0"},
|
NextProtos: []string{"bep/1.0"},
|
||||||
ServerName: myID,
|
ServerName: myID.String(),
|
||||||
ClientAuth: tls.RequestClientCert,
|
ClientAuth: tls.RequestClientCert,
|
||||||
SessionTicketsDisabled: true,
|
SessionTicketsDisabled: true,
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -63,7 +63,7 @@ func connect(target string) {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteID := certID(conn.ConnectionState().PeerCertificates[0].Raw)
|
remoteID := protocol.NewNodeID(conn.ConnectionState().PeerCertificates[0].Raw)
|
||||||
|
|
||||||
pc = protocol.NewConnection(remoteID, conn, conn, Model{})
|
pc = protocol.NewConnection(remoteID, conn, conn, Model{})
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func prtIndex(files []protocol.FileInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Index(nodeID string, repo string, files []protocol.FileInfo) {
|
func (m Model) Index(nodeID protocol.NodeID, repo string, files []protocol.FileInfo) {
|
||||||
log.Printf("Received index for repo %q", repo)
|
log.Printf("Received index for repo %q", repo)
|
||||||
if cmd == "idx" {
|
if cmd == "idx" {
|
||||||
prtIndex(files)
|
prtIndex(files)
|
||||||
@ -121,7 +121,7 @@ func getFile(f protocol.FileInfo) {
|
|||||||
fd.Close()
|
fd.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) IndexUpdate(nodeID string, repo string, files []protocol.FileInfo) {
|
func (m Model) IndexUpdate(nodeID protocol.NodeID, repo string, files []protocol.FileInfo) {
|
||||||
log.Printf("Received index update for repo %q", repo)
|
log.Printf("Received index update for repo %q", repo)
|
||||||
if cmd == "idx" {
|
if cmd == "idx" {
|
||||||
prtIndex(files)
|
prtIndex(files)
|
||||||
@ -131,16 +131,16 @@ func (m Model) IndexUpdate(nodeID string, repo string, files []protocol.FileInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessage) {
|
func (m Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterConfigMessage) {
|
||||||
log.Println("Received cluster config")
|
log.Println("Received cluster config")
|
||||||
log.Printf("%#v", config)
|
log.Printf("%#v", config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Request(nodeID, repo string, name string, offset int64, size int) ([]byte, error) {
|
func (m Model) Request(nodeID protocol.NodeID, repo string, name string, offset int64, size int) ([]byte, error) {
|
||||||
log.Println("Received request")
|
log.Println("Received request")
|
||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Close(nodeID string, err error) {
|
func (m Model) Close(nodeID protocol.NodeID, err error) {
|
||||||
log.Println("Received close")
|
log.Println("Received close")
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base32"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadCert(dir string) (tls.Certificate, error) {
|
func loadCert(dir string) (tls.Certificate, error) {
|
||||||
return tls.LoadX509KeyPair(filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem"))
|
cf := filepath.Join(dir, "cert.pem")
|
||||||
}
|
kf := filepath.Join(dir, "key.pem")
|
||||||
|
return tls.LoadX509KeyPair(cf, kf)
|
||||||
func certID(bs []byte) string {
|
|
||||||
hf := sha256.New()
|
|
||||||
hf.Write(bs)
|
|
||||||
id := hf.Sum(nil)
|
|
||||||
return strings.Trim(base32.StdEncoding.EncodeToString(id), "=")
|
|
||||||
}
|
}
|
||||||
|
@ -327,7 +327,7 @@ func restGetSystem(w http.ResponseWriter) {
|
|||||||
runtime.ReadMemStats(&m)
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
res := make(map[string]interface{})
|
res := make(map[string]interface{})
|
||||||
res["myID"] = myID
|
res["myID"] = myID.String()
|
||||||
res["goroutines"] = runtime.NumGoroutine()
|
res["goroutines"] = runtime.NumGoroutine()
|
||||||
res["alloc"] = m.Alloc
|
res["alloc"] = m.Alloc
|
||||||
res["sys"] = m.Sys
|
res["sys"] = m.Sys
|
||||||
|
@ -61,7 +61,7 @@ func init() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cfg config.Configuration
|
cfg config.Configuration
|
||||||
myID string
|
myID protocol.NodeID
|
||||||
confDir string
|
confDir string
|
||||||
logFlags int = log.Ltime
|
logFlags int = log.Ltime
|
||||||
rateBucket *ratelimit.Bucket
|
rateBucket *ratelimit.Bucket
|
||||||
@ -181,8 +181,8 @@ func main() {
|
|||||||
l.FatalErr(err)
|
l.FatalErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
myID = certID(cert.Certificate[0])
|
myID = protocol.NewNodeID(cert.Certificate[0])
|
||||||
l.SetPrefix(fmt.Sprintf("[%s] ", myID[:5]))
|
l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5]))
|
||||||
|
|
||||||
l.Infoln(LongVersion)
|
l.Infoln(LongVersion)
|
||||||
l.Infoln("My ID:", myID)
|
l.Infoln("My ID:", myID)
|
||||||
@ -263,7 +263,7 @@ func main() {
|
|||||||
tlsCfg := &tls.Config{
|
tlsCfg := &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
NextProtos: []string{"bep/1.0"},
|
NextProtos: []string{"bep/1.0"},
|
||||||
ServerName: myID,
|
ServerName: myID.String(),
|
||||||
ClientAuth: tls.RequestClientCert,
|
ClientAuth: tls.RequestClientCert,
|
||||||
SessionTicketsDisabled: true,
|
SessionTicketsDisabled: true,
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -567,7 +567,7 @@ func saveConfig() {
|
|||||||
saveConfigCh <- struct{}{}
|
saveConfigCh <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenConnect(myID string, m *model.Model, tlsCfg *tls.Config) {
|
func listenConnect(myID protocol.NodeID, m *model.Model, tlsCfg *tls.Config) {
|
||||||
var conns = make(chan *tls.Conn)
|
var conns = make(chan *tls.Conn)
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
@ -673,7 +673,7 @@ next:
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
remoteID := certID(certs[0].Raw)
|
remoteID := protocol.NewNodeID(certs[0].Raw)
|
||||||
|
|
||||||
if remoteID == myID {
|
if remoteID == myID {
|
||||||
l.Infof("Connected to myself (%s) - should not happen", remoteID)
|
l.Infof("Connected to myself (%s) - should not happen", remoteID)
|
||||||
|
@ -11,14 +11,12 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/base32"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
mr "math/rand"
|
mr "math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,14 +26,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func loadCert(dir string, prefix string) (tls.Certificate, error) {
|
func loadCert(dir string, prefix string) (tls.Certificate, error) {
|
||||||
return tls.LoadX509KeyPair(filepath.Join(dir, prefix+"cert.pem"), filepath.Join(dir, prefix+"key.pem"))
|
cf := filepath.Join(dir, prefix+"cert.pem")
|
||||||
}
|
kf := filepath.Join(dir, prefix+"key.pem")
|
||||||
|
return tls.LoadX509KeyPair(cf, kf)
|
||||||
func certID(bs []byte) string {
|
|
||||||
hf := sha256.New()
|
|
||||||
hf.Write(bs)
|
|
||||||
id := hf.Sum(nil)
|
|
||||||
return strings.Trim(base32.StdEncoding.EncodeToString(id), "=")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func certSeed(bs []byte) int64 {
|
func certSeed(bs []byte) int64 {
|
||||||
|
@ -23,7 +23,7 @@ var stopUsageReportingCh = make(chan struct{})
|
|||||||
|
|
||||||
func reportData(m *model.Model) map[string]interface{} {
|
func reportData(m *model.Model) map[string]interface{} {
|
||||||
res := make(map[string]interface{})
|
res := make(map[string]interface{})
|
||||||
res["uniqueID"] = strings.ToLower(certID([]byte(myID)))[:6]
|
res["uniqueID"] = strings.ToLower(myID.String()[:6])
|
||||||
res["version"] = Version
|
res["version"] = Version
|
||||||
res["longVersion"] = LongVersion
|
res["longVersion"] = LongVersion
|
||||||
res["platform"] = runtime.GOOS + "-" + runtime.GOARCH
|
res["platform"] = runtime.GOOS + "-" + runtime.GOARCH
|
||||||
|
@ -14,10 +14,10 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.google.com/p/go.crypto/bcrypt"
|
"code.google.com/p/go.crypto/bcrypt"
|
||||||
"github.com/calmh/syncthing/logger"
|
"github.com/calmh/syncthing/logger"
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
"github.com/calmh/syncthing/scanner"
|
"github.com/calmh/syncthing/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ type RepositoryConfiguration struct {
|
|||||||
Versioning VersioningConfiguration `xml:"versioning"`
|
Versioning VersioningConfiguration `xml:"versioning"`
|
||||||
SyncOrderPatterns []SyncOrderPattern `xml:"syncorder>pattern"`
|
SyncOrderPatterns []SyncOrderPattern `xml:"syncorder>pattern"`
|
||||||
|
|
||||||
nodeIDs []string
|
nodeIDs []protocol.NodeID
|
||||||
}
|
}
|
||||||
|
|
||||||
type VersioningConfiguration struct {
|
type VersioningConfiguration struct {
|
||||||
@ -113,7 +113,7 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RepositoryConfiguration) NodeIDs() []string {
|
func (r *RepositoryConfiguration) NodeIDs() []protocol.NodeID {
|
||||||
if r.nodeIDs == nil {
|
if r.nodeIDs == nil {
|
||||||
for _, n := range r.Nodes {
|
for _, n := range r.Nodes {
|
||||||
r.nodeIDs = append(r.nodeIDs, n.NodeID)
|
r.nodeIDs = append(r.nodeIDs, n.NodeID)
|
||||||
@ -138,9 +138,9 @@ func (r RepositoryConfiguration) FileRanker() func(scanner.File) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NodeConfiguration struct {
|
type NodeConfiguration struct {
|
||||||
NodeID string `xml:"id,attr"`
|
NodeID protocol.NodeID `xml:"id,attr"`
|
||||||
Name string `xml:"name,attr,omitempty"`
|
Name string `xml:"name,attr,omitempty"`
|
||||||
Addresses []string `xml:"address,omitempty"`
|
Addresses []string `xml:"address,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionsConfiguration struct {
|
type OptionsConfiguration struct {
|
||||||
@ -174,8 +174,8 @@ type GUIConfiguration struct {
|
|||||||
APIKey string `xml:"apikey,omitempty"`
|
APIKey string `xml:"apikey,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) NodeMap() map[string]NodeConfiguration {
|
func (cfg *Configuration) NodeMap() map[protocol.NodeID]NodeConfiguration {
|
||||||
m := make(map[string]NodeConfiguration, len(cfg.Nodes))
|
m := make(map[protocol.NodeID]NodeConfiguration, len(cfg.Nodes))
|
||||||
for _, n := range cfg.Nodes {
|
for _, n := range cfg.Nodes {
|
||||||
m[n.NodeID] = n
|
m[n.NodeID] = n
|
||||||
}
|
}
|
||||||
@ -276,7 +276,7 @@ func uniqueStrings(ss []string) []string {
|
|||||||
return us
|
return us
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(rd io.Reader, myID string) (Configuration, error) {
|
func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) {
|
||||||
var cfg Configuration
|
var cfg Configuration
|
||||||
|
|
||||||
setDefaults(&cfg)
|
setDefaults(&cfg)
|
||||||
@ -297,15 +297,6 @@ func Load(rd io.Reader, myID string) (Configuration, error) {
|
|||||||
cfg.Repositories = []RepositoryConfiguration{}
|
cfg.Repositories = []RepositoryConfiguration{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize node IDs
|
|
||||||
for i := range cfg.Nodes {
|
|
||||||
node := &cfg.Nodes[i]
|
|
||||||
// Strip spaces and dashes
|
|
||||||
node.NodeID = strings.Replace(node.NodeID, "-", "", -1)
|
|
||||||
node.NodeID = strings.Replace(node.NodeID, " ", "", -1)
|
|
||||||
node.NodeID = strings.ToUpper(node.NodeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for missing, bad or duplicate repository ID:s
|
// Check for missing, bad or duplicate repository ID:s
|
||||||
var seenRepos = map[string]*RepositoryConfiguration{}
|
var seenRepos = map[string]*RepositoryConfiguration{}
|
||||||
var uniqueCounter int
|
var uniqueCounter int
|
||||||
@ -321,13 +312,6 @@ func Load(rd io.Reader, myID string) (Configuration, error) {
|
|||||||
repo.ID = "default"
|
repo.ID = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range repo.Nodes {
|
|
||||||
node := &repo.Nodes[i]
|
|
||||||
// Strip spaces and dashes
|
|
||||||
node.NodeID = strings.Replace(node.NodeID, "-", "", -1)
|
|
||||||
node.NodeID = strings.Replace(node.NodeID, " ", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen, ok := seenRepos[repo.ID]; ok {
|
if seen, ok := seenRepos[repo.ID]; ok {
|
||||||
l.Warnf("Multiple repositories with ID %q; disabling", repo.ID)
|
l.Warnf("Multiple repositories with ID %q; disabling", repo.ID)
|
||||||
|
|
||||||
@ -390,8 +374,9 @@ func convertV1V2(cfg *Configuration) {
|
|||||||
for i, repo := range cfg.Repositories {
|
for i, repo := range cfg.Repositories {
|
||||||
cfg.Repositories[i].ReadOnly = cfg.Options.Deprecated_ReadOnly
|
cfg.Repositories[i].ReadOnly = cfg.Options.Deprecated_ReadOnly
|
||||||
for j, node := range repo.Nodes {
|
for j, node := range repo.Nodes {
|
||||||
if _, ok := nodes[node.NodeID]; !ok {
|
id := node.NodeID.String()
|
||||||
nodes[node.NodeID] = node
|
if _, ok := nodes[id]; !ok {
|
||||||
|
nodes[id] = node
|
||||||
}
|
}
|
||||||
cfg.Repositories[i].Nodes[j] = NodeConfiguration{NodeID: node.NodeID}
|
cfg.Repositories[i].Nodes[j] = NodeConfiguration{NodeID: node.NodeID}
|
||||||
}
|
}
|
||||||
@ -416,7 +401,7 @@ func convertV1V2(cfg *Configuration) {
|
|||||||
type NodeConfigurationList []NodeConfiguration
|
type NodeConfigurationList []NodeConfiguration
|
||||||
|
|
||||||
func (l NodeConfigurationList) Less(a, b int) bool {
|
func (l NodeConfigurationList) Less(a, b int) bool {
|
||||||
return l[a].NodeID < l[b].NodeID
|
return l[a].NodeID.Compare(l[b].NodeID) == -1
|
||||||
}
|
}
|
||||||
func (l NodeConfigurationList) Swap(a, b int) {
|
func (l NodeConfigurationList) Swap(a, b int) {
|
||||||
l[a], l[b] = l[b], l[a]
|
l[a], l[b] = l[b], l[a]
|
||||||
@ -425,10 +410,10 @@ func (l NodeConfigurationList) Len() int {
|
|||||||
return len(l)
|
return len(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureNodePresent(nodes []NodeConfiguration, myID string) []NodeConfiguration {
|
func ensureNodePresent(nodes []NodeConfiguration, myID protocol.NodeID) []NodeConfiguration {
|
||||||
var myIDExists bool
|
var myIDExists bool
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if node.NodeID == myID {
|
if node.NodeID.Equals(myID) {
|
||||||
myIDExists = true
|
myIDExists = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/calmh/syncthing/files"
|
"github.com/calmh/syncthing/files"
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
"github.com/calmh/syncthing/scanner"
|
"github.com/calmh/syncthing/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var node1, node2, node3, node4 protocol.NodeID
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
node1, _ = protocol.NodeIDFromString("AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ")
|
||||||
|
node2, _ = protocol.NodeIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
|
||||||
|
node3, _ = protocol.NodeIDFromString("LGFPDIT-7SKNNJL-VJZA4FC-7QNCRKA-CE753K7-2BW5QDK-2FOZ7FR-FEP57QJ")
|
||||||
|
node4, _ = protocol.NodeIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2")
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultValues(t *testing.T) {
|
func TestDefaultValues(t *testing.T) {
|
||||||
expected := OptionsConfiguration{
|
expected := OptionsConfiguration{
|
||||||
ListenAddress: []string{"0.0.0.0:22000"},
|
ListenAddress: []string{"0.0.0.0:22000"},
|
||||||
@ -31,7 +41,7 @@ func TestDefaultValues(t *testing.T) {
|
|||||||
UPnPEnabled: true,
|
UPnPEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(nil), "nodeID")
|
cfg, err := Load(bytes.NewReader(nil), node1)
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -45,10 +55,10 @@ func TestNodeConfig(t *testing.T) {
|
|||||||
v1data := []byte(`
|
v1data := []byte(`
|
||||||
<configuration version="1">
|
<configuration version="1">
|
||||||
<repository id="test" directory="~/Sync">
|
<repository id="test" directory="~/Sync">
|
||||||
<node id="NODE1" name="node one">
|
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
|
||||||
<address>a</address>
|
<address>a</address>
|
||||||
</node>
|
</node>
|
||||||
<node id="NODE2" name="node two">
|
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
|
||||||
<address>b</address>
|
<address>b</address>
|
||||||
</node>
|
</node>
|
||||||
</repository>
|
</repository>
|
||||||
@ -61,20 +71,20 @@ func TestNodeConfig(t *testing.T) {
|
|||||||
v2data := []byte(`
|
v2data := []byte(`
|
||||||
<configuration version="2">
|
<configuration version="2">
|
||||||
<repository id="test" directory="~/Sync" ro="true">
|
<repository id="test" directory="~/Sync" ro="true">
|
||||||
<node id="NODE1"/>
|
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
|
||||||
<node id="NODE2"/>
|
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
|
||||||
</repository>
|
</repository>
|
||||||
<node id="NODE1" name="node one">
|
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
|
||||||
<address>a</address>
|
<address>a</address>
|
||||||
</node>
|
</node>
|
||||||
<node id="NODE2" name="node two">
|
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
|
||||||
<address>b</address>
|
<address>b</address>
|
||||||
</node>
|
</node>
|
||||||
</configuration>
|
</configuration>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
for i, data := range [][]byte{v1data, v2data} {
|
for i, data := range [][]byte{v1data, v2data} {
|
||||||
cfg, err := Load(bytes.NewReader(data), "NODE1")
|
cfg, err := Load(bytes.NewReader(data), node1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -83,23 +93,23 @@ func TestNodeConfig(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ID: "test",
|
ID: "test",
|
||||||
Directory: "~/Sync",
|
Directory: "~/Sync",
|
||||||
Nodes: []NodeConfiguration{{NodeID: "NODE1"}, {NodeID: "NODE2"}},
|
Nodes: []NodeConfiguration{{NodeID: node1}, {NodeID: node4}},
|
||||||
ReadOnly: true,
|
ReadOnly: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedNodes := []NodeConfiguration{
|
expectedNodes := []NodeConfiguration{
|
||||||
{
|
{
|
||||||
NodeID: "NODE1",
|
NodeID: node1,
|
||||||
Name: "node one",
|
Name: "node one",
|
||||||
Addresses: []string{"a"},
|
Addresses: []string{"a"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeID: "NODE2",
|
NodeID: node4,
|
||||||
Name: "node two",
|
Name: "node two",
|
||||||
Addresses: []string{"b"},
|
Addresses: []string{"b"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedNodeIDs := []string{"NODE1", "NODE2"}
|
expectedNodeIDs := []protocol.NodeID{node1, node4}
|
||||||
|
|
||||||
if cfg.Version != 2 {
|
if cfg.Version != 2 {
|
||||||
t.Errorf("%d: Incorrect version %d != 2", i, cfg.Version)
|
t.Errorf("%d: Incorrect version %d != 2", i, cfg.Version)
|
||||||
@ -118,18 +128,13 @@ func TestNodeConfig(t *testing.T) {
|
|||||||
|
|
||||||
func TestNoListenAddress(t *testing.T) {
|
func TestNoListenAddress(t *testing.T) {
|
||||||
data := []byte(`<configuration version="1">
|
data := []byte(`<configuration version="1">
|
||||||
<repository directory="~/Sync">
|
|
||||||
<node id="..." name="...">
|
|
||||||
<address>dynamic</address>
|
|
||||||
</node>
|
|
||||||
</repository>
|
|
||||||
<options>
|
<options>
|
||||||
<listenAddress></listenAddress>
|
<listenAddress></listenAddress>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(data), "nodeID")
|
cfg, err := Load(bytes.NewReader(data), node1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -142,11 +147,6 @@ func TestNoListenAddress(t *testing.T) {
|
|||||||
|
|
||||||
func TestOverriddenValues(t *testing.T) {
|
func TestOverriddenValues(t *testing.T) {
|
||||||
data := []byte(`<configuration version="2">
|
data := []byte(`<configuration version="2">
|
||||||
<repository directory="~/Sync">
|
|
||||||
<node id="..." name="...">
|
|
||||||
<address>dynamic</address>
|
|
||||||
</node>
|
|
||||||
</repository>
|
|
||||||
<options>
|
<options>
|
||||||
<listenAddress>:23000</listenAddress>
|
<listenAddress>:23000</listenAddress>
|
||||||
<allowDelete>false</allowDelete>
|
<allowDelete>false</allowDelete>
|
||||||
@ -180,7 +180,7 @@ func TestOverriddenValues(t *testing.T) {
|
|||||||
UPnPEnabled: false,
|
UPnPEnabled: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(data), "nodeID")
|
cfg, err := Load(bytes.NewReader(data), node1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -193,13 +193,13 @@ func TestOverriddenValues(t *testing.T) {
|
|||||||
func TestNodeAddresses(t *testing.T) {
|
func TestNodeAddresses(t *testing.T) {
|
||||||
data := []byte(`
|
data := []byte(`
|
||||||
<configuration version="2">
|
<configuration version="2">
|
||||||
<node id="n1">
|
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
|
||||||
<address>dynamic</address>
|
|
||||||
</node>
|
|
||||||
<node id="n2">
|
|
||||||
<address></address>
|
<address></address>
|
||||||
</node>
|
</node>
|
||||||
<node id="n3">
|
<node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
|
||||||
|
</node>
|
||||||
|
<node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
|
||||||
|
<address>dynamic</address>
|
||||||
</node>
|
</node>
|
||||||
</configuration>
|
</configuration>
|
||||||
`)
|
`)
|
||||||
@ -207,25 +207,25 @@ func TestNodeAddresses(t *testing.T) {
|
|||||||
name, _ := os.Hostname()
|
name, _ := os.Hostname()
|
||||||
expected := []NodeConfiguration{
|
expected := []NodeConfiguration{
|
||||||
{
|
{
|
||||||
NodeID: "N1",
|
NodeID: node1,
|
||||||
Addresses: []string{"dynamic"},
|
Addresses: []string{"dynamic"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeID: "N2",
|
NodeID: node2,
|
||||||
Addresses: []string{"dynamic"},
|
Addresses: []string{"dynamic"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeID: "N3",
|
NodeID: node3,
|
||||||
Addresses: []string{"dynamic"},
|
Addresses: []string{"dynamic"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeID: "N4",
|
NodeID: node4,
|
||||||
Name: name, // Set when auto created
|
Name: name, // Set when auto created
|
||||||
Addresses: []string{"dynamic"},
|
Addresses: []string{"dynamic"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(data), "N4")
|
cfg, err := Load(bytes.NewReader(data), node4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -235,86 +235,32 @@ func TestNodeAddresses(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStripNodeIs(t *testing.T) {
|
|
||||||
data := []byte(`
|
|
||||||
<configuration version="2">
|
|
||||||
<node id="AAAA-BBBB-CCCC">
|
|
||||||
<address>dynamic</address>
|
|
||||||
</node>
|
|
||||||
<node id="AAAA BBBB DDDD">
|
|
||||||
<address></address>
|
|
||||||
</node>
|
|
||||||
<node id="AAAABBBBEEEE">
|
|
||||||
<address></address>
|
|
||||||
</node>
|
|
||||||
<repository directory="~/Sync">
|
|
||||||
<node id="AAA ABBB-BCC CC" name=""></node>
|
|
||||||
<node id="AA-AAB BBBD-DDD" name=""></node>
|
|
||||||
<node id="AAA AB-BBB EEE-E" name=""></node>
|
|
||||||
</repository>
|
|
||||||
</configuration>
|
|
||||||
`)
|
|
||||||
|
|
||||||
expected := []NodeConfiguration{
|
|
||||||
{
|
|
||||||
NodeID: "AAAABBBBCCCC",
|
|
||||||
Addresses: []string{"dynamic"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
NodeID: "AAAABBBBDDDD",
|
|
||||||
Addresses: []string{"dynamic"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
NodeID: "AAAABBBBEEEE",
|
|
||||||
Addresses: []string{"dynamic"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(data), "n4")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range expected {
|
|
||||||
if !reflect.DeepEqual(cfg.Nodes[i], expected[i]) {
|
|
||||||
t.Errorf("Nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Nodes[i])
|
|
||||||
}
|
|
||||||
if cfg.Repositories[0].Nodes[i].NodeID != expected[i].NodeID {
|
|
||||||
t.Errorf("Repo nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i].NodeID, cfg.Repositories[0].Nodes[i].NodeID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncOrders(t *testing.T) {
|
func TestSyncOrders(t *testing.T) {
|
||||||
data := []byte(`
|
data := []byte(`
|
||||||
<configuration version="2">
|
<configuration version="2">
|
||||||
<node id="AAAA-BBBB-CCCC">
|
|
||||||
<address>dynamic</address>
|
|
||||||
</node>
|
|
||||||
<repository directory="~/Sync">
|
<repository directory="~/Sync">
|
||||||
<syncorder>
|
<syncorder>
|
||||||
<pattern pattern="\.jpg$" priority="1" />
|
<pattern pattern="\.jpg$" priority="1" />
|
||||||
</syncorder>
|
</syncorder>
|
||||||
<node id="AAAA-BBBB-CCCC" name=""></node>
|
|
||||||
</repository>
|
</repository>
|
||||||
</configuration>
|
</configuration>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
expected := []SyncOrderPattern{
|
expected := []SyncOrderPattern{
|
||||||
{
|
{
|
||||||
Pattern: "\\.jpg$",
|
Pattern: "\\.jpg$",
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := Load(bytes.NewReader(data), "n4")
|
cfg, err := Load(bytes.NewReader(data), node1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range expected {
|
for i := range expected {
|
||||||
if !reflect.DeepEqual(cfg.Repositories[0].SyncOrderPatterns[i], expected[i]) {
|
if !reflect.DeepEqual(cfg.Repositories[0].SyncOrderPatterns[i], expected[i]) {
|
||||||
t.Errorf("Nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Repositories[0].SyncOrderPatterns[i])
|
t.Errorf("Patterns[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Repositories[0].SyncOrderPatterns[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,9 +307,9 @@ func TestFileSorter(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(f, expected) {
|
if !reflect.DeepEqual(f, expected) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"\n\nexpected:\n" +
|
"\n\nexpected:\n" +
|
||||||
formatFiles(expected) + "\n" +
|
formatFiles(expected) + "\n" +
|
||||||
"got:\n" +
|
"got:\n" +
|
||||||
formatFiles(f) + "\n\n",
|
formatFiles(f) + "\n\n",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/calmh/syncthing/discover"
|
"github.com/calmh/syncthing/discover"
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
"github.com/golang/groupcache/lru"
|
"github.com/golang/groupcache/lru"
|
||||||
"github.com/juju/ratelimit"
|
"github.com/juju/ratelimit"
|
||||||
)
|
)
|
||||||
@ -32,7 +33,7 @@ type address struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
nodes = make(map[string]node)
|
nodes = make(map[protocol.NodeID]node)
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
queries = 0
|
queries = 0
|
||||||
announces = 0
|
announces = 0
|
||||||
@ -182,8 +183,16 @@ func handleAnnounceV2(addr *net.UDPAddr, buf []byte) {
|
|||||||
updated: time.Now(),
|
updated: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var id protocol.NodeID
|
||||||
|
if len(pkt.This.ID) == 32 {
|
||||||
|
// Raw node ID
|
||||||
|
copy(id[:], pkt.This.ID)
|
||||||
|
} else {
|
||||||
|
id.UnmarshalText(pkt.This.ID)
|
||||||
|
}
|
||||||
|
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
nodes[pkt.This.ID] = node
|
nodes[id] = node
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,8 +208,16 @@ func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) {
|
|||||||
log.Printf("<- %v %#v", addr, pkt)
|
log.Printf("<- %v %#v", addr, pkt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var id protocol.NodeID
|
||||||
|
if len(pkt.NodeID) == 32 {
|
||||||
|
// Raw node ID
|
||||||
|
copy(id[:], pkt.NodeID)
|
||||||
|
} else {
|
||||||
|
id.UnmarshalText(pkt.NodeID)
|
||||||
|
}
|
||||||
|
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
node, ok := nodes[pkt.NodeID]
|
node, ok := nodes[id]
|
||||||
queries++
|
queries++
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package discover
|
package discover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,15 +15,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/calmh/syncthing/beacon"
|
"github.com/calmh/syncthing/beacon"
|
||||||
|
"github.com/calmh/syncthing/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Discoverer struct {
|
type Discoverer struct {
|
||||||
myID string
|
myID protocol.NodeID
|
||||||
listenAddrs []string
|
listenAddrs []string
|
||||||
localBcastIntv time.Duration
|
localBcastIntv time.Duration
|
||||||
globalBcastIntv time.Duration
|
globalBcastIntv time.Duration
|
||||||
beacon *beacon.Beacon
|
beacon *beacon.Beacon
|
||||||
registry map[string][]string
|
registry map[protocol.NodeID][]string
|
||||||
registryLock sync.RWMutex
|
registryLock sync.RWMutex
|
||||||
extServer string
|
extServer string
|
||||||
extPort uint16
|
extPort uint16
|
||||||
@ -41,7 +43,7 @@ var (
|
|||||||
// When we hit this many errors in succession, we stop.
|
// When we hit this many errors in succession, we stop.
|
||||||
const maxErrors = 30
|
const maxErrors = 30
|
||||||
|
|
||||||
func NewDiscoverer(id string, addresses []string, localPort int) (*Discoverer, error) {
|
func NewDiscoverer(id protocol.NodeID, addresses []string, localPort int) (*Discoverer, error) {
|
||||||
b, err := beacon.New(localPort)
|
b, err := beacon.New(localPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -52,7 +54,7 @@ func NewDiscoverer(id string, addresses []string, localPort int) (*Discoverer, e
|
|||||||
localBcastIntv: 30 * time.Second,
|
localBcastIntv: 30 * time.Second,
|
||||||
globalBcastIntv: 1800 * time.Second,
|
globalBcastIntv: 1800 * time.Second,
|
||||||
beacon: b,
|
beacon: b,
|
||||||
registry: make(map[string][]string),
|
registry: make(map[protocol.NodeID][]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
go disc.recvAnnouncements()
|
go disc.recvAnnouncements()
|
||||||
@ -78,7 +80,7 @@ func (d *Discoverer) ExtAnnounceOK() bool {
|
|||||||
return d.extAnnounceOK
|
return d.extAnnounceOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discoverer) Lookup(node string) []string {
|
func (d *Discoverer) Lookup(node protocol.NodeID) []string {
|
||||||
d.registryLock.Lock()
|
d.registryLock.Lock()
|
||||||
addr, ok := d.registry[node]
|
addr, ok := d.registry[node]
|
||||||
d.registryLock.Unlock()
|
d.registryLock.Unlock()
|
||||||
@ -94,15 +96,17 @@ func (d *Discoverer) Lookup(node string) []string {
|
|||||||
|
|
||||||
func (d *Discoverer) Hint(node string, addrs []string) {
|
func (d *Discoverer) Hint(node string, addrs []string) {
|
||||||
resAddrs := resolveAddrs(addrs)
|
resAddrs := resolveAddrs(addrs)
|
||||||
|
var id protocol.NodeID
|
||||||
|
id.UnmarshalText([]byte(node))
|
||||||
d.registerNode(nil, Node{
|
d.registerNode(nil, Node{
|
||||||
ID: node,
|
|
||||||
Addresses: resAddrs,
|
Addresses: resAddrs,
|
||||||
|
ID: id[:],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discoverer) All() map[string][]string {
|
func (d *Discoverer) All() map[protocol.NodeID][]string {
|
||||||
d.registryLock.RLock()
|
d.registryLock.RLock()
|
||||||
nodes := make(map[string][]string, len(d.registry))
|
nodes := make(map[protocol.NodeID][]string, len(d.registry))
|
||||||
for node, addrs := range d.registry {
|
for node, addrs := range d.registry {
|
||||||
addrsCopy := make([]string, len(addrs))
|
addrsCopy := make([]string, len(addrs))
|
||||||
copy(addrsCopy, addrs)
|
copy(addrsCopy, addrs)
|
||||||
@ -132,7 +136,7 @@ func (d *Discoverer) announcementPkt() []byte {
|
|||||||
}
|
}
|
||||||
var pkt = AnnounceV2{
|
var pkt = AnnounceV2{
|
||||||
Magic: AnnouncementMagicV2,
|
Magic: AnnouncementMagicV2,
|
||||||
This: Node{d.myID, addrs},
|
This: Node{d.myID[:], addrs},
|
||||||
}
|
}
|
||||||
return pkt.MarshalXDR()
|
return pkt.MarshalXDR()
|
||||||
}
|
}
|
||||||
@ -142,7 +146,7 @@ func (d *Discoverer) sendLocalAnnouncements() {
|
|||||||
|
|
||||||
var pkt = AnnounceV2{
|
var pkt = AnnounceV2{
|
||||||
Magic: AnnouncementMagicV2,
|
Magic: AnnouncementMagicV2,
|
||||||
This: Node{d.myID, addrs},
|
This: Node{d.myID[:], addrs},
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -153,7 +157,7 @@ func (d *Discoverer) sendLocalAnnouncements() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
anode := Node{node, resolveAddrs(addrs)}
|
anode := Node{node[:], resolveAddrs(addrs)}
|
||||||
pkt.Extra = append(pkt.Extra, anode)
|
pkt.Extra = append(pkt.Extra, anode)
|
||||||
}
|
}
|
||||||
d.registryLock.RUnlock()
|
d.registryLock.RUnlock()
|
||||||
@ -184,7 +188,7 @@ func (d *Discoverer) sendExternalAnnouncements() {
|
|||||||
if d.extPort != 0 {
|
if d.extPort != 0 {
|
||||||
var pkt = AnnounceV2{
|
var pkt = AnnounceV2{
|
||||||
Magic: AnnouncementMagicV2,
|
Magic: AnnouncementMagicV2,
|
||||||
This: Node{d.myID, []Address{{Port: d.extPort}}},
|
This: Node{d.myID[:], []Address{{Port: d.extPort}}},
|
||||||
}
|
}
|
||||||
buf = pkt.MarshalXDR()
|
buf = pkt.MarshalXDR()
|
||||||
} else {
|
} else {
|
||||||
@ -246,11 +250,11 @@ func (d *Discoverer) recvAnnouncements() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newNode bool
|
var newNode bool
|
||||||
if pkt.This.ID != d.myID {
|
if bytes.Compare(pkt.This.ID, d.myID[:]) != 0 {
|
||||||
n := d.registerNode(addr, pkt.This)
|
n := d.registerNode(addr, pkt.This)
|
||||||
newNode = newNode || n
|
newNode = newNode || n
|
||||||
for _, node := range pkt.Extra {
|
for _, node := range pkt.Extra {
|
||||||
if node.ID != d.myID {
|
if bytes.Compare(node.ID, d.myID[:]) != 0 {
|
||||||
n := d.registerNode(nil, node)
|
n := d.registerNode(nil, node)
|
||||||
newNode = newNode || n
|
newNode = newNode || n
|
||||||
}
|
}
|
||||||
@ -287,14 +291,16 @@ func (d *Discoverer) registerNode(addr net.Addr, node Node) bool {
|
|||||||
if debug {
|
if debug {
|
||||||
l.Debugf("discover: register: %s -> %#v", node.ID, addrs)
|
l.Debugf("discover: register: %s -> %#v", node.ID, addrs)
|
||||||
}
|
}
|
||||||
|
var id protocol.NodeID
|
||||||
|
copy(id[:], node.ID)
|
||||||
d.registryLock.Lock()
|
d.registryLock.Lock()
|
||||||
_, seen := d.registry[node.ID]
|
_, seen := d.registry[id]
|
||||||
d.registry[node.ID] = addrs
|
d.registry[id] = addrs
|
||||||
d.registryLock.Unlock()
|
d.registryLock.Unlock()
|
||||||
return !seen
|
return !seen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discoverer) externalLookup(node string) []string {
|
func (d *Discoverer) externalLookup(node protocol.NodeID) []string {
|
||||||
extIP, err := net.ResolveUDPAddr("udp", d.extServer)
|
extIP, err := net.ResolveUDPAddr("udp", d.extServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debug {
|
if debug {
|
||||||
@ -320,7 +326,7 @@ func (d *Discoverer) externalLookup(node string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := QueryV2{QueryMagicV2, node}.MarshalXDR()
|
buf := QueryV2{QueryMagicV2, node[:]}.MarshalXDR()
|
||||||
_, err = conn.Write(buf)
|
_, err = conn.Write(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debug {
|
if debug {
|
||||||
|
@ -11,7 +11,7 @@ const (
|
|||||||
|
|
||||||
type QueryV2 struct {
|
type QueryV2 struct {
|
||||||
Magic uint32
|
Magic uint32
|
||||||
NodeID string // max:64
|
NodeID []byte // max:32
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnnounceV2 struct {
|
type AnnounceV2 struct {
|
||||||
@ -21,7 +21,7 @@ type AnnounceV2 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID string // max:64
|
ID []byte // max:32
|
||||||
Addresses []Address // max:16
|
Addresses []Address // max:16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
|
|
||||||
// Use of this source code is governed by an MIT-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
package discover
|
package discover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -25,10 +21,10 @@ func (o QueryV2) MarshalXDR() []byte {
|
|||||||
|
|
||||||
func (o QueryV2) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o QueryV2) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteUint32(o.Magic)
|
xw.WriteUint32(o.Magic)
|
||||||
if len(o.NodeID) > 64 {
|
if len(o.NodeID) > 32 {
|
||||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||||
}
|
}
|
||||||
xw.WriteString(o.NodeID)
|
xw.WriteBytes(o.NodeID)
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +41,7 @@ func (o *QueryV2) UnmarshalXDR(bs []byte) error {
|
|||||||
|
|
||||||
func (o *QueryV2) decodeXDR(xr *xdr.Reader) error {
|
func (o *QueryV2) decodeXDR(xr *xdr.Reader) error {
|
||||||
o.Magic = xr.ReadUint32()
|
o.Magic = xr.ReadUint32()
|
||||||
o.NodeID = xr.ReadStringMax(64)
|
o.NodeID = xr.ReadBytesMax(32)
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,10 +108,10 @@ func (o Node) MarshalXDR() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||||
if len(o.ID) > 64 {
|
if len(o.ID) > 32 {
|
||||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||||
}
|
}
|
||||||
xw.WriteString(o.ID)
|
xw.WriteBytes(o.ID)
|
||||||
if len(o.Addresses) > 16 {
|
if len(o.Addresses) > 16 {
|
||||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||||
}
|
}
|
||||||
@ -138,7 +134,7 @@ func (o *Node) UnmarshalXDR(bs []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Node) decodeXDR(xr *xdr.Reader) error {
|
func (o *Node) decodeXDR(xr *xdr.Reader) error {
|
||||||
o.ID = xr.ReadStringMax(64)
|
o.ID = xr.ReadBytesMax(32)
|
||||||
_AddressesSize := int(xr.ReadUint32())
|
_AddressesSize := int(xr.ReadUint32())
|
||||||
if _AddressesSize > 16 {
|
if _AddressesSize > 16 {
|
||||||
return xdr.ErrElementSizeExceeded
|
return xdr.ErrElementSizeExceeded
|
||||||
|
19
gui/app.js
19
gui/app.js
@ -410,7 +410,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
|||||||
|
|
||||||
$('#editNode').modal('hide');
|
$('#editNode').modal('hide');
|
||||||
nodeCfg = $scope.currentNode;
|
nodeCfg = $scope.currentNode;
|
||||||
nodeCfg.NodeID = nodeCfg.NodeID.replace(/ /g, '').replace(/-/g, '').toUpperCase().trim();
|
nodeCfg.NodeID = nodeCfg.NodeID.replace(/ /g, '').replace(/-/g, '').toLowerCase().trim();
|
||||||
nodeCfg.Addresses = nodeCfg.AddressesStr.split(',').map(function (x) { return x.trim(); });
|
nodeCfg.Addresses = nodeCfg.AddressesStr.split(',').map(function (x) { return x.trim(); });
|
||||||
|
|
||||||
done = false;
|
done = false;
|
||||||
@ -711,7 +711,7 @@ function randomString(len, bits)
|
|||||||
newStr = Math.random().toString(bits).slice(2);
|
newStr = Math.random().toString(bits).slice(2);
|
||||||
outStr += newStr.slice(0, Math.min(newStr.length, (len - outStr.length)));
|
outStr += newStr.slice(0, Math.min(newStr.length, (len - outStr.length)));
|
||||||
}
|
}
|
||||||
return outStr.toUpperCase();
|
return outStr.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
syncthing.filter('natural', function () {
|
syncthing.filter('natural', function () {
|
||||||
@ -777,17 +777,6 @@ syncthing.filter('alwaysNumber', function () {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
syncthing.filter('chunkID', function () {
|
|
||||||
return function (input) {
|
|
||||||
if (input === undefined)
|
|
||||||
return "";
|
|
||||||
var parts = input.match(/.{1,6}/g);
|
|
||||||
if (!parts)
|
|
||||||
return "";
|
|
||||||
return parts.join('-');
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
syncthing.filter('shortPath', function () {
|
syncthing.filter('shortPath', function () {
|
||||||
return function (input) {
|
return function (input) {
|
||||||
if (input === undefined)
|
if (input === undefined)
|
||||||
@ -860,8 +849,8 @@ syncthing.directive('validNodeid', function() {
|
|||||||
// we shouldn't validate
|
// we shouldn't validate
|
||||||
ctrl.$setValidity('validNodeid', true);
|
ctrl.$setValidity('validNodeid', true);
|
||||||
} else {
|
} else {
|
||||||
var cleaned = viewValue.replace(/ /g, '').replace(/-/g, '').toUpperCase().trim();
|
var cleaned = viewValue.replace(/ /g, '').replace(/-/g, '').toLowerCase().trim();
|
||||||
if (cleaned.match(/^[A-Z2-7]{52}$/)) {
|
if (cleaned.match(/^[a-z2-7]{52}$/)) {
|
||||||
ctrl.$setValidity('validNodeid', true);
|
ctrl.$setValidity('validNodeid', true);
|
||||||
} else {
|
} else {
|
||||||
ctrl.$setValidity('validNodeid', false);
|
ctrl.$setValidity('validNodeid', false);
|
||||||
|
@ -418,8 +418,8 @@ found in the LICENSE file.
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="well well-sm text-monospace text-center">{{myID | chunkID}}</div>
|
<div class="well well-sm text-monospace text-center">{{myID}}</div>
|
||||||
<img ng-if="myID" class="center-block img-thumbnail" src="qr/{{myID | chunkID}}"/>
|
<img ng-if="myID" class="center-block img-thumbnail" src="qr/{{myID}}"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> Close</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> Close</button>
|
||||||
@ -442,7 +442,7 @@ found in the LICENSE file.
|
|||||||
<div class="form-group" ng-class="{'has-error': nodeEditor.nodeID.$invalid && nodeEditor.nodeID.$dirty}">
|
<div class="form-group" ng-class="{'has-error': nodeEditor.nodeID.$invalid && nodeEditor.nodeID.$dirty}">
|
||||||
<label for="nodeID">Node ID</label>
|
<label for="nodeID">Node ID</label>
|
||||||
<input ng-if="!editingExisting" name="nodeID" id="nodeID" class="form-control text-monospace" type="text" ng-model="currentNode.NodeID" required valid-nodeid></input>
|
<input ng-if="!editingExisting" name="nodeID" id="nodeID" class="form-control text-monospace" type="text" ng-model="currentNode.NodeID" required valid-nodeid></input>
|
||||||
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentNode.NodeID | chunkID}}</div>
|
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentNode.NodeID}}</div>
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
<span ng-if="nodeEditor.nodeID.$valid || nodeEditor.nodeID.$pristine">The node ID to enter here can be found in the "Edit > Show ID" dialog on the other node. Spaces and dashes are optional (ignored).
|
<span ng-if="nodeEditor.nodeID.$valid || nodeEditor.nodeID.$pristine">The node ID to enter here can be found in the "Edit > Show ID" dialog on the other node. Spaces and dashes are optional (ignored).
|
||||||
<span ng-show="!editingExisting">When adding a new node, keep in mind that <em>this node</em> must be added on the other side too.</span>
|
<span ng-show="!editingExisting">When adding a new node, keep in mind that <em>this node</em> must be added on the other side too.</span>
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
<configuration version="2">
|
<configuration version="2">
|
||||||
<repository id="default" directory="s2" ro="false" ignorePerms="false">
|
<repository id="default" directory="s2" ro="false" ignorePerms="false">
|
||||||
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA"></node>
|
|
||||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
||||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
||||||
|
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA"></node>
|
||||||
<versioning></versioning>
|
<versioning></versioning>
|
||||||
|
<syncorder></syncorder>
|
||||||
</repository>
|
</repository>
|
||||||
<repository id="s12" directory="s12-2" ro="false" ignorePerms="false">
|
<repository id="s12" directory="s12-2" ro="false" ignorePerms="false">
|
||||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
||||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
||||||
<versioning></versioning>
|
<versioning></versioning>
|
||||||
|
<syncorder></syncorder>
|
||||||
</repository>
|
</repository>
|
||||||
<repository id="s23" directory="s23-2" ro="false" ignorePerms="false">
|
<repository id="s23" directory="s23-2" ro="false" ignorePerms="false">
|
||||||
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA"></node>
|
|
||||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
||||||
|
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA"></node>
|
||||||
<versioning></versioning>
|
<versioning></versioning>
|
||||||
|
<syncorder></syncorder>
|
||||||
</repository>
|
</repository>
|
||||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="s1">
|
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="s1">
|
||||||
<address>127.0.0.1:22001</address>
|
<address>127.0.0.1:22001</address>
|
||||||
@ -41,5 +44,6 @@
|
|||||||
<maxChangeKbps>10000</maxChangeKbps>
|
<maxChangeKbps>10000</maxChangeKbps>
|
||||||
<startBrowser>false</startBrowser>
|
<startBrowser>false</startBrowser>
|
||||||
<upnpEnabled>true</upnpEnabled>
|
<upnpEnabled>true</upnpEnabled>
|
||||||
|
<urAccepted>-1</urAccepted>
|
||||||
</options>
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
iterations=${1:-5}
|
iterations=${1:-5}
|
||||||
|
|
||||||
id1=I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA
|
id1=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU
|
||||||
id2=JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ
|
id2=JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU
|
||||||
id3=373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA
|
id3=373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU
|
||||||
|
|
||||||
go build genfiles.go
|
go build genfiles.go
|
||||||
go build md5r.go
|
go build md5r.go
|
||||||
|
37
luhn/luhn.go
Normal file
37
luhn/luhn.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package luhn
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Alphabet string
|
||||||
|
|
||||||
|
var (
|
||||||
|
Base32 Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="
|
||||||
|
Base32Trimmed Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a Alphabet) Generate(s string) rune {
|
||||||
|
factor := 1
|
||||||
|
sum := 0
|
||||||
|
n := len(a)
|
||||||
|
|
||||||
|
for i := range s {
|
||||||
|
codepoint := strings.IndexByte(string(a), s[i])
|
||||||
|
addend := factor * codepoint
|
||||||
|
if factor == 2 {
|
||||||
|
factor = 1
|
||||||
|
} else {
|
||||||
|
factor = 2
|
||||||
|
}
|
||||||
|
addend = (addend / n) + (addend % n)
|
||||||
|
sum += addend
|
||||||
|
}
|
||||||
|
remainder := sum % n
|
||||||
|
checkCodepoint := (n - remainder) % n
|
||||||
|
return rune(a[checkCodepoint])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Alphabet) Validate(s string) bool {
|
||||||
|
t := s[:len(s)-1]
|
||||||
|
c := a.Generate(t)
|
||||||
|
return rune(s[len(s)-1]) == c
|
||||||
|
}
|
25
luhn/luhn_test.go
Normal file
25
luhn/luhn_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package luhn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/calmh/syncthing/luhn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
a := luhn.Alphabet("abcdef")
|
||||||
|
c := a.Generate("abcdef")
|
||||||
|
if c != 'e' {
|
||||||
|
t.Errorf("Incorrect check digit %c != e", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
a := luhn.Alphabet("abcdef")
|
||||||
|
if !a.Validate("abcdefe") {
|
||||||
|
t.Errorf("Incorrect validation response for abcdefe")
|
||||||
|
}
|
||||||
|
if a.Validate("abcdefd") {
|
||||||
|
t.Errorf("Incorrect validation response for abcdefd")
|
||||||
|
}
|
||||||
|
}
|
@ -49,8 +49,8 @@ type Model struct {
|
|||||||
|
|
||||||
repoCfgs map[string]config.RepositoryConfiguration // repo -> cfg
|
repoCfgs map[string]config.RepositoryConfiguration // repo -> cfg
|
||||||
repoFiles map[string]*files.Set // repo -> files
|
repoFiles map[string]*files.Set // repo -> files
|
||||||
repoNodes map[string][]string // repo -> nodeIDs
|
repoNodes map[string][]protocol.NodeID // repo -> nodeIDs
|
||||||
nodeRepos map[string][]string // nodeID -> repos
|
nodeRepos map[protocol.NodeID][]string // nodeID -> repos
|
||||||
suppressor map[string]*suppressor // repo -> suppressor
|
suppressor map[string]*suppressor // repo -> suppressor
|
||||||
rmut sync.RWMutex // protects the above
|
rmut sync.RWMutex // protects the above
|
||||||
|
|
||||||
@ -59,9 +59,9 @@ type Model struct {
|
|||||||
|
|
||||||
cm *cid.Map
|
cm *cid.Map
|
||||||
|
|
||||||
protoConn map[string]protocol.Connection
|
protoConn map[protocol.NodeID]protocol.Connection
|
||||||
rawConn map[string]io.Closer
|
rawConn map[protocol.NodeID]io.Closer
|
||||||
nodeVer map[string]string
|
nodeVer map[protocol.NodeID]string
|
||||||
pmut sync.RWMutex // protects protoConn and rawConn
|
pmut sync.RWMutex // protects protoConn and rawConn
|
||||||
|
|
||||||
sup suppressor
|
sup suppressor
|
||||||
@ -86,14 +86,14 @@ func NewModel(indexDir string, cfg *config.Configuration, clientName, clientVers
|
|||||||
clientVersion: clientVersion,
|
clientVersion: clientVersion,
|
||||||
repoCfgs: make(map[string]config.RepositoryConfiguration),
|
repoCfgs: make(map[string]config.RepositoryConfiguration),
|
||||||
repoFiles: make(map[string]*files.Set),
|
repoFiles: make(map[string]*files.Set),
|
||||||
repoNodes: make(map[string][]string),
|
repoNodes: make(map[string][]protocol.NodeID),
|
||||||
nodeRepos: make(map[string][]string),
|
nodeRepos: make(map[protocol.NodeID][]string),
|
||||||
repoState: make(map[string]repoState),
|
repoState: make(map[string]repoState),
|
||||||
suppressor: make(map[string]*suppressor),
|
suppressor: make(map[string]*suppressor),
|
||||||
cm: cid.NewMap(),
|
cm: cid.NewMap(),
|
||||||
protoConn: make(map[string]protocol.Connection),
|
protoConn: make(map[protocol.NodeID]protocol.Connection),
|
||||||
rawConn: make(map[string]io.Closer),
|
rawConn: make(map[protocol.NodeID]io.Closer),
|
||||||
nodeVer: make(map[string]string),
|
nodeVer: make(map[protocol.NodeID]string),
|
||||||
sup: suppressor{threshold: int64(cfg.Options.MaxChangeKbps)},
|
sup: suppressor{threshold: int64(cfg.Options.MaxChangeKbps)},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
|
|||||||
ci.Completion = int(100 * have / tot)
|
ci.Completion = int(100 * have / tot)
|
||||||
}
|
}
|
||||||
|
|
||||||
res[node] = ci
|
res[node.String()] = ci
|
||||||
}
|
}
|
||||||
|
|
||||||
m.rmut.RUnlock()
|
m.rmut.RUnlock()
|
||||||
@ -261,7 +261,7 @@ func (m *Model) NeedFilesRepo(repo string) []scanner.File {
|
|||||||
|
|
||||||
// Index is called when a new node is connected and we receive their full index.
|
// Index is called when a new node is connected and we receive their full index.
|
||||||
// Implements the protocol.Model interface.
|
// Implements the protocol.Model interface.
|
||||||
func (m *Model) Index(nodeID string, repo string, fs []protocol.FileInfo) {
|
func (m *Model) Index(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("IDX(in): %s %q: %d files", nodeID, repo, len(fs))
|
l.Debugf("IDX(in): %s %q: %d files", nodeID, repo, len(fs))
|
||||||
}
|
}
|
||||||
@ -297,7 +297,7 @@ func (m *Model) Index(nodeID string, repo string, fs []protocol.FileInfo) {
|
|||||||
|
|
||||||
// IndexUpdate is called for incremental updates to connected nodes' indexes.
|
// IndexUpdate is called for incremental updates to connected nodes' indexes.
|
||||||
// Implements the protocol.Model interface.
|
// Implements the protocol.Model interface.
|
||||||
func (m *Model) IndexUpdate(nodeID string, repo string, fs []protocol.FileInfo) {
|
func (m *Model) IndexUpdate(nodeID protocol.NodeID, repo string, fs []protocol.FileInfo) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("IDXUP(in): %s / %q: %d files", nodeID, repo, len(fs))
|
l.Debugf("IDXUP(in): %s / %q: %d files", nodeID, repo, len(fs))
|
||||||
}
|
}
|
||||||
@ -331,7 +331,7 @@ func (m *Model) IndexUpdate(nodeID string, repo string, fs []protocol.FileInfo)
|
|||||||
m.rmut.RUnlock()
|
m.rmut.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) repoSharedWith(repo, nodeID string) bool {
|
func (m *Model) repoSharedWith(repo string, nodeID protocol.NodeID) bool {
|
||||||
m.rmut.RLock()
|
m.rmut.RLock()
|
||||||
defer m.rmut.RUnlock()
|
defer m.rmut.RUnlock()
|
||||||
for _, nrepo := range m.nodeRepos[nodeID] {
|
for _, nrepo := range m.nodeRepos[nodeID] {
|
||||||
@ -342,7 +342,7 @@ func (m *Model) repoSharedWith(repo, nodeID string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessage) {
|
func (m *Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterConfigMessage) {
|
||||||
compErr := compareClusterConfig(m.clusterConfig(nodeID), config)
|
compErr := compareClusterConfig(m.clusterConfig(nodeID), config)
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("ClusterConfig: %s: %#v", nodeID, config)
|
l.Debugf("ClusterConfig: %s: %#v", nodeID, config)
|
||||||
@ -365,7 +365,7 @@ func (m *Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessag
|
|||||||
|
|
||||||
// Close removes the peer from the model and closes the underlying connection if possible.
|
// Close removes the peer from the model and closes the underlying connection if possible.
|
||||||
// Implements the protocol.Model interface.
|
// Implements the protocol.Model interface.
|
||||||
func (m *Model) Close(node string, err error) {
|
func (m *Model) Close(node protocol.NodeID, err error) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("%s: %v", node, err)
|
l.Debugf("%s: %v", node, err)
|
||||||
}
|
}
|
||||||
@ -397,7 +397,7 @@ func (m *Model) Close(node string, err error) {
|
|||||||
|
|
||||||
// Request returns the specified data segment by reading it from local disk.
|
// Request returns the specified data segment by reading it from local disk.
|
||||||
// Implements the protocol.Model interface.
|
// Implements the protocol.Model interface.
|
||||||
func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]byte, error) {
|
func (m *Model) Request(nodeID protocol.NodeID, repo, name string, offset int64, size int) ([]byte, error) {
|
||||||
// Verify that the requested file exists in the local model.
|
// Verify that the requested file exists in the local model.
|
||||||
m.rmut.RLock()
|
m.rmut.RLock()
|
||||||
r, ok := m.repoFiles[repo]
|
r, ok := m.repoFiles[repo]
|
||||||
@ -423,7 +423,7 @@ func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]by
|
|||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchFile
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug && nodeID != "<local>" {
|
if debug && nodeID != cid.LocalNodeID {
|
||||||
l.Debugf("REQ(in): %s: %q / %q o=%d s=%d", nodeID, repo, name, offset, size)
|
l.Debugf("REQ(in): %s: %q / %q o=%d s=%d", nodeID, repo, name, offset, size)
|
||||||
}
|
}
|
||||||
m.rmut.RLock()
|
m.rmut.RLock()
|
||||||
@ -489,7 +489,7 @@ func (cf cFiler) CurrentFile(file string) scanner.File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConnectedTo returns true if we are connected to the named node.
|
// ConnectedTo returns true if we are connected to the named node.
|
||||||
func (m *Model) ConnectedTo(nodeID string) bool {
|
func (m *Model) ConnectedTo(nodeID protocol.NodeID) bool {
|
||||||
m.pmut.RLock()
|
m.pmut.RLock()
|
||||||
_, ok := m.protoConn[nodeID]
|
_, ok := m.protoConn[nodeID]
|
||||||
m.pmut.RUnlock()
|
m.pmut.RUnlock()
|
||||||
@ -560,7 +560,7 @@ func (m *Model) updateLocal(repo string, f scanner.File) {
|
|||||||
m.rmut.RUnlock()
|
m.rmut.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) requestGlobal(nodeID, repo, name string, offset int64, size int, hash []byte) ([]byte, error) {
|
func (m *Model) requestGlobal(nodeID protocol.NodeID, repo, name string, offset int64, size int, hash []byte) ([]byte, error) {
|
||||||
m.pmut.RLock()
|
m.pmut.RLock()
|
||||||
nc, ok := m.protoConn[nodeID]
|
nc, ok := m.protoConn[nodeID]
|
||||||
m.pmut.RUnlock()
|
m.pmut.RUnlock()
|
||||||
@ -639,7 +639,7 @@ func (m *Model) AddRepo(cfg config.RepositoryConfiguration) {
|
|||||||
m.repoFiles[cfg.ID] = files.NewSet()
|
m.repoFiles[cfg.ID] = files.NewSet()
|
||||||
m.suppressor[cfg.ID] = &suppressor{threshold: int64(m.cfg.Options.MaxChangeKbps)}
|
m.suppressor[cfg.ID] = &suppressor{threshold: int64(m.cfg.Options.MaxChangeKbps)}
|
||||||
|
|
||||||
m.repoNodes[cfg.ID] = make([]string, len(cfg.Nodes))
|
m.repoNodes[cfg.ID] = make([]protocol.NodeID, len(cfg.Nodes))
|
||||||
for i, node := range cfg.Nodes {
|
for i, node := range cfg.Nodes {
|
||||||
m.repoNodes[cfg.ID][i] = node.NodeID
|
m.repoNodes[cfg.ID][i] = node.NodeID
|
||||||
m.nodeRepos[node.NodeID] = append(m.nodeRepos[node.NodeID], cfg.ID)
|
m.nodeRepos[node.NodeID] = append(m.nodeRepos[node.NodeID], cfg.ID)
|
||||||
@ -805,7 +805,7 @@ func (m *Model) loadIndex(repo string, dir string) []protocol.FileInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer node
|
// clusterConfig returns a ClusterConfigMessage that is correct for the given peer node
|
||||||
func (m *Model) clusterConfig(node string) protocol.ClusterConfigMessage {
|
func (m *Model) clusterConfig(node protocol.NodeID) protocol.ClusterConfigMessage {
|
||||||
cm := protocol.ClusterConfigMessage{
|
cm := protocol.ClusterConfigMessage{
|
||||||
ClientName: m.clientName,
|
ClientName: m.clientName,
|
||||||
ClientVersion: m.clientVersion,
|
ClientVersion: m.clientVersion,
|
||||||
@ -819,7 +819,7 @@ func (m *Model) clusterConfig(node string) protocol.ClusterConfigMessage {
|
|||||||
for _, node := range m.repoNodes[repo] {
|
for _, node := range m.repoNodes[repo] {
|
||||||
// TODO: Set read only bit when relevant
|
// TODO: Set read only bit when relevant
|
||||||
cr.Nodes = append(cr.Nodes, protocol.Node{
|
cr.Nodes = append(cr.Nodes, protocol.Node{
|
||||||
ID: node,
|
ID: node[:],
|
||||||
Flags: protocol.FlagShareTrusted,
|
Flags: protocol.FlagShareTrusted,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,13 @@ import (
|
|||||||
"github.com/calmh/syncthing/scanner"
|
"github.com/calmh/syncthing/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var node1, node2 protocol.NodeID
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
node1, _ = protocol.NodeIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
|
||||||
|
node2, _ = protocol.NodeIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
|
||||||
|
}
|
||||||
|
|
||||||
var testDataExpected = map[string]scanner.File{
|
var testDataExpected = map[string]scanner.File{
|
||||||
"foo": scanner.File{
|
"foo": scanner.File{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
@ -56,7 +63,7 @@ func TestRequest(t *testing.T) {
|
|||||||
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
||||||
m.ScanRepo("default")
|
m.ScanRepo("default")
|
||||||
|
|
||||||
bs, err := m.Request("some node", "default", "foo", 0, 6)
|
bs, err := m.Request(node1, "default", "foo", 0, 6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -64,7 +71,7 @@ func TestRequest(t *testing.T) {
|
|||||||
t.Errorf("Incorrect data from request: %q", string(bs))
|
t.Errorf("Incorrect data from request: %q", string(bs))
|
||||||
}
|
}
|
||||||
|
|
||||||
bs, err = m.Request("some node", "default", "../walk.go", 0, 6)
|
bs, err = m.Request(node1, "default", "../walk.go", 0, 6)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Unexpected nil error on insecure file read")
|
t.Error("Unexpected nil error on insecure file read")
|
||||||
}
|
}
|
||||||
@ -95,7 +102,7 @@ func BenchmarkIndex10000(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +114,7 @@ func BenchmarkIndex00100(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +123,11 @@ func BenchmarkIndexUpdate10000f10000(b *testing.B) {
|
|||||||
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
||||||
m.ScanRepo("default")
|
m.ScanRepo("default")
|
||||||
files := genFiles(10000)
|
files := genFiles(10000)
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.IndexUpdate("42", "default", files)
|
m.IndexUpdate(node1, "default", files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,12 +136,12 @@ func BenchmarkIndexUpdate10000f00100(b *testing.B) {
|
|||||||
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
||||||
m.ScanRepo("default")
|
m.ScanRepo("default")
|
||||||
files := genFiles(10000)
|
files := genFiles(10000)
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
|
|
||||||
ufiles := genFiles(100)
|
ufiles := genFiles(100)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.IndexUpdate("42", "default", ufiles)
|
m.IndexUpdate(node1, "default", ufiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,17 +150,17 @@ func BenchmarkIndexUpdate10000f00001(b *testing.B) {
|
|||||||
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
m.AddRepo(config.RepositoryConfiguration{ID: "default", Directory: "testdata"})
|
||||||
m.ScanRepo("default")
|
m.ScanRepo("default")
|
||||||
files := genFiles(10000)
|
files := genFiles(10000)
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
|
|
||||||
ufiles := genFiles(1)
|
ufiles := genFiles(1)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m.IndexUpdate("42", "default", ufiles)
|
m.IndexUpdate(node1, "default", ufiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeConnection struct {
|
type FakeConnection struct {
|
||||||
id string
|
id protocol.NodeID
|
||||||
requestData []byte
|
requestData []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,8 +168,8 @@ func (FakeConnection) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FakeConnection) ID() string {
|
func (f FakeConnection) ID() protocol.NodeID {
|
||||||
return string(f.id)
|
return f.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FakeConnection) Option(string) string {
|
func (f FakeConnection) Option(string) string {
|
||||||
@ -202,15 +209,15 @@ func BenchmarkRequest(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fc := FakeConnection{
|
fc := FakeConnection{
|
||||||
id: "42",
|
id: node1,
|
||||||
requestData: []byte("some data to return"),
|
requestData: []byte("some data to return"),
|
||||||
}
|
}
|
||||||
m.AddConnection(fc, fc)
|
m.AddConnection(fc, fc)
|
||||||
m.Index("42", "default", files)
|
m.Index(node1, "default", files)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
data, err := m.requestGlobal("42", "default", files[i%n].Name, 0, 32, nil)
|
data, err := m.requestGlobal(node1, "default", files[i%n].Name, 0, 32, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
@ -222,26 +229,26 @@ func BenchmarkRequest(b *testing.B) {
|
|||||||
|
|
||||||
func TestActivityMap(t *testing.T) {
|
func TestActivityMap(t *testing.T) {
|
||||||
cm := cid.NewMap()
|
cm := cid.NewMap()
|
||||||
fooID := cm.Get("foo")
|
fooID := cm.Get(node1)
|
||||||
if fooID == 0 {
|
if fooID == 0 {
|
||||||
t.Fatal("ID cannot be zero")
|
t.Fatal("ID cannot be zero")
|
||||||
}
|
}
|
||||||
barID := cm.Get("bar")
|
barID := cm.Get(node2)
|
||||||
if barID == 0 {
|
if barID == 0 {
|
||||||
t.Fatal("ID cannot be zero")
|
t.Fatal("ID cannot be zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := make(activityMap)
|
m := make(activityMap)
|
||||||
if node := m.leastBusyNode(1<<fooID, cm); node != "foo" {
|
if node := m.leastBusyNode(1<<fooID, cm); node != node1 {
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
t.Errorf("Incorrect least busy node %q", node)
|
||||||
}
|
}
|
||||||
if node := m.leastBusyNode(1<<barID, cm); node != "bar" {
|
if node := m.leastBusyNode(1<<barID, cm); node != node2 {
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
t.Errorf("Incorrect least busy node %q", node)
|
||||||
}
|
}
|
||||||
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != "foo" {
|
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != node1 {
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
t.Errorf("Incorrect least busy node %q", node)
|
||||||
}
|
}
|
||||||
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != "bar" {
|
if node := m.leastBusyNode(1<<fooID|1<<barID, cm); node != node2 {
|
||||||
t.Errorf("Incorrect least busy node %q", node)
|
t.Errorf("Incorrect least busy node %q", node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type requestResult struct {
|
type requestResult struct {
|
||||||
node string
|
node protocol.NodeID
|
||||||
file scanner.File
|
file scanner.File
|
||||||
filepath string // full filepath name
|
filepath string // full filepath name
|
||||||
offset int64
|
offset int64
|
||||||
@ -39,11 +39,11 @@ type openFile struct {
|
|||||||
done bool // we have sent all requests for this file
|
done bool // we have sent all requests for this file
|
||||||
}
|
}
|
||||||
|
|
||||||
type activityMap map[string]int
|
type activityMap map[protocol.NodeID]int
|
||||||
|
|
||||||
func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) string {
|
func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) protocol.NodeID {
|
||||||
var low int = 2<<30 - 1
|
var low int = 2<<30 - 1
|
||||||
var selected string
|
var selected protocol.NodeID
|
||||||
for _, node := range cm.Names() {
|
for _, node := range cm.Names() {
|
||||||
id := cm.Get(node)
|
id := cm.Get(node)
|
||||||
if id == cid.LocalID {
|
if id == cid.LocalID {
|
||||||
@ -61,7 +61,7 @@ func (m activityMap) leastBusyNode(availability uint64, cm *cid.Map) string {
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m activityMap) decrease(node string) {
|
func (m activityMap) decrease(node protocol.NodeID) {
|
||||||
m[node]--
|
m[node]--
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +540,7 @@ func (p *puller) handleRequestBlock(b bqBlock) bool {
|
|||||||
of.outstanding++
|
of.outstanding++
|
||||||
p.openFiles[f.Name] = of
|
p.openFiles[f.Name] = of
|
||||||
|
|
||||||
go func(node string, b bqBlock) {
|
go func(node protocol.NodeID, b bqBlock) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("pull: requesting %q / %q offset %d size %d from %q outstanding %d", p.repoCfg.ID, f.Name, b.block.Offset, b.block.Size, node, of.outstanding)
|
l.Debugf("pull: requesting %q / %q offset %d size %d from %q outstanding %d", p.repoCfg.ID, f.Name, b.block.Offset, b.block.Size, node, of.outstanding)
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,14 @@ func fileInfoFromFile(f scanner.File) protocol.FileInfo {
|
|||||||
return pf
|
return pf
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmMap(cm protocol.ClusterConfigMessage) map[string]map[string]uint32 {
|
func cmMap(cm protocol.ClusterConfigMessage) map[string]map[protocol.NodeID]uint32 {
|
||||||
m := make(map[string]map[string]uint32)
|
m := make(map[string]map[protocol.NodeID]uint32)
|
||||||
for _, repo := range cm.Repositories {
|
for _, repo := range cm.Repositories {
|
||||||
m[repo.ID] = make(map[string]uint32)
|
m[repo.ID] = make(map[protocol.NodeID]uint32)
|
||||||
for _, node := range repo.Nodes {
|
for _, node := range repo.Nodes {
|
||||||
m[repo.ID][node.ID] = node.Flags
|
var id protocol.NodeID
|
||||||
|
copy(id[:], node.ID)
|
||||||
|
m[repo.ID][id] = node.Flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
@ -72,7 +74,7 @@ func cmMap(cm protocol.ClusterConfigMessage) map[string]map[string]uint32 {
|
|||||||
type ClusterConfigMismatch error
|
type ClusterConfigMismatch error
|
||||||
|
|
||||||
// compareClusterConfig returns nil for two equivalent configurations,
|
// compareClusterConfig returns nil for two equivalent configurations,
|
||||||
// otherwise a decriptive error
|
// otherwise a descriptive error
|
||||||
func compareClusterConfig(local, remote protocol.ClusterConfigMessage) error {
|
func compareClusterConfig(local, remote protocol.ClusterConfigMessage) error {
|
||||||
lm := cmMap(local)
|
lm := cmMap(local)
|
||||||
rm := cmMap(remote)
|
rm := cmMap(remote)
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
|
|
||||||
// Use of this source code is governed by an MIT-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/calmh/syncthing/protocol"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testcases = []struct {
|
|
||||||
local, remote protocol.ClusterConfigMessage
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{},
|
|
||||||
remote: protocol.ClusterConfigMessage{},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{ClientName: "a", ClientVersion: "b"},
|
|
||||||
remote: protocol.ClusterConfigMessage{ClientName: "c", ClientVersion: "d"},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{ID: "foo"},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remote: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{ID: "foo"},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{
|
|
||||||
ID: "foo",
|
|
||||||
Nodes: []protocol.Node{
|
|
||||||
{ID: "a"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remote: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{ID: "foo"},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{
|
|
||||||
ID: "foo",
|
|
||||||
Nodes: []protocol.Node{
|
|
||||||
{ID: "a"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remote: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{
|
|
||||||
ID: "foo",
|
|
||||||
Nodes: []protocol.Node{
|
|
||||||
{ID: "a"},
|
|
||||||
{ID: "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
local: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{
|
|
||||||
ID: "foo",
|
|
||||||
Nodes: []protocol.Node{
|
|
||||||
{
|
|
||||||
ID: "a",
|
|
||||||
Flags: protocol.FlagShareReadOnly,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remote: protocol.ClusterConfigMessage{
|
|
||||||
Repositories: []protocol.Repository{
|
|
||||||
{
|
|
||||||
ID: "foo",
|
|
||||||
Nodes: []protocol.Node{
|
|
||||||
{
|
|
||||||
ID: "a",
|
|
||||||
Flags: protocol.FlagShareTrusted,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ID: "bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: `remote has different sharing flags for node "a" in repository "foo"`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareClusterConfig(t *testing.T) {
|
|
||||||
for i, tc := range testcases {
|
|
||||||
err := compareClusterConfig(tc.local, tc.remote)
|
|
||||||
switch {
|
|
||||||
case tc.err == "" && err != nil:
|
|
||||||
t.Errorf("#%d: unexpected error: %v", i, err)
|
|
||||||
|
|
||||||
case tc.err != "" && err == nil:
|
|
||||||
t.Errorf("#%d: unexpected nil error", i)
|
|
||||||
|
|
||||||
case tc.err != "" && err != nil && tc.err != err.Error():
|
|
||||||
t.Errorf("#%d: incorrect error: %q != %q", i, err, tc.err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,13 +24,13 @@ func newTestModel() *TestModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Index(nodeID string, repo string, files []FileInfo) {
|
func (t *TestModel) Index(nodeID NodeID, repo string, files []FileInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) IndexUpdate(nodeID string, repo string, files []FileInfo) {
|
func (t *TestModel) IndexUpdate(nodeID NodeID, repo string, files []FileInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Request(nodeID, repo, name string, offset int64, size int) ([]byte, error) {
|
func (t *TestModel) Request(nodeID NodeID, repo, name string, offset int64, size int) ([]byte, error) {
|
||||||
t.repo = repo
|
t.repo = repo
|
||||||
t.name = name
|
t.name = name
|
||||||
t.offset = offset
|
t.offset = offset
|
||||||
@ -38,11 +38,11 @@ func (t *TestModel) Request(nodeID, repo, name string, offset int64, size int) (
|
|||||||
return t.data, nil
|
return t.data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Close(nodeID string, err error) {
|
func (t *TestModel) Close(nodeID NodeID, err error) {
|
||||||
close(t.closedCh)
|
close(t.closedCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) ClusterConfig(nodeID string, config ClusterConfigMessage) {
|
func (t *TestModel) ClusterConfig(nodeID NodeID, config ClusterConfigMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) isClosed() bool {
|
func (t *TestModel) isClosed() bool {
|
||||||
|
@ -42,7 +42,7 @@ type Repository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
ID string // max:64
|
ID []byte // max:32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
MaxVersion uint64
|
MaxVersion uint64
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved.
|
|
||||||
// Use of this source code is governed by an MIT-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -337,10 +333,10 @@ func (o Node) MarshalXDR() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o Node) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||||
if len(o.ID) > 64 {
|
if len(o.ID) > 32 {
|
||||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||||
}
|
}
|
||||||
xw.WriteString(o.ID)
|
xw.WriteBytes(o.ID)
|
||||||
xw.WriteUint32(o.Flags)
|
xw.WriteUint32(o.Flags)
|
||||||
xw.WriteUint64(o.MaxVersion)
|
xw.WriteUint64(o.MaxVersion)
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
@ -358,7 +354,7 @@ func (o *Node) UnmarshalXDR(bs []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Node) decodeXDR(xr *xdr.Reader) error {
|
func (o *Node) decodeXDR(xr *xdr.Reader) error {
|
||||||
o.ID = xr.ReadStringMax(64)
|
o.ID = xr.ReadBytesMax(32)
|
||||||
o.Flags = xr.ReadUint32()
|
o.Flags = xr.ReadUint32()
|
||||||
o.MaxVersion = xr.ReadUint64()
|
o.MaxVersion = xr.ReadUint64()
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
|
@ -14,29 +14,29 @@ type nativeModel struct {
|
|||||||
next Model
|
next Model
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m nativeModel) Index(nodeID string, repo string, files []FileInfo) {
|
func (m nativeModel) Index(nodeID NodeID, repo string, files []FileInfo) {
|
||||||
for i := range files {
|
for i := range files {
|
||||||
files[i].Name = norm.NFD.String(files[i].Name)
|
files[i].Name = norm.NFD.String(files[i].Name)
|
||||||
}
|
}
|
||||||
m.next.Index(nodeID, repo, files)
|
m.next.Index(nodeID, repo, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m nativeModel) IndexUpdate(nodeID string, repo string, files []FileInfo) {
|
func (m nativeModel) IndexUpdate(nodeID NodeID, repo string, files []FileInfo) {
|
||||||
for i := range files {
|
for i := range files {
|
||||||
files[i].Name = norm.NFD.String(files[i].Name)
|
files[i].Name = norm.NFD.String(files[i].Name)
|
||||||
}
|
}
|
||||||
m.next.IndexUpdate(nodeID, repo, files)
|
m.next.IndexUpdate(nodeID, repo, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m nativeModel) Request(nodeID, repo string, name string, offset int64, size int) ([]byte, error) {
|
func (m nativeModel) Request(nodeID NodeID, repo string, name string, offset int64, size int) ([]byte, error) {
|
||||||
name = norm.NFD.String(name)
|
name = norm.NFD.String(name)
|
||||||
return m.next.Request(nodeID, repo, name, offset, size)
|
return m.next.Request(nodeID, repo, name, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m nativeModel) ClusterConfig(nodeID string, config ClusterConfigMessage) {
|
func (m nativeModel) ClusterConfig(nodeID NodeID, config ClusterConfigMessage) {
|
||||||
m.next.ClusterConfig(nodeID, config)
|
m.next.ClusterConfig(nodeID, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m nativeModel) Close(nodeID string, err error) {
|
func (m nativeModel) Close(nodeID NodeID, err error) {
|
||||||
m.next.Close(nodeID, err)
|
m.next.Close(nodeID, err)
|
||||||
}
|
}
|
||||||
|
136
protocol/nodeid.go
Normal file
136
protocol/nodeid.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base32"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/syncthing/luhn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodeID [32]byte
|
||||||
|
|
||||||
|
// NewNodeID generates a new node ID from the raw bytes of a certificate
|
||||||
|
func NewNodeID(rawCert []byte) NodeID {
|
||||||
|
var n NodeID
|
||||||
|
hf := sha256.New()
|
||||||
|
hf.Write(rawCert)
|
||||||
|
hf.Sum(n[:0])
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func NodeIDFromString(s string) (NodeID, error) {
|
||||||
|
var n NodeID
|
||||||
|
err := n.UnmarshalText([]byte(s))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the canonical string representation of the node ID
|
||||||
|
func (n NodeID) String() string {
|
||||||
|
id := base32.StdEncoding.EncodeToString(n[:])
|
||||||
|
id = strings.Trim(id, "=")
|
||||||
|
id = luhnify(id)
|
||||||
|
id = chunkify(id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NodeID) GoString() string {
|
||||||
|
return n.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NodeID) Compare(other NodeID) int {
|
||||||
|
return bytes.Compare(n[:], other[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NodeID) Equals(other NodeID) bool {
|
||||||
|
return bytes.Compare(n[:], other[:]) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeID) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(n.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NodeID) UnmarshalText(bs []byte) error {
|
||||||
|
id := string(bs)
|
||||||
|
id = strings.Trim(id, "=")
|
||||||
|
id = strings.ToUpper(id)
|
||||||
|
id = untypeoify(id)
|
||||||
|
id = unchunkify(id)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch len(id) {
|
||||||
|
case 56:
|
||||||
|
// New style, with check digits
|
||||||
|
id, err = unluhnify(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 52:
|
||||||
|
// Old style, no check digits
|
||||||
|
dec, err := base32.StdEncoding.DecodeString(id + "====")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(n[:], dec)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("node ID invalid: incorrect length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func luhnify(s string) string {
|
||||||
|
if len(s) != 52 {
|
||||||
|
panic("unsupported string length")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := s[i*13 : (i+1)*13]
|
||||||
|
l := luhn.Base32Trimmed.Generate(p)
|
||||||
|
res = append(res, fmt.Sprintf("%s%c", p, l))
|
||||||
|
}
|
||||||
|
return res[0] + res[1] + res[2] + res[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func unluhnify(s string) (string, error) {
|
||||||
|
if len(s) != 56 {
|
||||||
|
return "", fmt.Errorf("unsupported string length %d", len(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := s[i*14 : (i+1)*14-1]
|
||||||
|
l := luhn.Base32Trimmed.Generate(p)
|
||||||
|
if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
|
||||||
|
log.Printf("%q; %q", g, s[i*14:(i+1)*14])
|
||||||
|
return "", errors.New("check digit incorrect")
|
||||||
|
}
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
return res[0] + res[1] + res[2] + res[3], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func chunkify(s string) string {
|
||||||
|
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
||||||
|
s = strings.Trim(s, "-")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func unchunkify(s string) string {
|
||||||
|
s = strings.Replace(s, "-", "", -1)
|
||||||
|
s = strings.Replace(s, " ", "", -1)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func untypeoify(s string) string {
|
||||||
|
s = strings.Replace(s, "0", "O", -1)
|
||||||
|
s = strings.Replace(s, "1", "I", -1)
|
||||||
|
s = strings.Replace(s, "8", "B", -1)
|
||||||
|
return s
|
||||||
|
}
|
74
protocol/nodeid_test.go
Normal file
74
protocol/nodeid_test.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var formatted = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"
|
||||||
|
var formatCases = []string{
|
||||||
|
"P56IOI-7MZJNU-2IQGDR-EYDM2M-GTMGL3-BXNPQ6-W5BTBB-Z4TJXZ-WICQ",
|
||||||
|
"P56IOI-7MZJNU2Y-IQGDR-EYDM2M-GTI-MGL3-BXNPQ6-W5BM-TBB-Z4TJXZ-WICQ2",
|
||||||
|
"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ",
|
||||||
|
"P56IOI7 MZJNU2Y IQGDREY DM2MGTI MGL3BXN PQ6W5BM TBBZ4TJ XZWICQ2",
|
||||||
|
"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ",
|
||||||
|
"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq",
|
||||||
|
"P56IOI7MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMTBBZ4TJXZWICQ2",
|
||||||
|
"P561017MZJNU2YIQGDREYDM2MGTIMGL3BXNPQ6W5BMT88Z4TJXZWICQ2",
|
||||||
|
"p56ioi7mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmtbbz4tjxzwicq2",
|
||||||
|
"p561017mzjnu2yiqgdreydm2mgtimgl3bxnpq6w5bmt88z4tjxzwicq2",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatNodeID(t *testing.T) {
|
||||||
|
for i, tc := range formatCases {
|
||||||
|
var id NodeID
|
||||||
|
err := id.UnmarshalText([]byte(tc))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d UnmarshalText(%q); %v", i, tc, err)
|
||||||
|
} else if f := id.String(); f != formatted {
|
||||||
|
t.Errorf("#%d FormatNodeID(%q)\n\t%q !=\n\t%q", i, tc, f, formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validateCases = []struct {
|
||||||
|
s string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"", false},
|
||||||
|
{"P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2", true},
|
||||||
|
{"P56IOI7-MZJNU2-IQGDREY-DM2MGT-MGL3BXN-PQ6W5B-TBBZ4TJ-XZWICQ", true},
|
||||||
|
{"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ", true},
|
||||||
|
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ", true},
|
||||||
|
{"P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQCCCC", false},
|
||||||
|
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicq", true},
|
||||||
|
{"p56ioi7mzjnu2iqgdreydm2mgtmgl3bxnpq6w5btbbz4tjxzwicqCCCC", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNodeID(t *testing.T) {
|
||||||
|
for _, tc := range validateCases {
|
||||||
|
var id NodeID
|
||||||
|
err := id.UnmarshalText([]byte(tc.s))
|
||||||
|
if (err == nil && !tc.ok) || (err != nil && tc.ok) {
|
||||||
|
t.Errorf("ValidateNodeID(%q); %v != %v", tc.s, err, tc.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshallingNodeID(t *testing.T) {
|
||||||
|
n0 := NodeID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 10, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
|
||||||
|
n1 := NodeID{}
|
||||||
|
n2 := NodeID{}
|
||||||
|
|
||||||
|
bs, _ := n0.MarshalText()
|
||||||
|
n1.UnmarshalText(bs)
|
||||||
|
bs, _ = n1.MarshalText()
|
||||||
|
n2.UnmarshalText(bs)
|
||||||
|
|
||||||
|
if n2.String() != n0.String() {
|
||||||
|
t.Errorf("String marshalling error; %q != %q", n2.String(), n0.String())
|
||||||
|
}
|
||||||
|
if !n2.Equals(n0) {
|
||||||
|
t.Error("Equals error")
|
||||||
|
}
|
||||||
|
if n2.Compare(n0) != 0 {
|
||||||
|
t.Error("Compare error")
|
||||||
|
}
|
||||||
|
}
|
@ -48,19 +48,19 @@ var (
|
|||||||
|
|
||||||
type Model interface {
|
type Model interface {
|
||||||
// An index was received from the peer node
|
// An index was received from the peer node
|
||||||
Index(nodeID string, repo string, files []FileInfo)
|
Index(nodeID NodeID, repo string, files []FileInfo)
|
||||||
// An index update was received from the peer node
|
// An index update was received from the peer node
|
||||||
IndexUpdate(nodeID string, repo string, files []FileInfo)
|
IndexUpdate(nodeID NodeID, repo string, files []FileInfo)
|
||||||
// A request was made by the peer node
|
// A request was made by the peer node
|
||||||
Request(nodeID string, repo string, name string, offset int64, size int) ([]byte, error)
|
Request(nodeID NodeID, repo string, name string, offset int64, size int) ([]byte, error)
|
||||||
// A cluster configuration message was received
|
// A cluster configuration message was received
|
||||||
ClusterConfig(nodeID string, config ClusterConfigMessage)
|
ClusterConfig(nodeID NodeID, config ClusterConfigMessage)
|
||||||
// The peer node closed the connection
|
// The peer node closed the connection
|
||||||
Close(nodeID string, err error)
|
Close(nodeID NodeID, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
ID() string
|
ID() NodeID
|
||||||
Index(repo string, files []FileInfo)
|
Index(repo string, files []FileInfo)
|
||||||
Request(repo string, name string, offset int64, size int) ([]byte, error)
|
Request(repo string, name string, offset int64, size int) ([]byte, error)
|
||||||
ClusterConfig(config ClusterConfigMessage)
|
ClusterConfig(config ClusterConfigMessage)
|
||||||
@ -68,7 +68,7 @@ type Connection interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type rawConnection struct {
|
type rawConnection struct {
|
||||||
id string
|
id NodeID
|
||||||
receiver Model
|
receiver Model
|
||||||
|
|
||||||
reader io.ReadCloser
|
reader io.ReadCloser
|
||||||
@ -102,7 +102,7 @@ const (
|
|||||||
pingIdleTime = 60 * time.Second
|
pingIdleTime = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver Model) Connection {
|
func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver Model) Connection {
|
||||||
cr := &countingReader{Reader: reader}
|
cr := &countingReader{Reader: reader}
|
||||||
cw := &countingWriter{Writer: writer}
|
cw := &countingWriter{Writer: writer}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver M
|
|||||||
return wireFormatConnection{&c}
|
return wireFormatConnection{&c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) ID() string {
|
func (c *rawConnection) ID() NodeID {
|
||||||
return c.id
|
return c.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ func (c *rawConnection) readerLoop() (err error) {
|
|||||||
|
|
||||||
type incomingIndex struct {
|
type incomingIndex struct {
|
||||||
update bool
|
update bool
|
||||||
id string
|
id NodeID
|
||||||
repo string
|
repo string
|
||||||
files []FileInfo
|
files []FileInfo
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,11 @@ import (
|
|||||||
"testing/quick"
|
"testing/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
c0ID = NewNodeID([]byte{1})
|
||||||
|
c1ID = NewNodeID([]byte{2})
|
||||||
|
)
|
||||||
|
|
||||||
func TestHeaderFunctions(t *testing.T) {
|
func TestHeaderFunctions(t *testing.T) {
|
||||||
f := func(ver, id, typ int) bool {
|
f := func(ver, id, typ int) bool {
|
||||||
ver = int(uint(ver) % 16)
|
ver = int(uint(ver) % 16)
|
||||||
@ -54,8 +59,8 @@ func TestPing(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection("c0", ar, bw, nil).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, nil).(wireFormatConnection).next.(*rawConnection)
|
||||||
c1 := NewConnection("c1", br, aw, nil).(wireFormatConnection).next.(*rawConnection)
|
c1 := NewConnection(c1ID, br, aw, nil).(wireFormatConnection).next.(*rawConnection)
|
||||||
|
|
||||||
if ok := c0.ping(); !ok {
|
if ok := c0.ping(); !ok {
|
||||||
t.Error("c0 ping failed")
|
t.Error("c0 ping failed")
|
||||||
@ -78,8 +83,8 @@ func TestPingErr(t *testing.T) {
|
|||||||
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||||
|
|
||||||
c0 := NewConnection("c0", ar, ebw, m0).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, ebw, m0).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection("c1", br, eaw, m1)
|
NewConnection(c1ID, br, eaw, m1)
|
||||||
|
|
||||||
res := c0.ping()
|
res := c0.ping()
|
||||||
if (i < 4 || j < 4) && res {
|
if (i < 4 || j < 4) && res {
|
||||||
@ -106,8 +111,8 @@ func TestPingErr(t *testing.T) {
|
|||||||
// eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
// eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||||
// ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
// ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||||
|
|
||||||
// NewConnection("c0", ar, ebw, m0, nil)
|
// NewConnection(c0ID, ar, ebw, m0, nil)
|
||||||
// c1 := NewConnection("c1", br, eaw, m1, nil).(wireFormatConnection).next.(*rawConnection)
|
// c1 := NewConnection(c1ID, br, eaw, m1, nil).(wireFormatConnection).next.(*rawConnection)
|
||||||
|
|
||||||
// d, err := c1.Request("default", "tn", 1234, 5678)
|
// d, err := c1.Request("default", "tn", 1234, 5678)
|
||||||
// if err == e || err == ErrClosed {
|
// if err == e || err == ErrClosed {
|
||||||
@ -154,8 +159,8 @@ func TestVersionErr(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection("c0", ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection("c1", br, aw, m1)
|
NewConnection(c1ID, br, aw, m1)
|
||||||
|
|
||||||
c0.xw.WriteUint32(encodeHeader(header{
|
c0.xw.WriteUint32(encodeHeader(header{
|
||||||
version: 2,
|
version: 2,
|
||||||
@ -176,8 +181,8 @@ func TestTypeErr(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection("c0", ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection("c1", br, aw, m1)
|
NewConnection(c1ID, br, aw, m1)
|
||||||
|
|
||||||
c0.xw.WriteUint32(encodeHeader(header{
|
c0.xw.WriteUint32(encodeHeader(header{
|
||||||
version: 0,
|
version: 0,
|
||||||
@ -198,8 +203,8 @@ func TestClose(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection("c0", ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection("c1", br, aw, m1)
|
NewConnection(c1ID, br, aw, m1)
|
||||||
|
|
||||||
c0.close(nil)
|
c0.close(nil)
|
||||||
|
|
||||||
|
@ -14,11 +14,11 @@ type wireFormatConnection struct {
|
|||||||
next Connection
|
next Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) ID() string {
|
func (c wireFormatConnection) ID() NodeID {
|
||||||
return c.next.ID()
|
return c.next.ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) Index(node string, fs []FileInfo) {
|
func (c wireFormatConnection) Index(repo string, fs []FileInfo) {
|
||||||
var myFs = make([]FileInfo, len(fs))
|
var myFs = make([]FileInfo, len(fs))
|
||||||
copy(myFs, fs)
|
copy(myFs, fs)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ func (c wireFormatConnection) Index(node string, fs []FileInfo) {
|
|||||||
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.next.Index(node, myFs)
|
c.next.Index(repo, myFs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) Request(repo, name string, offset int64, size int) ([]byte, error) {
|
func (c wireFormatConnection) Request(repo, name string, offset int64, size int) ([]byte, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user