Dusting off the Tool Box

Posted on May 14, 2025 by Michael Keane Galloway
Tags: C, Unit Testing

I’ve often had the idea of having a git repository to just store odds and ends; tools that I’ve made; scripts etc. While setting up my latest Linux install, I rediscovered my tool_box git repository. I’ve mainly started using it as a way to share my new neovim configuration between work and home. That’s said, I found an interesting tool that I left in there back in 2017.

As a bit of back story, 2017 was the year that LoopNet fully integrated with CoStar’s database. There were quite a few things in motion, to get that task down, and one of the items that I was responsible for interacted with a database table that stored IPV4 addresses as unsigned 32 bit integers. I had recently read the K&R C book, so I thought it would be fun to write a quick tool in C to convert between the typical IPV4 string format and an unsigned integer. The following is the tool that I wrote.

Anatomy of iptool

In my iptool directory of the tool_box, I had the following files:

ipt.h
ipt.c
ipt.test.c
ipv4inttostr.c
ipv4strtoint.c
makefile

We have a header file to specify all of the functions within the tool, a C file to house the main functionality of the tool, unit tests in a .test.c file, a C file for each conversion function, and a make file to be able to build/test the tool. The first thing that I did was when I rediscovered this was to build the tool and run a couple of know IP addresses through the resulting binary. Everything seemed to be in working order. I then tried to run the unit tests, and I think my current version of Linux is missing some .so files for the unit testing framework.

The main C file

For fun, and since I’m not really going to switch my tool_box to public, I thought I’d start listing out some of the code and discussing what I see in it about 7 years after writing it. Starting of course with ipt.c where we have our main function.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "ipt.h"

void usage() {
    printf(
        "ipt <option> <value>\n"
        " -c IP address to decimal value.\n"
        " -f Decimal value to IP address.\n"
    );
}

void main(int argc, char** argv) {
    char* value = NULL;
    char resBuffer[__IP_MIN_BUFFER_LENGTH__] = "";

    int option = getopt(argc, argv, "c:f:");
    switch(option) {
        case 'c':
            value = optarg;
            printf("%u\n", ipv4strtoint(value, strlen(value)));
            break;
        case 'f':
            value = optarg;
            ipv4inttostr(strtoul(value, NULL, 0), resBuffer, __IP_MIN_BUFFER_LENGTH__);
            printf("%s\n", resBuffer);
            break;
        default:
            usage();
    }
}

One of the things that I really like about this code is that it has a simple command line switching approach. I’m using getopt. There’s only really two switches -c gets you the IP to decimal conversion, and -f does the opposite.

It looks like I had a buffer length constant that I was using to manage the buffer for the IP address. It was probably done to make sure that I’m rationally handling memory, but I’m not too certain that I’m handling this in a sufficient way. For example, I’m calling strlen and stttoul without using the buffer length to limit the examination of the buffer value to a known safe buffer size. That could cause bugs or en exploitable bug.

IP to Int

The following code is used to convert an IPv4 address to an integer. It does this by building by loading a substring from the current position within the string to the next . in the IP address. Then that temporary string is converted to an integer, shifted for the appropriate bit length for the octet, and bitwise or’d into the resulting unsigned int.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "ipt.h"

#define __TEMP_BUFF_SIZE__ 4

unsigned int ipv4strtoint(const char* str, int length) {
    unsigned int result = 0;
    int shift = 24;
    char temp[__TEMP_BUFF_SIZE__] = "";

    assert(NULL != str);
    assert(__IP_MIN_STR_LENGTH__ <= length);

    for(int i = 0; i < length && shift >= 0; i++, shift -= 8) {
        for(int k = 0; i < length && k < __TEMP_BUFF_SIZE__ && str[i] != '.'; i++, k++) {
            temp[k] = str[i];
        }

        result |= atoi(temp) << shift;

        memset(temp, 0, sizeof(temp));//this could get optimized out...
    }


    return result;
}

Looking back on this code, I’m wondering why I used i and k as counter variables instead of i and j? My initial guess is that the i and j might have looked too similar in my editor at the time. That counter variable issue also gave me the impression that I was building out the split of the input string. I’m not much of a C programmer, but looking at this, I’m left wondering if there’s a standard library implementation string split that I could have used to simplify my work.

Then for that comment, I believe there was some worry that zeroing out an array would get optimized out. I think I had heard about a class of security bugs in C where that happened. In retrospect, it’s kind of silly to worry about this in a simple tool that I built. I don’t think I ever compiled this with optimization flags, so this would have never happened. So 7 plus years later, I have found a liar’s comment.

Int to IP

The following function builds a string representation of an IPv4 address from an unsigned 32 bit integer. It does this by extracting each octet using a bitwise and between a mask and the unsigned int storing the IP address. After the octet is extracted the mask is shifted to get the next octet. The octet is then written to the output buffer using snprintf. The size of the octet is used to determine how to adjust the string pointer and length of allowed characters to properly use snprintf.

#include <stdio.h>
#include <assert.h>

