/src/postgres/src/backend/utils/adt/mac.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * mac.c |
4 | | * PostgreSQL type definitions for 6 byte, EUI-48, MAC addresses. |
5 | | * |
6 | | * Portions Copyright (c) 1998-2025, PostgreSQL Global Development Group |
7 | | * |
8 | | * IDENTIFICATION |
9 | | * src/backend/utils/adt/mac.c |
10 | | * |
11 | | *------------------------------------------------------------------------- |
12 | | */ |
13 | | |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "common/hashfn.h" |
17 | | #include "lib/hyperloglog.h" |
18 | | #include "libpq/pqformat.h" |
19 | | #include "port/pg_bswap.h" |
20 | | #include "utils/fmgrprotos.h" |
21 | | #include "utils/guc.h" |
22 | | #include "utils/inet.h" |
23 | | #include "utils/sortsupport.h" |
24 | | |
25 | | |
26 | | /* |
27 | | * Utility macros used for sorting and comparing: |
28 | | */ |
29 | | |
30 | | #define hibits(addr) \ |
31 | 0 | ((unsigned long)(((addr)->a<<16)|((addr)->b<<8)|((addr)->c))) |
32 | | |
33 | | #define lobits(addr) \ |
34 | 0 | ((unsigned long)(((addr)->d<<16)|((addr)->e<<8)|((addr)->f))) |
35 | | |
36 | | /* sortsupport for macaddr */ |
37 | | typedef struct |
38 | | { |
39 | | int64 input_count; /* number of non-null values seen */ |
40 | | bool estimating; /* true if estimating cardinality */ |
41 | | |
42 | | hyperLogLogState abbr_card; /* cardinality estimator */ |
43 | | } macaddr_sortsupport_state; |
44 | | |
45 | | static int macaddr_cmp_internal(macaddr *a1, macaddr *a2); |
46 | | static int macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup); |
47 | | static bool macaddr_abbrev_abort(int memtupcount, SortSupport ssup); |
48 | | static Datum macaddr_abbrev_convert(Datum original, SortSupport ssup); |
49 | | |
50 | | /* |
51 | | * MAC address reader. Accepts several common notations. |
52 | | */ |
53 | | |
54 | | Datum |
55 | | macaddr_in(PG_FUNCTION_ARGS) |
56 | 0 | { |
57 | 0 | char *str = PG_GETARG_CSTRING(0); |
58 | 0 | Node *escontext = fcinfo->context; |
59 | 0 | macaddr *result; |
60 | 0 | int a, |
61 | 0 | b, |
62 | 0 | c, |
63 | 0 | d, |
64 | 0 | e, |
65 | 0 | f; |
66 | 0 | char junk[2]; |
67 | 0 | int count; |
68 | | |
69 | | /* %1s matches iff there is trailing non-whitespace garbage */ |
70 | |
|
71 | 0 | count = sscanf(str, "%x:%x:%x:%x:%x:%x%1s", |
72 | 0 | &a, &b, &c, &d, &e, &f, junk); |
73 | 0 | if (count != 6) |
74 | 0 | count = sscanf(str, "%x-%x-%x-%x-%x-%x%1s", |
75 | 0 | &a, &b, &c, &d, &e, &f, junk); |
76 | 0 | if (count != 6) |
77 | 0 | count = sscanf(str, "%2x%2x%2x:%2x%2x%2x%1s", |
78 | 0 | &a, &b, &c, &d, &e, &f, junk); |
79 | 0 | if (count != 6) |
80 | 0 | count = sscanf(str, "%2x%2x%2x-%2x%2x%2x%1s", |
81 | 0 | &a, &b, &c, &d, &e, &f, junk); |
82 | 0 | if (count != 6) |
83 | 0 | count = sscanf(str, "%2x%2x.%2x%2x.%2x%2x%1s", |
84 | 0 | &a, &b, &c, &d, &e, &f, junk); |
85 | 0 | if (count != 6) |
86 | 0 | count = sscanf(str, "%2x%2x-%2x%2x-%2x%2x%1s", |
87 | 0 | &a, &b, &c, &d, &e, &f, junk); |
88 | 0 | if (count != 6) |
89 | 0 | count = sscanf(str, "%2x%2x%2x%2x%2x%2x%1s", |
90 | 0 | &a, &b, &c, &d, &e, &f, junk); |
91 | 0 | if (count != 6) |
92 | 0 | ereturn(escontext, (Datum) 0, |
93 | 0 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
94 | 0 | errmsg("invalid input syntax for type %s: \"%s\"", "macaddr", |
95 | 0 | str))); |
96 | | |
97 | 0 | if ((a < 0) || (a > 255) || (b < 0) || (b > 255) || |
98 | 0 | (c < 0) || (c > 255) || (d < 0) || (d > 255) || |
99 | 0 | (e < 0) || (e > 255) || (f < 0) || (f > 255)) |
100 | 0 | ereturn(escontext, (Datum) 0, |
101 | 0 | (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), |
102 | 0 | errmsg("invalid octet value in \"macaddr\" value: \"%s\"", str))); |
103 | | |
104 | 0 | result = (macaddr *) palloc(sizeof(macaddr)); |
105 | |
|
106 | 0 | result->a = a; |
107 | 0 | result->b = b; |
108 | 0 | result->c = c; |
109 | 0 | result->d = d; |
110 | 0 | result->e = e; |
111 | 0 | result->f = f; |
112 | |
|
113 | 0 | PG_RETURN_MACADDR_P(result); |
114 | 0 | } |
115 | | |
116 | | /* |
117 | | * MAC address output function. Fixed format. |
118 | | */ |
119 | | |
120 | | Datum |
121 | | macaddr_out(PG_FUNCTION_ARGS) |
122 | 0 | { |
123 | 0 | macaddr *addr = PG_GETARG_MACADDR_P(0); |
124 | 0 | char *result; |
125 | |
|
126 | 0 | result = (char *) palloc(32); |
127 | |
|
128 | 0 | snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x", |
129 | 0 | addr->a, addr->b, addr->c, addr->d, addr->e, addr->f); |
130 | |
|
131 | 0 | PG_RETURN_CSTRING(result); |
132 | 0 | } |
133 | | |
134 | | /* |
135 | | * macaddr_recv - converts external binary format to macaddr |
136 | | * |
137 | | * The external representation is just the six bytes, MSB first. |
138 | | */ |
139 | | Datum |
140 | | macaddr_recv(PG_FUNCTION_ARGS) |
141 | 0 | { |
142 | 0 | StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); |
143 | 0 | macaddr *addr; |
144 | |
|
145 | 0 | addr = (macaddr *) palloc(sizeof(macaddr)); |
146 | |
|
147 | 0 | addr->a = pq_getmsgbyte(buf); |
148 | 0 | addr->b = pq_getmsgbyte(buf); |
149 | 0 | addr->c = pq_getmsgbyte(buf); |
150 | 0 | addr->d = pq_getmsgbyte(buf); |
151 | 0 | addr->e = pq_getmsgbyte(buf); |
152 | 0 | addr->f = pq_getmsgbyte(buf); |
153 | |
|
154 | 0 | PG_RETURN_MACADDR_P(addr); |
155 | 0 | } |
156 | | |
157 | | /* |
158 | | * macaddr_send - converts macaddr to binary format |
159 | | */ |
160 | | Datum |
161 | | macaddr_send(PG_FUNCTION_ARGS) |
162 | 0 | { |
163 | 0 | macaddr *addr = PG_GETARG_MACADDR_P(0); |
164 | 0 | StringInfoData buf; |
165 | |
|
166 | 0 | pq_begintypsend(&buf); |
167 | 0 | pq_sendbyte(&buf, addr->a); |
168 | 0 | pq_sendbyte(&buf, addr->b); |
169 | 0 | pq_sendbyte(&buf, addr->c); |
170 | 0 | pq_sendbyte(&buf, addr->d); |
171 | 0 | pq_sendbyte(&buf, addr->e); |
172 | 0 | pq_sendbyte(&buf, addr->f); |
173 | 0 | PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); |
174 | 0 | } |
175 | | |
176 | | |
177 | | /* |
178 | | * Comparison function for sorting: |
179 | | */ |
180 | | |
181 | | static int |
182 | | macaddr_cmp_internal(macaddr *a1, macaddr *a2) |
183 | 0 | { |
184 | 0 | if (hibits(a1) < hibits(a2)) |
185 | 0 | return -1; |
186 | 0 | else if (hibits(a1) > hibits(a2)) |
187 | 0 | return 1; |
188 | 0 | else if (lobits(a1) < lobits(a2)) |
189 | 0 | return -1; |
190 | 0 | else if (lobits(a1) > lobits(a2)) |
191 | 0 | return 1; |
192 | 0 | else |
193 | 0 | return 0; |
194 | 0 | } |
195 | | |
196 | | Datum |
197 | | macaddr_cmp(PG_FUNCTION_ARGS) |
198 | 0 | { |
199 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
200 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
201 | |
|
202 | 0 | PG_RETURN_INT32(macaddr_cmp_internal(a1, a2)); |
203 | 0 | } |
204 | | |
205 | | /* |
206 | | * Boolean comparisons. |
207 | | */ |
208 | | |
209 | | Datum |
210 | | macaddr_lt(PG_FUNCTION_ARGS) |
211 | 0 | { |
212 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
213 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
214 | |
|
215 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) < 0); |
216 | 0 | } |
217 | | |
218 | | Datum |
219 | | macaddr_le(PG_FUNCTION_ARGS) |
220 | 0 | { |
221 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
222 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
223 | |
|
224 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) <= 0); |
225 | 0 | } |
226 | | |
227 | | Datum |
228 | | macaddr_eq(PG_FUNCTION_ARGS) |
229 | 0 | { |
230 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
231 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
232 | |
|
233 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) == 0); |
234 | 0 | } |
235 | | |
236 | | Datum |
237 | | macaddr_ge(PG_FUNCTION_ARGS) |
238 | 0 | { |
239 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
240 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
241 | |
|
242 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) >= 0); |
243 | 0 | } |
244 | | |
245 | | Datum |
246 | | macaddr_gt(PG_FUNCTION_ARGS) |
247 | 0 | { |
248 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
249 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
250 | |
|
251 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) > 0); |
252 | 0 | } |
253 | | |
254 | | Datum |
255 | | macaddr_ne(PG_FUNCTION_ARGS) |
256 | 0 | { |
257 | 0 | macaddr *a1 = PG_GETARG_MACADDR_P(0); |
258 | 0 | macaddr *a2 = PG_GETARG_MACADDR_P(1); |
259 | |
|
260 | 0 | PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) != 0); |
261 | 0 | } |
262 | | |
263 | | /* |
264 | | * Support function for hash indexes on macaddr. |
265 | | */ |
266 | | Datum |
267 | | hashmacaddr(PG_FUNCTION_ARGS) |
268 | 0 | { |
269 | 0 | macaddr *key = PG_GETARG_MACADDR_P(0); |
270 | |
|
271 | 0 | return hash_any((unsigned char *) key, sizeof(macaddr)); |
272 | 0 | } |
273 | | |
274 | | Datum |
275 | | hashmacaddrextended(PG_FUNCTION_ARGS) |
276 | 0 | { |
277 | 0 | macaddr *key = PG_GETARG_MACADDR_P(0); |
278 | |
|
279 | 0 | return hash_any_extended((unsigned char *) key, sizeof(macaddr), |
280 | 0 | PG_GETARG_INT64(1)); |
281 | 0 | } |
282 | | |
283 | | /* |
284 | | * Arithmetic functions: bitwise NOT, AND, OR. |
285 | | */ |
286 | | Datum |
287 | | macaddr_not(PG_FUNCTION_ARGS) |
288 | 0 | { |
289 | 0 | macaddr *addr = PG_GETARG_MACADDR_P(0); |
290 | 0 | macaddr *result; |
291 | |
|
292 | 0 | result = (macaddr *) palloc(sizeof(macaddr)); |
293 | 0 | result->a = ~addr->a; |
294 | 0 | result->b = ~addr->b; |
295 | 0 | result->c = ~addr->c; |
296 | 0 | result->d = ~addr->d; |
297 | 0 | result->e = ~addr->e; |
298 | 0 | result->f = ~addr->f; |
299 | 0 | PG_RETURN_MACADDR_P(result); |
300 | 0 | } |
301 | | |
302 | | Datum |
303 | | macaddr_and(PG_FUNCTION_ARGS) |
304 | 0 | { |
305 | 0 | macaddr *addr1 = PG_GETARG_MACADDR_P(0); |
306 | 0 | macaddr *addr2 = PG_GETARG_MACADDR_P(1); |
307 | 0 | macaddr *result; |
308 | |
|
309 | 0 | result = (macaddr *) palloc(sizeof(macaddr)); |
310 | 0 | result->a = addr1->a & addr2->a; |
311 | 0 | result->b = addr1->b & addr2->b; |
312 | 0 | result->c = addr1->c & addr2->c; |
313 | 0 | result->d = addr1->d & addr2->d; |
314 | 0 | result->e = addr1->e & addr2->e; |
315 | 0 | result->f = addr1->f & addr2->f; |
316 | 0 | PG_RETURN_MACADDR_P(result); |
317 | 0 | } |
318 | | |
319 | | Datum |
320 | | macaddr_or(PG_FUNCTION_ARGS) |
321 | 0 | { |
322 | 0 | macaddr *addr1 = PG_GETARG_MACADDR_P(0); |
323 | 0 | macaddr *addr2 = PG_GETARG_MACADDR_P(1); |
324 | 0 | macaddr *result; |
325 | |
|
326 | 0 | result = (macaddr *) palloc(sizeof(macaddr)); |
327 | 0 | result->a = addr1->a | addr2->a; |
328 | 0 | result->b = addr1->b | addr2->b; |
329 | 0 | result->c = addr1->c | addr2->c; |
330 | 0 | result->d = addr1->d | addr2->d; |
331 | 0 | result->e = addr1->e | addr2->e; |
332 | 0 | result->f = addr1->f | addr2->f; |
333 | 0 | PG_RETURN_MACADDR_P(result); |
334 | 0 | } |
335 | | |
336 | | /* |
337 | | * Truncation function to allow comparing mac manufacturers. |
338 | | * From suggestion by Alex Pilosov <alex@pilosoft.com> |
339 | | */ |
340 | | Datum |
341 | | macaddr_trunc(PG_FUNCTION_ARGS) |
342 | 0 | { |
343 | 0 | macaddr *addr = PG_GETARG_MACADDR_P(0); |
344 | 0 | macaddr *result; |
345 | |
|
346 | 0 | result = (macaddr *) palloc(sizeof(macaddr)); |
347 | |
|
348 | 0 | result->a = addr->a; |
349 | 0 | result->b = addr->b; |
350 | 0 | result->c = addr->c; |
351 | 0 | result->d = 0; |
352 | 0 | result->e = 0; |
353 | 0 | result->f = 0; |
354 | |
|
355 | 0 | PG_RETURN_MACADDR_P(result); |
356 | 0 | } |
357 | | |
358 | | /* |
359 | | * SortSupport strategy function. Populates a SortSupport struct with the |
360 | | * information necessary to use comparison by abbreviated keys. |
361 | | */ |
362 | | Datum |
363 | | macaddr_sortsupport(PG_FUNCTION_ARGS) |
364 | 0 | { |
365 | 0 | SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0); |
366 | |
|
367 | 0 | ssup->comparator = macaddr_fast_cmp; |
368 | 0 | ssup->ssup_extra = NULL; |
369 | |
|
370 | 0 | if (ssup->abbreviate) |
371 | 0 | { |
372 | 0 | macaddr_sortsupport_state *uss; |
373 | 0 | MemoryContext oldcontext; |
374 | |
|
375 | 0 | oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt); |
376 | |
|
377 | 0 | uss = palloc(sizeof(macaddr_sortsupport_state)); |
378 | 0 | uss->input_count = 0; |
379 | 0 | uss->estimating = true; |
380 | 0 | initHyperLogLog(&uss->abbr_card, 10); |
381 | |
|
382 | 0 | ssup->ssup_extra = uss; |
383 | |
|
384 | 0 | ssup->comparator = ssup_datum_unsigned_cmp; |
385 | 0 | ssup->abbrev_converter = macaddr_abbrev_convert; |
386 | 0 | ssup->abbrev_abort = macaddr_abbrev_abort; |
387 | 0 | ssup->abbrev_full_comparator = macaddr_fast_cmp; |
388 | |
|
389 | 0 | MemoryContextSwitchTo(oldcontext); |
390 | 0 | } |
391 | |
|
392 | 0 | PG_RETURN_VOID(); |
393 | 0 | } |
394 | | |
395 | | /* |
396 | | * SortSupport "traditional" comparison function. Pulls two MAC addresses from |
397 | | * the heap and runs a standard comparison on them. |
398 | | */ |
399 | | static int |
400 | | macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup) |
401 | 0 | { |
402 | 0 | macaddr *arg1 = DatumGetMacaddrP(x); |
403 | 0 | macaddr *arg2 = DatumGetMacaddrP(y); |
404 | |
|
405 | 0 | return macaddr_cmp_internal(arg1, arg2); |
406 | 0 | } |
407 | | |
408 | | /* |
409 | | * Callback for estimating effectiveness of abbreviated key optimization. |
410 | | * |
411 | | * We pay no attention to the cardinality of the non-abbreviated data, because |
412 | | * there is no equality fast-path within authoritative macaddr comparator. |
413 | | */ |
414 | | static bool |
415 | | macaddr_abbrev_abort(int memtupcount, SortSupport ssup) |
416 | 0 | { |
417 | 0 | macaddr_sortsupport_state *uss = ssup->ssup_extra; |
418 | 0 | double abbr_card; |
419 | |
|
420 | 0 | if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating) |
421 | 0 | return false; |
422 | | |
423 | 0 | abbr_card = estimateHyperLogLog(&uss->abbr_card); |
424 | | |
425 | | /* |
426 | | * If we have >100k distinct values, then even if we were sorting many |
427 | | * billion rows we'd likely still break even, and the penalty of undoing |
428 | | * that many rows of abbrevs would probably not be worth it. At this point |
429 | | * we stop counting because we know that we're now fully committed. |
430 | | */ |
431 | 0 | if (abbr_card > 100000.0) |
432 | 0 | { |
433 | 0 | if (trace_sort) |
434 | 0 | elog(LOG, |
435 | 0 | "macaddr_abbrev: estimation ends at cardinality %f" |
436 | 0 | " after " INT64_FORMAT " values (%d rows)", |
437 | 0 | abbr_card, uss->input_count, memtupcount); |
438 | 0 | uss->estimating = false; |
439 | 0 | return false; |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row |
444 | | * fudge factor allows us to abort earlier on genuinely pathological data |
445 | | * where we've had exactly one abbreviated value in the first 2k |
446 | | * (non-null) rows. |
447 | | */ |
448 | 0 | if (abbr_card < uss->input_count / 2000.0 + 0.5) |
449 | 0 | { |
450 | 0 | if (trace_sort) |
451 | 0 | elog(LOG, |
452 | 0 | "macaddr_abbrev: aborting abbreviation at cardinality %f" |
453 | 0 | " below threshold %f after " INT64_FORMAT " values (%d rows)", |
454 | 0 | abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count, |
455 | 0 | memtupcount); |
456 | 0 | return true; |
457 | 0 | } |
458 | | |
459 | 0 | if (trace_sort) |
460 | 0 | elog(LOG, |
461 | 0 | "macaddr_abbrev: cardinality %f after " INT64_FORMAT |
462 | 0 | " values (%d rows)", abbr_card, uss->input_count, memtupcount); |
463 | | |
464 | 0 | return false; |
465 | 0 | } |
466 | | |
467 | | /* |
468 | | * SortSupport conversion routine. Converts original macaddr representation |
469 | | * to abbreviated key representation. |
470 | | * |
471 | | * Packs the bytes of a 6-byte MAC address into a Datum and treats it as an |
472 | | * unsigned integer for purposes of comparison. On a 64-bit machine, there |
473 | | * will be two zeroed bytes of padding. The integer is converted to native |
474 | | * endianness to facilitate easy comparison. |
475 | | */ |
476 | | static Datum |
477 | | macaddr_abbrev_convert(Datum original, SortSupport ssup) |
478 | 0 | { |
479 | 0 | macaddr_sortsupport_state *uss = ssup->ssup_extra; |
480 | 0 | macaddr *authoritative = DatumGetMacaddrP(original); |
481 | 0 | Datum res; |
482 | | |
483 | | /* |
484 | | * On a 64-bit machine, zero out the 8-byte datum and copy the 6 bytes of |
485 | | * the MAC address in. There will be two bytes of zero padding on the end |
486 | | * of the least significant bits. |
487 | | */ |
488 | 0 | #if SIZEOF_DATUM == 8 |
489 | 0 | memset(&res, 0, SIZEOF_DATUM); |
490 | 0 | memcpy(&res, authoritative, sizeof(macaddr)); |
491 | | #else /* SIZEOF_DATUM != 8 */ |
492 | | memcpy(&res, authoritative, SIZEOF_DATUM); |
493 | | #endif |
494 | 0 | uss->input_count += 1; |
495 | | |
496 | | /* |
497 | | * Cardinality estimation. The estimate uses uint32, so on a 64-bit |
498 | | * architecture, XOR the two 32-bit halves together to produce slightly |
499 | | * more entropy. The two zeroed bytes won't have any practical impact on |
500 | | * this operation. |
501 | | */ |
502 | 0 | if (uss->estimating) |
503 | 0 | { |
504 | 0 | uint32 tmp; |
505 | |
|
506 | 0 | #if SIZEOF_DATUM == 8 |
507 | 0 | tmp = (uint32) res ^ (uint32) ((uint64) res >> 32); |
508 | | #else /* SIZEOF_DATUM != 8 */ |
509 | | tmp = (uint32) res; |
510 | | #endif |
511 | |
|
512 | 0 | addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp))); |
513 | 0 | } |
514 | | |
515 | | /* |
516 | | * Byteswap on little-endian machines. |
517 | | * |
518 | | * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer |
519 | | * 3-way comparator) works correctly on all platforms. Without this, the |
520 | | * comparator would have to call memcmp() with a pair of pointers to the |
521 | | * first byte of each abbreviated key, which is slower. |
522 | | */ |
523 | 0 | res = DatumBigEndianToNative(res); |
524 | |
|
525 | 0 | return res; |
526 | 0 | } |