Initial commit

This commit is contained in:
Sasha Koshka
2023-05-25 18:08:56 -04:00
commit c300567c0c
51 changed files with 42251 additions and 0 deletions

70
protocol/buf.go Normal file
View File

@@ -0,0 +1,70 @@
package protocol
import "io"
import "fmt"
// ReadBuf reads a size-prefixed buffer.
func ReadBuf (sizeBytes int, reader io.Reader) (buf []byte, err error) {
size, err := ReadInt(reader, sizeBytes)
if err != nil { return nil, err }
buf = make([]byte, size)
_, err = io.ReadFull(reader, buf)
return buf, err
}
// ReadBuf8 reads a size-prefixed buffer with an 8-bit size.
func ReadBuf8 (reader io.Reader) (buf []byte, err error) {
return ReadBuf(1, reader)
}
// ReadBuf16 reads a size-prefixed buffer with a 16-bit size.
func ReadBuf16 (reader io.Reader) (buf []byte, err error) {
return ReadBuf(2, reader)
}
// ReadString reads a size-prefixed string.
func ReadString (sizeBytes int, reader io.Reader) (str string, err error) {
buf, err := ReadBuf(sizeBytes, reader)
return string(buf), err
}
// ReadString8 reads a size-prefixed string with an 8-bit size.
func ReadString8 (reader io.Reader) (str string, err error) {
return ReadString(1, reader)
}
// ReadString16 reads a size-prefixed string with a 16-bit size.
func ReadString16 (reader io.Reader) (str string, err error) {
return ReadString(2, reader)
}
func WriteBuf (writer io.Writer, sizeBytes int, buf []byte) (err error) {
if uint(len(buf)) > MaxIntOfSize(sizeBytes) {
panic(fmt.Sprintf("len %d cannot fit in U%d", len(buf), sizeBytes * 8))
}
err = WriteInt(writer, sizeBytes, uint(len(buf)))
if err != nil { return }
_, err = writer.Write(buf)
return
}
func WriteBuf8 (writer io.Writer, buf []byte) error {
return WriteBuf(writer, 1, buf)
}
func WriteBuf16 (writer io.Writer, buf []byte) error {
return WriteBuf(writer, 2, buf)
}
func WriteString (writer io.Writer, sizeBytes int, str string) (err error) {
return WriteBuf(writer, sizeBytes, []byte(str))
}
func WriteString8 (writer io.Writer, str string) (err error) {
return WriteBuf8(writer, []byte(str))
}
func WriteString16 (writer io.Writer, str string) (err error) {
return WriteBuf16(writer, []byte(str))
}

144
protocol/buf_test.go Normal file
View File

