/src/php-src/ext/hash/hash_gost.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Copyright © The PHP Group and Contributors. | |
4 | | +----------------------------------------------------------------------+ |
5 | | | This source file is subject to the Modified BSD License that is | |
6 | | | bundled with this package in the file LICENSE, and is available | |
7 | | | through the World Wide Web at <https://www.php.net/license/>. | |
8 | | | | |
9 | | | SPDX-License-Identifier: BSD-3-Clause | |
10 | | +----------------------------------------------------------------------+ |
11 | | | Authors: Michael Wallner <mike@php.net> | |
12 | | | Sara Golemon <pollita@php.net> | |
13 | | +----------------------------------------------------------------------+ |
14 | | */ |
15 | | |
16 | | #include "php_hash.h" |
17 | | #include "php_hash_gost.h" |
18 | | #include "php_hash_gost_tables.h" |
19 | | |
20 | | /* {{{ Gost() |
21 | | * derived from gost_compress() by Markku-Juhani Saarinen <mjos@ssh.fi> |
22 | | */ |
23 | | |
24 | | #define round(tables, k1, k2) \ |
25 | 3.27M | t = (k1) + r; \ |
26 | 3.27M | l ^= tables[0][t & 0xff] ^ tables[1][(t >> 8) & 0xff] ^ \ |
27 | 3.27M | tables[2][(t >> 16) & 0xff] ^ tables[3][t >> 24]; \ |
28 | 3.27M | t = (k2) + l; \ |
29 | 3.27M | r ^= tables[0][t & 0xff] ^ tables[1][(t >> 8) & 0xff] ^ \ |
30 | 3.27M | tables[2][(t >> 16) & 0xff] ^ tables[3][t >> 24]; |
31 | | |
32 | | #define R(tables, key, h, i, t, l, r) \ |
33 | 204k | r = h[i]; \ |
34 | 204k | l = h[i + 1]; \ |
35 | 204k | round(tables, key[0], key[1]) \ |
36 | 204k | round(tables, key[2], key[3]) \ |
37 | 204k | round(tables, key[4], key[5]) \ |
38 | 204k | round(tables, key[6], key[7]) \ |
39 | 204k | round(tables, key[0], key[1]) \ |
40 | 204k | round(tables, key[2], key[3]) \ |
41 | 204k | round(tables, key[4], key[5]) \ |
42 | 204k | round(tables, key[6], key[7]) \ |
43 | 204k | round(tables, key[0], key[1]) \ |
44 | 204k | round(tables, key[2], key[3]) \ |
45 | 204k | round(tables, key[4], key[5]) \ |
46 | 204k | round(tables, key[6], key[7]) \ |
47 | 204k | round(tables, key[7], key[6]) \ |
48 | 204k | round(tables, key[5], key[4]) \ |
49 | 204k | round(tables, key[3], key[2]) \ |
50 | 204k | round(tables, key[1], key[0]) \ |
51 | 204k | t = r; \ |
52 | 204k | r = l; \ |
53 | 204k | l = t; \ |
54 | | |
55 | | #define X(w, u, v) \ |
56 | 204k | w[0] = u[0] ^ v[0]; \ |
57 | 204k | w[1] = u[1] ^ v[1]; \ |
58 | 204k | w[2] = u[2] ^ v[2]; \ |
59 | 204k | w[3] = u[3] ^ v[3]; \ |
60 | 204k | w[4] = u[4] ^ v[4]; \ |
61 | 204k | w[5] = u[5] ^ v[5]; \ |
62 | 204k | w[6] = u[6] ^ v[6]; \ |
63 | 204k | w[7] = u[7] ^ v[7]; |
64 | | |
65 | | #define P(key, w) \ |
66 | 204k | key[0] = (w[0] & 0x000000ff) | ((w[2] & 0x000000ff) << 8) | \ |
67 | 204k | ((w[4] & 0x000000ff) << 16) | ((w[6] & 0x000000ff) << 24); \ |
68 | 204k | key[1] = ((w[0] & 0x0000ff00) >> 8) | (w[2] & 0x0000ff00) | \ |
69 | 204k | ((w[4] & 0x0000ff00) << 8) | ((w[6] & 0x0000ff00) << 16); \ |
70 | 204k | key[2] = ((w[0] & 0x00ff0000) >> 16) | ((w[2] & 0x00ff0000) >> 8) | \ |
71 | 204k | (w[4] & 0x00ff0000) | ((w[6] & 0x00ff0000) << 8); \ |
72 | 204k | key[3] = ((w[0] & 0xff000000) >> 24) | ((w[2] & 0xff000000) >> 16) | \ |
73 | 204k | ((w[4] & 0xff000000) >> 8) | (w[6] & 0xff000000); \ |
74 | 204k | key[4] = (w[1] & 0x000000ff) | ((w[3] & 0x000000ff) << 8) | \ |
75 | 204k | ((w[5] & 0x000000ff) << 16) | ((w[7] & 0x000000ff) << 24); \ |
76 | 204k | key[5] = ((w[1] & 0x0000ff00) >> 8) | (w[3] & 0x0000ff00) | \ |
77 | 204k | ((w[5] & 0x0000ff00) << 8) | ((w[7] & 0x0000ff00) << 16); \ |
78 | 204k | key[6] = ((w[1] & 0x00ff0000) >> 16) | ((w[3] & 0x00ff0000) >> 8) | \ |
79 | 204k | (w[5] & 0x00ff0000) | ((w[7] & 0x00ff0000) << 8); \ |
80 | 204k | key[7] = ((w[1] & 0xff000000) >> 24) | ((w[3] & 0xff000000) >> 16) | \ |
81 | 204k | ((w[5] & 0xff000000) >> 8) | (w[7] & 0xff000000); |
82 | | |
83 | | #define A(x, l, r) \ |
84 | 153k | l = x[0] ^ x[2]; \ |
85 | 153k | r = x[1] ^ x[3]; \ |
86 | 153k | x[0] = x[2]; \ |
87 | 153k | x[1] = x[3]; \ |
88 | 153k | x[2] = x[4]; \ |
89 | 153k | x[3] = x[5]; \ |
90 | 153k | x[4] = x[6]; \ |
91 | 153k | x[5] = x[7]; \ |
92 | 153k | x[6] = l; \ |
93 | 153k | x[7] = r; |
94 | | |
95 | | #define AA(x, l, r) \ |
96 | 153k | l = x[0]; \ |
97 | 153k | r = x[2]; \ |
98 | 153k | x[0] = x[4]; \ |
99 | 153k | x[2] = x[6]; \ |
100 | 153k | x[4] = l ^ r; \ |
101 | 153k | x[6] = x[0] ^ r; \ |
102 | 153k | l = x[1]; \ |
103 | 153k | r = x[3]; \ |
104 | 153k | x[1] = x[5]; \ |
105 | 153k | x[3] = x[7]; \ |
106 | 153k | x[5] = l ^ r; \ |
107 | 153k | x[7] = x[1] ^ r; |
108 | | |
109 | | #define C(x) \ |
110 | 51.2k | x[0] ^= 0xff00ff00; \ |
111 | 51.2k | x[1] ^= 0xff00ff00; \ |
112 | 51.2k | x[2] ^= 0x00ff00ff; \ |
113 | 51.2k | x[3] ^= 0x00ff00ff; \ |
114 | 51.2k | x[4] ^= 0x00ffff00; \ |
115 | 51.2k | x[5] ^= 0xff0000ff; \ |
116 | 51.2k | x[6] ^= 0x000000ff; \ |
117 | 51.2k | x[7] ^= 0xff00ffff; |
118 | | |
119 | | #define S(s, l, r) \ |
120 | 204k | s[i] = r; \ |
121 | 204k | s[i + 1] = l; |
122 | | |
123 | | #define SHIFT12(u, m, s) \ |
124 | 51.2k | u[0] = m[0] ^ s[6]; \ |
125 | 51.2k | u[1] = m[1] ^ s[7]; \ |
126 | 51.2k | u[2] = m[2] ^ (s[0] << 16) ^ (s[0] >> 16) ^ (s[0] & 0xffff) ^ \ |
127 | 51.2k | (s[1] & 0xffff) ^ (s[1] >> 16) ^ (s[2] << 16) ^ s[6] ^ (s[6] << 16) ^ \ |
128 | 51.2k | (s[7] & 0xffff0000) ^ (s[7] >> 16); \ |
129 | 51.2k | u[3] = m[3] ^ (s[0] & 0xffff) ^ (s[0] << 16) ^ (s[1] & 0xffff) ^ \ |
130 | 51.2k | (s[1] << 16) ^ (s[1] >> 16) ^ (s[2] << 16) ^ (s[2] >> 16) ^ \ |
131 | 51.2k | (s[3] << 16) ^ s[6] ^ (s[6] << 16) ^ (s[6] >> 16) ^ (s[7] & 0xffff) ^ \ |
132 | 51.2k | (s[7] << 16) ^ (s[7] >> 16); \ |
133 | 51.2k | u[4] = m[4] ^ \ |
134 | 51.2k | (s[0] & 0xffff0000) ^ (s[0] << 16) ^ (s[0] >> 16) ^ \ |
135 | 51.2k | (s[1] & 0xffff0000) ^ (s[1] >> 16) ^ (s[2] << 16) ^ (s[2] >> 16) ^ \ |
136 | 51.2k | (s[3] << 16) ^ (s[3] >> 16) ^ (s[4] << 16) ^ (s[6] << 16) ^ \ |
137 | 51.2k | (s[6] >> 16) ^(s[7] & 0xffff) ^ (s[7] << 16) ^ (s[7] >> 16); \ |
138 | 51.2k | u[5] = m[5] ^ (s[0] << 16) ^ (s[0] >> 16) ^ (s[0] & 0xffff0000) ^ \ |
139 | 51.2k | (s[1] & 0xffff) ^ s[2] ^ (s[2] >> 16) ^ (s[3] << 16) ^ (s[3] >> 16) ^ \ |
140 | 51.2k | (s[4] << 16) ^ (s[4] >> 16) ^ (s[5] << 16) ^ (s[6] << 16) ^ \ |
141 | 51.2k | (s[6] >> 16) ^ (s[7] & 0xffff0000) ^ (s[7] << 16) ^ (s[7] >> 16); \ |
142 | 51.2k | u[6] = m[6] ^ s[0] ^ (s[1] >> 16) ^ (s[2] << 16) ^ s[3] ^ (s[3] >> 16) ^ \ |
143 | 51.2k | (s[4] << 16) ^ (s[4] >> 16) ^ (s[5] << 16) ^ (s[5] >> 16) ^ s[6] ^ \ |
144 | 51.2k | (s[6] << 16) ^ (s[6] >> 16) ^ (s[7] << 16); \ |
145 | 51.2k | u[7] = m[7] ^ (s[0] & 0xffff0000) ^ (s[0] << 16) ^ (s[1] & 0xffff) ^ \ |
146 | 51.2k | (s[1] << 16) ^ (s[2] >> 16) ^ (s[3] << 16) ^ s[4] ^ (s[4] >> 16) ^ \ |
147 | 51.2k | (s[5] << 16) ^ (s[5] >> 16) ^ (s[6] >> 16) ^ (s[7] & 0xffff) ^ \ |
148 | 51.2k | (s[7] << 16) ^ (s[7] >> 16); |
149 | | |
150 | | #define SHIFT16(h, v, u) \ |
151 | 51.2k | v[0] = h[0] ^ (u[1] << 16) ^ (u[0] >> 16); \ |
152 | 51.2k | v[1] = h[1] ^ (u[2] << 16) ^ (u[1] >> 16); \ |
153 | 51.2k | v[2] = h[2] ^ (u[3] << 16) ^ (u[2] >> 16); \ |
154 | 51.2k | v[3] = h[3] ^ (u[4] << 16) ^ (u[3] >> 16); \ |
155 | 51.2k | v[4] = h[4] ^ (u[5] << 16) ^ (u[4] >> 16); \ |
156 | 51.2k | v[5] = h[5] ^ (u[6] << 16) ^ (u[5] >> 16); \ |
157 | 51.2k | v[6] = h[6] ^ (u[7] << 16) ^ (u[6] >> 16); \ |
158 | 51.2k | v[7] = h[7] ^ (u[0] & 0xffff0000) ^ (u[0] << 16) ^ (u[7] >> 16) ^ \ |
159 | 51.2k | (u[1] & 0xffff0000) ^ (u[1] << 16) ^ (u[6] << 16) ^ (u[7] & 0xffff0000); |
160 | | |
161 | | #define SHIFT61(h, v) \ |
162 | 51.2k | h[0] = (v[0] & 0xffff0000) ^ (v[0] << 16) ^ (v[0] >> 16) ^ (v[1] >> 16) ^ \ |
163 | 51.2k | (v[1] & 0xffff0000) ^ (v[2] << 16) ^ (v[3] >> 16) ^ (v[4] << 16) ^ \ |
164 | 51.2k | (v[5] >> 16) ^ v[5] ^ (v[6] >> 16) ^ (v[7] << 16) ^ (v[7] >> 16) ^ \ |
165 | 51.2k | (v[7] & 0xffff); \ |
166 | 51.2k | h[1] = (v[0] << 16) ^ (v[0] >> 16) ^ (v[0] & 0xffff0000) ^ (v[1] & 0xffff) ^ \ |
167 | 51.2k | v[2] ^ (v[2] >> 16) ^ (v[3] << 16) ^ (v[4] >> 16) ^ (v[5] << 16) ^ \ |
168 | 51.2k | (v[6] << 16) ^ v[6] ^ (v[7] & 0xffff0000) ^ (v[7] >> 16); \ |
169 | 51.2k | h[2] = (v[0] & 0xffff) ^ (v[0] << 16) ^ (v[1] << 16) ^ (v[1] >> 16) ^ \ |
170 | 51.2k | (v[1] & 0xffff0000) ^ (v[2] << 16) ^ (v[3] >> 16) ^ v[3] ^ (v[4] << 16) ^ \ |
171 | 51.2k | (v[5] >> 16) ^ v[6] ^ (v[6] >> 16) ^ (v[7] & 0xffff) ^ (v[7] << 16) ^ \ |
172 | 51.2k | (v[7] >> 16); \ |
173 | 51.2k | h[3] = (v[0] << 16) ^ (v[0] >> 16) ^ (v[0] & 0xffff0000) ^ \ |
174 | 51.2k | (v[1] & 0xffff0000) ^ (v[1] >> 16) ^ (v[2] << 16) ^ (v[2] >> 16) ^ v[2] ^ \ |
175 | 51.2k | (v[3] << 16) ^ (v[4] >> 16) ^ v[4] ^ (v[5] << 16) ^ (v[6] << 16) ^ \ |
176 | 51.2k | (v[7] & 0xffff) ^ (v[7] >> 16); \ |
177 | 51.2k | h[4] = (v[0] >> 16) ^ (v[1] << 16) ^ v[1] ^ (v[2] >> 16) ^ v[2] ^ \ |
178 | 51.2k | (v[3] << 16) ^ (v[3] >> 16) ^ v[3] ^ (v[4] << 16) ^ (v[5] >> 16) ^ \ |
179 | 51.2k | v[5] ^ (v[6] << 16) ^ (v[6] >> 16) ^ (v[7] << 16); \ |
180 | 51.2k | h[5] = (v[0] << 16) ^ (v[0] & 0xffff0000) ^ (v[1] << 16) ^ (v[1] >> 16) ^ \ |
181 | 51.2k | (v[1] & 0xffff0000) ^ (v[2] << 16) ^ v[2] ^ (v[3] >> 16) ^ v[3] ^ \ |
182 | 51.2k | (v[4] << 16) ^ (v[4] >> 16) ^ v[4] ^ (v[5] << 16) ^ (v[6] << 16) ^ \ |
183 | 51.2k | (v[6] >> 16) ^ v[6] ^ (v[7] << 16) ^ (v[7] >> 16) ^ (v[7] & 0xffff0000); \ |
184 | 51.2k | h[6] = v[0] ^ v[2] ^ (v[2] >> 16) ^ v[3] ^ (v[3] << 16) ^ v[4] ^ \ |
185 | 51.2k | (v[4] >> 16) ^ (v[5] << 16) ^ (v[5] >> 16) ^ v[5] ^ (v[6] << 16) ^ \ |
186 | 51.2k | (v[6] >> 16) ^ v[6] ^ (v[7] << 16) ^ v[7]; \ |
187 | 51.2k | h[7] = v[0] ^ (v[0] >> 16) ^ (v[1] << 16) ^ (v[1] >> 16) ^ (v[2] << 16) ^ \ |
188 | 51.2k | (v[3] >> 16) ^ v[3] ^ (v[4] << 16) ^ v[4] ^ (v[5] >> 16) ^ v[5] ^ \ |
189 | 51.2k | (v[6] << 16) ^ (v[6] >> 16) ^ (v[7] << 16) ^ v[7]; |
190 | | |
191 | | #define PASS(tables) \ |
192 | 204k | X(w, u, v); \ |
193 | 204k | P(key, w); \ |
194 | 204k | R((tables), key, h, i, t, l, r); \ |
195 | 204k | S(s, l, r); \ |
196 | 204k | if (i != 6) { \ |
197 | 153k | A(u, l, r); \ |
198 | 153k | if (i == 2) { \ |
199 | 51.2k | C(u); \ |
200 | 51.2k | } \ |
201 | 153k | AA(v, l, r); \ |
202 | 153k | } |
203 | | |
204 | | static inline void Gost(PHP_GOST_CTX *context, uint32_t data[8]) |
205 | 51.2k | { |
206 | 51.2k | int i; |
207 | 51.2k | uint32_t l, r, t, key[8], u[8], v[8], w[8], s[8], *h = context->state, *m = data; |
208 | | |
209 | 51.2k | memcpy(u, context->state, sizeof(u)); |
210 | 51.2k | memcpy(v, data, sizeof(v)); |
211 | | |
212 | 256k | for (i = 0; i < 8; i += 2) { |
213 | 204k | PASS(*context->tables); |
214 | 204k | } |
215 | 51.2k | SHIFT12(u, m, s); |
216 | 51.2k | SHIFT16(h, v, u); |
217 | 51.2k | SHIFT61(h, v); |
218 | 51.2k | } |
219 | | /* }}} */ |
220 | | |
221 | | static inline void GostTransform(PHP_GOST_CTX *context, const unsigned char input[32]) |
222 | 50.9k | { |
223 | 50.9k | int i, j; |
224 | 50.9k | uint32_t data[8], temp = 0; |
225 | | |
226 | 458k | for (i = 0, j = 0; i < 8; ++i, j += 4) { |
227 | 407k | data[i] = ((uint32_t) input[j]) | (((uint32_t) input[j + 1]) << 8) | |
228 | 407k | (((uint32_t) input[j + 2]) << 16) | (((uint32_t) input[j + 3]) << 24); |
229 | 407k | context->state[i + 8] += data[i] + temp; |
230 | 407k | temp = context->state[i + 8] < data[i] ? 1 : (context->state[i + 8] == data[i] ? temp : 0); |
231 | 407k | } |
232 | | |
233 | 50.9k | Gost(context, data); |
234 | 50.9k | } |
235 | | |
236 | | PHP_HASH_API void PHP_GOSTInit(PHP_GOST_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) |
237 | 133 | { |
238 | 133 | memset(context, 0, sizeof(*context)); |
239 | 133 | context->tables = &tables_test; |
240 | 133 | } |
241 | | |
242 | | PHP_HASH_API void PHP_GOSTInitCrypto(PHP_GOST_CTX *context, ZEND_ATTRIBUTE_UNUSED HashTable *args) |
243 | 1 | { |
244 | 1 | PHP_GOSTInit(context, NULL); |
245 | 1 | context->tables = &tables_crypto; |
246 | 1 | } |
247 | | |
248 | | static const uint32_t MAX32 = 0xffffffffLU; |
249 | | |
250 | | PHP_HASH_API void PHP_GOSTUpdate(PHP_GOST_CTX *context, const unsigned char *input, size_t len) |
251 | 124 | { |
252 | 124 | if ((MAX32 - context->count[0]) < (len * 8)) { |
253 | 41 | context->count[1]++; |
254 | 41 | context->count[0] = MAX32 - context->count[0]; |
255 | 41 | context->count[0] = (len * 8) - context->count[0]; |
256 | 83 | } else { |
257 | 83 | context->count[0] += len * 8; |
258 | 83 | } |
259 | | |
260 | 124 | if (context->length + len < 32) { |
261 | 5 | memcpy(&context->buffer[context->length], input, len); |
262 | 5 | context->length += (unsigned char)len; |
263 | 119 | } else { |
264 | 119 | size_t i = 0, r = (context->length + len) % 32; |
265 | | |
266 | 119 | if (context->length) { |
267 | 106 | i = 32 - context->length; |
268 | 106 | memcpy(&context->buffer[context->length], input, i); |
269 | 106 | GostTransform(context, context->buffer); |
270 | 106 | } |
271 | | |
272 | 50.8k | for (; i + 32 <= len; i += 32) { |
273 | 50.7k | GostTransform(context, input + i); |
274 | 50.7k | } |
275 | | |
276 | 119 | memcpy(context->buffer, input + i, r); |
277 | 119 | ZEND_SECURE_ZERO(&context->buffer[r], 32 - r); |
278 | 119 | context->length = (unsigned char)r; |
279 | 119 | } |
280 | 124 | } |
281 | | |
282 | | PHP_HASH_API void PHP_GOSTFinal(unsigned char digest[32], PHP_GOST_CTX *context) |
283 | 124 | { |
284 | 124 | uint32_t i, j, l[8] = {0}; |
285 | | |
286 | 124 | if (context->length) { |
287 | 109 | GostTransform(context, context->buffer); |
288 | 109 | } |
289 | | |
290 | 124 | memcpy(l, context->count, sizeof(context->count)); |
291 | 124 | Gost(context, l); |
292 | 124 | memcpy(l, &context->state[8], sizeof(l)); |
293 | 124 | Gost(context, l); |
294 | | |
295 | 1.11k | for (i = 0, j = 0; j < 32; i++, j += 4) { |
296 | 992 | digest[j] = (unsigned char) (context->state[i] & 0xff); |
297 | 992 | digest[j + 1] = (unsigned char) ((context->state[i] >> 8) & 0xff); |
298 | 992 | digest[j + 2] = (unsigned char) ((context->state[i] >> 16) & 0xff); |
299 | 992 | digest[j + 3] = (unsigned char) ((context->state[i] >> 24) & 0xff); |
300 | 992 | } |
301 | | |
302 | 124 | ZEND_SECURE_ZERO(context, sizeof(*context)); |
303 | 124 | } |
304 | | |
305 | | static hash_spec_result php_gost_unserialize(php_hashcontext_object *hash, zend_long magic, const zval *zv) |
306 | 133 | { |
307 | 133 | PHP_GOST_CTX *ctx = (PHP_GOST_CTX *) hash->context; |
308 | 133 | hash_spec_result r = HASH_SPEC_FAILURE; |
309 | 133 | if (magic == PHP_HASH_SERIALIZE_MAGIC_SPEC |
310 | 132 | && (r = php_hash_unserialize_spec(hash, zv, PHP_GOST_SPEC)) == HASH_SPEC_SUCCESS |
311 | 125 | && ctx->length < sizeof(ctx->buffer)) { |
312 | 124 | return HASH_SPEC_SUCCESS; |
313 | 124 | } |
314 | | |
315 | 9 | return r != HASH_SPEC_SUCCESS ? r : CONTEXT_VALIDATION_FAILURE; |
316 | 133 | } |
317 | | |
318 | | const php_hash_ops php_hash_gost_ops = { |
319 | | "gost", |
320 | | (php_hash_init_func_t) PHP_GOSTInit, |
321 | | (php_hash_update_func_t) PHP_GOSTUpdate, |
322 | | (php_hash_final_func_t) PHP_GOSTFinal, |
323 | | php_hash_copy, |
324 | | php_hash_serialize, |
325 | | php_gost_unserialize, |
326 | | PHP_GOST_SPEC, |
327 | | 32, |
328 | | 32, |
329 | | sizeof(PHP_GOST_CTX), |
330 | | 1 |
331 | | }; |
332 | | |
333 | | const php_hash_ops php_hash_gost_crypto_ops = { |
334 | | "gost-crypto", |
335 | | (php_hash_init_func_t) PHP_GOSTInitCrypto, |
336 | | (php_hash_update_func_t) PHP_GOSTUpdate, |
337 | | (php_hash_final_func_t) PHP_GOSTFinal, |
338 | | php_hash_copy, |
339 | | php_hash_serialize, |
340 | | php_gost_unserialize, |
341 | | PHP_GOST_SPEC, |
342 | | 32, |
343 | | 32, |
344 | | sizeof(PHP_GOST_CTX), |
345 | | 1 |
346 | | }; |