diff --git a/bench/site_facts.py b/bench/site_facts.py index 5bbed0b..577c2de 100755 --- a/bench/site_facts.py +++ b/bench/site_facts.py @@ -23,8 +23,8 @@ RECORDED. Seconds, bytes, milliseconds. These are a machine, not a language, and README.md is scrupulous about saying so: it names the exact box and it -publishes a tired session reading 0.22 / 1.81 / 0.27 where a good one read -0.13 / 1.17 / 0.16. So a recorded number is never emitted on its own. It is +publishes other sessions reading 0.16 / 1.29 / 0.19 and 0.22 / 1.81 / 0.27 +where the table's read 0.13 / 1.17 / 0.16. So a recorded number is never emitted on its own. It is emitted as a group carrying the machine that produced it, the command that prints it, and every session README.md publishes, so the site can draw the spread and lead with the ratio. The ratio is the claim. The second is a @@ -306,14 +306,14 @@ def context_scope() -> dict: ("machine", r"\(([^()]*Ryzen[^()]*)\)", ("name",)), ( "edit_loop", - r"\| \*\*rebuild after one edit\*\* \| \*\*([\d.]+)\*\* \| ([\d.]+) \| ([\d.]+) \|", + r"\| \*\*one module's body\*\* \| \*\*([\d.]+)\*\* \| ([\d.]+) \| ([\d.]+) \|", ("iyi", "crystal", "go"), ), ( "edit_loop_sessions", - r"read (\d+\.\d+ / \d+\.\d+ / \d+\.\d+) and (\d+\.\d+ / \d+\.\d+ / \d+\.\d+) " - r"on the first row, and a tired session reads (\d+\.\d+ / \d+\.\d+ / \d+\.\d+)", - ("a", "b", "tired"), + r"other sessions on the same box read (\d+\.\d+ / \d+\.\d+ / \d+\.\d+) and\s+" + r"(\d+\.\d+ / \d+\.\d+ / \d+\.\d+) on the first row", + ("a", "b"), ), ("edit_loop_ratio", r"costs \*\*([\d.]+)x less\*\*", ("times",)), ( @@ -369,16 +369,23 @@ def context_scope() -> dict: ), ( "context_tokens", - r"\*\*(\d+)–(\d+)% fewer prompt tokens\*\* over ([a-z]+(?:-[a-z]+)*) measured runs", - ("low", "high", "runs"), + r"\*\*(\d+)–(\d+)% fewer prompt tokens\*\*", + ("low", "high"), ), # The other half of the same measurement, and the half that does not go # iyi's way: the model's rounds to green. README.md publishes it in the # same sentence as the token saving, so the site parses it from there and # states it beside the saving rather than leaving a reader to assume. + # README.md no longer counts the runs; it names where they are written + # up, and the site says that rather than a count nobody maintains. + ( + "context_runs", + r"over the runs written up in ([A-Z_]+\.md §\d+)", + ("where",), + ), ( "context_rounds", - r"rounds-to-green are the model's, tied ([a-z]+) times and lost ([a-z]+),", + r"rounds to green tied ([a-z]+) times and lost\s+([a-z]+)\.", ("tied", "lost"), ), ] @@ -400,6 +407,7 @@ def context_scope() -> dict: "runtime_arithmetic": "python3 bench/runtime.py", "context_pack": "python3 bench/context_pack.py", "context_tokens": "python3 bench/context_pack.py", + "context_runs": "python3 bench/context_pack.py", "context_rounds": "python3 bench/context_pack.py", } @@ -442,8 +450,10 @@ def sessions(raw: dict) -> list[dict[str, float]]: column is parsed, because a drift in the row is still a drift, and then dropped. """ - rows = [] - for slot in ("a", "b", "tired"): + # The table's own row is the first reading; the sentence under it gives + # the other two sessions. + rows = [{"iyi": float(raw["table"]["iyi"]), "crystal": float(raw["table"]["crystal"])}] + for slot in ("a", "b"): iyi, crystal, _ = (float(v) for v in raw[slot].split(" / ")) rows.append({"iyi": iyi, "crystal": crystal}) return rows @@ -461,7 +471,7 @@ def build() -> tuple[dict, list[str]]: return {}, missing default_machine = machine(raw["machine"]["name"]) - loop = sessions(raw["edit_loop_sessions"]) + loop = sessions({**raw["edit_loop_sessions"], "table": raw["edit_loop"]}) ratios = [round(s["crystal"] / s["iyi"], 2) for s in loop] tree = measured() # Counted here rather than by `measured()`, which measures that project's @@ -562,7 +572,7 @@ def build() -> tuple[dict, list[str]]: "size_high": int(raw["context_pack"]["size_high"]), "tokens_low": int(raw["context_tokens"]["low"]), "tokens_high": int(raw["context_tokens"]["high"]), - "runs": raw["context_tokens"]["runs"], + "runs_where": raw["context_runs"]["where"], "rounds_tied": raw["context_rounds"]["tied"], "rounds_lost": raw["context_rounds"]["lost"], "unit": "%", diff --git a/records/agent.json b/records/agent.json index da7d44f..40e4ea6 100644 --- a/records/agent.json +++ b/records/agent.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.17.0 (built on Crystal 1.22.0-dev [fe3db8354] (2026-10-14))", - "commit": "fe3db8354f56ce84d411611d48e6dad235172275", - "machine": "Linux 7.0.0-31-generic x86_64, QEMU Virtual CPU version 2.5+", + "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", + "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:agent", - "when": "2026-10-14T11:26:22.139Z" + "when": "2026-11-02T07:19:31.432Z" }, "project": [ { @@ -104,7 +104,7 @@ "command": "iyi test --json .", "exitCode": 0, "payload": "stdout", - "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.267}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", + "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.394}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", "stderr": "" }, { @@ -167,7 +167,7 @@ }, "serverInfo": { "name": "iyi", - "version": "0.17.0" + "version": "0.20.0" } }, "tools": [ diff --git a/records/break/share_reach.iyi b/records/break/share_reach.iyi new file mode 100644 index 0000000..063639f --- /dev/null +++ b/records/break/share_reach.iyi @@ -0,0 +1,21 @@ +# Breaks III.4.4's reach check: the block names nothing unshareable, but +# `greeting`, which it calls, reads `GREETING`, an `Array` any worker could +# reach while another changes it. The compiler walks the calls from the +# block and warns at the block, by the shortest path. A warning, so the +# build is asked to fail on one: `--error-on-warnings`, as CI would. +# `@[SafeReach("...")]` on the constant, saying why it is safe, quiets it. +module diagnostics/share_reach + +import std/http::{Response, Server} +import std/socket::{IyiSocket} + +GREETING = ["hello", "from", "every", "core"] + +def greeting : String + GREETING.join(" ") +end + +listener = IyiSocket.listen("127.0.0.1", 0) +Server.serve(listener) do |req| + Response.new(200, greeting) +end diff --git a/records/break/share_serve_capture.iyi b/records/break/share_serve_capture.iyi new file mode 100644 index 0000000..890505e --- /dev/null +++ b/records/break/share_serve_capture.iyi @@ -0,0 +1,17 @@ +# Breaks III.4.4 as III.4.13 applies it: `Server.serve` runs each connection +# on the pool's workers, so its block is checked as `spawn_parallel`'s is, +# and every value it captures must be `Share`. An `Array` is not: two +# workers appending at once would race its size. Everything else here is +# legal, and the refusal names both ways out - the array behind a `Mutex`, +# or `workers: 1`, which keeps the server on one thread. +module diagnostics/share_serve_capture + +import std/http::{Response, Server} +import std/socket::{IyiSocket} + +seen = [] of String +listener = IyiSocket.listen("127.0.0.1", 0) +Server.serve(listener) do |req| + seen << req.path + Response.new(200, "ok") +end diff --git a/records/diagnostics.json b/records/diagnostics.json index d10d250..2d10ef1 100644 --- a/records/diagnostics.json +++ b/records/diagnostics.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.17.0 (built on Crystal 1.22.0-dev [fe3db8354] (2026-10-14))", - "commit": "fe3db8354f56ce84d411611d48e6dad235172275", - "machine": "Linux 7.0.0-31-generic x86_64, QEMU Virtual CPU version 2.5+", + "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", + "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:diagnostics", - "when": "2026-10-14T11:26:14.391Z" + "when": "2026-11-02T07:19:23.048Z" }, "cases": [ { @@ -105,6 +105,24 @@ "command": "IYI_PATH=/src iyi build --no-codegen --no-color records/break/errors_non_exhaustive.iyi", "exitCode": 1, "stderr": "Showing last frame. Use --error-trace for full trace.\n\nIn records/break/errors_non_exhaustive.iyi:28:1\n\n 28 | case read(\"missing\")\n ^\nError: case is not exhaustive.\n\nMissing types:\n - Diagnostics::ErrorsNonExhaustive::IOError\n" + }, + { + "id": "share-serve-capture", + "rule": "III.4", + "title": "a server's block that captures an Array", + "path": "records/break/share_serve_capture.iyi", + "command": "IYI_PATH=/src iyi build --no-codegen --no-color records/break/share_serve_capture.iyi", + "exitCode": 1, + "stderr": "Showing last frame. Use --error-trace for full trace.\n\nIn records/break/share_serve_capture.iyi:15:3\n\n 15 | seen << req.path\n ^---\nError: the block Server.serve keeps, which may run on other threads captures `seen : Array(String)`, which is not Share: Array(String)'s field @size is assigned in `append`. Put it behind a lock, a `Mutex(Array(String))` (std/mutex), or pass `workers: 1` so the block runs on one thread (SPEC.md III.4.4)\n" + }, + { + "id": "share-reach", + "rule": "III.4", + "title": "a server's block that reaches module state through a call", + "path": "records/break/share_reach.iyi", + "command": "IYI_PATH=/src iyi build --no-codegen --no-color --error-on-warnings records/break/share_reach.iyi", + "exitCode": 1, + "stderr": "In records/break/share_reach.iyi:19:24\n\n 19 | Server.serve(listener) do |req|\n ^\nWarning: the block Server.serve keeps, which may run on other threads reaches `GREETING : Array(String)` (not Share) through `greeting` → `GREETING.join`: Array(String)'s field @size is assigned in `append`. If it is only read, make it a `List(String)` (std/list), which is Share. Keep the value behind a `Mutex` (std/mutex), or, when every thread may reach it safely - it is set once and then only read, or guarded some other way - say why on the constant: `@[SafeReach(\"...\")]` (SPEC.md III.4.4)\n\nA total of 1 warnings were found.\n" } ] } diff --git a/records/highlight.json b/records/highlight.json index 1e9b3ae..e4b069a 100644 --- a/records/highlight.json +++ b/records/highlight.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18))", - "commit": "2a9653052ab76f9d8084da47e059273f6ef45228", + "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", + "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:highlight", - "when": "2026-10-19T11:15:18.120Z" + "when": "2026-11-02T07:19:35.714Z" }, "files": { "samples/iyi/app/formal.iyi": "# A second module, exporting a `title` of its own. See `../modules.iyi`.\nmodule app/formal\n\npub def title : String\n "esteemed colleague"\nend\n\npub def address(name : String) : String\n "Good day, #{name}."\nend\n", @@ -15,9 +15,10 @@ "samples/iyi/calc/ast.iyi": "module calc/ast\n\n# What an evaluated program can go wrong with. Both are ordinary members of the\n# union an evaluation returns (SPEC.md III.1), so a caller either handles them\n# or propagates with `!`.\npub struct UnknownName\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Error for UnknownName\n def message : String\n "unknown name '#{name}'"\n end\nend\n\npub struct DividedByZero\n def initialize\n end\nend\n\nimpl Error for DividedByZero\n def message : String\n "divided by zero"\n end\nend\n\n# The one thing every node can do. A trait rather than a base class, because\n# there is nothing to inherit and R-3 has no opinion to break here.\npub trait Node\n abstract def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\nend\n\npub struct Number\n getter value : Int32\n\n def initialize(@value : Int32)\n end\nend\n\nimpl Node for Number\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n value\n end\nend\n\npub struct Name\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Node for Name\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n return UnknownName.new(name) unless scope.has_key?(name)\n scope[name]\n end\nend\n\npub class Binary\n getter op : Char\n getter left : Number | Name | Binary\n getter right : Number | Name | Binary\n\n def initialize(@op : Char, @left : Number | Name | Binary, @right : Number | Name | Binary)\n end\nend\n\nimpl Node for Binary\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n a = left.evaluate(scope)!\n b = right.evaluate(scope)!\n\n if op == '+'\n a + b\n elsif op == '-'\n a - b\n elsif op == '*'\n a * b\n else\n return DividedByZero.new if b == 0\n a // b\n end\n end\nend\n", "samples/iyi/calc/lexer.iyi": "module calc/lexer\n\n# What the scanner produces. A token is its kind, the text it covers and where\n# it started, because an error that cannot say where is half an error.\npub struct Token\n getter kind : String\n getter text : String\n getter at : Int32\n\n def initialize(@kind : String, @text : String, @at : Int32)\n end\nend\n\npub struct BadCharacter\n getter at : Int32\n getter found : Char\n\n def initialize(@at : Int32, @found : Char)\n end\nend\n\nimpl Error for BadCharacter\n def message : String\n "unexpected character at #{at}"\n end\nend\n\npub def scan(source : String) : Array(Token) | BadCharacter\n tokens = [] of Token\n index = 0\n\n while index < source.size\n c = source[index]\n\n # A line ends in CRLF on Windows: `echo 2 + 3 * 4| iyi run\n # samples\\iyi\\calc.iyi` in cmd answered "unexpected character at 9",\n # the `\\r`.\n if c == ' ' || c == '\\n' || c == '\\t' || c == '\\r'\n index = index + 1\n elsif digit?(c)\n start = index\n while index < source.size && digit?(source[index])\n index = index + 1\n end\n tokens << Token.new("number", source[start, index - start], start)\n elsif letter?(c)\n start = index\n while index < source.size && (letter?(source[index]) || digit?(source[index]))\n index = index + 1\n end\n tokens << Token.new("name", source[start, index - start], start)\n elsif operator?(c)\n tokens << Token.new("op", source[index, 1], index)\n index = index + 1\n else\n return BadCharacter.new(index, c)\n end\n end\n\n tokens << Token.new("end", "", index)\n tokens\nend\n\ndef digit?(c : Char) : Bool\n c >= '0' && c <= '9'\nend\n\ndef letter?(c : Char) : Bool\n (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'\nend\n\ndef operator?(c : Char) : Bool\n c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '='\nend\n", "samples/iyi/calc/parser.iyi": "module calc/parser\n\nimport calc/lexer::{Token}\nimport calc/ast::{Number, Name, Binary}\n\npub struct SyntaxError\n getter at : Int32\n getter wanted : String\n\n def initialize(@at : Int32, @wanted : String)\n end\nend\n\nimpl Error for SyntaxError\n def message : String\n "expected #{wanted} at #{at}"\n end\nend\n\n# One statement: `name = expression`, or an expression on its own, which the\n# runner prints. Kept as a pair rather than a node type, because a statement is\n# not something this language evaluates twice.\npub struct Statement\n getter target : String\n getter body : Number | Name | Binary\n\n def initialize(@target : String, @body : Number | Name | Binary)\n end\nend\n\n# A cursor over the tokens. A struct with an index rather than a shifting array,\n# because backing up one token is the parser's whole trick.\npub class Reader\n getter tokens : Array(Token)\n getter at : Int32\n\n def initialize(@tokens : Array(Token))\n @at = 0\n end\n\n def peek : Token\n tokens[at]\n end\n\n def advance : Token\n token = tokens[at]\n @at = at + 1\n token\n end\nend\n\npub def parse(tokens : Array(Token)) : Array(Statement) | SyntaxError\n reader = Reader.new(tokens)\n statements = [] of Statement\n\n while reader.peek.kind != "end"\n statements << statement(reader)!\n end\n\n statements\nend\n\ndef statement(reader : Reader) : Statement | SyntaxError\n first = reader.peek\n\n if first.kind == "name" && reader.tokens[reader.at + 1].text == "="\n reader.advance\n reader.advance\n Statement.new(first.text, expression(reader)!)\n else\n Statement.new("", expression(reader)!)\n end\nend\n\ndef expression(reader : Reader) : Number | Name | Binary | SyntaxError\n left = term(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "+" || reader.peek.text == "-")\n op = reader.advance.text[0]\n left = Binary.new(op, left, term(reader)!)\n end\n\n left\nend\n\ndef term(reader : Reader) : Number | Name | Binary | SyntaxError\n left = factor(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "*" || reader.peek.text == "/")\n op = reader.advance.text[0]\n left = Binary.new(op, left, factor(reader)!)\n end\n\n left\nend\n\ndef factor(reader : Reader) : Number | Name | Binary | SyntaxError\n token = reader.advance\n\n if token.kind == "number"\n Number.new(token.text.to_i)\n elsif token.kind == "name"\n Name.new(token.text)\n elsif token.text == "("\n inside = expression(reader)!\n closing = reader.advance\n return SyntaxError.new(closing.at, "')'") unless closing.text == ")"\n inside\n else\n SyntaxError.new(token.at, "a number, a name or '('")\n end\nend\n", - "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 21,567-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer::{scan, BadCharacter}\nimport calc/parser::{parse, Statement, SyntaxError}\nimport calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way. Its last two lines are errors on\n# purpose, and the demo exits 0 with them.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\n# Whether every statement answered. An error goes to standard error, and\n# input with one makes the program exit 1: all of them went to standard\n# output at exit 0, so `echo 1 / 0 | iyi run samples/iyi/calc.iyi && next`\n# ran `next`.\npub def run(source : String) : Bool\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n stderr.puts "error: #{tokens.message}"\n return false\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n stderr.puts "error: #{program.message}"\n return false\n end\n\n answered = true\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n stderr.puts "error: #{value.message}"\n answered = false\n end\n end\n answered\nend\n\n# Input that is only line ends is nothing piped in: PowerShell's `"" |`\n# sends a CRLF. And its pipe starts what it sends with a byte order mark,\n# which is not part of the program: "unexpected character at 0".\ntyped = read_input.lchop('\\uFEFF')\nif typed.strip.empty?\n run(DEMO)\nelsif !run(typed)\n exit 1\nend\n", + "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 25,725-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer::{scan, BadCharacter}\nimport calc/parser::{parse, Statement, SyntaxError}\nimport calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way. Its last two lines are errors on\n# purpose, and the demo exits 0 with them.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\n# Whether every statement answered. An error goes to standard error, and\n# input with one makes the program exit 1: all of them went to standard\n# output at exit 0, so `echo 1 / 0 | iyi run samples/iyi/calc.iyi && next`\n# ran `next`.\npub def run(source : String) : Bool\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n stderr.puts "error: #{tokens.message}"\n return false\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n stderr.puts "error: #{program.message}"\n return false\n end\n\n answered = true\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n stderr.puts "error: #{value.message}"\n answered = false\n end\n end\n answered\nend\n\n# Input that is only line ends is nothing piped in: PowerShell's `"" |`\n# sends a CRLF. And its pipe starts what it sends with a byte order mark,\n# which is not part of the program: "unexpected character at 0".\ntyped = read_input.lchop('\\uFEFF')\nif typed.strip.empty?\n run(DEMO)\nelsif !run(typed)\n exit 1\nend\n", "samples/iyi/collections.iyi": "# The `Enumerable` port, exercised — SPEC.md II.6.\n#\n# ./bin/iyi run samples/iyi/collections.iyi\n#\n# `std/enumerable.iyi` holds the trait. This file implements it twice and calls\n# every method it defines. Both matter: a default method is only typed when it\n# is called, so a trait that merely compiles has proved nothing about whether\n# its bodies are sound.\n#\n# `Nums` and `Words` answer `Elem` differently — `Int32` and `String` — and each\n# gets the same 57 default methods from one `each`. That is the claim II.6\n# makes, running.\n\nmodule samples/collections\n\nimport std/enumerable::{Enumerable}\n\npub struct Nums\n def initialize(@a : Array(Int32))\n end\nend\n\nimpl Enumerable for Nums\n type Elem = Int32\n\n def each(& : Int32 -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\npub struct Words\n def initialize(@a : Array(String))\n end\nend\n\nimpl Enumerable for Words\n type Elem = String\n\n def each(& : String -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\nn = Nums.new([3, 1, 4, 1, 5])\nw = Words.new(["pear", "fig", "plum", "kiwi"])\n\nputs "to_a #{n.to_a.join(",")}"\nputs "size #{n.size}"\nputs "empty? #{n.empty?} #{Nums.new([] of Int32).empty?}"\nputs "all? #{n.all? { |x| x > 0 }}"\nputs "any? #{n.any? { |x| x > 4 }}"\nputs "none? #{n.none? { |x| x > 9 }}"\nputs "one? #{n.one? { |x| x == 4 }}"\nputs "count #{n.count { |x| x == 1 }}"\nputs "find #{n.find { |x| x > 3 }} #{n.find { |x| x > 9 }.inspect}"\nputs "index #{n.index { |x| x == 4 }} #{n.index { |x| x == 9 }.inspect}"\nputs "first?/first #{n.first?} #{n.first}"\nputs "map #{n.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{n.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{n.map_with_index { |x, i| x * i }.join(",")}"\nputs "select #{n.select { |x| x > 2 }.join(",")}"\nputs "reject #{n.reject { |x| x > 2 }.join(",")}"\nputs "partition #{n.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{n.take(2).join(",")} #{n.skip(3).join(",")}"\nputs "take_while #{n.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{n.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{n.reduce(0) { |a, x| a + x }}"\nputs "each_with_index #{n.map_with_index { |x, i| i }.join(",")}"\nputs "each_with_object #{n.each_with_object([] of Int32) { |e, o| o << e * 10 }.join(",")}"\nputs "join #{n.join("-")}"\nputs "includes? #{n.includes?(4)} #{n.includes?(9)}"\nputs "max?/max #{n.max?} #{n.max}"\nputs "min?/min #{n.min?} #{n.min}"\nputs "sum/product #{n.sum} #{n.product}"\nputs "sum of nothing #{Nums.new([] of Int32).sum}"\nputs "tally #{n.tally[1]}"\nputs "sorted #{n.sorted.join(",")}"\nputs "max_by #{w.max_by { |s| s.size }}"\nputs "group_by #{w.group_by { |s| s.size }[4].join(",")}"\nputs "index_by #{w.index_by { |s| s.size }[3]}"\n\nputs "each_slice #{n.each_slice(2).map(&.join("+")).join(" ")}"\nputs "each_cons #{n.each_cons(2).map(&.join("+")).join(" ")}"\nputs "chunk_while #{n.chunk_while { |a, b| b >= a }.map(&.join("+")).join(" ")}"\nputs "first(n) #{n.first(3).join(",")}"\nputs "compact_map #{n.compact_map { |x| x > 2 ? x * 10 : nil }.join(",")}"\nputs "find_value #{n.find_value { |x| x > 3 ? "big" : nil }}"\nputs "accumulate #{n.accumulate(0) { |a, x| a + x }.join(",")}"\nputs "reduce? #{n.reduce? { |a, x| a + x }}"\nputs "in_groups_of #{n.in_groups_of(2, 0).map(&.join("+")).join(" ")}"\nputs "min_by #{w.min_by { |s| s.size }}"\nputs "max_of/min_of #{w.max_of { |s| s.size }} #{w.min_of { |s| s.size }}"\nputs "minmax #{n.minmax[0]},#{n.minmax[1]}"\nputs "tally_by #{w.tally_by { |s| s.size }[4]}"\nputs "sum_by #{w.sum_by { |s| s.size }}"\n\n# SPEC II.6 finding 6: one bound replaces `Indexable | Iterable | Iterator`,\n# and `O::Elem` names what the other collection yields. Four numbers for\n# four words: a shorter other collection is refused, as the other library\n# refuses it.\nputs "zip #{Nums.new([3, 1, 4, 1]).zip(w).map { |pair| "#{pair[0]}#{pair[1]}" }.join(" ")}"\n\ncycled = [] of Int32\nNums.new([1, 2]).cycle(2) { |x| cycled << x }\nputs "cycle #{cycled.join(",")}"\n\n# A trait with an associated type used as a type: one function, any implementer.\ndef describe(e : Enumerable) : String\n "#{e.size} items"\nend\n\nputs "as a type #{describe(n)} / #{describe(w)}"\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `Words.new([...]).sum` — `sum` is bounded `where Elem : Num` and\n# `String` implements `Comparable` but not `Num`. The error names the element\n# type and the bound rather than failing somewhere inside the method body.\n#\n# * A second `impl Enumerable for Nums` — a trait with an associated type is\n# implementable once per type, because a second answer for `Elem` would make\n# every call on `Nums` ambiguous.\n#\n# * `impl Num for X` with no `impl Comparable for X` before it —\n# `Num : Comparable` requires it, and the impls have to be written in\n# dependency order.\n", "samples/iyi/config.iyi": "# The first program that reads text - a `key = value` file, with the line\n# number in its error.\n#\n# ./bin/iyi run samples/iyi/config.iyi\n#\n# Text comes apart into lines, a line into its two halves around a `=`, and\n# each half loses its whitespace; a line that does not parse is an error\n# value carrying where it was (SPEC.md III.1), not a printed message. Like\n# `basics` and `inventory`, this documents no rule: it is here because the\n# prelude grows only when a program in this repository needs something\n# (SPEC.md 0.1.0 item 3), and cutting text is what the third program does.\nmodule samples/config\n\nstruct ParseError\n property line : Int32\n property message : String\n\n def initialize(@line : Int32, @message : String)\n end\nend\n\nimpl Error for ParseError\n def message : String\n "line #{@line}: #{@message}"\n end\nend\n\ndef parse(text : String) : Hash(String, String) | ParseError\n config = {} of String => String\n text.lines.each_with_index do |raw, index|\n line = raw.strip\n next if line.empty? || line.starts_with?('#')\n eq = line.index('=')\n return ParseError.new(index + 1, "no '=' in #{line.inspect}") unless eq\n key = line[0, eq].strip\n return ParseError.new(index + 1, "empty key") if key.empty?\n config[key] = line[eq + 1, line.size - eq - 1].strip\n end\n config\nend\n\ngood = "# the service\\nname = iyi\\nport= 8080\\n\\nmode =fast\\n"\ncase parse(good)\nin Hash(String, String)\n puts it # => {"name" => "iyi", "port" => "8080", "mode" => "fast"}\n puts it["port"].to_i + 1 # => 8081\n puts it.keys.join(", ") # => name, port, mode\nin ParseError then puts "error: #{it.message}"\nend\n\nbad = "name = iyi\\nbroken line\\n"\ncase parse(bad)\nin Hash(String, String) then puts "parsed"\nin ParseError then puts "error: #{it.message}" # => error: line 2: no '=' in "broken line"\nend\n\n# The pieces a parser cuts, on their own.\nputs "a,,b".split(',') # => ["a", "", "b"]\nputs "one\\ntwo\\n".lines # => ["one", "two"]\nputs " padded\\t".strip.inspect # => "padded"\nputs "config.iyi".ends_with?(".iyi") # => true\n", + "samples/iyi/cores.iyi": "# Work on every core - SPEC.md III.4.13, as a person writes it.\n#\n# ./bin/iyi run samples/iyi/cores.iyi\n#\n# `spawn` is a task on the thread that spawned it, and its block may touch\n# anything; `spawn_parallel` is a task a pool of workers runs on any core,\n# and its block may capture only what is `Share` (III.4.4). std/http's\n# `Server.serve` puts each connection on that pool, so its block is checked\n# the same way. When a block reaches module state through a call, the\n# compiler warns, and `@[SafeReach("why")]` on the state says why it is safe.\nmodule samples/cores\n\nimport std/http::{HTTP, Response, Server}\nimport std/socket::{IyiSocket}\nimport std/mutex::{Mutex}\n\ndef collatz_steps(n : Int64) : Int64\n steps = 0_i64\n while n != 1\n n = n % 2 == 0 ? n // 2 : 3 * n + 1\n steps += 1\n end\n steps\nend\n\n# 1. `spawn` and `spawn_parallel`.\n#\n# The tasks `spawn` starts share their spawner's thread, so they may append\n# to an `Array` they captured. The tasks `spawn_parallel` starts run on\n# whichever core is free; each answers its own value, and a task's answer\n# need not be `Share`, because it is handed over once, after the task ends.\nseen = [] of Int64\ngroup do |g|\n (1..3).each do |n|\n g.spawn do\n seen << collatz_steps(n.to_i64)\n 0\n end\n end\nend\nputs seen.sorted # => [0, 1, 7]\n\nlongest = 0_i64\ngroup do |g|\n handles = [] of IyiTask(Int64)\n 4.times do |part|\n handles << g.spawn_parallel do\n best = 0_i64\n start = part.to_i64 * 25_000 + 1\n (start..start + 24_999).each { |n| best = collatz_steps(n) if collatz_steps(n) > best }\n best\n end\n end\n handles.each do |handle|\n steps = handle.value\n longest = steps if steps.is_a?(Int64) && steps > longest\n end\nend\nputs longest # => 350, the longest chain below 100,000\n\n# 2. State the pool may share.\n#\n# A counter that every core adds to is an `Atomic`; an `Array` that every\n# core appends to sits behind a `Mutex`. Both are `Share`, so a parallel\n# block may capture them.\nhits = Atomic(Int64).new(0_i64)\npaths = Mutex.new { [] of String }\n\n# 3. Module state a call reaches.\n#\n# A parallel block that names `GREETING` is refused, because an `Array` is\n# not `Share`. One that only calls `greeting` used to compile with nothing\n# said; now the compiler warns at the block, naming the path. This constant\n# is set before the server starts and only read afterwards, so it says so.\n@[SafeReach("set once before the server starts, then only read")]\nGREETING = ["hello", "from", "every", "core"]\n\ndef greeting : String\n GREETING.join(" ")\nend\n\n# 4. A server on every core.\n#\n# `Server.serve` runs each connection on the pool's workers, so its block is\n# checked as `spawn_parallel`'s is. It stops through a channel: closing the\n# listener from inside the block would capture it, and `/stop` may arrive on\n# any worker. `workers: 1` keeps the one-thread server, whose block may\n# capture anything.\nlistener = IyiSocket.listen("127.0.0.1", 0)\nbase = "http://127.0.0.1:#{listener.local_port}"\nstop = Channel(Int32).new(1)\ngroup do |g|\n g.spawn do\n stop.receive\n listener.close\n 0\n end\n g.spawn do\n Server.serve(listener) do |req|\n hits.add(1_i64)\n path = req.path\n paths.lock { |list| list << path }\n if path == "/stop"\n stop.send(0)\n Response.new(200, "bye")\n else\n Response.new(200, greeting)\n end\n end\n 0\n end\n g.spawn do\n puts HTTP.get(base + "/").or_panic.body # => hello from every core\n HTTP.get(base + "/stop").or_panic\n 0\n end\nend\nputs hits.get # => 2\npaths.lock { |list| puts list.sorted } # => ["/", "/stop"]\n", "samples/iyi/derive.iyi": "# `derive`, and where it runs (SPEC.md R-5, II.4).\n#\n# ./bin/iyi run samples/iyi/derive.iyi\n#\n# A derive runs once, in the module that declares the type. What it generates is\n# an ordinary declaration of that module, so it travels in the module's\n# `.iyimod` and a consumer calls the derived method without running the macro\n# and without this module's source. `bench/samples_roundtrip.sh` builds this\n# sample from its artifacts with `std/derives` deleted, which is that claim\n# taken literally.\n#\n# The macro is handed the facts R-5 lets it read: the declaration's name, and\n# the fields written in its body, each with the type it was written as. It\n# cannot enumerate a type it was not given, which is what keeps a derive\n# bounded and cacheable.\n\nmodule samples/derive\n\nimport std/derives::*\n\npub struct Point\n @x : Int32\n @y : Int32\n\n def initialize(@x : Int32, @y : Int32)\n end\n\n derive described\nend\n\n# One derive, two declarations. The macro reads each one's own fields, so\n# neither type had to be written down inside the macro.\npub struct Span\n @from : Int32\n @to : Int32\n @label : String\n\n def initialize(@from : Int32, @to : Int32, @label : String)\n end\n\n derive described\nend\n\nputs Point.new(3, 4).described\nputs Span.new(1, 9, "range").described\n\n# The field type is a type, not a name, so a derive can ask what it implements.\n# `Name` and `Show` both belong to `std/derives`, and both arrive from that\n# module's artifact when its source is gone: `@label` is rendered through the\n# trait, and `@count` is skipped because `Int32` does not implement it.\n#\n# `getter label : Name` is a macro call, so the field it declares exists only\n# once it has run. A derive reads the declarations above it, which is why\n# `derive` goes last. Below `getter`, this would be an error rather than a\n# method generated over no fields.\n#\n# One line, two derives: each macro reads the same declaration and adds its own\n# methods.\npub struct Tag\n getter label : Name\n @count : Int32\n\n def initialize(@label : Name, @count : Int32)\n end\n\n derive described, shown\nend\n\nputs Tag.new(Name.new("hi"), 2).described\nputs Tag.new(Name.new("hi"), 2).shown\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `derive missing` where nothing exports a `missing` macro. The error names\n# the macro and the declaration it would have to be written as, rather than\n# failing as an undefined method on the type's metaclass.\n#\n# * `derive` outside a class or struct body. There is no declaration to read,\n# so there is nothing for the macro to be handed.\n#\n# * A `derive` inside a reopened type from another module. That is refused\n# before the derive is reached, by R-3: iyi has no open classes, so a module\n# cannot add to a type it does not declare.\n#\n# * A `getter` (or any macro call that declares a field) written below the\n# `derive` that should read it. The derive names the call and says which way\n# to move, rather than generating a method over the fields it happened to see.\n", "samples/iyi/enums.iyi": "# An `enum`, used the way a program uses one — SPEC.md IV.2.\n#\n# ./bin/iyi run samples/iyi/enums.iyi\n#\n# The compiler gives an enum two methods: `value`, the integer the member was\n# numbered with, and `new`, which wraps one. Everything below is the prelude's\n# (`src/iyi/enum.iyi`), and until this sample asked for it none of it was\n# there: `Level::Warn == Level::Warn` was false, `"#{Level::Warn}"` printed\n# `Samples::Enums::Level`, and a `case` over the members matched nothing —\n# because a `when` that names a member lowers to `Level::Warn === level`, and\n# `===` is `==`.\n#\n# Two enums, because the compiler writes different code for each: a plain one\n# whose `case` the exhaustiveness checker holds to every member, and a\n# `@[Flags]` one, where it synthesizes `None`, `All` and question methods that\n# ask `includes?`.\n\nmodule samples/enums\n\n# A level is an order as well as a name, which is why the threshold below\n# works: `Warn >= Info` is the whole reason a program writes one.\nenum Level : Int32\n Debug = 0\n Info = 1\n Warn = 2\n Error = 3\nend\n\n# A set of bits, one per member, which is what `@[Flags]` numbers them as.\n@[Flags]\nenum Mode\n Read\n Write\n Append\nend\n\n# 1. A member prints as its name, in interpolation and out of `to_s`.\nlevel = Level::Warn\nputs "level: #{level}"\nputs "spelled: " + Level::Error.to_s\n\n# 2. Equality, and the `case` that is built on it — a `when` naming a member\n# lowers to `===`, which is `==`.\nputs "same: #{level == Level::Warn}"\nputs "other: #{level == Level::Debug}"\n\n# `in` rather than `when`, which is the form the exhaustiveness checker holds\n# to every member: the day a member is added this sample stops compiling\n# rather than falling through, and the answer is a `String` rather than a\n# `String | Nil` because there is nothing left for an `else` to catch.\nverdict = case level\n in Level::Debug then "quiet"\n in Level::Info then "ordinary"\n in Level::Warn then "loud"\n in Level::Error then "loudest"\n end\nputs "verdict: #{verdict}"\n\n# 3. The question methods the compiler writes, which call the prelude's `==`.\nputs "warn? #{level.warn?}"\nputs "error? #{level.error?}"\n\n# 4. An order, so a level can be a threshold.\nputs "at least info: #{level >= Level::Info}"\nputs "below error: #{level < Level::Error}"\n\n# 5. The members, as a list — a menu, a validation table, a loop over all of\n# them. `names` is the same list said in text.\nLevel.values.each do |candidate|\n print candidate.to_s\n print " "\nend\nputs ""\nputs "names: #{Level.names.size}"\n\n# 6. A name read back, which is what a program does with an argument or a\n# configuration line. Unknown text answers `nil` rather than a panic, and the\n# lenient form takes the spelling a person types.\nputs "parsed: #{Level.parse?("Error").to_s}"\nputs "lenient: #{Level.parse?("warn").to_s}"\nputs "unknown: #{Level.parse?("shout").inspect}"\n\n# 7. The value, for a wire format or a C call, and the way back.\nputs "value: #{Level::Error.value}"\nputs "from 1: #{Level.from_value?(1).to_s}"\nputs "from 9: #{Level.from_value?(9).inspect}"\n\n# 8. A flags enum: the two set operators, membership, and a `to_s` that says\n# which bits are in it.\nmode = Mode::Read | Mode::Write\nputs "mode: #{mode}"\nputs "read: #{mode.includes?(Mode::Read)}"\nputs "append: #{mode.append?}"\nputs "none: #{Mode::None.to_s}"\nputs "masked: #{(mode & Mode::Write).to_s}"\n\n# 9. As a `Hash` key, which needs `hash` as well as `==`.\ncounts = {} of Level => Int32\ncounts[Level::Warn] = 2\ncounts[Level::Error] = 1\nputs "warns: #{counts[Level::Warn]}"\nputs "errors: #{counts[Level::Error]}"\n", "samples/iyi/errors.iyi": "# Errors are ordinary union members — SPEC.md III.1.\n#\n# ./bin/iyi run samples/iyi/errors.iyi\n#\n# No `Result` wrapper, no exception hierarchy, no new type machinery: unions\n# already exist and already carry a type id. What makes a member an *error*\n# member is that its type implements `Error`, a trait the compiler owns.\n\nmodule samples/errors\n\npub struct IOError\n getter path : String\n\n def initialize(@path : String)\n end\nend\n\n# `Error` is not declared here. It is the compiler's, so that `!` can ask\n# whether a member implements it without a name in scope deciding the answer.\nimpl Error for IOError\n def message : String\n "no such file: #{path}"\n end\nend\n\npub struct ParseError\n getter text : String\n\n def initialize(@text : String)\n end\nend\n\nimpl Error for ParseError\n def message : String\n "not a number: #{text}"\n end\nend\n\npub def read(path : String) : String | IOError\n return IOError.new(path) if path == "missing"\n path == "bad" ? "twelve" : "12"\nend\n\npub def to_number(text : String) : Int32 | ParseError\n return ParseError.new(text) unless text == "12"\n 12\nend\n\n# `!` propagates. If the value is a non-error member the expression evaluates to\n# it — narrowed, so `to_number` sees a `String` and not the union. If it is an\n# error member it is returned from here, which is why `load` has to admit both\n# errors in its own return type. Nothing enforces that separately: it is the\n# ordinary return-type check on the return the operator writes.\npub def load(path : String) : Int32 | IOError | ParseError\n text = read(path)!\n to_number(text)!\nend\n\n# Handling needs nothing new. An exhaustive `case` already checks totality, so\n# adding an error member to `load` turns every incomplete `case` on it into a\n# compile error. That is the main ergonomic argument for the whole approach and\n# it cost nothing to build.\n["config", "missing", "bad"].each do |path|\n case load(path)\n in Int32 then puts "read #{it}"\n in IOError then puts "io: #{it.message}"\n in ParseError then puts "parse: #{it.message}"\n end\nend\n\n# `it` is the value being matched, and it is narrowed per branch: the first sees\n# an `Int32`, the other two see one error type each, which is how `.message`\n# resolves without anything asking what the union holds. It is an assignment the\n# compiler writes into each branch, not new machinery — so `it` outlives the\n# `case` the way any variable assigned inside an `if` does, and a nested `case`\n# shadows it.\n\n# Where a `case` is overkill, `.or` takes a default and `.or_panic` takes the\n# value or dies (III.1.3). These cannot be ordinary methods: by II.1 a real\n# method call on `Int32 | IOError` would demand it from *both* members, and a\n# method every error type has to supply is what this design avoids. So the\n# compiler knows them by name — which is also why `or` and `or_panic` are not\n# names an iyi program can use for anything else.\n["config", "missing", "bad"].each do |path|\n puts "or: #{load(path).or(0)}"\nend\n\n# `.or` is an `is_a?(::Error)` and nothing more, so the result type falls out\n# instead of being computed: the default's type unioned with the non-error\n# members. Here that is `Int32`, and the default is only evaluated when there is\n# an error to recover from.\nputs "or: #{load("missing").or(-1)}"\n\n# `.or_panic` yields the non-error members alone, because what it calls never\n# returns. It reaches `message` on a value narrowed to `IOError | ParseError` —\n# two unrelated types — which resolves by II.1: both implement `Error`, so their\n# union does. Until panics exist (III.1.4), this unwinds as a Crystal exception.\nputs "panic: #{load("config").or_panic}"\n\n# ---------------------------------------------------------------------------\n# defer — III.1.4\n# ---------------------------------------------------------------------------\n#\n# Once errors are values returned early, the interesting exit from a body is an\n# ordinary `return` in the middle of it, and `begin`/`ensure` makes you wrap\n# everything after the acquisition to cover it. `defer` names the cleanup at the\n# acquisition instead. It expands to that same `ensure`, so it runs on a normal\n# exit, on a `!` propagation, and on an unwind — no new runtime machinery, only\n# a different shape.\npub def session(path : String) : Int32 | IOError | ParseError\n puts " open outer"\n defer puts " close outer"\n\n text = read(path)!\n\n puts " open inner"\n defer puts " close inner"\n\n to_number(text)!\nend\n\n# Cleanup is LIFO, and that is the nesting rather than a rule: the second\n# `defer` expands inside the first one's body, so its `ensure` is the inner one.\n# The failing run proves the part that matters — `read(path)!` returns from the\n# middle of the body and the cleanup still happens.\nputs "success:"\nputs session("config").or(-1)\nputs "propagating:"\nputs session("missing").or(-1)\n\n# The scope is the block, not the function. This is the departure from Go, and\n# it is what the expansion does rather than an extra rule: the cleanup lands\n# inside the loop body, so it runs each iteration instead of piling up until the\n# function returns — Go's best-known wart with the feature.\n2.times do |i|\n defer puts " iteration #{i} closed"\n puts " iteration #{i}"\nend\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * Dropping the `in ParseError` branch — `case is not exhaustive`.\n#\n# * `def go : String` with `read(p)!` in it — the error names the return type\n# and the error being propagated into it.\n#\n# * `!` on something with no error member, such as an `Int32?`. Rejected rather\n# than given a surprising meaning: `Nil` does not implement `Error` (III.1.5),\n# so without the check this would compile and silently do nothing.\n#\n# * `!` on a value whose every member is an error. It could never produce one,\n# and a function that genuinely never succeeds returns `NoReturn`.\n#\n# * `def sort!` — `!` is not part of a name (III.1.7). This is the one place\n# that still explains the naming convention, because after a `def` name there\n# is nothing to propagate.\n#\n# * `.or` and `.or_panic` on the same two operands `!` rejects. With no error\n# member there is nothing to recover; with every member an error, `.or` can\n# only return its default and `.or_panic` can only panic.\n#\n# * `.or 0` without parentheses — `.or` takes the default in parentheses, so\n# the shape cannot be confused with a call to a method of that name.\n#\n# * `it` in a `case` over a tuple, such as `case {a, b}` — there is no single\n# value to name, so `it` stays undefined rather than being bound to one of\n# the elements.\n#\n# * `defer close(f)!` — a `defer` runs while the scope is already being left,\n# possibly carrying an error of its own, so a second one leaving from there\n# is the error-during-error problem. Handle it in the `defer` body.\n#\n# * `impl Error for String` here — the orphan rule (R-3). `Error` is the\n# compiler's and `String` is the prelude's, so neither belongs to a module\n# this impl could be at home in, and every module that can name both would be\n# free to write its own.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * Panics (III.1.4). They are meant to be catchable only at task boundaries,\n# and what a task boundary is belongs to Part V.5. Until then `.or_panic`\n# raises a Crystal exception, and `defer` runs on that unwind either way.\n", @@ -28,7 +29,7 @@ "samples/iyi/grid.iyi": "# The first program with a table in it - rows of cells, built from their\n# coordinates, read across, down and along the diagonal.\n#\n# ./bin/iyi run samples/iyi/grid.iyi\n#\n# An array built by a block, a range asked for a value per position, and\n# the small set a list of numbers gets asked for: reversed, unique, paired\n# with another, one taken out. Like `basics`, `inventory` and `config`, this\n# documents no rule; it is here because the prelude grows only when a\n# program in this repository needs something (SPEC.md 0.1.0 item 3).\nmodule samples/grid\n\nn = 4\ngrid = Array(Array(Int32)).new(n) { |r| Array(Int32).new(n) { |c| r * n + c } }\ngrid.each { |row| puts row.map { |v| v.to_s.rjust(3) }.join }\n# => 0 1 2 3\n# => 4 5 6 7\n# => 8 9 10 11\n# => 12 13 14 15\n\nputs grid.map(&.sum).sum # => 120\nputs (0...n).map { |i| grid[i][i] } # => [0, 5, 10, 15]\nputs (0...n).map { |c| grid.map { |row| row[c] } }.first # => [0, 4, 8, 12]\n\n# 2. A list of numbers, asked the usual things.\nputs (1..5).to_a.reversed # => [5, 4, 3, 2, 1]\nputs (1..10).select { |i| i.even? } # => [2, 4, 6, 8, 10]\nputs (1..10).sum # => 55\nputs [3, 1, 3, 2, 1].uniq # => [3, 1, 2]\nputs [1, 2, 3].zip(["a", "b", "c"]) # => [{1, "a"}, {2, "b"}, {3, "c"}]\n\nletters = ["b", "a", "c"]\nputs letters.index("c").inspect # => 2\nputs letters.delete("a") # => a\nputs letters # => ["b", "c"]\n\n# 3. A line built piece by piece, then ended.\n5.times { |i| print i }\nputs # => 01234\n", "samples/iyi/hello.iyi": "# The first iyi program.\n#\n# ./bin/iyi run samples/iyi/hello.iyi\n#\n# Everything below compiles and runs today. What is still missing is listed at\n# the bottom — this file is meant to stay honest about how far the\n# implementation actually reaches.\n\n# R-1: the compilation-unit header. A module is the unit of compilation and of\n# recompilation; `import` edges between modules form a DAG. Reading a module's\n# `.iyimod` is what will one day replace re-analysing its source.\nmodule samples/hello\n\n# R-3: a trait is a named set of requirements. `abstract def` marks a method an\n# implementer must provide. Traits replace Crystal's open classes: a type's\n# method set is knowable without reading the whole program.\npub trait Greet\n abstract def greet : String\nend\n\npub trait Loud\n abstract def shout : String\nend\n\n# R-2: `pub` marks a declaration as exported, and exported declarations carry\n# full signatures. That signature is the module's contract, and the only thing\n# a consumer ever needs to read.\npub struct User\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\npub struct Robot\n getter id : Int32\n\n def initialize(@id : Int32)\n end\nend\n\n# `impl Trait for Type` — the replacement for reopening a class. R-3's orphan\n# rule requires it to live in the module defining either the trait or the\n# type, and that is enforced: both are defined right here. `modules.iyi` shows\n# the cross-module case, where the choice actually costs something.\nimpl Greet for User\n def greet : String\n "Hello, #{name}!"\n end\nend\n\n# A type may implement several traits...\nimpl Loud for User\n def shout : String\n greet.upcase\n end\nend\n\n# ...and a trait may be implemented by several types.\nimpl Greet for Robot\n def greet : String\n "BEEP #{id}"\n end\nend\n\n# `impl` is the only way to give a type a trait. SPEC.md II.8 — each of these\n# is refused, and each for its own reason:\n#\n# struct User\n# include Greet # a type acquires a trait by having an impl, whose\n# end # location R-3 can check; `include` has no such rule\n#\n# import samples/hello/greet::* # a trait exports no names to bring\n# # into scope — a trait method is resolved\n# # from the receiver, never from an\n# # import's names\n#\n# impl Greet for Loud # a blanket impl in disguise\n#\n# And leaving a requirement unmet is an error where the impl is written, not\n# where the type is later used:\n#\n# impl Loud for Robot\n# end\n# # => impl Loud for Robot is missing a method required by the trait: shout\n\n# Not exported. No signature required, because nothing outside this module can\n# depend on it — inference inside a module costs only what the module costs.\ndef describe(item)\n item.greet\nend\n\n# A trait bound on a method's own type parameter — SPEC.md II.7 rule 3. The\n# method exists for every `T`; the bound is checked where `T` binds, so a call\n# with a type that does not implement `Greet` is rejected at that call:\n#\n# announce(42)\n# # => Int32 does not implement Samples::Hello::Greet, required by `T`\n# # in `announce`\n#\n# This is the shape Kemal's router needs: `&block : Context -> B forall\n# B : IntoBody` replaces a per-request runtime type check with one at compile\n# time. Note this is *not* the impl-level bound below — nothing here is\n# conditional.\npub def announce(item : T) : String forall T : Greet\n "-> #{item.greet}"\nend\n\n# Ruby ergonomics, unchanged and free to compile: blocks, string\n# interpolation, everything-is-an-expression, local inference.\nputs User.new("iyi").greet\nputs User.new("iyi").shout\nputs Robot.new(42).greet\n\n# A trait as a type: both implementers dispatch through it.\ncrowd = [User.new("crystal"), Robot.new(7)] of Greet\ncrowd.each { |member| puts describe(member) }\n\nputs announce(Robot.new(9))\n\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * Trait bounds on a generic *impl*'s parameters (`impl Show for Box(T)\n# forall T : Show`) parse but are rejected — unlike the bound on `announce`\n# above, which works. A bound on an impl makes the impl conditional:\n# `Box(Int32)` would implement `Show` only if `Int32` did, which has to be\n# checked where the type is instantiated rather than where the impl is\n# written. A different mechanism, and the harder of the two.\n#\n# * Dictionary-passing generics is unimplemented: `announce` above is\n# monomorphised the way Crystal monomorphises it.\n#\n# * `.iyimod` reaches the front end and stops there. `modules.iyi` and\n# `immutable.iyi` compile with the imported module's source deleted, but a\n# build that reads artifacts cannot yet produce a program, because the\n# artifact carries declarations and no object code (SPEC.md IV.1f).\n#\n# * The prelude under this file is iyi's own, 833 lines of it, and that is why\n# building it costs 0.32 s rather than 2.10 s. It is not a standard library\n# and does not pretend to be one: no `Float64#to_s`, no `Range`, no `Set`,\n# no formatting, and `raise` is a panic that prints and exits. What is in it\n# is what a sample called for (SPEC.md, "0.1.0", item 3).\n#\n# See `modules.iyi` for `import` and its `::*` and `::{...}` forms across\n# files, `generics.iyi` for implementing a trait for a generic type and for\n# associated types, bounds and requirements, and `collections.iyi` for\n# `Enumerable` ported onto all of it.\n", "samples/iyi/immutable.iyi": "# The shareable immutable sequence, exercised — SPEC.md III.4.7.\n#\n# ./bin/iyi run samples/iyi/immutable.iyi\n#\n# `std/list.iyi` holds the type. This file is the evidence for the two claims\n# made about it, because a type that merely compiles has proved neither:\n#\n# * It is genuinely immutable — nothing a caller keeps a hold of can change\n# a list after it exists, which is what makes it shareable under III.4.4.\n# * It costs one `each` and gets the rest, which is II.6's claim tested on a\n# second type after `Enumerable` itself, and the first time on a *generic*\n# one.\n\nmodule samples/immutable\n\nimport std/list::{List}\n\n# Two ways of naming the same type, both across a module boundary. The second\n# lets the argument be inferred, and it is written that way on purpose: the\n# artifact carries no machine code for a generic, so `List(String)` is a type\n# this file makes, and inference puts `new` on `List(T)` instead of on the\n# instance. That difference once linked undefined and nothing here caught it,\n# because every line in this file wrote the argument out.\nnums = List(Int32).new([3, 1, 4, 1, 5])\nwords = List.new(["pear", "fig", "plum", "kiwi"])\n\n# ---------------------------------------------------------------------------\n# Its own surface: small on purpose\n# ---------------------------------------------------------------------------\nputs "size #{nums.size}"\nputs "empty? #{nums.empty?} #{List(Int32).new([] of Int32).empty?}"\nputs "at #{nums.at(0)} #{nums.at(4)}"\n\n# ---------------------------------------------------------------------------\n# Immutability, demonstrated rather than asserted\n# ---------------------------------------------------------------------------\n#\n# `appended` returns a new list and leaves this one alone. By III.1.7 the plain\n# verb names the mutating form, so the participle is the only name this type\n# can have — `push` and `sort` describe something it cannot do. The naming\n# convention was settled before any library code existed precisely so that it\n# would fall out here rather than be applied here.\nlonger = nums.appended(9)\nputs "appended #{longer.size} from #{nums.size}, original #{nums.to_a.join(",")}"\nputs "concatenated #{nums.concatenated(List(Int32).new([7])).to_a.join(",")} / #{nums.size}"\n\n# The constructor copies, and this is the line that shows why it must. `source`\n# is still held by this file; if the list had kept it rather than copying, a\n# task holding `taken` would see it change underneath. That copy is the price\n# of having no borrow checker to say "I own this now" — one copy, at the\n# boundary, once.\nsource = [1, 2]\ntaken = List(Int32).new(source)\nsource << 3\nputs "copied list #{taken.size}, source #{source.size}"\n\n# ---------------------------------------------------------------------------\n# Everything below here is the trait's, from one `each`\n# ---------------------------------------------------------------------------\nputs "to_a #{nums.to_a.join(",")}"\nputs "all?/any? #{nums.all? { |x| x > 0 }} #{nums.any? { |x| x > 4 }}"\nputs "none?/one? #{nums.none? { |x| x > 9 }} #{nums.one? { |x| x == 4 }}"\nputs "count #{nums.count { |x| x == 1 }}"\nputs "find #{nums.find { |x| x > 3 }} #{nums.find { |x| x > 9 }.inspect}"\nputs "index #{nums.index { |x| x == 4 }} #{nums.index { |x| x == 9 }.inspect}"\nputs "first?/first #{nums.first?} #{nums.first}"\nputs "map #{nums.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{nums.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{nums.map_with_index { |x, i| x * i }.join(",")}"\nputs "select/reject #{nums.select { |x| x > 2 }.join(",")} #{nums.reject { |x| x > 2 }.join(",")}"\nputs "partition #{nums.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{nums.take(2).join(",")} #{nums.skip(3).join(",")}"\nputs "take_while #{nums.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{nums.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{nums.reduce(0) { |a, x| a + x }}"\nputs "each_with_object #{nums.each_with_object([] of Int32) { |e, o| o << e * 10 }.join(",")}"\nputs "join #{nums.join("-")}"\nputs "includes? #{nums.includes?(4)} #{nums.includes?(9)}"\nputs "max/min #{nums.max} #{nums.min}"\nputs "sum/product #{nums.sum} #{nums.product}"\nputs "tally #{nums.tally[1]}"\nputs "sorted #{nums.sorted.join(",")}"\nputs "max_by #{words.max_by { |s| s.size }}"\nputs "group_by #{words.group_by { |s| s.size }[4].join(",")}"\n\n# The trait reaches a generic type, which is the part that was not built until\n# this file needed it. `type Elem = T` binds the associated type to the impl's\n# own parameter, so `List(Int32)` enumerates `Int32` and `List(String)`\n# enumerates `String` from the same impl — II.6 × II.7, which had never met.\nputs "two elem types #{nums.map { |x| x + 1 }.join(",")} / #{words.map { |s| s.size }.join(",")}"\n\n# ---------------------------------------------------------------------------\n# All of the above, with `src/std` deleted\n# ---------------------------------------------------------------------------\n#\n# ./bin/crystal build --no-codegen --emit-iyimod /tmp/mods samples/iyi/immutable.iyi\n# mv src/std /tmp/std-elsewhere\n# ./bin/crystal build --no-codegen --use-iyimod /tmp/mods samples/iyi/immutable.iyi # compiles\n# mv /tmp/std-elsewhere src/std\n#\n# This file is the hard case for R-1's contract, not `modules.iyi`: what has to\n# survive the round trip is a generic struct, a 575-line trait with an\n# associated type, and a generic impl that answers it. `mod dump --declarations\n# /tmp/mods/std/list.iyimod` is what the second build reads — the twelve\n# headers with no bodies, and `impl … for Std::List::List(T) forall T` with its\n# `type Elem = T`. Every call above is checked against those and against\n# nothing else (SPEC.md IV.1f).\n#\n# Not codegen: artifacts from a `--no-codegen` build carry no object code, so\n# the second build passes `--no-codegen` too and produces no program - one that\n# links against them is refused by name. `./bin/crystal run` above is still an\n# ordinary build that reads the sources.\n", - "samples/iyi/init_order.iyi": "# Module initialisation order: SPEC.md III.5.\n#\n# ./bin/iyi run samples/iyi/init_order.iyi\n#\n# Prints its five lines in the order they are numbered, and the numbering is\n# the whole point — none of it comes from where the `import` directives are\n# written. A module's top-level expressions are its initialiser (rule 3), and\n# a module initialises after every module it imports (rule 1).\n\nmodule samples/init_order\n\n# Written above the imports on purpose, and it still runs after both modules\n# below have initialised. The compiler holds a module's initialiser apart from\n# the site that imported it and puts them in the order the import DAG forces:\n# `boot/config` before `boot/registry` because the second imports the first,\n# and this file last because it imports both.\nputs "4. samples/init_order, above its own `import` lines"\n\nimport boot/registry\nimport boot/config\n\nputs "5. #{Boot::Registry.greeting}"\n\n# ---------------------------------------------------------------------------\n# What this sample does not promise\n# ---------------------------------------------------------------------------\n#\n# * The order of two modules that do not import each other. Rule 2 says that\n# order is *unobservable*, not merely unspecified — a module can only name\n# what it imports (R-1), reach what that module exports (R-2), and reopen\n# nothing (R-3), so an initialiser has nothing of an unrelated module to\n# look at. Nothing here prints from two independent modules, because that\n# would be documenting an order the spec refuses to have — and debug builds\n# shuffle it, so the documentation would be wrong most of the time. Build\n# with `--release` to get the load order back, or `IYI_INIT_SEED=1` to get\n# the same shuffled one twice.\n#\n# * Anything about the second `import` above. `boot/config` was already loaded\n# by `boot/registry`, so naming it here adds an edge and nothing else: a\n# module imported by several others is compiled once and initialised once.\n#\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * A top-level expression that propagates an error with `!`. Initialisation\n# may not fail (rule 4): if it can fail it is not initialisation, it is work,\n# and work belongs in a function the program calls when it is ready to handle\n# the failure. The error names III.5.\n#\n# * An import cycle. `import` forms a DAG (R-1), and a cycle has no order that\n# satisfies rule 1 — each module would have to initialise after the other.\n# The error names the cycle. Adding `import boot/registry` to `boot/config`\n# reports `boot/registry -> boot/config -> boot/registry`, the chain as it\n# was walked, not as it was written.\n#\n# * An `import` inside a `def` — `can't import inside def`, reported by the\n# parser. Inside any other expression the semantic pass reports `can't import\n# dynamically` instead. Rule 1 needs the import graph to be a DAG the\n# compiler can see before it runs anything.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * A module's initialiser as something an artifact could store. The compiler\n# holds the modules' *nodes* apart from their import sites, which is what\n# rules 1 and 2 needed, but `.iyimod` needs the step after: a callable\n# initialiser in the module's own object code (SPEC.md Part IV).\n", + "samples/iyi/init_order.iyi": "# Module initialisation order: SPEC.md III.5.\n#\n# ./bin/iyi run samples/iyi/init_order.iyi\n#\n# Prints its five lines in the order they are numbered, and the numbering is\n# the whole point — none of it comes from where the `import` directives are\n# written. A module's top-level expressions are its initialiser (rule 3), and\n# a module initialises after every module it imports (rule 1).\n\nmodule samples/init_order\n\n# Written above the imports on purpose, and it still runs after both modules\n# below have initialised. The compiler holds a module's initialiser apart from\n# the site that imported it and puts them in the order the import DAG forces:\n# `boot/config` before `boot/registry` because the second imports the first,\n# and this file last because it imports both.\nputs "4. samples/init_order, above its own `import` lines"\n\nimport boot/registry\nimport boot/config\n\nputs "5. #{Boot::Registry.greeting}"\n\n# ---------------------------------------------------------------------------\n# What this sample does not promise\n# ---------------------------------------------------------------------------\n#\n# * The order of two modules that do not import each other. Rule 2 leaves it\n# unspecified, and output that changes with it is the bug. Nothing here\n# prints from two independent modules, because that would be documenting an\n# order the spec refuses to have — and debug builds do not keep the load\n# order: they take the ready module whose path hashes lowest, the same\n# order on every build. Build with `--release` to get the load order back,\n# or with `IYI_INIT_SEED=1` to see another hashed one.\n#\n# * Anything about the second `import` above. `boot/config` was already loaded\n# by `boot/registry`, so naming it here adds an edge and nothing else: a\n# module imported by several others is compiled once and initialised once.\n#\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * A top-level expression that propagates an error with `!`. Initialisation\n# may not fail (rule 4): if it can fail it is not initialisation, it is work,\n# and work belongs in a function the program calls when it is ready to handle\n# the failure. The error names III.5.\n#\n# * An import cycle. `import` forms a DAG (R-1), and a cycle has no order that\n# satisfies rule 1 — each module would have to initialise after the other.\n# The error names the cycle. Adding `import boot/registry` to `boot/config`\n# reports `boot/registry -> boot/config -> boot/registry`, the chain as it\n# was walked, not as it was written.\n#\n# * An `import` inside a `def` — `can't import inside def`, reported by the\n# parser. Inside any other expression the semantic pass reports `can't import\n# dynamically` instead. Rule 1 needs the import graph to be a DAG the\n# compiler can see before it runs anything.\n#\n# ---------------------------------------------------------------------------\n# Not yet implemented\n# ---------------------------------------------------------------------------\n#\n# * A module's initialiser as something an artifact could store. The compiler\n# holds the modules' *nodes* apart from their import sites, which is what\n# rules 1 and 2 needed, but `.iyimod` needs the step after: a callable\n# initialiser in the module's own object code (SPEC.md Part IV).\n", "samples/iyi/inventory.iyi": "# The first program with a struct in it - what a person writes in the second\n# half hour, after `basics`.\n#\n# ./bin/iyi run samples/iyi/inventory.iyi\n#\n# A record type, a list of them, and the questions a list gets asked: which\n# ones, how many, the largest, each with its number, and the same list by\n# name. Like `basics`, this documents no rule. It is here because the prelude\n# grows only when a program in this repository needs something (SPEC.md 0.1.0\n# item 3), and these are the calls the second program makes that the first\n# did not.\nmodule samples/inventory\n\nstruct Item\n property name : String\n property price : Float64\n property quantity : Int32\n\n def initialize(@name : String, @price : Float64, @quantity : Int32)\n end\n\n def total : Float64\n price * quantity\n end\nend\n\nitems = [\n Item.new("apple", 0.5, 10),\n Item.new("pear", 0.75, 4),\n Item.new("melon", 3.0, 1),\n]\n\n# 1. A total, and a subset.\ngrand = 0.0\nitems.each { |item| grand += item.total }\nputs "total: #{grand}" # => total: 11.0\ncheap = items.select { |item| item.price < 1.0 }\nputs "cheap: #{cheap.map(&.name).join(", ")}" # => cheap: apple, pear\n\n# 2. Questions with one answer.\nputs items.count { |item| item.quantity > 1 } # => 2\nputs items.any? { |item| item.price > 2.0 } # => true\nputs items.all? { |item| item.quantity > 0 } # => true\nputs items.max_by { |item| item.price }.name # => melon\nputs items.min_by { |item| item.total }.name # => pear, the first of equals\nif pear = items.find { |item| item.name == "pear" }\n puts pear.total # => 3.0\nend\nputs items.find { |item| item.name == "plum" }.inspect # => nil\n\n# 3. A report, each line numbered.\nitems.each_with_index do |item, index|\n puts "#{index + 1}. #{item.name.ljust(6)} #{item.quantity.to_s.rjust(3)} #{item.total}"\nend\n# => 1. apple 10 5.0\n# => 2. pear 4 3.0\n# => 3. melon 1 3.0\n\n# 4. The same items by name.\nby_name = {} of String => Item\nitems.each { |item| by_name[item.name] = item }\nputs by_name.keys # => ["apple", "pear", "melon"]\nputs by_name.values.map(&.quantity).sum # => 15\nputs by_name.keys.sorted.last # => pear\n", "samples/iyi/io.iyi": "# IO: line-at-a-time reading of standard input.\n#\n# printf "first line\\nsecond line\\nthird line\\n" | ./bin/iyi run samples/iyi/io.iyi\n#\n# Reads standard input line by line through stdin.read_line, demonstrating\n# buffered stream reading.\n\nmodule samples/io\n\nline_number = 0\nwhile line = stdin.read_line(chomp: true)\n line_number = line_number + 1\n puts "#{line_number}: #{line}"\nend\n\nif line_number == 0\n puts "no lines read from standard input"\nelse\n puts "read #{line_number} line(s) from standard input"\nend\n", "samples/iyi/kemal/dsl.iyi": "# Kemal's DSL, ported from `kemal/src/kemal/dsl.cr`.\n#\n# The original's first line is the whole problem: "Kemal DSL is defined here\n# and it's baked into global scope." `require "kemal"` injects `get`, `post`\n# and `before_all` into the importing program's namespace — the same action at\n# a distance as an open class, and also Kemal's entire identity. Making users\n# write `kemal.get "/" do ... end` would be safe and would kill the framework.\n#\n# `import X::{...}` moves the decision to the consumer: the library exports\n# names, and the program says which ones it wants unqualified. See\n# `webapp.iyi`.\nmodule kemal/dsl\n\nimport kemal/router::{Router, Context, IntoBody}\n\nHTTP_METHODS = %w(get post put patch delete options head)\n\n# One application value, in place of `Kemal::RouteHandler::INSTANCE` and the\n# three singletons beside it. `router.cr:270` works around those being global\n# and mutable — "may have been cleared between tests" — and nothing about\n# separate compilation forced that; a clean sheet is just the moment to fix it.\nAPP = Router.new\n\n{% for method in HTTP_METHODS %}\n pub def {{ method.id }}(path : String, &block : Context -> B) : Nil forall B : IntoBody\n APP.{{ method.id }}(path, &block)\n end\n{% end %}\n\npub def before_all(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n APP.before(path, &block)\nend\n\npub def after_all(path : String = "*", &block : Context -> B) : Nil forall B : IntoBody\n APP.after(path, &block)\nend\n\npub def mount(path : String, router : Router) : Nil\n APP.mount(path, router)\nend\n\npub def app : Router\n APP\nend\n", @@ -93,6 +94,8 @@ "records/break/r3_unmet_trait_bound.iyi": "# Breaks R-3 from the calling side: a type's method set is closed, so a bound\n# on a type parameter is a question with a definite answer at the call. `Int32`\n# has no `impl Greet` and cannot acquire one later, so the call is refused as a\n# missing trait rather than as a failed overload match.\nmodule diagnostics/r3_unmet_trait_bound\n\npub trait Greet\n abstract def greet : String\nend\n\npub struct User\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Greet for User\n def greet : String\n "Hello, #{name}."\n end\nend\n\npub def announce(item : T) : String forall T : Greet\n "-> #{item.greet}"\nend\n\nputs announce(User.new("iyi"))\nputs announce(42)\n", "records/break/r4_dictionary.iyi": "# Breaks R-4 by asking for the one clause of it that has nothing behind it.\n#\n# R-4's third sentence says `@[Monomorphize]` forces specialisation across a\n# module boundary. SPEC.md III.9 records what that is worth today: nothing in\n# the compiler mentions the annotation, a dictionary or a GC shape. So this is\n# not a feature that misbehaves, it is a name the compiler has never heard,\n# and the rejection below is a compiler declining to accept a promise it\n# cannot keep rather than compiling the program as though the rule were built.\n#\n# What runs instead of the dictionary is the rule's second sentence applied\n# everywhere: a generic's body travels in the artifact and every consumer\n# compiles it again for the types it uses. `bench/generic_boundary.py` is the\n# measurement of what that costs a consumer's front end.\nmodule diagnostics/r4_dictionary\n\n@[Monomorphize]\npub def twice(x : T) : Array(T) forall T\n [x, x]\nend\n\nputs twice(1).size\n", "records/break/r5_global_question.iyi": "# Breaks R-5: a macro sees the declaration it is attached to, and nothing\n# global. Everything here is legal. `Point` is this module's own type, the\n# derive is written inside its body where a derive belongs, and `counted`\n# is a name this file imports from another module, which is the only way a\n# derive macro can arrive. What is refused is the one question the macro asks:\n# `all_subclasses` answers with the whole program rather than with this\n# declaration, so the answer would depend on what else was compiled and the\n# methods it generated could not travel in this module's artifact.\n#\n# The refusal lands at expansion, in the consumer's build, which is where the\n# macro runs. `dep/global_derive.iyi` is the macro (SPEC.md R-5, II.4).\nmodule diagnostics/r5_global_question\n\nimport dep/global_derive::*\n\npub struct Point\n @x : Int32\n\n def initialize(@x : Int32)\n end\n\n derive counted\nend\n\nputs Point.new(1).counted\n", + "records/break/share_reach.iyi": "# Breaks III.4.4's reach check: the block names nothing unshareable, but\n# `greeting`, which it calls, reads `GREETING`, an `Array` any worker could\n# reach while another changes it. The compiler walks the calls from the\n# block and warns at the block, by the shortest path. A warning, so the\n# build is asked to fail on one: `--error-on-warnings`, as CI would.\n# `@[SafeReach("...")]` on the constant, saying why it is safe, quiets it.\nmodule diagnostics/share_reach\n\nimport std/http::{Response, Server}\nimport std/socket::{IyiSocket}\n\nGREETING = ["hello", "from", "every", "core"]\n\ndef greeting : String\n GREETING.join(" ")\nend\n\nlistener = IyiSocket.listen("127.0.0.1", 0)\nServer.serve(listener) do |req|\n Response.new(200, greeting)\nend\n", + "records/break/share_serve_capture.iyi": "# Breaks III.4.4 as III.4.13 applies it: `Server.serve` runs each connection\n# on the pool's workers, so its block is checked as `spawn_parallel`'s is,\n# and every value it captures must be `Share`. An `Array` is not: two\n# workers appending at once would race its size. Everything else here is\n# legal, and the refusal names both ways out - the array behind a `Mutex`,\n# or `workers: 1`, which keeps the server on one thread.\nmodule diagnostics/share_serve_capture\n\nimport std/http::{Response, Server}\nimport std/socket::{IyiSocket}\n\nseen = [] of String\nlistener = IyiSocket.listen("127.0.0.1", 0)\nServer.serve(listener) do |req|\n seen << req.path\n Response.new(200, "ok")\nend\n", "records/agent/app/greeter.iyi": "# The dependency. Its exported surface is the whole of what a consumer, or an\n# agent editing one, is allowed to know: two functions with their types\n# written down, and a trait. `greeting` has no `pub`, so it is this module's\n# own business and appears in no consumer's grounding (SPEC.md R-1, IV.2).\nmodule app/greeter\n\npub trait Greet\n abstract def greet : String\nend\n\n# The exported surface carries its doc comment, so the grounding pack an agent\n# reads says what the function is for and not only what it takes.\npub def polite(name : String) : String\n "#{greeting}, #{name}."\nend\n\npub def shout(name : String) : String\n "#{greeting.upcase}, #{name.upcase}!"\nend\n\ndef greeting : String\n "Hello"\nend\n", "records/agent/app.iyi": "# The file being edited, in the state an agent meets it: one call misspelled.\n# The compiler knows the name it meant, so `iyi check -f json` carries a\n# `suggested_edit` with the exact replacement span and `iyi fix` applies it.\nmodule app\n\nimport app/greeter::*\n\npub def main : Int32\n puts polit("Türkiye")\n 0\nend\n", "records/agent/app_test.iyi": "# A test is a plain program that exits non-zero when it fails; the prelude's\n# `assert` is that in one word. No framework, so nothing here has to be\n# learned before it can be read.\nmodule app/app_test\n\nimport app/greeter::*\n\nassert polite("iyi") == "Hello, iyi.", "polite greets"\nassert shout("iyi") == "HELLO, IYI!", "shout shouts"\n", diff --git a/records/stdlib.json b/records/stdlib.json index 70324cd..41d10b5 100644 --- a/records/stdlib.json +++ b/records/stdlib.json @@ -1,16 +1,22 @@ { "recorded": { - "compiler": "iyi 0.18.0 (built on Crystal 1.22.0-dev [2a96530] (2026-10-18))", - "commit": "2a9653052ab76f9d8084da47e059273f6ef45228", - "machine": "Windows_NT 10.0.26200 x64, AMD Ryzen AI 9 465 w/ Radeon 880M", + "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", + "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "machine": "Linux 7.0.0-38-generic x64, QEMU Virtual CPU version 2.5+", "command": "npm run record:stdlib", - "when": "2026-10-21T13:45:42.974Z" + "when": "2026-11-02T07:19:39.580Z" }, "modules": [ + { + "name": "std/acme", + "path": "src/std/acme.iyi", + "sourceSha256": "3182946f098dcd1fcc2e03165324b06abc43d96ddd45a34d8b13c36df821ed19", + "surface": "module std/acme\n\n# An authorization (section 7.1.4): the identifier, its status, and the\n# challenges by which it can be proved.\npub struct AcmeAuthorization\n # The challenge of *kind* offered, or why there is none.\n def challenge(kind : String) : AcmeChallenge | AcmeError\n def challenges : Array(AcmeChallenge)\n def self.from(url : String, object : Hash(String, Any)) : AcmeAuthorization | AcmeError\n def identifier : String\n def initialize(url : String, identifier : String, status : String, wildcard : Bool, challenges : Array(AcmeChallenge))\n def status : String\n def url : String\n def wildcard : Bool\nend\n\n# A challenge (section 8): its type (`http-01`, `dns-01`, `tls-alpn-01`),\n# where to answer it, its token, and its status, with the problem that\n# made it invalid.\npub struct AcmeChallenge\n def error : AcmeError | ::Nil\n def self.from(object : Hash(String, Any)) : AcmeChallenge | AcmeError\n def initialize(kind : String, url : String, token : String, status : String, error : AcmeError | ::Nil)\n def kind : String\n def status : String\n def token : String\n def url : String\nend\n\n# A session with an ACME server under one account key.\npub class AcmeClient\n protected def account=(url : String | ::Nil) : String | ::Nil\n # The account's URL, the `kid` every request after newAccount carries.\n def account_url : String | ::Nil\n # The authorization at *url*, read (POST-as-GET).\n def authorization(url : String) : AcmeAuthorization | AcmeError\n # `badNonce` answers sent again, over this session.\n def bad_nonce_retries : Int32\n # The certificate chain of a valid *order*, PEM, leaf first (section\n # 7.4.2): every block std/x509 reads, or why one does not read.\n def certificate(order : AcmeOrder) : String | AcmeError\n # How long to wait, in milliseconds, before polling again, as\n # *retry_after* - a Retry-After value - says (RFC 9110 section 10.2.3:\n # seconds, or an HTTP-date, judged against *now*), or *fallback_ms*\n # when there is none or it is neither. A date already past is no wait.\n def self.delay(retry_after : String | ::Nil, fallback_ms : Int32, now : Time) : Int64\n def directory : AcmeDirectory\n # The name dns-01's TXT record is put under for *domain*: a wildcard's\n # base domain, with `_acme-challenge.` before it.\n def self.dns_name(domain : String) : String\n # The dns-01 TXT record's value for *token* (section 8.4).\n def dns_txt(token : String) : String\n # One HTTP exchange, its answer read whole up to `MAX_BODY`; nothing\n # sent to a URL that is not `https://`.\n protected def self.exchange(method : String, url : String, body : String, accept : String | ::Nil, timeout_ms : Int32) : AcmeResponse | AcmeError\n # The order finalized with a certificate request for its names that\n # *key* signs (section 7.4): polled until its authorizations make it\n # ready, finalized, and polled until it is valid, with the certificate's\n # URL; or the error that made it invalid.\n def finalize(order : AcmeOrder, key : SigningKey) : AcmeOrder | AcmeError\n # Nil when *url* is an `https://` URL with a host, else the error that\n # refuses it as *what*: RFC 8555 section 6.1 requires HTTPS of every\n # ACME request, and anything else is plaintext an attacker on the path\n # can answer as the CA.\n def self.https(url : String, what : String) : AcmeError | ::Nil\n def initialize(directory_url : String, key : SigningKey, directory : AcmeDirectory, timeout_ms : Int32)\n def key : SigningKey\n # *token*'s key authorization (section 8.1): `token.thumbprint`.\n def key_authorization(token : String) : String\n # The most polls one wait makes, and the most milliseconds it takes.\n def max_polls : Int32\n # The most polls one wait makes, and the most milliseconds it takes.\n def max_polls=(value : Int32) : Int32\n # A session with the server whose directory is at *url*, signing with\n # *key*: the directory read, or why it could not be. *account_url*, an\n # account `register` answered before, is the `kid` from the start, so a\n # session that kept it need not register again. Both are `https://`.\n def self.new(url : String, key : SigningKey, timeout_ms : Int32 = 30000, account_url : String | ::Nil = nil) : AcmeClient | AcmeError\n # A new order for *names*, DNS identifiers (section 7.4).\n def new_order(names : Array(String)) : AcmeOrder | AcmeError\n # The order at *url*, read (POST-as-GET).\n def order(url : String) : AcmeOrder | AcmeError\n # How long to wait between polls when an answer has no Retry-After.\n def poll_ms : Int32\n # How long to wait between polls when an answer has no Retry-After.\n def poll_ms=(value : Int32) : Int32\n # The account for this key, made if there is none (section 7.3) - with\n # *contact*'s URLs (`mailto:`), the terms agreed to when\n # *terms_agreed* - or only found when *only_existing*: its URL, which\n # every request after this carries as `kid`.\n def register(contact : Array(String) = [] of String, terms_agreed : Bool = false, only_existing : Bool = false) : String | AcmeError\n # Tells the server *challenge* is ready to be checked (section 7.5.1):\n # its URL posted `{}`. Put the answer in place first.\n def respond(challenge : AcmeChallenge) : AcmeChallenge | AcmeError\n # The account moved to *new_key* (section 7.3.5): keyChange's inner JWS\n # signed by the new key, carrying its `jwk`, inside one the old key\n # signs as the account.\n def roll_key(new_key : SigningKey) : Nil | AcmeError\n # The account key's JWK thumbprint (RFC 7638), SHA-256 and base64url.\n def thumbprint : String\n # Each HTTP exchange's connect, and each read and write in it.\n def timeout_ms : Int32\n # Each HTTP exchange's connect, and each read and write in it.\n def timeout_ms=(value : Int32) : Int32\n # The authorization at *url* polled until it is no longer pending: the\n # valid authorization, or the error that made it something else.\n def wait_authorization(url : String) : AcmeAuthorization | AcmeError\n def wait_ms : Int32\n def wait_ms=(value : Int32) : Int32\nend\n\n# The directory's URLs (section 7.1.1).\npub struct AcmeDirectory\n def external_account_required : Bool\n def initialize(new_nonce : String, new_account : String, new_order : String, key_change : String | ::Nil, revoke_cert : String | ::Nil, terms_of_service : String | ::Nil, external_account_required : Bool)\n def key_change : String | ::Nil\n def new_account : String\n def new_nonce : String\n def new_order : String\n def self.parse(text : String) : AcmeDirectory | AcmeError\n def revoke_cert : String | ::Nil\n def terms_of_service : String | ::Nil\nend\n\n# What a request answered that it should not have, or could not do: a\n# problem document's `type` and `detail` (RFC 7807), or, with\n# `problem_type` empty, what failed beneath ACME.\npub struct AcmeError\n def detail : String\n # The identifier a subproblem is about (section 6.7.1), \"\" otherwise.\n def identifier : String\n def initialize(detail : String, problem_type : String = \"\", status : Int32 = 0, identifier : String = \"\", subproblems : Array(AcmeError) = [] of AcmeError)\n # Whether this came from a problem document.\n def problem? : Bool\n def problem_type : String\n # The HTTP status the problem came with; 0 when there was no answer.\n def status : Int32\n def subproblems : Array(AcmeError)\n def to_s : String\nend\n\n# http-01's answers (section 8.3), by token, for a std/http server: GET\n# `/.well-known/acme-challenge/` is answered with the token's key\n# authorization, a token not held with 404, any other path with nil - the\n# server's own to answer.\n#\n# responder = AcmeHttp01.new\n# Server.serve(listener, workers: 1) { |request| responder.answer(request) || app(request) }\n#\n# The table of answers is written as orders run, so it is one thread's:\n# the server that answers from it serves on that thread (`workers: 1`).\npub class AcmeHttp01\n def add(token : String, key_authorization : String) : Nil\n def answer(request : Request) : Response | ::Nil\n def initialize\n def remove(token : String) : Nil\nend\n\n# JWS (RFC 7515) as ACME signs it: the flattened JSON serialization, the\n# account's key as RFC 7638's canonical JWK, and the signature in the\n# form JWA (RFC 7518) gives each algorithm.\npub struct AcmeJws\n # The JWS algorithm *key* signs under: ES256, ES384, RS256 or EdDSA.\n def self.alg(key : SigningKey) : String\n # *data* in base64url without padding (RFC 7515 section 2).\n def self.b64u(data : String | Array(UInt8)) : String\n def self.bytes(text : String) : Array(UInt8)\n # The protected header: `alg`, then `jwk` when *kid* is nil and `kid`\n # when it is not, `nonce` when there is one, and `url`.\n def self.header(key : SigningKey, nonce : String | ::Nil, url : String, kid : String | ::Nil) : String\n def initialize\n # *key* as a JWK holding its required members alone, sorted, without\n # whitespace (RFC 7638 section 3): the text its thumbprint is taken of,\n # and the `jwk` a request carries.\n def self.jwk(key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey) : String\n # A big-endian number without its leading zero bytes, as JWA writes\n # one (RFC 7518 section 6.3.1).\n def self.minimal(bytes : Array(UInt8)) : Array(UInt8)\n # An Ecdsa-Sig-Value's DER as R || S, each *size* bytes.\n def self.raw(der : Array(UInt8), size : Int32) : Array(UInt8)\n # The flattened JSON serialization (RFC 7515 section 7.2.2) of *payload*\n # - nil for the empty payload of POST-as-GET - under *header*.\n def self.sign(key : SigningKey, header : String, payload : String | ::Nil) : String\n # *key*'s JWS signature of *input*: R || S for ECDSA, each at the\n # curve's width (RFC 7518 section 3.4), where std/p256 and std/p384 sign\n # in X.509's DER; RSA's and Ed25519's as they are.\n def self.signature(key : SigningKey, input : Array(UInt8)) : Array(UInt8)\n # *key*'s JWK thumbprint (RFC 7638): base64url of the SHA-256 of `jwk`.\n def self.thumbprint(key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey) : String\nend\n\n# An order (section 7.1.3): its URL, status, names, the authorizations to\n# prove, where to finalize it, and, once it is valid, its certificate.\npub struct AcmeOrder\n def authorizations : Array(String)\n def certificate_url : String | ::Nil\n def error : AcmeError | ::Nil\n def finalize_url : String\n def self.from(url : String, object : Hash(String, Any)) : AcmeOrder | AcmeError\n def identifiers : Array(String)\n def initialize(url : String, status : String, identifiers : Array(String), authorizations : Array(String), finalize_url : String, certificate_url : String | ::Nil, error : AcmeError | ::Nil)\n def status : String\n def url : String\nend\n\n# tls-alpn-01 (RFC 8737): the certificate a TLS server shows to a client\n# whose ALPN offers `acme-tls/1`, and a std/tls config that shows it.\npub struct AcmeTlsAlpn01\n # The self-signed certificate for *domain* (section 3): its one dNSName\n # *domain*, and the critical acmeIdentifier extension, an OCTET STRING\n # of the SHA-256 of *key_authorization*; signed by *key*, valid from a\n # day before *now* to a week after. A *domain* past 64 characters is\n # refused, as std/x509 refuses its common name.\n def self.certificate(domain : String, key_authorization : String, key : SigningKey, now : Time = Time.utc) : Array(UInt8) | AcmeError\n # A std/tls server config that shows `certificate` for *domain*, under a\n # P-256 key made for it, and speaks `acme-tls/1` alone by ALPN.\n def self.config(domain : String, key_authorization : String) : TlsServerConfig | AcmeError\n def initialize\nend\n\nimpl Error for AcmeError\n def message : String\nend\n" + }, { "name": "std/aead", "path": "src/std/aead.iyi", - "sourceSha256": "cab5d3927d0d986de337812219eb2c30766d1814d11f99510cf1f8c5df445b8c", + "sourceSha256": "6b1bdaac65733c8d4b3cd6630ddc53535b7196378b048ebdaf7dcea1f75535a1", "surface": "module std/aead\n\n# The AES block cipher, FIPS 197: a 16, 24 or 32-byte key (10, 12 or 14\n# rounds), one 16-byte block at a time, either way. On its own it is a\n# building block; a message wants a mode, `AesGcm`.\npub class AES\n # FIPS 197's InvCipher: the block `encrypt_block` answered, back.\n def decrypt_block(block : String | Array(UInt8)) : String\n # FIPS 197's Cipher: 16 bytes in, 16 out.\n def encrypt_block(block : String | Array(UInt8)) : String\n def initialize(key : String | Array(UInt8))\nend\n\n# What `open` answers for sealed bytes it will not open: shorter than a\n# tag, or whose tag does not match.\npub struct AeadError\n def initialize(message : String)\n def to_s : String\nend\n\n# AES in Galois/Counter Mode, NIST SP 800-38D: CTR encryption from the\n# counter after the nonce's own, and a 128-bit tag that is GHASH over the\n# additional data and the ciphertext, each padded to a block, and their\n# lengths in bits, XORed with the key stream of the nonce's counter\n# block. The nonce is 12 bytes, and the counter block is it and a 32-bit\n# counter that starts at 1 (§7.1).\npub class AesGcm\n def initialize(key : String | Array(UInt8))\n # The plaintext *sealed* came from, when its tag is the one this key,\n # *nonce* and *aad* give; else an `AeadError`, and nothing decrypted.\n def open(nonce : String | Array(UInt8), sealed : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String | AeadError\n # The ciphertext of *plaintext*, as long as it, and the 16-byte tag.\n def seal(nonce : String | Array(UInt8), plaintext : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String\nend\n\n# The ChaCha20 stream cipher, RFC 8439 §2.4: a 32-byte key, a 12-byte\n# nonce and a 32-bit block counter, which starts at *counter*. `xor`\n# answers its data XORed with the key stream and carries on from where\n# it stopped, so a message XORed in pieces is the message XORed whole;\n# the same call decrypts. The counter is the RFC's 32 bits, 256 GiB of\n# key stream; a stream run past block 2^32 - 1 is refused rather than\n# wrapped onto key stream already used.\npub class ChaCha20\n def initialize(key : String | Array(UInt8), nonce : String | Array(UInt8), counter : UInt32 = 0_u32)\n # *count* bytes from *src* to *dst*, which may be the same bytes.\n def xor(src : Pointer(UInt8), dst : Pointer(UInt8), count : Int32) : Nil\n def xor(data : String | Array(UInt8)) : String\nend\n\n# ChaCha20 and Poly1305 as an AEAD, RFC 8439 §2.8: the Poly1305 key is\n# the first 32 bytes of block 0's key stream, the plaintext is encrypted\n# from block 1, and the tag covers the additional data and the\n# ciphertext, each padded with zeros to 16 bytes, then their lengths as\n# 64-bit little-endian numbers.\npub class ChaCha20Poly1305\n def initialize(key : String | Array(UInt8))\n # The plaintext *sealed* came from, when its tag is the one this key,\n # *nonce* and *aad* give; else an `AeadError`, and nothing decrypted.\n def open(nonce : String | Array(UInt8), sealed : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String | AeadError\n # The ciphertext of *plaintext*, as long as it, and the 16-byte tag.\n def seal(nonce : String | Array(UInt8), plaintext : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String\nend\n\n# Poly1305, RFC 8439 §2.5: a 16-byte MAC under a 32-byte one-time key, r\n# (clamped) and s. One key authenticates one message: a second message\n# under it lets whoever sees both tags forge a third, which is why the\n# AEAD derives a fresh key from each nonce, and why there is no `reset`.\n# `Poly1305.mac(key, data)` is one piece; `new`, `update` as the message\n# arrives, `final` once is the same in pieces. A tag someone sent is\n# compared with std/crypto's `Subtle.constant_time_compare`, not `==`.\npub class Poly1305\n # The 16-byte tag. A last block shorter than 16 bytes carries its own\n # 1 bit after its bytes (§2.5.1), rather than the 2^128 of a whole one.\n def final : String\n def initialize(key : String | Array(UInt8))\n def self.mac(key : String | Array(UInt8), data : String | Array(UInt8)) : String\n def update(data : Pointer(UInt8), count : Int32) : self\n def update(data : String | Array(UInt8)) : self\nend\n\nimpl Error for AeadError\n def message : String\nend\n" }, { @@ -22,7 +28,7 @@ { "name": "std/asn1", "path": "src/std/asn1.iyi", - "sourceSha256": "7075c10aad9a3588994e26f1624a685fc0b61b4010d11fe0d8ac5dfc30981bda", + "sourceSha256": "d8a5c75bdde73037027287b62ecc89ffc83595fdee77713cfce3d451f46645e4", "surface": "module std/asn1\n\n# What `Der.parse`, `children` and the typed readers answer for bytes that\n# are not the DER they were asked for: a sentence, and the offset in the\n# bytes `Der.parse` was given where the trouble is.\npub struct Asn1Error\n def initialize(message : String, offset : Int32)\n def offset : Int32\n def to_s : String\nend\n\npub struct Der\n def initialize\n # The one value *bytes* hold, and nothing after it.\n def self.parse(bytes : Array(UInt8)) : DerValue | Asn1Error\n # Every value under *value*, *value* first, in the order they are\n # written, each with its depth below it: the whole tree read and every\n # length and tag checked. Primitive contents are not decoded; that is\n # the typed readers' job, asked of the values a schema names.\n def self.walk(value : DerValue, & : (DerValue, Int32 -> Nil)) : Nil | Asn1Error\nend\n\n# The two bits at the top of an identifier byte.\npub enum DerClass : Int32\n Universal = 0\n Application = 1\n ContextSpecific = 2\n Private = 3\nend\n\n# The universal tag numbers this module reads (X.680 section 8.6).\npub struct DerTag\n def initialize\nend\n\n# One value: its identifier, and where its header and content are in the\n# bytes it was read from.\npub struct DerValue\n # A BIT STRING whose bits fill whole bytes: a key, a signature.\n def bit_string : Array(UInt8) | Asn1Error\n # A BIT STRING's bytes and how many bits at the end of the last are not\n # part of it, which DER sets to zero: a flags field such as keyUsage.\n def bit_string_bits : ::Tuple(Array(UInt8), Int32) | Asn1Error\n def boolean : Bool | Asn1Error\n # The values a constructed value holds, read one level down.\n def children : Array(DerValue) | Asn1Error\n def constructed? : Bool\n # The content bytes, copied.\n def content : Array(UInt8)\n # The offset of the first content byte, and of the byte after the value.\n def content_offset : Int32\n def content_size : Int32\n def context?(tag : Int32) : Bool\n # The identifier, said the way an error names it: `INTEGER`,\n # `[0] constructed`, `APPLICATION 3`.\n def describe : String\n def end_offset : Int32\n # An ENUMERATED that fits an Int64: a CRL entry's reason code, an\n # OCSP response's status.\n def enumerated : Int64 | Asn1Error\n # An ENUMERATED's two's-complement bytes, held to INTEGER's rules.\n def enumerated_bytes : Array(UInt8) | Asn1Error\n # The one value inside an explicit context tag `[n]`, as X.509 writes a\n # certificate's version and extensions.\n def explicit(tag : Int32) : DerValue | Asn1Error\n def header_size : Int32\n def initialize(data : Array(UInt8), tag_class : DerClass, constructed : Bool, tag : Int32, offset : Int32, header_size : Int32, content_size : Int32)\n # An INTEGER that fits an Int64.\n def integer : Int64 | Asn1Error\n # An INTEGER's two's-complement bytes, as written: checked to be the\n # fewest that hold it. Only an INTEGER: an ENUMERATED is written the same\n # way under another tag, and reading one here took an Ecdsa-Sig-Value\n # whose r or s was tagged ENUMERATED - a signature no DER reader accepts.\n def integer_bytes : Array(UInt8) | Asn1Error\n def null : Nil | Asn1Error\n def octet_string : Array(UInt8) | Asn1Error\n def offset : Int32\n # An OBJECT IDENTIFIER in dotted form, `\"2.5.4.3\"`. A subidentifier past\n # an Int64 is refused: no algorithm or attribute is one.\n def oid : String | Asn1Error\n # The whole value as it was written, header included: what a signature\n # over a certificate's `tbsCertificate` is computed on.\n def raw : Array(UInt8)\n # One value's header at *at*, its content within *stop*.\n def self.read(data : Array(UInt8), at : Int32, stop : Int32) : DerValue | Asn1Error\n # A SEQUENCE's elements: `children`, after checking it is one.\n def sequence : Array(DerValue) | Asn1Error\n # A SET's elements.\n def set : Array(DerValue) | Asn1Error\n # Any of the string types X.509 names are written in, as UTF-8.\n def string : String | Asn1Error\n def tag : Int32\n def tag_class : DerClass\n # A UTCTime or a GeneralizedTime, in the one form DER writes each.\n def time : Time | Asn1Error\n def universal?(tag : Int32) : Bool\n def self.universal_name(tag : Int32) : String\n # A non-negative INTEGER's magnitude, big-endian, without the 0x00 a\n # high bit makes DER write: an RSA modulus, a serial number. A negative\n # one is refused.\n def unsigned_integer : Array(UInt8) | Asn1Error\nend\n\nimpl Error for Asn1Error\n def message : String\nend\n" }, { @@ -31,6 +37,12 @@ "sourceSha256": "3d1c544d3770dfa4b48136cda61ea466fc8ce6ea670fbe91b370f8ff21eda336", "surface": "module std/atomic\n\nstruct ::Atomic(T)\n def add_acq_rel(value : T) : T\n def add_acquire(value : T) : T\n def add_relaxed(value : T) : T\n def add_release(value : T) : T\n # `word &= value`; answers what the word held before.\n def and(value : T) : T\n # `word |= value`; answers what the word held before.\n def bitwise_or(value : T) : T\n def compare_and_set_acq_rel(expected : T, desired : T) : Tuple(T, Bool)\n def compare_and_set_acquire(expected : T, desired : T) : Tuple(T, Bool)\n # `compare_and_set` at each weaker ordering: if the word holds\n # `expected` it now holds `desired`; answers what the word held and\n # whether the exchange happened.\n def compare_and_set_relaxed(expected : T, desired : T) : Tuple(T, Bool)\n def compare_and_set_release(expected : T, desired : T) : Tuple(T, Bool)\n # A full fence: nothing crosses it in either direction.\n def self.fence : Nil\n # Both halves, without the total order of `fence`.\n def self.fence_acq_rel : Nil\n # Nothing after this fence moves before it.\n def self.fence_acquire : Nil\n # Nothing before this fence moves after it.\n def self.fence_release : Nil\n # The word, read once; nothing after the read moves before it.\n def get_acquire : T\n # The word, read once, atomic and nothing more.\n def get_relaxed : T\n # `word = max(word, value)`, signed or unsigned as `T` is; answers what\n # the word held before.\n def max(value : T) : T\n # `word = min(word, value)`, signed or unsigned as `T` is; answers what\n # the word held before.\n def min(value : T) : T\n # `word = ~(word & value)`; answers what the word held before.\n def nand(value : T) : T\n # The word, written once, atomic and nothing more.\n def set_relaxed(value : T) : Nil\n # The word, written once; nothing before the write moves after it.\n def set_release(value : T) : Nil\n def sub_acq_rel(value : T) : T\n def sub_acquire(value : T) : T\n def sub_relaxed(value : T) : T\n def sub_release(value : T) : T\n def swap_acq_rel(value : T) : T\n def swap_acquire(value : T) : T\n def swap_relaxed(value : T) : T\n def swap_release(value : T) : T\n # `word ^= value`; answers what the word held before.\n def xor(value : T) : T\nend\n\nstruct ::IyiAtomic\n def self.fence(ordering : Symbol, singlethread : Bool) : Nil\nend\n" }, + { + "name": "std/autocert", + "path": "src/std/autocert.iyi", + "sourceSha256": "7342edc6a46dfb8bf30eaed23fc23d7b9fed3ecbd477ea88dadbe157a94e755e", + "surface": "module std/autocert\n\n# Where a manager keeps its account key and its certificates: text by\n# key, nil for a key not held. Every value it is given holds a private\n# key.\npub trait AutocertCache\n abstract def delete(key : String) : AutocertError | ::Nil\n abstract def get(key : String) : String | AutocertError | Nil\n abstract def put(key : String, data : String) : AutocertError | ::Nil\nend\n\n# A cache in a directory: an entry a file named by its key. The directory\n# is made, 0700, the first time it is written to where it is not there;\n# an entry is written to a file of its own in it, created 0600 (the\n# umask may take more away, never less), then renamed over the old one.\npub class AutocertDirCache\n def dir : String\n def initialize(dir : String)\nend\n\n# Why a certificate could not be had, or the cache not read or written.\npub struct AutocertError\n def detail : String\n def initialize(detail : String)\n def to_s : String\nend\n\n# A certificate a manager holds for a name: the identity handshakes are\n# shown, its dates, and when renewal is next tried.\npub class AutocertHeld\n # Whether a handshake at *now_ms* may be shown it.\n def current?(now_ms : Int64) : Bool\n def failures : Int32\n def failures=(value : Int32) : Int32\n def identity : TlsIdentity\n def initialize(identity : TlsIdentity, renew_at_ms : Int64)\n def not_after_ms : Int64\n def not_before_ms : Int64\n # When renewal is next tried: its due time, later after failures.\n def renew_at_ms : Int64\n # When renewal is next tried: its due time, later after failures.\n def renew_at_ms=(value : Int64) : Int64\nend\n\n# Gets, keeps and renews certificates for a server's names from an ACME\n# CA, and shows them to std/tls handshakes by server_name.\npub class AutocertManager\n # Whether the CA's terms of service are agreed to when the account is\n # made: a CA that has terms refuses an account without.\n def accept_terms : Bool\n # Whether the CA's terms of service are agreed to when the account is\n # made: a CA that has terms refuses an account without.\n def accept_terms=(value : Bool) : Bool\n # Puts up the answers to *name*'s challenge: tls-alpn-01's certificate\n # for *key_authorization*, shown under `acme-tls/1`, and http-01's\n # answer for *token* (`answer`). An order puts them up while the CA\n # checks; a program answering a challenge of its own can too.\n def add_challenge(name : String, token : String, key_authorization : String) : AutocertError | ::Nil\n # http-01's answer to a request on port 80\n # (`/.well-known/acme-challenge/`), or nil for any other path:\n # `Server.serve(listener, workers: 1) { |r| manager.answer(r) || ... }`,\n # on one thread, since the manager's state is that thread's.\n def answer(request : Request) : Response | ::Nil\n def cache : AutocertCache\n # The certificate for *name*, normalized as `normalize` does: the one\n # held while it is current; else one the cache holds for it, or a new\n # one from the CA - by one order however many ask at once, waited for\n # *left_ms* at most when it is given - unless the host policy refuses\n # the name or its last order failed too recently.\n def certificate(name : String, left_ms : Int32 | ::Nil = nil) : AutocertHeld | AutocertError\n # The challenges offered, in the order tried.\n def challenges : Array(String)\n # The challenges offered, in the order tried.\n def challenges=(value : Array(String)) : Array(String)\n def directory_url : String\n # The account's contact address, without `mailto:`; none when empty.\n def email : String\n # The account's contact address, without `mailto:`; none when empty.\n def email=(value : String) : String\n # *config* - `tls_config`'s, or one the program got from it and set\n # more of, a `min_version` or `groups` - frozen for std/http's\n # `Server.serve(tls:)` on more than one worker: its identity source\n # runs on every worker, answers what this manager holds there, and\n # asks `run` for the rest (the header says how). `run` must run while\n # it serves.\n def frozen_tls_config(config : TlsServerConfig = tls_config) : TlsFrozenServerConfig\n # Whether a name may have a certificate; asked before the cache or the\n # CA. The names given to `new` and no other, unless replaced.\n def host_policy : Proc(String, Bool)\n # Whether a name may have a certificate; asked before the cache or the\n # CA. The names given to `new` and no other, unless replaced.\n def host_policy=(value : Proc(String, Bool)) : Proc(String, Bool)\n # The identity a handshake for *server_name*, offering *alpn*, is shown:\n # tls-alpn-01's certificate to a client offering `acme-tls/1` alone, the\n # name's certificate - got now if it must be, waiting *left_ms* at most\n # - to any other; or why there is none, as the error the handshake is\n # refused with: unrecognized_name for a name not answered for,\n # internal_error for one whose certificate could not be had in time.\n def identity_for(server_name : String | ::Nil, alpn : Array(String) | ::Nil, left_ms : Int32 | ::Nil = nil) : TlsIdentity | TlsError\n # A manager for the CA whose directory is at *directory_url*, keeping\n # its keys and certificates in *cache*, that answers for *hosts* alone.\n def initialize(directory_url : String, cache : AutocertCache, hosts : Array(String))\n # *host* as a server_name is compared: IDNA's A-labels for a Unicode\n # name, no trailing dot, lower case. A name IDNA refuses is compared as\n # it is written.\n def self.normalize(host : String) : String\n # Certificates ordered, and renewed, by this manager.\n def orders : Int32\n def remove_challenge(name : String, token : String) : Nil\n # How long before expiry a certificate is renewed.\n def renew_before : Span\n # How long before expiry a certificate is renewed.\n def renew_before=(value : Span) : Span\n # When a certificate valid from *not_before_ms* to *not_after_ms* is\n # due for renewal: `renew_before` ahead of its end, or two thirds into\n # a life no longer than `renew_before`.\n def renew_time(not_before_ms : Int64, not_after_ms : Int64) : Int64\n def renewals : Int32\n # How long a failed order is remembered, and a failed renewal put off;\n # doubled for each failure after, to `MAX_RETRY_MS`.\n def retry_ms : Int32\n # How long a failed order is remembered, and a failed renewal put off;\n # doubled for each failure after, to `MAX_RETRY_MS`.\n def retry_ms=(value : Int32) : Int32\n # The roots a chain the CA issues, or the cache holds, must reach to be\n # kept and shown: the system's when nil.\n def roots : Array(Certificate) | ::Nil\n # The roots a chain the CA issues, or the cache holds, must reach to be\n # kept and shown: the system's when nil.\n def roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil\n # Renews certificates as they come due, until `stop`: run it in a task\n # of a group that lives as long as the server. Each held certificate is\n # looked at when it is due and whenever one is added; a renewal that\n # fails is tried again `retry_ms` later, doubling to an hour. While it\n # runs, the orders handshakes ask for run in its group too, so that one\n # outlives the handshake that asked; `stop` cancels those still running.\n # It answers a `frozen_tls_config`'s workers too, each ask in a task of\n # its group.\n def run : Nil\n # Ends `run`.\n def stop : Nil\n # A std/tls server config that shows this manager's certificates, and\n # speaks *alpn*'s protocols with `acme-tls/1` after them. Its own\n # `identity` is a self-signed certificate for `autocert.invalid` that\n # no handshake is shown: the manager answers each one itself.\n def tls_config(alpn : Array(String) = [\"http/1.1\"]) : TlsServerConfig\n # The longest a handshake waits for an order - less when its deadline\n # is nearer - and an order for the CA's polls.\n def wait_ms : Int32\n # The longest a handshake waits for an order - less when its deadline\n # is nearer - and an order for the CA's polls.\n def wait_ms=(value : Int32) : Int32\nend\n\nimpl Error for AutocertError\n def message : String\nend\n\nimpl AutocertCache for AutocertDirCache\n def get(key : String) : String | AutocertError | Nil\n def put(key : String, data : String) : AutocertError | ::Nil\n def delete(key : String) : AutocertError | ::Nil\nend\n" + }, { "name": "std/base32", "path": "src/std/base32.iyi", @@ -52,8 +64,8 @@ { "name": "std/big", "path": "src/std/big.iyi", - "sourceSha256": "5c1a303eb3115206ae3f49ce6881a9722ae2297e58b9fdc6cd41f23f66aa58cb", - "surface": "module std/big\n\npub struct BigDecimal\n def !=(other : BigDecimal) : Bool\n def %(other : BigDecimal) : BigDecimal\n def *(other : BigDecimal) : BigDecimal\n def *(other : Int32 | Int64) : BigDecimal\n def +(other : BigDecimal) : BigDecimal\n def +(other : Int32 | Int64) : BigDecimal\n def + : BigDecimal\n def -(other : BigDecimal) : BigDecimal\n def -(other : Int32 | Int64) : BigDecimal\n def - : BigDecimal\n def /(other : BigDecimal) : BigDecimal\n def /(other : Int32 | Int64) : BigDecimal\n def //(other : BigDecimal) : BigDecimal\n def abs : BigDecimal\n protected def self.align(a : BigDecimal, b : BigDecimal) : Tuple(BigInt, BigInt, Int32)\n # `self / other` rounded to `precision` fractional digits, half away from\n # zero. A quotient that is exact within `precision` digits is answered with\n # the fewest digits that write it: `1/4` is `0.25`, `6/3` is `2`. One that\n # is not keeps all `precision` digits, so the reader sees where it was cut.\n def div(other : BigDecimal, precision : Int32) : BigDecimal\n # The same value at another scale is the same key: `1.10`, `1.1` and\n # `11e-1` hash alike because `==` says they are equal. The digits without\n # their trailing zeros and the exponent that goes with them name the value;\n # multiplying a negative scale out made `1e1000000.hash` take 2.6 s.\n def hash : Int32\n def initialize(value : BigInt, scale : Int32 = 0)\n def initialize(num : Int32 = 0, scale : Int32 = 0)\n def initialize(num : Int64, scale : Int32 = 0)\n # Parses `[+-]digits[.digits][e[+-]digits]`; anything else is refused.\n def initialize(str : String)\n def inspect : String\n # Bounds on the place of the leading digit, from the limb count alone:\n # 10^(lo - 1) <= |self| < 10^hi. 30102 and 30103 in 100,000 bracket\n # log10(2) for each 32-bit limb.\n protected def magnitude_bounds : Tuple(Int64, Int64)\n def negate : BigDecimal\n def negative? : Bool\n # The same value with the smallest non-negative scale: trailing zeros of\n # the fraction are dropped and a negative scale is multiplied out.\n def normalized : BigDecimal\n def positive? : Bool\n def scale : Int32\n # The value without its trailing zeros, at most `limit` of them, and how\n # many went. Counted once in the digits and divided out once: one divmod\n # by ten per zero was quadratic, and the hash of `1.` and 40,000 zeros, or\n # `3.div(3, 40000)`, took 725 ms. A value that does not end in zero is\n # answered by one division by ten, without printing it.\n protected def strip_zeros(limit : Int32) : Tuple(BigInt, Int32)\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_f : Float64\n def to_f64 : Float64\n def to_i32 : Int32\n def to_i64 : Int64\n # Zero is `0` whatever its scale; every other value prints every digit of\n # its scale, so `1.10` stays `1.10`.\n def to_s : String\n def value : BigInt\n def zero? : Bool\nend\n\n# A binary floating-point number of any precision: `±mantissa · 2^exponent`,\n# the mantissa exactly *precision* bits long (128 unless asked), every\n# result rounded once, to the nearest value of that many bits, ties to even\n# - the rounding IEEE 754 gives a double, at any width. The other library's\n# BigFloat is GMP's `mpf`, which truncates and keeps more bits than it was\n# asked for; this one answers what MPFR does.\n#\n# a = BigFloat.new(2, 256)\n# a.sqrt.to_s\n# # => \"1.4142135623730950488016887242096980785696718753769480731766797379907324784621\"\n# BigFloat.new(\"0.1\", 24).to_f64 # => 0.10000000149011612\n#\n# An operation between two answers at the larger precision; with an\n# integer, at the BigFloat's. `to_s` writes the fewest decimal digits that\n# read back to the same value at its precision, positional from 1e-4 to\n# below 1e15 and with an exponent outside, as `Float64#to_s` writes. There\n# is no NaN or infinity: dividing by zero, the square root of a negative\n# number, a NaN or an infinite double, a precision under 1 and a string\n# that is no number are refused.\npub struct BigFloat\n def *(other : BigFloat) : BigFloat\n def *(other : Int32 | Int64 | BigInt) : BigFloat\n # The power, its exact value rounded once; a negative exponent is the\n # reciprocal of the power, rounded once.\n def **(exponent : Int32) : BigFloat\n def +(other : BigFloat) : BigFloat\n def +(other : Int32 | Int64 | BigInt) : BigFloat\n def + : BigFloat\n def -(other : BigFloat) : BigFloat\n def -(other : Int32 | Int64 | BigInt) : BigFloat\n def - : BigFloat\n def /(other : BigFloat) : BigFloat\n def /(other : Int32 | Int64 | BigInt) : BigFloat\n def ==(other : BigFloat) : Bool\n def abs : BigFloat\n def ceil : BigFloat\n protected def self.check(precision : Int32) : Nil\n # An integer at exactly the bits it has.\n protected def self.exactly(value : Int32 | Int64 | BigInt) : BigFloat\n protected def exponent : Int64\n def floor : BigFloat\n # Equal values hash alike whatever their precision: the mantissa without\n # its trailing zero bits.\n def hash : Int32\n def initialize(negative : Bool, mantissa : BigInt, exponent : Int64, precision : Int32)\n def inspect : String\n # The largest integer whose square is at most *value*, by Newton's\n # method from above.\n protected def self.isqrt(value : BigInt) : BigInt\n protected def mantissa : BigInt\n def negative? : Bool\n def self.new(value : Int32, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : Int64, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigInt, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # The double's exact value, rounded to *precision* bits.\n def self.new(value : Float64, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigRational, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigDecimal, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # A decimal - `-12.5`, `1e-30`, `6.02214076E23` - rounded to *precision*\n # bits; anything else is refused.\n def self.new(text : String, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # As `new`, nil for a string that is no number.\n def self.parse?(text : String, precision : Int32 = DEFAULT_PRECISION) : BigFloat | ::Nil\n # *digits* with its first digit at 10^*point*, as `Float64#to_s` puts\n # a point: positional from 10^-4 to 10^14, an exponent outside.\n protected def self.place(digits : String, point : Int64) : String\n def positive? : Bool\n def precision : Int32\n # Under one half is zero; from there the fraction is at most\n # *precision* bits, compared with one half exactly.\n def round : BigFloat\n # *num*/*den* · 2^*shift* (both non-negative, *den* not zero), rounded to\n # *precision* bits, ties to even: every value here is made by this, from\n # an exact one.\n protected def self.rounded(negative : Bool, num : BigInt, den : BigInt, shift : Int64, precision : Int32) : BigFloat\n # The square root, rounded once. The mantissa is widened to at least\n # 2·precision + 4 bits over an even power of two, so the integer root\n # has two bits past the ones kept, and a root that is not exact carries a\n # half below its last bit into the rounding, which no tie can be.\n def sqrt : BigFloat\n # The exact sum, rounded once. An addend more than *precision* + 2 bits\n # below the other cannot move it - it is under a quarter of the larger's\n # last place, even where that place halves below a power of two - and\n # is not shifted out to a size it would only be rounded away from.\n protected def sum(other : BigFloat, precision : Int32) : BigFloat\n # Toward zero, floor, ceiling, and to the nearest integer, ties to even.\n def to_big_i : BigInt\n # The exact value as a fraction.\n def to_big_r : BigRational\n # The double nearest, ties to even; past the largest double, infinite,\n # and below the smallest, zero.\n def to_f64 : Float64\n # The fewest decimal digits that read back as this value at its\n # precision, the nearest such when there are two; written as\n # `Float64#to_s` writes a double.\n def to_s : String\n # The place above the leading bit: 2^(top - 1) <= |self| < 2^top.\n protected def top : Int64\n def trunc : BigFloat\n protected def trunc_whole : BigInt\n # This value at another precision, rounded if it is fewer bits.\n def with_precision(bits : Int32) : BigFloat\n def zero? : Bool\nend\n\npub struct BigInt\n # Three-way comparison is `<=>`, from `impl Comparable` below.\n def !=(other : BigInt) : Bool\n def !=(other : Int64) : Bool\n def !=(other : Int32) : Bool\n def %(other : BigInt) : BigInt\n def %(other : Int64) : BigInt\n def %(other : Int32) : BigInt\n # Bitwise AND (&) with two's complement semantics\n def &(other : BigInt) : BigInt\n # Multiplication\n def *(other : BigInt) : BigInt\n def *(other : Int64) : BigInt\n def *(other : Int32) : BigInt\n # Exponentiation by squaring: a ** exp\n def **(exp : Int32) : BigInt\n def **(exp : Int64) : BigInt\n def **(exp : BigInt) : BigInt\n # Addition\n def +(other : BigInt) : BigInt\n def +(other : Int64) : BigInt\n def +(other : Int32) : BigInt\n def + : BigInt\n # Subtraction: the sum with the other's sign turned, without copying it.\n def -(other : BigInt) : BigInt\n def -(other : Int64) : BigInt\n def -(other : Int32) : BigInt\n def - : BigInt\n def //(other : BigInt) : BigInt\n def //(other : Int64) : BigInt\n def //(other : Int32) : BigInt\n def <(other : Int64) : Bool\n def <(other : Int32) : Bool\n # Bitwise Left Shift: a << count\n def <<(count : Int32) : BigInt\n def <=(other : Int64) : Bool\n def <=(other : Int32) : Bool\n # Comparison overloads for Int64 and Int32\n def <=>(other : Int64) : Int32\n def <=>(other : Int32) : Int32\n def ==(other : Int64) : Bool\n def ==(other : Int32) : Bool\n def >(other : Int64) : Bool\n def >(other : Int32) : Bool\n def >=(other : Int64) : Bool\n def >=(other : Int32) : Bool\n # Bitwise Right Shift (arithmetic): a >> count\n def >>(count : Int32) : BigInt\n # Bitwise XOR (^) with two's complement semantics\n def ^(other : BigInt) : BigInt\n # Returns the absolute value of self.\n def abs : BigInt\n def abs2 : BigInt\n # The digits of this value's magnitude, least significant first, onto\n # `sink`: exactly `width` of them when `width` is positive, zeros above.\n #\n # A long value is split by `powers[level]`, a power of the chunk about\n # half its size, and each half is written by the level below - the low\n # half to exactly the digits that power stands for, so its leading zeros\n # are kept. Dividing by one chunk at a time is a hardware division per\n # limb per nine digits, n^2 / 9 of them; the split does that only for the\n # short pieces at the bottom and multiplies its way through the rest,\n # dividing by `divmod_recursive`.\n protected def append_digits(radix : UInt64, chunk : UInt64, per : Int32, powers : Array(BigInt), level : Int32, width : Int32, sink : Array(UInt8)) : Nil\n # The bits |value| takes to write.\n protected def self.bit_length(value : BigInt) : Int32\n # `count` bits of this value, not negative, from bit `from` up, in one\n # copy.\n protected def bits(from : Int32, count : Int32) : BigInt\n # The bits one digit of a power-of-two base takes; for any other base,\n # the fewest bits a digit can carry, so that a limb's worth of bits\n # bounds how many digits it makes.\n protected def self.bits_per_digit(base : Int32) : Int32\n # Magnitude comparison: compares |self| and |other|.\n # Returns -1 if |self| < |other|, 0 if |self| == |other|, 1 if |self| > |other|.\n def cmp_abs(other : BigInt) : Int32\n protected def self.digit_byte(value : UInt64) : UInt8\n # A digit byte's value in any base up to 36, or 36 for a byte that is a\n # digit in none.\n protected def self.digit_value(byte : UInt8) : Int32\n # This value's `2n` bits and `low`'s `n` by `b`, `top` and `bottom` its\n # halves of `n` bits each.\n protected def div_three_by_two(low : BigInt, b : BigInt, top : BigInt, bottom : BigInt, n : Int32) : Tuple(BigInt, BigInt)\n # This value, under `divisor * 2^n`, by `divisor`, exactly `n` bits long\n # and `n` even until it is short enough for long division: the top\n # `n + n/2` bits by the divisor, then the remainder and the next `n/2`.\n # Each is a division by the divisor's top half, a product by its bottom\n # half, and at most two corrections, the divisor's top bit being set.\n protected def div_two_by_one(divisor : BigInt, n : Int32) : Tuple(BigInt, BigInt)\n # Division and Modulo. Returns {quotient, remainder}.\n # Division truncates towards zero; remainder follows the sign of the dividend (self).\n #\n # A long divisor and a long quotient go to `divmod_recursive`, the\n # recursive division `to_s` splits its digits with, on the magnitudes,\n # and take their signs after. Long division costs the product of the\n # quotient's length and the divisor's, and every division went to it: a\n # value twice a divisor of 524,288 bits took 282 ms in a release build\n # and one of 2,097,152 bits 4,929 ms, four times over per doubling,\n # where these take 179 and 1,964.\n def divmod(other : BigInt) : Tuple(BigInt, BigInt)\n # Knuth's Algorithm D, with the signs `divmod` gives.\n protected def divmod_long(other : BigInt) : Tuple(BigInt, BigInt)\n # |self| divided by |other|, two limbs or more, for a quotient under\n # 2^32: *shift* is the divisor's bit length less 32, and |self| over\n # 2^shift, A, fits 63 bits. With B the divisor's top 32 bits, the\n # quotient is estimated as A / (B + 1), which is never more than the\n # quotient and short of it by less than A / B^2 + 1, at most three; a - q\n # b is taken in one pass and the divisor taken off what is left while it\n # is not below it.\n protected def divmod_one_limb(other : BigInt, shift : Int32, q_sign : Int32, r_sign : Int32) : Tuple(BigInt, BigInt)\n # The quotient and remainder of this value, not negative, by `divisor`,\n # positive, for the time a few products of half the length take: long\n # division costs the product of the two lengths, and the split print\n # divides by a power half as long as the value, so `to_s` was quadratic -\n # 787 ms for 381,698 digits and 3,107 ms for twice as many. Burnikel and\n # Ziegler's recursive division, in the form Python's `_pylong` writes it,\n # by bits. A value past twice the divisor's length has its top divided\n # first, as long division takes a digit at a time; the rest is shifted so\n # that the divisor's length halves evenly down to where long division is\n # the faster, and the remainder shifted back.\n protected def divmod_recursive(divisor : BigInt) : Tuple(BigInt, BigInt)\n # Returns true if this number is even.\n def even? : Bool\n # The big-endian *bytes* as a non-negative BigInt: a key, a signature, a\n # coordinate as cryptography writes them. Four bytes a limb, the low\n # limb from the last four.\n def self.from_bytes(bytes : Array(UInt8)) : BigInt\n # The value of `digits`, most significant first, in `base`: the inverse\n # of `to_s(base)`, and built the same way. A power-of-two base packs the\n # digits' bits into limbs directly. Any other splits the digits at a\n # power of the chunk (`append_digits`), so that the halves meet in one\n # Karatsuba product each, and runs the short pieces at the bottom through\n # one multiply-add per chunk in place. This had shifted or multiplied a\n # whole new BigInt per digit - per nine digits in decimal - and 80,000\n # hex digits took more than a second.\n protected def self.from_digits(digits : Array(UInt8), base : Int32) : BigInt\n # Parses a hexadecimal string (with or without '0x' prefix).\n def self.from_hex(str : String) : BigInt\n # Greatest Common Divisor (Euclidean algorithm)\n def gcd(other : BigInt) : BigInt\n def gcd(other : Int64) : BigInt\n def gcd(other : Int32) : BigInt\n # FNV-1a over the sign and every limb, folded to 31 bits. Wrapping\n # multiplication (`&*`) is the point: a checked product would overflow on\n # the second limb, and a hash has to answer for any size.\n def hash : Int32\n # Raw constructor from sign and limbs. Trims leading zero limbs to enforce\n # the canonical representation invariant: zero has empty limbs and sign 0.\n def initialize(sign : Int32, limbs : Array(UInt64))\n # Least Common Multiple\n def lcm(other : BigInt) : BigInt\n def lcm(other : Int64) : BigInt\n def lcm(other : Int32) : BigInt\n def limbs : Array(UInt64)\n # Whether the *n* limbs at *r* are at least the *m* at *v*.\n protected def self.limbs_at_least(r : Pointer(UInt64), n : Int32, v : Pointer(UInt64), m : Int32) : Bool\n # Returns the negated value of self (-self).\n def negate : BigInt\n # Returns true if this number is strictly negative (< 0).\n def negative? : Bool\n # Constructs a BigInt from an Int64.\n def self.new(val : Int64) : BigInt\n # Constructs a BigInt from an Int32.\n def self.new(val : Int32) : BigInt\n # Parses a BigInt from a string in the given base (2..36, default 10).\n # Supports leading whitespace, optional sign ('+' or '-'), and base prefixes\n # (0x/0X for hex, 0b/0B for binary, 0o/0O for octal).\n def self.new(str : String, base : Int32 = 10) : BigInt\n # Returns true if this number is odd.\n def odd? : Bool\n # Parses a string with full error checking.\n def self.parse(str : String, base : Int32 = 10) : BigInt\n # Returns true if this number is strictly positive (> 0).\n def positive? : Bool\n # Modular exponentiation: (self ** exp) % mod, in 0...mod whatever the\n # base's sign. An odd modulus, which is every modulus RSA, Diffie-Hellman\n # and DSA work in, goes to `Montgomery` with a sliding window; an even one\n # keeps square-and-multiply with a division per step.\n #\n # Each step here was a product and a long division of twice the\n # modulus' length, and a 2048-bit modulus with a 2048-bit exponent took\n # 19.0 ms in a --release build where Python 3.14's `pow` took 13.3 (3072\n # bits: 63.2 and 42.3, 4096: 168.7 and 100.3). Montgomery's takes 2.5 ms,\n # 8.5 and 20.6 in the run of bench/std_big_exercise.sh that measured\n # Python at 13.7, 45.4 and 102.1, which prints these each run.\n def pow_mod(exp : BigInt, mod : BigInt) : BigInt\n def pow_mod(exp : Int64, mod : Int64) : BigInt\n def pow_mod(exp : Int32, mod : Int32) : BigInt\n # `pow_mod` for an exponent that is a secret, a private key's: a fixed\n # window, every entry of its table read for each window and the one\n # wanted kept by a mask, so neither a branch nor a memory address depends\n # on the exponent's bits, and as many windows as the longer of the\n # exponent and the modulus takes, so the exponent's length does not show\n # either. The modulus has to be odd (a Montgomery modulus): an even one\n # is a misuse, refused.\n #\n # What it does not hide: the base is reduced by `%`, whose time depends\n # on the base and the modulus, so a caller with a secret base (RSA's\n # CRT halves of a message) blinds it first, as std/rsa's signing does.\n def pow_mod_secret(exp : BigInt, mod : BigInt) : BigInt\n def sign : Int32\n # Number of limbs currently allocated.\n def size : Int32\n def to_big : BigInt\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_big_r : BigRational\n # The value big-endian in exactly *size* bytes, zeros in front: the\n # inverse of `from_bytes` at a fixed width, which is how a signature or a\n # coordinate is written. Nil for a negative value or one that does not\n # fit, never a value cut short.\n def to_bytes(size : Int32) : Array(UInt8) | ::Nil\n def to_i : Int32\n # Converts to Int32, raising an overflow error if the value exceeds Int32 range.\n def to_i32 : Int32\n # Converts to Int32 if the value fits, or nil if it overflows.\n def to_i32? : Int32 | ::Nil\n # Converts to Int64, raising an overflow error if the value exceeds Int64 range.\n def to_i64 : Int64\n # Converts to Int64 if the value fits, or nil if it overflows.\n def to_i64? : Int64 | ::Nil\n # Converts this BigInt to a string in the given base (default 10).\n # Bases 2..36 supported. Digits above 9 are lowercase 'a'..'z'.\n #\n # The digits are gathered least significant first and written into the\n # string once. A base that is a power of two reads them straight off the\n # limbs' bits; any other divides a copy of the limbs in place by the\n # largest power of the base a limb holds - 10^9 for decimal - and takes\n # that many digits from each remainder. This had built the text with\n # `res = res + s`, a copy of everything so far per chunk, and divided by\n # a fresh one-limb BigInt per chunk, or per digit outside bases 2, 10\n # and 16: 3 ** 400000 took a second in decimal and in binary, and nine\n # and a half in base 7.\n def to_s(base : Int32) : String\n def to_s : String\n # Helper: bitwise magnitude operation with sign inversion\n protected def twos_mag : BigInt\n # The value of `count` digits from `from`: the high part times the power\n # of the chunk that the low part's digits stand for, plus the low part.\n protected def self.value_of_digits(digits : Array(UInt8), from : Int32, count : Int32, base : UInt64, per : Int32, powers : Array(BigInt)) : BigInt\n # |value| over 2^shift, for one under 2^64: the limbs from bit *shift*.\n protected def self.window(limbs : Array(UInt64), shift : Int32) : UInt64\n # Returns true if this number is zero.\n def zero? : Bool\n # Bitwise OR (|) with two's complement semantics\n def |(other : BigInt) : BigInt\n # Bitwise NOT (~a = -a - 1 in two's complement)\n def ~ : BigInt\nend\n\npub struct BigRational\n def !=(other : BigRational) : Bool\n def *(other : BigRational) : BigRational\n def *(other : Int32 | Int64 | BigInt) : BigRational\n def **(exp : Int32) : BigRational\n def +(other : BigRational) : BigRational\n def +(other : Int32 | Int64 | BigInt) : BigRational\n def + : BigRational\n def -(other : BigRational) : BigRational\n def -(other : Int32 | Int64 | BigInt) : BigRational\n def - : BigRational\n def /(other : BigRational) : BigRational\n def /(other : Int32 | Int64 | BigInt) : BigRational\n def abs : BigRational\n def denominator : BigInt\n # A rational is stored reduced, so equal values have equal parts.\n def hash : Int32\n def initialize(numerator : BigInt, denominator : BigInt)\n def initialize(num : Int64 = 0_i64, den : Int64 = 1_i64)\n def initialize(num : Int32, den : Int32 = 1)\n def initialize(str : String)\n def inspect : String\n def inv : BigRational\n def negate : BigRational\n def negative? : Bool\n def numerator : BigInt\n def positive? : Bool\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_big_r : BigRational\n # The Float64 nearest this value, ties to even, as python's\n # `float(Fraction)` answers. This had gone through `to_big_d`, twenty\n # fractional digits: every value under 5e-21 was 0.0 and every one under\n # 1e-3 lost significant digits. The quotient is taken to 55 or 56 bits -\n # 55 at the smallest exponent, where the subnormals start - with a sticky\n # low bit for a nonzero remainder, rounded to the bits a Float64 keeps (53,\n # fewer as a subnormal), and scaled by the power of two it was shifted by,\n # which is exact: a value past the largest Float64 is Infinity.\n def to_f64 : Float64\n def to_s : String\n def zero? : Bool\nend\n\nimpl Std::Traits::Comparable for BigInt\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigInt\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigInt\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigDecimal\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigDecimal\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigDecimal\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigRational\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigRational\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigRational\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigFloat\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Hashable for BigFloat\n def hash_key : Int32\nend\n\nstruct ::Int32\n def *(other : Std::Big::BigInt) : Std::Big::BigInt\n def +(other : Std::Big::BigInt) : Std::Big::BigInt\n def -(other : Std::Big::BigInt) : Std::Big::BigInt\n def <=>(other : Std::Big::BigInt) : Int32\n def ==(other : Std::Big::BigInt) : Bool\n def to_big : Std::Big::BigInt\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n\nstruct ::Int64\n def *(other : Std::Big::BigInt) : Std::Big::BigInt\n def +(other : Std::Big::BigInt) : Std::Big::BigInt\n def -(other : Std::Big::BigInt) : Std::Big::BigInt\n def <=>(other : Std::Big::BigInt) : Int32\n def ==(other : Std::Big::BigInt) : Bool\n def to_big : Std::Big::BigInt\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n\nclass ::String\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n" + "sourceSha256": "979cc358dd97fd15026b0251c74420c2c3a6ddc1c6210d2d099c0105af7bbb6f", + "surface": "module std/big\n\npub struct BigDecimal\n def !=(other : BigDecimal) : Bool\n def %(other : BigDecimal) : BigDecimal\n def *(other : BigDecimal) : BigDecimal\n def *(other : Int32 | Int64) : BigDecimal\n def +(other : BigDecimal) : BigDecimal\n def +(other : Int32 | Int64) : BigDecimal\n def + : BigDecimal\n def -(other : BigDecimal) : BigDecimal\n def -(other : Int32 | Int64) : BigDecimal\n def - : BigDecimal\n def /(other : BigDecimal) : BigDecimal\n def /(other : Int32 | Int64) : BigDecimal\n def //(other : BigDecimal) : BigDecimal\n def abs : BigDecimal\n protected def self.align(a : BigDecimal, b : BigDecimal) : Tuple(BigInt, BigInt, Int32)\n # `self / other` rounded to `precision` fractional digits, half away from\n # zero. A quotient that is exact within `precision` digits is answered with\n # the fewest digits that write it: `1/4` is `0.25`, `6/3` is `2`. One that\n # is not keeps all `precision` digits, so the reader sees where it was cut.\n def div(other : BigDecimal, precision : Int32) : BigDecimal\n # The same value at another scale is the same key: `1.10`, `1.1` and\n # `11e-1` hash alike because `==` says they are equal. The digits without\n # their trailing zeros and the exponent that goes with them name the value;\n # multiplying a negative scale out made `1e1000000.hash` take 2.6 s.\n def hash : Int32\n def initialize(value : BigInt, scale : Int32 = 0)\n def initialize(num : Int32 = 0, scale : Int32 = 0)\n def initialize(num : Int64, scale : Int32 = 0)\n # Parses `[+-]digits[.digits][e[+-]digits]`; anything else is refused.\n def initialize(str : String)\n def inspect : String\n # Bounds on the place of the leading digit, from the limb count alone:\n # 10^(lo - 1) <= |self| < 10^hi. 30102 and 30103 in 100,000 bracket\n # log10(2) for each 32-bit limb.\n protected def magnitude_bounds : Tuple(Int64, Int64)\n def negate : BigDecimal\n def negative? : Bool\n # The same value with the smallest non-negative scale: trailing zeros of\n # the fraction are dropped and a negative scale is multiplied out.\n def normalized : BigDecimal\n def positive? : Bool\n def scale : Int32\n # The value without its trailing zeros, at most `limit` of them, and how\n # many went. Counted once in the digits and divided out once: one divmod\n # by ten per zero was quadratic, and the hash of `1.` and 40,000 zeros, or\n # `3.div(3, 40000)`, took 725 ms. A value that does not end in zero is\n # answered by one division by ten, without printing it.\n protected def strip_zeros(limit : Int32) : Tuple(BigInt, Int32)\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_f : Float64\n def to_f64 : Float64\n def to_i32 : Int32\n def to_i64 : Int64\n # Zero is `0` whatever its scale; every other value prints every digit of\n # its scale, so `1.10` stays `1.10`.\n def to_s : String\n def value : BigInt\n def zero? : Bool\nend\n\n# A binary floating-point number of any precision: `±mantissa · 2^exponent`,\n# the mantissa exactly *precision* bits long (128 unless asked), every\n# result rounded once, to the nearest value of that many bits, ties to even\n# - the rounding IEEE 754 gives a double, at any width. The other library's\n# BigFloat is GMP's `mpf`, which truncates and keeps more bits than it was\n# asked for; this one answers what MPFR does.\n#\n# a = BigFloat.new(2, 256)\n# a.sqrt.to_s\n# # => \"1.4142135623730950488016887242096980785696718753769480731766797379907324784621\"\n# BigFloat.new(\"0.1\", 24).to_f64 # => 0.10000000149011612\n#\n# An operation between two answers at the larger precision; with an\n# integer, at the BigFloat's. `to_s` writes the fewest decimal digits that\n# read back to the same value at its precision, positional from 1e-4 to\n# below 1e15 and with an exponent outside, as `Float64#to_s` writes. There\n# is no NaN or infinity: dividing by zero, the square root of a negative\n# number, a NaN or an infinite double, a precision under 1 and a string\n# that is no number are refused.\npub struct BigFloat\n def *(other : BigFloat) : BigFloat\n def *(other : Int32 | Int64 | BigInt) : BigFloat\n # The power, its exact value rounded once; a negative exponent is the\n # reciprocal of the power, rounded once.\n def **(exponent : Int32) : BigFloat\n def +(other : BigFloat) : BigFloat\n def +(other : Int32 | Int64 | BigInt) : BigFloat\n def + : BigFloat\n def -(other : BigFloat) : BigFloat\n def -(other : Int32 | Int64 | BigInt) : BigFloat\n def - : BigFloat\n def /(other : BigFloat) : BigFloat\n def /(other : Int32 | Int64 | BigInt) : BigFloat\n def ==(other : BigFloat) : Bool\n def abs : BigFloat\n def ceil : BigFloat\n protected def self.check(precision : Int32) : Nil\n # An integer at exactly the bits it has.\n protected def self.exactly(value : Int32 | Int64 | BigInt) : BigFloat\n protected def exponent : Int64\n def floor : BigFloat\n # Equal values hash alike whatever their precision: the mantissa without\n # its trailing zero bits.\n def hash : Int32\n def initialize(negative : Bool, mantissa : BigInt, exponent : Int64, precision : Int32)\n def inspect : String\n # The largest integer whose square is at most *value*, by Newton's\n # method from above.\n protected def self.isqrt(value : BigInt) : BigInt\n protected def mantissa : BigInt\n def negative? : Bool\n def self.new(value : Int32, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : Int64, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigInt, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # The double's exact value, rounded to *precision* bits.\n def self.new(value : Float64, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigRational, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n def self.new(value : BigDecimal, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # A decimal - `-12.5`, `1e-30`, `6.02214076E23` - rounded to *precision*\n # bits; anything else is refused.\n def self.new(text : String, precision : Int32 = DEFAULT_PRECISION) : BigFloat\n # As `new`, nil for a string that is no number.\n def self.parse?(text : String, precision : Int32 = DEFAULT_PRECISION) : BigFloat | ::Nil\n # *digits* with its first digit at 10^*point*, as `Float64#to_s` puts\n # a point: positional from 10^-4 to 10^14, an exponent outside.\n protected def self.place(digits : String, point : Int64) : String\n def positive? : Bool\n def precision : Int32\n # Under one half is zero; from there the fraction is at most\n # *precision* bits, compared with one half exactly.\n def round : BigFloat\n # *num*/*den* · 2^*shift* (both non-negative, *den* not zero), rounded to\n # *precision* bits, ties to even: every value here is made by this, from\n # an exact one.\n protected def self.rounded(negative : Bool, num : BigInt, den : BigInt, shift : Int64, precision : Int32) : BigFloat\n # The square root, rounded once. The mantissa is widened to at least\n # 2·precision + 4 bits over an even power of two, so the integer root\n # has two bits past the ones kept, and a root that is not exact carries a\n # half below its last bit into the rounding, which no tie can be.\n def sqrt : BigFloat\n # The exact sum, rounded once. An addend more than *precision* + 2 bits\n # below the other cannot move it - it is under a quarter of the larger's\n # last place, even where that place halves below a power of two - and\n # is not shifted out to a size it would only be rounded away from.\n protected def sum(other : BigFloat, precision : Int32) : BigFloat\n # Toward zero, floor, ceiling, and to the nearest integer, ties to even.\n def to_big_i : BigInt\n # The exact value as a fraction.\n def to_big_r : BigRational\n # The double nearest, ties to even; past the largest double, infinite,\n # and below the smallest, zero.\n def to_f64 : Float64\n # The fewest decimal digits that read back as this value at its\n # precision, the nearest such when there are two; written as\n # `Float64#to_s` writes a double.\n def to_s : String\n # The place above the leading bit: 2^(top - 1) <= |self| < 2^top.\n protected def top : Int64\n def trunc : BigFloat\n protected def trunc_whole : BigInt\n # This value at another precision, rounded if it is fewer bits.\n def with_precision(bits : Int32) : BigFloat\n def zero? : Bool\nend\n\npub struct BigInt\n # Three-way comparison is `<=>`, from `impl Comparable` below.\n def !=(other : BigInt) : Bool\n def !=(other : Int64) : Bool\n def !=(other : Int32) : Bool\n def %(other : BigInt) : BigInt\n def %(other : Int64) : BigInt\n def %(other : Int32) : BigInt\n # Bitwise AND (&) with two's complement semantics\n def &(other : BigInt) : BigInt\n # Multiplication\n def *(other : BigInt) : BigInt\n def *(other : Int64) : BigInt\n def *(other : Int32) : BigInt\n # Exponentiation by squaring: a ** exp\n def **(exp : Int32) : BigInt\n def **(exp : Int64) : BigInt\n def **(exp : BigInt) : BigInt\n # Addition\n def +(other : BigInt) : BigInt\n def +(other : Int64) : BigInt\n def +(other : Int32) : BigInt\n def + : BigInt\n # Subtraction: the sum with the other's sign turned, without copying it.\n def -(other : BigInt) : BigInt\n def -(other : Int64) : BigInt\n def -(other : Int32) : BigInt\n def - : BigInt\n def //(other : BigInt) : BigInt\n def //(other : Int64) : BigInt\n def //(other : Int32) : BigInt\n def <(other : Int64) : Bool\n def <(other : Int32) : Bool\n # Bitwise Left Shift: a << count\n def <<(count : Int32) : BigInt\n def <=(other : Int64) : Bool\n def <=(other : Int32) : Bool\n # Comparison overloads for Int64 and Int32\n def <=>(other : Int64) : Int32\n def <=>(other : Int32) : Int32\n def ==(other : Int64) : Bool\n def ==(other : Int32) : Bool\n def >(other : Int64) : Bool\n def >(other : Int32) : Bool\n def >=(other : Int64) : Bool\n def >=(other : Int32) : Bool\n # Bitwise Right Shift (arithmetic): a >> count\n def >>(count : Int32) : BigInt\n # Bitwise XOR (^) with two's complement semantics\n def ^(other : BigInt) : BigInt\n # Returns the absolute value of self.\n def abs : BigInt\n def abs2 : BigInt\n # The digits of this value's magnitude, least significant first, onto\n # `sink`: exactly `width` of them when `width` is positive, zeros above.\n #\n # A long value is split by `powers[level]`, a power of the chunk about\n # half its size, and each half is written by the level below - the low\n # half to exactly the digits that power stands for, so its leading zeros\n # are kept. Dividing by one chunk at a time is a hardware division per\n # limb per nine digits, n^2 / 9 of them; the split does that only for the\n # short pieces at the bottom and multiplies its way through the rest,\n # dividing by `divmod_recursive`.\n protected def append_digits(radix : UInt64, chunk : UInt64, per : Int32, powers : Array(BigInt), level : Int32, width : Int32, sink : Array(UInt8)) : Nil\n # The bits |value| takes to write.\n protected def self.bit_length(value : BigInt) : Int32\n # `count` bits of this value, not negative, from bit `from` up, in one\n # copy.\n protected def bits(from : Int32, count : Int32) : BigInt\n # The bits one digit of a power-of-two base takes; for any other base,\n # the fewest bits a digit can carry, so that a limb's worth of bits\n # bounds how many digits it makes.\n protected def self.bits_per_digit(base : Int32) : Int32\n # Magnitude comparison: compares |self| and |other|.\n # Returns -1 if |self| < |other|, 0 if |self| == |other|, 1 if |self| > |other|.\n def cmp_abs(other : BigInt) : Int32\n protected def self.digit_byte(value : UInt64) : UInt8\n # A digit byte's value in any base up to 36, or 36 for a byte that is a\n # digit in none.\n protected def self.digit_value(byte : UInt8) : Int32\n # This value's `2n` bits and `low`'s `n` by `b`, `top` and `bottom` its\n # halves of `n` bits each.\n protected def div_three_by_two(low : BigInt, b : BigInt, top : BigInt, bottom : BigInt, n : Int32) : Tuple(BigInt, BigInt)\n # This value, under `divisor * 2^n`, by `divisor`, exactly `n` bits long\n # and `n` even until it is short enough for long division: the top\n # `n + n/2` bits by the divisor, then the remainder and the next `n/2`.\n # Each is a division by the divisor's top half, a product by its bottom\n # half, and at most two corrections, the divisor's top bit being set.\n protected def div_two_by_one(divisor : BigInt, n : Int32) : Tuple(BigInt, BigInt)\n # Division and Modulo. Returns {quotient, remainder}.\n # Division truncates towards zero; remainder follows the sign of the dividend (self).\n #\n # A long divisor and a long quotient go to `divmod_recursive`, the\n # recursive division `to_s` splits its digits with, on the magnitudes,\n # and take their signs after. Long division costs the product of the\n # quotient's length and the divisor's, and every division went to it: a\n # value twice a divisor of 524,288 bits took 282 ms in a release build\n # and one of 2,097,152 bits 4,929 ms, four times over per doubling,\n # where these take 179 and 1,964.\n def divmod(other : BigInt) : Tuple(BigInt, BigInt)\n # Knuth's Algorithm D, with the signs `divmod` gives.\n protected def divmod_long(other : BigInt) : Tuple(BigInt, BigInt)\n # |self| divided by |other|, two limbs or more, for a quotient under\n # 2^32: *shift* is the divisor's bit length less 32, and |self| over\n # 2^shift, A, fits 63 bits. With B the divisor's top 32 bits, the\n # quotient is estimated as A / (B + 1), which is never more than the\n # quotient and short of it by less than A / B^2 + 1, at most three; a - q\n # b is taken in one pass and the divisor taken off what is left while it\n # is not below it.\n protected def divmod_one_limb(other : BigInt, shift : Int32, q_sign : Int32, r_sign : Int32) : Tuple(BigInt, BigInt)\n # The quotient and remainder of this value, not negative, by `divisor`,\n # positive, for the time a few products of half the length take: long\n # division costs the product of the two lengths, and the split print\n # divides by a power half as long as the value, so `to_s` was quadratic -\n # 787 ms for 381,698 digits and 3,107 ms for twice as many. Burnikel and\n # Ziegler's recursive division, in the form Python's `_pylong` writes it,\n # by bits. A value past twice the divisor's length has its top divided\n # first, as long division takes a digit at a time; the rest is shifted so\n # that the divisor's length halves evenly down to where long division is\n # the faster, and the remainder shifted back.\n protected def divmod_recursive(divisor : BigInt) : Tuple(BigInt, BigInt)\n # Returns true if this number is even.\n def even? : Bool\n # The big-endian *bytes* as a non-negative BigInt: a key, a signature, a\n # coordinate as cryptography writes them. Four bytes a limb, the low\n # limb from the last four.\n def self.from_bytes(bytes : Array(UInt8)) : BigInt\n # The value of `digits`, most significant first, in `base`: the inverse\n # of `to_s(base)`, and built the same way. A power-of-two base packs the\n # digits' bits into limbs directly. Any other splits the digits at a\n # power of the chunk (`append_digits`), so that the halves meet in one\n # Karatsuba product each, and runs the short pieces at the bottom through\n # one multiply-add per chunk in place. This had shifted or multiplied a\n # whole new BigInt per digit - per nine digits in decimal - and 80,000\n # hex digits took more than a second.\n protected def self.from_digits(digits : Array(UInt8), base : Int32) : BigInt\n # Parses a hexadecimal string (with or without '0x' prefix).\n def self.from_hex(str : String) : BigInt\n # Greatest Common Divisor (Euclidean algorithm)\n def gcd(other : BigInt) : BigInt\n def gcd(other : Int64) : BigInt\n def gcd(other : Int32) : BigInt\n # FNV-1a over the sign and every limb, folded to 31 bits. Wrapping\n # multiplication (`&*`) is the point: a checked product would overflow on\n # the second limb, and a hash has to answer for any size.\n def hash : Int32\n # Raw constructor from sign and limbs. Trims leading zero limbs to enforce\n # the canonical representation invariant: zero has empty limbs and sign 0.\n def initialize(sign : Int32, limbs : Array(UInt64))\n # The inverse of self modulo an odd *mod*, in 0...mod, or nil when the\n # two share a factor (Python's `pow(self, -1, mod)`, which raises\n # there): for a value that is a secret, such as RSA's blinding factor.\n # The steps taken, their branches and the words they read depend on the\n # modulus' length alone (`Montgomery#inverse`); an even modulus is a\n # misuse, refused. As with `pow_mod_secret`, self is reduced by `%`\n # first, whose time depends on it: a caller hands in a value below the\n # modulus.\n def inverse_mod_secret(mod : BigInt) : BigInt | ::Nil\n # Least Common Multiple\n def lcm(other : BigInt) : BigInt\n def lcm(other : Int64) : BigInt\n def lcm(other : Int32) : BigInt\n def limbs : Array(UInt64)\n # Whether the *n* limbs at *r* are at least the *m* at *v*.\n protected def self.limbs_at_least(r : Pointer(UInt64), n : Int32, v : Pointer(UInt64), m : Int32) : Bool\n # Returns the negated value of self (-self).\n def negate : BigInt\n # Returns true if this number is strictly negative (< 0).\n def negative? : Bool\n # Constructs a BigInt from an Int64.\n def self.new(val : Int64) : BigInt\n # Constructs a BigInt from an Int32.\n def self.new(val : Int32) : BigInt\n # Parses a BigInt from a string in the given base (2..36, default 10).\n # Supports leading whitespace, optional sign ('+' or '-'), and base prefixes\n # (0x/0X for hex, 0b/0B for binary, 0o/0O for octal).\n def self.new(str : String, base : Int32 = 10) : BigInt\n # Returns true if this number is odd.\n def odd? : Bool\n # Parses a string with full error checking.\n def self.parse(str : String, base : Int32 = 10) : BigInt\n # Returns true if this number is strictly positive (> 0).\n def positive? : Bool\n # Modular exponentiation: (self ** exp) % mod, in 0...mod whatever the\n # base's sign. An odd modulus, which is every modulus RSA, Diffie-Hellman\n # and DSA work in, goes to `Montgomery` with a sliding window; an even one\n # keeps square-and-multiply with a division per step.\n #\n # Each step here was a product and a long division of twice the\n # modulus' length, and a 2048-bit modulus with a 2048-bit exponent took\n # 19.0 ms in a --release build where Python 3.14's `pow` took 13.3 (3072\n # bits: 63.2 and 42.3, 4096: 168.7 and 100.3). Montgomery's takes 2.5 ms,\n # 8.5 and 20.6 in the run of bench/std_big_exercise.sh that measured\n # Python at 13.7, 45.4 and 102.1, which prints these each run.\n def pow_mod(exp : BigInt, mod : BigInt) : BigInt\n def pow_mod(exp : Int64, mod : Int64) : BigInt\n def pow_mod(exp : Int32, mod : Int32) : BigInt\n # `pow_mod` for an exponent that is a secret, a private key's: a fixed\n # window, every entry of its table read for each window and the one\n # wanted kept by a mask, so neither a branch nor a memory address depends\n # on the exponent's bits, and as many windows as the longer of the\n # exponent and the modulus takes, so the exponent's length does not show\n # either. The modulus has to be odd (a Montgomery modulus): an even one\n # is a misuse, refused.\n #\n # What it does not hide: the base is reduced by `%`, whose time depends\n # on the base and the modulus, so a caller with a secret base (RSA's\n # CRT halves of a message) blinds it first, as std/rsa's signing does.\n def pow_mod_secret(exp : BigInt, mod : BigInt) : BigInt\n def sign : Int32\n # Number of limbs currently allocated.\n def size : Int32\n def to_big : BigInt\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_big_r : BigRational\n # The value big-endian in exactly *size* bytes, zeros in front: the\n # inverse of `from_bytes` at a fixed width, which is how a signature or a\n # coordinate is written. Nil for a negative value or one that does not\n # fit, never a value cut short.\n def to_bytes(size : Int32) : Array(UInt8) | ::Nil\n def to_i : Int32\n # Converts to Int32, raising an overflow error if the value exceeds Int32 range.\n def to_i32 : Int32\n # Converts to Int32 if the value fits, or nil if it overflows.\n def to_i32? : Int32 | ::Nil\n # Converts to Int64, raising an overflow error if the value exceeds Int64 range.\n def to_i64 : Int64\n # Converts to Int64 if the value fits, or nil if it overflows.\n def to_i64? : Int64 | ::Nil\n # Converts this BigInt to a string in the given base (default 10).\n # Bases 2..36 supported. Digits above 9 are lowercase 'a'..'z'.\n #\n # The digits are gathered least significant first and written into the\n # string once. A base that is a power of two reads them straight off the\n # limbs' bits; any other divides a copy of the limbs in place by the\n # largest power of the base a limb holds - 10^9 for decimal - and takes\n # that many digits from each remainder. This had built the text with\n # `res = res + s`, a copy of everything so far per chunk, and divided by\n # a fresh one-limb BigInt per chunk, or per digit outside bases 2, 10\n # and 16: 3 ** 400000 took a second in decimal and in binary, and nine\n # and a half in base 7.\n def to_s(base : Int32) : String\n def to_s : String\n # Helper: bitwise magnitude operation with sign inversion\n protected def twos_mag : BigInt\n # The value of `count` digits from `from`: the high part times the power\n # of the chunk that the low part's digits stand for, plus the low part.\n protected def self.value_of_digits(digits : Array(UInt8), from : Int32, count : Int32, base : UInt64, per : Int32, powers : Array(BigInt)) : BigInt\n # |value| over 2^shift, for one under 2^64: the limbs from bit *shift*.\n protected def self.window(limbs : Array(UInt64), shift : Int32) : UInt64\n # Returns true if this number is zero.\n def zero? : Bool\n # Bitwise OR (|) with two's complement semantics\n def |(other : BigInt) : BigInt\n # Bitwise NOT (~a = -a - 1 in two's complement)\n def ~ : BigInt\nend\n\npub struct BigRational\n def !=(other : BigRational) : Bool\n def *(other : BigRational) : BigRational\n def *(other : Int32 | Int64 | BigInt) : BigRational\n def **(exp : Int32) : BigRational\n def +(other : BigRational) : BigRational\n def +(other : Int32 | Int64 | BigInt) : BigRational\n def + : BigRational\n def -(other : BigRational) : BigRational\n def -(other : Int32 | Int64 | BigInt) : BigRational\n def - : BigRational\n def /(other : BigRational) : BigRational\n def /(other : Int32 | Int64 | BigInt) : BigRational\n def abs : BigRational\n def denominator : BigInt\n # A rational is stored reduced, so equal values have equal parts.\n def hash : Int32\n def initialize(numerator : BigInt, denominator : BigInt)\n def initialize(num : Int64 = 0_i64, den : Int64 = 1_i64)\n def initialize(num : Int32, den : Int32 = 1)\n def initialize(str : String)\n def inspect : String\n def inv : BigRational\n def negate : BigRational\n def negative? : Bool\n def numerator : BigInt\n def positive? : Bool\n def to_big_d : BigDecimal\n def to_big_i : BigInt\n def to_big_r : BigRational\n # The Float64 nearest this value, ties to even, as python's\n # `float(Fraction)` answers. This had gone through `to_big_d`, twenty\n # fractional digits: every value under 5e-21 was 0.0 and every one under\n # 1e-3 lost significant digits. The quotient is taken to 55 or 56 bits -\n # 55 at the smallest exponent, where the subnormals start - with a sticky\n # low bit for a nonzero remainder, rounded to the bits a Float64 keeps (53,\n # fewer as a subnormal), and scaled by the power of two it was shifted by,\n # which is exact: a value past the largest Float64 is Infinity.\n def to_f64 : Float64\n def to_s : String\n def zero? : Bool\nend\n\nimpl Std::Traits::Comparable for BigInt\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigInt\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigInt\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigDecimal\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigDecimal\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigDecimal\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigRational\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Num for BigRational\n def self.zero : self\n def self.one : self\n def add(other : self) : self\n def mul(other : self) : self\nend\n\nimpl Std::Traits::Hashable for BigRational\n def hash_key : Int32\nend\n\nimpl Std::Traits::Comparable for BigFloat\n def <=>(other : self) : Int32\nend\n\nimpl Std::Traits::Hashable for BigFloat\n def hash_key : Int32\nend\n\nstruct ::Int32\n def *(other : Std::Big::BigInt) : Std::Big::BigInt\n def +(other : Std::Big::BigInt) : Std::Big::BigInt\n def -(other : Std::Big::BigInt) : Std::Big::BigInt\n def <=>(other : Std::Big::BigInt) : Int32\n def ==(other : Std::Big::BigInt) : Bool\n def to_big : Std::Big::BigInt\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n\nstruct ::Int64\n def *(other : Std::Big::BigInt) : Std::Big::BigInt\n def +(other : Std::Big::BigInt) : Std::Big::BigInt\n def -(other : Std::Big::BigInt) : Std::Big::BigInt\n def <=>(other : Std::Big::BigInt) : Int32\n def ==(other : Std::Big::BigInt) : Bool\n def to_big : Std::Big::BigInt\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n\nclass ::String\n def to_big_d : Std::Big::BigDecimal\n def to_big_i : Std::Big::BigInt\n def to_big_r : Std::Big::BigRational\nend\n" }, { "name": "std/bit_array", @@ -76,7 +88,7 @@ { "name": "std/bzip2", "path": "src/std/bzip2.iyi", - "sourceSha256": "33335a7b9f4b7cf41941e3e0a8f6d0f4f9d752f303ab85ef89a126b7ded704cb", + "sourceSha256": "e6ddde4436c552246e884befc9e59a8614c3c60ba2878550af35f2d6a53baa97", "surface": "module std/bzip2\n\npub struct Bzip2\n # *data* as one bzip2 stream with blocks of *level* * 100,000 bytes,\n # 1 to 9 (bzip2's -1 to -9); any other level is a panic.\n def self.compress(data : String, level : Int32 = 9) : String\n # Every byte *data*'s streams hold, or the error the first wrong thing in\n # them is. Bytes after the last stream that do not start another are\n # ignored, as bzip2(1) ignores them.\n def self.decompress(data : String) : String | Bzip2Error\n def initialize\nend\n\n# What `Bzip2Reader#read` and `Bzip2.decompress` answer for a stream they\n# cannot read: one that is corrupt, whose CRC does not match, or that ends\n# early.\npub struct Bzip2Error\n def initialize(message : String)\n def to_s : String\nend\n\n# A bzip2 stream read a piece at a time: the compressed bytes come from\n# the block, the decompressed ones go out through `read`, and memory\n# stays at one block's links and a 64 KiB buffer.\n#\n# ```\n# body = Bzip2Reader.new { file.read_bytes(65536) }\n# while (piece = body.read(65536)!) != \"\"\n# ...\n# end\n# ```\npub class Bzip2Reader\n # Whether every stream has ended, its CRCs checked, and every byte been\n # read: the next `read` answers \"\".\n def finished? : Bool\n # *source* answers the next bytes of the compressed stream, nil (or \"\")\n # when there are no more. It is called only when the decoder has no\n # bytes in hand for what it must read next, so it is never asked for\n # anything past a stream's end - except that the input ends only where\n # no stream follows, which takes a look at the next four bytes.\n def initialize(&source : (-> String | ::Nil))\n # Bytes the source gave beyond the last stream, for a caller that frames\n # other data after it; \"\" before the input has ended.\n def leftover : String\n # Up to *max* decompressed bytes, 64 KiB at most; \"\" once every stream\n # has ended and its CRCs been checked. A read that has bytes when a\n # block ends answers them rather than reading the next block, so it can\n # answer fewer than *max* before the end.\n #\n # A corrupt stream, a CRC that does not match, or input that ends early\n # is a `Bzip2Error`, after which every read answers the same error. A\n # block's bytes are given out before its CRC is known, as the CRC is\n # over them; the read that gives its last byte checks it, and answers\n # the error instead of those bytes. A *max* below 1 is a panic.\n def read(max : Int32 = 65536) : String | Bzip2Error\nend\n\nimpl Error for Bzip2Error\n def message : String\nend\n" }, { @@ -124,8 +136,8 @@ { "name": "std/curve25519", "path": "src/std/curve25519.iyi", - "sourceSha256": "ef68e5e6969424cf301ccc3a47001434d64f18f4544afc429266e5c6c760a7c9", - "surface": "module std/curve25519\n\n# What is wrong with a peer's key: `message` is the sentence.\npub struct CurveError\n def initialize(message : String)\n def to_s : String\nend\n\n# Ed25519, RFC 8032 §5.1: the seed is the 32-byte private key; SHA-512 of\n# it gives the secret scalar (its first half, clamped) and the prefix the\n# per-message nonce is hashed from (its second half). Signing is\n# deterministic: one seed and one message give one signature.\npub struct Ed25519\n def initialize\n # The 32-byte public key of *seed*, RFC 8032 §5.1.5.\n def self.public_key(seed : String | Array(UInt8)) : String\n # The 64-byte signature of *message* under *seed*, RFC 8032 §5.1.6:\n # R = [r]B for r = SHA-512(prefix || M) mod L, then\n # S = (r + SHA-512(R || A || M) * s) mod L. The public key A is worked\n # out again from the seed, which costs a second multiplication of the\n # base point; the two are each a fixed-window constant-time one.\n def self.sign(seed : String | Array(UInt8), message : String | Array(UInt8)) : String\n # Whether *signature* is *public_key*'s signature of *message*, RFC 8032\n # §5.1.7 with the cofactorless equation (the header says why). False,\n # not a panic, for a key or signature of the wrong size, a key that is\n # not a canonical encoding of a point on the curve, and an S of L or\n # more.\n def self.verify(public_key : String | Array(UInt8), message : String | Array(UInt8), signature : String | Array(UInt8)) : Bool\nend\n\n# An Ed25519 public key held as a value, as a certificate carries one (RFC\n# 8410): the 32 bytes, checked once to be the canonical encoding of a point\n# on the curve, so a key read from outside that is not one is refused where\n# it is read rather than failing every signature later.\npub struct Ed25519PublicKey\n def bytes : Array(UInt8)\n # The key *bytes* encode, or a `CurveError`: not 32 bytes, or not a\n # canonical encoding of a point on the curve (RFC 8032 §5.1.3).\n def self.from_bytes(bytes : String | Array(UInt8)) : Ed25519PublicKey | CurveError\n def initialize(bytes : Array(UInt8))\n # Whether *signature* is this key's signature of *message*, as\n # `Ed25519.verify` answers it.\n def verify(message : String | Array(UInt8), signature : String | Array(UInt8)) : Bool\nend\n\n# X25519, RFC 7748 §5: the u-coordinate of a clamped scalar times a point\n# on Curve25519. The private key is any 32 bytes; it is clamped here\n# (bits 0 to 2 and 255 cleared, bit 254 set), so the caller need not.\npub struct X25519\n def initialize\n # The public key of *private_key*: its multiple of the base point, u = 9.\n def self.public_key(private_key : String | Array(UInt8)) : String\n # The secret *private_key* shares with the owner of *peer_public*. A\n # peer key that is not 32 bytes, and a secret of all zeros (a peer key of\n # small order, RFC 7748 §6.1), are a `CurveError`. Whether the secret was\n # zero is the one thing that leaves the ladder by a branch, and it says\n # nothing of the private key: which keys give zero depends on the peer's.\n def self.shared(private_key : String | Array(UInt8), peer_public : String | Array(UInt8)) : String | CurveError\nend\n\nimpl Error for CurveError\n def message : String\nend\n" + "sourceSha256": "b1b7fdcd424af5a7d8484a2b984f886c45231a74f396566691a5976113c67132", + "surface": "module std/curve25519\n\n# What is wrong with a peer's key: `message` is the sentence.\npub struct CurveError\n def initialize(message : String)\n def to_s : String\nend\n\n# Ed25519, RFC 8032 §5.1: the seed is the 32-byte private key; SHA-512 of\n# it gives the secret scalar (its first half, clamped) and the prefix the\n# per-message nonce is hashed from (its second half). Signing is\n# deterministic: one seed and one message give one signature.\npub struct Ed25519\n def initialize\n # The 32-byte public key of *seed*, RFC 8032 §5.1.5.\n def self.public_key(seed : String | Array(UInt8)) : String\n # The 64-byte signature of *message* under *seed*, RFC 8032 §5.1.6:\n # R = [r]B for r = SHA-512(prefix || M) mod L, then\n # S = (r + SHA-512(R || A || M) * s) mod L. The public key A is worked\n # out again from the seed, which costs a second multiplication of the\n # base point; the two are each a fixed-window constant-time one.\n def self.sign(seed : String | Array(UInt8), message : String | Array(UInt8)) : String\n # Whether *signature* is *public_key*'s signature of *message*, RFC 8032\n # §5.1.7 with the cofactorless equation (the header says why). False,\n # not a panic, for a key or signature of the wrong size, a key that is\n # not a canonical encoding of a point on the curve, and an S of L or\n # more.\n def self.verify(public_key : String | Array(UInt8), message : String | Array(UInt8), signature : String | Array(UInt8)) : Bool\nend\n\n# An Ed25519 public key held as a value, as a certificate carries one (RFC\n# 8410): the 32 bytes, checked once to be the canonical encoding of a point\n# on the curve, so a key read from outside that is not one is refused where\n# it is read rather than failing every signature later.\npub struct Ed25519PublicKey\n def bytes : Array(UInt8)\n # The key *bytes* encode, or a `CurveError`: not 32 bytes, or not a\n # canonical encoding of a point on the curve (RFC 8032 §5.1.3).\n def self.from_bytes(bytes : String | Array(UInt8)) : Ed25519PublicKey | CurveError\n def initialize(bytes : Array(UInt8))\n # Whether *signature* is this key's signature of *message*, as\n # `Ed25519.verify` answers it.\n def verify(message : String | Array(UInt8), signature : String | Array(UInt8)) : Bool\nend\n\n# X25519, RFC 7748 §5: the u-coordinate of a clamped scalar times a point\n# on Curve25519. The private key is any 32 bytes; it is clamped here\n# (bits 0 to 2 and 255 cleared, bit 254 set), so the caller need not.\npub struct X25519\n def initialize\n # The public key of *private_key*: its multiple of the base point, u = 9.\n # That point is edwards25519's B under RFC 7748 §4.1's map, which keeps\n # multiples, so the clamped scalar times B on the Edwards curve, by the\n # fixed-base table, gives the same u as the ladder from 9 does: 8\n # microseconds where the ladder took 20.\n def self.public_key(private_key : String | Array(UInt8)) : String\n # The secret *private_key* shares with the owner of *peer_public*. A\n # peer key that is not 32 bytes, and a secret of all zeros (a peer key of\n # small order, RFC 7748 §6.1), are a `CurveError`. Whether the secret was\n # zero is the one thing that leaves the ladder by a branch, and it says\n # nothing of the private key: which keys give zero depends on the peer's.\n def self.shared(private_key : String | Array(UInt8), peer_public : String | Array(UInt8)) : String | CurveError\nend\n\nimpl Error for CurveError\n def message : String\nend\n" }, { "name": "std/debug", @@ -148,25 +160,25 @@ { "name": "std/digest", "path": "src/std/digest.iyi", - "sourceSha256": "624bfb79129f860695808e4f3c95109132a59c57fce35f67ce6eefb86927afe9", + "sourceSha256": "3a86961eb013ee516204d7f8e65615cc592e1ee624e570d8f49345d00894ab26", "surface": "module std/digest\n\n# The same convention as `CRC32`: `update(sum, data, count)` goes on from a\n# finished Adler-32, and 1 is the start (the Adler-32 of nothing), as in\n# zlib's `adler32(adler, buf, len)`.\npub struct Adler32\n def self.checksum(data : String) : UInt32\n def self.hexdigest(data : String) : String\n def initialize\n def self.update(sum : UInt32, data : Pointer(UInt8), count : Int32) : UInt32\nend\n\n# `checksum(data)` is the CRC-32 of *data*. `update(crc, data, count)` goes\n# on from a CRC-32 already taken, zlib's `crc32(crc, buf, len)` convention:\n# what goes in and what comes out are finished checksums, the inversion\n# before and after done inside, so 0 is the start (the CRC-32 of nothing)\n# and `update(update(0, a), b)` is the CRC-32 of *a* then *b*, the same\n# number `checksum` answers for the two joined.\npub struct CRC32\n def self.checksum(data : String) : UInt32\n def self.hexdigest(data : String) : String\n def initialize\n # Eight bytes a step through eight tables (slicing-by-8), where it had\n # been one bit a step: twenty megabytes took 105 ms, which was most of\n # what `Gzip.decompress` spent, the inflating itself taking 30.\n def self.update(crc : UInt32, data : Pointer(UInt8), count : Int32) : UInt32\nend\n\n# CRC-64 with ECMA-182's polynomial, reflected (0xc96c5795d7870f42), and\n# inverted before and after: the CRC-64 the .xz format checks a block\n# with (xz file format 1.0.4 §3.4, check ID 0x04), which reveng's\n# catalogue names CRC-64/XZ. The same convention as `CRC32`: `update(crc,\n# data, count)` goes on from a finished checksum, 0 is the start (the\n# CRC-64 of nothing), and `hexdigest` writes the number most significant\n# digit first, as `xz --robot --list` does; the stream stores it\n# little-endian.\npub struct CRC64\n def self.checksum(data : String) : UInt64\n def self.hexdigest(data : String) : String\n def initialize\n # Eight bytes a step through eight tables, as `CRC32` goes: the bytes\n # loaded as one little-endian word, xored into the checksum, and each\n # of the eight looked up in the table for how many bytes of zeros\n # follow it in the step.\n def self.update(crc : UInt64, data : Pointer(UInt8), count : Int32) : UInt64\nend\n\n# What every block hash shares: the message is cut into blocks, the last\n# one padded with a one bit, zeros and the message's length in bits, and\n# each block is compressed into the state. A subclass is the state and\n# its compression; this is the cutting and the padding.\npub abstract class Digest\n # Bytes compressed at a time: 64, or 128 for SHA-384 and SHA-512. HMAC\n # pads its key to this.\n def block_size : Int32\n # Becomes *other*: the same bytes taken, the same state, the same\n # `final` given or not. Nothing is allocated. *other* must be the same\n # algorithm, and a different one is refused by name.\n def copy_from(other : Digest) : Nil\n # Bytes in a digest: 16 for MD5, 20 for SHA-1, 28, 32, 48 and 64 for\n # the SHA-2s.\n abstract def digest_size : Int32\n def dup : self\n # The digest, into the first `digest_size` bytes of *dst*: no array is\n # made, which is what a loop that hashes a hash wants.\n def final(dst : Array(UInt8)) : Nil\n def final : Array(UInt8)\n def self.hash_bytes(data : String | Array(UInt8)) : Array(UInt8)\n def self.hexdigest(data : String | Array(UInt8)) : String\n def hexfinal : String\n def initialize(block_size : Int32)\n # The digest of no bytes yet, ready again after a `final`.\n def reset : Nil\n def update(data : Pointer(UInt8), count : Int32) : self\n def update(data : String) : self\n def update(data : Array(UInt8)) : self\nend\n\npub class MD5 < Digest\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA1 < Digest\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA224 < SHA256\n def digest_size : Int32\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\n# SHA-256, FIPS 180-4 §6.2. SHA-224 is the same compression from another\n# starting state, its digest the first 28 bytes.\npub class SHA256 < Digest\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\n# SHA-3, FIPS 202 section 6.1: Keccak[c] with a capacity twice the\n# digest, so a rate (the `block_size`) of 200 bytes less two digests.\n# The sponge has no length field, so the base's cutting into blocks is\n# kept and its padding is replaced. The digest is shorter than the rate\n# in all four, so it is the first bytes of the state after the last\n# block, with no further permutation.\npub abstract class SHA3 < Digest\n def initialize(rate : Int32)\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA384 < SHA512\n def digest_size : Int32\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA3_224 < SHA3\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA3_256 < SHA3\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA3_384 < SHA3\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHA3_512 < SHA3\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\n# SHA-512, FIPS 180-4 §6.4: 64-bit words, 128-byte blocks, 80 rounds.\n# SHA-384 is the same compression from another starting state, its digest\n# the first 48 bytes.\npub class SHA512 < Digest\n def digest_size : Int32\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHAKE128 < Shake\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\npub class SHAKE256 < Shake\n def initialize\n # The type's own name, which is what a value that has said nothing about\n # itself can honestly print. A type that wants better defines its own.\n def to_s : String\nend\n\n# SHAKE128 and SHAKE256, FIPS 202 section 6.2: the sponge at a capacity of\n# 256 and 512 bits, as much output as is asked for. `update` absorbs;\n# the first `read` pads and every `read` squeezes on from where the last\n# stopped, so reads of 3 and 5 bytes are the first 8 bytes of one read.\n# Absorbing after squeezing would be a different function, not more\n# input, and is refused until `reset`.\npub abstract class Shake\n # Bytes absorbed at a time: 168 for SHAKE128, 136 for SHAKE256.\n def block_size : Int32\n # *count* bytes of the output for *data*.\n def self.hash_bytes(data : String | Array(UInt8), count : Int32) : Array(UInt8)\n def self.hexdigest(data : String | Array(UInt8), count : Int32) : String\n def initialize(rate : Int32)\n # The next `dst.size` bytes of output into *dst*, with no array made.\n # While squeezing, `@fill` counts the bytes of the state given out.\n def read(dst : Array(UInt8)) : Nil\n def read(count : Int32) : Array(UInt8)\n def reset : Nil\n def update(data : Pointer(UInt8), count : Int32) : self\n def update(data : String) : self\n def update(data : Array(UInt8)) : self\nend\n\n# XXH64, the 64-bit xxHash (Yann Collet; the algorithm as xxhash's\n# `doc/xxhash_spec.md` gives it): the hash whose low 32 bits are a\n# Zstandard frame's content checksum (RFC 8878 §3.1.1), which is what\n# std/zstd takes it for. Not a cryptographic hash - a fast check that\n# bytes arrived as they left, with nothing to keep anyone from making two\n# messages that collide.\n#\n# `checksum(data, seed)` hashes one piece. `new(seed)`, then `update` as\n# the data arrives, then `digest`, hashes it in pieces; `digest` leaves\n# the state as it was, as xxhash's `XXH64_digest` does, so it can be\n# asked midway and the hash go on. `hexdigest` is xxhash's canonical form,\n# most significant byte first. A word of the message is loaded as one\n# little-endian load, which is what every target iyi builds for is.\npub class XXH64\n def self.checksum(data : String, seed : UInt64 = 0_u64) : UInt64\n # The hash of everything given since the start or `reset`; the state is\n # left as it was.\n def digest : UInt64\n def self.hexdigest(data : String, seed : UInt64 = 0_u64) : String\n def initialize(seed : UInt64 = 0_u64)\n def self.merge(acc : UInt64, v : UInt64) : UInt64\n # Back to the hash of nothing, under the same seed.\n def reset : Nil\n def self.rotl(x : UInt64, n : Int32) : UInt64\n def self.round(acc : UInt64, input : UInt64) : UInt64\n def update(data : Pointer(UInt8), count : Int32) : Nil\n def update(data : String) : Nil\nend\n" }, { "name": "std/dir", "path": "src/std/dir.iyi", "sourceSha256": "05d924d2c84ce21c8c566beaa058822b50fb1a4e5547b588a111f7085dbd2356", - "surface": "module std/dir\n\npub class Dir\n def self.[](patterns : String | Array(String)) : Array(String)\n protected def self.c_str(str : String) : ::Pointer(UInt8)\n def self.cd(path : String | Path) : String\n def self.cd(path : String | Path, &block : (-> Nil)) : Nil\n def children : Array(String)\n def self.children(dirname : String | Path) : Array(String)\n def close : Nil\n def closed? : Bool\n # One implementation, in the prelude: `Path#expand` needs the same\n # answer and cannot import this module (`std/dir` imports `std/path`),\n # so the platform arms live beside `Program.args` and `Program.env`,\n # which are the process's other facts.\n def self.current : String\n def self.current=(path : String | Path) : String\n def self.delete(path : String | Path) : Nil\n def self.delete?(path : String | Path) : Bool\n def each(&block : (String -> Nil)) : Nil\n def self.each(dirname : String | Path, &block : (String -> Nil)) : Nil\n def each_child(&block : (String -> Nil)) : Nil\n def self.each_child(dirname : String | Path, &block : (String -> Nil)) : Nil\n def self.empty?(path : String | Path) : Bool\n def entries : Array(String)\n def self.entries(dirname : String | Path) : Array(String)\n def self.exists?(path : String | Path) : Bool\n # The search pattern FindFirstFileW is opened with: the directory, wide,\n # with a separator and a `*` behind it. The directory is converted on its\n # own and the wildcard appended to the result, not converted with it,\n # because a long path's conversion runs through `GetFullPathNameW` and\n # that is a path normaliser rather than a pattern one — the `\\\\?\\` prefix\n # it produces is what a long directory needs here, and a `*` is no\n # business of its. A null pointer is the conversion having failed, which\n # the callers read as the directory not opening.\n protected def self.find_pattern(dir_path : String) : ::Pointer(UInt16)\n def self.glob(patterns : String | Array(String)) : Array(String)\n def self.glob(patterns : String | Array(String), &block : (String -> Nil)) : Nil\n def initialize(path : String | Path)\n def inspect : String\n def self.mkdir(path : String | Path, mode : Int32 = 511) : Nil\n def self.mkdir_p(path : String | Path, mode : Int32 = 511) : Nil\n def self.open(path : String | Path) : Dir\n def self.open(path : String | Path, &block : (Dir -> Nil)) : Nil\n def path : String\n def read : String | ::Nil\n def rewind : self\n def self.rmdir(path : String | Path) : Nil\n def self.tempdir : String\n def to_s : String\n # One trailing separator off, as Crystal's `Dir.tempdir` answers: darwin\n # sets TMPDIR to `/var/folders/.../T/`, and every path joined onto it had\n # two separators and a `dirname` that was not the directory it came from.\n # The root alone is left as it is - and on Windows a drive's root is\n # `C:\\`, whose separator is the root: without it `C:` is the current\n # directory on that drive, and a TMP of `C:\\` answered the directory\n # the program happened to be in.\n def self.without_trailing_separator(dir : String) : String\nend\n" + "surface": "module std/dir\n\npub class Dir\n def self.[](patterns : String | Array(String)) : Array(String)\n protected def self.c_str(str : String) : ::Pointer(UInt8)\n def self.cd(path : String | Path) : String\n def self.cd(path : String | Path, &block : (-> Nil)) : Nil\n def children : Array(String)\n def self.children(dirname : String | Path) : Array(String)\n def close : Nil\n def closed? : Bool\n # One implementation, in the prelude: `Path#expand` needs the same\n # answer and cannot import this module (`std/dir` imports `std/path`),\n # so the platform arms live beside `Program.args` and `Program.env`,\n # which are the process's other facts.\n def self.current : String\n def self.current=(path : String | Path) : String\n def self.delete(path : String | Path) : Nil\n def self.delete?(path : String | Path) : Bool\n def each(&block : (String -> Nil)) : Nil\n def self.each(dirname : String | Path, &block : (String -> Nil)) : Nil\n def each_child(&block : (String -> Nil)) : Nil\n def self.each_child(dirname : String | Path, &block : (String -> Nil)) : Nil\n def self.empty?(path : String | Path) : Bool\n def entries : Array(String)\n def self.entries(dirname : String | Path) : Array(String)\n def self.exists?(path : String | Path) : Bool\n def self.glob(patterns : String | Array(String)) : Array(String)\n def self.glob(patterns : String | Array(String), &block : (String -> Nil)) : Nil\n def initialize(path : String | Path)\n def inspect : String\n def self.mkdir(path : String | Path, mode : Int32 = 511) : Nil\n def self.mkdir_p(path : String | Path, mode : Int32 = 511) : Nil\n def self.open(path : String | Path) : Dir\n def self.open(path : String | Path, &block : (Dir -> Nil)) : Nil\n def path : String\n def read : String | ::Nil\n def rewind : self\n def self.rmdir(path : String | Path) : Nil\n def self.tempdir : String\n def to_s : String\n # One trailing separator off, as Crystal's `Dir.tempdir` answers: darwin\n # sets TMPDIR to `/var/folders/.../T/`, and every path joined onto it had\n # two separators and a `dirname` that was not the directory it came from.\n # The root alone is left as it is - and on Windows a drive's root is\n # `C:\\`, whose separator is the root: without it `C:` is the current\n # directory on that drive, and a TMP of `C:\\` answered the directory\n # the program happened to be in.\n def self.without_trailing_separator(dir : String) : String\nend\n\n# Linux: the kernel's own calls (SPEC.md III.9), in libc's shape so the\n# callers below read the same. A directory stream is a descriptor and\n# a buffer of `linux_dirent64` records from `getdents64`, which is the\n# record libc's `readdir` hands back: the name sits at byte 19 either way.\npub struct LibC\n def self.chdir(path : ::Pointer(UInt8)) : Int32\n def self.closedir(dirp : ::Pointer(Void)) : Int32\n def self.getcwd(buf : ::Pointer(UInt8), size : UInt64) : ::Pointer(UInt8)\n def initialize\n def self.mkdir(path : ::Pointer(UInt8), mode : Int32) : Int32\n def self.opendir(name : ::Pointer(UInt8)) : ::Pointer(Void)\n def self.readdir(dirp : ::Pointer(Void)) : ::Pointer(Void)\n def self.rewinddir(dirp : ::Pointer(Void)) : Nil\n def self.rmdir(path : ::Pointer(UInt8)) : Int32\n def self.sys(number : Int64, a : Int64, b : Int64, c : Int64) : Int64\nend\n" }, { "name": "std/dns", "path": "src/std/dns.iyi", - "sourceSha256": "bca672855bf97b25347fe10935098a3c2c6eaee988950470166ddf6c3a065875", - "surface": "module std/dns\n\n# Why a name was not resolved. One type with a question per answer, as\n# `SocketError` has, so a caller tells a name that does not exist\n# (`not_found?`, NXDOMAIN - authoritative, not worth asking again) from a\n# server that failed (`server_failure?`, SERVFAIL - maybe worth asking\n# again later) without reading a sentence.\npub struct DnsError\n # The same failure, said about *name*: a failure found while reading\n # bytes knows no name, and the resolver that asked does.\n def about(name : String, server : String) : DnsError\n def detail : String\n def initialize(kind : Int32, name : String, server : String = \"\", detail : String = \"\")\n # Not a host name at all, so nothing was asked.\n def invalid_name? : ::Bool\n def kind : Int32\n # An answer that carried the query's ID and question and could not be\n # read: a compression loop, a pointer past the end, a record running off\n # the message, an alias chain that does not end.\n def malformed? : ::Bool\n # The name as the caller wrote it.\n def name : String\n # The name exists and has no address of the kind asked for (NOERROR\n # with no records, \"NODATA\").\n def no_address? : ::Bool\n # NXDOMAIN: the name does not exist.\n def not_found? : ::Bool\n def reason : String\n # REFUSED, FORMERR or NOTIMP: the server would not answer this query.\n def refused? : ::Bool\n # The server whose answer this is, `address:port`; empty when no server\n # was asked.\n def server : String\n # SERVFAIL from every server asked.\n def server_failure? : ::Bool\n # No server answered within the timeout, on any attempt.\n def timed_out? : ::Bool\n # The server could not be reached: nothing listens on its port (an ICMP\n # refusal), no route to it, or the TCP connection the truncated answer\n # needed failed.\n def unreachable? : ::Bool\nend\n\n# A query, and the reading of an answer to one.\npub struct DnsMessage\n # The addresses of *type* this answer gives *name*, following CNAMEs:\n # the records for the end of the chain, the name the chain ended at, and\n # how many links it took. No addresses and a different name is a chain\n # that left this answer before it ended, which the resolver asks about\n # next. A chain of more than *budget* links is malformed.\n def addresses(name : String, type : Int32, budget : Int32 = MAX_CNAMES) : Tuple(Array(Bytes), String, Int32) | DnsError\n def answers : Array(DnsRecord)\n # Whether this is the answer to query *id* for *name* and *type*: a\n # response, a standard query's, with the ID, and repeating the question.\n # A refusal of the query (SERVFAIL, REFUSED, FORMERR, NOTIMP) may come\n # without the question, as some servers send it; an answer that says\n # anything about the name - NOERROR or NXDOMAIN - must carry it.\n def answers?(id : UInt16, name : String, type : Int32) : ::Bool\n def flags : Int32\n def id : UInt16\n # The ID in a datagram's first two bytes, or -1 when it is too short to\n # be a message: what the resolver reads before it reads anything else.\n def self.id_of(packet : Bytes) : Int32\n def initialize(id : UInt16, flags : Int32, question_name : String, question_type : Int32, question_class : Int32, answers : Array(DnsRecord))\n def self.malformed(why : String) : DnsError\n def opcode : Int32\n # The message in *packet*: its header, its one question, and - unless it\n # is truncated - its answer section. Anything that does not fit the\n # bytes is `DnsError` with `malformed?`, never a read past them.\n def self.parse(packet : Bytes) : DnsMessage | DnsError\n # The query for *name*'s records of *type*, class IN, with recursion\n # desired: a 12-byte header and one question. *name* is a host name\n # (`DnsName.valid?`); a trailing dot is dropped.\n def self.query(id : UInt16, name : String, type : Int32) : Bytes\n def question_class : Int32\n # The question the answer repeats; empty with `question_type` 0 when it\n # repeats none.\n def question_name : String\n def question_type : Int32\n def rcode : Int32\n # The name at *at* in *packet*, and the offset just past it where it\n # sits; nil when it cannot be read. Labels are 1 to 63 bytes, a label may\n # not hold a `.` (it would read as two), the name is at most 255 bytes on\n # the wire, and each compression pointer must point before the place the\n # name was last read from - RFC 1035 4.1.4's \"prior occurrence\" - so the\n # positions strictly fall and a loop cannot be written.\n def self.read_name(packet : Bytes, at : Int32) : Tuple(String, Int32) | ::Nil\n def response? : ::Bool\n # TC: the answer did not fit in the datagram and is to be asked again\n # over TCP. Its records are not read: what is there is a fragment.\n def truncated? : ::Bool\nend\n\n# What a host name may be, and the names a short one stands for.\npub struct DnsName\n # The fully qualified names a lookup of *name* asks for, in order, as\n # resolv.conf(5) says: a name ending in `.` is only itself; one with at\n # least *ndots* dots is tried as it is first and then under each search\n # domain; one with fewer is tried under each search domain first and as\n # it is last. A candidate longer than a name can be is left out.\n def self.candidates(name : String, search : Array(String), ndots : Int32) : Array(String)\n def initialize\n # *name* without the trailing dot that makes it absolute.\n def self.relative(name : String) : String\n # Two names are the same name in any case (RFC 4343), with or without\n # the trailing dot.\n def self.same?(a : String, b : String) : ::Bool\n # A host name as RFC 1123 and glibc's `res_hnok` read one: labels of 1 to\n # 63 letters, digits, `-` and `_`, no label starting with `-`, at most\n # 253 characters, one trailing dot allowed. The last label must not be\n # all digits (RFC 3696 2: no top-level domain is), so `999.1.1.1` and\n # `1.2.3` stay what the numeric parser refuses rather than becoming\n # questions to a name server.\n def self.valid?(name : String) : ::Bool\n # Why *name* is not a host name, or nil when it is.\n def self.why(name : String) : String | ::Nil\nend\n\n# One resource record from an answer section.\npub struct DnsRecord\n # The record's data as it came: four bytes for an A record, sixteen for\n # an AAAA, the encoded name for a CNAME.\n def data : Bytes\n def initialize(name : String, type : Int32, record_class : Int32, ttl : UInt32, data : Bytes, target : String)\n def name : String\n def record_class : Int32\n # A CNAME's target, read with compression; empty for any other type.\n def target : String\n def ttl : UInt32\n def type : Int32\nend\n\n# The hosts file: an address, then the names it answers for.\npub struct HostsFile\n def initialize\n # The addresses *text* gives *name*, as written and in the file's order:\n # every line whose names include it, in any case and with or without a\n # trailing dot. A line with an address and no name says nothing.\n def self.lookup(text : String, name : String) : Array(String)\nend\n\n# `/etc/resolv.conf`, as resolv.conf(5) describes it and glibc reads it.\npub struct ResolvConf\n def attempts : Int32\n def initialize(nameservers : Array(String) = [] of String, search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2)\n # Each an address as written - IPv4, or IPv6 without a zone - optionally\n # with a port (`192.0.2.1:5353`, `[2001:db8::1]:5353`), which\n # resolv.conf itself never has and a resolver built in a program may.\n def nameservers : Array(String)\n def ndots : Int32\n # The keywords glibc reads: `nameserver`, `domain`, `search` (the later\n # of `domain` and `search` wins, as there) and `options` with `ndots:`,\n # `timeout:` (seconds) and `attempts:`, each held to glibc's bounds - 15,\n # 30 and 5. A `#` or `;` starts a comment. Anything else is ignored, as\n # glibc ignores it: a file other programs read too is not this module's\n # to refuse.\n def self.parse(text : String) : ResolvConf\n def search : Array(String)\n def timeout_ms : Int32\nend\n\nimpl Error for DnsError\n def message : String\nend\n" + "sourceSha256": "bb493ba2b8a8be515c2c05d1f388ea1650a49d239c9f7973483a16b1e85f06ae", + "surface": "module std/dns\n\n# Why a name was not resolved. One type with a question per answer, as\n# `SocketError` has, so a caller tells a name that does not exist\n# (`not_found?`, NXDOMAIN - authoritative, not worth asking again) from a\n# server that failed (`server_failure?`, SERVFAIL - maybe worth asking\n# again later) without reading a sentence.\npub struct DnsError\n # The same failure, said about *name*: a failure found while reading\n # bytes knows no name, and the resolver that asked does.\n def about(name : String, server : String) : DnsError\n def detail : String\n def initialize(kind : Int32, name : String, server : String = \"\", detail : String = \"\")\n # Not a host name at all, so nothing was asked.\n def invalid_name? : ::Bool\n def kind : Int32\n # An answer that carried the query's ID and question and could not be\n # read: a compression loop, a pointer past the end, a record running off\n # the message, an alias chain that does not end.\n def malformed? : ::Bool\n # The name as the caller wrote it.\n def name : String\n # The name exists and has no address of the kind asked for (NOERROR\n # with no records, \"NODATA\").\n def no_address? : ::Bool\n # NXDOMAIN: the name does not exist.\n def not_found? : ::Bool\n def reason : String\n # REFUSED, FORMERR or NOTIMP: the server would not answer this query.\n def refused? : ::Bool\n # The server whose answer this is, `address:port`; empty when no server\n # was asked.\n def server : String\n # SERVFAIL from every server asked.\n def server_failure? : ::Bool\n # No server answered within the timeout, on any attempt.\n def timed_out? : ::Bool\n # The server could not be reached: nothing listens on its port (an ICMP\n # refusal), no route to it, or the TCP connection the truncated answer\n # needed failed.\n def unreachable? : ::Bool\nend\n\n# A query, and the reading of an answer to one.\npub struct DnsMessage\n # The addresses of *type* this answer gives *name*, following CNAMEs:\n # the records for the end of the chain, the name the chain ended at, and\n # how many links it took. No addresses and a different name is a chain\n # that left this answer before it ended, which the resolver asks about\n # next. A chain of more than *budget* links is malformed.\n def addresses(name : String, type : Int32, budget : Int32 = MAX_CNAMES) : Tuple(Array(Bytes), String, Int32) | DnsError\n def answers : Array(DnsRecord)\n # Whether this is the answer to query *id* for *name* and *type*: a\n # response, a standard query's, with the ID, and repeating the question.\n # A refusal of the query (SERVFAIL, REFUSED, FORMERR, NOTIMP) may come\n # without the question, as some servers send it; an answer that says\n # anything about the name - NOERROR or NXDOMAIN - must carry it.\n def answers?(id : UInt16, name : String, type : Int32) : ::Bool\n # Where the message at the start of *packet* ends: past its header, its\n # questions and every record of its three sections, as their counts\n # say; -1 when they run past the bytes or a name cannot be read. Over a\n # stream the frame is the message, so an answer whose frame holds bytes\n # past this point is refused as oversized there (`Resolver`'s encrypted\n # upstreams); a datagram is not held to it.\n def self.ends_at(packet : Bytes) : Int32\n def flags : Int32\n def id : UInt16\n # The ID in a datagram's first two bytes, or -1 when it is too short to\n # be a message: what the resolver reads before it reads anything else.\n def self.id_of(packet : Bytes) : Int32\n # *extended_rcode* is the upper eight bits of the RCODE, from the\n # answer's OPT record (RFC 6891 6.1.3); 0 without one.\n def initialize(id : UInt16, flags : Int32, question_name : String, question_type : Int32, question_class : Int32, answers : Array(DnsRecord), extended_rcode : Int32 = 0)\n def self.malformed(why : String) : DnsError\n def opcode : Int32\n # The message in *packet*: its header, its one question, and - unless it\n # is truncated - its answer section, and the RCODE's upper bits from the\n # OPT record in its additional section. The authority and additional\n # sections are walked to find that record, so their records must fit\n # the bytes too; one OPT record at most (RFC 6891 6.1.1), owned by the\n # root. Anything that does not fit the bytes is `DnsError` with\n # `malformed?`, never a read past them.\n def self.parse(packet : Bytes) : DnsMessage | DnsError\n # The query for *name*'s records of *type*, class IN, with recursion\n # desired: a 12-byte header and one question. *name* is a host name\n # (`DnsName.valid?`); a trailing dot is dropped.\n #\n # A *block* above 0 pads it (RFC 7830): an OPT record follows the\n # question - the root for its name, `EDNS_PAYLOAD` for its class,\n # extended RCODE, version and flags all 0 (RFC 6891 6.1.2-6.1.4) - and\n # holds a single Padding option of zero bytes (RFC 7830 3's SHOULD), as\n # many as bring the message to the next multiple of *block*. The length\n # brought there is the message's own, without the two bytes that frame\n # it over TCP (RFC 8467 3), so a question pads to the same length over\n # TLS as over HTTPS. With no *block* there is no OPT record: that is the\n # query that goes out in the clear, where RFC 7830 6 forbids padding.\n def self.query(id : UInt16, name : String, type : Int32, block : Int32 = 0) : Bytes\n def question_class : Int32\n # The question the answer repeats; empty with `question_type` 0 when it\n # repeats none.\n def question_name : String\n def question_type : Int32\n # The whole twelve-bit RCODE: the header's four bits under the OPT\n # record's eight. An answer that says BADVERS (16) has 0 in its header,\n # and read from the header alone it would be NOERROR without records.\n def rcode : Int32\n # The name at *at* in *packet*, and the offset just past it where it\n # sits; nil when it cannot be read. Labels are 1 to 63 bytes, a label may\n # not hold a `.` (it would read as two), the name is at most 255 bytes on\n # the wire, and each compression pointer must point before the place the\n # name was last read from - RFC 1035 4.1.4's \"prior occurrence\" - so the\n # positions strictly fall and a loop cannot be written.\n def self.read_name(packet : Bytes, at : Int32) : Tuple(String, Int32) | ::Nil\n def response? : ::Bool\n # TC: the answer did not fit in the datagram and is to be asked again\n # over TCP. Its records are not read: what is there is a fragment.\n def truncated? : ::Bool\nend\n\n# What a host name may be, and the names a short one stands for.\npub struct DnsName\n # The fully qualified names a lookup of *name* asks for, in order, as\n # resolv.conf(5) says: a name ending in `.` is only itself; one with at\n # least *ndots* dots is tried as it is first and then under each search\n # domain; one with fewer is tried under each search domain first and as\n # it is last. A candidate longer than a name can be is left out.\n def self.candidates(name : String, search : Array(String), ndots : Int32) : Array(String)\n def initialize\n # *name* without the trailing dot that makes it absolute.\n def self.relative(name : String) : String\n # Two names are the same name in any case (RFC 4343), with or without\n # the trailing dot.\n def self.same?(a : String, b : String) : ::Bool\n # A host name as RFC 1123 and glibc's `res_hnok` read one: labels of 1 to\n # 63 letters, digits, `-` and `_`, no label starting with `-`, at most\n # 253 characters, one trailing dot allowed. The last label must not be\n # all digits (RFC 3696 2: no top-level domain is), so `999.1.1.1` and\n # `1.2.3` stay what the numeric parser refuses rather than becoming\n # questions to a name server.\n def self.valid?(name : String) : ::Bool\n # Why *name* is not a host name, or nil when it is.\n def self.why(name : String) : String | ::Nil\nend\n\n# One resource record from an answer section.\npub struct DnsRecord\n # The character-strings of a TXT record's data (RFC 1035 3.3.14), each\n # a length byte and that many bytes; nil when the last one runs past the\n # data. A record holds one or more, and `Resolver#txt` joins them, as\n # SPF (RFC 7208 3.3) and DKIM read a record of several.\n def self.character_strings(data : Bytes) : Array(String) | ::Nil\n # The record's data as it came: four bytes for an A record, sixteen for\n # an AAAA, the encoded name for a CNAME.\n def data : Bytes\n def initialize(name : String, type : Int32, record_class : Int32, ttl : UInt32, data : Bytes, target : String)\n def name : String\n def record_class : Int32\n # A CNAME's target, read with compression; empty for any other type.\n def target : String\n def ttl : UInt32\n def type : Int32\nend\n\n# The hosts file: an address, then the names it answers for.\npub struct HostsFile\n def initialize\n # The addresses *text* gives *name*, as written and in the file's order:\n # every line whose names include it, in any case and with or without a\n # trailing dot. A line with an address and no name says nothing.\n def self.lookup(text : String, name : String) : Array(String)\nend\n\n# `/etc/resolv.conf`, as resolv.conf(5) describes it and glibc reads it.\npub struct ResolvConf\n def attempts : Int32\n def initialize(nameservers : Array(String) = [] of String, search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2)\n # Each an address as written - IPv4, or IPv6 without a zone - optionally\n # with a port (`192.0.2.1:5353`, `[2001:db8::1]:5353`), which\n # resolv.conf itself never has and a resolver built in a program may.\n def nameservers : Array(String)\n def ndots : Int32\n # The keywords glibc reads: `nameserver`, `domain`, `search` (the later\n # of `domain` and `search` wins, as there) and `options` with `ndots:`,\n # `timeout:` (seconds) and `attempts:`, each held to glibc's bounds - 15,\n # 30 and 5. A `#` or `;` starts a comment. Anything else is ignored, as\n # glibc ignores it: a file other programs read too is not this module's\n # to refuse.\n def self.parse(text : String) : ResolvConf\n def search : Array(String)\n def timeout_ms : Int32\nend\n\n# What SVCB resolution (RFC 9460 section 3) of a service came to: the\n# ServiceMode records found, by priority, and the name they were asked\n# at - the service's own, or the target of the last AliasMode record\n# followed (`aliases` of them). No records is resolution that found no\n# endpoint; where `aliases` is above 0 the name is still one to connect\n# to (section 3's last endpoint), and \".\" says the service is not there\n# (section 2.5.1). `Resolver#https_records` answers one.\npub struct SvcbAnswer\n def aliases : Int32\n def initialize(name : String, aliases : Int32, records : Array(SvcbRecord))\n def name : String\n def records : Array(SvcbRecord)\n # An AliasMode record's target was \".\": the service says it is not there.\n def unavailable? : ::Bool\nend\n\n# One SVCB or HTTPS record's data (RFC 9460 section 2.2): its SvcPriority,\n# its TargetName, and its SvcParams by key.\n#\n# A priority of 0 is AliasMode - the service is at *target*'s records of\n# the same type, and the params mean nothing (section 2.4.2) - and any\n# other ServiceMode, *target* an endpoint and the params how to reach it,\n# a smaller priority preferred. The target is written out without its\n# trailing dot, and is \".\" for the root, which in ServiceMode stands for\n# the record's owner (`endpoint`, section 2.5.2) and in AliasMode says\n# the service is not there (section 2.5.1).\n#\n# What `parse` refuses as malformed, with `DnsError#malformed?`: what\n# section 2.2 has a client refuse - a param cut short by the end of the\n# data, keys out of strictly increasing order, a key twice - a target\n# that is compressed (it must not be, section 2.2) or holds a label with\n# a dot in it (see `DnsMessage.read_name`), and a value of a key it\n# reads that is not of its key's form (sections 7.1 to 7.4, and RFC 9848\n# 3's ech): mandatory a list of keys in increasing order that does not\n# list itself, alpn one or more ids of 1 to 255 bytes filling the value,\n# no-default-alpn empty, port two bytes, ipv4hint and ipv6hint one or\n# more addresses, ech an ECHConfigList whose length prefix is its own.\n# So is a record that is not self-consistent (section 2.4.3): a key\n# mandatory lists that it does not carry, no-default-alpn without alpn.\n# Section 2.2 then has the whole RRset refused, which `Resolver` does.\n# A key past ech is kept as its bytes, whatever they are.\n#\n# `to_s` is the record's data in presentation format as dig 9.20 prints it,\n# for reading the record while debugging: bench/std_dns_exercise.sh holds\n# the two to the same text, record by record.\npub struct SvcbRecord\n # AliasMode: the service is at the target's records of the same type.\n def alias? : ::Bool\n # The alpn-ids of `alpn` (section 7.1), in their order; none without it.\n def alpn : Array(String)\n # The ECHConfigList of `ech` (RFC 9848 3), length prefix and all, as\n # std/tls's `TlsConfig#ech_configs` takes it; nil without it.\n def ech : Bytes | ::Nil\n # The host a ServiceMode record's endpoint is: the target, or the\n # record's owner when the target is \".\" (section 2.5.2).\n def endpoint : String\n def initialize(owner : String, priority : Int32, target : String, keys : Array(Int32), values : Array(Bytes))\n # The addresses of `ipv4hint` and `ipv6hint` (section 7.3), four bytes\n # and sixteen each, as they came.\n def ipv4hint : Array(Bytes)\n # Sixteen bytes as BIND's inet_ntop writes them, which is RFC 5952's\n # form with one more rule: the longest run of two or more zero groups,\n # the first of equal ones, as `::`; and an address whose first six\n # groups are zero, or five zero and then ffff, ending in its last four\n # bytes dotted - `::1.2.3.4`, `::ffff:192.0.2.1` - though not `::` and\n # `::1`, whose run of zeros is longer.\n def self.ipv6_text(address : Bytes) : String\n def ipv6hint : Array(Bytes)\n # *key*'s name in presentation format: one of `NAMES`, or `keyNNNNN`\n # (section 2.1).\n def self.key_name(key : Int32) : String\n # The keys in their order, which is increasing, and each one's value as\n # it came.\n def keys : Array(Int32)\n # The keys `mandatory` lists (section 8); none without it.\n def mandatory : Array(Int32)\n # Whether the scheme's default protocols are left out of the record's\n # (section 7.1): http/1.1, for HTTPS.\n def no_default_alpn? : ::Bool\n # The name the record was found at: what a ServiceMode target of \".\"\n # stands for.\n def owner : String\n # The record in *data*, found at *owner*; or why it is malformed (see\n # the struct).\n def self.parse(data : Bytes, owner : String = \"\") : SvcbRecord | DnsError\n # The port the endpoint is reached on (section 7.2); nil for the\n # origin's own.\n def port : Int32 | ::Nil\n def priority : Int32\n # *data* in pieces of *width* bytes; none for nil.\n def self.split(data : Bytes | ::Nil, width : Int32) : Array(Bytes)\n def target : String\n # The record's data in presentation format (section 2.1), as dig 9.20\n # writes it: the priority; the target, absolute, its bytes escaped as\n # BIND escapes a name's; then each param in the order it came, by its\n # name - alpn's ids quoted and joined by commas, a comma in one written\n # `\\\\,` and a backslash `\\\\\\\\`; port in decimal; the hints as addresses,\n # IPv6 as BIND's inet_ntop writes one (`SvcbRecord.ipv6_text`); ech in\n # Base64; a key this module does not read as `keyNNNNN` and its value\n # quoted, or alone when it is empty. Inside quotes a byte outside\n # printable ASCII is `\\DDD`, in decimal; alpn's ids take a space so too,\n # and the generic form takes it as it is (dig 9.20.24, measured).\n def to_s : String\n # The value of *key*, or nil when the record does not carry it.\n def value(key : Int32) : Bytes | ::Nil\n def values : Array(Bytes)\n # One byte of an alpn-id, inside the quotes: the list's own escapes (RFC\n # 9460 appendix A.1) under the quoted string's.\n def self.write_alpn_byte(text : String::Builder, byte : UInt8) : Nil\n # *name*, absolute, as BIND writes one: `\"`, `(`, `)`, `;`, `\\`, `@`\n # and `$` after a backslash, a byte outside printable ASCII as `\\DDD`.\n def self.write_name(text : String::Builder, name : String) : Nil\n # One byte of a value in the generic form, inside the quotes.\n def self.write_quoted_byte(text : String::Builder, byte : UInt8) : Nil\nend\n\nimpl Error for DnsError\n def message : String\nend\n" }, { "name": "std/ecdsa", "path": "src/std/ecdsa.iyi", - "sourceSha256": "f03b7a2eb1d7e257fb04f8a0a2cd787550f4f31ea2b1fd7ea1a212fafc1c0317", + "sourceSha256": "29ecb1c042308053d33ee2fc0289839f8a96ca4e5c6f36815c8f379fb3c57399", "surface": "module std/ecdsa\n\n# The curves a key can be on.\npub enum EcdsaCurve : Int32\n P256 = 0\n P384 = 1\n P521 = 2\n # The length of the group's order n in bits.\n def bits : Int32\n def self.from_oid(oid : String) : EcdsaCurve | ::Nil\n # The curve's OID, as a key's AlgorithmIdentifier names it.\n def oid : String\n # The bytes a coordinate, or a scalar, is written in.\n def size : Int32\nend\n\n# What reading a key answers for bytes that are not an ECDSA public key.\npub struct EcdsaError\n def initialize(message : String)\n def to_s : String\nend\n\n# The hashes a signature is made over.\npub enum EcdsaHash : Int32\n SHA256 = 0\n SHA384 = 1\n SHA512 = 2\n def digest(data : Array(UInt8)) : Array(UInt8)\nend\n\npub struct EcdsaPublicKey\n def curve : EcdsaCurve\n # The key from a point in SEC 1's uncompressed encoding (section 2.3.3):\n # 0x04, then x and y big-endian at the curve's width.\n def self.from_point(curve : EcdsaCurve, point : Array(UInt8)) : EcdsaPublicKey | EcdsaError\n # A SubjectPublicKeyInfo (RFC 5280 section 4.1.2.7) naming\n # id-ecPublicKey and one of the three curves (RFC 5480 section 2.1.1).\n def self.from_spki(der : Array(UInt8)) : EcdsaPublicKey | EcdsaError\n def self.from_spki(value : DerValue) : EcdsaPublicKey | EcdsaError\n def initialize(curve : EcdsaCurve, x : BigInt, y : BigInt, group : EcdsaGroup)\n # Whether *signature*, the DER of an Ecdsa-Sig-Value, is this key's\n # signature of *message* under *hash*.\n def verify(hash : EcdsaHash, message : Array(UInt8) | String, signature : Array(UInt8)) : Bool\n def x : BigInt\n def y : BigInt\nend\n\nimpl Error for EcdsaError\n def message : String\nend\n" }, { @@ -203,7 +215,7 @@ "name": "std/file", "path": "src/std/file.iyi", "sourceSha256": "356dadb8c25c92194b4438cb0c8891fc4758042e8c3d8e7c25c58952e94643bb", - "surface": "module std/file\n\npub alias File = ::File\n\nclass ::File\n def self.basename(path : String | Path, suffix : String | ::Nil = nil) : String\n def self.chmod(path : String | Path, mode : Int32) : Nil\n def self.chown(path : String | Path, uid : Int32, gid : Int32) : Nil\n def self.directory?(path : String | Path) : Bool\n def self.dirname(path : String | Path) : String\n # A line at a time, off the file as it is read: it read the whole file\n # into an array of its lines first, so a log of any size was the\n # program's memory twice over before the first line was seen. Lines as\n # `read_lines` gives them, without their `\\n` or a `\\r` before it.\n def self.each_line(path : String | Path, &block : (String -> Nil)) : Nil\n def self.empty?(path : String | Path) : Bool\n # On Windows a file runs by its extension, one of PATHEXT's - what cmd\n # and `CreateProcess`'s search go by - and a directory is searchable\n # when it can be read. `cmd.exe` answered false: the made-up mode gave\n # the execute bit to directories alone.\n def self.executable?(path : String | Path) : Bool\n def self.expand_path(path : String | Path, base : String | ::Nil = nil) : String\n def self.extname(path : String | Path) : String\n def self.file?(path : String | Path) : Bool\n def self.info(path : String | Path, follow_symlinks : Bool = true) : Info\n def self.info?(path : String | Path, follow_symlinks : Bool = true) : Info | ::Nil\n def self.join(first : String | Path, second : String | Path, third : String | Path) : String\n def self.join(first : String | Path, second : String | Path) : String\n def self.join(parts : Array(String)) : String\n def self.link(old_path : String | Path, new_path : String | Path) : Nil\n # Whether *path* matches the shell-style *pattern*: `*` any run of\n # characters within a segment, `**` any run of segments (a whole\n # segment of the pattern), `?` one character but a separator, `[abc]`,\n # `[a-z]` and `[^...]` one character of a set, `{a,b}` one of the\n # branches (nested to ten), and `\\` the next character literally. Only\n # `/` in the pattern matches a separator. On Windows a name's `\\` is a\n # separator as well, and a pattern's `\\` is one too rather than an\n # escape: `Dir.glob` answers with either.\n #\n # The other library's algorithm (a port of the glob-match crate, after\n # research.swtch.com/glob), linear in the name but for braces. This\n # had `*` and `?` alone, byte by byte: `*` crossed separators, `?` took\n # half of a two-byte character, `[a]` and `{a,b}` were literal text,\n # and `*a*a*a*a*a*a*a*a*a*a*b` against forty `a`s did not finish.\n def self.match?(path : String | Path, pattern : String) : Bool\n def self.read_lines(path : String | Path) : Array(String)\n def self.readable?(path : String | Path) : Bool\n def self.readlink(path : String | Path) : String\n def self.real_path(path : String | Path) : String\n def self.rename(old_path : String | Path, new_path : String | Path) : Nil\n def self.same?(path1 : String | Path, path2 : String | Path) : Bool\n def self.same_content?(path1 : String | Path, path2 : String | Path) : Bool\n def self.size(path : String | Path) : Int64\n def self.symlink(old_path : String | Path, new_path : String | Path) : Nil\n def self.symlink?(path : String | Path) : Bool\n # A new, empty file in the temporary directory, and its path.\n #\n # Made with an exclusive create - `O_CREAT | O_EXCL`, `CREATE_NEW` on\n # Windows - which fails when anything is at the path, a symlink\n # included, rather than asked whether something is there and written\n # after. The name was the process id and a counter, and the file was\n # written wherever the path led if nothing was there when asked: a\n # symlink planted at the next name in a shared `/tmp` between the\n # question and the write sent an upload wherever its owner pointed it.\n # The name is sixteen hex digits of the system's randomness now, so it\n # cannot be planted in advance either; Windows' `%TEMP%` belongs to the\n # user, and there the create's refusal is the guarantee and the name\n # needs only to be new. The file is the owner's alone, mode 0600.\n def self.tempfile(prefix : String = \"tmp\", suffix : String = \"\") : String\n # The file is deleted after the block if it is still there: the block\n # may have renamed it into place - write to a temporary name, then\n # rename, is what the name is for - and `delete` of a path that is gone\n # panicked after the block had done its work.\n def self.tempfile(prefix : String = \"tmp\", suffix : String = \"\", &block : (String -> Nil)) : Nil\n # *time* 0 is \"now\"; any other value is the Unix second both atime and\n # mtime are set to.\n #\n # The darwin branch passed a null `times` to `utimes`, which is the\n # kernel's spelling of \"now\" — so `touch(path, 1)` set the clock's time\n # there and the epoch second on Linux, one name with two behaviours\n # decided by the platform (SPEC.md III.1.7a). It writes the pair now, in\n # the same shape the Linux branch writes its `timespec`s: `timeval` is an\n # `Int64` second and an `Int32` microsecond padded to eight, so four\n # `Int64` slots are the two structures and the odd slots are the\n # microseconds, zero.\n def self.touch(path : String | Path, time : Int64 = 0_i64) : Nil\n def self.touch?(path : String | Path, time : Int64 = 0_i64) : Bool\n def self.truncate(path : String | Path, size : Int64 = 0_i64) : Nil\n # Why the last call failed, in Windows' own words. A message that names\n # only what was attempted — \"Cannot symlink A to B\" — leaves the caller\n # with nothing to act on, and on Windows the difference between \"a\n # required privilege is not held\" and \"the file already exists\" is the\n # difference between a machine setting and a bug in the program. The\n # code is kept beside the text because the text is localised and the\n # number is not.\n #\n # FORMAT_MESSAGE_FROM_SYSTEM is 0x1000 and FORMAT_MESSAGE_IGNORE_INSERTS\n # 0x200: without the second, a message with an insert in it fails rather\n # than printing the placeholder. Windows ends these sentences with CRLF\n # and sometimes a trailing space, which is a line break in the middle of\n # a panic, so the tail comes off. `std/dir`'s refusals say it too.\n def self.win_error : String\n def self.writable?(path : String | Path) : Bool\nend\n\nstruct ::File::Flags\n def initialize(value : Int32 = 0)\n def set_group? : Bool\n def set_user? : Bool\n def sticky? : Bool\n def value : Int32\nend\n\nstruct ::File::Info\n def dev : Int64\n def directory? : Bool\n def executable? : Bool\n def file? : Bool\n def flags : Flags\n def group_id : String\n def initialize(size : Int64, permissions : Permissions, type : Type, flags : Flags, modification_time : Int64, owner_id : String, group_id : String, dev : Int64 = 0_i64, ino : UInt64 = 0_u64)\n def ino : UInt64\n def modification_time : Int64\n def owner_id : String\n def permissions : Permissions\n def readable? : Bool\n # An identity of 0 is none: Windows answers a path it could not open\n # with the attributes alone (`info?`), and two such files - different\n # sizes, different contents - were the same file.\n def same_file?(other : Info) : Bool\n def size : Int64\n def symlink? : Bool\n def type : Type\n def writable? : Bool\nend\n\nstruct ::File::Permissions\n def group_execute? : Bool\n def group_read? : Bool\n def group_write? : Bool\n def initialize(value : Int32)\n def other_execute? : Bool\n def other_read? : Bool\n def other_write? : Bool\n def owner_execute? : Bool\n def owner_read? : Bool\n def owner_write? : Bool\n def to_i : Int32\n def to_s(io : IO) : Nil\n def to_s : String\n def value : Int32\nend\n\nenum ::File::Type : UInt8\n # Returns `true` if this enum value equals `BlockDevice`\n def block_device? : Bool\n # Returns `true` if this enum value equals `CharacterDevice`\n def character_device? : Bool\n def device? : Bool\n # Returns `true` if this enum value equals `Directory`\n def directory? : Bool\n # Returns `true` if this enum value equals `File`\n def file? : Bool\n # Returns `true` if this enum value equals `Pipe`\n def pipe? : Bool\n # Returns `true` if this enum value equals `Socket`\n def socket? : Bool\n # Returns `true` if this enum value equals `Symlink`\n def symlink? : Bool\nend\n\nclass ::IyiIO\n # Advisory locks on the whole file, the other library's `flock_*`: a\n # shared one many holders may have at once, an exclusive one a single\n # holder. The holder is this open file - another `File.open` of the same\n # path, in this process or another, is another holder - until\n # `flock_unlock`, or until the file is closed.\n #\n # File.open(\"app.lock\", \"w\") do |f|\n # f.flock_exclusive { run_alone }.or_panic\n # end\n #\n # A lock another holder has is waited for a task at a time: tried again\n # after 1, 2, 4 ... 64 ms, the thread's other tasks running meanwhile,\n # and cancellable. `try_flock_*` answers at once whether it took it.\n #\n # Linux and darwin lock with `flock(2)`, which only other locks heed, and\n # turn a shared lock this file holds into an exclusive one in place.\n # Windows locks every byte with `LockFileEx`, which is mandatory - while\n # one handle holds it exclusively, another handle's reads and writes of\n # the file fail - and counts each lock a handle takes: unlock a shared\n # lock before taking the exclusive one.\n def flock_exclusive : Nil | Cancelled\n # The block's answer, run with the lock held and unlocked after it, even\n # when the block panics.\n def flock_exclusive(&block : (-> R)) : R | Cancelled forall R\n def flock_shared : Nil | Cancelled\n def flock_shared(&block : (-> R)) : R | Cancelled forall R\n # Gives the lock back; one this file does not hold is no error.\n def flock_unlock : Nil\n # The kernel's move: the new offset, or a negative error. A stream with\n # no descriptor - a `Memory` - is -1, which Windows' `to_u64` of it\n # panicked \"arithmetic overflow\" on; it is EBADF everywhere, as Linux's\n # kernel answers it.\n protected def self.lseek(fd : Int64, offset : Int64, whence : Int32) : Int64\n # Bytes from the start to where the next read or write happens.\n def pos : Int64\n def pos=(position : Int64) : Int64\n # Moves to *offset* from the start, from here, or from the end, and\n # answers the new position.\n def seek(offset : Int64, whence : Seek = Seek::Set) : Int64\n def seek(offset : Int32, whence : Seek = Seek::Set) : Int64\n def try_flock_exclusive : Bool\n def try_flock_shared : Bool\nend\n\nenum ::IyiIO::Seek : Int32\nend\n" + "surface": "module std/file\n\npub alias File = ::File\n\n# Linux: the kernel's own calls, as `socket` and the prelude make them\n# (SPEC.md III.9: an own-prelude object asks libc for nothing). The\n# shape is libc's, so the callers above read the same; a failure comes\n# back as the negative errno the kernel answers, which every caller\n# here reads as \"not zero\". `realpath` is a walk over `readlinkat` and\n# `newfstatat`, since libc's is a library function and not a call.\npub struct LibC\n # x86_64 `struct stat`, 144 bytes, the layout glibc shares with the kernel.\n struct Stat\n def initialize\n def st_atime : Timespec\n def st_blksize : Int64\n def st_blocks : Int64\n def st_ctime : Timespec\n def st_dev : UInt64\n def st_gid : Int32\n def st_ino : UInt64\n def st_mode : Int32\n def st_mtime : Timespec\n def st_nlink : UInt64\n def st_pad0 : Int32\n def st_rdev : UInt64\n def st_reserved0 : Int64\n def st_reserved1 : Int64\n def st_reserved2 : Int64\n def st_size : Int64\n def st_uid : Int32\n end\n struct Timespec\n def initialize\n def tv_nsec : Int64\n def tv_sec : Int64\n end\n def self.access(path : ::Pointer(UInt8), mode : Int32) : Int32\n def self.chmod(path : ::Pointer(UInt8), mode : Int32) : Int32\n def self.chown(path : ::Pointer(UInt8), owner : Int32, group : Int32) : Int32\n def self.fstat(fd : Int32, buf : ::Pointer(Stat)) : Int32\n def self.ftruncate(fd : Int32, length : Int64) : Int32\n def initialize\n def self.link(oldpath : ::Pointer(UInt8), newpath : ::Pointer(UInt8)) : Int32\n def self.lstat(path : ::Pointer(UInt8), buf : ::Pointer(Stat)) : Int32\n def self.readlink(path : ::Pointer(UInt8), buf : ::Pointer(UInt8), bufsiz : UInt64) : Int64\n # The absolute path with every `.`, `..` and symbolic link resolved, or\n # null when a component does not exist. Forty links deep is the limit,\n # as it is in libc.\n def self.realpath(path : ::Pointer(UInt8), resolved : ::Pointer(UInt8)) : ::Pointer(UInt8)\n def self.rename(oldpath : ::Pointer(UInt8), newpath : ::Pointer(UInt8)) : Int32\n def self.stat(path : ::Pointer(UInt8), buf : ::Pointer(Stat)) : Int32\n def self.symlink(target : ::Pointer(UInt8), linkpath : ::Pointer(UInt8)) : Int32\n def self.sys(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64 = 0_i64, e : Int64 = 0_i64) : Int64\n def self.truncate(path : ::Pointer(UInt8), length : Int64) : Int32\n # *sec* 0 is \"now\" (a null timespec, as utimensat documents). Otherwise\n # atime and mtime are both that Unix second.\n def self.utimensat(path : ::Pointer(UInt8), sec : Int64) : Int32\nend\n\nclass ::File\n def self.basename(path : String | Path, suffix : String | ::Nil = nil) : String\n def self.chmod(path : String | Path, mode : Int32) : Nil\n def self.chown(path : String | Path, uid : Int32, gid : Int32) : Nil\n def self.directory?(path : String | Path) : Bool\n def self.dirname(path : String | Path) : String\n # A line at a time, off the file as it is read: it read the whole file\n # into an array of its lines first, so a log of any size was the\n # program's memory twice over before the first line was seen. Lines as\n # `read_lines` gives them, without their `\\n` or a `\\r` before it.\n def self.each_line(path : String | Path, &block : (String -> Nil)) : Nil\n def self.empty?(path : String | Path) : Bool\n # On Windows a file runs by its extension, one of PATHEXT's - what cmd\n # and `CreateProcess`'s search go by - and a directory is searchable\n # when it can be read. `cmd.exe` answered false: the made-up mode gave\n # the execute bit to directories alone.\n def self.executable?(path : String | Path) : Bool\n def self.expand_path(path : String | Path, base : String | ::Nil = nil) : String\n def self.extname(path : String | Path) : String\n def self.file?(path : String | Path) : Bool\n def self.info(path : String | Path, follow_symlinks : Bool = true) : Info\n def self.info?(path : String | Path, follow_symlinks : Bool = true) : Info | ::Nil\n def self.join(first : String | Path, second : String | Path, third : String | Path) : String\n def self.join(first : String | Path, second : String | Path) : String\n def self.join(parts : Array(String)) : String\n def self.link(old_path : String | Path, new_path : String | Path) : Nil\n # Whether *path* matches the shell-style *pattern*: `*` any run of\n # characters within a segment, `**` any run of segments (a whole\n # segment of the pattern), `?` one character but a separator, `[abc]`,\n # `[a-z]` and `[^...]` one character of a set, `{a,b}` one of the\n # branches (nested to ten), and `\\` the next character literally. Only\n # `/` in the pattern matches a separator. On Windows a name's `\\` is a\n # separator as well, and a pattern's `\\` is one too rather than an\n # escape: `Dir.glob` answers with either.\n #\n # The other library's algorithm (a port of the glob-match crate, after\n # research.swtch.com/glob), linear in the name but for braces. This\n # had `*` and `?` alone, byte by byte: `*` crossed separators, `?` took\n # half of a two-byte character, `[a]` and `{a,b}` were literal text,\n # and `*a*a*a*a*a*a*a*a*a*a*b` against forty `a`s did not finish.\n def self.match?(path : String | Path, pattern : String) : Bool\n def self.read_lines(path : String | Path) : Array(String)\n def self.readable?(path : String | Path) : Bool\n def self.readlink(path : String | Path) : String\n def self.real_path(path : String | Path) : String\n def self.rename(old_path : String | Path, new_path : String | Path) : Nil\n def self.same?(path1 : String | Path, path2 : String | Path) : Bool\n def self.same_content?(path1 : String | Path, path2 : String | Path) : Bool\n def self.size(path : String | Path) : Int64\n def self.symlink(old_path : String | Path, new_path : String | Path) : Nil\n def self.symlink?(path : String | Path) : Bool\n # A new, empty file in the temporary directory, and its path.\n #\n # Made with an exclusive create - `O_CREAT | O_EXCL`, `CREATE_NEW` on\n # Windows - which fails when anything is at the path, a symlink\n # included, rather than asked whether something is there and written\n # after. The name was the process id and a counter, and the file was\n # written wherever the path led if nothing was there when asked: a\n # symlink planted at the next name in a shared `/tmp` between the\n # question and the write sent an upload wherever its owner pointed it.\n # The name is sixteen hex digits of the system's randomness now, so it\n # cannot be planted in advance either; Windows' `%TEMP%` belongs to the\n # user, and there the create's refusal is the guarantee and the name\n # needs only to be new. The file is the owner's alone, mode 0600.\n def self.tempfile(prefix : String = \"tmp\", suffix : String = \"\") : String\n # The file is deleted after the block if it is still there: the block\n # may have renamed it into place - write to a temporary name, then\n # rename, is what the name is for - and `delete` of a path that is gone\n # panicked after the block had done its work.\n def self.tempfile(prefix : String = \"tmp\", suffix : String = \"\", &block : (String -> Nil)) : Nil\n # *time* 0 is \"now\"; any other value is the Unix second both atime and\n # mtime are set to.\n #\n # The darwin branch passed a null `times` to `utimes`, which is the\n # kernel's spelling of \"now\" — so `touch(path, 1)` set the clock's time\n # there and the epoch second on Linux, one name with two behaviours\n # decided by the platform (SPEC.md III.1.7a). It writes the pair now, in\n # the same shape the Linux branch writes its `timespec`s: `timeval` is an\n # `Int64` second and an `Int32` microsecond padded to eight, so four\n # `Int64` slots are the two structures and the odd slots are the\n # microseconds, zero.\n def self.touch(path : String | Path, time : Int64 = 0_i64) : Nil\n def self.touch?(path : String | Path, time : Int64 = 0_i64) : Bool\n def self.truncate(path : String | Path, size : Int64 = 0_i64) : Nil\n def self.writable?(path : String | Path) : Bool\nend\n\nstruct ::File::Flags\n def initialize(value : Int32 = 0)\n def set_group? : Bool\n def set_user? : Bool\n def sticky? : Bool\n def value : Int32\nend\n\nstruct ::File::Info\n def dev : Int64\n def directory? : Bool\n def executable? : Bool\n def file? : Bool\n def flags : Flags\n def group_id : String\n def initialize(size : Int64, permissions : Permissions, type : Type, flags : Flags, modification_time : Int64, owner_id : String, group_id : String, dev : Int64 = 0_i64, ino : UInt64 = 0_u64)\n def ino : UInt64\n def modification_time : Int64\n def owner_id : String\n def permissions : Permissions\n def readable? : Bool\n # An identity of 0 is none: Windows answers a path it could not open\n # with the attributes alone (`info?`), and two such files - different\n # sizes, different contents - were the same file.\n def same_file?(other : Info) : Bool\n def size : Int64\n def symlink? : Bool\n def type : Type\n def writable? : Bool\nend\n\nstruct ::File::Permissions\n def group_execute? : Bool\n def group_read? : Bool\n def group_write? : Bool\n def initialize(value : Int32)\n def other_execute? : Bool\n def other_read? : Bool\n def other_write? : Bool\n def owner_execute? : Bool\n def owner_read? : Bool\n def owner_write? : Bool\n def to_i : Int32\n def to_s(io : IO) : Nil\n def to_s : String\n def value : Int32\nend\n\nenum ::File::Type : UInt8\n # Returns `true` if this enum value equals `BlockDevice`\n def block_device? : Bool\n # Returns `true` if this enum value equals `CharacterDevice`\n def character_device? : Bool\n def device? : Bool\n # Returns `true` if this enum value equals `Directory`\n def directory? : Bool\n # Returns `true` if this enum value equals `File`\n def file? : Bool\n # Returns `true` if this enum value equals `Pipe`\n def pipe? : Bool\n # Returns `true` if this enum value equals `Socket`\n def socket? : Bool\n # Returns `true` if this enum value equals `Symlink`\n def symlink? : Bool\nend\n\nclass ::IyiIO\n # Advisory locks on the whole file, the other library's `flock_*`: a\n # shared one many holders may have at once, an exclusive one a single\n # holder. The holder is this open file - another `File.open` of the same\n # path, in this process or another, is another holder - until\n # `flock_unlock`, or until the file is closed.\n #\n # File.open(\"app.lock\", \"w\") do |f|\n # f.flock_exclusive { run_alone }.or_panic\n # end\n #\n # A lock another holder has is waited for a task at a time: tried again\n # after 1, 2, 4 ... 64 ms, the thread's other tasks running meanwhile,\n # and cancellable. `try_flock_*` answers at once whether it took it.\n #\n # Linux and darwin lock with `flock(2)`, which only other locks heed, and\n # turn a shared lock this file holds into an exclusive one in place.\n # Windows locks every byte with `LockFileEx`, which is mandatory - while\n # one handle holds it exclusively, another handle's reads and writes of\n # the file fail - and counts each lock a handle takes: unlock a shared\n # lock before taking the exclusive one.\n def flock_exclusive : Nil | Cancelled\n # The block's answer, run with the lock held and unlocked after it, even\n # when the block panics.\n def flock_exclusive(&block : (-> R)) : R | Cancelled forall R\n def flock_shared : Nil | Cancelled\n def flock_shared(&block : (-> R)) : R | Cancelled forall R\n # Gives the lock back; one this file does not hold is no error.\n def flock_unlock : Nil\n # The kernel's move: the new offset, or a negative error. A stream with\n # no descriptor - a `Memory` - is -1, which Windows' `to_u64` of it\n # panicked \"arithmetic overflow\" on; it is EBADF everywhere, as Linux's\n # kernel answers it.\n protected def self.lseek(fd : Int64, offset : Int64, whence : Int32) : Int64\n # Bytes from the start to where the next read or write happens.\n def pos : Int64\n def pos=(position : Int64) : Int64\n # Moves to *offset* from the start, from here, or from the end, and\n # answers the new position.\n def seek(offset : Int64, whence : Seek = Seek::Set) : Int64\n def seek(offset : Int32, whence : Seek = Seek::Set) : Int64\n def try_flock_exclusive : Bool\n def try_flock_shared : Bool\nend\n\nenum ::IyiIO::Seek : Int32\nend\n" }, { "name": "std/file_utils", @@ -247,6 +259,12 @@ "sourceSha256": "b360a4741701255597bd44285eab9b6577e63afd86cdc4425f8008b21a3a781c", "surface": "module std/hpack\n\npub alias Bytes = Std::Slice::Bytes\n\npub class Decoder\n def decode(payload : Bytes) : Array(HeaderField) | HpackError\n def dynamic_table : DynamicTable\n def initialize(max_table_size : Int32 = 4096, protocol_max_table_size : Int32 = 4096, max_header_list_size : Int32 = 65536, max_header_count : Int32 = 1000)\n def max_header_count : Int32\n def max_header_count=(value : Int32) : Int32\n def max_header_list_size : Int32\n def max_header_list_size=(value : Int32) : Int32\n def protocol_max_table_size : Int32\n # A SETTINGS_HEADER_TABLE_SIZE change reaches the table too: the limit\n # was only this field's, and the table went on refusing an update above\n # the size it was made with.\n def protocol_max_table_size=(limit : Int32) : Nil\nend\n\npub class DynamicTable\n def add(name : String, value : String, never_indexed : Bool = false) : Nil\n def add(field : HeaderField) : Nil\n def empty? : Bool\n def entries : Array(HeaderField)\n def find(name : String, value : String) : Tuple(Int32, Bool)\n def find_name(name : String) : Int32\n def get(relative_index : Int32) : HeaderField | ::Nil\n # A table is made within the limit it is resized within: made above it,\n # an encoder's table outgrew the peer's, and an Encoder made with 8192\n # sent blocks a default Decoder refused (\"Index 121 out of range (max:\n # 103)\") while `set_max_size(8192)` on it was refused.\n def initialize(max_size : Int32 = 4096, settings_max_size : Int32 = 4096)\n def length : Int32\n def max_size : Int32\n def set_max_size(new_max : Int32) : Nil | HpackError\n def settings_max_size : Int32\n # The protocol's limit (SETTINGS_HEADER_TABLE_SIZE) as it changes during\n # a connection; the size in use still moves only by `set_max_size`.\n def settings_max_size=(limit : Int32) : Nil\n def size : Int32\n def self.size_error(new_max : Int32, limit : Int32) : HpackError | ::Nil\nend\n\npub class Encoder\n def dynamic_table : DynamicTable\n def encode(headers : Array(HeaderField), force_huffman : Bool | ::Nil = nil) : Bytes\n def initialize(max_table_size : Int32 = 4096, protocol_max_table_size : Int32 = 4096, use_huffman : Bool = true)\n def protocol_max_table_size : Int32\n # A SETTINGS_HEADER_TABLE_SIZE change reaches the table too: the limit\n # was only this field's, so a raised one was refused by the table and a\n # lowered one left the table above it (RFC 7541 4.2), with no update.\n # A lowered limit is always signalled: the peer's table may still be the\n # size the old limit let it start at, which this encoder never sent.\n def protocol_max_table_size=(limit : Int32) : Nil\n def set_max_table_size(new_max : Int32) : Nil | HpackError\n def use_huffman : Bool\n def use_huffman=(value : Bool) : Bool\nend\n\npub struct HeaderField\n def ==(other : HeaderField) : Bool\n def indexing : IndexingMode\n def initialize(name : String, value : String, indexing : IndexingMode = IndexingMode::Incremental)\n def initialize(name : String, value : String, never_indexed : Bool)\n def name : String\n def never_indexed : Bool\n def size : Int32\n def to_s : String\n def value : String\nend\n\npub struct HpackError\n def initialize(message : String)\n def to_s : String\nend\n\npub class Huffman\n def self.decode(bytes : Bytes, offset : Int32, length : Int32) : String | HpackError\n def self.decode(bytes : Bytes) : String | HpackError\n def self.encode(input : String, buffer : Array(UInt8)) : Nil\n def self.encode(input : String) : Bytes\n def self.encode(input : Bytes) : Bytes\n def self.encoded_size(input : String) : Int32\nend\n\npub struct IndexingMode\n def ==(other : IndexingMode) : Bool\n def initialize(value : Int32)\n def value : Int32\nend\n\npub class Integer\n def self.decode(bytes : Bytes, offset : Int32, prefix_bits : Int32) : Tuple(UInt64, Int32) | HpackError\n def self.encode(val : UInt64, prefix_bits : Int32, prefix_byte : UInt8, buffer : Array(UInt8)) : Nil\n def self.encode(val : UInt64, prefix_bits : Int32, prefix_byte : UInt8 = 0_u8) : Bytes\nend\n\npub alias IntegerCodec = Integer\n\npub class StaticTable\n def self.find(name : String, value : String) : Tuple(Int32, Bool)\n def self.find_name(name : String) : Int32\n def self.get(index : Int32) : HeaderField | ::Nil\n def self.size : Int32\nend\n\npub class Table\n def self.find(dynamic_table : DynamicTable, name : String, value : String) : Tuple(Int32, Bool)\n def self.find_name(dynamic_table : DynamicTable, name : String) : Int32\n def self.get(dynamic_table : DynamicTable, global_index : Int32) : HeaderField | ::Nil\nend\n\nimpl Error for HpackError\n def message : String\nend\n" }, + { + "name": "std/hpke", + "path": "src/std/hpke.iyi", + "sourceSha256": "5435d94d48664066af94dd296f2ca5dad22025223f78530a40001a6863f66ccc", + "surface": "module std/hpke\n\n# The AEADs, by their code points (section 7.3, table 5).\npub enum HpkeAead : Int32\n Aes128Gcm = 1\n Aes256Gcm = 2\n ChaCha20Poly1305 = 3\n ExportOnly = 65535\n # Nk: the bytes of a key; none for export-only.\n def key_size : Int32\n def name : String\n # Nn: the bytes of a nonce; none for export-only.\n def nonce_size : Int32\n # Nt: the bytes of a tag; none for export-only.\n def tag_size : Int32\nend\n\n# What is wrong with an encapsulation, a key, a ciphertext or a suite from\n# outside, or a context out of nonces: `message` is the sentence.\npub struct HpkeError\n def initialize(message : String)\n def to_s : String\nend\n\n# The KDFs, by their code points (section 7.2, table 3).\npub enum HpkeKdf : Int32\n HkdfSha256 = 1\n HkdfSha384 = 2\n HkdfSha512 = 3\n # HKDF-Expand (RFC 5869 section 2.3) of *prk* to *length* bytes.\n def expand(prk : Array(UInt8), info : Array(UInt8), length : Int32) : Array(UInt8)\n # Nh: the bytes of the hash's output.\n def hash_size : Int32\n # HMAC under the hash, keyed with *key*.\n def mac(key : String | Array(UInt8)) : HMAC\n def name : String\nend\n\n# The KEMs, by their code points (section 7.1, table 2).\npub enum HpkeKem : Int32\n P256Sha256 = 16\n P384Sha384 = 17\n X25519Sha256 = 32\n # Decap, or AuthDecap when *sender_public* is given (section 4.1): the\n # shared secret *enc* carries to *key*, or an `HpkeError` for an *enc*\n # of the wrong length, not a point of the curve, or of small order.\n def decapsulate(key : HpkePrivateKey, enc : String | Array(UInt8), sender_public : HpkePublicKey | Nil = nil) : Array(UInt8) | HpkeError\n # Encap, or AuthEncap when *sender_key* is given (section 4.1): the\n # encapsulation, enc, and the shared secret, from the ephemeral key\n # DeriveKeyPair makes of *randomness* (at least Nsk bytes). An\n # `HpkeError` when a Diffie-Hellman step is refused: *recipient* is an\n # X25519 key of small order.\n def encapsulate(recipient : HpkePublicKey, randomness : String | Array(UInt8), sender_key : HpkePrivateKey | Nil = nil) : ::Tuple(Array(UInt8), Array(UInt8)) | HpkeError\n # The KDF the KEM's own steps use (section 4.1), whatever the suite's.\n def kdf : HpkeKdf\n # RFC 9180's name for it.\n def name : String\n # Nsk: the bytes of a serialised private key.\n def private_key_size : Int32\n # Nenc and Npk, which are the same: a serialised public key.\n def public_key_size : Int32\n # Nsecret: the bytes of a shared secret, the KEM's hash's.\n def secret_size : Int32\nend\n\n# The four modes of section 5, by the byte the key schedule binds.\npub enum HpkeMode : Int32\n Base = 0\n Psk = 1\n Auth = 2\n AuthPsk = 3\nend\n\n# A KEM's private key and the public key that goes with it.\npub struct HpkePrivateKey\n # DH(sk, pk) of section 4.1 with the serialised public key *peer*: the\n # X25519 secret, or the x-coordinate of the product on P-256 and P-384.\n # An `HpkeError` for a peer of the wrong length, a point not on the\n # curve, or an all-zero X25519 secret (section 7.1.4).\n def agree(peer : String | Array(UInt8)) : Array(UInt8) | HpkeError\n # SerializePrivateKey: the 32 bytes of an X25519 key, clamped; the\n # scalar of a P-256 or P-384 key, 32 or 48 bytes big-endian.\n def bytes : Array(UInt8)\n # DeriveKeyPair (section 7.1.3): the key pair of *ikm*, at least Nsk\n # bytes, under the KEM's own KDF. For P-256 and P-384, candidates are\n # drawn until one is in [1, n - 1].\n def self.derive(kem : HpkeKem, ikm : String | Array(UInt8)) : HpkePrivateKey\n # DeserializePrivateKey: the key *bytes* are, or an `HpkeError` for the\n # wrong length or, on P-256 and P-384, a scalar not in [1, n - 1]. An\n # X25519 key is clamped (section 7.1.2).\n def self.from_bytes(kem : HpkeKem, bytes : String | Array(UInt8)) : HpkePrivateKey | HpkeError\n def initialize(kem : HpkeKem, public_key : HpkePublicKey, secret : Array(UInt8), curve : P256PrivateKey | P384PrivateKey | Nil)\n def kem : HpkeKem\n def public_key : HpkePublicKey\nend\n\n# A KEM's public key, serialised as section 7.1.1 has it.\npub struct HpkePublicKey\n # SerializePublicKey: 32 bytes for X25519, the uncompressed point for\n # P-256 (65) and P-384 (97).\n def bytes : Array(UInt8)\n # DeserializePublicKey, validated: the key *bytes* are, or an\n # `HpkeError` for the wrong length or a point not on the curve.\n def self.from_bytes(kem : HpkeKem, bytes : String | Array(UInt8)) : HpkePublicKey | HpkeError\n def initialize(kem : HpkeKem, bytes : Array(UInt8))\n def kem : HpkeKem\nend\n\n# The recipient's context (section 5.2): messages opened at sequence 0, 1,\n# 2 and on, and secrets exported, the same ones the sender's exports.\npub class HpkeRecipient\n # Skips the sequence forward to *sequence*, never back: a message lost\n # in between is not opened.\n def advance_to(sequence : UInt128) : Nil\n # Context.Export (section 5.3).\n def export(exporter_context : String | Array(UInt8), length : Int32) : Array(UInt8)\n def initialize(cipher : HpkeCipher)\n # The mode the context was set up in, by the inputs given.\n def mode : HpkeMode\n # ContextR.Open: the plaintext of *ciphertext* at the next nonce, or an\n # `HpkeError` - its tag does not match, or the sequence is at 2^96 - 1 -\n # with the sequence left where it was.\n def open(ciphertext : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String | HpkeError\n # The sequence number the next message is opened at.\n def sequence : UInt128\nend\n\n# The sender's context (section 5.2): `enc` for the recipient, then\n# messages sealed at sequence 0, 1, 2 and on, and secrets exported.\npub class HpkeSender\n # Skips the sequence forward to *sequence*, never back, so no nonce is\n # used twice: for a protocol that numbers its messages itself.\n def advance_to(sequence : UInt128) : Nil\n # The encapsulation the recipient sets its context up from.\n def enc : Array(UInt8)\n # Context.Export (section 5.3): *length* bytes, at most 255 hashes,\n # bound to *exporter_context*.\n def export(exporter_context : String | Array(UInt8), length : Int32) : Array(UInt8)\n def initialize(enc : Array(UInt8), cipher : HpkeCipher)\n # The mode the context was set up in, by the inputs given.\n def mode : HpkeMode\n # ContextS.Seal: *plaintext* sealed under the next nonce, *aad*\n # authenticated with it; or an `HpkeError` once the sequence is at\n # 2^96 - 1, with nothing sealed.\n def seal(plaintext : String | Array(UInt8), aad : String | Array(UInt8) = \"\") : String | HpkeError\n # The sequence number the next message is sealed at.\n def sequence : UInt128\nend\n\n# A KEM, a KDF and an AEAD: what the two ends agree on before anything is\n# sealed, and what every function of sections 5 and 6 runs under.\npub struct HpkeSuite\n def aead : HpkeAead\n # The suite named by its three code points, or an `HpkeError` naming the\n # one this module does not have.\n def self.from_ids(kem_id : Int32, kdf_id : Int32, aead_id : Int32) : HpkeSuite | HpkeError\n def initialize(kem : HpkeKem, kdf : HpkeKdf, aead : HpkeAead)\n def kdf : HpkeKdf\n def kem : HpkeKem\n # Single-shot Open (section 6.1): the plaintext, or an `HpkeError`.\n def open(key : HpkePrivateKey, enc : String | Array(UInt8), info : String | Array(UInt8), aad : String | Array(UInt8), ciphertext : String | Array(UInt8), psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_public : HpkePublicKey | Nil = nil) : String | HpkeError\n # Single-shot ReceiveExport (section 6.2): what `send_export`\n # exported, or an `HpkeError` when *enc* is refused.\n def receive_export(key : HpkePrivateKey, enc : String | Array(UInt8), info : String | Array(UInt8), exporter_context : String | Array(UInt8), length : Int32, psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_public : HpkePublicKey | Nil = nil) : Array(UInt8) | HpkeError\n # SetupBaseR, SetupPSKR, SetupAuthR or SetupAuthPSKR (section 5.1): the\n # recipient's context for *enc*, or an `HpkeError` when *enc* is refused.\n # The mode is chosen as `sender`'s is, by *sender_public* in place of\n # the sender's key.\n def recipient(key : HpkePrivateKey, enc : String | Array(UInt8), info : String | Array(UInt8), psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_public : HpkePublicKey | Nil = nil) : HpkeRecipient | HpkeError\n # Single-shot Seal (section 6.1): enc and *plaintext* sealed, the\n # context's first message.\n def seal(recipient : HpkePublicKey, info : String | Array(UInt8), aad : String | Array(UInt8), plaintext : String | Array(UInt8), randomness : String | Array(UInt8), psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_key : HpkePrivateKey | Nil = nil) : ::Tuple(Array(UInt8), String) | HpkeError\n # Single-shot SendExport (section 6.2): enc and *length* bytes\n # exported under *exporter_context*.\n def send_export(recipient : HpkePublicKey, info : String | Array(UInt8), exporter_context : String | Array(UInt8), length : Int32, randomness : String | Array(UInt8), psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_key : HpkePrivateKey | Nil = nil) : ::Tuple(Array(UInt8), Array(UInt8)) | HpkeError\n # SetupBaseS, SetupPSKS, SetupAuthS or SetupAuthPSKS (section 5.1), by\n # which of *psk* and *psk_id*, and *sender_key*, are given: the sender's\n # context, whose `enc` goes to the recipient. An `HpkeError` when the\n # encapsulation is refused (*recipient* is an X25519 key of small order).\n def sender(recipient : HpkePublicKey, info : String | Array(UInt8), randomness : String | Array(UInt8), psk : String | Array(UInt8) | Nil = nil, psk_id : String | Array(UInt8) | Nil = nil, sender_key : HpkePrivateKey | Nil = nil) : HpkeSender | HpkeError\n def to_s : String\nend\n\nimpl Error for HpkeError\n def message : String\nend\n" + }, { "name": "std/html", "path": "src/std/html.iyi", @@ -256,8 +274,8 @@ { "name": "std/http", "path": "src/std/http.iyi", - "sourceSha256": "6ee9e23312eb79e5d1a46685ec5e928385b6985b36cb038bbd934a56b781245a", - "surface": "module std/http\n\n# A body as it arrives: from a connection, framed by its length, by chunks\n# or by the connection's close, or from a string already in hand. `read`\n# answers the next piece, \"\" once the body is done; what the other end\n# does wrong is an `HTTPError`, and a connection that fails or is\n# cancelled answers that. The first failure is kept: every read after it\n# answers the same. A body coded `gzip`, `deflate` or `zstd` is decoded\n# as it is read when the client asked for that.\n#\n# A body has a limit: past it a read answers an `HTTPError` with status\n# 413. The server's is `Server::MAX_BODY`; a client's is none.\npub class BodyReader\n # The body's coding undone from here on: `Envelope::Gzip` for gzip,\n # `Envelope::Zlib` for HTTP's deflate - which some servers send as the\n # bare stream instead, without zlib's two bytes of header, so the first\n # two bytes are looked at first, and a pair that is not a zlib header\n # (RFC 1950 §2.2: method 8, a multiple of 31) is read as the bare\n # stream. A body that does not decode is an `HTTPError` naming the\n # coding and why.\n def decode(envelope : Envelope) : Nil\n # The body's `zstd` coding (RFC 8878) undone from here on, by std/zstd's\n # `ZstdReader` as the pieces arrive, frames one after another. A frame\n # that asks for a window past *max_window* bytes is refused rather than\n # given the memory (a window outside 1 KiB to 512 MiB is a panic), and a\n # body that does not decode is an `HTTPError` naming the coding and why.\n def decode_zstd(max_window : Int32 = Zstd::WINDOW_LIMIT) : Nil\n # Reads past the rest of the body, *limit* bytes at most: whether it\n # ended within them. A server reads past what a block left unread, so\n # the next request on the connection starts where it should.\n def drain(limit : Int64) : Bool\n # Whether every byte of the body has been read, the framing's end too.\n def finished? : Bool\n # A body that arrives as an HTTP/2 stream's DATA: refused past *limit*.\n protected def self.frames(h2 : H2Connection, id : Int32, limit : Int64) : BodyReader\n def initialize(conn : Conn | ::Nil, kind : Int32, length : Int64, limit : Int64, text : String = \"\", hello : Proc(Bool) | ::Nil = nil)\n protected def on_frames(h2 : H2Connection, id : Int32) : Nil\n # The body's bytes as the framing delivers them, before any decoding.\n protected def raw(max : Int32) : String | Cancelled | SocketError | HTTPError\n # The next piece of the body, at most *max* bytes; \"\" when it is done.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the body as one string, refused past *limit* bytes (413).\n # The pieces are kept as they arrive and joined once: a builder of the\n # length a head declared was made before any of it had come, and a head\n # that declared 64 MB and sent two bytes cost the server 64 MB.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\n protected def remember(fault : HTTPError | Cancelled | SocketError) : HTTPError | Cancelled | SocketError\n # A body that is a string already in hand.\n def self.text(text : String) : BodyReader\n # Whether the body ends where the connection does: nothing can follow\n # it on the connection.\n def to_close? : Bool\nend\n\n# What a streamed body is written to: an answer's `Response.stream`\n# block's, and a client's upload from an `IyiIO`. Each `write` goes out as\n# one chunk (RFC 9112 §7.1), or as it is where the connection's close ends\n# the body, so a block that writes a line a second is read a line a\n# second. Under a server's `compress`, each write is a gzip piece flushed\n# whole (std/compress's `Deflater`), which decodes as it arrives too. The\n# first failure is kept, and every write after it answers it; `finish`,\n# which the server calls when the block returns, writes the end.\npub class BodyWriter\n # The end of the body: the gzip trailer, and the last chunk with the\n # blank line after it. Once; the server's call after a block that\n # finished its own body writes nothing.\n def finish : Nil | Cancelled | SocketError\n def initialize(socket : IyiSocket | TlsStream | H2Out, chunked : Bool, deflater : Deflater | ::Nil = nil)\n def write(text : String) : Nil | Cancelled | SocketError\n # Bytes of the body written so far, before any framing or coding.\n def written : Int64\nend\n\n# What `HTTP.request`'s `jar:` keeps cookies in: the `Cookie` header a\n# request to a URL carries, and an answer's `Set-Cookie` lines taken in.\n# std/cookie's `CookieJar` is one; std/http itself reads no clock, so the\n# expiry of a cookie is the store's to keep.\npub trait CookieStore\n abstract def header_for(url : String) : String | ::Nil\n abstract def update(url : String, response : Response) : Nil\nend\n\npub struct HTTP\n # For a request target in absolute form (RFC 9112 §3.2.2), where its\n # authority starts: after `http://` or `https://`, in any case.\n def self.absolute_form(target : String) : Int32 | ::Nil\n # Whether a list of codings a client accepts (`Accept-Encoding`, RFC\n # 9110 §12.5.3) takes *coding*: named, or `*`, with a `q` above 0.\n def self.accepts?(list : String, coding : String) : Bool\n # A 1xx, 204 or 304: an answer with no content (RFC 9110 §15).\n def self.bodiless?(status : Int32) : Bool\n # Where a chunked body that starts at *from* ends in `text` — the byte\n # after the last chunk's terminating CRLF (trailers included) — nil when\n # it has not all arrived, or why it is not a chunked body. What\n # `decode_chunked` will then read whole.\n def self.chunked_end(text : String, from : Int32 = 0) : Int32 | String | Nil\n # How much a list of codings a client accepts wants *coding*, as its\n # `q` in thousandths: the weight it is named with, else `*`'s, else 0 -\n # and 0 refuses it.\n def self.coding_weight(list : String, coding : String) : Int32\n # A `Content-Length` as a number, or nil when it is not one. Repeated\n # lines arrive joined, and are one length only when every one says the\n # same (RFC 9112 §6.3). A length past 2147483647 is still a length, too\n # large for either side, and is read as one past it: `to_i?` answered\n # nil for it, and 3000000000 was refused as \"not a number\".\n def self.content_length(value : String) : Int64 | ::Nil\n # A control character other than a tab, or DEL: what a field value may\n # not hold (RFC 9110 §5.5), nor a request target or a reason phrase.\n def self.control?(text : String) : Bool\n # The pairs of a request's `Cookie` header, `a=1; b=2` (RFC 6265 §4.2),\n # a value's quotes taken off; the first of a name is kept, as a server\n # reads the most specific cookie first.\n def self.cookie_pairs(value : String) : Hash(String, String)\n # A chunked body (RFC 9112 §7.1) as one string: each chunk's size line\n # is hexadecimal, extensions after `;` are ignored, and a zero-length\n # chunk ends it. What follows the last chunk is trailers, which nothing\n # here reads.\n def self.decode_chunked(text : String) : String\n def self.delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def self.find_headers_end(text : String, from : Int32 = 0) : Int32 | ::Nil\n # The request as it goes on the wire, whole. `Host`, `Connection` and\n # `Content-Length` are the request's own lines; a caller's header of\n # that name is refused rather than sent twice. A body's length is\n # always written for the methods a body belongs to, so `POST` with\n # nothing to say still says `Content-Length: 0`. A byte of the path past\n # 127 goes as `%` and two hex digits: a request target is ASCII (RFC\n # 9112 §3.2, RFC 3986 §2.1), and `/ü` went out as its UTF-8 bytes, raw.\n def self.format_request(method : String, path : String, host : String, body : String = \"\", headers : Hash(String, String) = {} of String => String) : String\n def self.get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # *text* in gzip, whole: an answer's body under a server's `compress`.\n def self.gzip(text : String) : String\n # A writer whose body goes out in gzip as it is written.\n def self.gzip_writer(socket : IyiSocket | TlsStream | H2Out, chunked : Bool) : BodyWriter\n def self.has_break?(text : String) : Bool\n # Whether a list of tokens (RFC 9110 §5.6.1), its repeated lines joined\n # with \", \", holds *token*, in any case.\n def self.has_token?(list : String, token : String) : Bool\n def self.head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The value of a chunk size's hex digits, nil for anything else or for\n # one past what an Int32 holds.\n def self.hex(text : String) : Int32 | ::Nil\n # *n* in lowercase hexadecimal: a chunk's size line.\n def self.hex_text(n : Int64) : String\n # A Host value - the `a` of `Host: a` - or an absolute-form target's\n # authority, made of what a host and its port are spelled with (RFC 3986\n # §3.2.2, §3.2.3): a name, an IPv4 address or an IPv6 one in brackets,\n # and digits after a colon. Empty is one too, for a target with no\n # authority (RFC 9112 §3.2). A blank, a `/`, an `@` - the bytes another\n # host or a path begins with - are not.\n def self.host?(text : String) : Bool\n def initialize\n # One request, and the block's answer to the response, its body still\n # arriving: `response.body_io.read` takes it a piece at a time, and\n # `response.body` all of it. The connection closes when the block\n # returns. A body of *body* is a string, sent with its length, or an\n # `IyiIO`, sent chunked as it is read - which a redirect that keeps the\n # method (307, 308) cannot send again, so that answer is handed to the\n # block as it is.\n #\n # The redirects followed are RFC 9110 §15.4's: 301 and 302 make a POST a\n # GET, 303 makes anything but a HEAD a GET, and 307 and 308 keep the\n # method and the body. `Authorization`, `Proxy-Authorization` and\n # `Cookie` written by the caller are not sent to another host. One more\n # than *redirects* is an `HTTPError`; with 0 the first answer is the\n # block's.\n #\n # *timeout_ms* bounds the connect, and each read and write after it -\n # a pause, not the whole exchange, as Python's `timeout` is: a body that\n # keeps arriving is read for as long as it takes. One that runs out is a\n # `SocketError` whose `timed_out?` is true. 0, the default, waits as\n # long as the server does, as Go's and Python's clients do; a client\n # that talks to servers it does not control should give one.\n #\n # *proxy* is a proxy's URL - `http://user:password@host:port`, or\n # `host:port` alone - and every request goes through it, every redirect\n # too: `http://` as an absolute-form request to the proxy (RFC 9112\n # §3.2.2), which carries `Proxy-Authorization: Basic` when the URL has\n # a user; `https://` through a tunnel the proxy opens with `CONNECT\n # host:port` (RFC 9110 §9.3.6), the credential on the CONNECT alone and\n # TLS to the server inside it, its certificate checked as without a\n # proxy. A caller's own `Proxy-Authorization` is the proxy's too: sent\n # on the CONNECT when the URL has no user, and never inside the tunnel.\n # A CONNECT answered with anything but a 2xx - 407 when the proxy wants\n # credentials, 403, 502 - is an `HTTPError` naming the proxy and its\n # answer. `http://` through a proxy is HTTP/1.1 whatever\n # *prior_knowledge* says. Which proxy, if any, a URL's environment\n # names is `proxy_for`'s, and std/http_proxy's `HTTPProxy.from_env`.\n def self.open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, client : HTTPClient | ::Nil = nil, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n # The field lines after the first, or why they are not fields. A name\n # repeated, in any case, is one field, its values joined in order with\n # \", \" (RFC 9110 §5.3): into a Hash by name, a second line spelled the\n # same replaced the first and one spelled otherwise was an entry\n # `header` never reached, so two different `Content-Length`s were read\n # as whichever came first or last. A value with a control character in\n # it - a CR or LF that ends no line, a NUL - is refused (§5.5) rather than\n # handed to whoever reads it or echoes it into a header.\n #\n # *unfold*, for an answer: a line that begins with a blank continues the\n # field above it (obs-fold, RFC 9112 §5.2), and a user agent joins it on\n # with one space; read as a field of its own, `HTTP.get` panicked with\n # \"header without a colon: \\\" b\\\"\". A server may refuse one, and this\n # one does.\n #\n # *apart*, when given, is handed each repeated field's values by the\n # name in the answer, a line each, before they are joined.\n def self.parse_fields(lines : Array(String), unfold : Bool = false, apart : Hash(String, Array(String)) | ::Nil = nil) : Hash(String, String) | String\n # The response as it came off the wire, whole. `head` says the request\n # was a `HEAD`, whose answer carries the headers of a body it does not\n # send. An interim answer - `100 Continue`, `103 Early Hints` - is read\n # past to the final one after it (RFC 9110 §15.2). A `101` switches\n # protocols, and HTTP ends with it. What is not a response panics with\n # why: this is the text a program has in hand; the client answers the\n # same sentences as `HTTPError`s.\n def self.parse_response(text : String, head : Bool = false) : Response\n # A response's head - the text before its blank line - read: status,\n # reason, the fields, and the lines of those on more than one. A line\n # that begins with a blank continues the field above it (obs-fold, RFC\n # 9112 §5.2), which a user agent joins on with one space.\n def self.parse_response_head(text : String) : ::Tuple(Int32, String, Hash(String, String), Hash(String, Array(String)) | ::Nil) | HTTPError\n # Where *text* goes on at *from* past the empty lines there: what a\n # server skips before a request line (RFC 9112 §2.2).\n def self.past_empty_lines(text : String, from : Int32) : Int32\n def self.post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The proxy a request for *url* goes through, given the values of\n # `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` - nil when it goes\n # direct. Go's `golang.org/x/net/http/httpproxy` is the rule, line for\n # line, as its documentation and tests state it:\n #\n # * `https://` takes *https_proxy*, `http://` *http_proxy*, any other\n # scheme none. A proxy written without a scheme is `http://`'s, and is\n # answered with it, ready for `open`'s *proxy*.\n # * `localhost` (in lower case: Go's comparison) and a loopback address\n # (127.0.0.0/8, ::1, the first as IPv4-mapped too) never go through a\n # proxy.\n # * *no_proxy* is a comma-separated list, each entry trimmed and read\n # without regard to case: `*` alone sends everything direct; an IP\n # address or a CIDR range (`10.0.0.0/8`, `2001:db8::/32`) matches the\n # addresses in it, IPv4 and its IPv4-mapped form alike, and a CIDR\n # range of one family never the other's; a domain matches itself and\n # every name under it (`foo.com`: `foo.com`, `bar.foo.com`, not\n # `barfoo.com`); with a leading dot, or `*.`, only the names under it\n # (`.y.com`: `x.y.com`, not `y.com`). An address or domain may carry\n # a port (`1.2.3.4:80`, `[::1]:443`, `foo.com:8080`), and matches\n # then on that port alone, the URL's own or its scheme's default.\n # An entry that is none of these (`:80`) is passed over.\n # * A host is compared as IDNA's ASCII form, as Go compares it.\n #\n # Pure: the values are the caller's, so std/http reaches no environment;\n # std/http_proxy reads them from it.\n def self.proxy_for(url : String, http_proxy : String | ::Nil, https_proxy : String | ::Nil, no_proxy : String | ::Nil = nil) : String | ::Nil\n def self.put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The reason phrase a status carries (RFC 9110 §15), for a response the\n # server writes; a status this table does not know gets its class's.\n def self.reason_for(status : Int32) : String\n # One request and its whole answer: the answer as `open` hands it, its\n # body read. Redirects are followed, *redirects* at most; *jar* keeps\n # the cookies the answers set and sends them back; *decompress* asks\n # for gzip, deflate and zstd and undoes them. A refused or reset\n # connection answers `SocketError`; a task cancelled answers\n # `Cancelled`; what the server does wrong answers `HTTPError`. `!`\n # passes any of them up, `.or_panic` refuses it.\n def self.request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Response | Cancelled | SocketError | HTTPError\n # A request's head: the request line, `Host`, `Connection` (*connection*:\n # `close`, or `Upgrade`), the caller's headers, and the body's framing -\n # *length*, chunks, or none.\n def self.request_head(method : String, path : String, host : String, headers : Hash(String, String), length : Int64 | ::Nil, chunked : Bool, connection : String) : String\n # Two field names for the same field: ASCII case is the only difference\n # a name may carry.\n def self.same_name?(a : String, b : String) : Bool\n def self.split_crlf(text : String) : Array(String)\n # A status line read: the status and its reason, or why it is not one.\n def self.status_line(line : String) : ::Tuple(Int32, String) | HTTPError\n # What an `https://` request is checked against and offers: the\n # system's roots, and ALPN's `http/1.1`, unless a program sets its own -\n # roots of its own for a server it runs, say.\n def self.tls_config : TlsConfig\n def self.tls_config=(config : TlsConfig) : TlsConfig\n # A token (RFC 9110 §5.6.2): what a method or a field name is made of.\n def self.token?(text : String) : Bool\n # Without the blanks at either end (RFC 9112 §5.1: field values are\n # trimmed of the whitespace around them).\n def self.trim(s : String) : String\n # The 101 an upgrade to *protocol* is answered with, and the connection,\n # which is the caller's from then on: std/websocket's handshake. Any\n # other answer is an `HTTPError` naming it.\n #\n # *proxy* is `open`'s, and so is its credential, on the CONNECT alone;\n # but an upgrade goes through a tunnel for `http://` as for `https://`,\n # the handshake inside it in origin form. `Upgrade` belongs to the\n # connection it is sent on (RFC 9110 §7.8), and a proxy forwarding a\n # request removes `Connection` and every field it names (§7.6.1), so a\n # handshake handed to a forward proxy in absolute form reaches the\n # server, at best, as a plain GET: the gate's proxy, which drops\n # `Connection`, leaves std/websocket's server a request it answers 400.\n def self.upgrade(url : String, protocol : String, headers : Hash(String, String) = {} of String => String, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Upgraded | Cancelled | SocketError | HTTPError\nend\n\n# Requests that keep their connections: an `HTTPClient` holds the connections\n# its answers left open, by host and port, and the next request to that\n# server goes over one of them instead of a new one - the TCP handshake,\n# and the slow start after it, paid once rather than per request. Its\n# methods are `HTTP`'s, redirects, cookies and decoding included:\n#\n# ```\n# client = HTTPClient.new\n# client.get(\"http://127.0.0.1:8080/a\")\n# client.get(\"http://127.0.0.1:8080/b\") # the same connection\n# client.close\n# ```\n#\n# A connection goes back once its answer has been read to the end (inside\n# `open`, up to 64 KiB left unread is read past for it) and the answer\n# allows it; at most *idle* wait per server, and the rest close. A server\n# may close an idle connection whenever it likes (RFC 9112 §9.3.1), so a\n# request whose kept connection turns out closed - the write fails, or it\n# ends before a byte of an answer - is sent again once on a new one, when\n# its method is idempotent (RFC 9110 §9.2.2) and its body a string;\n# anything else answers the failure. One task at a time per request: two\n# tasks sending at once take two connections, and a client is not for\n# sharing across threads.\n#\n# Over HTTP/2 - `https://` where ALPN chose `h2`, or `http://` with\n# *prior_knowledge* (h2c, RFC 9113 §3.3) - a kept connection carries the\n# next request on a new stream, until the server says GOAWAY.\npub class HTTPClient\n # Every idle connection closed. The client may be used after; it opens\n # new ones.\n def close : Nil\n # Connections this client has opened, and requests sent over one it\n # kept: what a program reads to see that its connections are kept.\n def connections_made : Int32\n def connections_reused : Int32\n def delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n protected def give_back(key : String, conn : Conn) : Nil\n def head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # *timeout_ms* and *proxy* are `HTTP.open`'s, for every request the\n # client sends.\n def initialize(idle : Int32 = 4, timeout_ms : Int32 = 0, prior_knowledge : Bool = false, proxy : String | ::Nil = nil)\n protected def made : Nil\n def open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n def post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # `http://` spoken as HTTP/2 from the first byte, without asking: for a\n # server known to take it.\n def prior_knowledge : Bool\n # The proxy every request goes through, `HTTP.open`'s *proxy*; its\n # connections are kept under it.\n def proxy : String | ::Nil\n def put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true) : Response | Cancelled | SocketError | HTTPError\n # A kept connection to *key*, nil when none waits. An HTTP/2 connection\n # that can take no more requests is closed rather than handed out.\n protected def take(key : String) : Conn | ::Nil\nend\n\n# What the other end did that HTTP does not allow, as a value: `reason` is\n# the sentence, `status` the answer a server gives a request that did it\n# (400, or 413 and 431 for the limits). `message` says it is HTTP's.\npub struct HTTPError\n def initialize(reason : String, status : Int32 = 400)\n def reason : String\n def status : Int32\n def to_s : String\nend\n\n# A `multipart/form-data` body (RFC 7578) read a part at a time, as it\n# arrives: `next_part` answers the next `Part`, nil after the last, and a\n# part's `read` answers its bytes up to the boundary, \"\" at its end. A part\n# not read to its end is read past by the next `next_part`. The body of a\n# request is `Request#multipart`'s; any `BodyReader` and boundary will do.\n# A part's head is refused past 16 KiB, and a body that ends before its\n# closing boundary is an `HTTPError`.\npub class Multipart\n # The boundary a `Content-Type` of `multipart/...` names, nil when it\n # names none.\n def self.boundary(content_type : String) : String | ::Nil\n # Where *pattern* is in what is held, -1 when it is not there yet.\n protected def find(pattern : String) : Int32\n def initialize(body : BodyReader, boundary : String)\n # The next part, nil after the last.\n def next_part : Part | Nil | Cancelled | SocketError | HTTPError\n # Up to *max* bytes of the part being read, \"\" at its boundary. What is\n # held back is what may yet be the start of the boundary.\n protected def part_read(max : Int32) : String | Cancelled | SocketError | HTTPError\n # One more piece of the body onto what is held: false at its end.\n protected def pull : Bool | Cancelled | SocketError | HTTPError\nend\n\n# A multipart/form-data body to send: fields and files in order. Its\n# boundary is chosen when the body is first asked for - `finish`,\n# `content_type` or `boundary`, whichever comes first, after which no\n# part is added - as `iyi-` and 32 hex digits of the parts' SHA-1, made\n# again from itself in the rare case a part holds it: so it is none of\n# the parts' bytes by construction rather than by chance, the same\n# fields make the same body, and std/http reaches no source of\n# randomness for it. A boundary given to `new` is used as it is, and a\n# part that holds it is refused.\npub class MultipartBuilder\n def boundary : String\n def content_type : String\n def field(name : String, value : String) : self\n def file(name : String, filename : String, data : String, content_type : String = MIME.from_extension(filename)) : self\n # The body, closing boundary and all.\n def finish : String\n # The boundary ends a part where it follows a CRLF, the part's first\n # bytes included, which follow the head's blank line.\n protected def self.holds?(data : String, mark : String) : Bool\n def initialize(boundary : String | ::Nil = nil)\n # A name inside quotes, as browsers write one: a quote and a line break\n # percent-encoded (the WHATWG form encoding), so no field can end the\n # parameter or the line early.\n protected def self.quoted(text : String) : String\nend\n\n# One part of a multipart body: its head's fields, its name and file name\n# from `Content-Disposition`, and its bytes, read up to its boundary.\npub class Part\n def content_type : String | ::Nil\n # The file name a browser sent the part under, `filename=`.\n def filename : String | ::Nil\n def finished? : Bool\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n def initialize(owner : Multipart, headers : Hash(String, String))\n # The form field's name, `name=` of `Content-Disposition`.\n def name : String | ::Nil\n # The part's next bytes, at most *max*; \"\" at its end.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the part as one string, refused past *limit* bytes.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\nend\n\n# What a server's block is handed: one request. Its body is `body`, read\n# whole before the block ran, or under `stream_bodies` read by the block\n# from `body_io` as it arrives; `body` then reads the rest itself, and a\n# body that cannot be read - the client went, or sent one that is not\n# HTTP - ends the connection with that sentence rather than be answered as\n# if it were whole.\npub class Request\n def body : String\n def body_io : BodyReader\n # The pairs of the request's `Cookie` header, the first of a name kept.\n def cookies : Hash(String, String)\n # The fields of a form: the query's, and an\n # `application/x-www-form-urlencoded` body's after them. The body is\n # read for it.\n def form : Params\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n # A request whose body is in hand: what `parse_request` answers.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), body : String, version : String = \"HTTP/1.1\", remote_address : String | ::Nil = nil)\n # A request whose body is still on the connection.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), version : String, reader : BodyReader, remote_address : String | ::Nil = nil)\n def method : String\n # The body as `multipart/form-data` parts, read as they arrive; nil when\n # the `Content-Type` is not multipart or names no boundary.\n def multipart : Multipart | ::Nil\n def path : String\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates : Array(Certificate)\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates=(value : Array(Certificate)) : Array(Certificate)\n def query : String | ::Nil\n # The client's address, as the server's socket saw it (no port): nil\n # for a request a program made, or when the client had already gone. A\n # proxy in front is what this names; `X-Forwarded-For` is the proxy's\n # word, and a header any client can write.\n def remote_address : String | ::Nil\n def version : String\nend\n\n# A response: what a server's block answers, and what a client is handed.\n#\n# To write one: `Response.new(200, \"hello\")`, a status and a string;\n# `Response.new(200, io)`, a stream the server copies out and closes, with\n# the caller's `Content-Length` when it gives one and chunked otherwise;\n# `Response.stream(200) { |w| w.write(\"...\") }`, whose block writes as it\n# goes; `Response.upgrade(headers) { |socket, leftover| ... }`, a 101 after\n# which the connection is the block's. `add_header` writes a field on a\n# line of its own, as each `Set-Cookie` must be.\n#\n# What a client is handed: the status, reason and headers, and the body -\n# `body` whole, or `body_io` as it arrives inside `HTTP.open`'s block.\npub class Response\n # One more line of the field *name*, written on a line of its own: what\n # two `Set-Cookie`s need, since the \", \" a field's lines are joined with\n # is part of a cookie's date.\n def add_header(name : String, value : String) : Nil\n # The lines of each field written on more than one, by its name in\n # `headers`; nil when every field is on one line.\n def apart : Hash(String, Array(String)) | ::Nil\n # The body: whole. Inside `HTTP.open`'s block it is read here, the first\n # time it is asked for, and a body that cannot be read panics with why;\n # `body_io` answers that as a value.\n def body : String\n # The body as it arrives. For a response a program made, its string.\n def body_io : BodyReader\n # Whether the client undid the body's `gzip`, `deflate` or `zstd`\n # coding; the `Content-Encoding` and `Content-Length` that described it\n # are gone from the headers then.\n def decoded? : Bool\n # A header by name, as the server spelled it or otherwise: a field\n # name is case-insensitive (RFC 9110 §5.1), and `content-length` is as\n # common on the wire as `Content-Length`.\n def header(name : String) : String | ::Nil\n # A field's lines apart, in the order they came: `header` joins them\n # with \", \", and `Set-Cookie` is the field that join breaks (RFC 9110\n # §5.3), since a cookie's `Expires` date has a comma in it - two cookies\n # read as \"a=1; Expires=Wed, 21 Oct 2026 07:28:00 GMT, b=2\", which no\n # split takes back apart. Empty for a field that is not there.\n def header_values(name : String) : Array(String)\n def headers : Hash(String, String)\n # A response a client read: its body comes from *reader*, and *url* is\n # where it came from.\n def initialize(status : Int32, reason : String, headers : Hash(String, String), apart : Hash(String, Array(String)) | ::Nil, reader : BodyReader, url : String, decoded : Bool)\n def initialize(status : Int32, reason : String, headers : Hash(String, String), body : String, apart : Hash(String, Array(String)) | ::Nil = nil)\n # A response to write: the reason is the status's own, the headers the\n # caller's. `Response.new(200, \"hello\")` is the common shape.\n def initialize(status : Int32, body : String = \"\", headers : Hash(String, String) = {} of String => String)\n # A response whose body is *source*, copied out by the server and\n # closed after.\n def initialize(status : Int32, source : IyiIO, headers : Hash(String, String) = {} of String => String)\n # The body read whole into the response, which is answered; why not,\n # when it cannot be.\n def load : Response | Cancelled | SocketError | HTTPError\n def reason : String\n def source : IyiIO | ::Nil\n def status : Int32\n # A response whose body the block writes as it goes, chunked.\n def self.stream(status : Int32, headers : Hash(String, String) = {} of String => String, &writer : (BodyWriter -> Nil)) : Response\n def success? : Bool\n def takeover : Proc(IyiSocket | TlsStream, String, Nil) | ::Nil\n protected def takeover=(takeover : Proc(IyiSocket | TlsStream, String, Nil)) : Proc(IyiSocket | TlsStream, String, Nil)\n # A 101 Switching Protocols with *headers* (`Upgrade` and the\n # protocol's own), after which the connection is the block's: it is\n # handed the connection - the socket, or under `serve(tls:)` the\n # `TlsStream` over it - and the bytes the client sent after its request,\n # and the connection closes when it returns.\n def self.upgrade(headers : Hash(String, String), &takeover : (IyiSocket | TlsStream, String -> Nil)) : Response\n # The URL the answer came from, after any redirects; nil for one a\n # program made.\n def url : String | ::Nil\n def writer : Proc(BodyWriter, Nil) | ::Nil\n protected def writer=(writer : Proc(BodyWriter, Nil)) : Proc(BodyWriter, Nil)\nend\n\npub struct Server\n # The headers a coded body goes with: the coding, and `Vary` so that a\n # cache keeps the answers apart. A `Content-Length` the block gave is\n # the plain body's, and goes.\n protected def self.coded(response : Response, coding : String) : Response\n # The coding *response* goes in to the client of *req*, or nil: one the\n # client takes (RFC 9110 §12.5.3, a `q` of 0 refusing it), for an\n # answer with a body and a type worth compressing, and no coding of its\n # own. A whole body goes `zstd` when the client's `q` for it is at least\n # its `q` for gzip, and `gzip` otherwise. The tie goes to zstd for the\n # server's time: measured on this machine, release builds, on\n # src/std/http.iyi (199,134 bytes) `Gzip.compress` wrote 54,484 bytes in\n # 5 ms and `Zstd.compress` 61,753 in 1 ms, and on CHANGELOG.md (1.5 MB)\n # 552,446 in 58 ms against 589,785 in 13 ms - a quarter of the time, for\n # an eighth more bytes at most. A stream - an `IyiIO` or a\n # `Response.stream` - goes gzip or as it is: std/compress's `Deflater`\n # writes each piece as it comes, where std/zstd's compressor takes its\n # input whole, and a frame a write would code each piece alone.\n def self.coding_for(req : Request, response : Response) : String | ::Nil\n # A string body compressed whole in *coding*, `zstd` or `gzip`, with\n # the headers that say so. `Zstd.compress`'s frame needs a window of\n # 1 MiB at most, inside the 8 MB RFC 9659 §3 holds an encoder to.\n protected def self.compressed(response : Response, coding : String) : Response\n # A response's head. `Content-Length` is *length*'s - the caller's own\n # line is checked against it, and kept only where no body follows it: a\n # HEAD, or a 304 that names the length a 200 would have (RFC 9110 §8.6) -\n # or `Transfer-Encoding: chunked` when *chunked*, or neither for a body\n # the connection's close ends. `Connection` says what the connection will\n # do next, except on an *upgrade*, whose own line says it. A 1xx, 204 or\n # 304 has no content (RFC 9110 §15), and writes neither a body nor a\n # length of its own (§8.6): the body was read by the client as the start\n # of the next answer, and `Content-Length: 0` on a 304 told a cache that\n # the resource had become empty. A field added on lines of its own\n # (`add_header`) is written a line each.\n def self.format_head(response : Response, head : Bool, close : Bool, length : Int64 | ::Nil, chunked : Bool, upgrade : Bool = false) : String\n # The response as it goes on the wire, whole: `format_head` for its\n # string body, and the body.\n def self.format_response(response : Response, head : Bool, close : Bool) : String\n # One connection: requests until the client is done. A request that is\n # not HTTP is answered with why and the connection closed, since the\n # bytes after it cannot be trusted to be a request either. Requests that\n # came in one read are read where they lie, one after another. A\n # connection that opens with HTTP/2's preface is served as HTTP/2. Under\n # *tls* the handshake comes first, and the connection ends with\n # close_notify.\n def self.handle(client : IyiSocket, handler : Proc(Request, Response), listener : IyiSocket | ::Nil = nil, idle : Hash(UInt64, IyiSocket) | ::Nil = nil, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 0, tls : TlsServerConfig | ::Nil = nil) : Nil\n def initialize\n # A request's head - the text before its blank line - read and checked,\n # or why it is not one.\n def self.parse_head(text : String) : RequestHead | HTTPError\n # A request at *from* in `text`, if a whole one is there. No request and\n # no error means \"not yet\". `consumed` counts from the start of `text`.\n def self.parse_request(text : String, from : Int32 = 0) : ParsedRequest\n # Serve `listener` until it is closed. The block runs on the\n # connection's fiber, once per request; what it answers is written back.\n # The close ends a connection waiting on its client too, for its next\n # request or for the rest of one, and one that is answering ends when its\n # answer is written: a keep-alive client that sat idle kept `serve` from\n # returning for as long as it stayed, and so did one that had sent\n # what has arrived of a request so far - one byte of it was enough.\n #\n # *stream_bodies*: the block reads each request's body from `body_io`\n # rather than being handed it whole. *compress*: an answer whose type\n # std/mime calls compressible goes zstd or gzip to a client that\n # accepts one (`coding_for`).\n #\n # *timeout_ms*: a request's head must have arrived within it, counted\n # from when the server starts waiting for one - a kept-alive connection's\n # idle time included - and no read of a body nor write of an answer may\n # wait longer; a connection that does is closed. Without it, a client\n # that sends a byte of its head every few seconds (Slowloris) holds a\n # connection and its fiber for as long as it likes, and so does one that\n # reads none of its answer. A minute by default, nginx's\n # `client_header_timeout`; 0 for none. A connection handed over by\n # `Response.upgrade` waits as its protocol likes.\n #\n # *tls*: each connection is TLS 1.3 (std/tls's `TlsServer`) under this\n # configuration, its handshake held to *timeout_ms* too; one whose\n # handshake fails is closed, the client told why by an alert. Inside\n # it a connection that opens with HTTP/2's preface - as a client whose\n # ALPN chose `h2` does, when `tls.alpn` offers it - is served HTTP/2,\n # and any other HTTP/1.1. A `Response.upgrade` block over TLS is handed\n # the `TlsStream`, so a WebSocket is `wss://`.\n def self.serve(listener : IyiSocket, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : TlsServerConfig | ::Nil = nil, &handler : (Request -> Response)) : Nil\n protected def self.writer_for(socket : IyiSocket | TlsStream, chunked : Bool, gzip : Bool) : BodyWriter\nend\n\n# A TLS connection used as a socket is: read into a buffer, written to,\n# closed, its timeouts set - by a `Conn`, a `BodyWriter`, and a connection\n# handed over by an upgrade (`wss://` at either end). What TLS refuses is a\n# `SocketError` carrying TLS's own sentence (`layer_reason`), so answers\n# keep the types they have over a plain socket.\npub class TlsStream\n def close : Nil\n def closed? : Bool\n def initialize(tls : TlsConnection, host : String)\n # The peer's certificate chain, leaf first, as the handshake checked it:\n # the server's, at a client; the client's, at a server that asked.\n def peer_certificates : Array(Certificate)\n # Up to *count* bytes into *buffer*: 0 once the peer closed with\n # close_notify.\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n # Up to *max_bytes* as a string, what is held first: \"\" once the peer\n # closed with close_notify. `IyiSocket#read`'s form, for a block an\n # upgrade hands either.\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n def write(text : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What `HTTP.upgrade` answers: the 101, and the connection, which is the\n# caller's now - the socket, and the bytes the server sent after its head.\npub class Upgraded\n def initialize(response : Response, socket : IyiSocket | TlsStream, leftover : String)\n def leftover : String\n def response : Response\n # The connection: the socket, or for `https://` the `TlsStream` over it.\n def socket : IyiSocket | TlsStream\nend\n\nimpl Error for HTTPError\n def message : String\nend\n" + "sourceSha256": "c834c001d26e1a45a8e7163e78255438004bb09caad82542270e011ef7dc82c3", + "surface": "module std/http\n\n# A body as it arrives: from a connection, framed by its length, by chunks\n# or by the connection's close, or from a string already in hand. `read`\n# answers the next piece, \"\" once the body is done; what the other end\n# does wrong is an `HTTPError`, and a connection that fails or is\n# cancelled answers that. The first failure is kept: every read after it\n# answers the same. A body coded `gzip`, `deflate` or `zstd` is decoded\n# as it is read when the client asked for that.\n#\n# A body has a limit: past it a read answers an `HTTPError` with status\n# 413. The server's is `Server::MAX_BODY`; a client's is none.\npub class BodyReader\n # The body's coding undone from here on: `Envelope::Gzip` for gzip,\n # `Envelope::Zlib` for HTTP's deflate - which some servers send as the\n # bare stream instead, without zlib's two bytes of header, so the first\n # two bytes are looked at first, and a pair that is not a zlib header\n # (RFC 1950 §2.2: method 8, a multiple of 31) is read as the bare\n # stream. A body that does not decode is an `HTTPError` naming the\n # coding and why.\n def decode(envelope : Envelope) : Nil\n # The body's `zstd` coding (RFC 8878) undone from here on, by std/zstd's\n # `ZstdReader` as the pieces arrive, frames one after another. A frame\n # that asks for a window past *max_window* bytes is refused rather than\n # given the memory (a window outside 1 KiB to 512 MiB is a panic), and a\n # body that does not decode is an `HTTPError` naming the coding and why.\n def decode_zstd(max_window : Int32 = Zstd::WINDOW_LIMIT) : Nil\n # Reads past the rest of the body, *limit* bytes at most: whether it\n # ended within them. A server reads past what a block left unread, so\n # the next request on the connection starts where it should.\n def drain(limit : Int64) : Bool\n # Whether every byte of the body has been read, the framing's end too.\n def finished? : Bool\n # A body that arrives as an HTTP/2 stream's DATA: refused past *limit*.\n protected def self.frames(h2 : H2Connection, id : Int32, limit : Int64) : BodyReader\n def initialize(conn : Conn | ::Nil, kind : Int32, length : Int64, limit : Int64, text : String = \"\", hello : Proc(Bool) | ::Nil = nil)\n protected def on_frames(h2 : H2Connection, id : Int32) : Nil\n # The body's bytes as the framing delivers them, before any decoding.\n protected def raw(max : Int32) : String | Cancelled | SocketError | HTTPError\n # The next piece of the body, at most *max* bytes; \"\" when it is done.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the body as one string, refused past *limit* bytes (413).\n # The pieces are kept as they arrive and joined once: a builder of the\n # length a head declared was made before any of it had come, and a head\n # that declared 64 MB and sent two bytes cost the server 64 MB.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\n protected def remember(fault : HTTPError | Cancelled | SocketError) : HTTPError | Cancelled | SocketError\n # A body that is a string already in hand.\n def self.text(text : String) : BodyReader\n # Whether the body ends where the connection does: nothing can follow\n # it on the connection.\n def to_close? : Bool\nend\n\n# What a streamed body is written to: an answer's `Response.stream`\n# block's, and a client's upload from an `IyiIO`. Each `write` goes out as\n# one chunk (RFC 9112 §7.1), or as it is where the connection's close ends\n# the body, so a block that writes a line a second is read a line a\n# second. Under a server's `compress`, each write is a gzip piece flushed\n# whole (std/compress's `Deflater`), which decodes as it arrives too. The\n# first failure is kept, and every write after it answers it; `finish`,\n# which the server calls when the block returns, writes the end.\npub class BodyWriter\n # The end of the body: the gzip trailer, and the last chunk with the\n # blank line after it. Once; the server's call after a block that\n # finished its own body writes nothing.\n def finish : Nil | Cancelled | SocketError\n def initialize(socket : IyiSocket | TlsStream | H2Out, chunked : Bool, deflater : Deflater | ::Nil = nil)\n def write(text : String) : Nil | Cancelled | SocketError\n # Bytes of the body written so far, before any framing or coding.\n def written : Int64\nend\n\n# What `HTTP.request`'s `jar:` keeps cookies in: the `Cookie` header a\n# request to a URL carries, and an answer's `Set-Cookie` lines taken in.\n# std/cookie's `CookieJar` is one; std/http itself reads no clock, so the\n# expiry of a cookie is the store's to keep.\npub trait CookieStore\n abstract def header_for(url : String) : String | ::Nil\n abstract def update(url : String, response : Response) : Nil\nend\n\npub struct HTTP\n # For a request target in absolute form (RFC 9112 §3.2.2), where its\n # authority starts: after `http://` or `https://`, in any case.\n def self.absolute_form(target : String) : Int32 | ::Nil\n # Whether a list of codings a client accepts (`Accept-Encoding`, RFC\n # 9110 §12.5.3) takes *coding*: named, or `*`, with a `q` above 0.\n def self.accepts?(list : String, coding : String) : Bool\n # A 1xx, 204 or 304: an answer with no content (RFC 9110 §15).\n def self.bodiless?(status : Int32) : Bool\n # Where a chunked body that starts at *from* ends in `text` — the byte\n # after the last chunk's terminating CRLF (trailers included) — nil when\n # it has not all arrived, or why it is not a chunked body. What\n # `decode_chunked` will then read whole.\n def self.chunked_end(text : String, from : Int32 = 0) : Int32 | String | Nil\n # How much a list of codings a client accepts wants *coding*, as its\n # `q` in thousandths: the weight it is named with, else `*`'s, else 0 -\n # and 0 refuses it.\n def self.coding_weight(list : String, coding : String) : Int32\n # A `Content-Length` as a number, or nil when it is not one. Repeated\n # lines arrive joined, and are one length only when every one says the\n # same (RFC 9112 §6.3). A length past 2147483647 is still a length, too\n # large for either side, and is read as one past it: `to_i?` answered\n # nil for it, and 3000000000 was refused as \"not a number\".\n def self.content_length(value : String) : Int64 | ::Nil\n # A control character other than a tab, or DEL: what a field value may\n # not hold (RFC 9110 §5.5), nor a request target or a reason phrase.\n def self.control?(text : String) : Bool\n # The pairs of a request's `Cookie` header, `a=1; b=2` (RFC 6265 §4.2),\n # a value's quotes taken off; the first of a name is kept, as a server\n # reads the most specific cookie first.\n def self.cookie_pairs(value : String) : Hash(String, String)\n # A chunked body (RFC 9112 §7.1) as one string: each chunk's size line\n # is hexadecimal, extensions after `;` are ignored, and a zero-length\n # chunk ends it. What follows the last chunk is trailers, which nothing\n # here reads.\n def self.decode_chunked(text : String) : String\n def self.delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def self.find_headers_end(text : String, from : Int32 = 0) : Int32 | ::Nil\n # The request as it goes on the wire, whole. `Host`, `Connection` and\n # `Content-Length` are the request's own lines; a caller's header of\n # that name is refused rather than sent twice. A body's length is\n # always written for the methods a body belongs to, so `POST` with\n # nothing to say still says `Content-Length: 0`. A byte of the path past\n # 127 goes as `%` and two hex digits: a request target is ASCII (RFC\n # 9112 §3.2, RFC 3986 §2.1), and `/ü` went out as its UTF-8 bytes, raw.\n def self.format_request(method : String, path : String, host : String, body : String = \"\", headers : Hash(String, String) = {} of String => String) : String\n def self.get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # *text* in gzip, whole: an answer's body under a server's `compress`.\n def self.gzip(text : String) : String\n # A writer whose body goes out in gzip as it is written.\n def self.gzip_writer(socket : IyiSocket | TlsStream | H2Out, chunked : Bool) : BodyWriter\n def self.has_break?(text : String) : Bool\n # Whether a list of tokens (RFC 9110 §5.6.1), its repeated lines joined\n # with \", \", holds *token*, in any case.\n def self.has_token?(list : String, token : String) : Bool\n def self.head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The value of a chunk size's hex digits, nil for anything else or for\n # one past what an Int32 holds.\n def self.hex(text : String) : Int32 | ::Nil\n # *n* in lowercase hexadecimal: a chunk's size line.\n def self.hex_text(n : Int64) : String\n # A Host value - the `a` of `Host: a` - or an absolute-form target's\n # authority, made of what a host and its port are spelled with (RFC 3986\n # §3.2.2, §3.2.3): a name, an IPv4 address or an IPv6 one in brackets,\n # and digits after a colon. Empty is one too, for a target with no\n # authority (RFC 9112 §3.2). A blank, a `/`, an `@` - the bytes another\n # host or a path begins with - are not.\n def self.host?(text : String) : Bool\n # The endpoints *answer*'s ServiceMode records name for a client whose\n # configuration offers *alpn* by ALPN (none: it speaks http/1.1 alone),\n # to an origin on *port*, in the order to try them: a record whose\n # mandatory keys are not all ones std/dns reads, or whose protocols -\n # alpn, and http/1.1 unless no-default-alpn - hold none of the client's,\n # names none (RFC 9460 sections 7.1.2 and 8), and neither does one whose\n # target is not a host name. Each is the record's target, or its owner\n # for \".\"; its port, or *port*; its `ech` where the ECHConfigList holds\n # a config std/tls can use (RFC 9849 section 6.1), nil otherwise; and\n # *alpn* with the protocols the record's alpn lists first, in *alpn*'s\n # order otherwise.\n def self.https_endpoints(answer : SvcbAnswer, alpn : Array(String), port : Int32) : Array(HttpsEndpoint)\n def initialize\n # One request, and the block's answer to the response, its body still\n # arriving: `response.body_io.read` takes it a piece at a time, and\n # `response.body` all of it. The connection closes when the block\n # returns. A body of *body* is a string, sent with its length, or an\n # `IyiIO`, sent chunked as it is read - which a redirect that keeps the\n # method (307, 308) cannot send again, so that answer is handed to the\n # block as it is.\n #\n # The redirects followed are RFC 9110 §15.4's: 301 and 302 make a POST a\n # GET, 303 makes anything but a HEAD a GET, and 307 and 308 keep the\n # method and the body. `Authorization`, `Proxy-Authorization` and\n # `Cookie` written by the caller are not sent to another scheme, host or\n # port - `https://` to `http://` at one host included, as curl does (Go\n # strips them for another host) - and a jar's `Secure` cookies go over\n # `https://` alone. The downgrade itself is followed, as Go's client and\n # `curl -L` follow it; any other header the caller wrote, and a 307 or\n # 308's body, go on in the clear. One more than *redirects* is an\n # `HTTPError`; with 0 the first answer is the block's.\n #\n # *timeout_ms* bounds the connect, and each read and write after it -\n # a pause, not the whole exchange, as Python's `timeout` is: a body that\n # keeps arriving is read for as long as it takes. One that runs out is a\n # `SocketError` whose `timed_out?` is true. 0, the default, waits as\n # long as the server does, as Go's and Python's clients do; a client\n # that talks to servers it does not control should give one.\n #\n # *proxy* is a proxy's URL - `http://user:password@host:port`, or\n # `host:port` alone - and every request goes through it, every redirect\n # too: `http://` as an absolute-form request to the proxy (RFC 9112\n # §3.2.2), which carries `Proxy-Authorization: Basic` when the URL has\n # a user; `https://` through a tunnel the proxy opens with `CONNECT\n # host:port` (RFC 9110 §9.3.6), the credential on the CONNECT alone and\n # TLS to the server inside it, its certificate checked as without a\n # proxy. A caller's own `Proxy-Authorization` is the proxy's too: sent\n # on the CONNECT when the URL has no user, and never inside the tunnel.\n # A CONNECT answered with anything but a 2xx - 407 when the proxy wants\n # credentials, 403, 502 - is an `HTTPError` naming the proxy and its\n # answer. `http://` through a proxy is HTTP/1.1 whatever\n # *prior_knowledge* says. Which proxy, if any, a URL's environment\n # names is `proxy_for`'s, and std/http_proxy's `HTTPProxy.from_env`.\n def self.open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, client : HTTPClient | ::Nil = nil, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n # The field lines after the first, or why they are not fields. A name\n # repeated, in any case, is one field, its values joined in order with\n # \", \" (RFC 9110 §5.3): into a Hash by name, a second line spelled the\n # same replaced the first and one spelled otherwise was an entry\n # `header` never reached, so two different `Content-Length`s were read\n # as whichever came first or last. A value with a control character in\n # it - a CR or LF that ends no line, a NUL - is refused (§5.5) rather than\n # handed to whoever reads it or echoes it into a header.\n #\n # *unfold*, for an answer: a line that begins with a blank continues the\n # field above it (obs-fold, RFC 9112 §5.2), and a user agent joins it on\n # with one space; read as a field of its own, `HTTP.get` panicked with\n # \"header without a colon: \\\" b\\\"\". A server may refuse one, and this\n # one does.\n #\n # *apart*, when given, is handed each repeated field's values by the\n # name in the answer, a line each, before they are joined.\n def self.parse_fields(lines : Array(String), unfold : Bool = false, apart : Hash(String, Array(String)) | ::Nil = nil) : Hash(String, String) | String\n # The response as it came off the wire, whole. `head` says the request\n # was a `HEAD`, whose answer carries the headers of a body it does not\n # send. An interim answer - `100 Continue`, `103 Early Hints` - is read\n # past to the final one after it (RFC 9110 §15.2). A `101` switches\n # protocols, and HTTP ends with it. What is not a response panics with\n # why: this is the text a program has in hand; the client answers the\n # same sentences as `HTTPError`s.\n def self.parse_response(text : String, head : Bool = false) : Response\n # A response's head - the text before its blank line - read: status,\n # reason, the fields, and the lines of those on more than one. A line\n # that begins with a blank continues the field above it (obs-fold, RFC\n # 9112 §5.2), which a user agent joins on with one space.\n def self.parse_response_head(text : String) : ::Tuple(Int32, String, Hash(String, String), Hash(String, Array(String)) | ::Nil) | HTTPError\n # Where *text* goes on at *from* past the empty lines there: what a\n # server skips before a request line (RFC 9112 §2.2).\n def self.past_empty_lines(text : String, from : Int32) : Int32\n def self.post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The proxy a request for *url* goes through, given the values of\n # `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` - nil when it goes\n # direct. Go's `golang.org/x/net/http/httpproxy` is the rule, line for\n # line, as its documentation and tests state it:\n #\n # * `https://` takes *https_proxy*, `http://` *http_proxy*, any other\n # scheme none. A proxy written without a scheme is `http://`'s, and is\n # answered with it, ready for `open`'s *proxy*.\n # * `localhost` (in lower case: Go's comparison) and a loopback address\n # (127.0.0.0/8, ::1, the first as IPv4-mapped too) never go through a\n # proxy.\n # * *no_proxy* is a comma-separated list, each entry trimmed and read\n # without regard to case: `*` alone sends everything direct; an IP\n # address or a CIDR range (`10.0.0.0/8`, `2001:db8::/32`) matches the\n # addresses in it, IPv4 and its IPv4-mapped form alike, and a CIDR\n # range of one family never the other's; a domain matches itself and\n # every name under it (`foo.com`: `foo.com`, `bar.foo.com`, not\n # `barfoo.com`); with a leading dot, or `*.`, only the names under it\n # (`.y.com`: `x.y.com`, not `y.com`). An address or domain may carry\n # a port (`1.2.3.4:80`, `[::1]:443`, `foo.com:8080`), and matches\n # then on that port alone, the URL's own or its scheme's default.\n # An entry that is none of these (`:80`) is passed over.\n # * A host is compared as IDNA's ASCII form, as Go compares it.\n #\n # Pure: the values are the caller's, so std/http reaches no environment;\n # std/http_proxy reads them from it.\n def self.proxy_for(url : String, http_proxy : String | ::Nil, https_proxy : String | ::Nil, no_proxy : String | ::Nil = nil) : String | ::Nil\n def self.put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The reason phrase a status carries (RFC 9110 §15), for a response the\n # server writes; a status this table does not know gets its class's.\n def self.reason_for(status : Int32) : String\n # One request and its whole answer: the answer as `open` hands it, its\n # body read. Redirects are followed, *redirects* at most; *jar* keeps\n # the cookies the answers set and sends them back; *decompress* asks\n # for gzip, deflate and zstd and undoes them. A refused or reset\n # connection answers `SocketError`; a task cancelled answers\n # `Cancelled`; what the server does wrong answers `HTTPError`. `!`\n # passes any of them up, `.or_panic` refuses it.\n def self.request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Response | Cancelled | SocketError | HTTPError\n # A request's head: the request line, `Host`, `Connection` (*connection*:\n # `close`, or `Upgrade`), the caller's headers, and the body's framing -\n # *length*, chunks, or none.\n def self.request_head(method : String, path : String, host : String, headers : Hash(String, String), length : Int64 | ::Nil, chunked : Bool, connection : String) : String\n # Two field names for the same field: ASCII case is the only difference\n # a name may carry.\n def self.same_name?(a : String, b : String) : Bool\n def self.split_crlf(text : String) : Array(String)\n # A status line read: the status and its reason, or why it is not one.\n def self.status_line(line : String) : ::Tuple(Int32, String) | HTTPError\n # What an `https://` request is checked against and offers: the\n # system's roots, and ALPN's `http/1.1`, unless a program sets its own -\n # roots of its own for a server it runs, say.\n def self.tls_config : TlsConfig\n def self.tls_config=(config : TlsConfig) : TlsConfig\n # A token (RFC 9110 §5.6.2): what a method or a field name is made of.\n def self.token?(text : String) : Bool\n # Without the blanks at either end (RFC 9112 §5.1: field values are\n # trimmed of the whitespace around them).\n def self.trim(s : String) : String\n # The 101 an upgrade to *protocol* is answered with, and the connection,\n # which is the caller's from then on: std/websocket's handshake. Any\n # other answer is an `HTTPError` naming it.\n #\n # *proxy* is `open`'s, and so is its credential, on the CONNECT alone;\n # but an upgrade goes through a tunnel for `http://` as for `https://`,\n # the handshake inside it in origin form. `Upgrade` belongs to the\n # connection it is sent on (RFC 9110 §7.8), and a proxy forwarding a\n # request removes `Connection` and every field it names (§7.6.1), so a\n # handshake handed to a forward proxy in absolute form reaches the\n # server, at best, as a plain GET: the gate's proxy, which drops\n # `Connection`, leaves std/websocket's server a request it answers 400.\n def self.upgrade(url : String, protocol : String, headers : Hash(String, String) = {} of String => String, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Upgraded | Cancelled | SocketError | HTTPError\nend\n\n# Requests that keep their connections: an `HTTPClient` holds the connections\n# its answers left open, by host and port, and the next request to that\n# server goes over one of them instead of a new one - the TCP handshake,\n# and the slow start after it, paid once rather than per request. Its\n# methods are `HTTP`'s, redirects, cookies and decoding included:\n#\n# ```\n# client = HTTPClient.new\n# client.get(\"http://127.0.0.1:8080/a\")\n# client.get(\"http://127.0.0.1:8080/b\") # the same connection\n# client.close\n# ```\n#\n# A connection goes back once its answer has been read to the end (inside\n# `open`, up to 64 KiB left unread is read past for it) and the answer\n# allows it; at most *idle* wait per server, and the rest close. A server\n# may close an idle connection whenever it likes (RFC 9112 §9.3.1), so a\n# request whose kept connection turns out closed - the write fails, or it\n# ends before a byte of an answer - is sent again once on a new one, when\n# its method is idempotent (RFC 9110 §9.2.2) and its body a string;\n# anything else answers the failure. One task at a time per request: two\n# tasks sending at once take two connections, and a client is not for\n# sharing across threads. An `https://` connection is kept under the\n# `HTTP.tls_config` it was made with, and handed only to a request made\n# under the same one (`TlsMadeUnder`): after `HTTP.tls_config=` the next\n# request makes a connection of its own, checked against the new roots.\n# A client made with *tls* uses that configuration instead of\n# `HTTP.tls_config`, and one made with *resolver* finds its hosts by it\n# instead of `Resolver.system` - an encrypted resolver, or a hosts text\n# that pins a name to an address: std/secure_dns's `DnsOverHttps` is a\n# client of its own with both, so its server is checked against the roots\n# it was given and reached at the address it was given.\n#\n# A client resumes TLS sessions (RFC 8446 §2.2): the tickets an\n# `https://` server sends are kept for its origin - scheme, host and\n# port, whichever proxy's tunnel they came through - under the TLS\n# configuration they came under, the client's own *tls* or else\n# `HTTP.tls_config` (`TlsMadeUnder`, as a kept connection is), and a new\n# connection to that origin offers the newest, each once.\n# A resumed handshake checks no certificate - the ticket stands for the\n# check the first one made - so a ticket is never offered to another\n# origin, nor under another configuration: after `HTTP.tls_config=`, or\n# roots, an identity, `now`, `min_version` or `require_staple` assigned\n# in place - in `HTTP.tls_config` or in the client's own *tls* - the next\n# connection is a full handshake under the new ones, and that origin's\n# tickets from under the old one are dropped. (std/tls's `TlsSession`\n# holds each ticket to its configuration too; this client drops them\n# where that one would only pass them over.) At most *origin_tickets* are\n# kept an origin - 4 by default, so that as many connections as *idle*\n# keeps by default can resume at once, where std/tls's server and\n# OpenSSL's send 2 a handshake - and *tickets* in all, 256 by default: as\n# many origins as that leaves room for at *origin_tickets* each, the one\n# longest unused dropped first. 0 for either keeps none, and nothing\n# resumes. `close`\n# leaves them. Nothing goes early (0-RTT), whatever a ticket allows: the\n# module's header says why. `connections_resumed`, `tickets_held` and\n# `Response#tls_resumed?` say what was kept and used. What it saves, by\n# bench/std_http_resume.iyi built --release on a 12-core machine other\n# jobs shared (medians of 200, the middle of three runs): the handshake\n# took 0.96 ms full and 0.51 resumed against std/http's server, 1.03 and\n# 0.57 against OpenSSL 3.5's s_server - and a GET on a new connection,\n# which waits about 40 ms at each iyi end that is not the handshake's,\n# 82.98 ms and 82.00, and 42.04 and 40.96.\n#\n# Over HTTP/2 - `https://` where ALPN chose `h2`, or `http://` with\n# *prior_knowledge* (h2c, RFC 9113 §3.3) - a kept connection carries the\n# next request on a new stream, until the server says GOAWAY.\n#\n# HTTPS records (RFC 9460), when `use_https_records` is set: before a new\n# connection to an `https://` origin - not through a proxy, and not for\n# an upgrade - the client asks its resolver for the origin's HTTPS\n# records (`Resolver#https_records`, AliasMode followed) and connects by\n# the first ServiceMode record it can use: one whose mandatory keys it\n# knows - the seven std/dns reads, port and no-default-alpn mandatory by\n# being there (section 9) - and whose protocols (alpn, and http/1.1\n# unless no-default-alpn) hold one of the configuration's `alpn`, or\n# http/1.1 where that is empty. Its target - the record's owner for \".\"\n# - and its port, or the origin's, are where the connection goes; its\n# `ech` is the connection's `ech_configs` (RFC 9848); and the protocols\n# it lists go first in the ClientHello's ALPN, which still offers every\n# one the configuration does (section 7.1.2: a record says which\n# protocols the endpoint has, and cannot take one away). The certificate\n# is checked, and the server named inside, for the origin's host,\n# whatever the target (section 2.3). An `ech` whose ECHConfigList holds\n# no config std/tls can use is passed over, as RFC 9849 section 6.1 has\n# it. The address hints are not read: the target's addresses are asked\n# for as any host's are.\n#\n# An endpoint that does not connect gives way to the next, then to the\n# last AliasMode target and to the origin itself (section 3) - unless\n# every endpoint carries `ech`, when the request fails rather than name\n# the server in the clear (RFC 9848 section 5.1). A TLS refusal is the\n# request's failure, but for one: a server that turned the encrypted\n# ClientHello down, its certificate checked for the config's\n# public_name, is asked once more on a new connection to the same\n# address, under the retry_configs it sent, or without ECH where it sent\n# none, which turns ECH off there (RFC 9849 section 6.1.6); a refusal of\n# that is the failure. No records (NXDOMAIN, NODATA), a set section 2.2\n# refuses, an AliasMode \".\" (the service says it is not there), or no\n# record the client can use: the connection is made as without records.\n# So is one whose lookup failed through a resolver that asks in the\n# clear, as section 3.1 lets a client choose; through encrypted\n# upstreams the failure is the request's, as section 3.1 asks, for\n# there the one who can drop the answer is an attacker taking the\n# records away. `localhost` and addresses are not asked about, and an\n# `http://` URL is not made `https://` by its records (section 9.5's\n# SHOULD): the program wrote the scheme.\n#\n# A record's `ech` and protocols make a configuration of their own - a\n# copy of the client's, made once and kept for the origin while the same\n# ones come under the same configuration - so that a kept connection and\n# a ticket are handed on only under the record they were made by\n# (`TlsMadeUnder` holds `ech_configs`). Off by default: every new\n# connection then waits on one DNS answer or more before its ClientHello\n# (RFC 9848 section 5.3 forbids sending it sooner), with no cache to\n# spare the next, and the records may move a connection to another host\n# and port, which is the program's to ask for.\npub class HTTPClient\n # Every idle connection closed. The client may be used after; it opens\n # new ones.\n def close : Nil\n # Connections this client has opened, and requests sent over one it\n # kept: what a program reads to see that its connections are kept.\n def connections_made : Int32\n # Connections this client opened whose TLS handshake resumed a session\n # by a ticket it kept.\n def connections_resumed : Int32\n def connections_reused : Int32\n def delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n protected def give_back(key : String, conn : Conn) : Nil\n def head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The configuration a connection to *origin* is made under by a record\n # whose `ech` is *ech* (nil: none) and whose protocols put *alpn* in its\n # order, *base* being the client's own or `HTTP.tls_config`: *base*\n # itself where those are its own, and otherwise a copy of it with them -\n # the same object every time while *base* is the same (`TlsMadeUnder`),\n # so that a connection kept and a ticket held under it serve the next\n # connection by the same record. It becomes the one the origin's kept\n # connections are taken under (`https_current`). The origin's\n # configurations from under another *base* go.\n protected def https_config(origin : String, base : TlsConfig, ech : Array(UInt8) | ::Nil, alpn : Array(String)) : TlsConfig\n # The configuration *origin*'s last connection by HTTPS records was made\n # under, while *base* is the one it came from - *base* itself where it\n # was made as without records: what a kept connection to the origin\n # must have been made under to be taken. Nil when none was made by them\n # under *base*, and then none is taken.\n protected def https_current(origin : String, base : TlsConfig) : TlsConfig | ::Nil\n # *timeout_ms* and *proxy* are `HTTP.open`'s, for every request the\n # client sends; *tls* and *resolver* are this client's own (above);\n # *origin_tickets* and *tickets* bound the TLS tickets kept, by origin\n # and in all.\n def initialize(idle : Int32 = 4, timeout_ms : Int32 = 0, prior_knowledge : Bool = false, proxy : String | ::Nil = nil, tls : TlsConfig | ::Nil = nil, resolver : Resolver | ::Nil = nil, origin_tickets : Int32 = 4, tickets : Int32 = 256)\n protected def made : Nil\n def open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n def post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # `http://` spoken as HTTP/2 from the first byte, without asking: for a\n # server known to take it.\n def prior_knowledge : Bool\n # The proxy every request goes through, `HTTP.open`'s *proxy*; its\n # connections are kept under it.\n def proxy : String | ::Nil\n def put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true) : Response | Cancelled | SocketError | HTTPError\n # What turns a host name into addresses for this client's connections,\n # instead of `Resolver.system`: encrypted upstreams, or a hosts text\n # alone that pins a name to an address, as std/secure_dns's\n # `DnsOverHttps` pins its server's.\n def resolver : Resolver | ::Nil\n protected def resumed : Nil\n # The session a new connection to *origin* under *tls* offers a ticket\n # from and keeps the server's new ones in; nil when the client keeps\n # none. The origin's tickets from under another configuration are\n # dropped, not left to take room that `TlsSession` would never offer\n # them from. The session handed out is the newest used; past the room\n # *tickets* leaves, the oldest goes.\n protected def session_for(origin : String, tls : TlsConfig) : TlsSession | ::Nil\n # A kept connection to *key*, nil when none waits. An HTTP/2 connection\n # that can take no more requests is closed rather than handed out, and\n # so is an `https://` one made under another TLS configuration than\n # *tls*, the one the request is made under (`TlsMadeUnder`): keyed by\n # scheme, host and port alone, a connection whose server was checked\n # against one set of roots carried requests after `HTTP.tls_config=`\n # named others that would have refused it.\n protected def take(key : String, tls : TlsConfig | ::Nil = nil) : Conn | ::Nil\n # The TLS tickets held for *origin* - `https://host:port`, the host in\n # lower case and the port written even where it is 443 - or for every\n # origin when nil.\n def tickets_held(origin : String | ::Nil = nil) : Int32\n # What this client's `https://` requests are checked against and offer,\n # instead of `HTTP.tls_config`: roots of its own, or ALPN's `h2` first.\n def tls : TlsConfig | ::Nil\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records : Bool\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records=(value : Bool) : Bool\nend\n\n# What the other end did that HTTP does not allow, as a value: `reason` is\n# the sentence, `status` the answer a server gives a request that did it\n# (400, or 413 and 431 for the limits). `message` says it is HTTP's.\npub struct HTTPError\n def initialize(reason : String, status : Int32 = 400)\n def reason : String\n def status : Int32\n def to_s : String\nend\n\n# One endpoint an origin's HTTPS records name, as `HTTP.https_endpoints`\n# reads it: the host and port to connect to, the ECHConfigList to encrypt\n# the ClientHello under (nil: none), and the protocols to offer by ALPN,\n# in order.\npub struct HttpsEndpoint\n def alpn : Array(String)\n def ech : Array(UInt8) | ::Nil\n def host : String\n def initialize(host : String, port : Int32, ech : Array(UInt8) | ::Nil, alpn : Array(String))\n def port : Int32\nend\n\n# A `multipart/form-data` body (RFC 7578) read a part at a time, as it\n# arrives: `next_part` answers the next `Part`, nil after the last, and a\n# part's `read` answers its bytes up to the boundary, \"\" at its end. A part\n# not read to its end is read past by the next `next_part`. The body of a\n# request is `Request#multipart`'s; any `BodyReader` and boundary will do.\n# A part's head is refused past 16 KiB, and a body that ends before its\n# closing boundary is an `HTTPError`.\npub class Multipart\n # The boundary a `Content-Type` of `multipart/...` names, nil when it\n # names none - or one of a length RFC 2046 §5.1.1 does not allow, 1 to\n # 70 characters, which `new` refuses with a panic. The type is the\n # peer's: `Request#multipart` handed a client's 90-character boundary\n # through to that panic (found by bench/std_fuzz_exercise.sh).\n def self.boundary(content_type : String) : String | ::Nil\n # Where *pattern* is in what is held, -1 when it is not there yet.\n protected def find(pattern : String) : Int32\n def initialize(body : BodyReader, boundary : String)\n # The next part, nil after the last.\n def next_part : Part | Nil | Cancelled | SocketError | HTTPError\n # Up to *max* bytes of the part being read, \"\" at its boundary. What is\n # held back is what may yet be the start of the boundary.\n protected def part_read(max : Int32) : String | Cancelled | SocketError | HTTPError\n # One more piece of the body onto what is held: false at its end.\n protected def pull : Bool | Cancelled | SocketError | HTTPError\nend\n\n# A multipart/form-data body to send: fields and files in order. Its\n# boundary is chosen when the body is first asked for - `finish`,\n# `content_type` or `boundary`, whichever comes first, after which no\n# part is added - as `iyi-` and 32 hex digits of the parts' SHA-1, made\n# again from itself in the rare case a part holds it: so it is none of\n# the parts' bytes by construction rather than by chance, the same\n# fields make the same body, and std/http reaches no source of\n# randomness for it. A boundary given to `new` is used as it is, and a\n# part that holds it is refused.\npub class MultipartBuilder\n def boundary : String\n def content_type : String\n def field(name : String, value : String) : self\n def file(name : String, filename : String, data : String, content_type : String = MIME.from_extension(filename)) : self\n # The body, closing boundary and all.\n def finish : String\n # The boundary ends a part where it follows a CRLF, the part's first\n # bytes included, which follow the head's blank line.\n protected def self.holds?(data : String, mark : String) : Bool\n def initialize(boundary : String | ::Nil = nil)\n # A name inside quotes, as browsers write one: a quote and a line break\n # percent-encoded (the WHATWG form encoding), so no field can end the\n # parameter or the line early.\n protected def self.quoted(text : String) : String\nend\n\n# One part of a multipart body: its head's fields, its name and file name\n# from `Content-Disposition`, and its bytes, read up to its boundary.\npub class Part\n def content_type : String | ::Nil\n # The file name a browser sent the part under, `filename=`.\n def filename : String | ::Nil\n def finished? : Bool\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n def initialize(owner : Multipart, headers : Hash(String, String))\n # The form field's name, `name=` of `Content-Disposition`.\n def name : String | ::Nil\n # The part's next bytes, at most *max*; \"\" at its end.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the part as one string, refused past *limit* bytes.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\nend\n\n# What a server's block is handed: one request. Its body is `body`, read\n# whole before the block ran, or under `stream_bodies` read by the block\n# from `body_io` as it arrives; `body` then reads the rest itself, and a\n# body that cannot be read - the client went, or sent one that is not\n# HTTP - ends the connection with that sentence rather than be answered as\n# if it were whole.\npub class Request\n def body : String\n def body_io : BodyReader\n # The pairs of the request's `Cookie` header, the first of a name kept.\n def cookies : Hash(String, String)\n # The fields of a form: the query's, and an\n # `application/x-www-form-urlencoded` body's after them. The body is\n # read for it.\n def form : Params\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n # A request whose body is in hand: what `parse_request` answers.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), body : String, version : String = \"HTTP/1.1\", remote_address : String | ::Nil = nil)\n # A request whose body is still on the connection.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), version : String, reader : BodyReader, remote_address : String | ::Nil = nil)\n def method : String\n # The body as `multipart/form-data` parts, read as they arrive; nil when\n # the `Content-Type` is not multipart or names no boundary\n # (`Multipart.boundary`).\n def multipart : Multipart | ::Nil\n def path : String\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates : Array(Certificate)\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates=(value : Array(Certificate)) : Array(Certificate)\n def query : String | ::Nil\n # The client's address, as the server's socket saw it (no port): nil\n # for a request a program made, or when the client had already gone. A\n # proxy in front is what this names; `X-Forwarded-For` is the proxy's\n # word, and a header any client can write.\n def remote_address : String | ::Nil\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the TLS connection the request came over resumed a session by\n # a ticket, under `serve(tls:)`: its `peer_certificates` are then the\n # ticket's. False in the clear.\n def tls_resumed? : Bool\n def version : String\nend\n\n# A response: what a server's block answers, and what a client is handed.\n#\n# To write one: `Response.new(200, \"hello\")`, a status and a string;\n# `Response.new(200, io)`, a stream the server copies out and closes, with\n# the caller's `Content-Length` when it gives one and chunked otherwise;\n# `Response.stream(200) { |w| w.write(\"...\") }`, whose block writes as it\n# goes; `Response.upgrade(headers) { |socket, leftover| ... }`, a 101 after\n# which the connection is the block's. `add_header` writes a field on a\n# line of its own, as each `Set-Cookie` must be.\n#\n# What a client is handed: the status, reason and headers, and the body -\n# `body` whole, or `body_io` as it arrives inside `HTTP.open`'s block.\npub class Response\n # One more line of the field *name*, written on a line of its own: what\n # two `Set-Cookie`s need, since the \", \" a field's lines are joined with\n # is part of a cookie's date.\n def add_header(name : String, value : String) : Nil\n # The lines of each field written on more than one, by its name in\n # `headers`; nil when every field is on one line.\n def apart : Hash(String, Array(String)) | ::Nil\n # The body: whole. Inside `HTTP.open`'s block it is read here, the first\n # time it is asked for, and a body that cannot be read panics with why;\n # `body_io` answers that as a value.\n def body : String\n # The body as it arrives. For a response a program made, its string.\n def body_io : BodyReader\n # Whether the client undid the body's `gzip`, `deflate` or `zstd`\n # coding; the `Content-Encoding` and `Content-Length` that described it\n # are gone from the headers then.\n def decoded? : Bool\n # A header by name, as the server spelled it or otherwise: a field\n # name is case-insensitive (RFC 9110 §5.1), and `content-length` is as\n # common on the wire as `Content-Length`.\n def header(name : String) : String | ::Nil\n # A field's lines apart, in the order they came: `header` joins them\n # with \", \", and `Set-Cookie` is the field that join breaks (RFC 9110\n # §5.3), since a cookie's `Expires` date has a comma in it - two cookies\n # read as \"a=1; Expires=Wed, 21 Oct 2026 07:28:00 GMT, b=2\", which no\n # split takes back apart. Empty for a field that is not there.\n def header_values(name : String) : Array(String)\n def headers : Hash(String, String)\n # A response a client read: its body comes from *reader*, and *url* is\n # where it came from.\n def initialize(status : Int32, reason : String, headers : Hash(String, String), apart : Hash(String, Array(String)) | ::Nil, reader : BodyReader, url : String, decoded : Bool)\n def initialize(status : Int32, reason : String, headers : Hash(String, String), body : String, apart : Hash(String, Array(String)) | ::Nil = nil)\n # A response to write: the reason is the status's own, the headers the\n # caller's. `Response.new(200, \"hello\")` is the common shape.\n def initialize(status : Int32, body : String = \"\", headers : Hash(String, String) = {} of String => String)\n # A response whose body is *source*, copied out by the server and\n # closed after.\n def initialize(status : Int32, source : IyiIO, headers : Hash(String, String) = {} of String => String)\n # The body read whole into the response, which is answered; why not,\n # when it cannot be.\n def load : Response | Cancelled | SocketError | HTTPError\n def reason : String\n def source : IyiIO | ::Nil\n def status : Int32\n # A response whose body the block writes as it goes, chunked.\n def self.stream(status : Int32, headers : Hash(String, String) = {} of String => String, &writer : (BodyWriter -> Nil)) : Response\n def success? : Bool\n def takeover : Proc(IyiSocket | TlsStream, String, Nil) | ::Nil\n protected def takeover=(takeover : Proc(IyiSocket | TlsStream, String, Nil)) : Proc(IyiSocket | TlsStream, String, Nil)\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the `https://` connection the answer came over resumed a TLS\n # session by a ticket an `HTTPClient` kept, rather than a full\n # handshake; false in the clear and for one a program made.\n def tls_resumed? : Bool\n # A 101 Switching Protocols with *headers* (`Upgrade` and the\n # protocol's own), after which the connection is the block's: it is\n # handed the connection - the socket, or under `serve(tls:)` the\n # `TlsStream` over it - and the bytes the client sent after its request,\n # and the connection closes when it returns.\n def self.upgrade(headers : Hash(String, String), &takeover : (IyiSocket | TlsStream, String -> Nil)) : Response\n # The URL the answer came from, after any redirects; nil for one a\n # program made.\n def url : String | ::Nil\n def writer : Proc(BodyWriter, Nil) | ::Nil\n protected def writer=(writer : Proc(BodyWriter, Nil)) : Proc(BodyWriter, Nil)\nend\n\npub struct Server\n # The headers a coded body goes with: the coding, and `Vary` so that a\n # cache keeps the answers apart. A `Content-Length` the block gave is\n # the plain body's, and goes.\n protected def self.coded(response : Response, coding : String) : Response\n # The coding *response* goes in to the client of *req*, or nil: one the\n # client takes (RFC 9110 §12.5.3, a `q` of 0 refusing it), for an\n # answer with a body and a type worth compressing, and no coding of its\n # own. A whole body goes `zstd` when the client's `q` for it is at least\n # its `q` for gzip, and `gzip` otherwise. The tie goes to zstd for the\n # server's time: measured on this machine, release builds, on\n # src/std/http.iyi (199,134 bytes) `Gzip.compress` wrote 54,484 bytes in\n # 5 ms and `Zstd.compress` 61,753 in 1 ms, and on CHANGELOG.md (1.5 MB)\n # 552,446 in 58 ms against 589,785 in 13 ms - a quarter of the time, for\n # an eighth more bytes at most. A stream - an `IyiIO` or a\n # `Response.stream` - goes gzip or as it is: std/compress's `Deflater`\n # writes each piece as it comes, where std/zstd's compressor takes its\n # input whole, and a frame a write would code each piece alone.\n def self.coding_for(req : Request, response : Response) : String | ::Nil\n # A string body compressed whole in *coding*, `zstd` or `gzip`, with\n # the headers that say so. `Zstd.compress`'s frame needs a window of\n # 1 MiB at most, inside the 8 MB RFC 9659 §3 holds an encoder to.\n protected def self.compressed(response : Response, coding : String) : Response\n # A response's head. `Content-Length` is *length*'s - the caller's own\n # line is checked against it, and kept only where no body follows it: a\n # HEAD, or a 304 that names the length a 200 would have (RFC 9110 §8.6) -\n # or `Transfer-Encoding: chunked` when *chunked*, or neither for a body\n # the connection's close ends. `Connection` says what the connection will\n # do next, except on an *upgrade*, whose own line says it. A 1xx, 204 or\n # 304 has no content (RFC 9110 §15), and writes neither a body nor a\n # length of its own (§8.6): the body was read by the client as the start\n # of the next answer, and `Content-Length: 0` on a 304 told a cache that\n # the resource had become empty. A field added on lines of its own\n # (`add_header`) is written a line each.\n def self.format_head(response : Response, head : Bool, close : Bool, length : Int64 | ::Nil, chunked : Bool, upgrade : Bool = false) : String\n # The response as it goes on the wire, whole: `format_head` for its\n # string body, and the body.\n def self.format_response(response : Response, head : Bool, close : Bool) : String\n # One connection: requests until the client is done. A request that is\n # not HTTP is answered with why and the connection closed, since the\n # bytes after it cannot be trusted to be a request either. Requests that\n # came in one read are read where they lie, one after another. A\n # connection that opens with HTTP/2's preface is served as HTTP/2. Under\n # *tls* the handshake comes first, and the connection ends with\n # close_notify. A connection this side wrote last on - an answer, a\n # refusal, a GOAWAY - closes by lingering (`Conn#close`), so the reset\n # a close with the client's bytes unread is cannot take what was\n # written from the client before it reads it. One that timed out or was\n # cancelled, with nothing of this side's waiting to be read, closes at\n # once, and so does one whose client said `close` and sent nothing past\n # its request (`serve_h1`).\n def self.handle(client : IyiSocket, handler : Proc(Request, Response), listener : IyiSocket | ServeStop | Nil = nil, idle : Hash(UInt64, Conn) | ::Nil = nil, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 0, tls : TlsServerConfig | TlsFrozenServerConfig | Nil = nil, request_timeout_ms : Int32 = 0) : Nil\n def initialize\n # A request's head - the text before its blank line - read and checked,\n # or why it is not one.\n def self.parse_head(text : String) : RequestHead | HTTPError\n # A request at *from* in `text`, if a whole one is there. No request and\n # no error means \"not yet\". `consumed` counts from the start of `text`.\n def self.parse_request(text : String, from : Int32 = 0) : ParsedRequest\n # Serve `listener` until it is closed, on *workers* threads: each\n # connection is a task on the pool `spawn_parallel` starts (SPEC.md\n # III.4.13), started by whichever worker is free and served there to\n # its end, while the thread that called this accepts. The block runs on\n # the connection's worker, once per request, on several workers at\n # once; what it answers is written back.\n # The close ends a connection waiting on its client too, for its next\n # request or for the rest of one, and one that is answering ends when its\n # answer is written: a keep-alive client that sat idle kept `serve` from\n # returning for as long as it stayed, and so did one that had sent\n # what has arrived of a request so far - one byte of it was enough.\n #\n # *stream_bodies*: the block reads each request's body from `body_io`\n # rather than being handed it whole. *compress*: an answer whose type\n # std/mime calls compressible goes zstd or gzip to a client that\n # accepts one (`coding_for`).\n #\n # *timeout_ms*: a request's head must have arrived within it, counted\n # from when the server starts waiting for one - a kept-alive connection's\n # idle time included - and no read of a body nor write of an answer may\n # wait longer; a connection that does is closed. Without it, a client\n # that sends a byte of its head every few seconds (Slowloris) holds a\n # connection and its fiber for as long as it likes, and so does one that\n # reads none of its answer. A minute by default, nginx's\n # `client_header_timeout`; 0 for none. A connection handed over by\n # `Response.upgrade` waits as its protocol likes.\n #\n # *request_timeout_ms*: the whole request - head and body - must have\n # arrived within it, counted from the same moment as *timeout_ms*, as\n # Go's `ReadTimeout` beside its `ReadHeaderTimeout`; the head is held to\n # the sooner of the two. Without it a body sent a byte every few seconds\n # was bounded per read alone, and held its connection for as long as it\n # dripped. A block that reads the body itself (*stream_bodies*) reads\n # under it until the block returns, and what it leaves unread is read\n # past under it too. Over HTTP/2 the wait for each request and its body\n # is held to it on the connection the same way, the head to the sooner\n # of the two. Five minutes by default, which a 64 MiB body (`MAX_BODY`)\n # meets at 224 KB/s; a server taking large uploads over slow links\n # raises it. 0 for none. A connection past it is closed, unanswered, as\n # one past *timeout_ms* is.\n #\n # *max_connections*: at most this many connections are served at once;\n # 1024 by default, 0 for no limit. Past it the server stops accepting\n # until one ends, so the next connection waits in the kernel's listen\n # queue - queued, not refused, as Go's `netutil.LimitListener` does - and\n # is served in its turn; a client that gives up waiting goes unserved.\n # Each served connection holds its place until it closes, a kept-alive\n # one between requests too, which *timeout_ms* bounds. The process's\n # own limit on descriptors may come first.\n #\n # *tls*: each connection is TLS (std/tls's `TlsServer`, 1.3 or 1.2) under this\n # configuration, its handshake held to *timeout_ms* too and run on the\n # worker that serves it; one whose handshake fails is closed, the\n # client told why by an alert. Inside it a connection that opens with\n # HTTP/2's preface - as a client whose ALPN chose `h2` does, when\n # `tls.alpn` offers it - is served HTTP/2, and any other HTTP/1.1. A\n # `Response.upgrade` block over TLS is handed the `TlsStream`, so a\n # WebSocket is `wss://`. On more than one worker the config is a\n # `TlsFrozenServerConfig` - `config.frozen` - since every worker reads\n # it at once: its ticket keys are one set behind a lock, so a ticket a\n # connection on one worker was sent resumes on any other, and an\n # identity source given to `frozen` is a block the compiler checked. A\n # `TlsServerConfig` itself is refused at the call (\"Server.serve hands\n # `tls : TlsServerConfig` to other threads, several at once, and it is\n # not Share: ... Pass `config.frozen` ...\"), not frozen here: `frozen`\n # is a copy, so a property assigned after the server started would be\n # served by no worker and nothing would say so, and a config with an\n # `identity_source` cannot be frozen without its source given again as\n # a block the compiler checks. Under `workers: 1` either is served.\n #\n # *workers*: the pool's size, `IyiWorkers.count` by default - `IYI_WORKERS`\n # from the environment, else the cores this process may run on. The\n # pool is the process's one, and it does not grow or shrink once it has\n # started, so a count other than the one it started with panics. Every\n # worker runs the block at once, so the compiler checks it where it is\n # written as it checks a parallel task's (`@[Share]`, SPEC.md III.4.4):\n # every value it captures must be `Share` - a table behind a `Mutex`\n # (std/mutex), a count in an `Atomic`, a `SharedRouter` (std/router) -\n # every captured local assigned once, and the constants and class\n # variables it names `Share`. The refusal names the variable, why its\n # type is not `Share`, and the two ways out: the lock, or `workers: 1`.\n # `workers: 1`, written as the literal, serves on the calling thread\n # alone - each connection a task beside the accept loop, as `spawn`\n # makes one - and its block is not checked: a block that calls a\n # `Router`, appends to an `Array` or keeps a handle that is not `Share`\n # is served there. A count in a variable is checked whatever it holds,\n # since the compiler cannot know it is 1. A count of 1 from the default\n # (`IYI_WORKERS=1`, one core) serves on the calling thread too.\n #\n # A connection stays on the worker that started it (III.4.13, part 1):\n # connections spread over the workers free as they arrive, and one kept\n # alive is not moved later. A block that computes without parking holds\n # its worker meanwhile, as any task does. The listener is the calling\n # thread's, and a close of it is made there: a block that stops the\n # server sends on a `Channel` a task beside the server receives from,\n # and that task closes it. At the stop each connection waiting on its\n # client is closed on its own worker, sent close_notify there first\n # under *tls*.\n def self.serve(listener : IyiSocket, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count, &handler : (Request -> Response)) : Nil forall C\n # `serve` with a value for its handler rather than a block: a\n # `SharedProc`, a `SharedRouter` (std/router), or one of the program's\n # own, whose `call(request : Request) : Response` answers each request,\n # as Go's `http.Handler` does. On more than one worker it must be\n # `Share` (SPEC.md III.4.4), since every connection on every worker\n # calls the one value at once: the compiler refuses one that is not at\n # the argument, naming the field that failed and the two ways out - a\n # count in an `Atomic`, a pool or a cache behind a `Mutex`, a\n # `SharedRouter` for a `Router`, or `workers: 1`, under which any value\n # with that `call` is served on the calling thread, a `Router`\n # included. What the check does not see is the edge `spawn_parallel`\n # has too: a method the handler calls that writes a module's state. A\n # struct handler is copied into each connection's task, and an `Atomic`\n # in it with it: what the connections share is a class, or held by\n # one.\n #\n # The rest is the block form's: *stream_bodies*, *compress*,\n # *timeout_ms*, *tls* (`config.frozen` on more than one worker),\n # *request_timeout_ms*, *max_connections* and *workers*, HTTP/2 by its\n # preface, `Response.upgrade`, and a close of *listener* - on the\n # thread that called this - ending every connection waiting on its\n # client and returning once the others have answered.\n def self.serve(listener : IyiSocket, handler : H, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count) : Nil forall H, C\n protected def self.writer_for(socket : IyiSocket | TlsStream, chunked : Bool, gzip : Bool) : BodyWriter\nend\n\n# A TLS connection used as a socket is: read into a buffer, written to,\n# closed, its timeouts set - by a `Conn`, a `BodyWriter`, and a connection\n# handed over by an upgrade (`wss://` at either end). What TLS refuses is a\n# `SocketError` carrying TLS's own sentence (`layer_reason`), so answers\n# keep the types they have over a plain socket.\npub class TlsStream\n # The socket closed at once, nothing sent or read first.\n def abort : Nil\n # close_notify, the sending half shut, and what the peer still sends\n # read past for a bounded time before the socket closes\n # (`TlsConnection#close`).\n def close : Nil\n # close_notify and the sending half shut; reads go on until the peer's\n # own (`TlsConnection#close_write`).\n def close_write : SocketError | ::Nil\n def closed? : Bool\n def initialize(tls : TlsConnection, host : String)\n # The peer's certificate chain, leaf first, as the handshake checked it:\n # the server's, at a client; the client's, at a server that asked.\n def peer_certificates : Array(Certificate)\n # Up to *count* bytes into *buffer*: 0 once the peer closed with\n # close_notify.\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n # Up to *max_bytes* as a string, what is held first: \"\" once the peer\n # closed with close_notify. `IyiSocket#read`'s form, for a block an\n # upgrade hands either.\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n # Whether the handshake resumed a session (`TlsConnection#resumed?`).\n def resumed? : Bool\n def write(text : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What `HTTP.upgrade` answers: the 101, and the connection, which is the\n# caller's now - the socket, and the bytes the server sent after its head.\npub class Upgraded\n def initialize(response : Response, socket : IyiSocket | TlsStream, leftover : String)\n def leftover : String\n def response : Response\n # The connection: the socket, or for `https://` the `TlsStream` over it.\n def socket : IyiSocket | TlsStream\nend\n\nimpl Error for HTTPError\n def message : String\nend\n" }, { "name": "std/http_proxy", @@ -304,8 +322,8 @@ { "name": "std/json", "path": "src/std/json.iyi", - "sourceSha256": "e5388d36116b5e92d043d653b8561cd6016e2092e82af72577df2ca5d3da8ff5", - "surface": "module std/json\n\n# `derive serializable`, in a struct or class whose fields are written with\n# their types: the type reads and writes itself as a JSON object, a key per\n# field, named as the field is.\n#\n# pub struct Coord\n# getter x : Float64\n# @[JSON::Field(key: \"label\")]\n# getter name : String\n# derive serializable\n# end\n#\n# Coord.from_json(%({\"x\": 1.5, \"label\": \"a\"})).x # => 1.5\n# coord.to_json # => {\"x\":1.5,\"label\":\"a\"}\n#\n# A field's value is read and written as `JSON.read_value` and\n# `JSON.write_value` read and write its type: the built-in values, an enum,\n# an `Array` or `Hash(String, V)` of them, an `Any`, another serializable\n# type. A key the type has no field for is passed over; a field with no\n# key keeps its default when it is written with one, as does a field with\n# a default whose key holds `null` where its type does not admit nil; a\n# field with neither is refused by name unless its type admits `nil`,\n# which it then holds. `@[JSON::Field(...)]` renames a field, leaves it\n# out either way, leaves a nil one out, or reads and writes it through a\n# converter (see `JSON::Field`). It is what the other library's `include\n# JSON::Serializable` writes, and the derive runs once, in the type's own\n# module (SPEC.md II.4), so a consumer calls the methods without the macro.\n#\n# Each field's options are read off the type when its methods are compiled,\n# in one `begin` so what a field's annotation says is known to every line\n# after it; and refused there, with the field named, when they do not make\n# sense.\npub macro serializable(declaration)\n\n# One JSON value of any kind, the result of `JSON.parse`. The `as_x` methods\n# panic when the value is of another kind; the `as_x?` ones answer `nil`.\npub struct Any\n alias Type = Nil | Bool | Int64 | Float64 | String | Array(Any) | Hash(String, Any)\n # Equal when the values are: an integer equals the float with the same\n # value, arrays element by element, objects key by key in any order.\n def ==(other : Any) : Bool\n def [](index : Int32) : Any\n def [](key : String) : Any\n def []?(index : Int32) : Any | ::Nil\n def []?(key : String) : Any | ::Nil\n def as_a : Array(Any)\n def as_a? : Array(Any) | ::Nil\n def as_bool : Bool\n def as_bool? : Bool | ::Nil\n # A float, or an integer read as one.\n def as_f : Float64\n def as_f? : Float64 | ::Nil\n def as_h : Hash(String, Any)\n def as_h? : Hash(String, Any) | ::Nil\n def as_i : Int32\n def as_i64 : Int64\n def as_i64? : Int64 | ::Nil\n def as_i? : Int32 | ::Nil\n def as_nil : Nil\n def as_s : String\n def as_s? : String | ::Nil\n def dig(key : String) : Any\n def dig(index : Int32) : Any\n def dig(k : String, *subkeys : String | Int32) : Any\n def dig(idx : Int32, *subkeys : String | Int32) : Any\n def dig?(key : String) : Any | ::Nil\n def dig?(index : Int32) : Any | ::Nil\n def dig?(k : String, *subkeys : String | Int32) : Any | ::Nil\n def dig?(idx : Int32, *subkeys : String | Int32) : Any | ::Nil\n protected def float_value? : Float64 | ::Nil\n # Consistent with `==`: a whole float hashes as the integer it equals, an\n # object's hash does not depend on the order its keys were written in.\n def hash : Int32\n def initialize(raw : Type)\n def inspect : String\n # The kind of value this is, in the word a refusal uses: `null`, `bool`,\n # `integer`, `float`, `string`, `array` or `object`.\n def kind : String\n def self.new(val : Int32) : Any\n def raw : Type\n # How many elements an array or an object holds. A scalar has no size.\n def size : Int32\n def size? : Int32 | ::Nil\n def to_ascii_json : String\n def to_json : String\n def to_pretty_json(indent : String = \" \") : String\n # A string as itself and null as nothing, as `YAML::Any#to_s` and the\n # other library answer them; a number, a boolean or a collection as its\n # JSON text. A string was its JSON text too, so `\"hello #{doc[\"name\"]}\"`\n # printed `hello \"Ada\"`, and null printed `null`.\n def to_s : String\nend\n\n# Writes a document one value at a time. `indent=` makes it pretty;\n# `ascii_only=` escapes every non-ASCII code point.\npub class Builder\n def array(& : (-> Nil)) : Nil\n def ascii_only : Bool\n def ascii_only=(value : Bool) : Bool\n def bool(val : Bool) : Nil\n # Whether no value has been written yet.\n def empty? : Bool\n def end_array : Nil\n def end_object : Nil\n def field(name : String, val : Any) : Nil\n def field(name : String, val : String) : Nil\n def field(name : String, val : Int32) : Nil\n def field(name : String, val : Int64) : Nil\n def field(name : String, val : Float64) : Nil\n def field(name : String, val : Bool) : Nil\n def field(name : String, val : Nil) : Nil\n # A key; the value that follows it is written by the next call. A key\n # whose text is one this object already holds is refused, naming both:\n # the reader refuses a duplicate key, and the builder wrote one - a key\n # given twice, or two Strings whose bytes are not UTF-8 and are both\n # written as U+FFFD (`{\"a\\xFF\" => 1, \"a\\xFE\" => 2}` was written\n # `{\"a\\uFFFD\":1,\"a\\uFFFD\":2}`, and `JSON.parse` refused it).\n def field(name : String) : Nil\n def field(name : String, & : (-> Nil)) : Nil\n def indent=(spaces : Int32) : Nil\n def indent=(str : String) : Nil\n def initialize\n def max_nesting : Int32\n def max_nesting=(value : Int32) : Int32\n def null : Nil\n def number(val : Int32) : Nil\n def number(val : Int64) : Nil\n def number(val : UInt8) : Nil\n def number(val : UInt64) : Nil\n def number(val : Float64) : Nil\n def object(& : (-> Nil)) : Nil\n # Text written as it is: the caller's own JSON.\n def raw(str : String) : Nil\n def start_array : Nil\n def start_object : Nil\n def string(val : String) : Nil\n # The document's text. A container still open or no value written at all\n # is not a document, and is refused.\n def to_s : String\nend\n\npub struct JSON\n # How `derive serializable` writes and reads one field, the other\n # library's `JSON::Field`:\n #\n # @[JSON::Field(key: \"userName\")]\n # getter user_name : String\n #\n # * `key:` the object key, where it is not the field's own name;\n # * `ignore: true` neither written nor read, `ignore_serialize: true` not\n # written, `ignore_deserialize: true` not read - a field that is not\n # read keeps its default, or is nil, and needs one or the other;\n # * `emit_null: false` a nil field left out of the object. A nil field is\n # written as `null` otherwise, where the other library leaves it out\n # unless asked;\n # * `converter:` a type whose `self.to_json(value, json : Builder)` writes\n # the field and `self.from_json(pull : PullParser)` reads it.\n #\n # An option it does not know, a key that is not a string, and two fields\n # written under one key are refused when the type is compiled.\n annotation Field\n end\n # Text written through a `Builder` the block is handed. `indent` is a\n # count of spaces or the string one level is indented by. A block that\n # writes no value, or leaves a container open, is refused.\n def self.build(indent : String | Int32 | Nil = nil, & : (Builder -> Nil)) : String\n # The document read into `type`: `Nil`, `Bool`, `Int32`, `Int64`, `UInt8`,\n # `UInt64`, `Float64`, `String`, `Any`, a type with `derive serializable`,\n # one of those or `nil`, or an `Array` or `Hash(String, V)` of those. An\n # integer outside the type is refused, as is anything after the root\n # value.\n def self.from_json(str : String, type : T.class) : T forall T\n def initialize\n # The document as an `Any`; panics with the line and column of the first\n # thing it refuses.\n def self.parse(input : String) : Any\n # The document as an `Any`, or `nil` when it is not one.\n def self.parse?(input : String) : Any | ::Nil\n def self.read_value(pull : PullParser, type : Nil.class) : Nil\n def self.read_value(pull : PullParser, type : Bool.class) : Bool\n def self.read_value(pull : PullParser, type : Int32.class) : Int32\n def self.read_value(pull : PullParser, type : Int64.class) : Int64\n def self.read_value(pull : PullParser, type : UInt8.class) : UInt8\n # An integer past Int64 reaches the reader as a float that still carries\n # its digits; those are read here, so the whole UInt64 range comes back.\n def self.read_value(pull : PullParser, type : UInt64.class) : UInt64\n def self.read_value(pull : PullParser, type : Float64.class) : Float64\n def self.read_value(pull : PullParser, type : String.class) : String\n def self.read_value(pull : PullParser, type : Any.class) : Any\n def self.read_value(pull : PullParser, type : Array(T).class) : Array(T) forall T\n def self.read_value(pull : PullParser, type : Hash(String, V).class) : Hash(String, V) forall V\n # A type that reads itself: the `initialize(pull)` a `derive\n # serializable` wrote. A type that admits nil reads `null` as the nil and\n # anything else as the other type (`x || raise`, as the derive asks it),\n # the way `write_or_null` writes it. That was `T.new(pull)` too, and\n # `Int32 | Nil` has no such `new`, so `JSON.from_json(\"5\", Int32?)` and\n # `Array(Int32?)` did not build.\n def self.read_value(pull : PullParser, type : T.class) : T forall T\n # Any value `write_value` knows: `nil`, a `Bool`, a prelude integer, a\n # `Float64`, a `String`, an `Any`, an `Array` or `Hash` of those, or a type\n # that implements `ToJSON`; each of them or `nil`.\n def self.to_json(obj : T) : String forall T\n def self.write_value(val : Nil, b : Builder) : Nil\n def self.write_value(val : Bool, b : Builder) : Nil\n def self.write_value(val : Int32, b : Builder) : Nil\n def self.write_value(val : Int64, b : Builder) : Nil\n def self.write_value(val : UInt8, b : Builder) : Nil\n def self.write_value(val : UInt64, b : Builder) : Nil\n def self.write_value(val : Float64, b : Builder) : Nil\n def self.write_value(val : String, b : Builder) : Nil\n def self.write_value(val : Any, b : Builder) : Nil\n def self.write_value(val : Array(T), b : Builder) : Nil forall T\n # A String key is written as it is, and two of them are never one text.\n def self.write_value(val : Hash(String, V), b : Builder) : Nil forall V\n # Any other key is written by its `to_s`, `nil` as `\"null\"`, the word a\n # JSON null is, so two keys can be one text: `{nil => 1, \"null\" => 2}`\n # was written `{\"null\":1,\"null\":2}`, which `JSON.parse` refuses\n # (\"duplicate key 'null' at line 1, column 11\"). The texts written are\n # kept, and a repeat is refused, naming both keys.\n def self.write_value(val : Hash(K, V), b : Builder) : Nil forall K, V\n # A `ToJSON` value writes itself. An enum member is written as the other\n # library writes one: its name underscored (`Color::DarkRed` is\n # `\"dark_red\"`), and a `@[Flags]` enum as the array of its members'\n # names; a value no member has is written as its number. Told apart here\n # and not by an overload restricted to `Enum`, which a program built\n # from this module's artifact read as a class and could not pass an enum\n # to.\n def self.write_value(val : T, b : Builder) : Nil forall T\nend\n\n# Reads a whole document into an `Any`. Like the lexer it records the first\n# failure instead of panicking; `JSON.parse` and `JSON.parse?` decide what to\n# do with it.\npub class Parser\n def error : String | ::Nil\n def initialize(input : String)\n def parse : Any\nend\n\n# Reads a document one event at a time: `kind` names what the reader is on,\n# the `read_x` methods take it and move on. The grammar is checked as it is\n# read, so a missing comma or colon is refused where it is, with a panic.\npub class PullParser\n enum Kind : Int32\n Null = 0\n Bool = 1\n Int = 2\n Float = 3\n String = 4\n BeginArray = 5\n EndArray = 6\n BeginObject = 7\n EndObject = 8\n EOF = 9\n # Returns `true` if this enum value equals `BeginArray`\n def begin_array? : ::Bool\n # Returns `true` if this enum value equals `BeginObject`\n def begin_object? : ::Bool\n # Returns `true` if this enum value equals `Bool`\n def bool? : ::Bool\n # Returns `true` if this enum value equals `EndArray`\n def end_array? : ::Bool\n # Returns `true` if this enum value equals `EndObject`\n def end_object? : ::Bool\n # Returns `true` if this enum value equals `EOF`\n def eof? : ::Bool\n # Returns `true` if this enum value equals `Float`\n def float? : ::Bool\n # Returns `true` if this enum value equals `Int`\n def int? : ::Bool\n # Returns `true` if this enum value equals `Null`\n def null? : ::Bool\n # Returns `true` if this enum value equals `String`\n def string? : ::Bool\n end\n def bool_value : Bool\n def column_number : Int32\n def float_value : Float64\n def initialize(input : String)\n def int_value : Int64\n def kind : Kind\n def line_number : Int32\n def max_nesting : Int32\n def max_nesting=(value : Int32) : Int32\n # Reads an object, yields once at the value of `key`, and skips the rest.\n def on_key(key : String, & : (-> Nil)) : Nil\n # The number the reader is on, as it was written.\n def raw_value : String\n # The value the reader is on, as an `Any`.\n def read_any : Any\n def read_array(& : (-> Nil)) : Nil\n def read_begin_array : Nil\n def read_begin_object : Nil\n def read_bool : Bool\n def read_end_array : Nil\n def read_end_object : Nil\n # A float, or an integer read as one.\n def read_float : Float64\n def read_int : Int64\n def read_next : Kind\n def read_null : Nil\n def read_null? : Bool\n # Yields each key of the object the reader is on, with the reader at its\n # value. A key named twice is refused, as `JSON.parse` refuses it.\n def read_object(& : (String -> Nil)) : Nil\n def read_object_key : String\n # The value the reader is on, written into `json` as it is read. A number\n # is written as its own text, the text it was read from: rebuilt from the\n # Int64 or Float64 it parsed to, `12345678901234567890` came back as\n # `1.2345678901234567e+19` and `1E+2` as `100.0`, and a UInt64 id passed\n # through here did not read back as itself.\n def read_raw(json : Builder) : Nil\n # The value the reader is on, as JSON text.\n def read_raw : String\n def read_string : String\n # Passes over the value the reader is on, whole.\n def skip : Nil\n def string_value : String\nend\n\n# A type that can write itself into a `Builder`. `JSON.to_json` accepts any\n# value whose type implements it, as a `derive serializable` type does.\npub trait ToJSON\n abstract def to_json(json : Builder) : Nil\nend\n\nimpl ToJSON for Any\n def to_json(json : Builder) : Nil\nend\n" + "sourceSha256": "3ee61ca665e8c34ebf067b55952d565a6aaabc6b0282daa11b7901532a319703", + "surface": "module std/json\n\n# `derive serializable`, in a struct or class whose fields are written with\n# their types: the type reads and writes itself as a JSON object, a key per\n# field, named as the field is.\n#\n# pub struct Coord\n# getter x : Float64\n# @[JSON::Field(key: \"label\")]\n# getter name : String\n# derive serializable\n# end\n#\n# Coord.from_json(%({\"x\": 1.5, \"label\": \"a\"})).x # => 1.5\n# coord.to_json # => {\"x\":1.5,\"label\":\"a\"}\n#\n# A field's value is read and written as `JSON.read_value` and\n# `JSON.write_value` read and write its type: the built-in values, an enum,\n# an `Array` or `Hash(String, V)` of them, an `Any`, another serializable\n# type, a union of those (`String | Int64`). A key the type has no field\n# for is passed over; a field with no key keeps its default when it is\n# written with one, as does a field with a default whose key holds `null`\n# where its type does not admit nil; a field with neither is refused by\n# name unless its type admits `nil`, which it then holds.\n# `@[JSON::Field(...)]` renames a field, leaves it out either way, leaves\n# a nil one out, or reads and writes it through a converter (see\n# `JSON::Field`). It is what the other library's `include\n# JSON::Serializable` writes, and the derive runs once, in the type's own\n# module (SPEC.md II.4), so a consumer calls the methods without the macro.\n#\n# Each field's options are read off the type when its methods are compiled,\n# in one `begin` so what a field's annotation says is known to every line\n# after it; and refused there, with the field named, when they do not make\n# sense.\npub macro serializable(declaration)\n\n# One JSON value of any kind, the result of `JSON.parse`. The `as_x` methods\n# panic when the value is of another kind; the `as_x?` ones answer `nil`.\npub struct Any\n alias Type = Nil | Bool | Int64 | Float64 | String | Array(Any) | Hash(String, Any)\n # Equal when the values are: an integer equals the float with the same\n # value, arrays element by element, objects key by key in any order.\n def ==(other : Any) : Bool\n def [](index : Int32) : Any\n def [](key : String) : Any\n def []?(index : Int32) : Any | ::Nil\n def []?(key : String) : Any | ::Nil\n def as_a : Array(Any)\n def as_a? : Array(Any) | ::Nil\n def as_bool : Bool\n def as_bool? : Bool | ::Nil\n # A float, or an integer read as one.\n def as_f : Float64\n def as_f? : Float64 | ::Nil\n def as_h : Hash(String, Any)\n def as_h? : Hash(String, Any) | ::Nil\n def as_i : Int32\n def as_i64 : Int64\n def as_i64? : Int64 | ::Nil\n def as_i? : Int32 | ::Nil\n def as_nil : Nil\n def as_s : String\n def as_s? : String | ::Nil\n def dig(key : String) : Any\n def dig(index : Int32) : Any\n def dig(k : String, *subkeys : String | Int32) : Any\n def dig(idx : Int32, *subkeys : String | Int32) : Any\n def dig?(key : String) : Any | ::Nil\n def dig?(index : Int32) : Any | ::Nil\n def dig?(k : String, *subkeys : String | Int32) : Any | ::Nil\n def dig?(idx : Int32, *subkeys : String | Int32) : Any | ::Nil\n protected def float_value? : Float64 | ::Nil\n # Consistent with `==`: a whole float hashes as the integer it equals, an\n # object's hash does not depend on the order its keys were written in.\n def hash : Int32\n def initialize(raw : Type)\n def inspect : String\n # The kind of value this is, in the word a refusal uses: `null`, `bool`,\n # `integer`, `float`, `string`, `array` or `object`.\n def kind : String\n def self.new(val : Int32) : Any\n def raw : Type\n # How many elements an array or an object holds. A scalar has no size.\n def size : Int32\n def size? : Int32 | ::Nil\n def to_ascii_json : String\n def to_json : String\n def to_pretty_json(indent : String = \" \") : String\n # A string as itself and null as nothing, as `YAML::Any#to_s` and the\n # other library answer them; a number, a boolean or a collection as its\n # JSON text. A string was its JSON text too, so `\"hello #{doc[\"name\"]}\"`\n # printed `hello \"Ada\"`, and null printed `null`.\n def to_s : String\nend\n\n# Writes a document one value at a time. `indent=` makes it pretty;\n# `ascii_only=` escapes every non-ASCII code point.\npub class Builder\n def array(& : (-> Nil)) : Nil\n def ascii_only : Bool\n def ascii_only=(value : Bool) : Bool\n def bool(val : Bool) : Nil\n # Whether no value has been written yet.\n def empty? : Bool\n def end_array : Nil\n def end_object : Nil\n def field(name : String, val : Any) : Nil\n def field(name : String, val : String) : Nil\n def field(name : String, val : Int32) : Nil\n def field(name : String, val : Int64) : Nil\n def field(name : String, val : Float64) : Nil\n def field(name : String, val : Bool) : Nil\n def field(name : String, val : Nil) : Nil\n # A key; the value that follows it is written by the next call. A key\n # whose text is one this object already holds is refused, naming both:\n # the reader refuses a duplicate key, and the builder wrote one - a key\n # given twice, or two Strings whose bytes are not UTF-8 and are both\n # written as U+FFFD (`{\"a\\xFF\" => 1, \"a\\xFE\" => 2}` was written\n # `{\"a\\uFFFD\":1,\"a\\uFFFD\":2}`, and `JSON.parse` refused it).\n def field(name : String) : Nil\n def field(name : String, & : (-> Nil)) : Nil\n def indent=(spaces : Int32) : Nil\n def indent=(str : String) : Nil\n def initialize\n def max_nesting : Int32\n def max_nesting=(value : Int32) : Int32\n def null : Nil\n def number(val : Int32) : Nil\n def number(val : Int64) : Nil\n def number(val : UInt8) : Nil\n def number(val : UInt64) : Nil\n def number(val : Float64) : Nil\n def object(& : (-> Nil)) : Nil\n # Text written as it is: the caller's own JSON.\n def raw(str : String) : Nil\n def start_array : Nil\n def start_object : Nil\n def string(val : String) : Nil\n # The document's text. A container still open or no value written at all\n # is not a document, and is refused.\n def to_s : String\nend\n\npub struct JSON\n # How `derive serializable` writes and reads one field, the other\n # library's `JSON::Field`:\n #\n # @[JSON::Field(key: \"userName\")]\n # getter user_name : String\n #\n # * `key:` the object key, where it is not the field's own name;\n # * `ignore: true` neither written nor read, `ignore_serialize: true` not\n # written, `ignore_deserialize: true` not read - a field that is not\n # read keeps its default, or is nil, and needs one or the other;\n # * `emit_null: false` a nil field left out of the object. A nil field is\n # written as `null` otherwise, where the other library leaves it out\n # unless asked;\n # * `converter:` a type whose `self.to_json(value, json : Builder)` writes\n # the field and `self.from_json(pull : PullParser)` reads it.\n #\n # An option it does not know, a key that is not a string, and two fields\n # written under one key are refused when the type is compiled.\n annotation Field\n end\n # Text written through a `Builder` the block is handed. `indent` is a\n # count of spaces or the string one level is indented by. A block that\n # writes no value, or leaves a container open, is refused.\n def self.build(indent : String | Int32 | Nil = nil, & : (Builder -> Nil)) : String\n # The document read into `type`: `Nil`, `Bool`, `Int32`, `Int64`, `UInt8`,\n # `UInt64`, `Float64`, `String`, `Any`, an enum, a type with `derive\n # serializable`, a union of them as `read_union` reads one, or an `Array`\n # or `Hash(String, V)` of those. An integer outside the type is refused,\n # as is anything after the root value.\n def self.from_json(str : String, type : T.class) : T forall T\n def initialize\n # The document as an `Any`; panics with the line and column of the first\n # thing it refuses.\n def self.parse(input : String) : Any\n # The document as an `Any`, or `nil` when it is not one.\n def self.parse?(input : String) : Any | ::Nil\n def self.read_value(pull : PullParser, type : Nil.class) : Nil\n def self.read_value(pull : PullParser, type : Bool.class) : Bool\n def self.read_value(pull : PullParser, type : Int32.class) : Int32\n def self.read_value(pull : PullParser, type : Int64.class) : Int64\n def self.read_value(pull : PullParser, type : UInt8.class) : UInt8\n # An integer past Int64 reaches the reader as a float that still carries\n # its digits; those are read here, so the whole UInt64 range comes back.\n def self.read_value(pull : PullParser, type : UInt64.class) : UInt64\n def self.read_value(pull : PullParser, type : Float64.class) : Float64\n def self.read_value(pull : PullParser, type : String.class) : String\n def self.read_value(pull : PullParser, type : Any.class) : Any\n def self.read_value(pull : PullParser, type : Array(T).class) : Array(T) forall T\n def self.read_value(pull : PullParser, type : Hash(String, V).class) : Hash(String, V) forall V\n # A type that reads itself: the `initialize(pull)` a `derive\n # serializable` wrote. A type that admits nil reads `null` as the nil and\n # anything else as the other type (`x || raise`, as the derive asks it),\n # the way `write_or_null` writes it. That was `T.new(pull)` too, and\n # `Int32 | Nil` has no such `new`, so `JSON.from_json(\"5\", Int32?)` and\n # `Array(Int32?)` did not build. Any other union is `read_union`'s.\n def self.read_value(pull : PullParser, type : T.class) : T forall T\n # Any value `write_value` knows: `nil`, a `Bool`, a prelude integer, a\n # `Float64`, a `String`, an `Any`, an enum, an `Array` or `Hash` of those,\n # or a type that implements `ToJSON`; a union of them, written as the\n # member it holds is.\n def self.to_json(obj : T) : String forall T\n def self.write_value(val : Nil, b : Builder) : Nil\n def self.write_value(val : Bool, b : Builder) : Nil\n def self.write_value(val : Int32, b : Builder) : Nil\n def self.write_value(val : Int64, b : Builder) : Nil\n def self.write_value(val : UInt8, b : Builder) : Nil\n def self.write_value(val : UInt64, b : Builder) : Nil\n def self.write_value(val : Float64, b : Builder) : Nil\n def self.write_value(val : String, b : Builder) : Nil\n def self.write_value(val : Any, b : Builder) : Nil\n def self.write_value(val : Array(T), b : Builder) : Nil forall T\n # A String key is written as it is, and two of them are never one text.\n def self.write_value(val : Hash(String, V), b : Builder) : Nil forall V\n # Any other key is written by its `to_s`, `nil` as `\"null\"`, the word a\n # JSON null is, so two keys can be one text: `{nil => 1, \"null\" => 2}`\n # was written `{\"null\":1,\"null\":2}`, which `JSON.parse` refuses\n # (\"duplicate key 'null' at line 1, column 11\"). The texts written are\n # kept, and a repeat is refused, naming both keys.\n def self.write_value(val : Hash(K, V), b : Builder) : Nil forall K, V\n # A `ToJSON` value writes itself. An enum member is written as the other\n # library writes one: its name underscored (`Color::DarkRed` is\n # `\"dark_red\"`), and a `@[Flags]` enum as the array of its members'\n # names; a value no member has is written as its number. Told apart here\n # and not by an overload restricted to `Enum`, which a program built\n # from this module's artifact read as a class and could not pass an enum\n # to.\n #\n # A union is matched here whole, `T` the union itself, so\n # `JSON.to_json(1.as(Int32 | String))` called `to_json` on an `Int32`\n # and did not build (issue #135). A member with an overload of its own,\n # an `Int32` or an `Array`, is sent there when the program runs, yet this\n # is still compiled for the whole union, and reached for the rest: an\n # enum, a `ToJSON` type. Each is written as it is alone, by\n # `Object#__json_write` below.\n def self.write_value(val : T, b : Builder) : Nil forall T\nend\n\n# Reads a whole document into an `Any`. Like the lexer it records the first\n# failure instead of panicking; `JSON.parse` and `JSON.parse?` decide what to\n# do with it.\npub class Parser\n def error : String | ::Nil\n def initialize(input : String)\n def parse : Any\nend\n\n# Reads a document one event at a time: `kind` names what the reader is on,\n# the `read_x` methods take it and move on. The grammar is checked as it is\n# read, so a missing comma or colon is refused where it is, with a panic.\npub class PullParser\n enum Kind : Int32\n Null = 0\n Bool = 1\n Int = 2\n Float = 3\n String = 4\n BeginArray = 5\n EndArray = 6\n BeginObject = 7\n EndObject = 8\n EOF = 9\n # Returns `true` if this enum value equals `BeginArray`\n def begin_array? : ::Bool\n # Returns `true` if this enum value equals `BeginObject`\n def begin_object? : ::Bool\n # Returns `true` if this enum value equals `Bool`\n def bool? : ::Bool\n # Returns `true` if this enum value equals `EndArray`\n def end_array? : ::Bool\n # Returns `true` if this enum value equals `EndObject`\n def end_object? : ::Bool\n # Returns `true` if this enum value equals `EOF`\n def eof? : ::Bool\n # Returns `true` if this enum value equals `Float`\n def float? : ::Bool\n # Returns `true` if this enum value equals `Int`\n def int? : ::Bool\n # Returns `true` if this enum value equals `Null`\n def null? : ::Bool\n # Returns `true` if this enum value equals `String`\n def string? : ::Bool\n end\n def bool_value : Bool\n def column_number : Int32\n def float_value : Float64\n def initialize(input : String)\n def int_value : Int64\n def kind : Kind\n def line_number : Int32\n def max_nesting : Int32\n def max_nesting=(value : Int32) : Int32\n # Reads an object, yields once at the value of `key`, and skips the rest.\n def on_key(key : String, & : (-> Nil)) : Nil\n # The number the reader is on, as it was written.\n def raw_value : String\n # The value the reader is on, as an `Any`.\n def read_any : Any\n def read_array(& : (-> Nil)) : Nil\n def read_begin_array : Nil\n def read_begin_object : Nil\n def read_bool : Bool\n def read_end_array : Nil\n def read_end_object : Nil\n # A float, or an integer read as one.\n def read_float : Float64\n def read_int : Int64\n def read_next : Kind\n def read_null : Nil\n def read_null? : Bool\n # Yields each key of the object the reader is on, with the reader at its\n # value. A key named twice is refused, as `JSON.parse` refuses it.\n def read_object(& : (String -> Nil)) : Nil\n def read_object_key : String\n # The value the reader is on, written into `json` as it is read. A number\n # is written as its own text, the text it was read from: rebuilt from the\n # Int64 or Float64 it parsed to, `12345678901234567890` came back as\n # `1.2345678901234567e+19` and `1E+2` as `100.0`, and a UInt64 id passed\n # through here did not read back as itself.\n def read_raw(json : Builder) : Nil\n # The value the reader is on, as JSON text.\n def read_raw : String\n def read_string : String\n # Passes over the value the reader is on, whole.\n def skip : Nil\n def string_value : String\nend\n\n# A type that can write itself into a `Builder`. `JSON.to_json` accepts any\n# value whose type implements it, as a `derive serializable` type does.\npub trait ToJSON\n abstract def to_json(json : Builder) : Nil\nend\n\nimpl ToJSON for Any\n def to_json(json : Builder) : Nil\nend\n\nclass ::Object\n def __json_write(json : ::Std::Json::Builder) : Nil\nend\n" }, { "name": "std/kernel", @@ -328,20 +346,20 @@ { "name": "std/log", "path": "src/std/log.iyi", - "sourceSha256": "8aabd06e381d1e9e121f042ff38fe26c6548d7a22d96b8f78612a52fd22e3c7f", - "surface": "module std/log\n\n# Entries written by a thread of their own, so a call that logs does not\n# wait for the write: the entry is formatted where it is logged and\n# queued, and a writer thread writes what has queued up since its last\n# write, in order, in one write, to *io*'s descriptor.\n#\n# AsyncBackend.open do |backend| # closed, and drained, at the end\n# Log.setup(Severity::Info, backend)\n# ...\n# end\n#\n# `close` waits for every queued entry to be written and the thread to\n# end; an entry written after it is refused. Entries queued when a program\n# ends without `close` are lost, which the block form rules out. The\n# backend is the thread that made it's, as every object not Share is.\n#\n# A thread needs the collector and a target with threads: under\n# `-Dgc_none` or `-Dgc_boehm`, or on another target, `new` refuses.\npub class AsyncBackend < Backend\n def close : Nil\n def formatter : Formatter\n def formatter=(value : Formatter) : Formatter\n def initialize(io : IO = STDOUT, formatter : Formatter = DefaultFormatter.new)\n # A backend for the block, closed and drained when the block ends.\n def self.open(io : IO = STDOUT, formatter : Formatter = DefaultFormatter.new, &block : (AsyncBackend -> R)) : R forall R\n def write(entry : Entry) : Nil\nend\n\npub abstract class Backend\n abstract def close : Nil\n abstract def write(entry : Entry) : Nil\nend\n\npub class DefaultFormatter < Formatter\n def format(entry : Entry) : String\nend\n\npub struct Entry\n # The task's context, the logger's bound data and the call's own, in\n # that order, a later key's value over an earlier's.\n def data : Hash(String, String)\n def initialize(source : String, severity : Severity, message : String, timestamp : Time = Time.utc, data : Hash(String, String) = {} of String => String)\n def message : String\n def severity : Severity\n def source : String\n def timestamp : Time\nend\n\npub abstract class Formatter\n def format(entry : Entry, io : IO) : Nil\n abstract def format(entry : Entry) : String\nend\n\npub class IOBackend < Backend\n def close : Nil\n def formatter : Formatter\n def formatter=(value : Formatter) : Formatter\n def initialize(io : IO, formatter : Formatter = DefaultFormatter.new)\n def io : IO\n def write(entry : Entry) : Nil\nend\n\n# The root logger and the calls that go straight to it.\npub class Log\n def self.backend : Backend | ::Nil\n def self.backend=(backend : Backend | ::Nil) : Nil\n # The data this task's open contexts give an entry, the outermost first.\n def self.context : Hash(String, String)\n def self.debug(msg : String, **data) : Nil\n def self.debug(**data, &b : (-> String)) : Nil\n def self.error(msg : String, **data) : Nil\n def self.error(**data, &b : (-> String)) : Nil\n def self.fatal(msg : String, **data) : Nil\n def self.fatal(**data, &b : (-> String)) : Nil\n def self.for(source : String) : Logger\n def self.info(msg : String, **data) : Nil\n def self.info(**data, &b : (-> String)) : Nil\n def self.level : Severity\n def self.level=(level : Severity) : Nil\n def self.notice(msg : String, **data) : Nil\n def self.notice(**data, &b : (-> String)) : Nil\n def self.root : Logger\n # Reconfigures the root in place, so loggers obtained earlier follow.\n def self.setup(level : Severity = Severity::Info, backend : Backend = IOBackend.new(STDOUT)) : Logger\n # `setup`, the level read from *variable* - a severity's name, any case\n # - or *default_level* when it is unset or empty. A name that is no\n # severity is refused, the severities named.\n def self.setup_from_env(default_level : Severity = Severity::Info, backend : Backend = IOBackend.new(STDOUT), variable : String = \"LOG_LEVEL\") : Logger\n def self.trace(msg : String, **data) : Nil\n def self.trace(**data, &b : (-> String)) : Nil\n def self.warn(msg : String, **data) : Nil\n def self.warn(**data, &b : (-> String)) : Nil\n # Runs the block with *data* on every entry logged in this task and in\n # the tasks it starts, over the context already open, and answers the\n # block's answer. The context before it is back when the block ends,\n # panic or not.\n def self.with_context(**data, &block : (-> R)) : R forall R\nend\n\npub class Logger\n # This logger's own backend, or the nearest ancestor's; nil silences.\n def backend : Backend | ::Nil\n def backend=(b : Backend | ::Nil) : Nil\n # A logger for the same source, its level and backend this one's, whose\n # entries all carry *data*: a request's id, a connection's peer.\n def bind(**data) : Logger\n # The data `bind` gave this logger and its parents, the root's first.\n protected def bound_into(found : Hash(String, String)) : Nil\n def debug(message : String, **data) : Nil\n def debug(**data, &b : (-> String)) : Nil\n def debug? : Bool\n def error(message : String, **data) : Nil\n def error(**data, &b : (-> String)) : Nil\n def error? : Bool\n def fatal(message : String, **data) : Nil\n def fatal(**data, &b : (-> String)) : Nil\n def fatal? : Bool\n # The logger for *child_name* under this one, created on first ask;\n # `\"a.b\"` is the child `b` of the child `a`.\n def for(child_name : String) : Logger\n protected def get_or_create_child(name : String) : Logger\n def info(message : String, **data) : Nil\n def info(**data, &b : (-> String)) : Nil\n def info? : Bool\n def initialize(source : String, level : Severity | ::Nil = nil, backend : Backend | ::Nil = nil, parent : Logger | ::Nil = nil, bound : Metadata | ::Nil = nil)\n # This logger's own level, or the nearest ancestor's; Info at a root\n # never given one.\n def level : Severity\n # nil hands the decision back to the parent.\n def level=(lvl : Severity | ::Nil) : Nil\n # None is a level and not a severity: an entry logged at None was\n # written by a logger set to None, the one level meant to write nothing.\n def log(severity : Severity, message : String, **data) : Nil\n def log(severity : Severity, **data, &block : (-> String)) : Nil\n def notice(message : String, **data) : Nil\n def notice(**data, &b : (-> String)) : Nil\n def notice? : Bool\n def own_backend : Backend | ::Nil\n def own_level : Severity | ::Nil\n def source : String\n def trace(message : String, **data) : Nil\n def trace(**data, &b : (-> String)) : Nil\n def trace? : Bool\n def warn(message : String, **data) : Nil\n def warn(**data, &b : (-> String)) : Nil\n def warn? : Bool\nend\n\npub class MemoryBackend < Backend\n def clear : Nil\n def close : Nil\n def entries : Array(Entry)\n def formatter : Formatter\n def formatter=(value : Formatter) : Formatter\n def initialize(formatter : Formatter = DefaultFormatter.new)\n # The entries as the formatter would have written them.\n def output : Array(String)\n def write(entry : Entry) : Nil\nend\n\n# Keys and values, the newest first: immutable, so a task's context can sit\n# in the table every thread reads.\npub class Metadata\n def initialize(key : String, value : String, rest : Metadata | ::Nil)\n def key : String\n # Into *into*, the oldest first, so a later key's value replaces an\n # earlier one's.\n def merge_into(into : Hash(String, String)) : Nil\n def rest : Metadata | ::Nil\n def to_h : Hash(String, String)\n def value : String\nend\n\npub enum Severity : Int32\n Trace = 0\n Debug = 1\n Info = 2\n Notice = 3\n Warn = 4\n Error = 5\n Fatal = 6\n None = 7\n def <(other : Severity) : Bool\n def <=(other : Severity) : Bool\n def >(other : Severity) : Bool\n def >=(other : Severity) : Bool\n def label : String\n def self.parse(name : String) : Severity\n # The severity named by *name*, case-insensitively, or nil: what a\n # `LOG_LEVEL` variable holds.\n def self.parse?(name : String) : Severity | ::Nil\n def to_s : String\nend\n" + "sourceSha256": "8c07afe8a92a47cd266230ef3c62fe4613c17f7870946f3526abdfc2a9389af7", + "surface": "module std/log\n\n# Entries written by a thread of their own, so a call that logs does not\n# wait for the write: the entry is formatted where it is logged and\n# queued, and a writer thread writes what has queued up since its last\n# write, in order, in one write, to *io*'s descriptor.\n#\n# AsyncBackend.open do |backend| # closed, and drained, at the end\n# Log.setup(Severity::Info, backend)\n# ...\n# end\n#\n# `close` waits for every queued entry to be written and the thread to\n# end; an entry written after it is refused. Entries queued when a program\n# ends without `close` are lost, which the block form rules out. Any\n# thread may write to the backend, and close it: it is Share, as every\n# backend is.\n#\n# A thread needs the collector and a target with threads: under\n# `-Dgc_none` or `-Dgc_boehm`, or on another target, `new` refuses.\npub class AsyncBackend < Backend\n def close : Nil\n def formatter : Formatter\n def initialize(io : IO = STDOUT, formatter : Formatter = DefaultFormatter.new)\n # A backend for the block, closed and drained when the block ends.\n def self.open(io : IO = STDOUT, formatter : Formatter = DefaultFormatter.new, &block : (AsyncBackend -> R)) : R forall R\n def write(entry : Entry) : Nil\n # The thread writing what *queue* holds to *handle* until the queue is\n # closed and drained; marking the queue finished is the last it does\n # under the lock.\n protected def self.writer(queue : Mutex(AsyncQueue), handle : Int64) : IyiThread\nend\n\npub abstract class Backend\n abstract def close : Nil\n abstract def write(entry : Entry) : Nil\nend\n\npub class DefaultFormatter < Formatter\n def format(entry : Entry) : String\nend\n\npub struct Entry\n # The data, as a hash of the caller's own.\n def data : Hash(String, String)\n def initialize(source : String, severity : Severity, message : String, timestamp : Time = Time.utc, data : Hash(String, String) = {} of String => String)\n def message : String\n # The task's context, the logger's bound data and the call's own, in\n # that order, a later key's value over an earlier's in the earlier's\n # place, as a `Hash` keeps them. Kept as pairs, which nothing writes once\n # the entry is made, so an entry is `Share` and goes into a\n # `MemoryBackend`'s lock from any thread; `data` is a hash of them.\n def pairs : List(::Tuple(String, String))\n protected def self.pairs_of(data : Hash(String, String)) : List(::Tuple(String, String))\n def severity : Severity\n def source : String\n def timestamp : Time\nend\n\npub abstract class Formatter\n abstract def format(entry : Entry) : String\nend\n\n# Each entry as one line to an IO: formatted in the calling thread, then\n# written with its newline holding the backend's lock, so the lines of\n# workers logging at once come out whole, never one inside another.\n# Twelve workers writing 100,000 lines each to one file through it as it\n# was, writing to the IO itself with no lock, left 2,563 to 4,378 of the\n# 1.2 million torn or lost in five runs, at 1,008 ns a line between them;\n# through it locked none, at 864 (--release, x86_64 Linux, 12 cores).\n#\n# The `IO` handed to `new` is not Share - its buffer is written in place,\n# and two threads writing one tear what they write - so the backend keeps\n# a handle of its own on the IO's descriptor behind its `Mutex`, as\n# `AsyncBackend`'s thread does, and flushes it after every line. What the\n# program writes to that IO itself goes out when the IO flushes, which\n# STDOUT and STDERR do at every write.\npub class IOBackend < Backend\n # Nothing is held back: each line was written as it came.\n def close : Nil\n def formatter : Formatter\n def initialize(io : IO, formatter : Formatter = DefaultFormatter.new)\n # The line and its end into the buffer, then out in one write.\n protected def self.put(io : IyiIO, line : String) : Nil\n def write(entry : Entry) : Nil\nend\n\n# The root logger and the calls that go straight to it.\npub class Log\n def self.backend : Backend | ::Nil\n def self.backend=(backend : Backend | ::Nil) : Nil\n # The data this task's open contexts give an entry, the outermost first.\n def self.context : Hash(String, String)\n def self.debug(msg : String, **data) : Nil\n def self.debug(**data, &b : (-> String)) : Nil\n def self.error(msg : String, **data) : Nil\n def self.error(**data, &b : (-> String)) : Nil\n def self.fatal(msg : String, **data) : Nil\n def self.fatal(**data, &b : (-> String)) : Nil\n def self.for(source : String) : Logger\n def self.info(msg : String, **data) : Nil\n def self.info(**data, &b : (-> String)) : Nil\n def self.level : Severity\n def self.level=(level : Severity) : Nil\n def self.notice(msg : String, **data) : Nil\n def self.notice(**data, &b : (-> String)) : Nil\n def self.root : Logger\n # Reconfigures the root in place, so loggers obtained earlier follow.\n def self.setup(level : Severity = Severity::Info, backend : Backend = IOBackend.new(STDOUT)) : Logger\n # `setup`, the level read from *variable* - a severity's name, any case\n # - or *default_level* when it is unset or empty. A name that is no\n # severity is refused, the severities named.\n def self.setup_from_env(default_level : Severity = Severity::Info, backend : Backend = IOBackend.new(STDOUT), variable : String = \"LOG_LEVEL\") : Logger\n def self.trace(msg : String, **data) : Nil\n def self.trace(**data, &b : (-> String)) : Nil\n def self.warn(msg : String, **data) : Nil\n def self.warn(**data, &b : (-> String)) : Nil\n # Runs the block with *data* on every entry logged in this task and in\n # the tasks it starts, over the context already open, and answers the\n # block's answer. The context before it is back when the block ends,\n # panic or not.\n def self.with_context(**data, &block : (-> R)) : R forall R\nend\n\npub class Logger\n # This logger's own backend, or the nearest ancestor's; nil silences.\n def backend : Backend | ::Nil\n def backend=(b : Backend | ::Nil) : Nil\n # A logger for the same source, its level and backend this one's, whose\n # entries all carry *data*: a request's id, a connection's peer.\n def bind(**data) : Logger\n # The data `bind` gave this logger and its parents, the root's first.\n protected def bound_into(found : Hash(String, String)) : Nil\n def debug(message : String, **data) : Nil\n def debug(**data, &b : (-> String)) : Nil\n def debug? : Bool\n def error(message : String, **data) : Nil\n def error(**data, &b : (-> String)) : Nil\n def error? : Bool\n def fatal(message : String, **data) : Nil\n def fatal(**data, &b : (-> String)) : Nil\n def fatal? : Bool\n # The logger for *child_name* under this one, created on first ask;\n # `\"a.b\"` is the child `b` of the child `a`.\n def for(child_name : String) : Logger\n protected def get_or_create_child(name : String) : Logger\n def info(message : String, **data) : Nil\n def info(**data, &b : (-> String)) : Nil\n def info? : Bool\n def initialize(source : String, level : Severity | ::Nil = nil, backend : Backend | ::Nil = nil, parent : Logger | ::Nil = nil, bound : Metadata | ::Nil = nil)\n # This logger's own level, or the nearest ancestor's; Info at a root\n # never given one.\n def level : Severity\n # nil hands the decision back to the parent.\n def level=(lvl : Severity | ::Nil) : Nil\n protected def level_value : Int32\n # None is a level and not a severity: an entry logged at None was\n # written by a logger set to None, the one level meant to write nothing.\n def log(severity : Severity, message : String, **data) : Nil\n def log(severity : Severity, **data, &block : (-> String)) : Nil\n def notice(message : String, **data) : Nil\n def notice(**data, &b : (-> String)) : Nil\n def notice? : Bool\n def own_backend : Backend | ::Nil\n def own_level : Severity | ::Nil\n protected def own_level_value : Int32\n protected def parent : Logger | ::Nil\n def source : String\n def trace(message : String, **data) : Nil\n def trace(**data, &b : (-> String)) : Nil\n def trace? : Bool\n def warn(message : String, **data) : Nil\n def warn(**data, &b : (-> String)) : Nil\n def warn? : Bool\nend\n\n# The entries, kept for a test to read back, behind a lock: the workers of\n# a parallel test may all log to one.\npub class MemoryBackend < Backend\n def clear : Nil\n def close : Nil\n # The entries written so far, oldest first: a copy, since the backend's\n # own are behind its lock, where another thread may be adding one.\n def entries : Array(Entry)\n def formatter : Formatter\n def initialize(formatter : Formatter = DefaultFormatter.new)\n # The entries as the formatter would have written them.\n def output : Array(String)\n def write(entry : Entry) : Nil\nend\n\n# Keys and values, the newest first: immutable, so a task's context can sit\n# in the table every thread reads.\npub class Metadata\n def initialize(key : String, value : String, rest : Metadata | ::Nil)\n def key : String\n # Into *into*, the oldest first, so a later key's value replaces an\n # earlier one's.\n def merge_into(into : Hash(String, String)) : Nil\n def rest : Metadata | ::Nil\n def to_h : Hash(String, String)\n def value : String\nend\n\npub enum Severity : Int32\n Trace = 0\n Debug = 1\n Info = 2\n Notice = 3\n Warn = 4\n Error = 5\n Fatal = 6\n None = 7\n def <(other : Severity) : Bool\n def <=(other : Severity) : Bool\n def >(other : Severity) : Bool\n def >=(other : Severity) : Bool\n def label : String\n def self.parse(name : String) : Severity\n # The severity named by *name*, case-insensitively, or nil: what a\n # `LOG_LEVEL` variable holds.\n def self.parse?(name : String) : Severity | ::Nil\n def to_s : String\nend\n" }, { "name": "std/mail", "path": "src/std/mail.iyi", - "sourceSha256": "ed9455aa36ceb50d7b93788cf2a0110bc59c2c9bad38b8fe26bab97a497d0fb5", - "surface": "module std/mail\n\npub struct Mail\n # A mailbox: an address and the name shown with it.\n class Address\n def ==(other : Address) : Bool\n # The addr-spec: the local part quoted where it is not a dot-atom.\n def address : String\n def domain : String\n # What `parse` makes, already read.\n def initialize(local : String, domain : String, name : String, read : Bool)\n # *address* is an addr-spec: `user@example.com`, or\n # `\"john doe\"@example.com` with its local part quoted. Anything else\n # is a panic, as is a name with a line break in it.\n def initialize(address : String, name : String = \"\")\n # The local part, unquoted, and the domain as written.\n def local : String\n # The display name, decoded; \"\" when there is none.\n def name : String\n # The one mailbox *text* holds.\n def self.parse(text : String) : Address | MailError\n # The mailboxes of an address list, a group's members in its place.\n def self.parse_list(text : String) : Array(Address) | MailError\n # As a person reads it: `Name `, the name quoted\n # when it is not atoms, nothing encoded.\n def to_s : String\n # Whether the address is more than ASCII (RFC 6532), which only a\n # server that offers SMTPUTF8 takes.\n def utf8? : Bool\n end\n # A message to be written: its addresses, subject, date and identifier,\n # its text and HTML, and its attachments.\n class Message\n protected def self.addresses(name : String, list : Array(Address)) : String\n # A file sent with the message, under *filename*, as *content_type*:\n # the type its extension names (std/mime) when nil.\n def attach(filename : String, data : String, content_type : String | ::Nil = nil) : self\n protected def self.attachment(filename : String, kind : String, data : String) : MailEntity\n def bcc : Array(Address)\n def cc : Array(Address)\n def date : String | ::Nil\n # An RFC 5322 date-time - `Mail.date` writes one - refused when it is\n # not one.\n def date=(value : String) : String\n # `filename=\"...\"` for a short name of printable ASCII; RFC 2231's\n # `filename*=utf-8''...` for any other, in continuations of whole\n # characters when it is longer - 50 encoded bytes in the first, after\n # its charset, and 60 in the rest, so each fits a line. All but the\n # last end in `;`.\n protected def self.filename_params(filename : String) : Array(String)\n def from : Address | ::Nil\n def from=(value : Address | ::Nil) : Address | ::Nil\n # A header of the caller's own, written as unstructured text. The\n # headers the message writes itself are refused here.\n def header(name : String, value : String) : self\n def html : String | ::Nil\n def html=(value : String) : String\n def initialize\n def message_id : String | ::Nil\n # ``, the brackets added when they are not there.\n def message_id=(value : String) : String\n # The parts under one boundary, made from their own hash.\n protected def self.multipart(subtype : String, children : Array(MailEntity)) : MailEntity\n # Every address the message goes to - To, Cc and Bcc - once each.\n def recipients : Array(String)\n def reply_to : Array(Address)\n # ASCII without NUL, in lines of 78 or fewer.\n protected def self.seven_bit?(text : String) : Bool\n def subject : String | ::Nil\n def subject=(value : String) : String\n def text : String | ::Nil\n def text=(value : String) : String\n protected def self.text_part(subtype : String, text : String) : MailEntity\n def to : Array(Address)\n # The message, CRLF line breaks and all.\n def to_s : String\n # Whether an address in the message is RFC 6532's UTF-8.\n def utf8? : Bool\n end\n # A message or one of its parts, as read: its header fields unfolded and\n # in order, and its body - a multipart's parts, a `message/rfc822`'s\n # message, or bytes in a transfer encoding.\n class Part\n # The mailboxes of every field named *name* - `From`, `To`, `Cc` -\n # none when there is no such field.\n def addresses(name : String) : Array(Address) | MailError\n def attachment? : Bool\n # The parts whose disposition is `attachment`, in order.\n def attachments : Array(Part)\n # The bytes the body stands for, its transfer encoding undone.\n def body : String | MailError\n # The charset text is in: `us-ascii` when none is named (RFC 2045\n # section 5.2), in lower case.\n def charset : String\n # The disposition in lower case - `attachment`, `inline` - or nil.\n def disposition : String | ::Nil\n # The file name of Content-Disposition, or else Content-Type's `name`.\n def filename : String | ::Nil\n # The first part of *media_type* that is not an attachment, depth\n # first: `find(\"text/plain\")` is the text a reader shows.\n def find(media_type : String) : Part | ::Nil\n protected def self.first(headers : Array(::Tuple(String, String)), lower : String) : String | ::Nil\n # The first field named *name*, any case, its encoded-words decoded.\n def header(name : String) : String | ::Nil\n # Every field named *name*, in order, decoded.\n def header_values(name : String) : Array(String)\n # Each field's name as written and its value unfolded, whitespace at\n # either end dropped, nothing decoded.\n def headers : Array(::Tuple(String, String))\n def initialize(source : String, headers : Array(::Tuple(String, String)), media_type : String, params : Hash(String, String), body_at : Int32, body_end : Int32, parts : Array(Part))\n # `type/subtype` in lower case: `text/plain` when the part names none,\n # or names one that cannot be read (RFC 2045 section 5.2).\n def media_type : String\n def multipart? : Bool\n # A Content-Type parameter, RFC 2231's decoded.\n def param(name : String) : String | ::Nil\n def self.parse(text : String) : Part | MailError\n # The parts of a multipart, or the message a `message/rfc822` holds.\n def parts : Array(Part)\n protected def self.prefix?(a : Pointer(UInt8), b : Pointer(UInt8), size : Int32) : Bool\n # The first field named *name*, as written.\n def raw_header(name : String) : String | ::Nil\n protected def self.read(source : String, start : Int32, stop : Int32, depth : Int32, default_type : String) : Part | MailError\n # The parts of a multipart body: between `--boundary` lines, the line\n # break before each line its own; to the `--boundary--` line. The\n # preamble and the epilogue are passed over.\n protected def self.split(source : String, start : Int32, stop : Int32, boundary : String, media : String) : Array(::Tuple(Int32, Int32)) | MailError\n def subject : String | ::Nil\n # The body as text in UTF-8, from the part's charset, each CRLF an LF.\n def text : String | MailError\n # The transfer encoding in lower case: `7bit` when none is named.\n def transfer_encoding : String\n protected def self.trim(value : String) : String\n # This part, then each under it, depth first.\n def walk(&block : (Part -> Nil)) : Nil\n end\n # RFC 5322's date-time for *unix* seconds, at *offset* minutes east of\n # UTC: `Sat, 17 Oct 2026 14:00:00 +0200`, as `email.utils.format_datetime`\n # writes it.\n def self.date(unix : Int64, offset : Int32 = 0) : String\n # What base64 *text* stands for, characters outside the alphabet passed\n # over.\n def self.decode_base64(text : String) : String\n # *text* from *charset* in UTF-8.\n def self.decode_charset(text : String, charset : String) : String | MailError\n # What quoted-printable *text* stands for, hard breaks as CRLF.\n def self.decode_quoted_printable(text : String) : String\n # *text* with its RFC 2047 encoded-words decoded.\n def self.decode_words(text : String) : String\n # *data* in base64, lines of 76 and a CRLF after each.\n def self.encode_base64(data : String) : String\n # *data* in quoted-printable, lines of 76 or fewer; its line breaks\n # hard ones written as CRLF, or with *binary* escaped like any byte.\n def self.encode_quoted_printable(data : String, binary : Bool = false) : String\n # An unstructured header line, `name: value` folded and encoded as the\n # builder writes it, CRLF and all.\n def self.fold(name : String, value : String) : String\n def initialize\n # A message read from *text*: its header section, then its body.\n def self.parse(text : String) : Part | MailError\n # A date-time (RFC 5322 section 3.3, with section 4.3's obsolete years\n # and zones) as Unix seconds and the offset it was written at, in\n # minutes. The day of the week is not held to the date, as readers do\n # not hold it. A military zone is -0000, as section 4.3 says.\n def self.parse_date(text : String) : ::Tuple(Int64, Int32) | MailError\nend\n\n# What a message, a header or an address is that it should not be, as a\n# value: `reason` says what; `message` says it is mail's.\npub struct MailError\n def initialize(reason : String)\n def reason : String\n def to_s : String\nend\n\nimpl Error for MailError\n def message : String\nend\n" + "sourceSha256": "205af4ec59cbf503bcb8e22a51fdcaa90bdc89d86e281a67ee6b170fdbdc5d85", + "surface": "module std/mail\n\npub struct Mail\n # A mailbox: an address and the name shown with it.\n class Address\n def ==(other : Address) : Bool\n # The addr-spec: the local part quoted where it is not a dot-atom.\n def address : String\n def domain : String\n # What `parse` makes, already read.\n def initialize(local : String, domain : String, name : String, read : Bool)\n # *address* is an addr-spec: `user@example.com`, or\n # `\"john doe\"@example.com` with its local part quoted. Anything else\n # is a panic, as is a name with a line break in it.\n def initialize(address : String, name : String = \"\")\n # The local part, unquoted, and the domain as written.\n def local : String\n # The display name, decoded; \"\" when there is none.\n def name : String\n # The one mailbox *text* holds.\n def self.parse(text : String) : Address | MailError\n # The mailboxes of an address list, a group's members in its place.\n def self.parse_list(text : String) : Array(Address) | MailError\n # As a person reads it: `Name `, the name quoted\n # when it is not atoms, nothing encoded.\n def to_s : String\n # Whether the address is more than ASCII (RFC 6532), which only a\n # server that offers SMTPUTF8 takes.\n def utf8? : Bool\n end\n # A message to be written: its addresses, subject, date and identifier,\n # its text and HTML, and its attachments.\n class Message\n protected def self.addresses(name : String, list : Array(Address)) : String\n # A file sent with the message, under *filename*, as *content_type*:\n # the type its extension names (std/mime) when nil.\n def attach(filename : String, data : String, content_type : String | ::Nil = nil) : self\n protected def self.attachment(filename : String, kind : String, data : String) : MailEntity\n def bcc : Array(Address)\n def cc : Array(Address)\n def date : String | ::Nil\n # An RFC 5322 date-time - `Mail.date` writes one - refused when it is\n # not one.\n def date=(value : String) : String\n # `filename=\"...\"` for a short name of printable ASCII; RFC 2231's\n # `filename*=utf-8''...` for any other, in continuations of whole\n # characters when it is longer - 50 encoded bytes in the first, after\n # its charset, and 60 in the rest, so each fits a line. All but the\n # last end in `;`.\n protected def self.filename_params(filename : String) : Array(String)\n def from : Address | ::Nil\n def from=(value : Address | ::Nil) : Address | ::Nil\n # A header of the caller's own, written as unstructured text. The\n # headers the message writes itself are refused here.\n def header(name : String, value : String) : self\n def html : String | ::Nil\n def html=(value : String) : String\n def initialize\n def message_id : String | ::Nil\n # ``, the brackets added when they are not there.\n def message_id=(value : String) : String\n # The parts under one boundary, made from their own hash.\n protected def self.multipart(subtype : String, children : Array(MailEntity)) : MailEntity\n # Every address the message goes to - To, Cc and Bcc - once each.\n def recipients : Array(String)\n def reply_to : Array(Address)\n # ASCII without NUL, in lines of 78 or fewer.\n protected def self.seven_bit?(text : String) : Bool\n def subject : String | ::Nil\n def subject=(value : String) : String\n def text : String | ::Nil\n def text=(value : String) : String\n protected def self.text_part(subtype : String, text : String) : MailEntity\n def to : Array(Address)\n # The message, CRLF line breaks and all. A From and a Date are\n # required - set the Date with `msg.date = Mail.date(...)` - and the\n # Message-ID is optional. Writing a message the caller left incomplete\n # is a mistake, so it panics, with one of:\n #\n # - `mail: a message has no From` when no From was set;\n # - `mail: a message has no Date` when no Date was set.\n #\n # Those are the only panics here: every other value - a header, an\n # address, a Date, a Message-ID, a body, an attachment - is checked,\n # and refused with a panic, by the setter or method that takes it.\n def to_s : String\n # Whether an address in the message is RFC 6532's UTF-8.\n def utf8? : Bool\n end\n # A message or one of its parts, as read: its header fields unfolded and\n # in order, and its body - a multipart's parts, a `message/rfc822`'s\n # message, or bytes in a transfer encoding.\n class Part\n # The mailboxes of every field named *name* - `From`, `To`, `Cc` -\n # none when there is no such field.\n def addresses(name : String) : Array(Address) | MailError\n def attachment? : Bool\n # The parts whose disposition is `attachment`, in order.\n def attachments : Array(Part)\n # The bytes the body stands for, its transfer encoding undone.\n def body : String | MailError\n # The charset text is in: `us-ascii` when none is named (RFC 2045\n # section 5.2), in lower case.\n def charset : String\n # The disposition in lower case - `attachment`, `inline` - or nil.\n def disposition : String | ::Nil\n # The file name of Content-Disposition, or else Content-Type's `name`.\n def filename : String | ::Nil\n # The first part of *media_type* that is not an attachment, depth\n # first: `find(\"text/plain\")` is the text a reader shows.\n def find(media_type : String) : Part | ::Nil\n protected def self.first(headers : Array(::Tuple(String, String)), lower : String) : String | ::Nil\n # The first field named *name*, any case, its encoded-words decoded.\n def header(name : String) : String | ::Nil\n # Every field named *name*, in order, decoded.\n def header_values(name : String) : Array(String)\n # Each field's name as written and its value unfolded, whitespace at\n # either end dropped, nothing decoded.\n def headers : Array(::Tuple(String, String))\n def initialize(source : String, headers : Array(::Tuple(String, String)), media_type : String, params : Hash(String, String), body_at : Int32, body_end : Int32, parts : Array(Part))\n # `type/subtype` in lower case: `text/plain` when the part names none,\n # or names one that cannot be read (RFC 2045 section 5.2).\n def media_type : String\n def multipart? : Bool\n # A Content-Type parameter, RFC 2231's decoded.\n def param(name : String) : String | ::Nil\n def self.parse(text : String) : Part | MailError\n # The parts of a multipart, or the message a `message/rfc822` holds.\n def parts : Array(Part)\n protected def self.prefix?(a : Pointer(UInt8), b : Pointer(UInt8), size : Int32) : Bool\n # The first field named *name*, as written.\n def raw_header(name : String) : String | ::Nil\n protected def self.read(source : String, start : Int32, stop : Int32, depth : Int32, default_type : String) : Part | MailError\n # The parts of a multipart body: between `--boundary` lines, the line\n # break before each line its own; to the `--boundary--` line. The\n # preamble and the epilogue are passed over.\n protected def self.split(source : String, start : Int32, stop : Int32, boundary : String, media : String) : Array(::Tuple(Int32, Int32)) | MailError\n def subject : String | ::Nil\n # The body as text in UTF-8, from the part's charset, each CRLF an LF.\n def text : String | MailError\n # The transfer encoding in lower case: `7bit` when none is named.\n def transfer_encoding : String\n protected def self.trim(value : String) : String\n # This part, then each under it, depth first.\n def walk(&block : (Part -> Nil)) : Nil\n end\n # RFC 5322's date-time for *unix* seconds, at *offset* minutes east of\n # UTC: `Sat, 17 Oct 2026 14:00:00 +0200`, as `email.utils.format_datetime`\n # writes it.\n def self.date(unix : Int64, offset : Int32 = 0) : String\n # What base64 *text* stands for, characters outside the alphabet passed\n # over.\n def self.decode_base64(text : String) : String\n # *text* from *charset* in UTF-8.\n def self.decode_charset(text : String, charset : String) : String | MailError\n # What quoted-printable *text* stands for, hard breaks as CRLF.\n def self.decode_quoted_printable(text : String) : String\n # *text* with its RFC 2047 encoded-words decoded.\n def self.decode_words(text : String) : String\n # *data* in base64, lines of 76 and a CRLF after each.\n def self.encode_base64(data : String) : String\n # *data* in quoted-printable, lines of 76 or fewer; its line breaks\n # hard ones written as CRLF, or with *binary* escaped like any byte.\n def self.encode_quoted_printable(data : String, binary : Bool = false) : String\n # An unstructured header line, `name: value` folded and encoded as the\n # builder writes it, CRLF and all.\n def self.fold(name : String, value : String) : String\n def initialize\n # A message read from *text*: its header section, then its body.\n def self.parse(text : String) : Part | MailError\n # A date-time (RFC 5322 section 3.3, with section 4.3's obsolete years\n # and zones) as Unix seconds and the offset it was written at, in\n # minutes. The day of the week is not held to the date, as readers do\n # not hold it. A military zone is -0000, as section 4.3 says.\n def self.parse_date(text : String) : ::Tuple(Int64, Int32) | MailError\nend\n\n# What a message, a header or an address is that it should not be, as a\n# value: `reason` says what; `message` says it is mail's.\npub struct MailError\n def initialize(reason : String)\n def reason : String\n def to_s : String\nend\n\nimpl Error for MailError\n def message : String\nend\n" }, { "name": "std/math", "path": "src/std/math.iyi", - "sourceSha256": "0e281b320dc9539d1f15984621a425d3ff60da20085b11d4ad811a795b9f74cb", - "surface": "module std/math\n\npub struct Math\n def self.acos(value : Float64) : Float64\n def self.acos(value : Float32) : Float32\n def self.acos(value : T) : Float64 forall T\n # Correctly rounded (CORE-MATH, e_acosh.c): NaN below 1 (and for NaN),\n # acosh(1) = 0, acosh(inf) = inf.\n def self.acosh(value : Float64) : Float64\n def self.acosh(value : Float32) : Float32\n def self.acosh(value : T) : Float64 forall T\n def self.asin(value : Float64) : Float64\n def self.asin(value : Float32) : Float32\n def self.asin(value : T) : Float64 forall T\n # Correctly rounded (CORE-MATH, s_asinh.c): odd, asinh(+-0) = +-0,\n # +-inf and NaN come back as x + x.\n def self.asinh(value : Float64) : Float64\n def self.asinh(value : Float32) : Float32\n def self.asinh(value : T) : Float64 forall T\n def self.atan(value : Float64) : Float64\n def self.atan(value : Float32) : Float32\n def self.atan(value : T) : Float64 forall T\n # The angle of (x, y), with the zeros' and infinities' signs read the\n # way C's atan2 reads them: atan2(0.0, -0.0) is pi, atan2(-0.0, -1.0) is\n # -pi, atan2(inf, inf) is pi/4.\n def self.atan2(y : Float64, x : Float64) : Float64\n def self.atan2(y : Float32, x : Float32) : Float32\n def self.atan2(y : T, x : U) : Float64 forall T, U\n # Correctly rounded (CORE-MATH, e_atanh.c): odd, atanh(+-1) = +-inf, NaN\n # beyond 1 in magnitude (and for NaN).\n def self.atanh(value : Float64) : Float64\n def self.atanh(value : Float32) : Float32\n def self.atanh(value : T) : Float64 forall T\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : Float64) : Float64\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : Float32) : Float32\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : T) : Float64 forall T\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : Float64) : Float64\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : Float32) : Float32\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : T) : Float64 forall T\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : Float64) : Float64\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : Float32) : Float32\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : T) : Float64 forall T\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : Float64) : Float64\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : Float32) : Float32\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : T) : Float64 forall T\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : Float64) : Float64\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : Float32) : Float32\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : T) : Float64 forall T\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : Float64) : Float64\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : Float32) : Float32\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : T) : Float64 forall T\n def self.cbrt(value : Float64) : Float64\n def self.cbrt(value : Float32) : Float32\n def self.cbrt(value : T) : Float64 forall T\n def self.copysign(mag : Float64, sgn : Float64) : Float64\n def self.copysign(mag : Float32, sgn : Float32) : Float32\n def self.cos(value : Float64) : Float64\n def self.cos(value : Float32) : Float32\n def self.cos(value : T) : Float64 forall T\n def self.cosh(value : Float64) : Float64\n def self.cosh(value : Float32) : Float32\n def self.cosh(value : T) : Float64 forall T\n # The error function, correctly rounded: glibc 2.43's CORE-MATH erf\n # (s_erf.c). NaN answers NaN, ±inf answer ±1, and ±0 keep their sign.\n def self.erf(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.erf(value : Float32) : Float32\n def self.erf(value : T) : Float64 forall T\n # 1 - erf(value), correctly rounded: glibc 2.43's CORE-MATH erfc\n # (s_erfc.c). NaN answers NaN, -inf 2 and +inf 0; the answer underflows\n # to 0 from 0x1.b39dc41e48bfdp+4 (about 27.23) up.\n def self.erfc(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.erfc(value : Float32) : Float32\n def self.erfc(value : T) : Float64 forall T\n def self.exp(value : Float64) : Float64\n def self.exp(value : Float32) : Float32\n def self.exp(value : T) : Float64 forall T\n def self.exp2(value : Float64) : Float64\n def self.exp2(value : Float32) : Float32\n def self.exp2(value : T) : Float64 forall T\n def self.expm1(value : Float64) : Float64\n def self.expm1(value : Float32) : Float32\n def self.expm1(value : T) : Float64 forall T\n # a * b + c rounded once, as IEEE 754 defines it and as the other\n # library's `Math.fma` answers through libm. The instruction where the\n # processor has one: every aarch64 processor (`fmadd`, LLVM's intrinsic),\n # and an x86_64 one with FMA3 and an operating system that saves its\n # registers, asked once by `cpuid` and `xgetbv` as `Float64#floor` asks\n # for `roundsd`. Elsewhere musl's `fma` (MIT; Copyright 2005-2020 Rich\n # Felker, et al.), in iyi: the significands as integers, their product's\n # 128 bits exact, the sum aligned with a sticky bit, one rounding at the\n # conversion back. This was Dekker's product and two more additions,\n # which round each, and answered the neighbour of the fused result for\n # arguments that cancel.\n def self.fma(a : Float64, b : Float64, c : Float64) : Float64\n # The single's: the instruction where there is one, and otherwise\n # musl's `fmaf` - the product of two singles is exact in a double and\n # the sum rounds once there, which is right unless that double sits on\n # a single's halfway point or among the subnormals; then the sum's\n # rounding error, which is exact, sends it to the odd neighbour, which\n # the conversion to a single cannot then misround.\n def self.fma(a : Float32, b : Float32, c : Float32) : Float32\n def self.fma(a : T, b : U, c : V) : Float64 forall T, U, V\n # value as m * 2^e with m in [0.5, 1) (or 0, NaN or an infinity as it\n # came, with 0), read from the bits: a subnormal is first scaled up by\n # 2^54 so that it has an exponent field of its own.\n def self.frexp(value : Float64) : ::Tuple(Float64, Int32)\n def self.gamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.gamma(value : Float32) : Float32\n def self.gamma(value : T) : Float64 forall T\n # Euclid on the side of zero where every magnitude fits: the non-positive\n # side for a signed type, its minimum included - gcd(-2147483648, 6) is 2\n # - and the non-negative side, the only one there is, for an unsigned\n # type. Only gcd(minimum, 0), whose answer does not fit, overflows. The\n # two arguments are one type, as `%` takes one: the signature took any\n # two integers, and an unsigned pair (it has no unary minus) or a pair of\n # widths (no `%` between them) failed to compile inside this method. A\n # pair of widths is refused at the call now, and a literal takes the\n # other argument's type.\n def self.gcd(a : T, b : T) : T forall T\n def self.hypot(x : Float64, y : Float64) : Float64\n def self.hypot(x : Float32, y : Float32) : Float32\n def self.hypot(x : T, y : U) : Float64 forall T, U\n # The exponent of value as an integer, as C's ilogb and glibc answer it:\n # a subnormal's true exponent, -2147483648 for zero and NaN, 2147483647\n # for an infinity.\n def self.ilogb(value : Float64) : Int32\n def self.ilogb(value : Float32) : Int32\n def self.ilogb(value : T) : Int32 forall T\n def initialize\n # The largest r with r * r <= value, in the value's own type, by the\n # other library's bit-at-a-time method. A negative value panics, where\n # that library raises.\n def self.isqrt(value : ::Int) : ::Int\n def self.lcm(a : T, b : T) : T forall T\n # value * 2^exponent, with one rounding: the exponent field is rewritten\n # outright, and a result that lands among the subnormals is scaled down\n # from a normal one in a single step.\n def self.ldexp(value : Float64, exponent : Int32) : Float64\n # log|gamma(value)|, correctly rounded (CORE-MATH, as glibc 2.43): the\n # poles at the non-positive whole numbers and both infinities answer\n # +infinity, NaN answers NaN.\n def self.lgamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.lgamma(value : Float32) : Float32\n def self.lgamma(value : T) : Float64 forall T\n def self.log(value : Float64, base : Float64) : Float64\n def self.log(value : T, base : U) : Float64 forall T, U\n def self.log(value : Float64) : Float64\n def self.log(value : Float32) : Float32\n def self.log(value : T) : Float64 forall T\n def self.log10(value : Float64) : Float64\n def self.log10(value : Float32) : Float32\n def self.log10(value : T) : Float64 forall T\n def self.log1p(value : Float64) : Float64\n def self.log1p(value : Float32) : Float32\n def self.log1p(value : T) : Float64 forall T\n def self.log2(value : Float64) : Float64\n def self.log2(value : Float32) : Float32\n def self.log2(value : T) : Float64 forall T\n # The exponent of value as a double: -inf for zero, +inf for an\n # infinity, NaN for NaN, as C's logb.\n def self.logb(value : Float64) : Float64\n def self.logb(value : Float32) : Float32\n def self.logb(value : T) : Float64 forall T\n # The larger, with the number when one side is NaN, and 0.0 over -0.0.\n def self.max(a : Float64, b : Float64) : Float64\n def self.max(a : Float32, b : Float32) : Float32\n # The smaller, with the number when one side is NaN, and -0.0 over 0.0.\n def self.min(a : Float64, b : Float64) : Float64\n def self.min(a : Float32, b : Float32) : Float32\n def self.pow(base : Float64, exp : Float64) : Float64\n def self.pow(base : Float32, exp : Float32) : Float32\n def self.pow(base : T, exp : U) : Float64 forall T, U\n # The least power of two not below v, 1 for v <= 1, in v's type; one\n # the type cannot hold panics \"arithmetic overflow\".\n def self.pw2ceil(v : ::Int) : ::Int\n # scalbn with an Int64 exponent: past the range an Int32 holds every\n # answer is zero or an infinity already, so the exponent is clamped.\n def self.scalbln(value : Float64, exp : Int64) : Float64\n def self.scalbln(value : Float32, exp : Int64) : Float32\n def self.scalbln(value : T, exp : U) : Float64 forall T, U\n # value * 2^exp, rounded once: `ldexp` under C's other name.\n def self.scalbn(value : Float64, exp : Int32) : Float64\n def self.scalbn(value : Float32, exp : Int32) : Float32\n def self.scalbn(value : T, exp : U) : Float64 forall T, U\n def self.sin(value : Float64) : Float64\n def self.sin(value : Float32) : Float32\n def self.sin(value : T) : Float64 forall T\n # sinh, cosh and tanh as glibc 2.43 computes them - fdlibm's\n # `e_sinh.c`, `e_cosh.c` and `s_tanh.c` (Sun's notice is at `expm1`), on\n # the `exp` and `expm1` above, which are glibc's to the last bit, so\n # these are too. Their branches are fdlibm's, at its thresholds, read off\n # the high word: a series near zero and `half_exp` past 709 answered\n # differently in the last bit for 952 arguments of sinh in 100,000, 217\n # of cosh and 4,863 of tanh.\n def self.sinh(value : Float64) : Float64\n def self.sinh(value : Float32) : Float32\n def self.sinh(value : T) : Float64 forall T\n def self.sqrt(value : Float64) : Float64\n def self.sqrt(value : Float32) : Float32\n def self.sqrt(value : T) : Float64 forall T\n def self.tan(value : Float64) : Float64\n def self.tan(value : Float32) : Float32\n def self.tan(value : T) : Float64 forall T\n def self.tanh(value : Float64) : Float64\n def self.tanh(value : Float32) : Float32\n def self.tanh(value : T) : Float64 forall T\n # gamma(value), correctly rounded (CORE-MATH, as glibc 2.43): +-0 answer\n # +-infinity, the negative whole numbers and -infinity NaN; past\n # 171.62 the answer overflows to infinity, and below -184 it is a zero\n # carrying gamma's sign.\n def self.tgamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.tgamma(value : Float32) : Float32\n def self.tgamma(value : T) : Float64 forall T\nend\n" + "sourceSha256": "c4a4757ccdfb7d46e982de1177ad20663690a0040f630cd33a3a822117819b36", + "surface": "module std/math\n\npub struct Math\n def self.acos(value : Float64) : Float64\n def self.acos(value : Float32) : Float32\n def self.acos(value : T) : Float64 forall T\n # Correctly rounded (CORE-MATH, e_acosh.c): NaN below 1 (and for NaN),\n # acosh(1) = 0, acosh(inf) = inf.\n def self.acosh(value : Float64) : Float64\n def self.acosh(value : Float32) : Float32\n def self.acosh(value : T) : Float64 forall T\n def self.asin(value : Float64) : Float64\n def self.asin(value : Float32) : Float32\n def self.asin(value : T) : Float64 forall T\n # Correctly rounded (CORE-MATH, s_asinh.c): odd, asinh(+-0) = +-0,\n # +-inf and NaN come back as x + x.\n def self.asinh(value : Float64) : Float64\n def self.asinh(value : Float32) : Float32\n def self.asinh(value : T) : Float64 forall T\n def self.atan(value : Float64) : Float64\n def self.atan(value : Float32) : Float32\n def self.atan(value : T) : Float64 forall T\n # The angle of (x, y), with the zeros' and infinities' signs read the\n # way C's atan2 reads them: atan2(0.0, -0.0) is pi, atan2(-0.0, -1.0) is\n # -pi, atan2(inf, inf) is pi/4.\n def self.atan2(y : Float64, x : Float64) : Float64\n def self.atan2(y : Float32, x : Float32) : Float32\n def self.atan2(y : T, x : U) : Float64 forall T, U\n # Correctly rounded (CORE-MATH, e_atanh.c): odd, atanh(+-1) = +-inf, NaN\n # beyond 1 in magnitude (and for NaN).\n def self.atanh(value : Float64) : Float64\n def self.atanh(value : Float32) : Float32\n def self.atanh(value : T) : Float64 forall T\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : Float64) : Float64\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : Float32) : Float32\n # The Bessel function of the first kind of the given *order*,\n # Jn(*value*), as glibc computes it.\n def self.besselj(order : Int32, value : T) : Float64 forall T\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : Float64) : Float64\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : Float32) : Float32\n # The Bessel function of the first kind of order 0, J0(*value*), as glibc\n # computes it.\n def self.besselj0(value : T) : Float64 forall T\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : Float64) : Float64\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : Float32) : Float32\n # The Bessel function of the first kind of order 1, J1(*value*), as glibc\n # computes it.\n def self.besselj1(value : T) : Float64 forall T\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : Float64) : Float64\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : Float32) : Float32\n # The Bessel function of the second kind of the given *order*,\n # Yn(*value*), as glibc computes it: NaN for negative *value*, -infinity\n # at zero (+infinity for odd negative *order*).\n def self.bessely(order : Int32, value : T) : Float64 forall T\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : Float64) : Float64\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : Float32) : Float32\n # The Bessel function of the second kind of order 0, Y0(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely0(value : T) : Float64 forall T\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : Float64) : Float64\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : Float32) : Float32\n # The Bessel function of the second kind of order 1, Y1(*value*), as glibc\n # computes it: NaN for negative *value*, -infinity at zero.\n def self.bessely1(value : T) : Float64 forall T\n def self.cbrt(value : Float64) : Float64\n def self.cbrt(value : Float32) : Float32\n def self.cbrt(value : T) : Float64 forall T\n # The magnitude of mag with the sign of sgn. Any other pair of numbers is\n # taken as two Float64s, as the other library's untyped overload takes\n # it: `copysign(3, -1)` was an ambiguous call between the two float\n # overloads, as `max(2, 5)` was.\n def self.copysign(mag : Float64, sgn : Float64) : Float64\n def self.copysign(mag : Float32, sgn : Float32) : Float32\n def self.copysign(mag : T, sgn : U) : Float64 forall T, U\n def self.cos(value : Float64) : Float64\n def self.cos(value : Float32) : Float32\n def self.cos(value : T) : Float64 forall T\n def self.cosh(value : Float64) : Float64\n def self.cosh(value : Float32) : Float32\n def self.cosh(value : T) : Float64 forall T\n # The error function, correctly rounded: glibc 2.43's CORE-MATH erf\n # (s_erf.c). NaN answers NaN, ±inf answer ±1, and ±0 keep their sign.\n def self.erf(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.erf(value : Float32) : Float32\n def self.erf(value : T) : Float64 forall T\n # 1 - erf(value), correctly rounded: glibc 2.43's CORE-MATH erfc\n # (s_erfc.c). NaN answers NaN, -inf 2 and +inf 0; the answer underflows\n # to 0 from 0x1.b39dc41e48bfdp+4 (about 27.23) up.\n def self.erfc(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.erfc(value : Float32) : Float32\n def self.erfc(value : T) : Float64 forall T\n def self.exp(value : Float64) : Float64\n def self.exp(value : Float32) : Float32\n def self.exp(value : T) : Float64 forall T\n def self.exp2(value : Float64) : Float64\n def self.exp2(value : Float32) : Float32\n def self.exp2(value : T) : Float64 forall T\n def self.expm1(value : Float64) : Float64\n def self.expm1(value : Float32) : Float32\n def self.expm1(value : T) : Float64 forall T\n # a * b + c rounded once, as IEEE 754 defines it and as the other\n # library's `Math.fma` answers through libm. The instruction where the\n # processor has one: every aarch64 processor (`fmadd`, LLVM's intrinsic),\n # and an x86_64 one with FMA3 and an operating system that saves its\n # registers, asked once by `cpuid` and `xgetbv` as `Float64#floor` asks\n # for `roundsd`. Elsewhere musl's `fma` (MIT; Copyright 2005-2020 Rich\n # Felker, et al.), in iyi: the significands as integers, their product's\n # 128 bits exact, the sum aligned with a sticky bit, one rounding at the\n # conversion back. This was Dekker's product and two more additions,\n # which round each, and answered the neighbour of the fused result for\n # arguments that cancel.\n def self.fma(a : Float64, b : Float64, c : Float64) : Float64\n # The single's: the instruction where there is one, and otherwise\n # musl's `fmaf` - the product of two singles is exact in a double and\n # the sum rounds once there, which is right unless that double sits on\n # a single's halfway point or among the subnormals; then the sum's\n # rounding error, which is exact, sends it to the odd neighbour, which\n # the conversion to a single cannot then misround.\n def self.fma(a : Float32, b : Float32, c : Float32) : Float32\n def self.fma(a : T, b : U, c : V) : Float64 forall T, U, V\n # value as m * 2^e with m in [0.5, 1) (or 0, NaN or an infinity as it\n # came, with 0), read from the bits: a subnormal is first scaled up by\n # 2^54 so that it has an exponent field of its own.\n def self.frexp(value : Float64) : ::Tuple(Float64, Int32)\n def self.gamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.gamma(value : Float32) : Float32\n def self.gamma(value : T) : Float64 forall T\n # Euclid on the side of zero where every magnitude fits: the non-positive\n # side for a signed type, its minimum included - gcd(-2147483648, 6) is 2\n # - and the non-negative side, the only one there is, for an unsigned\n # type. Only gcd(minimum, 0), whose answer does not fit, overflows. The\n # two arguments are one type, as `%` takes one: the signature took any\n # two integers, and an unsigned pair (it has no unary minus) or a pair of\n # widths (no `%` between them) failed to compile inside this method. A\n # pair of widths is refused at the call now, and a literal takes the\n # other argument's type.\n def self.gcd(a : T, b : T) : T forall T\n def self.hypot(x : Float64, y : Float64) : Float64\n def self.hypot(x : Float32, y : Float32) : Float32\n def self.hypot(x : T, y : U) : Float64 forall T, U\n # The exponent of value as an integer, as C's ilogb and glibc answer it:\n # a subnormal's true exponent, -2147483648 for zero and NaN, 2147483647\n # for an infinity.\n def self.ilogb(value : Float64) : Int32\n def self.ilogb(value : Float32) : Int32\n def self.ilogb(value : T) : Int32 forall T\n def initialize\n # The largest r with r * r <= value, in the value's own type, by the\n # other library's bit-at-a-time method. A negative value panics, where\n # that library raises.\n def self.isqrt(value : ::Int) : ::Int\n def self.lcm(a : T, b : T) : T forall T\n # value * 2^exponent, with one rounding: the exponent field is rewritten\n # outright, and a result that lands among the subnormals is scaled down\n # from a normal one in a single step.\n def self.ldexp(value : Float64, exponent : Int32) : Float64\n # log|gamma(value)|, correctly rounded (CORE-MATH, as glibc 2.43): the\n # poles at the non-positive whole numbers and both infinities answer\n # +infinity, NaN answers NaN.\n def self.lgamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.lgamma(value : Float32) : Float32\n def self.lgamma(value : T) : Float64 forall T\n def self.log(value : Float64, base : Float64) : Float64\n def self.log(value : T, base : U) : Float64 forall T, U\n def self.log(value : Float64) : Float64\n def self.log(value : Float32) : Float32\n def self.log(value : T) : Float64 forall T\n def self.log10(value : Float64) : Float64\n def self.log10(value : Float32) : Float32\n def self.log10(value : T) : Float64 forall T\n def self.log1p(value : Float64) : Float64\n def self.log1p(value : Float32) : Float32\n def self.log1p(value : T) : Float64 forall T\n def self.log2(value : Float64) : Float64\n def self.log2(value : Float32) : Float32\n def self.log2(value : T) : Float64 forall T\n # The exponent of value as a double: -inf for zero, +inf for an\n # infinity, NaN for NaN, as C's logb.\n def self.logb(value : Float64) : Float64\n def self.logb(value : Float32) : Float32\n def self.logb(value : T) : Float64 forall T\n def self.max(a : T, b : T) : T forall T\n # min and max take two values of one type, any type `<` compares, and\n # answer one of them. Each is one overload: they were a Float64 and a\n # Float32 one, which took no integer pair, and an integer overload beside\n # them ties with them wherever an integer variable can widen to a\n # double: with all three, `Math.max(1_i64, x)` with `x : Int32` is\n # \"ambiguous call, implicit cast of Int32 matches all of Float64,\n # Int64\". Two doubles or two singles are ordered as IEEE 754-2019's\n # minimumNumber and maximumNumber order them, as they were: the number\n # when one side is NaN, and -0.0 below 0.0. Every other type compares\n # with `<` alone and answers the first argument on a tie, as the other\n # library's untyped `Math.min` and `Math.max` do. The one type is the\n # first argument's that the other can be cast to, so `Math.max(2,\n # page - 1)` is an Int32, `Math.max(0, n)` with `n : Int64` an Int64,\n # `Math.max(2, 2.5)` the Float64 2.5, and `Math.max(n, 0.5)` with\n # `n : Int32` a Float64, as it was; a pair neither of which casts to the\n # other's type, an Int64 and a UInt64 or a number and a String, is\n # refused at the call.\n def self.min(a : T, b : T) : T forall T\n def self.pow(base : Float64, exp : Float64) : Float64\n def self.pow(base : Float32, exp : Float32) : Float32\n def self.pow(base : T, exp : U) : Float64 forall T, U\n # The least power of two not below v, 1 for v <= 1, in v's type; one\n # the type cannot hold panics \"arithmetic overflow\".\n def self.pw2ceil(v : ::Int) : ::Int\n # scalbn with an Int64 exponent: past the range an Int32 holds every\n # answer is zero or an infinity already, so the exponent is clamped.\n def self.scalbln(value : Float64, exp : Int64) : Float64\n def self.scalbln(value : Float32, exp : Int64) : Float32\n def self.scalbln(value : T, exp : U) : Float64 forall T, U\n # value * 2^exp, rounded once: `ldexp` under C's other name.\n def self.scalbn(value : Float64, exp : Int32) : Float64\n def self.scalbn(value : Float32, exp : Int32) : Float32\n def self.scalbn(value : T, exp : U) : Float64 forall T, U\n def self.sin(value : Float64) : Float64\n def self.sin(value : Float32) : Float32\n def self.sin(value : T) : Float64 forall T\n # sinh, cosh and tanh as glibc 2.43 computes them - fdlibm's\n # `e_sinh.c`, `e_cosh.c` and `s_tanh.c` (Sun's notice is at `expm1`), on\n # the `exp` and `expm1` above, which are glibc's to the last bit, so\n # these are too. Their branches are fdlibm's, at its thresholds, read off\n # the high word: a series near zero and `half_exp` past 709 answered\n # differently in the last bit for 952 arguments of sinh in 100,000, 217\n # of cosh and 4,863 of tanh.\n def self.sinh(value : Float64) : Float64\n def self.sinh(value : Float32) : Float32\n def self.sinh(value : T) : Float64 forall T\n def self.sqrt(value : Float64) : Float64\n def self.sqrt(value : Float32) : Float32\n def self.sqrt(value : T) : Float64 forall T\n def self.tan(value : Float64) : Float64\n def self.tan(value : Float32) : Float32\n def self.tan(value : T) : Float64 forall T\n def self.tanh(value : Float64) : Float64\n def self.tanh(value : Float32) : Float32\n def self.tanh(value : T) : Float64 forall T\n # gamma(value), correctly rounded (CORE-MATH, as glibc 2.43): +-0 answer\n # +-infinity, the negative whole numbers and -infinity NaN; past\n # 171.62 the answer overflows to infinity, and below -184 it is a zero\n # carrying gamma's sign.\n def self.tgamma(value : Float64) : Float64\n # The single's: the double's answer rounded, which is the correctly\n # rounded single but where rounding twice meets a halfway point.\n def self.tgamma(value : Float32) : Float32\n def self.tgamma(value : T) : Float64 forall T\nend\n" }, { "name": "std/mime", @@ -349,11 +367,17 @@ "sourceSha256": "a1cac6eb0a4b5a6da16cd36b295720e3d9b01abce0344518a311d168070f99c2", "surface": "module std/mime\n\npub struct MIME\n # Whether a body of this type shrinks under gzip or deflate: text,\n # JSON, JavaScript, XML and their `+json`/`+xml` kin (SVG among them),\n # YAML, TOML, scripts, WebAssembly, uncompressed fonts and archives,\n # bitmaps and icons. Not images, audio and video that are already\n # compressed, nor zip, gzip and the other archives, nor WOFF and WOFF2,\n # which are compressed fonts. WebAssembly is included because its code\n # roughly halves under gzip, which is why servers compress it.\n def self.compressible?(media_type : String) : Bool\n # The extensions a media type is written with, dotted and sorted\n # (\"text/html\" is [\".htm\", \".html\"]). Parameters are ignored and case\n # does not matter; empty when the type is unknown. The array is a fresh\n # copy the caller may change.\n def self.extensions(media_type : String) : Array(String)\n # The same, falling back to *default*.\n def self.from_extension(name : String, default : String = \"application/octet-stream\") : String\n # The media type for a file name or extension: \".html\", \"html\",\n # \"index.HTML\" and \"a/b.tar.gz\" (by its last extension) all work, in\n # any case. A path whose base name has no dot has no extension; a bare\n # word is taken as the extension itself. Nil when unknown.\n def self.from_extension?(name : String) : String | ::Nil\n def initialize\n # RFC 9110 §8.3.1: `type/subtype` followed by `; name=value` parameters,\n # whitespace allowed around the semicolons and at either end, a value a\n # token or a quoted string whose backslash escapes are undone. Answers\n # the type lowercased and the parameters with lowercased names and\n # values as written; a name given twice keeps its last value. Nil when\n # any part is not a token, a parameter has no `=`, or a quote is left\n # open.\n def self.parse?(value : String) : ::Tuple(String, Hash(String, String)) | ::Nil\n # The media type with `; charset=utf-8` added when it is text, JSON,\n # JavaScript or XML (`+json` and `+xml` included) and names no charset\n # yet. Anything else, and anything that does not parse, comes back as\n # given.\n def self.with_charset(media_type : String) : String\nend\n" }, + { + "name": "std/mlkem", + "path": "src/std/mlkem.iyi", + "sourceSha256": "8b6ae912b1eb37146021806f7dcfff82fc16d6f1d57f38ba723082bfb73cc106", + "surface": "module std/mlkem\n\n# ML-KEM-768's encapsulation, and its sizes (FIPS 203 section 8, table 3).\npub struct MlKem768\n # The ciphertext for *encapsulation_key* and the 32-byte shared secret it\n # carries, FIPS 203 Algorithm 17 from the 32 random bytes *randomness*\n # (m), or an `MlKemError`: a key that is not 1,184 bytes or fails the\n # modulus check of section 7.2.\n def self.encapsulate(encapsulation_key : String | Array(UInt8), randomness : String | Array(UInt8)) : ::Tuple(Array(UInt8), Array(UInt8)) | MlKemError\n def initialize\nend\n\n# An ML-KEM-768 key pair: the decapsulation key and the encapsulation key\n# that goes with it. Holds the secret vector in the NTT domain, and the\n# public matrix and vector that decapsulation encrypts with again.\npub struct MlKem768PrivateKey\n # The 32-byte shared secret *ciphertext* carries, FIPS 203 Algorithm 18,\n # or an `MlKemError` for a ciphertext that is not 1,088 bytes. A\n # ciphertext that does not re-encrypt to itself is answered with the\n # implicit-rejection key J(z || c), chosen by masks: the caller cannot\n # tell the two apart, and neither can the time taken.\n def decapsulate(ciphertext : String | Array(UInt8)) : Array(UInt8) | MlKemError\n # The 2,400-byte decapsulation key, as `from_bytes` reads it.\n def decapsulation_key : Array(UInt8)\n # The 1,184 bytes a peer encapsulates to: ByteEncode12 of t, then rho.\n def encapsulation_key : Array(UInt8)\n # The key whose 2,400-byte decapsulation key is *bytes* (ByteEncode12 of\n # s, the encapsulation key, H of it, z), or an `MlKemError`: the wrong\n # size, or a stored H(ek) that is not the encapsulation key's hash (FIPS\n # 203 section 7.3).\n def self.from_bytes(bytes : String | Array(UInt8)) : MlKem768PrivateKey | MlKemError\n # The key pair of *seed*, d then z, 32 bytes each: FIPS 203 Algorithm 16\n # (ML-KEM.KeyGen_internal) over Algorithm 13 (K-PKE.KeyGen).\n def self.from_seed(seed : String | Array(UInt8)) : MlKem768PrivateKey\n def initialize(encapsulation_key : Array(UInt8), s : Pointer(UInt32), t : Pointer(UInt32), a : Pointer(UInt32), h : Array(UInt8), z : Array(UInt8))\nend\n\n# What is wrong with a key or a ciphertext from outside: `message` is the\n# sentence.\npub struct MlKemError\n def initialize(message : String)\n def to_s : String\nend\n\nimpl Error for MlKemError\n def message : String\nend\n" + }, { "name": "std/mutex", "path": "src/std/mutex.iyi", - "sourceSha256": "8171d7e399fd514875a37f775459a9c6ed5b1baa978d0210aa84d07c59d24aa7", - "surface": "module std/mutex\n\npub class Mutex(T)\n protected def self.build(value : T) : Mutex(T)\n # The block's answer, worked out with the lock held. It must be `Share`,\n # because it leaves the lock.\n def get(&block : (T -> R)) : R | Cancelled forall R\n # Runs the block with the value, the lock held; answers nil, or\n # `Cancelled` when the task was cancelled while it waited.\n def lock(&block : (T -> _)) : Nil | Cancelled\n # Whether some task holds it now: an answer that may be old by the time\n # it is read, for a report rather than a decision.\n def locked? : Bool\n # The value is the block's: made here, so nothing outside holds it. The\n # constructor that takes a value is private for that reason.\n def self.new(&block : (-> U)) : Mutex(U) forall U\n protected def self.task_id : UInt64\n # Whether this thread has anything to run besides the waiting task:\n # tasks ready to run, tasks asleep, tasks waiting on I/O.\n protected def self.thread_busy? : Bool\n protected def self.thread_id : UInt64\n # The value replaced by the block's answer, with the lock held: what a\n # number or any other value type wants, which a block cannot change in\n # place.\n def update(&block : (T -> T)) : Nil | Cancelled\n # Runs the block with the lock held each time the value changes, until it\n # answers something other than nil, and answers that: a condition and\n # what is done once it holds, in one step no other task can come between.\n #\n # queue = Mutex.new { [] of Int32 }\n # job = queue.wait_for { |q| q.empty? ? nil : q.shift }.or_panic\n #\n # A change is any lock, get, update or wait_for of this Mutex by another\n # task; between them the waiter holds no lock, and what it does while it\n # waits is `lock`'s: yields, then sleeps a millisecond at a time, and on\n # Linux a thread with nothing else to run sleeps in the kernel until the\n # next release. Cancellable, as `lock` is. A wait nothing could end - one\n # thread, and nothing else on it to run - panics saying so.\n def wait_for(&block : (T -> R | ::Nil)) : R | Cancelled forall R\nend\n" + "sourceSha256": "e02a2548bfddd8681125bbb3c18272d4b266a1df4c1e04f3345d7c47109b2f3d", + "surface": "module std/mutex\n\npub class Mutex(T)\n # The program's one thread, with nothing on it to run but the waiting\n # task: no task runnable, asleep or waiting on I/O, and none another\n # thread has woken into its inbox (SPEC.md III.4.13). Nothing can\n # release or change the value then.\n protected def self.alone? : Bool\n protected def self.build(value : T) : Mutex(T)\n # The block's answer, worked out with the lock held. It must be `Share`,\n # because it leaves the lock.\n def get(&block : (T -> R)) : R | Cancelled forall R\n # Whether the holder, which took the lock on *thread*, may be running\n # now: another thread exists, and the holder is not on this one, which\n # is running the waiter.\n protected def self.holder_may_run?(thread : UInt64) : Bool\n # Runs the block with the value, the lock held; answers nil, or\n # `Cancelled` when the task was cancelled while it waited.\n def lock(&block : (T -> _)) : Nil | Cancelled\n # Whether some task holds it now: an answer that may be old by the time\n # it is read, for a report rather than a decision.\n def locked? : Bool\n # The value is the block's: made here, so nothing outside holds it. The\n # constructor that takes a value is private for that reason.\n def self.new(&block : (-> U)) : Mutex(U) forall U\n protected def self.task_id : UInt64\n protected def self.thread_id : UInt64\n # The value replaced by the block's answer, with the lock held: what a\n # number or any other value type wants, which a block cannot change in\n # place.\n def update(&block : (T -> T)) : Nil | Cancelled\n # Runs the block with the lock held each time the value changes, until it\n # answers something other than nil, and answers that: a condition and\n # what is done once it holds, in one step no other task can come between.\n #\n # queue = Mutex.new { [] of Int32 }\n # job = queue.wait_for { |q| q.empty? ? nil : q.shift }.or_panic\n #\n # A change is any lock, get, update or wait_for of this Mutex by another\n # task; between them the waiter holds no lock, and is parked on the count\n # of releases until another task's moves it. Cancellable, as `lock` is.\n # A wait nothing could end - one thread, and nothing else on it to run -\n # panics saying so.\n def wait_for(&block : (T -> R | ::Nil)) : R | Cancelled forall R\nend\n" }, { "name": "std/named_tuple", @@ -379,6 +403,12 @@ "sourceSha256": "a40cfd087457929c1ee319de0306b8d72b2e4ded7dbc359dcf3085f0d5668a5b", "surface": "module std/number_format\n\n# Which binary prefixes `humanize_bytes` writes.\npub enum BinaryPrefixFormat : Int32\n IEC = 0\n JEDEC = 1\nend\n\nstruct ::Int\n # A count of bytes scaled by powers of 1024, as `humanize` scales by\n # 1000.\n def humanize_bytes(precision : Int32 = 3, separator : ::Char = '.', *, significant : ::Bool = true, unit_separator : ::String = \"\", format : Std::NumberFormat::BinaryPrefixFormat = Std::NumberFormat::BinaryPrefixFormat::IEC) : ::String\nend\n\nstruct ::Number\n # The number with its integer digits in groups of *group*, *delimiter*\n # between them and *separator* before the fraction. *decimal_places*\n # rounds to that many places, half to even; nil keeps an integer whole\n # and a float's shortest digits. *only_significant* drops the fraction's\n # trailing zeros, and the separator if nothing is left.\n def format(separator : ::Char = '.', delimiter : ::Char = ',', decimal_places : Int32 | ::Nil = nil, *, group : Int32 = 3, only_significant : ::Bool = false) : ::String\n # The number scaled to an SI prefix, with *precision* significant\n # digits (or, with `significant: false`, places), *unit_separator*\n # between it and the prefix.\n def humanize(precision : Int32 = 3, separator : ::Char = '.', delimiter : ::Char = ',', *, significant : ::Bool = true, unit_separator : ::String = \"\") : ::String\nend\n" }, + { + "name": "std/ocsp", + "path": "src/std/ocsp.iyi", + "sourceSha256": "8d3219c058d2b4488d0d5a63180f72a3cea57b94ba7a6fd9e0b3952639607746", + "surface": "module std/ocsp\n\npub struct Ocsp\n def initialize\n # A CRLReason by the name RFC 5280 section 5.3.1 gives it.\n def self.reason_name(reason : Int32) : String\n # An OCSPRequest (RFC 6960 section 4.1.1) for *leaf*, *issuer* its\n # issuer's certificate: one Request, unsigned, and a nonce extension\n # holding *nonce* when one is given.\n def self.request(leaf : Certificate, issuer : Certificate, hash : OcspHash = OcspHash::SHA1, nonce : Array(UInt8) | ::Nil = nil) : Array(UInt8)\n # A responseStatus by the name RFC 6960 gives it.\n def self.status_name(status : Int64) : String\nend\n\n# A CertID (RFC 6960 section 4.1.1): the hash it is made under, the hashes\n# of the issuer's name and key, and the certificate's serial number (its\n# two's-complement bytes, as `Certificate#serial` holds it).\npub struct OcspCertId\n # Its DER: the hash's AlgorithmIdentifier with NULL parameters, as\n # OpenSSL writes it.\n def der : Array(UInt8)\n def hash : OcspHash\n def initialize(hash : OcspHash, issuer_name_hash : Array(UInt8), issuer_key_hash : Array(UInt8), serial : Array(UInt8))\n def issuer_key_hash : Array(UInt8)\n def issuer_name_hash : Array(UInt8)\n # Whether this is *leaf*'s CertID, *issuer* its issuer: the name hash\n # over the leaf's issuer name, the key hash over the issuer's key, and\n # the serial number the leaf's.\n def matches?(leaf : Certificate, issuer : Certificate) : Bool\n # *leaf*'s CertID, *issuer* its issuer's certificate.\n def self.of(leaf : Certificate, issuer : Certificate, hash : OcspHash = OcspHash::SHA1) : OcspCertId\n def serial : Array(UInt8)\nend\n\n# What a SingleResponse says of a certificate (RFC 6960 section 4.2.1).\npub enum OcspCertStatus : Int32\n Good = 0\n Revoked = 1\n Unknown = 2\n def word : String\nend\n\n# What reading or checking a response answers when it fails.\npub struct OcspError\n def initialize(message : String, response_status : Int32 | ::Nil = nil)\n # The responseStatus of a responder that answered with no response: 1\n # malformedRequest, 2 internalError, 3 tryLater, 5 sigRequired, 6\n # unauthorized. Nil for every other refusal.\n def response_status : Int32 | ::Nil\n def to_s : String\nend\n\n# The hashes a CertID is made under.\npub enum OcspHash : Int32\n SHA1 = 0\n SHA256 = 1\n SHA384 = 2\n SHA512 = 3\n def digest(data : Array(UInt8)) : Array(UInt8)\n def self.from_oid(oid : String) : OcspHash | ::Nil\n def oid : String\n def size : Int32\nend\n\npub struct OcspResponse\n # The certificates the response carries, for a delegated responder.\n def certificates : Array(Certificate)\n def initialize(raw : Array(UInt8), tbs : Array(UInt8), version : Int32, responder_name : Array(UInt8) | ::Nil, responder_key_hash : Array(UInt8) | ::Nil, produced_at : Time, responses : Array(OcspSingleResponse), nonce : Array(UInt8) | ::Nil, signature_algorithm : String, signature_parameters : DerValue | ::Nil, signature : Array(UInt8), certificates : Array(Certificate))\n # The nonce extension's OCTET STRING, nil when absent.\n def nonce : Array(UInt8) | ::Nil\n # The OCSPResponse *der* holds (RFC 6960 section 4.2.1).\n def self.parse(der : Array(UInt8)) : OcspResponse | OcspError\n def produced_at : Time\n # The whole OCSPResponse as it was read, and the ResponseData the\n # signature is over.\n def raw : Array(UInt8)\n def responder_key_hash : Array(UInt8) | ::Nil\n # The responderID: a Name's DER (byName), or the SHA-1 of the\n # responder's key (byKey); one of the two is nil.\n def responder_name : Array(UInt8) | ::Nil\n # The responderID, for a sentence.\n def responder_text : String\n def responses : Array(OcspSingleResponse)\n def signature : Array(UInt8)\n # The signature algorithm's OID, and the signature.\n def signature_algorithm : String\n # The SingleResponse for *leaf*, *issuer* its issuer, or nil.\n def single_for(leaf : Certificate, issuer : Certificate) : OcspSingleResponse | ::Nil\n def tbs : Array(UInt8)\n # The status this response gives *leaf*, *issuer* its issuer's\n # certificate, checked at *now* (see the module's header); or why it\n # gives none that can be taken.\n def verify(leaf : Certificate, issuer : Certificate, now : Time = Time.utc, skew_seconds : Int32 = Ocsp::SKEW_SECONDS) : OcspVerdict | OcspError\n def version : Int32\nend\n\n# One SingleResponse (RFC 6960 section 4.2.1): a certificate's status,\n# when it was revoked and why (a CRLReason, nil when not given), and the\n# time the status is for.\npub struct OcspSingleResponse\n def cert_id : OcspCertId\n def initialize(cert_id : OcspCertId, status : OcspCertStatus, revocation_time : Time | ::Nil, revocation_reason : Int32 | ::Nil, this_update : Time, next_update : Time | ::Nil)\n def next_update : Time | ::Nil\n def revocation_reason : Int32 | ::Nil\n def revocation_time : Time | ::Nil\n def status : OcspCertStatus\n def this_update : Time\nend\n\n# What `OcspResponse#verify` answers: the status of the leaf, the\n# SingleResponse that gave it, and the certificate that signed - the\n# issuer, or a delegated responder.\npub struct OcspVerdict\n def delegated : Bool\n def initialize(status : OcspCertStatus, single : OcspSingleResponse, signer : Certificate, delegated : Bool)\n def signer : Certificate\n def single : OcspSingleResponse\n def status : OcspCertStatus\nend\n\nimpl Error for OcspError\n def message : String\nend\n" + }, { "name": "std/option_parser", "path": "src/std/option_parser.iyi", @@ -394,9 +424,15 @@ { "name": "std/p256", "path": "src/std/p256.iyi", - "sourceSha256": "519791b0842b8d5f787903a32696dd3fc0d54915eb0cf5628817864c8bd5821d", + "sourceSha256": "fff30034b881b76ce3e290fc5952af15bf42391ebd61cd2149e1d7109422d551", "surface": "module std/p256\n\n# A P-256 private key, and the public key that goes with it.\npub struct P256PrivateKey\n # The key whose d is *bytes*, 32 big-endian, or why it is not one: not\n # 32 bytes, or not in [1, n - 1].\n def self.from_bytes(bytes : String | Array(UInt8)) : P256PrivateKey | EcdsaError\n # A PKCS #8 OneAsymmetricKey (RFC 5958) holding a P-256 key: version 0 or\n # 1, the algorithm id-ecPublicKey with prime256v1, and an ECPrivateKey\n # inside the privateKey OCTET STRING.\n def self.from_pkcs8(der : Array(UInt8)) : P256PrivateKey | EcdsaError\n # SEC 1's ECPrivateKey (RFC 5915): version 1, the 32-byte private key,\n # then optionally the curve ([0], which must be prime256v1) and the\n # public key ([1], which must be the private key's).\n def self.from_sec1(der : Array(UInt8)) : P256PrivateKey | EcdsaError\n def initialize(d : P256Num, public_key : EcdsaPublicKey, public_point : Array(UInt8))\n def public_key : EcdsaPublicKey\n # The public key's point, uncompressed: 0x04, then x and y, 32 bytes each.\n def public_point : Array(UInt8)\n # ECDH: x of [d]Q for the peer's point Q, 32 bytes big-endian - or why\n # *peer* is not a point of P-256 (checked before d is used).\n def shared_secret(peer : Array(UInt8)) : Array(UInt8) | EcdsaError\n # The signature of *message* under *hash*: the DER of Ecdsa-Sig-Value,\n # SEQUENCE { r INTEGER, s INTEGER }, as X.509 and TLS 1.3 carry it. The\n # nonce is RFC 6979's, so the same key and message sign alike.\n def sign(hash : EcdsaHash, message : String | Array(UInt8)) : Array(UInt8)\nend\n" }, + { + "name": "std/p384", + "path": "src/std/p384.iyi", + "sourceSha256": "bceeb1a2e690f13b5ab58010a40173ced4f43829058c224587d921dc643d53c1", + "surface": "module std/p384\n\n# A P-384 private key, and the public key that goes with it.\npub struct P384PrivateKey\n # The key whose d is *bytes*, 48 big-endian, or why it is not one: not\n # 48 bytes, or not in [1, n - 1].\n def self.from_bytes(bytes : String | Array(UInt8)) : P384PrivateKey | EcdsaError\n # A PKCS #8 OneAsymmetricKey (RFC 5958) holding a P-384 key: version 0 or\n # 1, the algorithm id-ecPublicKey with secp384r1, and an ECPrivateKey\n # inside the privateKey OCTET STRING.\n def self.from_pkcs8(der : Array(UInt8)) : P384PrivateKey | EcdsaError\n # SEC 1's ECPrivateKey (RFC 5915): version 1, the 48-byte private key,\n # then optionally the curve ([0], which must be secp384r1) and the\n # public key ([1], which must be the private key's).\n def self.from_sec1(der : Array(UInt8)) : P384PrivateKey | EcdsaError\n def initialize(d : P384Num, public_key : EcdsaPublicKey, public_point : Array(UInt8))\n def public_key : EcdsaPublicKey\n # The public key's point, uncompressed: 0x04, then x and y, 48 bytes each.\n def public_point : Array(UInt8)\n # ECDH: x of [d]Q for the peer's point Q, 48 bytes big-endian - or why\n # *peer* is not a point of P-384 (checked before d is used), or why the\n # answer is no secret.\n def shared_secret(peer : Array(UInt8)) : Array(UInt8) | EcdsaError\n # The signature of *message* under *hash*: the DER of Ecdsa-Sig-Value,\n # SEQUENCE { r INTEGER, s INTEGER }, as X.509 and TLS 1.3 carry it. The\n # nonce is RFC 6979's, so the same key and message sign alike.\n def sign(hash : EcdsaHash, message : String | Array(UInt8)) : Array(UInt8)\n # The signature of a message whose digest under *hash* is *digest*, as\n # `sign` makes it - or why *digest* is not one of *hash*'s.\n def sign_digest(hash : EcdsaHash, digest : Array(UInt8)) : Array(UInt8) | EcdsaError\nend\n" + }, { "name": "std/password", "path": "src/std/password.iyi", @@ -418,13 +454,13 @@ { "name": "std/process", "path": "src/std/process.iyi", - "sourceSha256": "28cc11c3a78c92f7c2dc62fa229a826e3648ed69eac46b894343f4b68f57da05", + "sourceSha256": "afad6c79111206b02bfb15419101ae82c131cdabc51d6e268de0c20eb0e3da14", "surface": "module std/process\n\n# The child's standard input, as a pipe this program writes.\npub class ChildInput\n # Closes the pipe: the child reads the end of its input.\n def close : Nil\n def initialize(child : ProcessChild)\n # Writes all of *text*, parking while the pipe is full. A child that\n # closed its input, or ended, is `ChildInputClosed`; `close` has made it\n # so too.\n def write(text : String) : Nil | ChildInputClosed | Cancelled\nend\n\n# A write to a child that had closed its input - or ended.\npub struct ChildInputClosed\n def initialize(pid : Int64)\n def pid : Int64\nend\n\n# One of the child's output streams, as a pipe this program reads. Each\n# read parks until the child has written, or its end of the pipe closed.\npub class ChildOutput\n def initialize(child : ProcessChild, output : ::Bool)\n # What the child has written that this program has not read, up to\n # *max* bytes, once there is some: nil at the end.\n def read(max : Int32 = 65536) : String | ::Nil | Cancelled\n # Everything the child writes from here to its end.\n def read_all : String | Cancelled\n # The next line, without its `\\n` (and a `\\r` before it) unless *chomp*\n # is false; the last one need not end in one. nil at the end.\n def read_line(chomp : ::Bool = true) : String | ::Nil | Cancelled\nend\n\n# A child started by `Process.spawn`, running beside this program.\npub class ChildProcess\n # Whether the child has ended, without waiting; once it has, `status`\n # says how.\n def ended? : ::Bool\n def initialize(child : ProcessChild, input : ::Bool, output : ::Bool, error : ::Bool)\n # Ends the child: SIGKILL on Linux and darwin, and on Windows the child\n # and every program it started. Nothing once it has ended.\n def kill : Nil\n # The child's process id, which the operating system may give another\n # process once this one has ended and been waited for.\n def pid : Int64\n # Sends *number* to the child, Linux and darwin. Windows has no signals,\n # and a call there panics: `terminate` and `kill` are what it has.\n def signal(number : Int32) : Nil\n # How the child ended, once it has; nil while it runs.\n def status : ProcessStatus | ::Nil\n # The child's standard error, asked for as `Redirect::Pipe`.\n def stderr : ChildOutput\n # The child's standard input, asked for as `Redirect::Pipe`.\n def stdin : ChildInput\n # The child's standard output, asked for as `Redirect::Pipe`.\n def stdout : ChildOutput\n # Asks the child to stop: SIGTERM on Linux and darwin, which it may\n # handle. Windows has no request to stop a program, and ends it and what\n # it started, as `kill` does. Nothing once it has ended.\n def terminate : Nil\n # Parks until the child has ended and answers how; at once if it had.\n # Its input, if a pipe, is closed first: a child reading to the end of\n # its input would otherwise wait for this program while this program\n # waits for it.\n def wait : ProcessStatus | Cancelled\nend\n\npub struct Process\n # Replaces this program with *command*, and answers only if it could not:\n # a `ProcessError`, with this program as it was, in the directory it was.\n #\n # On Linux and darwin the process becomes the program - the same pid,\n # this program's three standard streams and none of its other\n # descriptors (on Linux from 5.11, which marks them for the exec) - and\n # nothing after the call runs. Windows cannot replace a program: there\n # the program runs on this program's streams, this program waits for it\n # and ends with its exit code, and a pid is not kept.\n #\n # *command*, *args*, *chdir* and *env* are `run`'s. What this program had\n # written to its own buffered streams is flushed first.\n def self.exec(command : String, args : Array(String) = [] of String, chdir : String | ::Nil = nil, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ProcessError\n # This program's own executable, as a path; nil where the platform will\n # not say.\n def self.executable : String | ::Nil\n def initialize\n # This program's own process id.\n def self.pid : Int64\n # Runs *command* with *args* and answers once it has ended.\n #\n # *command* is a path, or a name looked for on `PATH` (on Windows also as\n # `NAME.exe` and `NAME.com`; a `.bat` or `.cmd` is refused). Each of\n # *args* reaches the child as one argument, and no shell reads them.\n # *input* is written to the child's standard input, and without it the\n # child reads nothing. *chdir* is the directory it runs in. *env* changes\n # the child's environment alone: a name given a value is set, a name\n # given `nil` removed. With *capture* false the child writes to this\n # program's own streams and the result's texts are empty.\n #\n # Answers a `ProcessResult` for a child that ran, whatever its exit code;\n # a `ProcessError` when it could not be started; and `Cancelled`, after\n # the child is ended, when the task running this is cancelled. Nothing\n # blocks the thread: the other tasks run while the child does.\n def self.run(command : String, args : Array(String) = [] of String, input : String | ::Nil = nil, chdir : String | ::Nil = nil, capture : ::Bool = true, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ProcessResult | ProcessError | Cancelled\n # Starts *command* and answers at once with the running child, which\n # this program then talks to and waits for: `ChildProcess`.\n #\n # *command*, *args*, *chdir* and *env* are `run`'s. Each of *input*,\n # *output* and *error* is a `Redirect`: `Pipe` to write the child's input\n # or read its output through the handle, `Inherit` to share this\n # program's own stream (which is flushed first), `Null` for the null\n # device. All three are inherited unless asked.\n #\n # Answers a `ProcessError` when the child could not be started.\n #\n # The child runs until it ends or is ended; `wait` collects its end and\n # is what a program owes every child it starts. A pipe nobody reads fills\n # and stops the child that writes it, so read what is asked for as a\n # pipe - on two tasks at once for both output and errors - before or\n # while waiting. A task cancelled in any of the handle's calls ends the\n # child before it answers `Cancelled`, as `run` does: a task that stops\n # leaves no program running.\n def self.spawn(command : String, args : Array(String) = [] of String, input : Redirect = Redirect::Inherit, output : Redirect = Redirect::Inherit, error : Redirect = Redirect::Inherit, chdir : String | ::Nil = nil, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ChildProcess | ProcessError\nend\n\n# A program that could not be started, and the platform's error number for\n# why. `directory` is set when it was the directory it was to run in.\npub struct ProcessError\n def code : Int32\n def command : String\n def denied? : ::Bool\n def directory : String | ::Nil\n def initialize(command : String, code : Int32, directory : String | ::Nil = nil)\n # Nothing by that name: not on PATH, no such file, no such directory.\n def not_found? : ::Bool\n def reason : String\nend\n\n# What a program that ran came back with. `exit_code` is the child's own;\n# on Linux and darwin a child ended by a signal has `signal` set and the\n# exit code a shell would report for it, 128 plus the signal. On Windows\n# `signal` is always 0 and a crash is its exception code, `0xC0000005` for\n# an access violation, read as an Int32.\npub struct ProcessResult\n def exit_code : Int32\n def initialize(exit_code : Int32, signal : Int32, stdout : String, stderr : String)\n def signal : Int32\n def stderr : String\n def stdout : String\n def success? : ::Bool\nend\n\n# How a child ended: `exit_code`, and on Linux and darwin the `signal`\n# that ended it, as `ProcessResult` says them.\npub struct ProcessStatus\n def exit_code : Int32\n def initialize(exit_code : Int32, signal : Int32)\n def signal : Int32\n def success? : ::Bool\nend\n\n# How one of a spawned child's standard streams is connected.\npub enum Redirect : Int32\n Pipe = 0\n Inherit = 1\n Null = 2\nend\n\nimpl Error for ProcessError\n def message : String\nend\n\nimpl Error for ChildInputClosed\n def message : String\nend\n" }, { "name": "std/random", "path": "src/std/random.iyi", - "sourceSha256": "1ecf78ec9a7fd494377b691adbed94a855bba353e974cb333c65c917022c207c", + "sourceSha256": "63c494faac054e6f96737ebb73e0edcaeaba7222a07d15355db09ef154091f0c", "surface": "module std/random\n\npub class Random\n # The operating system's randomness, for anything a guess must not reach:\n # tokens, keys, nonces, query IDs. It is a `Random`, so `rand(n)`,\n # `rand(range)`, `hex` and every other draw take their values from it, and\n # `Random::Secure.rand(n)` answers without an instance. There is no state to\n # seed: each draw is a fresh request to the kernel (or libSystem, or the\n # Windows or WASI host), and nothing is held between draws, so a `fork`\n # cannot hand the child the parent's next value.\n class Secure < ::Std::Random::Random\n # *count* bytes at *buffer*, every one from the platform's entropy source,\n # or a panic that says why there are none: a byte that only looks random\n # is never the answer.\n def self.fill(buffer : Pointer(UInt8), count : Int32) : Nil\n # The generator fields a `Random` carries, which nothing here reads.\n def initialize\n def next_u : UInt32\n # The whole buffer in one request rather than four bytes at a time.\n def random_bytes(buffer : Array(UInt8)) : Nil\n end\n # *n* random bytes in base64 with `+` `/` and `=` padding, no line breaks.\n def base64(n : Int32 = 16) : String\n def self.base64(n : Int32 = 16) : String\n # *n* random bytes as two lowercase hex digits each.\n def hex(n : Int32 = 16) : String\n def self.hex(n : Int32 = 16) : String\n # The stream came from the seed, and one seed was one stream: the other\n # library takes the stream apart from the seed, zero unless asked, and\n # the same seed gave two different sequences in the two libraries.\n def initialize(seed : UInt64, sequence : UInt64 = 0_u64)\n # Any integer seed and sequence, as the other library takes them. There\n # were two overloads, an `Int32` and an `Int64` seed with a `UInt64`\n # sequence, beside this class's own `UInt64` pair: `Random.new(7, 3)`,\n # the form the comment above writes, cast its literals to all three and\n # was an ambiguous call, and a `UInt32` seed matched none of them. A\n # negative seed or sequence panics in the conversion, as it raises there.\n def self.new(seed : ::Int, sequence : ::Int = 0_u64) : Random\n def self.new : Random\n def next_bool : Bool\n def self.next_bool : Bool\n # 2^53 values, the high draw first: two 32-bit draws make the integer,\n # and it is divided by 2^53 - 1, so both ends can come out.\n def next_float : Float64\n def next_u : UInt32\n # The same draws without a generator in hand, from one seeded the first\n # time it is asked, as the other library's `Random.rand` draws from its\n # default. `Random::Secure.rand(...)` and the rest are these, answered by\n # the operating system instead.\n def self.next_u : UInt32\n def rand(n : Int32) : Int32\n def rand(n : UInt32) : UInt32\n def rand(n : Int64) : Int64\n def rand(n : UInt64) : UInt64\n # In `[0, x)`: 2^53 evenly spaced values, scaled. Not `rand * x`, which\n # divides by 2^53 - 1 and can answer *x* itself.\n def rand(x : Float64) : Float64\n # The span is taken as the unsigned difference, so a range across zero\n # does not overflow on the way. The whole of `Int32` is one draw read as\n # an `Int32`, as the other library reads it, rather than added to the\n # bottom of the range, which would answer the same value with its top\n # bit turned over.\n def rand(range : Range(Int32, Int32)) : Int32\n # As the `Int32` range: the whole of `Int64` is 2^64 values, one more than\n # a `UInt64` span can count, and is two draws, high first, read as one.\n def rand(range : Range(Int64, Int64)) : Int64\n # An end left out is the other library's choice too: `rand(x)` cannot\n # reach *x*, and an inclusive range scales `rand`, which can.\n def rand(range : Range(Float64, Float64)) : Float64\n def rand : Float64\n def self.rand(n : Int32) : Int32\n def self.rand(n : UInt32) : UInt32\n def self.rand(n : Int64) : Int64\n def self.rand(n : UInt64) : UInt64\n def self.rand(x : Float64) : Float64\n def self.rand(range : Range(Int32, Int32)) : Int32\n def self.rand(range : Range(Int64, Int64)) : Int64\n def self.rand(range : Range(Float64, Float64)) : Float64\n def self.rand : Float64\n # Four bytes a draw, low byte first, the last draw cut short: a seeded\n # generator fills a buffer with the other library's bytes.\n #\n # The bytes are an `Array(UInt8)`, as `UUID#bytes` and std/digest's\n # `hash_bytes` are, rather than the other library's `Bytes`: std/slice\n # reaches this module through std/indexable's `sample`, and an import\n # back would be a cycle.\n def random_bytes(buffer : Array(UInt8)) : Nil\n def random_bytes(n : Int32 = 16) : Array(UInt8)\n def self.random_bytes(buffer : Array(UInt8)) : Nil\n def self.random_bytes(n : Int32 = 16) : Array(UInt8)\n # The bytes as a String, which is how std/base64 takes them.\n protected def self.text_of(bytes : Array(UInt8)) : String\n # *n* random bytes in the URL-safe alphabet, `-` and `_`; unpadded unless\n # asked, as the other library answers it, since a token in a URL or a\n # file name has no use for the `=`.\n def urlsafe_base64(n : Int32 = 16, padding : Bool = false) : String\n def self.urlsafe_base64(n : Int32 = 16, padding : Bool = false) : String\nend\n\nclass ::Array(T)\n def sample(random : Std::Random::Random | ::Nil = nil) : T\n # *n* distinct elements. One is `sample` in a list; more are a reservoir\n # (each element past the first *n* replaces a slot with probability\n # n/(i+1)) shuffled at the end - both as the other library does it.\n def sample(n : Int32, random : Std::Random::Random | ::Nil = nil) : Array(T)\n def shuffle(random : Std::Random::Random | ::Nil = nil) : Array(T)\n # Fisher-Yates from the back, `rand(i + 1)` a step: the other library's\n # `shuffle!`, so a seeded generator puts the elements where it would.\n def shuffle_in_place(random : Std::Random::Random | ::Nil = nil) : Array(T)\nend\n\nstruct ::StaticArray(T, N)\n def initialize\n # `Array#shuffle_in_place` for the array that holds its elements inline:\n # the other library's `StaticArray#shuffle!`, the same draws in the same\n # order, so a seeded generator puts the elements where it would. Its\n # sample of noise does exactly this to a 256-entry permutation table.\n def shuffle_in_place(random : Std::Random::Random | ::Nil = nil) : self\nend\n" }, { @@ -436,21 +472,27 @@ { "name": "std/regex", "path": "src/std/regex.iyi", - "sourceSha256": "7b6cbf68745e7ffe3f780415f5442cc6c7db6dd621b87cdc0c1ecf3757d4ea92", - "surface": "module std/regex\n\npub struct Regex\n # One match: the text it was found in and where each group began and\n # ended, in bytes. A group that took no part in the match - the second\n # of `(a)|(b)` when `a` matched, `(x)?` when `x` did not - has no span,\n # and `[]?` answers nil for it.\n class MatchData\n # The text group *n* matched. A group the pattern does not have, or one\n # that took no part in this match, is a mistake the program made, and\n # it panics with the reason.\n def [](n : Int32) : String\n # The text the group called *name* matched; a name the pattern does not\n # have, or a group that took no part, panics with the reason.\n def [](name : String) : String\n # The text group *n* matched, or nil for a group that took no part or\n # that the pattern does not have.\n def []?(n : Int32) : String | ::Nil\n # The text the group called *name* matched, or nil for a group that\n # took no part or a name the pattern does not have.\n def []?(name : String) : String | ::Nil\n # Where group *n* begins, in characters, as the other library counts:\n # `\"クリスタル\".match(/リ(ス)/).begin(0)` is 1 where `byte_begin` is 3.\n def begin(n : Int32 = 0) : Int32\n # Where group *n* begins, in bytes: what `String#[]` and `index` count\n # in. A negative *n* counts from the last group.\n def byte_begin(n : Int32 = 0) : Int32\n # Where group *n* ends, in bytes.\n def byte_end(n : Int32 = 0) : Int32\n # What each unnamed group matched, in order, nil for one that took no\n # part; the named ones are `named_captures`, as in the other library.\n def captures : Array(String | ::Nil)\n # Where group *n* ends, in characters.\n def end(n : Int32 = 0) : Int32\n # *replacement* with its references filled from this match: `\\0` to\n # `\\9` are groups, `\\k` a named one, `\\\\` one backslash, and any\n # other `\\` is itself - how the other library reads the replacement\n # `String#sub` and `gsub` are given. A group that took no part is\n # empty; a name the pattern does not have panics.\n def expand(replacement : String) : String\n # How many capture groups the pattern has, the whole match not counted.\n def group_size : Int32\n def initialize(regex : Regex, string : String, slots : Array(Int32))\n # `Regex::MatchData(\"ab\" 1:\"a\" name:\"b\")`, as the other library writes it.\n def inspect : String\n # What each named group matched, nil for one that took no part.\n def named_captures : Hash(String, String | ::Nil)\n # The text after the match.\n def post_match : String\n # The text before the match.\n def pre_match : String\n # The pattern that matched.\n def regex : Regex\n # How many groups there are, the whole match counted: `(a)(b)` is 3.\n def size : Int32\n # The text the match was found in.\n def string : String\n # Every group, the whole match first.\n def to_a : Array(String | ::Nil)\n # The text the whole pattern matched.\n def to_s : String\n end\n # The flags a whole pattern is compiled with, combined with `|`. The\n # numbers are the other library's, which is what lets a literal's flags\n # (`/a/i`, `/a/mx`) reach this type unchanged.\n enum Options : Int32\n IGNORE_CASE = 1\n MULTILINE = 6\n DOTALL = 2\n MULTILINE_ONLY = 4\n EXTENDED = 8\n end\n # The same pattern with the same flags.\n def ==(other : Regex) : Bool\n # Whether the pattern occurs anywhere in *text*: what `case text when\n # /x/` asks.\n def ===(text : String) : Bool\n # Where the first match begins, in bytes, or nil. It sets nothing: the\n # other library's `=~` also fills `$~`, which iyi does not have, and a\n # program that wants the groups asks `match`.\n def =~(text : String) : Int32 | ::Nil\n # Where the first match at or after byte *offset* begins, in bytes, as\n # `String#byte_index` answers for a string.\n def byte_index(text : String, offset : Int32 = 0) : Int32 | ::Nil\n # How many capture groups the pattern has.\n def capture_count : Int32\n # The machine is compiled first either way: it is what refuses a pattern\n # this engine does not take, and the fast paths only ever see one it did.\n # They read the pattern's text as written, so a pattern with flags, which\n # change what that text means, is the machine's alone. So is one holding\n # U+FFFD: the machine reads a byte that begins no character as that\n # character, and comparing bytes would find only its three-byte spelling.\n def self.compile(pattern : String, options : Options = Options::None) : Regex\n # Every non-overlapping match with its groups, leftmost first, as `scan`\n # finds them.\n def each_match(text : String, & : (MatchData -> Nil)) : Nil\n # *text* with every character a pattern would read as an operator\n # escaped, so `Regex.compile(Regex.escape(text))` matches *text* and\n # nothing else - with `x` too, whose whitespace and `#` are escaped.\n def self.escape(text : String) : String\n def find(text : String) : String | ::Nil\n # The number of the group called *name*, or nil.\n def group_index(name : String) : Int32 | ::Nil\n # The name of group *n*, or nil for an unnamed group or one the pattern\n # does not have.\n def group_name(n : Int32) : String | ::Nil\n def initialize(nfa : RxNfa, literal : String | ::Nil, runs : RxRuns | ::Nil, source : String, options : Options, names : Array(String | ::Nil))\n # The pattern as a literal would write it, `/a\\/b/ix`.\n def inspect : String\n # The first match at or after byte *pos*, with its groups; a negative\n # *pos* counts from the end. `^` and `\\A` still mean the start of the\n # text, not *pos*.\n def match(text : String, pos : Int32 = 0) : MatchData | ::Nil\n # Whether the pattern spans the whole of *text*. The other library's\n # `match?` asks whether it occurs anywhere, which is `===` and\n # `String#matches?` here.\n def match?(text : String) : Bool\n # The match that begins exactly at byte *pos*, with its groups, or nil:\n # the other library's `match_at_byte_index` with `MatchOptions::ANCHORED`,\n # Python's `pattern.match(text, pos)`, what a scanner asks at its\n # offset. As there, the text before *pos* is still the text: `^` and\n # `\\A` mean its start, not *pos*, and `\\b` sees the byte before *pos*.\n # A negative *pos* counts from the end, as `match`'s does.\n def match_at(text : String, pos : Int32) : MatchData | ::Nil\n # Each named group's number and name.\n def name_table : Hash(Int32, String)\n # The other library's name for `compile`.\n def self.new(pattern : String, options : Options = Options::None) : Regex\n # The flags the pattern was compiled with; inline ones are the\n # pattern's own text and are not here.\n def options : Options\n # Every match replaced, into one buffer: `out = out + ...` copied all of\n # the text so far at each match, and eleven substitutions over a 60 MB\n # sequence with a million matches each did not finish. The replacement\n # is taken as written; `String#gsub` is the one that reads `\\1` in it.\n def replace(text : String, replacement : String) : String\n # Every match replaced by what the block answers for it — the per-match\n # substitution (`gsub(pattern, hash)` in the other library) that a fixed\n # replacement cannot say.\n def replace(text : String, & : (String -> String)) : String\n def scan(text : String) : Array(String)\n # The pattern as written, the other library's `Regex#source`: what a\n # literal was compiled from, for a program that prints its patterns.\n def source : String\n def split(text : String) : Array(String)\n # The pattern with its flags inside it, `(?i-msx:a+)`, which compiles to\n # a pattern that matches as this one does: what `union` joins.\n def to_s : String\n # One pattern matching any of *patterns*: a string as its own text, a\n # `Regex` with its flags kept to itself (`(?i-msx:...)`, its `to_s`).\n # The groups of every pattern after the first are numbered after the\n # groups before them.\n def self.union(patterns : Array(T)) : Regex forall T\n def self.union(*patterns : String | Regex) : Regex\nend\n\nclass ::String\n def =~(pattern : Std::Regex::Regex) : Int32 | ::Nil\n def gsub(pattern : Std::Regex::Regex, replacement : String) : String\n def gsub(pattern : Std::Regex::Regex, & : (String, Std::Regex::Regex::MatchData -> String)) : String\n # Each match replaced by its entry in *table*; a match the table has no\n # entry for is dropped, as the other library drops it.\n def gsub(pattern : Std::Regex::Regex, table : Hash(String, String)) : String\n def index(pattern : Std::Regex::Regex, offset : Int32 = 0) : Int32 | ::Nil\n def match(pattern : Std::Regex::Regex, pos : Int32 = 0) : Std::Regex::Regex::MatchData | ::Nil\n def matches?(pattern : Std::Regex::Regex) : Bool\n def scan(pattern : Std::Regex::Regex) : Array(Std::Regex::Regex::MatchData)\n def scan(pattern : Std::Regex::Regex, & : (Std::Regex::Regex::MatchData -> Nil)) : String\n def sub(pattern : Std::Regex::Regex, replacement : String) : String\n def sub(pattern : Std::Regex::Regex, & : (String, Std::Regex::Regex::MatchData -> String)) : String\nend\n" + "sourceSha256": "a606bf1b00791cd897309faf04248074e1efafa47f1c7cb5e72538e2321fed35", + "surface": "module std/regex\n\npub struct Regex\n # One match: the text it was found in and where each group began and\n # ended, in bytes. A group that took no part in the match - the second\n # of `(a)|(b)` when `a` matched, `(x)?` when `x` did not - has no span,\n # and `[]?` answers nil for it.\n class MatchData\n # The text group *n* matched. A group the pattern does not have, or one\n # that took no part in this match, is a mistake the program made, and\n # it panics with the reason.\n def [](n : Int32) : String\n # The text the group called *name* matched; a name the pattern does not\n # have, or a group that took no part, panics with the reason.\n def [](name : String) : String\n # The text group *n* matched, or nil for a group that took no part or\n # that the pattern does not have.\n def []?(n : Int32) : String | ::Nil\n # The text the group called *name* matched, or nil for a group that\n # took no part or a name the pattern does not have.\n def []?(name : String) : String | ::Nil\n # Where group *n* begins, in characters, as the other library counts:\n # `\"クリスタル\".match(/リ(ス)/).begin(0)` is 1 where `byte_begin` is 3.\n def begin(n : Int32 = 0) : Int32\n # Where group *n* begins, in bytes: what `String#[]` and `index` count\n # in. A negative *n* counts from the last group.\n def byte_begin(n : Int32 = 0) : Int32\n # Where group *n* ends, in bytes.\n def byte_end(n : Int32 = 0) : Int32\n # What each unnamed group matched, in order, nil for one that took no\n # part; the named ones are `named_captures`, as in the other library.\n def captures : Array(String | ::Nil)\n # Where group *n* ends, in characters.\n def end(n : Int32 = 0) : Int32\n # *replacement* with its references filled from this match: `\\0` to\n # `\\9` are groups, `\\k` a named one, `\\\\` one backslash, and any\n # other `\\` is itself - how the other library reads the replacement\n # `String#sub` and `gsub` are given. A group that took no part is\n # empty; a name the pattern does not have panics.\n def expand(replacement : String) : String\n # How many capture groups the pattern has, the whole match not counted.\n def group_size : Int32\n def initialize(regex : Regex, string : String, slots : Array(Int32))\n # `Regex::MatchData(\"ab\" 1:\"a\" name:\"b\")`, as the other library writes it.\n def inspect : String\n # What each named group matched, nil for one that took no part.\n def named_captures : Hash(String, String | ::Nil)\n # The text after the match.\n def post_match : String\n # The text before the match.\n def pre_match : String\n # The pattern that matched.\n def regex : Regex\n # How many groups there are, the whole match counted: `(a)(b)` is 3.\n def size : Int32\n # The text the match was found in.\n def string : String\n # Every group, the whole match first.\n def to_a : Array(String | ::Nil)\n # The text the whole pattern matched.\n def to_s : String\n end\n # The flags a whole pattern is compiled with, combined with `|`. The\n # numbers are the other library's, which is what lets a literal's flags\n # (`/a/i`, `/a/mx`) reach this type unchanged.\n enum Options : Int32\n IGNORE_CASE = 1\n MULTILINE = 6\n DOTALL = 2\n MULTILINE_ONLY = 4\n EXTENDED = 8\n end\n # The same pattern with the same flags.\n def ==(other : Regex) : Bool\n # Whether the pattern occurs anywhere in *text*: what `case text when\n # /x/` asks.\n def ===(text : String) : Bool\n # Where the first match begins, in bytes, or nil. It sets nothing: the\n # other library's `=~` also fills `$~`, which iyi does not have, and a\n # program that wants the groups asks `match`.\n def =~(text : String) : Int32 | ::Nil\n # Where the first match at or after byte *offset* begins, in bytes, as\n # `String#byte_index` answers for a string.\n def byte_index(text : String, offset : Int32 = 0) : Int32 | ::Nil\n # How many capture groups the pattern has.\n def capture_count : Int32\n # The machine is compiled first either way: it is what refuses a pattern\n # this engine does not take, and the fast paths only ever see one it did.\n # They read the pattern's text as written, so a pattern with flags, which\n # change what that text means, is the machine's alone. So is one holding\n # U+FFFD: the machine reads a byte that begins no character as that\n # character, and comparing bytes would find only its three-byte spelling.\n def self.compile(pattern : String, options : Options = Options::None) : Regex\n # Every non-overlapping match with its groups, leftmost first, as `scan`\n # finds them.\n def each_match(text : String, & : (MatchData -> Nil)) : Nil\n # *text* with every character a pattern would read as an operator\n # escaped, so `Regex.compile(Regex.escape(text))` matches *text* and\n # nothing else - with `x` too, whose whitespace and `#` are escaped.\n def self.escape(text : String) : String\n def find(text : String) : String | ::Nil\n # The number of the group called *name*, or nil.\n def group_index(name : String) : Int32 | ::Nil\n # The name of group *n*, or nil for an unnamed group or one the pattern\n # does not have.\n def group_name(n : Int32) : String | ::Nil\n def initialize(nfa : RxNfa, literal : String | ::Nil, runs : RxRuns | ::Nil, source : String, options : Options, names : List(String | ::Nil))\n # The pattern as a literal would write it, `/a\\/b/ix`.\n def inspect : String\n # The first match at or after byte *pos*, with its groups; a negative\n # *pos* counts from the end. `^` and `\\A` still mean the start of the\n # text, not *pos*.\n def match(text : String, pos : Int32 = 0) : MatchData | ::Nil\n # Whether the pattern spans the whole of *text*. The other library's\n # `match?` asks whether it occurs anywhere, which is `===` and\n # `String#matches?` here.\n def match?(text : String) : Bool\n # The match that begins exactly at byte *pos*, with its groups, or nil:\n # the other library's `match_at_byte_index` with `MatchOptions::ANCHORED`,\n # Python's `pattern.match(text, pos)`, what a scanner asks at its\n # offset. As there, the text before *pos* is still the text: `^` and\n # `\\A` mean its start, not *pos*, and `\\b` sees the byte before *pos*.\n # A negative *pos* counts from the end, as `match`'s does.\n def match_at(text : String, pos : Int32) : MatchData | ::Nil\n # Each named group's number and name.\n def name_table : Hash(Int32, String)\n # The other library's name for `compile`.\n def self.new(pattern : String, options : Options = Options::None) : Regex\n # The flags the pattern was compiled with; inline ones are the\n # pattern's own text and are not here.\n def options : Options\n # Every match replaced, into one buffer: `out = out + ...` copied all of\n # the text so far at each match, and eleven substitutions over a 60 MB\n # sequence with a million matches each did not finish. The replacement\n # is taken as written; `String#gsub` is the one that reads `\\1` in it.\n def replace(text : String, replacement : String) : String\n # Every match replaced by what the block answers for it — the per-match\n # substitution (`gsub(pattern, hash)` in the other library) that a fixed\n # replacement cannot say.\n def replace(text : String, & : (String -> String)) : String\n def scan(text : String) : Array(String)\n # The pattern as written, the other library's `Regex#source`: what a\n # literal was compiled from, for a program that prints its patterns.\n def source : String\n def split(text : String) : Array(String)\n # The pattern with its flags inside it, `(?i-msx:a+)`, which compiles to\n # a pattern that matches as this one does: what `union` joins.\n def to_s : String\n # One pattern matching any of *patterns*: a string as its own text, a\n # `Regex` with its flags kept to itself (`(?i-msx:...)`, its `to_s`).\n # The groups of every pattern after the first are numbered after the\n # groups before them.\n def self.union(patterns : Array(T)) : Regex forall T\n def self.union(*patterns : String | Regex) : Regex\nend\n\nclass ::String\n def =~(pattern : Std::Regex::Regex) : Int32 | ::Nil\n def gsub(pattern : Std::Regex::Regex, replacement : String) : String\n def gsub(pattern : Std::Regex::Regex, & : (String, Std::Regex::Regex::MatchData -> String)) : String\n # Each match replaced by its entry in *table*; a match the table has no\n # entry for is dropped, as the other library drops it.\n def gsub(pattern : Std::Regex::Regex, table : Hash(String, String)) : String\n def index(pattern : Std::Regex::Regex, offset : Int32 = 0) : Int32 | ::Nil\n def match(pattern : Std::Regex::Regex, pos : Int32 = 0) : Std::Regex::Regex::MatchData | ::Nil\n def matches?(pattern : Std::Regex::Regex) : Bool\n def scan(pattern : Std::Regex::Regex) : Array(Std::Regex::Regex::MatchData)\n def scan(pattern : Std::Regex::Regex, & : (Std::Regex::Regex::MatchData -> Nil)) : String\n def sub(pattern : Std::Regex::Regex, replacement : String) : String\n def sub(pattern : Std::Regex::Regex, & : (String, Std::Regex::Regex::MatchData -> String)) : String\nend\n" }, { "name": "std/router", "path": "src/std/router.iyi", - "sourceSha256": "08330b79870a18dae9c55fd346f503d801db1b9adcaa243cfd081a9b7ea1db16", - "surface": "module std/router\n\n# A route's pattern, `[METHOD ][HOST]/[PATH]`, parsed: what `Router#handle`\n# registers. `match` reads a request path's wildcards; `relation` and\n# `conflict` are the comparisons that decide precedence and refuse two\n# patterns neither of which is more specific.\npub class Pattern\n # Two relations of parts (methods, then each segment) as the relation\n # of the whole: a superset in one part and a subset in another is\n # neither, an overlap.\n def self.combine(first : Int32, second : Int32) : Int32\n # The sentence refusing this pattern beside *other*, nil when the two\n # can be registered together: when they have the same host and match\n # the same requests, or some requests in common with neither more\n # specific. It names both and a request both match.\n def conflict(other : Pattern) : String | ::Nil\n # Whether this pattern matched *pieces* without its trailing wildcard\n # taking anything: `/x/` on `/x/`, not on `/x/y`. What decides whether\n # `/x` is redirected to `/x/`.\n def exact?(pieces : Array(String)) : Bool\n def host : String\n # A wildcard's name: an ASCII letter or `_`, then letters, digits, `_`.\n def self.identifier?(name : String) : Bool\n def initialize(text : String, method : String, host : String, kinds : Array(Int32), names : Array(String))\n protected def kinds : Array(Int32)\n # The wildcards' values when *path* - a request path, rooted and clean,\n # still percent-encoded - matches this pattern's path; nil when it does\n # not. The method and host are not looked at.\n def match(path : String) : Hash(String, String) | ::Nil\n # `match` on a path already cut into `pieces`.\n def match_pieces(pieces : Array(String)) : Hash(String, String) | ::Nil\n # Whether a request of *method* is one this pattern's method takes.\n def matches_method?(method : String) : Bool\n def method : String\n # Whether this pattern matches a strict subset of *other*'s requests,\n # methods and paths together; hosts are not compared.\n def more_specific?(other : Pattern) : Bool\n protected def names : Array(String)\n # *text* read as a pattern, or why it is not one: an invalid method, no\n # `/` to start the path, a `{` in the host, a path a request's cleaned\n # path never equals (`//`, `.`, `..`), a wildcard that is not a whole\n # segment, `{$}` or `{name...}` before the end, a name that is not an\n # identifier or is used twice, a malformed percent escape.\n def self.parse(text : String) : Pattern | RouterError\n # *path*'s segments after its first `/`, each percent-decoded: `/` is\n # one empty piece, and a trailing slash is a last empty one. Nil for a\n # path that is not rooted or holds a malformed escape. Decoded a segment\n # at a time, so `%2F` is inside a segment, never between two.\n def self.pieces(path : String) : Array(String) | ::Nil\n # How this pattern's requests relate to *other*'s, methods and paths\n # together: `EQUIVALENT`, `MORE_GENERAL`, `MORE_SPECIFIC`, `DISJOINT` or\n # `OVERLAPS`.\n def relation(other : Pattern) : Int32\n # The pattern as it was written.\n def to_s : String\nend\n\n# Requests sent to handlers by pattern, Go 1.22's `ServeMux`:\n#\n# router = Router.new\n# router.handle(\"GET /users/{id}\") { |req, params| Response.new(200, params[\"id\"]) }\n# router.handle(\"/static/\") { |req, params| FileServer.serve(root, req) }\n# Server.serve(listener) { |req| router.call(req) }\n#\n# `handle` panics on a pattern that is not one, on one that conflicts with\n# a pattern already registered, and after the router has answered a\n# request. `call` answers with the most specific pattern's handler, or a\n# 301 (to the cleaned path, or to a subtree's `/`), a 405 with `Allow`, a\n# 400 for a malformed escape, a 404.\npub class Router\n # The answer to *req*: its handler's, or the router's own 301, 400, 404\n # or 405.\n def call(req : Request) : Response\n # *path* - rooted - with `.` and empty segments dropped and `..` taking\n # the one before it off, its trailing slash kept: Go's `cleanPath`.\n def self.clean(path : String) : String\n # Sends the requests *pattern* matches to the block, with the request\n # and the wildcards' values by name.\n def handle(pattern : String, &handler : (Request, Hash(String, String) -> Response)) : Nil\n # A `Host` value without its port, as Go's `stripHostPort` has it: an\n # IPv6 address in brackets loses the brackets with the port.\n def self.host_of(value : String) : String\n def initialize\n # The patterns registered, in the order they were.\n def patterns : Array(Pattern)\n def self.plain(status : Int32, text : String) : Response\n def self.redirect(path : String, query : String | ::Nil) : Response\nend\n\n# Why a pattern is refused: `Pattern.parse`'s answer for text that is not\n# a pattern. `Router#handle` panics with its `message`.\npub struct RouterError\n def initialize(reason : String)\n def reason : String\n def to_s : String\nend\n\nimpl Error for RouterError\n def message : String\nend\n" + "sourceSha256": "46616dc94ec6b99be5edc5ff896bba1aec8f66faaa5c65587d76d9fec69c9647", + "surface": "module std/router\n\n# A route's pattern, `[METHOD ][HOST]/[PATH]`, parsed: what `Router#handle`\n# registers. `match` reads a request path's wildcards; `relation` and\n# `conflict` are the comparisons that decide precedence and refuse two\n# patterns neither of which is more specific.\npub class Pattern\n # Two relations of parts (methods, then each segment) as the relation\n # of the whole: a superset in one part and a subset in another is\n # neither, an overlap.\n def self.combine(first : Int32, second : Int32) : Int32\n # The sentence refusing this pattern beside *other*, nil when the two\n # can be registered together: when they have the same host and match\n # the same requests, or some requests in common with neither more\n # specific. It names both and a request both match.\n def conflict(other : Pattern) : String | ::Nil\n # Whether this pattern matched *pieces* without its trailing wildcard\n # taking anything: `/x/` on `/x/`, not on `/x/y`. What decides whether\n # `/x` is redirected to `/x/`.\n def exact?(pieces : Array(String)) : Bool\n def host : String\n # A wildcard's name: an ASCII letter or `_`, then letters, digits, `_`.\n def self.identifier?(name : String) : Bool\n def initialize(text : String, method : String, host : String, kinds : Array(Int32), names : Array(String))\n protected def kinds : List(Int32)\n # The wildcards' values when *path* - a request path, rooted and clean,\n # still percent-encoded - matches this pattern's path; nil when it does\n # not. The method and host are not looked at.\n def match(path : String) : Hash(String, String) | ::Nil\n # `match` on a path already cut into `pieces`.\n def match_pieces(pieces : Array(String)) : Hash(String, String) | ::Nil\n # Whether a request of *method* is one this pattern's method takes.\n def matches_method?(method : String) : Bool\n def method : String\n # Whether this pattern matches a strict subset of *other*'s requests,\n # methods and paths together; hosts are not compared.\n def more_specific?(other : Pattern) : Bool\n protected def names : List(String)\n # *text* read as a pattern, or why it is not one: an invalid method, no\n # `/` to start the path, a `{` in the host, a path a request's cleaned\n # path never equals (`//`, `.`, `..`), a wildcard that is not a whole\n # segment, `{$}` or `{name...}` before the end, a name that is not an\n # identifier or is used twice, a malformed percent escape.\n def self.parse(text : String) : Pattern | RouterError\n # *path*'s segments after its first `/`, each percent-decoded: `/` is\n # one empty piece, and a trailing slash is a last empty one. Nil for a\n # path that is not rooted or holds a malformed escape. Decoded a segment\n # at a time, so `%2F` is inside a segment, never between two.\n def self.pieces(path : String) : Array(String) | ::Nil\n # How this pattern's requests relate to *other*'s, methods and paths\n # together: `EQUIVALENT`, `MORE_GENERAL`, `MORE_SPECIFIC`, `DISJOINT` or\n # `OVERLAPS`.\n def relation(other : Pattern) : Int32\n # The pattern as it was written.\n def to_s : String\nend\n\n# Requests sent to handlers by pattern, Go 1.22's `ServeMux`:\n#\n# router = Router.new\n# router.handle(\"GET /users/{id}\") { |req, params| Response.new(200, params[\"id\"]) }\n# router.handle(\"/static/\") { |req, params| FileServer.serve(root, req) }\n# Server.serve(listener, router, workers: 1)\n#\n# A `Router` is written as it is set up and holds procs nobody checked,\n# so it is not `Share`: it is served on one thread (`workers: 1`), as the\n# handler or from a block that calls it, and refused at the argument on\n# more than one worker; `SharedRouter` below routes on every worker.\n#\n# `handle` panics on a pattern that is not one, on one that conflicts with\n# a pattern already registered, and after the router has answered a\n# request. `call` answers with the most specific pattern's handler, or a\n# 307 (to the cleaned path, or to a subtree's `/`), a 405 with `Allow`, a\n# 400 for a malformed escape, a 404.\npub class Router\n # The answer to *req*: its handler's, or the router's own 307, 400, 404\n # or 405.\n def call(req : Request) : Response\n # *path* - rooted - with `.` and empty segments dropped and `..` taking\n # the one before it off, its trailing slash kept: Go's `cleanPath`.\n def self.clean(path : String) : String\n # Sends the requests *pattern* matches to the block, with the request\n # and the wildcards' values by name.\n def handle(pattern : String, &handler : (Request, Hash(String, String) -> Response)) : Nil\n # A `Host` value without its port, as Go's `stripHostPort` has it: an\n # IPv6 address in brackets loses the brackets with the port.\n def self.host_of(value : String) : String\n def initialize\n # *pattern* parsed, panicking on text that is not a pattern and on one\n # that conflicts with one of *known*: what `handle` registers, here and\n # in `SharedRouter`.\n def self.parse_against(pattern : String, known : P) : Pattern forall P\n # The patterns registered, in the order they were.\n def patterns : Array(Pattern)\n def self.plain(status : Int32, text : String) : Response\n def self.redirect(path : String, query : String | ::Nil) : Response\n # Which of *patterns* - a `Router`'s or a `SharedRouter`'s - takes *req*:\n # its index and the wildcards' values, or the router's own 307, 400, 404\n # or 405.\n def self.route(patterns : P, req : Request) : ::Tuple(Int32, Hash(String, String)) | Response forall P\nend\n\n# Why a pattern is refused: `Pattern.parse`'s answer for text that is not\n# a pattern. `Router#handle` panics with its `message`.\npub struct RouterError\n def initialize(reason : String)\n def reason : String\n def to_s : String\nend\n\n# `Router`'s table for `Server.serve` on every worker, which route at\n# once (SPEC.md III.4.13):\n#\n# routes = SharedRouter.new\n# .handle(\"GET /users/{id}\") { |req, params| Response.new(200, params[\"id\"]) }\n# .handle(\"/static/\") { |req, params| FileServer.serve(root, req) }\n# Server.serve(listener, routes)\n#\n# The same patterns, precedence, conflicts and answers as `Router`'s, by\n# the same code (`Router.route`). What differs is what a handler may\n# capture: `handle` is `@[Share]`, so each block is checked where it is\n# written as a parallel task's is - every value it captures `Share`, a\n# pool behind a `Mutex`, a count in an `Atomic` - and kept as a\n# `SharedProc`. A `Router` holds procs nobody checked, so `Server.serve`\n# takes one under `workers: 1` alone. A `SharedRouter` is never written:\n# `handle` answers a new router with the route added, so the one being\n# served is the table it was when the server was given it, with no\n# `handle` after the first request to refuse.\npub class SharedRouter\n # The answer to *req*: its handler's, or the router's own 307, 400, 404\n # or 405.\n def call(req : Request) : Response\n # This router with the requests *pattern* matches sent to the block,\n # with the request and the wildcards' values by name. Panics as\n # `Router#handle` does on a pattern that is not one or that conflicts.\n def handle(pattern : String, &handler : (Request, Hash(String, String) -> Response)) : SharedRouter\n def initialize(patterns : List(Pattern) = List(Pattern).new([] of Pattern), handlers : List(SharedProc(::Tuple(Request, Hash(String, String)), Response)) = List(SharedProc(::Tuple(Request, Hash(String, String)), Response)).new([] of SharedProc(::Tuple(Request, Hash(String, String)), Response)))\n # The patterns registered, in the order they were.\n def patterns : Array(Pattern)\nend\n\nimpl Error for RouterError\n def message : String\nend\n" }, { "name": "std/rsa", "path": "src/std/rsa.iyi", - "sourceSha256": "bdfe4106b5fdda512740f693a6ff1d10748670ddb62c0285029f89f830aaddd8", + "sourceSha256": "25ebe39e70765207e03dfb418ac251c7d7f3ecd0f3d419baf428add7cbc354bd", "surface": "module std/rsa\n\n# What reading a key answers for bytes that are not an RSA key.\npub struct RsaError\n def initialize(message : String)\n def to_s : String\nend\n\n# The hashes a signature is made over.\npub enum RsaHash : Int32\n SHA1 = 0\n SHA256 = 1\n SHA384 = 2\n SHA512 = 3\n def digest(data : Array(UInt8)) : Array(UInt8)\n # The DER of a DigestInfo up to the digest itself (RFC 8017 section 9.2,\n # note 1): the algorithm's OID with NULL parameters, then the OCTET\n # STRING's header.\n def digest_info_prefix : Array(UInt8)\n def size : Int32\nend\n\n# An RSA private key with its CRT parameters (RFC 8017 section 3.2, the\n# first representation and the second together), which signs.\npub struct RsaPrivateKey\n # The modulus's length in bits, and in bytes: a signature's length.\n def bits : Int32\n # The one key in a PEM text: an `RSA PRIVATE KEY` block (PKCS #1) or a\n # `PRIVATE KEY` block (PKCS #8).\n def self.from_pem(text : String) : RsaPrivateKey | RsaError\n # An RSAPrivateKey (appendix A.1.2) of two primes: version 0 and the\n # eight numbers.\n def self.from_pkcs1(der : Array(UInt8)) : RsaPrivateKey | RsaError\n # A PKCS #8 PrivateKeyInfo or OneAsymmetricKey (RFC 5958) naming\n # rsaEncryption, its privateKey an RSAPrivateKey. Attributes are passed\n # over; a public key, which version 1 may carry, must be the key's own.\n def self.from_pkcs8(der : Array(UInt8)) : RsaPrivateKey | RsaError\n def initialize(public_key : RsaPublicKey, p : BigInt, q : BigInt, dp : BigInt, dq : BigInt, qi : BigInt, prf : Array(UInt8))\n # The key from its eight numbers, each big-endian - n, e, d, p, q, dp,\n # dq, qi - or why they are not one.\n def self.new(modulus : Array(UInt8), exponent : Array(UInt8), d : Array(UInt8), p : Array(UInt8), q : Array(UInt8), dp : Array(UInt8), dq : Array(UInt8), qi : Array(UInt8)) : RsaPrivateKey | RsaError\n def public_key : RsaPublicKey\n # RSASSA-PKCS1-v1_5 (section 8.2.1) of *message* under *hash*: 0x00\n # 0x01, the 0xFF padding, 0x00, the hash's DigestInfo and the digest.\n def sign_pkcs1v15(hash : RsaHash, message : Array(UInt8) | String) : Array(UInt8)\n # RSASSA-PSS (section 8.1.1) of *message* under *hash*, MGF1 over the\n # same hash: EMSA-PSS-ENCODE (section 9.1.1) with a salt as long as the\n # digest, drawn as the module's header says.\n def sign_pss(hash : RsaHash, message : Array(UInt8) | String) : Array(UInt8)\n def size : Int32\nend\n\npub struct RsaPublicKey\n # The modulus's length in bits, and in bytes: a signature's length.\n def bits : Int32\n def exponent : BigInt\n # An RSAPublicKey (RFC 8017 appendix A.1.1): SEQUENCE { modulus, exponent }.\n def self.from_pkcs1(der : Array(UInt8)) : RsaPublicKey | RsaError\n # A SubjectPublicKeyInfo (RFC 5280 section 4.1.2.7) naming rsaEncryption.\n def self.from_spki(der : Array(UInt8)) : RsaPublicKey | RsaError\n def self.from_spki(value : DerValue) : RsaPublicKey | RsaError\n def initialize(modulus : BigInt, exponent : BigInt, bits : Int32)\n def modulus : BigInt\n # The key from a modulus and an exponent, each big-endian.\n def self.new(modulus : Array(UInt8), exponent : Array(UInt8)) : RsaPublicKey | RsaError\n def size : Int32\n # Whether *signature* is the PKCS #1 v1.5 signature (RSASSA-PKCS1-v1_5,\n # section 8.2.2) of *message* under *hash*.\n def verify_pkcs1v15(hash : RsaHash, message : Array(UInt8) | String, signature : Array(UInt8)) : Bool\n # Whether *signature* is an RSASSA-PSS signature (section 8.1.2) of\n # *message* under *hash*, MGF1 over the same hash, with a salt of\n # *salt_length* bytes (the digest's length when nil).\n def verify_pss(hash : RsaHash, message : Array(UInt8) | String, signature : Array(UInt8), salt_length : Int32 | ::Nil = nil) : Bool\nend\n\nimpl Error for RsaError\n def message : String\nend\n" }, + { + "name": "std/secure_dns", + "path": "src/std/secure_dns.iyi", + "sourceSha256": "2ec99a10b574c9ffc740e71924e0db2bf74a7e3c884b2f69ca400f132d1acccf", + "surface": "module std/secure_dns\n\n# DNS over HTTPS to one URL: see the module's header.\npub class DnsOverHttps\n # The message a response carries: a 200 of `application/dns-message`\n # (parameters after it allowed, the type in any case) of at most\n # `MAX_ANSWER` bytes. RFC 8484 4.2.1 calls any 2xx a success, but no\n # other 2xx carries an answer to a query: 204 has no body and 206 a part\n # of one.\n def self.answer(response : Response, label : String) : Bytes | DnsError | Cancelled\n # RFC 8484 4.1's `dns` parameter: base64url (RFC 4648 5) \"with padding\n # characters removed\".\n def self.base64url(message : String) : String\n def close : Nil\n # Connections the HTTP client has opened, and requests sent over one it\n # kept.\n def connections_made : Int32\n def connections_reused : Int32\n def exchange(queries : Array(Bytes), timeout_ms : Int32) : Array(Bytes) | DnsError | Cancelled\n # *url* is `https://`: an `http://` upstream would be plaintext with\n # extra steps, and is refused. *address*, when given, is where the URL's\n # host is, and nothing is asked to find it. *config* holds the roots the\n # server must chain to and the ALPN offered: by default the system's\n # roots, and `h2` before `http/1.1`.\n def initialize(url : String, address : String | ::Nil = nil, config : TlsConfig | ::Nil = nil, method : String = \"POST\")\n def label : String\n # \"POST\" or \"GET\".\n def method : String\n def url : String\n def zero_id? : Bool\nend\n\n# DNS over TLS to one server: see the module's header.\npub class DnsOverTls\n # The address connected to, IPv4 or IPv6.\n def address : String\n def close : Nil\n def config : TlsConfig\n # Connections this upstream has opened: what a program reads to see its\n # connection kept.\n def connections_made : Int32\n def exchange(queries : Array(Bytes), timeout_ms : Int32) : Array(Bytes) | DnsError | Cancelled\n def idle_ms : Int32\n # *name* is what the server's certificate is checked for and *address*\n # where it is; *config* holds the roots it must chain to (the system's\n # by default) and the versions spoken. A name that is not a host name,\n # or an address that is not one, is the program's mistake and refused\n # here.\n def initialize(name : String, address : String, port : Int32 = PORT, config : TlsConfig | ::Nil = nil, idle_ms : Int32 = IDLE_MS)\n def label : String\n # The authentication domain name: what the certificate must carry.\n def name : String\n def port : Int32\n def zero_id? : Bool\nend\n\nimpl Std::Socket::DnsUpstream for DnsOverTls\nend\n\nimpl Std::Socket::DnsUpstream for DnsOverHttps\nend\n" + }, { "name": "std/semantic_version", "path": "src/std/semantic_version.iyi", @@ -463,6 +505,12 @@ "sourceSha256": "5cc08e55afdde00aa2b041123aaeabc9a86b56095a93fb74ce3d102587eea377", "surface": "module std/set\n\nclass ::Set(T)\n # Intersection: what is in both, in this set's order.\n def &(other : Set(U)) : Set(T) forall U\n # `+` is union: two sets added are one set.\n def +(other : Set(U)) : Set(T | U) forall U\n # Adds value and answers the set, for chaining: `set << 1 << 2`.\n def <<(value : T) : Set(T)\n # The same members, in any order, where one element type holds the\n # other, as tuples compare (src/iyi/object.iyi).\n def ==(other : Set(U)) : Bool forall U\n # Symmetric difference: what is in exactly one of the two.\n def ^(other : Set(U)) : Set(T | U) forall U\n # Adds every element of other; answers the set.\n def concat(other : Set(T)) : Set(T)\n # No element in common; two empty sets are disjoint.\n def disjoint?(other : Set(U)) : Bool forall U\n # A copy with the same elements in the same order.\n def dup : Set(T)\n # Whether value, of any type, is a member: one this set's type cannot\n # hold is not.\n protected def has_member?(value) : Bool\n # Equal sets hash alike, and unequal ones apart. Each member's hash goes\n # through splitmix64's finaliser before a wrapping sum, which does not\n # care about order the way the sets do not, and the size is folded in\n # after, as `Array#hash` folds it. The members' hashes summed as they\n # were put a set of small integers at their total, `Int32#hash` being the\n # value: {1, 4} and {2, 3} were one key, the 11,175 sets {i, j} with\n # i < j < 150 had 297 hashes between them, and a table of such keys grew\n # quadratically.\n def hash : Int32\n # At least one element in common.\n def intersects?(other : Set(U)) : Bool forall U\n # A subset that is not the same set.\n def proper_subset_of?(other : Set(U)) : Bool forall U\n def proper_superset_of?(other : Set(U)) : Bool forall U\n # Every element of this set is in other. The empty set is a subset of\n # every set, itself included.\n def subset_of?(other : Set(U)) : Bool forall U\n def superset_of?(other : Set(U)) : Bool forall U\n # Union: everything in either, this set's elements first. The other set\n # may be of another element type, as everything below may: the union of\n # a `Set(Int32)` and a `Set(Int32?)` is a `Set(Int32?)`, either way round.\n # Restricted to `Set(T)`, a set of the narrower type was taken for the\n # wider one and not back, and where the body then asked the narrower set\n # about a member of the wider type it failed to compile inside this file:\n # `Set(Int32?) == Set(Int32)`, `&`, `^`, `subset_of?` and the rest, while\n # `Set(Int32) == Set(Int32?)` answered false for the same members.\n def |(other : Set(U)) : Set(T | U) forall U\nend\n" }, + { + "name": "std/shared_proc", + "path": "src/std/shared_proc.iyi", + "sourceSha256": "a6654265e51523b44a081f94d9eb1b5603fa0b419697e1b45c5f1a7e90161949", + "surface": "module std/shared_proc\n\npub struct SharedProc(A, R)\n def call(argument : A) : R\n # The block, checked as a parallel task's block is, as a value any\n # thread may call.\n def self.of(&block : (A -> R)) : SharedProc(A, R)\nend\n" + }, { "name": "std/shlex", "path": "src/std/shlex.iyi", @@ -472,7 +520,7 @@ { "name": "std/signal", "path": "src/std/signal.iyi", - "sourceSha256": "9dc862b6a18ae292c72c36f482c3bc37efe7af9653b1f388f8d0883128d39a27", + "sourceSha256": "c2d35f9fe0952237ddd8df4c87b2c63520198d9bb1c71a3d76df2ec1779fc748", "surface": "module std/signal\n\npub enum Signal : Int32\n HUP = 1\n INT = 2\n QUIT = 3\n PIPE = 13\n ALRM = 14\n TERM = 15\n WINCH = 28\n USR1 = 10\n USR2 = 12\n CHLD = 17\n # Runs the block for each arrival of one of *signals*, in this task,\n # until the task is cancelled, and answers `Cancelled` then. Arrivals\n # while the block runs are kept, each signal once.\n def self.each(*signals : Signal, &block : (Signal -> _)) : Cancelled\n # Sets each of *signals* to be ignored by the process: its arrivals are\n # dropped, and one that was pending is forgotten. Windows can ignore\n # Ctrl-C (`INT`) alone; anything else is refused there.\n def self.ignore(*signals : Signal) : Nil\n # Gives each of *signals* its default action back: INT and TERM end the\n # process again, WINCH is ignored again. Windows: `INT` alone.\n def self.reset(*signals : Signal) : Nil\n # Parks until one of *signals* arrives; answers it.\n def self.wait(*signals : Signal) : Signal | Cancelled\nend\n" }, { @@ -484,14 +532,14 @@ { "name": "std/smtp", "path": "src/std/smtp.iyi", - "sourceSha256": "a683f9c8884d341eec2f4161f47ce2172425ddcc112f434ac872a1c520ba73b9", - "surface": "module std/smtp\n\npub enum SmtpAuth : Int32\n Plain = 0\n Login = 1\n CramMd5 = 2\nend\n\n# A session with a mail server.\npub class SmtpClient\n def self.ascii?(text : String) : Bool\n # AUTH as *user* with *password*, by *mechanism*, or by the first the\n # server offers of PLAIN, LOGIN and CRAM-MD5.\n def auth(user : String, password : String, mechanism : SmtpAuth | ::Nil = nil) : SmtpError | ::Nil\n # Whether a password may go over a connection without TLS.\n def cleartext_auth : Bool\n # Whether a password may go over a connection without TLS.\n def cleartext_auth=(value : Bool) : Bool\n # The connection closed, without QUIT.\n def close : Nil\n def closed? : Bool\n # *line* sent, and its reply.\n def command(line : String) : SmtpReply | SmtpError\n # A session with *host* on *port*: connected, greeted, EHLO said, and\n # under TLS as *security* asks.\n def self.connect(host : String, port : Int32, security : SmtpSecurity = SmtpSecurity::StartTls, config : TlsConfig = TlsConfig.new, helo : String | ::Nil = nil, timeout_ms : Int32 = TIMEOUT_MS) : SmtpClient | SmtpError\n def self.digit?(b : UInt8) : Bool\n # What EHLO named, by its keyword in upper case: each extension's\n # parameters (\"\" when none). Empty after HELO.\n def extensions : Hash(String, String)\n # The server's 220.\n def greeting : SmtpReply\n def initialize(socket : IyiSocket, host : String, helo : String, timeout_ms : Int32)\n # `[192.0.2.1]`, or `[IPv6:2001:db8::1]` (section 4.1.3).\n def self.literal(address : String) : String\n def self.mechanism_name(mechanism : SmtpAuth) : String\n protected def open(security : SmtpSecurity, config : TlsConfig) : SmtpError | ::Nil\n # QUIT, its 221 read, and the connection closed.\n def quit : SmtpError | ::Nil\n # The next reply, every line of it.\n def reply : SmtpReply | SmtpError\n # *data*, a whole message, from *sender* to *recipients*; *utf8* when\n # the message is RFC 6532's, as SMTPUTF8 carries.\n def send(sender : String, recipients : Array(String), data : String, utf8 : Bool = false) : SmtpError | ::Nil\n # *message* to its recipients - To, Cc and Bcc - from its From.\n def send(message : Mail::Message) : SmtpError | ::Nil\n # *data* as DATA sends it (section 4.5.2): line breaks CRLF, a dot\n # doubled at a line's start, a CRLF at the end if it had none, and the\n # lone dot after.\n def self.stuffed(data : String) : String\n # Whether the session is under TLS.\n def tls? : Bool\n def self.unsafe?(text : String) : Bool\nend\n\n# What a session answered that it should not have, or could not do, as a\n# value. `code` is the reply's code, 0 when there was no reply to have\n# one; `reply` the reply's text, its lines joined with newlines.\npub struct SmtpError\n def cancelled? : Bool\n def code : Int32\n def initialize(reason : String, code : Int32 = 0, reply : String = \"\", timed_out : Bool = false, cancelled : Bool = false)\n def reason : String\n def reply : String\n # Whether a read or a write waited `timeout_ms` and nothing came.\n def timed_out? : Bool\n def to_s : String\nend\n\n# One reply: its code and its lines' text, the code and separator taken\n# off each.\npub struct SmtpReply\n def code : Int32\n def initialize(code : Int32, lines : Array(String))\n def lines : Array(String)\n def text : String\nend\n\n# How the connection is kept from the wire.\npub enum SmtpSecurity : Int32\n StartTls = 0\n Tls = 1\n Cleartext = 2\nend\n\nimpl Error for SmtpError\n def message : String\nend\n" + "sourceSha256": "2d705943fb25cab029ae3af971d812a1df1c514695545e451fa4e99eea77500c", + "surface": "module std/smtp\n\npub enum SmtpAuth : Int32\n Plain = 0\n Login = 1\n CramMd5 = 2\nend\n\n# A session with a mail server.\npub class SmtpClient\n def self.ascii?(text : String) : Bool\n # AUTH as *user* with *password*, by *mechanism*, or by the first the\n # server offers of PLAIN, LOGIN and CRAM-MD5.\n def auth(user : String, password : String, mechanism : SmtpAuth | ::Nil = nil) : SmtpError | ::Nil\n # Whether a password may go over a connection without TLS.\n def cleartext_auth : Bool\n # Whether a password may go over a connection without TLS.\n def cleartext_auth=(value : Bool) : Bool\n # The connection closed, without QUIT.\n def close : Nil\n def closed? : Bool\n # *line* sent, and its reply.\n def command(line : String) : SmtpReply | SmtpError\n # A session with *host* on *port*: connected, greeted, EHLO said, and\n # under TLS as *security* asks.\n def self.connect(host : String, port : Int32, security : SmtpSecurity = SmtpSecurity::StartTls, config : TlsConfig = TlsConfig.new, helo : String | ::Nil = nil, timeout_ms : Int32 = TIMEOUT_MS) : SmtpClient | SmtpError\n def self.digit?(b : UInt8) : Bool\n # What EHLO named, by its keyword in upper case: each extension's\n # parameters (\"\" when none). Empty after HELO.\n def extensions : Hash(String, String)\n # The server's 220.\n def greeting : SmtpReply\n def initialize(socket : IyiSocket, host : String, helo : String, timeout_ms : Int32)\n # `[192.0.2.1]`, or `[IPv6:2001:db8::1]` (section 4.1.3).\n def self.literal(address : String) : String\n def self.mechanism_name(mechanism : SmtpAuth) : String\n protected def open(security : SmtpSecurity, config : TlsConfig) : SmtpError | ::Nil\n # QUIT, its 221 read, and the connection closed.\n def quit : SmtpError | ::Nil\n # The next reply, every line of it.\n def reply : SmtpReply | SmtpError\n # *data*, a whole message, from *sender* to *recipients*; *utf8* when\n # the message is RFC 6532's, as SMTPUTF8 carries. *data* is sent as it\n # is, so a message written with `Mail::Message#to_s` must have had its\n # Date to get here. No recipients, or an address that holds a line break\n # or an angle bracket, panics (`SMTP: a message with no recipients`,\n # `SMTP: ... is not an address to put in angle brackets`); every other\n # failure is the `SmtpError` returned.\n def send(sender : String, recipients : Array(String), data : String, utf8 : Bool = false) : SmtpError | ::Nil\n # *message* to its recipients - To, Cc and Bcc - from its From. The\n # message must have a From and a Date, as `Mail::Message#to_s` asks\n # (`msg.date = Mail.date(...)`), and at least one recipient. A message\n # without them is the caller's mistake and panics: `SMTP: a message has\n # no From`, then `to_s`'s `mail: a message has no Date`, then `SMTP: a\n # message with no recipients`. Every other failure - the connection, the\n # server's replies, a size or extension the server refuses - is the\n # `SmtpError` returned.\n def send(message : Mail::Message) : SmtpError | ::Nil\n # *data* as DATA sends it (section 4.5.2): line breaks CRLF, a dot\n # doubled at a line's start, a CRLF at the end if it had none, and the\n # lone dot after.\n def self.stuffed(data : String) : String\n # Whether the session is under TLS.\n def tls? : Bool\n def self.unsafe?(text : String) : Bool\nend\n\n# What a session answered that it should not have, or could not do, as a\n# value. `code` is the reply's code, 0 when there was no reply to have\n# one; `reply` the reply's text, its lines joined with newlines.\npub struct SmtpError\n def cancelled? : Bool\n def code : Int32\n def initialize(reason : String, code : Int32 = 0, reply : String = \"\", timed_out : Bool = false, cancelled : Bool = false)\n def reason : String\n def reply : String\n # Whether a read or a write waited `timeout_ms` and nothing came.\n def timed_out? : Bool\n def to_s : String\nend\n\n# One reply: its code and its lines' text, the code and separator taken\n# off each.\npub struct SmtpReply\n def code : Int32\n def initialize(code : Int32, lines : Array(String))\n def lines : Array(String)\n def text : String\nend\n\n# How the connection is kept from the wire.\npub enum SmtpSecurity : Int32\n StartTls = 0\n Tls = 1\n Cleartext = 2\nend\n\nimpl Error for SmtpError\n def message : String\nend\n" }, { "name": "std/socket", "path": "src/std/socket.iyi", - "sourceSha256": "47a6354bf8717cd28c325ac48c333d7e536fb29cf524bc22e18716216db54c53", - "surface": "module std/socket\n\n# What a resolver answers and is built from, under the names a program\n# that connects by name already imports from here.\npub alias DnsError = Std::Dns::DnsError\n\n# Either family's address: what a resolver answers for a name.\npub alias IPAddress = IPv4Address | IPv6Address\n\npub struct IPv4Address\n def b0 : UInt8\n def b1 : UInt8\n def b2 : UInt8\n def b3 : UInt8\n def initialize(b0 : UInt8, b1 : UInt8, b2 : UInt8, b3 : UInt8)\n def to_s : String\nend\n\n# Sixteen bytes in network order, held as two words: `high` is bytes 0 to\n# 7. Written the RFC 5952 way - lowercase, no leading zeros, the longest\n# run of two or more zero groups as `::`, the first of two equal runs -\n# and an IPv4-mapped address with its dotted tail, `::ffff:192.0.2.1`.\npub struct IPv6Address\n # Byte *index*, 0 to 15.\n def [](index : Int32) : UInt8\n # The 16-bit group *index*, 0 to 7. Not `group`: that name is the task\n # group's in iyi (SPEC.md III.4.9).\n def hextet(index : Int32) : Int32\n def high : UInt64\n def initialize(high : UInt64, low : UInt64)\n def low : UInt64\n # The sixteen bytes at *bytes*, network order.\n def self.new(bytes : Pointer(UInt8)) : IPv6Address\n def to_s : String\nend\n\npub class IyiSocket\n alias AcceptExFn = Proc(UInt64, UInt64, ::Pointer(Void), Int32, Int32, Int32, Pointer(Int32), ::Pointer(Void), Int32)\n alias ConnectExFn = Proc(UInt64, ::Pointer(Void), Int32, ::Pointer(Void), Int32, Pointer(Int32), ::Pointer(Void), Int32)\n lib LibAdvapi32\n end\n lib LibWs2_32\n # Two slots of an address and the sixteen bytes after it, room for\n # the widest family this file makes.\n struct AcceptAddresses\n end\n struct PollFd\n end\n # A WSABUF, and the two address slots AcceptEx fills in - each a\n # `sockaddr_in` and the sixteen bytes it insists on after one. They are\n # declared here so that the callers below can keep them on the stack:\n # the kernel reads and writes these while the fiber is parked, and a\n # heap chunk whose only reference is a local the posting call has\n # finished with is a chunk the collector can hand to somebody else\n # first. `bench/server_load.iyi` died of a memory fault on exactly\n # that, with the accepted addresses on the heap.\n struct WSABuf\n end\n end\n # Room for any family's `sockaddr`, zeroed, so a unix path the kernel\n # did not end with a NUL still ends.\n def self.__address_room : Pointer(UInt8)\n # A `sockaddr` the kernel filled, as text: dotted IPv4, IPv6 written as\n # RFC 5952 has it, or a unix socket's path.\n def self.__address_text(addr : Pointer(UInt8), family : Int32) : String\n # Winsock's address length is an `int*` where POSIX has a `socklen_t*`:\n # the same width and the same bytes, so the callers above keep one\n # signature.\n def self.__sys_accept(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32\n def self.__sys_accept_posted(s : UInt64, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32\n def self.__sys_addr_size(family : Int32) : Int32\n def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n # Every close of a socket in this file goes through here. `__iyi_close` is\n # the descriptor close, and on Windows a socket is not a descriptor:\n # `CloseHandle` on a SOCKET releases the kernel handle and leaves Winsock's\n # half of it standing, and the graceful close that makes a peer's read\n # answer EOF rather than a reset is `closesocket`'s.\n def self.__sys_close(fd : Int32) : Nil\n # The failures above arrive as the Winsock number negated.\n def self.__sys_code(result : Int64) : Int32\n def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n # A unix socket connects blocking, and is made non-blocking after:\n # `ConnectEx` is TCP's alone, and the plain `connect` on a socket\n # already non-blocking is not an answer here. Measured on the runner,\n # it came back connected to a path whose listener had closed, and,\n # waited for with `select`, refused a listener that was there. On a\n # socket still blocking it is Go's `connect` on this platform, and\n # its answer is the outcome: the listener is on this machine, and its\n # kernel queues the connection or refuses it at once.\n def self.__sys_connects_blocking?(family : Int32) : ::Bool\n # The plain call, not `__sys_connect`'s `ConnectEx`, which is TCP's: a\n # datagram socket's connect records the peer and returns.\n def self.__sys_dgram_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n # The plain call, not `__sys_send`'s posted one: a datagram of a\n # query's size goes out whole or not at all, and there is no room to\n # wait for.\n def self.__sys_dgram_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n # The resolver's datagram socket: UDP, non-blocking, and - unlike\n # `std/udp`'s - with Windows' report of an ICMP port-unreachable left\n # on. This one is connected to one name server, so the report says\n # that server has nothing on its port, and the next server is asked at\n # once instead of after the timeout.\n def self.__sys_dgram_socket(family : Int32) : Int32\n # Not `await_readable`: the scheduler's readable-wait is a zero-byte\n # overlapped read, and on a datagram socket that read takes the\n # datagram and leaves nothing for the receive after it (measured, and\n # the reason `std/udp` posts its receive instead). `WSAPoll` asks\n # without taking, but it blocks the thread for as long as it is told,\n # and every other task on the thread with it; so it is asked with no\n # wait at all, and the task sleeps on the scheduler between askings -\n # at Windows' timer tick, 15.6 ms, which is nothing next to a name\n # server's round trip.\n def self.__sys_dgram_wait(fd : Int32, ms : Int32) : ::Bool | Cancelled\n # The machine's resolver configuration, read from the registry:\n #\n # - name servers: the TCP/IP service's own `NameServer` (a policy's),\n # then each IPv4 interface that has an address - `NameServer` when\n # it was set by hand, else `DhcpNameServer` - then each IPv6\n # interface's `NameServer`, without repeats, at most\n # `ResolvConf::MAX_NAMESERVERS`;\n # - search list: `SearchList`, else the primary DNS suffix (`Domain`,\n # else `DhcpDomain`).\n #\n # What it cannot see: which interface is up (a stale DHCP lease on an\n # unplugged adapter still has an address in the registry), the order\n # of the interfaces' metrics (they are read in the registry's order),\n # IPv6 servers learned from router advertisements or DHCPv6 (stored as\n # binary, not read), and per-connection suffixes. Those need\n # iphlpapi; see the `lib` above for why it is not linked.\n def self.__sys_dns_config : ResolvConf\n # The failure the last call left, negated. The POSIX arms answer the\n # kernel's errno this way — Linux the negative return itself, darwin\n # `errno` read after a -1 — and this answers Winsock's number in the same\n # shape, so nothing above learns a second convention: a negative result\n # is the failure, and the two predicates below are the only things that\n # read which failure it was.\n def self.__sys_error : Int64\n # The pointer to one of mswsock's extension functions, asked of the\n # socket that will use it. Fetched where it is used rather than cached:\n # it is one call, and both callers are about to do a TCP handshake.\n def self.__sys_extension(s : UInt64, low : UInt64, high : UInt64) : Pointer(UInt64)\n def self.__sys_getpeername(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n # A SOCKET from the `Int32` the class carries, and back: see the note\n # above the `lib` block for why that is lossless.\n def self.__sys_handle(fd : Int32) : UInt64\n def self.__sys_in_progress?(result : Int32) : ::Bool\n # Winsock caps a backlog at SOMAXCONN, 200, and turns the next connect\n # away as refused - where Linux holds it - unless the backlog is asked\n # as SOMAXCONN_HINT(n), which is -n: 220 connects to a listener asked\n # for 256 lost 20.\n def self.__sys_listen(fd : Int32, backlog : Int32) : Int32\n # Windows raises no SIGPIPE; a write to a closed connection answers\n # WSAECONNRESET or WSAESHUTDOWN.\n def self.__sys_no_sigpipe(fd : Int32) : Nil\n # The wait a cancelled post still owes the kernel. `CancelIoEx` is a\n # request, and on a socket tied to a completion port the operation is not\n # over when it returns: measured here, the status stays STATUS_PENDING\n # and the address block, the descriptor and the OVERLAPPED are the\n # kernel's until the completion packet is taken *off the port* — the\n # poller's `GetQueuedCompletionStatus` is what writes them. The same\n # measurement on an unassociated socket had the status written by\n # `CancelIoEx` itself, which is why this is not obvious. So\n # `WSAGetOverlappedResult`'s own wait flag cannot be the wait either: it\n # waits on the OVERLAPPED's event, the scheduler's OVERLAPPED has none,\n # and the socket handle it falls back to is never signalled for a socket\n # on a port — it hung forever on the first cancelled receive tried.\n #\n # What ends the operation is the poller, so the frame parks once more and\n # lets it. `IyiScheduler.cancel` has already unregistered this fiber,\n # which is what would have left the packet to be dropped; parking puts it\n # back, and the wake means the dequeue has happened. Asked first, because\n # a completion the poller delivered *before* the cancel is already over\n # and no second packet is coming — parking on one would never wake.\n def self.__sys_overlapped_await(s : UInt64, ov : Pointer(UInt8)) : Int64\n # What a posted operation ended with, read off the operation itself. The\n # completion port's byte count cannot tell a failure from a success that\n # transferred nothing, and a successful `AcceptEx` or `ConnectEx` is\n # exactly that — both complete with zero bytes. Nothing waits here: the\n # completion has already arrived, which is what the 0 says.\n def self.__sys_overlapped_result(s : UInt64, ov : Pointer(UInt8)) : Int64\n # TCP names its protocol; a unix socket has only the one, and says 0.\n def self.__sys_protocol(family : Int32) : Int32\n def self.__sys_recv(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64\n # The send as the wait, with the write's deadline on it: a deadline\n # that comes first cancels the send, and the cancellation's completion\n # wakes this frame, so what the send did is known when it returns.\n def self.__sys_send_posted(s : UInt64, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64\n def self.__sys_set_nonblock(fd : Int32) : Int32\n # Not SO_REUSEADDR, which on Windows lets a *different* socket bind a\n # port this one is already bound to, and the last binder takes the\n # packets; what the POSIX arms ask for with it is only the waiver of\n # the TIME_WAIT wait, which Windows does not impose on a listener's\n # port. The opposite: SO_EXCLUSIVEADDRUSE (~SO_REUSEADDR, -5). Without\n # it the port was not the listener's either - a bind to 127.0.0.1:P went\n # through while 0.0.0.0:P listened, and the other way round, from any\n # process, and the more specific listener took the connections.\n def self.__sys_set_reuseaddr(fd : Int32) : Int32\n # IPV6_V6ONLY (27) at IPPROTO_IPV6 (41). Windows' default already, and\n # said anyway: the default is a registry's, and this is the program's.\n def self.__sys_set_v6only(fd : Int32) : Int32\n # SO_ERROR at SOL_SOCKET, as on the POSIX arms. `__sys_connect` above has\n # already waited for the handshake and answered what it ended with, so\n # the shared `connect` reaches this only when its task was cancelled —\n # and `wait_writable` answers `Cancelled` before it gets here. It reads\n # the option rather than guessing, so it is the truth for anything that\n # does ask.\n def self.__sys_sock_error(fd : Int32) : Int32\n def self.__sys_socket(family : Int32) : Int32\n def self.__sys_winsock : Nil\n # WSAEWOULDBLOCK answers both questions here: it is what a non-blocking\n # call with nothing to do says, and it is what a connect that is under\n # way says, where POSIX has EAGAIN and EINPROGRESS for the two.\n def self.__sys_would_block?(result : Int64) : ::Bool\n def accept : IyiSocket | Cancelled | SocketError\n def self.await_readable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n def self.await_writable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n # A timeout as the waits take it: milliseconds, -1 for none.\n def self.bound(ms : Int32 | ::Nil) : Int32\n protected def bound_path(path : String) : Nil\n # *path* with the NUL a system call reads to.\n def self.c_path(path : String) : Pointer(UInt8)\n def close : Nil\n def closed? : ::Bool\n # `localhost` is 127.0.0.1 and then ::1: a refused or unreachable\n # 127.0.0.1 is tried again at ::1. Windows names both, ::1 first, and a\n # dev server that binds `localhost` there is often on ::1 alone, where\n # a connect to 127.0.0.1 only was \"connection refused\". 127.0.0.1 goes\n # first because a listener here binds `localhost` there. Windows takes\n # 2 s to refuse a connect, so a ::1-only listener is reached in 2 s\n # (2017 ms measured) and a port nobody holds is refused in 4 s, not 2.\n #\n # Any other host name is resolved by the machine's resolver\n # (`Resolver.system`) and its addresses tried in the order the resolver\n # answers them - IPv4 first, then IPv6, as `localhost` is - until one\n # connects; a name that does not resolve is a `SocketError` whose\n # `unresolved?` is true and whose sentence names the host. It panicked,\n # \"cannot resolve address: \\\"example.com\\\"\", for every name but\n # `localhost`, so `HTTP.get(\"http://example.com/\")` could not run.\n #\n # *timeout_ms* bounds each address's handshake (-1: as long as the\n # kernel waits, which on Linux is about two minutes of SYN retries for\n # a host that drops them); one that runs out is a `SocketError` whose\n # `timed_out?` is true, and the next address is tried.\n def self.connect(host : String, port : Int32, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The same, with a host name resolved by *resolver*: name servers, a\n # hosts file and search list of the program's choosing, rather than the\n # machine's. An address and `localhost` are what they are either way.\n def self.connect(host : String, port : Int32, resolver : Resolver, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # A connect to *addr*, waiting at most *ms* milliseconds for the\n # handshake (-1: as long as the kernel does). The resolver's TCP query\n # is the caller with a bound; `connect` waits as the kernel does, as it\n # always has.\n def self.connect_to(addr : Pointer(UInt8), family : Int32, peer : String, ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The stream socket listening at *path*.\n def self.connect_unix(path : String) : IyiSocket | Cancelled | SocketError\n # *host* and *port* as a message names them: an IPv6 literal in its\n # brackets, as a URL writes it. Bare, `::1:57456` was an address with\n # no port, and `ff02::1:80` another address altogether.\n def self.endpoint(host : String, port : Int32) : String\n def fd : Int32\n # The address family, the platform's number for it: an accepted\n # connection is its listener's, and on Windows the socket `AcceptEx`\n # fills in has to be made in it.\n def initialize(fd : Int32, family : Int32 = AF_INET)\n def self.listen(host : String, port : Int32, backlog : Int32 = 128) : IyiSocket\n def self.listen(port : Int32, backlog : Int32 = 128) : IyiSocket\n # A stream socket at *path*. The path must not exist: a socket file an\n # earlier run left behind is refused rather than taken over, since\n # which program owns it is not something a bind can know. `close`\n # removes the file.\n def self.listen_unix(path : String, backlog : Int32 = 128) : IyiSocket\n def local_address : String\n # The port sits at the same two bytes of a `sockaddr_in` and a\n # `sockaddr_in6`.\n def local_port : Int32\n # `localhost` in any case, and with the one trailing dot of a name\n # written fully qualified: a host name's case is not part of it (RFC\n # 4343), and getaddrinfo, Python and curl all answer 127.0.0.1 for\n # `LOCALHOST` and `localhost.`. Matched exactly, these were \"cannot\n # resolve address: \\\"LOCALHOST\\\"\", and so were `Localhost` and\n # `localhost.`.\n def self.localhost?(host : String) : ::Bool\n def self.make_sockaddr_in(ip : IPv4Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_in6`: family, port, a zero flow label, the address, a zero\n # scope. The family's number and where it sits are the platform's.\n def self.make_sockaddr_in6(ip : IPv6Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_un`: the family, then the path and its NUL. The room for\n # the path is the platform's - 104 bytes on darwin, 108 elsewhere - and a\n # longer one is refused rather than cut, since a cut path is another\n # path.\n def self.make_sockaddr_un(path : String) : Pointer(UInt8)\n # Dotted-decimal IPv4 only: four octets of 0 to 255, no leading zero\n # (`\"010.1.1.1\"` is refused; glibc's `inet_aton` reads `010` as octal 8,\n # CVE-2021-29921, so it must not quietly be 10 here), plus `localhost`.\n # The refusal inspects the host so a stray space shows.\n def self.parse_ip(host : String) : IPv4Address\n # Eight groups of up to four hex digits, `::` once for a run of zero\n # groups, and a dotted IPv4 tail for the last two (`::ffff:1.2.3.4`). A\n # zone (`fe80::1%eth0`) is refused by name: it names an interface, and\n # this parser has no way to ask the platform which.\n def self.parse_ipv6(host : String) : IPv6Address\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The dotted-decimal reading alone, with the refusal's sentence as the\n # answer instead of a panic: the resolver reads the addresses a hosts\n # file and a resolv.conf write, and a line it cannot read is skipped\n # there, not the end of the program.\n def self.read_ipv4(host : String) : IPv4Address | String\n # The reading alone, the refusal's sentence as the answer: see\n # `read_ipv4`.\n def self.read_ipv6(host : String) : IPv6Address | String\n # The bounds on a read's and a write's wait, in milliseconds; nil waits\n # as long as it takes.\n def read_timeout_ms : Int32 | ::Nil\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n def recv(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def recv(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The other end's address and port, as `local_address` and\n # `local_port` give this end's: what a server logs a client by. A unix\n # socket's peer has a path at most, often none (\"\"), and no port. Nil\n # when the other end is gone - the connection was reset or never made -\n # which is the peer's doing and no fault of the program's.\n def remote_address : String | ::Nil\n def remote_port : Int32 | ::Nil\n # Every address *host* stands for, by the machine's resolver: what\n # `connect` tries, in the order it tries them. See `Resolver#resolve`.\n def self.resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def send(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def send(contents : String) : Int32 | Cancelled | SocketError\n # A host and port as the address a call takes, and its family: an IPv6\n # literal bare or in brackets, anything else IPv4.\n def self.sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), Int32)\n # This socket as a stream (`SocketIO`), the same one every time, so\n # what one read took ahead the next reads. It was a stream over the\n # descriptor that read a socket with nothing yet to read as failed.\n def to_io : Std::Socket::SocketIO\n def to_unsafe : Int32\n def write(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def write(contents : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms : Int32 | ::Nil\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\npub alias ResolvConf = Std::Dns::ResolvConf\n\n# A stub resolver: a host name's addresses, from a hosts file and then\n# from name servers, as the C library's resolver finds them - but iyi's,\n# so asking costs no library (see the header of this file).\n#\n# `Resolver.system` is the machine's: `/etc/resolv.conf` and `/etc/hosts`\n# on Linux and darwin, the registry and\n# `%SystemRoot%\\System32\\drivers\\etc\\hosts` on Windows (see\n# `__sys_dns_config` for what the registry can and cannot say). A\n# resolver built with `new` or `from_config` asks exactly what it was\n# given, which is how a test asks a server it runs itself.\n#\n# `resolve` answers every address for a name, IPv4 first and then IPv6\n# (the order `connect` tries `localhost` in), each family in the order\n# the hosts file or the server gave it:\n#\n# 1. An address, or `localhost` (127.0.0.1 and ::1: RFC 6761 6.3 keeps it\n# off the network), is itself. Anything that is not a host name is\n# `invalid_name?`.\n# 2. The hosts file: every line naming the host, in any case. A name\n# found there is not asked about.\n# 3. The names the search list and `ndots` make of it (`DnsName.candidates`),\n# in order. Each is asked for A and then AAAA records; NXDOMAIN or no\n# records moves on to the next, and any other failure ends the lookup\n# with that failure - a server that timed out on `db.corp.example` must\n# not turn the answer into `db.example`'s (musl's rule; glibc moves on).\n#\n# A query is one UDP datagram from a fresh socket (so a fresh source\n# port), with an ID from `Random::Secure`, to each server in turn, each\n# waited for `timeout_ms`, and the round repeated `attempts` times. A\n# datagram that does not carry the ID and the question is ignored and the\n# wait goes on; one from any other address never arrives, because the\n# socket is connected to the server. An answer with TC set is asked again\n# over TCP. SERVFAIL, REFUSED and a server that does not answer move on to\n# the next server; NXDOMAIN is the answer. An answer that cannot be read\n# is ignored like a forgery, and is the failure reported if nothing\n# better arrives before the timeout.\npub class Resolver\n def attempts : Int32\n # A resolver from a parsed resolv.conf and a hosts file's text. A\n # `nameserver` line this resolver cannot use - an IPv6 address with a\n # zone, a typo - is skipped as glibc skips one; with none left, the\n # servers a machine without a configuration is assumed to run.\n def self.from_config(config : ResolvConf, hosts : String = \"\") : Resolver\n # A resolver from a resolv.conf and a hosts file at these paths. A file\n # that is not there reads as empty, as it does to the C library.\n def self.from_files(resolv_conf : String, hosts : String) : Resolver\n # The hosts file's text, read before any server is asked.\n def hosts : String\n # *nameservers* are addresses, each with an optional port:\n # `\"192.0.2.1\"`, `\"192.0.2.1:5353\"`, `\"2001:db8::1\"`,\n # `\"[2001:db8::1]:5353\"`. One that is not is refused here, by name: it is\n # the program's own mistake. None at all is a resolver that reads only\n # *hosts*.\n def initialize(nameservers : Array(String), search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2, hosts : String = \"\")\n def nameservers : Array(String)\n def ndots : Int32\n # The monotonic clock in milliseconds: the runtime's, which its sleep\n # queue keeps deadlines by. A target without the runtime has no socket\n # to wait on either (`__sys_dgram_socket` refuses there), so nothing\n # there reads a time.\n def self.now_ms : Int64\n def self.rcode_name(rcode : Int32) : String\n # Every address *host* stands for: see the class's comment for the\n # order things are asked in.\n def resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def search : Array(String)\n # A name server written as an address with an optional port, or the\n # sentence saying why it is not one.\n def self.server(text : String) : NameServer | String\n # The machine's resolver, read afresh each time: a configuration the\n # network manager rewrote is the one the next lookup uses.\n def self.system : Resolver\n def timeout_ms : Int32\nend\n\n# A connection's own failure: which operation, and the platform's error\n# number for why - or, for a host name that could not be turned into an\n# address, the resolver's `DnsError`, and no number (`code` 0); or, for a\n# protocol layered on the connection (TLS), that protocol's own sentence,\n# and no number either.\npub struct SocketError\n def code : Int32\n # The peer is gone: it reset the connection, or closed it and the write\n # found the pipe broken, or the connection was aborted.\n def disconnected? : ::Bool\n # Why the host's name did not resolve; nil for every other failure.\n def dns_error : DnsError | ::Nil\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil)\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil)\n def initialize(operation : String, code : Int32)\n # What the protocol over the connection said went wrong; nil otherwise.\n def layer_reason : String | ::Nil\n # A failure of the protocol over the connection, in its words: `cannot\n # read from example.com: tls: a record that does not decrypt`.\n def self.layered(operation : String, reason : String) : SocketError\n def operation : String\n def reason : String\n def refused? : ::Bool\n # A wait for the peer that ran past its socket's timeout, carrying the\n # platform's own number for it so `reason` and `timed_out?` read it as\n # they read the kernel's.\n def self.timed_out(operation : String) : SocketError\n # The peer did not answer in time: a connect the network timed out, or a\n # read or write past its socket's timeout.\n def timed_out? : ::Bool\n def unreachable? : ::Bool\n # The host was a name, and no address came of it: `dns_error` says why\n # (`not_found?` for NXDOMAIN, `server_failure?` for SERVFAIL, ...).\n def unresolved? : ::Bool\nend\n\n# A socket as a stream, for what is written against streams: `read_line`,\n# `puts`, `IyiIO.copy` between it and a file or a pipe. Its reads and\n# writes are the socket's own, which park the task and keep its\n# timeouts. A stream has no error to answer, so a read the socket refuses\n# ends the stream, `read_failed?` true, and a write it refuses panics,\n# naming the reason; a program that answers those reads the socket.\n# Writes are buffered: `flush`, or `IyiIO.copy`, which flushes, sends\n# them. Closing it closes the socket.\npub class SocketIO < ::IyiIO\n def initialize(socket : IyiSocket, buffer_size : Int32 = 4096)\n def socket : IyiSocket\nend\n\nimpl Error for SocketError\n def message : String\nend\n" + "sourceSha256": "fe78564802a8a020e4fff120d2f676e30b86a1f5b31611d71c23f2f5b1ffed2f", + "surface": "module std/socket\n\n# What a resolver answers and is built from, under the names a program\n# that connects by name already imports from here.\npub alias DnsError = Std::Dns::DnsError\n\n# An encrypted way to a name server, asked by a resolver `Resolver#encrypted`\n# made instead of its plaintext servers: std/secure_dns's `DnsOverTls`\n# (RFC 7858) and `DnsOverHttps` (RFC 8484). The trait is here and they are\n# there because they speak std/tls and std/http, which import this module,\n# and R-1 refuses the cycle - as std/http's `CookieStore` is std/cookie's\n# to implement.\npub trait DnsUpstream\n # Its connections closed; the next exchange opens new ones.\n abstract def close : Nil\n # The answer to each of *queries*, in their order, each one whole\n # message as it came; or why there are none. *timeout_ms* bounds each\n # wait on the server. An answer is matched to its query by the\n # transport - by ID over one connection, by request over HTTP - and the\n # resolver then reads it as it reads a datagram.\n abstract def exchange(queries : Array(Bytes), timeout_ms : Int32) : Array(Bytes) | DnsError | Cancelled\n # How a message names it: `dns.test (127.0.0.1:853) over TLS`.\n abstract def label : String\n # Whether its queries carry the ID 0: DNS over HTTPS's, which RFC 8484\n # 4.1 asks for so that an HTTP cache sees one request for one question.\n # The answer must carry the ID the query did all the same.\n abstract def zero_id? : Bool\nend\n\n# Either family's address: what a resolver answers for a name.\npub alias IPAddress = IPv4Address | IPv6Address\n\npub struct IPv4Address\n def b0 : UInt8\n def b1 : UInt8\n def b2 : UInt8\n def b3 : UInt8\n def initialize(b0 : UInt8, b1 : UInt8, b2 : UInt8, b3 : UInt8)\n def to_s : String\nend\n\n# Sixteen bytes in network order, held as two words: `high` is bytes 0 to\n# 7. Written the RFC 5952 way - lowercase, no leading zeros, the longest\n# run of two or more zero groups as `::`, the first of two equal runs -\n# and an IPv4-mapped address with its dotted tail, `::ffff:192.0.2.1`.\npub struct IPv6Address\n # Byte *index*, 0 to 15.\n def [](index : Int32) : UInt8\n # The 16-bit group *index*, 0 to 7. Not `group`: that name is the task\n # group's in iyi (SPEC.md III.4.9).\n def hextet(index : Int32) : Int32\n def high : UInt64\n def initialize(high : UInt64, low : UInt64)\n def low : UInt64\n # The sixteen bytes at *bytes*, network order.\n def self.new(bytes : Pointer(UInt8)) : IPv6Address\n def to_s : String\nend\n\npub class IyiSocket\n # Room for any family's `sockaddr`, zeroed, so a unix path the kernel\n # did not end with a NUL still ends.\n def self.__address_room : Pointer(UInt8)\n # A `sockaddr` the kernel filled, as text: dotted IPv4, IPv6 written as\n # RFC 5952 has it, or a unix socket's path.\n def self.__address_text(addr : Pointer(UInt8), family : Int32) : String\n # What every connected and accepted socket starts with: TCP_NODELAY, on\n # TCP; a unix socket has no Nagle.\n def self.__send_at_once(fd : Int32, family : Int32) : Nil\n def self.__sys3(number : Int64, a : Int64, b : Int64, c : Int64) : Int64\n def self.__sys5(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64) : Int64\n def self.__sys6(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64, f : Int64) : Int64\n def self.__sys_accept(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32\n def self.__sys_addr_size(family : Int32) : Int64\n def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n # Every close of a socket in this file goes through here. `__iyi_close` is\n # the descriptor close, and on Windows a socket is not a descriptor:\n # `CloseHandle` on a SOCKET releases the kernel handle and leaves Winsock's\n # half of it standing, and the graceful close that makes a peer's read\n # answer EOF rather than a reset is `closesocket`'s.\n def self.__sys_close(fd : Int32) : Nil\n # A raw syscall's negative errno, as the number.\n def self.__sys_code(result : Int64) : Int32\n # *ms* is `connect_to`'s wait's: the call answers EINPROGRESS at once.\n def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), family : Int32, ms : Int32) : Int32\n def self.__sys_connects_blocking?(family : Int32) : ::Bool\n def self.__sys_dgram_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n def self.__sys_dgram_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n # The resolver's datagram socket: see the darwin arm. The raw call\n # answers the negative errno, which `__sys_code` reads.\n def self.__sys_dgram_socket(family : Int32) : Int32\n def self.__sys_dgram_wait(fd : Int32, ms : Int32) : ::Bool | Cancelled\n def self.__sys_getpeername(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_in_progress?(result : Int32) : ::Bool\n def self.__sys_listen(fd : Int32, backlog : Int32) : Int32\n def self.__sys_no_sigpipe(fd : Int32) : Nil\n def self.__sys_nodelay?(fd : Int32) : ::Bool\n # `ppoll` on one descriptor for *ms* milliseconds - a timespec, or none\n # for -1 - and no signal mask. 0 when the time ran out.\n def self.__sys_poll(fd : Int32, events : Int32, ms : Int32) : Int32\n def self.__sys_recv(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n # MSG_NOSIGNAL (0x4000): a write to a connection the peer closed answers\n # EPIPE instead of killing the process with SIGPIPE.\n def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64\n # TCP_NODELAY (1) at IPPROTO_TCP (6): Nagle's algorithm off when *on*.\n def self.__sys_set_nodelay(fd : Int32, on : ::Bool) : Int32\n # F_GETFL (3), then F_SETFL (4) with O_NONBLOCK (0x800) added.\n def self.__sys_set_nonblock(fd : Int32) : Int32\n def self.__sys_set_reuseaddr(fd : Int32) : Int32\n # IPV6_V6ONLY (26) at IPPROTO_IPV6 (41).\n def self.__sys_set_v6only(fd : Int32) : Int32\n def self.__sys_shutdown_write(fd : Int32) : Int64\n # SO_ERROR (4) at SOL_SOCKET (1): what a connect that was in progress\n # ended with, 0 when it went through.\n def self.__sys_sock_error(fd : Int32) : Int32\n def self.__sys_socket(family : Int32) : Int32\n # A raw syscall answers the negative errno: EAGAIN is 11, EINPROGRESS 115.\n def self.__sys_would_block?(result : Int64) : ::Bool\n def accept : IyiSocket | Cancelled | SocketError\n # The order `connect` tries a name's addresses in (RFC 8305 section 4):\n # one family's first address, then the other's, alternating, each family\n # in the resolver's order, and the longer family's rest at the end.\n # IPv6 goes first when this machine has a route to the first IPv6\n # address - RFC 6724's first rule, asked the way glibc's getaddrinfo\n # asks it, by connecting a UDP socket, which sends nothing - and IPv4\n # first when it has none, so a machine without IPv6 does not start every\n # connect with an attempt that cannot leave it. A name of one family is\n # left in the resolver's order, and asks nothing.\n def self.attempt_order(addresses : Array(IPAddress)) : Array(IPAddress)\n def self.await_readable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n def self.await_writable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n # A timeout as the waits take it: milliseconds, -1 for none.\n def self.bound(ms : Int32 | ::Nil) : Int32\n protected def bound_path(path : String) : Nil\n # *path* with the NUL a system call reads to.\n def self.c_path(path : String) : Pointer(UInt8)\n def close : Nil\n # The sending half ended, the receiving half kept: the peer reads the\n # end of the stream after what was written, and this end goes on reading\n # what the peer still sends until its own end. `shutdown(SHUT_WR)` on\n # POSIX, `shutdown(SD_SEND)` on Windows. A socket that is not connected\n # answers the platform's refusal as a `SocketError`; `close` is still\n # what gives the socket back.\n def close_write : SocketError | ::Nil\n def closed? : ::Bool\n # A host name is resolved by the machine's resolver (`Resolver.system`);\n # a name that does not resolve is a `SocketError` whose `unresolved?` is\n # true and whose sentence names the host. It panicked, \"cannot resolve\n # address: \\\"example.com\\\"\", for every name but `localhost`, so\n # `HTTP.get(\"http://example.com/\")` could not run.\n #\n # A name with more than one address is raced, Happy Eyeballs v2 (RFC\n # 8305): the first address is tried, the next `ATTEMPT_DELAY_MS` later\n # without waiting for the first to fail, or at once when an attempt\n # fails, and so on down the list (`attempt_order`'s: IPv6 first when\n # this machine has a route to it, the families alternating). The first\n # handshake to finish wins, and every other attempt is cancelled and its\n # socket closed before `connect` answers. The addresses were tried one\n # after another, IPv4 first, each with the whole timeout: a name whose\n # first address dropped SYNs cost all of *timeout_ms* - or the kernel's\n # two minutes of retries, unbounded - before the second was tried. Now\n # it costs the delay: 250 ms to a name whose first address is a full\n # listen queue and whose second listens (`bench/std_dns_exercise.sh`,\n # under a 5,000 ms bound).\n #\n # `localhost` is raced the same way, 127.0.0.1 and then ::1, without\n # asking the routing table: a listener here binds `localhost` at\n # 127.0.0.1, so it goes first, and Windows, which names ::1 first, has\n # dev servers that bind ::1 alone. This replaces the rule that tried\n # ::1 only after 127.0.0.1 refused: Windows takes 2 s to refuse a\n # connect, so a ::1-only listener was reached in 2017 ms and a port\n # nobody holds refused in 4059 ms, where now ::1 starts 250 ms after\n # 127.0.0.1 whatever 127.0.0.1 is still doing.\n #\n # *timeout_ms* bounds the whole connect, every attempt together (-1: as\n # long as the kernel waits, which on Linux is about two minutes of SYN\n # retries for a host that drops them). It bounded each address's\n # handshake, which a race has no use for: an attempt started 500 ms in\n # would have run 500 ms past the caller's bound. A connect that runs out\n # is a `SocketError` whose `timed_out?` is true. The name's lookup is\n # outside it, held by the resolver's own `timeout_ms` and `attempts`.\n #\n # A name none of whose addresses connects is one `SocketError` that\n # names each address in the order tried and how its attempt ended,\n # `tried`: `cannot connect to example.com:443: [2001:db8::1]:443 timed\n # out; 192.0.2.1:443 connection refused`. It answered the first\n # address's failure alone. Its `code` is the first attempt's, or a\n # timeout's when the time ran out first. A name with one address is\n # connected to as an address is, and fails with that address's error.\n def self.connect(host : String, port : Int32, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The same, with a host name resolved by *resolver*: name servers, a\n # hosts file and search list of the program's choosing, rather than the\n # machine's. An address and `localhost` are what they are either way.\n def self.connect(host : String, port : Int32, resolver : Resolver, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # A connect to *addr*, waiting at most *ms* milliseconds for the\n # handshake (-1: as long as the kernel does). The resolver's TCP query\n # and `connect` to an address are its callers with a bound; a race's\n # attempts wait as the kernel does, and the race keeps the bound. The\n # bound reaches `__sys_connect` as well as the wait after it, because\n # on Windows that call is the wait: it parks in `ConnectEx` until the\n # handshake is over.\n def self.connect_to(addr : Pointer(UInt8), family : Int32, peer : String, ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The stream socket listening at *path*.\n def self.connect_unix(path : String) : IyiSocket | Cancelled | SocketError\n # *host* and *port* as a message names them: an IPv6 literal in its\n # brackets, as a URL writes it. Bare, `::1:57456` was an address with\n # no port, and `ff02::1:80` another address altogether.\n def self.endpoint(host : String, port : Int32) : String\n # The address family, the platform's number for it: an accepted\n # connection is its listener's, and on Windows the socket `AcceptEx`\n # fills in has to be made in it. With `fd`, what makes the socket again\n # on another thread (std/http's `Server.serve`, on its pool).\n def family : Int32\n def fd : Int32\n def initialize(fd : Int32, family : Int32 = AF_INET)\n def self.listen(host : String, port : Int32, backlog : Int32 = 128) : IyiSocket\n def self.listen(port : Int32, backlog : Int32 = 128) : IyiSocket\n # A stream socket at *path*. The path must not exist: a socket file an\n # earlier run left behind is refused rather than taken over, since\n # which program owns it is not something a bind can know. `close`\n # removes the file.\n def self.listen_unix(path : String, backlog : Int32 = 128) : IyiSocket\n def local_address : String\n # The port sits at the same two bytes of a `sockaddr_in` and a\n # `sockaddr_in6`.\n def local_port : Int32\n # `localhost` in any case, and with the one trailing dot of a name\n # written fully qualified: a host name's case is not part of it (RFC\n # 4343), and getaddrinfo, Python and curl all answer 127.0.0.1 for\n # `LOCALHOST` and `localhost.`. Matched exactly, these were \"cannot\n # resolve address: \\\"LOCALHOST\\\"\", and so were `Localhost` and\n # `localhost.`.\n def self.localhost?(host : String) : ::Bool\n # What `localhost` stands for, in the order `connect` tries it.\n def self.loopback : Array(IPAddress)\n def self.make_sockaddr_in(ip : IPv4Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_in6`: family, port, a zero flow label, the address, a zero\n # scope. The family's number and where it sits are the platform's.\n def self.make_sockaddr_in6(ip : IPv6Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_un`: the family, then the path and its NUL. The room for\n # the path is the platform's - 104 bytes on darwin, 108 elsewhere - and a\n # longer one is refused rather than cut, since a cut path is another\n # path.\n def self.make_sockaddr_un(path : String) : Pointer(UInt8)\n # Whether a small write goes at once (TCP_NODELAY) rather than waiting\n # for an earlier one's acknowledgement, as Nagle's algorithm (RFC 896)\n # has it wait. A connected or accepted TCP socket here sends at once, as\n # Go's net package sets its own; `nodelay = false` turns Nagle back on\n # for a program that writes in pieces it would rather have joined. With\n # Nagle on, a write behind an unacknowledged one waited out the peer's\n # delayed ACK, 40 ms on Linux: measured on loopback, --release, medians\n # of 200, a GET on a new https:// connection took 82.89 ms between\n # std/http's ends and 42.00 to OpenSSL's s_server, and takes 1.07 and\n # 1.10 now; wrk against a server answering a streamed body went from\n # 1,211 to 10,702 requests a second. A unix socket has no Nagle to turn\n # off: it answers false and is left alone.\n def nodelay=(on : ::Bool) : ::Bool\n # Whether TCP_NODELAY is on, as the socket's option reads.\n def nodelay? : ::Bool\n # Dotted-decimal IPv4 only: four octets of 0 to 255, no leading zero\n # (`\"010.1.1.1\"` is refused; glibc's `inet_aton` reads `010` as octal 8,\n # CVE-2021-29921, so it must not quietly be 10 here), plus `localhost`.\n # The refusal inspects the host so a stray space shows.\n def self.parse_ip(host : String) : IPv4Address\n # Eight groups of up to four hex digits, `::` once for a run of zero\n # groups, and a dotted IPv4 tail for the last two (`::ffff:1.2.3.4`). A\n # zone (`fe80::1%eth0`) is refused by name: it names an interface, and\n # this parser has no way to ask the platform which.\n def self.parse_ipv6(host : String) : IPv6Address\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The dotted-decimal reading alone, with the refusal's sentence as the\n # answer instead of a panic: the resolver reads the addresses a hosts\n # file and a resolv.conf write, and a line it cannot read is skipped\n # there, not the end of the program.\n def self.read_ipv4(host : String) : IPv4Address | String\n # The reading alone, the refusal's sentence as the answer: see\n # `read_ipv4`.\n def self.read_ipv6(host : String) : IPv6Address | String\n # The bounds on a read's and a write's wait, in milliseconds; nil waits\n # as long as it takes.\n def read_timeout_ms : Int32 | ::Nil\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n def recv(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def recv(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The other end's address and port, as `local_address` and\n # `local_port` give this end's: what a server logs a client by. A unix\n # socket's peer has a path at most, often none (\"\"), and no port. Nil\n # when the other end is gone - the connection was reset or never made -\n # which is the peer's doing and no fault of the program's.\n def remote_address : String | ::Nil\n def remote_port : Int32 | ::Nil\n # Every address *host* stands for, by the machine's resolver, in the\n # resolver's order (see `Resolver#resolve`); `connect` tries them in\n # `attempt_order`'s.\n def self.resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def send(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def send(contents : String) : Int32 | Cancelled | SocketError\n # A host and port as the address a call takes, and its family: an IPv6\n # literal bare or in brackets, anything else IPv4.\n def self.sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), Int32)\n # This socket as a stream (`SocketIO`), the same one every time, so\n # what one read took ahead the next reads. It was a stream over the\n # descriptor that read a socket with nothing yet to read as failed.\n def to_io : Std::Socket::SocketIO\n def to_unsafe : Int32\n # Whether this is a unix socket, which has a path where TCP has a port:\n # `remote_port` and `local_port` refuse it. std/tls asks before it keys\n # a session's tickets by the port they came from.\n def unix? : ::Bool\n def write(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def write(contents : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms : Int32 | ::Nil\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\npub alias ResolvConf = Std::Dns::ResolvConf\n\n# A stub resolver: a host name's addresses, from a hosts file and then\n# from name servers, as the C library's resolver finds them - but iyi's,\n# so asking costs no library (see the header of this file).\n#\n# `Resolver.system` is the machine's: `/etc/resolv.conf` and `/etc/hosts`\n# on Linux and darwin, the registry and\n# `%SystemRoot%\\System32\\drivers\\etc\\hosts` on Windows (see\n# `__sys_dns_config` for what the registry can and cannot say). A\n# resolver built with `new` or `from_config` asks exactly what it was\n# given, which is how a test asks a server it runs itself.\n#\n# `resolve` answers every address for a name, IPv4 first and then IPv6,\n# each family in the order the hosts file or the server gave it;\n# `connect` races them in `IyiSocket.attempt_order`'s order:\n#\n# 1. An address, or `localhost` (127.0.0.1 and ::1: RFC 6761 6.3 keeps it\n# off the network), is itself. Anything that is not a host name is\n# `invalid_name?`.\n# 2. The hosts file: every line naming the host, in any case. A name\n# found there is not asked about.\n# 3. The names the search list and `ndots` make of it (`DnsName.candidates`),\n# in order. Each is asked for A and then AAAA records; NXDOMAIN or no\n# records moves on to the next, and any other failure ends the lookup\n# with that failure - a server that timed out on `db.corp.example` must\n# not turn the answer into `db.example`'s (musl's rule; glibc moves on).\n#\n# A and AAAA are asked one after the other, and `connect` starts its race\n# once both have answered. RFC 8305 section 3 asks both at once and\n# starts connecting on the A answer when AAAA is 50 ms behind it; that\n# makes a lookup answer in pieces, and saves the AAAA query's round trip\n# at most - 5 to 65 ms uncached for five names through systemd-resolved\n# on the machine this was written on, 0 to 1 ms cached (dig's query\n# times) - where the race saves what an address that drops SYNs costs,\n# the whole timeout. Asked in turn, an NXDOMAIN for A spares the AAAA\n# query, as it does for most of a search list's candidates.\n#\n# A query is one UDP datagram from a fresh socket (so a fresh source\n# port), with an ID from `Random::Secure`, to each server in turn, each\n# waited for `timeout_ms`, and the round repeated `attempts` times. A\n# datagram that does not carry the ID and the question is ignored and the\n# wait goes on; one from any other address never arrives, because the\n# socket is connected to the server. An answer with TC set is asked again\n# over TCP. SERVFAIL, REFUSED and a server that does not answer move on to\n# the next server; NXDOMAIN is the answer. An answer that cannot be read\n# is ignored like a forgery, and is the failure reported if nothing\n# better arrives before the timeout.\n#\n# Encrypted:\n# `encrypted(upstreams)` is the same resolver - hosts file, search list,\n# `ndots`, timeout and attempts - asking `DnsUpstream`s instead of its\n# plaintext servers: DNS over TLS or over HTTPS, std/secure_dns's\n# `DnsOverTls` and `DnsOverHttps`. Each upstream is asked in turn, the\n# round repeated `attempts` times, as the servers are; A and AAAA go in\n# one exchange. An answer over one is held to more than a datagram is:\n# the ID and the question as ever, and no TC and no byte past the message,\n# since a stream has room for any answer and its frame is the message.\n# The plaintext servers are not asked at all, not even when every\n# upstream is down, unless the caller passes *plaintext_fallback*: RFC\n# 8310 5's strict profile, where a lookup that cannot be private fails,\n# is the default, because falling back hands the very question the\n# program meant to keep private to anyone on the path, and a program\n# that would rather have an answer says so. With *plaintext_fallback*\n# (the opportunistic profile) the plaintext servers are asked only when\n# no upstream could be: one that answered, SERVFAIL included, has spoken.\n# One task at a time per resolver with upstreams: an upstream holds its\n# connection, as an `HTTPClient` does.\npub class Resolver\n protected def ask_upstreams_instead(upstreams : Array(DnsUpstream), plaintext_fallback : Bool) : Nil\n def attempts : Int32\n # *records* by ascending priority, those of one priority shuffled.\n def self.by_priority(records : Array(SvcbRecord)) : Array(SvcbRecord)\n # Every upstream's connections closed.\n def close : Nil\n # This resolver asking *upstreams* instead of its plaintext servers,\n # which are asked only with *plaintext_fallback*, and then only when no\n # upstream could be (see the class).\n def encrypted(upstreams : Array(DnsUpstream), plaintext_fallback : Bool = false) : Resolver\n def encrypted(upstream : DnsUpstream, plaintext_fallback : Bool = false) : Resolver\n # The machine's resolver - its hosts file and search list - asking\n # *upstreams*; its name servers only with *plaintext_fallback*.\n def self.encrypted(upstreams : Array(DnsUpstream), plaintext_fallback : Bool = false) : Resolver\n def self.encrypted(upstream : DnsUpstream, plaintext_fallback : Bool = false) : Resolver\n # A resolver from a parsed resolv.conf and a hosts file's text. A\n # `nameserver` line this resolver cannot use - an IPv6 address with a\n # zone, a typo - is skipped as glibc skips one; with none left, the\n # servers a machine without a configuration is assumed to run.\n def self.from_config(config : ResolvConf, hosts : String = \"\") : Resolver\n # A resolver from a resolv.conf and a hosts file at these paths. A file\n # that is not there reads as empty, as it does to the C library.\n def self.from_files(resolv_conf : String, hosts : String) : Resolver\n # The hosts file's text, read before any server is asked.\n def hosts : String\n # The HTTPS records (RFC 9460 section 9) of the origin `https://host:port`,\n # by SVCB resolution (section 3): the question is *host* for port 443\n # and `_port._https.host` for any other (section 9.1); an AliasMode\n # answer is followed to its target's HTTPS records, at most\n # `SvcbAnswer::MAX_ALIASES` of them, and CNAMEs on the way as `txt`\n # follows them; and the ServiceMode records found are answered by\n # priority, those of one priority in a random order (section 2.4.1).\n # The name is asked as written, as for `txt`, over the upstreams when\n # the resolver has them.\n #\n # Of an AliasMode set one record is followed, at random where there are\n # several (section 2.4.2), and any ServiceMode record beside it is\n # passed over (section 2.4.1). A target that has no HTTPS records ends\n # resolution there, with none: the answer's `name` is the target, which\n # section 3 still has a client connect to. An answer with a record\n # `SvcbRecord.parse` refuses is the whole set refused, `malformed?`, as\n # section 2.2 has it; so is a chain longer than the bound. NXDOMAIN and\n # NODATA for the origin's own name are `not_found?` and `no_address?`,\n # as they are for an address.\n def https_records(host : String, port : Int32 = 443) : SvcbAnswer | DnsError | Cancelled\n # *nameservers* are addresses, each with an optional port:\n # `\"192.0.2.1\"`, `\"192.0.2.1:5353\"`, `\"2001:db8::1\"`,\n # `\"[2001:db8::1]:5353\"`. One that is not is refused here, by name: it is\n # the program's own mistake. None at all is a resolver that reads only\n # *hosts*.\n def initialize(nameservers : Array(String), search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2, hosts : String = \"\")\n # What NODATA for *type* says the name lacks: its records, by name, for\n # the types asked other than as addresses.\n def self.missing(type : Int32) : String\n def nameservers : Array(String)\n def ndots : Int32\n # The monotonic clock in milliseconds: the runtime's, which its sleep\n # queue keeps deadlines by. A target without the runtime has no socket\n # to wait on either (`__sys_dgram_socket` refuses there), so nothing\n # there reads a time.\n def self.now_ms : Int64\n # The answer to one question over plaintext: each server in turn, the\n # round repeated `attempts` times, until one answers NOERROR or\n # NXDOMAIN. The failure answered otherwise is the last server's.\n protected def plaintext(name : String, type : Int32) : Tuple(DnsMessage, String) | DnsError | Cancelled\n # Whether `nameservers` are asked when no upstream could be (RFC 8310's\n # opportunistic profile); false, the strict one, by default.\n def plaintext_fallback : Bool\n def self.rcode_name(rcode : Int32) : String\n # What an answer's RCODE makes of it: the message for NOERROR, and the\n # failure it says otherwise.\n def self.read_rcode(message : DnsMessage, name : String, label : String) : Tuple(DnsMessage, String) | DnsError\n # An answer that came over a stream, read: see `ask_upstream`.\n def self.read_streamed(bytes : Bytes, id : UInt16, name : String, type : Int32, label : String) : Tuple(DnsMessage, String) | DnsError\n # Every address *host* stands for: see the class's comment for the\n # order things are asked in.\n def resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def search : Array(String)\n # A name server written as an address with an optional port, or the\n # sentence saying why it is not one.\n def self.server(text : String) : NameServer | String\n # The machine's resolver, read afresh each time: a configuration the\n # network manager rewrote is the one the next lookup uses.\n def self.system : Resolver\n def timeout_ms : Int32\n # The TXT records of *name*, each one's character-strings joined\n # (`DnsRecord.character_strings`), in the order the answer gave them,\n # CNAMEs followed as `resolve` follows them. The name is asked as\n # written - no hosts file, no search list - since a TXT name\n # (`_acme-challenge.example.com`, a domain's SPF) is written in full.\n def txt(name : String) : Array(String) | DnsError | Cancelled\n # The encrypted upstreams asked instead of `nameservers`; none for a\n # plaintext resolver.\n def upstreams : Array(DnsUpstream)\nend\n\n# A connection's own failure: which operation, and the platform's error\n# number for why - or, for a host name that could not be turned into an\n# address, the resolver's `DnsError`, and no number (`code` 0); or, for a\n# protocol layered on the connection (TLS), that protocol's own sentence,\n# and no number either; or, for a name whose addresses were all tried and\n# none connected, every one of them with how its attempt ended.\npub struct SocketError\n def code : Int32\n # The peer is gone: it reset the connection, or closed it and the write\n # found the pipe broken, or the connection was aborted.\n def disconnected? : ::Bool\n # Why the host's name did not resolve; nil for every other failure.\n def dns_error : DnsError | ::Nil\n # A connect to a name none of whose addresses took the connection:\n # *code* is what `refused?` and `timed_out?` read, and *tried* the\n # sentence naming every address.\n def self.every_address(operation : String, code : Int32, tried : String) : SocketError\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil, tried : String | ::Nil)\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil)\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil)\n def initialize(operation : String, code : Int32)\n # What the protocol over the connection said went wrong; nil otherwise.\n def layer_reason : String | ::Nil\n # A failure of the protocol over the connection, in its words: `cannot\n # read from example.com: tls: a record that does not decrypt`.\n def self.layered(operation : String, reason : String) : SocketError\n def operation : String\n def reason : String\n def refused? : ::Bool\n # A wait for the peer that ran past its socket's timeout, carrying the\n # platform's own number for it so `reason` and `timed_out?` read it as\n # they read the kernel's.\n def self.timed_out(operation : String) : SocketError\n # The peer did not answer in time: a connect the network timed out, or a\n # read or write past its socket's timeout.\n def timed_out? : ::Bool\n # For a connect to a name that stood for more than one address: each\n # address in the order it was tried and how its attempt ended -\n # `[2001:db8::1]:443 timed out; 192.0.2.1:443 connection refused` - which\n # `reason` answers. Nil for every other failure.\n def tried : String | ::Nil\n def unreachable? : ::Bool\n # The host was a name, and no address came of it: `dns_error` says why\n # (`not_found?` for NXDOMAIN, `server_failure?` for SERVFAIL, ...).\n def unresolved? : ::Bool\nend\n\n# A socket as a stream, for what is written against streams: `read_line`,\n# `puts`, `IyiIO.copy` between it and a file or a pipe. Its reads and\n# writes are the socket's own, which park the task and keep its\n# timeouts. A stream has no error to answer, so a read the socket refuses\n# ends the stream, `read_failed?` true, and a write it refuses panics,\n# naming the reason; a program that answers those reads the socket.\n# Writes are buffered: `flush`, or `IyiIO.copy`, which flushes, sends\n# them. Closing it closes the socket.\npub class SocketIO < ::IyiIO\n def initialize(socket : IyiSocket, buffer_size : Int32 = 4096)\n def socket : IyiSocket\nend\n\nimpl Error for SocketError\n def message : String\nend\n" }, { "name": "std/sse", @@ -562,14 +610,14 @@ { "name": "std/time", "path": "src/std/time.iyi", - "sourceSha256": "de71e76443d3820128318a8a725301c5aa6de7a06df82caf8993794355d43987", - "surface": "module std/time\n\npub struct DayOfWeek\n def ==(other : DayOfWeek) : Bool\n def ===(other : DayOfWeek) : Bool\n def friday? : Bool\n # Hashing follows `==`. Without it a day hashed to its type, `Object#hash`'s\n # default: `Monday.hash == Sunday.hash`, and every day a table held went to\n # one slot.\n def hash : Int32\n def initialize(value : Int32)\n def monday? : Bool\n def saturday? : Bool\n def sunday? : Bool\n def thursday? : Bool\n def to_s : String\n def tuesday? : Bool\n def value : Int32\n def wednesday? : Bool\nend\n\n# A time zone: the offsets from UTC a place has kept, and when it changed\n# from one to the next. `Location.load(\"Europe/Istanbul\")` reads the IANA\n# database the machine has; `Location.local` is the machine's own zone;\n# `Location.fixed(19800)` is an offset and nothing else; `Location::UTC` is\n# UTC. A `Time` carries one, and its date and clock are read in it.\n#\n# The database is the system's TZif files, RFC 8536: `$ZONEINFO` when it\n# names a directory, then `/usr/share/zoneinfo`, `/usr/share/lib/zoneinfo`\n# and `/etc/zoneinfo`. Versions 1 to 4 are read. A time before the first\n# change has the file's first offset, and one after the last has its POSIX\n# footer's rule, so a slim file answers for the year 2100 as a fat one does.\n# Leap seconds are not counted, as no clock this module reads counts them.\n#\n# `Location.local` is the `TZ` variable when it is set — the empty string\n# is UTC, a leading `:` is dropped, an absolute path is a TZif file, a name\n# is looked up in the database, and anything else is read as a POSIX TZ\n# string such as `EST5EDT,M3.2.0,M11.1.0` — and `/etc/localtime` when it is\n# not. Windows has no database to read: there `local` is the system's zone,\n# its rule from `GetTimeZoneInformation`, its zones named by their offsets,\n# and `load` reads `$ZONEINFO` alone. Where nothing answers, local is UTC.\n# The local zone is read once, the first time it is asked for.\n#\n# A location is built once and never changed, so a `Time` that holds one\n# can cross to another task.\npub class Location\n lib LibKernel32\n end\n # One offset a location keeps: its abbreviation (`CEST`, `+03`), its\n # seconds east of UTC, and whether it is summer time.\n struct Zone\n def ==(other : Zone) : Bool\n def dst? : Bool\n def initialize(name : String, offset : Int32, dst : Bool)\n def name : String\n def offset : Int32\n def to_s : String\n end\n def ==(other : Location) : Bool\n def self.fixed(name : String, offset : Int32) : Location\n # An offset of *offset* seconds east of UTC and nothing else, named as\n # the offset is written: `+05:30`. Less than a day either way.\n def self.fixed(offset : Int32) : Location\n # One offset, never changing.\n def fixed? : Bool\n def hash : Int32\n def initialize(name : String, zones : Array(Zone), transitions : Array(Int64), transition_zones : Array(Int32), rule : TzRule | ::Nil)\n def inspect : String\n # The zone the IANA database names *name*: `Europe/Istanbul`,\n # `America/New_York`. `UTC` and the empty name are UTC and `Local` is\n # `Location.local`, without a file. A name that climbs out of the database\n # (`..`, a leading `/`, a `\\`) is refused, and so is one no directory has.\n def self.load(name : String) : Location | LocationError\n # The machine's zone; see the type's comment for where it comes from.\n def self.local : Location\n # The machine's own zone, the one `Location.local` answers.\n def local? : Bool\n # The zone *time* is in here.\n def lookup(time : Time) : Zone\n def lookup(unix_seconds : Int64) : Zone\n def name : String\n # A location from the bytes of a TZif file, RFC 8536, named *name*.\n # Anything that is not one is refused with what was wrong and where.\n def self.read_zoneinfo(name : String, data : String) : Location | LocationError\n # The instant a wall clock here reads *wall* (seconds since 1970 as a\n # UTC clock would count them). A wall time a change repeats is the\n # earlier of the two instants; one a change skips is read on the clock\n # from before the change, which lands it past the gap: 02:30 on the\n # morning summer starts in Berlin is 03:30 CEST. Python's `fold=0` and\n # Go read them the same way. `Time.local` and a parse at a location ask\n # this.\n def resolve(wall : Int64) : Int64\n def to_s : String\n def utc? : Bool\nend\n\n# What `Location.load` and `Location.read_zoneinfo` answer when there is no\n# zone by that name, or the bytes are not a zone: a name the program was\n# handed or a file on the machine is not a bug in the program, so it is a\n# value and not a panic.\npub struct LocationError\n def initialize(message : String)\n def to_s : String\nend\n\npub struct Span\n def +(other : Span) : Span\n def -(other : Span) : Span\n def - : Span\n def <(other : Span) : Bool\n def <=(other : Span) : Bool\n def <=>(other : Span) : Int32\n def ==(other : Span) : Bool\n def >(other : Span) : Bool\n def >=(other : Span) : Bool\n # The seconds are an Int64, and a span that leaves it is refused here,\n # by name: `Span.seconds(Int64::MAX) + Span.seconds(1)` panicked with the\n # prelude's \"arithmetic overflow\", a sentence that named no span. The\n # wrapping sum is compared with an addend to see whether it wrapped; the\n # product is bounded before it is taken.\n def self.add_seconds(a : Int64, b : Int64) : Int64\n def days : Int64\n def self.days(n : Int64) : Span\n # Hashing follows `==`, the two parts mixed as a tuple's members are. Without\n # it every span hashed to its type: `Span.seconds(1).hash ==\n # Span.days(9).hash`, and a table of 16,000 spans took 0.95 s to fill where\n # the same pairs as tuples took 1 ms.\n def hash : Int32\n def hours : Int32\n def self.hours(n : Int64) : Span\n def initialize(seconds : Int64 = 0_i64, nanoseconds : Int32 = 0)\n def millisecond : Int32\n def milliseconds : Int32\n def self.milliseconds(n : Int64) : Span\n def minutes : Int32\n def self.minutes(n : Int64) : Span\n def self.mul_seconds(n : Int64, unit : Int64) : Int64\n def nanosecond : Int32\n def nanoseconds : Int32\n def self.nanoseconds(n : Int64) : Span\n def self.new(*, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, milliseconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Span\n def seconds : Int32\n def self.seconds(n : Int64) : Span\n def self.sub_seconds(a : Int64, b : Int64) : Int64\n def to_i : Int64\n def to_s : String\n def total_days : Float64\n def total_hours : Float64\n def total_milliseconds : Float64\n def total_minutes : Float64\n def total_nanoseconds : Float64\n def total_seconds : Float64\n def total_seconds_i64 : Int64\n def self.zero : Span\nend\n\npub struct Time\n alias DayOfWeek = Std::Time::DayOfWeek\n # A pattern of Crystal's directives, for writing a `Time` and reading one\n # back. Text that is not a directive is written as it is and must be\n # there, byte for byte, when reading; ASCII whitespace in the pattern is\n # written as it is and reads any run of whitespace, none included.\n #\n # %Y year, four digits or more, year -1 as -0001 %C year // 100\n # %y year % 100 %G %g %V the ISO 8601 week year, its last two digits,\n # and the week 01..53 %m month 01..12 %B January %b %h Jan\n # %d day 01..31 %e day \" 1\"..\"31\" %j day of the year 001..366\n # %H hour 00..23 %k \" 0\"..\"23\" %I 01..12 %l \" 1\"..\"12\" %p am %P AM\n # %M minute %S second %L milliseconds %3N %6N %9N the fraction to\n # 3, 6 and 9 digits %N nanoseconds, any number of digits when read\n # %A Sunday %a Sun %u day of the week, Monday 1..7 %w Sunday 0..6\n # %s seconds since the epoch %z +0530 %:z +05:30 %::z +05:30:00\n # %Z and %^Z: UTC at offset 0 and the offset, +05:30, at any other\n # %F %Y-%m-%d %T %X %H:%M:%S %R %H:%M %D %x %m/%d/%y\n # %r %I:%M:%S %P %c %a %b %e %H:%M:%S %Y %n newline %t tab %% %\n #\n # A number takes a flag between the `%` and its letter: `-` for no\n # padding (`%-d` is 5), `_` for spaces (`%_m` is \" 5\") and `0` for zeros\n # (`%0e` is 05); `^` upcases a name (`%^a` is SUN). The other library knows\n # `%-d`, `%-m`, `%_m` and the `^` names. The same flags on its other numbers\n # are this module's, where that library wrote the `%-` back out as text; a `%`\n # that starts no directive is text here as it is there.\n #\n # Reading takes up to a number's width in digits, fewer when the next byte\n # is not a digit, and a name in any case. `%Y` takes four digits when a\n # directive follows it (`%Y%m%d`), and when text or the end of the\n # pattern does (`%F`), an optional `-` and up to ten, so the years `to_s`\n # writes are the years it reads. A field outside its range, a day the\n # month does not have, a name that is not one and text after the pattern\n # are each a `TimeParseError`, never a panic. A day name is checked to be\n # one and not checked against the date, as Crystal, Go and Python read it.\n struct Format\n # The parts of an instant as they read at an offset, worked out once\n # per `format` rather than once per directive. *zone* is what `%Z`\n # writes, the location's abbreviation; with none it writes `UTC` at\n # offset 0 and the offset at any other.\n struct Fields\n def day : Int32\n def day_of_week : Int32\n def day_of_year : Int32\n def hour : Int32\n def initialize(time : Time, offset : Int32, zone : String | ::Nil)\n def minute : Int32\n def month : Int32\n def nanosecond : Int32\n def offset : Int32\n def second : Int32\n def unix : Int64\n def week : ::Tuple(Int64, Int32)\n def year : Int32\n def zone : String | ::Nil\n end\n # The HTTP date of RFC 9110 section 5.6.7. It is written as the\n # IMF-fixdate, always in GMT, `Sun, 06 Nov 1994 08:49:37 GMT`; it is\n # read in all three of the forms a recipient must accept: that one, the\n # obsolete RFC 850 `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's\n # `Sun Nov 6 08:49:37 1994`.\n module HTTP_DATE\n def self.format(time : Time) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # ISO 8601's calendar date, written `2016-02-15` as the date reads at\n # *offset* or in the time's own location; read extended or basic, and\n # as an ordinal or a week date, at *offset* since a date names no zone.\n module ISO_8601_DATE\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String, offset : Int32 = 0) : Time | TimeParseError\n end\n # ISO 8601's date and time, written as RFC 3339 writes it and read with\n # every form of `ISO_8601_DATE` and `ISO_8601_TIME` joined by an\n # optional `T`. The zone is required, as Crystal requires it.\n module ISO_8601_DATE_TIME\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # ISO 8601's time of day and its zone, `04:35:50Z`; read as on\n # 0001-01-01, the day Crystal reads it on.\n module ISO_8601_TIME\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # Reads a text by a pattern or by one of the named grammars. Every step\n # answers whether it matched; the first that did not leaves its sentence\n # in `@error`, and `finish` answers that, so no text a program is handed\n # ends it in a panic.\n class Parser\n def accept(char : Char) : Bool\n def am_pm : Bool\n def at_end : Bool\n # RFC 5322's CFWS: whitespace and parenthesised comments, which nest\n # and in which `\\` quotes the next byte. Answers whether there was any;\n # a comment left open is the error, and the parse fails on it.\n def cfws : Bool\n def day_name : Bool\n def self.digit?(b : UInt8) : Bool\n # Up to *max* digits and at least one. Nil, with the sentence, when\n # there are none, or when they are past what an Int64 holds.\n def digits(max : Int32, what : String) : Int64 | ::Nil\n def directive(letter : Char, flag : Char, digits : Int32, wide : Bool) : Bool\n # Exactly *count* digits, or nil, reading nothing and saying nothing:\n # the caller names what was missing.\n def exact(count : Int32) : Int32 | ::Nil\n def expect(char : Char) : Bool\n def fail(what : String) : Bool\n # A numeric directive's field, read as its flag and its padding say.\n def field(width : Int32, pad : Char, flag : Char, what : String) : Int32 | ::Nil\n # The instant the fields name, or the first sentence a step left. The\n # fields are checked the way `Time.utc` checks them, with RFC 3339's\n # allowance: `:60` is a leap second, read as the second after `:59`,\n # the instant it names on every clock that has none. A text that\n # names no zone is read at *default_offset*, or failing that on\n # *location*'s wall clock; with a location the time answered is in it.\n def finish(default_offset : Int32 | ::Nil, location : Location | ::Nil = nil) : Time | TimeParseError\n # A fraction of a second: up to *max* digits, scaled to nanoseconds,\n # and for `%N` any digits past the ninth read and dropped.\n def fraction(max : Int32, rest : Bool) : Bool\n # RFC 9110 section 5.6.7's three forms of an HTTP date: the\n # IMF-fixdate `Sun, 06 Nov 1994 08:49:37 GMT`, the obsolete RFC 850\n # `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's `Sun Nov 6 08:49:37\n # 1994`, which has no zone and is GMT. Crystal's reading: a day name,\n # short or long, in any of the three; the four-digit year IIS writes\n # in the RFC 850 shape; and the zones RFC 2822 names where RFC 9110\n # writes GMT.\n def http_date : Bool\n def initialize(input : String)\n # ISO 8601's dates, extended or basic: 2016-02-15 and 20160215, the\n # ordinal 2016-046 and 2016046, and the week date 2016-W07-1 and\n # 2016W071.\n def iso_date : Bool\n def iso_date_time : Bool\n # ISO 8601's time of day, extended or basic: 04:35:50 and 043550, a\n # fraction of the second after `.` or `,`, or the minutes or the hour\n # alone with a fraction of their own (04:35.5 is 04:35:30).\n def iso_time : Bool\n def self.letter?(b : UInt8) : Bool\n def literal(b : UInt8) : Bool\n def month_name(short : Bool) : Bool\n # A run of ASCII letters and the index in *names* of the name it\n # spells, in any case: for *short*, the first three letters of a name\n # and no more; otherwise the start of a name, three letters or longer,\n # as Crystal reads `%B` and `%A`.\n def name(names : Array(String), short : Bool, what : String) : Int32 | ::Nil\n def need_cfws : Bool\n def number(max : Int32, what : String) : Int32 | ::Nil\n def offset_number(allow_colon : Bool, force_colon : Bool, allow_seconds : Bool) : Bool\n # A number blank padded, as `%e` writes \" 5\": one space stands for a\n # digit.\n def padded_number(max : Int32, what : String) : Int32 | ::Nil\n def pattern(pattern : String) : Bool\n # The byte *ahead* of the current one, or 0 past the end, as Crystal's\n # reader answers '\\0' there.\n def peek(ahead : Int32 = 0) : UInt8\n # RFC 9110's rule for a two-digit year (section 5.6.7): a year that\n # appears to be more than 50 years in the future is the most recent\n # past year with those last two digits. The candidate is taken in this\n # century and moved a century to land in the 100 years ending 50 years\n # from now, so the rule holds in 2090 too. The other library read 00..49 as 20xx\n # and the rest as 19xx, whatever the year it was read in.\n def self.recent_year(two_digits : Int64) : Int64\n # A sentence about fields that were all read, where no one byte is to\n # blame.\n def refuse(what : String) : TimeParseError\n # RFC 5322 section 3.3's date-time, with section 4.3's obsolete\n # two- and three-digit years and zone names, as Crystal's `RFC_2822`\n # reads it: `Mon, 15 Feb 2016 04:35:50 +0000`, the day name and the\n # seconds optional, comments anywhere whitespace goes.\n def rfc_2822 : Bool\n def same_letters?(start : Int32, name : String, size : Int32) : Bool\n def skip_spaces : Bool\n def self.space?(b : UInt8) : Bool\n def text(start : Int32, stop : Int32) : String\n # HH:MM:SS, as both HTTP and RFC 850 write the time of day.\n def time_of_day : Bool\n # One whitespace byte, as the HTTP grammars put between their parts.\n def whitespace : Bool\n # `%Y`: four digits when a directive follows it, and an optional `-`\n # and up to ten when text does, which is every year an Int32 holds.\n def year(wide : Bool) : Int64 | ::Nil\n # Exactly four digits, as the HTTP and ISO 8601 grammars write a year.\n def year4 : Bool\n # `%z`, `%:z` and `%::z` alike, as Crystal reads them: `Z`, `UTC`, or\n # an offset with or without its colons.\n def zone : Bool\n # `%Z`: an offset, or a name read up to the next whitespace. Only the\n # names of UTC itself are read: an abbreviation names no single zone\n # (CST is three of them), so any other is refused by name rather than\n # read as UTC or guessed at. A text whose zone is known is read at\n # that `Location` instead.\n def zone_name : Bool\n # RFC 5322's zone (section 3.3, and 4.3 for the obsolete names): an\n # offset of four digits, or a name. A military letter is read as\n # `-0000`, the offset of a time whose zone is not known, as section\n # 4.3 says to; a name the RFC does not give is refused, where Crystal\n # read it as UTC in silence. `UTC` is not the RFC's, and is read as\n # Crystal reads it.\n def zone_rfc2822 : Bool\n # ISO 8601's zone: `Z` or an offset.\n def zone_z_or_offset : Bool\n end\n # RFC 2822's date and time, `Mon, 15 Feb 2016 04:35:50 +0000`, as\n # Crystal writes it: the day of the month unpadded, the offset at the\n # end, the time's own unless another is given. RFC 2822 writes an offset\n # in whole minutes, so an offset with seconds is refused rather than\n # written as some other instant.\n module RFC_2822\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # RFC 3339, the module's own serialisation: `to_rfc3339` and\n # `parse_rfc3339` are this module's `format` and `parse`, and\n # `parse_rfc3339` panics with the sentence `parse` answers. Written at\n # *offset*, or at the time's own when it is whole minutes and in UTC\n # when it is not.\n module RFC_3339\n def self.format(time : Time, fraction_digits : Int32 = 0, offset : Int32 | ::Nil = nil) : String\n def self.parse(str : String) : Time | TimeParseError\n end\n # An offset is less than a day either side of UTC: the widest RFC 3339\n # and RFC 2822 write is 23:59, and a day or more is a date, not a zone.\n # An offset is the program's own, so a wrong one is a panic.\n def self.check_offset(offset : Int32) : Nil\n def format(time : Time, offset : Int32) : String\n def format(time : Time) : String\n # *offset*, seconds east of UTC, is the one a parse reads a text at when\n # the text names no zone, and the one `format` writes at. Nil, as for\n # Crystal's `Format.new(pattern)`, makes a text without a zone an error,\n # and `format` writes a time in its own location.\n def initialize(pattern : String, offset : Int32 | ::Nil = nil)\n # A text that names no zone is read on *location*'s wall clock, and\n # every time is read and written in *location*.\n def initialize(pattern : String, location : Location)\n # The directive at the `%` at byte *i* of *pattern*: the byte after it,\n # its letter, its flag, the colons of `%:z` and `%::z`, and the digits of\n # `%3N`. A `%` that starts no directive answers the letter '\\0' and the\n # byte after the `%`: it is text, and so is what follows it, as Crystal\n # writes `%Q` back out as `%Q`.\n def self.lex(pattern : String, i : Int32) : ::Tuple(Int32, Char, Char, Int32, Int32)\n def location : Location | ::Nil\n def self.name(io : String::Builder, text : String, flag : Char) : Nil\n # A number padded to *width* with *pad*, '\\0' for none, unless a flag\n # says otherwise. The sign of a negative year goes in front of the\n # padding, as `-0001`, the way `to_rfc3339` writes it.\n def self.number(io : String::Builder, value : Int64, width : Int32, pad : Char, flag : Char) : Nil\n def self.numeric?(letter : Char) : Bool\n def offset : Int32 | ::Nil\n # `+hh:mm` or `+hhmm`, and the seconds never (0), when there are any (1)\n # or always (2), the way Crystal's `Zone#format` writes an offset.\n def self.offset_text(offset : Int32, colon : Bool, seconds : Int32) : String\n def parse(string : String, offset : Int32 | ::Nil) : Time | TimeParseError\n def parse(string : String) : Time | TimeParseError\n def pattern : String\n def self.plain?(letter : Char) : Bool\n def self.textual?(letter : Char) : Bool\n def self.twelve(hour : Int32) : Int64\n def self.write(io : String::Builder, pattern : String, f : Fields) : Nil\n def self.write_directive(io : String::Builder, letter : Char, flag : Char, colons : Int32, digits : Int32, f : Fields) : Nil\n end\n lib LibKernel32\n end\n alias Location = Std::Time::Location\n alias Span = Std::Time::Span\n # The sum is checked before it is taken, so a span too long for the\n # seconds is refused as one; a sum that fits but leaves the years an\n # Int32 names is refused by the constructor. A span is exact: a day\n # added across the night summer begins is 24 hours, and the clock reads\n # an hour later than it did.\n def +(span : Span) : Time\n def -(span : Span) : Time\n def -(other : Time) : Span\n def <(other : Time) : Bool\n def <=(other : Time) : Bool\n def <=>(other : Time) : Int32\n def ==(other : Time) : Bool\n def >(other : Time) : Bool\n def >=(other : Time) : Bool\n # The ISO 8601 week-numbering year and week this day falls in, as\n # Crystal's `calendar_week` answers: 2021-01-01 is in week 53 of 2020.\n def calendar_week : ::Tuple(Int32, Int32)\n # Converts days since 1970-01-01 into {year, month, day}.\n def self.civil_from_days(z : Int64) : ::Tuple(Int32, Int32, Int32)\n def day : Int32\n def day_of_week : DayOfWeek\n def day_of_year : Int32\n def self.days_from_civil(y : Int64, m : Int32, d : Int32) : Int64\n def self.days_in_month(year : Int32, month : Int32) : Int32\n def self.days_in_year(year : Int32) : Int32\n # Number of days between 1970-01-01 and the given civil date.\n # Follows Howard Hinnant's Gregorian calendar algorithm.\n def self.floor_div(a : Int64, b : Int64) : Int64\n def self.floor_mod(a : Int64, b : Int64) : Int64\n def self.from_unix(seconds : Int64, nanoseconds : Int32 = 0) : Time\n def self.from_unix_ms(milliseconds : Int64) : Time\n # Hashing follows `==`, as `Span`'s does. Without it every time hashed to its\n # type: `Time.unix(1).hash == Time.unix(1000000, 5).hash`, and a table of\n # 16,000 times took 1.2 s to fill where the same pairs as tuples took 1 ms.\n def hash : Int32\n def hour : Int32\n # The same instant read in *location*: `Time.utc(2024, 7, 1, 12).in(berlin)`\n # is 14:00 CEST.\n def in(location : Location) : Time\n def initialize(seconds : Int64, nanoseconds : Int32 = 0, location : Location = Location::UTC)\n # As the other library writes it: the date and time, the nanoseconds\n # when there are any, and `Z`. There was no `inspect`, so it was `to_s`,\n # and `Time.utc(2024, 1, 1, 0, 0, 0, nanosecond: 5)` inspected as\n # `2024-01-01 00:00:00 UTC`, the same as `Time.utc(2024, 1, 1)`, which\n # `==` tells apart from it. Out of UTC the offset follows, and the\n # location's name when it is more than an offset:\n # `2016-02-15 11:20:30 +01:00 Europe/Berlin`.\n def inspect : String\n # The ISO 8601 week-numbering year and week of a date, from its year, day\n # of the month, day of the year and day of the week (Monday is 1). This is\n # Crystal's computation: the week is the count of Mondays up to the day,\n # and the first days of January or the last of December can belong to a\n # week of the year next to it. The year is an Int64 because the first days\n # of the smallest Int32 year belong to the year before it.\n def self.iso_week(year : Int64, day : Int32, day_of_year : Int32, day_of_week : Int32) : ::Tuple(Int64, Int32)\n def self.leap_year?(year : Int32) : Bool\n # The time a clock in *location* shows as the date and time given, the\n # machine's own zone when none is. Its fields are checked as `utc` checks\n # them. A wall time a change of offset repeats, the hour after summer\n # ends, is the earlier of the two; one a change skips, the hour summer\n # begins with, is read past the gap (see `Location#resolve`).\n def self.local(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0, location : Location = Location.local) : Time\n # Now, read in *location*.\n def self.local(location : Location) : Time\n # Now, in the machine's own zone (`Location.local`).\n def self.local : Time\n def local? : Bool\n # The zone the date and clock are read in, and its offset from UTC at\n # this instant in seconds east. `==`, `<=>` and `hash` are the instant's\n # and do not look at either: noon in London and 13:00 in Paris are the\n # same time.\n def location : Location\n def millisecond : Int32\n def milliseconds : Int32\n def minute : Int32\n def self.monotonic : Span\n def self.monotonic_nanoseconds : UInt64\n def month : Int32\n def nanosecond : Int32\n def nanoseconds : Int32\n def self.new(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time\n def offset : Int32\n def self.pad2(n : Int32) : String\n def self.pad3(n : Int32) : String\n # Four digits, and a negative year keeps its sign in front of them rather\n # than inside: written as `n < 10 ? \"000\" + n.to_s`, year -1 printed\n # `000-1-01-01`, which is neither a year nor a date. ISO 8601 spells it\n # `-0001`, and the conversions themselves were right all along - the epoch\n # round-trips through it.\n def self.pad4(n : Int32) : String\n def self.pad6(n : Int32) : String\n def self.pad9(n : Int32) : String\n # Reads *string* by *pattern*'s directives (see `Time::Format`). A text\n # that names no zone is read at *offset* seconds east of UTC, as Crystal's\n # `Time.parse` reads one at the location it is given.\n def self.parse(string : String, pattern : String, offset : Int32) : Time | TimeParseError\n # Reads *string* by *pattern* in *location*: a text that names no zone is\n # the wall clock there, its offset the one the location kept at that date\n # (a wall time a change repeats or skips is read as `Time.local` reads\n # it), and the time answered is in *location* whether the text named a\n # zone or not.\n def self.parse(string : String, pattern : String, location : Location) : Time | TimeParseError\n def self.parse_iso8601(string : String) : Time | TimeParseError\n # `parse` in the machine's own zone.\n def self.parse_local(string : String, pattern : String) : Time | TimeParseError\n # Any of RFC 9110's three HTTP date forms; see `Time::Format::HTTP_DATE`.\n def self.parse_rfc1123(string : String) : Time | TimeParseError\n def self.parse_rfc2822(string : String) : Time | TimeParseError\n # Parses an RFC 3339 / ISO 8601 string into a UTC Time, and panics with the\n # parser's sentence when it is not one. Handles forms like:\n # 2024-02-29T12:30:45Z\n # 2024-02-29T12:30:45.123Z\n # 2024-02-29T12:30:45.123456789Z\n # 2024-02-29 12:30:45Z\n # 2024-02-29T12:30:45+05:00\n # 2024-02-29T12:30:45-08:00\n # -0001-01-01T00:00:00Z and 12345-06-07T08:09:10Z, as `to_rfc3339` writes them\n # `Time::Format::RFC_3339.parse` reads the same grammar and answers a\n # `TimeParseError` instead, for text the program did not write itself.\n def self.parse_rfc3339(str : String) : Time\n # `parse` at UTC when the text names no zone.\n def self.parse_utc(string : String, pattern : String) : Time | TimeParseError\n def second : Int32\n def seconds : Int64\n # Moves the date by calendar years and months, then by weeks and days,\n # then by the smaller units, in that order, as Crystal's `shift` does. A\n # day the month does not have becomes its last: `Time.utc(2024, 1,\n # 31).shift(months: 1)` is 2024-02-29. The date moves on the wall clock\n # of the time's location, so the time of day is kept across a change of\n # offset - a day after 12:00 on the Saturday summer begins is 12:00 on\n # Sunday, 23 hours later - and the hours, minutes, seconds and\n # nanoseconds are exact. A year past an Int32 is refused with the\n # constructor's sentence, and a span the Int64 seconds do not hold with\n # `Span`'s.\n def shift(*, years : Int32 = 0, months : Int32 = 0, weeks : Int64 = 0_i64, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Time\n def to_local : Time\n # RFC 1123's date as HTTP writes it, RFC 9110's IMF-fixdate, always in GMT:\n # `Sun, 06 Nov 1994 08:49:37 GMT`.\n def to_rfc1123 : String\n # RFC 2822's date, `Mon, 15 Feb 2016 04:35:50 +0000`, at *offset*, or at\n # the time's own offset when none is given.\n def to_rfc2822(offset : Int32 | ::Nil = nil) : String\n # RFC 3339 at the time's own offset: `Z` at UTC, `+02:00` in CEST. An\n # offset RFC 3339 cannot write, one with seconds in it as local mean time\n # before standard time had, is written as the same instant in UTC.\n def to_rfc3339(fraction_digits : Int32 = 0) : String\n # The instant written as it reads at *offset* seconds east of UTC:\n # `to_s(\"%F %T %:z\", 19800)` is the wall clock in India.\n def to_s(format : String, offset : Int32) : String\n # The time written by *format*'s directives (see `Time::Format`) as its\n # own location reads it; `%Z` is the zone's abbreviation, `CEST`.\n def to_s(format : String) : String\n # `2016-02-15 10:20:30 UTC`, and in any other location the offset in\n # place of `UTC`: `2016-02-15 11:20:30 +01:00`.\n def to_s : String\n def to_unix : Int64\n def to_unix_f : Float64\n # Refused with the module's sentence past the milliseconds an Int64\n # holds, some 292 million years either side of 1970, where the multiply\n # was the prelude's bare \"arithmetic overflow\" for a year `Time.utc`\n # accepts, 300,000,000. Both edges answer to the last millisecond that\n # fits: the lowest 808 an Int64 holds were refused, so\n # `Time.unix_ms(Int64::MIN).to_unix_ms` panicked. A negative second is\n # taken one up and its milliseconds from 1000 down, so the product fits\n # too.\n def to_unix_ms : Int64\n def to_utc : Time\n def self.unix(seconds : Int64, nanoseconds : Int32 = 0) : Time\n def self.unix_ms(milliseconds : Int64) : Time\n def self.utc(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time\n def self.utc : Time\n def utc? : Bool\n def self.wall_clock_nanoseconds : UInt64\n # The instant of a day of an ISO 8601 week, as Crystal's `week_date` makes\n # it: `Time.week_date(2016, 1, 1)` is Monday 2016-01-04. A week the year\n # does not have and a day past Sunday are refused, where the other library answered\n # a day of some other week.\n def self.week_date(year : Int32, week : Int32, day_of_week : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, nanosecond : Int32 = 0) : Time\n # Days since 1970-01-01 of a day of an ISO 8601 week. Week 1 is the week\n # holding January 4th, so its Monday is the Monday on or before that day.\n def self.week_date_days(year : Int32, week : Int32, day_of_week : Int32) : Int64\n # A year has 53 ISO 8601 weeks when it starts on a Thursday, or on a\n # Wednesday in a leap year: either way it has 53 Thursdays, and a week\n # belongs to the year that holds its Thursday.\n def self.weeks_in_year(year : Int32) : Int32\n def year : Int32\n # The zone this instant is in at its location: its abbreviation, offset\n # and whether it is summer time.\n def zone : Location::Zone\nend\n\n# What `Time::Format#parse` and the named parsers answer when the text is not\n# a time the pattern describes. Bad input is the caller's to handle, not a\n# panic: a header or a log line from somewhere else is not a bug in the\n# program reading it. `message` is the sentence, naming the byte the parser\n# stood at and the text; `position` is that byte, counted from 0.\npub struct TimeParseError\n def initialize(message : String, input : String, position : Int32)\n def input : String\n def position : Int32\n def to_s : String\nend\n\nimpl Error for TimeParseError\n def message : String\nend\n\nimpl Std::Traits::Comparable for Span\n def <=>(other : self) : Int32\nend\n\nimpl Error for LocationError\n def message : String\nend\n\nimpl Std::Traits::Comparable for Time\n def <=>(other : self) : Int32\nend\n" + "sourceSha256": "5282622222a57554e3e1f9379004f3fdb2194b89ef3c8829a804b95da1091ac3", + "surface": "module std/time\n\npub struct DayOfWeek\n def ==(other : DayOfWeek) : Bool\n def ===(other : DayOfWeek) : Bool\n def friday? : Bool\n # Hashing follows `==`. Without it a day hashed to its type, `Object#hash`'s\n # default: `Monday.hash == Sunday.hash`, and every day a table held went to\n # one slot.\n def hash : Int32\n def initialize(value : Int32)\n def monday? : Bool\n def saturday? : Bool\n def sunday? : Bool\n def thursday? : Bool\n def to_s : String\n def tuesday? : Bool\n def value : Int32\n def wednesday? : Bool\nend\n\n# A time zone: the offsets from UTC a place has kept, and when it changed\n# from one to the next. `Location.load(\"Europe/Istanbul\")` reads the IANA\n# database the machine has; `Location.local` is the machine's own zone;\n# `Location.fixed(19800)` is an offset and nothing else; `Location::UTC` is\n# UTC. A `Time` carries one, and its date and clock are read in it.\n#\n# The database is the system's TZif files, RFC 8536: `$ZONEINFO` when it\n# names a directory, then `/usr/share/zoneinfo`, `/usr/share/lib/zoneinfo`\n# and `/etc/zoneinfo`. Versions 1 to 4 are read. A time before the first\n# change has the file's first offset, and one after the last has its POSIX\n# footer's rule, so a slim file answers for the year 2100 as a fat one does.\n# Leap seconds are not counted, as no clock this module reads counts them.\n#\n# `Location.local` is the `TZ` variable when it is set — the empty string\n# is UTC, a leading `:` is dropped, an absolute path is a TZif file, a name\n# is looked up in the database, and anything else is read as a POSIX TZ\n# string such as `EST5EDT,M3.2.0,M11.1.0` — and `/etc/localtime` when it is\n# not. Windows has no database to read: there `local` is the system's zone,\n# its rule from `GetTimeZoneInformation`, its zones named by their offsets,\n# and `load` reads `$ZONEINFO` alone. Where nothing answers, local is UTC.\n# The local zone is read once, the first time it is asked for.\n#\n# A location is built once and never changed, so a `Time` that holds one\n# can cross to another task.\npub class Location\n # One offset a location keeps: its abbreviation (`CEST`, `+03`), its\n # seconds east of UTC, and whether it is summer time.\n struct Zone\n def ==(other : Zone) : Bool\n def dst? : Bool\n def initialize(name : String, offset : Int32, dst : Bool)\n def name : String\n def offset : Int32\n def to_s : String\n end\n def ==(other : Location) : Bool\n def self.fixed(name : String, offset : Int32) : Location\n # An offset of *offset* seconds east of UTC and nothing else, named as\n # the offset is written: `+05:30`. Less than a day either way.\n def self.fixed(offset : Int32) : Location\n # One offset, never changing.\n def fixed? : Bool\n def hash : Int32\n def initialize(name : String, zones : Array(Zone), transitions : Array(Int64), transition_zones : Array(Int32), rule : TzRule | ::Nil)\n def inspect : String\n # The zone the IANA database names *name*: `Europe/Istanbul`,\n # `America/New_York`. `UTC` and the empty name are UTC and `Local` is\n # `Location.local`, without a file. A name that climbs out of the database\n # (`..`, a leading `/`, a `\\`) is refused, and so is one no directory has.\n def self.load(name : String) : Location | LocationError\n # The machine's zone; see the type's comment for where it comes from.\n def self.local : Location\n # The machine's own zone, the one `Location.local` answers.\n def local? : Bool\n # The zone *time* is in here.\n def lookup(time : Time) : Zone\n def lookup(unix_seconds : Int64) : Zone\n def name : String\n # A location from the 172 bytes of a Windows TIME_ZONE_INFORMATION, which\n # is what `Location.local` reads on Windows; it is pure so that every\n # platform can check the decoding with bytes made by hand. The struct is\n # the bias in minutes west of UTC (UTC = local + bias), then the standard\n # name, date and bias, then the daylight ones, all little-endian. The\n # three biases are signed LONGs: a zone east of UTC has a negative Bias\n # (Turkey's is -180) and a summer usually a DaylightBias of -60, so each\n # is read as the 32 bits' two's complement. Read as unsigned and then\n # converted, as it once was, -180 was 4294967116 and the conversion\n # panicked with an arithmetic overflow, issue #133.\n #\n # A date with a year of 0 is a rule - the wDay'th wDayOfWeek of wMonth,\n # 5 the last, at the wall time then in force - which is a POSIX\n # `Mm.w.d/time`; one with a month of 0 is no summer. A date with a year\n # is a single year's, which is not a rule at all, and it is read as no\n # summer rather than as every year's. The names are the offset's, `+03`\n # or `-05`, as tzdata writes a zone it has no letters for: the struct's\n # own are Windows' long names, \"Turkey Standard Time\", which are not\n # abbreviations and are localised.\n def self.read_windows_zone(name : String, data : String) : Location | LocationError\n # A location from the bytes of a TZif file, RFC 8536, named *name*.\n # Anything that is not one is refused with what was wrong and where.\n def self.read_zoneinfo(name : String, data : String) : Location | LocationError\n # The instant a wall clock here reads *wall* (seconds since 1970 as a\n # UTC clock would count them). A wall time a change repeats is the\n # earlier of the two instants; one a change skips is read on the clock\n # from before the change, which lands it past the gap: 02:30 on the\n # morning summer starts in Berlin is 03:30 CEST. Python's `fold=0` and\n # Go read them the same way. `Time.local` and a parse at a location ask\n # this.\n def resolve(wall : Int64) : Int64\n def to_s : String\n def utc? : Bool\nend\n\n# What `Location.load` and `Location.read_zoneinfo` answer when there is no\n# zone by that name, or the bytes are not a zone: a name the program was\n# handed or a file on the machine is not a bug in the program, so it is a\n# value and not a panic.\npub struct LocationError\n def initialize(message : String)\n def to_s : String\nend\n\npub struct Span\n def +(other : Span) : Span\n def -(other : Span) : Span\n def - : Span\n def <(other : Span) : Bool\n def <=(other : Span) : Bool\n def <=>(other : Span) : Int32\n def ==(other : Span) : Bool\n def >(other : Span) : Bool\n def >=(other : Span) : Bool\n # The seconds are an Int64, and a span that leaves it is refused here,\n # by name: `Span.seconds(Int64::MAX) + Span.seconds(1)` panicked with the\n # prelude's \"arithmetic overflow\", a sentence that named no span. The\n # wrapping sum is compared with an addend to see whether it wrapped; the\n # product is bounded before it is taken.\n def self.add_seconds(a : Int64, b : Int64) : Int64\n def days : Int64\n def self.days(n : Int64) : Span\n # Hashing follows `==`, the two parts mixed as a tuple's members are. Without\n # it every span hashed to its type: `Span.seconds(1).hash ==\n # Span.days(9).hash`, and a table of 16,000 spans took 0.95 s to fill where\n # the same pairs as tuples took 1 ms.\n def hash : Int32\n def hours : Int32\n def self.hours(n : Int64) : Span\n def initialize(seconds : Int64 = 0_i64, nanoseconds : Int32 = 0)\n def millisecond : Int32\n def milliseconds : Int32\n def self.milliseconds(n : Int64) : Span\n def minutes : Int32\n def self.minutes(n : Int64) : Span\n def self.mul_seconds(n : Int64, unit : Int64) : Int64\n def nanosecond : Int32\n def nanoseconds : Int32\n def self.nanoseconds(n : Int64) : Span\n def self.new(*, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, milliseconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Span\n def seconds : Int32\n def self.seconds(n : Int64) : Span\n def self.sub_seconds(a : Int64, b : Int64) : Int64\n def to_i : Int64\n def to_s : String\n def total_days : Float64\n def total_hours : Float64\n def total_milliseconds : Float64\n def total_minutes : Float64\n def total_nanoseconds : Float64\n def total_seconds : Float64\n def total_seconds_i64 : Int64\n def self.zero : Span\nend\n\npub struct Time\n alias DayOfWeek = Std::Time::DayOfWeek\n # A pattern of Crystal's directives, for writing a `Time` and reading one\n # back. Text that is not a directive is written as it is and must be\n # there, byte for byte, when reading; ASCII whitespace in the pattern is\n # written as it is and reads any run of whitespace, none included.\n #\n # %Y year, four digits or more, year -1 as -0001 %C year // 100\n # %y year % 100 %G %g %V the ISO 8601 week year, its last two digits,\n # and the week 01..53 %m month 01..12 %B January %b %h Jan\n # %d day 01..31 %e day \" 1\"..\"31\" %j day of the year 001..366\n # %H hour 00..23 %k \" 0\"..\"23\" %I 01..12 %l \" 1\"..\"12\" %p am %P AM\n # %M minute %S second %L milliseconds %3N %6N %9N the fraction to\n # 3, 6 and 9 digits %N nanoseconds, any number of digits when read\n # %A Sunday %a Sun %u day of the week, Monday 1..7 %w Sunday 0..6\n # %s seconds since the epoch %z +0530 %:z +05:30 %::z +05:30:00\n # %Z and %^Z: UTC at offset 0 and the offset, +05:30, at any other\n # %F %Y-%m-%d %T %X %H:%M:%S %R %H:%M %D %x %m/%d/%y\n # %r %I:%M:%S %P %c %a %b %e %H:%M:%S %Y %n newline %t tab %% %\n #\n # A number takes a flag between the `%` and its letter: `-` for no\n # padding (`%-d` is 5), `_` for spaces (`%_m` is \" 5\") and `0` for zeros\n # (`%0e` is 05); `^` upcases a name (`%^a` is SUN). The other library knows\n # `%-d`, `%-m`, `%_m` and the `^` names. The same flags on its other numbers\n # are this module's, where that library wrote the `%-` back out as text; a `%`\n # that starts no directive is text here as it is there.\n #\n # Reading takes up to a number's width in digits, fewer when the next byte\n # is not a digit, and a name in any case. `%Y` takes four digits when a\n # directive follows it (`%Y%m%d`), and when text or the end of the\n # pattern does (`%F`), an optional `-` and up to ten, so the years `to_s`\n # writes are the years it reads. A field outside its range, a day the\n # month does not have, a name that is not one and text after the pattern\n # are each a `TimeParseError`, never a panic. A day name is checked to be\n # one and not checked against the date, as Crystal, Go and Python read it.\n struct Format\n # The parts of an instant as they read at an offset, worked out once\n # per `format` rather than once per directive. *zone* is what `%Z`\n # writes, the location's abbreviation; with none it writes `UTC` at\n # offset 0 and the offset at any other.\n struct Fields\n def day : Int32\n def day_of_week : Int32\n def day_of_year : Int32\n def hour : Int32\n def initialize(time : Time, offset : Int32, zone : String | ::Nil)\n def minute : Int32\n def month : Int32\n def nanosecond : Int32\n def offset : Int32\n def second : Int32\n def unix : Int64\n def week : ::Tuple(Int64, Int32)\n def year : Int32\n def zone : String | ::Nil\n end\n # The HTTP date of RFC 9110 section 5.6.7. It is written as the\n # IMF-fixdate, always in GMT, `Sun, 06 Nov 1994 08:49:37 GMT`; it is\n # read in all three of the forms a recipient must accept: that one, the\n # obsolete RFC 850 `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's\n # `Sun Nov 6 08:49:37 1994`.\n module HTTP_DATE\n def self.format(time : Time) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # ISO 8601's calendar date, written `2016-02-15` as the date reads at\n # *offset* or in the time's own location; read extended or basic, and\n # as an ordinal or a week date, at *offset* since a date names no zone.\n module ISO_8601_DATE\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String, offset : Int32 = 0) : Time | TimeParseError\n end\n # ISO 8601's date and time, written as RFC 3339 writes it and read with\n # every form of `ISO_8601_DATE` and `ISO_8601_TIME` joined by an\n # optional `T`. The zone is required, as Crystal requires it.\n module ISO_8601_DATE_TIME\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # ISO 8601's time of day and its zone, `04:35:50Z`; read as on\n # 0001-01-01, the day Crystal reads it on.\n module ISO_8601_TIME\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # Reads a text by a pattern or by one of the named grammars. Every step\n # answers whether it matched; the first that did not leaves its sentence\n # in `@error`, and `finish` answers that, so no text a program is handed\n # ends it in a panic.\n class Parser\n def accept(char : Char) : Bool\n def am_pm : Bool\n def at_end : Bool\n # RFC 5322's CFWS: whitespace and parenthesised comments, which nest\n # and in which `\\` quotes the next byte. Answers whether there was any;\n # a comment left open is the error, and the parse fails on it.\n def cfws : Bool\n def day_name : Bool\n def self.digit?(b : UInt8) : Bool\n # Up to *max* digits and at least one. Nil, with the sentence, when\n # there are none, or when they are past what an Int64 holds.\n def digits(max : Int32, what : String) : Int64 | ::Nil\n def directive(letter : Char, flag : Char, digits : Int32, wide : Bool) : Bool\n # Exactly *count* digits, or nil, reading nothing and saying nothing:\n # the caller names what was missing.\n def exact(count : Int32) : Int32 | ::Nil\n def expect(char : Char) : Bool\n def fail(what : String) : Bool\n # A numeric directive's field, read as its flag and its padding say.\n def field(width : Int32, pad : Char, flag : Char, what : String) : Int32 | ::Nil\n # The instant the fields name, or the first sentence a step left. The\n # fields are checked the way `Time.utc` checks them, with RFC 3339's\n # allowance: `:60` is a leap second, read as the second after `:59`,\n # the instant it names on every clock that has none. A text that\n # names no zone is read at *default_offset*, or failing that on\n # *location*'s wall clock; with a location the time answered is in it.\n def finish(default_offset : Int32 | ::Nil, location : Location | ::Nil = nil) : Time | TimeParseError\n # A fraction of a second: up to *max* digits, scaled to nanoseconds,\n # and for `%N` any digits past the ninth read and dropped.\n def fraction(max : Int32, rest : Bool) : Bool\n # RFC 9110 section 5.6.7's three forms of an HTTP date: the\n # IMF-fixdate `Sun, 06 Nov 1994 08:49:37 GMT`, the obsolete RFC 850\n # `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's `Sun Nov 6 08:49:37\n # 1994`, which has no zone and is GMT. Crystal's reading: a day name,\n # short or long, in any of the three; the four-digit year IIS writes\n # in the RFC 850 shape; and the zones RFC 2822 names where RFC 9110\n # writes GMT.\n def http_date : Bool\n def initialize(input : String)\n # ISO 8601's dates, extended or basic: 2016-02-15 and 20160215, the\n # ordinal 2016-046 and 2016046, and the week date 2016-W07-1 and\n # 2016W071.\n def iso_date : Bool\n def iso_date_time : Bool\n # ISO 8601's time of day, extended or basic: 04:35:50 and 043550, a\n # fraction of the second after `.` or `,`, or the minutes or the hour\n # alone with a fraction of their own (04:35.5 is 04:35:30).\n def iso_time : Bool\n def self.letter?(b : UInt8) : Bool\n def literal(b : UInt8) : Bool\n def month_name(short : Bool) : Bool\n # A run of ASCII letters and the index in *names* of the name it\n # spells, in any case: for *short*, the first three letters of a name\n # and no more; otherwise the start of a name, three letters or longer,\n # as Crystal reads `%B` and `%A`.\n def name(names : Array(String), short : Bool, what : String) : Int32 | ::Nil\n def need_cfws : Bool\n def number(max : Int32, what : String) : Int32 | ::Nil\n def offset_number(allow_colon : Bool, force_colon : Bool, allow_seconds : Bool) : Bool\n # A number blank padded, as `%e` writes \" 5\": one space stands for a\n # digit.\n def padded_number(max : Int32, what : String) : Int32 | ::Nil\n def pattern(pattern : String) : Bool\n # The byte *ahead* of the current one, or 0 past the end, as Crystal's\n # reader answers '\\0' there.\n def peek(ahead : Int32 = 0) : UInt8\n # RFC 9110's rule for a two-digit year (section 5.6.7): a year that\n # appears to be more than 50 years in the future is the most recent\n # past year with those last two digits. The candidate is taken in this\n # century and moved a century to land in the 100 years ending 50 years\n # from now, so the rule holds in 2090 too. The other library read 00..49 as 20xx\n # and the rest as 19xx, whatever the year it was read in.\n def self.recent_year(two_digits : Int64) : Int64\n # A sentence about fields that were all read, where no one byte is to\n # blame.\n def refuse(what : String) : TimeParseError\n # RFC 5322 section 3.3's date-time, with section 4.3's obsolete\n # two- and three-digit years and zone names, as Crystal's `RFC_2822`\n # reads it: `Mon, 15 Feb 2016 04:35:50 +0000`, the day name and the\n # seconds optional, comments anywhere whitespace goes.\n def rfc_2822 : Bool\n def same_letters?(start : Int32, name : String, size : Int32) : Bool\n def skip_spaces : Bool\n def self.space?(b : UInt8) : Bool\n def text(start : Int32, stop : Int32) : String\n # HH:MM:SS, as both HTTP and RFC 850 write the time of day.\n def time_of_day : Bool\n # One whitespace byte, as the HTTP grammars put between their parts.\n def whitespace : Bool\n # `%Y`: four digits when a directive follows it, and an optional `-`\n # and up to ten when text does, which is every year an Int32 holds.\n def year(wide : Bool) : Int64 | ::Nil\n # Exactly four digits, as the HTTP and ISO 8601 grammars write a year.\n def year4 : Bool\n # `%z`, `%:z` and `%::z` alike, as Crystal reads them: `Z`, `UTC`, or\n # an offset with or without its colons.\n def zone : Bool\n # `%Z`: an offset, or a name read up to the next whitespace. Only the\n # names of UTC itself are read: an abbreviation names no single zone\n # (CST is three of them), so any other is refused by name rather than\n # read as UTC or guessed at. A text whose zone is known is read at\n # that `Location` instead.\n def zone_name : Bool\n # RFC 5322's zone (section 3.3, and 4.3 for the obsolete names): an\n # offset of four digits, or a name. A military letter is read as\n # `-0000`, the offset of a time whose zone is not known, as section\n # 4.3 says to; a name the RFC does not give is refused, where Crystal\n # read it as UTC in silence. `UTC` is not the RFC's, and is read as\n # Crystal reads it.\n def zone_rfc2822 : Bool\n # ISO 8601's zone: `Z` or an offset.\n def zone_z_or_offset : Bool\n end\n # RFC 2822's date and time, `Mon, 15 Feb 2016 04:35:50 +0000`, as\n # Crystal writes it: the day of the month unpadded, the offset at the\n # end, the time's own unless another is given. RFC 2822 writes an offset\n # in whole minutes, so an offset with seconds is refused rather than\n # written as some other instant.\n module RFC_2822\n def self.format(time : Time, offset : Int32 | ::Nil = nil) : String\n def self.parse(string : String) : Time | TimeParseError\n end\n # RFC 3339, the module's own serialisation: `to_rfc3339` and\n # `parse_rfc3339` are this module's `format` and `parse`, and\n # `parse_rfc3339` panics with the sentence `parse` answers. Written at\n # *offset*, or at the time's own when it is whole minutes and in UTC\n # when it is not.\n module RFC_3339\n def self.format(time : Time, fraction_digits : Int32 = 0, offset : Int32 | ::Nil = nil) : String\n def self.parse(str : String) : Time | TimeParseError\n end\n # An offset is less than a day either side of UTC: the widest RFC 3339\n # and RFC 2822 write is 23:59, and a day or more is a date, not a zone.\n # An offset is the program's own, so a wrong one is a panic.\n def self.check_offset(offset : Int32) : Nil\n def format(time : Time, offset : Int32) : String\n def format(time : Time) : String\n # *offset*, seconds east of UTC, is the one a parse reads a text at when\n # the text names no zone, and the one `format` writes at. Nil, as for\n # Crystal's `Format.new(pattern)`, makes a text without a zone an error,\n # and `format` writes a time in its own location.\n def initialize(pattern : String, offset : Int32 | ::Nil = nil)\n # A text that names no zone is read on *location*'s wall clock, and\n # every time is read and written in *location*.\n def initialize(pattern : String, location : Location)\n # The directive at the `%` at byte *i* of *pattern*: the byte after it,\n # its letter, its flag, the colons of `%:z` and `%::z`, and the digits of\n # `%3N`. A `%` that starts no directive answers the letter '\\0' and the\n # byte after the `%`: it is text, and so is what follows it, as Crystal\n # writes `%Q` back out as `%Q`.\n def self.lex(pattern : String, i : Int32) : ::Tuple(Int32, Char, Char, Int32, Int32)\n def location : Location | ::Nil\n def self.name(io : String::Builder, text : String, flag : Char) : Nil\n # A number padded to *width* with *pad*, '\\0' for none, unless a flag\n # says otherwise. The sign of a negative year goes in front of the\n # padding, as `-0001`, the way `to_rfc3339` writes it.\n def self.number(io : String::Builder, value : Int64, width : Int32, pad : Char, flag : Char) : Nil\n def self.numeric?(letter : Char) : Bool\n def offset : Int32 | ::Nil\n # `+hh:mm` or `+hhmm`, and the seconds never (0), when there are any (1)\n # or always (2), the way Crystal's `Zone#format` writes an offset.\n def self.offset_text(offset : Int32, colon : Bool, seconds : Int32) : String\n def parse(string : String, offset : Int32 | ::Nil) : Time | TimeParseError\n def parse(string : String) : Time | TimeParseError\n def pattern : String\n def self.plain?(letter : Char) : Bool\n def self.textual?(letter : Char) : Bool\n def self.twelve(hour : Int32) : Int64\n def self.write(io : String::Builder, pattern : String, f : Fields) : Nil\n def self.write_directive(io : String::Builder, letter : Char, flag : Char, colons : Int32, digits : Int32, f : Fields) : Nil\n end\n struct KernelTimeSpec\n def initialize(tv_sec : Int64, tv_nsec : Int64)\n def tv_nsec : Int64\n def tv_nsec=(value : Int64) : Int64\n def tv_sec : Int64\n def tv_sec=(value : Int64) : Int64\n end\n alias Location = Std::Time::Location\n alias Span = Std::Time::Span\n # The sum is checked before it is taken, so a span too long for the\n # seconds is refused as one; a sum that fits but leaves the years an\n # Int32 names is refused by the constructor. A span is exact: a day\n # added across the night summer begins is 24 hours, and the clock reads\n # an hour later than it did.\n def +(span : Span) : Time\n def -(span : Span) : Time\n def -(other : Time) : Span\n def <(other : Time) : Bool\n def <=(other : Time) : Bool\n def <=>(other : Time) : Int32\n def ==(other : Time) : Bool\n def >(other : Time) : Bool\n def >=(other : Time) : Bool\n # The ISO 8601 week-numbering year and week this day falls in, as\n # Crystal's `calendar_week` answers: 2021-01-01 is in week 53 of 2020.\n def calendar_week : ::Tuple(Int32, Int32)\n # Converts days since 1970-01-01 into {year, month, day}.\n def self.civil_from_days(z : Int64) : ::Tuple(Int32, Int32, Int32)\n def day : Int32\n def day_of_week : DayOfWeek\n def day_of_year : Int32\n def self.days_from_civil(y : Int64, m : Int32, d : Int32) : Int64\n def self.days_in_month(year : Int32, month : Int32) : Int32\n def self.days_in_year(year : Int32) : Int32\n # Number of days between 1970-01-01 and the given civil date.\n # Follows Howard Hinnant's Gregorian calendar algorithm.\n def self.floor_div(a : Int64, b : Int64) : Int64\n def self.floor_mod(a : Int64, b : Int64) : Int64\n def self.from_unix(seconds : Int64, nanoseconds : Int32 = 0) : Time\n def self.from_unix_ms(milliseconds : Int64) : Time\n # Hashing follows `==`, as `Span`'s does. Without it every time hashed to its\n # type: `Time.unix(1).hash == Time.unix(1000000, 5).hash`, and a table of\n # 16,000 times took 1.2 s to fill where the same pairs as tuples took 1 ms.\n def hash : Int32\n def hour : Int32\n # The same instant read in *location*: `Time.utc(2024, 7, 1, 12).in(berlin)`\n # is 14:00 CEST.\n def in(location : Location) : Time\n def initialize(seconds : Int64, nanoseconds : Int32 = 0, location : Location = Location::UTC)\n # As the other library writes it: the date and time, the nanoseconds\n # when there are any, and `Z`. There was no `inspect`, so it was `to_s`,\n # and `Time.utc(2024, 1, 1, 0, 0, 0, nanosecond: 5)` inspected as\n # `2024-01-01 00:00:00 UTC`, the same as `Time.utc(2024, 1, 1)`, which\n # `==` tells apart from it. Out of UTC the offset follows, and the\n # location's name when it is more than an offset:\n # `2016-02-15 11:20:30 +01:00 Europe/Berlin`.\n def inspect : String\n # The ISO 8601 week-numbering year and week of a date, from its year, day\n # of the month, day of the year and day of the week (Monday is 1). This is\n # Crystal's computation: the week is the count of Mondays up to the day,\n # and the first days of January or the last of December can belong to a\n # week of the year next to it. The year is an Int64 because the first days\n # of the smallest Int32 year belong to the year before it.\n def self.iso_week(year : Int64, day : Int32, day_of_year : Int32, day_of_week : Int32) : ::Tuple(Int64, Int32)\n def self.kernel_clock_gettime(clock_id : Int64) : ::Tuple(Int64, Int64)\n def self.leap_year?(year : Int32) : Bool\n # The time a clock in *location* shows as the date and time given, the\n # machine's own zone when none is. Its fields are checked as `utc` checks\n # them. A wall time a change of offset repeats, the hour after summer\n # ends, is the earlier of the two; one a change skips, the hour summer\n # begins with, is read past the gap (see `Location#resolve`).\n def self.local(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0, location : Location = Location.local) : Time\n # Now, read in *location*.\n def self.local(location : Location) : Time\n # Now, in the machine's own zone (`Location.local`).\n def self.local : Time\n def local? : Bool\n # The zone the date and clock are read in, and its offset from UTC at\n # this instant in seconds east. `==`, `<=>` and `hash` are the instant's\n # and do not look at either: noon in London and 13:00 in Paris are the\n # same time.\n def location : Location\n def millisecond : Int32\n def milliseconds : Int32\n def minute : Int32\n def self.monotonic : Span\n def self.monotonic_nanoseconds : UInt64\n def month : Int32\n def nanosecond : Int32\n def nanoseconds : Int32\n def self.new(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time\n def offset : Int32\n def self.pad2(n : Int32) : String\n def self.pad3(n : Int32) : String\n # Four digits, and a negative year keeps its sign in front of them rather\n # than inside: written as `n < 10 ? \"000\" + n.to_s`, year -1 printed\n # `000-1-01-01`, which is neither a year nor a date. ISO 8601 spells it\n # `-0001`, and the conversions themselves were right all along - the epoch\n # round-trips through it.\n def self.pad4(n : Int32) : String\n def self.pad6(n : Int32) : String\n def self.pad9(n : Int32) : String\n # Reads *string* by *pattern*'s directives (see `Time::Format`). A text\n # that names no zone is read at *offset* seconds east of UTC, as Crystal's\n # `Time.parse` reads one at the location it is given.\n def self.parse(string : String, pattern : String, offset : Int32) : Time | TimeParseError\n # Reads *string* by *pattern* in *location*: a text that names no zone is\n # the wall clock there, its offset the one the location kept at that date\n # (a wall time a change repeats or skips is read as `Time.local` reads\n # it), and the time answered is in *location* whether the text named a\n # zone or not.\n def self.parse(string : String, pattern : String, location : Location) : Time | TimeParseError\n def self.parse_iso8601(string : String) : Time | TimeParseError\n # `parse` in the machine's own zone.\n def self.parse_local(string : String, pattern : String) : Time | TimeParseError\n # Any of RFC 9110's three HTTP date forms; see `Time::Format::HTTP_DATE`.\n def self.parse_rfc1123(string : String) : Time | TimeParseError\n def self.parse_rfc2822(string : String) : Time | TimeParseError\n # Parses an RFC 3339 / ISO 8601 string into a UTC Time, and panics with the\n # parser's sentence when it is not one. Handles forms like:\n # 2024-02-29T12:30:45Z\n # 2024-02-29T12:30:45.123Z\n # 2024-02-29T12:30:45.123456789Z\n # 2024-02-29 12:30:45Z\n # 2024-02-29T12:30:45+05:00\n # 2024-02-29T12:30:45-08:00\n # -0001-01-01T00:00:00Z and 12345-06-07T08:09:10Z, as `to_rfc3339` writes them\n # `Time::Format::RFC_3339.parse` reads the same grammar and answers a\n # `TimeParseError` instead, for text the program did not write itself.\n def self.parse_rfc3339(str : String) : Time\n # `parse` at UTC when the text names no zone.\n def self.parse_utc(string : String, pattern : String) : Time | TimeParseError\n def second : Int32\n def seconds : Int64\n # Moves the date by calendar years and months, then by weeks and days,\n # then by the smaller units, in that order, as Crystal's `shift` does. A\n # day the month does not have becomes its last: `Time.utc(2024, 1,\n # 31).shift(months: 1)` is 2024-02-29. The date moves on the wall clock\n # of the time's location, so the time of day is kept across a change of\n # offset - a day after 12:00 on the Saturday summer begins is 12:00 on\n # Sunday, 23 hours later - and the hours, minutes, seconds and\n # nanoseconds are exact. A year past an Int32 is refused with the\n # constructor's sentence, and a span the Int64 seconds do not hold with\n # `Span`'s.\n def shift(*, years : Int32 = 0, months : Int32 = 0, weeks : Int64 = 0_i64, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Time\n def to_local : Time\n # RFC 1123's date as HTTP writes it, RFC 9110's IMF-fixdate, always in GMT:\n # `Sun, 06 Nov 1994 08:49:37 GMT`.\n def to_rfc1123 : String\n # RFC 2822's date, `Mon, 15 Feb 2016 04:35:50 +0000`, at *offset*, or at\n # the time's own offset when none is given.\n def to_rfc2822(offset : Int32 | ::Nil = nil) : String\n # RFC 3339 at the time's own offset: `Z` at UTC, `+02:00` in CEST. An\n # offset RFC 3339 cannot write, one with seconds in it as local mean time\n # before standard time had, is written as the same instant in UTC.\n def to_rfc3339(fraction_digits : Int32 = 0) : String\n # The instant written as it reads at *offset* seconds east of UTC:\n # `to_s(\"%F %T %:z\", 19800)` is the wall clock in India.\n def to_s(format : String, offset : Int32) : String\n # The time written by *format*'s directives (see `Time::Format`) as its\n # own location reads it; `%Z` is the zone's abbreviation, `CEST`.\n def to_s(format : String) : String\n # `2016-02-15 10:20:30 UTC`, and in any other location the offset in\n # place of `UTC`: `2016-02-15 11:20:30 +01:00`.\n def to_s : String\n def to_unix : Int64\n def to_unix_f : Float64\n # Refused with the module's sentence past the milliseconds an Int64\n # holds, some 292 million years either side of 1970, where the multiply\n # was the prelude's bare \"arithmetic overflow\" for a year `Time.utc`\n # accepts, 300,000,000. Both edges answer to the last millisecond that\n # fits: the lowest 808 an Int64 holds were refused, so\n # `Time.unix_ms(Int64::MIN).to_unix_ms` panicked. A negative second is\n # taken one up and its milliseconds from 1000 down, so the product fits\n # too.\n def to_unix_ms : Int64\n def to_utc : Time\n def self.unix(seconds : Int64, nanoseconds : Int32 = 0) : Time\n def self.unix_ms(milliseconds : Int64) : Time\n def self.utc(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time\n def self.utc : Time\n def utc? : Bool\n def self.wall_clock_nanoseconds : UInt64\n # The instant of a day of an ISO 8601 week, as Crystal's `week_date` makes\n # it: `Time.week_date(2016, 1, 1)` is Monday 2016-01-04. A week the year\n # does not have and a day past Sunday are refused, where the other library answered\n # a day of some other week.\n def self.week_date(year : Int32, week : Int32, day_of_week : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, nanosecond : Int32 = 0) : Time\n # Days since 1970-01-01 of a day of an ISO 8601 week. Week 1 is the week\n # holding January 4th, so its Monday is the Monday on or before that day.\n def self.week_date_days(year : Int32, week : Int32, day_of_week : Int32) : Int64\n # A year has 53 ISO 8601 weeks when it starts on a Thursday, or on a\n # Wednesday in a leap year: either way it has 53 Thursdays, and a week\n # belongs to the year that holds its Thursday.\n def self.weeks_in_year(year : Int32) : Int32\n def year : Int32\n # The zone this instant is in at its location: its abbreviation, offset\n # and whether it is summer time.\n def zone : Location::Zone\nend\n\n# What `Time::Format#parse` and the named parsers answer when the text is not\n# a time the pattern describes. Bad input is the caller's to handle, not a\n# panic: a header or a log line from somewhere else is not a bug in the\n# program reading it. `message` is the sentence, naming the byte the parser\n# stood at and the text; `position` is that byte, counted from 0.\npub struct TimeParseError\n def initialize(message : String, input : String, position : Int32)\n def input : String\n def position : Int32\n def to_s : String\nend\n\nimpl Error for TimeParseError\n def message : String\nend\n\nimpl Std::Traits::Comparable for Span\n def <=>(other : self) : Int32\nend\n\nimpl Error for LocationError\n def message : String\nend\n\nimpl Std::Traits::Comparable for Time\n def <=>(other : self) : Int32\nend\n" }, { "name": "std/tls", "path": "src/std/tls.iyi", - "sourceSha256": "2f58e8374471f2f0d7e5d87a6cd69536dfe8c0af325c7a7b37e04faebac72766", - "surface": "module std/tls\n\n# The alerts of RFC 8446 section 6, by the code each is sent as.\npub struct TlsAlert\n def initialize\nend\n\n# A connection to a server, its handshake done.\npub class TlsClient < TlsConnection\n # A connection to *host* on *port*, its handshake done and the server's\n # certificate checked for *host*.\n def self.connect(host : String, port : Int32, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = -1) : TlsClient | TlsError\n def handshake : TlsError | ::Nil\n def initialize(socket : IyiSocket, host : String, config : TlsConfig)\n # The handshake over a connected *socket*, for *host*, done within\n # *timeout_ms* (none when 0).\n def self.over(socket : IyiSocket, host : String, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS) : TlsClient | TlsError\n def server_side? : Bool\nend\n\n# What a connection is checked against and offers.\npub class TlsConfig\n # Protocols offered by ALPN (RFC 7301), most wanted first: `[\"h2\",\n # \"http/1.1\"]`. None offered when empty.\n def alpn : Array(String)\n # Protocols offered by ALPN (RFC 7301), most wanted first: `[\"h2\",\n # \"http/1.1\"]`. None offered when empty.\n def alpn=(value : Array(String)) : Array(String)\n # The certificate and key sent to a server that asks for one (mutual\n # TLS), when the request names its scheme; none sent when nil.\n def identity : TlsIdentity | ::Nil\n # The certificate and key sent to a server that asks for one (mutual\n # TLS), when the request names its scheme; none sent when nil.\n def identity=(value : TlsIdentity | ::Nil) : TlsIdentity | ::Nil\n def initialize\n # The oldest version spoken. TLS 1.3 by default, and then a server that\n # answers in 1.2 is refused as one that does not speak 1.3; `TLS12`\n # offers 1.2 beside it (see the module's header for what it requires).\n def min_version : TlsVersion\n # The oldest version spoken. TLS 1.3 by default, and then a server that\n # answers in 1.2 is refused as one that does not speak 1.3; `TLS12`\n # offers 1.2 beside it (see the module's header for what it requires).\n def min_version=(value : TlsVersion) : TlsVersion\n # The time certificates are checked at; now when nil.\n def now : Time | ::Nil\n # The time certificates are checked at; now when nil.\n def now=(value : Time | ::Nil) : Time | ::Nil\n # The roots a server's chain must reach; the system's when nil, read\n # once for the whole process and kept, since reading them is most of a\n # handshake's own cost.\n def roots : Array(Certificate) | ::Nil\n # The roots a server's chain must reach; the system's when nil, read\n # once for the whole process and kept, since reading them is most of a\n # handshake's own cost.\n def roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil\n def trusted_roots : Array(Certificate) | TlsError\nend\n\n# What either end of a connection does once it has keys: records read and\n# written under them, alerts, KeyUpdate, close_notify. `TlsClient` and\n# `TlsServer` are its two ends; each runs its own half of the handshake.\npub abstract class TlsConnection\n def abort : Nil\n # The protocol chosen by ALPN, if one was.\n def alpn_protocol : String | ::Nil\n def cipher_suite : String\n # close_notify, then the socket closed.\n def close : Nil\n def closed? : Bool\n # *length* bytes bound to this connection, for *label* and *context*\n # (RFC 8446 section 7.5, RFC 5705's interface): the same at both ends and\n # nowhere else - a channel binding, or keys for another protocol riding\n # on this one. No context is the empty one, as TLS 1.3 has it; in TLS\n # 1.2 (RFC 5705 section 4, over the extended master secret) an empty\n # context is none, as OpenSSL's exporter takes it when told of none.\n def export_keying_material(label : String, context : Array(UInt8) = [] of UInt8, length : Int32 = 32) : Array(UInt8)\n # The alert the error names (its last parenthesised word), sent before\n # the socket closes, by either end (RFC 8446 section 6.2): under the keys\n # this side writes with, or in the clear before there are any. None for\n # an alert the peer sent, whose sentence names no alert of this side's.\n # A server then reads and drops what the client goes on sending, until\n # it closes, for at most `LINGER_BYTES` and `LINGER_MS` in all: a close\n # with the client's flight unread is a reset, which can take the alert\n # from the client's buffer before it is read - a client refused for its\n # certificate would read \"reset\", not why.\n protected def fail(error : TlsError) : TlsError\n def self.group_name(group : Int32) : String\n def initialize(socket : IyiSocket, peer : String, place : String)\n # Records one write key carries before the next (RFC 8446 section 5.5:\n # AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past\n # it, the next write moves to new keys first - or, in TLS 1.2, which has\n # no KeyUpdate, is refused, and the connection with it. Settable, for a\n # test.\n def key_limit : UInt64\n # Records one write key carries before the next (RFC 8446 section 5.5:\n # AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past\n # it, the next write moves to new keys first - or, in TLS 1.2, which has\n # no KeyUpdate, is refused, and the connection with it. Settable, for a\n # test.\n def key_limit=(value : UInt64) : UInt64\n # Moves this side's writes to new keys (section 4.6.3), asking the peer\n # to move its own when *request*: what `write` does by itself at\n # `key_limit`, for a program that wants it sooner. TLS 1.2 has no\n # KeyUpdate: there it is a `TlsError`, and the connection stays open.\n def key_update(request : Bool = false) : TlsError | ::Nil\n # The chain the peer's certificate was checked through, leaf first: the\n # server's, for a client; the client's, for a server that asked for one.\n def peer_certificates : Array(Certificate)\n # Up to *max* bytes the peer sent: \"\" once it has closed with\n # close_notify. The socket's read timeout bounds the whole call.\n def read(max : Int32 = 16384) : String | TlsError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n # Whether this is the server's end.\n abstract def server_side? : Bool\n # The version negotiated: \"TLSv1.3\", or \"TLSv1.2\" for a client whose\n # server chose it.\n def version : String\n # *block* under a deadline *ms* from now (none when 0), cleared after.\n protected def within(ms : Int32, &block : (-> T)) : T forall T\n # Sends *data*, in records of at most 2^14 bytes; answers its size.\n def write(data : String | Array(UInt8)) : Int32 | TlsError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What a connection answers when it cannot be made or kept, or a read that\n# came back with nothing yet.\npub struct TlsError\n # The alert this refusal sends the peer (`TlsAlert`), or nil: none for a\n # failure of the socket, or for an alert the peer sent.\n def alert : UInt8 | ::Nil\n def cancelled? : Bool\n # *message*, the alert's name after it when there is one: the sentence\n # shows the alert, and the alert is the field, not the sentence.\n def initialize(message : String, alert : UInt8 | ::Nil = nil, timed_out : Bool = false, cancelled : Bool = false)\n # A read that waited past its timeout, or that was cancelled: nothing was\n # lost and the connection is open - read again. Any other error ended it.\n def timed_out? : Bool\n def to_s : String\nend\n\n# A certificate chain, leaf first, and the key that signs for it -\n# Ed25519, ECDSA on P-256 (std/p256) or RSA (std/rsa), each in constant\n# time: what a server presents, and what a client presents when a server\n# asks.\npub class TlsIdentity\n # The ClientCertificateType a TLS 1.2 CertificateRequest must list for\n # this key: rsa_sign (1), or ecdsa_sign (64) for P-256 and, by RFC 8422\n # section 5.5, Ed25519.\n def certificate_type : UInt8\n # The chain as sent, leaf first, each certificate's DER.\n def chain : Array(Array(UInt8))\n # *chain*'s certificates, leaf first, and the 32-byte Ed25519 *seed*\n # that is the leaf's private key.\n def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsIdentity | TlsError\n # *chain*'s certificates, leaf first, and the P-256 *key* that is the\n # leaf's.\n def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsIdentity | TlsError\n # *chain*'s certificates, leaf first, and the RSA *key* that is the\n # leaf's (2048 bits or more, std/rsa's floor for signing).\n def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsIdentity | TlsError\n # The chain in *certificates*' CERTIFICATE blocks, leaf first, and the\n # leaf's private key in *private_key*: a PRIVATE KEY block (PKCS #8, an\n # Ed25519 key as RFC 8410 section 7 writes it, a P-256 one as RFC 5915\n # does, or an RSA one), an EC PRIVATE KEY block (SEC 1) or an RSA\n # PRIVATE KEY block (PKCS #1) - or why they are not a certificate and\n # key.\n def self.from_pem(certificates : String, private_key : String) : TlsIdentity | TlsError\n def initialize(chain : Array(Array(UInt8)), leaf : Certificate, schemes : Array(Int32), seed : Array(UInt8) | ::Nil, p256 : P256PrivateKey | ::Nil, rsa : RsaPrivateKey | ::Nil)\n def leaf : Certificate\n # The scheme to sign under for a peer that takes *offered* (its\n # signature_algorithms, in its order): the first of them this key makes,\n # or nil when it makes none.\n def scheme_for(offered : Array(Int32)) : Int32 | ::Nil\n # A scheme by the name RFC 8446 gives it.\n def self.scheme_name(scheme : Int32) : String\n # Schemes by name, for a sentence: \"a, b or c\".\n def self.scheme_names(schemes : Array(Int32)) : String\n # The schemes CertificateVerify can be signed under, by the key's kind:\n # ed25519 (0x0807), ecdsa_secp256r1_sha256 (0x0403), or for an RSA key\n # rsa_pss_rsae_sha256, -384 and -512 (0x0804 to 0x0806). PKCS #1 v1.5\n # is never one: RFC 8446 section 4.2.3 keeps it out of CertificateVerify.\n def schemes : Array(Int32)\n # The leaf key's signature of *content* under *scheme*, one of\n # `schemes`: what CertificateVerify carries.\n def sign(scheme : Int32, content : Array(UInt8)) : Array(UInt8)\nend\n\n# The key schedule of one connection (RFC 8446 section 7.1), over the\n# cipher suite's hash: SHA-384 for TLS_AES_256_GCM_SHA384, SHA-256 for the\n# other two.\npub class TlsKeySchedule\n # Derive-Secret over the messages so far.\n def derive_secret(secret : Array(UInt8), label : String, transcript : Array(UInt8)) : Array(UInt8)\n # The early secret with no PSK, and the \"derived\" salt for the next stage.\n def early_secret : Array(UInt8)\n # HKDF-Expand-Label: the label prefixed \"tls13 \", and the context, in an\n # HkdfLabel.\n def expand_label(secret : Array(UInt8), label : String, context : Array(UInt8), length : Int32) : Array(UInt8)\n def extract(salt : Array(UInt8), ikm : Array(UInt8)) : Array(UInt8)\n # A Finished message's verify_data under *secret* (section 4.4.4).\n def finished(secret : Array(UInt8), transcript : Array(UInt8)) : Array(UInt8)\n def handshake_secret(shared : Array(UInt8)) : Array(UInt8)\n def hash(data : Array(UInt8)) : Array(UInt8)\n def hash_size : Int32\n def hmac(key : Array(UInt8), data : Array(UInt8)) : Array(UInt8)\n def initialize(sha384 : Bool)\n def master_secret(handshake_secret : Array(UInt8)) : Array(UInt8)\n # The next generation of a traffic secret (section 7.2).\n def next_secret(secret : Array(UInt8)) : Array(UInt8)\n # TLS 1.2's PRF (RFC 5246 section 5): P_hash over this schedule's hash -\n # SHA-256, or SHA-384 for a suite that names it - keyed by *secret*, over\n # *label* and *seed*, *length* bytes of it.\n def prf(secret : Array(UInt8), label : String, seed : Array(UInt8), length : Int32) : Array(UInt8)\n def zeros : Array(UInt8)\nend\n\n# One direction's record protection (section 5.2): the AEAD keyed from a\n# traffic secret, and the record sequence number its nonces are made of -\n# or TLS 1.2's, keyed from its key block.\npub class TlsRecordCipher\n # TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256.\n def initialize(suite : Int32, schedule : TlsKeySchedule, secret : Array(UInt8))\n # TLS 1.2's (RFC 5246 section 6.2.3.3) for one of its AEAD suites, *key*\n # and *iv* from the key block: AES-GCM's four-byte salt, to which each\n # record's eight-byte explicit nonce is added (RFC 5288 section 3), or\n # ChaCha20-Poly1305's twelve bytes, XORed with the sequence number as TLS\n # 1.3 does it (RFC 7905 section 2).\n def initialize(suite : Int32, key : Array(UInt8), iv : Array(UInt8))\n def key_iv : Array(UInt8)\n # The type and content of a protected record whose 5-byte header is\n # *header* and body *body*.\n def open(header : Array(UInt8), body : Array(UInt8)) : ::Tuple(UInt8, Array(UInt8)) | TlsError\n # The whole record that carries *content* of *type*, protected.\n def seal(type : UInt8, content : Array(UInt8)) : Array(UInt8)\n def sequence : UInt64\nend\n\n# A connection from a client, its handshake done: TLS 1.3 as a server,\n# its certificate's key (Ed25519 or P-256) signing for it.\npub class TlsServer < TlsConnection\n # The handshake, or why it failed - the client sent that alert first.\n def handshake : TlsError | ::Nil\n # The certificate and key this connection was shown under: the config's\n # `identity`, or one of its `others` the client asked for by name.\n def identity : TlsIdentity\n def initialize(socket : IyiSocket, config : TlsServerConfig)\n # The handshake with the client on *socket*. A handshake that fails\n # sends the client the alert its error names, and the socket is closed.\n def self.over(socket : IyiSocket, config : TlsServerConfig, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS) : TlsServer | TlsError\n # The host the client named (server_name, RFC 6066), if it named one.\n def server_name : String | ::Nil\n def server_side? : Bool\nend\n\n# What a server presents and asks: its identity, the protocols it speaks\n# by ALPN, and - for mutual TLS - the roots a client's certificate must\n# chain to.\npub class TlsServerConfig\n # *identity* shown to a client asking for a host its certificate names.\n def add_identity(identity : TlsIdentity) : TlsServerConfig\n # Protocols this server speaks by ALPN (RFC 7301), most wanted first; a\n # client that offers some and none of these is refused. None when empty.\n def alpn : Array(String)\n # Protocols this server speaks by ALPN (RFC 7301), most wanted first; a\n # client that offers some and none of these is refused. None when empty.\n def alpn=(value : Array(String)) : Array(String)\n def chain : Array(Array(UInt8))\n def client_required : Bool\n def client_required=(value : Bool) : Bool\n # When set, the server asks for a client's certificate (a\n # CertificateRequest) and checks it as std/x509 checks a client's\n # (clientAuth) against these roots, then its CertificateVerify; a client\n # that sends none is refused (certificate_required) while\n # `client_required`. Not asked when nil.\n def client_roots : Array(Certificate) | ::Nil\n # When set, the server asks for a client's certificate (a\n # CertificateRequest) and checks it as std/x509 checks a client's\n # (clientAuth) against these roots, then its CertificateVerify; a client\n # that sends none is refused (certificate_required) while\n # `client_required`. Not asked when nil.\n def client_roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil\n def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsServerConfig | TlsError\n def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsServerConfig | TlsError\n def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsServerConfig | TlsError\n # `TlsIdentity.from_pem`'s identity, as a server's.\n def self.from_pem(certificates : String, private_key : String) : TlsServerConfig | TlsError\n def identity : TlsIdentity\n # The identity a client asking for *name*, taking signatures under\n # *schemes*, is shown: the first of `others` that names the host and\n # signs under a scheme the client takes, else `identity` if it does; nil\n # when none of them does.\n def identity_for(name : String | ::Nil, schemes : Array(Int32)) : TlsIdentity | ::Nil\n def initialize(identity : TlsIdentity)\n def leaf : Certificate\n # The time a client's certificate is checked at; now when nil.\n def now : Time | ::Nil\n # The time a client's certificate is checked at; now when nil.\n def now=(value : Time | ::Nil) : Time | ::Nil\n # More identities, each shown to a client that asks by server_name (RFC\n # 6066) for a host its certificate names; `identity` to any other.\n def others : Array(TlsIdentity)\n def schemes : Array(Int32)\nend\n\n# The versions a client may speak: TLS 1.3, or TLS 1.2 as well (RFC 5246),\n# for a server that never moved past it.\npub enum TlsVersion : Int32\n TLS12 = 0\n TLS13 = 1\nend\n\nimpl Error for TlsError\n def message : String\nend\n" + "sourceSha256": "6e485b1800591b3613e9bcb9b494f71ae0dbe9ecbeceafcc509475da662f98a2", + "surface": "module std/tls\n\n# The alerts of RFC 8446 section 6, by the code each is sent as.\npub struct TlsAlert\n def initialize\nend\n\n# A connection to a server, its handshake done.\npub class TlsClient < TlsConnection\n # A CertificateStatus's body (RFC 6066 section 8): status_type ocsp (1),\n # then the OCSPResponse's DER, which is not empty.\n def self.certificate_status(data : Array(UInt8)) : Array(UInt8) | TlsError\n # A connection to *host* on *port*, its handshake done and the server's\n # certificate checked for *host* - or, with a *session* that holds a\n # ticket for *host* and *port* under *config* (`TlsSession` says which\n # fit), resumed, and the tickets the server sends kept there.\n # *early_data* goes with the ClientHello (0-RTT, RFC 8446 section 2.3)\n # when the ticket taken allows that much, and after the handshake when it\n # did not, or the server refused it - unless the server then chose\n # another protocol by ALPN than the ticket's, which section 4.2.10 says\n # it must not be resent for; `early_data` says which. Early data can be\n # replayed by whoever sees it (section 8): send nothing early that is not\n # safe to run twice.\n def self.connect(host : String, port : Int32, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = -1, session : TlsSession | ::Nil = nil, early_data : String | Array(UInt8) | Nil = nil) : TlsClient | TlsError\n protected def early_bytes=(bytes : Array(UInt8) | ::Nil) : Array(UInt8) | ::Nil\n # What became of the early data given to `connect` or `over`.\n def early_data : TlsEarlyData\n def handshake : TlsError | ::Nil\n def initialize(socket : IyiSocket, host : String, config : TlsConfig, session : TlsSession | ::Nil = nil)\n # The handshake over a connected *socket*, for *host*, done within\n # *timeout_ms* (none when 0); *session* and *early_data* as for\n # `connect`.\n def self.over(socket : IyiSocket, host : String, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS, session : TlsSession | ::Nil = nil, early_data : String | Array(UInt8) | Nil = nil) : TlsClient | TlsError\n # *host* in the form RFC 6066 section 3 gives server_name: \"the\n # fully qualified DNS hostname\", ASCII, \"without a trailing dot\" - a\n # final dot dropped and a name in Unicode written as its A-labels by\n # IDNA, which is also the form `X509` matches a certificate's names in.\n # Before, `bücher.example.` went out as its UTF-8 bytes and its dot, and\n # a server that holds names to the RFC refused it (OpenSSL's s_server\n # with `-servername_fatal`: alert unrecognized_name). An ASCII name keeps\n # its case, as DNS ignores it. A name IDNA refuses, or nothing but a dot,\n # is an error before anything is sent.\n def self.server_name_for(host : String) : String | TlsError\n def server_side? : Bool\nend\n\n# What a connection is checked against and offers.\npub class TlsConfig\n # Protocols offered by ALPN (RFC 7301), most wanted first: `[\"h2\",\n # \"http/1.1\"]`. None offered when empty.\n def alpn : Array(String)\n # Protocols offered by ALPN (RFC 7301), most wanted first: `[\"h2\",\n # \"http/1.1\"]`. None offered when empty.\n def alpn=(value : Array(String)) : Array(String)\n # A new configuration holding what this one holds - the same roots and\n # identity objects, a copy of `alpn` - for a part to be changed in it\n # and not here: std/http's client makes the configuration an HTTPS\n # record's `ech` and protocols ask for so. It is another object, and\n # what was made under this one (`TlsMadeUnder`) is not carried under it.\n def dup : TlsConfig\n # An ECHConfigList (RFC 9849 section 4) - a DNS HTTPS record's `ech`\n # value, or a refusal's `TlsError#retry_configs` - to encrypt the\n # ClientHello under: the name connected to, ALPN and the ticket go in\n # the encrypted ClientHelloInner, and the ClientHelloOuter around it\n # names the config's public_name. The handshake holds only where the\n # server took it; one that did not is refused with ech_required (the\n # module's header). The first ECHConfig whose version, KEM and a suite\n # std/hpke has, and whose public_name is a host name, is taken; one list\n # holding none is refused before anything is sent, as is TLS 1.2 beside\n # it (`min_version`). A config is one server's: none when nil.\n def ech_configs : Array(UInt8) | ::Nil\n # An ECHConfigList (RFC 9849 section 4) - a DNS HTTPS record's `ech`\n # value, or a refusal's `TlsError#retry_configs` - to encrypt the\n # ClientHello under: the name connected to, ALPN and the ticket go in\n # the encrypted ClientHelloInner, and the ClientHelloOuter around it\n # names the config's public_name. The handshake holds only where the\n # server took it; one that did not is refused with ech_required (the\n # module's header). The first ECHConfig whose version, KEM and a suite\n # std/hpke has, and whose public_name is a host name, is taken; one list\n # holding none is refused before anything is sent, as is TLS 1.2 beside\n # it (`min_version`). A config is one server's: none when nil.\n def ech_configs=(value : Array(UInt8) | ::Nil) : Array(UInt8) | ::Nil\n # Whether a ClientHello without `ech_configs` carries a GREASE\n # encrypted_client_hello (RFC 9849 section 6.2): random bytes of an\n # encrypted one's shape, so that one that is real does not stand out.\n # On by default, as Chrome and Firefox send it.\n def ech_grease : Bool\n # Whether a ClientHello without `ech_configs` carries a GREASE\n # encrypted_client_hello (RFC 9849 section 6.2): random bytes of an\n # encrypted one's shape, so that one that is real does not stand out.\n # On by default, as Chrome and Firefox send it.\n def ech_grease=(value : Bool) : Bool\n # The certificate and key sent to a server that asks for one (mutual\n # TLS), when the request names its scheme; none sent when nil.\n def identity : TlsIdentity | ::Nil\n # The certificate and key sent to a server that asks for one (mutual\n # TLS), when the request names its scheme; none sent when nil.\n def identity=(value : TlsIdentity | ::Nil) : TlsIdentity | ::Nil\n def initialize\n # The oldest version spoken. TLS 1.3 by default, and then a server that\n # answers in 1.2 is refused as one that does not speak 1.3; `TLS12`\n # offers 1.2 beside it (see the module's header for what it requires).\n def min_version : TlsVersion\n # The oldest version spoken. TLS 1.3 by default, and then a server that\n # answers in 1.2 is refused as one that does not speak 1.3; `TLS12`\n # offers 1.2 beside it (see the module's header for what it requires).\n def min_version=(value : TlsVersion) : TlsVersion\n # The time certificates are checked at; now when nil.\n def now : Time | ::Nil\n # The time certificates are checked at; now when nil.\n def now=(value : Time | ::Nil) : Time | ::Nil\n # Whether X25519MLKEM768 is offered, its share sent beside X25519's (the\n # module's header has what it costs). On by default, as OpenSSL 3.5,\n # Chrome and Firefox have it; off, the ClientHello offers X25519 and\n # secp256r1 alone.\n def post_quantum : Bool\n # Whether X25519MLKEM768 is offered, its share sent beside X25519's (the\n # module's header has what it costs). On by default, as OpenSSL 3.5,\n # Chrome and Firefox have it; off, the ClientHello offers X25519 and\n # secp256r1 alone.\n def post_quantum=(value : Bool) : Bool\n # Whether a server's certificate must come with a stapled OCSP response\n # (RFC 6066's status_request, asked for by every ClientHello): with it,\n # a server that staples none is refused (bad_certificate_status_response),\n # as one whose certificate carries the TLS Feature extension's\n # status_request (RFC 7633, \"must staple\") always is. Off by default: a\n # staple that comes is checked either way (the module's header).\n def require_staple : Bool\n # Whether a server's certificate must come with a stapled OCSP response\n # (RFC 6066's status_request, asked for by every ClientHello): with it,\n # a server that staples none is refused (bad_certificate_status_response),\n # as one whose certificate carries the TLS Feature extension's\n # status_request (RFC 7633, \"must staple\") always is. Off by default: a\n # staple that comes is checked either way (the module's header).\n def require_staple=(value : Bool) : Bool\n # The roots a server's chain must reach; the system's when nil, read\n # once for the whole process and kept, since reading them is most of a\n # handshake's own cost.\n def roots : Array(Certificate) | ::Nil\n # The roots a server's chain must reach; the system's when nil, read\n # once for the whole process and kept, since reading them is most of a\n # handshake's own cost.\n def roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil\n def trusted_roots : Array(Certificate) | TlsError\nend\n\n# What either end of a connection does once it has keys: records read and\n# written under them, alerts, KeyUpdate, close_notify. `TlsClient` and\n# `TlsServer` are its two ends; each runs its own half of the handshake.\npub abstract class TlsConnection\n # Closes the socket at once: no close_notify, and nothing read past.\n def abort : Nil\n # The protocol chosen by ALPN, if one was.\n def alpn_protocol : String | ::Nil\n def cipher_suite : String\n # The connection ended: `close_write` - close_notify, then the sending\n # half shut - and what the peer still sends read and dropped, until its\n # close_notify or the socket's end, as long as it keeps coming\n # (`DRAIN_GAP_MS`), `DRAIN_MS` and `DRAIN_BYTES` at most; then the\n # socket closed. A socket closed with bytes unread in it is reset rather\n # than ended, and the reset takes from the\n # peer what it had received and not yet read: closed at once on a\n # request body it had not read, an HTTPS server lost its whole answer to\n # Python's http.client in each of 50 runs, and curl the end of it in\n # each of 50 (bench/std_http_exercise.sh, \"a close while the client\n # still sends\"). Nothing is waited for once the peer's close_notify has\n # been read: nothing comes after it. A socket already closed under the\n # connection leaves only the close: no alert, and nothing read. `abort`\n # is the close without any of this.\n def close : Nil\n # RFC 8446 section 6.1's end of this side's writing: close_notify, then\n # the socket's sending half shut (`IyiSocket#close_write`), so the peer\n # reads the alert and then the end of the stream. The reading half\n # stays: `read` goes on answering what the peer sends, until its own\n # close_notify (\"\") or the socket's end. `write` and `key_update` are\n # refused after it, and `close` or `abort` still gives the socket back.\n # TLS 1.2 has no such half: RFC 5246 section 7.2.1 has a peer answer a\n # close_notify with its own and close at once, discarding what it had\n # yet to write, so after this a 1.2 connection reads what was already\n # on its way, then the peer's close_notify. An error when the alert or\n # the shutdown could not be made, or when the socket was already closed\n # under the connection; the connection is still to be closed. A flight\n # still held (`hold`) is a handshake cut short, and is dropped, as\n # `fail` drops it: the close_notify goes alone.\n def close_write : TlsError | ::Nil\n # Whether the connection is over: closed, or its socket closed under\n # it - std/http's server closes the sockets of the connections it waits\n # on when it stops, a connection handed over by an upgrade among them -\n # after which a read or a write answers that it is closed rather than\n # reaching the socket, whose own read and write refuse a closed socket\n # by panicking.\n def closed? : Bool\n # Whether the handshake ran on the ClientHelloInner an encrypted\n # ClientHello carried (RFC 9849): the client's ECH taken - so the name,\n # the protocols and the ticket it asked with went encrypted, and the\n # server read them, not the ClientHelloOuter's.\n def ech_accepted? : Bool\n # *length* bytes bound to this connection, for *label* and *context*\n # (RFC 8446 section 7.5, RFC 5705's interface): the same at both ends and\n # nowhere else - a channel binding, or keys for another protocol riding\n # on this one. No context is the empty one, as TLS 1.3 has it; in TLS\n # 1.2 (RFC 5705 section 4, over the extended master secret) an empty\n # context is none, as OpenSSL's exporter takes it when told of none.\n def export_keying_material(label : String, context : Array(UInt8) = [] of UInt8, length : Int32 = 32) : Array(UInt8)\n # The alert the error names (its last parenthesised word), sent before\n # the socket closes, by either end (RFC 8446 section 6.2): under the keys\n # this side writes with, or in the clear before there are any. None for\n # an alert the peer sent, whose sentence names no alert of this side's.\n # no_renegotiation goes as the warning it always is (RFC 5246 section\n # 7.2.2); every other, fatal.\n # Then either end shuts its sending half, so the peer reads the alert\n # and then the end of the stream, and reads and drops what the peer\n # sent or goes on sending until it closes, `LINGER_BYTES` and\n # `LINGER_MS` at most. A close with bytes unread, or one that bytes\n # still on their way reach, is a reset, and Windows throws away what a\n # reset connection received and had not read, the alert with it: a\n # client refused for its certificate read \"reset\", not why, and the\n # server lost a client's ech_required in 18 and 15 of 20 runs\n # (bench/std_tls_ech_oracle.go). A client closed at once on a server's\n # 0.5-RTT records reset the connection in each of 100 refusals on Linux\n # and on darwin (bench/std_tls_server.py's untrusted-late), where the\n # server still read the alert, even read 300 ms after the reset; shut\n # and lingering, the alert and then the end of the stream in each of\n # 100. A peer that closes once it has the alert ends the linger then -\n # OpenSSL's s_server 0.2 ms after it, on darwin - and one that goes on\n # sending and never closes holds a refusal for the second\n # (untrusted-hold: 1000 to 1001 ms on Linux, 1001 to 1005 on darwin).\n protected def fail(error : TlsError) : TlsError\n def self.group_name(group : Int32) : String\n def initialize(socket : IyiSocket, peer : String, place : String)\n # Records one write key carries before the next (RFC 8446 section 5.5:\n # AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past\n # it, the next write moves to new keys first - or, in TLS 1.2, which has\n # no KeyUpdate, is refused, and the connection with it. Settable, for a\n # test.\n def key_limit : UInt64\n # Records one write key carries before the next (RFC 8446 section 5.5:\n # AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past\n # it, the next write moves to new keys first - or, in TLS 1.2, which has\n # no KeyUpdate, is refused, and the connection with it. Settable, for a\n # test.\n def key_limit=(value : UInt64) : UInt64\n # Moves this side's writes to new keys (section 4.6.3), asking the peer\n # to move its own when *request*: what `write` does by itself at\n # `key_limit`, for a program that wants it sooner. TLS 1.2 has no\n # KeyUpdate: there it is a `TlsError`, and the connection stays open.\n def key_update(request : Bool = false) : TlsError | ::Nil\n # The milliseconds left of the deadline `within` set, 0 once it has\n # passed; nil when there is none.\n protected def left_ms : Int32 | ::Nil\n # The key exchange group the handshake agreed on: \"X25519MLKEM768\",\n # \"X25519\", \"secp256r1\" or \"secp384r1\". One name for it, the one RFC\n # 8446 gives the field (NamedGroup); `key_exchange`, which the P-384 work\n # first called it, is KeyShareEntry's name for the share's bytes.\n def named_group : String\n # The OCSP response stapled to the server's certificate (RFC 6066, RFC\n # 8446 section 4.4.2.1) and the status it gives - for a client, checked\n # (std/ocsp); for a server, the one it sent. Nil when none was.\n def ocsp_response : OcspResponse | ::Nil\n def ocsp_status : OcspCertStatus | ::Nil\n # The chain the peer's certificate was checked through, leaf first: the\n # server's, for a client; the client's, for a server that asked for one.\n def peer_certificates : Array(Certificate)\n # Up to *max* bytes the peer sent: \"\" once it has closed with\n # close_notify. The socket's read timeout bounds the whole call.\n def read(max : Int32 = 16384) : String | TlsError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n # Whether the handshake resumed a session (see `TlsSession`).\n def resumed? : Bool\n # Whether this is the server's end.\n abstract def server_side? : Bool\n # The version negotiated: \"TLSv1.3\", or \"TLSv1.2\" where one end asked\n # for it and the other spoke nothing newer.\n def version : String\n # *block* under a deadline *ms* from now (none when 0), cleared after.\n protected def within(ms : Int32, &block : (-> T)) : T forall T\n # Sends *data*, in records of at most 2^14 bytes; answers its size.\n # Refused once `close_write` has sent this side's close_notify, and in\n # TLS 1.2 once the peer's has been read (see `read_now`).\n def write(data : String | Array(UInt8)) : Int32 | TlsError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What became of the early data a client was given (`TlsClient.connect`'s\n# *early_data*).\npub enum TlsEarlyData : Int32\n Unused = 0\n Accepted = 1\n Resent = 2\n Late = 3\n Withheld = 4\nend\n\n# One ECHConfig (RFC 9849 section 4), of version 0xfe0d, as read from an\n# ECHConfigList: what a client encrypts its ClientHello under, and what a\n# server's `TlsEchKey` publishes.\npub class TlsEchConfig\n def config_id : Int32\n # Its extensions (section 4.2), each its type and data.\n def extensions : Array(::Tuple(Int32, Array(UInt8)))\n # The suite a client seals under: the first of `suites` std/hpke has (the\n # export-only AEAD seals nothing), under the config's KEM - or nil for a\n # config this client passes over: a KEM std/hpke does not have, a\n # public_name that is not a host name (section 6.1.7), an extension\n # twice, or a mandatory one - its type's high bit set - which this does\n # not know (section 4.2; none is defined yet).\n def hpke_suite : HpkeSuite | ::Nil\n def initialize(raw : Array(UInt8), config_id : Int32, kem_id : Int32, public_key : Array(UInt8), suites : Array(::Tuple(Int32, Int32)), maximum_name_length : Int32, public_name : String, extensions : Array(::Tuple(Int32, Array(UInt8))))\n def kem_id : Int32\n def maximum_name_length : Int32\n # The ECHConfigs of version 0xfe0d an ECHConfigList holds, in its order -\n # one of another version passed over by its length, as section 4 says -\n # or a decode_error naming what does not read: a list cut short or with\n # bytes after it, an empty one, or a config of this version whose fields\n # break their bounds.\n def self.parse_list(list : Array(UInt8)) : Array(TlsEchConfig) | TlsError\n def public_key : Array(UInt8)\n def public_name : String\n # Whether *name* is a public_name a client takes (section 6.1.7): LDH\n # labels (RFC 5890 section 2.3.1) of 1 to 63 bytes, no hyphen at either\n # end of one, joined by dots, 255 bytes at most - and its last label\n # neither all digits nor \"0x\" and hexadecimal digits, which a URL parser\n # reads as an IPv4 address.\n def self.public_name?(name : String) : Bool\n # The whole ECHConfig - version, length and contents - as it came: what\n # the HPKE info is made of (section 6.1), and a list of them.\n def raw : Array(UInt8)\n # `parse_list`'s answer, its refusal a sentence.\n def self.read_list(list : Array(UInt8)) : Array(TlsEchConfig) | String\n # The HPKE KDF and AEAD a client may seal under, by their code points.\n def suites : Array(::Tuple(Int32, Int32))\nend\n\n# An ECH key a server holds (RFC 9849 section 7.1): an ECHConfig and the\n# HPKE private key for the public key in it. `TlsServerConfig#ech_keys`\n# holds several: the current one and the ones before it that clients may\n# still hold from DNS, each with a config_id of its own (section 4.1), so\n# that a ClientHello names the one to open it with.\npub class TlsEchKey\n def config : TlsEchConfig\n # The ECHConfigList of *keys*' configs, in their order: what a DNS HTTPS\n # record's `ech` carries (RFC 9848), and a client's `ech_configs`.\n def self.config_list(keys : Array(TlsEchKey)) : Array(UInt8)\n # The key a server kept as its `config.raw` and `private_key.bytes`: one\n # ECHConfig, not a list, of version 0xfe0d and a KEM std/hpke has, that\n # a client may use; and the KEM's private key (RFC 9180 section 7.1.2)\n # for the public key in it - or why not.\n def self.from_bytes(config : Array(UInt8), private_key : Array(UInt8)) : TlsEchKey | TlsError\n # A new key and its ECHConfig: *public_name* the name the server\n # answers for in the clear and has a certificate for (section 6.1.7\n # holds it to a host name); *config_id* random unless given - one the\n # server's other keys do not have (section 4.1); the KEM's key pair from\n # `Random::Secure`; the KDF and AEAD pairs a client may choose among, in\n # the server's order - HKDF-SHA256 with AES-128-GCM, then with\n # ChaCha20-Poly1305, unless given; and *maximum_name_length*, the\n # longest name it serves, which clients pad the name they ask for to\n # (section 6.1.3; 0 when not known). Its `config.raw` and\n # `private_key.bytes` are what `from_bytes` reads back.\n def self.generate(public_name : String, config_id : Int32 | ::Nil = nil, kem : HpkeKem = HpkeKem::X25519Sha256, suites : Array(::Tuple(HpkeKdf, HpkeAead)) = [{HpkeKdf::HkdfSha256, HpkeAead::Aes128Gcm}, {HpkeKdf::HkdfSha256, HpkeAead::ChaCha20Poly1305}], maximum_name_length : Int32 = 0) : TlsEchKey | TlsError\n def initialize(config : TlsEchConfig, private_key : HpkePrivateKey)\n def private_key : HpkePrivateKey\n # Whether the config goes in retry_configs, to a client whose encrypted\n # ClientHello this server turns down (section 7.1): true unless set; a\n # key kept only to open what holders of its old config still send is\n # set false, so that no client is told to retry with it.\n def retry : Bool\n # Whether the config goes in retry_configs, to a client whose encrypted\n # ClientHello this server turns down (section 7.1): true unless set; a\n # key kept only to open what holders of its old config still send is\n # set false, so that no client is told to retry with it.\n def retry=(value : Bool) : Bool\nend\n\n# What a connection answers when it cannot be made or kept, or a read that\n# came back with nothing yet.\npub struct TlsError\n # The alert this refusal sends the peer (`TlsAlert`), or nil: none for a\n # failure of the socket, or for an alert the peer sent.\n def alert : UInt8 | ::Nil\n def cancelled? : Bool\n # *message*, the alert's name after it when there is one: the sentence\n # shows the alert, and the alert is the field, not the sentence.\n def initialize(message : String, alert : UInt8 | ::Nil = nil, timed_out : Bool = false, cancelled : Bool = false, retry_configs : Array(UInt8) | ::Nil = nil)\n # The ECHConfigList a server that turned down this client's encrypted\n # ClientHello sent to retry with (RFC 9849 section 6.1.6), once its\n # certificate was checked for the config's public_name: on an\n # ech_required refusal, and nil on any other - and on an ech_required\n # refusal whose server sent none, which turned ECH off for it.\n def retry_configs : Array(UInt8) | ::Nil\n # A read that waited past its timeout, or that was cancelled: nothing was\n # lost and the connection is open - read again. Any other error ended it.\n def timed_out? : Bool\n def to_s : String\nend\n\n# A `TlsServerConfig` frozen (`TlsServerConfig#frozen`): what a server\n# whose connections run on several threads at once - std/http's\n# `Server.serve(tls:)` - reads on every worker. It is `Share`\n# (SPEC.md III.4.4), trusted rather than derived, and this is why it may\n# be. It holds a copy of the config it was made from (`dup`), which\n# nothing else holds and nothing writes after the constructor made it:\n# no method here assigns it or calls a setter on it, and it leaves only\n# for the `TlsServer` a handshake runs (`server`), which reads it and\n# has no getter for it. What the copy holds is `Share` itself or read\n# alone: its identities are `Share`, its ECH keys copied with it, its\n# arrays its own; the ticket keys and the replay guard it shares with\n# the config it was made from are `Share` by their fields, a `Mutex`\n# each, so a ticket one worker seals another opens and a replay one\n# refuses all refuse. Its identity source is a `SharedProc`, whose\n# block the compiler checked where it was written. The readers below\n# answer copies, a `List` or a new array, never the copy's own. What\n# changes while a server runs: an OCSP staple, through `staple`; the\n# ticket key, as it turns over; and whatever the identity source\n# answers, a certificate renewed (std/autocert). The rest - the\n# identities, the protocols, the ECH keys - is the server's for its\n# life: another set is another frozen config, served by another\n# `Server.serve`.\npub class TlsFrozenServerConfig\n def alpn : List(String)\n # `TlsServerConfig#ech_config_list`'s, of the keys frozen with it.\n def ech_config_list : Array(UInt8) | ::Nil\n def identity : TlsIdentity\n # *config* copied, its `identity_source` replaced by *source*. A config\n # with an `identity_source` and no *source* is refused by panicking, as\n # `TlsServerConfig#frozen` says.\n def initialize(config : TlsServerConfig, source : SharedProc(::Tuple(String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil), TlsIdentity | TlsError | Nil) | ::Nil)\n def others : List(TlsIdentity)\n def replay_guard : TlsReplayGuard\n # The server's half of a connection on *socket* under this config, for\n # `TlsServer.over` to run the handshake of.\n def server(socket : IyiSocket) : TlsServer\n # *der* stapled for *identity*, as `TlsServerConfig#staple` does, from\n # any thread: the handshakes after it, on every worker, staple it.\n def staple(der : Array(UInt8), identity : TlsIdentity = @settings.identity) : TlsError | ::Nil\n def ticket_keys : TlsTicketKeys\nend\n\n# One ticket a client holds: the server's bytes, and the PSK, suite,\n# arrival (monotonic ms), lifetime, age_add, host, port, configuration,\n# protocol and certificates of the connection it came on, when the full\n# handshake that checked those certificates was made (monotonic ms; a\n# resumption's tickets carry the first one's), and the most early data it\n# allows (0 for none).\npub class TlsHeldTicket\n def age_add : Int64\n def alpn : String | ::Nil\n def authenticated_ms : Int64\n def host : String\n def initialize(ticket : Array(UInt8), psk : Array(UInt8), suite : Int32, received_ms : Int64, lifetime_ms : Int64, age_add : Int64, host : String, port : Int32, under : TlsMadeUnder, alpn : String | ::Nil, peer_certificates : Array(Certificate), authenticated_ms : Int64, max_early_data : Int64 = 0_i64)\n def lifetime_ms : Int64\n def max_early_data : Int64\n def peer_certificates : Array(Certificate)\n def port : Int32\n def psk : Array(UInt8)\n def received_ms : Int64\n def suite : Int32\n def ticket : Array(UInt8)\n def under : TlsMadeUnder\nend\n\n# A certificate chain, leaf first, and the key that signs for it -\n# Ed25519, ECDSA on P-256 or P-384 (std/p256, std/p384) or RSA (std/rsa),\n# each in constant time: what a server presents, and what a client\n# presents when a server asks.\n#\n# `Share` (SPEC.md III.4.4), trusted rather than derived: a server's\n# workers sign with one identity at once, and an `identity_source` that\n# runs on every worker answers identities it keeps. What the structural\n# rule cannot see is that nothing writes one after it is made: no method\n# here assigns a field, and none of std/x509's `Certificate`, std/p256's,\n# std/p384's and std/rsa's private keys does - a signature is computed\n# into new values, nothing is cached on the key - so what fails the rule\n# is only that the chain and the certificate are held in `Array`s. The\n# constructor copies the arrays it is given, so a caller that keeps them\n# writes its own. The one part that changes while a server runs, the OCSP\n# response it staples, is behind a `Mutex` (`staple`), which every\n# worker reads through. The edge is the one `List` closes by never\n# lending its array and this cannot: `chain`, `schemes` and the leaf's\n# fields are lent as arrays, read by every method of std, and a program\n# that writes into one writes what other threads read.\npub class TlsIdentity\n # The ClientCertificateType a TLS 1.2 CertificateRequest must list for\n # this key: rsa_sign (1), or ecdsa_sign (64) for P-256, P-384 and, by RFC\n # 8422 section 5.5, Ed25519.\n def certificate_type : UInt8\n # The chain as sent, leaf first, each certificate's DER.\n def chain : Array(Array(UInt8))\n # *chain*'s certificates, leaf first, and the 32-byte Ed25519 *seed*\n # that is the leaf's private key.\n def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsIdentity | TlsError\n # *chain*'s certificates, leaf first, and the P-256 *key* that is the\n # leaf's.\n def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsIdentity | TlsError\n # *chain*'s certificates, leaf first, and the P-384 *key* that is the\n # leaf's.\n def self.from_der(chain : Array(Array(UInt8)), key : P384PrivateKey) : TlsIdentity | TlsError\n # *chain*'s certificates, leaf first, and the RSA *key* that is the\n # leaf's (2048 bits or more, std/rsa's floor for signing).\n def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsIdentity | TlsError\n # The chain in *certificates*' CERTIFICATE blocks, leaf first, and the\n # leaf's private key in *private_key*: a PRIVATE KEY block (PKCS #8, an\n # Ed25519 key as RFC 8410 section 7 writes it, a P-256 or P-384 one as\n # RFC 5915 does, or an RSA one), an EC PRIVATE KEY block (SEC 1) or an\n # RSA PRIVATE KEY block (PKCS #1) - or why they are not a certificate\n # and key.\n def self.from_pem(certificates : String, private_key : String) : TlsIdentity | TlsError\n def initialize(chain : Array(Array(UInt8)), leaf : Certificate, schemes : Array(Int32), seed : Array(UInt8) | ::Nil, p256 : P256PrivateKey | ::Nil, p384 : P384PrivateKey | ::Nil, rsa : RsaPrivateKey | ::Nil)\n def leaf : Certificate\n # The OCSP response kept for this chain (`staple`), its DER; nil when\n # none is.\n def ocsp_staple : Array(UInt8) | ::Nil\n # Whether the key is RSA's: a TLS 1.2 server's ECDHE_RSA suites, and not\n # ECDHE_ECDSA's.\n def rsa? : Bool\n # The scheme a TLS 1.2 ServerKeyExchange is signed under for a client\n # that takes *offered*: `scheme_for`'s - for an RSA key, RSA-PSS by the\n # client's order - and for an RSA key whose client takes no RSA-PSS,\n # PKCS #1 v1.5 by its order, which 1.2 permits and which the clients that\n # never moved past it may take alone; nil when the key makes none.\n def scheme12_for(offered : Array(Int32)) : Int32 | ::Nil\n # The scheme to sign under for a peer that takes *offered* (its\n # signature_algorithms, in its order): the first of them this key makes,\n # or nil when it makes none.\n def scheme_for(offered : Array(Int32)) : Int32 | ::Nil\n # A scheme by the name RFC 8446 gives it.\n def self.scheme_name(scheme : Int32) : String\n # Schemes by name, for a sentence: \"a, b or c\".\n def self.scheme_names(schemes : Array(Int32)) : String\n # The schemes CertificateVerify can be signed under, by the key's kind:\n # ed25519 (0x0807), ecdsa_secp256r1_sha256 (0x0403),\n # ecdsa_secp384r1_sha384 (0x0503), or for an RSA key rsa_pss_rsae_sha256,\n # -384 and -512 (0x0804 to 0x0806). PKCS #1 v1.5 is never one: RFC 8446\n # section 4.2.3 keeps it out of CertificateVerify.\n def schemes : Array(Int32)\n # The leaf key's signature of *content* under *scheme*, one of\n # `schemes` - or for an RSA key in TLS 1.2, PKCS #1 v1.5's: what\n # CertificateVerify and a ServerKeyExchange carry.\n def sign(scheme : Int32, content : Array(UInt8)) : Array(UInt8)\n # *der* kept as this chain's OCSP response, to be stapled for a client\n # that asks: refused unless it is an OCSPResponse std/ocsp reads, with a\n # SingleResponse for the leaf - its serial number and its issuer name's\n # hash. Its signature and times are the client's to check; this server\n # only leaves one past its nextUpdate unsent. A later call replaces it,\n # from any thread, and a handshake on any worker staples the one held\n # when it asks.\n def staple(der : Array(UInt8)) : TlsError | ::Nil\n # The response to staple at *now*, with what it says; nil when there is\n # none, or it is past its nextUpdate.\n def staple_at(now : Time) : ::Tuple(Array(UInt8), OcspResponse, OcspSingleResponse) | ::Nil\nend\n\n# The key schedule of one connection (RFC 8446 section 7.1), over the\n# cipher suite's hash: SHA-384 for TLS_AES_256_GCM_SHA384, SHA-256 for the\n# other two.\npub class TlsKeySchedule\n # A PSK binder (section 4.2.11.2): a Finished keyed by the resumption's\n # binder key, over *transcript* - the messages before the ClientHello and\n # the ClientHello up to its binders.\n def binder(psk : Array(UInt8), transcript : Array(UInt8)) : Array(UInt8)\n # Derive-Secret over the messages so far.\n def derive_secret(secret : Array(UInt8), label : String, transcript : Array(UInt8)) : Array(UInt8)\n # The early secret of a resumption, from its PSK.\n def early_secret(psk : Array(UInt8)) : Array(UInt8)\n # The early secret with no PSK, and the \"derived\" salt for the next stage.\n def early_secret : Array(UInt8)\n # The client's early traffic secret (section 7.1), over the ClientHello\n # alone: what 0-RTT data and EndOfEarlyData go under.\n def early_traffic(psk : Array(UInt8), hello : Array(UInt8)) : Array(UInt8)\n # HKDF-Expand-Label: the label prefixed \"tls13 \", and the context, in an\n # HkdfLabel.\n def expand_label(secret : Array(UInt8), label : String, context : Array(UInt8), length : Int32) : Array(UInt8)\n def extract(salt : Array(UInt8), ikm : Array(UInt8)) : Array(UInt8)\n # A Finished message's verify_data under *secret* (section 4.4.4).\n def finished(secret : Array(UInt8), transcript : Array(UInt8)) : Array(UInt8)\n # The handshake secret over the key exchange's *shared* secret, and over\n # *psk* in a resumption.\n def handshake_secret(shared : Array(UInt8), psk : Array(UInt8) | ::Nil = nil) : Array(UInt8)\n def hash(data : Array(UInt8)) : Array(UInt8)\n def hash_size : Int32\n def hmac(key : Array(UInt8), data : Array(UInt8)) : Array(UInt8)\n def initialize(sha384 : Bool)\n def master_secret(handshake_secret : Array(UInt8)) : Array(UInt8)\n # The next generation of a traffic secret (section 7.2).\n def next_secret(secret : Array(UInt8)) : Array(UInt8)\n # TLS 1.2's PRF (RFC 5246 section 5): P_hash over this schedule's hash -\n # SHA-256, or SHA-384 for a suite that names it - keyed by *secret*, over\n # *label* and *seed*, *length* bytes of it.\n def prf(secret : Array(UInt8), label : String, seed : Array(UInt8), length : Int32) : Array(UInt8)\n # The PSK a NewSessionTicket's *nonce* makes of the resumption master\n # secret (section 4.6.1): one ticket's, and no other's.\n def resumption_psk(secret : Array(UInt8), nonce : Array(UInt8)) : Array(UInt8)\n def zeros : Array(UInt8)\nend\n\n# The `TlsConfig` a client connection was made under, and the parts of it\n# that decide whom the connection trusts, what it presented and whether\n# it named its server in the clear: the roots, the identity, the time\n# certificates were checked at, the oldest version spoken, whether an\n# OCSP staple was required, and the ECHConfigList it was encrypted under.\n# Whatever was made under it - a `TlsSession`'s ticket, std/http's kept\n# connection - is used again only under a configuration that is the same\n# object holding the same ones, so what one trust store, identity or\n# staple rule checked is never carried under another: set by assigning a\n# new configuration, or by assigning `roots`, `identity`, `now`,\n# `min_version`, `require_staple` or `ech_configs` on it in place. Objects\n# are told apart by `object_id`, and the references are held, so an\n# address freed and handed to a new configuration is never taken for the\n# old one. Certificates added to the same roots array in place are not\n# seen: such an array is still the same object. `ech_configs` is held to\n# its bytes, kept as they were: a connection made in the clear is not\n# handed a request made under a configuration that asks for ECH, nor its\n# ticket offered inside one, and a ticket that came inside one is not\n# offered in a ClientHello that names the server in the clear, where it\n# would tie the two connections together for anyone watching (RFC 9849\n# section 6.1.8 keeps ECH state to the source it came from). One\n# definition serves std/tls and std/http, so the two cannot come to\n# compare different parts.\npub class TlsMadeUnder\n def initialize(config : TlsConfig)\n def same?(config : TlsConfig) : Bool\nend\n\n# One direction's record protection (section 5.2): the AEAD keyed from a\n# traffic secret, and the record sequence number its nonces are made of -\n# or TLS 1.2's, keyed from its key block.\npub class TlsRecordCipher\n # TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256.\n def initialize(suite : Int32, schedule : TlsKeySchedule, secret : Array(UInt8))\n # TLS 1.2's (RFC 5246 section 6.2.3.3) for one of its AEAD suites, *key*\n # and *iv* from the key block: AES-GCM's four-byte salt, to which each\n # record's eight-byte explicit nonce is added (RFC 5288 section 3), or\n # ChaCha20-Poly1305's twelve bytes, XORed with the sequence number as TLS\n # 1.3 does it (RFC 7905 section 2).\n def initialize(suite : Int32, key : Array(UInt8), iv : Array(UInt8))\n def key_iv : Array(UInt8)\n # The type and content of a protected record whose 5-byte header is\n # *header* and body *body*.\n def open(header : Array(UInt8), body : Array(UInt8)) : ::Tuple(UInt8, Array(UInt8)) | TlsError\n # The whole record that carries *content* of *type*, protected.\n def seal(type : UInt8, content : Array(UInt8)) : Array(UInt8)\n def sequence : UInt64\nend\n\n# What keeps a server from taking the same early data twice (RFC 8446\n# sections 8.2 and 8.3). A ClientHello's early data is taken only if it is\n# fresh - the ticket age the client gives is within `WINDOW_MS` of the\n# time this server counts since it issued the ticket - and only once:\n# each ClientHello whose early data is taken is recorded by its PSK\n# binder, which nobody without the ticket's PSK can make for another\n# ClientHello, and a ClientHello whose binder is recorded has its early\n# data refused (its handshake goes on, the data then sent after it). A\n# replay that is still fresh comes within two windows of its original, so\n# a binder is held that long and then dropped; with `capacity` held, early\n# data is refused until some are - the guard fails closed. A refusal costs\n# the client a round trip, never its request.\n#\n# What it does not do. It holds in this process, as the ticket keys do, so\n# a ticket - and the early data under it - is taken by no other server\n# and by no later run of this one; servers that shared ticket keys would\n# need to share this too, and neither is offered. It cannot stop a client\n# from sending the same request again after a connection whose early data\n# was taken broke before the answer came (appendix E.5), so what comes\n# early must be safe to run twice; and taking early data at all lets\n# whoever recorded the ClientHello replay it within the window to a server\n# that has not seen it - none, here.\n#\n# The binders are behind a `Mutex`, as the ticket keys are, so every\n# worker of a `TlsFrozenServerConfig`'s server records into one guard and\n# a replay sent to another worker is refused there too.\npub class TlsReplayGuard\n # Whether early data may be taken, at *now_ms* (monotonic), for a\n # ClientHello whose PSK binder is *binder*: not one held already, and not\n # past `capacity`. It is recorded when it may; a task cancelled while it\n # waits for the guard is refused.\n def admit(binder : Array(UInt8), now_ms : Int64) : Bool\n def capacity : Int32\n # Whether a ticket age the client gives, *client_ms*, and this server's\n # count since it issued the ticket, *server_ms*, are within the window.\n def self.fresh?(client_ms : Int64, server_ms : Int64) : Bool\n def initialize(capacity : Int32 = 65536)\n # How many binders are held.\n def size : Int32\nend\n\n# A connection from a client, its handshake done: TLS 1.3 as a server -\n# or 1.2, when its config's `min_version` asks and the client speaks\n# nothing newer - its certificate's key (Ed25519, P-256, P-384 or RSA)\n# signing for it.\npub class TlsServer < TlsConnection\n # Whether the client's early data was taken (0-RTT): `read_early` reads\n # it, and `read` never does.\n def early_data? : Bool\n # The rest of a handshake whose early data was taken: the early data not\n # yet read, kept for `read_early`, then EndOfEarlyData and the client's\n # Finished. Nil once the handshake is done, at once when nothing was\n # early. `read` calls it first; a server that answers early data before\n # the client's Finished calls it before it closes.\n def finish : TlsError | ::Nil\n # The handshake, or why it failed - the client sent that alert first.\n def handshake : TlsError | ::Nil\n # The certificate and key this connection was shown under: the config's\n # `identity`, or one of its `others` the client asked for by name.\n def identity : TlsIdentity\n def initialize(socket : IyiSocket, config : TlsServerConfig, shared_source : SharedProc(::Tuple(String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil), TlsIdentity | TlsError | Nil) | ::Nil = nil)\n # The ClientHelloOuter's server_name when its encrypted ClientHelloInner\n # was taken: the public_name the client named in the clear, where\n # `server_name` is the name it asked for inside. Nil otherwise.\n def outer_server_name : String | ::Nil\n # The handshake with the client on *socket*. A handshake that fails\n # sends the client the alert its error names, and the socket is closed.\n # Where the config allows early data (`max_early_data`) and *early_data*\n # is true, a ClientHello's early data may be taken: then the handshake\n # answers once this side's Finished is sent, `read_early` reads the\n # early data, a write goes at once (0.5-RTT), and `finish` - which\n # `read` calls - reads the client's Finished. Under a frozen config\n # (`TlsFrozenServerConfig`) the handshake may run on any thread, as many\n # at once as there are.\n def self.over(socket : IyiSocket, config : TlsServerConfig | TlsFrozenServerConfig, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS, early_data : Bool = true) : TlsServer | TlsError\n # Up to *max* bytes the client sent after the handshake - never its early\n # data, which `read_early` reads - the handshake finished first.\n def read(max : Int32 = 16384) : String | TlsError\n # Up to *max* bytes of the early data the client sent with its\n # ClientHello, \"\" once it has all been read, or when none was taken.\n # Early data may be a replay (RFC 8446 section 8; `TlsReplayGuard` says\n # what this server refuses and what it cannot), which is why it is read\n # here and never by `read`: a program can tell it from what came after\n # the handshake, and act on it only where twice is safe.\n def read_early(max : Int32 = 16384) : String | TlsError\n # The host the client named (server_name, RFC 6066), if it named one.\n def server_name : String | ::Nil\n def server_side? : Bool\n protected def takes_early=(takes : Bool) : Bool\nend\n\n# What a server presents and asks: its identity, the protocols it speaks\n# by ALPN, the oldest version it speaks, and - for mutual TLS - the roots\n# a client's certificate must chain to.\n#\n# A config is written as a server runs - its properties are assigned, an\n# identity is added - and its `identity_source` is a `Proc`, which may\n# have captured anything; so it is not `Share` (SPEC.md III.4.4), and a\n# server whose connections run on several threads at once (std/http's\n# `Server.serve`) takes `frozen` instead: a copy no property of\n# which changes, that every worker reads at once. What must change while\n# such a server runs is shared rather than copied, and synchronised: the\n# ticket keys and the replay guard, whose state is behind a `Mutex`, so\n# a ticket one worker seals another opens; and each identity's OCSP\n# staple (`staple`), which a fresh response replaces from any thread.\npub class TlsServerConfig\n # *identity* shown to a client asking for a host its certificate names.\n def add_identity(identity : TlsIdentity) : TlsServerConfig\n # Protocols this server speaks by ALPN (RFC 7301), most wanted first; a\n # client that offers some and none of these is refused. None when empty.\n def alpn : Array(String)\n # Protocols this server speaks by ALPN (RFC 7301), most wanted first; a\n # client that offers some and none of these is refused. None when empty.\n def alpn=(value : Array(String)) : Array(String)\n def chain : Array(Array(UInt8))\n def client_required : Bool\n def client_required=(value : Bool) : Bool\n # When set, the server asks for a client's certificate (a\n # CertificateRequest) and checks it as std/x509 checks a client's\n # (clientAuth) against these roots, then its CertificateVerify; a client\n # that sends none is refused (certificate_required; in TLS 1.2,\n # handshake_failure) while `client_required`. Not asked when nil.\n def client_roots : Array(Certificate) | ::Nil\n # When set, the server asks for a client's certificate (a\n # CertificateRequest) and checks it as std/x509 checks a client's\n # (clientAuth) against these roots, then its CertificateVerify; a client\n # that sends none is refused (certificate_required; in TLS 1.2,\n # handshake_failure) while `client_required`. Not asked when nil.\n def client_roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil\n # A copy, to be written apart from this one: the arrays copied - the\n # protocols, roots, identities, groups, and each ECH key, whose `retry`\n # is its own - and the ticket keys and the replay guard shared, so that\n # a ticket either seals the other opens, and early data either took\n # the other refuses.\n def dup : TlsServerConfig\n # `max_early_data`, held to 0 .. `MAX_EARLY_DATA`.\n def early_limit : Int64\n # The ECHConfigList of the `ech_keys` whose `retry` is set, in their\n # order: what this server publishes for clients (a DNS HTTPS record's\n # `ech`) and sends as retry_configs; nil when there are none.\n def ech_config_list : Array(UInt8) | ::Nil\n # The keys an encrypted ClientHello (RFC 9849) is opened with, as a\n # client-facing server in shared mode, which is its own backend: one\n # whose ECHClientHello names a key's config_id and suite and opens under\n # it is answered on its ClientHelloInner; one none opens, on the\n # ClientHelloOuter, for the public_name, with `ech_config_list` as\n # retry_configs. Several for rotation (`TlsEchKey`). None when empty: an\n # encrypted_client_hello is then passed over, unread past its type.\n def ech_keys : Array(TlsEchKey)\n # The keys an encrypted ClientHello (RFC 9849) is opened with, as a\n # client-facing server in shared mode, which is its own backend: one\n # whose ECHClientHello names a key's config_id and suite and opens under\n # it is answered on its ClientHelloInner; one none opens, on the\n # ClientHelloOuter, for the public_name, with `ech_config_list` as\n # retry_configs. Several for rotation (`TlsEchKey`). None when empty: an\n # encrypted_client_hello is then passed over, unread past its type.\n def ech_keys=(value : Array(TlsEchKey)) : Array(TlsEchKey)\n def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsServerConfig | TlsError\n def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsServerConfig | TlsError\n def self.from_der(chain : Array(Array(UInt8)), key : P384PrivateKey) : TlsServerConfig | TlsError\n def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsServerConfig | TlsError\n # `TlsIdentity.from_pem`'s identity, as a server's.\n def self.from_pem(certificates : String, private_key : String) : TlsServerConfig | TlsError\n # This config as it is now, for a server whose connections run on\n # several threads at once (`TlsFrozenServerConfig`). One with an\n # `identity_source` is refused by panicking: a `Proc` may have captured\n # anything, and nothing checked it - the source goes to the block form\n # below instead, where the compiler checks it.\n def frozen : TlsFrozenServerConfig\n # The same, with the block as its `identity_source`, asked with the\n # host, the protocols and the milliseconds left as that is. The def is\n # `@[Share]`, so the block is checked where it is written as a parallel\n # task's is (SPEC.md III.4.4, III.4.13): every value it captures must be\n # `Share` - identities, which are, a `Mutex` around a table of them, a\n # `Channel` to the thread that keeps them - every captured local\n # assigned once, and the constants and class variables it names\n # `Share`. It is called on whichever worker the handshake runs on,\n # several at once. `identity_source`, if set, is not asked.\n def frozen(&source : (String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil -> TlsIdentity | TlsError | Nil)) : TlsFrozenServerConfig\n # `groups`' code points, those this module has, once each.\n def group_codes : Array(Int32)\n # The key exchange groups a TLS 1.3 handshake takes, by the names\n # `named_group` gives them, most wanted first: X25519MLKEM768, X25519,\n # secp256r1 and secp384r1 unless set. The first of them a client lists is\n # taken where it sent a share in it, or is X25519MLKEM768 - for which a\n # client that sent no share is asked by HelloRetryRequest - and the next\n # otherwise; a client that sent a share in none of them it lists is asked\n # for the first it lists. A name this module has no group of is passed\n # over. TLS 1.2's groups are its own, as the module's header says.\n def groups : Array(String)\n # The key exchange groups a TLS 1.3 handshake takes, by the names\n # `named_group` gives them, most wanted first: X25519MLKEM768, X25519,\n # secp256r1 and secp384r1 unless set. The first of them a client lists is\n # taken where it sent a share in it, or is X25519MLKEM768 - for which a\n # client that sent no share is asked by HelloRetryRequest - and the next\n # otherwise; a client that sent a share in none of them it lists is asked\n # for the first it lists. A name this module has no group of is passed\n # over. TLS 1.2's groups are its own, as the module's header says.\n def groups=(value : Array(String)) : Array(String)\n def identity : TlsIdentity\n # The identity at *index*, as `identity_index` counts; nil past them.\n def identity_at(index : Int32) : TlsIdentity | ::Nil\n # The identity a client asking for *name*, taking signatures under\n # *schemes*, is shown: the first of `others` that names the host and\n # signs under a scheme the client takes, else `identity` if it does; nil\n # when none of them does.\n def identity_for(name : String | ::Nil, schemes : Array(Int32)) : TlsIdentity | ::Nil\n # The place of *identity* among this server's, which a ticket carries:\n # 0 for `identity`, then `others` in order.\n def identity_index(identity : TlsIdentity) : Int32\n # Asked first, in TLS 1.3 and 1.2, for the identity a client is shown:\n # given the host the client named by server_name (nil when it named\n # none), the protocols its ALPN offered (nil when it offered none), and\n # the milliseconds left of the handshake's deadline (0 once it has\n # passed, nil when the handshake has none). An identity it answers is\n # shown where one of `others` would have been - so a scheme of its key\n # the client takes, and in 1.2 a suite and a curve for it, or the client\n # is refused as for `identity`; nil leaves the choice to `others` and\n # `identity` as without it; an error refuses the handshake, the client\n # sent the alert that error names. Asked of each ClientHello that lists\n # signature_algorithms - a second one after a HelloRetryRequest too, and\n # one offering a ticket, which may yet come to a full handshake; a\n # resumption shows no certificate, and its ticket counts the session as\n # `identity`'s (`identity_index`). The handshake waits for it, and its\n # deadline cuts only the reads after it: a source that may wait - for a\n # CA, say - keeps its waits within the time it is given, or holds the\n # connection past the deadline. Not asked when nil.\n def identity_source : Proc(String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil, TlsIdentity | TlsError | Nil) | ::Nil\n # Asked first, in TLS 1.3 and 1.2, for the identity a client is shown:\n # given the host the client named by server_name (nil when it named\n # none), the protocols its ALPN offered (nil when it offered none), and\n # the milliseconds left of the handshake's deadline (0 once it has\n # passed, nil when the handshake has none). An identity it answers is\n # shown where one of `others` would have been - so a scheme of its key\n # the client takes, and in 1.2 a suite and a curve for it, or the client\n # is refused as for `identity`; nil leaves the choice to `others` and\n # `identity` as without it; an error refuses the handshake, the client\n # sent the alert that error names. Asked of each ClientHello that lists\n # signature_algorithms - a second one after a HelloRetryRequest too, and\n # one offering a ticket, which may yet come to a full handshake; a\n # resumption shows no certificate, and its ticket counts the session as\n # `identity`'s (`identity_index`). The handshake waits for it, and its\n # deadline cuts only the reads after it: a source that may wait - for a\n # CA, say - keeps its waits within the time it is given, or holds the\n # connection past the deadline. Not asked when nil.\n def identity_source=(value : Proc(String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil, TlsIdentity | TlsError | Nil) | ::Nil) : Proc(String | ::Nil, Array(String) | ::Nil, Int32 | ::Nil, TlsIdentity | TlsError | Nil) | ::Nil\n def initialize(identity : TlsIdentity)\n def leaf : Certificate\n # The ticket lifetime in milliseconds, held to 1 s .. seven days.\n def lifetime_ms : Int64\n # The most early data (0-RTT, RFC 8446 section 2.3) a ticket lets a\n # client send with its ClientHello, which `TlsServer#read_early` reads\n # apart from what `read` does: none when 0, as unless set, and at most\n # `MAX_EARLY_DATA`. Early data can be replayed: `replay_guard` refuses\n # what it can (see `TlsReplayGuard` for what it does not), and what\n # comes early must still be safe to act on twice.\n def max_early_data : Int32\n # The most early data (0-RTT, RFC 8446 section 2.3) a ticket lets a\n # client send with its ClientHello, which `TlsServer#read_early` reads\n # apart from what `read` does: none when 0, as unless set, and at most\n # `MAX_EARLY_DATA`. Early data can be replayed: `replay_guard` refuses\n # what it can (see `TlsReplayGuard` for what it does not), and what\n # comes early must still be safe to act on twice.\n def max_early_data=(value : Int32) : Int32\n # The oldest version this server speaks: TLS 1.3 by default, and then a\n # client that does not offer it is refused (protocol_version); `TLS12`\n # answers a client that offers 1.2 and not 1.3 in 1.2, held to what the\n # module's header says. A client that offers 1.3 is answered in it\n # either way.\n def min_version : TlsVersion\n # The oldest version this server speaks: TLS 1.3 by default, and then a\n # client that does not offer it is refused (protocol_version); `TLS12`\n # answers a client that offers 1.2 and not 1.3 in 1.2, held to what the\n # module's header says. A client that offers 1.3 is answered in it\n # either way.\n def min_version=(value : TlsVersion) : TlsVersion\n # The time a client's certificate is checked at; now when nil.\n def now : Time | ::Nil\n # The time a client's certificate is checked at; now when nil.\n def now=(value : Time | ::Nil) : Time | ::Nil\n # More identities, each shown to a client that asks by server_name (RFC\n # 6066) for a host its certificate names; `identity` to any other.\n def others : Array(TlsIdentity)\n # What refuses early data a server has taken before (RFC 8446 section 8).\n def replay_guard : TlsReplayGuard\n def schemes : Array(Int32)\n protected def share_state(keys : TlsTicketKeys, guard : TlsReplayGuard) : Nil\n # *der*, an OCSP response for *identity*'s leaf, stapled to its\n # certificate for a client that asks (RFC 6066's status_request; in TLS\n # 1.3 the leaf's CertificateEntry carries it, in 1.2 a CertificateStatus):\n # see `TlsIdentity#staple` for what is refused. A later call replaces it,\n # as a server fetching fresh responses would.\n def staple(der : Array(UInt8), identity : TlsIdentity = @identity) : TlsError | ::Nil\n # The keys this server's tickets are sealed under.\n def ticket_keys : TlsTicketKeys\n # How long a ticket may be resumed by, in seconds, from 1 to seven days\n # (604800, section 4.6.1's most); the period the ticket key turns over\n # in, too (`TlsTicketKeys`).\n def ticket_lifetime : Int32\n # How long a ticket may be resumed by, in seconds, from 1 to seven days\n # (604800, section 4.6.1's most); the period the ticket key turns over\n # in, too (`TlsTicketKeys`).\n def ticket_lifetime=(value : Int32) : Int32\n # NewSessionTickets sent after each handshake, for a client to resume\n # by (RFC 8446 section 4.6.1); none when 0, and at most `MAX_TICKETS`.\n def tickets : Int32\n # NewSessionTickets sent after each handshake, for a client to resume\n # by (RFC 8446 section 4.6.1); none when 0, and at most `MAX_TICKETS`.\n def tickets=(value : Int32) : Int32\nend\n\n# What a client keeps to resume a server's sessions (RFC 8446 section\n# 2.2): the tickets its NewSessionTickets carry, each with the PSK its\n# nonce makes. The caller holds it and hands it to `TlsClient.connect` or\n# `over`; there is no store of the process's own. A connection offers the\n# newest ticket that fits it, and takes it out, for a ticket goes out once\n# (appendix C.4): two resumptions are not tied together by it. A resumed\n# handshake checks no certificate - the ticket stands for the check the\n# first one made, and its chain is the resumed connection's\n# `peer_certificates` - so a ticket fits only a connection that check\n# would stand for:\n# - to the host and port it came from: section 4.6.1 has a client resume\n# only for a name the first handshake's certificate was checked for, and\n# a ticket offered to another port names the client to whatever server\n# listens there;\n# - under the configuration it came under (`TlsMadeUnder`): one session\n# shared by two configurations resumed the first one's ticket under the\n# second, its roots, `now` and `require_staple` never applied, and with\n# mutual TLS the server took the ticket's client certificate for the\n# connection's, so a connection made under identity B was identity A's;\n# - while the server's certificate is valid at the configuration's `now`\n# (the clock's when nil), as Go's client has it: a session past its\n# leaf's notAfter is a full handshake, which refuses it;\n# - within its lifetime, and within seven days (`MAX_LIFETIME`, section\n# 4.6.1's most) of the full handshake that checked those certificates,\n# however many resumptions came between: a server that sent fresh\n# tickets on every resumed connection otherwise kept the session alive\n# for as long as the client came back.\npub class TlsSession\n # A session holding at most *limit* tickets, held to 0 ..\n # `MAX_TICKETS`: std/http's `HTTPClient` keeps fewer for each origin.\n def initialize(limit : Int32 = MAX_TICKETS)\n # *ticket* held, the oldest gone past the session's limit.\n def keep(ticket : TlsHeldTicket) : Nil\n # How many tickets are held.\n def size : Int32\n # The newest ticket for *host* and *port* under *config* (the class's\n # header says which fit), taken out, at *now_ms* by the monotonic clock.\n # Those past their lifetimes or seven days from their full handshake go\n # too, and so does one that fits but whose server certificate has\n # expired.\n def take(host : String, port : Int32, config : TlsConfig, now_ms : Int64) : TlsHeldTicket | ::Nil\nend\n\n# The session a server's ticket seals, for a resumption to take up: the\n# suite, when it was issued (monotonic ms) and for how long (seconds),\n# its age_add, the PSK, the name the client asked for, the protocol\n# chosen, the identity shown (`TlsServerConfig#identity_index`), the\n# client's certificates (DER, leaf first), and when the first handshake\n# checked the certificates the session rests on (monotonic ms). The name's\n# length is two bytes and the identity's place four: in version 1 each\n# was one, and a name past 255 bytes, which the ClientHello then let\n# through, sealed a ticket that never opened, and a place past 255 among\n# `others` wrapped round. The PSK (48 bytes at most), the protocol (one\n# byte long in ALPN itself) and the chain's count - a chain of 256\n# certificates is far past `TlsServerConfig::MAX_TICKET`, which no ticket\n# is sent beyond - keep one byte.\npub class TlsTicket\n def age_add : Int64\n def alpn : String | ::Nil\n def authenticated_ms : Int64\n def chain : Array(Array(UInt8))\n # The state *bytes* hold, or nil when they are not one this encodes.\n def self.decode(bytes : Array(UInt8)) : TlsTicket | ::Nil\n def encode : Array(UInt8)\n def identity : Int32\n def initialize(suite : Int32, issued_ms : Int64, lifetime : Int64, age_add : Int64, psk : Array(UInt8), server_name : String | ::Nil, alpn : String | ::Nil, identity : Int32, chain : Array(Array(UInt8)), authenticated_ms : Int64, max_early_data : Int64 = 0_i64)\n def issued_ms : Int64\n def lifetime : Int64\n def max_early_data : Int64\n # The session *ticket* seals, opened by *keys* at *now_ms*: nil when no\n # key held opens it, or when its lifetime is over by this server's own\n # count from its issue.\n def self.open(keys : TlsTicketKeys, ticket : Array(UInt8), now_ms : Int64, period_ms : Int64) : TlsTicket | ::Nil\n def psk : Array(UInt8)\n def server_name : String | ::Nil\n def suite : Int32\nend\n\n# The keys a server seals its tickets under. RFC 8446 section 4.6.1 leaves\n# a ticket's form to the server; this one is RFC 5077 section 4's shape:\n# the key's 8-byte name, a 12-byte nonce drawn at random, and the state\n# sealed by ChaCha20-Poly1305 (std/aead, constant-time) with the name as\n# its additional data. A key made at random in this process seals for one\n# period - the ticket lifetime - and then only opens, for one more: a\n# ticket opens for all its lifetime, and no key opens anything past two\n# periods. A key idle past both goes with the one before it. The keys live\n# in this process: a ticket outlives neither a restart nor a move to\n# another server, which would need the keys shared and is not offered.\n#\n# Every thread of the process may seal and open at once - a server's\n# workers under `TlsFrozenServerConfig` share one set, so a ticket one\n# sealed another opens - so the keys are behind a `Mutex`, which makes\n# this type `Share` by its fields: the current name and key, and the\n# previous ones, are read under the lock, the key turned over there when\n# it is due, and the seal or open done outside it with the key read.\n# The lock is held for a comparison and four reads, and for two random\n# draws when the key turns over.\npub class TlsTicketKeys\n def initialize(now_ms : Int64 = TlsTime.now_ms)\n # The state *ticket* seals, when a key still held at *now_ms* opens it;\n # nil for any other bytes.\n def open(ticket : Array(UInt8), now_ms : Int64, period_ms : Int64) : Array(UInt8) | ::Nil\n # A new key from *now_ms*: what the one before sealed still opens, and\n # what the one before that sealed no longer does.\n def rotate(now_ms : Int64 = TlsTime.now_ms) : Nil\n # *state* sealed at *now_ms* (monotonic) under the current key - a new\n # one first, when *period_ms* has passed since it was made. Empty only\n # when the task was cancelled while it waited for the keys.\n def seal(state : Array(UInt8), now_ms : Int64, period_ms : Int64) : Array(UInt8)\nend\n\n# The versions a connection may speak: TLS 1.3, or TLS 1.2 as well (RFC\n# 5246), for a peer that never moved past it.\npub enum TlsVersion : Int32\n TLS12 = 0\n TLS13 = 1\nend\n\nimpl Error for TlsError\n def message : String\nend\n" }, { "name": "std/toml", @@ -592,8 +640,8 @@ { "name": "std/udp", "path": "src/std/udp.iyi", - "sourceSha256": "fe3d3e40e1586bbf90ce1a0db865944f9467e4499abcc8b3a6a392c3d5419ae0", - "surface": "module std/udp\n\npub alias Bytes = Std::Slice::Bytes\n\n# An incoming datagram with payload bytes and remote peer origin.\npub struct Datagram\n def [](i : Int32) : Bytes | String | Int32\n def data : Bytes\n def host : String\n def initialize(data : Bytes, host : String, port : Int32)\n def port : Int32\n # Decodes payload bytes as a String.\n def text : String\n def to_s : String\n def to_tuple : Tuple(Bytes, String, Int32)\nend\n\n# The address types, their parsers and the `sockaddr_in`/`sockaddr_in6`\n# writers are `std/socket`'s; this module had copies, and the copies had\n# drifted: its IPv6 parser read no dotted tail and its printer compressed\n# only `::` and `::1`.\npub alias IPv4Address = Std::Socket::IPv4Address\n\npub alias IPv6Address = Std::Socket::IPv6Address\n\npub class UdpSocket\n lib LibWs2_32\n struct PollFd\n end\n # Both are kept on the caller's frame rather than the heap: the kernel\n # reads and writes them after a post, while the fiber is parked, and a\n # heap chunk whose only reference is a local the posting call has\n # finished with is a chunk the collector can hand to somebody else\n # first.\n struct WSABuf\n end\n end\n alias SockLen = Int32\n def self.__is_eagain(res : Int64) : ::Bool\n # Never: Winsock has no EINTR. A call either answers, or answers\n # WSAEWOULDBLOCK, or is posted and completed.\n def self.__is_eintr(res : Int64) : ::Bool\n # A datagram that did not fit is an error here where it is a silent\n # truncation everywhere else: WSAEMSGSIZE (10040), with the buffer\n # already filled, which is why the caller answers its capacity.\n def self.__is_msgsize(res : Int64) : ::Bool\n # Never: the report is turned off in `__sys_socket`.\n def self.__is_refused(res : Int64) : ::Bool\n # WSAEINVAL (10022): Winsock's answer to a receive, or an address asked,\n # on a socket that has none yet - one `client` made and nothing sent\n # from. POSIX has nothing queued there and port 0; Windows panicked.\n def self.__is_unbound(res : Int64) : ::Bool\n def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), len : Int32) : Int32\n # A datagram socket's `connect` only records the peer, so there is no\n # handshake to wait for and nothing to post.\n def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), len : Int32) : Int32\n # Winsock's error number, negated, which is the shape the POSIX arms\n # answer the kernel's errno in; the two predicates at the bottom are the\n # only things that read which number it was.\n def self.__sys_error : Int64\n def self.__sys_get_buffer_size(fd : Int32, level : Int32, optname : Int32) : Int32\n def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_handle(fd : Int32) : UInt64\n # The wait a cancelled post still owes the kernel. `CancelIoEx` is a\n # request, and on a socket tied to a completion port the operation is not\n # over when it returns: measured here, the status stays STATUS_PENDING\n # and the descriptor, the caller's address and the OVERLAPPED are the\n # kernel's until the completion packet is taken *off the port* — the\n # poller's `GetQueuedCompletionStatus` is what writes them. The same\n # measurement on an unassociated socket had the status written by\n # `CancelIoEx` itself, which is why this is not obvious. So\n # `WSAGetOverlappedResult`'s own wait flag cannot be the wait either: it\n # waits on the OVERLAPPED's event, the scheduler's OVERLAPPED has none,\n # and the socket handle it falls back to is never signalled for a socket\n # on a port — it hung forever on the first cancelled receive tried.\n #\n # What ends the operation is the poller, so the frame parks once more and\n # lets it. `IyiScheduler.cancel` has already unregistered this fiber,\n # which is what would have left the packet to be dropped; parking puts it\n # back, and the wake means the dequeue has happened. Asked first, because\n # a completion the poller delivered *before* the cancel is already over\n # and no second packet is coming — parking on one would never wake.\n def self.__sys_overlapped_await(s : UInt64, ov : Pointer(UInt8)) : Int64\n # What the posted operation ended with, read off the operation itself:\n # the completion port's byte count cannot tell a failure from a datagram\n # of no bytes, which is a legal datagram. Nothing waits here, which is\n # what the 0 says.\n def self.__sys_overlapped_result(s : UInt64, ov : Pointer(UInt8)) : Int64\n # One descriptor, one event, and a bound on the wait, which is what the\n # epoll and kqueue arms do with theirs.\n def self.__sys_poll_read(fd : Int32, timeout_ms : Int32) : ::Bool\n # Winsock's address length is an `int*` where POSIX has a `socklen_t*`:\n # the same width and the same bytes, so the callers keep one signature.\n def self.__sys_recvfrom(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int64\n # The receive that waits, which on Windows is a posted one: see the note\n # in `receive_into` for why the readable-wait cannot be used here. The\n # buffer descriptor and the flags stay on this frame for the whole\n # operation, and the address and its length are the caller's, which\n # `receive_from` reads after this returns.\n def self.__sys_recvfrom_posted(fd : Int32, buf : Pointer(UInt8), capacity : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32 | Cancelled\n def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n def self.__sys_sendto(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Int32) : Int64\n def self.__sys_set_buffer_size(fd : Int32, level : Int32, optname : Int32, size : Int32) : Int32\n def self.__sys_socket(domain : Int32 = 2) : Int32\n def self.__unbound_now? : ::Bool\n # Binds a UDP socket to the specified host and port.\n # Automatically detects whether host is an IPv4 or IPv6 address.\n #\n # Without SO_REUSEADDR, on every platform. It waives TIME_WAIT for a\n # listener, and a datagram port never has one; what it does to a\n # datagram socket on Linux is let a second socket bind the same address\n # and port while the first holds it, and unicast then reaches only one\n # of them. The POSIX arms asked for it and the win32 arm did not, so a\n # second bind of a held port failed on Windows, measured, and went\n # through on Linux [INFERENCE: not run here].\n def self.bind(host : String, port : Int32) : UdpSocket\n # Binds a UDP socket to 0.0.0.0 and the specified port.\n def self.bind(port : Int32) : UdpSocket\n # Binds an IPv6 UDP socket to [::] and the specified port.\n def self.bind6(port : Int32) : UdpSocket\n # `bind`, with a refused bind answered rather than raised: the port\n # held, reserved (Windows' excluded ranges answer \"access denied\"), or\n # the address not this machine's - so a program can pick another. An\n # error's `message` is the sentence `bind` raises.\n def self.bind?(host : String, port : Int32) : UdpSocket | SocketError\n # Creates an unbound UDP client socket.\n def self.client(family : Int32 = 2) : UdpSocket\n # Creates an unbound IPv6 UDP client socket.\n def self.client_v6 : UdpSocket\n # Closes the socket file descriptor.\n def close : Nil\n def closed? : ::Bool\n # Connects the UDP socket to a remote host and port (sets default peer).\n def connect(host : String, port : Int32) : Nil\n def family : Int32\n def fd : Int32\n def self.format_address(addr : Pointer(UInt8)) : Tuple(String, Int32)\n def initialize(fd : Int32 = -1, family : Int32 = 2)\n # Reads back the locally bound IP address.\n def local_address : String\n # Reads back the locally bound port assigned by the OS.\n def local_port : Int32\n def self.make_sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), SockLen, Int32)\n # Creates an unbound UDP socket.\n def self.open(family : Int32 = 2) : UdpSocket\n # Polls whether the socket is readable within timeout_ms milliseconds.\n # Returns true if a datagram is ready to be read, false on timeout.\n def poll_read(timeout_ms : Int32 = 0) : ::Bool\n # Receives payload bytes from the connected peer, parking until some arrive.\n def receive(max_bytes : Int32 = 65536) : Bytes | Cancelled\n # Payload bytes queued now from the connected peer, or nil.\n def receive?(max_bytes : Int32 = 65536) : Bytes | ::Nil\n # Returns the current receive buffer size (SO_RCVBUF).\n def receive_buffer_size : Int32\n # Sets the receive buffer size (SO_RCVBUF).\n def receive_buffer_size=(size : Int32) : Nil\n # Receives a Datagram struct containing data, host, and port.\n def receive_datagram(max_bytes : Int32 = 65536) : Datagram | Cancelled\n # A Datagram of what is queued now, or nil.\n def receive_datagram?(max_bytes : Int32 = 65536) : Datagram | ::Nil\n # Receives a datagram into a newly allocated Bytes slice up to max_bytes.\n # Answers {payload, peer_host, peer_port}, parking until one arrives.\n def receive_from(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | Cancelled\n # Receives a datagram into a caller-supplied Bytes buffer.\n # Answers {subslice_of_received_bytes, peer_host, peer_port}.\n def receive_from(buffer : Bytes) : Tuple(Bytes, String, Int32) | Cancelled\n # What is queued now, or nil. Never parks.\n def receive_from?(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | ::Nil\n # Into a buffer: what is queued now, or nil. Never parks.\n def receive_from?(buffer : Bytes) : Tuple(Bytes, String, Int32) | ::Nil\n # Why a call failed: `std/socket`'s sentence for the platform's\n # number (`SocketError#reason`), and the refusals only a datagram meets\n # named as well. Each refusal was a fixed sentence that dropped the\n # number, so a broadcast, a datagram past 65,507 bytes, a port in use\n # and an IPv6 address from an IPv4 socket all said only \"cannot send to\n # H:P\" or \"cannot bind UDP socket to H:P\" - measured on Windows.\n def self.refusal(res : Int64, sending : ::Bool) : String\n # Sends bytes to the connected peer.\n def send(bytes : Bytes) : Int32\n # Sends string to the connected peer.\n def send(str : String) : Int32\n # Returns the current send buffer size (SO_SNDBUF).\n def send_buffer_size : Int32\n # Sets the send buffer size (SO_SNDBUF).\n def send_buffer_size=(size : Int32) : Nil\n # Sends a raw buffer to a destination host and port.\n def send_to(buffer : Pointer(UInt8), count : Int32, host : String, port : Int32) : Int32\n # Sends bytes to a destination host and port.\n def send_to(bytes : Bytes, host : String, port : Int32) : Int32\n # Sends string contents as a datagram to a destination host and port.\n def send_to(str : String, host : String, port : Int32) : Int32\n def to_unsafe : Int32\n # Creates an unbound UDP socket.\n def self.unbound(family : Int32 = 2) : UdpSocket\nend\n" + "sourceSha256": "de67e123e5505406321b5f1cf7626e43fedf141ada0f9e89a667f3ab7cc0d858", + "surface": "module std/udp\n\npub alias Bytes = Std::Slice::Bytes\n\n# An incoming datagram with payload bytes and remote peer origin.\npub struct Datagram\n def [](i : Int32) : Bytes | String | Int32\n def data : Bytes\n def host : String\n def initialize(data : Bytes, host : String, port : Int32)\n def port : Int32\n # Decodes payload bytes as a String.\n def text : String\n def to_s : String\n def to_tuple : Tuple(Bytes, String, Int32)\nend\n\n# The address types, their parsers and the `sockaddr_in`/`sockaddr_in6`\n# writers are `std/socket`'s; this module had copies, and the copies had\n# drifted: its IPv6 parser read no dotted tail and its printer compressed\n# only `::` and `::1`.\npub alias IPv4Address = Std::Socket::IPv4Address\n\npub alias IPv6Address = Std::Socket::IPv6Address\n\npub class UdpSocket\n alias SockLen = Int64\n def self.__is_eagain(res : Int64) : ::Bool\n def self.__is_eintr(res : Int64) : ::Bool\n # No such refusal: a datagram larger than the buffer fills it, the\n # rest is dropped and the length answered is what fit.\n def self.__is_msgsize(res : Int64) : ::Bool\n # ECONNREFUSED (111): the ICMP port-unreachable a *connected* datagram\n # socket's earlier send provoked, which Linux and BSD report once, to\n # the next send or receive on it (udp(7)), and then forget; an\n # unconnected one is never told. It is something the peer did, not a\n # failure of this socket, so the shared bodies read it as nothing\n # queued and send again. Read as a failure, the next `send` or\n # `receive?` panicked here, where Windows, which has the report turned\n # off, answered the byte count and nil. A `poll_read` the report wakes\n # still answers true [INFERENCE], and the `receive?` after it nil.\n def self.__is_refused(res : Int64) : ::Bool\n def self.__is_unbound(res : Int64) : ::Bool\n def self.__sys3(number : Int64, a : Int64, b : Int64, c : Int64) : Int64\n def self.__sys5(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64) : Int64\n def self.__sys6(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64, f : Int64) : Int64\n def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), len : Int64) : Int32\n def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), len : Int64) : Int32\n def self.__sys_get_buffer_size(fd : Int32, level : Int32, optname : Int32) : Int32\n def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_poll_read(fd : Int32, timeout_ms : Int32) : ::Bool\n def self.__sys_recvfrom(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int64\n def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n def self.__sys_sendto(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Int64) : Int64\n def self.__sys_set_buffer_size(fd : Int32, level : Int32, optname : Int32, size : Int32) : Int32\n def self.__sys_socket(domain : Int32 = 2) : Int32\n def self.__unbound_now? : ::Bool\n # Binds a UDP socket to the specified host and port.\n # Automatically detects whether host is an IPv4 or IPv6 address.\n #\n # Without SO_REUSEADDR, on every platform. It waives TIME_WAIT for a\n # listener, and a datagram port never has one; what it does to a\n # datagram socket on Linux is let a second socket bind the same address\n # and port while the first holds it, and unicast then reaches only one\n # of them. The POSIX arms asked for it and the win32 arm did not, so a\n # second bind of a held port failed on Windows, measured, and went\n # through on Linux [INFERENCE: not run here].\n def self.bind(host : String, port : Int32) : UdpSocket\n # Binds a UDP socket to 0.0.0.0 and the specified port.\n def self.bind(port : Int32) : UdpSocket\n # Binds an IPv6 UDP socket to [::] and the specified port.\n def self.bind6(port : Int32) : UdpSocket\n # `bind`, with a refused bind answered rather than raised: the port\n # held, reserved (Windows' excluded ranges answer \"access denied\"), or\n # the address not this machine's - so a program can pick another. An\n # error's `message` is the sentence `bind` raises.\n def self.bind?(host : String, port : Int32) : UdpSocket | SocketError\n # Creates an unbound UDP client socket.\n def self.client(family : Int32 = 2) : UdpSocket\n # Creates an unbound IPv6 UDP client socket.\n def self.client_v6 : UdpSocket\n # Closes the socket file descriptor.\n def close : Nil\n def closed? : ::Bool\n # Connects the UDP socket to a remote host and port (sets default peer).\n def connect(host : String, port : Int32) : Nil\n def family : Int32\n def fd : Int32\n def self.format_address(addr : Pointer(UInt8)) : Tuple(String, Int32)\n def initialize(fd : Int32 = -1, family : Int32 = 2)\n # Reads back the locally bound IP address.\n def local_address : String\n # Reads back the locally bound port assigned by the OS.\n def local_port : Int32\n def self.make_sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), SockLen, Int32)\n # Creates an unbound UDP socket.\n def self.open(family : Int32 = 2) : UdpSocket\n # Polls whether the socket is readable within timeout_ms milliseconds.\n # Returns true if a datagram is ready to be read, false on timeout.\n def poll_read(timeout_ms : Int32 = 0) : ::Bool\n # Receives payload bytes from the connected peer, parking until some arrive.\n def receive(max_bytes : Int32 = 65536) : Bytes | Cancelled\n # Payload bytes queued now from the connected peer, or nil.\n def receive?(max_bytes : Int32 = 65536) : Bytes | ::Nil\n # Returns the current receive buffer size (SO_RCVBUF).\n def receive_buffer_size : Int32\n # Sets the receive buffer size (SO_RCVBUF).\n def receive_buffer_size=(size : Int32) : Nil\n # Receives a Datagram struct containing data, host, and port.\n def receive_datagram(max_bytes : Int32 = 65536) : Datagram | Cancelled\n # A Datagram of what is queued now, or nil.\n def receive_datagram?(max_bytes : Int32 = 65536) : Datagram | ::Nil\n # Receives a datagram into a newly allocated Bytes slice up to max_bytes.\n # Answers {payload, peer_host, peer_port}, parking until one arrives.\n def receive_from(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | Cancelled\n # Receives a datagram into a caller-supplied Bytes buffer.\n # Answers {subslice_of_received_bytes, peer_host, peer_port}.\n def receive_from(buffer : Bytes) : Tuple(Bytes, String, Int32) | Cancelled\n # What is queued now, or nil. Never parks.\n def receive_from?(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | ::Nil\n # Into a buffer: what is queued now, or nil. Never parks.\n def receive_from?(buffer : Bytes) : Tuple(Bytes, String, Int32) | ::Nil\n # Why a call failed: `std/socket`'s sentence for the platform's\n # number (`SocketError#reason`), and the refusals only a datagram meets\n # named as well. Each refusal was a fixed sentence that dropped the\n # number, so a broadcast, a datagram past 65,507 bytes, a port in use\n # and an IPv6 address from an IPv4 socket all said only \"cannot send to\n # H:P\" or \"cannot bind UDP socket to H:P\" - measured on Windows.\n def self.refusal(res : Int64, sending : ::Bool) : String\n # Sends bytes to the connected peer.\n def send(bytes : Bytes) : Int32\n # Sends string to the connected peer.\n def send(str : String) : Int32\n # Returns the current send buffer size (SO_SNDBUF).\n def send_buffer_size : Int32\n # Sets the send buffer size (SO_SNDBUF).\n def send_buffer_size=(size : Int32) : Nil\n # Sends a raw buffer to a destination host and port.\n def send_to(buffer : Pointer(UInt8), count : Int32, host : String, port : Int32) : Int32\n # Sends bytes to a destination host and port.\n def send_to(bytes : Bytes, host : String, port : Int32) : Int32\n # Sends string contents as a datagram to a destination host and port.\n def send_to(str : String, host : String, port : Int32) : Int32\n def to_unsafe : Int32\n # Creates an unbound UDP socket.\n def self.unbound(family : Int32 = 2) : UdpSocket\nend\n" }, { "name": "std/unicode", @@ -634,8 +682,8 @@ { "name": "std/x509", "path": "src/std/x509.iyi", - "sourceSha256": "5bae4254f0d2d9b117c7bb5a89aee875693b1cb59a2760a005979f19e46de508", - "surface": "module std/x509\n\npub struct Certificate\n def authority_key_id : Array(UInt8) | ::Nil\n # basicConstraints: whether it was present, cA, and pathLenConstraint.\n def basic_constraints : Bool\n def ca : Bool\n # subjectAltName's dNSName and iPAddress entries.\n def dns_names : Array(String)\n def excluded_dns : Array(String)\n def excluded_ips : Array(Array(UInt8))\n # extKeyUsage's OIDs; nil when the extension is absent.\n def extended_key_usage : Array(String) | ::Nil\n # Every CERTIFICATE block in a PEM text, read; the first one that is not\n # a certificate is the answer.\n def self.from_pem(text : String) : Array(Certificate) | X509Error\n def initialize(raw : Array(UInt8), tbs : Array(UInt8), version : Int32, serial : Array(UInt8), issuer : Array(UInt8), subject : Array(UInt8), subject_name : String, issuer_name : String, not_before : Time, not_after : Time, public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey, signature_algorithm : String, signature_parameters : DerValue | ::Nil, signature : Array(UInt8), extensions : X509Extensions)\n def ip_addresses : Array(Array(UInt8))\n # Issuer and subject as their DER: two names are the same name when\n # these are the same bytes.\n def issuer : Array(UInt8)\n def issuer_name : String\n # keyUsage's bits, bit 0 digitalSignature to bit 8 decipherOnly; nil\n # when the extension is absent.\n def key_usage : Int32 | ::Nil\n # Whether *host* is a name this certificate is for: a DNS name or an IP\n # address in its subjectAltName (see the module's header).\n def matches_host?(host : String) : Bool\n def not_after : Time\n def not_before : Time\n def other_constraints : Bool\n # The certificate *der* holds (RFC 5280 section 4.1).\n def self.parse(der : Array(UInt8)) : Certificate | X509Error\n def path_length : Int32 | ::Nil\n # The DER of every CERTIFICATE block in a PEM text, unread: a store\n # can keep what it can read and count the rest.\n def self.pem_blocks(text : String) : Array(Array(UInt8)) | X509Error\n # nameConstraints: DNS names and IP ranges (address and mask), and\n # whether a constraint of any other kind is there.\n def permitted_dns : Array(String)\n def permitted_ips : Array(Array(UInt8))\n def public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey\n # The whole certificate as it was read, and its signed part.\n def raw : Array(UInt8)\n # The serial number's two's-complement bytes, as written.\n def serial : Array(UInt8)\n def signature : Array(UInt8)\n # The signature algorithm's OID, and the signature.\n def signature_algorithm : String\n # Why *issuer*'s key did not make this certificate's signature, or nil.\n def signature_error(issuer : Certificate) : String | ::Nil\n # Whether *issuer*'s key made this certificate's signature.\n def signed_by?(issuer : Certificate) : Bool\n def subject : Array(UInt8)\n def subject_key_id : Array(UInt8) | ::Nil\n # The subject and issuer written out for a person, `CN=..., O=...`.\n def subject_name : String\n def tbs : Array(UInt8)\n # Critical extensions this does not understand, by OID.\n def unhandled_critical : Array(String)\n def valid_at?(time : Time) : Bool\n def version : Int32\nend\n\npub struct X509\n def initialize\n # The DER of every block labelled *label* in a PEM text - `\"CERTIFICATE\"`\n # or `\"PRIVATE KEY\"` - each checked to be base64 before it is decoded.\n def self.pem(text : String, label : String) : Array(Array(UInt8)) | X509Error\n # The chain from *leaf* to one of *roots*, through *intermediates*, for\n # *host* (nil: any) at *now* - leaf first, root last - or why there is\n # none. *purpose* is what the leaf is for: a server's certificate\n # (serverAuth, the default) or a client's (clientAuth), each certificate\n # that carries extKeyUsage allowing it. See the module's header for what\n # else is checked.\n def self.verify(leaf : Certificate, intermediates : Array(Certificate), roots : Array(Certificate), host : String | ::Nil = nil, now : Time = Time.utc, purpose : X509Purpose = X509Purpose::Server) : Array(Certificate) | X509Error\nend\n\n# What reading a certificate, or checking a chain, answers when it fails.\npub struct X509Error\n def initialize(message : String)\n def to_s : String\nend\n\n# What a chain is checked for: a TLS server's certificate or a client's\n# (RFC 5280 section 4.2.1.12's id-kp-serverAuth and id-kp-clientAuth).\npub enum X509Purpose : Int32\n Server = 0\n Client = 1\n def holder : String\n def oid : String\n # The usage's name, as a sentence says it.\n def usage : String\nend\n\nimpl Error for X509Error\n def message : String\nend\n" + "sourceSha256": "d89cff9466a3354bf762ce60c9b11e255204257436a4028892ce12bcd1a46e72", + "surface": "module std/x509\n\n# A class, not a struct, for the stack's sake. Thirty-odd fields make 472\n# bytes, and a struct is copied whole into every array slot, block\n# argument and union it passes through. In a debug build each copy is a\n# stack slot of its own, and a TLS handshake inside a task ran out of\n# its 256 KiB (issue #130). Measured on Linux x86_64, a struct made\n# `Certificate.parse`'s frame 31,832 bytes (each `!` and early return\n# makes a `Certificate | X509Error`), `X509.grow`'s 13,640 and\n# `issuer_error`'s 6,184; a class makes them 5,224, 296 and 368. With\n# `EcdsaP256.check` split as well (std/ecdsa), `X509.verify` of a\n# two-certificate P-256 chain in a task went from 35,608 bytes of stack\n# to 11,768. Nothing in a certificate is written after `parse`, so\n# sharing one is not observable, and two are equal when they are the\n# same DER, as two structs were when every field was.\npub class Certificate\n # The same certificate: the same DER, from which every field is read.\n def ==(other : Certificate) : Bool\n def authority_key_id : Array(UInt8) | ::Nil\n # basicConstraints: whether it was present, cA, and pathLenConstraint.\n def basic_constraints : Bool\n def ca : Bool\n # subjectAltName's dNSName and iPAddress entries.\n def dns_names : Array(String)\n def excluded_dns : Array(String)\n def excluded_ips : Array(Array(UInt8))\n # extKeyUsage's OIDs; nil when the extension is absent.\n def extended_key_usage : Array(String) | ::Nil\n # Every CERTIFICATE block in a PEM text, read; the first one that is not\n # a certificate is the answer.\n def self.from_pem(text : String) : Array(Certificate) | X509Error\n def hash : Int32\n def initialize(raw : Array(UInt8), tbs : Array(UInt8), version : Int32, serial : Array(UInt8), issuer : Array(UInt8), subject : Array(UInt8), subject_name : String, issuer_name : String, not_before : Time, not_after : Time, public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey, signature_algorithm : String, signature_parameters : DerValue | ::Nil, signature : Array(UInt8), public_key_bits : Array(UInt8), extensions : X509Extensions)\n def ip_addresses : Array(Array(UInt8))\n # Issuer and subject as their DER: two names are the same name when\n # these are the same bytes.\n def issuer : Array(UInt8)\n def issuer_name : String\n # keyUsage's bits, bit 0 digitalSignature to bit 8 decipherOnly; nil\n # when the extension is absent.\n def key_usage : Int32 | ::Nil\n # Whether *host* is a name this certificate is for: a DNS name or an IP\n # address in its subjectAltName (see the module's header).\n def matches_host?(host : String) : Bool\n # Whether the TLS Feature extension (RFC 7633) asks for status_request:\n # a certificate that must be shown with a stapled OCSP response.\n def must_staple? : Bool\n def not_after : Time\n def not_before : Time\n # Whether id-pkix-ocsp-nocheck is here (RFC 6960 section 4.2.2.2.1): an\n # OCSP responder's certificate saying its own status is not asked.\n def ocsp_nocheck : Bool\n def other_constraints : Bool\n # The certificate *der* holds (RFC 5280 section 4.1).\n def self.parse(der : Array(UInt8)) : Certificate | X509Error\n def path_length : Int32 | ::Nil\n # The DER of every CERTIFICATE block in a PEM text, unread: a store\n # can keep what it can read and count the rest.\n def self.pem_blocks(text : String) : Array(Array(UInt8)) | X509Error\n # nameConstraints: DNS names and IP ranges (address and mask), and\n # whether a constraint of any other kind is there.\n def permitted_dns : Array(String)\n def permitted_ips : Array(Array(UInt8))\n def public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey\n # The subjectPublicKey BIT STRING's bytes, as written: what an OCSP\n # CertID's issuerKeyHash and a responder's byKey are hashes of (RFC\n # 6960 section 4.1.1).\n def public_key_bits : Array(UInt8)\n # The whole certificate as it was read, and its signed part.\n def raw : Array(UInt8)\n # The serial number's two's-complement bytes, as written.\n def serial : Array(UInt8)\n def signature : Array(UInt8)\n # The signature algorithm's OID, and the signature.\n def signature_algorithm : String\n # Why *issuer*'s key did not make this certificate's signature, or nil.\n def signature_error(issuer : Certificate) : String | ::Nil\n # Whether *issuer*'s key made this certificate's signature.\n def signed_by?(issuer : Certificate) : Bool\n def subject : Array(UInt8)\n def subject_key_id : Array(UInt8) | ::Nil\n # The subject and issuer written out for a person, `CN=..., O=...`.\n def subject_name : String\n def tbs : Array(UInt8)\n # The TLS Feature extension's features (RFC 7633), nil when absent:\n # 5, status_request, is \"must staple\".\n def tls_features : Array(Int32) | ::Nil\n # Critical extensions this does not understand, by OID.\n def unhandled_critical : Array(String)\n def valid_at?(time : Time) : Bool\n def version : Int32\nend\n\n# A private key that signs what this module writes - a certificate for\n# itself, a certificate request - and, through std/acme, an ACME account's\n# requests: P-256 or P-384 (std/p256, std/p384), RSA (std/rsa, 2048 bits\n# or more) or Ed25519 (std/curve25519), each signing in constant time.\n# The signature is the one X.509 carries: ECDSA's the DER of\n# Ecdsa-Sig-Value under SHA-256 on P-256 and SHA-384 on P-384, RSA's PKCS\n# #1 v1.5 under SHA-256, Ed25519's its 64 bytes.\npub struct SigningKey\n # The AlgorithmIdentifier of what `sign` makes: ecdsa-with-SHA256 or\n # -SHA384 without parameters (RFC 5758), sha256WithRSAEncryption with\n # its NULL (RFC 4055), id-Ed25519 without parameters (RFC 8410).\n def algorithm_identifier : Array(UInt8)\n # The Ed25519 key whose 32-byte seed (RFC 8032's private key) is *seed*.\n def self.ed25519(seed : Array(UInt8)) : SigningKey | X509Error\n # The key in *text*'s one PRIVATE KEY block (PKCS #8: P-256, P-384, RSA\n # or Ed25519), EC PRIVATE KEY block (SEC 1, P-256 or P-384) or RSA\n # PRIVATE KEY block (PKCS #1).\n def self.from_pem(text : String) : SigningKey | X509Error\n def initialize(p256 : P256PrivateKey | ::Nil, p384 : P384PrivateKey | ::Nil, rsa : RsaPrivateKey | ::Nil, seed : Array(UInt8) | ::Nil)\n # What the key is, for a person: `P-256`, `P-384`, `RSA-2048`, `Ed25519`.\n def kind : String\n def self.new(key : P256PrivateKey) : SigningKey\n def self.new(key : P384PrivateKey) : SigningKey\n def self.new(key : RsaPrivateKey) : SigningKey\n def p256 : P256PrivateKey | ::Nil\n # A new P-256 key, from the system's entropy (std/random's `Secure`).\n # The 32 bytes it is made from are drawn again in the 2^-32 chance that\n # they are not below the group's order.\n def self.p256 : SigningKey\n def p384 : P384PrivateKey | ::Nil\n # A new P-384 key, as `p256` makes one.\n def self.p384 : SigningKey\n def public_key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey\n # The key's SubjectPublicKeyInfo (RFC 5280 section 4.1.2.7): RFC 5480's\n # for P-256 and P-384, RFC 8017's rsaEncryption with its NULL, RFC 8410's\n # for Ed25519.\n def public_key_info : Array(UInt8)\n def rsa : RsaPrivateKey | ::Nil\n # *data* signed as `algorithm_identifier` says.\n def sign(data : Array(UInt8)) : Array(UInt8)\nend\n\npub struct X509\n def initialize\n # The DER of every block labelled *label* in a PEM text - `\"CERTIFICATE\"`\n # or `\"PRIVATE KEY\"` - each checked to be base64 before it is decoded.\n def self.pem(text : String, label : String) : Array(Array(UInt8)) | X509Error\n # A certificate request (PKCS #10, RFC 2986) *key* signs, as its DER: the\n # subject CN= when it fits the 64 characters RFC 5280\n # gives a common name and empty otherwise, the key, and an\n # extensionRequest attribute (RFC 2985 section 5.4.2) asking for\n # subjectAltName with *names* as dNSNames - which is what an ACME server\n # issues for (RFC 8555 section 7.4).\n def self.request(key : SigningKey, names : Array(String)) : Array(UInt8) | X509Error\n # A certificate *key* signs for itself (RFC 5280 section 4.1), as its\n # DER: version 3, *serial* (sixteen random bytes when nil), the subject\n # and issuer both CN=, valid from *not_before* to\n # *not_after*, and the extensions - subjectAltName with *names* as\n # dNSNames, then *extensions* in order. No basicConstraints and no\n # keyUsage: what this writes is a certificate shown for a name, as RFC\n # 8737's tls-alpn-01 shows one, not a CA's.\n #\n # A first name longer than `MAX_COMMON_NAME` is refused, not cut or left\n # out of the subject: a common name holds 64 characters at most, and a\n # subject without one would be empty, which a self-signed certificate's\n # issuer - the same name - must not be (section 4.1.2.4). `request`,\n # whose subject is no issuer, leaves the CN out instead. *serial* is the\n # big-endian magnitude of a positive number: refused when it is zero or\n # when its INTEGER, a sign byte put before a top bit that is set, runs\n # past `MAX_SERIAL_OCTETS` (section 4.1.2.2). The sixteen bytes drawn\n # when it is nil write 17 octets at most and are drawn again in the\n # 2^-128 chance they are all zero.\n def self.self_signed(key : SigningKey, names : Array(String), not_before : Time, not_after : Time, serial : Array(UInt8) | ::Nil = nil, extensions : Array(X509Extension) = [] of X509Extension) : Array(UInt8) | X509Error\n # Why *key*, *signer*'s, did not make *signature* over *signed* under\n # *algorithm* (an OID) and its *parameters*, or nil; *what* names what\n # was signed, for the sentence. A certificate's signature is checked\n # here, and an OCSP response's (std/ocsp).\n def self.signature_error(key : RsaPublicKey | EcdsaPublicKey | Ed25519PublicKey, algorithm : String, parameters : DerValue | ::Nil, signed : Array(UInt8), signature : Array(UInt8), what : String, signer : String) : String | ::Nil\n # The chain from *leaf* to one of *roots*, through *intermediates*, for\n # *host* (nil: any) at *now* - leaf first, root last - or why there is\n # none. *purpose* is what the leaf is for: a server's certificate\n # (serverAuth, the default) or a client's (clientAuth), each certificate\n # that carries extKeyUsage allowing it. See the module's header for what\n # else is checked.\n def self.verify(leaf : Certificate, intermediates : Array(Certificate), roots : Array(Certificate), host : String | ::Nil = nil, now : Time = Time.utc, purpose : X509Purpose = X509Purpose::Server) : Array(Certificate) | X509Error\n # Why *certificate*'s RSA key is under `MIN_RSA_BITS`, or nil.\n def self.weak_key_error(certificate : Certificate) : String | ::Nil\nend\n\n# What reading a certificate, or checking a chain, answers when it fails.\npub struct X509Error\n def initialize(message : String)\n def to_s : String\nend\n\n# An extension a certificate this module writes carries beside its\n# subjectAltName: its OID, whether it is critical, and *value*, the DER\n# that extnValue's OCTET STRING holds.\npub struct X509Extension\n def critical : Bool\n def initialize(oid : String, critical : Bool, value : Array(UInt8))\n def oid : String\n def value : Array(UInt8)\nend\n\n# What a chain is checked for: a TLS server's certificate or a client's\n# (RFC 5280 section 4.2.1.12's id-kp-serverAuth and id-kp-clientAuth).\npub enum X509Purpose : Int32\n Server = 0\n Client = 1\n def holder : String\n def oid : String\n # The usage's name, as a sentence says it.\n def usage : String\nend\n\nimpl Error for X509Error\n def message : String\nend\n" }, { "name": "std/xml", @@ -680,7 +728,7 @@ }, { "path": "src/iyi/concurrency.iyi", - "sha256": "377eeca7c46b21722720bf7c04f19bfd579fd596b3e3284295c3f29289991546" + "sha256": "0a2057fcd85011a1bdbd098176792092b1e5a5a4e0e9ddee6d23670e4fbf5214" }, { "path": "src/iyi/enum.iyi", @@ -716,7 +764,7 @@ }, { "path": "src/iyi/prelude.iyi", - "sha256": "cd5f17aa7ef81c6700acf40039efd7c6197b33da8a67889bf63a50fc3f674cea" + "sha256": "92596c2c55ebd108cc41a422f58d9ebd17be67f28639c8ad67aeae7482a99d14" }, { "path": "src/iyi/primitives.iyi", @@ -732,7 +780,7 @@ }, { "path": "src/iyi/string.iyi", - "sha256": "7c32dd645bdadfd7a49342fcc783ec65ab0bbff9db9dae7d12b13d23f3c5a02a" + "sha256": "bedeecee93e9b30cfe7f4410ab86cbbc77f0e85058baf668b506ff773561ebd8" }, { "path": "src/iyi/testing.iyi", @@ -740,7 +788,7 @@ }, { "path": "src/iyi/thread.iyi", - "sha256": "0b9b2073a96b7d963182fe8b2c2d778dcb3d49b5fb119a695ff1962d35ac6e31" + "sha256": "b64bff5faeafcb16053081d50199130cf93fff184ace18c3505e80dee6a34b40" } ], "types": [ @@ -762,7 +810,7 @@ }, { "name": "Channel", - "surface": "# ── Channel — III.4.6 ──────────────────────────────────────────────────\n#\n# `Channel(T).new` is a rendezvous, as Crystal's is: a send completes when\n# a receive takes it, and either side parks until the other arrives. A\n# parked sender carries its value in its node's box, and a parked\n# receiver has a slot a sender fills, so the handoff needs no third\n# party; `new(capacity)` buffers that many sends first. Closing drains\n# what was delivered, then refuses: a parked sender whose box is still\n# full failed, and its value dies with the node.\n#\n# `T : Share` is not enforced yet for the reason the file header gives:\n# one thread cannot race, and a marker that gates nothing cannot be\n# tested for gating. `select` is answered by this protocol — a node per\n# arm, one nonce, losers stale by arithmetic until the woken select takes\n# them out — at the bottom of the class.\nclass Channel(T)\n\n # Everyone parked is woken; what each wake means is read on the woken\n # side — a receiver finds the buffer, then `ChannelClosed`; a sender\n # finds its box still full.\n def close : Nil\n\n def closed? : Bool\n\n def new(capacity : Int32)\n\n def new\n\n # `select`'s arms park through these with one shared nonce; a plain\n # call parks with a fresh nonce of its own. *slot* is where a rendezvous\n # sender puts its value (`IyiWaiter#slot`).\n def park_receive_node(fiber : IyiFiber, nonce : UInt64, slot : UInt64) : IyiWaiter\n\n def park_send_node(fiber : IyiFiber, nonce : UInt64, value : T) : IyiSendNode(T)\n\n # Assumes `send_ready?`. A rendezvous puts the value in the parked\n # receiver's own slot (`hand`). It went into the buffer, and the send\n # answered delivered on the word of the wake: a select woken that way\n # took another arm ready by then, a receiver cancelled before it ran\n # answered `Cancelled`, and either way the value stayed in a channel of\n # no capacity after its sender left. A buffered send completes when the\n # buffer takes it, so there the woken receiver finds it in the buffer.\n def put_now(value : T) : Nil | ChannelClosed\n\n # A closed channel drains what was delivered before it refuses. A\n # rendezvous sender that finds this receive parked writes its value into\n # `got` and marks the node handed, and that is the answer even when the\n # task was cancelled before it ran: the sender was told it was taken.\n # A woken park that was handed nothing takes its node out of the queue,\n # so a cancelled one leaves nothing behind.\n def receive : T | ChannelClosed | Cancelled\n\n def receive_ready? : Bool\n\n # Nothing happens on the arm `take_now` answers: `ChannelClosed` is\n # the refusal here, where a closed channel's buffer is spent.\n def receive_refused? : Bool\n\n def receive_select_action : IyiReceiveSelect(T)\n\n def send(value : T) : Nil | ChannelClosed | Cancelled\n\n def send_ready? : Bool\n\n def send_select_action(value : T) : IyiSendSelect(T)\n\n # Assumes `receive_ready?`: the buffer, then a parked sender's box,\n # then the refusal.\n def take_now : T | ChannelClosed\n\n # Out of the receive queue from wherever it stands, in constant time,\n # and no longer linked anywhere; a node no queue holds is left as it\n # is. The queue is linked both ways because a select's losing arms\n # leave from the middle: they stayed until something walked them off\n # the head, so a loop selecting over a busy channel and an idle one\n # left a node in the idle one's queue for every park - 30 MiB kept\n # after two million rounds, freed only when the idle channel was used.\n def unlink_receiver(node : IyiWaiter) : Nil\n\n # The send queue's `unlink_receiver`.\n def unlink_sender(node : IyiSendNode(T)) : Nil\n\n # No arm ready answers -1, which the expansion's case meets with the\n # program's own `else`.\n def self.non_blocking_select(actions : U, bound : Int32) forall U\n\n # ── select — III.4.6, the library half of the expansion ────────────\n #\n # A `select` compiles to `%i, %v, %h = ::Channel.select({arm, arm}, b)`\n # and a case over the index; `non_blocking_select` when an `else` arm\n # exists. One def serves every arity, because a tuple's size is a\n # compile-time fact a macro walks. `%h` says the arm's operation\n # happened, and an arm nobody binds runs its body only then: it ran\n # unless its answer was an `Error`, so a `Channel(Boom)`'s receive took\n # the `Boom` and skipped the body. *bound* is the first arm that binds,\n # -1 for none, and a cancelled task's `Cancelled` goes to it, as that\n # arm's own primitive would have answered it.\n def self.select(actions : U, bound : Int32) forall U\nend\n" + "surface": "# ── Channel — III.4.6 ──────────────────────────────────────────────────\n#\n# `Channel(T).new` is a rendezvous, as Crystal's is: a send completes when\n# a receive takes it, and either side parks until the other arrives. A\n# parked sender carries its value in its node's box, and a parked\n# receiver has a slot a sender fills, so the handoff needs no third\n# party; `new(capacity)` buffers that many sends first. Closing drains\n# what was delivered, then refuses: a parked sender whose box is still\n# full failed, and its value dies with the node.\n#\n# A channel crosses threads (SPEC.md III.4.13): it is `@[Share]`,\n# trusted whenever `T` is - III.4.6's \"`Channel(T)` carries over, with\n# `T : Share`\" - so a parallel task and a thread may capture one whose\n# values are themselves safe on two threads, and every operation below\n# is behind the scheduler's lock, which a park lets go of before it\n# switches. A wake of a fiber on another thread goes through its inbox\n# (`IyiScheduler.ready`). `select` is answered by this protocol — a node\n# per arm, one nonce, losers stale by arithmetic until the woken select\n# takes them out — at the bottom of the class.\nclass Channel(T)\n\n # Everyone parked is woken; what each wake means is read on the woken\n # side — a receiver finds the buffer, then `ChannelClosed`; a sender\n # finds its box still full.\n def close : Nil\n\n def closed? : Bool\n\n def new(capacity : Int32)\n\n def new\n\n # `select`'s arms park through these with one shared nonce; a plain\n # call parks with a fresh nonce of its own. *slot* is where a rendezvous\n # sender puts its value (`IyiWaiter#slot`). These and the two unlinks\n # below are public for the arms' sake and change the queues, so like\n # the `*_held` halves they are called with the scheduler's lock held.\n def park_receive_node(fiber : IyiFiber, nonce : UInt64, slot : UInt64) : IyiWaiter\n\n def park_send_node(fiber : IyiFiber, nonce : UInt64, value : T) : IyiSendNode(T)\n\n # :nodoc: - the scheduler's lock held, and `send_ready_held?` true. A\n # rendezvous puts the value in the parked receiver's own slot (`hand`).\n # It went into the buffer, and the send answered delivered on the word\n # of the wake: a select woken that way took another arm ready by then, a\n # receiver cancelled before it ran answered `Cancelled`, and either way\n # the value stayed in a channel of no capacity after its sender left. A\n # buffered send completes when the buffer takes it, so there the woken\n # receiver finds it in the buffer.\n def put_held(value : T) : Nil | ChannelClosed\n\n # The value sent without waiting: to a parked receiver, or into the\n # buffer; `ChannelClosed` once the channel is. A full channel has no\n # room, and asking is a bug the call panics over; `select` with an\n # `else` arm is the send that may not happen.\n def put_now(value : T) : Nil | ChannelClosed\n\n # A closed channel drains what was delivered before it refuses. A\n # rendezvous sender that finds this receive parked writes its value into\n # `got` and marks the node handed, and that is the answer even when the\n # task was cancelled before it ran: the sender was told it was taken.\n # A woken park that was handed nothing takes its node out of the queue,\n # so a cancelled one leaves nothing behind.\n def receive : T | ChannelClosed | Cancelled\n\n def receive_ready? : Bool\n\n # :nodoc: - the scheduler's lock held.\n def receive_ready_held? : Bool\n\n # Nothing happens on the arm `take_held` answers: `ChannelClosed` is\n # the refusal here, where a closed channel's buffer is spent.\n def receive_refused? : Bool\n\n def receive_select_action : IyiReceiveSelect(T)\n\n def send(value : T) : Nil | ChannelClosed | Cancelled\n\n def send_ready? : Bool\n\n # :nodoc: - the scheduler's lock held.\n def send_ready_held? : Bool\n\n def send_select_action(value : T) : IyiSendSelect(T)\n\n # :nodoc: - the scheduler's lock held, and `receive_ready_held?` true:\n # the buffer, then a parked sender's box, then the refusal.\n def take_held : T | ChannelClosed\n\n # The next value without waiting: the buffer, then a parked sender's\n # box, then `ChannelClosed`. On an empty open channel there is nothing\n # to take, and asking is a bug the call panics over; `select` with an\n # `else` arm is the receive that may find nothing.\n def take_now : T | ChannelClosed\n\n # Out of the receive queue from wherever it stands, in constant time,\n # and no longer linked anywhere; a node no queue holds is left as it\n # is. The queue is linked both ways because a select's losing arms\n # leave from the middle: they stayed until something walked them off\n # the head, so a loop selecting over a busy channel and an idle one\n # left a node in the idle one's queue for every park - 30 MiB kept\n # after two million rounds, freed only when the idle channel was used.\n def unlink_receiver(node : IyiWaiter) : Nil\n\n # The send queue's `unlink_receiver`.\n def unlink_sender(node : IyiSendNode(T)) : Nil\n\n # No arm ready answers -1, which the expansion's case meets with the\n # program's own `else`.\n def self.non_blocking_select(actions : U, bound : Int32) forall U\n\n # ── select — III.4.6, the library half of the expansion ────────────\n #\n # A `select` compiles to `%i, %v, %h = ::Channel.select({arm, arm}, b)`\n # and a case over the index; `non_blocking_select` when an `else` arm\n # exists. One def serves every arity, because a tuple's size is a\n # compile-time fact a macro walks. `%h` says the arm's operation\n # happened, and an arm nobody binds runs its body only then: it ran\n # unless its answer was an `Error`, so a `Channel(Boom)`'s receive took\n # the `Boom` and skipped the body. *bound* is the first arm that binds,\n # -1 for none, and a cancelled task's `Cancelled` goes to it, as that\n # arm's own primitive would have answered it.\n #\n # Every look and every act is under the scheduler's lock, one lock for\n # every channel an arm names, so no arm's channel changes between the\n # look that found it ready and the act; the park lets go of it.\n def self.select(actions : U, bound : Int32) forall U\nend\n" }, { "name": "ChannelClosed", @@ -814,7 +862,7 @@ }, { "name": "Pointer", - "surface": "# A raw address: `Pointer(T).malloc(n)` is `n` elements of untyped memory,\n# `[]` and `[]=` read and write them, nothing checks the bounds.\nstruct Pointer(T) < Value\n\n def +(offset : Int64) : self\n\n def -(other : self) : Int64\n\n def [](offset : Int32) : T\n\n def []=(offset : Int32, value : T) : T\n\n def address : UInt64\n\n # Bytes, not elements: every caller here is copying string contents.\n def copy_from(source : Pointer(T), count : Int32) : Nil\n\n def new\n\n def realloc(size : UInt64) : self\n\n def value : T\n\n def value=(value : T)\n\n def self.malloc(size : UInt64)\n\n def self.new(address : UInt64)\nend\n" + "surface": "# A raw address: `Pointer(T).malloc(n)` is `n` elements of untyped memory,\n# `[]` and `[]=` read and write them, nothing checks the bounds.\nstruct Pointer(T) < Value\n\n def +(offset : Int64) : self\n\n def -(other : self) : Int64\n\n def [](offset : Int32) : T\n\n def []=(offset : Int32, value : T) : T\n\n def address : UInt64\n\n # Bytes, not elements: every caller here is copying string contents. A\n # million a run (`copy_run`, prelude.iyi), and a check for preemption\n # between runs (SPEC.md III.4.13, part 9); one run or less, none.\n def copy_from(source : Pointer(T), count : Int32) : Nil\n\n def copy_run(source : Pointer(T), count : Int32) : Nil\n\n def new\n\n def realloc(size : UInt64) : self\n\n def value : T\n\n def value=(value : T)\n\n def self.malloc(size : UInt64)\n\n def self.new(address : UInt64)\nend\n" }, { "name": "Proc", diff --git a/records/wasm/arrays.wasm b/records/wasm/arrays.wasm index d35da8c..4349456 100755 Binary files a/records/wasm/arrays.wasm and b/records/wasm/arrays.wasm differ diff --git a/records/wasm/blocks.wasm b/records/wasm/blocks.wasm index a696a65..e119f0a 100755 Binary files a/records/wasm/blocks.wasm and b/records/wasm/blocks.wasm differ diff --git a/records/wasm/bounds.wasm b/records/wasm/bounds.wasm index 53930ad..0d6178a 100755 Binary files a/records/wasm/bounds.wasm and b/records/wasm/bounds.wasm differ diff --git a/records/wasm/case.wasm b/records/wasm/case.wasm index 4d2f4fc..2d2c723 100755 Binary files a/records/wasm/case.wasm and b/records/wasm/case.wasm differ diff --git a/records/wasm/classes.wasm b/records/wasm/classes.wasm index f26cd4d..cababa4 100755 Binary files a/records/wasm/classes.wasm and b/records/wasm/classes.wasm differ diff --git a/records/wasm/conditions.wasm b/records/wasm/conditions.wasm index 98d7c49..8b23f3e 100755 Binary files a/records/wasm/conditions.wasm and b/records/wasm/conditions.wasm differ diff --git a/records/wasm/constants.wasm b/records/wasm/constants.wasm index 60f9b34..c9b831f 100755 Binary files a/records/wasm/constants.wasm and b/records/wasm/constants.wasm differ diff --git a/records/wasm/conversions.wasm b/records/wasm/conversions.wasm index 86d948d..d25ef42 100755 Binary files a/records/wasm/conversions.wasm and b/records/wasm/conversions.wasm differ diff --git a/records/wasm/defaults.wasm b/records/wasm/defaults.wasm index 2e60ac9..2f65f7d 100755 Binary files a/records/wasm/defaults.wasm and b/records/wasm/defaults.wasm differ diff --git a/records/wasm/defer.wasm b/records/wasm/defer.wasm index ff0860b..82770bb 100755 Binary files a/records/wasm/defer.wasm and b/records/wasm/defer.wasm differ diff --git a/records/wasm/dispatch.wasm b/records/wasm/dispatch.wasm index f2c65b7..9102983 100755 Binary files a/records/wasm/dispatch.wasm and b/records/wasm/dispatch.wasm differ diff --git a/records/wasm/errors.wasm b/records/wasm/errors.wasm index dcf8f87..93cda47 100755 Binary files a/records/wasm/errors.wasm and b/records/wasm/errors.wasm differ diff --git a/records/wasm/exports.wasm b/records/wasm/exports.wasm index 4ca4a8f..b0bbb15 100755 Binary files a/records/wasm/exports.wasm and b/records/wasm/exports.wasm differ diff --git a/records/wasm/functions.wasm b/records/wasm/functions.wasm index 0f79fa0..2f99c47 100755 Binary files a/records/wasm/functions.wasm and b/records/wasm/functions.wasm differ diff --git a/records/wasm/generic-functions.wasm b/records/wasm/generic-functions.wasm index e4a545c..9941ac5 100755 Binary files a/records/wasm/generic-functions.wasm and b/records/wasm/generic-functions.wasm differ diff --git a/records/wasm/generic-types.wasm b/records/wasm/generic-types.wasm index 0bc64ce..21b0886 100755 Binary files a/records/wasm/generic-types.wasm and b/records/wasm/generic-types.wasm differ diff --git a/records/wasm/hashes.wasm b/records/wasm/hashes.wasm index fe45839..c1c1da5 100755 Binary files a/records/wasm/hashes.wasm and b/records/wasm/hashes.wasm differ diff --git a/records/wasm/hello.wasm b/records/wasm/hello.wasm index 905d488..774e2f3 100755 Binary files a/records/wasm/hello.wasm and b/records/wasm/hello.wasm differ diff --git a/records/wasm/iyi-basics.wasm b/records/wasm/iyi-basics.wasm index 0140c52..05b3b4f 100755 Binary files a/records/wasm/iyi-basics.wasm and b/records/wasm/iyi-basics.wasm differ diff --git a/records/wasm/iyi-calc.wasm b/records/wasm/iyi-calc.wasm index 3bdb1d1..cc49223 100755 Binary files a/records/wasm/iyi-calc.wasm and b/records/wasm/iyi-calc.wasm differ diff --git a/records/wasm/iyi-collections.wasm b/records/wasm/iyi-collections.wasm index 4665477..9c4b282 100755 Binary files a/records/wasm/iyi-collections.wasm and b/records/wasm/iyi-collections.wasm differ diff --git a/records/wasm/iyi-config.wasm b/records/wasm/iyi-config.wasm index 99a1162..953769c 100755 Binary files a/records/wasm/iyi-config.wasm and b/records/wasm/iyi-config.wasm differ diff --git a/records/wasm/iyi-derive.wasm b/records/wasm/iyi-derive.wasm index a68fedf..d3ee91b 100755 Binary files a/records/wasm/iyi-derive.wasm and b/records/wasm/iyi-derive.wasm differ diff --git a/records/wasm/iyi-enums.wasm b/records/wasm/iyi-enums.wasm index 13ca526..01d1844 100755 Binary files a/records/wasm/iyi-enums.wasm and b/records/wasm/iyi-enums.wasm differ diff --git a/records/wasm/iyi-errors.wasm b/records/wasm/iyi-errors.wasm index b4fc874..659d176 100755 Binary files a/records/wasm/iyi-errors.wasm and b/records/wasm/iyi-errors.wasm differ diff --git a/records/wasm/iyi-files.wasm b/records/wasm/iyi-files.wasm index 30999f1..424402a 100755 Binary files a/records/wasm/iyi-files.wasm and b/records/wasm/iyi-files.wasm differ diff --git a/records/wasm/iyi-format.wasm b/records/wasm/iyi-format.wasm index b8db2fb..305b60a 100755 Binary files a/records/wasm/iyi-format.wasm and b/records/wasm/iyi-format.wasm differ diff --git a/records/wasm/iyi-formatting.wasm b/records/wasm/iyi-formatting.wasm index 78737bb..6fe2a74 100755 Binary files a/records/wasm/iyi-formatting.wasm and b/records/wasm/iyi-formatting.wasm differ diff --git a/records/wasm/iyi-generics.wasm b/records/wasm/iyi-generics.wasm index 2e8de3a..24e0744 100755 Binary files a/records/wasm/iyi-generics.wasm and b/records/wasm/iyi-generics.wasm differ diff --git a/records/wasm/iyi-grid.wasm b/records/wasm/iyi-grid.wasm index cada76d..b0670e1 100755 Binary files a/records/wasm/iyi-grid.wasm and b/records/wasm/iyi-grid.wasm differ diff --git a/records/wasm/iyi-hello.wasm b/records/wasm/iyi-hello.wasm index c3623ae..7785a65 100755 Binary files a/records/wasm/iyi-hello.wasm and b/records/wasm/iyi-hello.wasm differ diff --git a/records/wasm/iyi-immutable.wasm b/records/wasm/iyi-immutable.wasm index 6897d96..270b0d8 100755 Binary files a/records/wasm/iyi-immutable.wasm and b/records/wasm/iyi-immutable.wasm differ diff --git a/records/wasm/iyi-init_order.wasm b/records/wasm/iyi-init_order.wasm index 08f8594..187607c 100755 Binary files a/records/wasm/iyi-init_order.wasm and b/records/wasm/iyi-init_order.wasm differ diff --git a/records/wasm/iyi-inventory.wasm b/records/wasm/iyi-inventory.wasm index bbcabf6..1c87b21 100755 Binary files a/records/wasm/iyi-inventory.wasm and b/records/wasm/iyi-inventory.wasm differ diff --git a/records/wasm/iyi-io.wasm b/records/wasm/iyi-io.wasm index 988f46c..ee26d9f 100755 Binary files a/records/wasm/iyi-io.wasm and b/records/wasm/iyi-io.wasm differ diff --git a/records/wasm/iyi-modules.wasm b/records/wasm/iyi-modules.wasm index 6c46435..8097637 100755 Binary files a/records/wasm/iyi-modules.wasm and b/records/wasm/iyi-modules.wasm differ diff --git a/records/wasm/iyi-sessions.wasm b/records/wasm/iyi-sessions.wasm index 516b253..ef25f00 100755 Binary files a/records/wasm/iyi-sessions.wasm and b/records/wasm/iyi-sessions.wasm differ diff --git a/records/wasm/iyi-shapes.wasm b/records/wasm/iyi-shapes.wasm index 145d04e..a2b20c5 100755 Binary files a/records/wasm/iyi-shapes.wasm and b/records/wasm/iyi-shapes.wasm differ diff --git a/records/wasm/iyi-socket.wasm b/records/wasm/iyi-socket.wasm index e34f865..15e5564 100755 Binary files a/records/wasm/iyi-socket.wasm and b/records/wasm/iyi-socket.wasm differ diff --git a/records/wasm/iyi-std_iterator.wasm b/records/wasm/iyi-std_iterator.wasm index a13b71d..14a1765 100755 Binary files a/records/wasm/iyi-std_iterator.wasm and b/records/wasm/iyi-std_iterator.wasm differ diff --git a/records/wasm/iyi-std_text.wasm b/records/wasm/iyi-std_text.wasm index 9ad8c15..173bafb 100755 Binary files a/records/wasm/iyi-std_text.wasm and b/records/wasm/iyi-std_text.wasm differ diff --git a/records/wasm/iyi-std_time.wasm b/records/wasm/iyi-std_time.wasm index 7601b55..45b2667 100755 Binary files a/records/wasm/iyi-std_time.wasm and b/records/wasm/iyi-std_time.wasm differ diff --git a/records/wasm/iyi-visited.wasm b/records/wasm/iyi-visited.wasm index 4bd1edd..1497659 100755 Binary files a/records/wasm/iyi-visited.wasm and b/records/wasm/iyi-visited.wasm differ diff --git a/records/wasm/iyi-webapp.wasm b/records/wasm/iyi-webapp.wasm index 8da78ed..d1b1f8e 100755 Binary files a/records/wasm/iyi-webapp.wasm and b/records/wasm/iyi-webapp.wasm differ diff --git a/records/wasm/loops.wasm b/records/wasm/loops.wasm index d3f1375..9cb86d0 100755 Binary files a/records/wasm/loops.wasm and b/records/wasm/loops.wasm differ diff --git a/records/wasm/manifest.json b/records/wasm/manifest.json index 081edf5..cb43131 100644 --- a/records/wasm/manifest.json +++ b/records/wasm/manifest.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.18.0 (built on Crystal 1.22.0-dev [2a9653052] (2026-10-18))", - "commit": "2a9653052ab76f9d8084da47e059273f6ef45228", + "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", + "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:wasm", - "when": "2026-10-19T11:14:40.963Z" + "when": "2026-11-02T07:18:22.704Z" }, "samples": [ { @@ -13,8 +13,8 @@ "path": "samples/tour/hello.iyi", "sourceSha256": "676f8372f2cd5f8941b1dcd0ccb167ee6f6f27c1de4a77cef3af0372c323134d", "wasm": "hello.wasm", - "bytes": 122096, - "sha256": "d8d85975c2068cc2da1fc65f6399f9eaec1a1f386d9a5171b230d74294070a11", + "bytes": 123087, + "sha256": "b2f99b569507b06c72ac45d9afb48ddf6342bd7fcb333a857392aa6318f5c65b", "exitCode": 0, "nativeStdout": "Hello Türkiye!\n", "wasmStdout": "Hello Türkiye!\n", @@ -27,8 +27,8 @@ "path": "samples/tour/modules.iyi", "sourceSha256": "c908a80d8c5a2c3f42bc6c90cd9d9a822a4c0b077cbd4713cc627f2d9f4912c0", "wasm": "modules.wasm", - "bytes": 123851, - "sha256": "8d4664b217911e3ba42e798ba1e038afe923b32c470228a055d94a372a8ea0f8", + "bytes": 124842, + "sha256": "3148c581ed9ffba3999fec179835b1583eeb72ad033dd7ef3c5d7bd3fb110eb3", "exitCode": 0, "nativeStdout": "7\n", "wasmStdout": "7\n", @@ -41,8 +41,8 @@ "path": "samples/tour/exports.iyi", "sourceSha256": "1d341c749781b957ea8e5729a226f84852f816c914224de63771b5a03bfb6153", "wasm": "exports.wasm", - "bytes": 123847, - "sha256": "bc2e1341183ac2e7c50ef8f5e8263221df78b087a6ae1f9937824c6abbc4b30d", + "bytes": 124838, + "sha256": "c81410a6f19ab5fd8abdb5888a2eebb027f205f501ac9e1acc1a0211dbc0e612", "exitCode": 0, "nativeStdout": "7\n", "wasmStdout": "7\n", @@ -55,8 +55,8 @@ "path": "samples/tour/variables.iyi", "sourceSha256": "672aec63ec81d0a8dc9cc773688d853a6b9a246618705a4cfa782191efe3fea5", "wasm": "variables.wasm", - "bytes": 155118, - "sha256": "1cd3534d7b4f1a6460822ace0f288e030693a548294515b3c64d0c10c7c1db19", + "bytes": 156061, + "sha256": "e923e39c408a3c153216eff5469350c49d73ac7dd3e6074a739f4355d296e989", "exitCode": 0, "nativeStdout": "iyi\n2026\n0.75\ntrue\n2027\n10\n", "wasmStdout": "iyi\n2026\n0.75\ntrue\n2027\n10\n", @@ -69,8 +69,8 @@ "path": "samples/tour/types.iyi", "sourceSha256": "bbd35199bdfae3aa05673d869e512e1713213fa1aeef929e6477fec7ff6e4464", "wasm": "types.wasm", - "bytes": 157866, - "sha256": "0ee936e81b05606b1ffc61da3735003df0f291027aee3d0c88d017b7934fdfb0", + "bytes": 158793, + "sha256": "02ce0e08a24e6218fc6820eff9835b0fefdf204fa54e330889fdc94c5185045a", "exitCode": 0, "nativeStdout": "Int32\nInt64\nFloat64\nBool\nChar\nString\n3\n3.5\n1\n1024\n", "wasmStdout": "Int32\nInt64\nFloat64\nBool\nChar\nString\n3\n3.5\n1\n1024\n", @@ -83,8 +83,8 @@ "path": "samples/tour/conversions.iyi", "sourceSha256": "1b8cf5e80ec2dbe3ce9b207d12491e7b24761caf1eb2797a098f68f36e933b81", "wasm": "conversions.wasm", - "bytes": 186501, - "sha256": "f219dadcc4543f3a531c773c11b7ffa06aa60a998cdd90f69c89b42fc427a634", + "bytes": 187416, + "sha256": "2c054e3c89a1a116d5885db0d3e6f8da9d20e82bba44d23469f58802c84580b8", "exitCode": 0, "nativeStdout": "42.0\n42!\n9\n10.0\n18\n17\n\n65\nB\n", "wasmStdout": "42.0\n42!\n9\n10.0\n18\n17\n\n65\nB\n", @@ -97,8 +97,8 @@ "path": "samples/tour/constants.iyi", "sourceSha256": "e5e15868dc555999be7b1c15e2349147be0e335a5b45792b893aa9c9d3e139ba", "wasm": "constants.wasm", - "bytes": 159500, - "sha256": "4144f9bbbf3eb25d55eb06522c04764fc40258ff9281cfc4f957c767affece7e", + "bytes": 160443, + "sha256": "899cc10df244def0c0b41d12d716abcc61c467b2351c736900923f81ec6784d9", "exitCode": 0, "nativeStdout": "12.57\nMerhaba, dünya\ntrue\n", "wasmStdout": "12.57\nMerhaba, dünya\ntrue\n", @@ -111,8 +111,8 @@ "path": "samples/tour/functions.iyi", "sourceSha256": "1261f0c7c47c4f9469358362a6c52a84cd6450a6e4347e1d7ff9eba09a88f89a", "wasm": "functions.wasm", - "bytes": 124193, - "sha256": "9b1e589f87c9c4fa07a1c1b3376925f65ec03c9c72d930800dad450e40902072", + "bytes": 125184, + "sha256": "0d6e13b48be440c0b4158538681d8319bfd05bd8c9f843a6be119703e26b4470", "exitCode": 0, "nativeStdout": "5\nnegative\nHello, Ayşe!\nSelam, Ayşe!\n", "wasmStdout": "5\nnegative\nHello, Ayşe!\nSelam, Ayşe!\n", @@ -125,8 +125,8 @@ "path": "samples/tour/returns.iyi", "sourceSha256": "6b655fcd036d722550a028424fe4719cdfb89383fed7607e260c46fbf0fff932", "wasm": "returns.wasm", - "bytes": 126434, - "sha256": "f3173fd034b6d9b95fa858fd03f6cac7d6b2538704001c478d8170bd92746657", + "bytes": 127402, + "sha256": "a562301ac6ac68f527583c80c033aa83dc650e65fb4c2d8eaa80ef74cc359226", "exitCode": 0, "nativeStdout": "3\n2\n2\n1\n6\n\n", "wasmStdout": "3\n2\n2\n1\n6\n\n", @@ -139,8 +139,8 @@ "path": "samples/tour/nil.iyi", "sourceSha256": "a559c045cfef442a9927954673d8ad5fc61f610a0908950bcdd79182853a4e04", "wasm": "nil.wasm", - "bytes": 124485, - "sha256": "bd9d12d8fbedd7ca6102cd3ea69f5988422d815e046877225590a3b76a436410", + "bytes": 125476, + "sha256": "b910ce39a42b38d673f9888e725efd5c4fe263916392802f487c9a8702e54b6a", "exitCode": 0, "nativeStdout": "Ada\n\nADA\nnobody\n", "wasmStdout": "Ada\n\nADA\nnobody\n", @@ -153,8 +153,8 @@ "path": "samples/tour/loops.iyi", "sourceSha256": "18f56f89ce4e1bcf3f1c2abff8e3539b5bde85135ec978931583dddd7882992c", "wasm": "loops.wasm", - "bytes": 125458, - "sha256": "9d56bbd1f00c959a14fa6d802af486710261b1422e6816ec5c3f9ae6f5ffe9e9", + "bytes": 126424, + "sha256": "70b84276aadfc16114677bbf4beba2def27106595e68c4c125f60256869979bf", "exitCode": 0, "nativeStdout": "1\n2\n3\nround 0\nround 1\nround 2\n64\ncount 1\ncount 3\ncount 4\n", "wasmStdout": "1\n2\n3\nround 0\nround 1\nround 2\n64\ncount 1\ncount 3\ncount 4\n", @@ -167,8 +167,8 @@ "path": "samples/tour/ranges.iyi", "sourceSha256": "418036dc21744938451d1210eeceaca1931d0edb5329f3f8e801f626fa234fd6", "wasm": "ranges.wasm", - "bytes": 139692, - "sha256": "771193e793116140d70d1d077cce45f3651e07f169bd880379783a1a778ebfb3", + "bytes": 141760, + "sha256": "4765a3a9662c3d9d9bf6ffe04e86ec95481c9da2792ea4cf70e06bdd4886f750", "exitCode": 0, "nativeStdout": "1\n2\n3\n4\n5\n1\n2\n3\n4\n[1, 4, 9, 16, 25]\n5050\n[3, 6, 9]\nadult\n", "wasmStdout": "1\n2\n3\n4\n5\n1\n2\n3\n4\n[1, 4, 9, 16, 25]\n5050\n[3, 6, 9]\nadult\n", @@ -181,8 +181,8 @@ "path": "samples/tour/conditions.iyi", "sourceSha256": "8ed0a951d7e760d2bb95c784b20c3e59771080e0578a2cadf0b1bdf41fd2eed2", "wasm": "conditions.wasm", - "bytes": 123837, - "sha256": "c6147bb1a69a4c1ead224fea9e2c7d40450282836f87804fa722c30b0b9106d1", + "bytes": 124828, + "sha256": "0e3ae9ecb6b7748bf36c6736dd7fabe8f95e8f86c67870ca7d14b68695acb3fe", "exitCode": 0, "nativeStdout": "warm\nabove freezing\nnice day\nwarm\nodd\ntrue\nfalse\n", "wasmStdout": "warm\nabove freezing\nnice day\nwarm\nodd\ntrue\nfalse\n", @@ -195,8 +195,8 @@ "path": "samples/tour/case.iyi", "sourceSha256": "71de25c31bbd4ad83f406525df3f263bbb18c8dcab00eee4dba6baa166409ffb", "wasm": "case.wasm", - "bytes": 126815, - "sha256": "76446fb365856668f336a7176ca70b2a65b21682fa9a2504d1348ca6e7e14c4e", + "bytes": 127806, + "sha256": "33112c07e0f4db76abd152f20e8e9d857b8b80397a64fe77035dc7749dbd0bf8", "exitCode": 0, "nativeStdout": "weekend\nalmost\nweekday\nB\na number: 2\n", "wasmStdout": "weekend\nalmost\nweekday\nB\na number: 2\n", @@ -209,8 +209,8 @@ "path": "samples/tour/defer.iyi", "sourceSha256": "c39edacef8695b8aa0170cd169373a19ece2ec4fcf204439ef48e22aadcf06f2", "wasm": "defer.wasm", - "bytes": 127140, - "sha256": "ba4bfce71cbd6e63c0ce10685d3d3f302feeb5787002753eaa7076689842e038", + "bytes": 128138, + "sha256": "edbb73210e8210678d0e93205a1f4cbb77e54b466b842a07289403329871a2a1", "exitCode": 0, "nativeStdout": "open\nworking\nclose\n42\nfirst\nsecond\nthird\nturn 0\ndone with 0\nturn 1\ndone with 1\n", "wasmStdout": "open\nworking\nclose\n42\nfirst\nsecond\nthird\nturn 0\ndone with 0\nturn 1\ndone with 1\n", @@ -223,8 +223,8 @@ "path": "samples/tour/structs.iyi", "sourceSha256": "2354525f27753b7a4bdef384dc4c0c6507c12fe845e9d2927e7aec0f62c2814c", "wasm": "structs.wasm", - "bytes": 124581, - "sha256": "bce492322c202429148d06972aba6bde47e429c04c4be160ab5ade6c9533b6e7", + "bytes": 125572, + "sha256": "ab6c2b2e09b57aeac3f2ac3aa4384b8c265054c056ea511f5672e479e7fe46f6", "exitCode": 0, "nativeStdout": "1\n(4, 6)\n(1, 2)\n", "wasmStdout": "1\n(4, 6)\n(1, 2)\n", @@ -237,8 +237,8 @@ "path": "samples/tour/classes.iyi", "sourceSha256": "cfc6709102991d8da3357f3f6230408b0c79e8bf7199e63f8b40a272c65a2f6a", "wasm": "classes.wasm", - "bytes": 123407, - "sha256": "6fdb00be51c3c8a8947ee039d7eb1f064bec2b184ffae7f4804b7dc64003e7d3", + "bytes": 124398, + "sha256": "f09ca5018601c91f292d49427f0712b88af0b418a0072d7b6ef67da83d613152", "exitCode": 0, "nativeStdout": "2\n0\n", "wasmStdout": "2\n0\n", @@ -251,8 +251,8 @@ "path": "samples/tour/arrays.iyi", "sourceSha256": "7f4841af80023b70c3e6f897ec7d5ba82001acd91a905e4772cc6f6f0dec0aa9", "wasm": "arrays.wasm", - "bytes": 143866, - "sha256": "552e337e429793603390dd18026cf9082c863aa255bf608a5a366d8466057c59", + "bytes": 145881, + "sha256": "a936176105b1cf1b823a04753d93cb4cd86cb11a3dd7456c13830461b2a01f5e", "exitCode": 0, "nativeStdout": "5\n3\n3\n5\n[3, 1, 4, 1, 5, 9]\ntrue\n23\n[1, 1, 3, 4, 5, 9]\n[3, 4, 5, 9]\n[30, 10, 40, 10, 50, 90]\n23\nAyşe, Mehmet\n1. Ayşe\n2. Mehmet\n", "wasmStdout": "5\n3\n3\n5\n[3, 1, 4, 1, 5, 9]\ntrue\n23\n[1, 1, 3, 4, 5, 9]\n[3, 4, 5, 9]\n[30, 10, 40, 10, 50, 90]\n23\nAyşe, Mehmet\n1. Ayşe\n2. Mehmet\n", @@ -265,8 +265,8 @@ "path": "samples/tour/hashes.iyi", "sourceSha256": "1b5fb76d25d7f5979248800422d871da36d4c2d0b829ec9c311c7b200beb167b", "wasm": "hashes.wasm", - "bytes": 163805, - "sha256": "e7bdc623f09de975cab17d4f7467b33343cb6128407d718514b25bf719b3b4a7", + "bytes": 165179, + "sha256": "c57e6e01912536496dc25cf385feafc8fba8317663dc78b5f84e12b99cf3da27", "exitCode": 0, "nativeStdout": "31\n2\n[\"Ayşe\", \"Mehmet\", \"Zeynep\"]\n\nfalse\nAyşe is 31\nMehmet is 27\nZeynep is 40\n{\"a\" => 2, \"b\" => 1}\n", "wasmStdout": "31\n2\n[\"Ayşe\", \"Mehmet\", \"Zeynep\"]\n\nfalse\nAyşe is 31\nMehmet is 27\nZeynep is 40\n{\"a\" => 2, \"b\" => 1}\n", @@ -279,8 +279,8 @@ "path": "samples/tour/tuples.iyi", "sourceSha256": "75f2414a08f54a9d8ecc53d7b0621d8c4906a3f39d9a673dd1b8d2f219b4856b", "wasm": "tuples.wasm", - "bytes": 126788, - "sha256": "57086980e47e4c49ae6e0466a9479ec2f6624fd9923890e0e8d6f2faf1c32e61", + "bytes": 127759, + "sha256": "789f6da9fbc0ce1ce352cade9019cb9b17a85cf3d360dc0aca95cbeaf721eb19", "exitCode": 0, "nativeStdout": "1\none\n1 is one\nAyşe\n31\n2..9\n", "wasmStdout": "1\none\n1 is one\nAyşe\n31\n2..9\n", @@ -293,8 +293,8 @@ "path": "samples/tour/blocks.iyi", "sourceSha256": "f2ae3fc23344a9f94f896942b8609956979599387e313e98d49cb4977ba9dd17", "wasm": "blocks.wasm", - "bytes": 126911, - "sha256": "62b4d9b570f5485aa2d319fd5167a311a8e1c76e49d315ea05385272d6d20cd3", + "bytes": 127866, + "sha256": "b024efb9529caf394a9b41c630abb009f69a35dbc5d019ecbf8f51dc9df306d7", "exitCode": 0, "nativeStdout": "step 0\nstep 1\nstep 2\n6\n49\n2\n105\n", "wasmStdout": "step 0\nstep 1\nstep 2\n6\n49\n2\n105\n", @@ -307,8 +307,8 @@ "path": "samples/tour/methods.iyi", "sourceSha256": "f4fb9b8be821d24f79148d110edb8f6bd36239bab164d7be45bc4d3da29eba7a", "wasm": "methods.wasm", - "bytes": 160134, - "sha256": "bf95ec7eabc12af0182e97029cb9037daa18502038b4044f39253ddfbb91155c", + "bytes": 161051, + "sha256": "900fd19677f0e65142dd426d78cfe686ca7c4f60767cc3d9854549ccded70a0e", "exitCode": 0, "nativeStdout": "12.0\nfalse\n48.0\nIYI\n4.0\n6\n", "wasmStdout": "12.0\nfalse\n48.0\nIYI\n4.0\n6\n", @@ -321,8 +321,8 @@ "path": "samples/tour/traits.iyi", "sourceSha256": "98aaeb457da3a65d50c3ce5402f3378e431010d50122396f9a353b4d958fc8a8", "wasm": "traits.wasm", - "bytes": 159592, - "sha256": "ba83810977ebb13f0be4fa8375255b8d6a4c4ba96353f42512401ea06d059f61", + "bytes": 160535, + "sha256": "d414044f7f0e6ff94bc7b5ade6b095c32c968333c5c7e49acd2c8ea710ccd92e", "exitCode": 0, "nativeStdout": "3.14\n4.0\n", "wasmStdout": "3.14\n4.0\n", @@ -335,8 +335,8 @@ "path": "samples/tour/dispatch.iyi", "sourceSha256": "e9a5985790ff7c52c42846b394872bb18ac9040abfd59e9453d4ac71747fcfb9", "wasm": "dispatch.wasm", - "bytes": 134609, - "sha256": "dcd380f33eb32c1c484ae4dab221e69d43fa01a0de05431095481d8ee8ec95bd", + "bytes": 136069, + "sha256": "c619d9328cf1a563769a23b1fa9b75fdcd3e3432c5a78aebbd6590f77a587927", "exitCode": 0, "nativeStdout": "hav\nmiyav\nhav miyav\n", "wasmStdout": "hav\nmiyav\nhav miyav\n", @@ -349,8 +349,8 @@ "path": "samples/tour/defaults.iyi", "sourceSha256": "7e438eff3837000a1551861b7107a2a4f7f6f78988a283fec3174429a1f20844", "wasm": "defaults.wasm", - "bytes": 126119, - "sha256": "cbd2abebebe224d22ced7aa7d06c9c5e379ac622ff64cca62be365706f21ef1c", + "bytes": 127100, + "sha256": "c874918891eb024609cbfe95b478018e14ae1f53491ab22d3675e8bf8cfbf01e", "exitCode": 0, "nativeStdout": "Merhaba, Ada!\nMERHABA, ADA!\nBEEP unit 7\nBEEP UNIT 7\n", "wasmStdout": "Merhaba, Ada!\nMERHABA, ADA!\nBEEP unit 7\nBEEP UNIT 7\n", @@ -363,8 +363,8 @@ "path": "samples/tour/unions.iyi", "sourceSha256": "61cb534c8687c14b7392657f859b165eb063dc892ae64429ccc59dd241441466", "wasm": "unions.wasm", - "bytes": 134222, - "sha256": "bac598ec5c9bfb7d30c535eddbd7d2b3284d6dc72389791233ae57a0871ae330", + "bytes": 135190, + "sha256": "9b658342abd216814b620939a4d3506adf316add17b205b0d7725ed2c3e5b7e4", "exitCode": 0, "nativeStdout": "number 24\nproblem, not a number: x\nnumber 14\n6\n", "wasmStdout": "number 24\nproblem, not a number: x\nnumber 14\n6\n", @@ -377,8 +377,8 @@ "path": "samples/tour/errors.iyi", "sourceSha256": "af4c28f720136cd6c8b935273fa769fe234b55716da4abed70196e56d8b9faf7", "wasm": "errors.wasm", - "bytes": 159584, - "sha256": "9f4bab334b32c9ca883b781fa6647f8ef6f5cd4dabf9930999f42bac57e1c1a8", + "bytes": 160371, + "sha256": "bf4685973716b806c46817d9091b6ec0861e06f247781223e6991ded1e41c979", "exitCode": 0, "nativeStdout": "2027\nno such key: missing\ndefault\niyi\n", "wasmStdout": "2027\nno such key: missing\ndefault\niyi\n", @@ -391,8 +391,8 @@ "path": "samples/tour/strings.iyi", "sourceSha256": "704f192ecd578a9847d911563129b559b5b04cc276327f8de24fd1502565f542", "wasm": "strings.wasm", - "bytes": 159121, - "sha256": "3f87fc3881e0a2d3674c0d8c0e1ae9b6abd5dbea32c3aca8aa3c95e2b789331a", + "bytes": 160621, + "sha256": "6af479f7f4dd2febc2a18f5aed837b172b468b80f22e70f01b29d736bf5c54bc", "exitCode": 0, "nativeStdout": "10\nHELLO, IYI\nhello, iyi\ntrue\nHello, iyi!\n[\"Hello,\", \"iyi\"]\n2 and true and [1, 2]\n12,05 TL\ntotal: 12,05 TL\nitem 12\ntotal 99\n", "wasmStdout": "10\nHELLO, IYI\nhello, iyi\ntrue\nHello, iyi!\n[\"Hello,\", \"iyi\"]\n2 and true and [1, 2]\n12,05 TL\ntotal: 12,05 TL\nitem 12\ntotal 99\n", @@ -405,8 +405,8 @@ "path": "samples/tour/generic-functions.iyi", "sourceSha256": "2b0ace59365d9bb35c904102d1131a95f5787113ba585975a819c6a354666e7f", "wasm": "generic-functions.wasm", - "bytes": 150801, - "sha256": "4d9b0b77e65e3e8b700d72a4030b211f3a1d137ff4e029873414c5eb5537efe8", + "bytes": 151699, + "sha256": "46473ac6701e53a583ce021c8b0490161d086e01334c3b26f3088e92c98655be", "exitCode": 0, "nativeStdout": "10\na\n{\"one\", 1}\n101\n", "wasmStdout": "10\na\n{\"one\", 1}\n101\n", @@ -419,8 +419,8 @@ "path": "samples/tour/generic-types.iyi", "sourceSha256": "fe5f0b5626ef062f7af44196125d3b50d1914788fb1416d980d10e22f872aa8f", "wasm": "generic-types.wasm", - "bytes": 133417, - "sha256": "3e14278baa5d11be7f9055e13e565dd13b637beda249467bd0fae79dc94952f2", + "bytes": 135597, + "sha256": "5c3cfeed954628eb61366e149ad8b96558faaa70feb6b7ce63588796936c9bcc", "exitCode": 0, "nativeStdout": "2\n2\n1\n\niyi\na stack of 1\na stack of 0\n", "wasmStdout": "2\n2\n1\n\niyi\na stack of 1\na stack of 0\n", @@ -433,8 +433,8 @@ "path": "samples/tour/bounds.iyi", "sourceSha256": "cd5324bcc2756eef8d4ebeca406c61ecf12cfd5898b26a01ec8fa4fd1ef64d26", "wasm": "bounds.wasm", - "bytes": 128615, - "sha256": "b684d251752741a006601e38fa1049a9e3cd4f19ff213c50dd6941710297812c", + "bytes": 129541, + "sha256": "fdc11a3ae994b9bd087d03c63a8b98c391ff7a326cc8124b5d627a553d9211fa", "exitCode": 0, "nativeStdout": "200\n45\n30\n", "wasmStdout": "200\n45\n30\n", @@ -447,8 +447,8 @@ "path": "samples/iyi/basics.iyi", "sourceSha256": "2fab386bd172c15319db0a4e553008d7ae7114fd8cdef72e7a48a850c301c72c", "wasm": "iyi-basics.wasm", - "bytes": 209967, - "sha256": "c58a25d4c6471c5bdfae499ceff4528377a6b87dc5042dc16cf12e650ca29507", + "bytes": 213443, + "sha256": "1ceed0e4827fa6b8c8445d7c20558d0b0c304ceb052ecfc4dd3ce48ba6e3e625", "exitCode": 0, "nativeStdout": "55\n1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n3\n4,5,9,6\ntrue\n1,1,2,3,4,5,6,9\n3,1,4,1,5,9,2,6\n1,1,2,3,4,5,6,9\n31\n31\nhello, world\ntrue\n12\nKhoor, Zruog\n3\n3\n{\"a\" => 3, \"b\" => 1, \"c\" => 1}\n14\nthe 5\ncat 2\nsat 2\n[\"cat\", \"end\"]\n", "wasmStdout": "55\n1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n3\n4,5,9,6\ntrue\n1,1,2,3,4,5,6,9\n3,1,4,1,5,9,2,6\n1,1,2,3,4,5,6,9\n31\n31\nhello, world\ntrue\n12\nKhoor, Zruog\n3\n3\n{\"a\" => 3, \"b\" => 1, \"c\" => 1}\n14\nthe 5\ncat 2\nsat 2\n[\"cat\", \"end\"]\n", @@ -459,10 +459,10 @@ "id": "iyi/calc", "set": "iyi", "path": "samples/iyi/calc.iyi", - "sourceSha256": "3213a69b6f922b4d5ee4ac1ffb5696123087544e8fc503f4622c4ad7214e24a7", + "sourceSha256": "7310f0e547c6ec9632bcf318538cd8778d8c32409822dafd3b98c43fcbf4e614", "wasm": "iyi-calc.wasm", - "bytes": 198605, - "sha256": "1aeedad67db85314146f76bffa5e4d3e18d1780032735d26b5064b5d34df307e", + "bytes": 200618, + "sha256": "aa2cfc22916be0e807cad21a8d95de21cb0c097b45add05dba340ceafc629503", "exitCode": 0, "nativeStdout": "64\n", "wasmStdout": "64\n", @@ -475,8 +475,8 @@ "path": "samples/iyi/collections.iyi", "sourceSha256": "9b8bb4c0bf703238a08afe44995207bb1cb6292578a7d255b5f3ece0c2a80213", "wasm": "iyi-collections.wasm", - "bytes": 225717, - "sha256": "5bd53b43df4d81866f367608f26d432d91994971f3f11943508d8b0efe526c5d", + "bytes": 228588, + "sha256": "36964f3cc2d2ea02d38a0564a8ef13256bfc6ca1af838aa79dbb2b937b95d6e9", "exitCode": 0, "nativeStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", "wasmStdout": "to_a 3,1,4,1,5\nsize 5\nempty? false true\nall? true\nany? true\nnone? true\none? true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect 3,4,5\nreject 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_index 0,1,2,3,4\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax?/max 5 5\nmin?/min 1 1\nsum/product 14 60\nsum of nothing 0\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\nindex_by fig\neach_slice 3+1 4+1 5\neach_cons 3+1 1+4 4+1 1+5\nchunk_while 3 1+4 1+5\nfirst(n) 3,1,4\ncompact_map 30,40,50\nfind_value big\naccumulate 0,3,4,8,9,14\nreduce? 14\nin_groups_of 3+1 4+1 5+0\nmin_by fig\nmax_of/min_of 4 3\nminmax 1,5\ntally_by 3\nsum_by 15\nzip 3pear 1fig 4plum 1kiwi\ncycle 1,2,1,2\nas a type 5 items / 4 items\n", @@ -489,8 +489,8 @@ "path": "samples/iyi/config.iyi", "sourceSha256": "e1c60e92bb4150097bed4d7986dc678380776ef09fc95ff03b278746ff121c10", "wasm": "iyi-config.wasm", - "bytes": 175995, - "sha256": "ae5cc6635a089a05372b0ba1e2c5c29e487f7f625fba8e19933579080b412e79", + "bytes": 177376, + "sha256": "05857dcac41717b838f6759d649649fd76b56b6ca844daba07e794814b1b39c7", "exitCode": 0, "nativeStdout": "{\"name\" => \"iyi\", \"port\" => \"8080\", \"mode\" => \"fast\"}\n8081\nname, port, mode\nerror: line 2: no '=' in \"broken line\"\n[\"a\", \"\", \"b\"]\n[\"one\", \"two\"]\n\"padded\"\ntrue\n", "wasmStdout": "{\"name\" => \"iyi\", \"port\" => \"8080\", \"mode\" => \"fast\"}\n8081\nname, port, mode\nerror: line 2: no '=' in \"broken line\"\n[\"a\", \"\", \"b\"]\n[\"one\", \"two\"]\n\"padded\"\ntrue\n", @@ -503,8 +503,8 @@ "path": "samples/iyi/derive.iyi", "sourceSha256": "ab33030ac91db9cddf89e68f39e93fe96296f028f313e6e3c25afa76851aa32a", "wasm": "iyi-derive.wasm", - "bytes": 127243, - "sha256": "843198831452d7035c9f99138fb988ddd3adb33f32cd938db2f46b7f770666e4", + "bytes": 128268, + "sha256": "158451a926a6df41b90c175e6f8a6864386a9f26001a5fab8d1e88ee404dfedd", "exitCode": 0, "nativeStdout": "Point(@x=3 @y=4 )\nSpan(@from=1 @to=9 @label=range )\nTag(@label=Std::Derives::Name @count=2 )\n@label= \n", "wasmStdout": "Point(@x=3 @y=4 )\nSpan(@from=1 @to=9 @label=range )\nTag(@label=Std::Derives::Name @count=2 )\n@label= \n", @@ -517,8 +517,8 @@ "path": "samples/iyi/enums.iyi", "sourceSha256": "98b275c766e680a4adc21a3fc80c18b94e954317dd4668a4b303fcbeea8c0c5d", "wasm": "iyi-enums.wasm", - "bytes": 149063, - "sha256": "085e1a645d59ff3824ec459d635f35cd97ee9e66968b1a2d9d761e00f1f7f625", + "bytes": 151146, + "sha256": "61a95872438a7e5b64b38c0d2d03bec8dadea2d309c927af0ea5836a6b533418", "exitCode": 0, "nativeStdout": "level: Warn\nspelled: Error\nsame: true\nother: false\nverdict: loud\nwarn? true\nerror? false\nat least info: true\nbelow error: true\nDebug Info Warn Error \nnames: 4\nparsed: Error\nlenient: Warn\nunknown: nil\nvalue: 3\nfrom 1: Info\nfrom 9: nil\nmode: Read | Write\nread: true\nappend: false\nnone: None\nmasked: Write\nwarns: 2\nerrors: 1\n", "wasmStdout": "level: Warn\nspelled: Error\nsame: true\nother: false\nverdict: loud\nwarn? true\nerror? false\nat least info: true\nbelow error: true\nDebug Info Warn Error \nnames: 4\nparsed: Error\nlenient: Warn\nunknown: nil\nvalue: 3\nfrom 1: Info\nfrom 9: nil\nmode: Read | Write\nread: true\nappend: false\nnone: None\nmasked: Write\nwarns: 2\nerrors: 1\n", @@ -531,8 +531,8 @@ "path": "samples/iyi/errors.iyi", "sourceSha256": "89e937ddfc973d79b5f00c8ea3b81315a68b5c2c6cf4d1bac4172a3da1b79bd0", "wasm": "iyi-errors.wasm", - "bytes": 137637, - "sha256": "8b4619f04af492b2207666c584aded0fcdefabd8ef0d206e021d40b2354a4573", + "bytes": 138685, + "sha256": "568f8fd704e78223ce04ae518ea433b633c34de8620851cc9bd85012d8c8a353", "exitCode": 0, "nativeStdout": "read 12\nio: no such file: missing\nparse: not a number: twelve\nor: 12\nor: 0\nor: 0\nor: -1\npanic: 12\nsuccess:\n open outer\n open inner\n close inner\n close outer\n12\npropagating:\n open outer\n close outer\n-1\n iteration 0\n iteration 0 closed\n iteration 1\n iteration 1 closed\n", "wasmStdout": "read 12\nio: no such file: missing\nparse: not a number: twelve\nor: 12\nor: 0\nor: 0\nor: -1\npanic: 12\nsuccess:\n open outer\n open inner\n close inner\n close outer\n12\npropagating:\n open outer\n close outer\n-1\n iteration 0\n iteration 0 closed\n iteration 1\n iteration 1 closed\n", @@ -545,8 +545,8 @@ "path": "samples/iyi/files.iyi", "sourceSha256": "aaa45bbe135b26d2c882122da7a4fe69df4bcba517a6f8ed57ba0101eda8ba69", "wasm": "iyi-files.wasm", - "bytes": 124173, - "sha256": "36b49dd306972eded05da6ebc001ecbefc37901f09d4551941a495d3a22dff69", + "bytes": 125156, + "sha256": "2b85382b6a4182d4bade780067738aca23596bea2e6d9a1646a3f66e0158461a", "exitCode": 1, "nativeStdout": "hello from iyi\ntrue\nfalse\nfalse\n", "wasmStdout": "", @@ -559,8 +559,8 @@ "path": "samples/iyi/format.iyi", "sourceSha256": "54f6992a74d0bff957fed60b05e8bd9d7bb16b091ea1b79e8277acd7454128bb", "wasm": "iyi-format.wasm", - "bytes": 305987, - "sha256": "96b91976e60702adfaa6d826605ae10fcd3074504abbf28ccd4e7ad6893bef89", + "bytes": 308041, + "sha256": "147d0c42ecc035df2e8e50b0fd0452eb7a81478c36091d89bbc1da13361607ec", "exitCode": 0, "nativeStdout": "hello world\nchar 'A' (ascii 65)\nright ' hi'\nleft 'hi '\ndec: 255\nhex: ff\nHEX: 0XFF\noct: 0o377\nbin: 0b101\nplus: +42\nzero: 000042\nsign zero: +00042\nnegative zero: -00042\nint precision: 0007\nfloat precision: 3.14\nfloat precision: 3.1416\nstring precision: cin\nwider than field: 12345\nzero precision float: 4\nzero precision zero int: ' '\nmin int64: -9223372036854775808\nmax int64: 9223372036854775807\nString.format: answer = 42\npercent tuple: score = 0099\nprintf: run completed\n", "wasmStdout": "hello world\nchar 'A' (ascii 65)\nright ' hi'\nleft 'hi '\ndec: 255\nhex: ff\nHEX: 0XFF\noct: 0o377\nbin: 0b101\nplus: +42\nzero: 000042\nsign zero: +00042\nnegative zero: -00042\nint precision: 0007\nfloat precision: 3.14\nfloat precision: 3.1416\nstring precision: cin\nwider than field: 12345\nzero precision float: 4\nzero precision zero int: ' '\nmin int64: -9223372036854775808\nmax int64: 9223372036854775807\nString.format: answer = 42\npercent tuple: score = 0099\nprintf: run completed\n", @@ -573,8 +573,8 @@ "path": "samples/iyi/formatting.iyi", "sourceSha256": "ad90bbbd17da587770609673949d55c990aee356dda1d437ce23394231ec75ee", "wasm": "iyi-formatting.wasm", - "bytes": 142136, - "sha256": "ac72388d20522939cc55442459d112285825966672679c36f5e163e1b3660788", + "bytes": 143143, + "sha256": "bd023ab26162567aac8c0e688897d08812255bfd9e71d50e4b16a658389972e3", "exitCode": 0, "nativeStdout": "last 30\nfirst 10\nwritten 99\nchar o\nslice lo\nhex ff\nbin 101\nrjust ' ab'\nljust 'ab***'\nrep ababab\nnil nil\nmaybe 99\n", "wasmStdout": "last 30\nfirst 10\nwritten 99\nchar o\nslice lo\nhex ff\nbin 101\nrjust ' ab'\nljust 'ab***'\nrep ababab\nnil nil\nmaybe 99\n", @@ -587,8 +587,8 @@ "path": "samples/iyi/generics.iyi", "sourceSha256": "b3ec6ebe033571c8bedecdb4d00a5df97eeca44b709d94352066c8f215f4bbe1", "wasm": "iyi-generics.wasm", - "bytes": 142973, - "sha256": "601687dad67b1543438b59ac2df1fc1948f73aa2fa70af53ea3b592ed26f0659", + "bytes": 143824, + "sha256": "f50fbc70667358f6bd3117167fd7f901fe0f116b2b6fc21a9defdab12043ff01", "exitCode": 0, "nativeStdout": "Box(42)\nBox(hello)\nfirst=1, second=two\nBox(1)\nBox(two)\nfirst=3, second=four\nada, ada\n84\ntrue\n7\n", "wasmStdout": "Box(42)\nBox(hello)\nfirst=1, second=two\nBox(1)\nBox(two)\nfirst=3, second=four\nada, ada\n84\ntrue\n7\n", @@ -601,8 +601,8 @@ "path": "samples/iyi/grid.iyi", "sourceSha256": "2f28278d0111d0ecc5c45afa5c2de9841abb5ad5f0041417dc97caa4fbc5338f", "wasm": "iyi-grid.wasm", - "bytes": 184691, - "sha256": "2322c849d0c0de208abdce4d859241ecd39fa93082af7de9fb1ca31dbf94a99e", + "bytes": 187747, + "sha256": "ad676e5b372c5034175d09f58220bc40833acb96676b153cb95b3cc60fd7737f", "exitCode": 0, "nativeStdout": " 0 1 2 3\n 4 5 6 7\n 8 9 10 11\n 12 13 14 15\n120\n[0, 5, 10, 15]\n[0, 4, 8, 12]\n[5, 4, 3, 2, 1]\n[2, 4, 6, 8, 10]\n55\n[3, 1, 2]\n[{1, \"a\"}, {2, \"b\"}, {3, \"c\"}]\n2\na\n[\"b\", \"c\"]\n01234\n", "wasmStdout": " 0 1 2 3\n 4 5 6 7\n 8 9 10 11\n 12 13 14 15\n120\n[0, 5, 10, 15]\n[0, 4, 8, 12]\n[5, 4, 3, 2, 1]\n[2, 4, 6, 8, 10]\n55\n[3, 1, 2]\n[{1, \"a\"}, {2, \"b\"}, {3, \"c\"}]\n2\na\n[\"b\", \"c\"]\n01234\n", @@ -615,8 +615,8 @@ "path": "samples/iyi/hello.iyi", "sourceSha256": "27cdcc173bc626bf84c26f468628026979b15bc6127e67594c5adf2930483a83", "wasm": "iyi-hello.wasm", - "bytes": 129713, - "sha256": "1acbae015df99f4903adef5307879aba69198efcbc9eaa7bb3195222ec7e6477", + "bytes": 130696, + "sha256": "20965268d4cb996ccc46053610de0fc1ff5c2b0a8e616f234d50dac815137557", "exitCode": 0, "nativeStdout": "Hello, iyi!\nHELLO, IYI!\nBEEP 42\nHello, crystal!\nBEEP 7\n-> BEEP 9\n", "wasmStdout": "Hello, iyi!\nHELLO, IYI!\nBEEP 42\nHello, crystal!\nBEEP 7\n-> BEEP 9\n", @@ -629,8 +629,8 @@ "path": "samples/iyi/immutable.iyi", "sourceSha256": "ffdb52f45c0426a876e109c9e26862d43d4bbc7bca547612e8b31dc5a10e7b9a", "wasm": "iyi-immutable.wasm", - "bytes": 195488, - "sha256": "58c89ab0eefa139a7792c940bc20a14f2cd685300574b2029d04092713b9d4f5", + "bytes": 197267, + "sha256": "6b58efb5ee61baef3e766191b9e1705696c9f6d278b37546bf0d06df928af0f5", "exitCode": 0, "nativeStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", "wasmStdout": "size 5\nempty? false true\nat 3 5\nappended 6 from 5, original 3,1,4,1,5\nconcatenated 3,1,4,1,5,7 / 5\ncopied list 2, source 3\nto_a 3,1,4,1,5\nall?/any? true true\nnone?/one? true true\ncount 2\nfind 4 nil\nindex 2 nil\nfirst?/first 3 3\nmap 6,2,8,2,10\nflat_map 3,3,1,1,4,4,1,1,5,5\nmap_with_index 0,1,8,3,20\nselect/reject 3,4,5 1,1\npartition 3+4+5 | 1+1\ntake/skip 3,1 1,5\ntake_while 3,1\nskip_while 4,1,5\nreduce 14\neach_with_object 30,10,40,10,50\njoin 3-1-4-1-5\nincludes? true false\nmax/min 5 1\nsum/product 14 60\ntally 2\nsorted 1,1,3,4,5\nmax_by pear\ngroup_by pear,plum,kiwi\ntwo elem types 4,2,5,2,6 / 4,3,4,4\n", @@ -641,10 +641,10 @@ "id": "iyi/init_order", "set": "iyi", "path": "samples/iyi/init_order.iyi", - "sourceSha256": "8ed472b93db73e226edb292e1bcae123ae6bcaa67dfae2307107a20028525521", + "sourceSha256": "0eee830465aeffb6ce0cabe6230e1c8dd87b37458bb42f9ba9b567721dcdc340", "wasm": "iyi-init_order.wasm", - "bytes": 123772, - "sha256": "185d364aa2311cbda4c14add19b531eace60c1e336a3d64c85205f62a7858d8b", + "bytes": 124809, + "sha256": "c71d26e6e1579dd29cd5b44976cf9a6015ab83e04dd164075fa1fbc294707842", "exitCode": 0, "nativeStdout": "1. boot/config\n2. boot/registry, above its own `import` line\n3. boot/registry, below it\n4. samples/init_order, above its own `import` lines\n5. Hello, iyi.\n", "wasmStdout": "1. boot/config\n2. boot/registry, above its own `import` line\n3. boot/registry, below it\n4. samples/init_order, above its own `import` lines\n5. Hello, iyi.\n", @@ -657,8 +657,8 @@ "path": "samples/iyi/inventory.iyi", "sourceSha256": "8d17a1ab3590cbe103b0eedb8487c9df8cc2837e0b84b26a3c8abbe353af80d6", "wasm": "iyi-inventory.wasm", - "bytes": 215622, - "sha256": "0c81eccc6cf69bb58eb5f1d6c6c98f97ff7d00151f9022276a728c2761d8647c", + "bytes": 218027, + "sha256": "539236d4dd652a76671806850aec414c540083d9f1b21d90037981853f0501c1", "exitCode": 0, "nativeStdout": "total: 11.0\ncheap: apple, pear\n2\ntrue\ntrue\nmelon\npear\n3.0\nnil\n1. apple 10 5.0\n2. pear 4 3.0\n3. melon 1 3.0\n[\"apple\", \"pear\", \"melon\"]\n15\npear\n", "wasmStdout": "total: 11.0\ncheap: apple, pear\n2\ntrue\ntrue\nmelon\npear\n3.0\nnil\n1. apple 10 5.0\n2. pear 4 3.0\n3. melon 1 3.0\n[\"apple\", \"pear\", \"melon\"]\n15\npear\n", @@ -671,8 +671,8 @@ "path": "samples/iyi/io.iyi", "sourceSha256": "8878852be923dc681f060baf0ae863e04a30e87f123498813c37738c2c97d41f", "wasm": "iyi-io.wasm", - "bytes": 130653, - "sha256": "463ed1f747c8f509297ef35a5a6e6fb67e089a00464c0b6450f2f523203c530f", + "bytes": 131609, + "sha256": "5836629d0d408e7d5e0763d25608c69c95fd72fa14499f8bd2f9246ffbc2a75b", "exitCode": 0, "nativeStdout": "no lines read from standard input\n", "wasmStdout": "no lines read from standard input\n", @@ -685,8 +685,8 @@ "path": "samples/iyi/modules.iyi", "sourceSha256": "f4ea9f6e29e18cf9452eccc75ba8324c25d108f825b64765f44335c22c57e0be", "wasm": "iyi-modules.wasm", - "bytes": 125729, - "sha256": "b150d37a31db7df338514312ddeafdcc8c08f90bc5f0120def3b7b08a7cb0774", + "bytes": 126751, + "sha256": "3f62b81809aab9324b80610bef0d02d4deeeb9aeea7b33b66679e94d7b952d37", "exitCode": 0, "nativeStdout": "HELLO, IYI. Good day, iyi.\nfriend\nesteemed colleague\n", "wasmStdout": "HELLO, IYI. Good day, iyi.\nfriend\nesteemed colleague\n", @@ -699,8 +699,8 @@ "path": "samples/iyi/sessions.iyi", "sourceSha256": "d33fc3528bffdd710b785425e5b0559e606c53184c2234774d56fc7d9f2f55c9", "wasm": "iyi-sessions.wasm", - "bytes": 138729, - "sha256": "2aa3e82d9ae07300b532c328d8907507262da5cf162957b6e9d919d2b5bbbe26", + "bytes": 139540, + "sha256": "a727d57d37d0fef94b9250d39c4d9d417292225cc27704badf6fb3f74ccf49f6", "exitCode": 0, "nativeStdout": "three arrivals:\n ada since tick 1\n linus since tick 2\n grace since tick 3\n (3 signed in)\nlinus left, had been in since tick 2\n ada since tick 1\n grace since tick 3\n (2 signed in)\nnobody named bob: true\nlinus is back:\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n", "wasmStdout": "three arrivals:\n ada since tick 1\n linus since tick 2\n grace since tick 3\n (3 signed in)\nlinus left, had been in since tick 2\n ada since tick 1\n grace since tick 3\n (2 signed in)\nnobody named bob: true\nlinus is back:\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n ada since tick 1\n grace since tick 3\n linus since tick 4\n (3 signed in)\n", @@ -713,8 +713,8 @@ "path": "samples/iyi/shapes.iyi", "sourceSha256": "e3e4ca261b7f764ce2a168163c6da93909efc63d663fb1828f7b6d5a79b9f805", "wasm": "iyi-shapes.wasm", - "bytes": 194621, - "sha256": "ce5153b0d7d47987f25f4a8ae9d69abc090349c2d4ef12c7e240e671c3755e1e", + "bytes": 197147, + "sha256": "83afd9d2684181460bb4598b5fadd232bb9dfcdd995ea80fecc996544a847725", "exitCode": 0, "nativeStdout": "circle: 3.14\nrect: 6.0\n9.1\nrect\n[12, nil, 7, -3]\n16\n[12, 7, 3]\n1024\n0.25\n1099511627776\n128\n8\n", "wasmStdout": "circle: 3.14\nrect: 6.0\n9.1\nrect\n[12, nil, 7, -3]\n16\n[12, 7, 3]\n1024\n0.25\n1099511627776\n128\n8\n", @@ -727,8 +727,8 @@ "path": "samples/iyi/socket.iyi", "sourceSha256": "afb4e7913d7b5c01a6e3ba5f42b3945e6ede3a374552325d07fa51ac25fa43e3", "wasm": "iyi-socket.wasm", - "bytes": 322516, - "sha256": "a3da1ba9191916a504945d30afe72c50f3358a1bce634cae2c8d908bb6d1d4fd", + "bytes": 413767, + "sha256": "f73737f8d2b26b4f39b4eedd2958fb3b16648bd979191fc517b8075b7777d26d", "exitCode": 1, "nativeStdout": "server listening on an ephemeral port\nserver received: hello from client\nclient received: hello from server\nclosed\n", "wasmStdout": "", @@ -741,8 +741,8 @@ "path": "samples/iyi/std_iterator.iyi", "sourceSha256": "fdfedfc80d758e3490725c1fc6ebaa5648f5066854a28ccf1eb8cdb90b872fc8", "wasm": "iyi-std_iterator.wasm", - "bytes": 205324, - "sha256": "bb9096be2d7a41cf41fdc3145b94da1610154ed4088a4fd69c9e492ea33180b4", + "bytes": 208688, + "sha256": "92e59ce82a781f1fab12b70d2c721393e9aa4efaebf613fd9aa5a713722799c8", "exitCode": 0, "nativeStdout": "natural numbers first 5: [1, 2, 3, 4, 5]\npulls from generator: 5\neven squares first 4: [4, 16, 36, 64]\npulls for 4 matches: 8\nchained odds and evens: [1, 3, 5, 2, 4, 6]\nzipped pairs: [{\"apple\", 100}, {\"banana\", 95}, {\"cherry\", 88}]\neach_slice(3): [[1, 2, 3], [4, 5, 6], [7]]\neach_cons(3): [[10, 20, 30], [20, 30, 40], [30, 40, 50]]\nstepped by 2: [0, 20, 40, 60]\ncycled 2 times: [\"tick\", \"tock\", \"tick\", \"tock\"]\nwith_index(1): [{\"alpha\", 1}, {\"beta\", 2}, {\"gamma\", 3}]\nreduce sum: 100\nfirst > 10: 12\n", "wasmStdout": "natural numbers first 5: [1, 2, 3, 4, 5]\npulls from generator: 5\neven squares first 4: [4, 16, 36, 64]\npulls for 4 matches: 8\nchained odds and evens: [1, 3, 5, 2, 4, 6]\nzipped pairs: [{\"apple\", 100}, {\"banana\", 95}, {\"cherry\", 88}]\neach_slice(3): [[1, 2, 3], [4, 5, 6], [7]]\neach_cons(3): [[10, 20, 30], [20, 30, 40], [30, 40, 50]]\nstepped by 2: [0, 20, 40, 60]\ncycled 2 times: [\"tick\", \"tock\", \"tick\", \"tock\"]\nwith_index(1): [{\"alpha\", 1}, {\"beta\", 2}, {\"gamma\", 3}]\nreduce sum: 100\nfirst > 10: 12\n", @@ -755,8 +755,8 @@ "path": "samples/iyi/std_text.iyi", "sourceSha256": "c891f86fe35a876e7f8279c549a0d16a39f38715593fae69a4ca387cb6468276", "wasm": "iyi-std_text.wasm", - "bytes": 192507, - "sha256": "e5fea273a1e34bac60159c4b2a34021a5c6c06ede16af753e7496d00401483ac", + "bytes": 195805, + "sha256": "04241460a6d66a0766649a2f142d813f82856387e40cf1ae9035bf3972dd9c4d", "exitCode": 0, "nativeStdout": "cleaned line: 'GET /api/v1/users HTTP/1.1'\nHTTP method: GET, path: /api/v1/users, version: HTTP/1.1\nHeader 'Authorization' = 'Bearer token123'\nslug: hello-world-this-is-iyi-std-text-\ngreeting size: 11 characters, 12 bytes\nreversed greeting: dlrow olléh\nhex 'ff' to decimal: 255\n", "wasmStdout": "cleaned line: 'GET /api/v1/users HTTP/1.1'\nHTTP method: GET, path: /api/v1/users, version: HTTP/1.1\nHeader 'Authorization' = 'Bearer token123'\nslug: hello-world-this-is-iyi-std-text-\ngreeting size: 11 characters, 12 bytes\nreversed greeting: dlrow olléh\nhex 'ff' to decimal: 255\n", @@ -769,8 +769,8 @@ "path": "samples/iyi/std_time.iyi", "sourceSha256": "82a17d942f25fe20e124340ce9245293d280594fc4e07ee5a4e82dee62dd3620", "wasm": "iyi-std_time.wasm", - "bytes": 272786, - "sha256": "78a8553c63c8622d6d613ca92513ad0e54c26e1f46921de34c62e43992324808", + "bytes": 275376, + "sha256": "0a094095f5727d4557ada435e75dd3bf9cc041500621f24f85ad21b678d4986f", "exitCode": 0, "nativeStdout": "Reference time (UTC): 2024-02-29 12:30:45 UTC\nRFC 3339: 2024-02-29T12:30:45.123Z\nUnix timestamp: 1709209845 s (1709209845123 ms)\nYear: 2024\nMonth: 2\nDay: 29\nDay of week: Thursday\nDay of year: 60\nTomorrow: 2024-03-01T12:30:45Z\nYesterday: 2024-02-28T12:30:45Z\nLive wall clock valid: true\nMonotonic duration >= 0: true\nLeap day event: 2024-02-29 12:00:00 UTC\nIs leap year: true\n", "wasmStdout": "Reference time (UTC): 2024-02-29 12:30:45 UTC\nRFC 3339: 2024-02-29T12:30:45.123Z\nUnix timestamp: 1709209845 s (1709209845123 ms)\nYear: 2024\nMonth: 2\nDay: 29\nDay of week: Thursday\nDay of year: 60\nTomorrow: 2024-03-01T12:30:45Z\nYesterday: 2024-02-28T12:30:45Z\nLive wall clock valid: true\nMonotonic duration >= 0: true\nLeap day event: 2024-02-29 12:00:00 UTC\nIs leap year: true\n", @@ -783,8 +783,8 @@ "path": "samples/iyi/visited.iyi", "sourceSha256": "987608a702df760c9cb5f3c0c85a5f621553fda885504892acb2fbb67d57d744", "wasm": "iyi-visited.wasm", - "bytes": 174911, - "sha256": "1dc4239ebdc57a7089107ab86ab68aa3c0eda49181a24a5a03a291f29825ecf8", + "bytes": 176116, + "sha256": "096c6ee3b3198ec711291e770e4eb6c44dcdf808b9b095c067e6d58a6e1145ba", "exitCode": 0, "nativeStdout": "Set{\"home\", \"docs\", \"blog\", \"install\", \"about\"}\n5\nfalse\nSet{\"install\"}\nSet{\"orphan\"}\ntrue\nfalse\ntrue\ntrue\n[\"the\", \"cat\", \"and\", \"dog\", \"bird\"]\n", "wasmStdout": "Set{\"home\", \"docs\", \"blog\", \"install\", \"about\"}\n5\nfalse\nSet{\"install\"}\nSet{\"orphan\"}\ntrue\nfalse\ntrue\ntrue\n[\"the\", \"cat\", \"and\", \"dog\", \"bird\"]\n", @@ -797,8 +797,8 @@ "path": "samples/iyi/webapp.iyi", "sourceSha256": "5bba5cdefa6407b2fe87e8c84bfdcf195bf02177193fbaea3e646c143018f296", "wasm": "iyi-webapp.wasm", - "bytes": 166939, - "sha256": "e0e334dd8a7912b996e2957bfbee8b0222c5c29c1f901f73eef7951eebb83e99", + "bytes": 171353, + "sha256": "f02d415451f16bed548312611374d917535ea7414bb670ce250c3d75f8827592", "exitCode": 0, "nativeStdout": "GET /\nGET /users\nPOST /users\nGET /count\nGET /v1\nGET /v1/admin/dashboard\n1|42|0\n", "wasmStdout": "GET /\nGET /users\nPOST /users\nGET /count\nGET /v1\nGET /v1/admin/dashboard\n1|42|0\n", @@ -827,6 +827,16 @@ "exitCode": 0, "nativeStdout": "150\n" }, + { + "id": "iyi/cores", + "set": "iyi", + "path": "samples/iyi/cores.iyi", + "sourceSha256": "0f5c2670ab8af0eb23c403e053464e75c0fa2e4d053d556ac8c52331d526bc14", + "refusal": "Error: std/system_roots reads the operating system's trusted roots, and this target has none: Linux, darwin and Windows do.", + "reason": "A server on every core has no wasm32-wasi build to run. The program imports std/http, whose TLS reads the operating system's trusted roots through std/system_roots, and that module refuses to compile for a target with none; and its tasks, like every task, do not compile on wasm32 yet (SPEC.md III.4.8).", + "exitCode": 0, + "nativeStdout": "[0, 1, 7]\n350\nhello from every core\n2\n[\"/\", \"/stop\"]\n" + }, { "id": "iyi/workers", "set": "iyi", diff --git a/records/wasm/methods.wasm b/records/wasm/methods.wasm index faa22f8..f16fbf4 100755 Binary files a/records/wasm/methods.wasm and b/records/wasm/methods.wasm differ diff --git a/records/wasm/modules.wasm b/records/wasm/modules.wasm index 2e03712..0cf747a 100755 Binary files a/records/wasm/modules.wasm and b/records/wasm/modules.wasm differ diff --git a/records/wasm/nil.wasm b/records/wasm/nil.wasm index b42d2b7..e60c633 100755 Binary files a/records/wasm/nil.wasm and b/records/wasm/nil.wasm differ diff --git a/records/wasm/ranges.wasm b/records/wasm/ranges.wasm index 6455e66..8cc93c2 100755 Binary files a/records/wasm/ranges.wasm and b/records/wasm/ranges.wasm differ diff --git a/records/wasm/returns.wasm b/records/wasm/returns.wasm index cf9d1a5..7dff27f 100755 Binary files a/records/wasm/returns.wasm and b/records/wasm/returns.wasm differ diff --git a/records/wasm/strings.wasm b/records/wasm/strings.wasm index 3fbe085..8b40082 100755 Binary files a/records/wasm/strings.wasm and b/records/wasm/strings.wasm differ diff --git a/records/wasm/structs.wasm b/records/wasm/structs.wasm index 64636e9..722ece1 100755 Binary files a/records/wasm/structs.wasm and b/records/wasm/structs.wasm differ diff --git a/records/wasm/traits.wasm b/records/wasm/traits.wasm index ce84db1..73f4657 100755 Binary files a/records/wasm/traits.wasm and b/records/wasm/traits.wasm differ diff --git a/records/wasm/tuples.wasm b/records/wasm/tuples.wasm index 667d5be..a79acc2 100755 Binary files a/records/wasm/tuples.wasm and b/records/wasm/tuples.wasm differ diff --git a/records/wasm/types.wasm b/records/wasm/types.wasm index b343fa9..497c8d3 100755 Binary files a/records/wasm/types.wasm and b/records/wasm/types.wasm differ diff --git a/records/wasm/unions.wasm b/records/wasm/unions.wasm index ee1fc50..eecb8cb 100755 Binary files a/records/wasm/unions.wasm and b/records/wasm/unions.wasm differ diff --git a/records/wasm/variables.wasm b/records/wasm/variables.wasm index d750200..1db3ace 100755 Binary files a/records/wasm/variables.wasm and b/records/wasm/variables.wasm differ diff --git a/scripts/budget.json b/scripts/budget.json index 004a100..0a0b0ac 100644 --- a/scripts/budget.json +++ b/scripts/budget.json @@ -29,14 +29,15 @@ { "match": "/download/", "ceiling": 229376 }, { "match": "/agents/", "ceiling": 262144 }, { "match": "/", "ceiling": 294912 }, - { "match": "/404.html", "ceiling": 245760 }, + { "match": "/404.html", "ceiling": 229376 }, { "match": "/why/", "ceiling": 229376 }, { "match": "/install/", "ceiling": 229376 }, { "match": "/learn/", "ceiling": 245760 }, { "match": "/learn/*/", "ceiling": 245760 }, { "match": "/playground/", "ceiling": 229376 }, { "match": "/playground/iyi/*/", "ceiling": 262144 }, - { "match": "/playground/*/", "ceiling": 294912 }, + { "match": "/playground/evidence/", "ceiling": 327680 }, + { "match": "/playground/*/", "ceiling": 278528 }, { "match": "/reference/", "ceiling": 245760 }, { "match": "/reference/std/*/", "ceiling": 344064 }, { "match": "/reference/prelude/*/", "ceiling": 229376 } diff --git a/scripts/install-targets.mjs b/scripts/install-targets.mjs index 8f9553b..4248245 100755 --- a/scripts/install-targets.mjs +++ b/scripts/install-targets.mjs @@ -151,7 +151,7 @@ if (disagree.length > 0) { /* README's "Getting it" section, which is where a reader of the repository is * told which tarball the installer picks. */ -const documented = /picks the tarball for `uname` \(([^)]*)\)/.exec(readme); +const documented = /picks the\s+tarball for your machine,\s+([^,]*?),\s+and refuses/.exec(readme); if (!documented) { throw new Error( `install-targets: ${CLAIM} no longer says which tarball install.sh picks ` + @@ -172,20 +172,23 @@ if (undocumented.length > 0) { ); } -/* The platform words, out of the sentence under README's one-liner. Anchored - * on the one-liner's own fenced block: the sentence a reader is shown is the - * one directly under the command, and the first sentence after the fence is - * it. Anchoring on what comes after instead put this at the mercy of a - * paragraph about a platform this record does not carry. */ -const prose = /install\.sh \| sh[^`]*``` ([A-Z][^.;]*)[.;]/.exec(readme); +/* The platform words, out of the parenthesis that introduces README's + * one-liner: "Install the latest release into `~/.local` (Linux x86-64, macOS + * arm64; a program built for macOS runs on macOS 14.4 or later, ...):", + * directly above the fenced command. The platforms are the list before the + * first semicolon; what follows it is a note about one of them, as 0.20.0 + * added. Anchored on that fence, so a paragraph about a platform this record + * does not carry - the Windows one-liner's, under it - cannot be read + * instead. */ +const prose = /\(([^()]*)\):\s*```sh\s+curl [^`]*?install\.sh \| sh/.exec(readme); if (!prose) { throw new Error( - `install-targets: ${CLAIM} no longer states the platforms in a sentence ` + - `under the fenced ${SOURCE} one-liner, so there is no sentence in the ` + + `install-targets: ${CLAIM} no longer states the platforms in the ` + + `parenthesis above the fenced ${SOURCE} one-liner, so there is no phrase in the ` + `repository to take the prose name of each target from.`, ); } -const phrases = prose[1].split(/\s*(?:,|and)\s*/).filter(Boolean); +const phrases = prose[1].split(";")[0].split(/\s*(?:,|and)\s*/).filter(Boolean); /** The words of a phrase, and the parts of any word that is itself hyphenated, * so `x86-64` is matched whole as well as by its halves. */ diff --git a/scripts/record-diagnostics.mjs b/scripts/record-diagnostics.mjs index 1b9bbb1..201986d 100755 --- a/scripts/record-diagnostics.mjs +++ b/scripts/record-diagnostics.mjs @@ -126,6 +126,23 @@ const CASES = [ file: "errors_non_exhaustive.iyi", expect: "case is not exhaustive", }, + { + id: "share-serve-capture", + rule: "III.4", + title: "a server's block that captures an Array", + file: "share_serve_capture.iyi", + expect: "which is not Share", + }, + { + // A warning, not a refusal, so the build is told to fail on warnings: + // the case records what a build run that way prints. + id: "share-reach", + rule: "III.4", + title: "a server's block that reaches module state through a call", + file: "share_reach.iyi", + flags: ["--error-on-warnings"], + expect: "(not Share) through `greeting`", + }, ]; // --------------------------------------------------------------------------- @@ -232,7 +249,7 @@ const cases = []; for (const entry of CASES) { const relative = `records/break/${entry.file}`; - const args = ["build", "--no-codegen", "--no-color", relative]; + const args = ["build", "--no-codegen", "--no-color", ...(entry.flags ?? []), relative]; // The compiler is written by name rather than by path: the path is per // machine and `recorded.compiler` already pins the exact build. Everything diff --git a/scripts/record-wasm.mjs b/scripts/record-wasm.mjs index d1dc912..ab5fdbc 100755 --- a/scripts/record-wasm.mjs +++ b/scripts/record-wasm.mjs @@ -77,6 +77,12 @@ const NATIVE_ONLY = { "iyi/workers": TASKS_ON_WASM, tasks: TASKS_ON_WASM, channels: TASKS_ON_WASM, + "iyi/cores": + "A server on every core has no wasm32-wasi build to run. The program " + + "imports std/http, whose TLS reads the operating system's trusted roots " + + "through std/system_roots, and that module refuses to compile for a " + + "target with none; and its tasks, like every task, do not compile on " + + "wasm32 yet (SPEC.md III.4.8).", }; const NOTES = { diff --git a/scripts/samples.mjs b/scripts/samples.mjs index d035175..ae062cd 100755 --- a/scripts/samples.mjs +++ b/scripts/samples.mjs @@ -367,7 +367,7 @@ if (Object.keys(rules).length === 0) { // that section's opening paragraph, read here, so the sentence on the page is // the specification's sentence and a section that is rewritten changes the page // or fails the build. -const SECTIONS = ["III.1"]; +const SECTIONS = ["III.1", "III.4"]; for (const id of SECTIONS) { const escaped = id.replace(/\./g, "\\."); diff --git a/scripts/targets.mjs b/scripts/targets.mjs index 86bd423..3f9ff92 100755 --- a/scripts/targets.mjs +++ b/scripts/targets.mjs @@ -117,14 +117,14 @@ const claimed = (() => { */ const audit = (() => { const text = read(CLAIM); - const counted = text.match(/audits the emitted object\s*\n?\s*for (\S+) triples/); + const counted = text.match(/audits the emitted object\s+for\s+(\S+)\s+of\s+the\s+nine/); if (!counted) { throw new Error( `targets: ${CLAIM} no longer says how many triples CI audits the ` + `emitted object for, so the site cannot say which of them it does.`, ); } - const exempt = [...text.matchAll(/`([a-z0-9_]+-[a-z0-9_-]+)`(?=[^.]*type-check but are not object-audited)/g)].map( + const exempt = [...text.matchAll(/`([a-z0-9_]+-[a-z0-9_-]+)`(?=[^.]*only\s+type-check)/g)].map( (match) => match[1], ); if (exempt.length === 0) { diff --git a/src/content/learn/every-core.mdx b/src/content/learn/every-core.mdx new file mode 100644 index 0000000..d2de910 --- /dev/null +++ b/src/content/learn/every-core.mdx @@ -0,0 +1,148 @@ +--- +title: Work on every core, and what may cross +searchTitle: "Parallelism in iyi: every core, and what may cross" +order: 9 +summary: "`spawn` stays on its spawner's thread and may touch anything; `spawn_parallel` runs on any core and may capture only what is `Share`. `Server.serve` puts every connection on that pool, so its block is checked the same way, and `workers: 1` keeps a server on one thread." +description: "iyi's spawn_parallel runs on any core and may capture only what is Share. Server.serve puts each connection on that pool; workers: 1 keeps one thread." +rule: III.4 +sample: samples/iyi/cores.iyi +breaks: share-serve-capture +breaksMore: + - share-reach +bought: >- + A data race on a value a block captures does not compile, and a block that + reaches shared state through a call is named at the block, with the path, + rather than found by a race detector on the run that happened to hit it. A + server that was correct on one thread is told exactly what to change before + it runs on every core. +sources: + - file: samples/iyi/cores.iyi + note: both kinds of task, the state a pool may share, a server on every core that stops through a channel, and a constant marked with why it is safe + - file: src/std/http.iyi + note: "`Server.serve` and its `workers:` argument, and the mark that has its block checked as a parallel task's" + - file: src/std/mutex.iyi + note: the lock a waiting task parks on rather than holding its thread + - file: SPEC.md + note: III.4.4 for what `Share` is and the reach check, III.4.13 for the pool, how many workers it has, and how a task moves between them + - file: CHANGELOG.md + note: the release that made `serve` run on every core and removed `serve_parallel`, with the migration +--- + +import Sample from "../../components/Sample.astro"; + +Every step so far has been about what a module tells the modules that import +it. This one is about what a block tells the thread that runs it, and the +promise is the same shape: the compiler is told, so a mistake is refused where +it is written instead of turning up at runtime, or never. + +## Two ways to start a task + +`g.spawn` starts a task on the thread that spawned it. Its tasks take turns +at their parks, never two at the same instant, so a block may append to an +`Array` it captured and nothing is raced. + + + +`g.spawn_parallel` starts a task a pool of worker threads runs, on whichever +core is free, and a task that parks may resume on another one. It is a task +of its group like any other: joined at the group's end, cancelled with it, its +answer through `value`. + + + +What changed is what the block may capture. A parallel block may capture only +what is `Share`: an immutable value, an `Atomic`, a `Mutex`, a `List`, a +`Channel` of `Share` values. Each task above captures nothing but its own +`part`, and builds its answer itself. An answer need not be `Share`, because +it is handed over once, after the task has ended, and the join orders it. + +The pool starts at the first `spawn_parallel`, with one worker per core the +process may run on. `IYI_WORKERS=n` in the environment sets the count, and a +program without a parallel task starts no pool at all. + +## State every core may touch + +A counter that every core adds to is an `Atomic`, and an `Array` that every +core appends to sits behind a `Mutex`. Both are `Share`, so a parallel block +may capture them, and what is behind the lock is reached only inside `lock`. + + + +A task that waits for a held `Mutex` parks, as it would on a channel, so the +worker it was on runs other tasks meanwhile. + +## A server on every core + +std/http's `Server.serve` runs each connection as a parallel task on the +pool, so a server uses every core without asking. Its block is checked as a +parallel task's is. + + + +Two things in that block are there because of the check. The path is copied +into a `String` before the lock, because a block that runs behind a `Mutex` +may not capture the request it came from. And the server stops through a +channel: `/stop` may arrive on any worker, and closing the listener from the +block would capture it, so the block sends on `stop` and a task beside the +server, on the thread that opened the listener, closes it. + +`Server.serve(listener, workers: 1)` is the server on one thread, the +accepting one. Its block is not checked at all, because nothing it captures +leaves that thread: it may append to an `Array`, count in a plain variable, +close its own listener. A count in a variable is checked as a pool would be, +since nothing at compile time says it is one. + +### TLS on every core + +A server's `tls:` takes `config.frozen`, a `TlsFrozenServerConfig`: a copy +of the configuration no property of which changes, which every worker may +read. A `TlsServerConfig` itself, on more than one worker, is refused at the +argument, naming the same two ways out. `serve` does not freeze it for you, +because a property assigned while the server runs would reach no worker and +nothing would say so. An identity source goes to `frozen` as a block, whose +captures are checked like any other. + +## Module state a call reaches + +The check reads what a block names. A block that calls a method, which reads +a constant, names only the method, and used to compile with nothing said. +Now the compiler walks the calls from the block through the program's own +modules and its packages, and warns at the block for every constant and class +variable it reaches whose type is not `Share`, once each, by the shortest +path. + +Some such state is safe: set once at startup and only read after, or guarded +some other way. That is said on its declaration, with the reason as the +claim. + + + + + +The mark quiets the warning for that state alone. A block that names +`GREETING` itself is still refused, because a block's own text is checked +exactly, and the mark is a claim about calls the compiler cannot follow to the +end. It is a warning rather than a refusal so that nothing that compiled +stops compiling; `iyi build --error-on-warnings`, or `iyi check` given the +same, makes it fail a build. + +## Moving off `serve_parallel` + +0.19.1 had `Server.serve_parallel` beside a `serve` on one thread. It is gone, +with no alias, and `serve` is the pool. A call to `serve_parallel` becomes a +call to `serve` with the same arguments. A `serve` that was on one thread +needs nothing if its block captures nothing unshareable: it now uses every +core. One whose block captures a `Router`, an `Array`, a counter or the +listener it closes, or whose `tls:` is a `TlsServerConfig`, either passes +`workers: 1` and stays as it was, or moves to what crosses threads: the state +behind a `Mutex` or in an `Atomic`, the router to a `SharedRouter`, the +configuration to `config.frozen`, and the stop to a `Channel`, as the server +above does. + +## Capture what does not cross + +Here is a server whose block appends every path it is asked for to an +`Array`. On one thread that is fine. On every core two workers could append at +once, and the compiler refuses the capture where it is written, naming both +ways out. After it, the same server reaching a constant through a call, built +to fail on warnings. diff --git a/src/pages/agents.astro b/src/pages/agents.astro index ebb3020..b2692be 100644 --- a/src/pages/agents.astro +++ b/src/pages/agents.astro @@ -185,7 +185,7 @@ const diffs = [
@@ -401,7 +401,7 @@ const diffs = [
samples
{measuredOn.map((target) => target.file).join(" · ")}
token task
{taskFile}
-
runs
{pack.runs}
+
runs
{pack.runs_where}
arm A
sources
arm B
iyi mod context
pack size
{pack.size_low}–{pack.size_high}% of sources
diff --git a/src/pages/llms.txt.ts b/src/pages/llms.txt.ts index 7f40782..ba612c1 100644 --- a/src/pages/llms.txt.ts +++ b/src/pages/llms.txt.ts @@ -94,8 +94,8 @@ for Type\` lives with the trait or the type (R-3). on ${loop.machine}. - Agent experience (AX): a module's interface is a file (.iyimod). \`iyi mod context\` grounds an edit at ${pack.size_low} to ${pack.size_high}% of the sources' size; a model writing - against it spent ${pack.tokens_low} to ${pack.tokens_high}% fewer prompt tokens over ${pack.runs} measured - runs (\`${pack.command}\`). The loop is one verb per step, and every step of + against it spent ${pack.tokens_low} to ${pack.tokens_high}% fewer prompt tokens over the runs + ${pack.runs_where} writes up (\`${pack.command}\`). The loop is one verb per step, and every step of it is recorded from a real run at ${origin}/agents/: ${loopFrames} Both servers are in the binary, with nothing to install: \`iyi mcp\` serves