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