/*------------------------------------------------------------------------- * * mac8.c * PostgreSQL type definitions for 8 byte (EUI-64) MAC addresses. * * EUI-48 (6 byte) MAC addresses are accepted as input and are stored in * EUI-64 format, with the 4th and 5th bytes set to FF and FE, respectively. * * Output is always in 8 byte (EUI-64) format. * * The following code is written with the assumption that the OUI field * size is 24 bits. * * Portions Copyright (c) 1998-2021, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/utils/adt/mac8.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "common/hashfn.h" #include "libpq/pqformat.h" #include "utils/builtins.h" #include "utils/inet.h" /* * Utility macros used for sorting and comparing: */ #define hibits(addr) \ ((unsigned long)(((addr)->a<<24) | ((addr)->b<<16) | ((addr)->c<<8) | ((addr)->d))) #define lobits(addr) \ ((unsigned long)(((addr)->e<<24) | ((addr)->f<<16) | ((addr)->g<<8) | ((addr)->h))) static unsigned char hex2_to_uchar(const unsigned char *ptr, const unsigned char *str); static const signed char hexlookup[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; /* * hex2_to_uchar - convert 2 hex digits to a byte (unsigned char) * * This will ereport() if the end of the string is reached ('\0' found), or if * either character is not a valid hex digit. * * ptr is the pointer to where the digits to convert are in the string, str is * the entire string, which is used only for error reporting. */ static inline unsigned char hex2_to_uchar(const unsigned char *ptr, const unsigned char *str) { unsigned char ret = 0; signed char lookup; /* Handle the first character */ if (*ptr > 127) goto invalid_input; lookup = hexlookup[*ptr]; if (lookup < 0) goto invalid_input; ret = lookup << 4; /* Move to the second character */ ptr++; if (*ptr > 127) goto invalid_input; lookup = hexlookup[*ptr]; if (lookup < 0) goto invalid_input; ret += lookup; return ret; invalid_input: ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", str))); /* We do not actually reach here */ return 0; } /* * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations. */ Datum macaddr8_in(PG_FUNCTION_ARGS) { const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0); const unsigned char *ptr = str; macaddr8 *result; unsigned char a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; int count = 0; unsigned char spacer = '\0'; /* skip leading spaces */ while (*ptr && isspace(*ptr)) ptr++; /* digits must always come in pairs */ while (*ptr && *(ptr + 1)) { /* * Attempt to decode each byte, which must be 2 hex digits in a row. * If either digit is not hex, hex2_to_uchar will throw ereport() for * us. Either 6 or 8 byte MAC addresses are supported. */ /* Attempt to collect a byte */ count++; switch (count) { case 1: a = hex2_to_uchar(ptr, str); break; case 2: b = hex2_to_uchar(ptr, str); break; case 3: c = hex2_to_uchar(ptr, str); break; case 4: d = hex2_to_uchar(ptr, str); break; case 5: e = hex2_to_uchar(ptr, str); break; case 6: f = hex2_to_uchar(ptr, str); break; case 7: g = hex2_to_uchar(ptr, str); break; case 8: h = hex2_to_uchar(ptr, str); break; default: /* must be trailing garbage... */ ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", str))); } /* Move forward to where the next byte should be */ ptr += 2; /* Check for a spacer, these are valid, anything else is not */ if (*ptr == ':' || *ptr == '-' || *ptr == '.') { /* remember the spacer used, if it changes then it isn't valid */ if (spacer == '\0') spacer = *ptr; /* Have to use the same spacer throughout */ else if (spacer != *ptr) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", str))); /* move past the spacer */ ptr++; } /* allow trailing whitespace after if we have 6 or 8 bytes */ if (count == 6 || count == 8) { if (isspace(*ptr)) { while (*++ptr && isspace(*ptr)); /* If we found a space and then non-space, it's invalid */ if (*ptr) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", str))); } } } /* Convert a 6 byte MAC address to macaddr8 */ if (count == 6) { h = f; g = e; f = d; d = 0xFF; e = 0xFE; } else if (count != 8) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8", str))); result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = a; result->b = b; result->c = c; result->d = d; result->e = e; result->f = f; result->g = g; result->h = h; PG_RETURN_MACADDR8_P(result); } /* * MAC8 address (EUI-64) output function. Fixed format. */ Datum macaddr8_out(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); char *result; result = (char *) palloc(32); snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", addr->a, addr->b, addr->c, addr->d, addr->e, addr->f, addr->g, addr->h); PG_RETURN_CSTRING(result); } /* * macaddr8_recv - converts external binary format(EUI-48 and EUI-64) to macaddr8 * * The external representation is just the eight bytes, MSB first. */ Datum macaddr8_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); macaddr8 *addr; addr = (macaddr8 *) palloc0(sizeof(macaddr8)); addr->a = pq_getmsgbyte(buf); addr->b = pq_getmsgbyte(buf); addr->c = pq_getmsgbyte(buf); if (buf->len == 6) { addr->d = 0xFF; addr->e = 0xFE; } else { addr->d = pq_getmsgbyte(buf); addr->e = pq_getmsgbyte(buf); } addr->f = pq_getmsgbyte(buf); addr->g = pq_getmsgbyte(buf); addr->h = pq_getmsgbyte(buf); PG_RETURN_MACADDR8_P(addr); } /* * macaddr8_send - converts macaddr8(EUI-64) to binary format */ Datum macaddr8_send(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); StringInfoData buf; pq_begintypsend(&buf); pq_sendbyte(&buf, addr->a); pq_sendbyte(&buf, addr->b); pq_sendbyte(&buf, addr->c); pq_sendbyte(&buf, addr->d); pq_sendbyte(&buf, addr->e); pq_sendbyte(&buf, addr->f); pq_sendbyte(&buf, addr->g); pq_sendbyte(&buf, addr->h); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } /* * macaddr8_cmp_internal - comparison function for sorting: */ static int32 macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2) { if (hibits(a1) < hibits(a2)) return -1; else if (hibits(a1) > hibits(a2)) return 1; else if (lobits(a1) < lobits(a2)) return -1; else if (lobits(a1) > lobits(a2)) return 1; else return 0; } Datum macaddr8_cmp(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2)); } /* * Boolean comparison functions. */ Datum macaddr8_lt(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0); } Datum macaddr8_le(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0); } Datum macaddr8_eq(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0); } Datum macaddr8_ge(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0); } Datum macaddr8_gt(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0); } Datum macaddr8_ne(PG_FUNCTION_ARGS) { macaddr8 *a1 = PG_GETARG_MACADDR8_P(0); macaddr8 *a2 = PG_GETARG_MACADDR8_P(1); PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0); } /* * Support function for hash indexes on macaddr8. */ Datum hashmacaddr8(PG_FUNCTION_ARGS) { macaddr8 *key = PG_GETARG_MACADDR8_P(0); return hash_any((unsigned char *) key, sizeof(macaddr8)); } Datum hashmacaddr8extended(PG_FUNCTION_ARGS) { macaddr8 *key = PG_GETARG_MACADDR8_P(0); return hash_any_extended((unsigned char *) key, sizeof(macaddr8), PG_GETARG_INT64(1)); } /* * Arithmetic functions: bitwise NOT, AND, OR. */ Datum macaddr8_not(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = ~addr->a; result->b = ~addr->b; result->c = ~addr->c; result->d = ~addr->d; result->e = ~addr->e; result->f = ~addr->f; result->g = ~addr->g; result->h = ~addr->h; PG_RETURN_MACADDR8_P(result); } Datum macaddr8_and(PG_FUNCTION_ARGS) { macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0); macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = addr1->a & addr2->a; result->b = addr1->b & addr2->b; result->c = addr1->c & addr2->c; result->d = addr1->d & addr2->d; result->e = addr1->e & addr2->e; result->f = addr1->f & addr2->f; result->g = addr1->g & addr2->g; result->h = addr1->h & addr2->h; PG_RETURN_MACADDR8_P(result); } Datum macaddr8_or(PG_FUNCTION_ARGS) { macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0); macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = addr1->a | addr2->a; result->b = addr1->b | addr2->b; result->c = addr1->c | addr2->c; result->d = addr1->d | addr2->d; result->e = addr1->e | addr2->e; result->f = addr1->f | addr2->f; result->g = addr1->g | addr2->g; result->h = addr1->h | addr2->h; PG_RETURN_MACADDR8_P(result); } /* * Truncation function to allow comparing macaddr8 manufacturers. */ Datum macaddr8_trunc(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = addr->a; result->b = addr->b; result->c = addr->c; result->d = 0; result->e = 0; result->f = 0; result->g = 0; result->h = 0; PG_RETURN_MACADDR8_P(result); } /* * Set 7th bit for modified EUI-64 as used in IPv6. */ Datum macaddr8_set7bit(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = addr->a | 0x02; result->b = addr->b; result->c = addr->c; result->d = addr->d; result->e = addr->e; result->f = addr->f; result->g = addr->g; result->h = addr->h; PG_RETURN_MACADDR8_P(result); } /*---------------------------------------------------------- * Conversion operators. *---------------------------------------------------------*/ Datum macaddrtomacaddr8(PG_FUNCTION_ARGS) { macaddr *addr6 = PG_GETARG_MACADDR_P(0); macaddr8 *result; result = (macaddr8 *) palloc0(sizeof(macaddr8)); result->a = addr6->a; result->b = addr6->b; result->c = addr6->c; result->d = 0xFF; result->e = 0xFE; result->f = addr6->d; result->g = addr6->e; result->h = addr6->f; PG_RETURN_MACADDR8_P(result); } Datum macaddr8tomacaddr(PG_FUNCTION_ARGS) { macaddr8 *addr = PG_GETARG_MACADDR8_P(0); macaddr *result; result = (macaddr *) palloc0(sizeof(macaddr)); if ((addr->d != 0xFF) || (addr->e != 0xFE)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("macaddr8 data out of range to convert to macaddr"), errhint("Only addresses that have FF and FE as values in the " "4th and 5th bytes from the left, for example " "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted " "from macaddr8 to macaddr."))); result->a = addr->a; result->b = addr->b; result->c = addr->c; result->d = addr->f; result->e = addr->g; result->f = addr->h; PG_RETURN_MACADDR_P(result); }