mirror of https://github.com/nealey/vail.git
It works again
This commit is contained in:
parent
b45876bcf0
commit
314994adcd
|
@ -20,7 +20,7 @@ func TestBook(t *testing.T) {
|
||||||
c1.Expect(1)
|
c1.Expect(1)
|
||||||
|
|
||||||
// Send to an empty channel
|
// Send to an empty channel
|
||||||
m := Message{0, 0, []uint8{22, 33}}
|
m := Message{0, 0, []uint16{22, 33}}
|
||||||
b.Send("merf", m)
|
b.Send("merf", m)
|
||||||
b.loop()
|
b.loop()
|
||||||
if c1.Len() > 0 {
|
if c1.Len() > 0 {
|
||||||
|
|
124
cmd/vail/main.go
124
cmd/vail/main.go
|
@ -1,11 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
|
@ -13,6 +13,9 @@ import (
|
||||||
|
|
||||||
var book Book
|
var book Book
|
||||||
|
|
||||||
|
const JsonProtocol = "json.vail.woozle.org"
|
||||||
|
const BinaryProtocol = "binary.vail.woozle.org"
|
||||||
|
|
||||||
// Clock defines an interface for getting the current time.
|
// Clock defines an interface for getting the current time.
|
||||||
//
|
//
|
||||||
// We use this in testing to provide a fixed value for the current time, so we
|
// We use this in testing to provide a fixed value for the current time, so we
|
||||||
|
@ -36,51 +39,82 @@ type VailWebSocketConnection struct {
|
||||||
|
|
||||||
func (c *VailWebSocketConnection) Receive() (Message, error) {
|
func (c *VailWebSocketConnection) Receive() (Message, error) {
|
||||||
var m Message
|
var m Message
|
||||||
var err error
|
messageType, buf, err := c.Read(context.Background())
|
||||||
if c.usingJSON {
|
if err != nil {
|
||||||
err = websocket.JSON.Receive(c.Conn, &m)
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if messageType == websocket.MessageText {
|
||||||
|
err = json.Unmarshal(buf, &m)
|
||||||
} else {
|
} else {
|
||||||
buf := make([]byte, 64)
|
err = m.UnmarshalBinary(buf)
|
||||||
if err := websocket.Message.Receive(c.Conn, &buf); err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
if err := m.UnmarshalBinary(buf)
|
|
||||||
}
|
}
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VailWebSocketConnection) Send(m Message) error {
|
func (c *VailWebSocketConnection) Send(m Message) error {
|
||||||
|
var err error
|
||||||
|
var buf []byte
|
||||||
|
var messageType websocket.MessageType
|
||||||
|
|
||||||
|
log.Println("Send", m)
|
||||||
if c.usingJSON {
|
if c.usingJSON {
|
||||||
return websocket.JSON.Send(c.Conn, m)
|
messageType = websocket.MessageText
|
||||||
|
buf, err = json.Marshal(m)
|
||||||
} else {
|
} else {
|
||||||
return websocket.Message.Send(c.Conn, m)
|
messageType = websocket.MessageBinary
|
||||||
|
buf, err = m.MarshalBinary()
|
||||||
}
|
}
|
||||||
}
|
log.Println(buf, err)
|
||||||
|
if err != nil {
|
||||||
func (c *VailWebSocketConnection) Error(err error) {
|
return err
|
||||||
msg := fmt.Sprintf("Error: %#v", err)
|
|
||||||
websocket.JSON.Send(c.Conn, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
repeaterName string
|
|
||||||
usingJSON bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Client) Handle(ws *websocket.Conn) {
|
|
||||||
sock := &VailWebSocketConnection{
|
|
||||||
Conn: ws,
|
|
||||||
usingJSON: c.usingJSON,
|
|
||||||
}
|
}
|
||||||
nowMilli := time.Now().UnixMilli()
|
|
||||||
ws.MaxPayloadBytes = 50
|
log.Println("Sending")
|
||||||
book.Join(c.repeaterName, sock)
|
return c.Write(context.Background(), messageType, buf)
|
||||||
defer book.Part(c.repeaterName, sock)
|
}
|
||||||
|
|
||||||
|
func ChatHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Set up websocket
|
||||||
|
ws, err := websocket.Accept(
|
||||||
|
w, r,
|
||||||
|
&websocket.AcceptOptions{
|
||||||
|
Subprotocols: []string{JsonProtocol, BinaryProtocol},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ws.Close(websocket.StatusInternalError, "Internal error")
|
||||||
|
|
||||||
|
// Create our Vail websocket connection for books to send to
|
||||||
|
sock := VailWebSocketConnection{
|
||||||
|
Conn: ws,
|
||||||
|
}
|
||||||
|
|
||||||
|
// websockets apparently sends a subprotocol string, so we can ignore Accept headers!
|
||||||
|
switch ws.Subprotocol() {
|
||||||
|
case JsonProtocol:
|
||||||
|
sock.usingJSON = true
|
||||||
|
case BinaryProtocol:
|
||||||
|
sock.usingJSON = false
|
||||||
|
default:
|
||||||
|
ws.Close(websocket.StatusPolicyViolation, "client must speak a vail protocol")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the repeater
|
||||||
|
repeaterName := r.FormValue("repeater")
|
||||||
|
book.Join(repeaterName, &sock)
|
||||||
|
defer book.Part(repeaterName, &sock)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
// Read a packet
|
||||||
m, err := sock.Receive()
|
m, err := sock.Receive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sock.Error(err)
|
log.Println(err)
|
||||||
|
ws.Close(websocket.StatusInvalidFramePayloadData, err.Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,34 +124,20 @@ func (c Client) Handle(ws *websocket.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's wildly out of time, reject it
|
// If it's wildly out of time, reject it
|
||||||
timeDelta := (nowMilli - m.Timestamp)
|
timeDelta := (time.Now().UnixMilli() - m.Timestamp)
|
||||||
if timeDelta < 0 {
|
if timeDelta < 0 {
|
||||||
timeDelta = -timeDelta
|
timeDelta = -timeDelta
|
||||||
}
|
}
|
||||||
if timeDelta > 9999 {
|
if timeDelta > 9999 {
|
||||||
fmt.Fprintln(ws, "Bad timestamp")
|
log.Println(err)
|
||||||
ws.Close()
|
ws.Close(websocket.StatusInvalidFramePayloadData, "Your clock is off by too much")
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
book.Send(c.repeaterName, m)
|
book.Send(repeaterName, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChatHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c := Client{
|
|
||||||
repeaterName: r.FormValue("repeater"),
|
|
||||||
}
|
|
||||||
accept := r.Header.Get("Accept")
|
|
||||||
if strings.Contains(accept, "json") {
|
|
||||||
c.usingJSON = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// This API is confusing as hell.
|
|
||||||
// I suspect there's a better way to do this.
|
|
||||||
websocket.Handler(c.Handle).ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
book = NewBook()
|
book = NewBook()
|
||||||
http.Handle("/chat", http.HandlerFunc(ChatHandler))
|
http.Handle("/chat", http.HandlerFunc(ChatHandler))
|
||||||
|
|
|
@ -34,13 +34,13 @@ type Message struct {
|
||||||
// Message timing in ms.
|
// Message timing in ms.
|
||||||
// Timings alternate between tone and silence.
|
// Timings alternate between tone and silence.
|
||||||
// For example, `A` could be sent as [80, 80, 240]
|
// For example, `A` could be sent as [80, 80, 240]
|
||||||
Duration []uint8
|
Duration []uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
||||||
msg := Message{
|
msg := Message{
|
||||||
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
|
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
|
||||||
Duration: make([]uint8, len(durations)),
|
Duration: make([]uint16, len(durations)),
|
||||||
}
|
}
|
||||||
for i, dns := range durations {
|
for i, dns := range durations {
|
||||||
ms := dns.Milliseconds()
|
ms := dns.Milliseconds()
|
||||||
|
@ -49,7 +49,7 @@ func NewMessage(ts time.Time, durations ...time.Duration) Message {
|
||||||
} else if ms < 0 {
|
} else if ms < 0 {
|
||||||
ms = 0
|
ms = 0
|
||||||
}
|
}
|
||||||
msg.Duration[i] = uint8(ms)
|
msg.Duration[i] = uint16(ms)
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ func (m Message) MarshalBinary() ([]byte, error) {
|
||||||
return w.Bytes(), nil
|
return w.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshaling presumes something else is keeping track of lengths
|
// UnmarshalBinary unpacks a binary buffer into a Message.
|
||||||
func (m *Message) UnmarshalBinary(data []byte) error {
|
func (m *Message) UnmarshalBinary(data []byte) error {
|
||||||
r := bytes.NewReader(data)
|
r := bytes.NewReader(data)
|
||||||
if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil {
|
if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil {
|
||||||
|
@ -78,32 +78,14 @@ func (m *Message) UnmarshalBinary(data []byte) error {
|
||||||
if err := binary.Read(r, binary.BigEndian, &m.Clients); err != nil {
|
if err := binary.Read(r, binary.BigEndian, &m.Clients); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dlen := r.Len()
|
dlen := r.Len() / 2
|
||||||
m.Duration = make([]uint8, dlen)
|
m.Duration = make([]uint16, dlen)
|
||||||
if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil {
|
if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) MarshalJSON() ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprint(buf, "{")
|
|
||||||
fmt.Fprintf(buf, "\"Timestamp\":%d,", m.Timestamp)
|
|
||||||
fmt.Fprintf(buf, "\"Clients\":%d,", m.Clients)
|
|
||||||
fmt.Fprint(buf, "\"Duration\":[")
|
|
||||||
for i := 0; i < len(m.Duration); i++ {
|
|
||||||
fmt.Fprint(buf, m.Duration[i])
|
|
||||||
if i <= len(m.Duration)-1 {
|
|
||||||
fmt.Fprint(buf, ",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprint(buf)
|
|
||||||
fmt.Fprint(buf, "]")
|
|
||||||
fmt.Fprint(buf, "}")
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Message) Equal(m2 Message) bool {
|
func (m Message) Equal(m2 Message) bool {
|
||||||
if m.Timestamp != m2.Timestamp {
|
if m.Timestamp != m2.Timestamp {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessageStruct(t *testing.T) {
|
func TestMessageStruct(t *testing.T) {
|
||||||
m := Message{0x1122334455, 0, []uint8{0xaa, 0xbb, 0xcc}}
|
m := Message{0x1122334455, 0, []uint16{0xaa, 0xbb, 0xcc}}
|
||||||
m2 := Message{12, 0, []uint8{1}}
|
m2 := Message{12, 0, []uint16{1}}
|
||||||
|
|
||||||
if !m.Equal(m) {
|
if !m.Equal(m) {
|
||||||
t.Error("Equal messages did not compare equal")
|
t.Error("Equal messages did not compare equal")
|
||||||
|
@ -16,7 +16,7 @@ func TestMessageStruct(t *testing.T) {
|
||||||
if m.Equal(m2) {
|
if m.Equal(m2) {
|
||||||
t.Error("Unequal messages compared equal")
|
t.Error("Unequal messages compared equal")
|
||||||
}
|
}
|
||||||
if m.Equal(Message{m.Timestamp, 0, []uint8{1, 2, 3}}) {
|
if m.Equal(Message{m.Timestamp, 0, []uint16{1, 2, 3}}) {
|
||||||
t.Error("Messages with different payloads compared equal")
|
t.Error("Messages with different payloads compared equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func TestMessageStruct(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\xaa\xbb\xcc")) {
|
if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\x00\xaa\x00\xbb\x00\xcc")) {
|
||||||
t.Error("Encoded wrong:", bm)
|
t.Error("Encoded wrong:", bm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (tc *TestingClient) Len() int {
|
||||||
return len(tc.buf)
|
return len(tc.buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TestingClient) Expect(clients uint16, payload ...uint8) {
|
func (tc *TestingClient) Expect(clients uint16, payload ...uint16) {
|
||||||
m := Message{0, clients, payload}
|
m := Message{0, clients, payload}
|
||||||
tc.expected = append(tc.expected, m)
|
tc.expected = append(tc.expected, m)
|
||||||
if len(tc.buf) != len(tc.expected) {
|
if len(tc.buf) != len(tc.expected) {
|
||||||
|
|
|
@ -45,13 +45,13 @@ export class Vail {
|
||||||
}
|
}
|
||||||
console.info("Attempting to reconnect", this.wsUrl.href)
|
console.info("Attempting to reconnect", this.wsUrl.href)
|
||||||
this.clockOffset = 0
|
this.clockOffset = 0
|
||||||
this.socket = new WebSocket(this.wsUrl)
|
this.socket = new WebSocket(this.wsUrl, ["json.vail.woozle.org"])
|
||||||
this.socket.addEventListener("message", e => this.wsMessage(e))
|
this.socket.addEventListener("message", e => this.wsMessage(e))
|
||||||
this.socket.addEventListener(
|
this.socket.addEventListener(
|
||||||
"close",
|
"close",
|
||||||
() => {
|
msg => {
|
||||||
console.info("Repeater connection dropped.")
|
console.error("Repeater connection dropped:", msg.reason)
|
||||||
setTimeout(() => this.reopen(), 5*Second)
|
setTimeout(() => this.reopen(), 2*Second)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,7 @@ export class Vail {
|
||||||
clockOffset: this.clockOffset,
|
clockOffset: this.clockOffset,
|
||||||
clients: msg.Clients,
|
clients: msg.Clients,
|
||||||
}
|
}
|
||||||
|
console.log(msg)
|
||||||
if (typeof(msg) == "string") {
|
if (typeof(msg) == "string") {
|
||||||
console.error(msg)
|
console.error(msg)
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue