文档/Driver RPC protocol
此页面尚未翻译,当前显示英文版本。 查看英文版本
Driver RPC Protocol Specification
This document defines how DBFlux discovers, launches, and talks to RPC services over local IPC.
DBFlux now activates two runtime service families:
RpcServiceKind::Driver-> runtime database driversRpcServiceKind::AuthProvider-> runtime auth-provider registries in the app and MCP server
Source of truth
For active driver services, the service is the source of truth for:
- driver kind (
DbKind) - driver metadata (
DriverMetadataDto: name, icon, category, capabilities, query language, etc.) - connection form definition (
DriverFormDefDto)
DBFlux stores launch configuration in its SQLite-backed services config. RPC services are created and edited from Settings → RPC Services.
Integration model
At app startup, DBFlux loads configured RPC services from ~/.local/share/dbflux/dbflux.db, then for each service:
- discovers the persisted service descriptor, including
RpcServiceKind - branches by
kind - ensures the service is running (starts it if needed)
- performs the family-specific
Hellohandshake - reads runtime metadata from the service
- registers the adapted runtime service in the appropriate in-memory registry
If any step fails, that service is skipped without aborting startup. Driver failures do not break auth providers, and auth-provider failures do not break drivers.
Important behavior:
- Service configuration is read at startup. Restart DBFlux after changing RPC service settings.
socket_idis used as-is (it is not rewritten by DBFlux).- Internal registry key is
rpc:<socket_id>.
Transport
DBFlux uses local sockets via interprocess:
- Linux: abstract namespace Unix sockets (
\0name) - macOS: Unix sockets in
/tmp/ - Windows: named pipes (
\\.\pipe\...)
Messages are framed as:
- 4-byte little-endian length (
u32) - bincode payload
Maximum message size: 16 MiB.
Socket cleanup is automatic on process exit/drop (provided by interprocess).
Runtime configuration
Primary storage: ~/.local/share/dbflux/dbflux.db (cfg_services, cfg_service_args, cfg_service_env)
Settings UI: Settings → RPC Services
Notes:
socket_idis required.kindsupportsdriverandauth_provider.commandis optional.- If
commandis omitted andargsis empty, DBFlux expects the service to already be running. - For
driver, ifcommandis omitted andargsis non-empty, DBFlux launchesdbflux-driver-host. - For
auth_provider, managed launch requires an explicitcommand; DBFlux does not assume a default host binary.
- If
args,env, andstartup_timeout_msare optional.- DBFlux derives an internal driver registry key as
rpc:<socket_id>. - Only
driverservices are registered as database drivers. auth_providerservices are registered only in auth-provider registries and never receive arpc:<socket_id>driver identity.
Handshake contract
DBFlux connects and sends Hello first.
The active driver RPC API family is driver_rpc. In the current dedicated driver RPC transport, that family is implicit in the protocol itself rather than transmitted on the wire during Hello. Compatibility is enforced by the driver RPC endpoint plus the selected protocol major version; minor versions are additive and are negotiated deterministically within that major line.
Client request:
DriverRequestBody::Hello(DriverHelloRequest {
client_name: "dbflux_driver_ipc".to_string(),
client_version: "<version>".to_string(),
supported_versions: vec![
ProtocolVersion::new(1, 0),
ProtocolVersion::new(1, 1),
ProtocolVersion::new(1, 2),
ProtocolVersion::new(1, 3),
],
requested_capabilities: vec![
DriverCapability::Cancellation,
DriverCapability::ChunkedResults,
DriverCapability::SchemaIntrospection,
DriverCapability::MultiDatabase,
],
})
Server response must include:
selected_versioncapabilitiesdriver_kinddriver_metadataform_definition
Example:
DriverResponseBody::Hello(DriverHelloResponse {
server_name: "my-driver".to_string(),
server_version: "1.0.0".to_string(),
selected_version: DRIVER_RPC_VERSION,
capabilities: vec![DriverCapability::SchemaIntrospection],
driver_kind: DbKind::SQLite,
driver_metadata: DriverMetadataDto {
id: "my-driver".to_string(),
display_name: "My Driver".to_string(),
description: "External RPC driver".to_string(),
category: DatabaseCategory::Relational,
query_language: QueryLanguageDto::Sql,
capabilities: DriverCapabilities::RELATIONAL_BASE.bits(),
default_port: None,
uri_scheme: "mydriver".to_string(),
icon: Icon::Database,
},
form_definition: DriverFormDefDto {
tabs: vec![
// ...
],
},
})
If multiple compatible minors overlap, the host must select the highest mutual minor version.
If no compatible version exists, return DriverRpcErrorCode::VersionMismatch.
After Hello, every request and response envelope must use the negotiated selected_version. A peer that receives a different post-handshake envelope version must reject it as a version mismatch.
Current validation boundary:
- DBFlux persists per-service API family/version metadata for discovery and future runtime seams.
- The live driver handshake currently validates negotiated protocol versions, but it does not transmit or separately re-validate the API family string on the wire because the driver RPC transport is already family-specific.
Audit emission from drivers (v1.2+)
A driver that advertises DriverCapability::AuditEmit (driver RPC ≥ 1.2) may write to the host audit log by sending EmitAuditEvent intermediate frames (done=false) during any request/response cycle. The host sanitizes every event before persisting it to aud_audit_events.
Allowed categories: Connection, Query, System. All other categories are silently dropped.
The host overrides identity fields (actor_type → ExternalDriver, actor_id, source_id, driver_id, correlation_id) and the connection context from AppState, and truncates details_json to the configured limit. Rate limiting is shared with auth providers: 100 events per 60 seconds per socket_id; overflow events are dropped without erroring the session. Peers that negotiate below v1.2 or omit the capability remain silent. See Audit § external audit emission for the full sanitization contract.
Key-value read size gate (v1.3+)
KeyGetRequest carries max_value_bytes: Option<u64>, an optional upper bound on the value bytes a KvGetKey call may transfer. None means unbounded, which is also what a peer negotiating below v1.3 gets: the field is additive and defaults to None when absent from the wire payload, so older drivers and hosts keep fetching the full value.
KeyGetResult carries load_state: KeyLoadState, reporting whether value is the complete payload:
Loaded— the full value was fetched. Default when the field is absent from the wire payload.Truncated { returned_bytes, total_bytes }— only part of the value was fetched (for example a driver-side item cap on a collection type);total_bytesis the full size when the driver knows it.TooLarge { size_bytes, limit_bytes }— the value was not fetched because it exceedsmax_value_bytes;valueis empty.
Both fields are plain, #[serde(default)] struct fields on existing request/response types, not a new capability flag: no Hello negotiation gates them, and a driver ignoring max_value_bytes simply always returns Loaded.
Auth-provider RPC contract
The active auth-provider RPC API family is auth_provider_rpc at 1.3.
DBFlux uses persisted api_family / api_major metadata as a startup preflight. Compatible rows then negotiate the highest shared minor version during Hello.
Client request:
AuthProviderRequestBody::Hello(AuthProviderHelloRequest {
client_name: "dbflux_ipc".to_string(),
client_version: "<version>".to_string(),
supported_versions: vec![
ProtocolVersion::new(1, 3),
ProtocolVersion::new(1, 2),
ProtocolVersion::new(1, 1),
ProtocolVersion::new(1, 0),
],
auth_token: Some("<token>".to_string()),
})
Server response must include:
selected_versionprovider_iddisplay_nameform_definition
The v1.2 Hello response additionally carries secret_dependency_opt_in (bool), declaring whether the provider opts in to receiving secret field values inside dependency maps for dynamic option lookups. When false (default), DBFlux strips secret values from dependency maps before forwarding FetchDynamicOptions requests.
The v1.3 Hello response additionally carries audit_emit_opt_in (bool). Set this to true to enable audit event emission (see below). Default is false.
Supported request / response flow:
| Request | Response | Purpose |
|---|---|---|
Hello | Hello | protocol negotiation + provider identity |
ValidateSession | SessionState | validate cached auth state |
Login | LoginUrlProgress? + LoginResult | optional verification URL + terminal login result |
ResolveCredentials | Credentials | resolve runtime credential fields |
FetchDynamicOptions | DynamicOptions | resolve dynamic dropdown options for a DynamicSelect form field (v1.2+) |
| (any request) | EmitAuditEvent (intermediate) | audit event emission (v1.3+) |
Notes:
Loginmay emit zero or oneLoginUrlProgressevent beforeLoginResult.- If no progress event is sent, DBFlux treats the verification URL callback as
None. FetchDynamicOptionsis available only when the negotiated version is at least1.2. Providers that negotiate below v1.2 receive a permanent “not supported” outcome from the host without an IPC round-trip.detect_importable_profiles, profile write-back hooks, and provider-specific value-provider registration are intentionally out of scope for the RPC contract in this change.- Auth-provider runtime failures surface through existing
DbErrorhandling and do not abort startup.
Audit emission from auth providers (v1.3+)
Auth providers that negotiate v1.3+ and set audit_emit_opt_in: true may send EmitAuditEvent intermediate frames (done=false) during any request/response cycle. The host sanitizes and writes them to aud_audit_events.
Allowed category: Connection only. All other categories are silently dropped.
The AuditEventEmitDto payload follows the same structure as driver emit frames. The host overrides identity fields (actor_type, actor_id, source_id, driver_id, correlation_id). Rate limiting is shared with drivers: 100 events per 60 seconds per socket_id.
Form contract
The connection form shown in DBFlux is built from form_definition returned in Hello.
- The service defines fields/tabs/sections.
- DBFlux validates required fields in UI.
- On connect/save, DBFlux sends collected values through
DbConfig::External.valuesinOpenSessionprofile JSON.
If form_definition.tabs is empty, the connection form will show no driver-specific inputs.
Session lifecycle
HelloOpenSession- request/response operations
CloseSession
OpenSession still returns SessionOpened with metadata. Keep this consistent with Hello metadata.
DBFlux sends the saved profile JSON to OpenSession. For external drivers, the profile config is:
DbConfig::External {
kind: DbKind,
values: HashMap<String, String>,
}
values contains the field values collected from your form_definition.
The service should parse profile_json, expect DbConfig::External, and validate required fields again server-side.
Request/response overview
| Request | Response | Purpose |
|---|---|---|
Hello | Hello | protocol negotiation + driver identity |
OpenSession | SessionOpened | open connection/session |
CloseSession | SessionClosed | close session |
Ping | Pong | liveness |
Execute | ExecuteResult | query execution |
Schema | Schema | schema snapshot |
ListDatabases | Databases | database list |
The protocol also supports browse, CRUD, key-value, and code generation operations. See crates/dbflux_ipc/src/driver_protocol.rs for the full enum set.
Audit emission from drivers (v1.2+)
Drivers that negotiate protocol version v1.2 or higher may emit audit events back to the host as intermediate response frames (done=false). The host sanitizes, rate-limits, and writes them to aud_audit_events.
Opting in
Include DriverCapability::AuditEmit in your Hello response capabilities list. Drivers that do not advertise this capability will have any EmitAuditEvent frames silently discarded by the host.
Sending an audit frame
Emit a DriverResponseEnvelope with done = false and body = DriverResponseBody::EmitAuditEvent(AuditEventEmitDto { .. }) at any point during a request before the terminal response:
DriverResponseEnvelope {
protocol_version: negotiated_version,
request_id: request.request_id,
session_id: request.session_id,
done: false,
body: DriverResponseBody::EmitAuditEvent(AuditEventEmitDto {
ts_ms: chrono::Utc::now().timestamp_millis(),
level: EventSeverityDto::Info,
category: EventCategoryDto::Connection,
action: "session.open".to_string(),
outcome: EventOutcomeDto::Success,
summary: "Database session opened".to_string(),
object_type: None,
object_id: None,
duration_ms: Some(42),
error_code: None,
error_message: None,
details_json: None,
}),
}
Then send the terminal response as usual.
What the host supplies
The host always overrides these fields; do not include them in the DTO (they are intentionally absent from AuditEventEmitDto):
actor_type,actor_id,source_id,driver_id— always set toExternalDriver/rpc:<socket_id>connection_id,database_name— resolved from the active session contextcorrelation_id— one per session, host-generated
Allowed categories
Drivers may emit Connection, Query, and System events. All other categories are silently dropped.
Rate limit
100 events per 60 seconds per socket_id. Excess frames are dropped and counted in AuditService::external_audit_dropped_count().
Error handling
Return structured errors through DriverResponseBody::Error(DriverRpcError { ... }).
Common codes:
InvalidRequestUnsupportedMethodVersionMismatchSessionNotFoundTimeoutCancelledTransportDriverInternal
Use InvalidRequest for malformed profiles/form values and UnsupportedMethod for methods intentionally not implemented. Auth-provider RPC uses the parallel AuthProviderRpcErrorCode set with the same operational meaning (VersionMismatch, UnsupportedMethod, Timeout, Transport, etc.).
Process lifecycle and cleanup
When DBFlux starts a service process itself (via command or the supported default host command), that process is tracked as a managed host.
On DBFlux shutdown:
- all tracked managed hosts are killed (
kill + wait) - hosts started manually outside DBFlux are not tracked and are not killed
This guarantees DBFlux cleans up only the processes it owns.
If a managed host exits early or times out before the socket is ready, DBFlux reports the service id together with a bounded tail of recent stdout/stderr to aid troubleshooting.
Minimal implementation checklist
Your service should:
- bind socket via
interprocess - handle
Helloand return metadata/kind - return a form definition in
Hello - handle
OpenSession/CloseSession - implement at least one useful operation (
Execute) - return
UnsupportedMethodfor non-implemented operations
Recommended:
- validate
DbConfig::External.valuesinOpenSession - return clear
InvalidRequesterrors for missing/invalid form values - keep
Hellometadata andSessionOpenedmetadata consistent - stamp every post-
Helloenvelope with the negotiated version instead of assuming the latest constant
Working example in this repository
Use:
examples/custom_driver/src/main.rsexamples/custom_driver/README.mdexamples/custom_auth_provider/src/main.rsexamples/custom_auth_provider/README.md
Those examples are compatible with the current active driver-service integration model.
Quick test path:
- add a new Driver service in Settings → RPC Services
- point
commandto your built example binary - set
argsto--socket <your-socket-id> - restart DBFlux
- create either a connection (driver example) or an auth profile (auth-provider example) through the UI forms exposed by the service
References
crates/dbflux_ipc/src/driver_protocol.rscrates/dbflux_driver_ipc/src/transport.rscrates/dbflux_driver_host/src/main.rscrates/dbflux/src/app.rscrates/dbflux_driver_ipc/src/driver.rs- RPC services config