Add Options message to protocol
This commit is contained in:
@@ -193,6 +193,33 @@ model, the Index Update merely amends it with new or updated file
|
||||
information. Any files not mentioned in an Index Update are left
|
||||
unchanged.
|
||||
|
||||
### Options (Type = 7)
|
||||
|
||||
This informational message provides information about the client
|
||||
configuration, version, etc. It is sent at connection initiation and,
|
||||
optionally, when any of the sent parameters have changed. The message is
|
||||
in the form of a list of (key, value) pairs, both of string type.
|
||||
|
||||
struct OptionsMessage {
|
||||
KeyValue Options<>;
|
||||
}
|
||||
|
||||
struct KeyValue {
|
||||
string Key;
|
||||
string Value;
|
||||
}
|
||||
|
||||
Key ID:s apart from the well known ones are implementation
|
||||
specific. An implementation is expected to ignore unknown keys. An
|
||||
implementation may impose limits on key and value size.
|
||||
|
||||
Well known keys:
|
||||
|
||||
- "clientId" -- The name of the implementation. Example: "syncthing".
|
||||
- "clientVersion" -- The version of the client. Example: "v1.0.33-47". The
|
||||
Following the SemVer 2.0 specification for version strings is
|
||||
encouraged but not enforced.
|
||||
|
||||
Example Exchange
|
||||
----------------
|
||||
|
||||
|
||||
@@ -65,6 +65,14 @@ func (w *marshalWriter) writeResponse(data []byte) {
|
||||
w.writeBytes(data)
|
||||
}
|
||||
|
||||
func (w *marshalWriter) writeOptions(opts map[string]string) {
|
||||
w.writeUint32(uint32(len(opts)))
|
||||
for k, v := range opts {
|
||||
w.writeString(k)
|
||||
w.writeString(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *marshalReader) readHeader() header {
|
||||
return decodeHeader(r.readUint32())
|
||||
}
|
||||
@@ -109,3 +117,14 @@ func (r *marshalReader) readRequest() request {
|
||||
func (r *marshalReader) readResponse() []byte {
|
||||
return r.readBytes()
|
||||
}
|
||||
|
||||
func (r *marshalReader) readOptions() map[string]string {
|
||||
n := r.readUint32()
|
||||
opts := make(map[string]string, n)
|
||||
for i := 0; i < int(n); i++ {
|
||||
k := r.readString()
|
||||
v := r.readString()
|
||||
opts[k] = v
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
@@ -117,3 +117,23 @@ func BenchmarkWriteRequest(b *testing.B) {
|
||||
wr.writeRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
opts := map[string]string{
|
||||
"foo": "bar",
|
||||
"someKey": "otherValue",
|
||||
"hello": "",
|
||||
"": "42",
|
||||
}
|
||||
|
||||
var buf = new(bytes.Buffer)
|
||||
var wr = marshalWriter{w: buf}
|
||||
wr.writeOptions(opts)
|
||||
|
||||
var rd = marshalReader{r: buf}
|
||||
var ropts = rd.readOptions()
|
||||
|
||||
if !reflect.DeepEqual(opts, ropts) {
|
||||
t.Error("Incorrect options marshal/demarshal")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -18,6 +19,7 @@ const (
|
||||
messageTypePing = 4
|
||||
messageTypePong = 5
|
||||
messageTypeIndexUpdate = 6
|
||||
messageTypeOptions = 7
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -52,16 +54,18 @@ type Model interface {
|
||||
type Connection struct {
|
||||
sync.RWMutex
|
||||
|
||||
id string
|
||||
receiver Model
|
||||
reader io.Reader
|
||||
mreader *marshalReader
|
||||
writer io.Writer
|
||||
mwriter *marshalWriter
|
||||
closed bool
|
||||
awaiting map[int]chan asyncResult
|
||||
nextId int
|
||||
indexSent map[string][2]int64
|
||||
id string
|
||||
receiver Model
|
||||
reader io.Reader
|
||||
mreader *marshalReader
|
||||
writer io.Writer
|
||||
mwriter *marshalWriter
|
||||
closed bool
|
||||
awaiting map[int]chan asyncResult
|
||||
nextId int
|
||||
indexSent map[string][2]int64
|
||||
options map[string]string
|
||||
optionsLock sync.Mutex
|
||||
|
||||
hasSentIndex bool
|
||||
hasRecvdIndex bool
|
||||
@@ -81,7 +85,7 @@ const (
|
||||
pingIdleTime = 5 * time.Minute
|
||||
)
|
||||
|
||||
func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver Model) *Connection {
|
||||
func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver Model, options map[string]string) *Connection {
|
||||
flrd := flate.NewReader(reader)
|
||||
flwr, err := flate.NewWriter(writer, flate.BestSpeed)
|
||||
if err != nil {
|
||||
@@ -101,6 +105,20 @@ func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver M
|
||||
go c.readerLoop()
|
||||
go c.pingerLoop()
|
||||
|
||||
if options != nil {
|
||||
go func() {
|
||||
c.Lock()
|
||||
c.mwriter.writeHeader(header{0, c.nextId, messageTypeOptions})
|
||||
c.mwriter.writeOptions(options)
|
||||
err := c.flush()
|
||||
if err != nil {
|
||||
log.Printf("Warning:", err)
|
||||
}
|
||||
c.nextId++
|
||||
c.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
@@ -328,6 +346,11 @@ loop:
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
case messageTypeOptions:
|
||||
c.optionsLock.Lock()
|
||||
c.options = c.mreader.readOptions()
|
||||
c.optionsLock.Unlock()
|
||||
|
||||
default:
|
||||
c.close(fmt.Errorf("Protocol error: %s: unknown message type %#x", c.ID, hdr.msgType))
|
||||
break loop
|
||||
@@ -396,3 +419,9 @@ func (c *Connection) Statistics() Statistics {
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func (c *Connection) Option(key string) string {
|
||||
c.optionsLock.Lock()
|
||||
defer c.optionsLock.Unlock()
|
||||
return c.options[key]
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ func TestPing(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection("c0", ar, bw, nil)
|
||||
c1 := NewConnection("c1", br, aw, nil)
|
||||
c0 := NewConnection("c0", ar, bw, nil, nil)
|
||||
c1 := NewConnection("c1", br, aw, nil, nil)
|
||||
|
||||
if ok := c0.ping(); !ok {
|
||||
t.Error("c0 ping failed")
|
||||
@@ -67,8 +67,8 @@ func TestPingErr(t *testing.T) {
|
||||
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||
|
||||
c0 := NewConnection("c0", ar, ebw, m0)
|
||||
NewConnection("c1", br, eaw, m1)
|
||||
c0 := NewConnection("c0", ar, ebw, m0, nil)
|
||||
NewConnection("c1", br, eaw, m1, nil)
|
||||
|
||||
res := c0.ping()
|
||||
if (i < 4 || j < 4) && res {
|
||||
@@ -94,8 +94,8 @@ func TestRequestResponseErr(t *testing.T) {
|
||||
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||
|
||||
NewConnection("c0", ar, ebw, m0)
|
||||
c1 := NewConnection("c1", br, eaw, m1)
|
||||
NewConnection("c0", ar, ebw, m0, nil)
|
||||
c1 := NewConnection("c1", br, eaw, m1, nil)
|
||||
|
||||
d, err := c1.Request("tn", 1234, 3456, []byte("hashbytes"))
|
||||
if err == e || err == ErrClosed {
|
||||
@@ -143,8 +143,8 @@ func TestVersionErr(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection("c0", ar, bw, m0)
|
||||
NewConnection("c1", br, aw, m1)
|
||||
c0 := NewConnection("c0", ar, bw, m0, nil)
|
||||
NewConnection("c1", br, aw, m1, nil)
|
||||
|
||||
c0.mwriter.writeHeader(header{
|
||||
version: 2,
|
||||
@@ -165,8 +165,8 @@ func TestTypeErr(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection("c0", ar, bw, m0)
|
||||
NewConnection("c1", br, aw, m1)
|
||||
c0 := NewConnection("c0", ar, bw, m0, nil)
|
||||
NewConnection("c1", br, aw, m1, nil)
|
||||
|
||||
c0.mwriter.writeHeader(header{
|
||||
version: 0,
|
||||
@@ -187,8 +187,8 @@ func TestClose(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection("c0", ar, bw, m0)
|
||||
NewConnection("c1", br, aw, m1)
|
||||
c0 := NewConnection("c0", ar, bw, m0, nil)
|
||||
NewConnection("c1", br, aw, m1, nil)
|
||||
|
||||
c0.close(nil)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user