Code, with tests, for messages and repeaters

This commit is contained in:
Neale Pickett 2020-04-12 16:37:58 -06:00
parent 6939f8de6e
commit f70c2aaad1
4 changed files with 199 additions and 0 deletions

70
message.go Normal file
View File

@ -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
}

36
message_test.go Normal file
View File

@ -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)
}
}

48
repeater.go Normal file
View File

@ -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)
}
}
}

45
repeater_test.go Normal file
View File

@ -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)
}
}