1 # Generation on the host's cryptographic source 2 # ============================================= 3 # 4 # Generated contexts on the host's cryptographic source: WebCrypto in 5 # JavaScript and the operating system's generator natively. They apply the 6 # rules of trace_context.bend: four words per trace ID candidate and two per 7 # span ID candidate, most significant first, eight candidates per identifier, 8 # no all-zero identifier, no reuse of the parent's span ID or of a received 9 # trace ID, and an immediate stop at the first source error. There is no time, 10 # counter or other fallback. TC.Context.root_with and its siblings take a 11 # caller's source instead. 12 # 13 # The generation machine itself is pure: section 6 of LAWS.bend states its 14 # behavior over replayed tapes. This file only connects it, and the native 15 # HTTP shortcuts of native_http.bend, to the host source of entropy.bend. 16 # 17 # Every operation here relies on entropy.bend's foreign effect, so a check of 18 # a file that imports this module reports them and answers SOME PROOFS FAIL. 19 # No law imports it: the laws state the same operations on a caller's 20 # source, which is how PROOF.bend proves them without host code. 21 22 import Base 23 import ./trace_context.bend as TC 24 import ./native_http.bend as Adapter 25 import ./entropy.bend as Entropy 26 27 # The host source as the machine reads it: each read returns one word result 28 # from Entropy.read_u32, and the state is Unit because the host keeps none. 29 def Source.host(state: Unit) -> IO(Unit & Result<&1, &1, U32 & String, U32>): 30 IO.bind(Result<&1, &1, U32 & String, U32>, Unit & Result<&1, &1, U32 & String, U32>, 31 Entropy.read_u32(), word => IO.pure(Unit & Result<&1, &1, U32 & String, U32>, (state, word))) 32 33 # The host source has no state to hand back. 34 def on_host(-A: Type, action: IO(Unit & A)) -> IO(A): 35 IO.bind(Unit & A, A, action, done => IO.pure(A, Pair.snd(Unit, A, done))) 36 37 # Start a trace. The trace ID asserts random-trace-id and the root is not 38 # sampled, so it is emitted with flags 02. 39 def Context.root() -> IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>): 40 on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>, TC.Context.root_with(~Unit, ~Source.host, Unit{})) 41 42 # Continue a remote or local parent with a new operation. 43 def Context.child(parent: TC.Parent, sampling: TC.Sampling) -> IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>): 44 on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>, 45 TC.Context.child_with(~Unit, ~Source.host, Unit{}, parent, sampling)) 46 47 # Start a new trace instead of continuing a received context. 48 def Context.restart(previous: TC.RemoteContext) -> IO(Result<&2, &2, TC.GenerationError, TC.LocalContext>): 49 on_host(Result<&2, &2, TC.GenerationError, TC.LocalContext>, 50 TC.Context.restart_with(~Unit, ~Source.host, Unit{}, previous)) 51 52 # Give a service its own operation for a received message: a child of the 53 # context that extraction keeps, the message's or the base, a root without 54 # one, or a new trace in its place at a trust boundary 55 # (TC.Context.continue_or_start_with). 56 def Context.continue_or_start(+extraction: TC.Extraction, reception: TC.Reception, sampling: TC.Sampling, 57 +policy: TC.FailurePolicy) -> IO(TC.FailurePolicy.result(policy, TC.Service)): 58 on_host(TC.FailurePolicy.result(policy, TC.Service), 59 TC.Context.continue_or_start_with(~Unit, ~Source.host, Unit{}, extraction, reception, sampling, policy)) 60 61 # Give one message that the service sends a new child of the service's 62 # operation, or the fallback when none can be generated 63 # (TC.Context.send_with). 64 def Context.send(+limits: TC.Limits, service: TC.Service, sampling: TC.Sampling, +policy: TC.FailurePolicy, 65 +carrier: List<&2, TC.Header>) -> IO(TC.FailurePolicy.result(policy, TC.Sent)): 66 on_host(TC.FailurePolicy.result(policy, TC.Sent), 67 TC.Context.send_with(~Unit, ~Source.host, Unit{}, limits, service, sampling, policy, carrier)) 68 69 # The service's own operation for a received request, from the header map of 70 # the request, on the host's cryptographic source: native_http.bend's 71 # continue_or_start_with on Source.host. `base` is the context to keep when 72 # the request carries no usable traceparent. 73 def NativeHttp.continue_or_start(+limits: TC.Limits, headers: Adapter.HeaderMap(), base: Maybe<&2, TC.BaseContext>, 74 reception: TC.Reception, sampling: TC.Sampling, +policy: TC.FailurePolicy) -> 75 IO(TC.FailurePolicy.result(policy, TC.Service)): 76 on_host(TC.FailurePolicy.result(policy, TC.Service), 77 Adapter.continue_or_start_with(~Unit, ~Source.host, Unit{}, limits, headers, base, reception, sampling, policy)) 78 79 # Give one request that the service sends the context of a new operation, on 80 # the host's cryptographic source: native_http.bend's send_with on 81 # Source.host, which replaces the context fields of `headers`, the request's 82 # own header map. 83 def NativeHttp.send(+limits: TC.Limits, service: TC.Service, sampling: TC.Sampling, +policy: TC.FailurePolicy, 84 headers: Adapter.HeaderMap()) -> IO(TC.FailurePolicy.result(policy, Adapter.Outbound)): 85 on_host(TC.FailurePolicy.result(policy, Adapter.Outbound), 86 Adapter.send_with(~Unit, ~Source.host, Unit{}, limits, service, sampling, policy, headers))