I started working on tlslookieloo after failing to find a utility that I can use to basically MITM a client-server application I was testing at work. I looked at sslsniff and Cisco Talos’ Mutiny Fuzzing Framework and Decept Proxy to see if it provides what I need, or modify it for my own needs. In the end I decided to build my own from scratch. I did this because the parts I need are buried too deep under the functionalities those 2 tools provides; that in the end I’ll end up with a completely different beast anyway.
One of the humps I had to go over is how do I test my code that acts on the return code of POSIX and OpenSSL APIs that I’m using. OpenSSL s_client, and s_server is good for manual testing; but, I can’t feed that on circleci (note to self: Get the project testing there). Ok, maybe I could; but, that means waiting 5 actual seconds to trigger a timeout and the other hurdles that will bring up so I can use ctest as my primary test mechanism. The other alternative is to create an abstract class, and a corresponding concrete class, to wrap the C API functions in. However, I find code looking like this ugly
// CAUTION: Untested code
class AbstractWrapper
{
public:
virtual void mySelect(int, fd_set *, fd_set *, fd_set *, struct timeval *) = 0;
};
class ConcreteWrapper
{
public:
void select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
{
return ::select(nfds, readfds, writefds, exceptfds, timeout);
}
};
class MyObject
{
public:
MyObject() : selectWrapper(ConcreteWrapper)
{}
MyObject(AbstractWrapper &replacement) :
selectWrapper(replacement)
{}
bool useSelect()
{
....
if(selectWrapper.select(...))
doSomething();
else
squeekAboutSelect();
}
private:
AbstractWrapper selectWrapper;
}
Gah, all that CPU power that’ll be consumed by the optimization phase for the sake of unit testing. Don’t even get me started on the extra brain power to keep track of all the wrapper classes this project will end up with.
So, I decided that I will provide my own definition of the C APIs in unit testing instead. That way, I can keep all the “boilerplate” code in one place and there’s no need to deal with keeping track of wrapper classes. One declaration to rule them all… and in the linking bind them.
This technique borrows from the concept of Mock objects. The difference this take advantage of resolving function definitions at link time by defining our own definition that we can manipulate to our liking.
Take for example testing for timing out while waiting for a socket to be readable. I have a function SocketInfo::waitForReading(). This function uses the select() POSIX API call that needs to be mocked so I can verify the correct parameters are provided–we don’t want to accidentally wait for the socket to be writable, and control the return state. In this particular case, the socket’s file descriptor is set in the readfds parameter and 1 is returned simulating what select() would do. The following code accomplishes the select() mock in C++ 17
#include <functional>
#include <sys/select.h> // Get the select declaration
std::function<int(int, fd_set *, fd_set *, fd_set *, fd_set *, struct timeval *)> selectFunc;
extern "C"
{
int select(int nfds, fd_set *readFds, fd_set *writeFds, fd_set *exceptFds, struct timeval *timeout)
{
return selectFunc(nfds, readFds, writeFds, exceptFds, timeout);
}
The selectFunc variable will be set in the test code for the select mock to run. The select() definition is what SocketInfo::waitForReading() will be linked against in the unit test executable that’s using Googletest. Below is the code that tests timeout handling.
TEST(SocketInfo, waitForReadingTimeout)
{
SocketInfo s;
s.setSocket(4);
selectFunc =
[](int, fd_set *readFds, fd_set *, fd_set *, struct timeval *)->int {
EXPECT_TRUE(FD_ISSET(4, readFds));
return 0;
};
EXPECT_FALSE(s.waitForReading());
}
As you can see in the code, there’s a verification that the socket FD was set for the readFds parameter, and the select function will return 0. Since this is a lambda function we can use Googletest’s assertion macros.
Here is another test case to verify that the timeout being set is not driven by the default timeout value of the SocketInfo class.
TEST(SocketInfo, waitForReadingSetTimeout) // NOLINT
{
SocketInfo s;
s.setSocket(10);
s.setTimeout(100);
selectFunc =
[](int, fd_set *, fd_set *, fd_set *, struct timeval *timeout)->int {
EXPECT_NE(timeout, nullptr);
if(timeout)
EXPECT_EQ(100, timeout->tv_sec);
else
ADD_FAILURE() << "timeout param is nullptr";
return 1;
};
s.waitForReading();
}
Ideally, timeout being NULL should be a fatal assertion; however, Googletest ASSERT_* macros can only be used in functions returning void. See commit f8d3bc for all the code that adds the test cases for the SocketInfo::waitFor* functions. You can also look at tests/unit/socketinfo.cpp for other C APIs that are being mocked.
Although this post used C++17 code, this technique is not limited to C++. You can use this in C by changing the std::function<> by declaring a C function pointer.