Files
openfsd/postoffice/post_office.go
Reese Norris 57d54d6705 v0.1.0-alpha
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)
2024-10-07 12:50:39 -07:00

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())
}