Skip to main content
Became Hot Network Question
Tweeted twitter.com/StackCodeReview/status/1555841121930076160
edited tags
Link
200_success
  • 144.1k
  • 22
  • 188
  • 473
Source Link
ravi
  • 439
  • 4
  • 11

Porting C-style socket to CPP class

I am porting the C-style socket to design a simple wrapper around the telnet client in CPP. The telnet protocol is accomplished by using libtelnet in C. The C-style code for this wrapper is reviewed here and here as well.

I want to follow standard practices in my CPP code, such as using smart pointers in CPP instead of raw pointers, etc. Therefore, please see my implementation below:

1. simple_telnet_client.cpp

#include <simple_telnet_client/simple_telnet_client.hpp>

namespace simple_telnet_client {

class NotImplemented : public std::logic_error {
 public:
  NotImplemented() : std::logic_error("Function not yet implemented"){};
};

TelnetClient::TelnetClient(const std::string &serverIP,
                           const std::string &serverPort, 
                           const size_t timeOut,
                           const size_t bufferSize)
    : mBufferSize(bufferSize) {
  // define our buffer
  mBuffer = static_cast<char *>(malloc(sizeof(char) * mBufferSize));

  try {
    // let's make socket
    mSockFd = makeConnection(serverIP, serverPort);
  } catch (const std::runtime_error &error) {
    // throw it to the user
    throw std::runtime_error(error);
  }

  // socket related configuarations
  configureReadWriteFd();
  configureTimeout(timeOut);

  // telnet options
  const telnet_telopt_t telnetOpts[] = {
      {     TELNET_TELOPT_ECHO, TELNET_WONT,   TELNET_DO},
      {    TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT},
      {TELNET_TELOPT_COMPRESS2, TELNET_WONT,   TELNET_DO},
      {     TELNET_TELOPT_MSSP, TELNET_WONT,   TELNET_DO},
      {                     -1,           0,           0}};

  // initialize telnet box
  mTelnet = telnet_init(telnetOpts, trampoline, 0, this);
}

int TelnetClient::makeConnection(const std::string &serverIP,
                                 const std::string &serverPort) {
  int retVal;
  int sockFd;
  struct addrinfo *addrInfo;
  struct addrinfo addrHints;

  // look up server host
  memset(&addrHints, 0, sizeof(addrHints));
  addrHints.ai_family = AF_INET;
  addrHints.ai_socktype = SOCK_STREAM;

  if ((retVal = getaddrinfo(serverIP.c_str(), serverPort.c_str(), &addrHints, &addrInfo)) != 0) {
    throw std::runtime_error("getaddrinfo() failed for " + serverIP + " with error: " + gai_strerror(retVal));
  }

  // create server socket
  if ((sockFd = socket(addrInfo->ai_family, addrInfo->ai_socktype, addrInfo->ai_protocol)) == -1) {
    throw std::runtime_error("socket() failed: " + std::string(strerror(errno)));
  }

  // connect
  if (connect(sockFd, addrInfo->ai_addr, addrInfo->ai_addrlen) == -1) {
    auto error = std::string(strerror(errno));
    close(sockFd);
    throw std::runtime_error("connect() failed: " + error);
  }

  // free address lookup info
  freeaddrinfo(addrInfo);

  return sockFd;
}

void TelnetClient::trampoline(telnet_t *telnet, 
                              telnet_event_t *event,
                              void *user_data) {
  (void)telnet;  // to get rid of unused parameter warning
  TelnetClient *me = static_cast<TelnetClient *>(user_data);
  me->telnetEvent(event);
}

void TelnetClient::telnetEvent(telnet_event_t *event) {
  switch (event->type) {
    // data received
    case TELNET_EV_DATA:
      mReceivedMsg = std::string(event->data.buffer, event->data.size);
      #if DEBUG_MODE
        std::cout << "response: [" << mReceivedMsg << "]" << std::endl;
      #endif
      break;
    // data must be sent
    case TELNET_EV_SEND:
      sendAll(event->data.buffer, event->data.size);
      break;
    // execute to enable local feature (or receipt)
    case TELNET_EV_DO:
      throw NotImplemented();
    // demand to disable local feature (or receipt)
    case TELNET_EV_DONT:
      throw NotImplemented();
    // respond to TTYPE commands
    case TELNET_EV_TTYPE:
      throw NotImplemented();
    // respond to particular subnegotiations
    case TELNET_EV_SUBNEGOTIATION:
      throw NotImplemented();
    // error
    case TELNET_EV_ERROR:
      throw std::runtime_error("telnet error: " + std::string(event->error.msg));
    default:
      // ignore
      break;
  }
}

int TelnetClient::sendAll(const char *buffer, size_t size) {
  int retVal = -1;  // default value

  // send data
  while (size > 0) {
    retVal = send(mSockFd, buffer, size, 0);
    if (retVal == 0 || errno == EINTR) {
      // try again
      continue;
    } else if (retVal == -1) {
      throw std::runtime_error("send() failed: " + std::string(strerror(errno)));
    }

    // update pointer and size to see if we've got more to send
    buffer += retVal;
    size -= retVal;
  }
  return retVal;
}

void TelnetClient::configureReadWriteFd() {
  // clear the set ahead of time
  FD_ZERO(&mReadFd);
  FD_ZERO(&mWriteFd);

  // add our descriptors to the set
  FD_SET(mSockFd, &mReadFd);
  FD_SET(mSockFd, &mWriteFd);
}

void TelnetClient::configureTimeout(const size_t seconds) {
  mTimeout.tv_sec = seconds;
  mTimeout.tv_usec = 0;
}

void TelnetClient::execute(const std::string &command) {
  int retVal = select(mSockFd + 1, NULL, &mWriteFd, NULL, &mTimeout);

  if (retVal == -1) {
    // error occurred in select()
    throw std::runtime_error("select() failed: " + std::string(strerror(errno)));
  } else if (retVal == 0) {
    throw std::runtime_error("timeout occurred");
  } else {
    // send the execute
    if (FD_ISSET(mSockFd, &mWriteFd)) {
      #if DEBUG_MODE
        // print only for debugging
        std::cout << "request: [" << command << "]" << std::endl;
      #endif

      telnet_send(mTelnet, command.c_str(), command.length());
    }
  }
}

std::string TelnetClient::response() {
  // erase previously received msg by setting it to an empty string
  mReceivedMsg.clear();

  int retVal = select(mSockFd + 1, &mReadFd, NULL, NULL, &mTimeout);
  if (retVal == -1) {
    // error occurred in select()
    throw std::runtime_error("select() failed: " + std::string(strerror(errno)));
  } else if (retVal == 0) {
    throw std::runtime_error("timeout occurred");
  } else {
    // response the response
    if (FD_ISSET(mSockFd, &mReadFd)) {
      if ((retVal = recv(mSockFd, mBuffer, mBufferSize, 0)) > 0) {
        telnet_recv(mTelnet, mBuffer, retVal);
      } else if (retVal == 0) {
        throw std::runtime_error("connection has been closed.");
      } else {
        throw std::runtime_error("recv() failed: " + std::string(strerror(errno)));
      }
    }
  }
  return mReceivedMsg;
}

TelnetClient::~TelnetClient() {
  if (mBuffer) {
    free(mBuffer);
    mBuffer = NULL;
  }

  // clean up
  telnet_free(mTelnet);
  close(mSockFd);
}

}  // namespace simple_telnet_client

