From: J. Hamatoma Date: Sun, 24 Nov 2013 08:50:29 +0000 (+0100) Subject: initial state X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=39cba34cc56ba11143db20f386e10a75e9d9d6c1;p=reqt initial state --- 39cba34cc56ba11143db20f386e10a75e9d9d6c1 diff --git a/rplqt/core/rplconfig.cpp b/rplqt/core/rplconfig.cpp new file mode 100644 index 0000000..9b9091f --- /dev/null +++ b/rplqt/core/rplconfig.cpp @@ -0,0 +1,213 @@ +#include "rplcore.hpp" + +enum Locations{ + LOC_WRITE_1 = RPL_FIRST_OF(RPLMODULE_CONFIG), + LOC_WRITE_2, + LOC_READ_1, + LOC_READ_2, +}; + + +/** + * Constructor. + * + * Initializes the logger and reads the configuration file + * + * @param file name of the configuration file. May be NULL + * @param readOnly true: the configuration must not be written + * @param logger NULL or a logger + */ +RplConfig::RplConfig(const char* file, bool readOnly, RplLogger* logger) : + m_file(file), + m_lineList(), + m_readOnly(readOnly), + m_logger(logger), + m_ownLogger(logger == NULL) +{ + if (logger == NULL){ + initLogger(); + } + if (file != NULL) + read(file); +} + + +/** + * Destructor. + * + * Frees the resources. + */ +RplConfig::~RplConfig(){ + if (m_ownLogger) + delete m_logger; + m_logger = NULL; +} + +/** + * Inititializes a logger. + */ +void RplConfig::initLogger(){ + m_logger = new RplLogger(); + RplMemoryAppender* appender = new RplMemoryAppender(); + appender->setAutoDelete(true); + m_logger->addAppender(appender); + + RplStreamAppender* appender2 = new RplStreamAppender(stdout); + appender2->setAutoDelete(true); + m_logger->addAppender(appender2); +} + +/** + * Returns configuration value as an integer. + * + * @param key key of the wanted value + * @param defaultValue if the key does not exist this is the result + * @return defaultValue: key does not exist + * otherwise: the value assigned to key + */ +int RplConfig::asInt(const char* key, int defaultValue) const{ + int rc = defaultValue; + if (contains(key)){ + rc = atoi((*this)[key]); + } + return rc; +} + +/** + * Returns the configuration value as a boolean. + * + * @param key key of the wanted value + * @param defaultValue if the key does not exist this is the result + * @return defaultValue: key does not exist + * otherwise: the value assigned to key + */ +bool RplConfig::asBool(const char* key, bool defaultValue) const{ + bool rc = defaultValue; + if (contains(key)){ + QByteArray value = (*this)[key].toLower(); + rc = value == "1" || value == "y" || value == "yes" || value == "t" || value == "true"; + } + + return rc; +} + +/** + * Returns a configuration value. + * + * @param key key of the wanted value + * @param defaultValue if the key does not exist this is the result + * @return defaultValue: key does not exist + */ +QByteArray RplConfig::asString(const char* key, const char* defaultValue){ + QByteArray rc = defaultValue; + if (contains(key)){ + rc = (*this)[key]; + } + return rc; +} + +/** + * Reads a configuration file. + * + * @param file file to read. + * @return true: OK
+ * false: error occurred + */ +bool RplConfig::read(const char* file){ + bool rc = true; + m_lineList.reserve(1024); + FILE* fp = fopen(file, "r"); + if (fp == NULL){ + m_logger->logv(LOG_ERROR, LOC_READ_1, "cannot read: %s", file); + rc = false; + } else{ + char line[64000]; + char* separator; + int lineNo = 0; + while(fgets(line, sizeof line, fp) != NULL){ + lineNo++; + m_lineList.append(line); + if(isalnum(line[0]) && (separator = strchr(line, '=')) != NULL){ + QByteArray key(line, separator - line); + QByteArray value(separator + 1); + key = key.trimmed(); + value = value.trimmed(); + if (contains(key)) + m_logger->logv(LOG_WARNING, LOC_READ_2, "defined more than once: %s-%d: %s", + file, lineNo, line); + else + insert(key, value); + } + } + } + return rc; +} + +/** + * Writes a configuration file. + * + * @param file file to write. + * @return true: OK
+ * false: error occurred + */ +bool RplConfig::write(const char* file){ + bool rc = false; + if (m_readOnly) + m_logger->log(LOG_ERROR, LOC_WRITE_1, "cannot write: (readonly"); + else{ + m_logger->log(LOG_ERROR, LOC_WRITE_2, "not implemented: write()"); + } + return rc; +} + +// ------------------ +#if ! defined RPL_TEST +#define RPL_TEST +#endif +#ifdef RPL_TEST +#include "core/rpltest.hpp" +class ConfigTest : public RplTest +{ +public: + ConfigTest() : RplTest("RplConfig"){} + +public: + void testBasic(){ + QByteArray fn = getTempFile("test.data", "config"); + RplString::write( fn, "#comment\na=1\nb.1==x\n#=\nB=zzz"); + RplConfig config(fn.constData()); + checkE(3, config.size()); + checkE("1", config["a"]); + checkE("=x", config["b.1"]); + checkE("zzz", config["B"]); + } + void testAsX(){ + QByteArray fn = getTempFile("test.data", "config"); + RplString::write( fn, "i=123\nb=1\nb2=true\nb3=yes\ns=abc"); + RplConfig config(fn.constData()); + checkE(5, config.size()); + checkE(123, config.asInt("i", -1)); + checkE(-1, config.asInt("I", -1)); + checkT(config.asBool("b", false)); + checkT(config.asBool("b2", false)); + checkT(config.asBool("b3", false)); + checkT(config.asBool("-", true)); + checkF(config.asBool("-", false)); + checkE("abc", config.asString("s", "x")); + checkE("x", config.asString("S", "x")); + } + + virtual void doIt(){ + testAsX(); + testBasic(); + + } +}; + +#endif +void testRplConfig(){ +#ifdef RPL_TEST + ConfigTest test; + test.run(); +#endif +} diff --git a/rplqt/core/rplconfig.hpp b/rplqt/core/rplconfig.hpp new file mode 100644 index 0000000..beab9dd --- /dev/null +++ b/rplqt/core/rplconfig.hpp @@ -0,0 +1,41 @@ +#ifndef RPLCONFIG_HPP +#define RPLCONFIG_HPP + +/** + * Imports and exports a configuration file into a QHash instance. + * + * The format of the file:
+ * DEFS or COMMENTS + * + * DEFS ::= KEY=VALUE + * + * KEY is a string starting with an alphanumeric character and does not contain a '=' + * + * VALUE is a string + */ +class RplConfig : public QHash +{ +public: + RplConfig(const char* file = NULL, bool readOnly = true, RplLogger* logger = NULL); + virtual ~RplConfig(); + +public: + bool read(const char* file); + bool write(const char* file); + void clear(); + const QVector& getLines() const; + int asInt(const char* key, int defaultValue) const; + bool asBool(const char* key, bool defaultValue) const; + QByteArray asString(const char* key, const char* defaultValue); +private: + void initLogger(); +private: + const char* m_file; + QVector m_lineList; + bool m_readOnly; + RplLogger* m_logger; + // true: the logger must be destroyed in the destructor + bool m_ownLogger; +}; + +#endif // RPLCONFIG_HPP diff --git a/rplqt/core/rplcontainer.cpp b/rplqt/core/rplcontainer.cpp new file mode 100644 index 0000000..39e0221 --- /dev/null +++ b/rplqt/core/rplcontainer.cpp @@ -0,0 +1,515 @@ +#include "core/rplcore.hpp" + +enum { + // 11000 + LOC_FILL_1 = RPLMODULE_CONTAINER * 1000, + LOC_FILL_2, + LOC_FILL_3, + LOC_NEXT_BAG_1, + LOC_NEXT_ITEM_1, + LOC_NEXT_ITEM_2 = 11005, + LOC_NEXT_INT_1, + LOC_NEXT_ITEM_3, + LOC_NEXT_BAG_2, +}; + +const char* RplContainer::MAGIC_1 = "Rpl&1"; + +RplContainer::RplContainer(size_t sizeHint) : + m_data(""), + m_countBags(0), + m_typeList(""), + m_ixItem(0), + m_ixBag(0), + m_readPosition(NULL) +{ +} +/** + * Adds an type to the type list. + * @param tag the type tag + */ +void RplContainer::addType(type_tag_t tag){ + if (m_countBags == 0) + startBag(); + if (m_countBags == 1) + m_typeList.append((char) tag); +} + +/** + * Starts a new bag. + */ +void RplContainer::startBag(){ + m_countBags++; + m_ixBag = 0; +} +/** + * Adds a character to the current bag. + * + * @param value value to insert + */ +void RplContainer::addChar(char value){ + addType(TAG_CHAR); + //if (m_typeList.at(m_ixBag) != TAG_INT) + // RplLogger::logAndThrow(LOG_ERROR, __FILE__, __LINE__, 1, "unexpected type: %c instead of c", m_typeList.at(m_ixBag)); + m_data.append(value); +} +/** + * Adds an integer to the current bag. + * + * @param value value to add + */ +void RplContainer::addInt(int value){ + addType(TAG_INT); + char buffer[64]; + char* ptr = buffer; + if (value < 0){ + *ptr++ = '-'; + value = - value; + } + snprintf(ptr, sizeof buffer - 1, "%x ", value); + m_data.append(buffer); +} +/** + * Adds an integer to the current bag. + * + * @param value value to add + */ +void RplContainer::addInt(qint64 value){ + addType(TAG_INT); + char buffer[128]; + snprintf(buffer, sizeof buffer, "%llx ", value); + m_data.append(buffer); +} + +/** + * Adds a string to the current bag. + * + * @param value value to add + */ +void RplContainer::addString(const char* value){ + addType(TAG_STRING); + // store with trailing '\0' + m_data.append(value, strlen(value) + 1); +} +/** + * Adds binary data to the current bag. + * @param value binary data + * @param size size of the binary data in bytes + */ +void RplContainer::addData(byte_t* value, size_t size){ + if (size <= 255){ + addType(TAG_DATA255); + m_data.append((char) size); + } else if (size <= 0xffff){ + addType(TAG_DATA64K); + m_data.append((char) (size / 256)); + m_data.append((char) (size % 256)); + m_data.append((const char*) value, size); + } else { + addType(TAG_DATA4G); + m_data.append((char) (size / 256 / 256 / 256)); + m_data.append((char) (size / 256 / 256 % 256)); + m_data.append((char) (size / 256 % 256)); + m_data.append((char) (size % 256)); + m_data.append((const char*) value, size); + } + addType(TAG_DATA255); + +} + +/** + * Returns the container byte buffer. + * + * @return the container as a byte array + */ +const QByteArray& RplContainer::getData() { + if (m_typeList.length() != 0){ + char buffer[128]; + // RPL&1 0a b5[2]cis: !12 + snprintf(buffer, sizeof buffer, "%x[%d]%s:", (unsigned int) m_data.length(), m_countBags, m_typeList.data()); + char header[128+8]; + snprintf(header, sizeof header, "%s%02x%s", MAGIC_1, (unsigned int) strlen(buffer), buffer); + m_data.insert(0, header); + } + return m_data; +} + +/** + * Fills the container with an byte array. + * + * @param data the container as a byte array + */ +void RplContainer::fill(const QByteArray& data){ + m_data = data; + const char* ptr = m_data.data(); + if (strncmp(ptr, MAGIC_1, strlen(MAGIC_1)) != 0) + throw RplInvalidDataException(LOG_ERROR, LOC_FILL_1, "container has no magic", data.data(), data.length()); + ptr += strlen(MAGIC_1); + unsigned int headerSize = 0; + if (sscanf(ptr, "%02x", &headerSize) != 1) + throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no header size", ptr, 2); + ptr += 2; + + unsigned int dataSize = 0; + unsigned int countBags = 0; + if (sscanf(ptr, "%x[%x]", &dataSize, &countBags) != 2) + throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no data_size[bag_count]", ptr, 16); + m_countBags = countBags; + ptr = strchr(ptr, ']') + 1; + const char* end = ptr + strspn(ptr, "cisdDX!"); + if (end == ptr || *end != ':'){ + throw RplInvalidDataException(LOG_ERROR, LOC_FILL_2, "container has no valid typelist", ptr, 16); + } + m_typeList.clear(); + m_typeList.append(ptr, end - ptr); + m_ixBag = -1; + m_readPosition = (byte_t*) end + 1; + + +} +/** + * Returns the number of bags in the container. + * @return the number of bags + */ +int RplContainer::getCountBags() const{ + return m_countBags; +} +/** + * Sets the begin of the new bag. + */ +void RplContainer::nextBag(){ + if (m_ixItem < m_typeList.length() && m_ixItem != -1) + throw RplException(LOG_ERROR, LOC_NEXT_BAG_1, NULL, + "end of bag not reached: remaining items: %s", + m_typeList.data() + m_ixItem); + m_ixItem = 0; + m_ixBag++; + if (m_ixBag >= m_countBags) + throw RplException(LOG_ERROR, LOC_NEXT_BAG_2, NULL, + "no more bags: %d", m_ixBag); +} +/** + * Sets the next item. + * + * @param expected the expected data type + */ +void RplContainer::nextItem(type_tag_t expected){ + if (m_ixBag < 0){ + m_ixBag = 0; + m_ixItem = 0; + } + if (m_ixItem >= m_typeList.length()) + throw RplException(LOG_ERROR, LOC_NEXT_ITEM_1, "no more items in the bag"); + type_tag_t current = (type_tag_t) m_typeList.at(m_ixItem); + // Unify all data types: + if (current == TAG_DATA4G || current == TAG_DATA64K) + current = TAG_DATA255; + if (current != expected) + throw RplException(LOG_ERROR, LOC_NEXT_ITEM_2, NULL, "current item is a %c, not a %c", + (char) m_typeList.at(m_ixItem), (char) expected); + m_ixItem++; + if (m_readPosition > (byte_t*) (m_data.data() + m_data.length())) + throw RplException(LOG_ERROR, LOC_NEXT_ITEM_3, NULL, "container size too small. Bag: %d of %d Item: %d of %d", + 1 + m_ixBag, m_countBags, 1 + m_ixItem, m_typeList.length()); +} + +/** + * Reads the next character from the current item in the current bag. + * + * @return the next char from the container + */ +char RplContainer::nextChar(){ + nextItem(TAG_CHAR); + char rc = *m_readPosition++; + return rc; +} + +/** + * Reads the next integer from the current item in the current bag. + * + * @return the next integer from the container + */ +int RplContainer::nextInt(){ + nextItem(TAG_INT); + bool isNegativ = *m_readPosition == '-'; + if (isNegativ) + m_readPosition++; + unsigned int value = 0; + if (sscanf((const char*) m_readPosition, "%x ", &value) != 1) + throw RplInvalidDataException(LOG_ERROR, LOC_NEXT_INT_1, "not a hex_number", m_readPosition, 16); + m_readPosition = (byte_t*) strchr((const char*) m_readPosition, ' ') + 1; + if (isNegativ) + value = - value; + return value; +} +/** + * Reads the next integer from the current item in the current bag. + * + * @return the next integer from the container + */ +qint64 RplContainer::nextInt64(){ + nextItem(TAG_INT); + bool isNegativ = *m_readPosition == '-'; + if (isNegativ) + m_readPosition++; + qint64 value = 0; + if (sscanf((const char*) m_readPosition, "%llx ", &value) != 1) + throw RplInvalidDataException(LOG_ERROR, LOC_NEXT_INT_1, "not a hex_number", m_readPosition, 16); + m_readPosition = (byte_t*) strchr((const char*) m_readPosition, ' ') + 1; + if (isNegativ) + value = - value; + return value; +} + +/** + * Reads the next string from the current item in the current bag. + * + * @return the next '\0' delimited string from the container + */ +const char* RplContainer::nextString(){ + nextItem(TAG_STRING); + const char* rc = (const char*) m_readPosition; + m_readPosition += strlen(rc) + 1; + return rc; +} + +/** + * Reads the next string from the current item in the current bag. + * + * @param data OUT: the next data item from the container + * @param append true: the item data will be appended to data
+ * false: data contains the item data only + * @return the size of the read data + */ +size_t RplContainer::nextData(QByteArray& data, bool append){ + nextItem(TAG_DATA255); + type_tag_t tag = (type_tag_t) m_typeList.at(m_ixItem - 1); + size_t length = 0; + switch(tag){ + case TAG_DATA4G: + for (int ix = 3; ix >= 0; ix--){ + length = 256 * length + m_readPosition[ix]; + } + m_readPosition += 4; + break; + case TAG_DATA64K: + length = *m_readPosition++ * 256; + length += *m_readPosition++; + break; + case TAG_DATA255: + length = *m_readPosition++; + break; + default: + break; + } + if (! append) + data.clear(); + data.append((const char*) m_readPosition, length); + m_readPosition += length; + return length; +} + +/** + * Return an integer as an QByteArray. + * + * @param value value to convert + * @param format format like in sprintf() + * @return the ascii form of the value + */ +QByteArray RplContainer::toNumber(int value, const char* format){ + char buffer[128]; + snprintf(buffer, sizeof buffer, format, value); + return QByteArray(buffer); +} + +/** + * Returns a string with a given maximum length. + * + * @param source the source + * @param maxLength the maximum length of the result + * @param buffer Out: used if length of the result is shorter + * @param appendix if the result is cut this string will be appended.
+ * May be NULL. + * @return source: the source is enough short
+ * the prefix of source with the given length + */ +const QByteArray& RplContainer::cutString(const QByteArray& source, int maxLength, + QByteArray& buffer, const char* appendix){ + QByteArray& rc = source.length() <= maxLength ? (QByteArray&) source : buffer; + if (source.length() > maxLength){ + buffer = source.left(maxLength); + if (appendix != NULL && appendix[0] != '\0') + buffer.append(appendix); + } + return rc; +} + +/** + * Builds a hexadecimal dump. + * + * Format: a sequence of hex digits followed by the ascii interpretation. + * + * Example: "42 30 61 B0a" + * + * @param data data to convert + * @param length length of data + * @param bytesPerLine one line containes so many bytes of data + * @return the hex dump + */ +QByteArray RplContainer::hexDump(byte_t* data, int length, int bytesPerLine){ + QByteArray rc; + rc.reserve(length * 3 + length / bytesPerLine * 2 + 1 + 100); + int ixData = 0; + int col; + char buffer[16]; + // full filled lines: + for (int lineNo = 0; lineNo < length / bytesPerLine * bytesPerLine; lineNo++){ + for (col = 0; col < bytesPerLine; col++){ + snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]); + rc.append(buffer); + } + rc.append(' '); + for (col = 0; col < bytesPerLine; col++){ + byte_t cc = data[ixData + col]; + rc.append(cc > ' ' && cc < 128 ? (char) cc : '.'); + } + ixData += bytesPerLine; + rc.append('\n'); + } + // incomplete last line: + int restBytes = length - ixData - 1; + if (restBytes > 0){ + for (col = 0; col < restBytes; col++){ + snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]); + rc.append(buffer); + } + for (col = restBytes; col < bytesPerLine; col++){ + rc.append(" "); + } + for (col = 0; col < restBytes; col++){ + byte_t cc = data[ixData + col]; + rc.append(cc > ' ' && cc < 128 ? (char) cc : '.'); + } + rc.append('\n'); + } + return rc; +} + +QByteArray RplContainer::dump(const char* title, + int maxBags, int maxStringLength, int maxBlobLength, + char separatorItems){ + QByteArray rc; + rc.reserve(64000); + rc.append("=== ").append(title).append('\n'); + rc.append("Bags: ").append(toNumber(m_countBags)); + rc.append(" Types: ").append(m_typeList).append('\n'); + // save the current state: + int safeIxBag = m_ixBag; + int safeIxItem = m_ixItem; + m_ixBag = -1; + m_ixItem = 0; + int iValue; + QByteArray sValue; + if (maxBags > m_countBags) + maxBags = m_countBags; + for (int ixBag = 0; ixBag < maxBags; ixBag++){ + rc.append("--- bag ").append(toNumber(ixBag)).append(":\n"); + nextBag(); + QByteArray item; + int maxLength; + for (int ixItem; ixItem < m_typeList.length(); ixItem++){ + type_tag_t currentType = (type_tag_t) m_typeList.at(ixItem); + switch(currentType){ + case TAG_CHAR: + rc.append(" c: ").append(nextChar()).append(separatorItems); + break; + case TAG_INT: + iValue = nextInt(); + rc.append(" i: ").append(toNumber(iValue)).append(" / "); + rc.append(toNumber(iValue, "%x")).append(separatorItems); + break; + case TAG_STRING: + sValue = nextString(); + if (sValue.length() > maxStringLength) + sValue = sValue.left(maxStringLength); + rc.append(" s: ").append(sValue).append(separatorItems); + break; + case TAG_DATA255: + case TAG_DATA64K: + case TAG_DATA4G: + nextData(item, false); + rc.append(' ').append((char) currentType).append(": ["); + rc.append(toNumber(item.length())).append("] "); + maxLength = item.length() < maxBlobLength ? item.length() : maxBlobLength; + rc.append(hexDump(item.data(), maxLength, 16)).append(separatorItems); + break; + default: + break; + } + } + } + + // restore the current state: + m_ixBag = safeIxBag; + m_ixItem = safeIxItem; + return rc; +} + +// ------------------ +#if ! defined RPL_TEST +#define RPL_TEST +#endif +#ifdef RPL_TEST +#include "core/rpltest.hpp" +class ContainerTest : public RplTest +{ +public: + ContainerTest() : RplTest("RplContainer"){} + +public: + void testBasic(){ + RplContainer container(256); + // RPL&1 0a b5[2]cis: !123 Nirwana <0> Y -ab34 A long string with an trailing '0' <0>
+ container.startBag(); + container.addChar('!'); + container.addInt(123); + container.addString("Nirwana"); + container.startBag(); + container.addChar('Y'); + container.addInt(-0xab34); + container.addString("A long string with an trailing '0'"); + + QByteArray data = container.getData(); + + RplContainer container2(256); + container2.fill(data); + check(2, container2.getCountBags()); + check('!', container2.nextChar()); + check(123, container2.nextInt()); + check("Nirwana", container2.nextString()); + container2.nextBag(); + check('Y', container2.nextChar()); + check(-0xab34, container2.nextInt()); + check("A long string with an trailing '0'", container2.nextString()); + } + void testHexDump(){ + QByteArray data("abc123\nxyz"); + check(QByteArray(""), + RplContainer::hexDump((RplContainer::byte_t*) data.data(), data.length(), 4)); + check(QByteArray(""), + RplContainer::hexDump((RplContainer::byte_t*)data.data(), data.length(), 16)); + } + + virtual void doIt(){ + testHexDump(); + testBasic(); + } +}; + +#endif +void testRplContainer(){ +#ifdef RPL_TEST + ContainerTest test; + test.run(); +#endif +} diff --git a/rplqt/core/rplcontainer.hpp b/rplqt/core/rplcontainer.hpp new file mode 100644 index 0000000..e0f5d18 --- /dev/null +++ b/rplqt/core/rplcontainer.hpp @@ -0,0 +1,92 @@ +#ifndef RPLCONTAINER_HPP +#define RPLCONTAINER_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#include +#endif +/** + * @brief Implements a portable data container. + * + * The container contains a list of "bags". + * Each bag contains a sequence of items (with a simple data type). + * The items are portable: transported to another architecture + * the item is restored correct (independent of big/little endian). + * + * Format: + * container ::= magic header_size_hex2 container_size_hex '[' list_count ']' bag_descr list_of_bags
+ * list_of_bag ::= bag1 bag2 ...
+ * bag_descr ::= bag_type1 bag_type2 ... ':'
+ * bag_types := i(nteger) ' ' | s(tring) | b(indata255) | B(indata64k) X(blob4G)
+ * item_list ::= item1 item2...
+ * The binary items (bBX) are byte sequences with a starting size element. + * The size element can be a byte (255) or a word (64k) or a double word(4G). + * The order of the size element is big endian. + * + * Example (additional blanks for readibility: + * + * RPL&1 0a b5[2]cis: !123 Nirwana <0> Y 334455 A long string with an trailing '0' <0>
+ * magic header length: 0ah data length: b5h bag count: 2 item types: char int string + */ +class RplContainer +{ +public: + typedef enum { + TAG_CHAR = 'c', + TAG_INT = 'i', + TAG_STRING = 's', + TAG_DATA255 = 'd', + TAG_DATA64K = 'D', + TAG_DATA4G = 'X', + TAG_CONTAINER = '!' + } type_tag_t; + static const char* MAGIC_1; +public: + RplContainer(size_t sizeHint); +public: + // Building the container: + void addType(type_tag_t tag); + void startBag(); + void addChar(char cc); + void addInt(int value); + void addInt(qint64 value); + void addString(const char* value); + void addData(uint8_t* value, size_t size); + const QByteArray& getData(); + + // Getting data from the container: + void fill(const QByteArray& data); + int getCountBags() const; + const char* getTypeList() const; + void nextBag(); + char nextChar(); + int nextInt(); + qint64 nextInt64(); + const char* nextString(); + size_t nextData(QByteArray& data, bool append = false); + + QByteArray dump(const char* title, + int maxBags, int maxStringLength = 80, int maxBlobLength = 16, + char separatorItems = '\n'); +private: + void nextItem(type_tag_t expected); +private: + // the complete data of the container + QByteArray m_data; + // the number of elements in the container + int m_countBags; + // a string with the data types of a bag + QByteArray m_typeList; + + // Getting data from the container: + + // current read position in m_typeList + int m_ixItem; + // the index of the current current bag: + int m_ixBag; + // read position in m_data: + const uint8_t* m_readPosition; +}; + +#endif // RPLCONTAINER_HPP diff --git a/rplqt/core/rplcore.hpp b/rplqt/core/rplcore.hpp new file mode 100644 index 0000000..0642fb4 --- /dev/null +++ b/rplqt/core/rplcore.hpp @@ -0,0 +1,30 @@ +#ifndef RPLCORE_HPP +#define RPLCORE_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef unsigned char uint8_t; +#include "../rplmodules.hpp" +#include "rpllogger.hpp" +#include "rplexception.hpp" +#include "rplcontainer.hpp" +#include "rplstring.hpp" +#include "rplconfig.hpp" + +#endif // RPLCORE_HPP diff --git a/rplqt/core/rplexception.cpp b/rplqt/core/rplexception.cpp new file mode 100644 index 0000000..8bc25a9 --- /dev/null +++ b/rplqt/core/rplexception.cpp @@ -0,0 +1,97 @@ +#include "core/rplcore.hpp" + + +RplException::RplException(const char* message) : + m_message(message) +{ +} +RplException::RplException(const QByteArray& message) : + m_message(message) +{ +} +RplException::RplException(RplLoggerLevel level, int location, const char* message, RplLogger* logger) : + m_message(message) +{ + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(LOG_ERROR, location, message); +} +RplException::RplException(RplLoggerLevel level, int location, const QByteArray& message, RplLogger* logger) : + m_message(message) +{ + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(LOG_ERROR, location, message); +} +RplException::RplException(RplLoggerLevel level, int location, RplLogger* logger, const char* format, ...) : + m_message("") +{ + char buffer[64000]; + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + m_message = buffer; + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(LOG_ERROR, location, buffer); +} +RplException::RplException(RplLoggerLevel level, int location, RplLogger* logger, + const QByteArray& format, ...) : + m_message("") +{ + char buffer[64000]; + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + m_message = buffer; + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(LOG_ERROR, location, buffer); +} + +RplRangeException::RplRangeException(RplLoggerLevel level, int location, size_t current, + size_t lbound, size_t ubound, const char* message, RplLogger* logger) : + RplException("") +{ + char buffer[64000]; + if (message == NULL) + message = "value outside limits"; + snprintf(buffer, sizeof buffer, "%s: %lu [%lu, %lu]", + message == NULL ? "" : message, + current, lbound, ubound); + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(level, location, buffer); +} + +RplInvalidDataException::RplInvalidDataException(RplLoggerLevel level, int location, + const char* message, const void* data, + size_t dataSize, RplLogger* logger) : + RplException("") +{ + char buffer[64000]; + if (message == NULL) + message = "invalid data: "; + if (data == NULL) + data = ""; + if (dataSize > 16) + dataSize = 16; + size_t ix; + char* ptr = buffer + strlen(buffer); + for (ix = 0; ix < dataSize; ix++){ + snprintf(ptr, sizeof(buffer) - (ptr - buffer) - 1, "%02x ", ((unsigned char*) data)[ix]); + ptr += strlen(ptr); + } + for (ix = 0; ix < dataSize; ix++){ + char cc = ((char*) data)[ix]; + if (cc > ' ' && cc <= '~') + *ptr++ = cc; + else + *ptr++ = '.'; + } + if (logger == NULL) + logger = RplLogger::globalLogger(); + logger->log(level, location, buffer); +} diff --git a/rplqt/core/rplexception.hpp b/rplqt/core/rplexception.hpp new file mode 100644 index 0000000..f0c3b23 --- /dev/null +++ b/rplqt/core/rplexception.hpp @@ -0,0 +1,40 @@ +#ifndef RPLEXCEPTION_HPP +#define RPLEXCEPTION_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#endif +class RplException +{ +public: + RplException(const char* message); + RplException(const QByteArray& message); + RplException(RplLoggerLevel level, int location, const char* message, + RplLogger* logger = NULL); + RplException(RplLoggerLevel level, int location, const QByteArray& message, + RplLogger* logger = NULL); + RplException(RplLoggerLevel level, int location, RplLogger* logger, + const char* message, ...); + RplException(RplLoggerLevel level, int location, RplLogger* logger, + const QByteArray& message, ...); + const QByteArray& getMessage() const { + return m_message; + } +protected: + QByteArray m_message; +}; + +class RplRangeException : public RplException{ +public: + RplRangeException(RplLoggerLevel level, int location, size_t current, size_t lbound, size_t ubound, + const char* message = NULL, RplLogger* logger = NULL); +}; + +class RplInvalidDataException : public RplException{ +public: + RplInvalidDataException(RplLoggerLevel level, int location, const char* message, + const void* data = NULL, size_t dataSize = 0, RplLogger* logger = NULL); +}; + +#endif // RPLEXCEPTION_HPP diff --git a/rplqt/core/rpllogger.cpp b/rplqt/core/rpllogger.cpp new file mode 100644 index 0000000..bf028a0 --- /dev/null +++ b/rplqt/core/rpllogger.cpp @@ -0,0 +1,449 @@ +#include "core/rplcore.hpp" +#include +#include + +enum { + LOC_ADD_APPENDER_1 = RPL_FIRST_OF(RPLMODULE_LOGGER), // 10000 +}; + +RplLogger* RplLogger::m_globalLogger = NULL; + +/** + * Returns the global logger. + * + * If it does not exist it will be created (singleton). + * + * @return the global logger + */ +RplLogger* RplLogger::globalLogger(){ + if (m_globalLogger == NULL){ + m_globalLogger = new RplLogger(); + m_globalLogger->buildStandardAppender("globallogger"); + } + return m_globalLogger; +} +/** + * Frees the resources of the global logger. + */ +void RplLogger::destroyGlobalLogger(){ + delete m_globalLogger; + m_globalLogger = NULL; +} + +/** + * Constructor. + * + * @param name identifies the logger. Useful for RplLogger::findLogger() + */ +RplAppender::RplAppender(const QByteArray& name) : + m_name(name), + m_level(LOG_INFO) +{ + +} +/** + * Destructor. + */ +RplAppender::~RplAppender(){ +} + +/** + * Returns the name. + * + * @return the name of the instance + */ +const char* RplAppender::getName() const{ + return m_name.data(); +} + +/** + * Sets the level. + * + * @param level + */ +void RplAppender::setLevel(RplLoggerLevel level){ + m_level = level; +} +/** + * Returns the level. + * + * @return the level + */ +RplLoggerLevel RplAppender::getLevel() const{ + return m_level; +} +/** + * Checks whether the current location should be logged. + * + * @param level the level of the location. + * @return true: the location level is greater or equals to the appender's level + */ +bool RplAppender::isActive(RplLoggerLevel level){ + return level <= m_level; +} + +/** + * Sets or clears the automatic deletions. + * + * @param onNotOff the state of the auto deletion + */ +void RplAppender::setAutoDelete(bool onNotOff){ + m_autoDelete = onNotOff; +} + +/** + * Returns the state of the auto deletion. + * + * @return true: the logger destroys the instance + */ +bool RplAppender::isAutoDelete() const{ + return m_autoDelete; +} + +/** + * Constructor. + */ +RplLogger::RplLogger() : + m_countAppenders(0) +{ + memset(m_appenders, 0, sizeof m_appenders); +} + +/** + * Destructor. + */ +RplLogger::~RplLogger(){ + for (size_t ix = 0; ix < m_countAppenders; ix++){ + RplAppender* appender = m_appenders[ix]; + if (appender->isAutoDelete()){ + delete appender; + } + m_appenders[ix] = NULL; + } +} +/** + * Returns the first char of a logging line displaying the logging level. + * + * @param level the level to "convert" + * @return the assigned prefix char + */ +char RplLogger::getPrefixOfLevel(RplLoggerLevel level) const { + char rc = ' '; + switch(level){ + case LOG_ERROR: + rc = '!'; + break; + case LOG_WARNING: + rc = '+'; + break; + case LOG_INFO: + rc = ' '; + break; + case LOG_DEBUG: + rc = '='; + break; + default: + rc = '?'; + break; + } + return rc; +} +/** + * Returns the standard prefix of a logging line. + * + * If it does not exist it will be created. + * + * @param level the level of the location + * @param file the source file of the location. @see __FILE__ + * @param lineNo the lineNo of the location. @see __LINE__ + * @param location an unique identifier of the location + * @return the standard logging line prefix + */ +const QByteArray& RplLogger::getStdPrefix(RplLoggerLevel level, int location){ + if (m_stdPrefix.isEmpty()) + m_stdPrefix = buildStdPrefix(level, location); + return m_stdPrefix; +} + +/** + * Logs (or not) the calling location. + * + * @param level the level of the location + * @param file the source file of the location. @see __FILE__ + * @param lineNo the lineNo of the location. @see __LINE__ + * @param location an unique identifier of the location + * @param message the logging message + * @return true: for chaining + */ +bool RplLogger::log(RplLoggerLevel level, int location, const char* message){ + m_stdPrefix = ""; + for (size_t ix = 0; ix < m_countAppenders; ix++){ + RplAppender* appender = m_appenders[ix]; + if (appender->isActive(level)) + appender->log(level, location, message, this); + } + return true; +} + +/** + * Logs (or not) the calling location. + * + * @param level the level of the location + * @param file the source file of the location. @see __FILE__ + * @param lineNo the lineNo of the location. @see __LINE__ + * @param location an unique identifier of the location + * @param format the logging message with placeholders (like printf). + * @param ... the values of the placeholders (varargs) + * @return true: for chaining + */ +bool RplLogger::logv(RplLoggerLevel level, int location, const char* format, ...){ + char buffer[64000]; + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); + return log(level, location, buffer); +} + +/** + * Logs (or not) the calling location. + * + * @param level the level of the location + * @param file the source file of the location. @see __FILE__ + * @param lineNo the lineNo of the location. @see __LINE__ + * @param location an unique identifier of the location + * @param format the logging message with placeholders (like printf). + * @param varlist variable arguments + * @return true: for chaining + */ +bool RplLogger::log(RplLoggerLevel level, int location, const char* format, va_list& varlist){ + char buffer[64000]; + vsnprintf(buffer, sizeof buffer, format, varlist); + return log(level, location, buffer); +} + +/** + * Builds the standard prefix of a logging line. + * + * @param level the level of the location + * @param file the source file of the location. @see __FILE__ + * @param lineNo the lineNo of the location. @see __LINE__ + * @param location an unique identifier of the location + */ +QByteArray RplLogger::buildStdPrefix(RplLoggerLevel level, int location){ + time_t now = time(NULL); + struct tm* now2 = localtime(&now); + char buffer[64]; + snprintf(buffer, sizeof buffer, "%c%d.%02d.%02d %02d:%02d:%02d (%d): ", + getPrefixOfLevel(level), + now2->tm_year + 1900, + now2->tm_mon + 1, + now2->tm_mday, + now2->tm_hour, + now2->tm_min, + now2->tm_sec, + location); + return QByteArray(buffer); +} + +/** + * Adds an appender. + * + * @param appender appender to add + */ +void RplLogger::addAppender(RplAppender* appender){ + if (m_countAppenders < sizeof m_appenders / sizeof m_appenders[0]){ + m_appenders[m_countAppenders++] = appender; + } else { + log(LOG_ERROR, LOC_ADD_APPENDER_1, "too many appenders"); + } +} + +RplAppender* RplLogger::findAppender(const char* name) const{ + RplAppender* rc = NULL; + for (size_t ix = 0; ix < m_countAppenders; ix++){ + RplAppender* current = m_appenders[ix]; + if (strcmp(name, current->getName()) == 0){ + rc = current; + break; + } + } + return rc; +} + +/** + * Builds the standard appender for the instance: a console logger and a file logger. + * + * @param prefix the prefix of the log file name, e.g. /var/log/server + * @param maxSize the maximum of the file size + * @param maxCount the maximal count of files. If neccessary the oldest file will be deleted + */ +void RplLogger::buildStandardAppender(const QByteArray& prefix, int maxSize, int maxCount){ + RplStreamAppender* streamAppender = new RplStreamAppender(stderr); + streamAppender->setAutoDelete(true); + addAppender((RplAppender*) streamAppender); + RplFileAppender* fileAppender = new RplFileAppender(prefix, maxSize, maxCount); + fileAppender->setAutoDelete(true); + addAppender((RplAppender*) fileAppender); + +} + + +/** + * Constructor. + */ +RplStreamAppender::RplStreamAppender(FILE* file, const char* appenderName) : + RplAppender(QByteArray(appenderName)), + m_fp(file) +{ +} + +/** + * Destructor. + */ +RplStreamAppender::~RplStreamAppender(){ + fflush(m_fp); +} + + +/** + * Logs (or not) the current location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @param logger the calling logger + */ +void RplStreamAppender::log(RplLoggerLevel level, int location, const char* message, RplLogger* logger){ + const QByteArray& prefix = logger->getStdPrefix(level, location); + fputs(prefix, m_fp); + fputs(message, m_fp); + fputc('\n', m_fp); + fflush(m_fp); +} +#pragma GCC diagnostic warning "-Wunused-parameter" + + +/** + * Constructor. + * + * @param prefix the prefix of the log file name, e.g. /var/log/server + * @param maxSize the maximum of the file size + * @param maxCount the maximal count of files. If neccessary the oldest file will be deleted + * @param appenderName the name of the appender. @see RplLogger::findAppender() + */ +RplFileAppender::RplFileAppender(const QByteArray& prefix, int maxSize, int maxCount, const char* appenderName) : + RplAppender(QByteArray(appenderName)), + m_prefix(prefix), + m_maxSize(maxSize), + m_maxCount(maxCount), + m_currentSize(0), + m_currentNo(0), + m_fp(NULL) +{ + open(); +} + +/** + * Destructor. + */ +RplFileAppender::~RplFileAppender(){ + if (m_fp != NULL){ + fclose(m_fp); + m_fp = NULL; + } +} + +/** + * Opens the next log file. + */ +void RplFileAppender::open(){ + if (m_fp != NULL) + fclose(m_fp); + char fullName[512]; + snprintf(fullName, sizeof fullName, "%s.%03d.log", m_prefix.data(), ++m_currentNo); + m_fp = fopen(fullName, "a"); + if (m_fp == NULL) + fprintf(stderr, "cannot open: %s\n", fullName); + else{ + //@ToDo + m_currentSize = 0; + } +} + +/** + * Logs (or not) the current location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @param logger the calling logger + */ +#pragma GCC diagnostic ignored "-Wunused-parameter" +void RplFileAppender::log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger) +{ + if (m_fp != NULL){ + const QByteArray& prefix = logger->getStdPrefix(level, location); + fputs(prefix, m_fp); + fputs(message, m_fp); + fputc('\n', m_fp); + fflush(m_fp); + } +} +#pragma GCC diagnostic warning "-Wunused-parameter" + +/** + * Constructor. + * + * @param maxLines the maximum of lines. + * If the buffer is full the oldest lines will be deleted + * @param appenderName NULL or the name of the appender + */ +RplMemoryAppender::RplMemoryAppender(int maxLines, const char* appenderName) : + RplAppender(appenderName), + m_lines(), + m_maxLines(maxLines) +{ + m_lines.reserve(maxLines); +} + +/** + * Destructor. + */ +RplMemoryAppender::~RplMemoryAppender(){ +} + +/** + * Logs (or not) the current location. + * + * @param level the level of the location + * @param location an unique identifier of the location + * @param message the logging message + * @param logger the calling logger + */ +#pragma GCC diagnostic ignored "-Wunused-parameter" +void RplMemoryAppender::log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger) +{ + if (m_lines.size() >= m_maxLines) + m_lines.removeFirst(); + m_lines.append(message); +} +#pragma GCC diagnostic warning "-Wunused-parameter" + +/** + * Returns the list of lines. + * + * @return the line list + */ +const QVector& RplMemoryAppender::getLines() const{ + return m_lines; +} + +/** + * Deletes all log lines. + */ +void RplMemoryAppender::clear(){ + m_lines.clear(); +} diff --git a/rplqt/core/rpllogger.hpp b/rplqt/core/rpllogger.hpp new file mode 100644 index 0000000..80c0d1f --- /dev/null +++ b/rplqt/core/rpllogger.hpp @@ -0,0 +1,149 @@ +#ifndef RPLLOGGER_HPP +#define RPLLOGGER_HPP + +class RplLogger; + +/** + * Logging level: for controlling of the logging. + * Each logging location defines one of the following level. + * If the level of an appender is lower or equals to this level + * the logging is done. + */ +enum RplLoggerLevel { + LOG_ERROR = 10, + LOG_WARNING = 15, + LOG_INFO = 20, + LOG_DEBUG = 25 +}; +/** + * Base class for an object which puts the logging info to a medium like a file. + */ +class RplAppender +{ +public: + RplAppender(const QByteArray& name); + virtual ~RplAppender(); + virtual void log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger) = 0; + bool isActive(RplLoggerLevel level); + void setLevel(RplLoggerLevel level); + void setAutoDelete(bool onNotOff); + bool isAutoDelete() const; + RplLoggerLevel getLevel() const; + const char* getName() const; + +private: + // Name of the appender. Used to find the appender in a list of appenders + QByteArray m_name; + // only locations with a lower or equal level will be logged + RplLoggerLevel m_level; + // true: the logger destroys the instance. false: the deletion must be done outside of the logger + bool m_autoDelete; +}; + +/** + * Implements a logging facility. + * The logging is controlled by a logging level (@see RplLoggerLevel). + * The logger must be supported by at least one appender (@see RplAppender) + */ +class RplLogger +{ +public: + static RplLogger* globalLogger(); + static void destroyGlobalLogger(); +private: + // the standard logger, can be called (with globalLogger()) from each location + static RplLogger* m_globalLogger; +public: + RplLogger(); + virtual ~RplLogger(); +public: + bool log(RplLoggerLevel level, int location, const char* message); + bool log(RplLoggerLevel level, int location, const QByteArray& message){ + return log(level, location, message.data()); + } + bool log(RplLoggerLevel level, int location, const QString& message){ + return log(level, location, message.toUtf8().data()); + } + + bool logv(RplLoggerLevel level, int location, const char* format, ...); + bool logv(RplLoggerLevel level, int location, const QByteArray& format, ...); + bool log(RplLoggerLevel level, int location, const char* format, va_list& varlist); + void addAppender(RplAppender* appender); + RplAppender* findAppender(const char* name) const; + void buildStandardAppender(const QByteArray& prefix, int maxSize = 10*1024*1024, int maxCount = 5); + QByteArray buildStdPrefix(RplLoggerLevel level, int location); + const QByteArray& getStdPrefix(RplLoggerLevel level, int location); + char getPrefixOfLevel(RplLoggerLevel level) const; +private: + // the assigned appenders: + RplAppender* m_appenders[16]; + // the number of appenders in m_appenders: + size_t m_countAppenders; + // "" or the cache of the prefix of the current logging line: This can be reused by any appender. + QByteArray m_stdPrefix; +}; + +/** + * Implements an appender which puts the messages to a standard stream: stdout or stderr + */ +class RplStreamAppender : public RplAppender +{ +public: + RplStreamAppender(FILE* stream, const char* appenderName = "FileAppender"); + virtual ~RplStreamAppender(); +public: + virtual void log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger); +private: + // stdout or stderr: + FILE* m_fp; +}; + +/** + * Implements an appender which puts the messages to a file + */ +class RplFileAppender : public RplAppender +{ +public: + RplFileAppender(const QByteArray& name, int maxSize, int maxCount, const char* appenderName = "FileAppender"); + virtual ~RplFileAppender(); +public: + void open(); + virtual void log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger); + +private: + // prefix of the log file name. Will be appended by "..log" + QByteArray m_prefix; + // maximal size of a logging file: + int m_maxSize; + // maximal count of logging files. If neccessary the oldest file will be deleted. + int m_maxCount; + // the size of the current log file: + int m_currentSize; + // the number of the current log file: + int m_currentNo; + // the current log file: + FILE* m_fp; +}; + +/** + * Stores the log messages in a list. + */ +class RplMemoryAppender : public RplAppender{ +public: + RplMemoryAppender(int maxLines = 1024, const char* appenderName = "MemoryAppender"); + ~RplMemoryAppender(); +public: + virtual void log(RplLoggerLevel level, int location, const char* message, + RplLogger* logger); + const QVector& getLines() const; + void clear(); +private: + QVector m_lines; + // maximum count of m_lines. If larger the oldest lines will be deleted. + int m_maxLines; +}; + +#endif // RPLLOGGER_HPP diff --git a/rplqt/core/rplstring.cpp b/rplqt/core/rplstring.cpp new file mode 100644 index 0000000..17e4486 --- /dev/null +++ b/rplqt/core/rplstring.cpp @@ -0,0 +1,267 @@ +#include "rplcore.hpp" + +/** + * Counts the occurrences of a string in a string. + * + * @param source in this string will be searched + * @param item this item will be searched + * @return the count of occurrences + */ +int RplString::count(const char* source, const char* item){ + const char* end = source; + int rc = 0; + int lengthItem = strlen(item); + while (true){ + const char* start = end; + end = strstr(start, item); + if (end == NULL) + break; + else{ + rc++; + end += lengthItem; + } + } + return rc; +} + +/** + * Returns a string with a given maximum length. + * + * @param source the source + * @param maxLength the maximum length of the result + * @param buffer Out: used if length of the result is shorter + * @param appendix if the result is cut this string will be appended.
+ * May be NULL. + * @return source: the source is enough short
+ * the prefix of source with the given length + */ +const QByteArray& RplString::cutString(const QByteArray& source, int maxLength, + QByteArray& buffer, const char* appendix){ + QByteArray& rc = source.length() <= maxLength ? (QByteArray&) source : buffer; + if (source.length() > maxLength){ + buffer = source.left(maxLength); + if (appendix != NULL && appendix[0] != '\0') + buffer.append(appendix); + } + return rc; +} + +/** + * Builds a hexadecimal dump. + * + * Format: a sequence of hex digits followed by the ascii interpretation. + * + * Example: "42 30 61 B0a" + * + * @param data data to convert + * @param length length of data + * @param bytesPerLine one line containes so many bytes of data + * @return the hex dump + */ +QByteArray RplString::hexDump(uint8_t* data, int length, int bytesPerLine){ + QByteArray rc; + int fullLines = length / bytesPerLine; + int expectedLength = (bytesPerLine * 4 + 2) * (fullLines + 1); + rc.reserve(expectedLength + 100); + int ixData = 0; + int col; + char buffer[16]; + for (int lineNo = 0; lineNo < fullLines; lineNo++){ + for (col = 0; col < bytesPerLine; col++){ + snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]); + rc.append(buffer); + } + rc.append(' '); + for (col = 0; col < bytesPerLine; col++){ + uint8_t cc = data[ixData + col]; + rc.append(cc > ' ' && cc < 128 ? (char) cc : '.'); + } + ixData += bytesPerLine; + rc.append('\n'); + } + // incomplete last line: + int restBytes = length - ixData; + if (restBytes > 0){ + for (col = 0; col < restBytes; col++){ + snprintf(buffer, sizeof buffer, "%02x ", data[ixData + col]); + rc.append(buffer); + } + for (col = restBytes; col < bytesPerLine; col++){ + rc.append(" "); + } + rc.append(' '); + for (col = 0; col < restBytes; col++){ + uint8_t cc = data[ixData + col]; + rc.append(cc > ' ' && cc < 128 ? (char) cc : '.'); + } + rc.append('\n'); + } + return rc; +} + + +/** + * Reads a file into a string. + * + * @param file file to read + * @param removeLastNewline true: if the last character is a newline + * the result will not contain this + * @return the file's content + */ +QByteArray RplString::read(const char* file, bool removeLastNewline){ + QByteArray rc; + struct stat info; + size_t size; + if (stat(file, &info) == 0 && (size = info.st_size) > 0){ + FILE* fp = fopen(file, "r"); + if (fp != NULL){ + rc.resize(info.st_size); + fread(rc.data(), 1, size, fp); + fclose(fp); + if (removeLastNewline && rc.at(size - 1) == '\n'){ + rc.resize(size - 1); + } + } + } + return rc; +} + +/** + * Converts a string into an array of strings. + * + * @param source string to convert + * @param separator the separator between the items to split + * @return an array with the splitted source + */ +QVector RplString::toArray(const char* source, const char* separator){ + const char* end = source; + QVector rc; + rc.reserve(count(source, separator) + 1); + int lengthItem = strlen(separator); + while (*end != '\0'){ + const char* start = end; + end = strstr(start, separator); + if (end == NULL){ + end = start + strlen(start); + } + rc.append(QByteArray(start, end - start)); + if (end[0] != '\0') + end += lengthItem; + } + return rc; +} + + +/** + * Return an integer as an QByteArray. + * + * @param value value to convert + * @param format format like in sprintf() + * @return the ascii form of the value + */ +QByteArray RplString::toNumber(int value, const char* format){ + char buffer[128]; + snprintf(buffer, sizeof buffer, format, value); + return QByteArray(buffer); +} + +/** + * Writes a string to a file. + * + * @param file the file's name + * @param content NULL or the file's content + * @param mode the file open mode: "w" for write, "a" for append + * @return true: successful
+ * false: error occurred + */ +bool RplString::write(const char* file, const char* content, const char* mode){ + FILE* fp = fopen(file, mode); + if (fp != NULL){ + fputs(content, fp); + fclose(fp); + } + return fp != NULL; +} + +// ------------------ +#if ! defined RPL_TEST +#define RPL_TEST +#endif +#ifdef RPL_TEST +#include "core/rpltest.hpp" +class StringTest : public RplTest +{ +public: + StringTest() : RplTest("RplString"){} + +public: + void testCount(){ + checkE(0, RplString::count("abc", " ")); + checkE(1, RplString::count("abc", "b")); + checkE(2, RplString::count("axx", "x")); + + checkE(0, RplString::count("abbc", "bbb")); + checkE(1, RplString::count("\n\n", "\n\n")); + checkE(2, RplString::count(" a ", " ")); + } + + void testCutString(){ + QByteArray source("123"); + QByteArray buffer; + checkE(QByteArray("123"), RplString::cutString(source, 4, buffer)); + checkE(QByteArray("123"), RplString::cutString(source, 3, buffer)); + checkE(QByteArray("12..."), RplString::cutString(source, 2, buffer)); + checkE(QByteArray("12"), RplString::cutString(source, 2, buffer, "")); + } + + void testHexDump(){ + QByteArray data("abc123\nxyz"); + checkE(QByteArray("61 62 63 31 abc1\n" + "32 33 0a 78 23.x\n" + "79 7a yz\n"), + RplString::hexDump((uint8_t*) data.constData(), data.length(), 4)); + checkE(QByteArray("61 62 63 31 32 33 0a 78 79 7a abc123.xyz"), + RplString::hexDump((uint8_t*) data.constData(), data.length(), 10)); + checkE(QByteArray("61 62 63 31 32 33 0a 78 79 7a abc123.xyz"), + RplString::hexDump((uint8_t*) data.constData(), data.length(), 12)); + } + + void testReadWrite(){ + QByteArray fn = getTempFile("test.dat"); + const char* content = "Hello world\nLine2\n"; + checkT(RplString::write(fn, content)); + checkE(content, RplString::read(fn, false)); + checkE(content, RplString::read(fn, true) + "\n"); + } + + void testToArray(){ + QVector array = RplString::toArray("1 abc 3", " "); + checkE(3, array.size()); + checkE("1", array.at(0)); + checkE("abc", array.at(1)); + checkE("3", array.at(2)); + } + + void testToNumber(){ + checkE("3", RplString::toNumber(3)); + checkE("-33", RplString::toNumber(-33)); + checkE("003", RplString::toNumber(3, "%03d")); + } + + virtual void doIt(){ + testCount(); + testCutString(); + testToNumber(); + testToArray(); + testHexDump(); + testReadWrite(); + } +}; + +#endif +void testRplString(){ +#ifdef RPL_TEST + StringTest test; + test.run(); +#endif +} diff --git a/rplqt/core/rplstring.hpp b/rplqt/core/rplstring.hpp new file mode 100644 index 0000000..b422e2f --- /dev/null +++ b/rplqt/core/rplstring.hpp @@ -0,0 +1,26 @@ +#ifndef RPLSTRING_HPP +#define RPLSTRING_HPP + +/** + * Implements some services around strings. + * + * This is a class with static members only. + */ +class RplString +{ +public: + static int count(const char* source, const char* item); + static const QByteArray& cutString(const QByteArray& source, int maxLength, + QByteArray& buffer, const char* appendix = "..."); + static QByteArray hexDump(uint8_t* data, int length, int bytesPerLine = 16); + static QByteArray hexDump(const void* data, int length, int bytesPerLine = 16){ + return hexDump((uint8_t*) data, length, bytesPerLine); + } + static QByteArray read(const char* file, bool removeLastNewline = true); + static bool write(const char* file, const char* content = NULL, + const char* mode = "w"); + static QVector toArray(const char* source, const char* separator); + static QByteArray toNumber(int value, const char* format = "%d"); +}; + +#endif // RPLSTRING_HPP diff --git a/rplqt/core/rpltest.cpp b/rplqt/core/rpltest.cpp new file mode 100644 index 0000000..555d5aa --- /dev/null +++ b/rplqt/core/rpltest.cpp @@ -0,0 +1,313 @@ +#include "core/rplcore.hpp" +#include "core/rpltest.hpp" +/** + * Constructor. + * + * @param name + */ +RplTest::RplTest(const char* name) : + m_errors(0), + m_name(name), + m_logger() +{ + m_logger.buildStandardAppender(getTempDir("rpltest")); + log(QByteArray("Start of ") + m_name); +} +void RplTest::run(){ + try { + doIt(); + } catch (RplException e){ + error("unexpected RplException: %s", e.getMessage().data()); + } + + if (m_errors > 0){ + error("Unit %s has %d error(s)", m_name.data(), m_errors); + // error() increments, we decrement: + m_errors--; + } +} + +/** + * Destructor. + */ +RplTest::~RplTest(){ +} + +/** + * Tests the equality of two values. + * + * Differences will be logged. + * + * @param expected the expected value + * @param current the current value + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: equal + */ +bool RplTest::assertEquals(int expected, int current, const char* file, int lineNo){ + if (expected != current) + error("%s-%d: error: %d != %d / %x != %x)", file, lineNo, expected, current, (unsigned int) expected, (unsigned int) current); + return expected == current; +} + +/** + * Tests the equality of two values. + * + * Differences will be logged. + * + * @param expected the expected value + * @param current the current value + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: equal + */ +bool RplTest::assertEquals(const char* expected, const char* current, + const char* file, int lineNo){ + bool equal = strcmp(expected, current) == 0; + if (! equal){ + if (strchr(expected, '\n') != NULL || strchr(current, '\n')){ + QVector exp = RplString::toArray(expected, "\n"); + QVector cur = RplString::toArray(current, "\n"); + equal = assertEquals(exp, cur, file, lineNo); + } else { + int ix = 0; + while(expected[ix] == current[ix] && expected[ix] != '\0') + ix++; + char pointer[12+1]; + char *ptr = pointer; + int maxIx = ix > 10 ? 10 : ix; + for (int ii = 0; ii < maxIx - 1; ii++) + *ptr++ = '-'; + *ptr++ = '^'; + *ptr = '\0'; + if (ix < 10) + error("%s-%d: error: diff at index %d\n%s\n%s\n%s", + file, lineNo, ix, expected, current, pointer); + else + error("%s-%d: error: diff at index %d\n%s\n...%s\n...%s\n%s", + file, lineNo, ix, current, + expected + ix - 10 + 3, + current + ix - 10 + 3, + pointer); + } + } + return equal; +} + +/** + * Tests the equality of two values. + * + * Differences will be logged. + * + * @param expected the expected value + * @param current the current value + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: equal + */ +bool RplTest::assertEquals(const QVector& expected, + const QVector& current, const char* file, int lineNo){ + int nMax = expected.size(); + bool rc = true; + if (current.size() < nMax) + nMax = current.size(); + for (int ix = 0; ix < nMax; ix++){ + if (expected.at(ix) != current.at(ix)){ + error("%s-%d: difference in line %d", file, lineNo, ix+1); + m_errors--; + assertEquals(expected.at(ix).constData(), current.at(ix).constData(), + file, lineNo); + rc = false; + break; + } + } + if (rc){ + if (expected.size() > nMax) + error("%s-%d: less lines than expected (%d):\n%s", + file, lineNo, nMax, expected.at(nMax).constData()); + else if (expected.size() < nMax) + error("%s-%d: more lines than expected (%d):\n%s", + file, lineNo, nMax, current.at(nMax).constData()); + } + return rc; +} +/** + * Tests the equality of two values. + * + * Differences will be logged. + * + * @param expected the expected value + * @param current the current value + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: equal + */ +bool RplTest::assertEquals(const QByteArray& expected, const QByteArray& current, const char* file, int lineNo){ + return assertEquals(expected.data(), current.data(), file, lineNo); +} + +/** + * Tests the equality of two values. + * + * Differences will be logged. + * + * @param expected the expected value + * @param current the current value + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: equal + */ +bool RplTest::assertEquals(const char* expected, const QByteArray& current, + const char* file, int lineNo){ + return assertEquals(expected, current.constData(), file, lineNo); +} + +/** + * Tests whether a value is true. + * + * A value of false will be logged. + * + * @param condition value to test + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return condition + */ +bool RplTest::assertTrue(bool condition, const char* file, int lineNo){ + if (! condition) + error("%s-%d: not TRUE", file, lineNo); + return condition; +} + +/** + * Tests whether a value is false. + * + * A value of true will be logged. + * + * @param condition value to test + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return ! condition + */ +bool RplTest::assertFalse(bool condition, const char* file, int lineNo){ + if (condition) + error("%s-%d: not FALSE", file, lineNo); + return ! condition; +} + +/** + * Tests whether a value is NULL. + * + * A value of not NULL will be logged. + * + * @param ptr value to test + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: ptr is NULL + */ +bool RplTest::assertNull(const void* ptr, const char* file, int lineNo){ + if (ptr != NULL) + error("%s-%d: not NULL", file, lineNo); + return ptr == NULL; +} + +/** + * Tests whether a value is not NULL. + * + * A value of NULL will be logged. + * + * @param ptr value to test + * @param file the file containing the test + * @param lineNo the line number containing the test + * @return true: ptr is not NULL + */ +bool RplTest::assertNotNull(const void* ptr, const char* file, int lineNo){ + if (ptr == NULL) + error("%s-%d: is NULL", file, lineNo); + return ptr != NULL; +} + +/** + * Writes an info. + * + * @param message message to show + * @return true (for chaining) + */ +bool RplTest::log(const char* message){ + m_logger.log(LOG_INFO, 0, message); + return true; +} + + +/** + * Writes an error. + * + * @param message message to show + * @return false (for chaining) + */ +bool RplTest::error(const char* format, ...){ + m_errors++; + va_list ap; + va_start(ap, format); + m_logger.log(LOG_ERROR, 0, format, ap); + va_end(ap); + return false; +} + +/** + * Returns the name of a directory in the temp dir. + * + * If the named directory does not exist it will be created. + * + * @param node NULL or the node (name without path) + * @param parent NULL or a node of the parent + * @param withSeparator true: the result ends with slash/backslash + * @return the name of a existing directory + */ +QByteArray RplTest::getTempDir(const char* node, const char* parent, + bool withSeparator){ + QByteArray temp("c:\\temp"); + struct stat info; + const char* ptr; + if ( (ptr = getenv("TMP")) != NULL ) + temp = ptr; + else if ( (ptr = getenv("TEMP")) != NULL ) + temp = ptr; + else if (stat("/tmp", &info) == 0) + temp = "/tmp"; + char sep = m_separator = temp.indexOf('/') >= 0 ? '/' : '\\'; + if (temp.at(temp.length() - 1) != sep) + temp += sep; + if (parent != NULL){ + temp += parent; + if (stat(temp.constData(), &info) != 0) + mkdir(temp.constData(), (-1)); + temp += sep; + } + if (node != NULL){ + temp += node; + temp += sep; + if (stat(temp.data(), &info) != 0) + mkdir(temp.data(), -1); + } + if (! withSeparator) + temp.resize(temp.length() - 1); + return temp; +} + +/** + * Returns a name of a file in a temporary directory. + * + * @param node the file's name without path + * @param parent NULL or the name of a subdirectory the file will be inside + * @param deleteIfExists true: if the file exists it will be removed + * @return the full name of a temporary file + */ +QByteArray RplTest::getTempFile(const char* node, const char* parent, + bool deleteIfExists){ + QByteArray dir = getTempDir(parent); + QByteArray rc = dir + m_separator + node; + struct stat info; + if (deleteIfExists && stat(rc.constData(), &info) == 0) + unlink(rc.constData()); + return rc; +} diff --git a/rplqt/core/rpltest.hpp b/rplqt/core/rpltest.hpp new file mode 100644 index 0000000..18e4540 --- /dev/null +++ b/rplqt/core/rpltest.hpp @@ -0,0 +1,40 @@ +#ifndef RPLTEST_HPP +#define RPLTEST_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#endif + +class RplTest +{ +public: + RplTest(const char* name); + virtual ~RplTest(); +public: + bool assertEquals(int expected, int current, const char* file, int lineNo); + bool assertEquals(const char* expected, const char* current, const char* file, int lineNo); + bool assertEquals(const QByteArray& expected, const QByteArray& current, const char* file, int lineNo); + bool assertTrue(bool condition, const char* file, int lineNo); + bool assertFalse(bool condition, const char* file, int lineNo); + bool assertNull(const void* ptr, const char* file, int lineNo); + bool assertNotNull(const void* ptr, const char* file, int lineNo); + bool log(const char* message); + bool error(const char* message, ...); + QByteArray getTempDir(const char* node, const char* parent = NULL); + + void run(); + virtual void doIt() = 0; + +protected: + int m_errors; + QByteArray m_name; + RplLogger m_logger; +}; +#define check(expected, current) assertEquals(expected, current, __FILE__, __LINE__) +#define checkT(current) assertTrue(current, __FILE__, __LINE__) +#define checkF(current) assertTrue(current, __FILE__, __LINE__) +#define checkN(current) assertNull(current, __FILE__, __LINE__) +#define checkNN(current) assertNotNull(current, __FILE__, __LINE__) + +#endif // RPLTEST_HPP diff --git a/rplqt/util/rpltcpserver.cpp b/rplqt/util/rpltcpserver.cpp new file mode 100644 index 0000000..5c67dbf --- /dev/null +++ b/rplqt/util/rpltcpserver.cpp @@ -0,0 +1,82 @@ +#include "rplutil.hpp" + +/** + * Constructor. + * + * @param socketDescriptor socket of the connection to handle + * @param threadId an unique id for the thread + * @param handler does the work + */ +RplTcpThread::RplTcpThread(qintptr socketDescriptor, int threadId, RplTaskHandler* handler) : + m_threadId(threadId), + m_taskHandler(handler), + m_socketDescriptor(socketDescriptor) +{ +} +/** + * Destructor. + */ +RplTcpThread::~RplTcpThread(){ + +} + +void RplTcpThread::run(){ + QTcpSocket tcpSocket; + if (!tcpSocket.setSocketDescriptor(getSocketDescriptor())) { + emit error(tcpSocket.error()); + } else { + while(m_taskHandler->handle(&tcpSocket)){ + // do nothing + } + tcpSocket.disconnectFromHost(); + tcpSocket.waitForDisconnected(); + } +} + +/** + * Returns the thread id. + * + * @return the thread id + */ +int RplTcpThread::getThreadId() const{ + return m_threadId; +} +/** + * Returns the task handler + * @return the task handler + */ +RplTaskHandler* RplTcpThread::getTaskHandler() const{ + return m_taskHandler; +} +/** + * Returns the tcp socket of the served connection. + * + * @return the socket + */ +qintptr RplTcpThread::getSocketDescriptor() const{ + return m_socketDescriptor; +} +/** + * Constructor. + * + * @param taskHandler this handler reads from the tcp and interprets the content + * @param parent NULL or the parent which deletes the childen + */ +RplTcpServer::RplTcpServer(RplTaskHandler* taskHandler, QObject *parent) : + QTcpServer(parent), + m_taskHandler(taskHandler), + m_threadId(0) +{ +} + +/** + * The slot handling a new tcp connection. + * + * @param socketDescriptor the tcp socket + */ +void RplTcpServer::incomingConnection(qintptr socketDescriptor) +{ + RplTcpThread *thread = createThread(socketDescriptor, ++m_threadId, m_taskHandler); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread->start(); +} diff --git a/rplqt/util/rpltcpserver.hpp b/rplqt/util/rpltcpserver.hpp new file mode 100644 index 0000000..812cd63 --- /dev/null +++ b/rplqt/util/rpltcpserver.hpp @@ -0,0 +1,100 @@ +#ifndef RPLTCPSERVER_HPP +#define RPLTCPSERVER_HPP + +// the sources generated from QT include this file directly: +#ifndef RPLCORE_HPP +#include +#include +#include +#include +#include +#endif + +class RplToServer{ +public: + RplToServer() { + memset(this, 0, sizeof *this); + } + unsigned short m_application; + unsigned short m_command; + unsigned long m_size; +}; +class RplToClient{ +public: + RplToClient() { + memset(this, 0, sizeof *this); + } + unsigned short m_application; + unsigned short m_command; + unsigned long m_size; +}; + + +class RplTaskHandler +{ +public: + RplTaskHandler(); + virtual ~RplTaskHandler(); +public: + + bool handle(QAbstractSocket* socket); + virtual bool process(char application, int command, int size, const void* params, + int answerCode, int& answerSize, void*& answerData) = 0; + void setThreadId(int id); + int getThreadId(); +private: + int m_threadId; +}; + +/** + * Serves one connection of a multihreaded TCP server. + * + * Note: The real thing is done by the RplTaskHandler instance. + */ +class RplTcpThread : public QThread { + Q_OBJECT +public: + RplTcpThread(qintptr socketDescriptor, int threadId, RplTaskHandler* handler); + virtual ~RplTcpThread(); + void run(); + int getThreadId() const; + RplTaskHandler* getTaskHandler() const; + qintptr getSocketDescriptor() const; + +signals: + void error(QTcpSocket::SocketError socketError); + +private: + // a unique id for the thread + int m_threadId; + // this handler interprets the info from the TCP connection + RplTaskHandler* m_taskHandler; + // the assigned socket + qintptr m_socketDescriptor; +}; + +/** + * Implements a multithreaded TCP server. + */ +class RplTcpServer : public QTcpServer +{ + Q_OBJECT +public: + explicit RplTcpServer(RplTaskHandler* taskHandler, QObject *parent = 0); +public: + bool handleTask(); + /** + * Creates a thread derived from RplTcpThread. + * @return a new thread + */ + virtual RplTcpThread* createThread(qintptr socketDescriptor, int threadId, RplTaskHandler* taskHandler) = 0; + +protected slots: + void incomingConnection(qintptr socketDescriptor); + +private: + RplTaskHandler* m_taskHandler; + int m_threadId; +}; + +#endif // RPLTCPSERVER_HPP diff --git a/rplqt/util/rplutil.hpp b/rplqt/util/rplutil.hpp new file mode 100644 index 0000000..bd6454e --- /dev/null +++ b/rplqt/util/rplutil.hpp @@ -0,0 +1,12 @@ +#ifndef RPLUTIL_HPP +#define RPLUTIL_HPP + +#include +#include +#include + +#include +#include + +#include "rpltcpserver.hpp" +#endif // RPLUTIL_HPP