mirror of
https://github.com/renorris/openfsd
synced 2026-03-30 11:05:34 +08:00
Changes:
- Implement bootstrapping library for managing several concurrent internal services
- Refactor concurrency model for connections/logical clients and their associated I/O
- Refactor server context singleton
- Refactor error handling
- Most errors are now gracefully sent to the FSD client directly encoded as an $ER packet,
enhancing visibility and debugging
- Most errors are now rightfully treated as non-fatal
- Refactor package/dependency graph
- Refactor calling conventions/interfaces for many packages
- Refactor database package
- Refactor post office
Features:
- Add VATSIM-esque HTTP/JSON "data feed"
- Add ephemeral in-memory database option
- Add user management REST API
- Add improved web interface
- Add MySQL support (drop SQLite support)
294 lines
5.2 KiB
Go
294 lines
5.2 KiB
Go
package postoffice
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/renorris/openfsd/protocol"
|
|
"github.com/stretchr/testify/assert"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type testAddress struct {
|
|
name string
|
|
mailbox chan string
|
|
killbox chan string
|
|
rating protocol.NetworkRating
|
|
geohash Geohash
|
|
}
|
|
|
|
func (t *testAddress) Name() string {
|
|
return t.name
|
|
}
|
|
|
|
func (t *testAddress) SendMail(s string) {
|
|
select {
|
|
case t.mailbox <- s:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (t *testAddress) SendKill(s string) error {
|
|
select {
|
|
case t.killbox <- s:
|
|
return nil
|
|
default:
|
|
return errors.New("killbox defaulted")
|
|
}
|
|
}
|
|
|
|
func (t *testAddress) NetworkRating() protocol.NetworkRating {
|
|
return t.rating
|
|
}
|
|
|
|
func (t *testAddress) Geohash() Geohash {
|
|
return t.geohash
|
|
}
|
|
|
|
func (t *testAddress) State() AddressState {
|
|
return AddressState{}
|
|
}
|
|
|
|
func TestPostOffice(t *testing.T) {
|
|
PO := NewPostOffice()
|
|
|
|
c1 := testAddress{
|
|
name: "1",
|
|
mailbox: make(chan string, 16),
|
|
killbox: make(chan string, 16),
|
|
rating: protocol.NetworkRatingOBS,
|
|
geohash: Geohash{},
|
|
}
|
|
|
|
c2 := testAddress{
|
|
name: "2",
|
|
mailbox: make(chan string, 16),
|
|
killbox: make(chan string, 16),
|
|
rating: protocol.NetworkRatingSUP,
|
|
geohash: Geohash{},
|
|
}
|
|
|
|
// Test RegisterAddress
|
|
|
|
// Register "1"
|
|
{
|
|
err := PO.RegisterAddress(&c1)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// Register "1" twice
|
|
{
|
|
err := PO.RegisterAddress(&c1)
|
|
assert.NotNil(t, err)
|
|
assert.ErrorIs(t, KeyInUseError, err)
|
|
}
|
|
|
|
// Register "2"
|
|
{
|
|
err := PO.RegisterAddress(&c2)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// Register "2" twice
|
|
{
|
|
err := PO.RegisterAddress(&c2)
|
|
assert.NotNil(t, err)
|
|
assert.ErrorIs(t, KeyInUseError, err)
|
|
}
|
|
|
|
// Test SendMail
|
|
{
|
|
m := Mail{
|
|
mailType: MailTypeDirect,
|
|
source: &c1,
|
|
recipient: "2",
|
|
packet: "hello",
|
|
}
|
|
|
|
PO.SendMail(&m)
|
|
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.Empty(t, c2.mailbox)
|
|
}
|
|
|
|
{
|
|
m := Mail{
|
|
mailType: MailTypeBroadcast,
|
|
source: &c1,
|
|
recipient: "2",
|
|
packet: "hello",
|
|
}
|
|
|
|
PO.SendMail(&m)
|
|
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
}
|
|
|
|
// Test supervisor message
|
|
{
|
|
m := Mail{
|
|
mailType: MailTypeSupervisorBroadcast,
|
|
source: &c1,
|
|
packet: "hello",
|
|
}
|
|
|
|
PO.SendMail(&m)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
}
|
|
|
|
// Test ranged broadcast mail
|
|
{
|
|
// c1 and c2 in range:
|
|
c1.geohash = PO.SetLocation(&c1, 45.0, 45.0)
|
|
c2.geohash = PO.SetLocation(&c2, 45.0, 45.0)
|
|
|
|
m := Mail{
|
|
mailType: MailTypeGeneralProximityBroadcast,
|
|
source: &c1,
|
|
packet: "hello",
|
|
}
|
|
|
|
PO.SendMail(&m)
|
|
|
|
// c2 should receive the broadcast
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
|
|
// Move c2 far away from c1
|
|
c2.geohash = PO.SetLocation(&c2, -45.0, -45.0)
|
|
|
|
// Try again, nobody should see anything now
|
|
PO.SendMail(&m)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.Empty(t, c2.mailbox)
|
|
|
|
// Move back
|
|
c2.geohash = PO.SetLocation(&c2, 45.0, 45.0)
|
|
|
|
// Should see the message again
|
|
PO.SendMail(&m)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
|
|
// Move c2 to a general proximity neighbor cell
|
|
c2.geohash = PO.SetLocation(&c2, 45.7, 47.1)
|
|
|
|
// c1 geohash: v00
|
|
// c2 geohash: v01 (neighbor)
|
|
|
|
PO.SendMail(&m)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
|
|
// Test close proximity broadcast
|
|
m2 := Mail{
|
|
mailType: MailTypeCloseProximityBroadcast,
|
|
source: &c1,
|
|
packet: "hello",
|
|
}
|
|
|
|
// Set client 1 and client 2 in the same location
|
|
c1.geohash = PO.SetLocation(&c1, 32.713026, -117.176283)
|
|
c2.geohash = PO.SetLocation(&c2, 32.713026, -117.176283)
|
|
|
|
PO.SendMail(&m2)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
|
|
// Move client 2 to a neighbor close-proximity cell
|
|
c2.geohash = PO.SetLocation(&c2, 32.719491, -117.134442)
|
|
|
|
// client 2 should still see close-proximity mail
|
|
PO.SendMail(&m2)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.NotEmpty(t, c2.mailbox)
|
|
|
|
{
|
|
msg := <-c2.mailbox
|
|
assert.Equal(t, "hello", msg)
|
|
}
|
|
|
|
// Move c2 two close-proximity cells away
|
|
c2.geohash = PO.SetLocation(&c2, 32.715297, -117.091257)
|
|
|
|
// Nobody should see any mail
|
|
PO.SendMail(&m2)
|
|
assert.Empty(t, c1.mailbox)
|
|
assert.Empty(t, c2.mailbox)
|
|
}
|
|
|
|
// Unavailable browser mailbox should not block SendMail
|
|
{
|
|
m := Mail{
|
|
mailType: MailTypeDirect,
|
|
source: &c1,
|
|
recipient: "2",
|
|
packet: "hello",
|
|
}
|
|
|
|
c2.mailbox = nil
|
|
|
|
timer := time.NewTimer(100 * time.Millisecond)
|
|
done := make(chan interface{})
|
|
go func() {
|
|
PO.SendMail(&m)
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-timer.C:
|
|
assert.Fail(t, "SendMail timeout")
|
|
case <-done:
|
|
}
|
|
}
|
|
|
|
// Deregister "2"
|
|
PO.DeregisterAddress(&c2)
|
|
|
|
// Deregister "2" twice
|
|
PO.DeregisterAddress(&c2)
|
|
|
|
// Deregister "1"
|
|
PO.DeregisterAddress(&c1)
|
|
|
|
// Deregister "1" twice
|
|
PO.DeregisterAddress(&c1)
|
|
}
|