#include "ipv4.h" #include namespace roro::net { bool parseIpv4(const std::string& text, uint32_t& out) { uint32_t value = 0; int parts = 0, digits = 0, part = 0; for (char c : text) { if (c >= '0' && c <= '9') { if (++digits > 3) return false; part = part * 10 + (c - '0'); if (part > 255) return false; } else if (c == '.') { if (digits == 0 || ++parts > 3) return false; value = value << 8 | part; part = digits = 0; } else { return false; } } if (digits == 0 || parts != 3) return false; out = value << 8 | part; return true; } std::string formatIpv4(uint32_t a) { char s[16]; std::snprintf(s, sizeof s, "%u.%u.%u.%u", static_cast(a >> 24), static_cast(a >> 16 & 255), static_cast(a >> 8 & 255), static_cast(a & 255)); return s; } uint32_t maskOf(int prefix) { return prefix <= 0 ? 0 : prefix >= 32 ? 0xFFFFFFFFu : ~0u << (32 - prefix); } std::string checkFixed(const FixedIp& f) { if (f.prefix < 1 || f.prefix > 30) return "The prefix must be 1 to 30"; if (f.address == 0) return "0.0.0.0 isn't an address a device can have"; uint32_t mask = maskOf(f.prefix), network = f.address & mask, broadcast = network | ~mask; if (f.address == network) return formatIpv4(f.address) + " is the network's own address"; if (f.address == broadcast) return formatIpv4(f.address) + " is the broadcast address"; if (f.gateway == 0) return ""; if (f.gateway == f.address) return "The gateway can't be this device's address"; if ((f.gateway & mask) != network) return "The gateway " + formatIpv4(f.gateway) + " isn't in " + formatIpv4(network) + "/" + std::to_string(f.prefix); if (f.gateway == broadcast) return "The gateway " + formatIpv4(f.gateway) + " is the broadcast address"; if (f.gateway == network) return "The gateway " + formatIpv4(f.gateway) + " is the network's own address"; return ""; } std::string parseFixed(const std::string& text, FixedIp& out) { size_t slash = text.find('/'); if (slash == std::string::npos) return "Write it as address/prefix, then the gateway if there is one"; size_t space = text.find(' ', slash); std::string address = text.substr(0, slash); std::string prefix = text.substr(slash + 1, space == std::string::npos ? std::string::npos : space - slash - 1); std::string gateway = space == std::string::npos ? "" : text.substr(space + 1); FixedIp f; if (!parseIpv4(address, f.address)) return address + " isn't an IPv4 address"; int p = 0; if (prefix.empty() || prefix.size() > 2) return "The prefix must be 1 to 30"; for (char c : prefix) { if (c < '0' || c > '9') return "The prefix must be 1 to 30"; p = p * 10 + (c - '0'); } f.prefix = static_cast(p); if (!gateway.empty() && !parseIpv4(gateway, f.gateway)) return gateway + " isn't an IPv4 address"; std::string why = checkFixed(f); if (why.empty()) out = f; return why; } std::string formatFixed(const FixedIp& f) { std::string s = formatIpv4(f.address) + "/" + std::to_string(f.prefix); if (f.gateway) s += " " + formatIpv4(f.gateway); return s; } bool validHost(const std::string& text) { if (text.empty() || text.size() > 63) return false; uint32_t ignored; if (parseIpv4(text, ignored)) return true; bool allNumeric = true; // digits and dots only, but not an address: "1.2.3", "999.1.1.1" char previous = '.'; for (char c : text) { bool letter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'), digit = c >= '0' && c <= '9'; if (!letter && !digit && c != '-' && c != '.') return false; if (c == '.' && (previous == '.' || previous == '-')) return false; // empty label, or one ending in '-' if (c == '-' && previous == '.') return false; // a label starting with '-' if (letter || c == '-') allNumeric = false; previous = c; } return previous != '.' && previous != '-' && !allNumeric; } } // namespace roro::net