mirror of
https://github.com/renorris/openfsd
synced 2026-03-22 14:35:36 +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)
124 lines
4.3 KiB
Go
124 lines
4.3 KiB
Go
package postoffice
|
|
|
|
import (
|
|
"github.com/renorris/openfsd/protocol"
|
|
)
|
|
|
|
// Addresses are placed into geographical buckets.
|
|
// A bucket is determined by lowering the precision of an address
|
|
// into a 15-bit geohash "bounding box."
|
|
// For proximity broadcasts, the caller address's source bucket is determined,
|
|
// then each neighbor is sent the message. This does not magically avoid the
|
|
// O(n^2) problem, but it shrinks it to the point of disregard in 99.9% of
|
|
// reasonable cases.
|
|
|
|
// PostOffice handles the routing of messages between clients
|
|
type PostOffice struct {
|
|
addressRegistry *Registry // Map address name (usually a callsign) to its respective Address
|
|
supervisorAddressRegistry *Registry // Map supervisor address name to its respective Address
|
|
world *World // World container holding all geohash buckets
|
|
}
|
|
|
|
func NewPostOffice() *PostOffice {
|
|
return &PostOffice{
|
|
addressRegistry: NewRegistry(1024),
|
|
supervisorAddressRegistry: NewRegistry(16),
|
|
world: NewWorld(),
|
|
}
|
|
}
|
|
|
|
// RegisterAddress registers an address to the post
|
|
// office, making it a valid recipient for other addresses.
|
|
// Returns KeyInUseError if the address name (callsign) is already in use.
|
|
func (p *PostOffice) RegisterAddress(address Address) error {
|
|
if err := p.addressRegistry.Store(address.Name(), address); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Always acquire a key in the main address registry first *before* adding anything to the supervisor map.
|
|
// On delete, perform the operations in the reverse order: remove from supervisor registry,
|
|
// then finally remove from main address registry.
|
|
|
|
// If the address is a supervisor, add them to the supervisor registry
|
|
if address.NetworkRating() >= protocol.NetworkRatingSUP {
|
|
if err := p.supervisorAddressRegistry.Store(address.Name(), address); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeregisterAddress removes an address from the post office.
|
|
func (p *PostOffice) DeregisterAddress(address Address) {
|
|
// Remove any supervisor entry *first.*
|
|
if address.NetworkRating() >= protocol.NetworkRatingSUP {
|
|
p.supervisorAddressRegistry.Delete(address.Name())
|
|
}
|
|
|
|
p.addressRegistry.Delete(address.Name())
|
|
|
|
p.removeAddressFromGeohashBucket(address.Geohash().AsPrecision(geohashBucketPrecisionBits), address)
|
|
}
|
|
|
|
// NumRegistered returns a snapshot of how many addresses were registered at the time of calling
|
|
func (p *PostOffice) NumRegistered() int {
|
|
return p.addressRegistry.Len()
|
|
}
|
|
|
|
// ForEachRegistered runs the provided function for each registered address.
|
|
// If the function returns false, iteration will cease and return early.
|
|
func (p *PostOffice) ForEachRegistered(f func(name string, Address Address) bool) {
|
|
p.addressRegistry.ForEach(f)
|
|
}
|
|
|
|
// SendMail forwards Mail to its recipients
|
|
func (p *PostOffice) SendMail(mail *Mail) {
|
|
switch mail.Type() {
|
|
case MailTypeDirect:
|
|
p.sendDirectMail(mail)
|
|
case MailTypeGeneralProximityBroadcast:
|
|
p.sendProximityMail(mail, geohashGeneralProximityPrecisionBits)
|
|
case MailTypeCloseProximityBroadcast:
|
|
p.sendProximityMail(mail, geohashCloseProximityPrecisionBits)
|
|
case MailTypeBroadcast:
|
|
p.sendBroadcastMail(mail)
|
|
case MailTypeSupervisorBroadcast:
|
|
p.sendSupervisorBroadcastMail(mail)
|
|
}
|
|
}
|
|
|
|
// SetLocation marks where an address is geographically located in order to
|
|
// properly handle any future proximity broadcast messages.
|
|
// Returns the updated geohash.
|
|
func (p *PostOffice) SetLocation(address Address, lat, lng float64) (newGeohash Geohash) {
|
|
|
|
// Make a full precision geohash from the caller's coordinates
|
|
newGeohash = NewGeohash(lat, lng)
|
|
|
|
// Check if the provided coordinates lie outside the address's current geohash
|
|
currentBucketHash := address.Geohash().AsPrecision(geohashBucketPrecisionBits)
|
|
newBucketHash := newGeohash.AsPrecision(geohashBucketPrecisionBits)
|
|
if currentBucketHash == newBucketHash {
|
|
return
|
|
}
|
|
|
|
// Remove the address from the current bucket
|
|
p.removeAddressFromGeohashBucket(currentBucketHash, address)
|
|
|
|
// Put it into the new bucket
|
|
p.addAddressToGeohashBucket(newBucketHash, address)
|
|
|
|
return
|
|
}
|
|
|
|
// Kill finds an address associated with `pdu` and sends a kill signal
|
|
func (p *PostOffice) Kill(pdu *protocol.KillRequestPDU) error {
|
|
addr, exists := p.addressRegistry.Load(pdu.To)
|
|
if !exists {
|
|
return AddressNotRegisteredError
|
|
}
|
|
|
|
return addr.SendKill(pdu.Serialize())
|
|
}
|