From 5460c9e3c945a12803a2c1ee84512973be036cc6 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sat, 30 May 2020 22:23:53 -0600 Subject: [PATCH] Log clock offset, work on #14 --- book.go | 30 +++++++++++++++--------------- book_test.go | 8 ++++---- main.go | 5 +++++ message.go | 20 +++++++++++++++++++- message_test.go | 32 ++++++++++++++++++++++++-------- repeater_test.go | 4 ++-- static/vail.js | 11 +++++++++++ 7 files changed, 80 insertions(+), 30 deletions(-) diff --git a/book.go b/book.go index dfb587e..15c0823 100644 --- a/book.go +++ b/book.go @@ -7,18 +7,18 @@ import ( type Book struct { entries map[string]*Repeater - events chan bookEvent + events chan bookEvent } - func NewBook() Book { return Book{ entries: make(map[string]*Repeater), - events: make(chan bookEvent, 5), + events: make(chan bookEvent, 5), } } type bookEventType int + const ( joinEvent = bookEventType(iota) partEvent @@ -27,32 +27,32 @@ const ( type bookEvent struct { eventType bookEventType - name string - w io.Writer - p []byte + name string + w io.Writer + p []byte } func (b Book) Join(name string, w io.Writer) { b.events <- bookEvent{ eventType: joinEvent, - name: name, - w: w, + name: name, + w: w, } } func (b Book) Part(name string, w io.Writer) { b.events <- bookEvent{ eventType: partEvent, - name: name, - w: w, + name: name, + w: w, } } func (b Book) Send(name string, p []byte) { b.events <- bookEvent{ eventType: sendEvent, - name: name, - p: p, + name: name, + p: p, } } @@ -68,13 +68,13 @@ func (b Book) loop() { switch event.eventType { case joinEvent: - if ! ok { + if !ok { repeater = NewRepeater() b.entries[event.name] = repeater } repeater.Join(event.w) case partEvent: - if ! ok { + if !ok { log.Println("WARN: Parting an empty channel:", event.name) break } @@ -83,7 +83,7 @@ func (b Book) loop() { delete(b.entries, event.name) } case sendEvent: - if ! ok { + if !ok { log.Println("WARN: Sending to an empty channel:", event.name) break } diff --git a/book_test.go b/book_test.go index 20d7819..0eabb9f 100644 --- a/book_test.go +++ b/book_test.go @@ -14,14 +14,14 @@ func TestBook(t *testing.T) { if len(b.entries) != 1 { t.Error("Wrong number of entries") } - + // Send to an empty channel b.Send("merf", []byte("goober")) b.loop() if buf1.String() != "buf1" { t.Error("Sending to empty channel sent to non-empty channel") } - + // Send to a non-empty channel! b.Send("moo", []byte("goober")) b.loop() @@ -43,11 +43,11 @@ func TestBook(t *testing.T) { if buf2.String() != "buf2snerk" { t.Error("Send to 2-member channel busted", buf2) } - + // Part a client b.Part("moo", buf1) b.loop() - + b.Send("moo", []byte("peanut")) b.loop() if buf1.String() != "buf1goobersnerk" { diff --git a/main.go b/main.go index 77cada8..b602fa7 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "time" "os" "log" "net/http" @@ -17,6 +19,9 @@ func (c Client) Handle(ws *websocket.Conn) { ws.MaxPayloadBytes = 500 book.Join(c.repeaterName, ws) defer book.Part(c.repeaterName, ws) + + // Tell the client what time we think it is + fmt.Fprintf(ws, "[%d]", time.Now().UnixNano() / time.Millisecond.Nanoseconds()) for { buf := make([]byte, ws.MaxPayloadBytes) diff --git a/message.go b/message.go index d42defc..c9d80db 100644 --- a/message.go +++ b/message.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/binary" + "time" ) // VailMessage is a single Vail message. @@ -10,7 +11,7 @@ type Message struct { // Relative time in ms of this message. // These timestamps need to be consistent, but the offset can be anything. // ECMAScript `performance.now()` is ideal. - Timestamp uint64 + Timestamp int64 // Message timing in ms. // Timings alternate between tone and silence. @@ -18,6 +19,23 @@ type Message struct { Duration []uint8 } +func NewMessage(ts time.Time, durations []time.Duration) Message { + msg := Message{ + Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(), + Duration: make([]uint8, len(durations)), + } + for i, dns := range durations { + ms := dns.Milliseconds() + if (ms > 255) { + ms = 255 + } else if (ms < 0) { + ms = 0 + } + msg.Duration[i] = uint8(ms) + } + return msg +} + // Marshaling presumes something else is keeping track of lengths func (m Message) MarshalBinary() ([]byte, error) { var w bytes.Buffer diff --git a/message_test.go b/message_test.go index bc31454..94354c6 100644 --- a/message_test.go +++ b/message_test.go @@ -3,34 +3,50 @@ package main import ( "bytes" "testing" + "time" ) func TestMessage(t *testing.T) { - m := Message{0x1122334455667788, []uint8{0xaa, 0xbb, 0xcc}} + m := Message{0x1122334455, []uint8{0xaa, 0xbb, 0xcc}} m2 := Message{12, []uint8{1}} - - if ! m.Equal(m) { + + if !m.Equal(m) { t.Error("Equal messages did not compare equal") } if m.Equal(m2) { t.Error("Unequal messages compared equal") } - if m.Equal(Message{m.Timestamp, []uint8{1,2,3}}) { + if m.Equal(Message{m.Timestamp, []uint8{1, 2, 3}}) { t.Error("Messages with different payloads compared equal") } - + bm, err := m.MarshalBinary() if err != nil { t.Error(err) } - if ! bytes.Equal(bm, []byte("\x11\x22\x33\x44\x55\x66\x77\x88\xaa\xbb\xcc")) { + if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\xaa\xbb\xcc")) { t.Error("Encoded wrong:", bm) } - + if err := m2.UnmarshalBinary(bm); err != nil { t.Error(err) } - if ! m.Equal(m2) { + if !m.Equal(m2) { t.Error("Decoded wrong", m2) } + + m3 := NewMessage( + time.Unix( + 0, + m.Timestamp*time.Millisecond.Nanoseconds(), + ), + []time.Duration{ + time.Duration(m.Duration[0]) * time.Millisecond, + time.Duration(m.Duration[1]) * time.Millisecond, + time.Duration(m.Duration[2]) * time.Millisecond, + }, + ) + if !m.Equal(m3) { + t.Error("NewMessage didn't work", m, m3) + } } diff --git a/repeater_test.go b/repeater_test.go index 0f8b5be..c09d5f0 100644 --- a/repeater_test.go +++ b/repeater_test.go @@ -17,7 +17,7 @@ func TestRepeater(t *testing.T) { if buf1.String() != "buf1moo" { t.Error("Client 1 not repeating", buf1) } - + buf2 := bytes.NewBufferString("buf2") r.Join(buf2) r.Send([]byte("bar")) @@ -27,7 +27,7 @@ func TestRepeater(t *testing.T) { if buf2.String() != "buf2bar" { t.Error("Client 2 not repeating", buf2) } - + r.Part(buf1) r.Send([]byte("baz")) if buf1.String() != "buf1moobar" { diff --git a/static/vail.js b/static/vail.js index 705b958..cccb3fd 100644 --- a/static/vail.js +++ b/static/vail.js @@ -291,6 +291,7 @@ class Vail { this.sent = [] this.lagTimes = [0] this.rxDurations = [0] + this.clockOffset = null // How badly our clock is off of the server's this.rxDelay = 0 // Milliseconds to add to incoming timestamps this.beginTxTime = null // Time when we began transmitting @@ -477,6 +478,16 @@ class Vail { } let beginTxTime = msg[0] let durations = msg.slice(1) + + // Server is telling us the current time + if (durations.length == 0) { + let offset = now - beginTxTime + console.log("Our clock ahead of server by", offset, "ms") + if (this.clockOffset === null) { + this.clockOffset = offset + } + return + } let sent = this.sent.filter(e => e != jmsg) if (sent.length < this.sent.length) {