# 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 ) ) ;
std : : cout < < conv_addr < < std : : endl ;
abort ( ) ;
ret_val . push_back ( conv_addr ) ;
ret_val . push_back ( port ) ;
}
return ret_val ;
}
}