You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
netpong/connect_code.cpp

186 lines
7.5 KiB
C++

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <cstring>
#include <cstdint>
#include "includes/connect_code.hpp"
#include "includes/numeric_base.hpp"
#include "includes/easysock.h"
#if defined(_WIN32)
#include <In6addr.h>
#include <Ws2tcpip.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
namespace connect_code {
/* Tokenizes a string, based on the given delimiter */
std::vector<std::string> tokenize_str(std::string str, std::string delim) {
std::vector<std::string> result;
/* &str[0] is used to convert an std::string to a char*. I tried using string.data(),
but that appears to return a const char*. */
char* c_str = &str[0];
char* c_delim = &delim[0];
char* tok = strtok(c_str, c_delim);
while (tok != NULL) {
result.push_back(std::string(tok));
tok = strtok(NULL, c_delim);
}
return result;
}
/* Convert an IPv4 address from decimal to dotted decimal notation */
std::string dec_to_dotted_dec(std::string addr) {
uint32_t addr_val = std::stoul(addr); /* 32 bit address */
uint8_t addr_1 = (addr_val & (0xFF << 24)) >> 24; /* First octet (Bitwise AND the address with 255.0.0.0, and shift it to the right to obtain the first octet) */
uint8_t addr_2 = (addr_val & (0xFF << 16)) >> 16;
uint8_t addr_3 = (addr_val & (0xFF << 8)) >> 8;
uint8_t addr_4 = (addr_val & 0xFF);
std::string ret_val = std::string(std::to_string(addr_1) + "." + std::to_string(addr_2) + "." + std::to_string(addr_3) + "." + std::to_string(addr_4));
return ret_val;
}
/* Convert an IPv4 address from dotted deecimal to decimal */
std::string dotted_dec_to_dec(std::string addr) {
std::vector<std::string> octets = tokenize_str(addr, ".");
uint32_t addr_val = (std::stoul(octets[0]) << 24) + (std::stoul(octets[1]) << 16) + (std::stoul(octets[2]) << 8) + (std::stoul(octets[3]));
return std::to_string(addr_val);
}
/* Expand an IPv6 address (expand '::' into ':0000:', for example).
This is done by first converting the address into a binary representation,
and then printing every character of the binary representation into a string. */
std::string expand_ip6_addr(std::string addr) {
char ip6_string[40]; // 32 characters + 7 colons
struct in6_addr* ip6_s_ptr = (struct in6_addr *)malloc(sizeof(in6_addr)); // Struct pointer, to store the binary representation of the address
inet_pton(AF_INET6, addr.data(), ip6_s_ptr); // Convert the string representation into a binary form
/* This abomination, converts the binary representation into a string.
It uses sprintf to print every byte in the binary representation into a string.
The bytes are formatted as 2-character hexadecimal values. */
sprintf(ip6_string,
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
ip6_s_ptr->s6_addr[0], ip6_s_ptr->s6_addr[1],
ip6_s_ptr->s6_addr[2], ip6_s_ptr->s6_addr[3],
ip6_s_ptr->s6_addr[4], ip6_s_ptr->s6_addr[5],
ip6_s_ptr->s6_addr[6], ip6_s_ptr->s6_addr[7],
ip6_s_ptr->s6_addr[8], ip6_s_ptr->s6_addr[9],
ip6_s_ptr->s6_addr[10], ip6_s_ptr->s6_addr[11],
ip6_s_ptr->s6_addr[12], ip6_s_ptr->s6_addr[13],
ip6_s_ptr->s6_addr[14], ip6_s_ptr->s6_addr[15]);
return std::string(ip6_string);
}
std::string encode(std::string address, std::string port) {
std::string addr_coded = "";
if (check_ip_ver(address.data()) == 4) {
/* First, convert the address into a decimal format. Then convert this decimal format
into base-32, and also convert the port number into base-32. Join these together with
a "_". */
/* I don't really have a reason to use my own function (dotted_dec_to_dec()
and dec_to_dotted_dec()), to convert the IP address from text to binary.
The inet_pton() and inet_ntop() functions can do this just fine, and also
take care of edge cases. Maybe someday, I might change this code. I could probably
repurpose the functions for something else, though. */
/* First, convert the address into a 32-bit integer (the integer is stored as a string).
Then, convert the address into base-32. */
addr_coded = dotted_dec_to_dec(address);
addr_coded = base_convert(addr_coded, 10, 32);
}
if (check_ip_ver(address.data()) == 6) {
/* First, expand the address into the full 39-character format (32 hex values + 7 colons).
Then, tokenize the string, using colons as the delimiters.
Finally, take each token in the string, and convert it from base-16 to base-32, appending a '-' as a delimiter. */
std::string addr_expanded = expand_ip6_addr(address);
std::vector<std::string> addr_tokenized = tokenize_str(addr_expanded, ":");
for (size_t i = 0; i < addr_tokenized.size()-1; i++ ) {
addr_coded += base_convert(addr_tokenized[i], 16, 32);
addr_coded += "-";
}
addr_coded += base_convert(addr_tokenized[addr_tokenized.size()-1], 16, 32); // I put this outside the loop, because I don't want a hyphen after it
/* TODO - Check if the IP address is actually converted properly, and test if the server socket is created correctly.
Also do the same for client side, and check client-server connection. */
}
/* Convert the port to hex */
std::string port_coded = base_convert(port, 10, 32);
std::string ret_val = addr_coded + "_" + port_coded;
return ret_val;
}
std::vector<std::string> decode(std::string connect_code) {
if (connect_code.find("_") == std::string::npos) {
throw std::invalid_argument("Invalid code entered."); // There must be an underscore, to separate the address part from the port part
}
int ip_ver = 0;
if (connect_code.find("-") != std::string::npos) {
ip_ver = 6; // If the string contains hyphens, it must be an IPv6 address encoding.
} else {
ip_ver = 4;
}
std::vector<std::string> result = tokenize_str(connect_code, "_"); /* Split the string into address and port */
std::string address = result[0]; /* Address (in base 32) */
std::string port = result[1]; /* Port (in base 32) */
std::vector<std::string> ret_val;
/* The IPv6 and IPv4 encodings are slightly different - I use a hyphen as a delimiter
for IPv6, while there is no delimiter for IPv4. This is why I need to check if the address
is IPv4 or IPv6. */
if (ip_ver == 4) {
/* Base 32 to base 10 - These lines convert the string to a base 10 number, and convert the result back into a string */
address = std::to_string(std::stoul(address, 0, 32));
port = std::to_string(std::stoul(port, 0, 32));
/* Convert decimal address to dotted decimal */
address = dec_to_dotted_dec(address);
/* Create a vector containing the address and the port, which will be returned */
ret_val.push_back(address);
ret_val.push_back(port);
} else {
/* IPv6 */
/* There are three main steps to decoding for IPv6:
1. Tokenize the address using the delimiter set while encoding ('-', in my case).
2. Convert each token from base-32 to base-16.
3. Join the string vector back together into a string, this time using ':' as a delimiter. This will give us our IP address. */
std::string conv_addr = ""; // Stores the final address
std::vector<std::string> address_tokenized = tokenize_str(address, "-"); // Step 1
for (size_t i = 0; i < address_tokenized.size()-1; i++) {
address_tokenized[i] = base_convert(address_tokenized[i], 32, 16); // Step 2
conv_addr += address_tokenized[i] + ":"; // Step 3
}
conv_addr += base_convert(address_tokenized[address_tokenized.size()-1], 32, 16); // Add the last token
port = std::to_string(std::stoul(port, 0, 32));
ret_val.push_back(conv_addr);
ret_val.push_back(port);
}
return ret_val;
}
}