--- /dev/null
+CC = g++
+CL = g++
+CFLAGS = -Wall
+LDFLAGS = -lbcm2835 -lpthread
+
+OBJ_UTIL = timer.o thread.o dynbuffer.o logger.o
+OBJ = cpidjinn.o $(OBJ_UTIL) tcpserver.o gpioprocessor.o
+PROG = cpidjinn
+
+OBJ_CLIENT = $(OBJ_UTIL) tcpclient.o client.o
+CLIENT = client
+
+all: $(PROG)
+
+$(CLIENT) : $(OBJ_CLIENT)
+ $(CL) -o $(CLIENT) $(OBJ_CLIENT)
+
+debug:all
+ $(CC) $(CFLAGS) -g -o $(PROG) $(OBJ)
+stable:all
+ $(CC) $(CFLAGS) -o cipdjinn $(OBJ)
+
+.PHONY: clean
+clean:
+ rm -vfr *~ $(PROG) $(OBJ)
+
+$(PROG): $(OBJ)
+ $(CL) $(LDFLAGS) -o $(PROG) $(OBJ)
+
+%.hpp: %.cpp
+ $(CC) $(CFLAGS) -g -c $<
+
+%.o: %.cpp
+ $(CC) $(CFLAGS) -g -c $<
+
--- /dev/null
+#include "util.hpp"
+#include "tcpclient.hpp"
+
+#if 0
+static void prepareBlink(DynBuffer& buffer){
+
+}
+#endif
+
+static void addTone(int duration, int millihz, DynBuffer& buffer){
+ int high, low;
+ high = low = 1000*1000*1000 / millihz / 2 + 1;
+ buffer.appendAsLE(duration, 2);
+ buffer.appendAsLE(high, 3);
+ buffer.appendAsLE(low, 3);
+}
+
+static void prepareMelody(DynBuffer& buffer){
+ int duration = 2000;
+ int pin = 0;
+ int countTones = 3;
+ buffer.clear().append("MELO");
+ buffer.appendAsLE(pin, 1);
+ buffer.appendAsLE(countTones, 1);
+ // tone 'a' 440 Hz
+ addTone(duration, 440000, buffer);
+ // tone c#: 554,365 Hz
+ addTone(duration, 554365, buffer);
+ // tone e: 659,255 Hz
+ addTone(duration, 659255, buffer);
+}
+int main (int argc, char **argv) {
+ const char* host = "127.0.0.1";
+ int port = 15000;
+ Logger logger;
+ DynBuffer buffer;
+ TcpClient client(&logger);
+ if (client.connect(host, port)){
+ prepareMelody(buffer);
+ client.sendAndReceive(buffer);
+ }
+ client.disconnect();
+ return 0;
+}
--- /dev/null
+#include "cpidjinn.hpp"
+
+/**
+ * Main function.
+ *
+ * @param argc number of arguments
+ * @param argv argument vector
+ * @return exit code
+ */
+int main(int argc, char** argv){
+ Logger logger;
+ int port = argc < 2 ? 15000 : atol(argv[1]);
+ logger.say(LOG_INFO, "start");
+ GPIOProcessor processor;
+ TcpServer server(port, &logger);
+ server.addProcessor(processor);
+ // interpreter for the standard commands:
+ server.addProcessor(server);
+ server.listen();
+ return 0;
+}
+
--- /dev/null
+#ifndef CPIDJINN_HPP
+#include "util.hpp"
+#include "tcpserver.hpp"
+#include "gpioprocessor.hpp"
+#endif
--- /dev/null
+../util/dynbuffer.cpp
\ No newline at end of file
--- /dev/null
+../util/dynbuffer.hpp
\ No newline at end of file
--- /dev/null
+#include "cpidjinn.hpp"
+#include "bcm2835.h"
+
+/**
+ * Constructor.
+ */
+GPIOProcessor::GPIOProcessor()
+{
+}
+/**
+ * Destructor.
+ */
+GPIOProcessor::~GPIOProcessor()
+{
+
+}
+/**
+ * Lets a GPIO pin blink.
+ *
+ * Syntax: BLNK[pin][period_high][period_low]<br>
+ * [pin]: 1 byte. Numbering like WiringPi: 0 is GPIO17...<br>
+ * [period_x]: 4 byte little endian, in microseconds (< 35 minutes)
+ *
+ * @param buffer IN: contains the blink command<br>
+ * OUT: answer for the client
+ */
+void GPIOProcessor::blink(DynBuffer& buffer){
+ if (buffer.length() != 4 + 1 + 2*4){
+ buffer.clear().appendFormatted("ERR wrong packet length %d instead of %d",
+ buffer.length(), 4 + 1 + 2*4);
+ } else {
+ int pin = buffer.str()[4];
+ int periodHigh = buffer.valueOfLE(4, 5);
+ int periodLow = buffer.valueOfLE(4, 5 + 4);
+ printf("Pin: %d high: %d low: %d\n", pin, periodHigh, periodLow);
+ buffer.set("OK ");
+ }
+}
+
+/**
+ * "Writes" a melody to a GPIO pin.
+ *
+ * Syntax: MELO[pin][count_tones][duration_1][period_high_1][period_high_1]...<br>
+ * [pin]: 1 byte. Numbering like WiringPi: 0 is GPIO17...<br>
+ * [count_tones]: 1 byte<br>
+ * [duration_y]: 2 byte little endian, number of millisec (1 msec - 65 sec)<br>
+ * [period_x_y]: 3 byte little endian, unit: microsec (1 usec - 16.8 sec)
+ *
+ * @param buffer IN: contains the command describing a melody to play<br>
+ * OUT: answer for the client
+ */
+void GPIOProcessor::melody(DynBuffer& buffer){
+ if (buffer.length() < 4 + 1 + 1 + 2 + 2*3){
+ buffer.clear().appendFormatted("ERR wrong packet length %d instead of %d",
+ buffer.length(), 4 + 1 + 2*4);
+ } else {
+ int pin = buffer.at(4);
+ int countTones = buffer.at(5);
+ if ( (buffer.length() - 4 - 1 - 1) % (2 + 2*3) != 0){
+ buffer.clear().appendFormatted("ERR incomplete tones definitions %d count: %d",
+ buffer.length(), countTones);
+ } else {
+ int offset = 4 + 1 + 1;
+ printf("Pin: %d count: %d\n", pin, countTones);
+ while(countTones-- > 0) {
+ int periodHigh, periodLow;
+ int duration = buffer.valueOfLE(2, offset);
+ offset += 2;
+ periodHigh = buffer.valueOfLE(3, offset);
+ offset += 3;
+ periodLow = buffer.valueOfLE(3, offset);
+ offset += 3;
+ int count = duration * 1000 / (periodHigh + periodLow);
+ printf("high: %d low: %d duration: %d msec count: %d\n",
+ periodHigh, periodLow, duration, count);
+ }
+ buffer.set("OK ", 4);
+ }
+ }
+}
+/**
+ * "Writes" a PWM code to the a pin.
+ *
+ * Syntax: PWM [pin][divider][countValues][pause][size_values][value1]...<br>
+ * [pin]: '1' (constant): GPIO18 is the only available pin for PWM<br>
+ * [divider]: 1 byte, 1 << [divider] is the real value<br>
+ * [countValues]: 2 byte little endian: number of values to write as PWM<br>
+ * [pause]: 4 byte little endian in microseconds (< 35 minutes), pause between 2 data<br>
+ *
+ *
+ * @param buffer IN: the command describing a PulseWidthModulation command<br>
+ * OUT: answer for the client
+ */
+void GPIOProcessor::pulseWidthModulation(DynBuffer& buffer){
+ buffer.set("ERROR not implemented");
+}
+
+/**
+ * Processes the received buffer content.
+ *
+ * @param buffer IN: the client command<br>
+ * OUT: the answer to th client
+ */
+TcpProcessor::State GPIOProcessor::process(DynBuffer& buffer){
+ State rc = stProcessed;
+
+ if (buffer.startsWith("BLNK", 4)){
+ blink(buffer);
+ } else if (buffer.startsWith("MELO", 4)){
+ melody(buffer);
+ } else if (buffer.startsWith("PWM ", 4)){
+ pulseWidthModulation(buffer);
+ } else {
+ rc = stIgnored;
+ }
+ return rc;
+}
--- /dev/null
+#ifndef GPIOPROCESSOR_H
+#define GPIOPROCESSOR_H
+
+class GPIOProcessor : public TcpProcessor
+{
+public:
+ GPIOProcessor();
+ ~GPIOProcessor();
+private:
+ // not implemented, private use only
+ GPIOProcessor(const GPIOProcessor& other);
+ GPIOProcessor& operator=(const GPIOProcessor& other);
+ void blink(DynBuffer& buffer);
+ void melody(DynBuffer& buffer);
+ void pulseWidthModulation(DynBuffer& buffer);
+public:
+ virtual State process(DynBuffer& buffer);
+
+};
+
+#endif // GPIOPROCESSOR_H
--- /dev/null
+../util/logger.cpp
\ No newline at end of file
--- /dev/null
+../util/logger.hpp
\ No newline at end of file
--- /dev/null
+#include "util.hpp"
+#include "tcpclient.hpp"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/**
+ * Constructor.
+ */
+TcpClient::TcpClient(Logger* logger) :
+ m_socket(socket (AF_INET, SOCK_STREAM, 0)),
+ m_buffer(64*1024),
+ m_logger(logger),
+ m_connected(false)
+{
+}
+/**
+ * Destructor.
+ */
+TcpClient::~TcpClient() {
+ disconnect();
+}
+/**
+ * Connects to a server.
+ *
+ * @param host the host as numerical iP address
+ * @param port the port
+ * @return <i>true</i>: success
+ */
+bool TcpClient::connect(const char* host, int port){
+ bool rc = true;
+ struct sockaddr_in address;
+ address.sin_family = AF_INET;
+ address.sin_port = htons (port);
+ inet_aton (host, &address.sin_addr);
+ if (m_connected)
+ disconnect();
+ if (::connect ( m_socket, (struct sockaddr *) &address, sizeof (address)) != 0){
+ m_logger->sayf(LOG_ERROR, "connect() failed: %d", errno);
+ rc = false;
+ } else {
+ m_connected = true;
+ }
+ return rc;
+}
+/**
+ * Closes the connection.
+ */
+void TcpClient::disconnect(){
+ if (m_connected){
+ close(m_socket);
+ m_connected = false;
+ }
+}
+/**
+ * Sends a message to the server.
+ *
+ * @param buffer the message to send
+ */
+void TcpClient::send(DynBuffer& buffer){
+ if (::send(m_socket, buffer.str(), buffer.length(), 0) != 0){
+ m_logger->sayf(LOG_ERROR, "send(%.4s, %d) failed: %d",
+ buffer.str(), buffer.length(), errno);
+ }
+}
+/**
+ * Sends a message to the server and receives the answer.
+ *
+ * @param buffer IN: the message to send<br>
+ * OUT: the answer from the server
+ */
+void TcpClient::sendAndReceive(DynBuffer& buffer){
+ if (::send(m_socket, buffer.str(), buffer.length(), 0) <= 0){
+ m_logger->sayf(LOG_ERROR, "send(%.4s, %d) failed: %d",
+ buffer.str(), buffer.length(), errno);
+ } else {
+ int length = ::recv(m_socket, buffer.buffer(), buffer.size(), 0);
+ if (length < 0){
+ m_logger->sayf(LOG_ERROR, "receive() failed: %d", errno);
+ buffer.clear();
+ } else {
+ buffer.setLength(length);
+ if (buffer.startsWith("ERRO", 4)){
+ m_logger->say(LOG_ERROR, buffer.str());
+ }
+ }
+ }
+}
--- /dev/null
+#ifndef TCPCLIENT_H
+#define TCPCLIENT_H
+
+class TcpClient {
+public:
+ TcpClient(Logger* logger);
+ ~TcpClient();
+private:
+ // not implemented, only private use
+ TcpClient ( const TcpClient& other );
+ TcpClient& operator= ( const TcpClient& other );
+public:
+ bool connect(const char* host, int port);
+ void disconnect();
+ void send(DynBuffer& buffer);
+ void sendAndReceive(DynBuffer& buffer);
+private:
+ int m_socket;
+ DynBuffer m_buffer;
+ Logger* m_logger;
+ bool m_connected;
+};
+
+#endif // TCPCLIENT_H
--- /dev/null
+#include "cpidjinn.hpp"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <signal.h>
+
+//#define SIGPIPE 13
+volatile sig_atomic_t s_brokenPipes = 0;
+typedef void (*sighandler_t)(int);
+
+static sighandler_t prepareSignal (int signalNo, sighandler_t signalHandler) {
+#if 1
+ sighandler_t oldHandler = signal(signalNo, signalHandler);
+ (void) oldHandler;
+ return NULL;
+#else
+ struct sigaction newSignal, oldSignal;
+ newSignal.sa_handler = signalHandler;
+ sigemptyset (&newSignal.sa_mask);
+ newSignal.sa_flags = SA_RESTART;
+ if (sigaction (signalNo, &newSignal, &oldSignal) < 0)
+ return SIG_ERR;
+ return oldSignal.sa_handler;
+#endif
+}
+static void catchSignal(int signal) {
+ if (signal == SIGPIPE)
+ s_brokenPipes++;
+ prepareSignal(signal, &catchSignal);
+}
+
+/**
+ * Constructor.
+ *
+ * @param port the port to listen
+ */
+TcpServer::TcpServer(int port, Logger* logger) :
+ m_port(port),
+ m_buffer(1024),
+ m_listeningSocket( socket (AF_INET, SOCK_STREAM, 0)),
+ m_logger(logger),
+ m_processors(),
+ m_processorCount(0)
+
+{
+ int set = 1;
+ setsockopt(m_listeningSocket, SOL_SOCKET, MSG_NOSIGNAL, (void *)&set, sizeof(int));
+ prepareSignal(SIGPIPE, catchSignal);
+
+}
+/**
+ * Destructor.
+ */
+TcpServer::~TcpServer()
+{
+}
+
+/**
+ * Adds a processor for the client messages.
+ *
+ * @param processor processor to add
+ */
+void TcpServer::addProcessor(TcpProcessor& processor){
+ if ((size_t) m_processorCount >= sizeof m_processors / sizeof m_processors[0])
+ m_logger->sayf(LOG_ERROR, "too many processors: %d", m_processorCount);
+ else {
+ m_processors[m_processorCount++] = &processor;
+ }
+}
+
+/**
+ * Processes standard messages.
+ *
+ * @param buffer IN: the client message<br>
+ * OUT: the answer for the client
+ * @return <i>stIgnored</i>: unknown message<br>
+ * <i>stProcessed</i>: command recognized and processed.
+ * <i>stStop</i>QUIT recognized
+ */
+TcpProcessor::State TcpServer::process(DynBuffer& buffer){
+ State rc = stProcessed;
+ if (buffer.startsWith("ECHO", 4)){
+ // nothing to do: the buffer is returned unchanged.
+ } else if (buffer.startsWith("QUIT", 4)){
+ rc = stStop;
+ buffer.clear();
+ } else if (buffer.startsWith("TIME", 4)){
+ buffer.resize(64);
+ struct tm* tm_info;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ tm_info = localtime(&tv.tv_sec);
+ strftime(buffer.buffer(), 64, "TIME: %Y.%m:%d %H:%M:%S", tm_info);
+ buffer.appendInt(tv.tv_usec/1000, ".%03d");
+ } else {
+ rc = stIgnored;
+ }
+ return rc;
+}
+/**
+ * Starts the listening state.
+ */
+void TcpServer::listen(){
+ socklen_t addrlen;
+ size_t length;
+ bool again = true;
+ struct sockaddr_in address;
+ const int dummy = 1;
+ setsockopt( m_listeningSocket, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(int));
+ address.sin_family = AF_INET;
+ address.sin_addr.s_addr = INADDR_ANY;
+ address.sin_port = htons (m_port);
+ int lastBrokenPipe = s_brokenPipes;
+ if (bind ( m_listeningSocket, (struct sockaddr *) &address, sizeof (address)) != 0) {
+ m_logger->sayf(LOG_ERROR, "port in use: %d", m_port);
+ } else {
+ m_logger->sayf(LOG_INFO, "listen on port %d...", m_port);
+ ::listen (m_listeningSocket, 5);
+ addrlen = sizeof (struct sockaddr_in);
+ while (again) {
+ int connectionSocket = accept ( m_listeningSocket, (struct sockaddr *) &address, &addrlen );
+ if (connectionSocket <= 0)
+ m_logger->sayf (LOG_ERROR, "accept() failed: %d", errno);
+ else {
+ int set = 1;
+ setsockopt(connectionSocket, SOL_SOCKET, MSG_NOSIGNAL, (void *)&set, sizeof(int));
+ m_logger->sayf (LOG_INFO, "new connection: %s ...", inet_ntoa (address.sin_addr));
+ State state = stUndef;
+ do {
+ length = ::recv (connectionSocket, m_buffer.buffer(), m_buffer.size()-1, 0);
+ if (lastBrokenPipe != s_brokenPipes && length <= 0){
+ m_logger->sayf(LOG_INFO, "connection lost");
+ lastBrokenPipe = s_brokenPipes;
+ break;
+ }
+ if (length > 0) {
+ m_buffer.setLength(length);
+ m_logger->sayf(LOG_DEBUG, "received: %d bytes, %.4s", length, m_buffer.str());
+ } else if (length == 0){
+ int errorNo = errno;
+ if (errorNo == ENOPROTOOPT){
+ m_logger->say(LOG_INFO, "connection lost.");
+ break;
+ } else {
+ m_logger->sayf(LOG_ERROR, "recv() returns 0: %d", errorNo);
+ }
+ } else {
+ m_logger->sayf(LOG_ERROR, "recv() returns %d: %d", length, errno);
+ }
+ state = stUndef;
+ if (length >= 4){
+ bool again = true;
+ for (int ix = 0; again && ix < m_processorCount; ix++){
+ State state = m_processors[ix]->process(m_buffer);
+ switch(state){
+ case stUndef:
+ case stIgnored:
+ break;
+ case stProcessed:
+ again = false;
+ break;
+ case stStop:
+ break;
+ }
+ }
+ }
+ if (state == stIgnored){
+ char buf[5];
+ memcpy(buf, m_buffer.str(), 4);
+ buf[4] = '\n';
+ m_buffer.clear().appendFormatted("ERROR: unknown command: %s", buf);
+ m_logger->say(LOG_ERROR, m_buffer.str());
+ }
+ if (m_buffer.length() > 0){
+ ::send (connectionSocket, m_buffer.str(), m_buffer.length(), 0);
+ }
+ } while (state != stStop);
+ close (connectionSocket);
+ }
+ }
+ }
+ close (m_listeningSocket);
+}
+
+
--- /dev/null
+#ifndef TCPSERVER_H
+#define TCPSERVER_H
+typedef unsigned char ubyte_t;
+
+class TcpProcessor {
+public:
+ enum State {
+ stUndef = 0,
+ /// buffer content recognized and processed:
+ stProcessed,
+ /// unknown buffer content, not processed
+ stIgnored,
+ // server should stop
+ stStop
+ };
+public:
+ virtual State process(DynBuffer& buffer) = 0;
+};
+
+class TcpServer : public TcpProcessor
+{
+public:
+ TcpServer(int port, Logger* logger);
+ virtual ~TcpServer();
+private:
+ // not implemented! Fails if used!
+ TcpServer(const TcpServer& other);
+ TcpServer& operator=(const TcpServer& other);
+ bool operator==(const TcpServer& other) const;
+public:
+ void addProcessor(TcpProcessor& processor);
+ void listen();
+ virtual State process(DynBuffer& buffer);
+private:
+ int m_port;
+ DynBuffer m_buffer;
+ int m_listeningSocket;
+ Logger* m_logger;
+ TcpProcessor* m_processors[8];
+ int m_processorCount;
+};
+
+#endif // TCPSERVER_H
--- /dev/null
+../util/thread.cpp
\ No newline at end of file
--- /dev/null
+../util/thread.hpp
\ No newline at end of file
--- /dev/null
+../util/timer.cpp
\ No newline at end of file
--- /dev/null
+../util/timer.hpp
\ No newline at end of file
--- /dev/null
+../util/timeutils.cpp
\ No newline at end of file
--- /dev/null
+../util/timeutils.hpp
\ No newline at end of file
--- /dev/null
+../util/trace.hpp
\ No newline at end of file
--- /dev/null
+../util/util.hpp
\ No newline at end of file
--- /dev/null
+CC = g++
+CFLAGS = "-Wall"
+LDFLAGS = -lm -lpthread
+CL = g++
+
+OBJ = cutimeutils.o timeutils.o cutimer.o cudynbuffer.o thread.o timer.o dynbuffer.o \
+ logger.o unittest.o test.o
+PROG = test
+
+all : $(PROG)
+
+$(PROG): $(OBJ)
+ $(CC) $(LDFLAGS) -g -o $(PROG) $(OBJ)
+
+%.o: %.cpp
+ $(CC) $(CFLAGS) -g -c $<
+
+debug: all
+ $(CC) $(LDFLAGS) -g -o $(PROG) $(OBJ)
+stable: all
+ $(CL) $(LDFLAGS) -o $(PROG) $(OBJ)
+
+.PHONY: clean
+clean:
+ rm -vfr *~ $(PROG) $(OBJ)
--- /dev/null
+#include "test.hpp"
+
+class TestDynBuffer : public UnitTest {
+public:
+ TestDynBuffer() :
+ UnitTest("dynbuffer")
+ {
+
+ }
+public:
+ virtual void run(){
+ testBasic();
+ testAppend();
+ testAppendFormatted();
+ testAppendInt();
+ testAppendLittleEndian();
+ testAt();
+ testBufferClearLength();
+ testResize();
+ testSet();
+ testSetLength();
+ testStartsWith();
+ testValueOfLE();
+ testMemory();
+ }
+ void testBasic(){
+ DynBuffer buf("Hi", 0, 2);
+ buf.setBlocksize(2);
+ DynBuffer buf2(buf);
+ checkE("Hi", buf2);
+ DynBuffer buf3("hi!", 2);
+ checkE("hi", buf3);
+ DynBuffer buf4(4, 8);
+ buf4.append("1234");
+ checkE(4, buf4.length());
+ checkE(4, buf4.size());
+ buf4.append("5");
+ checkE(5, buf4.length());
+ checkE("12345", buf4);
+ checkE(4+8, buf4.size());
+ checkT(buf.length() <= buf.size());
+
+ buf = "abc";
+ checkE("abc", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf = DynBuffer("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ checkE("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buf);
+ checkE(26, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testAppend(){
+ DynBuffer buf("1", 1, 1);
+ checkE(1, buf.length());
+ checkT(buf.length() <= buf.size());
+ buf.append("2x", 1);
+ checkE("12", buf);
+ checkE(2, buf.length());
+ checkT(buf.length() <= buf.size());
+ DynBuffer buf2("xy");
+ buf.append(buf2);
+ checkE("12xy", buf);
+ checkE(4, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.clear();
+ checkE(0, buf.length());
+ buf.append("a");
+ checkE("a", buf);
+ checkE(1, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.append("zzz", 2);
+ checkE("azz", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testAppendFormatted(){
+ DynBuffer buf(2, 4);
+ buf.appendFormatted("%03d%c%s", 7, 'x', "Z.").appendFormatted("%.2s", "+++++");
+ checkE("007xZ.++", buf);
+ checkE(8, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.clear();
+ buf.appendFormatted("%x", 15);
+ checkE("f", buf);
+ checkE(1, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.appendFormatted("%c", '!');
+ checkE("f!", buf);
+ checkE(2, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testAppendInt(){
+ DynBuffer buf(2,2);
+ buf.appendInt(123);
+ checkE("123", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.appendInt(64, "%4x");
+ checkE("123 40", buf);
+ checkE(3 + 4, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.clear();
+ buf.appendInt(2);
+ checkE("2", buf);
+ checkE(1, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.appendInt(8, "%02d");
+ checkE("208", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testAppendLittleEndian(){
+ DynBuffer buf(2,2);
+ buf.appendAsLE(0x31323334, 4);
+ checkE("4321", buf);
+ checkE(4, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.appendAsLE(0x414243, 3);
+ checkE("4321CBA", buf);
+ checkE(7, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.clear();
+ buf.appendAsLE(0x6162, 2);
+ checkE("ba", buf);
+ checkE(2, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.appendAsLE(0x21);
+ checkE("ba!", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testAt(){
+ DynBuffer buf("abcd");
+ checkT('a' == buf.at(0));
+ checkT('b' == buf.at(1));
+ checkT('c' == buf.at(2));
+ checkT('d' == buf.at(3));
+ checkT(0 == buf.at(-1));
+ checkT(0 == buf.at(4));
+ }
+
+ void testBufferClearLength(){
+ DynBuffer buf(2, 2);
+ checkE(2, buf.size());
+ checkE(0, buf.length());
+ const char* longString = "123456789 123456789 123456789";
+ int len = strlen(longString);
+ strcpy(buf.buffer(len), longString);
+ buf.setLength(len);
+ checkE(longString, buf);
+ checkE(29, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ buf.clear();
+ checkE("", buf);
+ checkE(0, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testResize(){
+ DynBuffer buf(2, 2);
+ checkE(2, buf.size());
+ checkE(0, buf.length());
+ // blocksize is 2:
+ buf.resize(3);
+ checkE(4, buf.size());
+ checkE(0, buf.length());
+ // reserve more than blocksize:
+ buf.resize(7);
+ checkE(7, buf.size());
+ checkE(0, buf.length());
+ }
+ void testSet(){
+ DynBuffer buf("123");
+ checkE("123", buf);
+ checkE(3, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ // a shorter string:
+ buf.set("ab");
+ checkE("ab", buf);
+ checkE(2, buf.length());
+ checkT(buf.length() <= buf.size());
+
+ // a longer string:
+ buf.set("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ checkE("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buf);
+ checkE(26, buf.length());
+ checkT(buf.length() <= buf.size());
+ }
+ void testSetBlocksize(){
+ DynBuffer buf(2, 4);
+ checkE(2, buf.size());
+ checkE(0, buf.length());
+ checkE(4, buf.blocksize());
+
+ buf.setBlocksize(23);
+ checkE(2, buf.size());
+ checkE(0, buf.length());
+ checkE(23, buf.blocksize());
+ }
+ void testSetLength(){
+ DynBuffer buf("12345");
+ buf.setBlocksize(2);
+ checkE("12345", buf);
+ checkE(5, buf.size());
+ checkE(5, buf.length());
+
+ // make it shorter:
+ buf.setLength(4);
+ checkE("1234", buf);
+ checkE(4, buf.length());
+
+ // make it larger:
+ buf.setLength(8);
+ checkT(buf.startsWith("1234"));
+ checkE(8, buf.length());
+ checkE(8, buf.size());
+ }
+ // testSize() not implemented
+ void testStartsWith(){
+ DynBuffer buf("abc");
+ checkE(3, buf.length());
+
+ checkT(buf.startsWith("abc"));
+ checkT(buf.startsWith("ab"));
+ checkT(buf.startsWith("a"));
+
+ checkF(buf.startsWith("A"));
+ checkF(buf.startsWith("aBc"));
+
+ buf.clear().appendAsLE(0x32310041, 4);
+ checkE(4, buf.length());
+ checkE(0x41, buf.at(0));
+ checkE(0, buf.at(1));
+ checkE(0x31, buf.at(2));
+ checkE(0x32, buf.at(3));
+
+ checkT(buf.startsWith("A"));
+ checkE(4, buf.length());
+ DynBuffer buf2(buf);
+ checkT(buf.startsWith(buf2.str()));
+ for (int len = 3; len > 0; len--){
+ buf2.append("x");
+ checkF(buf.startsWith(buf2.str(), buf2.length()));
+ buf2.setLength(len);
+ checkT(buf.startsWith(buf2.str()));
+ }
+ }
+ void testValueOfLE(){
+ // int valueOfLE(int size, int index = 0, int defaultValue = -1);
+ DynBuffer buf("abc");
+ buf.appendAsLE(0x01020304, 4).append("xyz");
+ checkE(0x04, buf.valueOfLE(1, 3));
+ checkE(0x0304, buf.valueOfLE(2, 3));
+ checkE(0x020304, buf.valueOfLE(3, 3));
+ checkE(0x01020304, buf.valueOfLE(4, 3));
+ buf.buffer()[0] = 0x7f;
+ buf.buffer()[1] = 0xab;
+ buf.buffer()[2] = 0x77;
+ checkE(0x7f, buf.valueOfLE(1));
+ checkE(0xab7f, buf.valueOfLE(2));
+ checkE(0x77ab7f, buf.valueOfLE(3));
+ }
+ void testMemory(){
+ clock_t start = clock();
+ int count = 1000*1000;
+ int size = 10*1024*1024;
+ for (int ix = 0; ix < 1000; ix++){
+ DynBuffer big(size);
+ big.setLength(size + 10);
+ }
+ double duration = (clock() - start) / CLOCKS_PER_SEC;
+ printf("allocation of %d blocks with %d MiByte: %.3f sec\n", count, size / 1024 / 1024, duration);
+ }
+};
+
+void testDynBuffer(){
+ TestDynBuffer test;
+ test.run();
+}
--- /dev/null
+#include "test.hpp"
+//#define TRACE_ON
+#include "trace.hpp"
+
+static Logger s_logger;
+static ThreadPool s_timerPool(256, &s_logger);
+
+class Counter : public Timer{
+public:
+ Counter(Unit unit, int count, int* result, Announcer* logger, ThreadPool* pool) :
+ Timer(count, 10, 1, unit, true, logger, pool),
+ m_counter(result)
+ {
+
+ }
+public:
+ virtual void timerTask(){
+ trace1("Counter::timerTask %d\n", threadId());
+ m_pool.lock();
+ ++*m_counter;
+ m_pool.unlock();
+ }
+public:
+ int* m_counter;
+};
+class TestTimer : public UnitTest {
+public:
+ TestTimer() :
+ UnitTest("cutimer")
+ {
+
+ }
+public:
+ virtual void run(){
+ testMany();
+ test2Timers();
+ testBasic();
+ testTimer();
+ }
+ void testTimer(){
+ m_logger.say(LOG_INFO, "Start testTimer");
+
+ TimeMeasurement m;
+ int max = 1000000;
+ for (int ix = max; ix > 0; --ix){
+ struct timespec time;
+ clock_gettime(CLOCK_REALTIME, &time);
+ }
+ m.stop();
+ m.logDuration(max, &m_logger, "clock_gettime()");
+
+ m.start();
+ for (int ix = 50; ix > 0; --ix){
+ Thread::microSleep(10*1000);
+ }
+ m.stop();
+ m.logDuration(50, &m_logger, "microsleep(10*1000):");
+
+ m.start();
+ for (int ix = 500; ix > 0; --ix){
+ Thread::microSleep(1000);
+ }
+ m.stop();
+ m.logDuration(500, &m_logger, "microsleep(1000): ");
+
+ //extern int gv_calls, gv_rounds;
+ //m_logger.sayf(LOG_INFO, "calls: %d rounds: %d", gv_calls, gv_rounds);
+
+ m.start();
+ for (int ix = 10000; ix > 0; --ix){
+ Thread::microSleep(10);
+ }
+ m.stop();
+ m.logDuration(10000, &m_logger, "microsleep(10): ");
+
+ m.start();
+ for (int ix = 10000; ix > 0; --ix){
+ Thread::microSleep(1);
+ }
+ m.stop();
+ m.logDuration(10000, &m_logger, "microsleep(1): ");
+
+ }
+
+ void testBasic(){
+ int count = 0;
+ TimeMeasurement m;
+ Counter* counter1 = new Counter(Timer::uMilliSeconds, 50, &count, &s_logger, &s_timerPool);
+ int id1 = counter1->threadId();
+ counter1->start();
+ s_timerPool.join(id1);
+ m.stop();
+ m.logDuration(&m_logger);
+ checkT(m.m_durationReal >= 0.06);
+ checkE(50, count);
+ }
+ void test2Timers(){
+ int count = 0;
+ TimeMeasurement m;
+ Counter* counter1 = new Counter(Timer::uMilliSeconds, 10, &count, &s_logger, &s_timerPool);
+ int id1 = counter1->threadId();
+ Counter* counter2 = new Counter(Timer::uMilliSeconds, 10, &count, &s_logger, &s_timerPool);
+ int id2 = counter2->threadId();
+ counter1->start();
+ counter2->start();
+ m_logger.say(LOG_INFO, "counter1 + 2 started");
+ s_timerPool.join(id1);
+ s_timerPool.join(id2);
+ m.stop();
+ m.logDuration(&m_logger, "test2timers:");
+ checkT(m.m_durationReal >= 0.02);
+ checkE(20, count);
+ }
+ void testMany(){
+ TimeMeasurement m;
+ int count = 0;
+ const int MAX = 2000;
+ int id = 1;
+ while(id <= MAX){
+ if (s_timerPool.count() >= 256){
+ Thread::microSleep(100);
+ } else {
+ Counter* counter = new Counter(Timer::uMicroSeconds, 5, &count,
+ &s_logger, &s_timerPool);
+ counter->start();
+ id++;
+ }
+ }
+
+ for (int id = 1; id <= MAX; id++){
+ s_timerPool.join(id);
+ }
+ m.stop();
+ m.logDuration(MAX, &m_logger, "testMany:");
+ checkE(MAX*5, count);
+ }
+};
+
+void testTimer(){
+ TestTimer test;
+ test.run();
+}
--- /dev/null
+#include "test.hpp"
+#include "math.h"
+
+static Logger s_logger;
+static ThreadPool s_timerPool(2, &s_logger);
+
+class TestTimeUtils : public UnitTest {
+public:
+ TestTimeUtils() :
+ UnitTest("cutimeutils")
+ {
+
+ }
+public:
+ virtual void run(){
+ testBasic();
+ testTimeMeasurement();
+ }
+ void testBasic(){
+ const uint64_t MRD = 1000000000;
+ time_t start = time(NULL);
+ while(time(NULL) == start){
+ // do nothing
+ }
+ start++;
+ double dummy = 0.0;
+ uint64_t startReal = TimeUtils::nanosecSinceEpoche();
+ uint64_t startCpu = TimeUtils::nanosecSinceBoot();
+ int count;
+ uint64_t current;
+ while(startReal + MRD > (current = TimeUtils::nanosecSinceEpoche())){
+ dummy = dummy * 12.45 + 1.12345 * sin(dummy) + tan(dummy*7);
+ if (dummy > 1 || dummy < -1)
+ dummy = 1 / dummy;
+ ++count;
+ }
+ double durationCpu = (TimeUtils::nanosecSinceBoot() - startCpu) / (double) MRD;
+ double durationReal = (current - startReal) / (double) MRD;
+ int duration = time(NULL) - start;
+ m_logger.sayf(LOG_INFO, "duration: %.3f / %.3f", durationReal, durationCpu);
+ checkT(durationReal >= 1.0);
+ m_logger.sayf(LOG_INFO, "diff real-cpu: %f", durationReal - durationCpu);
+ checkT(durationCpu <= durationReal);
+ checkE(1, duration);
+ }
+ void testTimeMeasurement(){
+ time_t start = time(NULL);
+ while(time(NULL) == start){
+ // do nothing
+ }
+ TimeMeasurement m;
+ ++start;
+ double dummy = 0.0;
+ int count = 0;
+ while(time(NULL) == start){
+ dummy = dummy * 12.45 + 1.12345 * sin(dummy) + tan(dummy*7);
+ if (dummy > 1 || dummy < -1)
+ dummy = 1 / dummy;
+ ++count;
+ }
+ m.stop();
+ m.logDuration(&m_logger);
+ m.logDuration(count, &m_logger);
+ }
+
+};
+
+void testTimerUtils(){
+ TestTimeUtils test;
+ test.run();
+}
--- /dev/null
+#include "util.hpp"
+
+/**
+ * Constructor.
+ *
+ * @param bufferSize initial size of the buffer
+ * @param blocksize minimal increment of size when reallocation is needed<br>
+ * 0: use <i>bufferSize</i> as block size
+ */
+DynBuffer::DynBuffer(size_t bufferSize, size_t blocksize) :
+ m_size(bufferSize == 0 ? 16 : bufferSize),
+ m_length(0),
+ m_blocksize(blocksize == 0 ? m_size : blocksize),
+ m_buffer(new char[m_size + 1]){
+ m_buffer[0] = '\0';
+}
+/**
+ * Constructor.
+ *
+ * @param value initial value
+ * @param length 0: <i>strlen(value)</i> will be taken<br>
+ * otherwise: the length of value
+ * @param size the initial buffer size<br>
+ * 0: size depends on <i>length</i>
+ */
+DynBuffer::DynBuffer(const char* value, size_t length, size_t size) :
+ m_size(0),
+ m_length(length == 0 ? strlen(value) : length),
+ m_blocksize(256),
+ m_buffer(NULL){
+ m_size = size == 0 ? m_length : size < m_length ? m_length : size;
+ m_buffer = new char[m_size + 1];
+ memcpy(m_buffer, value, m_length);
+ m_buffer[m_length] = '\0';
+ }
+
+/**
+ * Destructor.
+ */
+DynBuffer::~DynBuffer(){
+ if (m_buffer != NULL){
+ delete[] m_buffer;
+ }
+ m_buffer = NULL;
+ m_length = 0;
+ m_size = 0;
+}
+/**
+ * Copy constructor.
+ *
+ * @param source the source to copy
+ */
+DynBuffer::DynBuffer(const DynBuffer& source) :
+ m_size(source.m_length < 16 ? 16 : source.m_length),
+ m_length(source.m_length),
+ m_blocksize(256),
+ m_buffer(new char[m_size + 1])
+{
+ // copy including the trailing '\0':
+ memcpy(m_buffer, source.m_buffer, m_length + 1);
+}
+/**
+ * Assign operator.
+ *
+ * @param source the source to copy
+ */
+DynBuffer& DynBuffer::operator =(const DynBuffer& source){
+ if (source.m_length == 0){
+ clear();
+ } else if (m_size >= source.m_length){
+ // copy with '\0':
+ memcpy(m_buffer, source.m_buffer, (m_length = source.m_length) + 1);
+ } else {
+ delete[] m_buffer;
+ m_buffer = new char[(m_size = m_length = source.m_length) + 1];
+ // copy with '\0':
+ memcpy(m_buffer, source.m_buffer, m_length + 1);
+ }
+ return *this;
+}
+/**
+ * Appends a string to the buffer.
+ *
+ * @param string the string to append
+ * @param size the length of the <i>string</i>. 0: use <i>strlen(string)</i>
+ * @return <i>*this</i> (for chaining)
+ */
+DynBuffer& DynBuffer::append(const char* string, size_t length){
+ if (length == 0)
+ length = strlen(string);
+ resize(m_length + length);
+ memcpy(m_buffer + m_length, string, length);
+ m_length += length;
+ m_buffer[m_length] = '\0';
+ return *this;
+}
+/**
+ * Appends a formatted string to the buffer.
+ *
+ * @param format the format like <i>sprintf()</i>
+ * @param ... the argument matching the format
+ * @return <i>*this</i> (for chaining)
+ */
+DynBuffer& DynBuffer::appendFormatted(const char* format, ...)
+{
+ char buffer[1024 * 64];
+ va_list argptr;
+ va_start(argptr, format);
+ vsnprintf(buffer, sizeof buffer, format, argptr);
+ va_end(argptr);
+ return append(buffer, strlen(buffer));
+}
+/**
+ * Appends an integer as string.
+ *
+ * @param value the value to add
+ * @param format the format like <i>sprintf()</i>
+ * @return <i>*this</i> (for chaining)
+ */
+DynBuffer& DynBuffer::appendInt(int value, const char* format){
+ char buffer[256];
+ snprintf(buffer, sizeof buffer, format, value);
+ return append(buffer, strlen(buffer));
+}
+/**
+ * Appends an integer as little endian binary number.
+ *
+ * @param value the value to append
+ * @param size the number of bytes to store: 1..sizeof(int)
+ * @return <i>*this</i> (for chaining)
+ */
+DynBuffer& DynBuffer::appendAsLE(int value, size_t size){
+ resize(m_length + size);
+ while(size-- > 0){
+ m_buffer[m_length++] = (char) value;
+ value >>= 8;
+ }
+ m_buffer[m_length] = '\0';
+ return *this;
+}
+/**
+ * Ensures that the size is greater or equal a given size.
+ *
+ * @param size the wanted size (not including the implicite trailing '\0')
+ * @return <i>*this</i> (for chaining)
+ */
+DynBuffer& DynBuffer::resize(size_t size){
+ if (m_length + size > m_size){
+ m_size = m_size + m_blocksize;
+ if (size > m_size)
+ m_size = size;
+ char* newBuffer = new char[m_size + 1];
+ // copy with '\0':
+ memcpy(newBuffer, m_buffer, m_length + 1);
+ delete[] m_buffer;
+ m_buffer = newBuffer;
+ }
+ return *this;
+}
+/**
+ * Gets a binary number in little endian format from the buffer.
+ *
+ * @param size the size of the number: 1..8
+ * @param index the first index
+ * @param defaultValue the return value if error occurs
+ * @return <i>defaultValue</i>: the index is wrong<br>
+ * otherwise: the integer from the given position
+ */
+int DynBuffer::valueOfLE(int size, int index, int defaultValue){
+ int ix;
+ int rc = 0;
+ if (index < 0 || size_t(index + size) > m_length)
+ rc = defaultValue;
+ else {
+ for (ix = index + size - 1; ix >= index; ix--){
+ rc = (rc << 8) + (ubyte_t) m_buffer[ix];
+ }
+ }
+ return rc;
+}
+
--- /dev/null
+#ifndef DYNBUFFER_H
+#define DYNBUFFER_H
+
+/**
+ * Implements a dynamic growing buffer.
+ * Can be used as "poor man's string implementation":
+ * the buffer always is terminated with a '\0' (like a C string).
+ * This ending zero is not counted by <i>m_length</i>.
+ */
+class DynBuffer {
+public:
+ DynBuffer(size_t bufferSize = 16, size_t blocksize = 0);
+ DynBuffer(const char* value, size_t length = 0, size_t size = 0);
+ ~DynBuffer();
+ DynBuffer(const DynBuffer& source);
+ DynBuffer& operator =(const DynBuffer& source);
+public:
+ DynBuffer& append(const char* string, size_t length = 0);
+ inline DynBuffer& append(const DynBuffer& source){
+ return append(source.str(), source.length());
+ }
+ DynBuffer& appendFormatted(const char* format, ...);
+ DynBuffer& appendInt(int value, const char* format = "%d");
+ DynBuffer& appendAsLE(int value, size_t size = 1);
+ /**
+ * Returns the character at a given position.
+ *
+ * @param index the index of the wanted character
+ * @return '\0': wrong index<br>
+ * otherwise: the character at the position <i>index</i>
+ */
+ inline char at(int index) const {
+ return index < 0 || index >= (int) m_length ? '\0' : m_buffer[index];
+ }
+ /**
+ * Returns the current blocksize.
+ *
+ * @return the current blocksize
+ */
+ inline size_t blocksize(){
+ return m_blocksize;
+ }
+ /**
+ * Returns the address of the internal buffer for writing to it.
+ *
+ * @param neededSize 0: do not change the size<br>
+ * otherwise: the buffer is resized to this value
+ * @return the internal buffer
+ */
+ inline char* buffer(size_t neededSize = 0){
+ if (neededSize > 0)
+ resize(neededSize);
+ return m_buffer;
+ }
+ /**
+ * Remove the buffer contents.
+ *
+ * @return <i>*this</i> (for chaining)
+ */
+ inline DynBuffer& clear(){
+ m_length = 0;
+ m_buffer[0] = '\0';
+ return *this;
+ }
+ /**
+ * Returns the current buffer length.
+ *
+ * @return the count of bytes in the buffer
+ */
+ inline size_t length() const {
+ return m_length;
+ }
+ DynBuffer& resize(size_t length);
+ /**
+ * Replace the buffer contents with a given string.
+ *
+ * @param source the string to set
+ * @param length 0: <i>strlen(source)</i> will be used<br>
+ * otherwise: the length of <i>source</i>
+ * @return <i>*this</i> (for chaining)
+ */
+ DynBuffer& set(const char* source, size_t length = 0){
+ return clear().append(source, length);
+ }
+ /**
+ * Sets the block size.
+ *
+ * @param blocksize the new block size, the minimal increment when reallocation
+ */
+ void setBlocksize(size_t blocksize){
+ m_blocksize = blocksize;
+ }
+ /**
+ * Sets the length of the buffer.
+ *
+ * @param length the new length. If larger than the size the size is increased
+ * but the content is not changed.
+ * @return <i>*this</i> (for chaining)
+ */
+ inline DynBuffer& setLength(int length){
+ if (length > (int) m_size)
+ resize(length);
+ m_buffer[m_length = length] = '\0';
+ return *this;
+ }
+ /**
+ * Returns the current size of the buffer.
+ *
+ * @return the current size
+ */
+ inline size_t size() const{
+ return m_size;
+ }
+ /**
+ * Tests whether the buffer starts with a given string.
+ *
+ * @param text text to inspect
+ * @param length length of <i>length</i><br>
+ * 0: use <i>strlen(text)</i>
+ * @return <i>true</i>: the text is found at the beginning of the buffer
+ */
+ inline bool startsWith(const char* text, size_t length = 0){
+ return memcmp(m_buffer, text, length == 0 ? strlen(text) : length) == 0;
+ }
+ /**
+ * Returns the buffer content as constant string.
+ */
+ inline const char* str() const {
+ return m_buffer;
+ }
+ /**
+ * Returns a binary stored integer (stored with <i>appendAsLE()</i>).
+ *
+ * @param size the size of the stored integer: 1..8
+ * @param index the index of the first byte of the integer
+ * @param defaultValue the value returned if the index is out of bounds
+ * @return <i>defaultValue</i>: index is wrong<br>
+ * otherwise: the integer stored at the given position
+ */
+ int valueOfLE(int size, int index = 0, int defaultValue = -1);
+private:
+ /// size of the buffer (without the trailing '\0')
+ size_t m_size;
+ /// length without the trailing '\0'
+ size_t m_length;
+ /// the minimal increment when reallocation is done
+ size_t m_blocksize;
+ /// Holds the buffer contents
+ char* m_buffer;
+};
+
+#endif // DYNBUFFER_H
--- /dev/null
+#include "util.hpp"
+
+/**
+ * Constructor.
+ */
+Logger::Logger() :
+ m_buffer(1024, 2048)
+{
+
+}
+
+/**
+ * Destructor.
+ */
+Logger::~Logger()
+{
+}
+
+/**
+ * Writes a log message.
+ *
+ * @param level type of the message: LOG_ERROR, LOG_WARNING, LOG_INFO
+ * @param message the message to Writes
+ */
+void Logger::say(LogLevel level, const char* message){
+ const char* prefix;
+ switch(level){
+ case LOG_ERROR:
+ m_errors++;
+ prefix = "+++ ";
+ break;
+ case LOG_WARNING:
+ m_warnings++;
+ prefix = "*** ";
+ break;
+ default:
+ prefix = "";
+ break;
+ }
+ printf("%s%s\n", prefix, message);
+}
--- /dev/null
+#ifndef LOGGER_H
+#define LOGGER_H
+
+enum LogLevel {
+ LOG_ERROR = 1,
+ LOG_WARNING,
+ LOG_INFO,
+ LOG_DEBUG
+};
+class Announcer{
+public:
+ Announcer() :
+ m_errors(0),
+ m_warnings(0)
+ {}
+public:
+ virtual void say(LogLevel level, const char* message) = 0;
+public:
+ void say(LogLevel level, const DynBuffer& message){
+ say(level, message.str());
+ }
+ void sayf(LogLevel level, const char* format, ...){
+ char dest[1024 * 64];
+ va_list argptr;
+ va_start(argptr, format);
+ vsnprintf(dest, sizeof dest, format, argptr);
+ va_end(argptr);
+ say(level, dest);
+ }
+ int errors() const {
+ return m_errors;
+ }
+ int warnings() const {
+ return m_warnings;
+ }
+protected:
+ int m_errors;
+ int m_warnings;
+};
+
+class Logger : public Announcer
+{
+public:
+ Logger();
+ ~Logger();
+private:
+ // no implementation, no external usage!
+ Logger(const Logger& other);
+ Logger& operator=(const Logger& other);
+public:
+ void say(LogLevel level, const char* message);
+private:
+ DynBuffer m_buffer;
+};
+
+#endif // LOGGER_H
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv) {
+
+ extern void testDynBuffer();
+ extern void testTimer();
+ extern void testTimerUtils();
+
+ testTimer();
+ testTimerUtils();
+ testDynBuffer();
+ return 0;
+}
--- /dev/null
+#ifndef __TEST_HPP
+#define __TEST_HPP
+#include "util.hpp"
+#include "unittest.hpp"
+
+#endif
--- /dev/null
+#include "util.hpp"
+//#define TRACE_ON
+#include "trace.hpp"
+
+int ThreadPool::m_currentMaxThreads = 128;
+ThreadPool* ThreadPool::m_instance = NULL;
+const pthread_mutex_t ThreadPool::m_mutexInitializer = PTHREAD_MUTEX_INITIALIZER;
+
+int gv_rounds = 0;
+int gv_calls;
+
+/**
+ * Connects the posix thread to the class instance.
+ */
+void* threadStarter(void *param){
+ trace("threadstarter");
+ Thread* thread = reinterpret_cast<Thread*>(param);
+ thread->execute();
+ return param;
+}
+/**
+ * Constructor.
+ *
+ * @param pool the thread pool
+ * @param autoDelete <i>true</i>: the instance destroys itself after <i>run()</i>
+ * If <i>true</i> the <i>new</i> operator must be used for creation
+ * @param logger the logger
+ */
+Thread::Thread(Announcer* logger, bool autoDelete, ThreadPool* pool) :
+ m_threadId(0),
+ m_shouldStop(false),
+ m_pool(pool == NULL ? *ThreadPool::instance(logger) : *pool),
+ m_logger(logger),
+ m_pthread(0),
+ m_autoDelete(autoDelete),
+ m_running(false)
+{
+ pool->append(*this);
+ trace1("Thread(%d)\n", m_threadId);
+}
+
+/**
+ * Destructor.
+ */
+Thread::~Thread() {
+ trace1("~Thread(%d)\n", m_threadId);
+}
+/**
+ * Executes the "run" of the thread and deactivates itself.
+ */
+void Thread::execute(){
+ trace1("Thread::execute(%d)\n", m_threadId);
+ m_running = true;
+ run();
+ m_pool.remove(*this);
+ if (m_autoDelete){
+ trace1("Thread::execute(%d) autodelete\n", m_threadId);
+ delete this;
+ }
+}
+
+/**
+ * Waits at least a given amount of microseconds.
+ *
+ * @param microseconds the time to wait
+ */
+void Thread::microSleep(uint64_t microseconds){
+ struct timespec wait;
+ wait.tv_sec = microseconds / 1000000;
+ wait.tv_nsec = microseconds % 1000000 * 1000;
+ nanosleep(&wait , NULL);
+}
+
+/**
+ * Lets the thread run.
+ */
+void Thread::start(){
+ pthread_create(&m_pthread, NULL, &threadStarter, this);
+ trace2("Thread::start(%d): %lx\n", m_threadId, m_pthread);
+}
+/**
+ * Introduces the termination of the thread.
+ *
+ * Cooperative mode: Sets only a flag. The thread must test this flag
+ * and terminate if set.
+ */
+void Thread::terminate(){
+ trace1("Thread::terminate(%d)\n", m_threadId);
+ m_shouldStop = true;
+}
+/**
+ * Returns the thread id.
+ *
+ * @return the thread id
+ */
+int Thread::threadId() const{
+ return m_threadId;
+}
+
+/**
+ * Constructor.
+ *
+ * @param maxThreads the maximum of active threads
+ * @param factory a factory for creating threads
+ * @param logger the logger
+ */
+ThreadPool::ThreadPool(int maxThreads, Announcer* logger) :
+ m_maxThreads(maxThreads),
+ m_list(new Thread*[m_currentMaxThreads]),
+ m_count(0),
+ m_nextId(1),
+ m_maxExitTime(50),
+ m_logger(logger),
+ m_mutex(m_mutexInitializer)
+{
+}
+/**
+ * Destructor.
+ */
+ThreadPool::~ThreadPool() {
+ trace1("~ThreadPool: %d\n", m_count);
+ terminateAll();
+ int maxMillisec = m_maxExitTime * 1000 + 500;
+ while (m_count != 0 && maxMillisec > 0){
+ usleep(10*1000);
+ maxMillisec -= 10;
+ }
+ // Force the termination of the non terminated:
+ pthread_t pthread;
+ trace1("~ThreadPool: force: %d\n", m_count);
+ while (m_count-- > 0){
+ lock();
+ Thread* thread = m_list[m_count];
+ pthread = thread->m_pthread;
+ unlock();
+ pthread_cancel(pthread);
+ }
+
+}
+/**
+ * Appends a thread to the pool.
+ *
+ * @param thread the thred to add
+ */
+void ThreadPool::append(Thread& thread){
+ lock();
+ thread.m_threadId = m_nextId++;
+ if (m_count == m_maxThreads){
+ unlock();
+ m_logger->sayf(LOG_ERROR, "too many threads: %d", m_maxThreads);
+ } else {
+ m_list[m_count++] = &thread;
+ unlock();
+ trace1("ThreadPool::append(): id: %d\n", thread.m_threadId);
+ }
+}
+
+/**
+ * Waits for the finishing of a given thread.
+ *
+ * @param threadId the thread identifier
+ * @return <i>NULL</i>: the thread does not already exist<br>
+ * otherwise: the exit code of the thread
+ */
+void* ThreadPool::join(int threadId){
+ trace1("ThreadPool::join(%d):\n", threadId);
+ void* rc = NULL;
+ Thread* thread = NULL;
+ pthread_t pthread = -1;
+ bool running = false;
+ do {
+ lock();
+ thread = findById(threadId);
+ if(thread == NULL){
+ unlock();
+ break;
+ } else {
+ pthread = thread->m_pthread;
+ running = thread->m_running;
+ }
+ unlock();
+ if (! running)
+ Thread::microSleep(10*1000);
+ } while(! running);
+ trace2("join: thread: %c %lx\n", thread != NULL ? 't' : 'f', pthread);
+ // Does the thread already exist?
+ if (pthread != (pthread_t) -1)
+ pthread_join(pthread, &rc);
+ trace1("join: rc: %c\n", rc == NULL ? 't' : 'f');
+ return rc;
+}
+
+/**
+ * Returns a singleton instance of the </i>ThreadPool</i>.
+ *
+ * @param logger the logger
+ * @return the singleton instance
+ */
+ThreadPool* ThreadPool::instance(Announcer* logger)
+{
+ if (m_instance == NULL)
+ m_instance = new ThreadPool(m_currentMaxThreads, logger);
+ return m_instance;
+}
+
+/**
+ * Deactivates a thread.
+ *
+ * @param thread the thread to deactivate.
+ */
+void ThreadPool::remove(Thread& thread){
+ // Destructor already called?
+ trace1("ThreadPool::remove(%d)\n", thread.threadId());
+ lock();
+ for (int ix = 0; ix < m_count; ix++){
+ if (m_list[ix] == &thread){
+ // 012x45
+ // (count - ix - 1)
+ memmove(m_list + ix, m_list + ix + 1,
+ (--m_count - ix) * sizeof m_list[0]);
+ thread.m_threadId = 0;
+ break;
+ }
+ }
+ unlock();
+}
+
+/**
+ * Search a thread given by its id.
+ *
+ * @param id the thread id
+ * @return NULL: not found<br>
+ * the thread with the given id
+ */
+Thread* ThreadPool::findById(int id){
+ Thread* rc = NULL;
+ for (int ix = 0; ix < m_count; ix++){
+ if (m_list[ix]->threadId() == id){
+ rc = m_list[ix];
+ break;
+ }
+ }
+ trace2("ThreadPool::findBy(%d): %c\n", id, rc == NULL ? 'f' : 't');
+ return rc;
+}
+/**
+ * Deactivates a thread.
+ *
+ * @param thread the thread to deactivate.
+ */
+void ThreadPool::terminateAll(){
+ trace("terminateAll()\n");
+ for (int ix = m_count - 1; ix >= 0; ix--){
+ m_list[ix]->terminate();
+ }
+}
--- /dev/null
+#ifndef THREAD_H
+#define THREAD_H
+#include "pthread.h"
+class ThreadPool;
+
+/**
+ * Implements a base class of a posix thread.
+ */
+class Thread {
+ friend ThreadPool;
+public:
+ Thread(Announcer* logger, bool autoDelete = true, ThreadPool* pool = NULL);
+ virtual ~Thread();
+private:
+ // not implemented, private avoids usage.
+ Thread ( const Thread& other );
+ Thread& operator= ( const Thread& other );
+public:
+ int threadId() const;
+ /**
+ * Does the real things.
+ */
+ virtual void run() = 0;
+ void start();
+ void terminate();
+public:
+ static void microSleep(uint64_t microseconds);
+private:
+ friend void* threadStarter(void *);
+ void execute();
+protected:
+ int m_threadId;
+ bool m_shouldStop;
+ ThreadPool& m_pool;
+ Announcer* m_logger;
+ pthread_t m_pthread;
+ bool m_autoDelete;
+ bool m_running;
+};
+
+/**
+ * Manages a pool of threads.
+ *
+ * Allows waiting for all threads...
+ * Can (but not must) be used as singleton.
+ */
+class ThreadPool {
+public:
+ ThreadPool(int maxThreads, Announcer* logger);
+ virtual ~ThreadPool();
+private:
+ // not implemented, private avoids usage:
+ ThreadPool(const ThreadPool& source);
+ ThreadPool& operator =(const ThreadPool& source);
+public:
+ static ThreadPool* instance(Announcer* logger);
+ static void microSleep(int microseconds);
+public:
+ void append(Thread& thread);
+ /**
+ * Returns the number of threads.
+ *
+ * @return the number of threads in the pool
+ */
+ inline int count() const {
+ return m_count;
+ }
+ void* join(int threadId);
+ /**
+ * Locks the pool specific mutual exclusion object.
+ */
+ inline void lock(){
+ pthread_mutex_lock(&m_mutex);
+ }
+ void remove(Thread& thread);
+ void terminateAll();
+ /**
+ * Unlocks the pool specific mutual exclusion object.
+ */
+ inline void unlock(){
+ pthread_mutex_unlock(&m_mutex);
+ }
+private:
+ Thread* findById(int id);
+public:
+ /// Change this value before calling the constructor.
+ /// The constructor can be called implicite by <i>instance()</i>.
+ static int m_currentMaxThreads;
+ static const pthread_mutex_t m_mutexInitializer;
+private:
+ static ThreadPool* m_instance;
+private:
+ int m_maxThreads;
+ Thread** m_list;
+ int m_count;
+ int m_nextId;
+ /// maximal waiting time for deactivating active threads while destructing (in 10 msec)
+ int m_maxExitTime;
+ Announcer* m_logger;
+ pthread_mutex_t m_mutex;
+};
+#endif // THREAD_H
--- /dev/null
+#include "util.hpp"
+//#define TRACE_ON
+#include "trace.hpp"
+/**
+ * Constructor.
+ *
+ * @param count number of actions
+ * @param delay the number of units to wait after the action
+ * @param startDelay number of units to wait before the first action
+ * @param unit the unit used for the times: <i>uMicroSecond</i>...
+ * @param autoDelete <i>true</i>: the instance destroys itself after the actions.
+ * If <i>true</i> the <i>new</i> operator must be used for creation
+ * @param logger the logger
+ * @param pool the thread pool. If <i>NULL</i> the global singleton is used
+ */
+Timer::Timer(int count, int delay, int m_startDelay, Unit unit,
+ bool autoDelete, Announcer* logger, ThreadPool* pool) :
+ Thread(logger, autoDelete, pool),
+ m_count(count),
+ m_delay(delay),
+ m_startDelay(delay),
+ m_unit(unit)
+{
+ trace2("Timer(%d, %d)\n", count, delay);
+}
+
+Timer::~Timer() {
+
+}
+
+/**
+ * The action called by <i>Thread</i>.
+ */
+void Timer::run(){
+ trace1("Timer::run(%d)\n", threadId());
+ if (m_startDelay > 0){
+ switch (m_unit){
+ case uSeconds:
+ sleep(m_startDelay);
+ break;
+ case uMilliSeconds:
+ microSleep(m_startDelay * 1000);
+ break;
+ case uMicroSeconds:
+ default:
+ microSleep(m_startDelay);
+ break;
+ }
+ }
+ while(m_count-- > 0){
+ trace2("Timer::run(%d): %d\n", threadId(), m_count);
+ timerTask();
+ switch (m_unit){
+ case Timer::uSeconds:
+ sleep(m_delay);
+ break;
+ case Timer::uMilliSeconds:
+ microSleep(m_delay * 1000);
+ break;
+ case Timer::uMicroSeconds:
+ usleep(m_delay);
+ break;
+ default:
+ break;
+ }
+ }
+}
--- /dev/null
+#ifndef TIMER_H
+#define TIMER_H
+
+/**
+ * Administrates threads for delayed actions.
+ *
+ * This is a abstract base class.
+ */
+class Timer : public Thread{
+public:
+ enum Unit {
+ uMicroSeconds,
+ uMilliSeconds,
+ uSeconds,
+ };
+public:
+ Timer(int count, int delay, int m_startDelay, Unit unit,
+ bool autoDelete, Announcer* logger, ThreadPool* pool = NULL);
+ ~Timer();
+private:
+ // not implemented, private to avoid usage:
+ Timer ( const Timer& other );
+ Timer& operator= ( const Timer& other );
+public:
+ virtual void timerTask() = 0;
+private:
+ virtual void run();
+private:
+ int m_count;
+ int m_delay;
+ int m_startDelay;
+ Unit m_unit;
+};
+
+#endif // TIMER_H
--- /dev/null
+#include "util.hpp"
+
+/**
+ * Constructor.
+ */
+TimeMeasurement::TimeMeasurement() :
+ m_startReal(0),
+ m_startCpu(0)
+{
+ start();
+}
+
+/**
+ * Destructor.
+ */
+TimeMeasurement::~TimeMeasurement() {
+
+}
+
+/**
+ * Logs the duration.
+ *
+ * @param logger the logger
+ * @param format the format (like in <i>sprintf()</i>) with 2 double placeholders<br>
+ * if <i>NULL</i>: "duration real: %.3f cpu: %3f"
+ */
+void TimeMeasurement::logDuration(Announcer* logger, const char* format){
+ static const char* stdFormat1 = "duration real: %.3f sec cpu: %3f sec";
+ static const char* stdFormat2 = "duration real: %.6f sec cpu: %6f sec";
+ DynBuffer format2;
+ if (format == NULL)
+ format = m_durationReal >= 1.0 ? stdFormat1 : stdFormat2;
+ else if (strchr(format, '%') == NULL){
+ format2.append(format).append(" ").append(m_durationReal >= 1 ? stdFormat1 : stdFormat2);
+ format = format2.str();
+ }
+ logger->sayf(LOG_INFO, format, m_durationReal, m_durationCpu);
+}
+/**
+ * Logs the duration of a mass event measurement.
+ *
+ * @param count number of events
+ * @param logger the logger
+ * @param format the format (like in <i>sprintf()</i>) with 1 integer
+ * and 4 double placeholders<br>
+ * if the format does not contain a placeholder ('%') it will be used
+ * as format2 to the standard format<br>
+ * if <i>NULL</i>: "count: %d duration real: %.3f (%.1f/sec) cpu: %3f (%.1f/s)"
+ */
+void TimeMeasurement::logDuration(int count, Announcer* logger, const char* format){
+ DynBuffer format2;
+ static const char* stdFormat1 = "count: %d duration real: %.3f sec (%.3g/sec) cpu: %.3f sec (%.3g/sec)";
+ static const char* stdFormat2 = "count: %d duration real: %.6f sec (%.3g/sec) cpu: %.6f sec (%.3g/sec)";
+ if (format == NULL)
+ format = m_durationReal >= 1.0 ? stdFormat1 : stdFormat2;
+ else if (strchr(format, '%') == NULL){
+ format2.append(format).append(" ").append(m_durationReal >= 1 ? stdFormat1 : stdFormat2);
+ format = format2.str();
+ }
+ logger->sayf(LOG_INFO, format,
+ count, m_durationReal, m_durationReal == 0 ? 0.0 : count / m_durationReal,
+ m_durationCpu, m_durationCpu == 0 ? 0.0 : count / m_durationCpu);
+}
--- /dev/null
+#ifndef TIME_UTILS_HPP
+#define TIME_UTILS_HPP
+
+class TimeUtils {
+public:
+ inline static uint64_t nanosecSinceEpoche(){
+ struct timespec time;
+ clock_gettime(CLOCK_REALTIME, &time);
+ return time.tv_sec * (uint64_t)1000000000 + time.tv_nsec;
+ }
+ inline static uint64_t nanosecSinceBoot(){
+ struct timespec time;
+ clock_gettime(CLOCK_REALTIME, &time);
+ return time.tv_sec * (uint64_t)1000000000 + time.tv_nsec;
+ }
+};
+
+class Announcer;
+class TimeMeasurement{
+public:
+ TimeMeasurement();
+ ~TimeMeasurement();
+private:
+ // not implemented, private to avoid usage:
+ TimeMeasurement ( const TimeMeasurement& other );
+ TimeMeasurement& operator= ( const TimeMeasurement& other );
+public:
+ void logDuration(Announcer* logger, const char* format = NULL);
+ void logDuration(int count, Announcer* logger, const char* format = NULL);
+ /**
+ * Starts a measurement event.
+ */
+ inline void start(){
+ m_startReal = TimeUtils::nanosecSinceEpoche();
+ m_startCpu = TimeUtils::nanosecSinceBoot();
+ }
+ /**
+ * Stops a measurement event.
+ */
+ inline void stop(){
+ m_durationCpu = (TimeUtils::nanosecSinceBoot() - m_startCpu) * 1E-9;
+ m_durationReal = (TimeUtils::nanosecSinceEpoche() - m_startReal) * 1E-9;
+ }
+public:
+ uint64_t m_startReal;
+ uint64_t m_startCpu;
+ double m_durationReal;
+ double m_durationCpu;
+};
+#endif // TIME_UTILS_HPP
--- /dev/null
+#ifndef TRACE_HPP
+#define TRACE_HPP
+
+#ifdef TRACE_ON
+#define trace(msg) printf(msg)
+#define trace1(format, arg1) printf(format, arg1)
+#define trace2(format, arg1, arg2) printf(format, arg1, arg2)
+#define tracef(arg) printf(arg)
+#else
+#define trace(msg)
+#define trace1(format, arg1)
+#define trace2(format, arg1, arg2)
+#define tracef(arg)
+#endif // TRACE_ON
+
+#endif // TRACE_HPP
--- /dev/null
+#include "test.hpp"
+
+/**
+ * Constructor.
+ *
+ * @param name name of the tested module
+ */
+UnitTest::UnitTest(const char* name) :
+ m_name(name),
+ m_logger()
+{
+ m_logger.sayf(LOG_INFO, "%s:", name);
+}
+
+/**
+ * Destructor.
+ */
+UnitTest::~UnitTest() {
+ if (m_logger.errors() > 0)
+ m_logger.sayf(LOG_ERROR, "%s has %d error(s)", m_name.str(), m_logger.errors());
+ else
+ m_logger.sayf(LOG_INFO, "%s successfully finished", m_name.str());
+}
+/**
+ * Tests, whether two strings are equals.
+ *
+ * @param expected the expected value
+ * @param current the current value
+ * @param line line number for the error mesage
+ */
+void UnitTest::equals(const char* expected, const char* current, int line){
+ if (strcmp(expected, current) != 0){
+ char divider = strlen(expected) > 20 ? '\n' : ' ';
+ error("%s-%d different:%c'%s'%c'%s'", m_name.str(), line,
+ divider, expected, divider, current);
+ }
+}
+/**
+ * Tests, whether two strings are equals.
+ *
+ * @param expected the expected value
+ * @param current the current value
+ * @param line line number for the error mesage
+ */
+void UnitTest::equals(const char* expected, const DynBuffer& current, int line){
+ equals(expected, current.str(), line);
+}
+/**
+ * Tests, whether two numbers are equals.
+ *
+ * @param expected the expected value
+ * @param current the current value
+ * @param line line number for the error mesage
+ */
+void UnitTest::equals(int expected, int current, int line){
+ if (expected != current){
+ error("%s-%d: different:%d/%d [%x/%x]", m_name.str(), line,
+ expected, current, expected, current);
+ }
+}
+/**
+ * Tests, whether the condition is false
+ *
+ * @param condition the condition to test
+ * @param line line number for the error mesage
+ */
+void UnitTest::isFalse(bool condition, int line){
+ if (condition)
+ error("%s-%d: is true", m_name.str(), line);
+}
+/**
+ * Tests, whether the pointer is not null
+ *
+ * @param pointer the pointer to test
+ * @param line line number for the error mesage
+ */
+void UnitTest::isNotNull(void* ptr, int line){
+ if (ptr == NULL)
+ error("%s-%d: is null", m_name.str(), line);
+}
+/**
+ * Tests, whether the pointer is null
+ *
+ * @param pointer the pointer to test
+ * @param line line number for the error mesage
+ */
+void UnitTest::isNull(void* ptr, int line){
+ if (ptr != NULL)
+ error("%s-%d: is not null", m_name.str(), line);
+}
+/**
+ * Tests, whether the condition is false
+ *
+ * @param condition the condition to test
+ * @param line line number for the error mesage
+ */
+void UnitTest::isTrue(bool condition, int line){
+ if (! condition)
+ error("%s-%d: is false", m_name.str(), line);
+}
+/**
+ * Tests, whether the condition is false
+ *
+ * @param condition the condition to test
+ * @param line line number for the error mesage
+ */
+void UnitTest::error(const char* format, ...){
+ char buffer[64000];
+ va_list argptr;
+ va_start(argptr, format);
+ vsnprintf(buffer, sizeof buffer, format, argptr);
+ va_end(argptr);
+ m_logger.say(LOG_ERROR, buffer);
+}
--- /dev/null
+#ifndef UNITTEST_HPP
+#define UNITTEST_HPP
+
+/**
+ * Unit tests like JUnit (java)
+ */
+class UnitTest {
+public:
+ UnitTest(const char* name);
+ ~UnitTest();
+private:
+ // not implemented, only private use.
+ UnitTest ( const UnitTest& other );
+ UnitTest& operator= ( const UnitTest& other );
+public:
+ void equals(const char* expected, const char* current, int line);
+ void equals(const char* expected, const DynBuffer& current, int line);
+ void equals(int expected, int current, int line);
+ void isFalse(bool condition, int line);
+ void isNotNull(void* ptr, int line);
+ void isNull(void* ptr, int line);
+ void isTrue(bool condition, int line);
+ virtual void run() = 0;
+protected:
+ void error(const char* format, ...);
+protected:
+ DynBuffer m_name;
+ Logger m_logger;
+};
+
+#define checkE(expected, current) equals(expected, current, __LINE__)
+#define checkF(condition) isFalse(condition, __LINE__)
+#define checkT(condition) isTrue(condition, __LINE__)
+#define checkNN(condition) isNotNull(pointer, __LINE__)
+#define checkN(condition) isNull(pointer, __LINE__)
+
+#endif // UNITTEST_H
--- /dev/null
+#ifndef UTIL_HPP
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <pthread.h>
+
+typedef unsigned char ubyte_t;
+typedef long long unsigned uint64_t;
+#include "dynbuffer.hpp"
+#include "timeutils.hpp"
+#include "logger.hpp"
+#include "thread.hpp"
+#include "timer.hpp"
+#endif