+    if (m_config.transport == "tcp")
+        return connectTCP();
+    else if (m_config.transport == "unix")
+        return connectUNIX();
+    throw Error("Invalid transport type: '" + m_config.transport + "'. Should be 'tcp' or 'unix'.");
+}
+
+int STG_CLIENT::Impl::connectTCP()
+{
+    addrinfo hints;
+    memset(&hints, 0, sizeof(addrinfo));
+
+    hints.ai_family = AF_INET;       /* Allow IPv4 */
+    hints.ai_socktype = SOCK_STREAM; /* Stream socket */
+    hints.ai_flags = 0;     /* For wildcard IP address */
+    hints.ai_protocol = 0;           /* Any protocol */
+    hints.ai_canonname = NULL;
+    hints.ai_addr = NULL;
+    hints.ai_next = NULL;
+
+    addrinfo* ais = NULL;
+    int res = getaddrinfo(m_config.address.c_str(), m_config.portStr.c_str(), &hints, &ais);
+    if (res != 0)
+        throw Error("Error resolvin address '" + m_config.address + "': " + gai_strerror(res));
+
+    for (addrinfo* ai = ais; ai != NULL; ai = ai->ai_next)
+    {
+        int fd = socket(AF_INET, SOCK_STREAM, 0);
+        if (fd == -1)
+        {
+            Error error(std::string("Error creating TCP socket: ") + strerror(errno));
+            freeaddrinfo(ais);
+            throw error;
+        }
+        if (::connect(fd, ai->ai_addr, ai->ai_addrlen) == -1)
+        {
+            shutdown(fd, SHUT_RDWR);
+            close(fd);
+            // TODO: log it.
+            continue;
+        }
+        freeaddrinfo(ais);
+        return fd;
+    }
+
+    freeaddrinfo(ais);
+
+    throw Error("Failed to resolve '" + m_config.address);
+};
+
+int STG_CLIENT::Impl::connectUNIX()
+{
+    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (fd == -1)
+        throw Error(std::string("Error creating UNIX socket: ") + strerror(errno));
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, m_config.address.c_str(), m_config.address.length());
+    if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
+    {
+        Error error(std::string("Error binding UNIX socket: ") + strerror(errno));
+        shutdown(fd, SHUT_RDWR);
+        close(fd);
+        throw error;
+    }
+    return fd;
+}
+
+bool STG_CLIENT::Impl::read()
+{
+    static std::vector<char> buffer(1024);
+    ssize_t res = ::read(m_sock, buffer.data(), buffer.size());
+    if (res < 0)
+    {
+        //m_logger("Failed to read data from '" + m_remote + "': " + strerror(errno));
+        return false;
+    }
+    m_lastActivity = time(NULL);
+    if (res == 0)
+    {
+        if (!m_parser.done())
+        {
+            //m_logger("Failed to read data from '" + m_remote + "': " + strerror(errno));
+            return false;
+        }
+        return process();
+    }
+    return m_parser.append(buffer.data(), res);
+}
+
+bool STG_CLIENT::Impl::tick()
+{
+    time_t now = time(NULL);
+    if (difftime(now, m_lastActivity) > CONN_TIMEOUT)
+    {
+        //m_logger("Connection to " + m_remote + " timed out.");
+        return false;
+    }
+    if (difftime(now, m_lastPing) > PING_TIMEOUT)
+        sendPing();
+    return true;
+}
+
+bool STG_CLIENT::Impl::process()
+{
+    switch (m_parser.packet())
+    {
+        case PING:
+            return processPing();
+        case PONG:
+            return processPong();
+        case DATA:
+            return processData();
+    }
+    //m_logger("Received invalid packet type: " + m_parser.packetStr());
+    return false;
+}
+
+bool STG_CLIENT::Impl::processPing()
+{
+    return sendPong();
+}
+
+bool STG_CLIENT::Impl::processPong()
+{
+    m_lastActivity = time(NULL);
+    return true;
+}
+
+bool STG_CLIENT::Impl::processData()
+{
+    RESULT result;
+    for (PairsParser::Pairs::const_iterator it = m_parser.reply().begin(); it != m_parser.reply().end(); ++it)
+        result.reply.push_back(std::make_pair(it->first, it->second));
+    for (PairsParser::Pairs::const_iterator it = m_parser.modify().begin(); it != m_parser.modify().end(); ++it)
+        result.modify.push_back(std::make_pair(it->first, it->second));
+    return m_callback(m_data, result);
+}
+
+bool STG_CLIENT::Impl::sendPing()
+{
+    PacketGen gen("ping");
+
+    m_lastPing = time(NULL);
+
+    return generate(gen, &STG_CLIENT::Impl::write, this);
+}
+
+bool STG_CLIENT::Impl::sendPong()
+{
+    PacketGen gen("pong");
+
+    m_lastPing = time(NULL);
+
+    return generate(gen, &STG_CLIENT::Impl::write, this);
+}
+
+bool STG_CLIENT::Impl::write(void* data, const char* buf, size_t size)
+{
+    STG_CLIENT::Impl& impl = *static_cast<STG_CLIENT::Impl*>(data);
+    while (size > 0)
+    {
+        ssize_t res = ::write(impl.m_sock, buf, size);
+        if (res < 0)
+        {
+            //conn.m_logger("Failed to write pong to '" + conn.m_remote + "': " + strerror(errno));
+            return false;