primarysource 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- primarysource/__init__.py +5 -0
- primarysource/bbf/__init__.py +56 -0
- primarysource/bbf/_xml.py +63 -0
- primarysource/bbf/catalogue.json +777 -0
- primarysource/bbf/catalogue.py +137 -0
- primarysource/bbf/cli.py +79 -0
- primarysource/bbf/datamodel.py +281 -0
- primarysource/bbf/importer.py +151 -0
- primarysource/bbf/library.py +380 -0
- primarysource/bbf/server.py +72 -0
- primarysource/budget.py +63 -0
- primarysource/c2sp/__init__.py +53 -0
- primarysource/c2sp/_fetch.py +5 -0
- primarysource/c2sp/catalogue.json +231 -0
- primarysource/c2sp/catalogue.py +95 -0
- primarysource/c2sp/cli.py +62 -0
- primarysource/c2sp/importer.py +90 -0
- primarysource/c2sp/library.py +309 -0
- primarysource/c2sp/markdown.py +31 -0
- primarysource/c2sp/server.py +68 -0
- primarysource/cavp/__init__.py +42 -0
- primarysource/cavp/cli.py +84 -0
- primarysource/cavp/data/vectors.toml +47 -0
- primarysource/cavp/importer.py +156 -0
- primarysource/cavp/model.py +279 -0
- primarysource/cavp/parse.py +269 -0
- primarysource/cavp/paths.py +17 -0
- primarysource/cavp/server.py +67 -0
- primarysource/cavp/tools.py +169 -0
- primarysource/cli.py +107 -0
- primarysource/cursor.py +82 -0
- primarysource/cve/__init__.py +5 -0
- primarysource/cve/__main__.py +6 -0
- primarysource/cve/cli.py +144 -0
- primarysource/cve/collection.py +20 -0
- primarysource/cve/config.py +58 -0
- primarysource/cve/errors.py +16 -0
- primarysource/cve/kernel.py +232 -0
- primarysource/cve/mcp.py +139 -0
- primarysource/cve/parse.py +318 -0
- primarysource/cve/py.typed +0 -0
- primarysource/cve/service.py +760 -0
- primarysource/cve/store.py +214 -0
- primarysource/cve/sync.py +438 -0
- primarysource/cve/views.py +304 -0
- primarysource/debpolicy/__init__.py +16 -0
- primarysource/debpolicy/catalogue.py +263 -0
- primarysource/debpolicy/cli.py +12 -0
- primarysource/debpolicy/server.py +11 -0
- primarysource/draft/__init__.py +11 -0
- primarysource/draft/catalogue.py +117 -0
- primarysource/draft/cli.py +149 -0
- primarysource/draft/collection.py +21 -0
- primarysource/draft/datatracker.py +158 -0
- primarysource/draft/importer.py +163 -0
- primarysource/draft/licence.py +89 -0
- primarysource/draft/names.py +64 -0
- primarysource/draft/net.py +133 -0
- primarysource/draft/segment.py +192 -0
- primarysource/draft/server.py +91 -0
- primarysource/draft/status.py +146 -0
- primarysource/draft/store.py +199 -0
- primarysource/draft/tracked.json +104 -0
- primarysource/draft/views.py +430 -0
- primarysource/draft/xmlmeta.py +74 -0
- primarysource/ecma/__init__.py +52 -0
- primarysource/ecma/_pdf.py +143 -0
- primarysource/ecma/catalogue.json +22 -0
- primarysource/ecma/catalogue.py +100 -0
- primarysource/ecma/cli.py +67 -0
- primarysource/ecma/identify.py +25 -0
- primarysource/ecma/importer.py +108 -0
- primarysource/ecma/library.py +240 -0
- primarysource/ecma/server.py +67 -0
- primarysource/errors.py +29 -0
- primarysource/fetch.py +360 -0
- primarysource/fsutil.py +150 -0
- primarysource/gitdocs/__init__.py +15 -0
- primarysource/gitdocs/catalogue.py +116 -0
- primarysource/gitdocs/cli.py +12 -0
- primarysource/gitdocs/server.py +8 -0
- primarysource/home.py +65 -0
- primarysource/iana/__init__.py +32 -0
- primarysource/iana/catalogue.json +244 -0
- primarysource/iana/catalogue.py +69 -0
- primarysource/iana/cli.py +69 -0
- primarysource/iana/importer.py +157 -0
- primarysource/iana/model.py +325 -0
- primarysource/iana/reader.py +315 -0
- primarysource/iana/server.py +67 -0
- primarysource/iana/store.py +83 -0
- primarysource/iana/xmlsafe.py +47 -0
- primarysource/identify.py +186 -0
- primarysource/ieee/__init__.py +58 -0
- primarysource/ieee/_html.py +49 -0
- primarysource/ieee/catalogue.json +74 -0
- primarysource/ieee/catalogue.py +123 -0
- primarysource/ieee/cli.py +79 -0
- primarysource/ieee/identify.py +24 -0
- primarysource/ieee/ieee802.py +47 -0
- primarysource/ieee/importer.py +305 -0
- primarysource/ieee/posix.py +233 -0
- primarysource/ieee/server.py +79 -0
- primarysource/index.py +446 -0
- primarysource/iso/__init__.py +51 -0
- primarysource/iso/cli.py +98 -0
- primarysource/iso/designation.py +95 -0
- primarysource/iso/identify.py +100 -0
- primarysource/iso/importer.py +224 -0
- primarysource/iso/library.py +249 -0
- primarysource/iso/server.py +69 -0
- primarysource/itu/__init__.py +41 -0
- primarysource/itu/__main__.py +8 -0
- primarysource/itu/asn1mod.py +478 -0
- primarysource/itu/catalog.py +472 -0
- primarysource/itu/cli.py +445 -0
- primarysource/itu/collection.py +20 -0
- primarysource/itu/corpus.py +118 -0
- primarysource/itu/errata.py +16 -0
- primarysource/itu/formal.py +565 -0
- primarysource/itu/identify.py +26 -0
- primarysource/itu/index.py +490 -0
- primarysource/itu/mcp.py +474 -0
- primarysource/itu/py.typed +0 -0
- primarysource/itu/sources.py +600 -0
- primarysource/itu/text.py +609 -0
- primarysource/kconfig/__init__.py +16 -0
- primarysource/kconfig/catalogue.py +98 -0
- primarysource/kconfig/cli.py +12 -0
- primarysource/kconfig/server.py +8 -0
- primarysource/model.py +63 -0
- primarysource/nist/__init__.py +5 -0
- primarysource/nist/collection.py +46 -0
- primarysource/nist/data/acquired-nist.json +1657 -0
- primarysource/nist/data/nist-catalog.json +12090 -0
- primarysource/nist/data/sources.toml +1996 -0
- primarysource/nist/identify.py +41 -0
- primarysource/nist/ingest/__init__.py +1 -0
- primarysource/nist/ingest/acquire_nist.py +128 -0
- primarysource/nist/ingest/controls.py +82 -0
- primarysource/nist/ingest/discover_nist.py +218 -0
- primarysource/nist/ingest/extract.py +48 -0
- primarysource/nist/ingest/fetch.py +211 -0
- primarysource/nist/ingest/index.py +14 -0
- primarysource/nist/ingest/oscal.py +480 -0
- primarysource/nist/ingest/prose_layouts.py +236 -0
- primarysource/nist/ingest/sources.py +94 -0
- primarysource/nist/ingest/status.py +255 -0
- primarysource/nist/models.py +7 -0
- primarysource/nist/paths.py +33 -0
- primarysource/nist/server/__init__.py +3 -0
- primarysource/nist/server/corpus.py +38 -0
- primarysource/nist/server/mcp.py +19 -0
- primarysource/nist/server/tools.py +35 -0
- primarysource/nist/spec.py +38 -0
- primarysource/pdfcorpus/__init__.py +16 -0
- primarysource/pdfcorpus/corpus.py +159 -0
- primarysource/pdfcorpus/fts.py +175 -0
- primarysource/pdfcorpus/index.py +70 -0
- primarysource/pdfcorpus/models.py +30 -0
- primarysource/pdfcorpus/server.py +96 -0
- primarysource/pdfcorpus/spec.py +37 -0
- primarysource/pdfcorpus/split.py +391 -0
- primarysource/pdfcorpus/tools.py +244 -0
- primarysource/pep/__init__.py +15 -0
- primarysource/pep/catalogue.py +191 -0
- primarysource/pep/cli.py +12 -0
- primarysource/pep/server.py +8 -0
- primarysource/pkgdocs/__init__.py +19 -0
- primarysource/pkgdocs/fetch.py +356 -0
- primarysource/pkgdocs/index.py +89 -0
- primarysource/pkgdocs/model.py +76 -0
- primarysource/pkgdocs/readers/__init__.py +1 -0
- primarysource/pkgdocs/readers/_common.py +134 -0
- primarysource/pkgdocs/readers/asciidoc.py +160 -0
- primarysource/pkgdocs/readers/latex.py +315 -0
- primarysource/pkgdocs/readers/markdown.py +173 -0
- primarysource/pkgdocs/readers/pod.py +132 -0
- primarysource/pkgdocs/readers/rst.py +239 -0
- primarysource/pkgdocs/readers/scdoc.py +108 -0
- primarysource/pkgdocs/runtime.py +552 -0
- primarysource/pkgdocs/serving.py +158 -0
- primarysource/pkgdocs/store.py +167 -0
- primarysource/pms/__init__.py +16 -0
- primarysource/pms/catalogue.py +88 -0
- primarysource/pms/cli.py +12 -0
- primarysource/pms/server.py +8 -0
- primarysource/py.typed +0 -0
- primarysource/readers/__init__.py +10 -0
- primarysource/readers/bookmarks.py +192 -0
- primarysource/readers/html.py +1312 -0
- primarysource/readers/markdown.py +189 -0
- primarysource/readers/pdf.py +187 -0
- primarysource/readers/xml.py +230 -0
- primarysource/refs.py +87 -0
- primarysource/registry.py +161 -0
- primarysource/render.py +49 -0
- primarysource/rfc/__init__.py +3 -0
- primarysource/rfc/__main__.py +6 -0
- primarysource/rfc/cite.py +204 -0
- primarysource/rfc/cli.py +903 -0
- primarysource/rfc/collection.py +19 -0
- primarysource/rfc/config.py +138 -0
- primarysource/rfc/errata.py +203 -0
- primarysource/rfc/fsutil.py +82 -0
- primarysource/rfc/graph.py +213 -0
- primarysource/rfc/index/__init__.py +1 -0
- primarysource/rfc/index/chunker.py +281 -0
- primarysource/rfc/index/embed.py +166 -0
- primarysource/rfc/index/hybrid.py +305 -0
- primarysource/rfc/index/lexical.py +223 -0
- primarysource/rfc/index/rfcxml.py +673 -0
- primarysource/rfc/index/search.py +508 -0
- primarysource/rfc/index/txtseg.py +585 -0
- primarysource/rfc/library.py +294 -0
- primarysource/rfc/mcp/__init__.py +1 -0
- primarysource/rfc/mcp/server.py +381 -0
- primarysource/rfc/packs/__init__.py +1 -0
- primarysource/rfc/packs/builder.py +228 -0
- primarysource/rfc/packs/collections/all-current-txt.json +10 -0
- primarysource/rfc/packs/collections/essentials.json +19 -0
- primarysource/rfc/packs/collections/fyi-current.json +10 -0
- primarysource/rfc/packs/collections/standards-current.json +10 -0
- primarysource/rfc/packs/collections/year-2025.json +10 -0
- primarysource/rfc/packs/collections/year-2026.json +10 -0
- primarysource/rfc/packs/collections.py +163 -0
- primarysource/rfc/packs/docsetexport.py +163 -0
- primarysource/rfc/packs/epub.py +159 -0
- primarysource/rfc/packs/export.py +306 -0
- primarysource/rfc/packs/htmlexport.py +82 -0
- primarysource/rfc/packs/serve.py +143 -0
- primarysource/rfc/packs/zimexport.py +171 -0
- primarysource/rfc/query.py +466 -0
- primarysource/rfc/subjects.py +457 -0
- primarysource/rfc/sync/__init__.py +1 -0
- primarysource/rfc/sync/downloader.py +132 -0
- primarysource/rfc/sync/manifest.py +162 -0
- primarysource/rfc/sync/scanner.py +49 -0
- primarysource/rfc/sync/state.py +464 -0
- primarysource/rfc/views.py +1067 -0
- primarysource/rpmdocs/__init__.py +15 -0
- primarysource/rpmdocs/catalogue.py +117 -0
- primarysource/rpmdocs/cli.py +12 -0
- primarysource/rpmdocs/server.py +8 -0
- primarysource/scaffold.py +223 -0
- primarysource/serve.py +75 -0
- primarysource/settings.py +152 -0
- primarysource/usb/__init__.py +5 -0
- primarysource/usb/collection.py +37 -0
- primarysource/usb/data/acquired.json +2025 -0
- primarysource/usb/data/sources-usb.toml +852 -0
- primarysource/usb/data/sources.toml +10 -0
- primarysource/usb/data/usb-catalog.json +1663 -0
- primarysource/usb/data/usb-classification.json +1986 -0
- primarysource/usb/data/usb-corpus-build.json +608 -0
- primarysource/usb/data/usb-pdf-choice.json +839 -0
- primarysource/usb/data/usb-split-measure.json +1803 -0
- primarysource/usb/ingest/__init__.py +1 -0
- primarysource/usb/ingest/acquire.py +130 -0
- primarysource/usb/ingest/discover_usb.py +88 -0
- primarysource/usb/ingest/extract.py +48 -0
- primarysource/usb/ingest/fetch.py +197 -0
- primarysource/usb/ingest/index.py +14 -0
- primarysource/usb/ingest/sources.py +85 -0
- primarysource/usb/models.py +7 -0
- primarysource/usb/paths.py +33 -0
- primarysource/usb/server/__init__.py +3 -0
- primarysource/usb/server/corpus.py +38 -0
- primarysource/usb/server/mcp.py +19 -0
- primarysource/usb/server/tools.py +35 -0
- primarysource/usb/spec.py +20 -0
- primarysource/usbids/__init__.py +56 -0
- primarysource/usbids/cli.py +65 -0
- primarysource/usbids/importer.py +272 -0
- primarysource/usbids/parse.py +283 -0
- primarysource/usbids/server.py +55 -0
- primarysource/usbids/store.py +80 -0
- primarysource/usbids/tools.py +174 -0
- primarysource/zim/__init__.py +5 -0
- primarysource/zim/__main__.py +8 -0
- primarysource/zim/_libzim.py +37 -0
- primarysource/zim/catalog.py +594 -0
- primarysource/zim/cli.py +555 -0
- primarysource/zim/collection.py +41 -0
- primarysource/zim/errors.py +100 -0
- primarysource/zim/groups.py +30 -0
- primarysource/zim/library.py +668 -0
- primarysource/zim/manage.py +636 -0
- primarysource/zim/markdown.py +1247 -0
- primarysource/zim/mcp.py +201 -0
- primarysource/zim/purl.py +130 -0
- primarysource/zim/py.typed +0 -0
- primarysource/zim/refs.py +90 -0
- primarysource/zim/service.py +1182 -0
- primarysource/zim/snapshot.py +197 -0
- primarysource/zim/sync.py +269 -0
- primarysource/zim/views.py +230 -0
- primarysource-0.1.0.dist-info/METADATA +118 -0
- primarysource-0.1.0.dist-info/RECORD +303 -0
- primarysource-0.1.0.dist-info/WHEEL +4 -0
- primarysource-0.1.0.dist-info/entry_points.txt +2 -0
- primarysource-0.1.0.dist-info/licenses/LICENSE +203 -0
- primarysource-0.1.0.dist-info/licenses/NOTICE +51 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Read, outline, search and cite the installed C2SP specifications. Local files only; nothing here fetches.
|
|
2
|
+
|
|
3
|
+
A ref is `c2sp:static-ct-api@09f3e51§Log Entries¶2`, with the `c2sp:` prefix and the `@<commit prefix>` both
|
|
4
|
+
optional (no `@` means the newest import). `§` names a section by its heading text, or by its GitHub-style slug
|
|
5
|
+
(`log-entries`) when the heading contains `:`, `#`, `¶` or `§`, or when two headings read alike. `¶n` is the nth
|
|
6
|
+
block of that section's span (see `markdown.py`). A ref with no section is the whole document.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import difflib
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from primarysource import budget, cursor
|
|
18
|
+
from primarysource.c2sp import markdown
|
|
19
|
+
from primarysource.c2sp._fetch import sha256
|
|
20
|
+
from primarysource.c2sp.catalogue import DOC_ID
|
|
21
|
+
from primarysource.c2sp.catalogue import catalogue as packaged_catalogue
|
|
22
|
+
from primarysource.errors import LibError
|
|
23
|
+
from primarysource.home import collection_home
|
|
24
|
+
from primarysource.model import span_hash
|
|
25
|
+
from primarysource.refs import Ref, RefGrammar
|
|
26
|
+
from primarysource.render import render
|
|
27
|
+
|
|
28
|
+
PREFIX = "c2sp:"
|
|
29
|
+
IMPORT = "primarysource c2sp import"
|
|
30
|
+
_COMMIT_PREFIX = re.compile(r"[0-9a-f]{7,40}")
|
|
31
|
+
_UNSAFE_IN_REF = re.compile(r"[:#¶§]")
|
|
32
|
+
_HEADING_LINE = re.compile(r"#{1,6}(?: |$)")
|
|
33
|
+
_TERM = re.compile(r"\w+", re.UNICODE)
|
|
34
|
+
MAX_QUERY_TERMS = 16
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def grammar() -> RefGrammar:
|
|
38
|
+
from primarysource.c2sp import C2SP_DOC, C2SP_SECTION
|
|
39
|
+
|
|
40
|
+
return RefGrammar(C2SP_DOC, C2SP_SECTION)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class Loaded:
|
|
45
|
+
"""One installed specification, parsed."""
|
|
46
|
+
|
|
47
|
+
id: str
|
|
48
|
+
commit: str
|
|
49
|
+
title: str
|
|
50
|
+
document: markdown.MarkdownDocument
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def short(self) -> str:
|
|
54
|
+
return self.commit[:7]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def default_home() -> Path:
|
|
58
|
+
return collection_home("c2sp")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def installs(home: Path) -> list[tuple[str, dict[str, object]]]:
|
|
62
|
+
"""Every install under `home` as (commit, record), newest import first. A directory without a readable
|
|
63
|
+
`install.json` is not an install."""
|
|
64
|
+
found: list[tuple[str, dict[str, object]]] = []
|
|
65
|
+
if home.is_dir():
|
|
66
|
+
for child in home.iterdir():
|
|
67
|
+
record = child / "install.json"
|
|
68
|
+
if child.is_dir() and re.fullmatch(r"[0-9a-f]{40}", child.name) and record.is_file():
|
|
69
|
+
try:
|
|
70
|
+
loaded = json.loads(record.read_text(encoding="utf-8"))
|
|
71
|
+
except (ValueError, OSError):
|
|
72
|
+
continue
|
|
73
|
+
if isinstance(loaded, dict) and loaded.get("commit") == child.name:
|
|
74
|
+
found.append((child.name, loaded))
|
|
75
|
+
found.sort(key=lambda item: str(item[1].get("imported", "")), reverse=True)
|
|
76
|
+
return found
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _split_doc(token: str) -> tuple[str, str | None]:
|
|
80
|
+
doc, _, version = token.partition("@")
|
|
81
|
+
if not DOC_ID.fullmatch(doc):
|
|
82
|
+
raise LibError(f"not a C2SP specification id: {doc!r}", "c2sp_docs lists the specification ids")
|
|
83
|
+
if version and not _COMMIT_PREFIX.fullmatch(version):
|
|
84
|
+
raise LibError(f"not a commit prefix: {version!r}", "use at least 7 lowercase hex digits of the commit")
|
|
85
|
+
return doc, version or None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def load(token: str, home: Path | None = None) -> Loaded:
|
|
89
|
+
"""Load `doc[@commit-prefix]`, verifying the file against the install record's sha256."""
|
|
90
|
+
home = home or default_home()
|
|
91
|
+
doc_id, version = _split_doc(token)
|
|
92
|
+
available = installs(home)
|
|
93
|
+
if not available:
|
|
94
|
+
raise LibError("no C2SP specifications are imported", "c2sp_docs lists the catalogue", IMPORT)
|
|
95
|
+
for commit, record in available:
|
|
96
|
+
if version and not commit.startswith(version):
|
|
97
|
+
continue
|
|
98
|
+
files = record.get("files")
|
|
99
|
+
pin = files.get(doc_id) if isinstance(files, dict) else None
|
|
100
|
+
if not isinstance(pin, str):
|
|
101
|
+
continue
|
|
102
|
+
data = (home / commit / f"{doc_id}.md").read_bytes() if (home / commit / f"{doc_id}.md").is_file() else None
|
|
103
|
+
if data is None:
|
|
104
|
+
continue
|
|
105
|
+
if sha256(data) != pin:
|
|
106
|
+
raise LibError(f"{doc_id}@{commit[:7]} on disk does not match its recorded sha256", "", IMPORT)
|
|
107
|
+
parsed = markdown.parse(data)
|
|
108
|
+
return Loaded(doc_id, commit, parsed.title or doc_id, parsed)
|
|
109
|
+
have = ", ".join(f"{c[:7]}" for c, _ in available)
|
|
110
|
+
what = f"{doc_id}@{version}" if version else doc_id
|
|
111
|
+
raise LibError(f"{what} is not imported (imported commits: {have})", "c2sp_docs lists what is imported", IMPORT)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parse_ref(text: str) -> Ref:
|
|
115
|
+
"""Parse a c2sp ref, accepting the optional `c2sp:` prefix."""
|
|
116
|
+
body = text.strip()
|
|
117
|
+
if body.lower().startswith(PREFIX):
|
|
118
|
+
body = body[len(PREFIX) :]
|
|
119
|
+
body, doc_para = cursor.split_doc_para(body)
|
|
120
|
+
ref = grammar().parse(body)
|
|
121
|
+
if doc_para is not None:
|
|
122
|
+
ref = Ref(ref.doc, None, doc_para, None, None)
|
|
123
|
+
if ref.lines is not None:
|
|
124
|
+
raise LibError(f"not a c2sp ref: {text!r}", "c2sp refs address headings and blocks, not line ranges")
|
|
125
|
+
if ref.anchor is not None:
|
|
126
|
+
raise LibError(f"not a c2sp ref: {text!r}", "c2sp has no #anchor; name the heading after §")
|
|
127
|
+
if ref.doc is None:
|
|
128
|
+
raise LibError(f"not a c2sp ref: {text!r}", "start with the specification id, e.g. static-ct-api")
|
|
129
|
+
return Ref(ref.doc, ref.section.strip() if ref.section else None, ref.para, None, None)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def find_heading(doc: Loaded, name: str) -> tuple[markdown.Heading, str]:
|
|
133
|
+
"""The heading `name` names, and a warning ("" if none). Exact title, then slug, then case-folded title."""
|
|
134
|
+
headings = doc.document.headings
|
|
135
|
+
wanted = name.strip()
|
|
136
|
+
for match in (
|
|
137
|
+
[h for h in headings if h.title == wanted],
|
|
138
|
+
[h for h in headings if h.slug == wanted.lower()],
|
|
139
|
+
[h for h in headings if h.title.casefold() == wanted.casefold()],
|
|
140
|
+
):
|
|
141
|
+
if match:
|
|
142
|
+
warning = ""
|
|
143
|
+
if len(match) > 1:
|
|
144
|
+
slugs = ", ".join(h.slug for h in match)
|
|
145
|
+
warning = f"{len(match)} headings are titled {wanted!r}; this is the first. Use a slug: {slugs}"
|
|
146
|
+
return match[0], warning
|
|
147
|
+
close = difflib.get_close_matches(wanted, [h.title for h in headings], n=3, cutoff=0.5)
|
|
148
|
+
hint = f"did you mean: {', '.join(close)}" if close else f"c2sp_outline('{doc.id}') lists the headings"
|
|
149
|
+
raise LibError(f"no section {wanted!r} in {doc.id}@{doc.short}", hint)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def section_ref(doc: Loaded, heading: markdown.Heading) -> str:
|
|
153
|
+
"""The canonical ref of a heading: its title, or its slug if the title cannot appear in a ref."""
|
|
154
|
+
same = [h for h in doc.document.headings if h.title == heading.title]
|
|
155
|
+
use_slug = _UNSAFE_IN_REF.search(heading.title) is not None or len(same) > 1
|
|
156
|
+
return f"{doc.id}@{doc.short}§{heading.slug if use_slug else heading.title}"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _cite_text(doc: Loaded, heading: markdown.Heading | None, para: int | str | None) -> str:
|
|
160
|
+
where = f", §{heading.title}" if heading else ""
|
|
161
|
+
if para:
|
|
162
|
+
where += f" ¶{para}"
|
|
163
|
+
return (
|
|
164
|
+
f"C2SP, {doc.id} ({doc.title}), commit {doc.short}{where}. CC BY 4.0. "
|
|
165
|
+
f"https://github.com/C2SP/C2SP/blob/{doc.commit}/{doc.id}.md"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _doc(ref: Ref) -> str:
|
|
170
|
+
"""The document of a parsed ref; `parse_ref` has already refused a ref without one."""
|
|
171
|
+
if ref.doc is None:
|
|
172
|
+
raise LibError("the ref names no document", "start with the document id")
|
|
173
|
+
return ref.doc
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _select(ref: Ref, home: Path | None) -> tuple[Loaded, markdown.Heading | None, tuple[str, ...], str]:
|
|
177
|
+
doc = load(_doc(ref), home)
|
|
178
|
+
if ref.section is None:
|
|
179
|
+
return doc, None, doc.document.units(), ""
|
|
180
|
+
heading, warning = find_heading(doc, ref.section)
|
|
181
|
+
return doc, heading, heading.units, warning
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def read(ref_text: str, max_tokens: int = 2000, home: Path | None = None) -> str:
|
|
185
|
+
"""The text of a ref, cut at block boundaries within `max_tokens`. A `¶n` ref starts at block n and runs to the
|
|
186
|
+
end of its scope; the `next:` footer is the ref that reads the next page."""
|
|
187
|
+
ref = parse_ref(ref_text)
|
|
188
|
+
doc, heading, units, warning = _select(ref, home)
|
|
189
|
+
if not units:
|
|
190
|
+
raise LibError(f"{ref_text}: that section has no text of its own", "read its subsections, see c2sp_outline")
|
|
191
|
+
first = cursor.first_unit(ref.para, len(units), "blocks", ref_text)
|
|
192
|
+
chosen = budget.page(units, start=first, max_tokens=max_tokens)
|
|
193
|
+
body = "\n\n".join(chosen.units)
|
|
194
|
+
scope = section_ref(doc, heading) if heading else f"{doc.id}@{doc.short}"
|
|
195
|
+
footer = {
|
|
196
|
+
"cite": _cite_text(doc, heading, cursor.para_label(ref.para, chosen)),
|
|
197
|
+
"unit": "block",
|
|
198
|
+
"span": span_hash(body),
|
|
199
|
+
"next": cursor.read_next(scope, chosen),
|
|
200
|
+
"warning": warning,
|
|
201
|
+
}
|
|
202
|
+
return render(body, footer)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def outline(token: str, home: Path | None = None) -> str:
|
|
206
|
+
"""The headings of one specification, indented by level, each with the ref that reads it."""
|
|
207
|
+
doc = load(token, home)
|
|
208
|
+
lines = [f"{doc.title} [{doc.id}@{doc.short}]"]
|
|
209
|
+
for heading in doc.document.headings:
|
|
210
|
+
indent = " " * (heading.level - 1)
|
|
211
|
+
lines.append(f"{indent}{heading.title} {section_ref(doc, heading)} ({len(heading.units)} blocks)")
|
|
212
|
+
return "\n".join(lines)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _terms(query: str) -> list[str]:
|
|
216
|
+
terms = [t for t in _TERM.findall(query.casefold()) if len(t) <= 64]
|
|
217
|
+
if not terms:
|
|
218
|
+
raise LibError("the query has no searchable words", "use words or identifiers, e.g. 'checkpoint origin'")
|
|
219
|
+
return terms[:MAX_QUERY_TERMS]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _snippet(text: str, terms: list[str]) -> str:
|
|
223
|
+
flat = " ".join(text.split())
|
|
224
|
+
low = flat.casefold()
|
|
225
|
+
at = min((i for i in (low.find(t) for t in terms) if i >= 0), default=0)
|
|
226
|
+
begin = max(0, at - 60)
|
|
227
|
+
return ("..." if begin else "") + flat[begin : begin + 160] + ("..." if begin + 160 < len(flat) else "")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def search(
|
|
231
|
+
query: str,
|
|
232
|
+
doc: str | None = None,
|
|
233
|
+
limit: int = 10,
|
|
234
|
+
start: int = 0,
|
|
235
|
+
home: Path | None = None,
|
|
236
|
+
) -> str:
|
|
237
|
+
"""Blocks containing every query word (else at least half of them, flagged `relaxed`), best first: one line
|
|
238
|
+
per hit, `<ref>¶n: <snippet>`. Ranked by term frequency with a bonus for a match in the heading."""
|
|
239
|
+
terms = _terms(query)
|
|
240
|
+
home = home or default_home()
|
|
241
|
+
available = installs(home)
|
|
242
|
+
if not available:
|
|
243
|
+
raise LibError("no C2SP specifications are imported", "c2sp_docs lists the catalogue", IMPORT)
|
|
244
|
+
ids: list[str] = []
|
|
245
|
+
if doc:
|
|
246
|
+
ids = [doc]
|
|
247
|
+
else:
|
|
248
|
+
record_files = available[0][1].get("files")
|
|
249
|
+
ids = sorted(record_files) if isinstance(record_files, dict) else []
|
|
250
|
+
scored: list[tuple[float, int, str, str]] = []
|
|
251
|
+
relaxed = False
|
|
252
|
+
for need_all in (True, False):
|
|
253
|
+
for order, token in enumerate(ids):
|
|
254
|
+
loaded = load(token, home)
|
|
255
|
+
for heading in loaded.document.headings:
|
|
256
|
+
title = heading.title.casefold()
|
|
257
|
+
for index, block in enumerate(heading.units, start=1):
|
|
258
|
+
if _HEADING_LINE.match(block):
|
|
259
|
+
break # what follows belongs to a subsection, which is searched under its own heading
|
|
260
|
+
words = _TERM.findall(block.casefold())
|
|
261
|
+
hits = [words.count(t) for t in terms]
|
|
262
|
+
present = sum(1 for h in hits if h)
|
|
263
|
+
if present == 0 or (need_all and present < len(terms)) or present * 2 < len(terms):
|
|
264
|
+
continue
|
|
265
|
+
score = sum(hits) + 3 * present + sum(2 for t in terms if t in title)
|
|
266
|
+
ref = f"{section_ref(loaded, heading)}¶{index}"
|
|
267
|
+
scored.append((-score, order, ref, _snippet(block, terms)))
|
|
268
|
+
if scored:
|
|
269
|
+
relaxed = not need_all
|
|
270
|
+
break
|
|
271
|
+
if not scored:
|
|
272
|
+
return render("no matches", {})
|
|
273
|
+
scored.sort()
|
|
274
|
+
shown, nxt = cursor.window(scored, start, limit)
|
|
275
|
+
body = "\n".join(f"{ref}: {snip}" for _, _, ref, snip in shown)
|
|
276
|
+
return render(body, {"next": nxt, "relaxed": "only some query words matched" if relaxed else ""})
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def cite(ref_text: str, home: Path | None = None) -> str:
|
|
280
|
+
"""The citation string for a ref, with the span hash of the exact text cited."""
|
|
281
|
+
ref = parse_ref(ref_text)
|
|
282
|
+
doc, heading, units, warning = _select(ref, home)
|
|
283
|
+
if ref.para is not None:
|
|
284
|
+
if ref.para > len(units):
|
|
285
|
+
raise LibError(f"{ref_text}: the span has {len(units)} blocks", f"use ¶1 to ¶{len(units)}")
|
|
286
|
+
units = (units[ref.para - 1],)
|
|
287
|
+
footer = {
|
|
288
|
+
"cite": _cite_text(doc, heading, ref.para),
|
|
289
|
+
"unit": "block",
|
|
290
|
+
"span": span_hash("\n\n".join(units)),
|
|
291
|
+
"warning": warning,
|
|
292
|
+
}
|
|
293
|
+
return render(f"{doc.id}@{doc.short}" + (f"§{heading.title}" if heading else ""), footer)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def docs(home: Path | None = None) -> str:
|
|
297
|
+
"""The catalogue with what is imported: one line per specification."""
|
|
298
|
+
home = home or default_home()
|
|
299
|
+
cat = packaged_catalogue()
|
|
300
|
+
present: set[str] = set()
|
|
301
|
+
commits = installs(home)
|
|
302
|
+
for _, record in commits:
|
|
303
|
+
files = record.get("files")
|
|
304
|
+
if isinstance(files, dict):
|
|
305
|
+
present.update(files)
|
|
306
|
+
lines = [f"commit {cat.commit[:7]} ({cat.terms.licence}, placement {cat.terms.placement})"]
|
|
307
|
+
for spec in cat.specs:
|
|
308
|
+
lines.append(f"{'imported' if spec.id in present else 'not imported':<13} {spec.id} {spec.title}")
|
|
309
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The c2sp collection's Markdown reader: a re-export of the core's `primarysource.readers.markdown`.
|
|
2
|
+
|
|
3
|
+
The reader moved to the core (`cl.reader_markdown`); this module keeps `primarysource.c2sp.markdown` importable so
|
|
4
|
+
the importer, the library and their tests are unchanged.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from primarysource.readers.markdown import (
|
|
10
|
+
MAX_BYTES,
|
|
11
|
+
MAX_LINE,
|
|
12
|
+
MAX_SECTIONS,
|
|
13
|
+
Heading,
|
|
14
|
+
MarkdownDocument,
|
|
15
|
+
decode,
|
|
16
|
+
parse,
|
|
17
|
+
plain,
|
|
18
|
+
slugify,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"MAX_BYTES",
|
|
23
|
+
"MAX_LINE",
|
|
24
|
+
"MAX_SECTIONS",
|
|
25
|
+
"Heading",
|
|
26
|
+
"MarkdownDocument",
|
|
27
|
+
"decode",
|
|
28
|
+
"parse",
|
|
29
|
+
"plain",
|
|
30
|
+
"slugify",
|
|
31
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""MCP server for the c2sp collection: `primarysource mcp --collection c2sp`.
|
|
2
|
+
|
|
3
|
+
Imported only when served, so `mcp` (the `[mcp]` extra) is needed only then. Tools carry the `c2sp_` prefix,
|
|
4
|
+
answer in text with the fixed footer, and turn a `LibError` into an MCP tool error naming the remedy. Every tool
|
|
5
|
+
reads local files only; none writes or fetches.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from mcp.server.mcpserver import MCPServer
|
|
13
|
+
from mcp.types import ToolAnnotations
|
|
14
|
+
|
|
15
|
+
from primarysource import __version__
|
|
16
|
+
from primarysource.c2sp import library
|
|
17
|
+
from primarysource.scaffold import add_tools
|
|
18
|
+
|
|
19
|
+
READ_ONLY = ToolAnnotations(read_only_hint=True, idempotent_hint=True, destructive_hint=False, open_world_hint=False)
|
|
20
|
+
|
|
21
|
+
INSTRUCTIONS = (
|
|
22
|
+
"C2SP specifications (CC BY 4.0) at a pinned commit: static-ct-api, tlog-tiles, tlog-checkpoint, signed-note, "
|
|
23
|
+
"and the rest of the C2SP repository's top-level documents. Loop: c2sp_search('<words>') -> c2sp_read('<ref>'). "
|
|
24
|
+
"Refs: 'static-ct-api§Log Entries' (a heading by its text or slug), '...¶2' (its 2nd block), "
|
|
25
|
+
"'static-ct-api@09f3e51§...' to pin a commit. c2sp_outline('<doc>') lists headings as refs; c2sp_cite('<ref>') "
|
|
26
|
+
"gives the citation and the hash of the cited text. If nothing is imported, the error says how."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def c2sp_read(ref: str, max_tokens: int = 2000) -> str:
|
|
31
|
+
"""Read C2SP text by ref, cut at block boundaries within max_tokens. A 'next: <ref>' footer line is the ref
|
|
32
|
+
that reads the next page: pass it back as ref.
|
|
33
|
+
|
|
34
|
+
ref: 'static-ct-api' (whole document) | 'static-ct-api§Log Entries' (a section, subsections included) |
|
|
35
|
+
'...§Log Entries¶2' (from its 2nd block) | 'static-ct-api@09f3e51§...' (pinned commit). A 'c2sp:' prefix is
|
|
36
|
+
allowed. c2sp_cite gives exactly one block."""
|
|
37
|
+
return library.read(ref, max_tokens=max_tokens)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def c2sp_search(query: str, doc: str | None = None, limit: int = 10, start: int = 0) -> str:
|
|
41
|
+
"""Find blocks containing the query words (all of them, else at least half, flagged 'relaxed'). One line per
|
|
42
|
+
hit: '<ref>¶n: <snippet>'. doc restricts to one specification. A 'next: start=N' footer line means call again
|
|
43
|
+
with start=N (a 0-based offset)."""
|
|
44
|
+
return library.search(query, doc=doc, limit=limit, start=start)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def c2sp_outline(doc: str) -> str:
|
|
48
|
+
"""List a specification's headings, indented by level, each with the ref that reads it. doc: 'tlog-tiles'."""
|
|
49
|
+
return library.outline(doc)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def c2sp_cite(ref: str) -> str:
|
|
53
|
+
"""The citation string for a ref, with the sha256 prefix of the exact text cited (cite: and span: lines)."""
|
|
54
|
+
return library.cite(ref)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def c2sp_docs() -> str:
|
|
58
|
+
"""List the catalogued specifications and whether each is imported."""
|
|
59
|
+
return library.docs()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _build() -> Any:
|
|
63
|
+
built = MCPServer("primarysource-c2sp", version=__version__, instructions=INSTRUCTIONS)
|
|
64
|
+
add_tools(built, (c2sp_read, c2sp_search, c2sp_outline, c2sp_cite, c2sp_docs), annotations=READ_ONLY)
|
|
65
|
+
return built
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
server = _build()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""NIST cryptographic test vectors as a primarysource collection: CAVP `.rsp` files and ACVP JSON vector sets.
|
|
2
|
+
|
|
3
|
+
Vectors are not documents: the unit is a case, and the ref names set, group and case
|
|
4
|
+
(`nist:cavp/ecdsa/SigVer.rsp#[P-256,SHA-256]/3`), so this is its own collection rather than part of `nist`.
|
|
5
|
+
Importing this package needs only the standard library; `mcp` is imported only by `server.py` when served.
|
|
6
|
+
`docs/cavp.md` is the user-facing description.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from primarysource.cavp.model import format_ref, parse_ref
|
|
12
|
+
from primarysource.errors import LibError
|
|
13
|
+
from primarysource.model import Unit
|
|
14
|
+
from primarysource.refs import Ref
|
|
15
|
+
from primarysource.registry import Collection
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CavpRefs:
|
|
19
|
+
"""The collection's `RefParser`, over `model.parse_ref`: the set id is `doc`, the group `section` and the case
|
|
20
|
+
`anchor` (`nist:cavp/ecdsa/SigVer.rsp#[P-256,SHA-256]/3`)."""
|
|
21
|
+
|
|
22
|
+
def parse(self, text: str) -> Ref:
|
|
23
|
+
ref = parse_ref(text)
|
|
24
|
+
return Ref(ref.set_id, ref.group, None, ref.case)
|
|
25
|
+
|
|
26
|
+
def format(self, ref: Ref) -> str:
|
|
27
|
+
if ref.doc is None or ref.para is not None or ref.lines is not None:
|
|
28
|
+
raise LibError(f"not a vector ref: {ref!r}", "a vector ref is a set, optionally a group and a case")
|
|
29
|
+
return format_ref(ref.doc, ref.section, ref.anchor)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
COLLECTION = Collection(
|
|
33
|
+
name="cavp",
|
|
34
|
+
title="NIST cryptographic test vectors (CAVP .rsp, ACVP JSON), pinned by sha256",
|
|
35
|
+
prefix="cavp_",
|
|
36
|
+
unit=Unit.BLOCK,
|
|
37
|
+
grammar=CavpRefs(),
|
|
38
|
+
server="primarysource.cavp.server:cavp_server",
|
|
39
|
+
cli="primarysource.cavp.cli:main",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = ["COLLECTION"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""`primarysource cavp ...`: the collection's own commands.
|
|
2
|
+
|
|
3
|
+
primarysource cavp fetch [id ...] download the pinned sources in vectors.toml, import them
|
|
4
|
+
primarysource cavp import <id> <file> [<expected-file>] import files you already have, under the same pins
|
|
5
|
+
primarysource cavp pin <file> print the sha256 to pin
|
|
6
|
+
primarysource cavp sets [query]
|
|
7
|
+
primarysource cavp groups <set-ref>
|
|
8
|
+
primarysource cavp read <ref> [--max-tokens N]
|
|
9
|
+
primarysource cavp find <set-ref> <field> <value> [--group G]
|
|
10
|
+
primarysource cavp cite <ref>
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from primarysource.cavp import importer, tools
|
|
21
|
+
from primarysource.cavp.model import sha256_bytes
|
|
22
|
+
from primarysource.cavp.paths import open_store
|
|
23
|
+
from primarysource.errors import LibError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parser() -> argparse.ArgumentParser:
|
|
27
|
+
parser = argparse.ArgumentParser(prog="primarysource cavp", description="NIST CAVP/ACVP test vectors")
|
|
28
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
29
|
+
sub.add_parser("fetch", help="download and import the pinned sources").add_argument("ids", nargs="*")
|
|
30
|
+
imp = sub.add_parser("import", help="import local files under a vectors.toml pin")
|
|
31
|
+
imp.add_argument("id")
|
|
32
|
+
imp.add_argument("file", type=Path)
|
|
33
|
+
imp.add_argument("expected", type=Path, nargs="?")
|
|
34
|
+
sub.add_parser("pin", help="print a file's sha256").add_argument("file", type=Path)
|
|
35
|
+
sub.add_parser("sets").add_argument("query", nargs="?")
|
|
36
|
+
sub.add_parser("groups").add_argument("ref")
|
|
37
|
+
read = sub.add_parser("read")
|
|
38
|
+
read.add_argument("ref")
|
|
39
|
+
read.add_argument("--max-tokens", type=int, default=2000)
|
|
40
|
+
find = sub.add_parser("find")
|
|
41
|
+
find.add_argument("ref")
|
|
42
|
+
find.add_argument("field")
|
|
43
|
+
find.add_argument("value")
|
|
44
|
+
find.add_argument("--group")
|
|
45
|
+
sub.add_parser("cite").add_argument("ref")
|
|
46
|
+
return parser
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _say(text: str) -> int:
|
|
50
|
+
sys.stdout.write(text if text.endswith("\n") else text + "\n")
|
|
51
|
+
return 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main(argv: Sequence[str]) -> int:
|
|
55
|
+
args = _parser().parse_args(list(argv))
|
|
56
|
+
store = open_store()
|
|
57
|
+
try:
|
|
58
|
+
if args.command == "pin":
|
|
59
|
+
return _say(sha256_bytes(args.file.read_bytes()))
|
|
60
|
+
if args.command in ("fetch", "import"):
|
|
61
|
+
known = {s.id: s for s in importer.load_sources()}
|
|
62
|
+
wanted = args.ids if args.command == "fetch" else [args.id]
|
|
63
|
+
unknown = [i for i in wanted if i not in known]
|
|
64
|
+
if unknown:
|
|
65
|
+
raise LibError(f"not in vectors.toml: {', '.join(unknown)}", f"known: {', '.join(known)}")
|
|
66
|
+
for source in (known[i] for i in (wanted or list(known))):
|
|
67
|
+
if args.command == "fetch":
|
|
68
|
+
entry = importer.fetch(store, source)
|
|
69
|
+
else:
|
|
70
|
+
entry = importer.import_files(store, source, args.file, args.expected)
|
|
71
|
+
print(f"{entry.id}: {entry.groups} groups, {entry.cases} cases, sha256 {entry.sha256}")
|
|
72
|
+
return 0
|
|
73
|
+
if args.command == "sets":
|
|
74
|
+
return _say(tools.sets(store, args.query))
|
|
75
|
+
if args.command == "groups":
|
|
76
|
+
return _say(tools.groups(store, args.ref))
|
|
77
|
+
if args.command == "read":
|
|
78
|
+
return _say(tools.read(store, args.ref, args.max_tokens))
|
|
79
|
+
if args.command == "find":
|
|
80
|
+
return _say(tools.find(store, args.ref, args.field, args.value, args.group))
|
|
81
|
+
return _say(tools.cite(store, args.ref))
|
|
82
|
+
except LibError as exc:
|
|
83
|
+
print(exc.as_text(), file=sys.stderr)
|
|
84
|
+
return 1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Vector sets `primarysource cavp fetch` may download. Each entry pins the sha256 of the artifact it downloads (the
|
|
2
|
+
# zip, or the ACVP prompt.json), measured 2026-11-13. No vector data is stored in this repository.
|
|
3
|
+
#
|
|
4
|
+
# CAVP zips are the ones CSRC lists on its "Digital Signatures" CAVP page for FIPS 186-4; the RSA zip is named
|
|
5
|
+
# 186-3 upstream and holds the FIPS 186-4 RSA SigVer response files. ACVP files are pinned to a commit of
|
|
6
|
+
# usnistgov/ACVP-Server (master at 2026-08-12), never to a branch.
|
|
7
|
+
|
|
8
|
+
[[vector]]
|
|
9
|
+
id = "cavp/ecdsa/SigVer.rsp"
|
|
10
|
+
kind = "cavp"
|
|
11
|
+
algorithm = "ECDSA"
|
|
12
|
+
url = "https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-4ecdsatestvectors.zip"
|
|
13
|
+
sha256 = "fe47cc92b4cee418236125c9ffbcd9bb01c8c34e74a4ba195d954bcb72824752"
|
|
14
|
+
member = "186-4ecdsatestvectors/SigVer.rsp"
|
|
15
|
+
|
|
16
|
+
[[vector]]
|
|
17
|
+
id = "cavp/rsa/SigVer15_186-3.rsp"
|
|
18
|
+
kind = "cavp"
|
|
19
|
+
algorithm = "RSA"
|
|
20
|
+
url = "https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-3rsatestvectors.zip"
|
|
21
|
+
sha256 = "8405aeb3572a4f98ed4b1a3ccb3f2f49e725462dd28ec4759d6a15d88855d19c"
|
|
22
|
+
member = "SigVer15_186-3.rsp"
|
|
23
|
+
|
|
24
|
+
[[vector]]
|
|
25
|
+
id = "cavp/rsa/SigVerPSS_186-3.rsp"
|
|
26
|
+
kind = "cavp"
|
|
27
|
+
algorithm = "RSA"
|
|
28
|
+
url = "https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-3rsatestvectors.zip"
|
|
29
|
+
sha256 = "8405aeb3572a4f98ed4b1a3ccb3f2f49e725462dd28ec4759d6a15d88855d19c"
|
|
30
|
+
member = "SigVerPSS_186-3.rsp"
|
|
31
|
+
|
|
32
|
+
[[vector]]
|
|
33
|
+
id = "cavp/rsa/SigVer931_186-3.rsp"
|
|
34
|
+
kind = "cavp"
|
|
35
|
+
algorithm = "RSA"
|
|
36
|
+
url = "https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-3rsatestvectors.zip"
|
|
37
|
+
sha256 = "8405aeb3572a4f98ed4b1a3ccb3f2f49e725462dd28ec4759d6a15d88855d19c"
|
|
38
|
+
member = "SigVer931_186-3.rsp"
|
|
39
|
+
|
|
40
|
+
[[vector]]
|
|
41
|
+
id = "acvp/ECDSA-SigVer-1.0"
|
|
42
|
+
kind = "acvp"
|
|
43
|
+
algorithm = "ECDSA"
|
|
44
|
+
url = "https://raw.githubusercontent.com/usnistgov/ACVP-Server/975de31eb83d87039ec88934fdc47d8c312b892d/gen-val/json-files/ECDSA-SigVer-1.0/prompt.json"
|
|
45
|
+
sha256 = "eb41243f151f14ecb8e906d5d20a2809262fa9d8442a48cb4299ced5e6f30f8b"
|
|
46
|
+
expected_url = "https://raw.githubusercontent.com/usnistgov/ACVP-Server/975de31eb83d87039ec88934fdc47d8c312b892d/gen-val/json-files/ECDSA-SigVer-1.0/expectedResults.json"
|
|
47
|
+
expected_sha256 = "8a2b5e18f08cfa4846dcbc64c5f38bde94127c9549feeb42130acc4fcfa5f97e"
|