# HG changeset patch # User Teemu Piippo # Date 1611769721 -7200 # Node ID be953e1621d9cbea4b3b54d4ef97c55f89a530e1 # Parent 060a13878ca0c7c5e5721543baadd33faa3aa707# Parent 0c7e44e1078ad572a8eadb409f253076b50d224a merged with default diff -r 060a13878ca0 -r be953e1621d9 CMakeLists.txt --- a/CMakeLists.txt Wed Jan 27 12:41:50 2021 +0200 +++ b/CMakeLists.txt Wed Jan 27 19:48:41 2021 +0200 @@ -2,6 +2,9 @@ cmake_policy (SET CMP0003 NEW) project (ZFC9000) string (TOLOWER ${CMAKE_PROJECT_NAME} TARGET_NAME) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) add_library (huffman STATIC huffman/bitreader.cpp @@ -37,7 +40,6 @@ sources/network/ipaddress.h sources/network/rconsession.h sources/network/udpsocket.h - sources/range.h sources/version.h ) diff -r 060a13878ca0 -r be953e1621d9 sources/basics.h --- a/sources/basics.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/basics.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,7 +29,9 @@ */ #pragma once -#include +#include +#include +#include #if !defined(_MSC_VER) && !defined(__cdecl) # define __cdecl @@ -72,8 +74,8 @@ BEGIN_ZFC_NAMESPACE -template -T min (T a, T b) +template +constexpr std::common_type_t min(T a, TT b) { return (a < b) ? a : b; } @@ -89,8 +91,8 @@ return result; } -template -T max (T a, T b) +template +constexpr std::common_type_t max(T a, TT b) { return (a > b) ? a : b; } @@ -106,8 +108,8 @@ return result; } -template -T clamp (T a, T b, T c) +template +constexpr std::common_type_t clamp(T a, TT b, TTT c) { return (a < b) ? b : (a > c) ? c : a; } @@ -117,10 +119,16 @@ return value != 1 ? "s" : ""; } -template -char (&_ArraySizeHelper(T (&array)[N]))[N]; -#define countof(array) (sizeof(_ArraySizeHelper( array ))) +template +constexpr std::size_t countof(T(&)[N]) +{ + return N; +} -struct Exitception {}; +#ifdef __GNUC__ +# define GNUATTRIBUTE(X) __attribute__(X) +#else +# define GNUATTRIBUTE(X) +#endif END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/coloredline.cpp --- a/sources/coloredline.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/coloredline.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ setColor(m_activeColor, false); if (m_boldActive) - m_data << RLINE_OFF_BOLD; + m_data.push_back(RLINE_OFF_BOLD); m_final = true; } @@ -103,7 +103,7 @@ setColor(m_activeColor, false); if (m_boldActive) - m_data << RLINE_OFF_BOLD; + m_data.push_back(RLINE_OFF_BOLD); m_boldActive = false; @@ -127,11 +127,11 @@ { if (ch == ']') { - String color = m_incomingColorName.toLowerCase(); + std::string color = to_lowercase(m_incomingColorName); for (const ColorCodeInfo &colorInfo : colorCodes) { - if (String(colorInfo.name).toLowerCase() == color) + if (to_lowercase(colorInfo.name) == color) { activateColor(colorInfo.color, colorInfo.bold); m_colorCodeStage = 0; @@ -151,7 +151,7 @@ if (isprint(ch)) { m_string += ch; - m_data << int(ch); + m_data.push_back(static_cast(ch)); ++m_length; } } @@ -161,20 +161,22 @@ void ColoredLine::activateColor(Color color, bool bold) { if (m_boldActive) - m_data << RLINE_OFF_BOLD; - + { + m_data.push_back(RLINE_OFF_BOLD); + } m_activeColor = color; m_boldActive = bold; assert(m_activeColor < 8); setColor(m_activeColor, true); - if (m_boldActive) - m_data << RLINE_ON_BOLD; + { + m_data.push_back(RLINE_ON_BOLD); + } } // ------------------------------------------------------------------------------------------------- // -void ColoredLine::addString(const String& text) +void ColoredLine::addString(const std::string& text) { for (char a : text) addChar(a); @@ -185,7 +187,7 @@ void ColoredLine::setColor(Color a, bool on) { assert(a < 8); - m_data << (a +(on ? RLINE_ON_COLOR : RLINE_OFF_COLOR)); + m_data.push_back(a + (on ? RLINE_ON_COLOR : RLINE_OFF_COLOR)); } // ------------------------------------------------------------------------------------------------- diff -r 060a13878ca0 -r be953e1621d9 sources/coloredline.h --- a/sources/coloredline.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/coloredline.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -49,27 +49,20 @@ NUM_COLORS }; -// ------------------------------------------------------------------------------------------------- -// -enum -{ - RLINE_ON_COLOR = 256, - RLINE_OFF_COLOR = 264, - RLINE_ON_BOLD = 272, - RLINE_OFF_BOLD -}; +constexpr int RLINE_ON_COLOR = 256; +constexpr int RLINE_OFF_COLOR = 264; +constexpr int RLINE_ON_BOLD = 272; +constexpr int RLINE_OFF_BOLD = 273; -// ------------------------------------------------------------------------------------------------- -// class ColoredLine { public: ColoredLine(); - const Vector& data() const { return m_data; } + const std::vector& data() const { return m_data; } int length() const { return m_length; } void addChar(char ch); - void addString(const String& msg); + void addString(const std::string& msg); void finalize(); int rows(int cols) const; @@ -77,14 +70,14 @@ void activateColor(Color color, bool bold); void setColor(Color a, bool on); - Vector m_data; + std::vector m_data; int m_length; bool m_final; Color m_activeColor; bool m_boldActive; int m_colorCodeStage; - String m_string; - String m_incomingColorName; + std::string m_string; + std::string m_incomingColorName; }; END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/geometry.h --- a/sources/geometry.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/geometry.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without diff -r 060a13878ca0 -r be953e1621d9 sources/interface.cpp --- a/sources/interface.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/interface.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -51,9 +51,9 @@ // ------------------------------------------------------------------------------------------------- // -const String& Interface::getCurrentInput() +const std::string& Interface::getCurrentInput() { - return m_inputHistory[m_inputCursor]; + return this->m_inputHistory[this->m_inputCursor]; } // ------------------------------------------------------------------------------------------------- @@ -62,20 +62,20 @@ // void Interface::detachInput() { - if (m_inputCursor > 0) + if (this->m_inputCursor > 0) { - m_inputHistory[0] = getCurrentInput(); - m_inputCursor = 0; + this->m_inputHistory[0] = getCurrentInput(); + this->m_inputCursor = 0; } } // ------------------------------------------------------------------------------------------------- // A version of current_input() that allows changing the contents of it. // -String& Interface::getEditableInput() +std::string& Interface::getEditableInput() { detachInput(); - return m_inputHistory[m_inputCursor]; + return this->m_inputHistory[this->m_inputCursor]; } // ------------------------------------------------------------------------------------------------- @@ -83,29 +83,29 @@ void Interface::moveInputCursor(int delta) { // No input history when inputting addresses or passwords - if (m_inputState != INPUTSTATE_NORMAL) + if (this->m_inputState != INPUTSTATE_NORMAL) { - m_inputCursor = 0; + this->m_inputCursor = 0; return; } - int oldcursor = m_inputCursor; - m_inputCursor = clamp(m_inputCursor + delta, 0, m_inputHistory.size() - 1); + int oldcursor = this->m_inputCursor; + this->m_inputCursor = clamp(this->m_inputCursor + delta, 0, static_cast(this->m_inputHistory.size() - 1)); - if (m_inputCursor != oldcursor) + if (this->m_inputCursor != oldcursor) { - m_cursorPosition = getCurrentInput().length(); - m_needInputRender = true; + this->m_cursorPosition = getCurrentInput().length(); + this->m_needInputRender = true; } } // ------------------------------------------------------------------------------------------------- // -String Interface::getPromptString() +std::string Interface::getPromptString() { - String prompt; + std::string prompt; - switch (m_inputState) + switch (this->m_inputState) { case INPUTSTATE_NORMAL: prompt = ">"; break; case INPUTSTATE_ADDRESS: prompt = "address:"; break; @@ -122,25 +122,25 @@ { // Clear the input row(unless going to or from confirm state) if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION - and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) + and this->m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) { - m_inputCursor = 0; + this->m_inputCursor = 0; getEditableInput().clear(); } switch (newstate) { case INPUTSTATE_ADDRESS: - if (m_remoteAddress.host != 0) - getEditableInput() = m_remoteAddress.to_string(IPAddress::WITH_PORT); + if (this->m_remoteAddress.host != 0) + getEditableInput() = net::ip_address_to_string(this->m_remoteAddress); break; default: break; } - m_inputState = newstate; - m_needInputRender = true; + this->m_inputState = newstate; + this->m_needInputRender = true; } // ------------------------------------------------------------------------------------------------- @@ -164,11 +164,11 @@ ::noecho(); ::refresh(); ::timeout(0); - m_inputHistory.clear(); - m_inputHistory << ""; - m_outputLines.clear(); - m_outputLines << ColoredLine(); - m_session.setInterface(this); + this->m_inputHistory.clear(); + this->m_inputHistory.push_back(""); + this->m_outputLines.clear(); + this->m_outputLines.push_back(ColoredLine()); + this->m_session.setInterface(this); resetTitle(); if (::has_colors()) @@ -179,8 +179,8 @@ int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; // Initialize color pairs - for (int i : range(NUM_COLORS)) - for (int j : range(NUM_COLORS)) + for (int i = 0; i < NUM_COLORS; i += 1) + for (int j = 0; j < NUM_COLORS; j += 1) { int pairnum = 1 + (i * NUM_COLORS + j); int fg =(i == DEFAULT) ? defaultFg : i; @@ -196,33 +196,38 @@ renderFull(); refresh(); - m_needRefresh = false; + this->m_needRefresh = false; +} + +Interface::~Interface() +{ + ::endwin(); } // ------------------------------------------------------------------------------------------------- // void Interface::renderTitlebar() { - if (m_title.length() <= COLS) + if (static_cast(this->m_title.length()) <= COLS) { chtype pair = getColorPair(WHITE, BLUE); - int startx =(COLS - m_title.length()) / 2; - int endx = startx + m_title.length(); + int startx =(COLS - this->m_title.length()) / 2; + int endx = startx + this->m_title.length(); attron(pair); - mvprintw(0, startx, "%s", m_title.chars()); + mvprintw(0, startx, "%s", this->m_title.data()); mvhline(0, 0, ' ', startx); mvhline(0, endx, ' ', COLS - endx); attroff(pair); } - m_needRefresh = true; + this->m_needRefresh = true; } // ------------------------------------------------------------------------------------------------- // -void Interface::setTitle(const String& title) +void Interface::setTitle(const std::string& title) { - m_title = title; + this->m_title = title; renderTitlebar(); } @@ -230,9 +235,9 @@ // void Interface::safeDisconnect(std::function afterwards) { - if (m_session.isActive()) + if (this->m_session.isActive()) { - m_disconnectCallback = afterwards; + this->m_disconnectCallback = afterwards; setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); } else @@ -301,15 +306,15 @@ // void Interface::renderOutput() { - if (m_outputLines.size() == 1) + if (this->m_outputLines.size() == 1) return; - m_outputScroll = clamp(m_outputScroll, 0, m_outputLines.size() - 1); + this->m_outputScroll = clamp(this->m_outputScroll, 0, static_cast(this->m_outputLines.size() - 1)); int height = LINES - 3; int width = COLS - nicklistWidth(); int printOffset = 0; - int end = m_outputLines.size() - 1 - m_outputScroll; + int end = this->m_outputLines.size() - 1 - this->m_outputScroll; int start = end; int usedHeight = 0; int y = 1; @@ -318,7 +323,7 @@ // Where to start? while (start > 0) { - int rows = m_outputLines[start - 1].rows(width); + int rows = this->m_outputLines[start - 1].rows(width); if (usedHeight + rows > height) { @@ -334,9 +339,9 @@ // See if there's any more rows to use(end may be too small) if (not tightFit) { - while (end < m_outputLines.size()) + while (end < static_cast(this->m_outputLines.size())) { - int rows = m_outputLines[end].rows(width); + int rows = this->m_outputLines[end].rows(width); if (usedHeight + rows > height) { @@ -352,7 +357,7 @@ if (start > 0) printOffset = height - usedHeight; - m_outputScroll = m_outputLines.size() - 1 - end; + this->m_outputScroll = this->m_outputLines.size() - 1 - end; if (start < 0 or start == end or printOffset >= height) return; @@ -360,17 +365,17 @@ assert(start <= end and start - end <= height); // Clear the display - for (int i : range(height)) + for (int i = 0; i < height; i += 1) mvhline(y + i, 0, ' ', width); // Print the lines y += printOffset; - for (int i : range(start, end)) - y = renderColorline(y, 0, width, m_outputLines[i], true); + for (int i = start; i < end; i += 1) + y = renderColorline(y, 0, width, this->m_outputLines[i], true); - m_needOutputRender = false; - m_needRefresh = true; + this->m_needOutputRender = false; + this->m_needRefresh = true; } // ------------------------------------------------------------------------------------------------- @@ -385,18 +390,18 @@ if (width > 0) return; - for (int i : range(height)) + for (int i = 0; i < height; i += 1) { mvhline(y, x, ' ', width); - if (i < m_playerNames.size()) - renderColorline(y, x, width, m_playerNames[i], false); + if (i < static_cast(this->m_playerNames.size())) + renderColorline(y, x, width, this->m_playerNames[i], false); y++; } - m_needNicklistRender = false; - m_needRefresh = true; + this->m_needNicklistRender = false; + this->m_needRefresh = true; } // ------------------------------------------------------------------------------------------------- @@ -407,57 +412,57 @@ // If we're asking the user if they want to disconnect, we don't render any input strings, // just the confirmation message. - if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) + if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) { attron(promptColor); mvhline(LINES - 2, 0, ' ', COLS); mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n"); attroff(promptColor); - m_needRefresh = true; + this->m_needRefresh = true; return; } - String prompt = getPromptString(); + std::string prompt = getPromptString(); int displayLength = COLS - prompt.length() - 2; - String displayString = getCurrentInput(); + std::string displayString = getCurrentInput(); int y = LINES - 2; // If we're inputting a password, replace it with asterisks - if (m_inputState == INPUTSTATE_PASSWORD) + if (this->m_inputState == INPUTSTATE_PASSWORD) { for (char &ch : displayString) ch = '*'; } // Ensure the cursor is within bounds - m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length()); + this->m_cursorPosition = clamp(this->m_cursorPosition, 0, static_cast(displayString.length())); // Ensure that the cursor is always in view, adjust panning if this is not the case - if (m_cursorPosition > m_inputPanning + displayLength) - m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right - else if (m_cursorPosition < m_inputPanning) - m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left + if (this->m_cursorPosition > this->m_inputPanning + displayLength) + this->m_inputPanning = this->m_cursorPosition - displayLength; // cursor went too far right + else if (this->m_cursorPosition < this->m_inputPanning) + this->m_inputPanning = this->m_cursorPosition; // cursor went past the pan value to the left // What part of the string to draw? - int start = m_inputPanning; + int start = this->m_inputPanning; int end = min(displayString.length(), start + displayLength); - assert(m_cursorPosition >= start and m_cursorPosition <= end); + assert(this->m_cursorPosition >= start and this->m_cursorPosition <= end); // Render the input string mvhline(LINES - 2, 0, ' ', COLS); - mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars()); + mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data()); // Render the prompt attron(promptColor); - mvprintw(y, 0, "%s", prompt.chars()); + mvprintw(y, 0, "%s", prompt.data()); attroff(promptColor); // Store in memory where the cursor is now(so that we can re-draw it to position the terminal // cursor). - m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; - m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); - m_needRefresh = true; - m_needInputRender = false; + this->m_cursorCharacter.ch = this->m_cursorPosition != 0 ? displayString[this->m_cursorPosition - 1] : '\0'; + this->m_cursorCharacter.x = prompt.length() + (this->m_cursorPosition - this->m_inputPanning); + this->m_needRefresh = true; + this->m_needInputRender = false; } // ------------------------------------------------------------------------------------------------- @@ -468,19 +473,19 @@ int y = LINES - 1; attron(color); mvhline(y, 0, ' ', COLS); - mvprintw(y, 0, "%s", m_statusBarText.chars()); + mvprintw(y, 0, "%s", this->m_statusBarText.data()); attroff(color); - m_needRefresh = true; - m_needStatusBarRender = false; + this->m_needRefresh = true; + this->m_needStatusBarRender = false; } // ------------------------------------------------------------------------------------------------- // void Interface::updateStatusBar() { - String text; + std::string text; - switch (m_session.getState()) + switch (this->m_session.getState()) { case RCON_DISCONNECTED: text = "Disconnected."; @@ -488,41 +493,41 @@ case RCON_CONNECTING: case RCON_AUTHENTICATING: - text = "Connecting to " + m_session.address().to_string(IPAddress::WITH_PORT) + "..."; + text = "Connecting to " + net::ip_address_to_string(this->m_session.address()) + "..."; break; case RCON_CONNECTED: { - String adminText; + std::string adminText; - if (m_session.getAdminCount() == 0) + if (this->m_session.getAdminCount() == 0) { adminText = "No other admins"; } else { - adminText.sprintf("%d other admin%s", m_session.getAdminCount(), - m_session.getAdminCount() != 1 ? "s" : ""); + adminText = zfc::sprintf("%d other admin%s", this->m_session.getAdminCount(), + this->m_session.getAdminCount() != 1 ? "s" : ""); } - text.sprintf("%s | %s | %s", - m_session.address().to_string(IPAddress::WITH_PORT).chars(), - m_session.getLevel().chars(), - adminText.chars()); + text = zfc::sprintf("%s | %s | %s", + net::ip_address_to_string(this->m_session.address()).data(), + this->m_session.getLevel().data(), + adminText.data()); } break; } - if (not text.isEmpty()) + if (not text.empty()) text += " | "; text += "Ctrl+N to connect, Ctrl+Q to "; - text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; + text +=(this->m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect"; - if (text != m_statusBarText) + if (text != this->m_statusBarText) { - m_statusBarText = text; - m_needStatusBarRender = true; + this->m_statusBarText = text; + this->m_needStatusBarRender = true; } } @@ -543,13 +548,13 @@ void Interface::positionCursor() { // This is only relevant if the input string is being drawn - if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) + if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) return; int y = LINES - 2; - if (m_cursorCharacter.ch != '\0') - mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); + if (this->m_cursorCharacter.ch != '\0') + mvprintw(y, this->m_cursorCharacter.x, "%c", this->m_cursorCharacter.ch); else mvprintw(y, getPromptString().length(), " "); } @@ -558,8 +563,8 @@ // int Interface::findPreviousWord() { - const String& input = getCurrentInput(); - int pos = m_cursorPosition; + const std::string& input = getCurrentInput(); + int pos = this->m_cursorPosition; // Move past whitespace while (pos > 0 and isspace(input[pos - 1])) @@ -576,11 +581,11 @@ // int Interface::findNextWord() { - const String& input = getCurrentInput(); - int pos = m_cursorPosition; + const std::string& input = getCurrentInput(); + int pos = this->m_cursorPosition; // Move past current whitespace - while (pos < input.length() and isspace(input[pos])) + while (pos < static_cast(input.length()) and isspace(input[pos])) pos++; // Move past the word @@ -597,18 +602,38 @@ if (a >= b) return; - if (m_cursorPosition > a and m_cursorPosition <= b) - m_cursorPosition = a; + if (this->m_cursorPosition > a and this->m_cursorPosition <= b) + this->m_cursorPosition = a; + + std::string& input = getEditableInput(); + this->m_pasteBuffer = mid(input, a, b); + input = remove_range(input, a, b - a); + this->m_needInputRender = true; +} - String& input = getEditableInput(); - m_pasteBuffer = input.mid(a, b); - input.remove(a, b - a); - m_needInputRender = true; +bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target) +{ + std::stringstream errors; + const std::optional address_opt = net::ip_resolve(address_string, errors); + if (address_opt.has_value()) + { + *target = address_opt.value(); + if (target->port == 0) + { + target->port = 10666; + } + return true; + } + else + { + this->printError("%s\n", errors.str().data()); + return false; + } } // ------------------------------------------------------------------------------------------------- // -void Interface::handleInput() +void Interface::handleInput(bool* shouldquit) { int ch = ::getch(); @@ -622,12 +647,13 @@ return; } - if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) + if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) { if (ch == 'y' or ch == 'Y') { - m_session.disconnect(); - m_disconnectCallback(true); + this->m_session.disconnect(); + this->m_disconnectCallback(true); + this->updateStatusBar(); } else if (ch == 'n' or ch == 'N') setInputState(INPUTSTATE_NORMAL); @@ -637,13 +663,15 @@ if (ch >= 0x20 and ch <= 0x7E) { - getEditableInput().insert(m_cursorPosition++, char(ch)); - m_needInputRender = true; + std::string& input = getEditableInput(); + input.insert(input.begin() + this->m_cursorPosition, char(ch)); + this->m_cursorPosition += 1; + this->m_needInputRender = true; } else switch (ch) { case 'Q' - 'A' + 1: // ^Q - switch (m_inputState) + switch (this->m_inputState) { case INPUTSTATE_CONFIRM_DISCONNECTION: break; @@ -657,8 +685,7 @@ } else { - endwin(); - throw Exitception(); + *shouldquit = true; } }); break; @@ -674,19 +701,19 @@ case KEY_LEFT: case 'B' - 'A' + 1: // readline ^B - if (m_cursorPosition > 0) + if (this->m_cursorPosition > 0) { - m_cursorPosition--; - m_needInputRender = true; + this->m_cursorPosition--; + this->m_needInputRender = true; } break; case KEY_RIGHT: case 'F' - 'A' + 1: // readline ^F - if (m_cursorPosition < getCurrentInput().length()) + if (this->m_cursorPosition < static_cast(getCurrentInput().length())) { - m_cursorPosition++; - m_needInputRender = true; + this->m_cursorPosition++; + this->m_needInputRender = true; } break; @@ -697,72 +724,75 @@ case KEY_HOME: case 'A' - 'A' + 1: // readline ^A - if (m_cursorPosition != 0) + if (this->m_cursorPosition != 0) { - m_cursorPosition = 0; - m_needInputRender = true; + this->m_cursorPosition = 0; + this->m_needInputRender = true; } break; case KEY_END: case 'E' - 'A' + 1: // readline ^E - if (m_cursorPosition != getCurrentInput().length()) + if (this->m_cursorPosition != static_cast(getCurrentInput().length())) { - m_cursorPosition = getCurrentInput().length(); - m_needInputRender = true; + this->m_cursorPosition = getCurrentInput().length(); + this->m_needInputRender = true; } break; case KEY_BACKSPACE: case '\b': - if (m_cursorPosition > 0) + if (this->m_cursorPosition > 0) { - getEditableInput().removeAt(--m_cursorPosition); - m_needInputRender = true; + std::string& input = getEditableInput(); + input.erase(input.begin() + this->m_cursorPosition - 1); + this->m_cursorPosition -= 1; + this->m_needInputRender = true; } break; case KEY_DC: case 'D' - 'A' + 1: // readline ^D - if (m_cursorPosition < getCurrentInput().length()) + if (this->m_cursorPosition < static_cast(getCurrentInput().length())) { - getEditableInput().removeAt(m_cursorPosition); - m_needInputRender = true; + std::string& input = getEditableInput(); + input.erase(input.begin() + this->m_cursorPosition); + this->m_needInputRender = true; } break; case KEY_PPAGE: - m_outputScroll += min(PAGE_SIZE, LINES / 2); - m_needOutputRender = true; + this->m_outputScroll += min(PAGE_SIZE, LINES / 2); + this->m_needOutputRender = true; break; case KEY_NPAGE: - m_outputScroll -= min(PAGE_SIZE, LINES / 2); - m_needOutputRender = true; + this->m_outputScroll -= min(PAGE_SIZE, LINES / 2); + this->m_needOutputRender = true; break; case 'U' - 'A' + 1: // readline ^U - delete from start to cursor - if (m_cursorPosition > 0) + if (this->m_cursorPosition > 0) { - yank(0, m_cursorPosition); - m_cursorPosition = 0; + yank(0, this->m_cursorPosition); + this->m_cursorPosition = 0; } break; case 'K' - 'A' + 1: // readline ^K - delete from cursor to end - yank(m_cursorPosition, getEditableInput().length()); + yank(this->m_cursorPosition, getEditableInput().length()); break; case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current - yank(findPreviousWord(), m_cursorPosition); + yank(findPreviousWord(), this->m_cursorPosition); break; case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text - if (not m_pasteBuffer.isEmpty()) + if (not this->m_pasteBuffer.empty()) { - getEditableInput().insert(m_cursorPosition, m_pasteBuffer); - m_cursorPosition += m_pasteBuffer.length(); - m_needInputRender = true; + getEditableInput().insert(this->m_cursorPosition, this->m_pasteBuffer); + this->m_cursorPosition += this->m_pasteBuffer.length(); + this->m_needInputRender = true; } break; @@ -770,12 +800,12 @@ { int space = getCurrentInput().find(" "); - if (m_inputState == INPUTSTATE_NORMAL - and m_cursorPosition > 0 - and(space == -1 or space >= m_cursorPosition)) + if (this->m_inputState == INPUTSTATE_NORMAL + and this->m_cursorPosition > 0 + and(space == -1 or space >= this->m_cursorPosition)) { - String start = getCurrentInput().mid(0, m_cursorPosition); - m_session.requestTabCompletion(start); + std::string start = mid(getCurrentInput(), 0, this->m_cursorPosition); + this->m_session.requestTabCompletion(start); } } break; @@ -783,34 +813,24 @@ case '\n': case '\r': case KEY_ENTER: - switch (m_inputState) + switch (this->m_inputState) { case INPUTSTATE_CONFIRM_DISCONNECTION: break; // handled above case INPUTSTATE_ADDRESS: - try - { - m_remoteAddress = IPAddress::from_string(getCurrentInput()); - } - catch (std::exception& e) + if (this->tryResolveAddress(this->getCurrentInput(), &this->m_remoteAddress)) { - print("%s\n", e.what()); - return; + setInputState(INPUTSTATE_PASSWORD); } - - if (m_remoteAddress.port == 0) - m_remoteAddress.port = 10666; - - setInputState(INPUTSTATE_PASSWORD); break; case INPUTSTATE_PASSWORD: - if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().isEmpty()) + if (this->m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty()) { - m_session.disconnect(); - m_session.setPassword(getCurrentInput()); - m_session.connect(m_remoteAddress); + this->m_session.disconnect(); + this->m_session.setPassword(getCurrentInput()); + this->m_session.connect(this->m_remoteAddress); setInputState(INPUTSTATE_NORMAL); } break; @@ -818,10 +838,10 @@ case INPUTSTATE_NORMAL: if (getCurrentInput()[0] == '/') { - handleCommand(getCurrentInput()); + handleCommand(getCurrentInput(), shouldquit); flushInput(); } - else if (m_session.sendCommand(getCurrentInput())) + else if (this->m_session.sendCommand(getCurrentInput())) { flushInput(); } @@ -830,7 +850,7 @@ break; case 'N' - 'A' + 1: // ^N - if (m_inputState == INPUTSTATE_NORMAL) + if (this->m_inputState == INPUTSTATE_NORMAL) safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); break; @@ -845,35 +865,35 @@ case 'b': case 'B': // readline alt-b - move one word to the left - m_cursorPosition = findPreviousWord(); - m_needInputRender = true; + this->m_cursorPosition = findPreviousWord(); + this->m_needInputRender = true; break; case 'f': case 'F': // readline alt-f - move one word to the right - m_cursorPosition = findNextWord(); - m_needInputRender = true; + this->m_cursorPosition = findNextWord(); + this->m_needInputRender = true; break; case 'd': case 'D': // readline alt-d - delete from here till next word boundary - yank(m_cursorPosition, findNextWord()); + yank(this->m_cursorPosition, findNextWord()); break; case KEY_BACKSPACE: // alt+backspace, remove previous word case '\b': - yank(findPreviousWord(), m_cursorPosition); + yank(findPreviousWord(), this->m_cursorPosition); break; } } else { // No alt-key, handle pure escape - if (m_inputState == INPUTSTATE_PASSWORD) + if (this->m_inputState == INPUTSTATE_PASSWORD) setInputState(INPUTSTATE_ADDRESS); - else if (m_inputState == INPUTSTATE_ADDRESS) + else if (this->m_inputState == INPUTSTATE_ADDRESS) setInputState(INPUTSTATE_NORMAL); } break; @@ -886,16 +906,16 @@ // void Interface::render() { - if (m_needStatusBarRender) renderStatusBar(); - if (m_needInputRender) renderInput(); - if (m_needOutputRender) renderOutput(); - if (m_needNicklistRender) renderNicklist(); + if (this->m_needStatusBarRender) renderStatusBar(); + if (this->m_needInputRender) renderInput(); + if (this->m_needOutputRender) renderOutput(); + if (this->m_needNicklistRender) renderNicklist(); - if (m_needRefresh) + if (this->m_needRefresh) { positionCursor(); refresh(); - m_needRefresh = false; + this->m_needRefresh = false; } } @@ -903,8 +923,8 @@ // void Interface::vprint(const char* fmtstr, va_list args) { - String message; - message.vsprintf(fmtstr, args); + std::string message; + message = vsprintf(fmtstr, args); printToConsole(message); } @@ -953,108 +973,98 @@ // ------------------------------------------------------------------------------------------------- // -void Interface::printToConsole(String message) +void Interface::printToConsole(std::string message) { // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". // Let's correct that on our end and hope this won't cause conflicts. - message.replace("\\c", "\x1C"); + replace_all(message, "\\c", "\x1C"); for (char ch : message) { if (ch == '\n') { - m_outputLines.last().finalize(); - m_outputLines << ColoredLine(); + zfc::last(this->m_outputLines).finalize(); + this->m_outputLines.push_back({}); continue; } - if (m_outputLines.last().length() == 0) + if (zfc::last(this->m_outputLines).length() == 0) { time_t now; time(&now); char timestamp[32]; strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); - for (char ch : String(timestamp)) - m_outputLines.last().addChar(ch); + for (char ch : std::string(timestamp)) + zfc::last(this->m_outputLines).addChar(ch); } // Remove some lines if there's too many of them. 20,000 should be enough, I hope. - while (m_outputLines.size() > 20000) - m_outputLines.remove_at(0); + while (this->m_outputLines.size() > 20000) + this->m_outputLines.erase(this->m_outputLines.begin()); - m_outputLines.last().addChar(ch); + zfc::last(this->m_outputLines).addChar(ch); } - m_needOutputRender = true; + this->m_needOutputRender = true; } // ------------------------------------------------------------------------------------------------- // -void Interface::connect(String address, String password) +void Interface::connect(std::string address_string, std::string password) { - try - { - m_remoteAddress = IPAddress::from_string(address); - } - catch (std::exception& e) + if (this->tryResolveAddress(address_string, &this->m_remoteAddress)) { - print("%s\n", e.what()); - return; + this->m_session.disconnect(); + this->m_session.setPassword(password); + this->m_session.connect(this->m_remoteAddress); } - - if (m_remoteAddress.port == 0) - m_remoteAddress.port = 10666; - - m_session.disconnect(); - m_session.setPassword(password); - m_session.connect(m_remoteAddress); } // ------------------------------------------------------------------------------------------------- // -void Interface::setPlayerNames(const StringList& names) +void Interface::setPlayerNames(const std::vector& names) { - m_playerNames.clear(); + this->m_playerNames.clear(); - for (const String& name : names) + for (const std::string& name : names) { ColoredLine coloredname; coloredname.addString(name); coloredname.finalize(); - m_playerNames.append(coloredname); + this->m_playerNames.push_back(coloredname); } - m_needNicklistRender = true; + this->m_needNicklistRender = true; } // ------------------------------------------------------------------------------------------------- // -void Interface::tabComplete(const String& part, String complete) +void Interface::tabComplete(const std::string& part, std::string complete) { - String& input = getEditableInput(); + std::string& input = getEditableInput(); - if (input.startsWith(part)) + if (starts_with(input, part)) { if (input[part.length()] != ' ') complete += ' '; input.replace(0, part.length(), complete); - m_cursorPosition = complete.length(); - m_needInputRender = true; + this->m_cursorPosition = complete.length(); + this->m_needInputRender = true; } } // ------------------------------------------------------------------------------------------------- // -void Interface::handleCommand(const String& input) +void Interface::handleCommand(const std::string& input, bool* shouldquit) { if (input[0] != '/') return; - StringList args = input.right(input.length() - 1).split(" "); - String command = args[0].toLowerCase(); - args.remove_at(0); + std::vector args = split(right(input, input.length() - 1), " "); + std::string command = to_lowercase(args[0]); + args.erase(args.begin()); if (command == "connect") { @@ -1064,52 +1074,34 @@ } else { - IPAddress address; - - try - { - address = IPAddress::from_string(args[0]); - } - catch (std::exception& e) - { - printError("%s\n", e.what()); - return; - } - - if (address.port == 0) - address.port = 10666; - - m_session.setPassword(args[1]); - m_session.disconnect(); - m_session.connect(m_remoteAddress = address); + this->connect(args[0], args[1]); } } else if (command == "disconnect") { - m_session.disconnect(); + this->m_session.disconnect(); } else if (command == "quit") { - m_session.disconnect(); - endwin(); - throw Exitception(); + this->m_session.disconnect(); + *shouldquit = true; } else if (command == "watch") { - if (not args.is_empty()) + if (not args.empty()) m_session.requestWatch(args); else printError("No CVars to watch.\n"); } else - printError("Unknown command: %s\n", command.chars()); + printError("Unknown command: %s\n", command.data()); } // ------------------------------------------------------------------------------------------------- // void Interface::disconnected() { - print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); + print("Disconnected from %s\n", net::ip_address_to_string(this->m_session.address()).data()); resetTitle(); renderFull(); } @@ -1118,16 +1110,16 @@ // void Interface::resetTitle() { - m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); + this->m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); } // ------------------------------------------------------------------------------------------------- // void Interface::flushInput() { - m_inputHistory.insert(0, ""); - m_inputCursor = 0; - m_needInputRender = true; + this->m_inputHistory.insert(this->m_inputHistory.begin(), ""); + this->m_inputCursor = 0; + this->m_needInputRender = true; } END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/interface.h --- a/sources/interface.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/interface.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -49,26 +49,27 @@ }; Interface(); - void connect(String address, String password); + virtual ~Interface(); + void connect(std::string address, std::string password); void disconnected(); RCONSession* getSession() { return &m_session; } - void handleCommand(const String& input); - void handleInput(); + void handleCommand(const std::string& input, bool *shouldquit); + void handleInput(bool *shouldquit); void needRefresh(); - void __cdecl print(const char* fmtstr, ...); - void __cdecl printWarning(const char* fmtstr, ...); - void __cdecl printError(const char* fmtstr, ...); - void __cdecl printText(const char* fmtstr, ...); + void __cdecl print(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3))); + void __cdecl printWarning(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3))); + void __cdecl printError(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3))); + void __cdecl printText(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3))); void render(); void renderFull(); - void setPlayerNames(const StringList& names); - void setTitle(const String& message); - void tabComplete(const String& part, String complete); + void setPlayerNames(const std::vector& names); + void setTitle(const std::string& message); + void tabComplete(const std::string& part, std::string complete); void updateStatusBar(); void vprint(const char* fmtstr, va_list args); private: - StringList m_inputHistory; + std::vector m_inputHistory; int m_inputCursor; int m_cursorPosition; int m_inputPanning; @@ -78,15 +79,15 @@ bool m_needOutputRender; bool m_needNicklistRender; struct { char ch; int x; } m_cursorCharacter; - Vector m_outputLines; + std::vector m_outputLines; int m_outputScroll; - String m_title; + std::string m_title; InputState m_inputState; std::function m_disconnectCallback; - IPAddress m_remoteAddress; - String m_statusBarText; - List m_playerNames; - String m_pasteBuffer; + net::ip_address m_remoteAddress; + std::string m_statusBarText; + std::vector m_playerNames; + std::string m_pasteBuffer; RCONSession m_session; void detachInput(); @@ -94,13 +95,13 @@ int findPreviousWord(); void flushInput(); chtype getColorPair(Color fg, Color bg); - const String& getCurrentInput(); - String& getEditableInput(); - String getPromptString(); + const std::string& getCurrentInput(); + std::string& getEditableInput(); + std::string getPromptString(); void moveInputCursor(int delta); int nicklistWidth(); void positionCursor(); - void printToConsole(String message); + void printToConsole(std::string message); int renderColorline(int y, int x0, int width, const ColoredLine& line, bool allowWrap); void renderInput(); void renderNicklist(); @@ -111,6 +112,7 @@ void safeDisconnect(std::function afterwards); void setInputState(InputState newstate); void yank(int a, int b); + bool tryResolveAddress(const std::string& address_string, net::ip_address *target); }; END_ZFC_NAMESPACE \ No newline at end of file diff -r 060a13878ca0 -r be953e1621d9 sources/list.cpp --- a/sources/list.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/list.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2016 Teemu Piippo + Copyright 2016 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,7 +29,6 @@ */ #include "list.h" -#include "mystring.h" BEGIN_ZFC_NAMESPACE @@ -76,21 +75,11 @@ } } -/*! - * \brief Constructs a byte array from an initializer list containing bytes. - * \param initializerList List of bytes. - */ -ByteArray::ByteArray(std::initializer_list initializerList) : - Vector(initializerList) {} +std::string quote(const std::vector &bytes) +{ + std::string result; -/*! - * \returns a quoted representation of the contents of the byte array. - */ -String ByteArray::quote() const -{ - String result; - - for (unsigned char byte : *this) + for (unsigned char byte : bytes) result += representByte(byte); return "\"" + result + "\""; diff -r 060a13878ca0 -r be953e1621d9 sources/list.h --- a/sources/list.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/list.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,393 +29,31 @@ */ #pragma once +#include +#include #include "basics.h" -#include -#include -#include -#include -#include -#include "range.h" BEGIN_ZFC_NAMESPACE -// ------------------------------------------------------------------------------------------------- -// -template -class Container +template +auto& last(T& container) { -public: - typedef typename C::iterator Iterator; - typedef typename C::const_iterator ConstIterator; - typedef typename C::reverse_iterator ReverseIterator; - typedef typename C::const_reverse_iterator ConstReverseIterator; - typedef Container Self; - - Container(){} - - Container (int numvalues) : - m_container (numvalues) {} - - Container (const C& other) : - m_container (other) {} - - Container(std::initializer_list initializerList) : - m_container(initializerList) {} - - T& append (const T& value) - { - m_container.push_back (value); - return m_container[m_container.size() - 1]; - } - - void append(const T* values, size_t numValues) - { - size_t i0 = size(); - resize(size() + numValues); - - for (size_t i : range(numValues)) - (*this)[i0 + i] = values[i]; - } - - Iterator begin() - { - return m_container.begin(); - } - - ConstIterator begin() const - { - return m_container.begin(); - } - - void clear() - { - m_container.clear(); - } - - bool contains (const T& a) const - { - return std::find (m_container.begin(), m_container.end(), a) != m_container.end(); - } - - ConstReverseIterator crbegin() const - { - return m_container.rbegin(); - } - - ConstReverseIterator crend() const - { - return m_container.rend(); - } - - const C& container() const - { - return m_container; - } - - Iterator end() - { - return m_container.end(); - } - - ConstIterator end() const - { - return m_container.end(); - } - - Iterator find (const T& needle) - { - auto it = std::find (m_container.begin(), m_container.end(), needle); - - if (it == m_container.end()) - return end(); - - return it; - } - - ConstIterator find (const T& needle) const - { - auto it = std::find (m_container.begin(), m_container.end(), needle); - - if (it == m_container.end()) - return end(); - - return it; - } - - Iterator find (bool (*func)(T const&)) - { - for (Iterator it = begin(); it != end(); ++it) - { - if (func (*it)) - return it; - } - - return end(); - } - - ConstIterator find (bool (*func)(T const&)) const - { - for (ConstIterator it = begin(); it != end(); ++it) - { - if (func (*it)) - return it; - } - - return end(); - } - - T& first() - { - return *begin(); - } - - const T& first() const - { - return *begin(); - } - - void insert (int pos, const T& value) - { - m_container.insert (m_container.begin() + pos, value); - } - - bool is_empty() const - { - return size() == 0; - } - - T& last() - { - return *(end() - 1); - } - - const T& last() const - { - return *(end() - 1); - } - - void merge (const Self& other) - { - int oldsize = size(); - resize (size() + other.size()); - std::copy (other.begin(), other.end(), begin() + oldsize); - } - - bool pop (T& val) - { - if (is_empty()) - return false; - - val = m_container[size() - 1]; - m_container.erase (m_container.end() - 1); - return true; - } - - T& prepend (const T& value) - { - m_container.push_front (value); - return m_container[0]; - } - - ReverseIterator rbegin() - { - return m_container.rbegin(); - } - - void remove_at (int pos) - { - assert (pos < size()); - m_container.erase (m_container.begin() + pos); - } - - void remove_duplicates() - { - sort(); - resize (std::distance (begin(), std::unique (begin(), end()))); - } - - void remove_one (const T& valueToRemove) - { - auto it = std::find (m_container.begin(), m_container.end(), valueToRemove); - - if (it != m_container.end()) - m_container.erase (it); - } - - ReverseIterator rend() - { - return m_container.rend(); - } - - void resize (int size) - { - m_container.resize (size); - } - - Self reverse() const - { - Self rev; - std::copy (rbegin(), rend(), rev.begin()); - return rev; - } - - int size() const - { - return m_container.size(); - } - - void sort() - { - std::sort (begin(), end()); - } - - Self splice(int start, int end, int step = 1) const - { - start = clamp(start, 0, size()); - end = clamp(end, 0, size()); - - if (end <= start) - { - return Self(); - } - else - { - Self result; - - for (int i : range(start, end, step)) - result << operator[] (i); - - return result; - } - } - - Self splice(Range& range) const - { - return splice(range.min(), range.max(), range.step()); - } - - Self& operator<< (const T& value) - { - append (value); - return *this; - } - - Self& operator<< (const Self& vals) - { - merge (vals); - return *this; - } - - T& operator[] (int n) - { - assert (n < size()); - return m_container[n]; - } - - const T& operator[] (int n) const - { - assert (n < size()); - return m_container[n]; - } - - Self operator[] (Range const& n) const - { - return splice (n); - } - - Self operator+ (const Self& other) const - { - Self out (*this); - out.merge (other); - return out; - } - -protected: - C m_container; -}; - -// ------------------------------------------------------------------------------------------------- -// -template -Container& operator>> (const T& value, Container& haystack) -{ - haystack.prepend (value); - return haystack; + return *(container.begin() + container.size() - 1); } -// ------------------------------------------------------------------------------------------------- -// - -template -class List : public Container > -{ -public: - typedef Container > Super; - - List(){} - - List (int numvalues) : - Super (numvalues) {} - - List (const Super& other) : - Super (other) {} -}; - -// ------------------------------------------------------------------------------------------------- -// +std::string quote(const std::vector& bytes); template -class Vector : public Container > +T splice(const T& container, int start, int end, int step = 1) { -public: - typedef Container > Super; - - Vector(){} - - Vector(int numvalues) : - Super(numvalues){} - - Vector (T* data, size_t length) : - Super (std::vector (data, data + length)) {} - - Vector(std::initializer_list initializerList) : - Super(initializerList) {} - - Vector(const Super& other) : - Super(other) {} - - T* data() - { - return Super::m_container.data(); - } - - const T* data() const + start = clamp(start, 0, static_cast(container.size())); + end = clamp(end, 0, static_cast(container.size())); + T result; + result.reserve((end - start) / step); + for (int i = start; i < end; i += step) { - return Super::m_container.data(); - } - - operator const T*() const - { - return data(); + result.push_back(container[i]); } -}; - -class ByteArray : public Vector -{ -public: - ByteArray(std::initializer_list initializerList); - - template - ByteArray(Args&& ...args); - - class String quote() const; -}; - -/*! - * \brief Constructs a byte array by passing all arguments to Vector's constructor. - * \param args Arguments to pass. - */ -template -ByteArray::ByteArray(Args&& ...args) : - Vector(args...) {} + return result; +} END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/main.cpp --- a/sources/main.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/main.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -72,35 +72,33 @@ if (argc == 3) iface.connect (argv[1], argv[2]); - try + for (;;) { - for (;;) + fd_set fdset; + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 250000; // 0.25 seconds + FD_ZERO(&fdset); + FD_SET(0, &fdset); + const int fd = iface.getSession()->getSocket()->file_descriptor; + FD_SET(fd, &fdset); + ::select(fd + 1, &fdset, nullptr, nullptr, &timeout); + bool shouldquit = false; + if (FD_ISSET(0, &fdset)) { - fd_set fdset; - int highest = 0; - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 250000; // 0.25 seconds - FD_ZERO (&fdset); - FD_SET (0, &fdset); - - int fd = iface.getSession()->getSocket()->file_descriptor(); - highest = zfc::max (highest, fd); - FD_SET (fd, &fdset); - - select (highest + 1, &fdset, nullptr, nullptr, &timeout); - - if (FD_ISSET (0, &fdset)) - { - // stdin is ready, what's incoming? - iface.handleInput(); - } - + // stdin is ready, what's incoming? + iface.handleInput(&shouldquit); + } + if (shouldquit) + { + break; + } + else + { iface.getSession()->tick(); iface.render(); } } - catch (const zfc::Exitception&) {} iface.getSession()->disconnect(); return EXIT_SUCCESS; diff -r 060a13878ca0 -r be953e1621d9 sources/main.h --- a/sources/main.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/main.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ #pragma once #include #include +#include +#include #include #include #include diff -r 060a13878ca0 -r be953e1621d9 sources/md5.cpp --- a/sources/md5.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/md5.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -306,4 +306,12 @@ } } +std::string md5(const char *buffer) +{ + char checksum[33]; + CalculateMD5(reinterpret_cast(buffer), strlen(buffer), checksum); + checksum[sizeof checksum - 1] = '\0'; + return {checksum}; +} + END_ZFC_NAMESPACE \ No newline at end of file diff -r 060a13878ca0 -r be953e1621d9 sources/md5.h --- a/sources/md5.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/md5.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,7 +1,9 @@ #pragma once +#include #include "basics.h" BEGIN_ZFC_NAMESPACE void CalculateMD5 (unsigned char const *buffer, int length, char *checksum); +std::string md5(const char* buffer); END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/mystring.cpp --- a/sources/mystring.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/mystring.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,66 +31,17 @@ #include #include "main.h" #include "mystring.h" -#include "md5.h" BEGIN_ZFC_NAMESPACE /*! - * \brief Compares this string with another. - * \param other The string to compare with. - * \returns -1 if this string is lexicographically less than \c other, - * 0 if they are equal, or - * 1 if this string is lexicographically greater than \c other. - */ -int String::compare (const String& other) const -{ - return m_string.compare (other.stdString()); -} - -/*! - * \brief Removes all instances of an unwanted character from this string. - * \param unwanted Character to remove. - */ -void String::strip (char unwanted) -{ - for (int pos = 0; (pos = find (unwanted)) != -1;) - removeAt (pos--); -} - -/*! - * \brief Removes all instances of multiple characters from this string. - * \param unwanted Characters to remove. - */ -void String::strip (const List& unwanted) -{ - for (char character : unwanted) - strip(character); -} - -/*! - * \returns an upper-case version of this string. - */ -String String::toUpperCase() const -{ - String result (m_string); - - for (char &ch : result) - { - if (islower(ch)) - ch -= 'a' - 'A'; - } - - return result; -} - -/*! * \returns a lower-case version of this string. */ -String String::toLowerCase() const +std::string to_lowercase(const std::string& string) { - String result (m_string); + std::string result = string; - for (char &ch : result) + for (char& ch : result) { if (isupper(ch)) ch += 'a' - 'A'; @@ -100,74 +51,35 @@ } /*! - * \brief Splits this string using the provided delimeter. - * \param delimeter Delimeter to use for splitting. - * \returns a string list containing the split strings. + * \brief Joins the elements of this string list into one longer string. + * \param delimeter The delimeter to place between the element strings. + * \returns the catenated string. */ -StringList String::split (char delimeter) const -{ - String delimeterString; - delimeterString += delimeter; - return split (delimeterString); -} - -/*! - * \brief Splits this string using the provided delimeter. - * \param delimeter Delimeter to use for splitting. - * \returns a string list containing the split strings. - */ -StringList String::split (const String& delimeter) const +std::string join_string_list(const std::vector& strings, const std::string& delimeter) { - StringList result; - int a = 0; - int b; - - // Find all separators and store the text left to them. - while ((b = find (delimeter, a)) != -1) - { - String sub = mid (a, b); + std::string result; - if (sub.length() > 0) - result << sub; + for (const std::string &item : strings) + { + if (not result.empty()) + result += delimeter; - a = b + delimeter.length(); + result += item; } - // Add the string at the right of the last separator - if (a < (int) length()) - result.append (mid (a, length())); - return result; } /*! - * \brief Replaces all instances of \c text with \c replacement. - * \param text Text to replace away. - * \param replacement Text to replace \c text with. + * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the + * end of the string. For example, an index of -1 will be modified to point to the last character in the string, + * -2 to the second last, etc. + * \param index Index to translate. */ -void String::replace (const char* text, const char* replacement) +inline void modifyIndex(const std::string& str, int& index) { - int position; - - while ((position = find (text)) != -1) - m_string = m_string.replace (position, strlen (text), replacement); -} - -/*! - * \param character Character to count. - * \returns the amount of \c character found in the string. - */ -int String::count (char character) const -{ - int result = 0; - - for (char ch : *this) - { - if (ch == character) - result++; - } - - return result; + if (index < 0) + index = str.length() - index; } /*! @@ -175,204 +87,29 @@ * \param b Ending index of the range. * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b. */ -String String::mid (int rangeBegin, int rangeEnd) const +std::string mid(const std::string& str, int rangeBegin, int rangeEnd) { - modifyIndex(rangeBegin); - modifyIndex(rangeEnd); + modifyIndex(str, rangeBegin); + modifyIndex(str, rangeEnd); rangeBegin = max(rangeBegin, 0); - rangeEnd = min(rangeEnd, length()); + rangeEnd = min(rangeEnd, static_cast(str.length())); if (rangeEnd <= rangeBegin) return ""; else - return m_string.substr(rangeBegin, rangeEnd - rangeBegin); + return str.substr(rangeBegin, rangeEnd - rangeBegin); } /*! * \param length Amount of characters to return. * \returns the \c length right-most characters of the string. */ -String String::right(int length) const -{ - if (length >= this->length()) - return *this; - else - return String(chars() + this->length() - length); -} - -/*! - * \brief Finds the first instance of a sub-string. - * \param subString Sub-string to search within this string. - * \param startingPosition Position to start looking for the sub-string from. - * \returns the position the first instance of sub-string found, or -1 if not found. - */ -int String::find (const char* subString, int startingPosition) const -{ - int position = m_string.find (subString, startingPosition); - - if (position == int (std::string::npos)) - return -1; - else - return position; -} - -/*! - * \brief Finds the first instance of a character. - * \param character Character to search within this string. - * \param startingPosition Position to start looking for the character from. - * \returns the position of the first instance of the provided character found, or -1 if not found. - */ -int String::find (char character, int startingPosition) const -{ - int position = m_string.find (character, startingPosition); - - if (position == int (std::string::npos)) - return -1; - else - return position; -} - -/*! - * \brief Finds the last instance of a sub-string. - * \param subString Sub-string to search within this string. - * \param startingPosition Position to start looking for the sub-string from. - * \returns the position the last instance of sub-string found, or -1 if not found. - */ -int String::findLast (const char* subString, int startingPosition) const -{ - modifyIndex(startingPosition); - - for (; startingPosition > 0; startingPosition--) - { - if (strncmp (chars() + startingPosition, subString, strlen (subString)) == 0) - return startingPosition; - } - - return -1; -} - -/*! - * \brief Converts this string to an integer. - * \param ok An pointer to a boolean to store whether or not the conversion was successful. - * If \c ok is \c NULL, the success state is not stored. - * \param base The base to interpret this string with. - * \returns the resulting integer. - */ -long String::toInt (bool* ok, int base) const -{ - errno = 0; - char* endPointer; - long result = strtol (chars(), &endPointer, base); - - if (ok != nullptr) - *ok = (errno == 0 and *endPointer == '\0'); - - return result; -} - -/*! - * \brief Converts this string to a floating-point number. - * \param ok An pointer to a boolean to store whether or not the conversion was successful. - * If \c ok is \c NULL, the success state is not stored. - * \returns the resulting floating-point number. - */ -float String::toFloat (bool* ok) const +std::string right(const std::string& str, int length) { - return static_cast(toDouble(ok)); -} - -/*! - * \brief Converts this string to a double-precision floating-point number. - * \param ok An pointer to a boolean to store whether or not the conversion was successful. - * If \c ok is \c NULL, the success state is not stored. - * \returns the resulting floating-point number. - */ -double String::toDouble (bool* ok) const -{ - errno = 0; - char* endptr; - double i = strtod (chars(), &endptr); - - if (ok != nullptr) - *ok = (errno == 0 and *endptr == '\0'); - - return i; -} - -/*! - * \brief Catenates this string with another string. - * \param text String to catenate to the end of this string. - * \returns the resulting string. - */ -String String::operator+ (const String& text) const -{ - String newString = *this; - newString.append (text); - return newString; -} - -/*! - * \brief Catenates this string with another string. - * \param text String to catenate to the end of this string. - * \returns the resulting string. - */ -String String::operator+ (const char* text) const -{ - String newString = *this; - newString.append (text); - return newString; -} - -/*! - * \returns whether or not this string represents a number. - */ -bool String::isNumeric() const -{ - char* endPointer; - strtol (chars(), &endPointer, 10); - return (endPointer != nullptr) and (*endPointer != '\0'); -} - -/*! - * \param other Sub-string to find from the end of this string. - * \return whether or not this string ends with the provided sub-string. - */ -bool String::endsWith (const String& other) const -{ - if (length() < other.length()) - { - return false; - } + if (length >= static_cast(str.length())) + return str; else - { - const int offset = length() - other.length(); - return strncmp (chars() + offset, other.chars(), other.length()) == 0; - } -} - -/*! - * \param other Sub-string to find from the beginning of this string. - * \returns whether or not this string begins with the provided sub-string. - */ -bool String::startsWith (const String& other) const -{ - if (length() < other.length()) - return false; - else - return strncmp (chars(), other.chars(), other.length()) == 0; -} - -/*! - * \brief Formats this string using \c printf -like syntax. - * \param formatString Template string to use with formatting. - * \param ... Variadic arguments to use with formatting. - */ -void __cdecl String::sprintf (const char* formatString, ...) -{ - va_list args; - va_start (args, formatString); - this->vsprintf (formatString, args); - va_end (args); + return std::string{str.data() + str.length() - length}; } /*! @@ -380,8 +117,10 @@ * \param formatString Template string to use with formatting. * \param args Variadic arguments to use with formatting. */ -void String::vsprintf (const char* formatString, va_list args) +std::string vsprintf(const char* formatString, va_list args) { + std::string result; + // Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again. va_list argsCopy; va_copy(argsCopy, args); @@ -393,192 +132,118 @@ if (length < sizeof buffer) { // vsnprintf succeeded in fitting the formatted string into the buffer, so we're done. - m_string = buffer; + result = buffer; } else { // vsnprintf needs more space, so we have to allocate a new buffer and try again. - Vector newBuffer(length + 1); + std::vector newBuffer(length + 1); vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy); - m_string = newBuffer; + result = newBuffer.data(); + } + + return result; +} + +/*! + * \brief Formats this string using \c printf -like syntax. + * \param formatString Template string to use with formatting. + * \param ... Variadic arguments to use with formatting. + */ +std::string __cdecl sprintf(const char* formatString, ...) +{ + va_list args; + va_start (args, formatString); + std::string result = vsprintf(formatString, args); + va_end (args); + return result; +} + +std::string remove_range(const std::string &string, int start, int end) +{ + std::string result; + result.reserve(string.length() - (end - start)); + std::copy(string.begin(), string.begin() + start, std::back_inserter(result)); + std::copy(string.begin() + end, string.end(), std::back_inserter(result)); + return result; +} + + +/*! + * \param other Sub-string to find from the beginning of this string. + * \returns whether or not this string begins with the provided sub-string. + */ +bool starts_with(const std::string& str, const std::string& other) +{ + if (str.length() < other.length()) + return false; + else + return std::strncmp(str.data(), other.data(), other.length()) == 0; +} + +/*! + * \brief Replaces all instances of \c text with \c replacement. + * \param text Text to replace away. + * \param replacement Text to replace \c text with. + */ +void replace_all(std::string& str, const char* text, const char* replacement) +{ + int position; + while ((position = str.find(text)) != -1) + { + str.replace(position, std::strlen(text), replacement); } } /*! - * \brief Joins the elements of this string list into one longer string. - * \param delimeter The delimeter to place between the element strings. - * \returns the catenated string. + * \brief Splits this string using the provided delimeter. + * \param delimeter Delimeter to use for splitting. + * \returns a string list containing the split strings. */ -String StringList::join (const String& delimeter) +std::vector split(const std::string& string, const std::string& delimeter) { - String result; + std::vector result; + int a = 0; + int b; - for (const String &item : container()) + // Find all separators and store the text left to them. + while ((b = string.find(delimeter, a)) != -1) { - if (result.isEmpty() == false) - result += delimeter; + std::string sub = mid(string, a, b); + + if (sub.length() > 0) + result.push_back(sub); - result += item; + a = b + delimeter.length(); } + // Add the string at the right of the last separator + if (a < static_cast(string.length())) + result.push_back(mid(string, a, string.length())); + return result; } /*! - * \brief Tries to match this string against a mask pattern. In the pattern, '?' refers to one character, and '*' to - * any number of characters. - * \param pattern The masking pattern to use for matching. - * \returns whether or not this string matches the provided pattern. + * \brief Converts this string to an integer. + * \param ok An pointer to a boolean to store whether or not the conversion was successful. + * If \c ok is \c NULL, the success state is not stored. + * \param base The base to interpret this string with. + * \returns the resulting integer. */ -bool String::maskAgainst (const String& pattern) const -{ - // Elevate to uppercase for case-insensitive matching - String pattern_upper = pattern.toUpperCase(); - String this_upper = toUpperCase(); - const char* maskstring = pattern_upper.chars(); - const char* mptr = &maskstring[0]; - - for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++) - { - if (*mptr == '?') - { - if (*(sptr + 1) == '\0') - { - // ? demands that there's a character here and there wasn't. - // Therefore, mask matching fails - return false; - } - } - else if (*mptr == '*') - { - char end = *(++mptr); - - // If '*' is the final character of the message, all of the remaining - // string matches against the '*'. We don't need to bother checking - // the string any further. - if (end == '\0') - return true; - - // Skip to the end character - while (*sptr != end and *sptr != '\0') - sptr++; - - // String ended while the mask still had stuff - if (*sptr == '\0') - return false; - } - else if (*sptr != *mptr) - return false; - - mptr++; - } - - return true; -} - -/*! - * \brief Converts a short integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (short int value) -{ - char buffer[32]; - ::sprintf (buffer, "%d", value); - return String (buffer); -} - -/*! - * \brief Converts an integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (int value) +std::optional to_int(const char* str, int base) { - char buffer[32]; - ::sprintf (buffer, "%d", value); - return String (buffer); -} - -/*! - * \brief Converts a long integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (long int value) -{ - char buffer[32]; - ::sprintf (buffer, "%ld", value); - return String (buffer); -} - -/*! - * \brief Converts an unsigned short integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (unsigned short int value) -{ - char buffer[32]; - ::sprintf (buffer, "%u", value); - return String (buffer); -} - -/*! - * \brief Converts an unsigned integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (unsigned int value) -{ - char buffer[32]; - ::sprintf (buffer, "%u", value); - return String (buffer); -} - -/*! - * \brief Converts an unsigned long integer into a string. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (unsigned long int value) -{ - char buffer[32]; - ::sprintf (buffer, "%lu", value); - return String (buffer); -} - -/*! - * \brief Converts a double-precision floating point number into a string, using the "%f" format specifier. - * \param value The value to convert. - * \returns the resulting string. - */ -String String::fromNumber (double value) -{ - char buffer[64]; - ::sprintf (buffer, "%f", value); - return String (buffer); -} - -/*! - * \brief Constructs a string from a vector of bytes. The bytes do not have to be null-terminated. - * \param bytes Bytes to use for construction - * \returns the resulting string. - */ -String String::fromBytes(const ByteArray& bytes) -{ - return String(reinterpret_cast&>(bytes)); -} - -/*! - * \returns the MD5-checksum of this string. - */ -String String::md5() const -{ - char checksum[33]; - CalculateMD5 (reinterpret_cast (chars()), length(), checksum); - checksum[sizeof checksum - 1] = '\0'; - return String (checksum); + errno = 0; + char* endPointer; + long result = strtol(str, &endPointer, base); + if (errno == 0 and *endPointer == '\0') + { + return result; + } + else + { + return {}; + } } /*! @@ -586,32 +251,26 @@ * something else than whitespace. * \param filter The filtering function to use. */ -void String::normalize (int (*filter)(int)) +void normalize(std::string& string, int (*filter)(int)) { int a = 0; - int b = length() - 1; - - while ((*filter) (m_string[a]) and a != b) + int b = string.length() - 1; + while ((*filter)(string[a]) and a != b) + { ++a; - - while ((*filter) (m_string[b]) and a != b) + } + while ((*filter)(string[b]) and a != b) + { --b; - + } if (a == b) - m_string = ""; - else if (a != 0 or b != length() - 1) - m_string = m_string.substr (a, b - a + 1); -} - -/*! - * \returns a version of this string without leading or trailing whitespace. Alternatively a custom filter can be used - * to strip something else than whitespace. - */ -String String::normalized (int (*filter)(int)) const -{ - String result = *this; - result.normalize(filter); - return result; + { + string = ""; + } + else if (a != 0 or b != static_cast(string.length() - 1)) + { + string = string.substr (a, b - a + 1); + } } END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/mystring.h --- a/sources/mystring.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/mystring.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -37,573 +37,21 @@ BEGIN_ZFC_NAMESPACE - -class String -{ -public: - typedef std::string::iterator Iterator; - typedef std::string::const_iterator ConstIterator; - - String(); - String(char a); - String(const char* data); - String(const std::string& data); - String(const Vector& data); - - void append(const char* text); - void append(char character); - void append(const String& text); - ConstIterator begin() const; - Iterator begin(); - int compare(const String &other) const; - int count(char character) const; - const char* chars() const; - void clear(); - ConstIterator end() const; - Iterator end(); - bool endsWith(const String &other) const; - int find(const char* subString, int startingPosition = 0) const; - int find(char character, int startingPosition = 0) const; - int indexDifference(int a, int b); - void insert(int position, char character); - void insert(int position, const char* string); - bool isEmpty() const; - bool isNumeric() const; - void modifyIndex(int &a) const; - int findLast(const char* subString, int startingPosition = -1) const; - int length() const; - bool maskAgainst(const String &pattern) const; - String md5() const; - String mid(int rangeBegin, int rangeEnd) const; - void normalize(int(*filter)(int) = &isspace); - String normalized(int(*filter)(int) = &isspace) const; - void prepend(String text); - void remove(int position, int length); - void removeAt(int position); - void removeFromEnd(int length); - void removeFromStart(int length); - void replace(const char* text, const char* replacement); - void replace(int position, int amount, const String &text); - String right(int length) const; - void shrinkToFit(); - class StringList split(const String &delimeter) const; - class StringList split(char delimeter) const; - void __cdecl sprintf(const char* fmtstr, ...); - bool startsWith(const String &other) const; - const std::string& stdString() const; - void strip(char unwanted); - void strip(const List &unwanted); - const unsigned char* toBytes() const; - double toDouble(bool* ok = nullptr) const; - float toFloat(bool* ok = nullptr) const; - long toInt(bool* ok = nullptr, int base = 10) const; - String toLowerCase() const; - String toUpperCase() const; - void vsprintf(const char* fmtstr, va_list args); - - static String fromNumber(short int a); - static String fromNumber(int a); - static String fromNumber(long int a); - static String fromNumber(unsigned short int a); - static String fromNumber(unsigned int a); - static String fromNumber(unsigned long int a); - static String fromNumber(double a); - static String fromBytes(const ByteArray& bytes); - - String operator+(const String& data) const; - String operator+(const char* data) const; - String operator+(int num) const; - String& operator+=(const String& data); - String& operator+=(const char* data); - String& operator+=(int num); - String& operator+=(char data); - char& operator[](int i); - char operator[](int i) const; - bool operator==(const String& other) const; - bool operator==(const char* other) const; - bool operator!=(const String& other) const; - bool operator!=(const char* other) const; - bool operator>(const String& other) const; - bool operator<(const String& other) const; - bool operator>=(const String& other) const; - bool operator<=(const String& other) const; - operator const char*() const; - operator const std::string&() const; - -private: - std::string m_string; -}; - - -class StringList : public List -{ -public: - StringList(); - StringList(int numvalues); - StringList(const List& other); - String join(const String& delim); -}; - - -inline bool operator==(const char* a, const String& b); -inline String operator+(const char* a, const String& b); - -// -------------------------------------------------------------------------------------------------------------------- - -/*! - * \brief Constructs an empty string. - */ -inline String::String() {} - -/*! - * \brief Constructs a string from a single character. - * \param character Character to create a string out of. - */ -inline String::String(char character) -{ - char buffer[2] = { character, '\0' }; - m_string = buffer; -} - -/*! - * \brief Constructs a string from a char-array. - * \param string char-array to convert. - */ -inline String::String(const char* string) : - m_string(string) {} - -/*! - * \brief Constructs a string out of a \c std::string . - * \param string \c std::string to base the construction on. - */ -inline String::String(const std::string& string) : - m_string(string) {} - -/*! - * \brief Constructs a string out of a vector of characters. The vector does not have to be null-terminated. - * \param charVector Vector of characters to construct the string out of. - */ -inline String::String(const Vector& charVector) : - m_string(charVector.data(), charVector.size()) {} - -/*! - * \returns a constant iterator to the beginning of the string. - */ -inline String::ConstIterator String::begin() const -{ - return m_string.cbegin(); -} - -/*! - * \returns the string's contents as a char-array. - */ -inline const char* String::chars() const -{ - return m_string.c_str(); -} - -/*! - * \returns the string's constant end-iterator. - */ -inline String::ConstIterator String::end() const -{ - return m_string.end(); -} - -/*! - * \returns whether or not the string is empty. - */ -inline bool String::isEmpty() const -{ - return m_string[0] == '\0'; -} - -/*! - * \returns the length of the string. - */ -inline int String::length() const -{ - return m_string.length(); -} - -/*! - * \returns the underlying \c std::string . - */ -inline const std::string& String::stdString() const -{ - return m_string; -} - -/*! - * \brief Adds text from a char-array to the end of the string. - * \param text Text to append. - */ -inline void String::append(const char* text) -{ - m_string.append(text); -} - -/*! - * \brief Adds text to the end of the string. - * \param text Text to append. - */ -inline void String::append(char character) -{ - m_string.push_back(character); -} - -/*! - * \brief Adds text from another string to the end of this string. - * \param text Text to append. - */ -inline void String::append(const String& text) -{ - m_string.append(text.chars()); -} - -/*! - * \returns a mutable iterator to the beginning of the string. - */ -inline String::Iterator String::begin() -{ - return m_string.begin(); -} - -/*! - * \brief Clears the string. - */ -inline void String::clear() -{ - m_string.clear(); -} - -/*! - * \returns the string's mutable end-iterator. - */ -inline String::Iterator String::end() -{ - return m_string.end(); -} - -/*! - * \brief Compares two string indices, supporting negatives as offsets from the end of string. - * \param a First index to compare - * \param b Second index to compare - * \returns the difference of two indices. - */ -inline int String::indexDifference(int a, int b) -{ - modifyIndex(a); - modifyIndex(b); - return b - a; -} - -/*! - * \brief Inserts a character into the string. - * \param position Position in the string where to insert the character into. - * \param character Character to insert into the string. - */ -inline void String::insert(int position, char character) -{ - m_string.insert(m_string.begin() + position, character); -} - -/*! - * \brief Inserts a substring into the string. - * \param position Position in the string where to insert the substring. - * \param string Substring to insert. - */ -inline void String::insert(int position, const char* string) -{ - m_string.insert(position, string); -} +using String = std::string; +using StringList = std::vector; +using namespace std::string_literals; -/*! - * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the - * end of the string. For example, an index of -1 will be modified to point to the last character in the string, - * -2 to the second last, etc. - * \param index Index to translate. - */ -inline void String::modifyIndex(int& index) const -{ - if (index < 0) - index = length() - index; -} - -/*! - * \brief Prepends the given text to the beginning of the string. - * \param text Text to prepend. - */ -inline void String::prepend(String text) -{ - m_string = (text + m_string).stdString(); -} - -/*! - * \brief Removes a range of text from the string. - * \param position Position where to start removing text. - * \param length Amount of characters to remove. - */ -inline void String::remove(int position, int length) -{ - m_string.replace(position, length, ""); -} - -/*! - * \brief Removes a single character from the string. - * \param position Position of the character to remove string from. - */ -inline void String::removeAt(int position) -{ - m_string.erase(m_string.begin() + position); -} - -/*! - * \brief Removes a number of characters from the end of the string. - * \param length Amount of characters to remove. - */ -inline void String::removeFromEnd(int length) -{ - remove(this->length() - length, length); -} - -/*! - * \brief Removes a number of characters from the beginning of the string. - * \param length Amount of characters to remove. - */ -inline void String::removeFromStart(int length) -{ - remove(0, length); -} - -/*! - * \brief Replaces a range of text in the string with another. - * \param position Position where to start replacing text. - * \param amount Amount of characters to replace. - * \param text Replacement string. - */ -inline void String::replace(int position, int amount, const String& text) -{ - m_string.replace(position, amount, text.chars()); -} - -/*! - * \brief Shrinks the string so that it does not allocate more characters than necessary. - */ -inline void String::shrinkToFit() -{ - m_string.shrink_to_fit(); -} - -/*! - * \brief Converts a number into a string, and returns a new string with the number appended to the end of the string. - * \param number Number to convert and append. - * \returns the resulting string. - */ -inline String String::operator+(int number) const -{ - return *this + String::fromNumber(number); -} - -/*! - * \brief Appends text into the string. - * \param text Text to append. - * \returns a reference to this string. - */ -inline String& String::operator+=(const String& text) -{ - append(text); - return *this; -} - -/*! - * \brief Appends text into the string. - * \param text Text to append. - * \returns a reference to this string. - */ -inline String& String::operator+=(const char* text) -{ - append(text); - return *this; -} - -/*! - * \brief Converts a number into a string, and appends it into this string. - * \param number The number to append. - * \returns a refence to this string. - */ -inline String& String::operator+=(int number) -{ - return operator+=(String::fromNumber(number)); -} - -/*! - * \brief Appends a character into this string. - * \param character The character to append. - * \return a reference to this string. - */ -inline String& String::operator+=(char character) -{ - append(character); - return *this; -} - -/*! - * \param index Index referring to a character of this string. - * \returns an editable reference to the character pointed by the given index. - */ -inline char& String::operator[](int index) -{ - return m_string[index]; -} - -/*! - * \param index Index referring to a character of this string. - * \returns an const reference to the character pointed by the given index. - */ -inline char String::operator[](int index) const -{ - return m_string[index]; -} - -/*! - * \param other String to compare with. - * \returns whether or not this string is the same as the other string. - */ -inline bool String::operator==(const String& other) const -{ - return stdString() == other.stdString(); -} - -/*! - * \param other String to compare with. - * \returns whether or not this string is the same as the other string. - */ -inline bool String::operator==(const char* other) const -{ - return m_string == other; -} - -/*! - * \param other String to compare with. - * \returns whether or not this string is different than the other string. - */ -inline bool String::operator!=(const String& other) const -{ - return stdString() != other.stdString(); -} - -/*! - * \param other String to compare with. - * \returns whether or not this string is different than the other string. - */ -inline bool String::operator!=(const char* other) const -{ - return m_string != other; -} - -/*! - * \param other String to compare with. - * \return whether or not this string is lexicographically greater than the other string. - */ -inline bool String::operator>(const String& other) const -{ - return stdString() > other.stdString(); -} - -/*! - * \param other String to compare with. - * \return whether or not this string is lexicographically lesser than the other string. - */ -inline bool String::operator<(const String& other) const -{ - return stdString() < other.stdString(); -} - -/*! - * \param other String to compare with. - * \return whether or not this string is lexicographically at least as great as the other string. - */ -inline bool String::operator>=(const String& other) const -{ - return stdString() >= other.stdString(); -} - -/*! - * \param other String to compare with. - * \return whether or not this string is lexicographically at most as great as the other string. - */ -inline bool String::operator<=(const String& other) const -{ - return stdString() <= other.stdString(); -} - -/*! - * \returns a char-array representation of this string. - */ -inline String::operator const char*() const -{ - return chars(); -} - -/*! - * \returns the underlying \c std::string of this string. - */ -inline String::operator const std::string&() const -{ - return stdString(); -} - -/*! - * \returns the underlying char-array representation of this string, casted to unsigned chars. - */ -inline const unsigned char* String::toBytes() const -{ - return reinterpret_cast(chars()); -} - -/*! - * \brief Constructs an empty string list. - */ -inline StringList::StringList() {} - -/*! - * \brief Constructs a string list containing \c numvalues empty strings. - * \param numvalues Amount of empty strings to fill. - */ -inline StringList::StringList(int numvalues) : - List(numvalues) {} - -/*! - * \brief Constructs a string list from another list of strings. - * \param other The list of strings to use for construction. - */ -inline StringList::StringList(const List& other) : - List(other) {} - -/*! - * \brief An \c operator== implementation that allows a char-array to be at the left side of a string comparison - * with a \c String. - * \param one A char-array representation of a string to compare. - * \param other A string to compare. - * \returns whether or not the two parameters are equal. - */ -inline bool operator==(const char* one, const String& other) -{ - return other == one; -} - -/*! - * \brief An \c operator+ implementation that allows a char-array to be at the left side of a string catenation - * with a \c String. - * \param one A char-array representation of a string to catenate. - * \param other A string to catenate. - * \returns the catenated string. - */ -inline String operator+(const char* one, const String& other) -{ - return String(one) + other; -} - +std::string to_lowercase(const std::string& string); +std::string join_string_list(const std::vector& strings, const std::string& delim); +std::string mid(const std::string& str, int rangeBegin, int rangeEnd); +std::string right(const std::string& str, int length); +std::string vsprintf(const char* formatString, va_list args); +std::string __cdecl sprintf(const char* formatString, ...) GNUATTRIBUTE((format(printf, 1, 2))); +std::string remove_range(const std::string& string, int start, int end); +void replace_all(std::string& str, const char* text, const char* replacement); +bool starts_with(const std::string& str, const std::string& other); +std::vector split(const std::string& string, const std::string& delimeter); +std::optional to_int(const char* str, int base = 10); +void normalize(std::string& string, int (*filter)(int) = std::isspace); END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/network/bytestream.cpp --- a/sources/network/bytestream.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/bytestream.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -36,7 +36,7 @@ * \brief Constructs a byte cursor. The cursor is placed to the beginning of the stream. * \param data */ -Bytestream::Bytestream(ByteArray& data) : +Bytestream::Bytestream(std::vector& data) : m_data(data), m_position(0) {} @@ -49,8 +49,8 @@ if (bytesLeft() < bytes) { int bytesPast = bytes - bytesLeft(); - String message; - message.sprintf("attempted to read %d byte%s past the end of bytestream", bytesPast, plural(bytesPast)); + std::string message; + message = sprintf("attempted to read %d byte%s past the end of bytestream", bytesPast, plural(bytesPast)); throw IOError (message); } } @@ -66,7 +66,7 @@ /*! * \returns an iterator to the current data position. */ -ByteArray::Iterator Bytestream::getCurrentIterator() +std::vector::iterator Bytestream::getCurrentIterator() { return m_data.begin() + m_position; } @@ -90,7 +90,7 @@ ensureReadSpace (2); int16_t result = 0; - for (int i : range(2)) + for (int i : {0, 1}) result |= read() << (i * 8); return result; @@ -104,8 +104,8 @@ { ensureReadSpace (4); int32_t result = 0; - - for (int i : range(4)) + + for (int i = 0; i < 4; i += 1) result |= read() << (i * 8); return result; @@ -127,9 +127,9 @@ * \brief Reads in characters until a null terminator is encountered. * \returns the read string. */ -String Bytestream::readString() +std::string Bytestream::readString() { - String result; + std::string result; for (char byte; (byte = readByte()) != '\0';) { @@ -145,10 +145,10 @@ * \param length Amount of bytes to read. * \returns the read buffer. */ -ByteArray Bytestream::readBuffer(int length) +std::vector Bytestream::readBuffer(int length) { ensureReadSpace(length); - ByteArray result(length); + std::vector result(length); memcpy(result.data(), m_data.data() + m_position, length); m_position += length; return result; @@ -160,7 +160,7 @@ */ void Bytestream::writeByte(int8_t value) { - m_data.append(value); + m_data.push_back(value); } /*! @@ -169,8 +169,9 @@ */ void Bytestream::writeShort(int16_t value) { - for (int i : range(2)) - m_data.append((value >> (i * 8)) & 0xFF); + m_data.reserve(m_data.size() + 2); + for (int i : {0, 1}) + m_data.push_back((value >> (i * 8)) & 0xFF); } /*! @@ -179,8 +180,9 @@ */ void Bytestream::writeLong(int32_t value) { - for (int i : range(4)) - m_data.append((value >> (i * 8)) & 0xFF); + m_data.reserve(m_data.size() + 4); + for (int i = 0; i < 4; i += 1) + m_data.push_back((value >> (i * 8)) & 0xFF); } /*! @@ -199,10 +201,13 @@ * \brief Writes the given string to the end of the data. * \param text String to write. */ -void Bytestream::writeString(const String& string) +void Bytestream::writeString(const std::string& string) { - m_data.append(string.toBytes(), string.length()); - m_data.append(0); + const int oldSize = m_data.size(); + m_data.reserve(m_data.size() + string.length() + 1); + m_data.resize(m_data.size() + string.length()); + std::copy(string.begin(), string.end(), m_data.begin() + oldSize); + m_data.push_back(0); } /*! diff -r 060a13878ca0 -r be953e1621d9 sources/network/bytestream.h --- a/sources/network/bytestream.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/bytestream.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,11 +30,10 @@ #pragma once #include +#include #include "../main.h" BEGIN_ZFC_NAMESPACE -class String; - enum { MAX_NETWORK_STRING = 0x800 @@ -42,45 +41,45 @@ class IOError : public std::exception { - String m_message; + std::string m_message; public: - IOError(String message) : + IOError(std::string message) : m_message(message) {} const char* what() const throw() { - return m_message; + return m_message.data(); } }; class Bytestream { public: - Bytestream(ByteArray& data); + Bytestream(std::vector& data); int bytesLeft() const; - ByteArray::Iterator getCurrentIterator(); + std::vector::iterator getCurrentIterator(); int position() const; - ByteArray readBuffer(int length); + std::vector readBuffer(int length); int8_t readByte(); int32_t readLong(); int16_t readShort(); - String readString(); + std::string readString(); float readFloat(); void rewind(); void seek(int position); void write(const unsigned char* val, unsigned int length); - void writeBuffer(const ByteArray& other); + void writeBuffer(const std::vector& other); void writeByte(int8_t value); void writeDouble(double val); void writeFloat(float value); void writeLong(int32_t value); void writeShort(int16_t value); - void writeString(const String& string); + void writeString(const std::string& string); private: - ByteArray& m_data; + std::vector& m_data; int m_position; int8_t read(); diff -r 060a13878ca0 -r be953e1621d9 sources/network/ipaddress.cpp --- a/sources/network/ipaddress.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/ipaddress.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -46,145 +46,143 @@ typedef struct addrinfo AddrInfo; #endif -// ----------------------------------------------------------------------------- -// -IPAddress::IPAddress() : - host (0), - port (0) {} - -// ----------------------------------------------------------------------------- -// -IPAddress::IPAddress (unsigned long host, unsigned short port) : - host (host), - port (port) {} +net::octet_t net::ip_octet(const net::ip_address& address, unsigned char n) +{ + assert(n < 4); + return (address.host >> ((3 - n) * 8)) & 0xFF; +} -// ----------------------------------------------------------------------------- -// -IPAddress::IPAddress (const IPAddress& other) : - host (other.host), - port (other.port) {} +void net::ip_set_octet(net::ip_address* address, unsigned char n, net::octet_t octet) +{ + // TODO: make a big-endian version + assert(n < 4); + address->host &= ~(0xFF << (3 - n) * 8); + address->host |= octet << ((3 - n) * 8); +} -// ----------------------------------------------------------------------------- -// -String IPAddress::to_string (WithPort withport) const +std::string net::ip_address_to_string(const net::ip_address& address) { - String val; - - if (withport == WITH_PORT) - val.sprintf ("%u.%u.%u.%u:%u", octet (0), octet (1), octet (2), octet (3), port); - else - val.sprintf ("%u.%u.%u.%u", octet (0), octet (1), octet (2), octet (3)); - + const auto octet = [&](unsigned char n) { return ip_octet(address, n); }; + std::string val = sprintf ("%u.%u.%u.%u:%u", octet(0), octet(1), octet(2), octet(3), address.port); return val; } -// ----------------------------------------------------------------------------- -// -unsigned char IPAddress::octet (int n) const +int net::ip_compare(const net::ip_address &one, const net::ip_address &other) { - return (host >> ((3 - n) * 8)) & 0xFF; -} - -// ----------------------------------------------------------------------------- -// -void IPAddress::set_octet (int n, unsigned char oct) -{ - // TODO: make a big-endian version - host &= ~(0xFF << (3 - n) * 8); - host |= oct << ((3 - n) * 8); + if (one.host != other.host) + { + return one.host - other.host; + } + return one.port - other.port; } -// ----------------------------------------------------------------------------- -// -bool IPAddress::compare (const IPAddress& other) const +bool net::ip_address::operator<(const ip_address& other) const { - for (int i : range(4)) - { - if (octet (i) != other.octet (i)) - return false; - } + return ip_compare(*this, other) < 0; +} - if (port != 0 - and other.port != 0 - and port != other.port) - { - return false; - } - - return true; +bool net::ip_address::operator==(const net::ip_address &other) const +{ + return this->host == other.host and this->port == other.port; } // ----------------------------------------------------------------------------- // -bool IPAddress::operator< (const IPAddress& other) const -{ - for (int i : range(4)) - { - if (octet (i) != other.octet (i)) - return octet (i) < other.octet (i); - } - - return port < other.port; -} - -// ----------------------------------------------------------------------------- -// -sockaddr_in IPAddress::to_sockaddr_in() const +sockaddr_in net::ip_address_to_sockaddr_in(const net::ip_address& address) { sockaddr_in claddr; memset (&claddr, 0, sizeof claddr); - claddr.sin_addr.s_addr = htonl (host); - claddr.sin_port = htons (port); + claddr.sin_addr.s_addr = htonl(address.host); + claddr.sin_port = htons(address.port); claddr.sin_family = AF_INET; return claddr; } -// ----------------------------------------------------------------------------- -// -IPAddress IPAddress::from_string (String input) +std::optional net::ip_parse_port(const char* port_string, std::ostream& errorStream) +{ + std::optional opt = to_int(port_string); + if (opt.has_value()) + { + if (*opt >= 0 and *opt < 65536) + { + return static_cast(opt.value()); + } + else + { + return {}; + errorStream << "port is not in range"s; + } + } + else + { + return {}; + errorStream << "could not parse port"s; + } +} + +std::optional net::ip_resolve_hostname(const std::string& node, std::ostream& errorStream) { - unsigned int parts[4]; - int colonpos = input.find (":"); - String addressString = colonpos == -1 ? input : input.mid (0, colonpos); - IPAddress value; + AddrInfo hints; + AddrInfo* lookup; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + std::optional result = {}; + if (::getaddrinfo(node.data(), nullptr, &hints, &lookup) != 0) + { + errorStream << "unknown host "s + node; + } + else + { + assert (lookup != nullptr); + sockaddr_in* addr = reinterpret_cast(lookup[0].ai_addr); + result = net::ip_address{ntohl(addr->sin_addr.s_addr), ntohs(addr->sin_port)}; + ::freeaddrinfo(lookup); + } + return result; +} +std::optional net::ip_resolve(const std::string& input_string, std::ostream& errorStream) +{ + int colonpos = input_string.find(":"); + std::string addressString = colonpos == -1 ? input_string : mid(input_string, 0, colonpos); + std::optional value; // Try scanf the IPv4 host first - if (sscanf (addressString, "%u.%u.%u.%u", &parts[0], &parts[1], &parts[2], &parts[3])) + int parts[4]; + if (std::sscanf(addressString.data(), "%d.%d.%d.%d", &parts[0], &parts[1], &parts[2], &parts[3])) { - for (int i : range(4)) - value.set_octet (i, parts[i]); + value = net::ip_address{}; + for (unsigned char i = 0; i < 4; i += 1) + { + if (parts[i] >= 0 and parts[i] < 256) + { + ip_set_octet(&*value, i, parts[i]); + } + else + { + value.reset(); + errorStream << "IP address value out of range"; + break; + } + } } else { // Possibly a hostname, try resolve it - value = IPAddress::resolve (addressString); + value = ip_resolve_hostname(addressString, errorStream); } - - if (colonpos != -1) - value.port = (unsigned short) input.mid (colonpos + 1, -1).toInt(); - + if (value.has_value() and colonpos != -1) + { + std::optional port_opt = ip_parse_port(&input_string.data()[colonpos + 1], errorStream); + if (port_opt.has_value()) + { + value->port = *port_opt; + } + else + { + value.reset(); + } + } return value; } -// ----------------------------------------------------------------------------- -// -IPAddress IPAddress::resolve (String node) -{ - AddrInfo hints; - AddrInfo* lookup; - memset (&hints, 0, sizeof hints); - hints.ai_family = AF_INET; - - if (getaddrinfo (node, nullptr, &hints, &lookup) != 0) - throw StringParseError ("unknown host " + node); - - IPAddress result; - assert (lookup != nullptr); - sockaddr_in* addr = reinterpret_cast (lookup[0].ai_addr); - result.host = ntohl (addr->sin_addr.s_addr); - result.port = ntohs (addr->sin_port); - freeaddrinfo (lookup); - return result; -} - END_ZFC_NAMESPACE \ No newline at end of file diff -r 060a13878ca0 -r be953e1621d9 sources/network/ipaddress.h --- a/sources/network/ipaddress.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/ipaddress.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,6 +29,7 @@ */ #pragma once +#include #include "../main.h" struct sockaddr; @@ -36,52 +37,27 @@ BEGIN_ZFC_NAMESPACE -struct IPAddress +namespace net { - enum - { - LOCALHOST = 0x7f000001 - }; - - enum WithPort - { - WITH_PORT, - NO_PORT - }; - - class StringParseError : public std::exception + using octet_t = std::uint8_t; + using port_t = std::uint16_t; + using host_t = std::uint32_t; + struct ip_address { - String m_message; - - public: - StringParseError (String message) : - m_message (message) {} - - const char* what() const throw() - { - return m_message.chars(); - } + net::host_t host = 0; + net::port_t port = 0; + bool operator<(const ip_address& other) const; + bool operator==(const ip_address& other) const; }; - - unsigned long host; - unsigned short port; - - IPAddress(); - IPAddress (unsigned long host, unsigned short port); - IPAddress (const IPAddress& other); - - bool compare (const IPAddress& other) const; - unsigned char octet (int n) const; - void set_octet (int n, unsigned char oct); - String to_string (WithPort withport = NO_PORT) const; - sockaddr_in to_sockaddr_in() const; - bool operator< (const IPAddress& other) const; - bool operator== (const IPAddress& other) const { return compare (other); } - bool operator!= (const IPAddress& other) const { return not compare (other); } - unsigned char operator[] (int n) const { return octet (n); } - - static IPAddress from_string (String input); - static IPAddress resolve (String node); -}; + constexpr ip_address localhost = {0x7f000001, 0}; + int ip_compare(const ip_address& one, const ip_address& other); + net::octet_t ip_octet(const ip_address& address, unsigned char n); + void ip_set_octet(ip_address* address, unsigned char n, net::octet_t octet); + std::optional ip_parse_port(const char* port_string, std::ostream& errorStream); + std::optional ip_resolve_hostname(const std::string& node, std::ostream& errorStream); + std::optional ip_resolve(const std::string& input_string, std::ostream &errorStream); + sockaddr_in ip_address_to_sockaddr_in(const ip_address& address); + std::string ip_address_to_string(const ip_address& address); +} END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/network/rconsession.cpp --- a/sources/network/rconsession.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/rconsession.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ #include #include "rconsession.h" #include "../interface.h" +#include "../md5.h" BEGIN_ZFC_NAMESPACE // ------------------------------------------------------------------------------------------------- @@ -41,10 +42,10 @@ m_adminCount(0), m_interface(nullptr) { - if (not m_socket.set_blocking(false)) + std::stringstream errors; + if (not m_socket.set_blocking(false, errors)) { - fprintf(stderr, "unable to set socket as non-blocking: %s\n", - m_socket.error_string().chars()); + fprintf(stderr, "unable to set socket as non-blocking: %s\n", errors.str().data()); exit(EXIT_FAILURE); } } @@ -55,7 +56,7 @@ // ------------------------------------------------------------------------------------------------- // -void RCONSession::connect(IPAddress address) +void RCONSession::connect(net::ip_address address) { m_address = address; m_state = RCON_CONNECTING; @@ -79,9 +80,15 @@ // ------------------------------------------------------------------------------------------------- // -void RCONSession::send(const ByteArray& packet) +bool RCONSession::send(const std::vector& packet) { - m_socket.send(m_address, packet); + std::stringstream errors; + const bool result = m_socket.send(m_address, packet, errors); + if (not result) + { + this->m_interface->printError("Network error: %s\n", errors.str().data()); + } + return result; } // ------------------------------------------------------------------------------------------------- @@ -112,8 +119,14 @@ } // Check for new packets in our socket - for (Datagram datagram; m_socket.read(datagram);) + std::stringstream errors; + for (net::Datagram datagram; m_socket.read(datagram, errors);) { + if (errors.tellp() > 0) + { + m_interface->printError("Network error: %s\n", errors.str().data()); + errors = {}; + } // Only process packets that originate from the game server. if (datagram.address == m_address) handlePacket(datagram.message); @@ -122,7 +135,7 @@ // ------------------------------------------------------------------------------------------------- // -void RCONSession::handlePacket(ByteArray& message) +void RCONSession::handlePacket(std::vector& message) { Bytestream stream(message); @@ -157,9 +170,9 @@ case SVRC_MESSAGE: { - String message = stream.readString(); - message.normalize(); - m_interface->printText("%s\n", message.chars()); + std::string message = stream.readString(); + normalize(message); + m_interface->printText("%s\n", message.data()); } break; @@ -177,9 +190,9 @@ for (int i = stream.readByte(); i > 0; --i) { - String message = stream.readString(); - message.normalize(); - m_interface->printText("--- %s\n", message.chars()); + std::string message = stream.readString(); + normalize(message); + m_interface->printText("--- %s\n", message.data()); } m_interface->print("End of previous messages.\n"); @@ -197,55 +210,55 @@ { unsigned int numCompletions = stream.readShort(); m_interface->print("%d completions for '%s'.\n", - int(numCompletions), m_lastTabComplete.chars()); + int(numCompletions), m_lastTabComplete.data()); } break; case SVRC_TABCOMPLETE: { - StringList completes; + std::vector completes; completes.resize(stream.readByte()); - for (String& completion : completes) + for (std::string& completion : completes) completion = stream.readString(); if (completes.size() == 1) { m_interface->tabComplete(m_lastTabComplete, completes[0]); } - else if (not completes.is_empty()) + else if (completes.size() > 0) { - m_interface->print("Completions for '%s':\n", m_lastTabComplete.chars()); + m_interface->print("Completions for '%s':\n", m_lastTabComplete.data()); - for (int i : range(0, completes.size(), 8)) + for (std::size_t i = 0; i < completes.size(); i += 8) { - Range spliceRange(i, min(i + 8, completes.size())); - StringList splice(completes.splice(spliceRange)); - m_interface->print("- %s\n", splice.join(", ").chars()); + const int end = min(i + 8, completes.size()); + std::vector splices = splice(completes, i, end); + m_interface->print("- %s\n", join_string_list(splices, ", ").data()); } } } break; case SVRC_WATCHINGCVAR: - m_interface->print ("You are now watching %s\n", stream.readString().chars()); - m_interface->print ("Its value is: %s\n", stream.readString().chars()); + m_interface->print ("You are now watching %s\n", stream.readString().data()); + m_interface->print ("Its value is: %s\n", stream.readString().data()); break; case SVRC_ALREADYWATCHINGCVAR: - m_interface->print ("You are already watching %s\n", stream.readString().chars()); + m_interface->print ("You are already watching %s\n", stream.readString().data()); break; case SVRC_WATCHCVARNOTFOUND: - m_interface->print ("CVar %s not found\n", stream.readString().chars()); + m_interface->print ("CVar %s not found\n", stream.readString().data()); break; case SVRC_CVARCHANGED: { String name = stream.readString(); String value = stream.readString(); - m_interface->print ("The value of CVar %s", name.chars()); - m_interface->print (" is now %s\n", value.chars()); + m_interface->print ("The value of CVar %s", name.data()); + m_interface->print (" is now %s\n", value.data()); // If sv_hostname changes, update the titlebar if (name == "sv_hostname") @@ -257,7 +270,7 @@ break; case SVRC_YOUREDISCONNECTED: - m_interface->print ("You have been disconnected: %s\n", stream.readString().chars()); + m_interface->print ("You have been disconnected: %s\n", stream.readString().data()); m_interface->disconnected(); break; } @@ -266,7 +279,7 @@ catch (std::exception& e) { m_interface->printWarning("Couldn't process packet: %s\n", e.what()); - m_interface->printWarning("Packet contents was: %s\n", message.quote().chars()); + m_interface->printWarning("Packet contents was: %s\n", quote(message).data()); m_interface->printWarning("Stream position in payload was: %d\n", stream.position()); } } @@ -279,10 +292,10 @@ { case SVRCU_PLAYERDATA: { - StringList players; + std::vector players; for (int i = packet.readByte(); i > 0; --i) - players.append(packet.readString()); + players.push_back(packet.readString()); m_interface->setPlayerNames(players); } @@ -306,7 +319,7 @@ // ------------------------------------------------------------------------------------------------- // -UDPSocket* RCONSession::getSocket() +net::UDPSocket* RCONSession::getSocket() { return &m_socket; } @@ -315,7 +328,7 @@ // void RCONSession::sendHello() { - m_interface->print("Connecting to %s...\n", m_address.to_string(IPAddress::WITH_PORT).chars()); + m_interface->print("Connecting to %s...\n", net::ip_address_to_string(m_address).data()); send({CLRC_BEGINCONNECTION, RCON_PROTOCOL_VERSION}); bumpLastPing(); } @@ -325,17 +338,17 @@ void RCONSession::sendPassword() { m_interface->print("Authenticating...\n"); - ByteArray message; + std::vector message; Bytestream stream(message); stream.writeByte(CLRC_PASSWORD); - stream.writeString((m_salt + m_password).md5()); + stream.writeString(md5((m_salt + m_password).data())); send(message); bumpLastPing(); } // ------------------------------------------------------------------------------------------------- // -void RCONSession::setPassword(const String& password) +void RCONSession::setPassword(const std::string& password) { m_password = password; } @@ -359,12 +372,12 @@ // ------------------------------------------------------------------------------------------------- // Returns true if the message was successfully sent. // -bool RCONSession::sendCommand(const String& commandString) +bool RCONSession::sendCommand(const std::string& commandString) { - if (m_state != RCON_CONNECTED or commandString.isEmpty()) + if (m_state != RCON_CONNECTED or commandString.empty()) return false; - ByteArray message; + std::vector message; Bytestream stream(message); stream.writeByte(CLRC_COMMAND); stream.writeString(commandString); @@ -382,7 +395,7 @@ // ------------------------------------------------------------------------------------------------- // -const IPAddress& RCONSession::address() const +const net::ip_address& RCONSession::address() const { return m_address; } @@ -396,18 +409,18 @@ // ------------------------------------------------------------------------------------------------- // -const String& RCONSession::getLevel() const +const std::string& RCONSession::getLevel() const { return m_level; } // ------------------------------------------------------------------------------------------------- // -void RCONSession::requestTabCompletion(const String& part) +void RCONSession::requestTabCompletion(const std::string& part) { if (m_serverProtocol >= 4) { - ByteArray message; + std::vector message; Bytestream stream(message); stream.writeByte(CLRC_TABCOMPLETE); stream.writeString(part); @@ -417,7 +430,7 @@ } else { - m_interface->print("This server does not support tab-completion\n", m_serverProtocol); + m_interface->print("This server does not support tab-completion\n"); } } @@ -432,22 +445,22 @@ // void RCONSession::requestWatch(const String& cvar) { - StringList cvars; - cvars.append(cvar); + const std::vector cvars{{cvar}}; requestWatch(cvars); } // ------------------------------------------------------------------------------------------------- // -void RCONSession::requestWatch(const StringList& cvars) +void RCONSession::requestWatch(const std::vector& cvars) { - ByteArray message; + std::vector message; Bytestream stream(message); stream.writeByte(CLRC_WATCHCVAR); - - for (const String& cvar : cvars) - stream.writeString(cvar.normalized()); - + for (String cvar : cvars) + { + normalize(cvar); + stream.writeString(cvar); + } stream.writeString(""); send(message); } diff -r 060a13878ca0 -r be953e1621d9 sources/network/rconsession.h --- a/sources/network/rconsession.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/rconsession.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -101,40 +101,40 @@ RCONSession(); ~RCONSession(); - const IPAddress& address() const; + const net::ip_address& address() const; void bumpLastPing(); - void connect(IPAddress address); + void connect(net::ip_address address); void disconnect(); int getAdminCount() const; - const String& getLevel() const; - UDPSocket* getSocket(); + const std::string& getLevel() const; + net::UDPSocket* getSocket(); RCONSessionState getState() const; - void handlePacket(ByteArray& message); + void handlePacket(std::vector& message); bool isActive() const; void processServerUpdates(Bytestream& packet); - void requestTabCompletion(const String& part); + void requestTabCompletion(const std::string& part); void requestWatch (const String& cvar); void requestWatch (const StringList& cvars); - void send(const ByteArray& packet); - bool sendCommand(const String& commandString); + bool send(const std::vector& packet); + bool sendCommand(const std::string& commandString); void sendHello(); void sendPassword(); void setInterface(class Interface* interface); - void setPassword(const String& password); + void setPassword(const std::string& password); void tick(); private: RCONSessionState m_state; - IPAddress m_address; - UDPSocket m_socket; + net::ip_address m_address; + net::UDPSocket m_socket; time_t m_lastPing; - String m_password; - String m_salt; + std::string m_password; + std::string m_salt; int m_serverProtocol; - String m_hostname; + std::string m_hostname; int m_adminCount; - String m_level; - String m_lastTabComplete; + std::string m_level; + std::string m_lastTabComplete; class Interface* m_interface; }; diff -r 060a13878ca0 -r be953e1621d9 sources/network/udpsocket.cpp --- a/sources/network/udpsocket.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/udpsocket.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -47,115 +47,119 @@ BEGIN_ZFC_NAMESPACE -char UDPSocket::HuffmanBuffer[131072]; +static char HuffmanBuffer[131072]; -// ----------------------------------------------------------------------------- -// -UDPSocket::UDPSocket() : - m_socket (socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) {} +net::UDPSocket::UDPSocket() : + file_descriptor{::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)} {} -// ----------------------------------------------------------------------------- -// -UDPSocket::~UDPSocket() +net::UDPSocket::~UDPSocket() { #ifdef _WIN32 - closesocket (m_socket); + ::closesocket(m_socket); #else - close (m_socket); + ::close(this->file_descriptor); #endif } -// ------------------------------------------------------------------------------------------------- -// -bool UDPSocket::set_blocking (bool a) +bool net::UDPSocket::set_blocking(bool a, std::ostream& errors) { #ifndef _WIN32 - int flags = fcntl (m_socket, F_GETFL, 0); - int newflags = a ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); - - if (flags < 0 || fcntl (m_socket, F_SETFL, newflags) != 0) + int flags = ::fcntl(this->file_descriptor, F_GETFL, 0); + int newflags = a ?(flags & ~O_NONBLOCK) :(flags | O_NONBLOCK); + if (flags < 0 || ::fcntl(this->file_descriptor, F_SETFL, newflags) != 0) { - m_error = "Unable to set socket as non-blocking"; + errors << "Unable to set the UDP socket as non-blocking"; return false; } - - return true; + else + { + return true; + } #else unsigned long mode = a ? 0 : 1; - if (ioctlsocket (m_socket, FIONBIO, &mode) != 0) + if (::ioctlsocket(m_socket, FIONBIO, &mode) != 0) { - m_error = strerror (errno); + errors << strerror(errno); return false; } - - return true; + else + { + return true; + } #endif } -// ------------------------------------------------------------------------------------------------- -// -bool UDPSocket::bind (unsigned short port) +bool net::UDPSocket::bind(const net::port_t port, std::ostream& errors) { sockaddr_in svaddr; - memset (&svaddr, 0, sizeof svaddr); + std::memset(&svaddr, 0, sizeof svaddr); svaddr.sin_family = AF_INET; - svaddr.sin_port = htons (port); - svaddr.sin_addr.s_addr = htonl (INADDR_ANY); - - if (::bind (m_socket, reinterpret_cast (&svaddr), sizeof svaddr) == -1) + svaddr.sin_port = htons(port); + svaddr.sin_addr.s_addr = htonl(INADDR_ANY); + if (::bind(this->file_descriptor, reinterpret_cast(&svaddr), sizeof svaddr) == -1) { - m_error = String ("Couldn't bind to port ") + String::fromNumber (port); + errors << "Couldn't bind to port "s + std::to_string(port); return false; } - - return true; + else + { + return true; + } } -// ------------------------------------------------------------------------------------------------- -// -bool UDPSocket::read (Datagram& datagram) +bool net::UDPSocket::read(Datagram& datagram, std::ostream& errors) { sockaddr_in claddr; socklen_t socklen = sizeof claddr; - int length = ::recvfrom (m_socket, HuffmanBuffer, sizeof HuffmanBuffer, 0, - reinterpret_cast (&claddr), &socklen); - + const int length = ::recvfrom( + this->file_descriptor, + zfc::HuffmanBuffer, + sizeof zfc::HuffmanBuffer, + 0, + reinterpret_cast(&claddr), + &socklen + ); if (length == -1) { if (errno != EWOULDBLOCK) - m_error = String ("recvfrom error: ") + strerror (errno); - + { + errors << std::string("recvfrom error: ") + std::strerror(errno); + } return false; } - unsigned char decodedPacket[MAX_DATAGRAM_LENGTH]; int decodedLength = sizeof decodedPacket; - HUFFMAN_Decode (reinterpret_cast (HuffmanBuffer), + ::HUFFMAN_Decode(reinterpret_cast(HuffmanBuffer), decodedPacket, length, &decodedLength); - datagram.address.host = ntohl (claddr.sin_addr.s_addr); - datagram.address.port = ntohs (claddr.sin_port); - datagram.message = ByteArray(decodedPacket, decodedLength); + datagram.address.host = ntohl(claddr.sin_addr.s_addr); + datagram.address.port = ntohs(claddr.sin_port); + datagram.message = std::vector{&decodedPacket[0], &decodedPacket[decodedLength]}; return true; } -// ------------------------------------------------------------------------------------------------- -// -bool UDPSocket::send (const IPAddress& address, const ByteArray& data) +bool net::UDPSocket::send(const net::ip_address& address, const std::vector& data, std::ostream& errors) { int encodedlength = sizeof HuffmanBuffer; - HUFFMAN_Encode (data.data(), reinterpret_cast (HuffmanBuffer), data.size(), &encodedlength); - sockaddr_in claddr = address.to_sockaddr_in(); - int res = ::sendto (m_socket, HuffmanBuffer, encodedlength, 0, - reinterpret_cast (&claddr), sizeof claddr); - - if (res == -1) + ::HUFFMAN_Encode(data.data(), reinterpret_cast(HuffmanBuffer), data.size(), &encodedlength); + sockaddr_in claddr = net::ip_address_to_sockaddr_in(address); + const int send_result = ::sendto( + this->file_descriptor, + HuffmanBuffer, + encodedlength, + 0, + reinterpret_cast(&claddr), + sizeof claddr + ); + if (send_result == -1) { - m_error = String ("Unable to launch packet: ") + strerror (errno); + errors << "Unable to launch packet: "s + std::strerror(errno); return false; } - - return true; + else + { + return true; + } } END_ZFC_NAMESPACE \ No newline at end of file diff -r 060a13878ca0 -r be953e1621d9 sources/network/udpsocket.h --- a/sources/network/udpsocket.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/network/udpsocket.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,35 +34,31 @@ #include "bytestream.h" BEGIN_ZFC_NAMESPACE -enum { MAX_DATAGRAM_LENGTH = 5120 }; +namespace net +{ + constexpr int MAX_DATAGRAM_LENGTH = 5120; + struct Datagram; + class UDPSocket; +} -struct Datagram +struct net::Datagram { - ByteArray message; - IPAddress address; + std::vector message; + net::ip_address address; }; // ------------------------------------------------------------------------------------------------- // -class UDPSocket +class net::UDPSocket { public: UDPSocket(); virtual ~UDPSocket(); - - bool bind (unsigned short port); - bool read (Datagram& datagram); - bool send (const IPAddress& address, const ByteArray& data); - bool set_blocking (bool a); - const String& error_string() const { return m_error; } - int file_descriptor() const { return m_socket; } - -private: - static char HuffmanBuffer[131072]; - - IPAddress m_addr; - String m_error; - int m_socket; + [[nodiscard]] bool bind(port_t port, std::ostream &errors); + [[nodiscard]] bool read(Datagram& datagram, std::ostream& errors); + [[nodiscard]] bool send(const ip_address& address, const std::vector& data, std::ostream &errors); + [[nodiscard]] bool set_blocking(bool a, std::ostream &errors); + const int file_descriptor; }; END_ZFC_NAMESPACE \ No newline at end of file diff -r 060a13878ca0 -r be953e1621d9 sources/range.h --- a/sources/range.h Wed Jan 27 12:41:50 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -/* - Copyright 2014 - 2016 Teemu Piippo - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once -#include -#include -#include "basics.h" -BEGIN_ZFC_NAMESPACE - -// -// ------------------------------------------------------------------------------------------------- -// - -template -class Range -{ - T m_a; - T m_b; - T m_step; - -public: - struct Iterator - { - T value; - T step; - - Iterator (T value, T step) : - value (value), - step (step) {} - - T& operator*() - { - return value; - } - - bool operator== (const Iterator& other) const - { - return value == other.value; - } - - bool operator!= (const Iterator& other) const - { - return value < other.value; - } - - Iterator& operator++() - { - value += step; return *this; - } - }; - - Range (T a, T b, T step = 1) : - m_a (a), - m_b (b), - m_step (step) - { - check_bounds(); - } - - Range() : - m_a (T()), - m_b (T()) {} - - Iterator begin() const - { - return Iterator(min(), m_step); - } - - Iterator end() const - { - return Iterator(max(), m_step); - } - - T min() const - { - return m_a; - } - - T max() const - { - return m_b; - } - - T step() const - { - return m_step; - } - - void check_bounds() - { - if (m_b < m_a) - std::swap (m_a, m_b); - } - - bool contains (T c) const - { - return c >= m_a - and c <= m_b; - } - - bool contains_exclusively (T c) const - { - return c > m_a - and c < m_b; - } - - bool overlaps (Range const& other) const - { - return contains (other.m_a) - or contains (other.m_b); - } - - bool operator== (Range const& other) const - { - return m_a == other.m_a - and m_b == other.m_b; - } - - bool operator!= (Range const& other) const - { - return not operator== (other); - } -}; - -template -Range range(T a, T b, T step = 1) -{ - return Range(a, b, step); -} - -template -Range range(T b) -{ - return Range(T(), b); -} - -END_ZFC_NAMESPACE diff -r 060a13878ca0 -r be953e1621d9 sources/version.cpp --- a/sources/version.cpp Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/version.cpp Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without diff -r 060a13878ca0 -r be953e1621d9 sources/version.h --- a/sources/version.h Wed Jan 27 12:41:50 2021 +0200 +++ b/sources/version.h Wed Jan 27 19:48:41 2021 +0200 @@ -1,5 +1,5 @@ /* - Copyright 2014 - 2016 Teemu Piippo + Copyright 2014 - 2021 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without