#include "ipt.h"

void ipv4inttostr(unsigned int ip, char* str, int length) {
    assert(NULL != str);
    assert(__IP_MIN_BUFFER_LENGTH__ <= length);

    unsigned int mask = 0xff000000;
    int shift = 24;

    while(shift >= 0) {
        unsigned char b = (mask & ip) >> shift;
        mask = mask >> 8;
        shift -= 8;

        if (shift != -8) {
            snprintf(str, length, "%u.", b);
        }
        else {
            snprintf(str, length, "%u", b);
        }

        int adj = 0;

        if (b >= 100) {
            adj = 4;
        }
        else if (b >= 10) {
            adj = 3;
        }
        else {
            adj = 2;
        }

        str += adj;
        length -= adj;
    }
}

Overall I really like this function. Years later, I can still figure out what it does. The only part that took me a little bit of effort to understand was calculating the adjustment for the str pointer and length.

The Unit Tests

The following code block contains the unit tests that I wrote for this project. I know it’s likely overkill to write unit tests for such a small personal project, but I remember learning a lot about how tests work in C. I had never had to use a unit testing library for C while in school, and I thought it would be a neat learning experience.

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

#include "ipt.h"

static void ipv4inttostr_NULL_expectFailure(void **state) {
    (void) state;

    expect_assert_failure(ipv4inttostr(1345678, NULL, 0));
}

static void ipv4inttostr_emptyStr_expectFailure(void **state) {
    (void) state;

    expect_assert_failure(ipv4inttostr(1345678, "", 0));
}

static void ipv4inttostr_0_expect0s(void **state) {
    (void) state;

    char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
    char expected[] = "0.0.0.0";

    ipv4inttostr(0, buffer, __IP_MIN_BUFFER_LENGTH__);

    assert_string_equal(buffer, expected);
}

static void ipv4inttostr_dec_expected(void **state) {
    (void) state;

    char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
    char expected[] = "2.119.4.14";

    ipv4inttostr(41354254, buffer, __IP_MIN_BUFFER_LENGTH__);

    assert_string_equal(buffer, expected);
}

static void ipv4strtoint_NULL_expectFailure(void **state) {
    (void) state;

    expect_assert_failure(ipv4strtoint(NULL, 0));
}

static void ipv4strtoint_empty_expectFailure(void **state) {
    (void) state;

    expect_assert_failure(ipv4strtoint("", 0));
}

static void ipv4strtoint_home_dec(void **state) {
    (void) state;

    int expected = 2130706433;

    assert_int_equal(ipv4strtoint("127.0.0.1", 9), expected);
}

static void ipv4strtoint_loopback_dec(void **state) {
    (void) state;

    int expected = 0;

    assert_int_equal(ipv4strtoint("0.0.0.0", 8), expected);
}

static void ipv4strtoint_router_dec(void **state) {
    (void) state;

    unsigned int expected = 3232235777;
    unsigned int result = ipv4strtoint("192.168.1.1", 12);

    assert_true(expected == result);
}

static void crossconfirm_home_passes(void **state) {
    (void) state;

    char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
    char expected[] = "127.0.0.1";

    unsigned int intermediate = ipv4strtoint(expected, 9);
    ipv4inttostr(intermediate, buffer, __IP_MIN_BUFFER_LENGTH__);

    assert_string_equal(expected, buffer);
}

static void crossconfirm_router_passes(void **state) {
    (void) state;

    char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
    char expected[] = "192.168.1.1";

    unsigned int intermediate = ipv4strtoint(expected, 12);
    ipv4inttostr(intermediate, buffer, __IP_MIN_BUFFER_LENGTH__);

    assert_string_equal(expected, buffer);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(ipv4inttostr_NULL_expectFailure),
        cmocka_unit_test(ipv4inttostr_emptyStr_expectFailure),
        cmocka_unit_test(ipv4inttostr_0_expect0s),
        cmocka_unit_test(ipv4inttostr_dec_expected),
        cmocka_unit_test(ipv4strtoint_NULL_expectFailure),
        cmocka_unit_test(ipv4strtoint_empty_expectFailure),
        cmocka_unit_test(ipv4strtoint_home_dec),
        cmocka_unit_test(ipv4strtoint_loopback_dec),
        cmocka_unit_test(ipv4strtoint_router_dec),
        cmocka_unit_test(crossconfirm_home_passes),
        cmocka_unit_test(crossconfirm_router_passes),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}

I can definitely see the influence of how I learned to write unit tests for C#. I often have my unit tests named in the convention <function_name>_<scenario_description>_<expected_outcome>. Other than that, not much else sticks out to me about these unit tests. I’m sure there could be better coverage, but everything is pretty simple and straightforward, so this probably is more than adequate for a practice tool that I wrote.

The Future of my tool_box

I recently started stashing configurations that I want to have on multiple machines in my tool_box repository. Mainly configurations for tools like nvim. I think I’ll continue to have it house configurations and some convenience shell scripts. Anything that I would want to be public will probably get spun out into a public repository.