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/README.md b/README.md
index 9b5db27..ee3a510 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
## About
isinetaddr is a simple C library that provides an interface that can
-be used to validate one or more IPv4 addresses (with optional support
-for CIDR notation as well). The library is guided by easy to extend
-[testcases](test/) that help verify safety and correctness.
+be used to validate one or more IPv(4|6) addresses
+(with optional support for CIDR notation as well). The library is
+guided by easy to extend [testcases](test/) that help verify safety
+and correctness.
## Examples
@@ -122,6 +123,61 @@ foobar is an invalid IPv4 address
127.0.0.1/64 is an invalid IPv4 address
```
+### IPv6
+
+The following example demonstrates the `isinetaddr6` function with
+both valid and invalid inputs. The `isinetaddr6` function returns 1
+when the input given is valid, and otherwise returns 0.
+
+```C
+#include
+#include
+#include
+
+const char *strings[] = {
+ /* valid */
+ "::",
+ "::1",
+ "0000:0000:0000:0000:0000:0000:0000:0000",
+
+ /* invalid */
+ "foobar",
+ NULL,
+ "00:::0",
+};
+
+int
+main(void)
+{
+ const char *str;
+ const int i = sizeof(strings) / sizeof(strings[0]);
+ for (int j = 0; j < i; j++) {
+ str = strings[j];
+ if (isinetaddr6(str)) {
+ printf("%s is a valid IPv6 address\n", str);
+ } else {
+ printf("%s is an invalid IPv6 address\n", str);
+ }
+ }
+ return EXIT_SUCCESS;
+}
+```
+
+When the above source code is compiled and run the output is
+expected to be as follows:
+
+```
+$ cc -Iinclude src/*.c share/isinetaddr/examples/isinetaddr6.c -o example
+$ ./example
+0x1eef [isinetaddr] % ./example
+:: is a valid IPv6 address
+::1 is a valid IPv6 address
+0000:0000:0000:0000:0000:0000:0000:0000 is a valid IPv6 address
+foobar is an invalid IPv6 address
+(null) is an invalid IPv6 address
+00:::0 is an invalid IPv6 address
+```
+
## Sources
* [Source code (GitHub)](https://github.com/0x1eef/isinetaddr#readme)
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;
+}