Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

README.md

v0.1.0-build.85 Latest Commit ede38f5

Size
7.9 KB
Lines
159
Language
Markdown
SHA-256
84350ec87a52…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

CKey for .NET

CKey optical codes for .NET. The package encodes payloads, renders native pixels (and exports PNG, SVG and PDF), and scans grayscale camera frames. It is a thin P/Invoke wrapper over the CKey C core and does not reimplement encoding, decoding or rendering.

Package Target Contents
CKey net8.0, netstandard2.0 Headless core: encode, render, certify, scan, payload helpers, telemetry. No UI dependency.
CKey.Wpf net8.0-windows Optional adapter: WriteableBitmap output and BitmapSource scanning.

Native libraries ship inside the package as runtimes/<rid>/native/ assets for win-x64, linux-x64, osx-x64 and osx-arm64. On net8.0 the bindings use [LibraryImport] and are trim- and AOT-compatible. On netstandard2.0 they use [DllImport]. .NET Framework apps get ckeynative.dll (win-x64) copied by the package's build targets.

Install

dotnet add package CKey
dotnet add package CKey.Wpf   # optional, WPF only
<PackageReference Include="CKey" Version="0.1.0-build.85" />

The namespace is OpenCKey.

Encode and render

using OpenCKey;

CKeySymbol symbol = CKeySymbol.Encode("https://openckey.org/hello");      // version 0 = smallest that fits
CKeySymbol big = CKeySymbol.Encode(bytes, version: 12, level: EccLevel.Q, layout: Layout.Print);

// Native pixels: size x size RGBA8, straight alpha, no padding.
CKeyImage image = symbol.RenderRgba(512, RasterPolicy.Coverage, transparent: false, style: CKeyStyle.Concept);
byte[] rgba = image.Rgba;

// Exports
byte[] png = symbol.ToPng(1024);
string svg = symbol.ToSvg(scale: 8, style: CKeyStyle.Plain);
byte[] pdf = symbol.ToPdf(widthPt: 144);                 // 2 inches wide, vector
CKeyGrayImage print = symbol.RenderPrint(1.0, 600);      // 1 inch at 600 dpi, pure black/white

CKeySymbol is a managed copy of the C symbol struct. It holds no native resources, so it needs no Dispose.

Styles and optical safety

var style = CKeyStyle.Concept;                // or CKeyStyle.Plain, or new CKeyStyle()
style.Modules = ModuleShape.Circle;
style.Ink = CKeyColor.Parse("#1a1a2e");

if (!symbol.CheckStyle(style, out string why)) Console.WriteLine(why);

// Certification renders the style, degrades it with a camera model and decodes it.
CKeyCertification c = symbol.Certify(style);
// A transparent symbol needs to know what it sits on:
c = symbol.Certify(null, transparent: true, backdropColours: new[] { CKeyColor.White });

Under the default StylePolicy.Strict, renderers refuse a style that fails the checks and throw CKeyException. With StylePolicy.Adaptive, the style is repaired instead (see CKeyCertification.AdjustedStyle). StylePolicy.Override draws the style as given.

WPF

using OpenCKey.Wpf;

imageControl.Source = symbol.ToWriteableBitmap(512, transparent: true);

Scan

// gray: one 8-bit luma plane (for example the Y plane of NV12), any stride.
CKeyResult? result = CKeyDecoder.Decode(gray, width, height, stride);
if (result != null)
{
    Console.WriteLine(result.ProfileName);    // "Profile 0 · v3"
    Console.WriteLine(result.Text ?? result.PayloadHex);
    CKeyPayloadAction? action = result.Action;  // e.g. Kind = Web, Label = "Open openckey.org"
    // Show the payload and the action; act only when the user confirms.
}

The pipeline is the iOS reader's. It finds up to six border candidates (CKeyDecoder.FindBorders). For each one it refines the quadrilateral to the Profile 0 field and decodes, then tries the Profile 1, 2 and 3 fields. Detection and decode are also available separately, via FindBorders and DecodeCandidate. Scanning never needs the renderer, telemetry or a network.

For continuous camera scanning, use a session. It also drives the telemetry session counters:

