LCOV - code coverage report
Current view: top level - src/backend/utils/adt - mac8.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 84.0 % 263 221
Test Date: 2026-02-17 17:20:33 Functions: 86.4 % 22 19
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * mac8.c
       4              :  *    PostgreSQL type definitions for 8 byte (EUI-64) MAC addresses.
       5              :  *
       6              :  * EUI-48 (6 byte) MAC addresses are accepted as input and are stored in
       7              :  * EUI-64 format, with the 4th and 5th bytes set to FF and FE, respectively.
       8              :  *
       9              :  * Output is always in 8 byte (EUI-64) format.
      10              :  *
      11              :  * The following code is written with the assumption that the OUI field
      12              :  * size is 24 bits.
      13              :  *
      14              :  * Portions Copyright (c) 1998-2026, PostgreSQL Global Development Group
      15              :  *
      16              :  * IDENTIFICATION
      17              :  *        src/backend/utils/adt/mac8.c
      18              :  *
      19              :  *-------------------------------------------------------------------------
      20              :  */
      21              : 
      22              : #include "postgres.h"
      23              : 
      24              : #include "common/hashfn.h"
      25              : #include "libpq/pqformat.h"
      26              : #include "nodes/nodes.h"
      27              : #include "utils/fmgrprotos.h"
      28              : #include "utils/inet.h"
      29              : 
      30              : /*
      31              :  *  Utility macros used for sorting and comparing:
      32              :  */
      33              : #define hibits(addr) \
      34              :   ((unsigned long)(((addr)->a<<24) | ((addr)->b<<16) | ((addr)->c<<8) | ((addr)->d)))
      35              : 
      36              : #define lobits(addr) \
      37              :   ((unsigned long)(((addr)->e<<24) | ((addr)->f<<16) | ((addr)->g<<8) | ((addr)->h)))
      38              : 
      39              : static unsigned char hex2_to_uchar(const unsigned char *ptr, bool *badhex);
      40              : 
      41              : static const signed char hexlookup[128] = {
      42              :     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      43              :     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      44              :     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      45              :     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
      46              :     -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      47              :     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      48              :     -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      49              :     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
      50              : };
      51              : 
      52              : /*
      53              :  * hex2_to_uchar - convert 2 hex digits to a byte (unsigned char)
      54              :  *
      55              :  * Sets *badhex to true if the end of the string is reached ('\0' found), or if
      56              :  * either character is not a valid hex digit.
      57              :  */
      58              : static inline unsigned char
      59         8179 : hex2_to_uchar(const unsigned char *ptr, bool *badhex)
      60              : {
      61              :     unsigned char ret;
      62              :     signed char lookup;
      63              : 
      64              :     /* Handle the first character */
      65         8179 :     if (*ptr > 127)
      66            0 :         goto invalid_input;
      67              : 
      68         8179 :     lookup = hexlookup[*ptr];
      69         8179 :     if (lookup < 0)
      70           12 :         goto invalid_input;
      71              : 
      72         8167 :     ret = lookup << 4;
      73              : 
      74              :     /* Move to the second character */
      75         8167 :     ptr++;
      76              : 
      77         8167 :     if (*ptr > 127)
      78            0 :         goto invalid_input;
      79              : 
      80         8167 :     lookup = hexlookup[*ptr];
      81         8167 :     if (lookup < 0)
      82            9 :         goto invalid_input;
      83              : 
      84         8158 :     ret += lookup;
      85              : 
      86         8158 :     return ret;
      87              : 
      88           21 : invalid_input:
      89           21 :     *badhex = true;
      90           21 :     return 0;
      91              : }
      92              : 
      93              : /*
      94              :  * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations.
      95              :  */
      96              : Datum
      97         1211 : macaddr8_in(PG_FUNCTION_ARGS)
      98              : {
      99         1211 :     const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0);
     100         1211 :     Node       *escontext = fcinfo->context;
     101         1211 :     const unsigned char *ptr = str;
     102         1211 :     bool        badhex = false;
     103              :     macaddr8   *result;
     104         1211 :     unsigned char a = 0,
     105         1211 :                 b = 0,
     106         1211 :                 c = 0,
     107         1211 :                 d = 0,
     108         1211 :                 e = 0,
     109         1211 :                 f = 0,
     110         1211 :                 g = 0,
     111         1211 :                 h = 0;
     112         1211 :     int         count = 0;
     113         1211 :     unsigned char spacer = '\0';
     114              : 
     115              :     /* skip leading spaces */
     116         1259 :     while (*ptr && isspace(*ptr))
     117           48 :         ptr++;
     118              : 
     119              :     /* digits must always come in pairs */
     120         9351 :     while (*ptr && *(ptr + 1))
     121              :     {
     122              :         /*
     123              :          * Attempt to decode each byte, which must be 2 hex digits in a row.
     124              :          * If either digit is not hex, hex2_to_uchar will throw ereport() for
     125              :          * us.  Either 6 or 8 byte MAC addresses are supported.
     126              :          */
     127              : 
     128              :         /* Attempt to collect a byte */
     129         8191 :         count++;
     130              : 
     131         8191 :         switch (count)
     132              :         {
     133         1211 :             case 1:
     134         1211 :                 a = hex2_to_uchar(ptr, &badhex);
     135         1211 :                 break;
     136         1205 :             case 2:
     137         1205 :                 b = hex2_to_uchar(ptr, &badhex);
     138         1205 :                 break;
     139         1193 :             case 3:
     140         1193 :                 c = hex2_to_uchar(ptr, &badhex);
     141         1193 :                 break;
     142         1193 :             case 4:
     143         1193 :                 d = hex2_to_uchar(ptr, &badhex);
     144         1193 :                 break;
     145         1187 :             case 5:
     146         1187 :                 e = hex2_to_uchar(ptr, &badhex);
     147         1187 :                 break;
     148         1187 :             case 6:
     149         1187 :                 f = hex2_to_uchar(ptr, &badhex);
     150         1187 :                 break;
     151          506 :             case 7:
     152          506 :                 g = hex2_to_uchar(ptr, &badhex);
     153          506 :                 break;
     154          497 :             case 8:
     155          497 :                 h = hex2_to_uchar(ptr, &badhex);
     156          497 :                 break;
     157           12 :             default:
     158              :                 /* must be trailing garbage... */
     159           12 :                 goto fail;
     160              :         }
     161              : 
     162         8179 :         if (badhex)
     163           21 :             goto fail;
     164              : 
     165              :         /* Move forward to where the next byte should be */
     166         8158 :         ptr += 2;
     167              : 
     168              :         /* Check for a spacer, these are valid, anything else is not */
     169         8158 :         if (*ptr == ':' || *ptr == '-' || *ptr == '.')
     170              :         {
     171              :             /* remember the spacer used, if it changes then it isn't valid */
     172         4466 :             if (spacer == '\0')
     173          857 :                 spacer = *ptr;
     174              : 
     175              :             /* Have to use the same spacer throughout */
     176         3609 :             else if (spacer != *ptr)
     177           12 :                 goto fail;
     178              : 
     179              :             /* move past the spacer */
     180         4454 :             ptr++;
     181              :         }
     182              : 
     183              :         /* allow trailing whitespace after if we have 6 or 8 bytes */
     184         8146 :         if (count == 6 || count == 8)
     185              :         {
     186         1678 :             if (isspace(*ptr))
     187              :             {
     188           72 :                 while (*++ptr && isspace(*ptr));
     189              : 
     190              :                 /* If we found a space and then non-space, it's invalid */
     191           18 :                 if (*ptr)
     192            6 :                     goto fail;
     193              :             }
     194              :         }
     195              :     }
     196              : 
     197              :     /* Convert a 6 byte MAC address to macaddr8 */
     198         1160 :     if (count == 6)
     199              :     {
     200          678 :         h = f;
     201          678 :         g = e;
     202          678 :         f = d;
     203              : 
     204          678 :         d = 0xFF;
     205          678 :         e = 0xFE;
     206              :     }
     207          482 :     else if (count != 8)
     208            6 :         goto fail;
     209              : 
     210         1154 :     result = palloc0_object(macaddr8);
     211              : 
     212         1154 :     result->a = a;
     213         1154 :     result->b = b;
     214         1154 :     result->c = c;
     215         1154 :     result->d = d;
     216         1154 :     result->e = e;
     217         1154 :     result->f = f;
     218         1154 :     result->g = g;
     219         1154 :     result->h = h;
     220              : 
     221         1154 :     PG_RETURN_MACADDR8_P(result);
     222              : 
     223           57 : fail:
     224           57 :     ereturn(escontext, (Datum) 0,
     225              :             (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     226              :              errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
     227              :                     str)));
     228              : }
     229              : 
     230              : /*
     231              :  * MAC8 address (EUI-64) output function. Fixed format.
     232              :  */
     233              : Datum
     234         1072 : macaddr8_out(PG_FUNCTION_ARGS)
     235              : {
     236         1072 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     237              :     char       *result;
     238              : 
     239         1072 :     result = (char *) palloc(32);
     240              : 
     241         1072 :     snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
     242         1072 :              addr->a, addr->b, addr->c, addr->d,
     243         1072 :              addr->e, addr->f, addr->g, addr->h);
     244              : 
     245         1072 :     PG_RETURN_CSTRING(result);
     246              : }
     247              : 
     248              : /*
     249              :  * macaddr8_recv - converts external binary format(EUI-48 and EUI-64) to macaddr8
     250              :  *
     251              :  * The external representation is just the eight bytes, MSB first.
     252              :  */
     253              : Datum
     254            0 : macaddr8_recv(PG_FUNCTION_ARGS)
     255              : {
     256            0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     257              :     macaddr8   *addr;
     258              : 
     259            0 :     addr = palloc0_object(macaddr8);
     260              : 
     261            0 :     addr->a = pq_getmsgbyte(buf);
     262            0 :     addr->b = pq_getmsgbyte(buf);
     263            0 :     addr->c = pq_getmsgbyte(buf);
     264              : 
     265            0 :     if (buf->len == 6)
     266              :     {
     267            0 :         addr->d = 0xFF;
     268            0 :         addr->e = 0xFE;
     269              :     }
     270              :     else
     271              :     {
     272            0 :         addr->d = pq_getmsgbyte(buf);
     273            0 :         addr->e = pq_getmsgbyte(buf);
     274              :     }
     275              : 
     276            0 :     addr->f = pq_getmsgbyte(buf);
     277            0 :     addr->g = pq_getmsgbyte(buf);
     278            0 :     addr->h = pq_getmsgbyte(buf);
     279              : 
     280            0 :     PG_RETURN_MACADDR8_P(addr);
     281              : }
     282              : 
     283              : /*
     284              :  * macaddr8_send - converts macaddr8(EUI-64) to binary format
     285              :  */
     286              : Datum
     287            0 : macaddr8_send(PG_FUNCTION_ARGS)
     288              : {
     289            0 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     290              :     StringInfoData buf;
     291              : 
     292            0 :     pq_begintypsend(&buf);
     293            0 :     pq_sendbyte(&buf, addr->a);
     294            0 :     pq_sendbyte(&buf, addr->b);
     295            0 :     pq_sendbyte(&buf, addr->c);
     296            0 :     pq_sendbyte(&buf, addr->d);
     297            0 :     pq_sendbyte(&buf, addr->e);
     298            0 :     pq_sendbyte(&buf, addr->f);
     299            0 :     pq_sendbyte(&buf, addr->g);
     300            0 :     pq_sendbyte(&buf, addr->h);
     301              : 
     302            0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     303              : }
     304              : 
     305              : 
     306              : /*
     307              :  * macaddr8_cmp_internal - comparison function for sorting:
     308              :  */
     309              : static int32
     310        27785 : macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2)
     311              : {
     312        27785 :     if (hibits(a1) < hibits(a2))
     313        12526 :         return -1;
     314        15259 :     else if (hibits(a1) > hibits(a2))
     315        10400 :         return 1;
     316         4859 :     else if (lobits(a1) < lobits(a2))
     317          111 :         return -1;
     318         4748 :     else if (lobits(a1) > lobits(a2))
     319           12 :         return 1;
     320              :     else
     321         4736 :         return 0;
     322              : }
     323              : 
     324              : Datum
     325        10019 : macaddr8_cmp(PG_FUNCTION_ARGS)
     326              : {
     327        10019 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     328        10019 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     329              : 
     330        10019 :     PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2));
     331              : }
     332              : 
     333              : /*
     334              :  * Boolean comparison functions.
     335              :  */
     336              : 
     337              : Datum
     338         8432 : macaddr8_lt(PG_FUNCTION_ARGS)
     339              : {
     340         8432 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     341         8432 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     342              : 
     343         8432 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0);
     344              : }
     345              : 
     346              : Datum
     347         1846 : macaddr8_le(PG_FUNCTION_ARGS)
     348              : {
     349         1846 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     350         1846 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     351              : 
     352         1846 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0);
     353              : }
     354              : 
     355              : Datum
     356         2506 : macaddr8_eq(PG_FUNCTION_ARGS)
     357              : {
     358         2506 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     359         2506 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     360              : 
     361         2506 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0);
     362              : }
     363              : 
     364              : Datum
     365         1497 : macaddr8_ge(PG_FUNCTION_ARGS)
     366              : {
     367         1497 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     368         1497 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     369              : 
     370         1497 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0);
     371              : }
     372              : 
     373              : Datum
     374         3479 : macaddr8_gt(PG_FUNCTION_ARGS)
     375              : {
     376         3479 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     377         3479 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     378              : 
     379         3479 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0);
     380              : }
     381              : 
     382              : Datum
     383            6 : macaddr8_ne(PG_FUNCTION_ARGS)
     384              : {
     385            6 :     macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
     386            6 :     macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
     387              : 
     388            6 :     PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0);
     389              : }
     390              : 
     391              : /*
     392              :  * Support function for hash indexes on macaddr8.
     393              :  */
     394              : Datum
     395           90 : hashmacaddr8(PG_FUNCTION_ARGS)
     396              : {
     397           90 :     macaddr8   *key = PG_GETARG_MACADDR8_P(0);
     398              : 
     399           90 :     return hash_any((unsigned char *) key, sizeof(macaddr8));
     400              : }
     401              : 
     402              : Datum
     403           30 : hashmacaddr8extended(PG_FUNCTION_ARGS)
     404              : {
     405           30 :     macaddr8   *key = PG_GETARG_MACADDR8_P(0);
     406              : 
     407           30 :     return hash_any_extended((unsigned char *) key, sizeof(macaddr8),
     408           30 :                              PG_GETARG_INT64(1));
     409              : }
     410              : 
     411              : /*
     412              :  * Arithmetic functions: bitwise NOT, AND, OR.
     413              :  */
     414              : Datum
     415           60 : macaddr8_not(PG_FUNCTION_ARGS)
     416              : {
     417           60 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     418              :     macaddr8   *result;
     419              : 
     420           60 :     result = palloc0_object(macaddr8);
     421           60 :     result->a = ~addr->a;
     422           60 :     result->b = ~addr->b;
     423           60 :     result->c = ~addr->c;
     424           60 :     result->d = ~addr->d;
     425           60 :     result->e = ~addr->e;
     426           60 :     result->f = ~addr->f;
     427           60 :     result->g = ~addr->g;
     428           60 :     result->h = ~addr->h;
     429              : 
     430           60 :     PG_RETURN_MACADDR8_P(result);
     431              : }
     432              : 
     433              : Datum
     434           60 : macaddr8_and(PG_FUNCTION_ARGS)
     435              : {
     436           60 :     macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
     437           60 :     macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
     438              :     macaddr8   *result;
     439              : 
     440           60 :     result = palloc0_object(macaddr8);
     441           60 :     result->a = addr1->a & addr2->a;
     442           60 :     result->b = addr1->b & addr2->b;
     443           60 :     result->c = addr1->c & addr2->c;
     444           60 :     result->d = addr1->d & addr2->d;
     445           60 :     result->e = addr1->e & addr2->e;
     446           60 :     result->f = addr1->f & addr2->f;
     447           60 :     result->g = addr1->g & addr2->g;
     448           60 :     result->h = addr1->h & addr2->h;
     449              : 
     450           60 :     PG_RETURN_MACADDR8_P(result);
     451              : }
     452              : 
     453              : Datum
     454           60 : macaddr8_or(PG_FUNCTION_ARGS)
     455              : {
     456           60 :     macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
     457           60 :     macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
     458              :     macaddr8   *result;
     459              : 
     460           60 :     result = palloc0_object(macaddr8);
     461           60 :     result->a = addr1->a | addr2->a;
     462           60 :     result->b = addr1->b | addr2->b;
     463           60 :     result->c = addr1->c | addr2->c;
     464           60 :     result->d = addr1->d | addr2->d;
     465           60 :     result->e = addr1->e | addr2->e;
     466           60 :     result->f = addr1->f | addr2->f;
     467           60 :     result->g = addr1->g | addr2->g;
     468           60 :     result->h = addr1->h | addr2->h;
     469              : 
     470           60 :     PG_RETURN_MACADDR8_P(result);
     471              : }
     472              : 
     473              : /*
     474              :  * Truncation function to allow comparing macaddr8 manufacturers.
     475              :  */
     476              : Datum
     477           60 : macaddr8_trunc(PG_FUNCTION_ARGS)
     478              : {
     479           60 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     480              :     macaddr8   *result;
     481              : 
     482           60 :     result = palloc0_object(macaddr8);
     483              : 
     484           60 :     result->a = addr->a;
     485           60 :     result->b = addr->b;
     486           60 :     result->c = addr->c;
     487           60 :     result->d = 0;
     488           60 :     result->e = 0;
     489           60 :     result->f = 0;
     490           60 :     result->g = 0;
     491           60 :     result->h = 0;
     492              : 
     493           60 :     PG_RETURN_MACADDR8_P(result);
     494              : }
     495              : 
     496              : /*
     497              :  * Set 7th bit for modified EUI-64 as used in IPv6.
     498              :  */
     499              : Datum
     500            3 : macaddr8_set7bit(PG_FUNCTION_ARGS)
     501              : {
     502            3 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     503              :     macaddr8   *result;
     504              : 
     505            3 :     result = palloc0_object(macaddr8);
     506              : 
     507            3 :     result->a = addr->a | 0x02;
     508            3 :     result->b = addr->b;
     509            3 :     result->c = addr->c;
     510            3 :     result->d = addr->d;
     511            3 :     result->e = addr->e;
     512            3 :     result->f = addr->f;
     513            3 :     result->g = addr->g;
     514            3 :     result->h = addr->h;
     515              : 
     516            3 :     PG_RETURN_MACADDR8_P(result);
     517              : }
     518              : 
     519              : /*----------------------------------------------------------
     520              :  *  Conversion operators.
     521              :  *---------------------------------------------------------*/
     522              : 
     523              : Datum
     524            0 : macaddrtomacaddr8(PG_FUNCTION_ARGS)
     525              : {
     526            0 :     macaddr    *addr6 = PG_GETARG_MACADDR_P(0);
     527              :     macaddr8   *result;
     528              : 
     529            0 :     result = palloc0_object(macaddr8);
     530              : 
     531            0 :     result->a = addr6->a;
     532            0 :     result->b = addr6->b;
     533            0 :     result->c = addr6->c;
     534            0 :     result->d = 0xFF;
     535            0 :     result->e = 0xFE;
     536            0 :     result->f = addr6->d;
     537            0 :     result->g = addr6->e;
     538            0 :     result->h = addr6->f;
     539              : 
     540              : 
     541            0 :     PG_RETURN_MACADDR8_P(result);
     542              : }
     543              : 
     544              : Datum
     545           12 : macaddr8tomacaddr(PG_FUNCTION_ARGS)
     546              : {
     547           12 :     macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
     548              :     macaddr    *result;
     549              : 
     550           12 :     result = palloc0_object(macaddr);
     551              : 
     552           12 :     if ((addr->d != 0xFF) || (addr->e != 0xFE))
     553            0 :         ereport(ERROR,
     554              :                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     555              :                  errmsg("macaddr8 data out of range to convert to macaddr"),
     556              :                  errhint("Only addresses that have FF and FE as values in the "
     557              :                          "4th and 5th bytes from the left, for example "
     558              :                          "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
     559              :                          "from macaddr8 to macaddr.")));
     560              : 
     561           12 :     result->a = addr->a;
     562           12 :     result->b = addr->b;
     563           12 :     result->c = addr->c;
     564           12 :     result->d = addr->f;
     565           12 :     result->e = addr->g;
     566           12 :     result->f = addr->h;
     567              : 
     568           12 :     PG_RETURN_MACADDR_P(result);
     569              : }
        

Generated by: LCOV version 2.0-1