Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

Kelp C++ Client

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 in kelp/client.h: method path + serialized protobuf bytes.
  • Synurang ABI exports in the shared client library (kelp.dll on 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.

Build

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-xp

Docker 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.

Windows Artifacts

Each Windows release archive ships the architecture-specific client ABI in these forms:

  • kelp.dll: XP-targeted runtime DLL.
  • kelp.lib: VC++ import library for kelp.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.lib

VC++ 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.

CLI

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 \
  status

Raw 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.pb

Simple 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-get

call 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-tls

Then 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.

C ABI

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.

Browser Proxies

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.

Synurang

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=1

Use 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.hpp is generated from proto/kelp.proto by protoc-gen-synurang-ffi-rs.
  • synurang_lite.hpp is the shared C++ lite runtime copied from github.com/ivere27/synurang at the pinned SYNURANG_REV in the root Makefile.

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);