Skip to content

chore(sdk): Lets CreateTDF use ChunkedWriter - #3946

Merged
dmihalcik-virtru merged 2 commits into
mainfrom
dspx-2604-17-createtdf-delegates
Sep 24, 2026
Merged

dmihalcik-virtru merged 2 commits into
mainfrom
dspx-2604-17-createtdf-delegates

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Part 17 of 20 in the DSPX-2604 re-cut. Base branch: dspx-2604-base-17.

This stack replaces #3782 / #3865 / #3921, which stay open and untouched
until it lands. Nothing here is a rebase of those branches — the work was
re-cut from the ticket so each PR stands on its own.

Proposed Changes

CreateTDFContext and the chunked writer had grown two full implementations of
the same thing: build a policy, split the DEK, wrap each share to its KAS,
encrypt segments, accumulate an aggregate hash, sign assertions, emit a
manifest. Two copies of TDF construction is one too many — every spec change
has to land twice, and the second copy is the one that gets forgotten.

CreateTDF now delegates. It keeps the parts that are genuinely its own — the
KAO template, autoconfigure, the input-size resolution and read loop from the
previous commit — and hands each segment to the chunked writer, which owns
manifest assembly for both paths from here on.

The two paths differ in when key access is resolved, so that is what the
writer is now parameterized on. The chunked writer defers to a KeySplitter at
Finalize, because a caller may still be adding attributes while segments are
in flight. CreateTDF cannot: it knows its attributes up front and wants an
unreachable KAS to fail the call before a single payload byte reaches the
output writer. Both are expressed as a keyAccessResolver, with the DEK now
injectable so CreateTDF can wrap it ahead of time and hand the writer a
staticKeyAccess.

Everything downstream of that split is shared: resolvePolicyAndKeyAccess and
buildKeyAccessObjects replace prepareManifest's inline loop and the chunked
writer's buildChunkedPolicy/buildChunkedKeyAccessObjects, so both paths now
emit byte-identical policy and key access for the same attributes.

Two things fall out of the unification, both moving the chunked writer onto
the shipped classic behavior:

  • With zero attributes the policy body's "dataAttributes" and "dissem" are
    now null rather than []. createPolicyObjectFromFQNs initializes them
    inside the attribute loop; the deleted buildChunkedPolicy did so
    unconditionally. The classic path has always emitted null here.

  • A KAS named by a split but missing a public key is still rejected rather
    than skipped (the check moved into buildKeyAccessObjects), but the error
    now names the missing PEM rather than the absent map entry — the two cases
    were indistinguishable in practice and only the outcome matters.

The writer also gained an explicit segment size. It previously reported the
first segment's actual length as defaultSegmentSize, which is only correct
when every segment is full; a single-segment TDF would advertise a short
default. CreateTDF knows the configured size and now says so.

TDFObject loses aesGcm and payloadKey, which only ever existed to carry state
between prepareManifest and the encrypt loop.

Since #3940 (revised) fixed the chunked writer's root to HS256 and its
segments to GMAC, there is nothing to plumb through here: CreateTDF stops
passing TDFConfig.rootIntegrityAlg and segmentIntegrityAlg to the writer.
Those were already the only values the defaults could hold and no exported
option set either, so the manifest is unchanged.

