Skip to content

[#398] Resolve "signalc: relay messages to/from signalboost over unix socket"

aguestuser requested to merge 398-relay-messages-over-unix-socket into main

Closes #398 (closed)

Context

  • in prior MRs we have given signalc the ability to send and receive messages over signal, storing user data in postgresql across sessions
  • in this MR, we give it the ability to receive messages over a UNIX socket from signalboost and send responses back
  • currently, signalc understands only the bare minimum of commands necessary to work with signalboost:
    • register
    • verify
    • subscribe
    • send
  • which is just to say, it implements all the features necessary to be load tested against signald! 🎉

Changes

Socket Layer

  • new arch elements: SocketServer, SocketReceiver, SocketSender
  • SocketServer
    • listens to connections
    • on connections: stores a hash map of known connections and assigns receivers and senders to each
    • can shutdown a subset or all connections gracefully
  • SocketReceiver
    • acts as a router for incoming socket requests (json deserialized using the new SocketRequest class)
    • handles all commands decleared in signald interface:
      • actuallyimplements register, verity, subscribe, send
      • performs NOOPs for set_expiration, trust, unsubscribe, and version
    • after performing appropriate business logic, produces a response (json serialized using new SocketResponse class) and routes it back to socket viw the SocketSender
    • has slightly more resilient handling of a disrupted subscribe request that signald (attempts to restart disrupted connections and reports them back to signalboost)
  • SocketSender:
    • uses actors to create a pool of PrintWriters, spread traffic randomly over each of them, and ensure one-at-a-timne writing of them
    • this means: responses will be loadbalanced over all open connections, rather than always going to the connection that opened a subscription (as in signald)

Application/Config/Main

  • add run and stop methods
  • before calling run, all app components are initialized
  • after calling run, the appropriate configs are read in and components are initialized to either their real or mocked values
  • trick to make this work:
    • provie default mocks for every app component (so they don't need to be declared explicitly every time in tests)
    • provide Config helpers to mock an arbitrary subset of classes in app for any given test (uses default mocks declared in application class)
  • now Main is very thin: it does nothing except instantiate Application with correct configs, provide it a coroutine scope and call run

Signal Layer

These classes stayed more or less the same except we memoize signal service receivers and senders and in the receiver class we:

  • exaustively pattern match on all possible envelope types
  • handle PREKEY_BUNDLE envelopes like CIPHERTEXT because that is what the first given ciphertext from any new correspondent is labeled as by signal
  • are a bit smarter about how we are using coroutiens

New Conventions / Code Health

  • extact CacheUtil#getMemoized to abstract the 4-5 instances in which we want to create memoized instances of Signal Service objects
  • prefer explicit async.await() calls to withContext for wrapping blocking code
  • add custom SignalcErrors
  • provide EnvelopeType and SignalcAddress classes for kotlin-ifying corresponding SignalService classes

Tests

  • provide new TestSocketServer and TestScoketClient classes for reusably and cleanly sending socket messages
  • use this to improve on semantics of socket tests by receiving connections off of channels (rather than waiting an arbitrary amount of time and grabbing connections out of maps -- which is brittle and breaks encapsulation!)
  • implement our first "big bracket" unit test, SocketServerBigTest, which tests round-trip message handling from: client to socket server to socket reader to signal sender, to signal receiver, to socket sender and back to client (yay!)
  • provide tons of new test dat generators and custom matchers

Tooling

  • a whole new logging paradigm using sl4j (and some mods to the signalboost logger to be consistent with it)
    • configs defined in main.resources.logback.xml
    • reads log level from LOG_LEVEL env var
    • provides custom template for logs -- including thread/coroutine info for DEBUG and ERROR log levels
  • lots of make scripts to run everything
  • docs and tooling to attach a remote debugger to signalc while it runs inside a docker container
  • docker tooling to inject a different db for signalboost in the sc stack
  • env var flags to disable unimplemented signalboost jobs in the sc stack, or set socket pools/shards to lower sizes

Merge request reports