diff --git a/SPEC.md b/SPEC.md index 3d054c8..cae855b 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# openkal Specification, version 0.6 +# openkal Specification, version 0.7 ## 1. Scope @@ -38,13 +38,14 @@ provides an interface in whole or not at all. | `openkal.memory` | a region of the address space | core | | `openkal.env` | the parameters a program receives at inception | standard | | `openkal.time` | a time source | standard | +| `openkal.random` | a source of unpredictable bytes | optional | | `openkal.fs` | a directory, and an open file | standard | | `openkal.process` | a program image that has been started | standard | | `openkal.task` | an execution context, and a suspension primitive | standard | | `openkal.exec` | a region of the address space a program may execute | optional | | `openkal.event` | readiness of a set of resources | reserved | -Version 0.6 specifies the core, standard and optional interfaces. The reserved +Version 0.7 specifies the core, standard and optional interfaces. The reserved row is not specified, and its name shall not be used for other purposes. *Core* denotes an interface every implementation provides. *Standard* denotes @@ -152,6 +153,7 @@ implementation provides neither. | `openkal.fs` | `openkal.fs` | `openkal/fs.h` | | `openkal.process` | `openkal.process` | `openkal/process.h` | | `openkal.task` | `openkal.task` | `openkal/task.h` | +| `openkal.random` | `openkal.random` | `openkal/random.h` | | `openkal.exec` | `openkal.exec` | `openkal/exec.h` | `openkal.h` includes every header, for a consumer that uses several. diff --git a/SURFACE.txt b/SURFACE.txt index 2fb7dd2..c33abe7 100644 --- a/SURFACE.txt +++ b/SURFACE.txt @@ -33,6 +33,9 @@ kal_time_monotonic_granularity kal_time_props kal_time_sleep kal_time_wall +# openkal.random +kal_random_fill +kal_random_props # openkal.fs kal_fs_close_dir kal_fs_close_file diff --git a/conformance/mcpp.toml b/conformance/mcpp.toml index 4788f8a..0526dc5 100644 --- a/conformance/mcpp.toml +++ b/conformance/mcpp.toml @@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal" # What it depends upon is therefore openkal and the language. The formatting in # okc.report is the price of that, and it is sixty lines. [dependencies] -openkal = "0.6.0" +openkal = "0.7.0" # The implementation under examination is not named here. # @@ -56,6 +56,7 @@ default = ["core"] core = [] env = [] time = [] +random = [] fs = [] process = ["fs"] # a program is started relative to a directory task = [] @@ -74,7 +75,23 @@ standard = ["hosted"] # rather than an observation that did not hold. The caller names this set for an # implementation that provides these interfaces, and omits it for one that does # not --- which is the same choice the implementation itself made. -optional = ["exec"] +# ⚠️ `random` IS HERE AND NOT IN `hosted`, AND THE NOTE ABOVE ALREADY SAID WHY. +# +# It was put in `hosted` first, and the suite then failed to LINK against every +# backend that had not yet implemented it: +# +# lld-link: error: undefined symbol: kal_random_fill +# +# — which is exactly the outcome the paragraph above warns about: a report that +# is a link failure rather than an observation that did not hold. Clause 6.1 +# makes an absent interface not a deviation, so a set that demands it turns a +# permitted choice into a build error. +# +# `random` has the same shape as `exec`: every hosted platform provides it, a +# bare-metal one provides it only if its board has a source, and neither is a +# deviation. The caller names this set for an implementation that provides +# these interfaces and omits it for one that does not. +optional = ["exec", "random"] # The kinds of examination. Behaviour is always performed; the other three are # selected, because each costs time that a reader running the suite to answer diff --git a/conformance/src/declarations.c b/conformance/src/declarations.c index df8c8b9..10a3a4b 100644 --- a/conformance/src/declarations.c +++ b/conformance/src/declarations.c @@ -64,6 +64,8 @@ void okc_declarations_c(void) (void)sizeof(&kal_process_spawn); (void)sizeof(&kal_process_terminate); (void)sizeof(&kal_process_wait); + (void)sizeof(&kal_random_fill); + (void)sizeof(&kal_random_props); (void)sizeof(&kal_stderr); (void)sizeof(&kal_stdin); (void)sizeof(&kal_stdout); diff --git a/conformance/src/sections/random.cpp b/conformance/src/sections/random.cpp new file mode 100644 index 0000000..13646c9 --- /dev/null +++ b/conformance/src/sections/random.cpp @@ -0,0 +1,70 @@ +module okc.random; + +import openkal.types; +import openkal.random; +import okc.report; +import okc.spec; + +namespace okc::random { + +void run() { + heading("openkal.random"); +#ifndef MCPP_FEATURE_RANDOM + unobserved(kind::behaviour, "openkal.random", "the interface was not selected"); + return; +#else + claim("kal_random_props", kal_random_props); + + // A fill either succeeds completely or changes nothing. The buffer is + // pre-set to a value the source is unlikely to produce for every byte, so + // that a partial fill --- the state this interface does not have --- would + // be visible rather than being mistaken for entropy. + { + unsigned char buf[32]; + for (auto& b : buf) b = 0xA5; + const int rc = kal_random_fill(buf, sizeof buf); + observe(kind::behaviour, rc == kal_ok || rc == kal_err_again, + "a fill reports success or a momentarily empty source"); + if (rc == kal_ok) { + bool all_untouched = true; + for (auto b : buf) if (b != 0xA5) { all_untouched = false; break; } + observe(kind::behaviour, !all_untouched, + "a successful fill wrote the buffer"); + } + } + + // ⚠️ TWO FILLS DIFFER, AND THE CHANCE OF A FALSE REPORT IS STATED RATHER + // THAN LEFT FOR A READER TO WONDER ABOUT. + // + // A source that returned a constant would satisfy every check above. Two + // fills of 32 bytes agreeing by chance has probability 2^-256, which is not + // reachable; a source that agrees is returning a constant, and that is what + // this observes. + { + unsigned char a[32], b[32]; + const int ra = kal_random_fill(a, sizeof a); + const int rb = kal_random_fill(b, sizeof b); + if (ra == kal_ok && rb == kal_ok) { + bool identical = true; + for (unsigned i = 0; i < sizeof a; ++i) + if (a[i] != b[i]) { identical = false; break; } + observe(kind::behaviour, !identical, + "two fills do not return the same bytes"); + } else { + unobserved(kind::behaviour, "two fills differ", + "a fill reported no entropy"); + } + } + + // A zero-length fill is not an error: a caller computing a length may + // legitimately arrive at zero, and refusing would oblige every caller to + // branch before calling. + { + const int rc = kal_random_fill(nullptr, 0); + observe(kind::behaviour, rc == kal_ok, + "a fill of zero bytes succeeds"); + } +#endif +} + +} diff --git a/conformance/src/sections/random.cppm b/conformance/src/sections/random.cppm new file mode 100644 index 0000000..1a33f07 --- /dev/null +++ b/conformance/src/sections/random.cppm @@ -0,0 +1,6 @@ +// The section that examines openkal.random. +export module okc.random; + +export namespace okc::random { +void run(); +} diff --git a/conformance/src/suite.cpp b/conformance/src/suite.cpp index c20d24e..bb5fc94 100644 --- a/conformance/src/suite.cpp +++ b/conformance/src/suite.cpp @@ -7,6 +7,7 @@ import okc.stream; import okc.memory; import okc.env; import okc.time; +import okc.random; import okc.fs; import okc.process; import okc.task; @@ -26,6 +27,7 @@ int run_all() { memory::run(); env::run(); time::run(); + random::run(); fs::run(); process::run(); task::run(); diff --git a/include/openkal.h b/include/openkal.h index 5ca179e..2995ad0 100644 --- a/include/openkal.h +++ b/include/openkal.h @@ -17,6 +17,7 @@ #include "openkal/memory.h" #include "openkal/env.h" #include "openkal/time.h" +#include "openkal/random.h" #include "openkal/fs.h" #include "openkal/process.h" #include "openkal/task.h" diff --git a/include/openkal/random.h b/include/openkal/random.h new file mode 100644 index 0000000..b2587a2 --- /dev/null +++ b/include/openkal/random.h @@ -0,0 +1,57 @@ +/* openkal.random --- a source of unpredictable bytes. + * + * ⚠️ NOT A GENERATOR. This interface answers "give me bytes this environment + * considers unpredictable". It does not define a pseudo-random algorithm, hold + * state between calls, or promise a distribution. A program wanting a + * reproducible sequence seeds its own generator from these bytes once and does + * not come back; a program wanting unpredictability comes back. + * + * ⭐ THE INTERFACE EXISTS BECAUSE NOTHING ELSE HERE CAN SUPPLY IT. Entropy is + * not derivable from the other eight: a clock is not a source (a reading is + * unpredictable to a reader of the source and not to an adversary), and + * `openkal.fs` deliberately cannot open a platform-named object such as + * `/dev/urandom` --- a capability-oriented filesystem hands over roots, not + * absolute paths, and that refusal is the model working. + * + * ⚠️ AND IT IS NOT UNIVERSAL, WHICH IS WHY IT IS ITS OWN INTERFACE. Every + * hosted platform has one; a bare-metal machine has one only if its board does. + * Clause 6.1 already expresses that: an implementation providing no source + * omits these names, and a program that asks fails to link rather than + * receiving zeros. + */ +#ifndef OPENKAL_RANDOM_H +#define OPENKAL_RANDOM_H +#include "types.h" + +/* Positions in kal_random_props. + * + * ⚠️ THERE IS NO `AVAILABLE` POSITION, AND ITS ABSENCE IS THE DESIGN. Whether + * an environment has a source is answered by whether this interface is present + * at all (clause 6.1), not by a word a program reads after linking. A backend + * that defined `kal_random_props = 0` while providing no operation would let a + * program past the point the linker exists to stop it at. */ +#define KAL_RANDOM_PROP_BLOCKING ((kal_uintptr)1u << 0) +#define KAL_RANDOM_PROP_HARDWARE ((kal_uintptr)1u << 1) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Fills `len` bytes at `out`. + * + * ⚠️ NO PARTIAL SUCCESS. Either every byte is filled or none is. A caller that + * had to loop would have to distinguish "short read" from "no more entropy", + * and the second is not a state this interface has: an environment either has a + * source or does not provide the interface. + * + * `kal_err_again` reports a source that is momentarily empty --- a machine + * early in boot whose pool has not been seeded. It is not "this environment has + * no source", which is answered by the interface's absence. */ +int kal_random_fill(void* out, kal_uintptr len); + +extern const kal_uintptr kal_random_props; + +#ifdef __cplusplus +} +#endif +#endif /* OPENKAL_RANDOM_H */ diff --git a/mcpp.toml b/mcpp.toml index 8df7260..cbffd40 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal" -version = "0.6.0" +version = "0.7.0" description = "openkal: a portable kernel ABI specification. This package carries the normative declarations; implementations are separate packages." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/src/random.cppm b/src/random.cppm new file mode 100644 index 0000000..989a033 --- /dev/null +++ b/src/random.cppm @@ -0,0 +1,53 @@ +// openkal.random --- a source of unpredictable bytes. +// +// ⭐ THE INTERFACE EXISTS BECAUSE NOTHING ELSE HERE CAN SUPPLY IT, WHICH IS THE +// ONLY REASON ANY INTERFACE HERE EXISTS. +// +// Entropy is not derivable from the other eight. A clock reading is +// unpredictable to a reader of the source and not to an adversary — a +// distinction a port that tried it has already had to write down. And +// `openkal.fs` deliberately cannot open a platform-named object such as +// `/dev/urandom`: a capability-oriented filesystem hands a program its roots +// rather than the whole namespace, so that refusal is the model working rather +// than a gap in it. +// +// ⚠️ NOT UNIVERSAL, AND THAT IS WHY IT IS ITS OWN INTERFACE RATHER THAN AN +// OPERATION ON AN EXISTING ONE. Every hosted platform has a source; a +// bare-metal machine has one only if its board does. Clause 6.1 already +// expresses exactly that shape: an implementation with no source omits these +// names, and a program that asks fails to LINK rather than receiving zeros at +// run time. +module; +#include + +export module openkal.random; +export import openkal.types; + +export using ::kal_random_fill; +export using ::kal_random_props; + +export namespace kal::random { + +struct props_tag; +using props = kal::props; + +// Positions in kal_random_props. A position, once assigned, retains its +// meaning; an unassigned position reads as zero, so that a program compiled +// against a later specification behaves correctly against an earlier +// implementation. +// +// ⚠️ THERE IS NO `available` POSITION. Whether an environment has a source is +// answered by whether this module can be imported and its names linked, not by +// a word read after linking — see the note on the header. +inline constexpr props blocking{KAL_RANDOM_PROP_BLOCKING}; +inline constexpr props hardware{KAL_RANDOM_PROP_HARDWARE}; + +// Fills `out` with `len` unpredictable bytes, or fills none of it. +inline int fill(void* out, kal_uintptr len) { + return kal_random_fill(out, len); +} + +inline props properties() { return props{kal_random_props}; } +inline bool has(props p) { return properties().has(p); } + +}