mirror of https://github.com/nealey/vail.git
Code, with tests, for messages and repeaters
This commit is contained in:
parent
6939f8de6e
commit
f70c2aaad1
|
@ -0,0 +1,70 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VailMessage is a single Vail message.
|
||||||
|
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
|
||||||
|
|
||||||
|
// Message timing in ms.
|
||||||
|
// Timings alternate between tone and silence.
|
||||||
|
// For example, `A` could be sent as [80, 80, 240]
|
||||||
|
Duration []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Message) MarshalBinary() ([]byte, error) {
|
||||||
|
var w bytes.Buffer
|
||||||
|
if err := binary.Write(&w, binary.BigEndian, m.Timestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dlen := uint16(len(m.Duration))
|
||||||
|
if err := binary.Write(&w, binary.BigEndian, dlen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&w, binary.BigEndian, m.Duration); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return w.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) UnmarshalBinary(data []byte) error {
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("timestamp %x", m.Timestamp)
|
||||||
|
var dlen uint16
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &dlen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Duration = make([]uint8, dlen)
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Message) Equal(m2 Message) bool {
|
||||||
|
if m.Timestamp != m2.Timestamp {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Duration) != len(m2.Duration) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range m.Duration {
|
||||||
|
if m.Duration[i] != m2.Duration[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
m := Message{0x1122334455667788, []uint8{0xaa, 0xbb, 0xcc}}
|
||||||
|
m2 := Message{12, []uint8{1}}
|
||||||
|
|
||||||
|
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}}) {
|
||||||
|
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\x00\x03\xaa\xbb\xcc")) {
|
||||||
|
t.Error("Encoded wrong:", bm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m2.UnmarshalBinary(bm); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if ! m.Equal(m2) {
|
||||||
|
t.Error("Decoded wrong", m2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repeater struct {
|
||||||
|
Joins chan io.Writer
|
||||||
|
Parts chan io.Writer
|
||||||
|
Sends chan []byte
|
||||||
|
subscribers []io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepeater() *Repeater {
|
||||||
|
return &Repeater{
|
||||||
|
Joins: make(chan io.Writer, 5),
|
||||||
|
Parts: make(chan io.Writer, 5),
|
||||||
|
Sends: make(chan []byte, 5),
|
||||||
|
subscribers: make([]io.Writer, 0, 20),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repeater) Run() {
|
||||||
|
for {
|
||||||
|
r.loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repeater) loop() {
|
||||||
|
select {
|
||||||
|
case w := <- r.Joins:
|
||||||
|
// Add subscriber
|
||||||
|
r.subscribers = append(r.subscribers, w)
|
||||||
|
case w := <- r.Parts:
|
||||||
|
// Remove subscriber
|
||||||
|
for i, s := range r.subscribers {
|
||||||
|
if s == w {
|
||||||
|
nsubs := len(r.subscribers)
|
||||||
|
r.subscribers[i] = r.subscribers[nsubs-1]
|
||||||
|
r.subscribers = r.subscribers[:nsubs-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case p := <- r.Sends:
|
||||||
|
for _, s := range r.subscribers {
|
||||||
|
s.Write(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepeater(t *testing.T) {
|
||||||
|
r := NewRepeater()
|
||||||
|
|
||||||
|
buf1 := bytes.NewBufferString("buf1")
|
||||||
|
r.Joins <- buf1
|
||||||
|
r.loop()
|
||||||
|
if len(r.subscribers) != 1 {
|
||||||
|
t.Error("Joining did nothing")
|
||||||
|
}
|
||||||
|
r.Sends <- []byte("moo")
|
||||||
|
r.loop()
|
||||||
|
if buf1.String() != "buf1moo" {
|
||||||
|
t.Error("Client 1 not repeating", buf1)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf2 := bytes.NewBufferString("buf2")
|
||||||
|
r.Joins <- buf2
|
||||||
|
r.loop()
|
||||||
|
r.Sends <- []byte("bar")
|
||||||
|
r.loop()
|
||||||
|
if buf1.String() != "buf1moobar" {
|
||||||
|
t.Error("Client 1 not repeating", buf1)
|
||||||
|
}
|
||||||
|
if buf2.String() != "buf2bar" {
|
||||||
|
t.Error("Client 2 not repeating", buf2)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Parts <- buf1
|
||||||
|
r.loop()
|
||||||
|
r.Sends <- []byte("baz")
|
||||||
|
r.loop()
|
||||||
|
if buf1.String() != "buf1moobar" {
|
||||||
|
t.Error("Client 1 still getting data after part", buf1)
|
||||||
|
}
|
||||||
|
if buf2.String() != "buf2barbaz" {
|
||||||
|
t.Error("Client 2 not getting data after part", buf2)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue