#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <arpa/inet.h>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <random>
#include <sstream>
#include <cstring>
#include <cstdio>

// xips
//
// ... by Eli Fulkerson.  See http://www.elifulkerson.com for updates
//
// Eat input.  Expand it into an IP list.
// new!  mode -s -- summarize the input you ate
//
// Original intended use - something like:
// echo 172.20.141.0/24 | xips | xargs -n 1 tcping -n 1
//


/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


using namespace std;


string cidr_to_subnetmask(int cidr) {
    switch(cidr) {
        case 0:
            return "0.0.0.0";
        case 1:
            return "128.0.0.0";
        case 2:
            return "192.0.0.0";
        case 3:
            return "224.0.0.0";
        case 4:
            return "240.0.0.0";
        case 5:
            return "248.0.0.0";
        case 6:
            return "252.0.0.0";
        case 7:
            return "254.0.0.0";
        case 8:
            return "255.0.0.0";
        case 9:
            return "255.128.0.0";
        case 10:
            return "255.192.0.0";
        case 11:
            return "255.224.0.0";
        case 12:
            return "255.240.0.0";
        case 13:
            return "255.248.0.0";
        case 14:
            return "255.252.0.0";
        case 15:
            return "255.254.0.0";
        case 16:
            return "255.255.0.0";
        case 17:
            return "255.255.128.0";
        case 18:
            return "255.255.192.0";
        case 19:
            return "255.255.224.0";
        case 20:
            return "255.255.240.0";
        case 21:
            return "255.255.248.0";
        case 22:
            return "255.255.252.0";
        case 23:
            return "255.255.254.0";
        case 24:
            return "255.255.255.0";
        case 25:
            return "255.255.255.128";
        case 26:
            return "255.255.255.192";
        case 27:
            return "255.255.255.224";
        case 28:
            return "255.255.255.240";
        case 29:
            return "255.255.255.248";
        case 30:
            return "255.255.255.252";
        case 31:
            return "255.255.255.254";
        case 32:
            return "255.255.255.255";
        default:
            return "";
    }

    return "";
}

string int_to_bitstring(int val) {
    string buf = "";

    for (int i = 7; i >= 0; i--) {
        if (val >= pow(2,i)) {
            buf += "1";
            val -= pow(2,i);
        } else {
            buf += "0";
        }
    }
    
    return buf; 
}

string ipv6_address_to_bitstring(sockaddr_in6 &ipv6_address) {

    string buf = "";
    
    for (int i = 0; i < 16; i++) {
        buf += int_to_bitstring(ipv6_address.sin6_addr.s6_addr[i]);
    }
    return buf;
}

void bitstring_to_ipv6_address(string bits, sockaddr_in6 &ipv6_address) {
    int scratch[16];

    for (int i = 0; i < 16; i++) {
        scratch[i] = 0;
        for (int j = 0; j < 8; j++) {

            if ((i*8) + j >= bits.length()) {

            } else {

                //if (bits.at( (i*8)+j) == '0') {
                    // well, nothing
                //}

                if (bits.at( (i*8)+j) == '1') {
                    
                    scratch[i] += pow(2,7-j);
                }
            }
        }
    }

    for (int i = 0; i < 16; i++) {
        ipv6_address.sin6_addr.s6_addr[i] = scratch[i];
    }

}



string int_to_binary_string(int val) {

    string buffer = "";

    for (int i=7; i >= 0; i--) {

        if (val >= pow(2, i)) {
            buffer += "1";
            val = val - pow(2,i);
        } else {
            buffer += "0";
        }

    }
    return buffer;

}

string rightpad_string_bits(string bits) {

    // right pad with zeros..
    while (bits.length() < 32) {
        bits = bits + "0";
    }

    return bits;
}

string string_bits_to_string_ip(string bits) {
    bits = rightpad_string_bits(bits);

    //cout << "bits are " << bits << endl << flush;

    short a,b,c,d;
    short pval;

    a = 0;
    pval = 7;
    for (int i=0; i < 8; i++) {

        if (bits.at(i) == '1') {
            a = a + pow(2, pval);
        }
        pval--;

    }

    b = 0;
    pval = 7;
    for (int i=8; i < 16; i++) {

        if (bits.at(i) == '1') {
            b = b + pow(2, pval);
        }
        pval--;        
    }

    c = 0;
    pval = 7;
    for (int i=16; i < 24; i++) {

        if (bits.at(i) == '1') {
            c = c + pow(2, pval);
        }
        pval--;        
    }

    d = 0;
    pval = 7;
    for (int i=24; i < 32; i++) {

        if (bits.at(i) == '1') {
            d = d + pow(2, pval);
        }
        pval--;
    }

    string buffer = to_string(a) + "." + to_string(b) + "." + to_string(c) + "." + to_string(d);
    return buffer;

}



struct node
{
    int value;  //0 or 1
    unsigned long long count;  //@@ this only gets me 64 bits I think - I want 128.  So I'll have to do some sort of double setup if I want to summarize *all* the ipv6s

    node *leaf0;   // the node where the next bit is a 0
    node *leaf1;   // the node where the next bit is a 1
};

class ipbtree
{
    public:
        ipbtree();
        ~ipbtree();

        void insert(string ip);  // insert a string of bits
        void insert6(string ip);  // insert a string of bits

        bool exists(string bits);  // check to see if a string of bits exists in the tree

	    void summarize(int maxdepth, bool display_cidr, bool display_network_object, int threshold);
	    void summarize6(int maxdepth, bool display_cidr, bool display_network_object, int threshold);

	    void sort(bool display_cidr, bool display_network_object);
    	void sort(int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object);

	    void sort6(bool display_cidr, bool display_network_object);
    	void sort6(int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object);

        void collect(vector<string>& results);
        void collect6(vector<string>& results);
        void setPortSuffix(const string& s) { port_suffix = s; }
        unsigned long long getCount() { return root->count; }
        unsigned __int128 getCount6() { return count_addresses6(0, root); }

        void cidr_list();
        void cidr_list6();

        node* get_root() const { return root; }
        void insert_range_v4(uint32_t start, uint32_t end);
        void insert_range_v6(const sockaddr_in6& s, const sockaddr_in6& e);
        void diff_output_v4(node* b);
        void diff_output_v6(node* b);

        void destroy_tree();

    private:
        void destroy_tree(node *leaf);
        void insert(int depth, string bits, node *leaf);
        bool exists (int depth, string bits, node *leaf);
    	void summarize(int maxdepth, int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object, int threshold);
	    void summarize6(int maxdepth, int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object, int threshold);
        void cidr_list(int curdepth, string buf, node *leaf);
        void cidr_list6(int curdepth, string buf, node *leaf);
        unsigned __int128 count_addresses6(int depth, node *leaf);
        void collect(int curdepth, string buf, node *leaf, vector<string>& results);
        void collect6(int curdepth, string buf, node *leaf, vector<string>& results);
        void insert_range_v4(int depth, uint64_t block_start, uint32_t range_start, uint32_t range_end, node* leaf);
        void insert_range_v6(int depth, uint8_t blk_s[16], uint8_t blk_e[16], const uint8_t rng_s[16], const uint8_t rng_e[16], node* leaf);
        void diff_v4(int depth, uint64_t block_start, string buf, node* a, node* b);
        void diff_v6(int depth, uint8_t blk_s[16], uint8_t blk_e[16], string buf, node* a, node* b);

        node *root;
        string port_suffix;
};

ipbtree::ipbtree(){
    root = new node;

    root->leaf0 = NULL;
    root->leaf1 = NULL;
    root->count = 0;
    root->value = 0;
    port_suffix = "";
}

ipbtree::~ipbtree(){
    destroy_tree();
}

void ipbtree::destroy_tree(){
    destroy_tree(root);
}

void ipbtree::destroy_tree(node *leaf) {
    if (leaf == NULL) return;
    destroy_tree(leaf->leaf0);
    destroy_tree(leaf->leaf1);
    delete leaf;
}

void ipbtree::insert(string ip) {



    unsigned short a, b, c, d;

    sscanf(ip.c_str(), "%hu.%hu.%hu.%hu", &a, &b, &c, &d);

    string bits = int_to_binary_string(a) + int_to_binary_string(b) + int_to_binary_string(c) + int_to_binary_string(d);

    if (!exists(bits)) {
        insert(0, bits, root);
    } 
}

void ipbtree::insert6(string ip) {
    // @@need to get string bits from an ipv6
    
    struct sockaddr_in6 sa6;
    inet_pton(AF_INET6, ip.c_str(), &(sa6.sin6_addr));

    string bits = ipv6_address_to_bitstring(sa6);

    if (!exists(bits)) {
        insert(0, bits, root);
    }

}

void ipbtree::insert(int depth, string bits, node *leaf) {

    if (depth == bits.length()) {
        return;
    }

    if (bits.at(depth) == '0') {
        if (leaf->leaf0 == NULL) {

            leaf->leaf0 = new node;
            leaf->leaf0->leaf0 = NULL;
            leaf->leaf0->leaf1 = NULL;
            leaf->leaf0->count = 0;
            leaf->leaf0->value = 0;
        }
        
        leaf->count++;
        insert(depth+1, bits, leaf->leaf0);
        
    }

    if (bits.at(depth) == '1') {
        if (leaf->leaf1 == NULL) {

            leaf->leaf1 = new node;
            leaf->leaf1->leaf0 = NULL;
            leaf->leaf1->leaf1 = NULL;
            leaf->leaf1->count = 0;
            leaf->leaf1->value = 1;
        }
        
        leaf->count++;
        insert(depth+1, bits, leaf->leaf1);
    }

}

bool ipbtree::exists(string bits) {

    return exists(0, bits, root);
}

bool ipbtree::exists(int depth, string bits, node *leaf) {

    if (depth == bits.length()) {
        return true;
    }

    bool found_leaf0 = false;
    bool found_leaf1 = false;
    
    if (bits.at(depth) == '0') {
        if (leaf->leaf0 == NULL) {
        } else {
            found_leaf0 = exists(depth+1, bits, leaf->leaf0);
        }
    }

    if (bits.at(depth) == '1') {
        if (leaf->leaf1 == NULL) {
        } else {
            found_leaf1 = exists(depth+1, bits, leaf->leaf1);
        }
    }

    if (found_leaf0 == true || found_leaf1 == true) {
        return true;
    }

    return false;
}

void ipbtree::sort(bool display_cidr, bool display_network_object) {
    string buf = "";
    sort(0,buf,root, display_cidr, display_network_object);
}

void ipbtree::sort(int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object) {
    if (curdepth > 32) {
        return;
    }

    // we are sorting single IP addresses, so we only care about level 32
    if (curdepth == 32) {
        string ip = string_bits_to_string_ip(buf);
		if (display_network_object) {
			cout << "network-object host " << ip << endl << flush;
		} else if (!port_suffix.empty()) {
	        cout << ip << ":" << port_suffix << endl << flush;
		} else {
	        cout << ip << endl << flush;
		}
        return;
    }

    uint64_t block_size_s = 1ULL << (32 - curdepth);
    uint64_t half_s = block_size_s >> 1;
    bool is_full_s = ((uint64_t)leaf->count >= block_size_s);
    node ssyn0 = {0, (unsigned long long)half_s, NULL, NULL};
    node ssyn1 = {1, (unsigned long long)half_s, NULL, NULL};
    node* sl = is_full_s ? &ssyn0 : leaf->leaf0;
    node* sr = is_full_s ? &ssyn1 : leaf->leaf1;
    if (sl) sort(curdepth+1, buf+"0", sl, display_cidr, display_network_object);
    if (sr) sort(curdepth+1, buf+"1", sr, display_cidr, display_network_object);
    return;
}

void ipbtree::sort6(bool display_cidr, bool display_network_object) {
    string buf = "";
    sort6(0,buf,root, display_cidr, display_network_object);
}

void ipbtree::sort6(int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object) {


    if (curdepth > 128) {
        return;
    }

    // we are sorting single IP addresses, so we only care about level 128
    if (curdepth == 128) {

        struct sockaddr_in6 scratch6;
        char buf2[INET6_ADDRSTRLEN];

        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);

		if (display_network_object) {
			cout << "network-object host " << buf2 << endl << flush;
		} else if (!port_suffix.empty()) {
	        cout << "[" << buf2 << "]:" << port_suffix << endl << flush;
		} else {
	        cout << buf2 << endl << flush;
		}

        return;
    }

    bool is_full_s6 = (leaf->count == ~0ULL);
    node ssyn6_0 = {0, ~0ULL, NULL, NULL};
    node ssyn6_1 = {1, ~0ULL, NULL, NULL};
    node* sl6 = is_full_s6 ? &ssyn6_0 : leaf->leaf0;
    node* sr6 = is_full_s6 ? &ssyn6_1 : leaf->leaf1;
    if (sl6) sort6(curdepth+1, buf+"0", sl6, display_cidr, display_network_object);
    if (sr6) sort6(curdepth+1, buf+"1", sr6, display_cidr, display_network_object);
    return;
}

void ipbtree::summarize(int maxdepth, bool display_cidr, bool display_network_object, int threshold) {
    string buf = "";
    summarize(maxdepth, 0, buf, root, display_cidr, display_network_object, threshold);
}

void ipbtree::summarize(int maxdepth, int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object, int threshold) {

    if (curdepth == maxdepth) {
        if (display_cidr) {
            cout << string_bits_to_string_ip(buf) << "/" << curdepth << endl << flush;
        } else {
			if (display_network_object) {
				cout << "network-object " << string_bits_to_string_ip(buf) << " " << cidr_to_subnetmask(curdepth) << endl << flush;
			} else {
	            cout << string_bits_to_string_ip(buf) << " " << cidr_to_subnetmask(curdepth) << endl << flush;
			}
        }
        return;
    }

    // if our count exceeds the threshold percentage of capacity
    if (leaf->count * 100 > pow(2, (32 - curdepth)) * threshold) {
        if (display_cidr) {
            cout << string_bits_to_string_ip(buf) << "/" << curdepth << endl << flush;
        } else {
			if (display_network_object) {
				cout << "network-object " << string_bits_to_string_ip(buf) << " " << cidr_to_subnetmask(curdepth) << endl << flush;
			} else {
	            cout << string_bits_to_string_ip(buf) << " " << cidr_to_subnetmask(curdepth) << endl << flush;
			}

        }
        return;
    }

    if (leaf->leaf0 != NULL) {
        summarize(maxdepth, curdepth+1, buf + "0", leaf->leaf0, display_cidr, display_network_object, threshold);
    }

    if (leaf->leaf1 != NULL) {
        summarize(maxdepth, curdepth+1, buf + "1", leaf->leaf1, display_cidr, display_network_object, threshold);
    }

    return;

}

void ipbtree::summarize6(int maxdepth, bool display_cidr, bool display_network_object, int threshold) {
    string buf = "";
    summarize6(maxdepth, 0, buf, root, display_cidr, display_network_object, threshold);
}

void ipbtree::summarize6(int maxdepth, int curdepth, string buf, node *leaf, bool display_cidr, bool display_network_object, int threshold) {

    struct sockaddr_in6 scratch6;
    char buf2[INET6_ADDRSTRLEN];

    if (curdepth == maxdepth) {

        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);

        if (display_network_object) {
            cout << "network-object " << buf2 << "/" << curdepth << endl << flush;
        } else {
            cout << buf2 << "/" << curdepth << endl << flush;
        }
        return;
    }

    // ok - pow(is) is only double.  This means this will break if try to summate more than that many ipv6's

        // if this block is fully packed (sentinel ~0ULL) or our count exceeds the
        // threshold percentage of capacity, collapse and print at this depth.
        // (v6 nodes store only 0/1/~0ULL, so the full sentinel must be checked explicitly.)
    if (leaf->count == ~0ULL || leaf->count * 100 > pow(2, (128 - curdepth)) * threshold) {


        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);

        if (display_network_object) {
            cout << "network-object " << buf2 << "/" << curdepth << endl << flush;
        } else {
            cout << buf2 << "/" << curdepth << endl << flush;
        }
        return;
    }


    if (leaf->leaf0 != NULL) {
        summarize6(maxdepth, curdepth+1, buf + "0", leaf->leaf0, display_cidr, display_network_object, threshold);
    }

    if (leaf->leaf1 != NULL) {
        summarize6(maxdepth, curdepth+1, buf + "1", leaf->leaf1, display_cidr, display_network_object, threshold);
    }

    return;
}

void ipbtree::cidr_list() {
    string buf = "";
    cidr_list(0, buf, root);
}

void ipbtree::cidr_list(int curdepth, string buf, node *leaf) {
    if (curdepth > 32) return;
    if (curdepth == 32) {
        cout << string_bits_to_string_ip(buf) << "/32" << endl << flush;
        return;
    }
    // collapse if this block is fully packed (count >= 2^(32-depth))
    if (leaf->count >= (unsigned long long)pow(2, 32 - curdepth)) {
        cout << string_bits_to_string_ip(buf) << "/" << curdepth << endl << flush;
        return;
    }
    if (leaf->leaf0 != NULL) cidr_list(curdepth+1, buf + "0", leaf->leaf0);
    if (leaf->leaf1 != NULL) cidr_list(curdepth+1, buf + "1", leaf->leaf1);
}

void ipbtree::cidr_list6() {
    string buf = "";
    cidr_list6(0, buf, root);
}

void ipbtree::cidr_list6(int curdepth, string buf, node *leaf) {
    if (curdepth > 128) return;
    if (curdepth == 128) {
        struct sockaddr_in6 scratch6;
        char buf2[INET6_ADDRSTRLEN];
        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);
        cout << buf2 << "/128" << endl << flush;
        return;
    }
    // collapse if this block is fully packed (sentinel ~0ULL, or count covers the block)
    if (leaf->count == ~0ULL || (double)leaf->count >= pow(2, 128 - curdepth)) {
        struct sockaddr_in6 scratch6;
        char buf2[INET6_ADDRSTRLEN];
        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);
        cout << buf2 << "/" << curdepth << endl << flush;
        return;
    }
    if (leaf->leaf0 != NULL) cidr_list6(curdepth+1, buf + "0", leaf->leaf0);
    if (leaf->leaf1 != NULL) cidr_list6(curdepth+1, buf + "1", leaf->leaf1);
}

// Render an unsigned 128-bit integer as a decimal string (std::cout can't print __int128).
string u128_to_string(unsigned __int128 v) {
    if (v == 0) return "0";
    string s;
    while (v > 0) {
        s.insert(s.begin(), char('0' + (int)(v % 10)));
        v /= 10;
    }
    return s;
}

// Total addresses covered by the v6 tree. v6 nodes carry no real count (the field is the
// 0/1/~0ULL full/partial sentinel), so we sum block sizes from the tree shape instead: a
// fully-packed node at depth d covers 2^(128-d) addresses; partial nodes recurse.
unsigned __int128 ipbtree::count_addresses6(int depth, node *leaf) {
    if (leaf == NULL || leaf->count == 0) return 0;
    if (leaf->count == ~0ULL) {
        // A full ::/0 holds 2^128 addresses, which does not fit in 128 bits; saturate.
        if (depth == 0) return ~(unsigned __int128)0;
        return (unsigned __int128)1 << (128 - depth);
    }
    return count_addresses6(depth + 1, leaf->leaf0) + count_addresses6(depth + 1, leaf->leaf1);
}

void ipbtree::collect(vector<string>& results) {
    string buf = "";
    collect(0, buf, root, results);
}

void ipbtree::collect(int curdepth, string buf, node *leaf, vector<string>& results) {
    if (curdepth > 32) return;
    if (curdepth == 32) {
        string ip = string_bits_to_string_ip(buf);
        results.push_back(port_suffix.empty() ? ip : ip + ":" + port_suffix);
        return;
    }
    uint64_t block_size_c = 1ULL << (32 - curdepth);
    uint64_t half_c = block_size_c >> 1;
    bool is_full_c = ((uint64_t)leaf->count >= block_size_c);
    node csyn0 = {0, (unsigned long long)half_c, NULL, NULL};
    node csyn1 = {1, (unsigned long long)half_c, NULL, NULL};
    node* cl = is_full_c ? &csyn0 : leaf->leaf0;
    node* cr = is_full_c ? &csyn1 : leaf->leaf1;
    if (cl) collect(curdepth+1, buf+"0", cl, results);
    if (cr) collect(curdepth+1, buf+"1", cr, results);
}

void ipbtree::collect6(vector<string>& results) {
    string buf = "";
    collect6(0, buf, root, results);
}

void ipbtree::collect6(int curdepth, string buf, node *leaf, vector<string>& results) {
    if (curdepth > 128) return;
    if (curdepth == 128) {
        struct sockaddr_in6 scratch6;
        char buf2[INET6_ADDRSTRLEN];
        bitstring_to_ipv6_address(buf, scratch6);
        inet_ntop(AF_INET6, &(scratch6.sin6_addr), buf2, INET6_ADDRSTRLEN);
        results.push_back(port_suffix.empty() ? string(buf2) : "[" + string(buf2) + "]:" + port_suffix);
        return;
    }
    bool is_full_c6 = (leaf->count == ~0ULL);
    node csyn6_0 = {0, ~0ULL, NULL, NULL};
    node csyn6_1 = {1, ~0ULL, NULL, NULL};
    node* cl6 = is_full_c6 ? &csyn6_0 : leaf->leaf0;
    node* cr6 = is_full_c6 ? &csyn6_1 : leaf->leaf1;
    if (cl6) collect6(curdepth+1, buf+"0", cl6, results);
    if (cr6) collect6(curdepth+1, buf+"1", cr6, results);
}


void ipbtree::insert_range_v4(uint32_t start, uint32_t end) {
    insert_range_v4(0, 0ULL, start, end, root);
}

void ipbtree::insert_range_v4(int depth, uint64_t block_start, uint32_t range_start, uint32_t range_end, node* leaf) {
    uint64_t block_size = 1ULL << (32 - depth);
    uint64_t block_end  = block_start + block_size - 1;
    if ((uint64_t)leaf->count >= block_size) return;
    if ((uint64_t)range_end < block_start || (uint64_t)range_start > block_end) return;
    if ((uint64_t)range_start <= block_start && (uint64_t)range_end >= block_end) {
        leaf->count = (unsigned long long)block_size; return;
    }
    if (depth == 32) { leaf->count = 1; return; }
    uint64_t half = block_size >> 1;
    uint64_t mid  = block_start + half;
    if ((uint64_t)range_start < mid) {
        if (!leaf->leaf0) { leaf->leaf0 = new node; leaf->leaf0->value=0; leaf->leaf0->count=0; leaf->leaf0->leaf0=leaf->leaf0->leaf1=NULL; }
        insert_range_v4(depth+1, block_start, range_start, range_end, leaf->leaf0);
    }
    if ((uint64_t)range_end >= mid) {
        if (!leaf->leaf1) { leaf->leaf1 = new node; leaf->leaf1->value=1; leaf->leaf1->count=0; leaf->leaf1->leaf0=leaf->leaf1->leaf1=NULL; }
        insert_range_v4(depth+1, mid, range_start, range_end, leaf->leaf1);
    }
    leaf->count = (leaf->leaf0 ? leaf->leaf0->count : 0) + (leaf->leaf1 ? leaf->leaf1->count : 0);
}

void ipbtree::insert_range_v6(const sockaddr_in6& s, const sockaddr_in6& e) {
    uint8_t blk_s[16] = {0};
    uint8_t blk_e[16]; memset(blk_e, 0xFF, 16);
    insert_range_v6(0, blk_s, blk_e, s.sin6_addr.s6_addr, e.sin6_addr.s6_addr, root);
}

void ipbtree::insert_range_v6(int depth, uint8_t blk_s[16], uint8_t blk_e[16],
                              const uint8_t rng_s[16], const uint8_t rng_e[16], node* leaf) {
    if (leaf->count == ~0ULL) return;
    if (memcmp(rng_e, blk_s, 16) < 0 || memcmp(rng_s, blk_e, 16) > 0) return;
    if (memcmp(rng_s, blk_s, 16) <= 0 && memcmp(rng_e, blk_e, 16) >= 0) {
        leaf->count = ~0ULL; return;
    }
    if (depth == 128) { leaf->count = ~0ULL; return; }
    uint8_t left_e[16], right_s[16];
    memcpy(left_e, blk_s, 16); memcpy(right_s, blk_s, 16);
    right_s[depth/8] |= (uint8_t)(0x80 >> (depth%8));
    for (int bit = depth+1; bit < 128; bit++) left_e[bit/8] |= (uint8_t)(0x80 >> (bit%8));
    if (memcmp(rng_s, right_s, 16) < 0) {
        if (!leaf->leaf0) { leaf->leaf0 = new node; leaf->leaf0->value=0; leaf->leaf0->count=0; leaf->leaf0->leaf0=leaf->leaf0->leaf1=NULL; }
        insert_range_v6(depth+1, blk_s, left_e, rng_s, rng_e, leaf->leaf0);
    }
    if (memcmp(rng_e, right_s, 16) >= 0) {
        if (!leaf->leaf1) { leaf->leaf1 = new node; leaf->leaf1->value=1; leaf->leaf1->count=0; leaf->leaf1->leaf0=leaf->leaf1->leaf1=NULL; }
        insert_range_v6(depth+1, right_s, blk_e, rng_s, rng_e, leaf->leaf1);
    }
    bool lf = leaf->leaf0 && leaf->leaf0->count == ~0ULL;
    bool rf = leaf->leaf1 && leaf->leaf1->count == ~0ULL;
    leaf->count = (lf && rf) ? ~0ULL : ((leaf->leaf0 && leaf->leaf0->count) || (leaf->leaf1 && leaf->leaf1->count) ? 1ULL : 0ULL);
}

void ipbtree::diff_output_v4(node* b) {
    diff_v4(0, 0ULL, string(""), root, b);
}

void ipbtree::diff_v4(int depth, uint64_t block_start, string buf, node* a, node* b) {
    if (!a || a->count == 0) return;
    uint64_t block_size = 1ULL << (32 - depth);
    if (b && (uint64_t)b->count >= block_size) return;
    bool a_full  = ((uint64_t)a->count >= block_size);
    bool b_empty = (!b || b->count == 0);
    if (a_full && b_empty) {
        if (depth == 32) cout << string_bits_to_string_ip(buf) << "\n";
        else             cout << string_bits_to_string_ip(buf) << "/" << depth << "\n";
        return;
    }
    if (depth == 32) { cout << string_bits_to_string_ip(buf) << "\n"; return; }
    uint64_t half = block_size >> 1;
    node sl = {0, (unsigned long long)half, NULL, NULL};
    node sr = {1, (unsigned long long)half, NULL, NULL};
    node* al = a_full ? &sl : a->leaf0;
    node* ar = a_full ? &sr : a->leaf1;
    diff_v4(depth+1, block_start,        buf+"0", al, b ? b->leaf0 : NULL);
    diff_v4(depth+1, block_start + half, buf+"1", ar, b ? b->leaf1 : NULL);
}

void ipbtree::diff_output_v6(node* b) {
    uint8_t blk_s[16] = {0};
    uint8_t blk_e[16]; memset(blk_e, 0xFF, 16);
    diff_v6(0, blk_s, blk_e, string(""), root, b);
}

void ipbtree::diff_v6(int depth, uint8_t blk_s[16], uint8_t blk_e[16], string buf, node* a, node* b) {
    if (!a || a->count == 0) return;
    if (b && b->count == ~0ULL) return;
    bool a_full  = (a->count == ~0ULL);
    bool b_empty = (!b || b->count == 0);
    if (a_full && b_empty) {
        struct sockaddr_in6 sa; memset(&sa, 0, sizeof(sa));
        bitstring_to_ipv6_address(buf, sa);
        char str[INET6_ADDRSTRLEN];
        inet_ntop(AF_INET6, &sa.sin6_addr, str, INET6_ADDRSTRLEN);
        if (depth == 128) cout << str << "\n";
        else              cout << str << "/" << depth << "\n";
        return;
    }
    if (depth == 128) {
        struct sockaddr_in6 sa; memset(&sa, 0, sizeof(sa));
        bitstring_to_ipv6_address(buf, sa);
        char str[INET6_ADDRSTRLEN];
        inet_ntop(AF_INET6, &sa.sin6_addr, str, INET6_ADDRSTRLEN);
        cout << str << "\n"; return;
    }
    uint8_t left_e[16], right_s[16];
    memcpy(left_e, blk_s, 16); memcpy(right_s, blk_s, 16);
    right_s[depth/8] |= (uint8_t)(0x80 >> (depth%8));
    for (int bit = depth+1; bit < 128; bit++) left_e[bit/8] |= (uint8_t)(0x80 >> (bit%8));
    node sl = {0, ~0ULL, NULL, NULL};
    node sr = {1, ~0ULL, NULL, NULL};
    node* al = a_full ? &sl : a->leaf0;
    node* ar = a_full ? &sr : a->leaf1;
    diff_v6(depth+1, blk_s,   left_e, buf+"0", al, b ? b->leaf0 : NULL);
    diff_v6(depth+1, right_s, blk_e,  buf+"1", ar, b ? b->leaf1 : NULL);
}

bool has_only_digits(const string s){
  return s.find_first_not_of( "0123456789" ) == string::npos;
}

bool has_only_hex_digits(const string s){
  return s.find_first_not_of( "0123456789abcdefABCDEF" ) == string::npos;
}

unsigned int SwapBytes(unsigned int original)
{
	return ((original & 0x000000ff) << 24) + ((original & 0x0000ff00) << 8) + ((original & 0x00ff0000) >> 8) + ((original & 0xff000000) >> 24);
}

bool is_private_ipv4(uint32_t addr) {
	if ((addr & 0xFF000000) == 0x0A000000) return true;  // 10/8
	if ((addr & 0xFFF00000) == 0xAC100000) return true;  // 172.16/12
	if ((addr & 0xFFFF0000) == 0xC0A80000) return true;  // 192.168/16
	if ((addr & 0xFF000000) == 0x7F000000) return true;  // 127/8
	if ((addr & 0xFFFF0000) == 0xA9FE0000) return true;  // 169.254/16
	return false;
}

bool is_private_ipv6(const sockaddr_in6& sa6) {
	uint8_t b0 = sa6.sin6_addr.s6_addr[0];
	uint8_t b1 = sa6.sin6_addr.s6_addr[1];
	bool loopback = (sa6.sin6_addr.s6_addr[15] == 1);
	for (int i = 0; i < 15 && loopback; i++)
		if (sa6.sin6_addr.s6_addr[i] != 0) loopback = false;
	if (loopback) return true;
	if ((b0 & 0xFE) == 0xFC) return true;  // fc00::/7 unique local
	if (b0 == 0xFE && (b1 & 0xC0) == 0x80) return true;  // fe80::/10 link local
	return false;
}

int getBroadcastv4(sockaddr_in host, sockaddr_in mask, sockaddr_in &broadcast) {
	broadcast.sin_addr.s_addr = host.sin_addr.s_addr | ~mask.sin_addr.s_addr;
	return 0;
}

int getNetworkNumberv4(sockaddr_in host, sockaddr_in mask, sockaddr_in &network) {
	network.sin_addr.s_addr = host.sin_addr.s_addr & mask.sin_addr.s_addr;
	return 0;
}

int getBroadcastv6(sockaddr_in6 host, sockaddr_in6 mask, sockaddr_in6 &broadcast) {
	// I realize that "broadcast is a misnomer, but I'm keeping it as slang for-the-top-address in solidarity with the v4 of this function.
	for (int i = 0; i < 16; i++) {
		broadcast.sin6_addr.s6_addr[i] = host.sin6_addr.s6_addr[i] | ~mask.sin6_addr.s6_addr[i];
	}
	return 0;
}
int getNetworkNumberv6(sockaddr_in6 host, sockaddr_in6 mask, sockaddr_in6 &network) {

	for (int i = 0; i < 16; i++) {
		network.sin6_addr.s6_addr[i] = host.sin6_addr.s6_addr[i] & mask.sin6_addr.s6_addr[i];
	}
	return 0;
}

int convertMaskv6(string value, sockaddr_in6 &mask) {

	// we are only allowing /X syntax here.  Does anybody actually do FFFF:FFFF:FFFF:: etc?
	
	//int numbits = atoi(value.c_str());
	int numbits = strtol(value.c_str(), NULL, 10);

	if (numbits == 0 && value != "0") {  // strtol uses 0 as error sentinel, but "0" is a valid mask
		return 1;
	}

	int b = -8;
	
	for (int i = 0; i < 16; i++) {
	
		b += 8;

		if (numbits - b >= 8) {
	
			mask.sin6_addr.s6_addr[i] = mask.sin6_addr.s6_addr[i] | 0xFF;	
			continue;
		}

		if (numbits - b <= 0) {
			mask.sin6_addr.s6_addr[i] = 0x0000;
			continue;
		}

		mask.sin6_addr.s6_addr[i] = 0x00;
		
		for (int j = 0; j < (numbits - b); j++) {
			mask.sin6_addr.s6_addr[i] = mask.sin6_addr.s6_addr[i] + pow(2, (8-j-1));
		}
	}
	
	return 0;
}


int convertMaskv4(string value, sockaddr_in &mask) {

	//cout << "!!@@@" << value << endl;

	// I realize that without checking the first octet you can't really say its /A or whatever.  We are using the /A etc here simply as a common shorthand.

	if (value == "0" || value == "0.0.0.0") {
		return (inet_pton(AF_INET, "0.0.0.0", &mask.sin_addr));
	}
	if (value == "1" || value == "128.0.0.0") {
		return (inet_pton(AF_INET, "128.0.0.0", &mask.sin_addr));
	}
	if (value == "2" || value == "192.0.0.0") {
		return (inet_pton(AF_INET, "192.0.0.0", &mask.sin_addr));
	}
	if (value == "3" || value == "224.0.0.0") {
		return (inet_pton(AF_INET, "224.0.0.0", &mask.sin_addr));
	}
	if (value == "4" || value == "240.0.0.0") {
		return (inet_pton(AF_INET, "240.0.0.0", &mask.sin_addr));
	}
	if (value == "5" || value == "248.0.0.0") {
		return (inet_pton(AF_INET, "248.0.0.0", &mask.sin_addr));
	}
	if (value == "6" || value == "252.0.0.0") {
		return (inet_pton(AF_INET, "252.0.0.0", &mask.sin_addr));
	}
	if (value == "7" || value == "254.0.0.0") {
		return (inet_pton(AF_INET, "254.0.0.0", &mask.sin_addr));
	}
	if (value == "8" || value == "255.0.0.0" || value == "A" || value == "a") {
		return (inet_pton(AF_INET, "255.0.0.0", &mask.sin_addr));
	}
	if (value == "9" || value == "255.128.0.0") {
		return (inet_pton(AF_INET, "255.128.0.0", &mask.sin_addr));
	}
	if (value == "10" || value == "255.192.0.0") {
		return (inet_pton(AF_INET, "255.192.0.0", &mask.sin_addr));
	}
	if (value == "11" || value == "255.224.0.0") {
		return (inet_pton(AF_INET, "255.224.0.0", &mask.sin_addr));
	}
	if (value == "12" || value == "255.240.0.0") {
		return (inet_pton(AF_INET, "255.240.0.0", &mask.sin_addr));
	}
	if (value == "13" || value == "255.248.0.0") {
		return (inet_pton(AF_INET, "255.248.0.0", &mask.sin_addr));
	}
	if (value == "14" || value == "255.252.0.0") {
		return (inet_pton(AF_INET, "255.252.0.0", &mask.sin_addr));
	}
	if (value == "15" || value == "255.254.0.0") {
		return (inet_pton(AF_INET, "255.254.0.0", &mask.sin_addr));
	}
	if (value == "16" || value == "255.255.0.0" || value == "B" || value == "b"){
		return (inet_pton(AF_INET, "255.255.0.0", &mask.sin_addr));
	}
	if (value == "17" || value == "255.255.128.0") {
		return (inet_pton(AF_INET, "255.255.128.0", &mask.sin_addr));
	}
	if (value == "18" || value == "255.255.192.0") {
		return (inet_pton(AF_INET, "255.255.192.0", &mask.sin_addr));
	}
	if (value == "19" || value == "255.255.224.0") {
		return (inet_pton(AF_INET, "255.255.224.0", &mask.sin_addr));
	}
	if (value == "20" || value == "255.255.240.0") {
		return (inet_pton(AF_INET, "255.255.240.0", &mask.sin_addr));
	}
	if (value == "21" || value == "255.255.248.0") {
		return (inet_pton(AF_INET, "255.255.248.0", &mask.sin_addr));
	}
	if (value == "22" || value == "255.255.252.0") {
		return (inet_pton(AF_INET, "255.255.252.0", &mask.sin_addr));
	}
	if (value == "23" || value == "255.255.254.0") {
		return (inet_pton(AF_INET, "255.255.254.0", &mask.sin_addr));
	}
	if (value == "24" || value == "255.255.255.0" || value == "C" || value == "c") {
		return (inet_pton(AF_INET, "255.255.255.0", &mask.sin_addr));
	}
	if (value == "25" || value == "255.255.255.128") {
		return (inet_pton(AF_INET, "255.255.255.128", &mask.sin_addr));
	}
	if (value == "26" || value == "255.255.255.192") {
		return (inet_pton(AF_INET, "255.255.255.192", &mask.sin_addr));
	}
	if (value == "27" || value == "255.255.255.224") {
		return (inet_pton(AF_INET, "255.255.255.224", &mask.sin_addr));
	}
	if (value == "28" || value == "255.255.255.240") {
		return (inet_pton(AF_INET, "255.255.255.240", &mask.sin_addr));
	}
	if (value == "29" || value == "255.255.255.248") {
		return (inet_pton(AF_INET, "255.255.255.248", &mask.sin_addr));
	}
	if (value == "30" || value == "255.255.255.252") {
		return (inet_pton(AF_INET, "255.255.255.252", &mask.sin_addr));
	}
	if (value == "31" || value == "255.255.255.254") {
		return (inet_pton(AF_INET, "255.255.255.254", &mask.sin_addr));
	}
	if (value == "32" || value == "255.255.255.255") {
		return (inet_pton(AF_INET, "255.255.255.255", &mask.sin_addr));
	}
	
	//cout << "mask returns 1" << endl;
	return 0;
}

bool isMaskv4(string value) {
	struct sockaddr_in tmpmask;

	// check if its a valid ip first, since convertMask accepts some things that aren't
	if (inet_pton(AF_INET, value.c_str(), &tmpmask) && convertMaskv4(value.c_str(), tmpmask) != 0) {
		return true;
	}

	return false;
}


bool ipv6_range_exceeds_limit(const sockaddr_in6& start, const sockaddr_in6& end, unsigned long long limit) {
    // If any of the top 10 bytes differ the range is at least 2^48, far above any sane limit.
    for (int i = 0; i < 10; i++) {
        if (end.sin6_addr.s6_addr[i] != start.sin6_addr.s6_addr[i]) return true;
    }
    // Compute the 6-byte tail as uint64 values (safe, no overflow).
    uint64_t s = 0, e = 0;
    for (int i = 10; i < 16; i++) {
        s = (s << 8) | start.sin6_addr.s6_addr[i];
        e = (e << 8) | end.sin6_addr.s6_addr[i];
    }
    if (e < s) return true;  // malformed / inverted range
    return (e - s + 1) > limit;
}

bool parse_mac(const string& s, uint8_t mac[6]) {
    // xx:xx:xx:xx:xx:xx or xx-xx-xx-xx-xx-xx
    if (s.size() == 17 && (s[2] == ':' || s[2] == '-')) {
        char sep = s[2];
        for (int i = 0; i < 6; i++) {
            int pos = i * 3;
            if (i > 0 && s[pos - 1] != sep) return false;
            string b = s.substr(pos, 2);
            if (!has_only_hex_digits(b)) return false;
            mac[i] = (uint8_t)strtol(b.c_str(), nullptr, 16);
        }
        return true;
    }
    // xxxx.xxxx.xxxx (Cisco)
    if (s.size() == 14 && s[4] == '.' && s[9] == '.') {
        string raw = s.substr(0, 4) + s.substr(5, 4) + s.substr(10, 4);
        if (!has_only_hex_digits(raw)) return false;
        for (int i = 0; i < 6; i++)
            mac[i] = (uint8_t)strtol(raw.substr(i * 2, 2).c_str(), nullptr, 16);
        return true;
    }
    // aabbccddeeff (bare 12 hex)
    if (s.size() == 12 && has_only_hex_digits(s)) {
        for (int i = 0; i < 6; i++)
            mac[i] = (uint8_t)strtol(s.substr(i * 2, 2).c_str(), nullptr, 16);
        return true;
    }
    return false;
}

string mac_to_linklocal(const uint8_t mac[6]) {
    struct sockaddr_in6 sa6;
    memset(&sa6, 0, sizeof(sa6));
    sa6.sin6_addr.s6_addr[0]  = 0xFE;
    sa6.sin6_addr.s6_addr[1]  = 0x80;
    sa6.sin6_addr.s6_addr[8]  = mac[0] ^ 0x02;  // flip U/L bit
    sa6.sin6_addr.s6_addr[9]  = mac[1];
    sa6.sin6_addr.s6_addr[10] = mac[2];
    sa6.sin6_addr.s6_addr[11] = 0xFF;
    sa6.sin6_addr.s6_addr[12] = 0xFE;
    sa6.sin6_addr.s6_addr[13] = mac[3];
    sa6.sin6_addr.s6_addr[14] = mac[4];
    sa6.sin6_addr.s6_addr[15] = mac[5];
    char buf[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &sa6.sin6_addr, buf, INET6_ADDRSTRLEN);
    return string(buf);
}

bool eui64_to_mac(const sockaddr_in6& sa6, uint8_t mac[6]) {
    if (sa6.sin6_addr.s6_addr[11] != 0xFF || sa6.sin6_addr.s6_addr[12] != 0xFE) return false;
    mac[0] = sa6.sin6_addr.s6_addr[8] ^ 0x02;  // flip U/L bit back
    mac[1] = sa6.sin6_addr.s6_addr[9];
    mac[2] = sa6.sin6_addr.s6_addr[10];
    mac[3] = sa6.sin6_addr.s6_addr[13];
    mac[4] = sa6.sin6_addr.s6_addr[14];
    mac[5] = sa6.sin6_addr.s6_addr[15];
    return true;
}

string format_mac(const uint8_t mac[6]) {
    char buf[18];
    snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    return string(buf);
}

string cleanup_matchstring(string w1, string w2, string w3) {
	/*
	1.1.1.1 - 2.2.2.2
	1.1.1.1/a etc
	1.1.1.1/A etc
	1.1.1.1 255.255.255.0
	1.1.1.1 to 2.2.2.2
	1.1.1.1- 2.2.2.2
	1.1.1.1 -2.2.2.2
	1.1.1.1 <cr> (any of the previous)
	*/

    // OK we don't need most of this, because "xips" doesn't work on arbitrary parts of text the way "ips" does, so
    // what we are really doing is...
    string matchstring = w1 + w2 + w3;

    if (isMaskv4(w2)) {  // only have to worry about v4 here, since we aren't doing FFFF.FFFF.... for ipv6.
        matchstring = w1 + " " + w2;    
        return matchstring;
    }


    // if the middle character is needs to be a "-", make it a "-"
    if ((w2 == "to") || (w2 == "-") || (w2 == "through") || (w2 == "thru" /* JC */)) {           
        matchstring = w1 + "-" + w3;    
        return matchstring;
    }

    return matchstring;
}

int string_to_ipv6_range(string w1, string w2, string w3, string& prettystring, sockaddr_in6 &ipv6_range_start, sockaddr_in6 &ipv6_range_end) {
	
	string matchstring = cleanup_matchstring(w1, w2, w3);

	int countdash = 0;
	int countslash = 0;
	int countspace = 0;


	//@@ replace this with a proper function...
	for (int j = 0; j < matchstring.size(); j++) {
		if (matchstring[j] == '-') {
			countdash++;
		}
		if (matchstring[j] == '/') {
			countslash++;
		}
		if (matchstring[j] == ' ') {
			countspace++;
		}
	}

	// if we have more than 1 of any of those at this point, its a fail
	if (countslash > 1 || countdash > 1 || countspace > 1) {
		return 1;
	}

	prettystring = matchstring;

	// we know what we are working with from the count* variables, we don't need the symbols anymore...
	replace(matchstring.begin(), matchstring.end(), '-', ' ');
	replace(matchstring.begin(), matchstring.end(), '/', ' ');

	istringstream iss(matchstring);

	string firstarg;
	string secondarg;

	iss >> firstarg;
	iss >> secondarg;

	struct sockaddr_in6 sa6;

	// is this legitimate ipv4?  time to check
	if (inet_pton(AF_INET6, firstarg.c_str(), &(sa6.sin6_addr))) {
		// firstarg is an ipv6, we're good
	}
	else {
		
		return 1;
	}

	// if there is a dash, process it as a range
	if (countdash > 0) {

		// added this later - I want to support people doing "192.168.1.X-Y" for sub ranges as well as the full expansion, only on the last octet though.		
		// ipv6 version.  I think we're safe here - the "last_of" for a :: will be the trailing :, and we're putting it right back...
		if ( (secondarg.compare("0") == 0) || ( has_only_hex_digits(secondarg) && strtol(secondarg.c_str(), NULL, 16) > 0  && strtol(secondarg.c_str(), NULL, 16) < 65536)) {
			int pos = firstarg.find_last_of(":");
			secondarg = firstarg.substr(0,pos) + ":" + secondarg;
		}

		if (inet_pton(AF_INET6, firstarg.c_str(), &ipv6_range_start.sin6_addr) && inet_pton(AF_INET6, secondarg.c_str(), &ipv6_range_end.sin6_addr))
		{
		
			return(0);
		}
	}
	// no dash, process it as a mask...
	else {
		// /24 or /C or 255.255.255.128 syntax, we treat as mask
		struct sockaddr_in6 tmpmask;
		if (convertMaskv6(secondarg, tmpmask) == 0) {
			
			if (inet_pton(AF_INET6, firstarg.c_str(), &ipv6_range_start.sin6_addr)) {
				getNetworkNumberv6(ipv6_range_start, tmpmask, ipv6_range_start);
				getBroadcastv6(ipv6_range_start, tmpmask, ipv6_range_end);
		
				return (0);
			}
		}
	
	}
	// lastly, its OK to fail
	return 1;

}

void ipv6_range_to_cidrs_unused(const sockaddr_in6& start6, const sockaddr_in6& end6) {
    // Work with 128-bit values represented as two uint64_t (hi, lo)
    auto to128 = [](const sockaddr_in6& a, uint64_t& hi, uint64_t& lo) {
        hi = lo = 0;
        for (int i = 0; i < 8; i++) hi = (hi << 8) | a.sin6_addr.s6_addr[i];
        for (int i = 8; i < 16; i++) lo = (lo << 8) | a.sin6_addr.s6_addr[i];
    };
    auto from128 = [](uint64_t hi, uint64_t lo, sockaddr_in6& a) {
        memset(&a, 0, sizeof(a));
        for (int i = 7; i >= 0; i--) { a.sin6_addr.s6_addr[i] = hi & 0xFF; hi >>= 8; }
        for (int i = 15; i >= 8; i--) { a.sin6_addr.s6_addr[i] = lo & 0xFF; lo >>= 8; }
    };
    auto cmp128 = [](uint64_t ah, uint64_t al, uint64_t bh, uint64_t bl) -> int {
        if (ah < bh) return -1; if (ah > bh) return 1;
        if (al < bl) return -1; if (al > bl) return 1;
        return 0;
    };

    uint64_t sh, sl, eh, el;
    to128(start6, sh, sl);
    to128(end6, eh, el);

    while (cmp128(sh, sl, eh, el) <= 0) {
        // find largest block fitting at 'start'
        int bits = 128;
        while (bits > 0) {
            int b = bits - 1;
            // mask for a prefix of length b in 128-bit space
            uint64_t mh, ml;
            if (b == 0) {
                mh = 0; ml = 0;
            } else if (b <= 64) {
                mh = (b == 64) ? ~0ULL : ~((1ULL << (64-b))-1);
                ml = 0;
            } else {
                mh = ~0ULL;
                ml = (b == 128) ? ~0ULL : ~((1ULL << (128-b))-1);
            }
            // check alignment: (start & mask) == start
            if ((sh & mh) != sh || (sl & ml) != sl) break;
            // compute block_end = start | ~mask
            uint64_t beh = sh | ~mh, bel = sl | ~ml;
            if (cmp128(beh, bel, eh, el) > 0) break;
            bits--;
        }
        // emit start/bits
        sockaddr_in6 sa;
        from128(sh, sl, sa);
        char buf[INET6_ADDRSTRLEN];
        inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN);
        cout << buf << "/" << bits << endl << flush;
        // advance start by block_size = 2^(128-bits)
        int shift = 128 - bits;
        uint64_t carry_lo = 0, carry_hi = 0;
        if (shift < 64) {
            uint64_t inc = 1ULL << shift;
            uint64_t new_lo = sl + inc;
            carry_lo = (new_lo < sl) ? 1 : 0;
            sl = new_lo;
            sh += carry_lo;
            carry_hi = (sh < carry_lo) ? 1 : 0; // overflow: done
            if (carry_hi) return;
        } else if (shift == 64) {
            sh += 1;
            sl = 0;
            if (sh == 0) return;
        } else {
            uint64_t inc = 1ULL << (shift - 64);
            uint64_t new_hi = sh + inc;
            if (new_hi < sh) return;
            sh = new_hi;
            sl = 0;
        }
    }
}

// Parse hex IP input in various formats into a 4-byte or 16-byte array.
// Returns 4 for IPv4, 16 for IPv6, 0 on failure.
// Accepts: 0xAABBCCDD, \xAA\xBB\xCC\xDD, %AA%BB%CC%DD, 0bBINARY, AABBCCDD (bare)
int parse_hex_ip(const string& s, uint8_t bytes[16]) {
    string hex;
    if (s.size() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
        hex = s.substr(2);
    } else if (s.size() >= 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) {
        // binary prefix
        string bits = s.substr(2);
        if (bits.size() != 32 && bits.size() != 128) return 0;
        for (char c : bits) if (c != '0' && c != '1') return 0;
        int nbytes = bits.size() / 8;
        for (int i = 0; i < nbytes; i++) {
            uint8_t v = 0;
            for (int j = 0; j < 8; j++) v = (v << 1) | (bits[i*8+j] - '0');
            bytes[i] = v;
        }
        return nbytes;
    } else if (s.size() >= 4 && s[0] == '\\' && (s[1] == 'x' || s[1] == 'X')) {
        // \xAA\xBB... escape sequences
        string raw = s;
        hex = "";
        for (size_t i = 0; i < raw.size(); ) {
            if (raw[i] == '\\' && i+1 < raw.size() && (raw[i+1] == 'x' || raw[i+1] == 'X')) {
                if (i+3 >= raw.size()) return 0;
                hex += raw.substr(i+2, 2);
                i += 4;
            } else return 0;
        }
    } else if (!s.empty() && s[0] == '%') {
        // URL encoding %AA%BB...
        string raw = s;
        hex = "";
        for (size_t i = 0; i < raw.size(); ) {
            if (raw[i] == '%') {
                if (i+2 >= raw.size()) return 0;
                hex += raw.substr(i+1, 2);
                i += 3;
            } else return 0;
        }
    } else {
        // bare hex
        hex = s;
    }
    if (!has_only_hex_digits(hex)) return 0;
    if (hex.size() == 8) {
        for (int i = 0; i < 4; i++)
            bytes[i] = (uint8_t)strtol(hex.substr(i*2, 2).c_str(), nullptr, 16);
        return 4;
    }
    if (hex.size() == 32) {
        for (int i = 0; i < 16; i++)
            bytes[i] = (uint8_t)strtol(hex.substr(i*2, 2).c_str(), nullptr, 16);
        return 16;
    }
    return 0;
}

string ipv4_to_hex(uint32_t addr_host_order, bool big_endian) {
    uint8_t b[4];
    if (big_endian) {
        b[0] = (addr_host_order >> 24) & 0xFF;
        b[1] = (addr_host_order >> 16) & 0xFF;
        b[2] = (addr_host_order >>  8) & 0xFF;
        b[3] =  addr_host_order        & 0xFF;
    } else {
        b[0] =  addr_host_order        & 0xFF;
        b[1] = (addr_host_order >>  8) & 0xFF;
        b[2] = (addr_host_order >> 16) & 0xFF;
        b[3] = (addr_host_order >> 24) & 0xFF;
    }
    char buf[11];
    snprintf(buf, sizeof(buf), "0x%02x%02x%02x%02x", b[0], b[1], b[2], b[3]);
    return string(buf);
}

string ipv6_to_hex(const uint8_t bytes[16], bool big_endian) {
    char buf[36];
    buf[0] = '0'; buf[1] = 'x';
    for (int i = 0; i < 16; i++) {
        int idx = big_endian ? i : (15 - i);
        snprintf(buf + 2 + i*2, 3, "%02x", bytes[idx]);
    }
    return string(buf);
}

bool ipv6_in_range(const sockaddr_in6& addr, const sockaddr_in6& lo, const sockaddr_in6& hi) {
    for (int i = 0; i < 16; i++) {
        if (addr.sin6_addr.s6_addr[i] < lo.sin6_addr.s6_addr[i]) return false;
        if (addr.sin6_addr.s6_addr[i] > lo.sin6_addr.s6_addr[i]) break;
    }
    for (int i = 0; i < 16; i++) {
        if (addr.sin6_addr.s6_addr[i] > hi.sin6_addr.s6_addr[i]) return false;
        if (addr.sin6_addr.s6_addr[i] < hi.sin6_addr.s6_addr[i]) break;
    }
    return true;
}

bool is_v4mapped_ipv6(const sockaddr_in6& sa6) {
    for (int i = 0; i < 10; i++)
        if (sa6.sin6_addr.s6_addr[i] != 0) return false;
    return sa6.sin6_addr.s6_addr[10] == 0xFF && sa6.sin6_addr.s6_addr[11] == 0xFF;
}

string ipv4_to_mapped_string(const sockaddr_in& sa4) {
    struct sockaddr_in6 sa6;
    memset(&sa6, 0, sizeof(sa6));
    sa6.sin6_addr.s6_addr[10] = 0xFF;
    sa6.sin6_addr.s6_addr[11] = 0xFF;
    memcpy(&sa6.sin6_addr.s6_addr[12], &sa4.sin_addr.s_addr, 4);
    char buf[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &sa6.sin6_addr, buf, INET6_ADDRSTRLEN);
    return string(buf);
}

string ipv4_to_arpa(uint32_t addr_host_order) {
    char buf[32];
    snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa",
        addr_host_order & 0xFF,
        (addr_host_order >> 8) & 0xFF,
        (addr_host_order >> 16) & 0xFF,
        (addr_host_order >> 24) & 0xFF);
    return string(buf);
}

string ipv6_to_arpa(const uint8_t bytes[16]) {
    char buf[128];
    int pos = 0;
    for (int i = 15; i >= 0; i--)
        pos += snprintf(buf + pos, sizeof(buf) - pos, "%x.%x.", bytes[i] & 0xF, (bytes[i] >> 4) & 0xF);
    snprintf(buf + pos, sizeof(buf) - pos, "ip6.arpa");
    return string(buf);
}

int ipv4_range_to_list(sockaddr_in &ipv4_range_start, sockaddr_in &ipv4_range_end, bool mode_use_tree, ipbtree *summary_tree, bool display_network_object, bool mode_private_only, bool mode_public_only, const string& port_sfx, bool mode_hosts, const vector<pair<uint32_t,uint32_t>>& exclude_v4, bool mode_hex_be, bool mode_hex_le, bool mode_to_mapped, bool mode_arpa) {

    unsigned int start = 0;
    unsigned int end = 0;

    sockaddr_in scratch;

    start = SwapBytes(ipv4_range_start.sin_addr.s_addr);
    end = SwapBytes(ipv4_range_end.sin_addr.s_addr);


    for (unsigned int i = start; i <= end; i++) {

        if (mode_hosts && start + 1 < end && (i == start || i == end)) continue;
        if (mode_private_only && !is_private_ipv4(i)) continue;
        if (mode_public_only && is_private_ipv4(i)) continue;
        bool excluded = false;
        for (const auto& ex : exclude_v4) {
            if (i >= ex.first && i <= ex.second) { excluded = true; break; }
        }
        if (excluded) continue;

        scratch.sin_addr.s_addr = SwapBytes(i);

        if (mode_use_tree) {
            summary_tree->insert(inet_ntoa(scratch.sin_addr));
        } else {
            if (mode_hex_be || mode_hex_le) {
                cout << ipv4_to_hex(i, mode_hex_be) << endl << flush;
            } else if (mode_to_mapped) {
                cout << ipv4_to_mapped_string(scratch) << endl << flush;
            } else if (mode_arpa) {
                cout << ipv4_to_arpa(i) << endl << flush;
            } else if (display_network_object) {
				cout << "network-object host " << inet_ntoa(scratch.sin_addr) << endl << flush;
			} else if (!port_sfx.empty()) {
				cout << inet_ntoa(scratch.sin_addr) << ":" << port_sfx << endl << flush;
			} else {
				cout << inet_ntoa(scratch.sin_addr) << endl << flush;
			}
        }
    }

    return 0;
}

int ipv6_range_to_list(sockaddr_in6 &ipv6_range_start, sockaddr_in6 &ipv6_range_end, bool mode_use_tree, ipbtree *summary_tree, bool display_network_object, bool mode_private_only, bool mode_public_only, const string& port_sfx, bool mode_hosts, const vector<pair<sockaddr_in6,sockaddr_in6>>& exclude_v6, bool mode_hex_be, bool mode_hex_le, bool mode_arpa) {

    // oh my.
    unsigned short i0;
    unsigned short i1;
    unsigned short i2;
    unsigned short i3;
    unsigned short i4;
    unsigned short i5;
    unsigned short i6;
    unsigned short i7;
    unsigned short i8;
    unsigned short i9;
    unsigned short i10;
    unsigned short i11;
    unsigned short i12;
    unsigned short i13;
    unsigned short i14;
    unsigned short i15;

    sockaddr_in6 scratch;
    char buf[INET6_ADDRSTRLEN];

    //apologies to formatting purists
    for (i0 = ipv6_range_start.sin6_addr.s6_addr[0]; i0 <= ipv6_range_end.sin6_addr.s6_addr[0]; i0++) {
        scratch.sin6_addr.s6_addr[0] = i0;
    for (i1 = ipv6_range_start.sin6_addr.s6_addr[1]; i1 <= ipv6_range_end.sin6_addr.s6_addr[1]; i1++) {
        scratch.sin6_addr.s6_addr[1] = i1;
    for (i2 = ipv6_range_start.sin6_addr.s6_addr[2]; i2 <= ipv6_range_end.sin6_addr.s6_addr[2]; i2++) {    
        scratch.sin6_addr.s6_addr[2] = i2;
    for (i3 = ipv6_range_start.sin6_addr.s6_addr[3]; i3 <= ipv6_range_end.sin6_addr.s6_addr[3]; i3++) {
        scratch.sin6_addr.s6_addr[3] = i3;
    for (i4 = ipv6_range_start.sin6_addr.s6_addr[4]; i4 <= ipv6_range_end.sin6_addr.s6_addr[4]; i4++) {
        scratch.sin6_addr.s6_addr[4] = i4;
    for (i5 = ipv6_range_start.sin6_addr.s6_addr[5]; i5 <= ipv6_range_end.sin6_addr.s6_addr[5]; i5++) {
        scratch.sin6_addr.s6_addr[5] = i5;
    for (i6 = ipv6_range_start.sin6_addr.s6_addr[6]; i6 <= ipv6_range_end.sin6_addr.s6_addr[6]; i6++) {
        scratch.sin6_addr.s6_addr[6] = i6;
    for (i7 = ipv6_range_start.sin6_addr.s6_addr[7]; i7 <= ipv6_range_end.sin6_addr.s6_addr[7]; i7++) {
        scratch.sin6_addr.s6_addr[7] = i7;
    for (i8 = ipv6_range_start.sin6_addr.s6_addr[8]; i8 <= ipv6_range_end.sin6_addr.s6_addr[8]; i8++) {    
        scratch.sin6_addr.s6_addr[8] = i8;
    for (i9 = ipv6_range_start.sin6_addr.s6_addr[9]; i9 <= ipv6_range_end.sin6_addr.s6_addr[9]; i9++) {
        scratch.sin6_addr.s6_addr[9] = i9;
    for (i10 = ipv6_range_start.sin6_addr.s6_addr[10]; i10 <= ipv6_range_end.sin6_addr.s6_addr[10]; i10++) {
        scratch.sin6_addr.s6_addr[10] = i10;
    for (i11 = ipv6_range_start.sin6_addr.s6_addr[11]; i11 <= ipv6_range_end.sin6_addr.s6_addr[11]; i11++) {
        scratch.sin6_addr.s6_addr[11] = i11;
    for (i12 = ipv6_range_start.sin6_addr.s6_addr[12]; i12 <= ipv6_range_end.sin6_addr.s6_addr[12]; i12++) {
        scratch.sin6_addr.s6_addr[12] = i12;
    for (i13 = ipv6_range_start.sin6_addr.s6_addr[13]; i13 <= ipv6_range_end.sin6_addr.s6_addr[13]; i13++) {
        scratch.sin6_addr.s6_addr[13] = i13;
    for (i14 = ipv6_range_start.sin6_addr.s6_addr[14]; i14 <= ipv6_range_end.sin6_addr.s6_addr[14]; i14++) {    
        scratch.sin6_addr.s6_addr[14] = i14;
    for (i15 = ipv6_range_start.sin6_addr.s6_addr[15]; i15 <= ipv6_range_end.sin6_addr.s6_addr[15]; i15++) {
        scratch.sin6_addr.s6_addr[15] = i15;

        inet_ntop(AF_INET6, &(scratch.sin6_addr), buf, INET6_ADDRSTRLEN);

        if (mode_hosts) {
            bool is_singleton = (memcmp(&ipv6_range_start.sin6_addr, &ipv6_range_end.sin6_addr, 16) == 0);
            if (!is_singleton) {
                if (memcmp(&scratch.sin6_addr, &ipv6_range_start.sin6_addr, 16) == 0) continue;
                if (memcmp(&scratch.sin6_addr, &ipv6_range_end.sin6_addr, 16) == 0) continue;
            }
        }
        if (mode_private_only && !is_private_ipv6(scratch)) continue;
        if (mode_public_only && is_private_ipv6(scratch)) continue;
        bool excluded6 = false;
        for (const auto& ex : exclude_v6) {
            if (ipv6_in_range(scratch, ex.first, ex.second)) { excluded6 = true; break; }
        }
        if (excluded6) continue;

        if (mode_use_tree) {
            summary_tree->insert_range_v6(scratch, scratch);
        } else {
            if (mode_hex_be || mode_hex_le) {
                cout << ipv6_to_hex(scratch.sin6_addr.s6_addr, mode_hex_be) << endl << flush;
            } else if (mode_arpa) {
                cout << ipv6_to_arpa(scratch.sin6_addr.s6_addr) << endl << flush;
            } else if (display_network_object) {
				cout << "network-object host " << buf << endl << flush;
			} else if (!port_sfx.empty()) {
	            cout << "[" << buf << "]:" << port_sfx << endl << flush;
			} else {
	            cout << buf << endl << flush;
			}
        }

    }}}}}}}}}}}}}}}}  // bam!

    return 0;
}


int string_to_ipv4_range(bool mode_use_old_aton_behavior, string w1, string w2, string w3, string& prettystring, sockaddr_in &ipv4_range_start, sockaddr_in &ipv4_range_end) {
	
	//@@ once this works, it can probably replace the mostly duplicate code in the original range calculator
	string matchstring = cleanup_matchstring(w1, w2, w3);

	int countdash = 0;
	int countslash = 0;
	int countspace = 0;


	//@@ replace this with a proper function...
	for (int j = 0; j < matchstring.size(); j++) {
		if (matchstring[j] == '-') {
			countdash++;
		}
		if (matchstring[j] == '/') {
			countslash++;
		}
		if (matchstring[j] == ' ') {
			countspace++;
		}
	}

	// if we have more than 1 of any of those at this point, its a fail
	if (countslash > 1 || countdash > 1 || countspace > 1) {
		return 1;
	}

	prettystring = matchstring;

	// we know what we are working with from the count* variables, we don't need the symbols anymore...
	replace(matchstring.begin(), matchstring.end(), '-', ' ');
	replace(matchstring.begin(), matchstring.end(), '/', ' ');

	istringstream iss(matchstring);

	string firstarg;
	string secondarg;

	iss >> firstarg;
	iss >> secondarg;

	struct sockaddr_in sa;

	// is this legitimate ipv4?  time to check
	if ((mode_use_old_aton_behavior && inet_aton(firstarg.c_str(), &(sa.sin_addr))) ||
		(!mode_use_old_aton_behavior && inet_pton(AF_INET, firstarg.c_str(), &(sa.sin_addr)))) {
		// firstarg is an ipv4, we're good
	}
	else {
		return 1;
	}

	// if there is a dash, process it as a range
	if (countdash > 0) {

		// added this later - I want to support people doing "192.168.1.X-Y" for sub ranges as well as the full expansion, only on the last octet though.		
		if ( (secondarg.compare("0") == 0) || ( has_only_digits(secondarg) && atoi(secondarg.c_str()) > 0  && atoi(secondarg.c_str()) < 256)) {
			int pos = firstarg.find_last_of(".");
			secondarg = firstarg.substr(0,pos) + "." + secondarg;
		}

		if ((!mode_use_old_aton_behavior && inet_pton(AF_INET, firstarg.c_str(), &ipv4_range_start.sin_addr) && inet_pton(AF_INET, secondarg.c_str(), &ipv4_range_end.sin_addr)) ||
			(mode_use_old_aton_behavior && inet_aton(firstarg.c_str(), &ipv4_range_start.sin_addr) && inet_aton(secondarg.c_str(), &ipv4_range_end.sin_addr))) {
			return 0;
		}
	}
	// no dash, process it as a mask...
	else {
		// /24 or /C or 255.255.255.128 syntax, we treat as mask
		struct sockaddr_in tmpmask;
		if (convertMaskv4(secondarg, tmpmask) != 0) {

			if (mode_use_old_aton_behavior && inet_aton(firstarg.c_str(), &ipv4_range_start.sin_addr)) {
				getNetworkNumberv4(ipv4_range_start, tmpmask, ipv4_range_start);
				getBroadcastv4(ipv4_range_start, tmpmask, ipv4_range_end);
				return 0;
			} 

			//printf("@@biff");

			if (inet_pton(AF_INET, firstarg.c_str(), &ipv4_range_start.sin_addr)) {
				getNetworkNumberv4(ipv4_range_start, tmpmask, ipv4_range_start);
				getBroadcastv4(ipv4_range_start, tmpmask, ipv4_range_end);
				return 0;
			}

			if (!mode_use_old_aton_behavior && inet_pton(AF_INET, firstarg.c_str(), &ipv4_range_start.sin_addr)) {
				getNetworkNumberv4(ipv4_range_start, tmpmask, ipv4_range_start);
				getBroadcastv4(ipv4_range_start, tmpmask, ipv4_range_end);
				return 0;
			}
		}
	}
	return 1;
	
}



struct InputRange {
    int family;
    uint32_t s4, e4;
    sockaddr_in6 s6, e6;
    string pretty;
};

string range_pretty_v4(uint32_t s, uint32_t e) {
    for (int p = 0; p <= 32; p++) {
        uint32_t mask = (p == 0) ? 0U : (~0U << (32 - p));
        if ((s & mask) == s && (s | ~mask) == e) {
            struct sockaddr_in t; t.sin_addr.s_addr = htonl(s);
            char buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &t.sin_addr, buf, INET_ADDRSTRLEN);
            return string(buf) + "/" + to_string(p);
        }
    }
    char b1[INET_ADDRSTRLEN], b2[INET_ADDRSTRLEN];
    struct sockaddr_in t1, t2;
    t1.sin_addr.s_addr = htonl(s); inet_ntop(AF_INET, &t1.sin_addr, b1, INET_ADDRSTRLEN);
    t2.sin_addr.s_addr = htonl(e); inet_ntop(AF_INET, &t2.sin_addr, b2, INET_ADDRSTRLEN);
    return string(b1) + " - " + string(b2);
}

string range_pretty_v6(const sockaddr_in6& s, const sockaddr_in6& e) {
    for (int p = 0; p <= 128; p++) {
        sockaddr_in6 tmpmask, tmpnet, tmpbcast;
        memset(&tmpmask, 0, sizeof(tmpmask));
        convertMaskv6(to_string(p), tmpmask);
        getNetworkNumberv6(const_cast<sockaddr_in6&>(s), tmpmask, tmpnet);
        getBroadcastv6(const_cast<sockaddr_in6&>(s), tmpmask, tmpbcast);
        if (memcmp(&tmpnet.sin6_addr, &s.sin6_addr, 16) == 0 &&
            memcmp(&tmpbcast.sin6_addr, &e.sin6_addr, 16) == 0) {
            char buf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &s.sin6_addr, buf, INET6_ADDRSTRLEN);
            return string(buf) + "/" + to_string(p);
        }
    }
    char b1[INET6_ADDRSTRLEN], b2[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &s.sin6_addr, b1, INET6_ADDRSTRLEN);
    inet_ntop(AF_INET6, &e.sin6_addr, b2, INET6_ADDRSTRLEN);
    return string(b1) + " - " + string(b2);
}

void print_subnet_info_v4(uint32_t s, uint32_t e) {
    int prefix = -1;
    for (int p = 0; p <= 32; p++) {
        uint32_t mask = (p == 0) ? 0U : (~0U << (32 - p));
        if ((s & mask) == s && (s | ~mask) == e) { prefix = p; break; }
    }
    auto u32s = [](uint32_t v) {
        struct sockaddr_in t; t.sin_addr.s_addr = htonl(v);
        char buf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &t.sin_addr, buf, INET_ADDRSTRLEN);
        return string(buf);
    };
    uint64_t total = (uint64_t)(e - s) + 1;
    uint64_t usable = (total <= 2) ? total : total - 2;
    uint32_t fh = (total > 2) ? s + 1 : s;
    uint32_t lh = (total > 2) ? e - 1 : e;
    string mask_s, wild_s, pre_s;
    if (prefix >= 0) {
        uint32_t mask = (prefix == 0) ? 0U : (~0U << (32 - prefix));
        mask_s = u32s(mask); wild_s = u32s(~mask); pre_s = "/" + to_string(prefix);
    } else { mask_s = wild_s = pre_s = "N/A"; }
    cout << u32s(s) << "\t" << u32s(e) << "\t" << mask_s << "\t" << wild_s
         << "\t" << pre_s << "\t" << u32s(fh) << "\t" << u32s(lh)
         << "\t" << total << "\t" << usable << "\n";
}

void print_subnet_info_v6(const sockaddr_in6& s6, const sockaddr_in6& e6) {
    int prefix = -1;
    for (int p = 0; p <= 128; p++) {
        sockaddr_in6 tmpmask, tmpnet, tmpbcast;
        memset(&tmpmask, 0, sizeof(tmpmask));
        convertMaskv6(to_string(p), tmpmask);
        getNetworkNumberv6(const_cast<sockaddr_in6&>(s6), tmpmask, tmpnet);
        getBroadcastv6(const_cast<sockaddr_in6&>(s6), tmpmask, tmpbcast);
        if (memcmp(&tmpnet.sin6_addr, &s6.sin6_addr, 16) == 0 &&
            memcmp(&tmpbcast.sin6_addr, &e6.sin6_addr, 16) == 0) { prefix = p; break; }
    }
    char ss[INET6_ADDRSTRLEN], es[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &s6.sin6_addr, ss, INET6_ADDRSTRLEN);
    inet_ntop(AF_INET6, &e6.sin6_addr, es, INET6_ADDRSTRLEN);
    string pre_s = (prefix >= 0) ? "/" + to_string(prefix) : "N/A";
    string total_s;
    if (prefix >= 0) {
        int exp = 128 - prefix;
        if (exp == 0) total_s = "1";
        else if (exp <= 63) total_s = to_string(1ULL << exp);
        else total_s = "2^" + to_string(exp);
    } else total_s = "N/A";
    cout << ss << "\tN/A\tN/A\tN/A\t" << pre_s
         << "\t" << ss << "\t" << es << "\t" << total_s << "\t" << total_s << "\n";
}

void run_check_v4(const string& label, uint32_t cs, uint32_t ce,
                  const vector<InputRange>& input_ranges) {
    uint64_t check_total = (uint64_t)(ce - cs) + 1;
    cout << "Checking: " << label;
    if (check_total == 1) cout << " (1 address)\n";
    else cout << " (" << check_total << " addresses)\n";
    bool any = false;
    for (const auto& ir : input_ranges) {
        if (ir.family != 4) continue;
        uint32_t rs = ir.s4, re = ir.e4;
        uint32_t ov_s = (cs > rs) ? cs : rs;
        uint32_t ov_e = (ce < re) ? ce : re;
        if (ov_s > ov_e) continue;
        any = true;
        uint64_t overlap = (uint64_t)(ov_e - ov_s) + 1;
        uint64_t range_total = (uint64_t)(re - rs) + 1;
        if (cs >= rs && ce <= re) {
            cout << "  Contained in: " << ir.pretty << "\n";
        } else {
            cout << "  Overlaps: " << ir.pretty
                 << " (" << overlap << " of " << check_total << " checked, "
                 << overlap << " of " << range_total << " in range)\n";
        }
    }
    if (!any) cout << "  No overlap with any input range.\n";
    cout << "\n";
}

void run_check_v6(const string& label, const sockaddr_in6& cs, const sockaddr_in6& ce,
                  const vector<InputRange>& input_ranges) {
    cout << "Checking: " << label << "\n";
    auto cmp6 = [](const uint8_t* a, const uint8_t* b) { return memcmp(a, b, 16); };
    bool any = false;
    for (const auto& ir : input_ranges) {
        if (ir.family != 6) continue;
        const uint8_t* cs_b = cs.sin6_addr.s6_addr;
        const uint8_t* ce_b = ce.sin6_addr.s6_addr;
        const uint8_t* rs_b = ir.s6.sin6_addr.s6_addr;
        const uint8_t* re_b = ir.e6.sin6_addr.s6_addr;
        const uint8_t* ov_s = (cmp6(cs_b, rs_b) >= 0) ? cs_b : rs_b;
        const uint8_t* ov_e = (cmp6(ce_b, re_b) <= 0) ? ce_b : re_b;
        if (cmp6(ov_s, ov_e) > 0) continue;
        any = true;
        bool contained = (cmp6(cs_b, rs_b) >= 0 && cmp6(ce_b, re_b) <= 0);
        if (contained) {
            cout << "  Contained in: " << ir.pretty << "\n";
        } else {
            cout << "  Overlaps: " << ir.pretty << " (partial overlap)\n";
        }
    }
    if (!any) cout << "  No overlap with any input range.\n";
    cout << "\n";
}

int main(int argc, char* argv[]) {

    string input = "";

	bool mode_ipv4 = true;
	bool mode_ipv6 = true;
   
	bool mode_use_old_aton_behavior = false;

    int range_type = 0; // 4, or 6. 

    struct sockaddr_in ipv4_range_start;
    struct sockaddr_in ipv4_range_end;
    struct sockaddr_in6 ipv6_range_start;
    struct sockaddr_in6 ipv6_range_end;

    struct sockaddr_in scratch;
    struct sockaddr_in6 scratch6;

    bool mode_summarize = false;
    bool mode_sort = false;

    int max_cidr = 32;
    int max_cidr_ipv6 = 64;


    bool display_cidr = true;
	bool display_network_object = false;

	int threshold = 50;

	bool mode_count = false;
	bool mode_shuffle = false;
	int port_number = -1;
	bool mode_private = false;
	bool mode_public = false;
	bool mode_hosts = false;
	bool mode_mac_to_ipv6 = false;
	bool mode_ipv6_to_mac = false;
	bool mode_anyway = false;
	bool mode_cidr_list = false;
	bool mode_hex_be = false;
	bool mode_hex_le = false;
	bool mode_to_mapped = false;
	bool mode_arpa = false;
	bool mode_subnet_info = false;
	bool mode_check = false;
	vector<string> check_specs;
	string check_file;
	vector<InputRange> input_ranges;
	bool mode_diff = false;
	string diff_file;
	ipbtree *diff_tree = new ipbtree;
	ipbtree *diff_tree6 = new ipbtree;
	const unsigned long long EXPAND_LIMIT = 1000000;

	vector<pair<uint32_t,uint32_t>> exclude_v4;
	vector<pair<sockaddr_in6,sockaddr_in6>> exclude_v6;

	std::vector<std::string> input_files;

	//parse some arguments.  Starting at 1 because 0 is "ips"
	for ( int i = 1; i < argc; i++) {

		std::string arg = argv[i];

		if (arg == "-4") {
			mode_ipv6 = false;
			continue;
		}

		if (arg == "-6") {
			mode_ipv4 = false;
			continue;
		}

		if (arg == "--aton") {
			//because stuff like 10:1 used to be a valid way to say 10:0:0:1, but not anymore (inet_aton vs inet_pton)
			mode_use_old_aton_behavior = true;
			continue;
		}


        if (arg == "-s" || arg == "--summarize") {
            mode_summarize = true;
            continue;
        }

        if (arg == "--sort" ) {
            mode_sort = true;
            continue;
        }


        // the idea with plain -m is that you probably aren't doing both ipv4/ipv6 in the same thing, so meh
        if (arg == "-m" || arg == "--max-depth") {
            if ( i+1 < argc) {
                max_cidr = atoi(argv[i+1]);
                max_cidr_ipv6 = atoi(argv[i+1]);
                i++;
                continue;
            }
        }

        if (arg == "--max-depth-ipv4") {
            if ( i+1 < argc) {
                max_cidr = atoi(argv[i+1]);
                i++;
                continue;
            }
        }

        if (arg == "--max-depth-ipv6") {
            if ( i+1 < argc) {
                max_cidr_ipv6 = atoi(argv[i+1]);
                i++;
                continue;
            }
        }

        if (arg == "--cidr") {
            display_cidr = true;
            continue;
        }

        if (arg == "--mask") {
            display_cidr = false;
            continue;
        }

		if (arg == "--asa") {
			display_network_object = true;
			continue;
		}

		if (arg == "-f" || arg == "--file") {
			if (i+1 < argc) {
				input_files.push_back(argv[i+1]);
				i++;
				continue;
			}
		}

		if (arg == "--threshold") {
			if (i+1 < argc) {
				threshold = atoi(argv[i+1]);
				if (threshold < 1) threshold = 1;
				if (threshold > 100) threshold = 100;
				i++;
				continue;
			}
		}

		if (arg == "--count") {
			mode_count = true;
			continue;
		}

		if (arg == "--shuffle") {
			mode_shuffle = true;
			continue;
		}

		if (arg == "--port") {
			if (i+1 < argc) {
				port_number = atoi(argv[i+1]);
				i++;
				continue;
			}
		}

		if (arg == "--private") {
			mode_private = true;
			continue;
		}

		if (arg == "--public") {
			mode_public = true;
			continue;
		}

		if (arg == "--hosts") {
			mode_hosts = true;
			continue;
		}

		if (arg == "--mac-to-ipv6") {
			mode_mac_to_ipv6 = true;
			continue;
		}

		if (arg == "--ipv6-to-mac") {
			mode_ipv6_to_mac = true;
			continue;
		}

		if (arg == "--anyway") {
			mode_anyway = true;
			continue;
		}

		if (arg == "--cidr-list") {
			mode_cidr_list = true;
			continue;
		}

		if (arg == "--hex-be" || arg == "--hex") {
			mode_hex_be = true;
			continue;
		}

		if (arg == "--hex-le") {
			mode_hex_le = true;
			continue;
		}

		if (arg == "--to-mapped") {
			mode_to_mapped = true;
			continue;
		}

		if (arg == "--arpa") {
			mode_arpa = true;
			continue;
		}

		if (arg == "--subnet-info") {
			mode_subnet_info = true;
			continue;
		}

		if (arg == "--check") {
			if (i+1 < argc) {
				check_specs.push_back(argv[i+1]);
				i++;
			}
			mode_check = true;
			continue;
		}

		if (arg == "--check-file") {
			if (i+1 < argc) {
				check_file = argv[i+1];
				i++;
			}
			mode_check = true;
			continue;
		}

		if (arg == "--diff") {
			if (i+1 < argc) {
				diff_file = argv[i+1];
				i++;
			}
			mode_diff = true;
			continue;
		}

		if (arg == "--exclude") {
			if (i+1 < argc) {
				string ex_arg = argv[i+1];
				i++;
				struct sockaddr_in ex4s, ex4e;
				struct sockaddr_in6 ex6s, ex6e;
				string ex_pretty;
				if (string_to_ipv4_range(false, ex_arg, "", "", ex_pretty, ex4s, ex4e) == 0) {
					exclude_v4.push_back({SwapBytes(ex4s.sin_addr.s_addr), SwapBytes(ex4e.sin_addr.s_addr)});
				} else if (string_to_ipv6_range(ex_arg, "", "", ex_pretty, ex6s, ex6e) == 0) {
					exclude_v6.push_back({ex6s, ex6e});
				} else {
					cerr << "xips: --exclude: cannot parse range: " << ex_arg << endl;
				}
			}
			continue;
		}

		if (arg == "-v" || arg == "--version") {
			cout << "xips v0.6  11 May 2026" << endl
				 << "by Eli Fulkerson.  See http://www.elifulkerson.com for updates" << endl << flush;
			return 0;
		}

		if (arg == "-h" || arg == "--help" || arg == "--usage" || arg == "?" || arg == "/?" || arg == "-?" || arg == "/h" || arg == "/H") {
			cout << "Syntax: your_command | xips [-4] [-6] [-v] [--cidr|--mask] [file ...]" << endl
				<< endl
				<< "Eats STDIN, expands IP addresses."
				<< endl
				<< "Options:" << endl
				<< " -4     : expand IPv4 addresses only" << endl
				<< " -6     : expand IPv6 addresses only" << endl
                << " -s     : summarize, rather than expand (collapses at > 50% utilization by default)" << endl
                << " --sort : expand, dedupe and sort numerically" << endl
                << " -m X   : summarize to a max depth of /X (also --max-depth X)" << endl
			  /*                << " --cidr : output /cidr notation (default)" << endl*/
                << " --mask : output subnet mask notation instead of cidr" << endl
				<< " --asa  : output asa network-object notation" << endl
				<< " -f file: read input from file instead of stdin (also --file; may be repeated)" << endl
				<< " -v     : Display version information" << endl
                << " --threshold X : set summarize collapse threshold percentage, 1-100 (default: 50)" << endl
                << " --max-depth-ipv4 X : summarize to a max cidr of X for IPv4 only" << endl
                << " --max-depth-ipv6 X : summarize to a max cidr of X for IPv6 only" << endl
                << " --count       : count unique IPs and print total instead of outputting them" << endl
                << " --shuffle     : output IPs in random order (deduplicates like --sort)" << endl
                << " --port N      : append port suffix to each IP (1.2.3.4:N or [::1]:N)" << endl
                << " --private     : only output private/RFC1918 addresses" << endl
                << " --public      : only output non-private addresses" << endl
                << " --hosts       : exclude network and broadcast (first/last) addresses from ranges" << endl
                << " --mac-to-ipv6 : convert MAC addresses to EUI-64 link-local IPv6 (fe80::)" << endl
                << " --ipv6-to-mac : extract MAC from EUI-64 MAC-derived IPv6 addresses" << endl
                << " --anyway      : bypass the >1000000 address guard rail" << endl
                << " --exclude R   : exclude range R from output (may be repeated)" << endl
                << " --cidr-list   : decompose range into exact covering CIDR blocks" << endl
                << " --hex-be      : output IPs as big-endian hex (also --hex)" << endl
                << " --hex-le      : output IPs as little-endian hex" << endl
                << " --to-mapped   : output IPv4 addresses as ::ffff:x.x.x.x (IPv4-mapped IPv6)" << endl
                << " --arpa        : output IPs as reverse-DNS zone names (in-addr.arpa / ip6.arpa)" << endl
                << " --subnet-info : for each input subnet, print TSV row: Network/Broadcast/Mask/Wildcard/Prefix/FirstHost/LastHost/Total/Usable" << endl
                << " --check IP    : check if IP or subnet is within/overlaps input ranges (may repeat; also --check-file FILE)" << endl
                << " --diff FILE   : output CIDRs from input ranges NOT covered by ranges in FILE" << endl
                << "Hex input accepted: 0xAABBCCDD  \\xAA\\xBB...  %AA%BB...  0b<32bits>  AABBCCDD" << endl

                << endl
                << "IP/Range Syntax:" << endl
                << " 192.168.1.1" << endl
                << " 192.168.1.1-192.168.1.255" << endl
                << " 192.168.1.1/24" << endl
                << " 192.168.1.1/C" << endl
                << " '192.168.1.1 255.255.255.0'" << endl
                << " 192.168.1.5-15" << endl
				<< flush;

			return 0;
		}

		// should fail to hit if all flags
		input_files.push_back(argv[i]);
	}


    ipbtree *summary_tree = new ipbtree;
    ipbtree *summary_tree6 = new ipbtree;

    bool found_an_ipv4 = false;
    bool found_an_ipv6 = false;

	bool mode_use_tree = mode_summarize || mode_sort || mode_shuffle || mode_count || mode_cidr_list;
	// Modes whose output is compact (CIDRs or a single count) never expand the
	// range to a per-address list, so the EXPAND_LIMIT guard does not apply to them.
	// (sort/shuffle still expand their output, so they remain subject to the limit.)
	bool mode_compact_output = mode_summarize || mode_count || mode_cidr_list;
	string port_str = (port_number >= 0) ? to_string(port_number) : "";

	auto process_stream = [&](std::istream& stream) {
	while (getline(stream, input)) {

        range_type = 0;

		// strip Windows-style \r in case input came from a CRLF file or pipe
		if (!input.empty() && input.back() == '\r') {
			input.pop_back();
		}

        // try to parse hex input formats and normalize to dotted/colon notation
        {
            uint8_t hbytes[16];
            int hlen = parse_hex_ip(input, hbytes);
            if (hlen == 4) {
                struct sockaddr_in hsa;
                memcpy(&hsa.sin_addr.s_addr, hbytes, 4);
                char hbuf[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &hsa.sin_addr, hbuf, INET_ADDRSTRLEN);
                input = string(hbuf);
            } else if (hlen == 16) {
                struct sockaddr_in6 hsa6;
                memcpy(hsa6.sin6_addr.s6_addr, hbytes, 16);
                char hbuf[INET6_ADDRSTRLEN];
                inet_ntop(AF_INET6, &hsa6.sin6_addr, hbuf, INET6_ADDRSTRLEN);
                input = string(hbuf);
            }
        }

        if (mode_mac_to_ipv6) {
            uint8_t mac[6];
            if (parse_mac(input, mac))
                cout << mac_to_linklocal(mac) << endl << flush;
            continue;
        }

        if (mode_ipv6_to_mac) {
            struct sockaddr_in6 sa6;
            memset(&sa6, 0, sizeof(sa6));
            if (inet_pton(AF_INET6, input.c_str(), &sa6.sin6_addr)) {
                uint8_t mac[6];
                if (eui64_to_mac(sa6, mac))
                    cout << format_mac(mac) << endl << flush;
            }
            continue;
        }

        // ok, first off, if its just a singleton IP rather than an expandable range, we want to just spit it out.

        if (mode_ipv4) {
            if ((!mode_use_old_aton_behavior && inet_pton(AF_INET, input.c_str(), &(scratch.sin_addr))) || (mode_use_old_aton_behavior && inet_aton(input.c_str(), &(scratch.sin_addr)))) {

                if (mode_private && !is_private_ipv4(SwapBytes(scratch.sin_addr.s_addr))) continue;
                if (mode_public && is_private_ipv4(SwapBytes(scratch.sin_addr.s_addr))) continue;

                if (mode_subnet_info) {
                    uint32_t h = SwapBytes(scratch.sin_addr.s_addr);
                    print_subnet_info_v4(h, h); found_an_ipv4 = true; continue;
                }
                if (mode_check) {
                    uint32_t h = SwapBytes(scratch.sin_addr.s_addr);
                    char nbuf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &scratch.sin_addr, nbuf, INET_ADDRSTRLEN);
                    input_ranges.push_back({4, h, h, {}, {}, string(nbuf)});
                    found_an_ipv4 = true; continue;
                }
                if (mode_diff) {
                    uint32_t h = SwapBytes(scratch.sin_addr.s_addr);
                    summary_tree->insert_range_v4(h, h);
                    found_an_ipv4 = true; continue;
                }

                if (mode_use_tree) {
                    summary_tree->insert(input);
                } else {
                    if (mode_hex_be || mode_hex_le) {
                        cout << ipv4_to_hex(SwapBytes(scratch.sin_addr.s_addr), mode_hex_be) << endl << flush;
                    } else if (mode_to_mapped) {
                        cout << ipv4_to_mapped_string(scratch) << endl << flush;
                    } else if (mode_arpa) {
                        cout << ipv4_to_arpa(SwapBytes(scratch.sin_addr.s_addr)) << endl << flush;
                    } else if (display_network_object) {
						cout << "network-object host " << input << endl << flush;
					} else if (port_number >= 0) {
						cout << input << ":" << port_str << endl << flush;
					} else {
						cout << input << endl << flush;
					}
                }

                found_an_ipv4 = true;

                continue;
            }
        }

        if (mode_ipv6) {
            if (inet_pton(AF_INET6, input.c_str(), &(scratch6.sin6_addr))) {

                // auto-detect v4mapped: treat ::ffff:x.x.x.x as IPv4 if mode_ipv4 is on
                if (is_v4mapped_ipv6(scratch6) && mode_ipv4) {
                    struct sockaddr_in sa4;
                    memset(&sa4, 0, sizeof(sa4));
                    memcpy(&sa4.sin_addr.s_addr, &scratch6.sin6_addr.s6_addr[12], 4);
                    if (mode_private && !is_private_ipv4(SwapBytes(sa4.sin_addr.s_addr))) continue;
                    if (mode_public && is_private_ipv4(SwapBytes(sa4.sin_addr.s_addr))) continue;
                    if (mode_subnet_info) {
                        uint32_t h = SwapBytes(sa4.sin_addr.s_addr);
                        print_subnet_info_v4(h, h); found_an_ipv4 = true; continue;
                    }
                    if (mode_check) {
                        uint32_t h = SwapBytes(sa4.sin_addr.s_addr);
                        input_ranges.push_back({4, h, h, {}, {}, range_pretty_v4(h, h)});
                        found_an_ipv4 = true; continue;
                    }
                    if (mode_diff) {
                        uint32_t h = SwapBytes(sa4.sin_addr.s_addr);
                        summary_tree->insert_range_v4(h, h);
                        found_an_ipv4 = true; continue;
                    }
                    if (mode_use_tree) {
                        summary_tree->insert(inet_ntoa(sa4.sin_addr));
                    } else {
                        if (mode_hex_be || mode_hex_le) {
                            cout << ipv4_to_hex(SwapBytes(sa4.sin_addr.s_addr), mode_hex_be) << endl << flush;
                        } else if (mode_to_mapped) {
                            cout << input << endl << flush;
                        } else if (mode_arpa) {
                            cout << ipv4_to_arpa(SwapBytes(sa4.sin_addr.s_addr)) << endl << flush;
                        } else {
                            cout << inet_ntoa(sa4.sin_addr) << endl << flush;
                        }
                    }
                    found_an_ipv4 = true;
                    continue;
                }

                if (mode_private && !is_private_ipv6(scratch6)) continue;
                if (mode_public && is_private_ipv6(scratch6)) continue;

                if (mode_subnet_info) {
                    print_subnet_info_v6(scratch6, scratch6); found_an_ipv6 = true; continue;
                }
                if (mode_check) {
                    InputRange ir; ir.family = 6; ir.s6 = scratch6; ir.e6 = scratch6;
                    ir.pretty = range_pretty_v6(scratch6, scratch6);
                    input_ranges.push_back(ir); found_an_ipv6 = true; continue;
                }
                if (mode_diff) {
                    summary_tree6->insert_range_v6(scratch6, scratch6);
                    found_an_ipv6 = true; continue;
                }

                if (mode_use_tree) {
                    // Use the range-insert (sentinel) representation for consistency with
                    // CIDR/range inserts, so count/summarize/cidr-list see one tree shape.
                    summary_tree6->insert_range_v6(scratch6, scratch6);
                } else {
                    if (mode_hex_be || mode_hex_le) {
                        cout << ipv6_to_hex(scratch6.sin6_addr.s6_addr, mode_hex_be) << endl << flush;
                    } else if (mode_arpa) {
                        cout << ipv6_to_arpa(scratch6.sin6_addr.s6_addr) << endl << flush;
                    } else if (display_network_object) {
						cout << "network-object host " << input << endl << flush;
					} else if (port_number >= 0) {
						cout << "[" << input << "]:" << port_str << endl << flush;
					} else {
						cout << input << endl << flush;
					}
                }

                found_an_ipv6 = true;
                continue;
            }
        }

        // we aren't actually using this here but the function demands..
        string prettystring = "";

        // ok - we actually want to use w1,w2,w3 contrary to prior beliefs, so lets just split input into them
        istringstream iss(input);


        // unlike 'ips', this is just going to be the first three ' ' delineated words on the line
        string w1 = "";
        string w2 = "";
        string w3 = "";

        getline(iss, w1, ' ');
        getline(iss, w2, ' ');
        getline(iss, w3, ' ');

        if (string_to_ipv4_range(mode_use_old_aton_behavior, w1, w2, w3, prettystring, ipv4_range_start, ipv4_range_end) == 0) {
            range_type = 4;
        }
        else {
            if (string_to_ipv6_range(w1, w2, w3, prettystring, ipv6_range_start, ipv6_range_end) == 0) {
                range_type = 6;
                // auto-detect: if both endpoints are v4mapped, convert to IPv4 range
                if (mode_ipv4 && is_v4mapped_ipv6(ipv6_range_start) && is_v4mapped_ipv6(ipv6_range_end)) {
                    memcpy(&ipv4_range_start.sin_addr.s_addr, &ipv6_range_start.sin6_addr.s6_addr[12], 4);
                    memcpy(&ipv4_range_end.sin_addr.s_addr, &ipv6_range_end.sin6_addr.s6_addr[12], 4);
                    range_type = 4;
                }
            }
        }

        // we didn't detect any ranges, so no error
        if (range_type == 4  && mode_ipv4) {

            unsigned int istart = SwapBytes(ipv4_range_start.sin_addr.s_addr);
            unsigned int iend   = SwapBytes(ipv4_range_end.sin_addr.s_addr);
            unsigned long long size = (iend >= istart) ? (unsigned long long)(iend - istart) + 1 : 0;
            if (size > EXPAND_LIMIT && !mode_anyway && !mode_subnet_info && !mode_check && !mode_diff && !mode_compact_output) {
                cerr << "xips: \"" << prettystring << "\" expands to " << size
                     << " addresses (limit " << EXPAND_LIMIT << "). Use --anyway to proceed." << endl;
                continue;
            }

            if (mode_subnet_info) {
                print_subnet_info_v4(istart, iend); found_an_ipv4 = true; continue;
            }
            if (mode_check) {
                input_ranges.push_back({4, istart, iend, {}, {}, range_pretty_v4(istart, iend)});
                found_an_ipv4 = true; continue;
            }
            if (mode_diff) {
                summary_tree->insert_range_v4(istart, iend);
                found_an_ipv4 = true; continue;
            }
            if (mode_use_tree
                && !mode_private && !mode_public && !mode_hosts && exclude_v4.empty()) {
                summary_tree->insert_range_v4(istart, iend);
                found_an_ipv4 = true; continue;
            }
            ipv4_range_to_list(ipv4_range_start, ipv4_range_end, mode_use_tree, summary_tree, display_network_object, mode_private, mode_public, port_str, mode_hosts, exclude_v4, mode_hex_be, mode_hex_le, mode_to_mapped, mode_arpa);
            found_an_ipv4 = true;
            continue;
        }

        if (range_type == 6 && mode_ipv6) {

            if (!mode_anyway && !mode_subnet_info && !mode_check && !mode_diff && !mode_compact_output && ipv6_range_exceeds_limit(ipv6_range_start, ipv6_range_end, EXPAND_LIMIT)) {
                cerr << "xips: \"" << prettystring << "\" exceeds " << EXPAND_LIMIT
                     << " addresses. Use --anyway to proceed." << endl;
                continue;
            }

            if (mode_subnet_info) {
                print_subnet_info_v6(ipv6_range_start, ipv6_range_end); found_an_ipv6 = true; continue;
            }
            if (mode_check) {
                InputRange ir; ir.family = 6; ir.s6 = ipv6_range_start; ir.e6 = ipv6_range_end;
                ir.pretty = range_pretty_v6(ipv6_range_start, ipv6_range_end);
                input_ranges.push_back(ir); found_an_ipv6 = true; continue;
            }
            if (mode_diff) {
                summary_tree6->insert_range_v6(ipv6_range_start, ipv6_range_end);
                found_an_ipv6 = true; continue;
            }
            if (mode_use_tree
                && !mode_private && !mode_public && !mode_hosts && exclude_v6.empty()) {
                summary_tree6->insert_range_v6(ipv6_range_start, ipv6_range_end);
                found_an_ipv6 = true; continue;
            }
            ipv6_range_to_list(ipv6_range_start, ipv6_range_end, mode_use_tree, summary_tree6, display_network_object, mode_private, mode_public, port_str, mode_hosts, exclude_v6, mode_hex_be, mode_hex_le, mode_arpa);
            found_an_ipv6 = true;
            continue;
        }

	}
	}; // end process_stream

	if (mode_subnet_info) {
		cout << "Network\tBroadcast\tMask\tWildcard\t/Prefix\tFirst Host\tLast Host\tTotal IPs\tUsable Hosts\n";
	}

	if (input_files.empty()) {
		process_stream(cin);
	} else {
		for (const std::string& filename : input_files) {
			std::ifstream f(filename);
			if (!f.is_open()) {
				cerr << "xips: cannot open file: " << filename << endl;
				continue;
			}
			process_stream(f);
		}
	}

	if (mode_check) {
		// Load check specs from --check-file
		if (!check_file.empty()) {
			std::ifstream cf(check_file);
			if (!cf.is_open()) {
				cerr << "xips: --check-file: cannot open: " << check_file << endl;
			} else {
				string line;
				while (getline(cf, line)) {
					if (!line.empty() && line.back() == '\r') line.pop_back();
					if (!line.empty()) check_specs.push_back(line);
				}
			}
		}
		// Process each check spec
		for (const string& spec : check_specs) {
			struct sockaddr_in cs4, ce4;
			struct sockaddr_in6 cs6, ce6;
			string cp;
			// Try plain single IP first (inet_pton), then full range syntax
			if (inet_pton(AF_INET, spec.c_str(), &cs4.sin_addr)) {
				uint32_t h = SwapBytes(cs4.sin_addr.s_addr);
				run_check_v4(range_pretty_v4(h, h), h, h, input_ranges);
			} else if (inet_pton(AF_INET6, spec.c_str(), &cs6.sin6_addr)) {
				run_check_v6(range_pretty_v6(cs6, cs6), cs6, cs6, input_ranges);
			} else if (string_to_ipv4_range(mode_use_old_aton_behavior, spec, "", "", cp, cs4, ce4) == 0) {
				uint32_t s = SwapBytes(cs4.sin_addr.s_addr), e = SwapBytes(ce4.sin_addr.s_addr);
				run_check_v4(range_pretty_v4(s, e), s, e, input_ranges);
			} else if (string_to_ipv6_range(spec, "", "", cp, cs6, ce6) == 0) {
				run_check_v6(range_pretty_v6(cs6, ce6), cs6, ce6, input_ranges);
			} else {
				cerr << "xips: --check: cannot parse: " << spec << endl;
			}
		}
	}

    if (port_number >= 0) {
        summary_tree->setPortSuffix(port_str);
        summary_tree6->setPortSuffix(port_str);
    }

    if (mode_count) {
        unsigned __int128 count = 0;
        if (mode_ipv4 && found_an_ipv4) count += summary_tree->getCount();
        if (mode_ipv6 && found_an_ipv6) count += summary_tree6->getCount6();
        cout << u128_to_string(count) << endl;
    } else if (mode_shuffle) {
        vector<string> all_ips;
        if (mode_ipv4 && found_an_ipv4) summary_tree->collect(all_ips);
        if (mode_ipv6 && found_an_ipv6) summary_tree6->collect6(all_ips);
        mt19937 rng(random_device{}());
        shuffle(all_ips.begin(), all_ips.end(), rng);
        for (const auto& ip : all_ips) {
            cout << ip << endl;
        }
    } else if (mode_summarize) {
        if (mode_ipv4 && found_an_ipv4) {
            summary_tree->summarize(max_cidr, display_cidr, display_network_object, threshold);
        }
        if (mode_ipv6 && found_an_ipv6) {
            summary_tree6->summarize6(max_cidr_ipv6, display_cidr, display_network_object, threshold);
        }
    } else if (mode_sort) {
        if (mode_ipv4 && found_an_ipv4) {
            summary_tree->sort(display_cidr, display_network_object);
        }
        if (mode_ipv6 && found_an_ipv6) {
            summary_tree6->sort6(display_cidr, display_network_object);
        }
    } else if (mode_cidr_list) {
        if (mode_ipv4 && found_an_ipv4) {
            summary_tree->cidr_list();
        }
        if (mode_ipv6 && found_an_ipv6) {
            summary_tree6->cidr_list6();
        }
    } else if (mode_diff) {
        if (!diff_file.empty()) {
            std::ifstream df(diff_file);
            if (!df.is_open()) {
                cerr << "xips: --diff: cannot open file: " << diff_file << endl;
            } else {
                string dline;
                while (getline(df, dline)) {
                    if (!dline.empty() && dline.back() == '\r') dline.pop_back();
                    if (dline.empty()) continue;
                    struct sockaddr_in ds4, de4; struct sockaddr_in6 ds6, de6; string dp;
                    if (inet_pton(AF_INET, dline.c_str(), &ds4.sin_addr)) {
                        uint32_t h = SwapBytes(ds4.sin_addr.s_addr);
                        diff_tree->insert_range_v4(h, h);
                    } else if (inet_pton(AF_INET6, dline.c_str(), &ds6.sin6_addr)) {
                        diff_tree6->insert_range_v6(ds6, ds6);
                    } else if (string_to_ipv4_range(mode_use_old_aton_behavior, dline, "", "", dp, ds4, de4) == 0) {
                        uint32_t s = SwapBytes(ds4.sin_addr.s_addr), e = SwapBytes(de4.sin_addr.s_addr);
                        diff_tree->insert_range_v4(s, e);
                    } else if (string_to_ipv6_range(dline, "", "", dp, ds6, de6) == 0) {
                        diff_tree6->insert_range_v6(ds6, de6);
                    }
                }
            }
        }
        if (mode_ipv4 && found_an_ipv4) summary_tree->diff_output_v4(diff_tree->get_root());
        if (mode_ipv6 && found_an_ipv6) summary_tree6->diff_output_v6(diff_tree6->get_root());
    }

    return 0;
}
