A reconstruction of the gRPC and Protocol Buffers surface of /usr/Engine/Engine, intended for anyone writing software that talks to Engine OS hardware.
Everything here was recovered from the SC6000 5.0.4 binary using the method in Engine Ghidra Analysis. Transport behavior was then verified against two live SC6000 units. This is not an official schema, and no vendor documentation was consulted.
Transport
Property
Value
Port
TCP 50010, hardcoded (movw #0xc35a in Engine)
Discovery
UDP 11224 beacon, 6 bytes: EAAS + 0x01 0x00, broadcast ~2.5 s per device
Bind address
Every address returned by QNetworkInterface::addressEntries() that passes an interface filter. Confirmed reachable on the Wi-Fi address
Protocol
gRPC over cleartext HTTP/2, no TLS, no ALPN negotiation
Server preface
Sends SETTINGS immediately on connect (MAX_CONCURRENT_STREAMS=0x7fffffff, INITIAL_WINDOW_SIZE=4 MiB)
Content type
application/grpc
Reflection
Not available: grpc.reflection.v1alpha.ServerReflection returns UNIMPLEMENTED
Compression
Not observed; send the message with the compression flag clear
Required metadata
uuid: <host uuid> on most methods (see below)
Since there is no TLS and no reflection, a client is a plain HTTP/2 connection with content-type: application/grpc and standard 5-byte-prefixed messages. No credentials plugin, no channel certificates.
Which services are actually served on 50010
Only one. This trips people up, because the binary contains client stubs and server bases for several packages:
Service
On port 50010
enginesync.v1.EngineSyncService
✅ served
enginelibrary.v1.EngineLibraryService
❌ UNIMPLEMENTED
networktrust.v1.NetworkTrustService
❌ UNIMPLEMENTED
grpc.reflection.v1alpha.ServerReflection
❌ UNIMPLEMENTED
UNIMPLEMENTED is what gRPC returns for a service that was never registered, so the library and pairing services are either exposed elsewhere, exposed only under some condition, or used purely as clients on this device. Where they live is not yet known. See the open questions below.
The uuid metadata header
Two of the three enginesync methods require a metadata header whose key is literally uuid and whose value is the caller's host UUID. Omitting it produces:
grpc-status : 9 (FAILED_PRECONDITION)
grpc-message: The client did not pass the uuid in the metadata.
A second server-side check exists whose message is in .rodata:
The requesting host has not been discovered on the server side yet. Try again later.
Both stages were confirmed live, and they are distinguishable by the error you get:
What you send
grpc-status
grpc-message
no uuid header
9 FAILED_PRECONDITION
The client did not pass the uuid in the metadata.
a uuid the device has not discovered
9 FAILED_PRECONDITION
The requesting host has not been discovered on the server side yet. Try again later.
So the gate is: present a uuid, and that UUID must already be in the device's discovered-host table (maintained by SyncServiceDiscoverer, whose onHostAdded/onHostRemoved(uuid) slots appear in the Qt metadata). The device's own UUID is user-visible as the /Network/NetworkId state property.
Service definitions
enginesync.v1.EngineSyncService: beat/tempo sync
Method
Kind
Request
Response
SetSyncState
unary
SyncState
google.protobuf.Empty
ExchangeTimestamps
bidi streaming
TimestampSync
TimestampSync
UpdateSyncLeadAudioInfo
client streaming
SyncLeadAudioInfo
google.protobuf.Empty
// Reconstructed from /usr/Engine/Engine (Engine OS 5.0.4, SC6000).
// NOT an official schema. Field numbers and wire types are recovered from the compiled
// _InternalSerialize functions and are reliable; field NAMES exist only for `string`
// fields (protobuf emits a UTF-8 verification literal for those and nothing else), and
// scalar types are narrowed only as far as the wire type allows.
//
// Wire-type -> possible declared types:
// varint : int32 int64 uint32 uint64 sint32 sint64 bool enum
// fixed64 : double fixed64 sfixed64
// fixed32 : float fixed32 sfixed32
//
// Verified live against SC6000 units on 5.0.4: this service is served on TCP 50010
// over cleartext HTTP/2.
syntax = "proto3";
package enginesync.v1;
import "google/protobuf/empty.proto";
service EngineSyncService {
// Unary. NOTE: on 5.0.4 this method does NOT require the `uuid` metadata header that
// the other two enforce. See the Security page.
rpc SetSyncState (SyncState) returns (google.protobuf.Empty);
// Bidirectional streaming. Requires metadata `uuid: <host uuid>`.
rpc ExchangeTimestamps (stream TimestampSync) returns (stream TimestampSync);
// Client streaming. Requires metadata `uuid: <host uuid>`.
rpc UpdateSyncLeadAudioInfo (stream SyncLeadAudioInfo) returns (google.protobuf.Empty);
}
message DeckID {
// field 1 is written before hostUuid via an outlined varint writer; the field number
// is not visible as an immediate, so 1 is inferred from ordering. Semantically this
// is almost certainly the deck number (compare DeckID.hostUuid + deck index usage in
// the QML channel-assignment strings).
uint32 deck_number_UNCONFIRMED = 1; // varint, name unknown
string hostUuid = 2; // NOTE: camelCase, unlike the other packages
}
message DeckState {
DeckID deck_id_UNCONFIRMED = 1; // message
uint32 field2_UNKNOWN = 2; // varint
uint32 field3_UNKNOWN = 3; // varint
uint32 field4_UNKNOWN = 4; // varint
// field 5 is a nested message; type not determined
bytes field5_UNKNOWN = 5; // message
}
// Request type of SetSyncState. An empty SyncState is accepted by the device and
// returns an empty response, which is why an empty-body call succeeds.
message SyncState {
repeated DeckState deck_states_UNCONFIRMED = 1; // repeated message
}
message SyncLeadAudioInfo {
bytes field1_UNKNOWN = 1; // message
uint32 field2_UNKNOWN = 2; // varint
double field3_UNKNOWN = 3; // fixed64
double field4_UNKNOWN = 4; // fixed64
}
message TimestampSync {
uint64 field1_UNKNOWN = 1; // varint
uint64 field2_UNKNOWN = 2; // varint
uint64 field3_UNKNOWN = 3; // varint
string hostUuid = 4;
}
The request carries an Ed25519 public key as a string (not bytes; so it is hex or base64 text) plus a device_name that is shown to the user. The response is one of CreateTrustGranted, CreateTrustDenied or CreateTrustBusy, and the strings Password Received: fields: and Cannot enter a password if there is not an active request indicate an interactive prompt on the device with a single request in flight at a time.
// Reconstructed from /usr/Engine/Engine (Engine OS 5.0.4, SC6000). NOT an official schema.
// See the enginesync snippet header for how to read the type annotations.
//
// NOT observed listening on TCP 50010 - the device returns UNIMPLEMENTED for this service
// on that port. Where (or whether) it is exposed on a network interface is unknown; the
// client side lives in eaas::GRPCNetworkTrustClient.
syntax = "proto3";
package networktrust.v1;
service NetworkTrustService {
rpc CreateTrust (CreateTrustRequest) returns (CreateTrustResponse);
}
message CreateTrustRequest {
// Declared as `string`, not `bytes` - protobuf only emits a UTF-8 verification literal
// for string fields, and one exists for this field. So the public key is carried as
// text (hex or base64), not as 32 raw bytes.
string ed25519_pk = 1;
uint32 field2_UNKNOWN = 2; // varint. Possibly a port or a protocol/device-type enum
string device_name = 3; // shown to the user in the pairing prompt
}
// Serializes a single message field at number 3. The three outcome messages below are the
// candidates for it; a `oneof` is the natural reading but the wrapper only ever wrote one
// field number, so this is the least certain part of the reconstruction.
message CreateTrustResponse {
// oneof result {
// CreateTrustGranted granted = ?;
// CreateTrustDenied denied = ?;
// CreateTrustBusy busy = ?;
// }
bytes result_UNCONFIRMED = 3; // message
}
message CreateTrustGranted {
// No serialized fields recovered: may be an empty marker message.
}
message CreateTrustDenied {
// No serialized fields recovered: may be an empty marker message.
}
message CreateTrustBusy {
uint32 field1_UNKNOWN = 1; // varint. Plausibly a retry-after hint
}
remotehostscreen.v1 is not gRPC
Worth stating plainly, because the package looks like the others: there is no remotehostscreen service and no method-path strings. All 75 message types are fields of one envelope, HybridModeMessage, exchanged as framed protobuf over its own transport. The relevant binary is /usr/bin/planck-remote-screen a JUCE application (juce::String, SmexControlHost, SmexControlClient), a completely different stack from the Qt-based Engine launched by /usr/Engine/Scripts/remote-screen.sh.
The envelope has 77 message-typed fields at sparse numbers (1-4, 10-22, 31-35, a contiguous 40-77, then 90-107), which suggests commands were appended over successive releases. The individual command messages are well recovered; the mapping from envelope field number to command type is not, because a serialized message field records only its number and length.
// Reconstructed from /usr/Engine/Engine (Engine OS 5.0.4, SC6000). NOT an official schema.
//
// IMPORTANT: remotehostscreen.v1 is NOT a gRPC service. There is no service definition and
// no /package.Service/Method path string anywhere in the binary. Instead every command is a
// field of the single envelope message HybridModeMessage, which is exchanged as framed
// protobuf over its own transport (see /usr/bin/planck-remote-screen and
// /usr/Engine/Scripts/remote-screen.sh).
//
// Field numbers below are recovered from the compiled serializers. The envelope's member
// types are NOT recoverable from the wire code (a message field only records its number and
// length), so the mapping from envelope field number to command message is UNKNOWN except
// where noted. The numbering is sparse, which suggests fields were added over time.
syntax = "proto3";
package remotehostscreen.v1;
// The envelope. 77 message-typed fields at these numbers:
// 1 2 3 4 10 12 14 15 16 17 18 19 20 21 22 31 32 34 35
// 40..77 (contiguous), 90 91 92 94 95 96 98 99
// 100 101 102 103 104 105 106 107
message HybridModeMessage {
// Each field is a distinct command message. Numbers confirmed; payload types unknown.
// e.g. bytes field_1 = 1; ... bytes field_107 = 107;
}
// ------------------------------------------------------------------ command messages
// "deck" below is the leading varint present on nearly every deck-scoped command
// (field 1). It is almost certainly a deck index.
message KeepAlive { string appName = 1; string appVersion = 2; }
message Goodbye { }
message DebugString { string message = 1; }
message SetDeviceConfig {
uint32 f1 = 1; uint32 f2 = 2; uint32 f3 = 3; uint32 f4 = 4; // all varint
}
// ---- deck state
message SetDeckLoaded { uint32 deck = 1; }
message SetDeckCleared { uint32 deck = 1; }
message LoadTrackToDeck { uint32 deck = 1; }
message EjectTrackFromDeck { uint32 deck = 1; }
message InstantDoubleToDeck { uint32 deck = 1; }
message SetDeckKeyLock { uint32 deck = 1; uint32 locked = 2; }
message SetDeckTempo { uint32 deck = 1; float tempo = 2; } // fixed32
message SetDeckSpeedState { uint32 deck = 1; float speed = 2; } // fixed32
message SetDeckSpeedRange { uint32 deck = 1; string value = 2; }
message SetDeckPlayheadPosition { uint32 deck = 1; double p1 = 2; double p2 = 3; double p3 = 4; }
message DeckNeedleDrop { uint32 deck = 1; double position = 2; }
message SetDeckTempCue { uint32 deck = 1; double position = 2; }
message ChangeDeckKey { uint32 deck = 1; uint32 key = 2; }
message SetDeckKey { uint32 deck = 1; uint32 f2 = 2; uint32 f3 = 3; uint32 f4 = 4; }
message DeckInputSourceChanged { uint32 deck = 1; uint32 source = 2; }
message SetMixerChannelMode { uint32 deck = 1; uint32 mode = 2; }
// ---- deck track info
message SetDeckTrackTitle { uint32 deck = 1; string title = 2; }
message SetDeckTrackArtist { uint32 deck = 1; string artist = 2; }
message SetDeckTrackData { uint32 deck = 1; string title = 2; string artist = 3; double f4 = 4; }
message SetDeckAlbumArt { uint32 deck = 1; uint32 art_id = 2; }
// ---- loops and cues
message SetDeckAutoLoopEnabled { uint32 deck = 1; uint32 enabled = 2; }
message SetDeckAutoLoopLabel { uint32 deck = 1; string value = 2; }
message SetDeckBeatJumpLabel { uint32 deck = 1; string value = 2; }
message SetDeckLoopRegion {
uint32 deck = 1;
// field 2 not serialized
double start = 3; double end = 4; // fixed64
string name = 5;
uint32 f6 = 6; uint32 f7 = 7; // varint
}
message ClearDeckLoopRegion { uint32 deck = 1; }
message SetDeckQuickCue {
uint32 deck = 1; uint32 index = 2;
double position = 3; // fixed64
string name = 4;
uint32 f5 = 5; // varint
}
message ClearDeckQuickCue { uint32 deck = 1; uint32 index = 2; }
// ---- waveforms and beatgrid
message SetDeckBeatGrid { uint32 deck = 1; bytes anchors = 2; double f3 = 3; }
message SetDeckBeatGrid_BeatAnchor { } // no serialized fields recovered
message SetShowBeatGrid { uint32 show = 1; }
message SetDeckOverviewWaveform { uint32 deck = 1; bytes waveform = 2; }
message UpdateWaveformChunk { uint32 deck = 1; uint32 index = 2; bytes chunk = 3; }
// ---- images
message SetAlbumArtImage { uint32 id = 1; bytes image = 2; }
message SetIconImage { uint32 id = 1; bytes image = 2; uint32 f3 = 3; }
// ---- playlist / browser panes
message SetPlaylistsHeader { string title = 1; }
message SetPlaylistsHeaderIcon { uint32 icon = 1; }
message SetPlaylistsHeaderHasParent { } // no serialized fields recovered
message SetPlaylistsHeaderSelectable { } // no serialized fields recovered
message SetPlaylistsCurrentRow { uint32 row = 1; }
message SetPlaylistsCurrentLevel { uint32 level = 1; }
message SetPlaylistsRowCount { uint32 count = 1; }
message SetPlaylistsViewportPosition { uint32 position = 1; }
message SetPlaylistTitle { uint32 index = 1; string title = 2; }
message SetPlaylistIcon { uint32 index = 1; uint32 icon = 2; }
message SetPlaylistData { uint32 index = 1; string title = 2; uint32 f3 = 3; uint32 f4 = 4; }
message PlaylistBankAction { uint32 f1 = 1; uint32 f2 = 2; }
// ---- track list pane
message SetTracklistRowCount { uint32 count = 1; }
message SetTracklistCurrentRow { uint32 row = 1; }
message SetTracklistViewportPosition { uint32 position = 1; }
message SetTracklistShowPlayOrder { } // no serialized fields recovered
message SetTracklistSortMode { uint32 f1 = 1; uint32 f2 = 2; }
message SetTrackTitle { uint32 index = 1; string title = 2; }
message SetTrackArtist { uint32 index = 1; string artist = 2; }
message SetTrackAlbum { uint32 index = 1; string album = 2; }
message SetTrackData {
uint32 index = 1;
string title = 2; string artist = 3; string album = 4;
uint32 f5 = 5; uint32 f6 = 6; uint32 f7 = 7;
// field 8 not serialized
uint32 f9 = 9; uint32 f10 = 10;
}
message SetTrackKey { uint32 index = 1; uint32 key = 2; }
message SetTrackTempo { uint32 index = 1; uint32 tempo = 2; }
message SetTrackTimeLength { uint32 index = 1; uint32 length = 2; }
message SetTrackPlayOrder { uint32 index = 1; uint32 order = 2; }
message SetTrackState { uint32 index = 1; uint32 state = 2; }
message SetTrackAlbumArt { uint32 index = 1; uint32 art_id = 2; }
// ---- FX and behaviour toggles
message FXBeatsChanged { uint32 beats = 1; }
message FXDepthChanged { } // no serialized fields recovered
message FXInputSourceChanged { uint32 source = 1; }
message SetQuantizeEnabled { } // no serialized fields recovered
message SetFocusArea { uint32 area = 1; }
message SetCensorButtonBehavior { uint32 behavior = 1; }
message SetKeyAdjustBehavior { uint32 behavior = 1; }
message SetStemFxBehavior { uint32 behavior = 1; }
message SetShowSilentCueIcon { uint32 f1 = 1; uint32 f2 = 2; }
Talking to the device
No .proto or generated stubs are needed. A raw HTTP/2 client is enough. Minimum viable call:
TCP connect to <device>:50010.
Send the HTTP/2 client preface PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n and an empty SETTINGS frame.
Open a stream with HEADERS: :method POST, :scheme http, :path /enginesync.v1.EngineSyncService/SetSyncState, :authority <device>:50010, content-type application/grpc, te trailers, and uuid <your-host-uuid> for the methods that require it.
Send DATA: one byte compression flag (0x00), four bytes big-endian message length, then the serialized protobuf.
Read grpc-status from the response trailers (or headers, for an immediate failure).
A SyncState with no fields set is a valid, accepted message, which makes it a convenient reachability probe. Useful status codes seen in practice:
grpc-status
Meaning here
0 OK
Accepted
9 FAILED_PRECONDITION
Missing uuid metadata, or host not yet discovered
12 UNIMPLEMENTED
Service or method not registered on this port. Also returned for a malformed request body
Validation
The four .proto files in this page are not just plausible-looking.
They compile. All four pass protoc 3.21.12 cleanly (--descriptor_set_out=/dev/null).
They generate usable stubs.grpc_tools.protoc produces working Python bindings, and the encoder round-trips: a SyncState containing one DeckState with field2 = 7 serializes to 0a 02 10 07 (field 1, length 2, containing field 2 varint 7, exactly as the wire format requires.)
A real device accepts messages built from them. Encoding SyncState values with the generated bindings and posting them to 10.2.0.209:50010 returns grpc-status 0 in every case:
Message sent
Encoded body
Result
empty SyncState
(empty)
0 OK
one DeckState with DeckID{deck=1, hostUuid=…}
0a2a0a280801122430303030…
0 OK
one DeckState with deck=2 and fields 2/3/4 set
0a0a0a020802100118012001
0 OK
four DeckState entries
0a040a0208010a040a020802…
0 OK
That confirms the field numbers and wire types for SyncState, DeckState and DeckID are correct as far as the device's parser is concerned. It does not confirm the semantics. The device accepted the messages, but what it did with them was not observed.
How this was recovered, and where it is weak
The binary uses protobuf-lite, which strips descriptors. There is no FileDescriptorProto to dump and no reflection to query. Each piece came from somewhere different:
What
Source
Confidence
Service and method names
/package.Service/Method string literals
Exact
Method kind, request/response types
C++ template parameters in RTTI typeinfo names, e.g. RpcMethodHandler<...Service, SyncState, protobuf::Empty>
Exact
Message inventory
Demangled typeinfo names per package
Exact
Field numbers and wire types
Tag bytes and field-number arguments in each message's _InternalSerialize (vtable slot 12)
Exact where recovered
string field names
WireFormatLite::VerifyUtf8String(..., "pkg.Msg.field") protobuf emits this only for string fields
Exact
string vs bytes
Same mechanism, used as a discriminator: a length-delimited field written without a UTF-8 verification call is bytes, not string
Exact
Map fields
Generated *_LoopsEntry_DoNotUse helper types
Exact that a map exists
Everything else
Not recoverable
Known weaknesses, so nobody trusts this further than it deserves:
Non-string field names are unknown.bytes, numeric, bool, enum and message fields leave no name behind. They are labeled fieldN_UNKNOWN.
Wire types under-determine declared types. A varint may be any of int32 int64 uint32 uint64 sint32 sint64 bool enum; fixed64 may be double fixed64 sfixed64.
Message field types are unknown. A message-typed field records only its number, so GetTrackResponse fields 1-3 are known to be messages, but their types are inferred at best.
Some serializers are shared. protobuf deduplicates identical layouts, so several "single repeated message field" messages resolve to the same function (0113fb90). The shape is right; the element type is inferred from the method signature.
A few messages report no fields. Either genuinely empty marker messages, or their fields are emitted through a path the extraction does not follow.
TrackPerformanceData has a conflicting recovery for field 1. Flagged inline.
Field numbers not listed are not necessarily unused; TrackMetadata skips 2 and SetTrackData skips 8, which usually means reserved or removed fields.
Open questions
Concrete things a contributor could close:
Where are enginelibrary.v1 and networktrust.v1 exposed? They are not on 50010. Candidates: one of the ~12 per-device ephemeral ports, a different interface, or only after pairing.
What are the ~12 high ports? They differ per device and are silent to an HTTP/2 preface, so they are probably airNetworkExchange directory/service sockets, not gRPC.
Envelope field mapping for HybridModeMessage: a capture of a real remote-screen session would settle it quickly.
Non-string field names would need either a debug build, the .proto files, or careful correlation of observed traffic against known UI state.
Eaas has its own gRPC server (Libraries/private/Planck/Eaas/src/grpcServer/, including Handlers/TrackWriterHandler.h) which is not covered here at all.
Related Files
Engine Networking: ports, discovery, the two HTTP servers, and cloud endpoints.
Engine Ghidra Analysis: how to reproduce this recovery on another binary or firmware version.