kelp-cpp-client is the native client transport for existing Windows clients.
It is C++17, uses gRPC C++, and is built by the same MinGW XP SP2 toolchain as
kelp.exe.
The public surface has three layers:
kelp::client::Client: C++ wrapper around generic gRPC calls.extern "C"ABI inkelp/client.h: method path + serialized protobuf bytes.- Synurang ABI exports in the shared client library (
kelp.dllon Windows): host-loadable Proxy/Admin transports for generated Synurang clients.
That C ABI is intentionally synurang-friendly. Generate typed bindings from
proto/kelp.proto, let the generated layer serialize requests and parse
replies, and wire its transport to kelp_client_unary or kelp_client_stream.
The existing application does not need Rust and does not need to hand-build gRPC
messages at the call site.
Host build:
cmake -S . -B build/native
JOBS="$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)"
JOBS="$((JOBS > 2 ? JOBS - 2 : 1))"
cmake --build build/native --target kelp-client -j"$JOBS"
cmake --build build/native --target kelp-client-ffi -j"$JOBS"XP SP2/Server 2003 MinGW build uses the same dependency inputs as kelp.exe:
make build-mingw-xpDocker release builds include kelp.dll in each Windows archive. The standalone
kelp-client executable is a developer build target; release packages use
kelp.exe client ... instead.
Each Windows release archive ships the architecture-specific client ABI in these forms:
kelp.dll: XP-targeted runtime DLL.kelp.lib: VC++ import library forkelp.dll.kelp.dll.a/libkelp.dll.a: MinGW import library.libkelp-client.a: MinGW static archive.
kelp.lib is not a static library. A VC++ host links against it, but still
loads kelp.dll at runtime. Build the VC++ host as 32-bit x86 and
define KELP_CLIENT_USE_DLL before including kelp/client.h:
cl /EHsc /DKELP_CLIENT_USE_DLL /I clients\cpp\include app.cpp kelp.libVC++ hosts can also avoid the import library and load the same C ABI with
LoadLibrary/GetProcAddress. Do not link libkelp-client.a with VC++; that
archive is for MinGW.
build/native/kelp client \
--target localhost:5002 \
--tls-root /tmp/kelp.crt \
--tls-client-cert /tmp/client.crt \
--tls-client-key /tmp/client.key \
--api-key dev-secret \
statusRaw protobuf call:
build/native/kelp client \
--target localhost:5002 \
--tls-root /tmp/kelp.crt \
--api-key dev-secret \
raw-unary \
--method /kelp.v1.Admin/GetProxyStatus \
--request-bin request.pb \
--response-bin response.pbSimple COM call:
build/native/kelp client \
--target localhost:5001 \
--tls-root /tmp/kelp.crt \
--api-key dev-secret \
call --prog-id ADODB.Recordset --member State --property-getcall opens a temporary session when --session-id is omitted. The ProgID must
already be allowed on the server, for example with --allow ADODB.Recordset:*
or Admin.AddComponent.
Use the Proxy listener for call and other kelp.v1.Proxy methods. Use the
Admin listener for status and raw kelp.v1.Admin methods.
When the server uses managed TLS, pass the CA certificate as --tls-root. If
the server also requires client certificates, generate client mTLS material with:
kelp tls client create app-client --path /tmp/kelp-tlsThen pass /tmp/kelp-tls/kelp-ca.crt as --tls-root and the generated
clients/app-client.crt/key files as --tls-client-cert and
--tls-client-key. Leave the client cert/key empty for server TLS only.
typedef struct KelpClient KelpClient;
typedef struct KelpClientStream KelpClientStream;
const char* kelp_client_version(void);
KelpClient* kelp_client_connect(const char* target,
const char* api_key,
const char* tls_root_cert_path,
int plaintext);
KelpClient* kelp_client_connect_mtls(const char* target,
const char* api_key,
const char* tls_root_cert_path,
const char* tls_client_cert_path,
const char* tls_client_key_path,
int plaintext);
KelpClient* kelp_client_connect_mtls_with_key_passphrase(
const char* target,
const char* api_key,
const char* tls_root_cert_path,
const char* tls_client_cert_path,
const char* tls_client_key_path,
const char* tls_client_key_passphrase,
int plaintext);
void kelp_client_free(KelpClient* client);
int kelp_client_unary(KelpClient* client,
const char* method,
const unsigned char* request,
size_t request_len,
unsigned char** response,
size_t* response_len);
int kelp_client_stream(KelpClient* client,
const char* method,
kelp_client_next_message_fn next,
void* next_ctx,
kelp_client_on_message_fn on_message,
void* on_message_ctx);
int kelp_client_stream_open(KelpClient* client,
const char* method,
KelpClientStream** stream);
int kelp_client_stream_send(KelpClientStream* stream,
const unsigned char* request,
size_t request_len);
int kelp_client_stream_close_send(KelpClientStream* stream);
int kelp_client_stream_recv(KelpClientStream* stream,
unsigned char** response,
size_t* response_len);
void kelp_client_stream_close(KelpClientStream* stream);
const char* kelp_client_stream_last_error(KelpClientStream* stream);
int kelp_client_stream_last_grpc_code(KelpClientStream* stream);
void kelp_client_free_buffer(unsigned char* ptr);
const char* kelp_client_last_error(KelpClient* client);
int kelp_client_last_grpc_code(KelpClient* client);kelp_client_stream is the callback convenience API: it writes request messages
from next, half-closes, then delivers response messages to on_message. The
kelp_client_stream_open/send/recv/close functions expose the same gRPC stream
as an explicit handle for hosts that need pull-style receive loops. Both forms
cover Kelp's current streaming APIs, including recordset downloads/uploads and
COM+ export/install payloads.
kelp.dll also exports loopback browser helpers for native hosts that embed a
browser or WebView but want the DLL to own upstream TLS/mTLS:
int KelpSynurang_StartStaticProxy(...);
void KelpSynurang_StopStaticProxy(void);
int KelpSynurang_StartAdminDashboardProxy(...);
void KelpSynurang_StopAdminDashboardProxy(void);
int KelpSynurang_ProbeServerCertificatePem(...);KelpSynurang_StartAdminDashboardProxy targets the kelp.exe --web-listen
endpoint. The host navigates the browser to
http://127.0.0.1:<port>/__kelp/<token>/; dashboard assets and gRPC-Web
Admin/Proxy calls are forwarded upstream over certificate-verified TLS/mTLS.
See CLIENT-STATIC-PROXY.md for the full
ABI and security notes.
KelpSynurang_ProbeServerCertificatePem connects with kelp.dll's OpenSSL
stack and copies the presented peer certificate PEM bundle into the caller's
buffer. It intentionally disables server-certificate verification so native
hosts can display or save a certificate before the configured root is available.
The shared client library exports the Synurang plugin ABI directly:
char* Synurang_Invoke_Proxy(const char* method,
const char* data,
int32_t data_len,
int32_t* resp_len);
char* Synurang_Invoke_Admin(const char* method,
const char* data,
int32_t data_len,
int32_t* resp_len);
uint64_t Synurang_Stream_Proxy_Open(const char* method);
uint64_t Synurang_Stream_Admin_Open(const char* method);
int32_t Synurang_Stream_Send(uint64_t handle, const char* data, int32_t data_len);
char* Synurang_Stream_Recv(uint64_t handle, int32_t* resp_len, int32_t* status);
void Synurang_Stream_CloseSend(uint64_t handle);
void Synurang_Stream_Close(uint64_t handle);
void Synurang_Free(void* ptr);Configure the library before invoking through Synurang:
KelpSynurang_ConfigureProxy("localhost:5001", "proxy-secret",
"/tmp/ca.crt", NULL, NULL, 1);
KelpSynurang_ConfigureAdmin("127.0.0.1:5002", "admin-secret",
"/tmp/ca.crt", NULL, NULL, 1);The final tls argument is 1 for server TLS and 0 for an insecure h2c
development listener. With tls=1, mTLS is enabled only when both
tls_client_cert_path and tls_client_key_path are non-empty. Use the
KelpSynurang_Configure*WithKeyPassphrase variants when the client private key
is encrypted.
Or use environment variables before loading the library:
export KELP_PROXY_TARGET=localhost:5001
export KELP_ADMIN_TARGET=127.0.0.1:5002
export KELP_PROXY_API_KEY=proxy-secret
export KELP_ADMIN_API_KEY=admin-secret
export KELP_TLS_ROOT_CERT_PATH=/tmp/ca.crt
export KELP_TLS_CLIENT_CERT_PATH=/tmp/client.crt
export KELP_TLS_CLIENT_KEY_PATH=/tmp/client.key
export KELP_TLS_CLIENT_KEY_PASSPHRASE=optional-passphrase
export KELP_TLS=1Use KELP_TLS=0 for an insecure development listener. Service-specific
overrides are also accepted: KELP_PROXY_TLS, KELP_ADMIN_TLS,
KELP_PROXY_TLS_ROOT_CERT_PATH, KELP_ADMIN_TLS_ROOT_CERT_PATH,
KELP_PROXY_TLS_CLIENT_CERT_PATH, KELP_ADMIN_TLS_CLIENT_CERT_PATH,
KELP_PROXY_TLS_CLIENT_KEY_PATH, KELP_ADMIN_TLS_CLIENT_KEY_PATH,
KELP_PROXY_TLS_CLIENT_KEY_PASSPHRASE,
KELP_ADMIN_TLS_CLIENT_KEY_PASSPHRASE, KELP_PROXY_TIMEOUT_MS, and
KELP_ADMIN_TIMEOUT_MS.
TLS configuration errors in the client process are returned through the
Synurang error response rather than printed by kelp.dll. For the kelp client
CLI, those errors are printed to stderr.
For C++ lite clients, the Kelp package includes the generated lite header and
the header-only Synurang lite runtime under clients/cpp/include:
generated/kelp_lite.hppis generated fromproto/kelp.protobyprotoc-gen-synurang-ffi-rs.synurang_lite.hppis the shared C++ lite runtime copied fromgithub.com/ivere27/synurangat the pinnedSYNURANG_REVin the rootMakefile.
Regenerate both with make codegen-synurang. Keep those two headers on the
same Synurang revision; the generated Kelp header includes the runtime header.
#include "kelp/synurang_transport.hpp"
#include "generated/kelp_lite.hpp"
kelp::synurang_transport::ClientTransport transport(
"localhost:5001", "proxy-secret", "/tmp/ca.crt", 0);
kelp::v1::ProxyLite proxy(&transport);