source: trunk/src/allmydata/storage/mutable_schema.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 3.8 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5import struct
6
7import attr
8
9from ..util.hashutil import (
10    tagged_hash,
11)
12from .lease import (
13    LeaseInfo,
14)
15from .lease_schema import (
16    v1_mutable,
17    v2_mutable,
18)
19
20def _magic(version):
21    # type: (int) -> bytes
22    """
23    Compute a "magic" header string for a container of the given version.
24
25    :param version: The version number of the container.
26    """
27    # Make it easy for people to recognize
28    human_readable = u"Tahoe mutable container v{:d}\n".format(version).encode("ascii")
29    # But also keep the chance of accidental collision low
30    if version == 1:
31        # It's unclear where this byte sequence came from.  It may have just
32        # been random.  In any case, preserve it since it is the magic marker
33        # in all v1 share files.
34        random_bytes = b"\x75\x09\x44\x03\x8e"
35    else:
36        # For future versions, use a reproducable scheme.
37        random_bytes = tagged_hash(
38            b"allmydata_mutable_container_header",
39            human_readable,
40            truncate_to=5,
41        )
42    magic = human_readable + random_bytes
43    assert len(magic) == 32
44    if version > 1:
45        # The chance of collision is pretty low but let's just be sure about
46        # it.
47        assert magic != _magic(version - 1)
48
49    return magic
50
51def _header(magic, extra_lease_offset, nodeid, write_enabler):
52    # type: (bytes, int, bytes, bytes) -> bytes
53    """
54    Construct a container header.
55
56    :param nodeid: A unique identifier for the node holding this
57        container.
58
59    :param write_enabler: A secret shared with the client used to
60        authorize changes to the contents of this container.
61    """
62    fixed_header = struct.pack(
63        ">32s20s32sQQ",
64        magic,
65        nodeid,
66        write_enabler,
67        # data length, initially the container is empty
68        0,
69        extra_lease_offset,
70    )
71    blank_leases = b"\x00" * LeaseInfo().mutable_size() * 4
72    extra_lease_count = struct.pack(">L", 0)
73
74    return b"".join([
75        fixed_header,
76        # share data will go in between the next two items eventually but
77        # for now there is none.
78        blank_leases,
79        extra_lease_count,
80    ])
81
82
83_HEADER_FORMAT = ">32s20s32sQQ"
84
85# This size excludes leases
86_HEADER_SIZE = struct.calcsize(_HEADER_FORMAT)
87
88_EXTRA_LEASE_OFFSET = _HEADER_SIZE + 4 * LeaseInfo().mutable_size()
89
90
91@attr.s(frozen=True)
92class _Schema(object):
93    """
94    Implement encoding and decoding for the mutable container.
95
96    :ivar int version: the version number of the schema this object supports
97
98    :ivar lease_serializer: an object that is responsible for lease
99        serialization and unserialization
100    """
101    version = attr.ib()
102    lease_serializer = attr.ib()
103    _magic = attr.ib()
104
105    @classmethod
106    def for_version(cls, version, lease_serializer):
107        return cls(version, lease_serializer, magic=_magic(version))
108
109    def magic_matches(self, candidate_magic):
110        # type: (bytes) -> bool
111        """
112        Return ``True`` if a candidate string matches the expected magic string
113        from a mutable container header, ``False`` otherwise.
114        """
115        return candidate_magic[:len(self._magic)] == self._magic
116
117    def header(self, nodeid, write_enabler):
118        return _header(self._magic, _EXTRA_LEASE_OFFSET, nodeid, write_enabler)
119
120ALL_SCHEMAS = {
121    _Schema.for_version(version=2, lease_serializer=v2_mutable),
122    _Schema.for_version(version=1, lease_serializer=v1_mutable),
123}
124ALL_SCHEMA_VERSIONS = {schema.version for schema in ALL_SCHEMAS}
125NEWEST_SCHEMA_VERSION = max(ALL_SCHEMAS, key=lambda schema: schema.version)
126
127def schema_from_header(header):
128    # (int) -> Optional[type]
129    """
130    Find the schema object that corresponds to a certain version number.
131    """
132    for schema in ALL_SCHEMAS:
133        if schema.magic_matches(header):
134            return schema
135    return None
Note: See TracBrowser for help on using the repository browser.