forked from EnAccess/OpenPAYGO-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtoken_decode.py
More file actions
239 lines (229 loc) · 8.9 KB
/
token_decode.py
File metadata and controls
239 lines (229 loc) · 8.9 KB
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
from .token_shared import OpenPAYGOTokenShared, TokenType
from .token_shared_extended import OpenPAYGOTokenSharedExtended
class OpenPAYGOTokenDecoder(object):
MAX_TOKEN_JUMP = 64
MAX_TOKEN_JUMP_COUNTER_SYNC = 100
MAX_UNUSED_OLDER_TOKENS = 8 * 2
@classmethod
def decode_token(
cls,
token,
secret_key,
count,
used_counts=None,
starting_code=None,
value_divider=1,
restricted_digit_set=False,
):
secret_key = OpenPAYGOTokenShared.load_secret_key_from_hex(secret_key)
if not starting_code:
# We generate the starting code from the key if not provided
starting_code = OpenPAYGOTokenShared.generate_starting_code(secret_key)
if not restricted_digit_set:
if len(token) <= 9:
extended_token = False
elif len(token) <= 12:
extended_token = True
else:
raise ValueError("Token is too long")
elif restricted_digit_set:
if len(token) <= 15:
extended_token = False
elif len(token) <= 20:
extended_token = True
else:
raise ValueError("Token is too long")
token = int(token)
if not extended_token:
(
value,
token_type,
count,
updated_counts,
) = cls.get_activation_value_count_and_type_from_token(
token,
starting_code,
secret_key,
count,
restricted_digit_set,
used_counts,
)
else:
(
value,
token_type,
count,
updated_counts,
) = cls.get_activation_value_count_from_extended_token(
token,
starting_code,
secret_key,
count,
restricted_digit_set,
used_counts,
)
if value and value_divider:
value = value / value_divider
return value, token_type, count, updated_counts
@classmethod
def get_activation_value_count_and_type_from_token(
cls,
token,
starting_code,
key,
last_count,
restricted_digit_set=False,
used_counts=None,
):
if restricted_digit_set:
token = OpenPAYGOTokenShared.convert_from_4_digit_token(token)
valid_older_token = False
token_base = OpenPAYGOTokenShared.get_token_base(
token
) # We get the base of the token
current_code = OpenPAYGOTokenShared.put_base_in_token(
starting_code, token_base
) # We put it into the starting code
starting_code_base = OpenPAYGOTokenShared.get_token_base(
starting_code
) # We get the base of the starting code
value = cls._decode_base(
starting_code_base, token_base
) # If there is a match we get the value from the token
# We try all combination up until last_count + TOKEN_JUMP, or to the larger jump
# if syncing counter.
# We could start directly the loop at the last count if we kept the token value
# for the last count
if value == OpenPAYGOTokenShared.COUNTER_SYNC_VALUE:
max_count_try = last_count + cls.MAX_TOKEN_JUMP_COUNTER_SYNC + 1
else:
max_count_try = last_count + cls.MAX_TOKEN_JUMP + 1
for count in range(0, max_count_try):
masked_token = OpenPAYGOTokenShared.put_base_in_token(
current_code, token_base
)
if count % 2:
if value == OpenPAYGOTokenShared.COUNTER_SYNC_VALUE:
this_type = TokenType.COUNTER_SYNC
elif value == OpenPAYGOTokenShared.PAYG_DISABLE_VALUE:
this_type = TokenType.DISABLE_PAYG
else:
this_type = TokenType.SET_TIME
else:
this_type = TokenType.ADD_TIME
if masked_token == token:
if cls._count_is_valid(
count, last_count, value, this_type, used_counts
):
updated_counts = cls.update_used_counts(
used_counts, value, count, this_type
)
return value, this_type, count, updated_counts
else:
valid_older_token = True
current_code = OpenPAYGOTokenShared.generate_next_token(
current_code, key
) # If not we go to the next token
if valid_older_token:
return None, TokenType.ALREADY_USED, None, None
return None, TokenType.INVALID, None, None
@classmethod
def _count_is_valid(cls, count, last_count, value, type, used_counts):
if value == OpenPAYGOTokenShared.COUNTER_SYNC_VALUE:
if count > (last_count - cls.MAX_TOKEN_JUMP):
return True
elif count > last_count:
return True
elif cls.MAX_UNUSED_OLDER_TOKENS > 0:
if count > last_count - cls.MAX_UNUSED_OLDER_TOKENS:
if count not in used_counts and type == TokenType.ADD_TIME:
return True
return False
@classmethod
def update_used_counts(cls, past_used_counts, value, new_count, type):
if not past_used_counts:
return None
highest_count = max(past_used_counts) if past_used_counts else 0
if new_count > highest_count:
highest_count = new_count
bottom_range = highest_count - cls.MAX_UNUSED_OLDER_TOKENS
used_counts = []
if (
type != TokenType.ADD_TIME
or value == OpenPAYGOTokenShared.COUNTER_SYNC_VALUE
or value == OpenPAYGOTokenShared.PAYG_DISABLE_VALUE
):
# If it is not an Add-Time token, we mark all the past tokens as used in the
# range
for count in range(bottom_range, highest_count + 1):
used_counts.append(count)
else:
# If it is an Add-Time token, we just mark the tokens actually used in the
# range
for count in range(bottom_range, highest_count + 1):
if count == new_count or count in past_used_counts:
used_counts.append(count)
return used_counts
@classmethod
def _decode_base(cls, starting_code_base, token_base):
decoded_value = token_base - starting_code_base
if decoded_value < 0:
return decoded_value + 1000
else:
return decoded_value
@classmethod
def get_activation_value_count_from_extended_token(
cls,
token,
starting_code,
key,
last_count,
restricted_digit_set=False,
used_counts=None,
):
if restricted_digit_set:
token = OpenPAYGOTokenSharedExtended.convert_from_4_digit_token(token)
token_base = OpenPAYGOTokenSharedExtended.get_token_base(
token
) # We get the base of the token
current_code = OpenPAYGOTokenSharedExtended.put_base_in_token(
starting_code, token_base
) # We put it into the starting code
starting_code_base = OpenPAYGOTokenSharedExtended.get_token_base(
starting_code
) # We get the base of the starting code
value = cls._decode_base_extended(
starting_code_base, token_base
) # If there is a match we get the value from the token
max_count_try = last_count + cls.MAX_TOKEN_JUMP + 1
for count in range(0, max_count_try):
masked_token = OpenPAYGOTokenSharedExtended.put_base_in_token(
current_code, token_base
)
if count % 2:
this_type = TokenType.SET_TIME
else:
this_type = TokenType.ADD_TIME
if masked_token == token:
if cls._count_is_valid(
count, last_count, value, this_type, used_counts
):
updated_counts = cls.update_used_counts(
used_counts, value, count, this_type
)
return value, this_type, count, updated_counts
else:
valid_older_token = True
current_code = OpenPAYGOTokenSharedExtended.generate_next_token(
current_code, key
) # If not we go to the next token
if valid_older_token:
return None, TokenType.ALREADY_USED, None, None
return None, TokenType.INVALID, None, None
@classmethod
def _decode_base_extended(cls, starting_code_base, token_base):
decoded_value = token_base - starting_code_base
if decoded_value < 0:
return decoded_value + 1000000
else:
return decoded_value