/*
 * Copyright ©2026 Soham Pardeshi. All rights reserved.
 * Permission is hereby granted to students registered for University of
 * Washington CSE 333 for use solely during Summer Quarter 2026 for
 * purposes of the course. No other use, copying, distribution, or
 * modification is permitted without prior written consent. Copyrights
 * for third-party components of this work must be honored. Instructors
 * interested in reusing these course materials should contact the author.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

// The client half of the sockets API, end to end:
//
//   getaddrinfo()  hostname     -> sockaddr
//   socket()       sockaddr     -> fd (not connected to anything yet)
//   connect()      fd+sockaddr  -> TCP connection established
//   close()
//
//   ./connect attu.cs.washington.edu 22
//
// Note that socket() needs the address family, which comes from the
// lookup: you cannot create the socket until you know whether you are
// talking IPv4 or IPv6.
void Usage(char* progname);
bool LookupName(char* name, unsigned short port,
                struct sockaddr_storage* ret_addr, size_t* ret_addrlen);

int main(int argc, char** argv) {
  if (argc != 3) {
    Usage(argv[0]);
  }

  unsigned short port = 0;
  if (sscanf(argv[2], "%hu", &port) != 1) {
    Usage(argv[0]);
  }

  // sockaddr_storage is big enough for either family, so the caller
  // doesn't have to know which one the lookup will return.
  struct sockaddr_storage addr;
  size_t addrlen;
  if (!LookupName(argv[1], port, &addr, &addrlen)) {
    Usage(argv[0]);
  }

  int socket_fd = socket(addr.ss_family, SOCK_STREAM, 0);
  if (socket_fd == -1) {
    std::cerr << "socket() failed: " << strerror(errno) << std::endl;
    return EXIT_FAILURE;
  }

  int res = connect(socket_fd, reinterpret_cast<sockaddr*>(&addr), addrlen);
  if (res == -1) {
    std::cerr << "connect() failed: " << strerror(errno) << std::endl;
    close(socket_fd);
    return EXIT_FAILURE;
  }

  std::cout << "connected to " << argv[1] << ":" << port
            << " on fd " << socket_fd << std::endl;

  close(socket_fd);
  return EXIT_SUCCESS;
}

void Usage(char* progname) {
  std::cerr << "usage: " << progname << " hostname port" << std::endl;
  exit(EXIT_FAILURE);
}

bool LookupName(char* name, unsigned short port,
                struct sockaddr_storage* ret_addr, size_t* ret_addrlen) {
  struct addrinfo hints, *results;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  int retval = getaddrinfo(name, nullptr, &hints, &results);
  if (retval != 0) {
    std::cerr << "getaddrinfo failed: " << gai_strerror(retval) << std::endl;
    return false;
  }

  // getaddrinfo fills in the address but not the port, so patch it in --
  // in network byte order, and in the right struct for the family.
  if (results->ai_family == AF_INET) {
    struct sockaddr_in* v4addr =
        reinterpret_cast<sockaddr_in*>(results->ai_addr);
    v4addr->sin_port = htons(port);
  } else if (results->ai_family == AF_INET6) {
    struct sockaddr_in6* v6addr =
        reinterpret_cast<sockaddr_in6*>(results->ai_addr);
    v6addr->sin6_port = htons(port);
  } else {
    std::cerr << "getaddrinfo returned neither IPv4 nor IPv6" << std::endl;
    freeaddrinfo(results);
    return false;
  }

  // Copy the first result out before freeing the list.
  assert(results != nullptr);
  memcpy(ret_addr, results->ai_addr, results->ai_addrlen);
  *ret_addrlen = results->ai_addrlen;

  freeaddrinfo(results);
  return true;
}