aboutsummaryrefslogtreecommitdiffstats
path: root/bsfs/utils/uuid.py
blob: 6366b180300a7bc0908042fca2b0870d1bd2c0d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""

Part of the BlackStar filesystem (bsfs) module.
A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2022
"""
# imports
from collections import abc
import hashlib
import os
import platform
import random
import threading
import time
import typing
import uuid

# constants
HASH = hashlib.sha256

# exports
__all__: typing.Sequence[str] = [
    'UCID',
    'UUID',
    ]


## code ##

class UUID(abc.Iterator, abc.Callable): # type: ignore [misc] # abc.Callable "is an invalid base class"
    """Generate 256-bit universally unique IDs.

    This is a 'best-effort' kind of implementation that tries to ensure global
    uniqueness, even tough actual uniqueness cannot be guaranteed.
    The approach is different from python's uuid module (which implements
    RFC 4122) in that it generates longer UUIDs and in that it cannot be
    reconstructed whether two UUIDs were generated on the same system.

    The ID is a cryptographic hash over several components:
    * host
    * system
    * process
    * thread
    * random
    * time
    * cpu cycles
    * content (if available)

    """

    # host identifier
    host: str

    # system identifier
    system: str

    # process identifier
    process: str

    # thread identifier
    thread: str

    def __init__(self, seed: typing.Optional[int] = None):
        # initialize static components
        self.host = str(uuid.getnode())
        self.system = '-'.join(platform.uname())
        self.process = str(os.getpid())
        self.thread = str(threading.get_ident())
        # initialize random component
        random.seed(seed)

    def __call__(self, content: typing.Optional[str] = None) -> str: # pylint: disable=arguments-differ
        """Return a globally unique ID."""
        # content component
        content = str(content) if content is not None else ''
        # time component
        now = str(time.time())
        # clock component
        clk = str(time.perf_counter())
        # random component
        rnd = str(random.random())
        # build the token from all available components
        token = self.host + self.system + self.process + self.thread + rnd + now + clk + content
        # return the token's hash
        return HASH(token.encode('ascii', 'ignore')).hexdigest()

    def __iter__(self) -> typing.Iterator[str]:
        """Iterate indefinitely over universally unique IDs."""
        return self

    def __next__(self) -> str:
        """Generate universally unique IDs."""
        return self()


class UCID():
    """Generate 256-bit content IDs.

    Effectively computes a cryptographic hash over the content.

    """
    @staticmethod
    def from_path(path: str) -> str:
        """Read the content from a file."""
        with open(path, 'rb') as ifile:
            return HASH(ifile.read()).hexdigest()

## EOF ##