Engine Networking
Everything on this page was recovered statically from /usr/Engine/Engine in the SC6000 5.0.4 firmware using the method in Engine Ghidra Analysis. Nothing here was confirmed by talking to a device, so items are marked as observed-in-binary rather than verified on the wire.
Engine links libgrpc.so.37 and uses Protocol Buffers for all of its device-to-device and device-to-cloud RPC. The gRPC service and message names survive in .rodata as full method-path strings and protobuf descriptor field names, so the interface can be enumerated exactly.
gRPC services
Four service packages appear in the binary. For each, Engine contains both the generated client-side Stub classes and the server-side Service/WithCallbackMethod_* base classes, which means the device both calls and serves these interfaces.
enginelibrary.v1.EngineLibraryService
Library sharing: the interface behind linked/shared libraries between players.
Method |
|---|
|
|
|
|
|
|
|
|
|
|
|
enginesync.v1.EngineSyncService
Cross-device beat/tempo sync.
Method |
|---|
|
|
|
Messages carry a hostUuid field (enginesync.v1.TimestampSync.hostUuid, enginesync.v1.DeckID.hostUuid).
networktrust.v1.NetworkTrustService
Device pairing. One method, CreateTrust.
The request carries two fields:
Field | Purpose |
|---|---|
| Requesting peer's Ed25519 public key |
| Human-readable name shown to the user |
The response is a one-of over three outcomes: CreateTrustGranted, CreateTrustDenied, CreateTrustBusy; which together with the strings Password Received: fields: and Cannot enter a password if there is not an active request indicates an interactive approve/deny prompt on the device, with only one pairing request in flight at a time.
The client side lives in eaas::GRPCNetworkTrustClient, and the assertion string _gRPCNetworkTrustClient\nPrecondition failed\n.../Libraries/Streaming/Providers/Eaas/src/GRPCWebAccessor.cpp:473 places it in the Eaas (streaming) subsystem.
remotehostscreen.v1
Remote screen mirroring: pushing deck/library state to another device's display. No method-path strings, but a large set of message descriptors: KeepAlive (with appName, appVersion), SetDeckTrackData, SetPlaylistData, SetDeckQuickCue, SetDeckLoopRegion, SetTrackTitle, DebugString and many more. The controllers are under App/src/Planck/Controller/RemoteHostScreen/.
How the server binds
grpcutils::Server::addServiceOnInterfaceToBuilder (0115a9a0) does the listener setup:
Calls
QNetworkInterface::addressEntries()and iterates the interface's IP addresses.Applies a caller-supplied
std::functionfilter to each address.Formats the endpoint as
%1:%2, or[%1]:%2for IPv6.Calls
grpc::InsecureServerCredentials().Calls
grpc::ServerBuilder::AddListeningPort(builder, "<ip>:<port>", creds, nullptr).
Services are attached by grpcutils::Server::registerServicesOnBuilder (0115b334) via grpc::ServerBuilder::RegisterService.
The listening port is not a compile-time constant in these functions; it is read from a service descriptor built by the caller, and the fourth argument to AddListeningPort is nullptr, so the code never reads back the port actually bound.
Observed ports
Measured on two SC6000 units running 5.0.4, attached over Wi-Fi:
Port | Both devices? | What it is |
|---|---|---|
| ✅ | gRPC over cleartext HTTP/2, hosting |
| ✅ | License-activation callback inside Engine; route |
~12 high ports | ❌ | Differ per device; silent to an HTTP/2 preface. Probably the |
| ❌ | Closed: not enabled by default in 5.0.4 |
The high ports are transient, not fixed listeners
Once network library sharing was active (one deck playing from an SD card physically inserted in the other deck) a rescan of the library host showed three additional ports (33199, 41315, 41965), while the client deck was unchanged. By the time a probe reached them seconds later they were refusing connections, and a follow-up full sweep of 30000-47000 found them gone along with one port from the original baseline.
So these are per-session or per-transfer sockets with short lifetimes, consistent with airNetworkExchange::DirectorySocket/ServiceServer instances rather than long-lived services. Anything scanning them has to probe in the same pass as the scan, or it will lose the race.
Note also that only the deck hosting the media opened extra ports, which is a useful signal about which side of a shared-library session serves data.
50010 is the port the community associates with StagelinQ, and it is a fixed, well-known value rather than the dynamic one the code structure suggested.
How clients connect
grpcutils::createInsecureChannel (01158c44) is the only channel factory in the binary. It calls grpc::CreateCustomChannel(target, grpc::InsecureChannelCredentials(), args).
Its callers are:
enginelibrary::v1::EngineLibraryService::Stubregistration (00fc8ed0)EngineSyncClient(00d6a9e8)NetworkTrustService::CreateTrust(00fceecc)
So all three device-to-device services share one insecure channel implementation.
Per-call credentials
engineSync::EngineSyncNetwork attaches exactly one gRPC metadata header before each call:
The key string lives at 02a98028: a std::string whose small-string buffer contains uuid with length 4. No other metadata is attached, and no signature or MAC accompanies it.
The server confirms this is the credential it checks. Calling ExchangeTimestamps or UpdateSyncLeadAudioInfo without it returns FAILED_PRECONDITION and the message The client did not pass the uuid in the metadata. A second gate exists whose message is present in .rodata (The requesting host has not been discovered on the server side yet. Try again later.) so a caller must both present a uuid and already appear in the server's discovered-host table, maintained by SyncServiceDiscoverer (onHostAdded/onHostRemoved(uuid)).
The same identifier is user-visible as the /Network/NetworkId state property. It is displayed in the device's own settings UI and embedded in QML channel-assignment strings, e.g. Planck.engineConnect.generateChannelAssignmentString(networkId, deckLayerIndex + 1).
Service discovery
Discovery does not use mDNS/DNS-SD. There are no _tcp./_udp. /service-type strings in Engine, and while Avahi does run on the device (see the journalctl snippet in Prime Go (JP11)) it reports No service file found in /etc/avahi/services.
EAAS discovery beacon: UDP 11224
Observed live on two SC6000s running 5.0.4. Both units broadcast a 6-byte UDP datagram to port 11224, roughly every 2.5 seconds each, from an ephemeral source port:
That is the ASCII magic EAAS followed by 0x01 0x00: plausibly a version or a version/flags pair. The beacon carries no device token, no UUID and no port list, so it appears to announce presence only; a peer then connects to the fixed gRPC port 50010 to do anything useful.
Useful cross-reference: chrisle/StageLinq is a TypeScript implementation covering both the classic StagelinQ services (StateMap, BeatInfo, FileTransfer, TimeSync, Directory, Broadcast (UDP 51337), airD magic, length-prefixed UTF-16BE strings) and the newer EAAS layer. Two of its EAAS details did not match these 5.0.4 units:
That project documents | Observed on SC6000 5.0.4 |
|---|---|
| Both return |
HTTP | TCP 50020 is closed on both units: the cpp-httplib |
Those are the routes the EaasHttpServer registers in the binary (/ping and /download/(<)(.*)(>)), so the code is present but the server was not running in any state tested, including with tracks loaded and playing. Whether it starts under some other condition (a linked library, a logged-in streaming account) is unknown.
Instead, there are two custom subsystems, both recovered as RTTI class namespaces:
Namespace | Notable classes |
|---|---|
|
|
|
|
The Sender/Receiver pair in airNetworkDiscoverer is consistent with a UDP broadcast/announcement scheme, and airNetworkExchange::DirectoryServer::sendServiceInfo (016f99a8) is where a peer is told which services exist and on which port. Two related state properties are exposed to the UI: /Network/NumberOfDirectorySockets ("Connected Sockets") and /Network/NetworkId ("Network ID").
This is very likely the same family of protocols the community calls StagelinQ, but that correspondence has not been verified here.
Ableton Link: UDP multicast 224.76.78.75:20808
With Ableton Link enabled, both decks join the standard Link session and multicast their state in cleartext. This is Ableton's open protocol, not an inMusic one, but it is by far the easiest way to read a deck's live tempo and beat phase from another machine: no pairing, no uuid, no gRPC. Multicast is delivered to an ordinary managed-mode Wi-Fi station, so it needs no elevated privileges.
Observed packet (107 bytes, sent a few times a second by each deck):
Layout: a 12-byte header, an 8-byte node id, then length-prefixed TLV sections keyed by a 4-character ASCII tag.
Offset / tag | Meaning |
|---|---|
| magic |
| protocol version ( |
| message type: |
| TTL ( |
| group id ( |
| 8-byte node id |
| timeline, 24 bytes = three big-endian int64: micros-per-beat, beat origin in micro-beats, time origin in micros |
| 8-byte session id: identical across peers in the same session |
| start/stop state, 17 bytes: int64 timestamp then a play flag |
| measurement endpoint, 6 bytes: 4-byte IPv4 then 2-byte UDP port, for Link's latency ping/pong |
Decoded from the two SC6000s while both were playing:
Field |
|
|
|---|---|---|
node id |
|
|
session id |
|
|
tempo | 638290 µs/beat → 94.001 BPM | identical |
beat origin | 13474.269 beats | identical |
|
|
|
|
|
|
Two things worth noting. The timeline is byte-identical across both decks, which is what a converged Link session should look like. And the stst play flag reads 0 even with both decks playing, so Engine evidently does not drive Link's optional start/stop sync; only tempo and phase.
Network file access
A set of NFS classes is present, indicating library media is read over the network rather than copied: airNfsFileAdapter, airNfsFile, airNfsQtFileEngine, airNfsSubDirectory, plus a generic airVfsFileEngine and airFileFactory. These are Qt QAbstractFileEngine subclasses, so NFS paths are transparent to the rest of the application.
Cloud endpoints
Not device-to-device, but present in the same binary:
Endpoint | Purpose |
|---|---|
| Streaming account linking |
| Dropbox integration |
| Dropbox reachability check |
| OAuth 2.0 device flow |
| Local loopback HTTP |
The OAuth device-code flow is used for streaming-service login (accessToken, refreshToken, client_secret, id_token, TOKEN_KEYS, access_token_creation_date).
Two separate HTTP servers
Engine contains two unrelated HTTP server implementations, which is easy to conflate:
Server | Implementation | Routes | Notes |
|---|---|---|---|
| cpp-httplib ( |
| Restarted per interface via |
License-activation callback | Hand-rolled |
| This is what answers on port 41401. Belongs to |
The two are distinguishable on the wire: cpp-httplib emits conformant responses with headers, whereas the hand-rolled server sends a bare HTTP/1.1 404 Not Found with no CRLF terminator and no headers at all (which is why curl shows nothing for it).
Eaas also has its own gRPC server (Libraries/private/Planck/Eaas/src/grpcServer/, including Handlers/TrackWriterHandler.h), separate from the enginesync.v1 server on 50010.
OAuth flows
Engine implements several OAuth variants, one per provider; they should not be conflated:
Flow | Where | Notes |
|---|---|---|
Device Authorization Grant (RFC 8628) | TIDAL ( | Handles |
Authorization code with |
| Builds |
Implicit ( |
| Same parameter set with |
Authorization code without |
| A third-party provider; also references |
License activation callback | Server loop at |
|
code_verifier, code_challenge and code_challenge_method do not occur anywhere in the binary, so PKCE is not implemented in any of these flows. For the device grant that matters little; for the redirect-based flows it matters more.
Streaming DRM is Widevine (wvcdm::, video_widevine:: and drm_metrics:: classes are compiled in), along with a CryptoPP build.
Related Files
Engine Ghidra Analysis covers how these names were recovered.
Engine covers the application generally.
Updating covers the SSH access used to reach a running device.