source: trunk/integration/vectors/vectors.py

Last change on this file was 781f448, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-01-20T21:26:23Z

Get the segment size parameter right

  • Property mode set to 100644
File size: 4.2 KB
Line 
1"""
2A module that loads pre-generated test vectors.
3
4:ivar DATA_PATH: The path of the file containing test vectors.
5
6:ivar capabilities: The capability test vectors.
7"""
8
9from __future__ import annotations
10
11from typing import TextIO
12from attrs import frozen
13from yaml import safe_load, safe_dump
14from base64 import b64encode, b64decode
15
16from twisted.python.filepath import FilePath
17
18from .model import Param, Sample, SeedParam
19from ..util import CHK, SSK
20
21DATA_PATH: FilePath = FilePath(__file__).sibling("test_vectors.yaml")
22
23# The version of the persisted test vector data this code can interpret.
24CURRENT_VERSION: str = "2023-01-16.2"
25
26@frozen
27class Case:
28    """
29    Represent one case for which we want/have a test vector.
30    """
31    seed_params: Param
32    convergence: bytes
33    seed_data: Sample
34    fmt: CHK | SSK
35    segment_size: int
36
37    @property
38    def data(self):
39        return stretch(self.seed_data.seed, self.seed_data.length)
40
41    @property
42    def params(self):
43        return self.seed_params.realize(self.fmt.max_shares)
44
45
46def encode_bytes(b: bytes) -> str:
47    """
48    Base64 encode some bytes to text so they are representable in JSON.
49    """
50    return b64encode(b).decode("ascii")
51
52
53def decode_bytes(b: str) -> bytes:
54    """
55    Base64 decode some text to bytes.
56    """
57    return b64decode(b.encode("ascii"))
58
59
60def stretch(seed: bytes, size: int) -> bytes:
61    """
62    Given a simple description of a byte string, return the byte string
63    itself.
64    """
65    assert isinstance(seed, bytes)
66    assert isinstance(size, int)
67    assert size > 0
68    assert len(seed) > 0
69
70    multiples = size // len(seed) + 1
71    return (seed * multiples)[:size]
72
73
74def save_capabilities(results: list[tuple[Case, str]], path: FilePath = DATA_PATH) -> None:
75    """
76    Save some test vector cases and their expected values.
77
78    This is logically the inverse of ``load_capabilities``.
79    """
80    path.setContent(safe_dump({
81        "version": CURRENT_VERSION,
82        "vector": [
83            {
84                "convergence": encode_bytes(case.convergence),
85                "format": {
86                    "kind": case.fmt.kind,
87                    "params": case.fmt.to_json(),
88                },
89                "sample": {
90                    "seed": encode_bytes(case.seed_data.seed),
91                    "length": case.seed_data.length,
92                },
93                "zfec": {
94                    "segmentSize": case.segment_size,
95                    "required": case.params.required,
96                    "total": case.params.total,
97                },
98                "expected": cap,
99            }
100            for (case, cap)
101            in results
102        ],
103    }).encode("ascii"))
104
105
106def load_format(serialized: dict) -> CHK | SSK:
107    """
108    Load an encrypted object format from a simple description of it.
109
110    :param serialized: A ``dict`` describing either CHK or SSK, possibly with
111        some parameters.
112    """
113    if serialized["kind"] == "chk":
114        return CHK.load(serialized["params"])
115    elif serialized["kind"] == "ssk":
116        return SSK.load(serialized["params"])
117    else:
118        raise ValueError(f"Unrecognized format: {serialized}")
119
120
121def load_capabilities(f: TextIO) -> dict[Case, str]:
122    """
123    Load some test vector cases and their expected results from the given
124    file.
125
126    This is logically the inverse of ``save_capabilities``.
127    """
128    data = safe_load(f)
129    if data is None:
130        return {}
131    if data["version"] != CURRENT_VERSION:
132        print(
133            f"Current version is {CURRENT_VERSION}; "
134            f"cannot load version {data['version']} data."
135        )
136        return {}
137
138    return {
139        Case(
140            seed_params=SeedParam(case["zfec"]["required"], case["zfec"]["total"]),
141            segment_size=case["zfec"]["segmentSize"],
142            convergence=decode_bytes(case["convergence"]),
143            seed_data=Sample(decode_bytes(case["sample"]["seed"]), case["sample"]["length"]),
144            fmt=load_format(case["format"]),
145        ): case["expected"]
146        for case
147        in data["vector"]
148    }
149
150
151try:
152    with DATA_PATH.open() as f:
153        capabilities: dict[Case, str] = load_capabilities(f)
154except FileNotFoundError:
155    capabilities = {}
Note: See TracBrowser for help on using the repository browser.