It works again

This commit is contained in:
Neale Pickett 2022-06-06 09:54:55 -06:00
parent b45876bcf0
commit 314994adcd
6 changed files with 90 additions and 87 deletions

View File

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

View File

@ -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)
} else {
buf := make([]byte, 64)
if err := websocket.Message.Receive(c.Conn, &buf); err != nil {
return m, err return m, err
} }
if err := m.UnmarshalBinary(buf)
if messageType == websocket.MessageText {
err = json.Unmarshal(buf, &m)
} else {
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 {
return err
} }
func (c *VailWebSocketConnection) Error(err error) { log.Println("Sending")
msg := fmt.Sprintf("Error: %#v", err) return c.Write(context.Background(), messageType, buf)
websocket.JSON.Send(c.Conn, msg)
} }
type Client struct { func ChatHandler(w http.ResponseWriter, r *http.Request) {
repeaterName string // Set up websocket
usingJSON bool 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")
func (c Client) Handle(ws *websocket.Conn) { // Create our Vail websocket connection for books to send to
sock := &VailWebSocketConnection{ sock := VailWebSocketConnection{
Conn: ws, Conn: ws,
usingJSON: c.usingJSON,
} }
nowMilli := time.Now().UnixMilli()
ws.MaxPayloadBytes = 50 // websockets apparently sends a subprotocol string, so we can ignore Accept headers!
book.Join(c.repeaterName, sock) switch ws.Subprotocol() {
defer book.Part(c.repeaterName, sock) 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))

View File

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

View File

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

View File

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

View File

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