2. simple_telnet_client.hpp

#ifndef SIMPLE_TELNET_CLIENT__TELNET_CLIENT_HPP_
#define SIMPLE_TELNET_CLIENT__TELNET_CLIENT_HPP_

#include <netdb.h>
#include <string.h>
#include <unistd.h>

#include <libtelnet.h>

#include <iostream>
#include <stdexcept>

// flag to print requests and responses in the console for debugging purposes
#define DEBUG_MODE true

namespace simple_telnet_client {
class TelnetClient {
 public:
  TelnetClient(const std::string &serverIP, 
               const std::string &serverPort,
               const size_t timeOut = 1, 
               const size_t bufferSize = 2048);

  // execute a command on the server
  void execute(const std::string &command);

  // receive the response from the server
  std::string response();

  ~TelnetClient();

 private:
  int mSockFd;
  const size_t mBufferSize;
  std::string mReceivedMsg;

  fd_set mReadFd;
  fd_set mWriteFd;
  struct timeval mTimeout;

  // todo: use std::unique_ptr?
  telnet_t *mTelnet;

  // todo: how about this?
  char *mBuffer;

  static void trampoline(telnet_t *telnet, 
                        telnet_event_t *event,
                         void *user_data);

  int makeConnection(const std::string &serverIP,
                     const std::string &serverPort);

  void telnetEvent(telnet_event_t *event);

  int sendAll(const char *mBuffer, 
              size_t size);

  void configureTimeout(const size_t seconds);

  void configureReadWriteFd();
};

}  // namespace simple_telnet_client

#endif  // SIMPLE_TELNET_CLIENT__TELNET_CLIENT_HPP_

PS: I am using C++14 and GCC 9.4 in Ubuntu 20.04 LTS.