uox3/source/network.h
2022-06-17 12:54:40 -04:00

616 lines
19 KiB
C++

#ifndef __CNETWORK_H__
#define __CNETWORK_H__
#include <mutex>
#include <ostream>
#include <cstdint>
#include <vector>
#include <list>
#include <deque>
#include <cstdlib>
#include <type_traits>
#include <utility>
#include <algorithm>
#include <stdexcept>
#include <string>
#include <charconv>
#include <array>
#if PLATFORM != WINDOWS
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <winsock2.h>
#undef min
#undef max
#endif
//=========================================================
// bytebuffer_bounds
//=========================================================
//=========================================================
//=========================================================
struct bytebuffer_bounds : public std::out_of_range {
int offset ;
int amount ;
int buffersize ;
std::string _msg ;
explicit bytebuffer_bounds(int offset, int amount, int size);
auto what() const noexcept ->const char* override;
};
//==========================================================
// Radix that are supported
// Normally part of strutil
enum class radix_t {dec=10,oct=8,hex=16,bin=2};
//=========================================================
// bytebuffer_t
//=========================================================
//=========================================================
class bytebuffer_t {
// These are compatability to "look" like a the original CPacketStream
//=============================================================
public:
auto ReserveSize(size_t len) ->void {
this->size(static_cast<int>(len),0);
}
auto WriteByte( size_t pos, std::uint8_t toWrite )->void {
this->write(static_cast<int>(pos), toWrite);
}
// Why isn't thist std::int16_t?
auto WriteShort(size_t pos, std::int32_t toWrite) ->void {
this->write(static_cast<int>(pos),static_cast<int16_t>(toWrite));
}
auto WriteLong( size_t pos, std::uint32_t toWrite ) ->void{
this->write(static_cast<int>(pos),toWrite);
}
auto WriteString( size_t pos, const std::string& toWrite, size_t len ) ->void {
this->write(static_cast<int>(pos),toWrite,static_cast<int>(len),false) ;
}
auto WriteArray( size_t pos, const std::uint8_t *toWrite, size_t len ) ->void{
this->write(static_cast<int>(pos),toWrite,static_cast<int>(len),false) ;
}
auto GetByte(size_t pos) const ->std::uint8_t {
try{
return this->read<std::uint8_t>(static_cast<int>(pos));
}
catch(...){
return 0 ;
}
}
auto GetShort(size_t pos) const ->std::int16_t {
try{
return this->read<std::int16_t>(static_cast<int>(pos));
}
catch(...) {
return 0 ;
}
}
auto GetUShort(size_t pos) const ->std::uint16_t {
try{
return this->read<std::uint16_t>(static_cast<int>(pos));
}
catch(...){
return 0 ;
}
}
auto GetLong(size_t pos) const ->std::int32_t {
try {
return this->read<std::int32_t>(static_cast<int>(pos));
}
catch(...){
return 0 ;
}
}
auto GetULong(size_t pos) const ->std::uint32_t {
try{
return this->read<std::uint32_t>(static_cast<int>(pos));
}
catch(...) {
return 0 ;
}
}
auto GetBuffer() const ->const std::uint8_t* {
return this->raw() ;
}
auto GetSize() const ->size_t {
return this->size() ;
}
// End compatability of original CPacketStream
//===============================================================
//============================================================
// Normally part of strutil, but included here for stand alone
public:
//==========================================================
// The maximum characters in a string number for conversion sake
static constexpr auto max_characters_in_number = 12 ;
//===========================================================
// Dumps a byte buffer, formatted to a provided stream.
// The entries_line indicate how many bytes to display per line.
static auto dump(std::ostream &output,const std::uint8_t *buffer, std::size_t length,radix_t radix=radix_t::hex,int entries_line = 8)->void;
private:
//==========================================================
// Convert a bool to a string
// the true_value/false_value are returned based on the bool
static auto ntos(bool value,const std::string &true_value = "true",const std::string &false_value = "false")->std::string;
//==========================================================
// Convert a number to a string, with options on radix,prefix,size,pad
// Radix indicates the radix the string will represent
// prefix indicates if the "radix" indicator will be present at the front of the string
// size is the minimum size the string must be (if the number is not large enough
// it will use the "pad" character prepended to the number
template <typename T>
static auto ntos(T value,radix_t radix=radix_t::dec,bool prefix=false,int size=0,char pad='0')->std::string {
if constexpr( std::is_integral_v<T> && !std::is_same_v<T, bool>){
// first, thing we need to convert the value to a string
std::array<char,max_characters_in_number> str ;
if (auto [pc,ec] = std::to_chars(str.data(), str.data()+str.size(), value,static_cast<int>(radix)); ec==std::errc()) {
// How many characters did this number take
auto numchars = static_cast<int>(std::distance(str.data(),pc)) ;
// what is larger, that is the size of our string
auto sizeofstring = std::max(numchars,size) ;
// where do we start adding the number into our string ?
auto index = sizeofstring - numchars ;
if (prefix) {
// We have a prefix, so we add two characters to the beginning
sizeofstring += 2;
index +=2 ;
}
auto rvalue = std::string(sizeofstring,pad);
// copy the value into the string
std::copy(str.data(),pc,rvalue.begin()+index);
// do we need our prefix?
if (prefix){
switch (static_cast<int>(radix)) {
case static_cast<int>(radix_t::dec):
// We dont add anything for decimal!
break;
case static_cast<int>(radix_t::hex):
rvalue[0]='0';
rvalue[1]='x';
break;
case static_cast<int>(radix_t::oct):
rvalue[0]='0';
rvalue[1]='o';
break;
case static_cast<int>(radix_t::bin):
rvalue[0]='0';
rvalue[1]='b';
break;
default:
break;
}
}
return rvalue ;
}
else {
// The conversion was not successful, so we return an empty string
return std::string();
}
}
}
// End of strutil inclusion
//============================================================
protected:
mutable int _index ;
std::vector<std::uint8_t> _bytedata ;
auto exceeds(int offset,int bytelength) const ->bool ;
auto exceeds(int offset,int bytelength,bool expand) ->bool ;
public:
bytebuffer_t(int size=0,int reserve=0);
auto size() const ->size_t ;
auto size(int value,std::uint8_t fill = 0) ->void ;
auto index() const ->int ;
auto index(int value) ->void ;
auto raw() const ->const std::uint8_t* ;
auto raw() -> std::uint8_t* ;
auto operator[](int index) const -> const std::uint8_t & ;
auto operator[](int index) -> std::uint8_t & ;
auto fill(std::uint8_t value, int offset,int length)->void ;
auto log(std::ostream &output,radix_t radix=radix_t::hex,int entries_line = 8)const ->void;
//=========================================================
// we need to read :integral/floating, vectors/list/strings
template <typename T>
auto read(int offset=-1,int amount=-1,bool reverse=true) const ->T {
if (offset < 0){
offset = _index ;
}
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>){
// we ignore amount for integrals and floating point
auto size = static_cast<int>(sizeof(T)) ;
if constexpr (std::is_same_v<T,bool>){
size = 1 ;
}
if (exceeds(offset,size)){
throw bytebuffer_bounds(offset,size,static_cast<int>(_bytedata.size()));
}
T value(0);
std::copy(_bytedata.data()+offset,_bytedata.data()+offset+size,reinterpret_cast<std::uint8_t*>(&value));
if (reverse && (size>1) ){
std::reverse(reinterpret_cast<std::uint8_t*>(&value),reinterpret_cast<std::uint8_t*>(&value)+size) ;
}
_index = offset + size;
return value ;
}
else if constexpr (std::is_class_v<T> ){
if constexpr (std::is_integral_v<typename T::value_type>){
// It is a supported container (hopefully) they want back!
// what is the size of the entry in the container
auto entry_size = static_cast<int>(sizeof(typename T::value_type));
if (amount <0){
// what we do is calcualate how many entries are left in the buffer
amount = (static_cast<int>(_bytedata.size()) - offset )/entry_size ;
}
auto requested_size = entry_size * amount ;
// are we exceeding that?
if (exceeds(offset, requested_size)){
throw bytebuffer_bounds(offset,requested_size,static_cast<int>(_bytedata.size()));
}
// we need to loop through and read a "character" at a time
typename T::value_type character =0 ;
T rvalue ;
for (auto i=0 ; i< amount ;++i){
std::copy(_bytedata.data()+offset + i*entry_size,_bytedata.data()+offset + (i+1)*entry_size,reinterpret_cast<std::uint8_t*>(&character)) ;
// should we "reverse it" ?
if ((entry_size >1) && reverse) {
std::reverse(reinterpret_cast<std::uint8_t*>(&character),reinterpret_cast<std::uint8_t*>(&character)+entry_size) ;
}
// If this is a string, we stop at a null terminator. Do we really want to do this?
if constexpr (std::is_same_v<std::string,T> || std::is_same_v<std::wstring,T> || std::is_same_v<std::u16string,T> || std::is_same_v<std::u32string,T>){
if (character == 0) {
break;
}
}
rvalue.push_back(character) ;
}
_index = offset+ requested_size ;
return rvalue ;
}
}
}
//=========================================================
// This reads into the buffer supplied, only for integral types ;
template<typename T>
auto read(int offset, T *value,int amount = -1,bool reverse = true) const ->void {
if (offset <0){
offset = _index ;
}
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>){
auto entry_size = static_cast<int>(sizeof(T)) ;
if constexpr (std::is_same_v<T,bool>){
entry_size = 1 ;
}
auto size = amount * entry_size ;
if (amount <0){
size = entry_size ;
amount = 1 ;
}
if (amount >0){
if (exceeds(offset,size)){
throw bytebuffer_bounds(offset,size,static_cast<int>(_bytedata.size()));
}
// We now get to read
T input ;
for (auto i=0 ; i< amount ; ++i){
std::copy(_bytedata.begin()+offset + (i*entry_size),_bytedata.begin()+offset+((i+1)*entry_size),reinterpret_cast<std::uint8_t*>(&input)) ;
if (reverse) {
std::reverse(reinterpret_cast<std::uint8_t*>(&input), reinterpret_cast<std::uint8_t*>(&input)+entry_size) ;
}
// Now put it into the data stream
std::copy(reinterpret_cast<std::uint8_t*>(&input),reinterpret_cast<std::uint8_t*>(&input)+entry_size ,value+(i*entry_size)) ;
}
_index = offset + amount * entry_size ;
}
}
}
//=========================================================
template<typename T>
auto write(int offset,const T *value,int amount=-1,bool reverse = true,bool expand = true)->bytebuffer_t&{
if (offset <0){
offset = _index ;
}
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>){
auto entry_size = static_cast<int>(sizeof(T)) ;
if constexpr (std::is_same_v<T,bool>){
entry_size = 1 ;
}
auto size = amount * entry_size ;
if (amount <0){
size = entry_size ;
amount = 1 ;
}
if (amount >0){
if (exceeds(offset,size,expand)){
throw bytebuffer_bounds(offset,size,static_cast<int>(_bytedata.size()));
}
// we need to write it
for (auto i= 0 ; i< amount ; ++i){
auto input = value[i] ;
if (reverse && (entry_size >1)){
std::reverse(reinterpret_cast<std::uint8_t*>(&input),reinterpret_cast<std::uint8_t*>(&input)+entry_size) ;
}
std::copy(reinterpret_cast<std::uint8_t*>(&input),reinterpret_cast<std::uint8_t*>(&input)+entry_size,_bytedata.begin()+offset + (i*entry_size));
}
_index = offset + (entry_size * amount);
}
return *this ; // we put this here, versue at the end, for we want a compile error if a type not caught with if constexpr. So a return in each if constexpr at the top level
}
}
//=========================================================
template<typename T>
auto write(int offset,const T &value,int amount=-1,bool reverse = true,bool expand = true)->bytebuffer_t&{
if (offset <0){
offset = _index ;
}
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>){
// we ignore amount for integrals and floating point
auto size = static_cast<int>(sizeof(T)) ;
if constexpr (std::is_same_v<T,bool>){
size = 1 ;
}
if (exceeds(offset,size,expand)){
throw bytebuffer_bounds(offset,size,static_cast<int>(_bytedata.size()));
}
// we need to write it
T temp = value ;
if (reverse) {
std::reverse(reinterpret_cast<std::uint8_t*>(&temp),reinterpret_cast<std::uint8_t*>(&temp)+size);
}
std::copy(reinterpret_cast<std::uint8_t*>(&temp),reinterpret_cast<std::uint8_t*>(&temp)+size,_bytedata.begin()+offset);
_index = offset + size ;
return *this ; // we put this here, versue at the end, for we want a compile error if a type not caught with if constexpr. So a return in each if constexpr at the top level
}
else if constexpr (std::is_class_v< T>){
if constexpr (std::is_integral_v<typename T::value_type>){
// It is a supported container (hopefully) they want back!
// what is the size of the entry in the container
auto entry_size = static_cast<int>(sizeof(typename T::value_type));
auto container_size = static_cast<int>(value.size()) ;
auto fill_size = 0 ;
auto write_size = container_size ;
if (amount <0){
amount = container_size ;
}
else {
write_size = amount ;
if (amount > container_size){
fill_size = amount - container_size ;
write_size = container_size ;
}
}
auto requested_size = (write_size + fill_size) * entry_size;
// are we exceeding that?
if (exceeds(offset, requested_size,expand)){
throw bytebuffer_bounds(offset,requested_size,static_cast<int>(_bytedata.size()));
}
// Ok, so now we get to go and do our thing
for (auto i=0 ; i< write_size;++i){
auto entry = value[i] ;
if (reverse && (entry_size > 1)) {
std::reverse(reinterpret_cast<std::uint8_t*>(&entry),reinterpret_cast<std::uint8_t*>(&entry)+entry_size) ;
}
std::copy(reinterpret_cast<std::uint8_t*>(&entry),reinterpret_cast<std::uint8_t*>(&entry)+entry_size,_bytedata.data()+offset+i*entry_size);
}
_index = offset + (write_size *entry_size);
// Now we need to do the fill if anyway
if (fill_size>0){
std::fill(_bytedata.data()+_index,_bytedata.data()+_index+(fill_size*entry_size),0);
}
_index += fill_size * entry_size ;
}
return *this ;
}
else if constexpr (std::is_array_v<T>){
if constexpr (std::is_integral_v<typename std::remove_extent<T>::type> || std::is_floating_point_v<typename std::remove_extent<T>::type>){
// It is an array!
auto entry_size = static_cast<int>(sizeof(typename std::remove_extent<T>::type));
if (amount <0){
amount = 1 ;
}
auto requested_size = amount * entry_size ;
if (exceeds(offset,requested_size,expand)){
throw bytebuffer_bounds(offset,requested_size,static_cast<int>(_bytedata.size()));
}
// We need to check and loop through if we are reversing ;
// we need to write it
for (auto i= 0 ; i< amount ; ++i){
auto input = value[i] ;
if (reverse && (entry_size >1)){
std::reverse(reinterpret_cast<std::uint8_t*>(&input),reinterpret_cast<std::uint8_t*>(&input)+entry_size) ;
}
std::copy(reinterpret_cast<std::uint8_t*>(&input),reinterpret_cast<std::uint8_t*>(&input)+entry_size,_bytedata.begin()+offset + (i*entry_size));
}
_index = offset + (entry_size * amount);
return *this ;
}
}
}
};
class socket_error : public std::runtime_error
{
private:
UI32 errorNum;
public:
socket_error( const std::string& what_arg );
socket_error( const UI32 errorNumber );
socket_error( void );
UI32 ErrorNumber( void ) const;
const char *what( void ) const throw();
};
class CPUOXBuffer
{
private:
std::vector< UI08 > packedBuffer;
bool isPacked;
UI32 packedLength;
protected:
bytebuffer_t pStream;
virtual void InternalReset( void );
public:
CPUOXBuffer();
virtual ~CPUOXBuffer();
CPUOXBuffer( CPUOXBuffer *initBuffer );
CPUOXBuffer &operator=( CPUOXBuffer &copyFrom );
UI32 Pack( void );
virtual bool ClientCanReceive( CSocket *mSock );
bytebuffer_t& GetPacketStream( void );
UI32 PackedLength( void ) const;
const UI08 * PackedPointer( void ) const;
virtual void Log( std::ofstream &outStream, bool fullHeader = true );
};
class CPInputBuffer
{
protected:
CSocket * tSock;
public:
CPInputBuffer();
CPInputBuffer( CSocket *input );
virtual ~CPInputBuffer()
{
}
virtual void Receive( void ) = 0;
virtual void Log( std::ofstream &outStream, bool fullHeader = true );
virtual bool Handle( void );
void SetSocket( CSocket *toSet );
CSocket * GetSocket( void ) const;
};
class cNetworkStuff
{
public:
cNetworkStuff();
~cNetworkStuff();
auto startup() ->void ;
void Disconnect( UOXSOCKET s );
void Disconnect( CSocket *s );
void ClearBuffers( void );
void CheckLoginMessage( void );
void CheckMessage( void );
void SockClose( void );
void setLastOn( CSocket *s );
CSocket * GetSockPtr( UOXSOCKET s );
UOXSOCKET FindNetworkPtr( CSocket *toFind );
CSocket * FirstSocket( void );
CSocket * NextSocket( void );
CSocket * PrevSocket( void );
CSocket * LastSocket( void );
bool FinishedSockets( void );
void CheckConnections( void );
void CheckMessages( void );
void Transfer( CSocket *s );
size_t PeakConnectionCount( void ) const;
void PushConn( void );
void PopConn( void );
void pushConn(void) ;
void popConn(void) ;
void pushLogg() ;
void popLogg() ;
void PushLogg( void );
void PopLogg( void );
// Login Specific
void LoginDisconnect(UOXSOCKET s);
void LoginDisconnect(CSocket *s);
void RegisterPacket( UI08 packet, UI08 subCmd, UI16 scriptID );
// We don't want to do this, but given we have outside classes
// we can either friend a lot of things, or just put it out here
std::mutex internallock;
std::vector< CSocket * > connClients, loggedInClients;
private:
struct FirewallEntry
{
SI16 b[4];
FirewallEntry( SI16 p1, SI16 p2, SI16 p3, SI16 p4 )
{
b[0] = p1; b[1] = p2; b[2] = p3; b[3] = p4;
}
};
std::map< UI16, UI16 > packetOverloads;
std::vector< FirewallEntry > slEntries;
SI32 a_socket;
struct sockaddr_in client_addr;
size_t peakConnectionCount;
std::vector< std::vector< CSocket * >::iterator > connIteratorBackup, loggIteratorBackup;
std::vector< CSocket * >::iterator currConnIter, currLoggIter;
void LoadFirewallEntries( void );
void GetMsg( UOXSOCKET s );
void sockInit( void );
void GetLoginMsg( UOXSOCKET s );
UOXSOCKET FindLoginPtr( CSocket *s );
void CheckConn( void );
void LogOut( CSocket *s );
bool IsFirewallBlocked( UI08 part[4] );
};
extern cNetworkStuff *Network;
#endif