@@ -0,0 +1,144 @@
package protocol
import "bytes"
import "testing"
func makeTestBuffer (length int) (buffer []byte) {
buffer = make([]byte, length)
for index := range buffer {
buffer[index] = byte(index)
}
return
}
func sliceEqual[T comparable] (left, right []T) bool {
if len(left) != len(right) { return false }
for index, item := range left {
if item != right[index] { return false }
}
return true
}
func TestBuf8 (test *testing.T) {
var conduit bytes.Buffer
input := makeTestBuffer(97)
test.Log("generated buffer")
err := WriteBuf8(&conduit, input)
if err != nil {
test.Fatal(err)
return
}
test.Log("wrote buffer")
output, err := ReadBuf8(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("read buffer")
if len(input) != len(output) {
test.Fatalf("len(input) %d != len(output) %d", len(input), len(output))
return
}
if !sliceEqual(input, output) {
test.Fatal("input != output")
return
}
}
func TestBuf16 (test *testing.T) {
var conduit bytes.Buffer
input := makeTestBuffer(1731)
test.Log("generated buffer")
err := WriteBuf16(&conduit, input)
if err != nil {
test.Fatal(err)
return
}
test.Log("wrote buffer")
output, err := ReadBuf16(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("read buffer")
if len(input) != len(output) {
test.Fatalf("len(input) %d != len(output) %d", len(input), len(output))
return
}
if !sliceEqual(input, output) {
test.Fatal("input != output")
return
}
}
func TestString8 (test *testing.T) {
var conduit bytes.Buffer
input := "The quick brown fox jumped over the lazy dog."
test.Log("generated string")
err := WriteString8(&conduit, input)
if err != nil {
test.Fatal(err)
return
}
test.Log("wrote buffer")
output, err := ReadString8(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("read buffer")
if len(input) != len(output) {
test.Fatalf("len(input) %d != len(output) %d", len(input), len(output))
return
}
if input != output {
test.Fatal("input != output")
return
}
}
func TestString16 (test *testing.T) {
var conduit bytes.Buffer
input := `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Blandit aliquam etiam erat velit scelerisque. Euismod elementum nisi quis eleifend. Lorem ipsum dolor sit amet. Pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus. Tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula. Lacus vel facilisis volutpat est velit egestas dui id. Mi quis hendrerit dolor magna. Sed ullamcorper morbi tincidunt ornare massa eget egestas purus viverra. Purus gravida quis blandit turpis cursus in hac. Enim nulla aliquet porttitor lacus luctus.
Cras adipiscing enim eu turpis egestas pretium aenean pharetra. Mauris vitae ultricies leo integer malesuada nunc vel risus. Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Amet dictum sit amet justo donec enim diam. Vulputate ut pharetra sit amet. Tellus integer feugiat scelerisque varius morbi enim. Pulvinar etiam non quam lacus suspendisse faucibus interdum posuere. Mauris in aliquam sem fringilla ut. Sagittis id consectetur purus ut faucibus pulvinar elementum integer enim. Fermentum odio eu feugiat pretium nibh. Arcu risus quis varius quam quisque.`
test.Log("generated buffer")
err := WriteString16(&conduit, input)
if err != nil {
test.Fatal(err)
return
}
test.Log("wrote buffer")
output, err := ReadString16(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("read buffer")
if len(input) != len(output) {
test.Fatalf("len(input) %d != len(output) %d", len(input), len(output))
return
}
if input != output {
test.Fatal("input != output")
return
}
}

1
protocol/database.go Normal file
View File

@@ -0,0 +1 @@
package protocol

112
protocol/gemini.go Normal file
View File

@@ -0,0 +1,112 @@
package protocol
import "io"
// MessageGeminiRequest represents a gemini request. It is sent from the router
// to the service.
type MessageGeminiRequest struct {
ID
Address string
URL string
}
func ReadMessageGeminiRequest (reader io.Reader) (message MessageGeminiRequest, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Address, err = ReadString8(reader)
if err != nil { return }
message.URL, err = ReadString16(reader)
return
}
func (message MessageGeminiRequest) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
err = WriteString8(writer, message.Address)
if err != nil { return }
return WriteString16(writer, message.URL)
}
func (message MessageGeminiRequest) Send (writer io.Writer) (err error) {
err = TypeGeminiRequest.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageGeminiResponse represents a gemini response. It is sent from the
// service to the router.
type MessageGeminiResponse struct {
ID
Status uint8
Meta string
}
func ReadMessageGeminiResponse (reader io.Reader) (message MessageGeminiResponse, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Status, err = ReadU8(reader)
if err != nil { return }
message.Meta, err = ReadString16(reader)
return
}
func (message MessageGeminiResponse) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
err = WriteU8(writer, message.Status)
if err != nil { return }
return WriteString16(writer, message.Meta)
}
func (message MessageGeminiResponse) Send (writer io.Writer) (err error) {
err = TypeGeminiResponse.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageGeminiBodySegment is a segment of a gemini response body. It is sent
// from the service to the router.
type MessageGeminiBodySegment struct {
ID
Data []byte
}
func ReadMessageGeminiBodySegment (reader io.Reader) (message MessageGeminiBodySegment, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Data, err = ReadBuf16(reader)
return
}
func (message MessageGeminiBodySegment) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
return WriteBuf16(writer, message.Data)
}
func (message MessageGeminiBodySegment) Send (writer io.Writer) (err error) {
err = TypeGeminiBodySegment.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageGeminiBodyEnd denotes the end of a gemini response body. It is sent
// from the service to the router. This must always be sent by the service to
// properly close the sequence, even if no response body segments were sent.
type MessageGeminiBodyEnd struct {
ID
}
func ReadMessageGeminiBodyEnd (reader io.Reader) (message MessageGeminiBodyEnd, err error) {
message.ID, err = ReadID(reader)
return
}
func (message MessageGeminiBodyEnd) Serialize (writer io.Writer) (err error) {
return message.ID.Serialize(writer)
}
func (message MessageGeminiBodyEnd) Send (writer io.Writer) (err error) {
err = TypeGeminiBodyEnd.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}

151
protocol/http.go Normal file
View File

@@ -0,0 +1,151 @@
package protocol
import "io"
// MessageHTTPRequest represents an HTTP or HTTPS request. It is sent from the
// router to the service.
type MessageHTTPRequest struct {
ID
Address string
Method string
URL string
Header Map
}
func ReadMessageHTTPRequest (reader io.Reader) (message MessageHTTPRequest, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Address, err = ReadString8(reader)
if err != nil { return }
message.Method, err = ReadString8(reader)
if err != nil { return }
message.URL, err = ReadString16(reader)
if err != nil { return }
message.Header, err = ReadMap(reader)
return
}
func (message MessageHTTPRequest) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
err = WriteString8(writer, message.Address)
if err != nil { return }
err = WriteString8(writer, message.Method)
if err != nil { return }
err = WriteString16(writer, message.URL)
if err != nil { return }
return message.Header.Serialize(writer)
}
func (message MessageHTTPRequest) Send (writer io.Writer) (err error) {
err = TypeHTTPRequest.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageHTTPResponse represents an HTTP or HTTPS response. It is sent from the
// service to the router.
type MessageHTTPResponse struct {
ID
Status uint16
Header Map
}
func ReadMessageHTTPResponse (reader io.Reader) (message MessageHTTPResponse, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Status, err = ReadU16(reader)
if err != nil { return }
message.Header, err = ReadMap(reader)
return
}
func (message MessageHTTPResponse) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
err = WriteU16(writer, message.Status)
if err != nil { return }
return message.Header.Serialize(writer)
}
func (message MessageHTTPResponse) Send (writer io.Writer) (err error) {
err = TypeHTTPResponse.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageHTTPBodyRequest is sent by the service to the router to ask it to
// begin transmitting the request body as a series of MessageHTTPBodySegment.
// The router will not give the service the request body unless the service
// sends it this message.
type MessageHTTPBodyRequest struct {
ID
}
func ReadMessageHTTPBodyRequest (reader io.Reader) (message MessageHTTPBodyRequest, err error) {
message.ID, err = ReadID(reader)
return
}
func (message MessageHTTPBodyRequest) Serialize (writer io.Writer) error {
return message.ID.Serialize(writer)
}
func (message MessageHTTPBodyRequest) Send (writer io.Writer) (err error) {
err = TypeHTTPBodyRequest.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageHTTPBodySegment is sent by both the router and the service. When the
// router sends this message, it is a segment of the request body. When the
// service sends this message, it is a segment of the response body.
type MessageHTTPBodySegment struct {
ID
Data []byte
}
func ReadMessageHTTPBodySegment (reader io.Reader) (message MessageHTTPBodySegment, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Data, err = ReadBuf16(reader)
return
}
func (message MessageHTTPBodySegment) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
return WriteBuf16(writer, message.Data)
}
func (message MessageHTTPBodySegment) Send (writer io.Writer) (err error) {
err = TypeHTTPBodySegment.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageHTTPBodySegment is sent by both the router and the service. When the
// router sends this message, it means that the HTTP request body has been sent
// in its entirety. The router will not send this message unless the service has
// sent a MessageHTTPBodyRequest first. When the service sends this message, it
// means that the HTTP resposne body has been sent in its entirety. The service
// must always sends this message when it fulfills a request, as it properly
// terminates the sequence.
type MessageHTTPBodyEnd struct {
ID
}
func ReadMessageHTTPBodyEnd (reader io.Reader) (message MessageHTTPBodyEnd, err error) {
message.ID, err = ReadID(reader)
return
}
func (message MessageHTTPBodyEnd) Serialize (writer io.Writer) error {
return message.ID.Serialize(writer)
}
func (message MessageHTTPBodyEnd) Send (writer io.Writer) (err error) {
err = TypeHTTPBodyEnd.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}

114
protocol/int.go Normal file
View File

@@ -0,0 +1,114 @@
package protocol
import "io"
import "fmt"
// ReadInt reads a little-endian integer from a reader and returns it as result.
// It will read the amount of bytes specified by bytes.
func ReadInt (reader io.Reader, bytes int) (result uint, err error) {
buf := [1]byte { }
shift := 0
for {
justRead := 0
for justRead == 0 && err == nil {
justRead, err = reader.Read(buf[:])
if err != nil { break }
}
result += uint(buf[0]) << shift
bytes --
shift += 8
if bytes == 0 {
break
}
}
return
}
func MaxIntOfSize (bytes int) (max uint) {
for {
max |= 0xFF
bytes --
if bytes > 0 {
max <<= 8
} else {
break
}
}
return
}
// ReadU8 reads an 8-bit integer.
func ReadU8 (reader io.Reader) (result uint8, err error) {
untypedResult, err := ReadInt(reader, 1)
return uint8(untypedResult), err
}
// ReadU16 reads a 16-bit integer.
func ReadU16 (reader io.Reader) (result uint16, err error) {
untypedResult, err := ReadInt(reader, 2)
return uint16(untypedResult), err
}
// ReadU24 reads a 24-bit integer.
func ReadU24 (reader io.Reader) (result uint32, err error) {
untypedResult, err := ReadInt(reader, 3)
return uint32(untypedResult), err
}
// ReadU32 reads a 32-bit integer.
func ReadU32 (reader io.Reader) (result uint32, err error) {
untypedResult, err := ReadInt(reader, 4)
return uint32(untypedResult), err
}
// WriteInt writes a little-endian integer to a writer. It will write the amount
// of bytes specified by bytes.
func WriteInt (writer io.Writer, bytes int, integer uint) (err error) {
if integer > MaxIntOfSize(bytes) {
panic(fmt.Sprintf("uint %d cannot fit in %d bytes", integer, bytes))
}
buf := [1]byte { }
for {
buf[0] = byte(integer & 0xFF)
justWrote := 0
for justWrote == 0 {
justWrote, err = writer.Write(buf[:])
if err != nil { return }
}
bytes --
if bytes > 0 {
integer >>= 8
} else {
break
}
}
return
}
// WriteU8 writes an 8-bit integer.
func WriteU8 (writer io.Writer, integer uint8) error {
return WriteInt(writer, 1, uint(integer))
}
// WriteU16 writes an 16-bit integer.
func WriteU16 (writer io.Writer, integer uint16) error {
return WriteInt(writer, 2, uint(integer))
}
// WriteU24 writes an 24-bit integer.
func WriteU24 (writer io.Writer, integer uint32) error {
return WriteInt(writer, 3, uint(integer))
}
// WriteU32 writes an 32-bit integer.
func WriteU32 (writer io.Writer, integer uint32) error {
return WriteInt(writer, 4, uint(integer))
}

59
protocol/int_test.go Normal file
View File

@@ -0,0 +1,59 @@
package protocol
import "bytes"
import "testing"
func TestU8 (test *testing.T) {
var conduit bytes.Buffer
numbers := []uint8 { 0, 1, 255, 100, 18, 16, 127, 99 }
for _, number := range numbers {
err := WriteU8(&conduit, number)
if err != nil { test.Fatal(err) }
}
for _, correct := range numbers {
number, err := ReadU8(&conduit)
if err != nil { test.Fatal(err) }
if number != correct {
test.Fatalf("expected %d, read back %d", correct, number)
}
}
}
func TestU16 (test *testing.T) {
var conduit bytes.Buffer
numbers := []uint16 { 0, 57, 900, 1000, 10000, 7881, 50023 }
for _, number := range numbers {
err := WriteU16(&conduit, number)
if err != nil { test.Fatal(err) }
}
for _, correct := range numbers {
number, err := ReadU16(&conduit)
if err != nil { test.Fatal(err) }
if number != correct {
test.Fatalf("expected %d, read back %d", correct, number)
}
}
}
func TestU32 (test *testing.T) {
var conduit bytes.Buffer
numbers := []uint32 { 0, 57, 901239, 1000, 3480238490, 10000, 7881, 50023 }
for _, number := range numbers {
err := WriteU32(&conduit, number)
if err != nil { test.Fatal(err) }
}
for _, correct := range numbers {
number, err := ReadU32(&conduit)
if err != nil { test.Fatal(err) }
if number != correct {
test.Fatalf("expected %d, read back %d", correct, number)
}
}
}

174
protocol/login.go Normal file
View File

@@ -0,0 +1,174 @@
package protocol
import "io"
import "fmt"
// Status is an 8-bit code that routers and services can send to each-other to
// inform the other about issues (or lack thereof) with the last recieved
// message in a given sequence. This is not to be confused with an HTTP status
// code.
type Status uint8; const (
StatusOk Status = iota
StatusBadMessageType
StatusMalformedMessage
StatusBadVersion
StatusBadCredentials
StatusBadMount
)
// ReadStatus reads a Status code from a Reader.
func ReadStatus (reader io.Reader) (status Status, err error) {
untyped, err := ReadU8(reader)
return Status(untyped), err
}
// Serialize writes a Status code to a Writer.
func (status Status) Serialize (writer io.Writer) error {
return WriteU8(writer, uint8(status))
}
// String returns a human-readable representation of a status code.
func (status Status) String () string {
switch status {
case StatusOk: return "ok"
case StatusBadMessageType: return "invalid message type"
case StatusMalformedMessage: return "malformed message"
case StatusBadVersion: return "unsupported protocol version"
case StatusBadCredentials: return "invalid credentials"
case StatusBadMount: return "invalid mount"
}
return fmt.Sprintf("Status(%d)", status)
}
// Error is equivalent to String. This lets a status be returned as an error.
func (status Status) Error () string {
return status.String()
}
// Version represents a Hnakra protocol version.
type Version struct {
Major uint8
Minor uint8
}
// ReadVersion reads version information from a Reader.
func ReadVersion (reader io.Reader) (version Version, err error) {
version.Major, err = ReadU8(reader)
if err != nil { return }
version.Minor, err = ReadU8(reader)
return
}
// Serialize writes version information to a Writer.
func (version Version) Serialize (writer io.Writer) error {
err := WriteU8(writer, uint8(version.Major))
if err != nil { return err }
return WriteU8(writer, uint8(version.Minor))
}
// String returns a human-readable representation of a version.
func (version Version) String () string {
return fmt.Sprintf("%d.%d", version.Major, version.Minor)
}
// MessageLogin is a message sent to a router by a service requesting to log in
// and mount on a path. The service should not send anything before this
// message. The router must reply with a MessageStatus containing StatusOk if
// the login and mount suceeded. or some other code if it did not. While waiting
// for this message to be sent, the service should not send anything.
type MessageLogin struct {
ID
Version
User string
Key []byte
Name string
Description string
Scheme string
Host string
Path string
}
// ReadMessageLogin reads a login message from a Reader, starting after the type
// code.
func ReadMessageLogin (reader io.Reader) (message MessageLogin, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Version, err = ReadVersion(reader)
if err != nil { return }
message.User, err = ReadString8(reader)
if err != nil { return }
message.Key, err = ReadBuf8(reader)
if err != nil { return }
message.Name, err = ReadString8(reader)
if err != nil { return }
message.Description, err = ReadString8(reader)
if err != nil { return }
message.Scheme, err = ReadString8(reader)
if err != nil { return }
message.Host, err = ReadString16(reader)
if err != nil { return }
message.Path, err = ReadString16(reader)
return
}
// Serialize writes the login message to a Writer, starting after the type code.
func (message MessageLogin) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
err = message.Version.Serialize(writer)
if err != nil { return }
err = WriteString8(writer, message.User)
if err != nil { return }
err = WriteBuf8(writer, message.Key)
if err != nil { return }
err = WriteString8(writer, message.Name)
if err != nil { return }
err = WriteString8(writer, message.Description)
if err != nil { return }
err = WriteString8(writer, message.Scheme)
if err != nil { return }
err = WriteString16(writer, message.Host)
if err != nil { return }
err = WriteString16(writer, message.Path)
return
}
// Send writes the type code and the message to a Writer.
func (message MessageLogin) Send (writer io.Writer) (err error) {
err = TypeLogin.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}
// MessageStatus is a message that carries a Status code. If sent, it must be in
// reply to the last recieved message with an equivalent ID.
type MessageStatus struct {
ID
Status
}
// ReadMessageStatus reads a status message from a Reader, starting after the
// type code.
func ReadMessageStatus (reader io.Reader) (message MessageStatus, err error) {
message.ID, err = ReadID(reader)
if err != nil { return }
message.Status, err = ReadStatus(reader)
return
}
// Serialize writes the status message to a Writer, starting after the type
// code.
func (message MessageStatus) Serialize (writer io.Writer) (err error) {
err = message.ID.Serialize(writer)
if err != nil { return }
return message.Status.Serialize(writer)
}
// Send writes the type code and the message to a Writer.
func (message MessageStatus) Send (writer io.Writer) (err error) {
err = TypeStatus.Serialize(writer)
if err != nil { return }
return message.Serialize(writer)
}

58
protocol/map.go Normal file
View File

@@ -0,0 +1,58 @@
package protocol
import "io"
import "fmt"
// Map is a string-to-string map with non-unique keys. Each key can be
// associated with multiple values. This is typically used for things like HTTP
// headers.
type Map map[string] []string
// ReadMap reads a map with a 16-bit length containing strings which have a
// 16-bit size. The length of the map corresponds to how many key/value pairs
// it has, and not how long the map is.
func ReadMap (reader io.Reader) (m Map, err error) {
length, err := ReadU16(reader)
if err != nil { return nil, err }
m = make(Map)
for length > 0 {
key, err := ReadString16(reader)
if err != nil { return m, err }
value, err := ReadString16(reader)
if err != nil { return m, err }
m.Add(key, value)
length --
}
return m, err
}
func (m Map) Serialize (writer io.Writer) (err error) {
if uint(len(m)) > MaxIntOfSize(2) {
panic(fmt.Sprintf("len(%d) cannot fit in U16", len(m)))
}
length := 0
for _, item := range m {
length += len(item)
}
err = WriteU16(writer, uint16(length))
if err != nil { return }
for key, values := range m {
for _, value := range values {
err = WriteString16(writer, key)
if err != nil { return }
err = WriteString16(writer, value)
if err != nil { return }
}}
return
}
// Add adds a value to the map under the specified key.
func (m Map) Add (key, value string) {
m[key] = append(m[key], value)
}

51
protocol/map_test.go Normal file
View File

@@ -0,0 +1,51 @@
package protocol
import "bytes"
import "testing"
func mapEqual (left, right Map) bool {
if len(left) != len(right) { return false }
for key, value := range left {
if !sliceEqual(value, right[key]) { return false }
}
return true
}
func TestMap (test *testing.T) {
var conduit bytes.Buffer
input := Map {
"thing1": []string { "asdkjas" },
"thing2": []string { "ooo" },
"multivalue": []string { "value 1", "value 2", "value 3" },
"keysmash": []string { "dsijfhlsdkjfhlkjsahdflkjahsdlfjhsadlkfhaslkdjaslkdjlkashd" },
"Lorem Ipsum\n": []string {`Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Blandit aliquam etiam erat velit scelerisque. Euismod elementum nisi quis eleifend. Lorem ipsum dolor sit amet. Pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus. Tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula. Lacus vel facilisis volutpat est velit egestas dui id. Mi quis hendrerit dolor magna. Sed ullamcorper morbi tincidunt ornare massa eget egestas purus viverra. Purus gravida quis blandit turpis cursus in hac. Enim nulla aliquet porttitor lacus luctus.
Cras adipiscing enim eu turpis egestas pretium aenean pharetra. Mauris vitae ultricies leo integer malesuada nunc vel risus. Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Amet dictum sit amet justo donec enim diam. Vulputate ut pharetra sit amet. Tellus integer feugiat scelerisque varius morbi enim. Pulvinar etiam non quam lacus suspendisse faucibus interdum posuere. Mauris in aliquam sem fringilla ut. Sagittis id consectetur purus ut faucibus pulvinar elementum integer enim. Fermentum odio eu feugiat pretium nibh. Arcu risus quis varius quam quisque.` },
}
err := input.Serialize(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("wrote map")
output, err := ReadMap(&conduit)
if err != nil {
test.Fatal(err)
return
}
test.Log("read map")
if len(input) != len(output) {
test.Fatalf("len(input) %d != len(output) %d", len(input), len(output))
return
}
if !mapEqual(input, output) {
test.Fatal("input != output")
return
}
}

View File

@@ -0,0 +1,70 @@
package protocol
import "bytes"
import "testing"
func TestMessageLogin (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageLogin {
ID: 0,
Version: Version { 1, 3 },
User: "SomeUser",
Key: []byte { 100, 2, 49 },
Name: "SomeName",
Description: "SomeDescription",
Scheme: "SomeScheme",
Host: "SomeHost",
Path: "SomePath",
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeLogin { test.Fatal("expected", TypeLogin, "got", ty) }
got, err := ReadMessageLogin(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Version == got.Version &&
message.User == got.User &&
sliceEqual(message.Key, got.Key) &&
message.Name == got.Name &&
message.Description == got.Description &&
message.Scheme == got.Scheme &&
message.Host == got.Host &&
message.Path == got.Path
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageStatus (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageStatus {
ID: 0,
Status: StatusBadMount,
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeStatus { test.Fatal("expected", TypeStatus, "got", ty) }
got, err := ReadMessageStatus(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Status == got.Status
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}

View File

@@ -0,0 +1,109 @@
package protocol
import "bytes"
import "testing"
func TestMessageGeminiBodyEnd (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageGeminiBodyEnd {
ID: 493234,
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeGeminiBodyEnd { test.Fatal("expected", TypeGeminiBodyEnd, "got", ty) }
got, err := ReadMessageGeminiBodyEnd(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal := message.ID == got.ID
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageGeminiBodySegment (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageGeminiBodySegment {
ID: 22441,
Data: makeTestBuffer(10301),
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeGeminiBodySegment { test.Fatal("expected", TypeGeminiBodySegment, "got", ty) }
got, err := ReadMessageGeminiBodySegment(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
sliceEqual(message.Data, got.Data)
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageGeminiRequest (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageGeminiRequest {
ID: 9994562,
Address: "some.thing",
URL: "gemini://example.com/hahaha",
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeGeminiRequest { test.Fatal("expected", TypeGeminiRequest, "got", ty) }
got, err := ReadMessageGeminiRequest(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Address == got.Address &&
message.URL == got.URL
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageGeminiResponse (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageGeminiResponse {
ID: 77777,
Status: 20,
Meta: "text/gemini",
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeGeminiResponse { test.Fatal("expected", TypeGeminiResponse, "got", ty) }
got, err := ReadMessageGeminiResponse(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Status == got.Status &&
message.Meta == got.Meta
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}

View File

@@ -0,0 +1,142 @@
package protocol
import "bytes"
import "testing"
func TestMessageHTTPBodyEnd (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageHTTPBodyEnd {
ID: 493234,
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeHTTPBodyEnd { test.Fatal("expected", TypeHTTPBodyEnd, "got", ty) }
got, err := ReadMessageHTTPBodyEnd(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal := message.ID == got.ID
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageHTTPBodyRequest (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageHTTPBodyRequest {
ID: 493234,
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeHTTPBodyRequest { test.Fatal("expected", TypeHTTPBodyRequest, "got", ty) }
got, err := ReadMessageHTTPBodyRequest(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal := message.ID == got.ID
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageHTTPBodySegment (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageHTTPBodySegment {
ID: 22441,
Data: makeTestBuffer(10301),
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeHTTPBodySegment { test.Fatal("expected", TypeHTTPBodySegment, "got", ty) }
got, err := ReadMessageHTTPBodySegment(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
sliceEqual(message.Data, got.Data)
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageHTTPRequest (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageHTTPRequest {
ID: 9994562,
Address: "some.thing",
Method: "GET",
URL: "https://example.com/hahaha",
Header: Map {
"Bird": []string { "Bird1", "Bird2" },
"Tree": []string { "Tree1" },
},
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeHTTPRequest { test.Fatal("expected", TypeHTTPRequest, "got", ty) }
got, err := ReadMessageHTTPRequest(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Address == got.Address &&
message.Method == got.Method &&
message.URL == got.URL &&
mapEqual(message.Header, got.Header)
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}
func TestMessageHTTPResponse (test *testing.T) {
conduit := new(bytes.Buffer)
message := MessageHTTPResponse {
ID: 77777,
Status: 200,
Header: Map {
"Bird": []string { "Bird1", "Bird2" },
"Tree": []string { "Tree1" },
},
}
message.Send(conduit)
ty, err := ReadType(conduit)
if err != nil { test.Fatal("when reading type code:", err) }
if ty != TypeHTTPResponse { test.Fatal("expected", TypeHTTPResponse, "got", ty) }
got, err := ReadMessageHTTPResponse(conduit)
if err != nil { test.Fatal("when reading message:", err) }
equal :=
message.ID == got.ID &&
message.Status == got.Status &&
mapEqual(message.Header, got.Header)
if !equal { test.Fatal("structures not equal") }
if conduit.Len() != 0 {
test.Fatal("buffer len", conduit.Len(), "instead of 0")
}
}

237
protocol/protocol.go Normal file
View File

@@ -0,0 +1,237 @@
// 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
}