Deliberately not in this commit: removing enableEncryption, tdfFormat,
readActionName, and the two now-unread integrity fields, which are dead but
unrelated; they are a separate cleanup (#3947).

Testing: the existing TDFSuite round trips pin byte-level output across
segment sizes, target modes, and multi-KAS splits, and pass unchanged. Also
verified against the streaming-input and input-size
coverage added in the previous commit, the experimental chunked writer suite,
and cross-module builds of examples, otdfctl, service, and tests-bdd.

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

cd sdk && go test ./... -race

The existing TDFSuite round trips pin byte-level output across segment
sizes, target modes and multi-KAS splits, and pass unchanged — that is the
main assurance here.

This touches key access construction on the shipped path, so it wants a
cross-SDK run before merge. This is the branch to pin it to for the whole
writer-delegation half of the stack: xtest drives the Go side through
otdfctl → SDK.CreateTDF, and this is the first commit where that call
reaches the chunked writer at all.

gh workflow run xtest.yml --repo opentdf/tests --ref main \
  -f platform-ref=dspx-2604-17-createtdf-delegates \
  -f otdfctl-ref=dspx-2604-17-createtdf-delegates \
  -f java-ref=main -f js-ref=main

otdfctl-ref must name the branch, not main — otherwise the CLI is built
against main's sdk/ and the run passes without executing any of this. The
job label should read go@dspx-2604-17-createtdf-delegates.

The full DSPX-2604 stack — 20 PRs
# PR Based on
01 #3930 chore: bump go.work toolchain to go1.25.12 and simplify an rt_test condition main
02 #3931 feat(sdk): make the zipstream clock injectable for deterministic ZIP output main
03 #3932 fix(sdk): reject a zipstream write set that omits segment 0 #3931
04 #3933 fix(sdk): map ReadAt plaintext offsets from cumulative segment sizes main
05 #3934 chore(sdk): extract integrityAlgorithmString, createPolicyBinding, signAssertions main
06 #3935 chore(sdk): add direct tests for createKeyAccess, encryptMetadata and tdfSalt main
07 #3936 fix(sdk): fill each segment with io.ReadFull and size the buffer to the input main
08 #3937 chore(cli): move streaming IO helpers into pkg main
09 #3938 fix(cli): stream encrypt instead of buffering the whole payload #3937
10 #3939 fix(cli): stream decrypt and inspect instead of buffering #3938
11 #3940 feat(sdk): add a chunked segment writer (experimental) dspx-2604-base-11 = #3932 + #3934 + #3935
12 #3941 fix(sdk): stop GetManifest from splitting the key under the lock #3940
13 #3942 fix(sdk): reject a chunked split naming a KAS with no resolved public key #3941
14 #3943 chore(sdk): alias experimental/tdf manifest and assertion types #3942
15 #3944 fix(sdk): emit spec-compliant key access in experimental/tdf and delegate Writer #3943
16 #3945 feat(sdk): accept io.Reader in CreateTDF and drop the 64 GB payload cap #3936
17 #3946 chore(sdk): rewrite CreateTDF on top of the chunked writer dspx-2604-base-17 = #3944 + #3945
18 #3947 chore(sdk): drop dead TDFConfig fields and deprecate the TDFFormat enum #3946
19 #3948 fix(cli): drop the encrypt-side stdin spool dspx-2604-base-19 = #3947 + #3939
20 #3949 feat(sdk): graduate the chunked writer to stable API #3948

Reviewable in parallel right now, since they sit directly on main and depend on
nothing else: 01, 02, 04, 05, 06, 07, 08.

Why three PRs have a dspx-2604-base-* base. A GitHub PR takes one base branch,
but 11, 17 and 19 each build on more than one parent. The base-* branches are empty
merge commits that exist only to join those parents so the PR diff shows exactly its
own change and nothing else. They contain no code, have no PR of their own, and go
away once their parents land — retarget the child onto main at that point.

Wants a cross-SDK xtest run before merge: 15, 17 (and therefore 20). They touch
the KAS wire format. Dispatch it against 17 or 20, never 15 on its own: xtest drives
the Go side through otdfctl -> SDK.CreateTDF, and 17 is the first commit where
that call reaches the rewritten writer. Set otdfctl-ref to the same branch as
platform-ref -- it defaults to main, which builds the CLI against main's sdk/
and makes the run vacuous.

Red checks you may see are network flakes, not this stack. Four distinct ones hit
this batch and all clear on re-run: golangci-lint config verify timing out on
https://golangci-lint.run/.../golangci.v2.8.jsonschema.json (fails the whole go (<module>) job and fail-fast cancels its siblings), the bats installer getting a 403,
Docker Hub timing out on keycloak/keycloak:26.4, and buf reporting "the server
hosted at that remote is unavailable" while the Java SDK generates sources. The
govulncheck step also emits ##[error] annotations against the go1.25.11 stdlib, but
it is continue-on-error: true and never fails a job — 01 bumps the toolchain and
clears those annotations.

Summary by CodeRabbit

  • Refactor
    • TDF creation now uses a consistent process for resolving key access and encrypting payload segments.
    • Key shares are validated and checked for successful reconstruction before payload data is written.
    • Existing encrypted output behavior is preserved, including segment sizing based on the configured size or the first segment when no size is configured.

@dmihalcik-virtru
dmihalcik-virtru requested review from a team as code owners September 1, 2026 02:57
@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 21 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: bec259c5-c0aa-402d-ac8e-e9cd6555564f

📥 Commits

Reviewing files that changed from the base of the PR and between 9318a77 and 0a6f0c7.

📒 Files selected for processing (1)
  • sdk/key_splitter.go

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 576966fc-e412-4932-a384-deb8594a0870

📥 Commits

Reviewing files that changed from the base of the PR and between 20ff505 and 9318a77.

📒 Files selected for processing (5)
  • sdk/chunked_test.go
  • sdk/chunked_writer.go
  • sdk/key_splitter.go
  • sdk/tdf.go
  • sdk/tdf_helpers_test.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

TDF creation now uses the chunked writer for segment encryption and manifest finalization. Key-access resolution builds policy strings and key access objects from DEK shares. The writer supports a preset DEK and configurable segment sizing.

Changes

TDF creation flow

Layer / File(s) Summary
Resolve policy and key access
sdk/key_splitter.go, sdk/tdf.go, sdk/chunked_writer.go, sdk/chunked_test.go, sdk/tdf_helpers_test.go
Key-access resolvers build policy strings and key access objects from split shares. TDF creation groups template entries by split ID and divides the DEK into shares. Tests now use the split-share representation and string policy values.
Wire key access into the chunked writer
sdk/chunked_writer.go
The writer accepts a preset DEK and key-access resolver. Manifest construction uses the resolver, and segment-size fields use the configured size when positive.
Route TDF creation through chunked writing
sdk/tdf.go
TDF creation resolves key access before writing payload bytes, sends encrypted segments through the chunked writer, and uses its final manifest and data.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SDK as SDK.CreateTDF
  participant Resolver as resolveKeyAccess
  participant Writer as chunkedWriter
  participant Output as TDF output
  SDK->>Resolver: Resolve DEK shares and key access
  SDK->>Writer: WriteSegment for each payload segment
  Writer->>Output: Return encrypted segment data
  SDK->>Writer: Finalize with TDF options
  Writer->>Output: Return manifest and final data
Loading

Suggested reviewers: sujankota

Merge Risk: ⚪ Minimal · up to 9318a

TDF creation now reuses the chunked writer for encryption and manifest building. No concrete regression was found in policy serialization, key access, segment sizing, or integrity settings, and the change appears ready to merge after normal CI.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 5 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: updating CreateTDF to use ChunkedWriter. It is concise and specific.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the keys in rows
Then watches each encrypted segment go
The writer gathers data tight
The manifest joins the file at night
Soft paws applaud the finished flow

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added comp:sdk A software development kit, including library, for client applications and inter-service communicati size/l labels Sep 1, 2026
This was referenced Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 260.573394ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 135.553264ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 417.368815ms
Throughput 239.60 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.737562519s
Average Latency 436.48954ms
Throughput 114.32 requests/second

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 275.391918ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 139.401877ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 443.371855ms
Throughput 225.54 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.279606203s
Average Latency 442.099151ms
Throughput 112.92 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 215.383156ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 124.946883ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 439.466545ms
Throughput 227.55 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 56.745860101s
Average Latency 565.987724ms
Throughput 88.11 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 227.248752ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 122.327995ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 445.028556ms
Throughput 224.70 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 1m0.35277253s
Average Latency 602.027396ms
Throughput 82.85 requests/second

@dmihalcik-virtru
dmihalcik-virtru force-pushed the dspx-2604-17-createtdf-delegates branch from 79e745d to a5d2340 Compare September 24, 2026 14:50
@dmihalcik-virtru
dmihalcik-virtru requested a review from a team as a code owner September 24, 2026 14:50
@dmihalcik-virtru
dmihalcik-virtru deleted the branch main September 24, 2026 14:51
@dmihalcik-virtru
dmihalcik-virtru changed the base branch from dspx-2604-base-17 to dspx-2604-15-delegate-writer September 24, 2026 14:51
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 235.192464ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 136.61755ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 430.3713ms
Throughput 232.36 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 1m1.348476641s
Average Latency 612.121672ms
Throughput 81.50 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • otdfctl
  • service
  • tests-bdd

See the workflow run for details.

CreateTDFContext and the chunked writer had grown two full implementations of
the same thing: build a policy, split the DEK, wrap each share to its KAS,
encrypt segments, accumulate an aggregate hash, sign assertions, emit a
manifest. Two copies of TDF construction is one too many — every spec change
has to land twice, and the second copy is the one that gets forgotten.

CreateTDF now delegates. It keeps the parts that are genuinely its own — the
KAO template, autoconfigure, the input-size resolution and read loop from the
previous commit — and hands each segment to the chunked writer, which owns
manifest assembly for both paths from here on.

The two paths differ in when key access is resolved, so that is what the
writer is now parameterized on. The chunked writer defers to a KeySplitter at
Finalize, because a caller may still be adding attributes while segments are
in flight. CreateTDF cannot: it knows its attributes up front and wants an
unreachable KAS to fail the call before a single payload byte reaches the
output writer. Both are expressed as a keyAccessResolver, with the DEK now
injectable so CreateTDF can wrap it ahead of time and hand the writer a
staticKeyAccess.

Everything downstream of that split is shared: resolvePolicyAndKeyAccess and
buildKeyAccessObjects replace prepareManifest's inline loop and the chunked
writer's buildChunkedPolicy/buildChunkedKeyAccessObjects, so both paths now
emit byte-identical policy and key access for the same attributes. The
splitter-contract checks move with them into splitterKeyAccess.resolve, which
is now the single place a third-party KeySplitter's output is validated and
verified to XOR back to the DEK before anything is wrapped.

Two things fall out of the unification, both moving the chunked writer onto
the shipped classic behavior:

  - With zero attributes the policy body's "dataAttributes" and "dissem" are
    now null rather than []. createPolicyObjectFromFQNs initializes them
    inside the attribute loop; the deleted buildChunkedPolicy did so
    unconditionally. The classic path has always emitted null here.

  - A KAS named by a split but missing a public key is still rejected rather
    than skipped (the check moved into buildKeyAccessObjects), but the error
    now names the missing PEM rather than the absent map entry — the two cases
    were indistinguishable in practice and only the outcome matters.

The writer also gained an explicit segment size. It previously reported the
first segment's actual length as defaultSegmentSize, which is only correct
when every segment is full; a single-segment TDF would advertise a short
default. CreateTDF knows the configured size and now says so.

TDFObject loses aesGcm and payloadKey, which only ever existed to carry state
between prepareManifest and the encrypt loop.

Deliberately not in this commit: removing enableEncryption, tdfFormat, and
readActionName, which are dead but unrelated; they are a separate cleanup.

Testing: the existing TDFSuite round trips pin byte-level output across
integrity algorithms, segment sizes, target modes, and multi-KAS splits, and
pass unchanged. Also verified against the streaming-input and input-size
coverage added in the previous commit, the experimental chunked writer suite,
and cross-module builds of examples, otdfctl, service, and tests-bdd.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>

wip: resolve conflict
@dmihalcik-virtru
dmihalcik-virtru force-pushed the dspx-2604-17-createtdf-delegates branch from a5d2340 to 9318a77 Compare September 24, 2026 15:10
@dmihalcik-virtru
dmihalcik-virtru changed the base branch from dspx-2604-15-delegate-writer to main September 24, 2026 15:10
sujankota
sujankota previously approved these changes Sep 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 252.568497ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 133.235913ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 420.307136ms
Throughput 237.92 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 57.551605702s
Average Latency 573.899595ms
Throughput 86.88 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • otdfctl
  • service
  • tests-bdd

See the workflow run for details.

@dmihalcik-virtru dmihalcik-virtru changed the title chore(sdk): rewrite CreateTDF on top of the chunked writer chore(sdk): Lets CreateTDF use ChunkedWriter Sep 24, 2026
Comment thread sdk/key_splitter.go Outdated
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • otdfctl
  • service
  • tests-bdd

See the workflow run for details.

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 258.329257ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 143.081392ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 444.825693ms
Throughput 224.81 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 59.869523232s
Average Latency 597.307127ms
Throughput 83.51 requests/second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:sdk A software development kit, including library, for client applications and inter-service communicati size/l

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants