-
Notifications
You must be signed in to change notification settings - Fork 516
/
util.py
152 lines (112 loc) · 4.04 KB
/
util.py
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""Utils for messages."""
import logging
import re
from datetime import datetime, timedelta, timezone
from hashlib import sha256
from math import floor
from typing import Any, Union
LOGGER = logging.getLogger(__name__)
I32_BOUND = 2 ** 31
def datetime_to_str(dt: Union[str, datetime]) -> str:
"""Convert a datetime object to an indy-standard datetime string.
Args:
dt: May be a string or datetime to allow automatic conversion
"""
if isinstance(dt, datetime):
dt = dt.replace(tzinfo=timezone.utc).isoformat(" ").replace("+00:00", "Z")
return dt
def str_to_datetime(dt: Union[str, datetime]) -> datetime:
"""Convert an indy-standard datetime string to a datetime.
Using a fairly lax regex pattern to match slightly different formats.
In Python 3.7 datetime.fromisoformat might be used.
Args:
dt: May be a string or datetime to allow automatic conversion
"""
if isinstance(dt, str):
match = re.match(
r"^(\d{4})-(\d\d)-(\d\d)[T ](\d\d):(\d\d)"
r"(?:\:(\d\d(?:\.\d{1,6})?))?([+-]\d\d:?\d\d|Z|)$",
dt,
)
if not match:
raise ValueError("String does not match expected time format")
year, month, day = match[1], match[2], match[3]
hour, minute, second = match[4], match[5], match[6]
tz = match[7]
if not second:
second = 0
microsecond = 0
else:
flt_second = float(second)
second = floor(flt_second)
microsecond = round((flt_second - second) * 1_000_000)
result = datetime(
int(year),
int(month),
int(day),
int(hour),
int(minute),
int(second),
microsecond,
timezone.utc,
)
if tz != "Z" and tz != "":
tz_sgn = int(tz[0] + "1")
tz_hours = int(tz[1:3])
tz_mins = int(tz[-2:])
if tz_hours or tz_mins:
result = result - timedelta(minutes=tz_sgn * (tz_hours * 60 + tz_mins))
return result
return dt
def str_to_epoch(dt: Union[str, datetime]) -> int:
"""Convert an indy-standard datetime string to epoch seconds.
Args:
dt: May be a string or datetime to allow automatic conversion
"""
return int(str_to_datetime(dt).timestamp())
def epoch_to_str(epoch: int) -> str:
"""Convert epoch seconds to indy-standard datetime string.
Args:
epoch: epoch seconds
"""
return datetime_to_str(datetime.fromtimestamp(epoch, tz=timezone.utc))
def datetime_now() -> datetime:
"""Timestamp in UTC."""
return datetime.utcnow().replace(tzinfo=timezone.utc)
def time_now() -> str:
"""Timestamp in ISO format."""
return datetime_to_str(datetime_now())
def encode(orig: Any) -> str:
"""
Encode a credential value as an int.
Encode credential attribute value, purely stringifying any int32
and leaving numeric int32 strings alone, but mapping any other
input to a stringified 256-bit (but not 32-bit) integer.
Predicates in indy-sdk operate
on int32 values properly only when their encoded values match their raw values.
Args:
orig: original value to encode
Returns:
encoded value
"""
if isinstance(orig, int) and -I32_BOUND <= orig < I32_BOUND:
return str(int(orig)) # python bools are ints
try:
i32orig = int(str(orig)) # don't encode floats as ints
if -I32_BOUND <= i32orig < I32_BOUND:
return str(i32orig)
except (ValueError, TypeError):
pass
rv = int.from_bytes(sha256(str(orig).encode()).digest(), "big")
return str(rv)
def canon(raw_attr_name: str) -> str:
"""
Canonicalize input attribute name for indy proofs and credential offers.
Args:
raw_attr_name: raw attribute name
Returns:
canonicalized attribute name
"""
if raw_attr_name: # do not dereference None, and "" is already canonical
return raw_attr_name.replace(" ", "").lower()
return raw_attr_name