diff --git a/.gitignore b/.gitignore index d4aa077..21c4153 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test/isinetaddr test/iscidraddr +test/isinetaddr6 diff --git a/Makefile b/Makefile index 54fb911..c64f5e1 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SRCDIR = src -SRCFILES = $(SRCDIR)/isinetaddr.c $(SRCDIR)/iscidraddr.c +SRCFILES = $(SRCDIR)/isinetaddr.c $(SRCDIR)/iscidraddr.c $(SRCDIR)/isinetaddr6.c INCDIR = include TESTDIR = test @@ -7,6 +7,10 @@ CC = cc CFLAGS = -fstack-protector-all -I$(INCDIR) -Wall -Wextra -pedantic test: + @make test4 + @make test6 + +test4: @$(CC) $(CFLAGS) $(SRCFILES) $(TESTDIR)/isinetaddr_test.c -o $(TESTDIR)/isinetaddr @echo -n test/isinetaddr: '' @$(TESTDIR)/isinetaddr @@ -14,4 +18,9 @@ test: @echo -n test/iscidraddr: '' @$(TESTDIR)/iscidraddr +test6: + @$(CC) $(CFLAGS) $(SRCFILES) $(TESTDIR)/isinetaddr6_test.c -o $(TESTDIR)/isinetaddr6 + @echo -n test/isinetaddr6: '' + @$(TESTDIR)/isinetaddr6 + .PHONY: test diff --git a/include/isinetaddr.h b/include/isinetaddr.h index 80e7696..798b6d8 100644 --- a/include/isinetaddr.h +++ b/include/isinetaddr.h @@ -1,3 +1,4 @@ #pragma once int isinetaddr(const char *str); int iscidraddr(const char *str); +int isinetaddr6(const char *str); diff --git a/src/isinetaddr6.c b/src/isinetaddr6.c new file mode 100644 index 0000000..7ef79a2 --- /dev/null +++ b/src/isinetaddr6.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +static const int MAX_DIGITLEN = 4; +static const int MAX_HEXTETS = 8; +static const int MAX_HEXDIGITS = 32; +static const int MAX_STRLEN = 40; +static const char SEP = ':'; + +static int has_consecutive_chars(const char *str, char c, int n); +static char* expand(const char *str, size_t strlen, char *new_str, size_t headlen); + +int +isinetaddr6(const char *str) +{ + int hextets = 1, digitlen = 0, hexdigits = 0; + size_t len = (str == NULL ? 0 : strnlen(str, MAX_STRLEN)); + + if (len == 0) { + return 0; + } else if (strncasecmp(str, "::ffff", 6) == 0) { + return isinetaddr(&str[7]); + } + for (size_t i = 0; i < len; i++) { + if (has_consecutive_chars(&str[i], SEP, 3)) { + return 0; + } else if (has_consecutive_chars(&str[i], SEP, 2)) { + char new_str[MAX_STRLEN]; + return isinetaddr6(expand(str, len, new_str, i)); + } else if (str[i] == SEP) { + if (digitlen < MAX_DIGITLEN) { + return 0; + } else { + digitlen = 0; + hextets++; + } + } else if (isxdigit(str[i])) { + if (digitlen == MAX_DIGITLEN) { + return 0; + } else { + digitlen++; + hexdigits++; + } + } else { + return 0; + } + } + if (hextets == MAX_HEXTETS) { + return hexdigits <= MAX_HEXDIGITS && digitlen == MAX_DIGITLEN; + } else { + return 0; + } +} + +static int +has_consecutive_chars(const char *str, char c, int n) +{ + for (int i = 0; i < n; i++) { + if (*str != c) { + return 0; + } + str++; + } + return 1; +} + +static char* +expand(const char *str, size_t strlen, char *new_str, size_t headlen) +{ + char *ptr = new_str; + size_t taillen = (strlen - headlen) - 2; + size_t bodylen = MAX_STRLEN - taillen; + size_t i = headlen + 2; + size_t j = headlen; + + while (i++ < strlen) { + if (has_consecutive_chars(&str[i], SEP, 2)) { + return NULL; + } + } + memcpy(ptr, &str[0], headlen); + ptr += headlen; + while (++j < bodylen) { + *ptr++ = j % 5 == 0 ? ':' : '0'; + } + memcpy(ptr, &str[headlen + 2], taillen); + return new_str; +} diff --git a/test/isinetaddr6_test.c b/test/isinetaddr6_test.c new file mode 100644 index 0000000..7e4fe11 --- /dev/null +++ b/test/isinetaddr6_test.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +const char *valid[] = { + /* valid IPv6 (single colon)*/ + "0000:0000:0000:0000:0000:0000:0000:0001", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "abcd:abcd:abcd:abcd:abcd:abcd:abcd:abcd", + "1234:5678:9abc:def0:face:face:b00c:0ffe", + "2001:19f0:5401:0000:0000:ffff:1e61:face", + "dead:beef:cafe:babe:affe:8a2e:0370:7334", + + /* valid IPv6 (double colon) */ + "fe80::c001:a0ff:fe12:3456", + "2001:0db8::1", + "2001:abcd:ef01:2345::", + "1234::5678", + "0000:00::1111", + "000::1111:1111", + "000::1111:1111:1111", + "0000:00::1111", + "0000:0000:0000:0000:0000:0000:0000:00::", + "2001:0db8:85a3:0000:0000::8a2e:0370:7334", + "2001::5", + "::ffff:192.168.2.1", + "::FFFF:192.168.2.1", + "::", + "::1", +}; + +const char *invalid[] = { + /* invalid IPv6 (single colon) */ + "0000:0000:0000:0000:0000:0000:0000:000Z", + "0000:0000:0000:0000:0000:0000:0000:0", + "0000:0000:0000:0000:0000:0000:0000:", + + /* invalid IPv6 (double colon) */ + ":::", "2001:::1", "2001:::1::", + "2001::1::", "::1::", + "2001:db8:85a3::8a2e:3700:7334", + "::ffff", "::ffff:", "::ffff:00", + + /* edge cases */ + NULL +}; + +int +main(void) { + size_t len; + /* IPv6: valid */ + len = sizeof(valid) / sizeof(valid[0]); + for (size_t i = 0; i < len; i++) { + if (isinetaddr6(valid[i]) != 1) { + fprintf(stderr, "assertion failed: '%s' should be valid\n", valid[i]); + abort(); + } + } + /* IPv6: invalid */ + len = sizeof(invalid) / sizeof(invalid[0]); + for (size_t i = 0; i < len; i++) { + if (isinetaddr6(invalid[i]) != 0) { + fprintf(stderr, "assertion failed: '%s' should NOT be valid\n", invalid[i]); + abort(); + } + } + /* Done */ + printf("OK\n"); + return EXIT_SUCCESS; +}