using var session = new CKeyScanSession();       // starts a scan session
foreach (Frame f in camera.Frames)
{
    if (session.ProcessFrame(f.Gray, f.Width, f.Height, f.Stride) is { } r) { Show(r); break; }
}
// Dispose ends the session (failed if nothing decoded). Restart() is "scan again".

CKeyPayload.Classify(text) maps text to an action using spec/CKey-Payload-Conventions.md. Only https/http, mailto, tel, sms, geo, address: and vCards are recognized; anything else is plain text. CKeyPayload.ReadableText(bytes) applies the convention's text rule.

Safe Actions

A Safe Action (ckey-action:1:…) is a signed code from a known site for this app. CKeySafeActions verifies it with the core and decides whether it runs; the app only pins keys and registers handlers.

var actions = new CKeySafeActions("com.viadere")
    .Trust("citeory.com", "2026-11", "<64 hex digits>", "https://citeory.com/api/ckey/requests/{request_id}")
    .Register("open.library", new[] { "citeory.com" }, a => OpenItem(a.ParamsText))
    .Register("signin.request", new[] { "citeory.com" }, CKeySafeActionScope.CrossDevice, null, a => ApproveSignInAsync(a));

var outcome = await actions.HandleAsync(text);
switch (outcome.Kind)
{
    case CKeySafeActionOutcomeKind.NotASafeAction: ShowPayload(text); break;          // the ordinary payload rules
    case CKeySafeActionOutcomeKind.Performed: break;                                   // same-device: already ran
    case CKeySafeActionOutcomeKind.NeedsConfirmation:
        await OpenCKey.Wpf.CKeySafeActionPrompt.ShowAsync(window, outcome.Request!); break; // or CKeySafeActionConsolePrompt.AskAsync
    case CKeySafeActionOutcomeKind.Rejected: ShowUnverified(outcome.Reason); break;
}

Same-device actions run at once. Cross-device actions run only from an SDK prompt, which shows the verified issuer and who is asking; there is no other way to approve one. Anything that fails a check runs nothing; show that the code claims to be a Safe Action but could not be verified, with the reason. See spec/CKey-Safe-Actions.md.

Telemetry

The default mode is 1. In mode 1 the SDK counts scan sessions, frames, decodes per profile and coarse durations. Roughly once an hour it sends one anonymous aggregate batch to https://openckey.org/api/telemetry/v1/batch. It never sends payloads, URLs, user or device identifiers, or location. Sending happens on a thread-pool timer and never blocks or delays scanning. Only CKeyScanSession records anything; CKeyDecoder and rendering do not. See docs/adoption-and-telemetry.md.

To opt out:

CKey.Telemetry = 0;   // off: nothing counted, nothing sent, counters purged
CKey.Telemetry = 2;   // local only: counters via CKeyTelemetry.Snapshot(), never sent
CKey.Telemetry = 1;   // default

Any other value throws ArgumentOutOfRangeException, and the mode stays unchanged.

CKeyTelemetry.FlushAsync() tries a send immediately, for example when your app is deactivated or moves to the background (the hourly limit still applies). ExportState() and ImportState() persist counters across restarts if you want that; persistence is off unless you store the state. A native library built with -DCKEY_DOTNET_TELEMETRY=OFF contains no telemetry code at all. In that case CKeyTelemetry.IsAvailable is false and the mode reads 0.

Disclose telemetry according to your own app-store and legal requirements.

Building from source

# Native library for this machine -> sdk/dotnet/artifacts/native/<rid>/
cmake -S sdk/dotnet/native -B build-dotnet -DCMAKE_BUILD_TYPE=Release -DCKEY_DOTNET_RID=linux-x64
cmake --build build-dotnet --config Release

# Check the C struct layouts the C# interop mirrors
./build-dotnet/ckey_layout_probe | diff sdk/dotnet/native/tools/layout_expected.txt -

dotnet test sdk/dotnet/tests/CKey.Tests -p:CKeyHostRid=linux-x64
dotnet pack sdk/dotnet/src/CKey -c Release     # add -p:CKeyRequireAllNatives=true to insist on all four RIDs

The package version comes from the repository's VERSION file.

License

MIT.