238 lines
8.0 KiB
Go
238 lines
8.0 KiB
Go
// Package protocol defines what is sent accross the TCP connection between the
|
|
// router and service. It provides primitives for serializing various integer
|
|
// types and data structures, as well as entire messages for the protocols it
|
|
// supports.
|
|
package protocol
|
|
|
|
import "io"
|
|
import "fmt"
|
|
import "sync"
|
|
|
|
// Message represents a message that can be sent along an io.Writer, and read
|
|
// back when it comes out the other end. All messages must prefix themselves
|
|
// with a Type code.
|
|
type Message interface {
|
|
// Send serializes the message to a writer, prefixed with its Type code.
|
|
Send (io.Writer) error
|
|
}
|
|
|
|
// Serializer represents anything who's *content* can be serialized to a byte
|
|
// stream. This does not include type information (such as a message Type code
|
|
// prefix).
|
|
type Serializer interface {
|
|
// Serialize serializes the object's content to a writer.
|
|
Serialize (io.Writer) error
|
|
}
|
|
|
|
// ID is a number that identifies an ongoing sequence. It is similar to a cookie
|
|
// in the X11 protocol. All messages that are a part of the same sequence have
|
|
// the same ID. ID zero is the initial ID which is generated by the service upon
|
|
// sending a MessageLogin to the router. The corresponding sequence must always
|
|
// be treated as active as long as the connection is open, and must never be
|
|
// reused for something else.
|
|
//
|
|
// If the leftmost bit of an ID is set, that means it was generated by
|
|
// the router. If the leftmost bit of an ID is unset, that means it was
|
|
// generated by the service. When generating IDs, the service will start at 1
|
|
// and progressively increment the ID number. If the next ID would have its
|
|
// leftmost bit set, it must loop back around to 1. Similarly, the router must
|
|
// start at the maximum unsigned 32 bit integer (0xFFFFFFFF) and decrement until
|
|
// the leftmost bit of the ID would be unset, and then loop back around to the
|
|
// initial value.
|
|
//
|
|
// If either router or service generate an ID that is currently
|
|
// in use by a sequence, that ID must be skipped over. For this reason, both
|
|
// router and service must maintain a list of ongoing sequences along with their
|
|
// active IDs. These lists (and corresponding generators) must be
|
|
// connection-specific.
|
|
//
|
|
// Sequences are implicitly started by a message of the first type in any
|
|
// particular protocol sector (see the documentation for Type in this package),
|
|
// and must be sent with a newly generated ID as described above. The sequence
|
|
// must consist only of messages that are part of the same protocol sector as
|
|
// the message that started it (i.e. you cannot send a MessageGeminiResponse in
|
|
// a sequence that was started by a MessageHTTPRequest). The only exception to
|
|
// this is MessageStatus, which can be sent in any context. If this rule is
|
|
// violated in any other capacity, the router or service should send a
|
|
// MessageStatus with StatusBadMessageType and close the connection.
|
|
//
|
|
// The termination of any given sequence is determined by the rules of the
|
|
// particular protocol sector it is a part of.
|
|
type ID uint32
|
|
|
|
const RouterIDStart ID = 0xFFFFFFFF
|
|
const ServiceIDStart ID = 0
|
|
|
|
// ReadID reads an ID from a Reader.
|
|
func ReadID (reader io.Reader) (id ID, err error) {
|
|
untyped, err := ReadU32(reader)
|
|
return ID(untyped), err
|
|
}
|
|
|
|
// Serialize writes an ID to a Writer.
|
|
func (id ID) Serialize (writer io.Writer) error {
|
|
return WriteU32(writer, uint32(id))
|
|
}
|
|
|
|
// IsRouter returns true if this ID was generated by a router.
|
|
func (id ID) IsRouter () bool {
|
|
return id >> 31 == 1
|
|
}
|
|
|
|
// IsService returns true if this ID was generated by a service.
|
|
func (id ID) IsService () bool {
|
|
return !id.IsRouter()
|
|
}
|
|
|
|
// IDFactory generates IDs and keeps track of which ones are active. Its methods
|
|
// can be called concurrently.
|
|
type IDFactory struct {
|
|
lock sync.Mutex
|
|
|
|
next ID
|
|
router bool
|
|
active map[ID] struct { }
|
|
}
|
|
|
|
// NewRouterIDFactory returns a new IDFactory that generates router IDs.
|
|
func NewRouterIDFactory () (factory *IDFactory) {
|
|
factory = new(IDFactory)
|
|
factory.active = make(map[ID] struct { })
|
|
factory.router = true
|
|
factory.next = RouterIDStart
|
|
return
|
|
}
|
|
|
|
// NewServiceIDFactory returns a new IDFactory that generates service IDs.
|
|
func NewServiceIDFactory () (factory *IDFactory) {
|
|
factory = new(IDFactory)
|
|
factory.active = make(map[ID] struct { })
|
|
factory.next = ServiceIDStart
|
|
return
|
|
}
|
|
|
|
// Next generates a new ID and returns it. The ID will be marked as active, and
|
|
// a call to Free() is required to mark it as inactive when its corresponding
|
|
// sequence has completed.
|
|
func (factory *IDFactory) Next () ID {
|
|
// FIXME: this is not the best way of doing it. it is unlikely that this
|
|
// will ever cause problems, but the possibility is there.
|
|
|
|
factory.lock.Lock()
|
|
defer factory.lock.Unlock()
|
|
|
|
id := factory.next
|
|
if factory.router {
|
|
next := id - 1
|
|
for factory.isActive(id) {
|
|
next --
|
|
if next.IsService() {
|
|
next = RouterIDStart
|
|
}
|
|
}
|
|
factory.next = next
|
|
} else {
|
|
next := id + 1
|
|
for factory.isActive(id) {
|
|
next ++
|
|
if next.IsRouter() {
|
|
next = ServiceIDStart
|
|
}
|
|
}
|
|
factory.next = next
|
|
}
|
|
return id
|
|
}
|
|
|
|
// Free marks an ID as inactive. Note that calling Free(0) is in violation of
|
|
// the protocol.
|
|
func (factory *IDFactory) Free (id ID) {
|
|
factory.lock.Lock()
|
|
defer factory.lock.Unlock()
|
|
|
|
delete(factory.active, id)
|
|
}
|
|
|
|
func (factory *IDFactory) isActive (id ID) bool {
|
|
_, active := factory.active[id]
|
|
return active
|
|
}
|
|
|
|
// Type is an 8-bit code that comes before each message, and determines which
|
|
// kind of message it is. The 256 type code values are divided into eight
|
|
// 32-code sectors, where each sector corresponds to a different protocol.
|
|
type Type uint8; const (
|
|
// Initial sector
|
|
TypeNone Type = iota + 0x00
|
|
TypeLogin
|
|
TypeStatus
|
|
|
|
// HTTP/HTTPS protocol sector
|
|
TypeHTTPRequest Type = iota + 0x20
|
|
TypeHTTPResponse
|
|
TypeHTTPBodyRequest
|
|
TypeHTTPBodySegment
|
|
TypeHTTPBodyEnd
|
|
|
|
// Gemini protocol sector TODO implement
|
|
TypeGeminiRequest Type = iota + 0x40
|
|
TypeGeminiResponse
|
|
TypeGeminiBodySegment
|
|
TypeGeminiBodyEnd
|
|
|
|
// Database access sector TODO implement
|
|
TypeDatabaseGet Type = iota + 0x40
|
|
TypeDatabaseSet
|
|
TypeDatabaseValue
|
|
)
|
|
|
|
// ReadType reads a Type code from a Reader.
|
|
func ReadType (reader io.Reader) (ty Type, err error) {
|
|
untyped, err := ReadU8(reader)
|
|
return Type(untyped), err
|
|
}
|
|
|
|
// Serialize writes a Type code to a Writer.
|
|
func (ty Type) Serialize (writer io.Writer) error {
|
|
return WriteU8(writer, uint8(ty))
|
|
}
|
|
|
|
// String returns the name of the type as a string.
|
|
func (ty Type) String () string {
|
|
switch ty {
|
|
case TypeLogin: return "TypeLogin"
|
|
case TypeStatus: return "TypeStatus"
|
|
case TypeHTTPRequest: return "TypeHTTPRequest"
|
|
case TypeHTTPResponse: return "TypeHTTPResponse"
|
|
case TypeHTTPBodyRequest: return "TypeHTTPBodyRequest"
|
|
case TypeHTTPBodySegment: return "TypeHTTPBodySegment"
|
|
case TypeHTTPBodyEnd: return "TypeHTTPBodyEnd"
|
|
case TypeGeminiRequest: return "TypeGeminiRequest"
|
|
case TypeGeminiResponse: return "TypeGeminiResponse"
|
|
case TypeGeminiBodySegment: return "TypeGeminiBodySegment"
|
|
case TypeGeminiBodyEnd: return "TypeGeminiBodyEnd"
|
|
}
|
|
return fmt.Sprintf("Type(%d)", ty)
|
|
}
|
|
|
|
// ReadMessage reads a Type code and its corresponding message from a Reader.
|
|
// The type of message can be determined using a type switch.
|
|
func ReadMessage (reader io.Reader) (Message, error) {
|
|
ty, err := ReadType(reader)
|
|
if err != nil { return nil, err }
|
|
switch ty {
|
|
case TypeLogin: return ReadMessageLogin(reader)
|
|
case TypeStatus: return ReadMessageStatus(reader)
|
|
case TypeHTTPRequest: return ReadMessageHTTPRequest(reader)
|
|
case TypeHTTPResponse: return ReadMessageHTTPResponse(reader)
|
|
case TypeHTTPBodyRequest: return ReadMessageHTTPBodyRequest(reader)
|
|
case TypeHTTPBodySegment: return ReadMessageHTTPBodySegment(reader)
|
|
case TypeHTTPBodyEnd: return ReadMessageHTTPBodyEnd(reader)
|
|
case TypeGeminiRequest: return ReadMessageGeminiRequest(reader)
|
|
case TypeGeminiResponse: return ReadMessageGeminiResponse(reader)
|
|
case TypeGeminiBodySegment: return ReadMessageGeminiBodySegment(reader)
|
|
case TypeGeminiBodyEnd: return ReadMessageGeminiBodyEnd(reader)
|
|
}
|
|
return nil, StatusBadMessageType
|
|
}
|