hnakra/protocol/protocol.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
}