Skip to content

Commit bb94abf

Browse files
authored
Development (#3)
* feat: Validate IPv6 address format and handle multiple '::' occurrences * feat: Add IPv6 address handling and tests for IPAnalyzer functionality
1 parent dddeb58 commit bb94abf

2 files changed

Lines changed: 110 additions & 35 deletions

File tree

src/ip_analyzer.cc

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,42 @@ IPv6Address::IPv6Address(std::string_view address)
5959

6060
std::string IPv6Address::expand_ipv6_address(std::string_view address)
6161
{
62+
6263
std::string expanded = std::string(address);
63-
auto double_colon_pos = expanded.find("::");
64+
65+
if (expanded.find(":::") != std::string::npos) {
66+
throw std::invalid_argument("Invalid IPv6 address format: ':::' found");
67+
}
6468

65-
if (double_colon_pos != std::string::npos)
66-
{
67-
std::string left = expanded.substr(0, double_colon_pos);
68-
std::string right = expanded.substr(double_colon_pos + 2);
69+
size_t first_double_colon = expanded.find("::");
70+
if (first_double_colon != std::string::npos) {
71+
if (expanded.find("::", first_double_colon + 2) != std::string::npos) {
72+
throw std::invalid_argument("Invalid IPv6 address format: multiple '::' occurrences");
73+
}
6974

70-
int left_groups = std::count(left.begin(), left.end(), ':') + 1;
71-
int right_groups = std::count(right.begin(), right.end(), ':') + 1;
75+
std::string left = expanded.substr(0, first_double_colon);
76+
std::string right = expanded.substr(first_double_colon + 2);
77+
78+
int left_groups = left.empty() ? 0 : std::count(left.begin(), left.end(), ':') + 1;
79+
int right_groups = right.empty() ? 0 : std::count(right.begin(), right.end(), ':') + 1;
7280
int missing_groups = 8 - left_groups - right_groups;
81+
if (missing_groups < 0)
82+
throw std::invalid_argument("Invalid IPv6 address format: too many groups");
83+
84+
std::string middle;
85+
for (int i = 0; i < missing_groups; i++) {
86+
if (i > 0)
87+
middle += ':';
88+
middle += "0000";
89+
}
90+
91+
if (!left.empty())
92+
expanded = left + ":" + middle;
93+
else
94+
expanded = middle;
7395

74-
std::string middle(missing_groups, ':');
75-
expanded = left + middle + right;
96+
if (!right.empty())
97+
expanded += ":" + std::string(right);
7698
}
7799

78100
std::istringstream iss(expanded);
@@ -88,8 +110,8 @@ std::string IPv6Address::expand_ipv6_address(std::string_view address)
88110

89111
while (i < 8)
90112
{
91-
oss << ":0000";
92-
i++;
113+
oss << ":0000";
114+
i++;
93115
}
94116

95117
return oss.str();

tests/ip_analyzer_tests.cc

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
#define CATCH_CONFIG_MAIN
22
#include <catch2/catch_all.hpp>
33
#include "ip_analyzer.hh"
4+
#include <limits>
45

5-
TEST_CASE("IPv4Address construction and methods", "[ipv4address]")
6-
{
6+
TEST_CASE("IPv4Address construction and methods", "[ipv4address]") {
77
IPv4Address ip("192.168.0.1");
88

99
REQUIRE(ip.to_string() == "192.168.0.1");
1010
REQUIRE(ip.to_binary_string() == "11000000101010000000000000000001");
1111
REQUIRE(ip.to_uint32() == 3232235521);
1212
}
1313

14-
TEST_CASE("IPAnalyzer functionality", "[ipanalyzer]")
15-
{
14+
TEST_CASE("IPAnalyzer functionality", "[ipanalyzer]") {
1615
IPAnalyzer analyzer("192.168.0.1/24");
1716

1817
REQUIRE(analyzer.get_ip()->to_string() == "192.168.0.1");
@@ -29,43 +28,36 @@ TEST_CASE("IPAnalyzer functionality", "[ipanalyzer]")
2928
REQUIRE(analyzer.get_cidr() == 24);
3029
}
3130

32-
TEST_CASE("Edge cases for IPv4Address", "[ipv4address]")
33-
{
34-
SECTION("Minimum IP address")
35-
{
31+
TEST_CASE("Edge cases for IPv4Address", "[ipv4address]") {
32+
SECTION("Minimum IP address") {
3633
IPv4Address min_ip("0.0.0.0");
3734
REQUIRE(min_ip.to_string() == "0.0.0.0");
3835
REQUIRE(min_ip.to_uint32() == 0);
3936
}
4037

41-
SECTION("Maximum IP address")
42-
{
38+
SECTION("Maximum IP address") {
4339
IPv4Address max_ip("255.255.255.255");
4440
REQUIRE(max_ip.to_string() == "255.255.255.255");
4541
REQUIRE(max_ip.to_uint32() == 4294967295);
4642
}
4743

48-
SECTION("Invalid IP address formats")
49-
{
44+
SECTION("Invalid IP address formats") {
5045
REQUIRE_THROWS_AS(IPv4Address("256.0.0.1"), std::invalid_argument);
5146
REQUIRE_THROWS_AS(IPv4Address("192.168.0"), std::invalid_argument);
5247
REQUIRE_THROWS_AS(IPv4Address("192.168.0.1.2"), std::invalid_argument);
5348
REQUIRE_THROWS_AS(IPv4Address("192.168.0.a"), std::invalid_argument);
5449
}
5550
}
5651

57-
TEST_CASE("Edge cases for IPAnalyzer", "[ipanalyzer]")
58-
{
59-
SECTION("Minimum CIDR")
60-
{
52+
TEST_CASE("Edge cases for IPAnalyzer", "[ipanalyzer]") {
53+
SECTION("Minimum CIDR") {
6154
IPAnalyzer analyzer("192.168.0.1/0");
6255
REQUIRE(analyzer.get_network()->to_string() == "0.0.0.0");
6356
REQUIRE(analyzer.get_broadcast()->to_string() == "255.255.255.255");
6457
REQUIRE(analyzer.get_num_hosts() == 4294967294);
6558
}
6659

67-
SECTION("Maximum CIDR")
68-
{
60+
SECTION("Maximum CIDR") {
6961
IPAnalyzer analyzer("192.168.0.1/32");
7062
REQUIRE(analyzer.get_network()->to_string() == "192.168.0.1");
7163
REQUIRE(analyzer.get_broadcast()->to_string() == "192.168.0.1");
@@ -76,22 +68,83 @@ TEST_CASE("Edge cases for IPAnalyzer", "[ipanalyzer]")
7668
REQUIRE(last->to_string() == "192.168.0.1");
7769
}
7870

79-
SECTION("Invalid CIDR values")
80-
{
71+
SECTION("Invalid CIDR values") {
8172
REQUIRE_THROWS_AS(IPAnalyzer("192.168.0.1/33"), std::invalid_argument);
8273
REQUIRE_THROWS_AS(IPAnalyzer("192.168.0.1/-1"), std::invalid_argument);
8374
}
8475

85-
SECTION("Private IP ranges")
86-
{
76+
SECTION("Private IP ranges") {
8777
REQUIRE(IPAnalyzer("10.0.0.1/24").is_private() == true);
8878
REQUIRE(IPAnalyzer("172.16.0.1/24").is_private() == true);
8979
REQUIRE(IPAnalyzer("192.168.0.1/24").is_private() == true);
9080
REQUIRE(IPAnalyzer("8.8.8.8/24").is_private() == false);
9181
}
9282

93-
SECTION("Class A, B, C network boundaries")
94-
{
83+
SECTION("Class A, B, C network boundaries") {
9584
REQUIRE(IPAnalyzer("127.255.255.255/8").get_network()->to_string() == "127.0.0.0");
9685
}
86+
}
87+
88+
TEST_CASE("IPv6Address construction and methods", "[ipv6address]") {
89+
IPv6Address ip("2001:0db8:0000:0000:0000:0000:0000:0001");
90+
REQUIRE(ip.to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
91+
92+
std::string binary = ip.to_binary_string();
93+
REQUIRE(binary.length() == 128);
94+
95+
REQUIRE(ip.is_private() == false);
96+
}
97+
98+
TEST_CASE("IPAnalyzer IPv6 functionality", "[ipanalyzer][ipv6]") {
99+
IPAnalyzer analyzer("2001:0db8:0000:0000:0000:0000:0000:0001/64");
100+
REQUIRE(analyzer.get_ip()->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
101+
REQUIRE(analyzer.get_network()->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0000");
102+
REQUIRE(analyzer.get_netmask()->to_string() == "ffff:ffff:ffff:ffff:0000:0000:0000:0000");
103+
REQUIRE(analyzer.get_broadcast()->to_string() == "2001:0db8:0000:0000:ffff:ffff:ffff:ffff");
104+
105+
auto [first, last] = analyzer.get_host_range();
106+
REQUIRE(first->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0002");
107+
REQUIRE(last->to_string() == "2001:0db8:0000:0000:ffff:ffff:ffff:fffe");
108+
109+
REQUIRE(analyzer.get_num_hosts() == std::numeric_limits<uint64_t>::max());
110+
REQUIRE(analyzer.is_private() == false);
111+
REQUIRE(analyzer.get_cidr() == 64);
112+
}
113+
114+
TEST_CASE("Default CIDR values when not provided", "[ipanalyzer]") {
115+
SECTION("Default IPv4 CIDR is /32") {
116+
IPAnalyzer analyzer("192.168.0.1");
117+
REQUIRE(analyzer.get_cidr() == 32);
118+
}
119+
120+
SECTION("Default IPv6 CIDR is /128") {
121+
IPAnalyzer analyzer("2001:0db8:0000:0000:0000:0000:0000:0001");
122+
REQUIRE(analyzer.get_cidr() == 128);
123+
REQUIRE(analyzer.get_network()->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
124+
REQUIRE(analyzer.get_broadcast()->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
125+
auto [first, last] = analyzer.get_host_range();
126+
REQUIRE(first->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
127+
REQUIRE(last->to_string() == "2001:0db8:0000:0000:0000:0000:0000:0001");
128+
}
129+
}
130+
131+
TEST_CASE("Invalid IPv6 addresses and CIDR values", "[ipv6address]") {
132+
SECTION("Invalid IPv6 format") {
133+
REQUIRE_THROWS_AS(IPv6Address("2001:db8:::1"), std::invalid_argument);
134+
}
135+
SECTION("Invalid IPv6 CIDR") {
136+
REQUIRE_THROWS_AS(IPAnalyzer("2001:0db8:0000:0000:0000:0000:0000:0001/129"), std::invalid_argument);
137+
REQUIRE_THROWS_AS(IPAnalyzer("2001:0db8:0000:0000:0000:0000:0000:0001/-1"), std::invalid_argument);
138+
}
139+
}
140+
141+
TEST_CASE("IPv6 private address detection", "[ipv6address]") {
142+
IPv6Address ip1("fd00:0000:0000:0000:0000:0000:0000:0001");
143+
REQUIRE(ip1.is_private() == true);
144+
145+
IPv6Address ip2("fc00:0000:0000:0000:0000:0000:0000:0001");
146+
REQUIRE(ip2.is_private() == true);
147+
148+
IPv6Address ip3("fe80:0000:0000:0000:0000:0000:0000:0001");
149+
REQUIRE(ip3.is_private() == false);
97150
}

0 commit comments

Comments
 (0)