diff --git a/src/docs/Exceptions.dox b/src/docs/Exceptions.dox new file mode 100644 index 0000000..8493915 --- /dev/null +++ b/src/docs/Exceptions.dox @@ -0,0 +1,48 @@ +/* lw-support/src/docs/Exceptions.txt + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +/*! \page exceptions Exception Hierarchy + +LW Support provides a comprehensive exception subsystem. There are two +main categories of exception: errors (these are derived from the +lw::Exception class, the most important of which is probably lw::SystemError) +and program exceptions (which are derived from the lw::ProgramException +class). + +Errors will likely require user intervention, or at least some form of +error handling in higher level code. These exceptions are therefore +quite comprehensive, containing a human-readable message and also copies +of data that might be required to solve the error. + +Program exceptions are exceptional conditions which arise from time to +time but are seen as normal functioning of the program. Since these do +not denote errors, they are not intended to be propagated back to users +or higher level code, and therefore do not contain the same +comprehensive facilities as error classes. + +All LW Support exceptions are derived from the \a std::exception class, +which means they all support the \a what() member function, which +returns a human-readable string describing the error message. Since LW +Support uses UCS-4 strings, the message is converted into UTF-8 first. +The Exception base class also provides functions to explicitly get a +UTF-8 or UCS-4 string: lw::Exception::toUtf8() and lw::Exception::toString(). + +Error classes, which are all derived from lw::Exception (and may possibly be +part of a larger hierarchy themselves), also expose additional +functionality -- you can often request additional information about the +error. + +Program exceptions don't have a nice error message (they just use a +stock one telling you that you shouldn't see the error) and don't +generally have any further information. + +A useful feature is building up a chain of code through which the error +has propagated. This can be done with the lw::Exception::chain() member. +This feature should allow a user to narrow down the root cause of an +error. It is mainly of use when an exception has to propagate through +several layers of indirection and cannot be dealt with automatically. + +*/ diff --git a/src/docs/MainPage.dox b/src/docs/MainPage.dox index 7f84bc3..8f79acc 100644 --- a/src/docs/MainPage.dox +++ b/src/docs/MainPage.dox @@ -1,15 +1,22 @@ -/* lw-support/src/docs/MainPage.dox +/* lw-support/src/docs/MainPage.txt * - * (c)2006, Laurence Withers, . - * Released under the GNU GPLv2. See file COPYING or - * http://www.gnu.org/copyleft/gpl.html for details. + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. */ -/*! \mainpage +/*! \mainpage LW Support + +\section intro Introduction + +LW Support is a C++ library intended to provide additional functionality +to C++. For the most part, it simply exposes C library functionality in +an object-oriented manner. Some general pages about the various concepts +LW Support provides are listed here, but the majority of the +documentation is to be found in the class listings. + +\section concepts Concepts + +\li \ref exceptions LW Support's exception hierarchy. +\li \ref timedate Time and Date classes. */ - -/* options for text editors -kate: replace-trailing-space-save true; space-indent true; tab-width 4; -vim: expandtab:ts=4:sw=4 -*/ diff --git a/src/docs/TimeDate.dox b/src/docs/TimeDate.dox new file mode 100644 index 0000000..62d009a --- /dev/null +++ b/src/docs/TimeDate.dox @@ -0,0 +1,33 @@ +/* lw-support/src/docs/TimeDate.txt + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +/*! \page timedate Time and Date Classes + +LW Support has extensive support for time and date representation and +manipulation. It uses +ISO8601 to portably convert to/from a human-readable string +representation. + +The time support classes are split into two: the elapsed time classes +(which are used to represent a period of time), and the day time classes +(which are used to represent specific points in time). + +\section elapsed Elapsed Time + +The class used to represent an elapsed time period is lw::ElapsedTime. +It uses a nanosecond resolution, and can only represent positive time +periods (since negative time periods don't make sense). It is +supplemented by the stopwatch class, lw::StopWatch. + +\section calendar Calendar Time + +LW Support allows you to represent and manipulate calendar times (i.e. +times in the 24-hour clock and/or Gregorian dates). The 24-hour clock +is represented by lw::DayTime, with Gregorian dates stored in +lw::Date. These two classes are combined in the convenience class +lw::DateTime. + +*/ \ No newline at end of file diff --git a/src/docs/docs.in b/src/docs/docs.in new file mode 100644 index 0000000..a507fba --- /dev/null +++ b/src/docs/docs.in @@ -0,0 +1,3 @@ +src/docs/MainPage.txt +src/docs/Exceptions.txt +src/docs/TimeDate.txt diff --git a/src/liblw-support/BottomHeader.h b/src/liblw-support/BottomHeader.h index 48302a4..65a6d54 100644 --- a/src/liblw-support/BottomHeader.h +++ b/src/liblw-support/BottomHeader.h @@ -1,13 +1,18 @@ -/* lw-support/src/liblw-support/BottomHeader.h +/* lw-support/src/lib/BottomHeader.h * - * (c)2006, Laurence Withers, . - * Released under the GNU GPLv2. See file COPYING or - * http://www.gnu.org/copyleft/gpl.html for details. + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. */ +// deprecated classes go here +namespace lw { +#ifndef DOXYGEN +#define DEPRECATED(className) \ + typedef class className __attribute__((deprecated)) className +#undef DEPRECATED +#endif +} + +// end of include guard + #endif - -/* options for text editors -kate: replace-trailing-space-save true; space-indent true; tab-width 4; -vim: expandtab:ts=4:sw=4 -*/ diff --git a/src/liblw-support/Enums/IOMode.h b/src/liblw-support/Enums/IOMode.h new file mode 100644 index 0000000..0ac91fc --- /dev/null +++ b/src/liblw-support/Enums/IOMode.h @@ -0,0 +1,33 @@ +/* lw-support/src/lib/Enums/IOMode.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + +/*! \brief I/O open modes. + +This enumeration represents what a device is opened for; reading, +writing, both or neither. It also has a member to report that the +device isn't open at all. + +*/ +enum IOMode { + /// Device isn't open. + IOClosed, + + /// Device was opened in non-read, non-write mode. + IONone, + + /// Device is read only. + IORead, + + /// Device is write only. + IOWrite, + + /// Device is read/write. + IOReadWrite +}; + +} diff --git a/src/liblw-support/Enums/IOTimeoutMode.h b/src/liblw-support/Enums/IOTimeoutMode.h new file mode 100644 index 0000000..8f6643f --- /dev/null +++ b/src/liblw-support/Enums/IOTimeoutMode.h @@ -0,0 +1,76 @@ +/* lw-support/src/lib/Enums/IOTimeoutMode.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + +/*! \brief I/O timeout modes. + +There are a few different ways for I/O to time out, depending on +how you want the program to behave. There can either be a "hard +timeout", which specifies the absolute maximum waiting time, or a "soft +timeout", which can be overruled by chunks of incoming data. Similarly, +it might be acceptable to return with only some data transferred, or it +might not. + +*/ +enum IOTimeoutMode { + /*! \brief Hard timeout, partial transfer OK. + + This mode has a hard timeout, but partial data transfers are OK. In + this mode, the read is guaranteed to return within the timeout with + whatever data is available, but it doesn't guarantee to transfer the + full amount of data. No data will be lost. An IOTimeout exception + will only be thrown if no data is transferred at all during the + specified timeout. + + */ + IOTimeoutHardPartial, + + /*! \brief Hard timeout, full data only. + + This mode has a hard timeout during which all data must be + transferred. If only partial data transfer occurs, an IOTimeout + exception is thrown at the end of the timeout period. + + */ + IOTimeoutHardFull, + + /*! \brief Soft timeout, partial transfer OK. + + This mode has a soft timeout, which is to say that the timeout + period is restarted every time a partial transfer of data takes + place. An IOTimeout exception will only be thrown if no data is + transferred at all during the specified timeout. + + */ + IOTimeoutSoftPartial, + + /*! \brief Soft timeout, full data only. + + This mode has a soft timeout, which is to say that the timeout + period is restarted every time a partial transfer of data takes + place. However, upon reaching the end of the timeout period, an + exception will be thrown if all the data has not been transferred. + + */ + IOTimeoutSoftFull, + + /*! \brief One-shot data transfer. + + This mode is used when you want to perform all available I/O and + then return immediately (for instance, you might be listening for + commands on a network connection, but not know the size of each + command in advance). + + It will always return after it has completed as much data transfer + as possible. It will only throw an IOTimeout exception if no data is + transferred within the specified time limit. + + */ + IOTimeoutOneShot +}; + +} diff --git a/src/liblw-support/Enums/ISO8601Format.h b/src/liblw-support/Enums/ISO8601Format.h new file mode 100644 index 0000000..eed5c5e --- /dev/null +++ b/src/liblw-support/Enums/ISO8601Format.h @@ -0,0 +1,35 @@ +/* lw-support/src/lib/Enums/ISO8601Format.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Denotes the different formats accepted for ISO8601 dates. +enum ISO8601Format { + ISO8601FormatDetect, ///< Used to autodetect format when reading. + ISO8601FormatCalendar, ///< yyyymmdd + ISO8601FormatOrdinal, ///< yyyyddd + ISO8601FormatWeekDate ///< yyyyWwwd +}; + + + +/// Allows you to specify precision when converting to ISO8601 format. +enum ISO8601Precision { + ISO8601PrecisionFull, ///< Converts everything. + ISO8601PrecisionYear, ///< Only converts year. + ISO8601PrecisionWeek, ///< Converts year and week. + ISO8601PrecisionMonth, ///< Converts year and month. + ISO8601PrecisionDay, ///< Converts year, month and day. + ISO8601PrecisionHour, ///< Converts date and hour. + ISO8601PrecisionMinute, ///< Converts date, hour and minute. + ISO8601PrecisionSecond ///< Converts date and time. +}; + + + +} diff --git a/src/liblw-support/Events/CallbackIO.h b/src/liblw-support/Events/CallbackIO.h new file mode 100644 index 0000000..d481c19 --- /dev/null +++ b/src/liblw-support/Events/CallbackIO.h @@ -0,0 +1,43 @@ +/* lw-support/src/lib/Events/CallbackIO.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// I/O event callback object. +class EventCallbackIO { +public: + /// Destructor. Does nothing. + virtual ~EventCallbackIO() + { } + + /*! \brief I/O ready callback. + + \param flags A bitfield of events that have occurred. + + This function is called when the device it is associated with becomes ready for I/O. The details + will be indicated through the \a flags argument, which is a bitfield of: + - IOEventRead + - IOEventWrite + - IOEventUrgent + - IOEventError + - IOEventHUP + + Note that you cannot call any lw::EventManager functions within this callback, with the + exception of modifyDevice(). + + */ + virtual void ioReady(uint32_t flags) throw() = 0; +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Events/CallbackTimer.h b/src/liblw-support/Events/CallbackTimer.h new file mode 100644 index 0000000..31b9229 --- /dev/null +++ b/src/liblw-support/Events/CallbackTimer.h @@ -0,0 +1,37 @@ +/* lw-support/src/lib/Events/CallbackTimer.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Timer event callback object. +class EventCallbackTimer { +public: + /// Destructor. Does nothing. + virtual ~EventCallbackTimer() + { } + + + + /*! \brief Timer event callback. + + \param key The key of the timer event. + + This function is called whenever a timer event expires. It must not throw any exceptions. The + value of \a key is the value returned when you initially registered the timer. + + */ + virtual void timer(Key key) throw() = 0; +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Events/CallbackUpdate.cpp b/src/liblw-support/Events/CallbackUpdate.cpp new file mode 100644 index 0000000..477a1a3 --- /dev/null +++ b/src/liblw-support/Events/CallbackUpdate.cpp @@ -0,0 +1,22 @@ +/* lw-support/src/lib/Events/CallbackUpdate.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +EventCallbackUpdate::~EventCallbackUpdate() +{ + for(uint i = 0, end = managers.size(); i < end; i++) managers[i]->ignoreUpdate(this); +} + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Events/CallbackUpdate.h b/src/liblw-support/Events/CallbackUpdate.h new file mode 100644 index 0000000..8faa3ce --- /dev/null +++ b/src/liblw-support/Events/CallbackUpdate.h @@ -0,0 +1,44 @@ +/* lw-support/src/lib/Events/CallbackUpdate.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Update event callback object. + +Using lw::EventManager's update mechanism, objects can (in the event loop) register themselves as +requiring an update. After the event loop has completed, all objects needing an update will then be +processed. During this processing stage, it is safe for objects to delete themselves or other +objects (which may also be on the update list). The update function will be called at most once +per event loop, and only if the object has requested that it be added to the update list. + +It is possible for an object to be registered with more than one lw::EventManager's update list at +a time; but it will only be called a maximum of once per call to lw::EventManager::processEvents(), +regardless of how many times it is registered. Deleting an object which is registered with an +lw::EventManager will cause the object to become unregisterd. + +*/ +class EventCallbackUpdate { +public: + /// Destructor. Unregisters the device from any update lists it may be in. + virtual ~EventCallbackUpdate(); + + /// Update callback. The object can safely delete itself in here. + virtual void update(void) throw() = 0; + +private: + friend class EventManager; + std::vector managers; +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Events/EventManager.cpp b/src/liblw-support/Events/EventManager.cpp new file mode 100644 index 0000000..f2fb969 --- /dev/null +++ b/src/liblw-support/Events/EventManager.cpp @@ -0,0 +1,385 @@ +/* lw-support/src/lib/Events/EventManager.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +const uint32_t IOEventRead = EPOLLIN; +const uint32_t IOEventWrite = EPOLLOUT; +const uint32_t IOEventHUP = EPOLLHUP; +const uint32_t IOEventUrgent = EPOLLPRI; +const uint32_t IOEventError = EPOLLERR; + + + +struct EventManager::pimpl { + pimpl() + : epollFd(-1), epollEvents(0), numEvents(0), shutdownFlag(false), processing(false), + last_ns(now_ns()) + { + pipeFd[0] = -1; + pipeFd[1] = -1; + } + + ~pimpl() + { + delete [] epollEvents; + close(epollFd); + close(pipeFd[0]); + close(pipeFd[1]); + } + + // epoll-related structures + int epollFd; + struct epoll_event* epollEvents; + int numEvents; + + // pipe for signals/shutdown + int pipeFd[2]; + + // operational status + bool shutdownFlag, processing; + class ModifiedWhileProcessing : public lw::ProgramException { }; + void processingCheck(const wchar_t* chain) + { + if(processing) throw ModifiedWhileProcessing().chain(chain); + } + + // timer events + uint64_t last_ns; + static uint64_t now_ns() + { + struct timeval tv; + gettimeofday(&tv, 0); + uint64_t ret = tv.tv_sec; + ret *= tenExp9; + ret += tv.tv_usec * tenExp3; + return ret; + } + + struct TimerEntry { + EventCallbackTimer* timerCallback; + ElapsedTime remainingPeriod, recurringPeriod; + Key key; + + TimerEntry(EventCallbackTimer* timerCallback, ElapsedTime initialPeriod, + ElapsedTime recurringPeriod) + : timerCallback(timerCallback), remainingPeriod(initialPeriod), + recurringPeriod(recurringPeriod), key(Key::get()) + { } + }; + + struct TimerCmp { + bool operator()(const struct TimerEntry& a, const struct TimerEntry& b) + { + return a.remainingPeriod < b.remainingPeriod; + } + }; + + std::vector timers; // note -- this is a sorted list! + + enum InternalEvent { + InternalEventShutdown + }; + + void dieHorribly() + { + processing = false; + shutdownFlag = true; + throw lw::ProgramException() + .chain(L"EventManager::pimpl::dieHorribly()") + .chain(L"EventManager::processEvents()"); + } + + void processInternalEvents() + { + InternalEvent ev; + + while(true) { + ssize_t ret = read(pipeFd[pipeRead], (char*)&ev, sizeof(ev)); + if(ret == -1 && errno == EAGAIN) break; + if(ret != sizeof(ev)) dieHorribly(); + + switch(ev) { + case InternalEventShutdown: + shutdownFlag = true; + break; + + default: + dieHorribly(); + } + } + } + + // objects registered for updates (sorted list) + std::vector updateList; +}; + + + +EventManager::EventManager(int numDevicesHint, int numEvents) + : p(new pimpl) +{ + try { + if(numDevicesHint < 0) throw BadArgument(L"numDevicesHint", L">= 0", numDevicesHint); + if(numEvents < 1) throw BadArgument(L"numEvents", L">= 1", numEvents); + + // create epoll structure + p->numEvents = numEvents; + p->epollEvents = new struct epoll_event[numEvents]; + p->epollFd = epoll_create(numDevicesHint + 1); // for the pipe + if(p->epollFd < 0) throw SystemError().chain(L"epoll_create()"); + + // set up pipes (non-blocking mode required) + if(pipe(p->pipeFd) == -1) throw SystemError().chain(L"pipe()"); + int pipeFdFlags; + if( (pipeFdFlags = fcntl(p->pipeFd[0], F_GETFL, 0)) == -1 || + fcntl(p->pipeFd[0], F_SETFL, pipeFdFlags | O_NONBLOCK) || + (pipeFdFlags = fcntl(p->pipeFd[1], F_GETFL, 0)) == -1 || + fcntl(p->pipeFd[1], F_SETFL, pipeFdFlags | O_NONBLOCK)) + { + throw SystemError().chain(L"fcntl()"); + } + + struct epoll_event ev; + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN | EPOLLERR | EPOLLET; + if(epoll_ctl(p->epollFd, EPOLL_CTL_ADD, p->pipeFd[pipeRead], &ev)) + throw SystemError().chain(L"epoll_ctl(EPOLL_CTL_ADD)"); + } + catch(lw::Exception& e) { + delete p; + e.chain(L"EventManager::EventManager()"); + throw; + } +} + + + +EventManager::~EventManager() +{ + delete p; +} + + + +void EventManager::processEvents(int userTimeout) +{ + if(p->shutdownFlag) throw Shutdown(); + + // find the shortest required timeout + int timerTimeout = p->timers.empty() ? -1 : (int)(p->timers[0].remainingPeriod.to_ms()); + int timeout = std::min(userTimeout, timerTimeout); + if(timeout < 0) { + if(userTimeout >= 0) timeout = userTimeout; + else if(timerTimeout >= 0) timeout = timerTimeout; + else timeout = -1; + } + + // run the event loop + int ret = epoll_wait(p->epollFd, p->epollEvents, p->numEvents, timeout); + + if(ret == -1) { + if(errno == EINTR) return; + throw SystemError().chain(L"epoll_wait()").chain(L"EventManager::processEvents()"); + } + if(!ret && timerTimeout == -1) throw Timeout(); + + // process events + for(int i = 0; i < ret; ++i) { + EventCallbackIO* cb = (EventCallbackIO*)(p->epollEvents[i].data.ptr); + if(cb) cb->ioReady(p->epollEvents[i].events); + else p->processInternalEvents(); + } + + // evaluate timers + uint64_t now_ns = p->now_ns(), ns_elapsed = now_ns - p->last_ns; + p->last_ns = now_ns; + ElapsedTime elapsedTime = ElapsedTime::from_ns(ns_elapsed); + + bool timerFired = false; + int timerErased = 0; + for(uint i = 0, end = p->timers.size(); i < end; ++i) { + if(p->timers[i].remainingPeriod <= elapsedTime) { + // fire timer + timerFired = true; + + if(!p->timers[i].recurringPeriod) { + p->timers[i].timerCallback->timer(p->timers[i].key); + p->timers[i].remainingPeriod.set_ns(~(uint64_t)0); + ++timerErased; + continue; + } + + uint64_t remaining = ns_elapsed, + oldPeriod = p->timers[i].remainingPeriod.to_ns(), + recur = p->timers[i].recurringPeriod.to_ns(); + while(true) { + p->timers[i].timerCallback->timer(p->timers[i].key); + oldPeriod += recur; + if(remaining < oldPeriod) break; + remaining -= recur; + } + + oldPeriod -= remaining; + p->timers[i].remainingPeriod.set_ns(oldPeriod); + } else { + p->timers[i].remainingPeriod -= elapsedTime; + } + } + + // resort list if appropriate + if(timerFired) { + std::stable_sort(p->timers.begin(), p->timers.end(), pimpl::TimerCmp()); + if(timerErased) p->timers.erase(p->timers.end() - timerErased, p->timers.end()); + } + + // deal with all the objects that need to be updated + int x; + while((x = p->updateList.size())) { + --x; + p->updateList[x]->update(); + ignoreUpdate(p->updateList[x]); + } +} + + + +void EventManager::registerDevice(IODevice* ioDevice, EventCallbackIO* eventCallback, + uint32_t eventFlags) +{ + p->processingCheck(L"EventManager::registerDevice()"); + NullPointer::check(ioDevice, L"ioDevice", L"EventManager::registerDevice()"); + NullPointer::check(eventCallback, L"eventCallback", L"EventManager::registerDevice()"); + + struct epoll_event ev; + memset(&ev, 0, sizeof(ev)); + ev.data.ptr = eventCallback; + ev.events = (eventFlags & (EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP)) | EPOLLET; + if(epoll_ctl(p->epollFd, EPOLL_CTL_ADD, ioDevice->getListenFd(), &ev)) throw SystemError() + .chain(L"epoll_ctl(EPOLL_CTL_ADD)").chain(L"EventManager::registerDevice()"); +} + + + +void EventManager::modifyDevice(IODevice* ioDevice, EventCallbackIO* eventCallback, + uint32_t eventFlags) +{ + NullPointer::check(ioDevice, L"ioDevice", L"EventManager::modifyDevice()"); + NullPointer::check(eventCallback, L"eventCallback", L"EventManager::modifyDevice()"); + + struct epoll_event ev; + memset(&ev, 0, sizeof(ev)); + ev.data.ptr = eventCallback; + ev.events = (eventFlags & (EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP)) | EPOLLET; + if(epoll_ctl(p->epollFd, EPOLL_CTL_MOD, ioDevice->getListenFd(), &ev)) throw SystemError() + .chain(L"epoll_ctl(EPOLL_CTL_ADD)").chain(L"EventManager::modifyDevice()"); +} + + + +void EventManager::ignoreDevice(IODevice* ioDevice) +{ + p->processingCheck(L"EventManager::ignoreDevice()"); + NullPointer::check(ioDevice, L"ioDevice", L"EventManager::modifyDevice()"); + + if(epoll_ctl(p->epollFd, EPOLL_CTL_DEL, ioDevice->getListenFd(), 0)) throw SystemError() + .chain(L"epoll_ctl(EPOLL_CTL_DEL)").chain(L"EventManager::ignoreDevice()"); +} + + + +Key EventManager::registerTimer(EventCallbackTimer* timerCallback, ElapsedTime initialPeriod, + ElapsedTime recurringPeriod) +{ + p->processingCheck(L"EventManager::registerTimer()"); + NullPointer::check(timerCallback, L"timerCallback", L"EventManager::registerTimer()"); + if(!recurringPeriod) throw BadArgument(L"recurringPeriod", L"non-zero", L"zero"); + + pimpl::TimerEntry t(timerCallback, initialPeriod, recurringPeriod); + p->timers.insert(std::upper_bound(p->timers.begin(), p->timers.end(), t, pimpl::TimerCmp()), t); + return t.key; +} + + + +Key EventManager::registerTimer(EventCallbackTimer* timerCallback, ElapsedTime period) +{ + p->processingCheck(L"EventManager::registerTimer()"); + NullPointer::check(timerCallback, L"timerCallback", L"EventManager::registerTimer()"); + + pimpl::TimerEntry t(timerCallback, period, lw::ElapsedTime()); + p->timers.insert(std::upper_bound(p->timers.begin(), p->timers.end(), t, pimpl::TimerCmp()), t); + return t.key; +} + + + +void EventManager::ignoreTimer(Key key) +{ + p->processingCheck(L"EventManager::ignoreTimer()"); + for(uint i = 0, end = p->timers.size(); i < end; ++i) { + if(p->timers[i].key == key) { + p->timers.erase(p->timers.begin() + i); + return; + } + } +} + + + +void EventManager::registerUpdate(lw::EventCallbackUpdate* updateCallback) +{ + // search for it in the list, in case it's already registered + std::vector::iterator iter = + std::lower_bound(p->updateList.begin(), p->updateList.end(), updateCallback); + if(*iter == updateCallback) return; + + // do a sorted insert + p->updateList.insert(iter, updateCallback); + + // register ourselves with it, in case it's deleted + updateCallback->managers.push_back(this); +} + + + +void EventManager::ignoreUpdate(lw::EventCallbackUpdate* updateCallback) +{ + // search for it in the list, return if not found + std::vector::iterator iter = + std::lower_bound(p->updateList.begin(), p->updateList.end(), updateCallback); + if(*iter != updateCallback) return; + + // remove it, unregister with it + std::vector& evlist = (*iter)->managers; + p->updateList.erase(iter); + + for(uint i = 0, end = evlist.size(); i < end; ++i) { + if(evlist[i] == this) { + evlist.erase(evlist.begin() + i); + return; + } + } +} + + + +void EventManager::shutdown() +{ + pimpl::InternalEvent ev = pimpl::InternalEventShutdown; + write(p->pipeFd[pipeWrite], (char*)&ev, sizeof(ev)); + // TODO: yes, we ignore error handling here -- but what can we do? +} + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Events/EventManager.h b/src/liblw-support/Events/EventManager.h new file mode 100644 index 0000000..2fcedc6 --- /dev/null +++ b/src/liblw-support/Events/EventManager.h @@ -0,0 +1,265 @@ +/* lw-support/src/lib/Events/EventManager.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Flag to denote I/O device has input available. +extern const uint32_t IOEventRead; + +/// Flag to denote I/O device is ready for output. +extern const uint32_t IOEventWrite; + +/// Flag to denote I/O device has urgent input available. +extern const uint32_t IOEventUrgent; + +/// Flag to denote I/O device has developed an error condition. +extern const uint32_t IOEventError; + +/// Flag to denote I/O device has signalled HUP. +extern const uint32_t IOEventHUP; + + + +/*! \brief Event manager. + +This object is responsible for driving an event-driven application. It collects system events (I/O, +timers and signals) and dispatches them appropriately. This class uses \c epoll() (which is a +\c select() replacement) to implement fast and efficient I/O event notification. To use it, +interested objects must register themselves through the various registration functions. + +To poll for events, call the processEvents() function. This will process any events in the queue or, +if the queue is empty, will wait (possibly only for a limited time) for more. Object processing is +split into two phases within this function: first, all of the I/O, timer and signal callbacks are +processed, and then the update callbacks. + +During the first phase of callbacks, it is not legal for objects to delete themselves, delete other +objects registered with the event manager, or to call any of the event manager functions (other than +modifyDevice() and registerUpdate()). During the second phase (the update callbacks), these +restrictions no longer apply. It is not possible to throw exceptions from callback functions. + +*/ +class EventManager : public Uncopyable { +public: + /*! \brief Constructor. Sets up event manager. + + \param numDevicesHint The maximum likely number of devices that will be polled. + \param numEvents The number of events to process in a single loop. + \throws SystemError if an error occurs. + + The constructor creates and sets up the initial event manager. It expects two parameters: a + hint as to how many devices you might be interested in (the kernel uses this to pre-allocate + space, but the buffer size is dynamic anyway), and the number of events to process at once. This + parameter is actually the size of the buffer the kernel uses to pass events to userspace; a + larger buffer means more events to process per system call, but at the expense of memory usage. + A reasonable value is to match \a numDevicesHint. + + */ + EventManager(int numDevicesHint, int numEvents); + + + + /// Destructor. Cleans up. + ~EventManager(); + + + + /*! \brief Register interest in a device's I/O readiness. + + \param ioDevice The I/O device in which you are interested. + \param eventCallback The event sink object. + \param eventFlags A bitfield of flags signifying which events you are interested in receiving. + You may pass \c ~0 if you are interested in all events. + \throws SystemError on error. + \throws NullPointer if \a ioDevice or \a eventCallback are null. + + This function will register interest in receiving readiness notification for an I/O device. You + specify the event sink object, which receives a callback for each readiness change, and the + eventFlags object, which is a bitfield comprising the events you are interested in, and may + include: + - lw::IOEventRead + - lw::IOEventWrite + - lw::IOEventUrgent + - lw::IOEventError + - lw::IOEventHUP + + Once registered, you may cancel the callback by closing the device (once the kernel detects + the file descriptor is closed, it will automatically remove it from the list of devices we are + interested in), or explicitly by calling ignoreDevice() with the same parameters. If you do + close the device's file descriptor, you should not call ignoreDevice(). + + Note that it is your responsibility to free the EventCallbackIO object after calling + ignoreDevice() or closing the file descriptor. + + */ + void registerDevice(lw::IODevice* ioDevice, lw::EventCallbackIO* eventCallback, + uint32_t eventFlags); + + + + /*! \brief Modify device. + + \param ioDevice The I/O device in which you are interested. + \param eventCallback The event sink object. + \param eventFlags A bitfield of flags signifying which events you are interested in receiving. + You may pass \c ~0 if you are interested in all events. + \throws SystemError on error. + \throws NullPointer if \a ioDevice or \a eventCallback are null. + + This is like registerDevice(), only it modifies the information for a device you have already + added. It can be used to change the callback object or to change the events in which you are + interested. It can be called at any time, even from within a callback. + + */ + void modifyDevice(lw::IODevice* ioDevice, lw::EventCallbackIO* eventCallback, + uint32_t eventFlags); + + + + /*! \brief Stop listening to a device. + + \param ioDevice The I/O device in which you are no longer interested. + \throws SystemError on error. + \throws NullPointer if \a ioDevice is null. + + This stops reporting events for a particular device. It does not delete the event callback + object or close the device. If you have already closed the device, you do not need to call this + function, since devices are automatically removed from the epoll set when their file descriptors + are closed. + + */ + void ignoreDevice(lw::IODevice* ioDevice); + + + + /*! \brief Register a timer callback. + + \param timerCallback The callback object. + \param initialPeriod The initial time to wait before calling the callback. + \param recurringPeriod After the initial callback, this specifies the period at which the event + will be repeated. + \throws NullPointer if \a timerCallback is null. + \throws BadArgument if \a initialPeriod or \a recurringPeriod are zero. + \returns A unique key. + + This function allows you to register a timer object for regular callback events. It returns a + unique key that can be used with \a ignoreTimer (and which is also passed to the + EventCallbackTimer::timer() callback function). + + This system is limited by the timing resolution of the operating system, which is typically on + the order of 1-10 milliseconds. However, the average frequency over time will match what you + request; the events will simply be bunched up. + + */ + lw::Key registerTimer(lw::EventCallbackTimer* timerCallback, lw::ElapsedTime initialPeriod, + lw::ElapsedTime recurringPeriod); + + + + /*! \brief Register a one-shot timer callback. + + \param timerCallback The callback object. + \param period The time to wait before calling the callback. + \throws NullPointer if \a timerCallback is null. + \throws BadArgument if \a period is zero. + \returns A unique key. + + This function allows you to register a timer object for a single callback event. It returns a + unique key that can be used with \a ignoreTimer (and which is also passed to the + EventCallbackTimer::timer() callback function). + + This system is limited by the timing resolution of the operating system, which is typically on + the order of 1-10 milliseconds. + + */ + lw::Key registerTimer(lw::EventCallbackTimer* timerCallback, lw::ElapsedTime period); + + + + /*! \brief Stop a timer event. + + \param key The unique key returned from registerTimer(). + + Calling this function will stop a timer event from occuring. If the value of \a key you + specified does not exist, it will be ignored. + + */ + void ignoreTimer(lw::Key key); + + + + /*! \brief Register an object for updating. + + */ + void registerUpdate(lw::EventCallbackUpdate* updateCallback); + + + + /*! \brief Remove an object from the update list. + + */ + void ignoreUpdate(lw::EventCallbackUpdate* updateCallback); + + + + /*! \brief Process events. + + \param timeout The timeout, in milliseconds. Passing 0 means the + function will time out or return immediately; passing a negative + value means it will never time out. + \throws SystemError if an error occurred. This could either be an + error from the epoll_wait() function used internally, or an + asynchronous error from the backing thread. In either case, it + is only safe to delete the object and propagate the error. + \throws EventManager::Shutdown if the object has been shut down. + \throws Timeout if no events are processed during the specified + timeout period. + + This function will wait until there are some events to process. It + will then call the relevant callbacks and return. See the + registerDevice() function for more information about callbacks. + + This function is not reentrant. It should only ever be called by one + thread at once. + + */ + void processEvents(int timeout); + + /// Thrown when an EventManager times out. + class Timeout : public ProgramException { }; + + + + /*! \brief Shut down the object. + + \throws SystemError on error. + + This function marks the object as shut down. Any thread currently + waiting in processEvents(), or that calls processEvents() from now + on, will be + + This function is thread safe and reentrant. + + */ + void shutdown(); + + /// Thrown when an EventManager is shut down. + class Shutdown : public ProgramException { }; + +private: + // use pimpl here, since this class will only be created infrequently + struct pimpl; + pimpl* p; +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Exceptions/BadArgument.cpp b/src/liblw-support/Exceptions/BadArgument.cpp new file mode 100644 index 0000000..19362de --- /dev/null +++ b/src/liblw-support/Exceptions/BadArgument.cpp @@ -0,0 +1,37 @@ +/* lw-support/src/lib/Exceptions/BadArgument.cpp + * + * (c)2006, Laurence Withers. Released under the GNU GPL. See file COPYING for more information + * and terms of license, or read http://www.gnu.org/copyleft/gpl.html . +*/ + +namespace lw { + + + +BadArgument::BadArgument(const std::wstring& argName, const std::wstring& acceptableRange, + const std::wstring& value) + : Exception(format(argName, acceptableRange, value)), argName(argName), + acceptableRange(acceptableRange), value(value) +{ +} + + + +std::wstring BadArgument::format(const std::wstring& argName, const std::wstring& acceptableRange, + const std::wstring& value) +{ + std::wostringstream ostr; + ostr << "Invalid argument to function." + "\n Argument name : " << argName << + "\n Acceptable range: " << acceptableRange << + "\n Actual value : " << value; + return ostr.str(); +} + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Exceptions/BadArgument.h b/src/liblw-support/Exceptions/BadArgument.h new file mode 100644 index 0000000..76020ff --- /dev/null +++ b/src/liblw-support/Exceptions/BadArgument.h @@ -0,0 +1,78 @@ +/* lw-support/src/lib/Exceptions/BadArgument.h + * + * (c)2006, Laurence Withers. Released under the GNU GPL. See file COPYING for more information + * and terms of license, or read http://www.gnu.org/copyleft/gpl.html . +*/ + +namespace lw { + + + +/*! \brief Exception class: bad argument to function. + +This exception class is used when an invalid argument is passed to a function (for instance, a +negative size in bytes, etc.). It stores some useful details for tracking down the bad argument. + +*/ +class BadArgument : public lw::Exception { +public: + /*! \brief Constructor. + + \param argName Name of the invalid argument. + \param acceptableRange Human-understandable range of valid arguments. + \param value Actual value passed to function. + + */ + BadArgument(const std::wstring& argName, const std::wstring& acceptableRange, + const std::wstring& value); + + + + /*! \brief Constructor. + + \param argName Name of the invalid argument. + \param acceptableRange Human-understandable range of valid arguments. + \param value Actual value passed to function. + + */ + template BadArgument(const std::wstring& argName, const std::wstring& acceptableRange, + const T& value) + : Exception(format(argName, acceptableRange, mkValue(value))), argName(argName), + acceptableRange(acceptableRange), value(mkValue(value)) + { } + + + + /// Destructor (does nothing). + virtual ~BadArgument() throw() { } + + + + /// Name of the invalid argument. + const std::wstring argName; + + /// Human-understandable range of valid arguments. + const std::wstring acceptableRange; + + /// Actual value passed to function. + const std::wstring value; + +private: + template std::wstring mkValue(const T& value) + { + std::wostringstream ostr; + ostr << value; + return ostr.str(); + } + + std::wstring format(const std::wstring& argName, const std::wstring& acceptableRange, + const std::wstring& value); +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Exceptions/BadString.cpp b/src/liblw-support/Exceptions/BadString.cpp new file mode 100644 index 0000000..0b260b5 --- /dev/null +++ b/src/liblw-support/Exceptions/BadString.cpp @@ -0,0 +1,67 @@ +/* lw-support/src/lib/Exceptions/BadString.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring BadSourceChar::buildDesc(const char* seqStart, + const char* seqEnd, int seqOffset, const std::wstring& encodingName, + const std::wstring& reason) +{ + std::wostringstream o; + + o << L"Error parsing a string. Details:\n" + L" Encoding : " << encodingName << L"\n" + L" Reason : " << reason << L"\n" + L" Byte offset : " << seqOffset << L"\n" + L" Bad sequence: "; + o << std::hex << std::setfill(L'0'); + for(; seqStart != seqEnd; ++seqStart) + o << std::setw(2) << (uint)(uint8_t)(*seqStart); + + return o.str(); +} + + +BadSourceChar::BadSourceChar(const char* seqStart, const char* seqEnd, + int seqOffset, const std::wstring& encodingName, const std::wstring& reason) + : BadString(buildDesc( + seqStart, seqEnd, seqOffset, encodingName, reason)), + byteSeq(seqStart, seqEnd), seqOffset(seqOffset), + encoding(encodingName), reason(reason) +{ +} + + + +std::wstring CannotConvertChar::buildDesc(wchar_t srcChar, int seqOffset, + const std::wstring& srcEnc, const std::wstring& destEnc) +{ + std::wostringstream o; + + o << L"Error converting a string. Details:\n" + L" Source encoding : " << srcEnc << L"\n" + L" Destination encoding: " << destEnc << L"\n" + L" Byte offset : " << seqOffset << L"\n" + L" Source character : 0x" << std::hex << uint(srcChar) + << L", ``" << srcChar << L"''."; + + return o.str(); +} + + + +CannotConvertChar::CannotConvertChar(wchar_t srcChar, int seqOffset, + const std::wstring& srcEnc, const std::wstring& destEnc) + : BadString(buildDesc(srcChar, seqOffset, srcEnc, destEnc)), + srcChar(srcChar), seqOffset(seqOffset), srcEnc(srcEnc), destEnc(destEnc) +{ +} + + + +} diff --git a/src/liblw-support/Exceptions/BadString.h b/src/liblw-support/Exceptions/BadString.h new file mode 100644 index 0000000..c8f030f --- /dev/null +++ b/src/liblw-support/Exceptions/BadString.h @@ -0,0 +1,153 @@ +/* lw-support/src/lib/Exceptions/BadString.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base exception class for string-related errors. + +This simply serves as the base class for string- and character-related +errors. + +*/ +class BadString : public Exception { +public: + /*! \brief Constructor. + + \param desc A description of the error. + + */ + BadString(const std::wstring& desc) + : Exception(desc) + { } + + /// Destructor. + virtual ~BadString() throw() { } +}; + + + +/*! \brief Cannot represent source character. + +This exception is thrown when a source character in a given encoding +cannot be represented in the destination encoding (e.g. ASCII cannot +represent arbitrary Unicode characters). + +*/ +class CannotConvertChar : public BadString { +private: + wchar_t srcChar; + int seqOffset; + std::wstring srcEnc, destEnc; + + static std::wstring buildDesc(wchar_t srcChar, int seqOffset, + const std::wstring& srcEnc, const std::wstring& destEnc); + +public: + /*! \brief Constructor. + + \param srcChar The (Unicode) code point that cannot be represented. + \param seqOffset How far in the source string the conversion process + reached before encountering the code point. + \param srcEnc The name of the source encoding, e.g. "UCS-4". + \param destEnc The name of the destination encoding, e.g. "ASCII". + + */ + CannotConvertChar(wchar_t srcChar, int seqOffset, + const std::wstring& srcEnc, const std::wstring& destEnc); + + /// Destructor. + virtual ~CannotConvertChar() throw() + { } + + + + /// Find the source character (Unicode). + wchar_t getSourceChar() const + { return srcChar; } + + /// Find the offset in the source sequence. + int getSeqOffset() const + { return seqOffset; } + + /// Find the source encoding. + const std::wstring& getSourceEncoding() + { return srcEnc; } + + /// Find the destination encoding. + const std::wstring& getDestEncoding() + { return destEnc; } +}; + + + +/*! \brief Bad source character. + +This exception is thrown when converting a string from one encoding to +another, and a byte sequence in the source string is not a valid +character. This can happen with UTF-8 for example. + +The exception provides some context about where in the conversion +process the error occurred, potentially allowing a continuation of the +process, but certainly providing a detailed human-readable description +of the error. + +*/ +class BadSourceChar : public BadString { +private: + std::vector byteSeq; // the invalid byte sequence + int seqOffset; + std::wstring encoding; // the source encoding name + std::wstring reason; // a specific reason (if there is one) + + static std::wstring buildDesc(const char* seqStart, const char* seqEnd, + int seqOffset, const std::wstring& encodingName, + const std::wstring& reason); + +public: + /*! \brief Construct from a byte sequence. + + \param seqStart Pointer to the start of the invalid byte sequence. + \param seqEnd Pointer to one-past-the-end byte of the invalid byte + sequence. + \param seqOffset Offset (in bytes) of the invalid sequence from the + start of the string. This could potentially be used to restart a + conversion. + \param encodingName Name of the encoding (in UCS-4). + \param reason A specific reason for the character being bad, if one + can be ascertained. + + */ + BadSourceChar(const char* seqStart, const char* seqEnd, + int seqOffset, const std::wstring& encodingName, + const std::wstring& reason = std::wstring()); + + /// Destructor. + virtual ~BadSourceChar() throw() { } + + + + /// Query the invalid byte sequence. + const std::vector& getByteSeq() const + { return byteSeq; } + + /// Query the error offset. + int getSeqOffset() const + { return seqOffset; } + + /// Query the encoding. + const std::wstring& getEncoding() const + { return encoding; } + + /// Query the reason. + const std::wstring& getReason() const + { return reason; } +}; + + + +} diff --git a/src/liblw-support/Exceptions/BadTime.h b/src/liblw-support/Exceptions/BadTime.h new file mode 100644 index 0000000..f3473aa --- /dev/null +++ b/src/liblw-support/Exceptions/BadTime.h @@ -0,0 +1,66 @@ +/* lw-support/src/lib/Exceptions/BadTime.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Base class of all time-related exceptions. +class BadTime : public Exception { +public: + /// Constructor. + BadTime(const std::wstring& errorDesc) + : Exception(errorDesc) + { } + + /// Destructor. + virtual ~BadTime() throw() + { } +}; + + + +/*! \brief Thrown when a time operation overflows. + +This exception will be thrown when a subtraction operation on an elapsed +time period would have resulted in a negative time period. Since this +result does not make sense, the operation cannot proceed. + +*/ +class BadTimeOverflow : public BadTime { +public: + /// Constructor. + BadTimeOverflow() : BadTime(L"Cannot represent negative time periods.") + { } +}; + + + +/*! \brief Thrown when a time value is bad. + +This exception is thrown when a time value is bad (for instance, you are +trying to specify a negative period, or a minute of 61, etc.). + +*/ +class BadTimeValue : public BadTime { +public: + /// Constructor. + BadTimeValue() : BadTime(L"Bad time value.") + { } +}; + + + +/// Thrown when a bad date is specified. +class BadDate : public BadTime { +public: + BadDate() : BadTime(L"Bad date specified.") + { } +}; + + + +} diff --git a/src/liblw-support/Exceptions/DNS.cpp b/src/liblw-support/Exceptions/DNS.cpp new file mode 100644 index 0000000..fecb5cc --- /dev/null +++ b/src/liblw-support/Exceptions/DNS.cpp @@ -0,0 +1,31 @@ +/* lw-support/src/lib/Exceptions/DNS.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring DnsError::buildDesc(const std::wstring& reason, + const std::string& host) +{ + std::wostringstream o; + o << L"DNS operation failed.\n" + L" Description: " << reason << L"\n" + L" Host : " << utf8ToUcs4(host, true); + return o.str(); +} + + + +DnsError::DnsError(const std::wstring& reason, const std::string& host) + : Exception(buildDesc(reason, host)), + reason(reason), host(host) +{ +} + + + +} diff --git a/src/liblw-support/Exceptions/DNS.h b/src/liblw-support/Exceptions/DNS.h new file mode 100644 index 0000000..cc68ef1 --- /dev/null +++ b/src/liblw-support/Exceptions/DNS.h @@ -0,0 +1,50 @@ +/* lw-support/src/lib/Exceptions/DNS.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Thrown when a DNS error occurs. + +This class is thrown when a DNS lookup fails. It contains the name +of the host being looked up, and a description of what failed. + +*/ +class DnsError : public Exception { +private: + std::wstring reason; + std::string host; + + static std::wstring buildDesc(const std::wstring& reason, + const std::string& host); + +public: + /*! \brief Constructor. + + \param reason A description of what failed. + \param host The host name being manipulated. + + */ + DnsError(const std::wstring& reason, + const std::string& host); + + /// Destructor. + virtual ~DnsError() throw() + { } + + /// Get reason for failure. + const std::wstring& getReason() const + { return reason; } + + /// Get host name being looked up. + const std::string& getHost() const + { return host; } +}; + + + +} diff --git a/src/liblw-support/Exceptions/Exception.cpp b/src/liblw-support/Exceptions/Exception.cpp new file mode 100644 index 0000000..ecb5387 --- /dev/null +++ b/src/liblw-support/Exceptions/Exception.cpp @@ -0,0 +1,63 @@ +/* lw-support/src/lib/Exceptions/Exception.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +Exception::Exception(const std::wstring& errorDesc) + : errorDesc(errorDesc), whatBuffer(0) +{ +} + + + +Exception::~Exception() throw() +{ + free(whatBuffer); +} + + + +Exception& Exception::chain(const std::wstring& chainDesc) +{ + errorDesc.append(L"\n From: "); + errorDesc.append(chainDesc); + return *this; +} + + + +const char* Exception::what() const throw() +{ + free(whatBuffer); + whatBuffer = strdup(toUtf8().c_str()); + return whatBuffer ?: "Couldn't allocate memory for exception conversion."; +} + + + +std::string Exception::toUtf8() const +{ + try { + return ucs4ToUtf8(errorDesc); + } + catch(...) { + return "Couldn't encode exception text into UTF-8."; + } +} + + + +std::wostream& operator<<(std::wostream& ostr, const Exception& e) +{ + ostr << e.toString(); + return ostr; +} + + + +} diff --git a/src/liblw-support/Exceptions/Exception.h b/src/liblw-support/Exceptions/Exception.h new file mode 100644 index 0000000..762d880 --- /dev/null +++ b/src/liblw-support/Exceptions/Exception.h @@ -0,0 +1,143 @@ +/* lw-support/src/lib/Exceptions/Exception.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief LW Support exception base class. + +This is the base class for all LW Support exceptions. It is derived +from std::exception, allowing you to treat it as a standard library +exception, if that is desired. It provides an implementation of the +what() function that returns a UTF-8 representation of the error; the +error can also be accessed through the toString() and toUtf8() +methods. + +Exceptions chains may be built; these simply track the progress of the +exception through the library. An example of such a chain is: + +
void a()
+{
+    throw Exception("errorMessage");
+}
+
+void b()
+{
+    try {
+        a();
+    }
+    catch(Exception& e) {
+        e.chain("b()");
+    }
+}
+
+int main()
+{
+    try {
+        b();
+    }
+    catch(Exception& e) {
+        cout << e.what() << endl;
+    }
+    return 0;
+}
+ +This would give the following output: + +
errorMessage
+ From: b()
+ +It is guaranteed that no exceptions will be thrown from this class, +unless the system is totally out of memory and cannot allocate std::string +objects. + +*/ +class Exception : public std::exception { +private: + // description of the error itself + std::wstring errorDesc; + + // buffer for what() + mutable char* whatBuffer; + +public: + /*! \brief Construct from error message. + + \param errorDesc A UCS-4 description of the error. + + */ + Exception(const std::wstring& errorDesc); + + /// Destructor. + virtual ~Exception() throw(); + + + + /*! \brief Add a link in the error chain. + + \param chainDesc A UCS-4 description of the chain. + \returns a reference to the exception, so you can chain again. + + This allows you to add another link into the exception chain, after + the current exception. This is intended to be used when you want to + propagate a lower-level error to a higher level, but want to mark + where the exception has been on its way through. + + \note It is good practice to give a function name here. + + */ + Exception& chain(const std::wstring& chainDesc); + + + + /// Return UTF-8 error report. + virtual const char* what() const throw(); + + /// Return UTF-8 error report. + std::string toUtf8() const; + + /// Return UCS-4 error report. + const std::wstring& toString() const + { return errorDesc; } +}; + + + +/*! \brief LW Support program exception base class. + +Program exceptions are exceptions that are expected to occur, and do +not necessarily indicate system failure. They do signal that an +exceptional condition has arisen, but this is something that the program +should foresee and cope with. This sort of exception probably won't lead +directly to user intervention, so no description is required. + +*/ +class ProgramException : public Exception { +public: + ProgramException() + : Exception(L"Program exception. Should not be seen by users!") + { } +}; + + + +/// Numeric overflow class. +class NumericOverflow : public Exception { +public: + NumericOverflow() + : Exception(L"Numeric overflow.") + { } +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const lw::Exception&); + + + +} diff --git a/src/liblw-support/Exceptions/FIFO.cpp b/src/liblw-support/Exceptions/FIFO.cpp new file mode 100644 index 0000000..9f19110 --- /dev/null +++ b/src/liblw-support/Exceptions/FIFO.cpp @@ -0,0 +1,24 @@ +/* lw-support/src/lib/Exceptions/FIFO.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +namespace lw { + + + +std::wstring FIFOOutOfMemory::getDesc(size_t amt) +{ + std::wostringstream o; + + o << L"Ran out of system memory allocating " + << amt << L" bytes for FIFO."; + + return o.str(); +} + + + +} diff --git a/src/liblw-support/Exceptions/FIFO.h b/src/liblw-support/Exceptions/FIFO.h new file mode 100644 index 0000000..74df89b --- /dev/null +++ b/src/liblw-support/Exceptions/FIFO.h @@ -0,0 +1,63 @@ +/* lw-support/src/lib/Exceptions/FIFO.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// FIFO exception base class. +class FIFOException : public Exception { +public: + /// Constructor. + FIFOException(const std::wstring& desc) + : Exception(desc) + { } +}; + + + +/// FIFO memory limit exceeded. +class FIFOOverflow : public FIFOException { +public: + /// Constructor. + FIFOOverflow() + : FIFOException(L"FIFO buffer overflowed (too much data).") + { } +}; + + + +/// Ran out of system memory while enlarging FIFO. +class FIFOOutOfMemory : public FIFOException { +private: + static std::wstring getDesc(size_t amt); + size_t amt; + +public: + /// Constructor. + FIFOOutOfMemory(size_t amt) + : FIFOException(getDesc(amt)), amt(amt) + { } + + /// Get amount allocated, in bytes. + size_t getAmount() const + { return amt; } +}; + + + +/// Not enough data in FIFO. +class FIFOUnderflow : public FIFOException { +public: + /// Constructor. + FIFOUnderflow() + : FIFOException(L"FIFO buffer underflowed (not enough data).") + { } +}; + + + +} diff --git a/src/liblw-support/Exceptions/HTTP.cpp b/src/liblw-support/Exceptions/HTTP.cpp new file mode 100644 index 0000000..783a3bc --- /dev/null +++ b/src/liblw-support/Exceptions/HTTP.cpp @@ -0,0 +1,28 @@ +/* lw-support/src/lib/Exceptions/HTTP.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring HTTPMalformed::format(const std::wstring& msg) +{ + std::wostringstream o; + o << L"Error parsing HTTP header fields: " << msg << L"."; + return o.str(); +} + + + +HTTPMalformed::HTTPMalformed(const std::wstring& msg) + : Exception(format(msg)) +{ + chain(L"while parsing HTTP header fields"); +} + + + +} diff --git a/src/liblw-support/Exceptions/HTTP.h b/src/liblw-support/Exceptions/HTTP.h new file mode 100644 index 0000000..3dae4d0 --- /dev/null +++ b/src/liblw-support/Exceptions/HTTP.h @@ -0,0 +1,23 @@ +/* lw-support/src/lib/Exceptions/HTTP.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Thrown when HTTP is malformed. +class HTTPMalformed : public Exception { +private: + static std::wstring format(const std::wstring& msg); + +public: + /// Constructor. + HTTPMalformed(const std::wstring& msg); +}; + + + +} diff --git a/src/liblw-support/Exceptions/IO.cpp b/src/liblw-support/Exceptions/IO.cpp new file mode 100644 index 0000000..31b12aa --- /dev/null +++ b/src/liblw-support/Exceptions/IO.cpp @@ -0,0 +1,136 @@ +/* lw-support/src/lib/Exceptions/IO.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring IOModeError::getModeDesc(IOMode mode) +{ + switch(mode) { + case IOClosed: + return L"closed"; + case IONone: + return L"not opened for reading or writing"; + case IORead: + return L"opened for reading"; + case IOWrite: + return L"opened for writing"; + case IOReadWrite: + return L"opened for reading and writing"; + } + + // shouldn't reach here + return L"Bug in IOModeError::getModeDesc()"; +} + + + +std::wstring IOModeError::getDesc(IOMode currentMode, IOMode requiredMode, + const std::wstring& op) +{ + std::wostringstream o; + + o << L"Could not perform operation on device in its current mode.\n" + L" Current mode : " << getModeDesc(currentMode) << L"\n" + L" Required mode: " << getModeDesc(requiredMode) << L"\n" + L" Operation : " << op; + return o.str(); +} + + + +IOModeError::IOModeError(IOInterface* ioInterface, const std::wstring& op, + IOMode requiredMode) + : IOException(getDesc(ioInterface->getIOMode(), requiredMode, op), + ioInterface), + op(op), currentMode(ioInterface->getIOMode()), + requiredMode(requiredMode) +{ +} + + + +std::wstring IOTimeout::getDesc(const std::wstring& op, int timeout, + IOTimeoutMode timeoutMode, size_t amountTransferred) +{ + std::wostringstream o; + + o << L"I/O operation timed out.\n" + L" Operation : " << op << L"\n" + L" Timeout : " << timeout; + if(timeout < 0) o << L" (never -- so why are we here?)"; + else if(!timeout) o << L" (immediately)"; + else o << "ms"; + + o << L"\n Timeout mode: "; + switch(timeoutMode) { + case IOTimeoutHardPartial: + o << L"hard timeout, partial transfer OK"; + break; + + case IOTimeoutHardFull: + o << L"hard timeout, full transfer only"; + break; + + case IOTimeoutSoftPartial: + o << L"soft timeout, partial transfer OK"; + + case IOTimeoutSoftFull: + o << L"soft timeout, full transfer only"; + break; + + case IOTimeoutOneShot: + o << L"one-shot"; + break; + } + + o << L"\n Bytes transferred before timeout: " << amountTransferred; + + return o.str(); +} + + + +IOTimeout::IOTimeout(IOInterface* ioInterface, const std::wstring& op, + int timeout, IOTimeoutMode timeoutMode, size_t amountTransferred) + : IOException(getDesc(op, timeout, timeoutMode, amountTransferred), + ioInterface), + op(op), timeout(timeout), timeoutMode(timeoutMode), + amountTransferred(amountTransferred) +{ +} + + + +std::wstring FileNotFound::getDesc(const char* fileName) +{ + std::wostringstream o; + + o << L"File not found.\n" + L" Filename: "; + if(fileName) o << utf8ToUcs4(fileName, true); + else o << L"(null)"; + + return o.str(); +} + + + +FileNotFound::FileNotFound(const char* fileName) + : Exception(getDesc(fileName)), fileName(fileName) +{ +} + + + +FileNotFound::~FileNotFound() throw() +{ +} + + + +} diff --git a/src/liblw-support/Exceptions/IO.h b/src/liblw-support/Exceptions/IO.h new file mode 100644 index 0000000..b95b4f4 --- /dev/null +++ b/src/liblw-support/Exceptions/IO.h @@ -0,0 +1,170 @@ +/* lw-support/src/lib/Exceptions/IO.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Base class for all I/O device exceptions. +class IOException : public Exception { +private: + IOInterface* ioInterface; + +public: + /// Constructor. + IOException(const std::wstring& desc, IOInterface* ioInterface) + : Exception(desc), + ioInterface(ioInterface) + { } + + /// Destructor. Does nothing. + virtual ~IOException() throw() + { } + + /// Get the underlying device. + IOInterface* getIOInterface() const + { return ioInterface; } +}; + + + +/// Thrown when trying to read past end-of-file. +class EndOfFile : public IOException { +public: + /// Constructor. + EndOfFile(IOInterface* ioInterface) + : IOException(L"End of file reached.", ioInterface) + { } + + /// Destructor. Does nothing. + virtual ~EndOfFile() throw() + { } +}; + + + +/*! \brief Operation not permitted on device in current mode. + +This exception is thrown whenever you try to carry out an operation on a +device, but the device isn't opened in the correct mode (e.g. if you try +to read from a write-only device, or if you try to write to an unopened +device). + +*/ +class IOModeError : public IOException { +private: + const std::wstring op; + const IOMode currentMode, requiredMode; + + static std::wstring getModeDesc(IOMode mode); + static std::wstring getDesc(IOMode currentMode, IOMode requiredMode, + const std::wstring& op); + +public: + /*! \brief Constructor. + + \param ioInterface The interface which was the source of the error. + \param requiredMode What the device needed to be opened in for the + operation to be legal. + \param op A quick description of the operation (probably the + function name). + + */ + IOModeError(IOInterface* ioInterface, const std::wstring& op, + IOMode requiredMode); + + /// Destructor. Does nothing. + virtual ~IOModeError() throw() + { } + + /// Returns the name of the operation that was attempted. + const std::wstring& getOp() const + { return op; } + + /// Returns the device's current mode. + IOMode getCurrentMode() const + { return currentMode; } + + /// Returns the mode required for the operation. + IOMode getRequiredMode() const + { return requiredMode; } +}; + + + +/*! \brief I/O timed out. + +This exception is thrown whenever an I/O operation times out. + +*/ +class IOTimeout : public IOException { +private: + const std::wstring op; + const int timeout; + const IOTimeoutMode timeoutMode; + const size_t amountTransferred; + + static std::wstring getDesc(const std::wstring& op, int timeout, + IOTimeoutMode timeoutMode, size_t amountTransferred); + + +public: + /*! \brief Constructor. + + \param ioInterface The device that timed out. + \param op Name of the operation that was being performed (probably + the function name). + \param timeout The timeout value, in milliseconds. + \param timeoutMode The timeout mode. + \param amountTransferred The amount actually transferred before + timing out. + + */ + IOTimeout(IOInterface* ioInterface, const std::wstring& op, int timeout, + IOTimeoutMode timeoutMode, size_t amountTransferred); + + /// Destructor. Does nothing. + virtual ~IOTimeout() throw() + { } + + /// Returns the name of the operation that was attempted. + const std::wstring& getOp() const + { return op; } + + /// Returns the timeout value. + int getTimeout() const + { return timeout; } + + /// Returns the timeout mode. + IOTimeoutMode getTimeoutMode() const + { return timeoutMode; } + + /// Returns the amount of data actually transferred before timeout. + size_t getAmountTransferred() const + { return amountTransferred; } +}; + + + +/// File not found exception. +class FileNotFound : public Exception { +private: + static std::wstring getDesc(const char* fileName); + +public: + /// Constructor. + FileNotFound(const char* fileName); + + /// Destructor. + ~FileNotFound() throw(); + + /// The filename. + const std::string fileName; +}; + + + +} diff --git a/src/liblw-support/Exceptions/NullPointer.h b/src/liblw-support/Exceptions/NullPointer.h new file mode 100644 index 0000000..f16f509 --- /dev/null +++ b/src/liblw-support/Exceptions/NullPointer.h @@ -0,0 +1,53 @@ +/* lw-support/src/lib/Exceptions/NullPointer.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Null pointer exception. + +This exception is used whenever a function that is expecting a valid pointer is passed a null +pointer. + +*/ +class NullPointer : public Exception { +public: + /// Construct from pointer name. + NullPointer(const std::wstring& pointerName) + : lw::Exception(L"Null pointer exception: " + pointerName), pointerName(pointerName) + { } + + /// Destructor (does nothing). + virtual ~NullPointer() throw() + { } + + /// Name of the pointer causing the problem. + const std::wstring pointerName; + + /*! \brief Check a pointer. + + \param ptr The pointer to check. + \param name Descriptive name of the pointer. + \param func Name of the function, passed to chain(). May be omitted. + + If \a ptr is a null (zero) pointer, this function will throw a NullPointer exception. + Otherwise, it does nothing. + + */ + inline static void check(const void* ptr, const wchar_t* name, const wchar_t* func = 0) + { + if(!ptr) { + if(func) throw NullPointer(name).chain(func); + throw NullPointer(name); + } + } + +}; + + + +} diff --git a/src/liblw-support/Exceptions/ParseError.cpp b/src/liblw-support/Exceptions/ParseError.cpp new file mode 100644 index 0000000..d2c608c --- /dev/null +++ b/src/liblw-support/Exceptions/ParseError.cpp @@ -0,0 +1,46 @@ +/* lw-support/src/lib/Exceptions/ParseError.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring ParseError::constructDesc(const std::wstring& reason, + const std::wstring& src, std::wstring::size_type errorPos) +{ + std::wostringstream o; + std::wstring::size_type len = src.length(); + + if(!len) { + o << "Error while parsing input.\n" + " Reason: " << reason << "\n" + " (std::string is empty)"; + return o.str(); + } + + std::wstring::size_type start = std::max(errorPos, std::wstring::size_type(32)) - 32, + end = std::min(errorPos + 32, len); + + o << "Error while parsing input.\n" + " Reason : " << reason << "\n" + " Char offset : " << errorPos << "\n" + " Nearby : " << src.substr(start, end) << "\n" + " Error at : " << std::wstring(errorPos - start, L' ') << '^'; + return o.str(); +} + + + +ParseError::ParseError(const std::wstring& reason, + const std::wstring& src, std::wstring::size_type errorPos) + : Exception(constructDesc(reason, src, errorPos)), + reason(reason), src(src), errorPos(errorPos) +{ +} + + + +} diff --git a/src/liblw-support/Exceptions/ParseError.h b/src/liblw-support/Exceptions/ParseError.h new file mode 100644 index 0000000..b680423 --- /dev/null +++ b/src/liblw-support/Exceptions/ParseError.h @@ -0,0 +1,58 @@ +/* lw-support/src/lib/Exceptions/ParseError.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Exception thrown when parsing a std::string fails. + +This exception should be thrown when the parsing of a std::string fails. It +is intended for std::strings which are "displayable", since the exception +message will contain a copy of (part of) the std::string being parsed. The +exception message is intended for output onto a fixed-width font +display. + +*/ +class ParseError : public Exception { +private: + std::wstring reason, src; + std::wstring::size_type errorPos; + + static std::wstring constructDesc(const std::wstring& reason, + const std::wstring& src, std::wstring::size_type errorPos); + +public: + /*! \brief Constructor. + + \param reason A description of why the parsing error occured. + \param src The source std::string. + \param errorPos The character offset at which the error occured. + + */ + ParseError(const std::wstring& reason, + const std::wstring& src, std::wstring::size_type errorPos); + + /// Destructor. + virtual ~ParseError() throw() + { } + + /// Gets the reason for the parsing error. + const std::wstring& getReason() const + { return reason; } + + /// Gets the source std::string. + const std::wstring& getSource() const + { return src; } + + /// Gets the position (in characters) at which the error occured. + std::wstring::size_type getErrorPos() const + { return errorPos; } +}; + + + +} diff --git a/src/liblw-support/Exceptions/SystemError.cpp b/src/liblw-support/Exceptions/SystemError.cpp new file mode 100644 index 0000000..c88c19a --- /dev/null +++ b/src/liblw-support/Exceptions/SystemError.cpp @@ -0,0 +1,90 @@ +/* lw-support/src/lib/Exceptions/SystemError.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring SystemError::errnoToString(int errnum) +{ + std::wostringstream o; + o << L"System error.\n" + L" Error number: " << errnum << L"\n" + L" Description : "; + +#if 0 + // this is needed if we're using the non-GNU version of strerror_r() + // specified by POSIX, with signature: + // int strerror_r(int, char*, int) + // + // the GNU version may go away one day, according to the manpage. + int bufSize = 500; + char* buf = 0; + bool loop = true; + while(loop) { + delete [] buf; + bufSize <<= 1; + buf = new char[bufSize]; + + errno = 0; + if(strerror_r(errnum, buf, bufSize)) { + switch(errno) { + case ERANGE: + break; // go through again + case EINVAL: + o << L"(unknown -- invalid error code)"; + loop = false; + break; + default: + o << L"(unknown -- error " << errno + << L" during strerror_r conversion)"; + loop = false; + break; + } + } else { + // conversion was OK + try { + o << utf8ToUcs4(buf); + } + catch(...) { + delete [] buf; + o << "(invalid UTF-8 std::string)"; + } + loop = false; + } + } + + delete [] buf; +#else + // this is needed for the GNU version of strerror_r(), with + // signature: + // char* strerror_r(int, char*, int); + // + // see implementation at glibc/sysdeps/generic/_strerror.c + char buf[30]; + o << utf8ToUcs4(strerror_r(errnum, buf, sizeof(buf)), true); +#endif + + return o.str(); +} + + + +SystemError::SystemError() + : Exception(errnoToString(errno)) +{ +} + + + +SystemError::SystemError(int errnum) + : Exception(errnoToString(errnum)) +{ +} + + + +} diff --git a/src/liblw-support/Exceptions/SystemError.h b/src/liblw-support/Exceptions/SystemError.h new file mode 100644 index 0000000..a1519a0 --- /dev/null +++ b/src/liblw-support/Exceptions/SystemError.h @@ -0,0 +1,51 @@ +/* lw-support/src/lib/Exceptions/SystemError.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief System call error. + +This exception is used whenever a system call results in an error that +cannot be handled by LW Support (for instance, if an I/O device fails to +open). Many of the important cases will be handled with their own, +specific exception classes; those that aren't will be handled with this +class. + +This class is intended as a direct replacement for the global variable +\a errno. + +*/ +class SystemError : public Exception { +private: + static std::wstring errnoToString(int errnum); + +public: + /*! \brief Construct from errno. + + This constructor will examine the current value of \a errno and + build the exception from there. The exception is always built, even + if \a errno is 0. + + */ + SystemError(); + + /*! \brief Construct from error number. + + \param errnum The error number. + + This constructor doesn't use the global \a errno variable, but + instead uses the user-supplied value. The exception is always built, + even if \a errnum is 0. + + */ + SystemError(int errnum); +}; + + + +} diff --git a/src/liblw-support/ForwardDeclare.h b/src/liblw-support/ForwardDeclare.h new file mode 100644 index 0000000..aa059d4 --- /dev/null +++ b/src/liblw-support/ForwardDeclare.h @@ -0,0 +1,90 @@ +/* lw-support/src/lib/ForwardDeclare.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +// This file simply contains forward declarations of all LW Support +// classes, to facilitate header ordering, etc. + + + +/// This namespace holds the LW Support core classes. +namespace lw { + + + +class BadDate; +class BadSourceChar; +class BadString; +class BadTime; +class BadTimeOverflow; +class BadTimeValue; +class CannotConvertChar; +class CompletionCallback; +class CompletionData; +class CompletionList; +class CompletionNotifier; +class CompletionTask; +class Date; +class DateTime; +class DayTime; +class DnsError; +class ElapsedTime; +class EndOfFile; +class EndPipe; +class EventCallbackIO; +class EventCallbackNetServer; +class EventCallbackTimer; +class EventManager; +class Exception; +class FIFO; +class FIFOException; +class FIFOOutOfMemory; +class FIFOOverflow; +class FIFOUnderflow; +class FileNotFound; +class HTTPHeaderParser; +class HTTPMalformed; +class HTTPRequestParser; +class HTTPResponseParser; +class IODevice; +class IOException; +class IOFilter; +class IOInterface; +class IOModeError; +class IOPosixDevice; +class IOSerialPort; +class IOTimeout; +class IOWriteTask; +class Key; +class Log; +class LoggingSMTPClient; +class Mutex; +class MutexHolder; +class MutexPadlock; +class MutexPadlockRecursive; +class NetAddress; +class NetAddressIPv4; +class NetAddressIPv6; +class NetClient; +class NetClientTCP; +class NetServer; +class NetServerCallback; +class NetServerTCP; +class NullPointer; +class NumericOverflow; +class ParseError; +class Pipe; +class ProgramException; +class SMTPClient; +class Semaphore; +class StopWatch; +class SystemError; +class TaskAlreadyCompleted; +class Thread; +class Uncopyable; + + + +} diff --git a/src/liblw-support/IO/Device.h b/src/liblw-support/IO/Device.h new file mode 100644 index 0000000..a22ed1c --- /dev/null +++ b/src/liblw-support/IO/Device.h @@ -0,0 +1,53 @@ +/* lw-support/src/lib/IO/Device.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base I/O device class. + +This class is the base of the I/O device hierarchy. It specifies the +common functionality, such as closing, reading and writing. It provides +for both blocking and non-blocking I/O (which means that an 'open' +function would have to open its file descriptor in non-blocking mode). + +*/ +class IODevice : public IOInterface { +protected: + friend class EventManager; + + /// Returns a file descriptor suitable for EventManager. + virtual int getListenFd() const = 0; + +public: + /// Constructor. Does nothing. + IODevice() + { } + + /// Destructor. Calls close(). + virtual ~IODevice() + { } + + + + /*! \brief Closes the device. + + \throws SystemError if a system error occurs. + + This call will close a device. + + */ + virtual void close() = 0; + + // implemented virtuals + virtual IOMode getIOMode() const + { return ioMode; } +}; + + + +} diff --git a/src/liblw-support/IO/Filter.cpp b/src/liblw-support/IO/Filter.cpp new file mode 100644 index 0000000..92c1c3e --- /dev/null +++ b/src/liblw-support/IO/Filter.cpp @@ -0,0 +1,32 @@ +/* lw-support/src/lib/IO/Filter.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +IOFilter::IOFilter(IOInterface* ioDev) + : ioDev(ioDev) +{ +} + + + +void IOFilter::sync() +{ + try { + flush(); + ioDev->sync(); + } + catch(Exception& e) { + e.chain(L"IOFilter::sync()"); + throw; + } +} + + + +} diff --git a/src/liblw-support/IO/Filter.h b/src/liblw-support/IO/Filter.h new file mode 100644 index 0000000..0e4aa21 --- /dev/null +++ b/src/liblw-support/IO/Filter.h @@ -0,0 +1,94 @@ +/* lw-support/src/lib/IO/Filter.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief I/O filter base class. + +The I/O filter class sits between the program and an underlying device +(or another filter, since it is possible to create arbitrary chains of +such filters). It can perform various transformations on the data. One +example is a gzip filter, which compresses data before writing it and +decompresses it when reading. + +\warning Closing and reopening the underlying device may leave data in + the filter's buffers. The solution is to call the reset() + method (or delete the filter and create a new one). + +*/ +class IOFilter : public IOInterface { +protected: + /// The source device. + IOInterface* ioDev; + +public: + /// Constructor. Takes pointer to underlying device. + IOFilter(IOInterface* ioDev); + + /// Destructor. Flushes write cache. + virtual ~IOFilter() + { } + + + + /*! \brief Empty the filter's write buffers. + + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for writing. + + This causes the filter to flush its write buffers to the underlying + device. If the underlying device has its own write buffer, this + function does not cause the underlying buffer to be flushed; that + has to be done explicitly. + + \sa sync() + + */ + virtual void flush() = 0; + + + + /*! \brief Reset the filter. + + This should only be called if you want to clear all buffer data and + state information in the filter (e.g. if you have closed and + reopened the underlying device). It's important to note that calling + this will simply discard any data waiting to be read/written, so you + should call flush() first. + + */ + virtual void reset() = 0; + + + + // implemented virtuals + virtual IOMode getIOMode() const + { return ioDev->getIOMode(); } + + + + /*! \brief Ensure data is written to disk. + + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for writing. + + This function will flush any output buffers to the underlying + device, and will then propagate down the chain, until it reaches + the eventual sink device (e.g. file or network connection), at which + point it will call fdatasync() to ensure the data is physically + written to disk/network/whatever. + + \sa flush() + + */ + virtual void sync(); +}; + + + +} diff --git a/src/liblw-support/IO/Interface.h b/src/liblw-support/IO/Interface.h new file mode 100644 index 0000000..280e9e7 --- /dev/null +++ b/src/liblw-support/IO/Interface.h @@ -0,0 +1,183 @@ +/* lw-support/src/lib/IO/Interface.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief I/O interface. + +This class is the interface that any I/O device or filter will +implement. It has both blocking and non-blocking functions. + +*/ +class IOInterface { +protected: + /// The read/write mode of the device. + IOMode ioMode; + +public: + /// Constructor. Initialises ioMode. + IOInterface() + : ioMode(IOClosed) + { } + + + + /// Destructor -- does nothing. + virtual ~IOInterface() { } + + + + /// Gets the mode the device was opened in. + virtual IOMode getIOMode() const = 0; + + + + /*! \brief Read some data (non-blocking). + + \param buf Buffer to read data into. + \param amt Maximum amount of data to read (i.e. size of buffer). + \throws SystemError if a system error occurs. + \throws EndOfFile if end-of-file has been reached. + \throws IOModeError if the file isn't open for reading. + \returns Number of bytes actually read (which will be zero if no + data is currently available). + + Reads some data in a non-blocking manner. This call will return + immediately and report the number of bytes read (which may well be + less than the number of bytes requested). It will return zero if no + data is available. + + */ + virtual size_t read(char* buf, size_t amt) = 0; + + + + /*! \brief Write some data (non-blocking). + + \param buf Data to write. + \param amt Maximum amount of data to write. + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for writing. + \returns Number of bytes actually written (will be zero if the + device's output buffer is currently full). + + Writes some data in a non-blocking manner. This call will return + immediately and report the number of bytes written (which may well + be less than the number of bytes requested). It will return zero if + the device wasn't ready for writing. + + */ + virtual size_t write(const char* buf, size_t amt) = 0; + + + + /*! \brief Wait for I/O. + + \param read If \a true, returns when the device has input available. + \param write If \a true, returns when you can output to the device. + \param timeout Timeout period, in milliseconds. 0 means immediate; + negative means forever. + \throws IOTimeout if a timeout occurs. + \throws SystemError if a system error occurs. + \retval true if an exceptional condition occurred. + \retval false if no exception condition occurred. + + This function waits for the underlying device to become ready for + the specified I/O operations, and returns when it is. It will + return immediately if there is some exceptional condition, you can + check for this by looking at the return value, which will be + \a true if there is such a condition. Exceptional conditions + include things such as out-of-band data and asynchronous errors. + + */ + virtual bool wait(bool read, bool write, int timeout) = 0; + + + + /*! \brief Read some data (blocking) (full buffer). + + \param buf Buffer to read data into. + \param amt Maximum amount of data to read (i.e. size of buffer). + \param timeout Timeout period, in milliseconds. 0 means immediate; + negative means forever. + \param timeoutMode The timeout mode. + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for reading. + \throws EndOfFile if end-of-file has been reached. + \throws IOTimeout if a timeout occurs. + \returns Number of bytes actually read (always more than zero). + + Reads some data, blocking if none is available. Various timeout + options are available. By default, the entire buffer must be filled + in the specified time period (but this is set to "forever" by + default). Should the end of file be reached before the buffer is + completely filled, an EndOfFile exception will be thrown; instead, + you could use wait() and read() individually to get around this. + + */ + virtual size_t readBlock(char* buf, size_t amt, int timeout = -1, + IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0; + + + + /*! \brief Write some data (blocking). + + \param buf Data to write. + \param amt Amount of data to write. + \param timeout Timeout period, in milliseconds. 0 means immediate; + negative means forever. + \param timeoutMode The timeout mode. + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for writing. + \throws IOTimeout if a timeout occurs. + \returns Number of bytes actually written (always more than zero). + + Writes some data, blocking until it is sent. Various timeout + options are available. By default, the entire buffer must be sent + in the specified time period (but this is set to "forever" by + default). + + */ + virtual size_t writeBlock(const char* buf, size_t amt, int timeout + = -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0; + + + + /*! \brief Ensure data is written to disk. + + \throws SystemError if a system error occurs. + \throws IOModeError if the file isn't open for writing. + + This function will flush any output buffers, then call fdatasync() + on the underlying device, ensuring the data is written to disk (or + network, etc.). + + */ + virtual void sync() = 0; + + + + /*! \brief Check for asynchronous errors. + + \throws SystemError if there are any asynchronous errors. + + This function should be called to check for asynchronous errors. + Examples of where you would use it are after a non-blocking + connection has completed (socket will be marked as ready for + reading/writing, so you need to check what's going on), and if + you receive an error signal while waiting for events on the + device, etc. Implementations of this function may do nothing if + asynchronous errors can't occur (e.g. on standard disk files). + + */ + virtual void checkErrors() = 0; +}; + + + +} diff --git a/src/liblw-support/IO/Pipe.cpp b/src/liblw-support/IO/Pipe.cpp new file mode 100644 index 0000000..de81a58 --- /dev/null +++ b/src/liblw-support/IO/Pipe.cpp @@ -0,0 +1,61 @@ +/* lw-support/src/lib/IO/Pipe.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +Pipe::Pipe() + : read(0), write(0) +{ + int fds[2]; + if(TEMP_FAILURE_RETRY(::pipe(fds))) + throw SystemError() + .chain(L"Pipe::Pipe()").chain(L"pipe()"); + try { + read = new EndPipe(this, fds[0], true); + read->setNonBlocking(); + write = new EndPipe(this, fds[1], false); + write->setNonBlocking(); + } + catch(...) { + delete read; + delete write; + throw; + } +} + + + +Pipe::~Pipe() +{ + delete read; + delete write; +} + + + +EndPipe::EndPipe(Pipe* myPipe, int fd, bool read) + : myPipe(myPipe) +{ + this->fd = fd; + this->ioMode = read ? IORead : IOWrite; +} + + + +EndPipe::~EndPipe() +{ + // unregister with the containing Pipe object + if(ioMode == IORead) myPipe->read = 0; + else myPipe->write = 0; + + // closing happens automatically in IOPosixDevice's destructor. +} + + + +} diff --git a/src/liblw-support/IO/Pipe.h b/src/liblw-support/IO/Pipe.h new file mode 100644 index 0000000..75c3919 --- /dev/null +++ b/src/liblw-support/IO/Pipe.h @@ -0,0 +1,70 @@ +/* lw-support/src/lib/IO/Pipe.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Pipe class container. + +This class, when instantiated, creates a POSIX pipe device. This device +has two ends (of type EndPipe); you can write to one of these ends, and +read from the other. + +This class just acts as a container for the two ends of the pipe. +Deleting it will delete the two end pipes. + +*/ +class Pipe : public Uncopyable { +private: + friend class EndPipe; + +public: + /*! \brief Constructor. + + \throws lw::SystemError if an error occurs while creating the pipes. + + The constructor sets up both ends of the pipe. + + */ + Pipe(); + + /// Destructor. Closes both ends of the pipe. + ~Pipe(); + + /// Reading end of the pipe. + EndPipe* read; + + /// Writing end of the pipe. + EndPipe* write; +}; + + + +/*! \brief One end of a pipe connection. + +See the lw::Pipe class for a better description of POSIX pipes. This +object has a private constructor, since it can only be created as part +of an lw::Pipe container object. + +The destructor will automatically unregister the object with its +containing lw::Pipe, in order to avoid double deletion when the Pipe +instance is deleted. + +*/ +class EndPipe : public IOPosixDevice { +private: + friend class Pipe; + Pipe* myPipe; + EndPipe(Pipe* myPipe, int fd, bool read); + +public: + virtual ~EndPipe(); +}; + + + +} diff --git a/src/liblw-support/IO/PosixDevice.cpp b/src/liblw-support/IO/PosixDevice.cpp new file mode 100644 index 0000000..4cf559c --- /dev/null +++ b/src/liblw-support/IO/PosixDevice.cpp @@ -0,0 +1,379 @@ +/* lw-support/src/lib/IO/PosixDevice.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +namespace { + +// This class is a helper class used to keep track of timeouts and +// implement timeout behaviour for an I/O device. +class IOCountdown { +private: + StopWatch started; + IOInterface* ioInterface; + std::wstring op; + int timeout; + IOTimeoutMode timeoutMode; + size_t totalTransferred; + +public: + IOCountdown(IOInterface* ioInterface, const std::wstring& op, int timeout, + IOTimeoutMode timeoutMode) + : ioInterface(ioInterface), op(op), timeout(timeout), + timeoutMode(timeoutMode), totalTransferred(0) + { } + + // Call this when some data is transferred. + void transferred(size_t amt) + { + // record amount transferred + totalTransferred += amt; + + // reset countdown timer + if(amt && (timeoutMode == IOTimeoutSoftFull || + timeoutMode == IOTimeoutSoftPartial)) + { + started.start(); + } + } + + // Call this when poll() returns a timeout. Returns no. of bytes + // transferred, but throws an exception if specified by behaviour. + size_t timedOut() + { + switch(timeoutMode) { + case IOTimeoutHardPartial: + case IOTimeoutSoftPartial: + case IOTimeoutOneShot: + if(totalTransferred) break; + // fall through + case IOTimeoutHardFull: + case IOTimeoutSoftFull: + throw IOTimeout(ioInterface, op, timeout, + timeoutMode, totalTransferred); + } + return totalTransferred; + } + + // Call this when you need to update the timeout variable. It will + // return false if the timeout has happened. + bool update(int& remaining) + { + /* Special case for one-shot mode: after initial transfer has + completed, we want to continue retrying until the I/O is + drained; therefore, we set the timeout to zero. + + FIXME: a stray signal could cause poll() to fail with EINTR, + at which point update() will be called event though no I/O + has yet occurred, possibly leading to a premature timeout. + */ + if(timeoutMode == IOTimeoutOneShot) return 0; + + if(timeout < 0) { + remaining = timeout; + return true; + } + + remaining = timeout - started.elapsed().to_ms(); + return (remaining >= 0); + } +}; + +} + + + +IOPosixDevice::~IOPosixDevice() +{ + if(fd >= 0) TEMP_FAILURE_RETRY(::close(fd)); +} + + + +void IOPosixDevice::close() +{ + if(fd != -1) { + int ret = TEMP_FAILURE_RETRY(::close(fd)); + fd = -1; + ioMode = IOClosed; + + if(ret) { + throw SystemError() + .chain(L"close()") + .chain(L"IOPosixDevice::close()"); + } + } +} + + + +size_t IOPosixDevice::read(char* buf, size_t amt) +{ + try { + checkRead(); + + ssize_t ret = TEMP_FAILURE_RETRY(::read(fd, buf, amt)); + if(!ret) throw EndOfFile(this); + if(ret == -1) { + if(errno == EAGAIN) return 0; + throw SystemError().chain(L"read()"); + } + return ret; + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::read()"); + throw; + } +} + + + +size_t IOPosixDevice::write(const char* buf, size_t amt) +{ + try { + checkWrite(); + + ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, buf, amt)); + if(ret == -1) { + if(errno == EAGAIN) return 0; + throw SystemError().chain(L"write()"); + } + return ret; + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::write()"); + throw; + } +} + + + +bool IOPosixDevice::wait(bool read, bool write, int timeout) +{ + struct pollfd ufd = { fd, POLLPRI | (read ? POLLIN : 0) | (write ? POLLOUT : 0), 0 }; + switch(poll(&ufd, 1, timeout)) { + case -1: + if(errno == EINTR) return false; + throw SystemError().chain(L"IOPosixDevice::wait()").chain(L"poll()"); + + case 0: + // timeout + throw IOTimeout(this, L"wait()", timeout, IOTimeoutHardFull, 0); + + default: + break; + } + return (ufd.revents & (POLLERR | POLLPRI | POLLHUP | POLLNVAL)); +} + + + +size_t IOPosixDevice::readBlock(char* buf, size_t amt, int timeout, + IOTimeoutMode timeoutMode) +{ + try { + checkRead(); + + size_t remaining = amt; + int timeout_remaining = timeout; + struct pollfd ufd = { fd, POLLIN, 0 }; + IOCountdown countdown(this, L"readBlock()", timeout, timeoutMode); + + while(true) { + switch(poll(&ufd, 1, timeout_remaining)) { + case -1: + if(errno == EINTR) break; + throw SystemError().chain(L"poll()"); + + case 0: + // timeout + return countdown.timedOut(); + + default: + // perform read + size_t res = read(buf, remaining); + if( !(remaining -= res) ) return amt; + buf += res; + countdown.transferred(res); + break; + } + + // update timeout + if(!countdown.update(timeout_remaining)) + return countdown.timedOut(); + } + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::readBlock()"); + throw; + } +} + + + +size_t IOPosixDevice::writeBlock(const char* buf, size_t amt, int timeout, + IOTimeoutMode timeoutMode) +{ + try { + checkWrite(); + + size_t remaining = amt; + int timeout_remaining = timeout; + struct pollfd ufd = { fd, POLLOUT, 0 }; + IOCountdown countdown(this, L"writeBlock()", timeout, timeoutMode); + + while(true) { + switch(poll(&ufd, 1, timeout_remaining)) { + case -1: + if(errno == EINTR) break; + throw SystemError().chain(L"poll()"); + + case 0: + // timeout + return countdown.timedOut(); + + default: + // perform read + size_t res = write(buf, remaining); + if( !(remaining -= res) ) return amt; + buf += res; + countdown.transferred(res); + break; + } + + // update timeout + if(!countdown.update(timeout_remaining)) + return countdown.timedOut(); + } + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::writeBlock()"); + throw; + } +} + + + +void IOPosixDevice::sync() +{ + try { + checkWrite(); + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::sync()"); + throw; + } + + if(TEMP_FAILURE_RETRY(::fdatasync(fd))) { + throw SystemError() + .chain(L"fdatasync()") + .chain(L"IOPosixDevice::sync()"); + } +} + + + +void IOPosixDevice::setNonBlocking() +{ + int flags = fcntl(fd, F_GETFL, 0); + if(flags == -1) { + throw SystemError() + .chain(L"fcntl(..., F_GETFL, ...)") + .chain(L"IOPosixDevice::setNonBlocking()"); + } + if(flags & O_NONBLOCK) return; + flags |= O_NONBLOCK; + if(fcntl(fd, F_SETFL, flags)) { + throw SystemError() + .chain(L"fcntl(..., F_SETFL, ...)") + .chain(L"IOPosixDevice::setNonBlocking()"); + } +} + + + +int IOPosixDevice::ioModeFlags(IOMode mode) +{ + switch(mode) { + case IOClosed: + throw IOModeError(this, L"open", IONone); + + case IONone: + return 0; + + case IORead: + return O_RDONLY; + + case IOWrite: + return O_WRONLY; + + case IOReadWrite: + return O_RDWR; + } + throw ProgramException(); +} + + + +void IOPosixDevice::checkRead() const +{ + IOMode mode = getIOMode(); + if(mode == IORead || mode == IOReadWrite) return; + throw IOModeError(const_cast(this), L"read", IORead); +} + + + +void IOPosixDevice::checkWrite() const +{ + IOMode mode = getIOMode(); + if(mode == IOWrite || mode == IOReadWrite) return; + throw IOModeError(const_cast(this), L"write", IOWrite); +} + + + +void IOPosixDevice::checkOpen(const std::wstring& op) const +{ + IOMode mode = getIOMode(); + if(mode != IOClosed) return; + throw IOModeError(const_cast(this), op, IOWrite); +} + + + +void IOPosixDevice::posixOpen(const char* path, IOMode mode) +{ + fd = TEMP_FAILURE_RETRY(::open(path, ioModeFlags(mode))); + if(fd == -1) { + if(errno == ENOENT) throw FileNotFound(path); + throw SystemError() + .chain(L"open()") + .chain(L"IOPosixDevice::posixOpen()"); + } + try { + setNonBlocking(); + } + catch(Exception& e) { + TEMP_FAILURE_RETRY(::close(fd)); + fd = -1; + e.chain(L"IOPosixDevice::posixOpen()"); + throw; + } + ioMode = mode; +} + + + +void IOPosixDevice::checkErrors() +{ +} + + + +} diff --git a/src/liblw-support/IO/PosixDevice.h b/src/liblw-support/IO/PosixDevice.h new file mode 100644 index 0000000..abe7cc8 --- /dev/null +++ b/src/liblw-support/IO/PosixDevice.h @@ -0,0 +1,73 @@ +/* lw-support/src/lib/IO/PosixDevice.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief POSIX I/O device class. + +Many standard devices can be accessed through a POSIX file descriptor. +This is the base class for all such devices. It provides implementations +for reading, writing, etc.; but not for actually opening devices. + +\todo Port readBlock() and writeBlock() over to use wait(), and deal + with any exceptional conditions somehow. + +*/ +class IOPosixDevice : public IODevice, public Uncopyable { +protected: + /// The POSIX file descriptor. + int fd; + + // implemented virtuals + virtual int getListenFd() const + { return fd; } + + /// Convenience function: sets the fd into non-blocking mode. + void setNonBlocking(); + + /// Convenience function: converts enumeration value into GNU + /// \c open() bitfield, throwing an exception if necessary. + int ioModeFlags(IOMode mode); + + /// Convenience function: POSIX open() wrapper, throws exception. + void posixOpen(const char* path, IOMode mode); + + /// Convenience function: check that we can read or throw exception. + void checkRead() const; + + /// Convenience function: check that we can write or throw exception. + void checkWrite() const; + + /// Convenience function: check device is open or throw exception. + void checkOpen(const std::wstring& op) const; + +public: + /// Constructor. Does nothing. + IOPosixDevice() + : fd(-1) + { } + + /// Destructor. Closes device. + virtual ~IOPosixDevice(); + + // implemented virtuals + virtual void close(); + virtual size_t read(char* buf, size_t amt); + virtual size_t write(const char* buf, size_t amt); + virtual bool wait(bool read, bool write, int timeout); + virtual size_t readBlock(char* buf, size_t amt, int timeout = -1, + IOTimeoutMode timeoutMode = IOTimeoutHardFull); + virtual size_t writeBlock(const char* buf, size_t amt, int timeout + = -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull); + virtual void sync(); + virtual void checkErrors(); +}; + + + +} diff --git a/src/liblw-support/IO/SerialPort.cpp b/src/liblw-support/IO/SerialPort.cpp new file mode 100644 index 0000000..e42b6b5 --- /dev/null +++ b/src/liblw-support/IO/SerialPort.cpp @@ -0,0 +1,76 @@ +/* lw-support/src/lib/IO/SerialPort.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +void IOSerialPort::open(IOMode mode, const char* path, int baud, bool twoStopBits) +{ + if(fd != -1) close(); + + try { + posixOpen(path, mode); + setOptions(baud, twoStopBits); + } + catch(Exception& e) { + e.chain(L"IOSerialPort::open()"); + close(); + throw; + } +} + + + +void IOSerialPort::setOptions(int baud, bool twoStopBits) +{ + // read current characteristics + struct termios term; + if(tcgetattr(fd, &term) < 0) { + throw SystemError() + .chain(L"tcgetattr()") + .chain(L"IOSerialPort::setOptions()"); + } + + // set 8N1 mode + term.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR + | ICRNL | IXON | IXOFF); + term.c_oflag &= ~OPOST; + term.c_cflag &= ~(PARENB | CSIZE | CSTOPB); + term.c_cflag |= CS8; + term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + + // set baud rate and stop bits + if(twoStopBits) term.c_cflag |= CSTOPB; + if(cfsetspeed(&term, baud)) { + throw SystemError() + .chain(L"cfsetspeed()") + .chain(L"IOSerialPort::setOptions()"); + } + + // set attributes + if(tcsetattr(fd, TCSAFLUSH, &term)) { + throw SystemError() + .chain(L"tcsetattr()") + .chain(L"IOSerialPort::setOptions()"); + } + +} + + + +void IOSerialPort::discardInput() +{ + if(TEMP_FAILURE_RETRY(tcflush(fd, TCIFLUSH))) { + throw SystemError() + .chain(L"tcflush()") + .chain(L"IOSerialPort::discardInput()"); + } +} + + + +} diff --git a/src/liblw-support/IO/SerialPort.h b/src/liblw-support/IO/SerialPort.h new file mode 100644 index 0000000..4b25ae2 --- /dev/null +++ b/src/liblw-support/IO/SerialPort.h @@ -0,0 +1,70 @@ +/* lw-support/src/lib/IO/SerialPort.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Serial port I/O device. + +This class allows you to control a serial (RS232) port or equivalent +device. It provides control over the baud rate and whether you want to +use one stop bit (the standard) or two (useful for communicating with +embedded devices when the baud rate generators don't exactly match). It +does not provide support for parity, flow control or other than 8 data +bits. + +*/ +class IOSerialPort : public IOPosixDevice { +public: + /// Constructor (doesn't open any device). + IOSerialPort() + { } + + + + /*! \brief Open or reopen device. + + \param mode The mode in which the device should be opened. + \param path Path to the device node to open. + \param baud The baud rate you want. + \param twoStopBits \a true if you want two stop bits; \a false if + you don't. + \throws IOModeError if you pass lw::IOClosed as the mode. + \throws SystemError if the device couldn't be opened or if the + options cannot be set. + + Opens the device, setting the desired baud rate and number of stop + bits. If a device is already open, it is closed first. + + */ + void open(IOMode mode, const char* path, int baud, + bool twoStopBits = false); + + + + /*! \brief Change device options (baud rate etc.). + + \param baud The new baud rate. + \param twoStopBits \a true if you want two stop bits; \a false if + you don't. + \throws IOModeError if the device is closed. + \throws SystemError if the options cannot be set. + + Changes the baud rate and number of stop bits. + + */ + void setOptions(int baud, bool twoStopBits = false); + + + + /// Discard any input waiting to be read. + void discardInput(); +}; + + + +} diff --git a/src/liblw-support/IO/Util/WriteTask.cpp b/src/liblw-support/IO/Util/WriteTask.cpp new file mode 100644 index 0000000..bbe5e3f --- /dev/null +++ b/src/liblw-support/IO/Util/WriteTask.cpp @@ -0,0 +1,119 @@ +/* lw-support/src/lib/IO/Util/WriteTask.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +IOWriteTask::IOWriteTask(lw::EventManager& eventManager, IODevice* ioDev, int timeoutInactive, + int timeoutOverall, CompletionCallback* completion, + const char* data, size_t amt, bool copy) + : CompletionTask(completion), eventManager(eventManager), ioDev(ioDev), + timeoutInactive(timeoutInactive), timeoutOverall(timeoutOverall), data(0), wr(0), amt(amt), + copy(copy) +{ + try { + NullPointer::check(data, L"data"); + NullPointer::check(ioDev, L"ioDev"); + + eventManager.registerDevice(ioDev, this, lw::IOEventHUP | lw::IOEventWrite + | lw::IOEventError); +#if 0 + timerKey2 = eventManager.registerTimer(this, lw::ElapsedTime(0)); +#endif + + // copy data if necessary + if(copy) { + this->data = new char[amt]; + memcpy(this->data, data, amt); + } else { + this->data = (char*)data; + } + wr = this->data; + + // set timeouts if necessary + if(timeoutInactive >= 0) + timerKey0 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutInactive)); + if(timeoutOverall >= 0) + timerKey1 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutOverall)); + } + catch(lw::Exception& e) { + if(copy) delete [] this->data; + e.chain(L"IOWriteTask::IOWriteTask()"); + throw; + } +} + + + +IOWriteTask::~IOWriteTask() +{ + if(this->timeoutInactive >= 0) eventManager.ignoreTimer(timerKey0); + if(this->timeoutOverall >= 0) eventManager.ignoreTimer(timerKey1); + delete ioDev; + // if(copy) delete [] data; +} + + + +void IOWriteTask::taskAbort(const lw::Exception& e) +{ + taskAborted(e); + delete this; +} + + + +bool IOWriteTask::ioReady(uint32_t flags) +{ + try { + if(flags & lw::IOEventError) ioDev->checkErrors(); + if(flags & lw::IOEventHUP) throw lw::Exception(L"Remote device closed connection."); + if(flags & lw::IOEventWrite) tryWrite(); + } + catch(lw::Exception& e) { + taskAbort(e); + delete this; + return false; + } + + return true; +} + + + +void IOWriteTask::tryWrite() +{ + while(amt) { + size_t w = ioDev->write(wr, amt); + if(!w) return; + + wr += w; + amt -= w; + + if(timeoutInactive >= 0) { + eventManager.ignoreTimer(timerKey0); + timerKey0 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutInactive), 0); + } + } + + // done! + taskCompleted(); + delete this; +} + + + +bool IOWriteTask::timer(Key) +{ + // must be a timeout + taskAbort(lw::Exception(L"WriteTask timeout.")); + return false; +} + + + +} diff --git a/src/liblw-support/IO/Util/WriteTask.h b/src/liblw-support/IO/Util/WriteTask.h new file mode 100644 index 0000000..1db7739 --- /dev/null +++ b/src/liblw-support/IO/Util/WriteTask.h @@ -0,0 +1,81 @@ +/* lw-support/src/lib/IO/Util/WriteTask.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Class which writes some data to an I/O device and then closes it. + +Instances of this class will write data to an I/O device, using non-blocking I/O, and (on +destruction) will close the device and delete it. It has options for timeouts and deals correctly +with errors. On task completion, the object will delete itself. + +A typical usage example would be to accept a TCP connection, instantiate an IOWriteTask (with a +suitable message and timeout, pointed at the TCP connection, connected to a CompletionList object), +register the task with the CompletionList, and then simply process events as usual, forgetting about +the TCP connection and the IOWriteTask instance. + +*/ +class IOWriteTask : public CompletionTask, + private EventCallbackIO, + private EventCallbackTimer +{ +private: + lw::EventManager& eventManager; + IOInterface* ioDev; + int timeoutInactive, timeoutOverall; + char* data, * wr; + size_t amt; + bool copy; + Key timerKey0, timerKey1; + + void tryWrite(); + + // from EventCallbackIO + virtual bool ioReady(uint32_t flags); + + // from EventCallbackTimer + virtual bool timer(Key key); + +public: + /*! \brief Constructor. Sets up the task. + + \param eventManager The event manager object to use. + \param ioDev The I/O device in question. + \param timeoutInactive An inactivity timeout period, in milliseconds. -1 to disable. + \param timeoutOverall An overall timeout period, in milliseconds. -1 to disable. + \param completion The object which will be notified about completion. Can be 0. + \param data Pointer to the block of data to write. + \param amt Number of bytes of data to write. + \param copy Pass \c true if you want the data to be copied, or \c false if you guarantee its + persistence until the task is completed. + + \throws NullPointer if \a ioDev or \a data is 0. + \throws SystemError if a system call fails. + + Begins the process. Registers the device with the event manager, attempts to write some data + (these can result in system errors). On completion, the object will delete itself. + + */ + IOWriteTask(lw::EventManager& eventManager, IODevice* ioDev, int timeoutInactive, + int timeoutOverall, CompletionCallback* completion, + const char* data, size_t amt, bool copy); + + + + /// Destructor. Closes and deletes I/O device. + virtual ~IOWriteTask(); + + + + /// Aborts task, closing and deleting the I/O device and the instance. + virtual void taskAbort(const lw::Exception& e); +}; + + + +} diff --git a/src/liblw-support/Net/Address/Address.cpp b/src/liblw-support/Net/Address/Address.cpp new file mode 100644 index 0000000..0bb572d --- /dev/null +++ b/src/liblw-support/Net/Address/Address.cpp @@ -0,0 +1,19 @@ +/* lw-support/src/lib/Net/Address/Address.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wostream& operator<<(std::wostream& ostr, const lw::NetAddress& addr) +{ + ostr << addr.toString(); + return ostr; +} + + + +} diff --git a/src/liblw-support/Net/Address/Address.h b/src/liblw-support/Net/Address/Address.h new file mode 100644 index 0000000..5f60ecd --- /dev/null +++ b/src/liblw-support/Net/Address/Address.h @@ -0,0 +1,38 @@ +/* lw-support/src/lib/Net/Address/Address.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base class for network addresses. + +Since network addresses are used in very different ways, this class +doesn't provide much of an interface. However, it does allow for +dynamic casting, which is its main purpose. + +*/ +class NetAddress { +public: + /// Destructor. Does nothing. + virtual ~NetAddress() + { } + + /// Convert to std::string representation + virtual std::wstring toString() const = 0; + + /// Clone address. + virtual NetAddress* clone() const = 0; +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const lw::NetAddress&); + + + +} diff --git a/src/liblw-support/Net/Address/IPv4.cpp b/src/liblw-support/Net/Address/IPv4.cpp new file mode 100644 index 0000000..b087657 --- /dev/null +++ b/src/liblw-support/Net/Address/IPv4.cpp @@ -0,0 +1,129 @@ +/* lw-support/src/lib/Net/Address/IPv4.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +const NetAddressIPv4 NetAddressIPv4::any(0, 0, 0, 0); +const NetAddressIPv4 NetAddressIPv4::loopback(127, 0, 0, 1); + + + +std::wstring NetAddressIPv4::toString() const +{ + std::wostringstream o; + + o << (uint)(addr[0]) << L'.' + << (uint)(addr[1]) << L'.' + << (uint)(addr[2]) << L'.' + << (uint)(addr[3]); + + return o.str(); +} + + + +NetAddressIPv4 NetAddressIPv4::fromString(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + uint8_t addr[4]; + int addrPos = 0; + uint p = 0; + bool seen = false; + wchar_t ch; + + std::wstring::size_type pos = 0, len = str.length(); + + for(pos = 0; pos < len; ++pos) { + ch = str[pos]; + switch(ch) { + case '0' ... '9': + seen = true; + p *= 10; + p += (ch - '0'); + if(p > 255) { + throw ParseError(L"Octet cannot be > 255.", str, pos) + .chain(L"NetAddressIPv4::fromString()"); + } + break; + + case '.': + if(!seen) { + throw ParseError(L"Empty octet.", str, pos) + .chain(L"NetAddressIPv4::fromString()"); + } + if(addrPos == 3) { + throw ParseError(L"Too many octets.", str, pos) + .chain(L"NetAddressIPv4::fromString()"); + } + + addr[addrPos++] = p; + p = 0; + seen = false; + break; + + default: + throw ParseError(L"Unexpected symbol.", str, pos) + .chain(L"NetAddressIPv4::fromString()"); + } + } + + if(addrPos != 3 || !seen) { + throw ParseError(L"Not enough octets.", str, pos) + .chain(L"NetAddressIPv4::fromString()"); + } + + addr[3] = p; + return NetAddressIPv4(addr); +} + + + +NetAddressIPv4 NetAddressIPv4::fromDNS(const std::string& host) +{ + // attempt to convert from numeric form first + try { + return fromString(lw::asciiToUcs4(host)); + } + catch(...) { } + + // now perform blocking DNS lookup + struct hostent he, * hep = 0; + int err = 0, result = 0; + char buffer[4096]; + + result = gethostbyname2_r(host.c_str(), AF_INET, + &he, buffer, sizeof(buffer), &hep, &err); + switch(result) { + case 0: + // success! + break; + + case HOST_NOT_FOUND: + throw DnsError(L"Host not found.", host); + + case TRY_AGAIN: + throw DnsError(L"Temporary failure in name resolution.", host); + + case NO_RECOVERY: + throw DnsError(L"Unrecoverable failure in name resolution.", host); + + case NO_ADDRESS: + throw DnsError(L"Host does not have an associated IPv4 address.", host); + + default: + throw DnsError(L"Unknown DNS error.", host); + } + + + return NetAddressIPv4(he.h_addr); +} + + + +} diff --git a/src/liblw-support/Net/Address/IPv4.h b/src/liblw-support/Net/Address/IPv4.h new file mode 100644 index 0000000..02964a1 --- /dev/null +++ b/src/liblw-support/Net/Address/IPv4.h @@ -0,0 +1,175 @@ +/* lw-support/src/lib/Net/Address/IPv4.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief IPv4 network address. + +This class is used to represent an IPv4 network address. It has a basic +blocking DNS lookup function. + +*/ +class NetAddressIPv4 : public NetAddress { +private: + uint8_t addr[4]; + +public: + //BEGIN // Well-known addresses //////////////////////////////////// + + /// 0.0.0.0 + static const NetAddressIPv4 any; + + /// 127.0.0.1 + static const NetAddressIPv4 loopback; + + + + //BEGIN // Constructors //////////////////////////////////////////// + + /// Default constructor: address 0.0.0.0. + NetAddressIPv4() + { + memset(addr, 0, 4); + } + + /// Copy constructor. + NetAddressIPv4(const NetAddressIPv4& other) + : NetAddress() + { + memcpy(addr, other.addr, 4); + } + + /// Constructor: specify numerically. + NetAddressIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d) + { + addr[0] = a; + addr[1] = b; + addr[2] = c; + addr[3] = d; + } + + /// Constructor: from array. + NetAddressIPv4(const uint8_t* newAddr) + { + memcpy(addr, newAddr, 4); + } + + /// Constructor: from memory. + NetAddressIPv4(const char* newAddr) + { + memcpy(addr, newAddr, 4); + } + + // implemented virtual + virtual NetAddressIPv4* clone() const + { return new NetAddressIPv4(*this); } + + + + //BEGIN // Modifying functions ///////////////////////////////////// + + /// Assignment operator. + NetAddressIPv4& operator=(const NetAddressIPv4& other) + { + memcpy(addr, other.addr, 4); + return *this; + } + + /// Set numerically. + NetAddressIPv4& set(uint8_t a, uint8_t b, uint8_t c, uint8_t d) + { + addr[0] = a; + addr[1] = b; + addr[2] = c; + addr[3] = d; + return *this; + } + + /// Set from array. + NetAddressIPv4& set(const uint8_t* newAddr) + { + memcpy(addr, newAddr, 4); + return *this; + } + + /// Set from memory. + NetAddressIPv4& set(const char* newAddr) + { + memcpy(addr, newAddr, 4); + return *this; + } + + + + //BEGIN // Query functions ///////////////////////////////////////// + + /// Copy to array. + uint8_t* copy(uint8_t* dest) const + { + memcpy(dest, addr, 4); + return dest; + } + + /// Copy to memory. + char* copy(char* dest) const + { + memcpy(dest, addr, 4); + return dest; + } + + /// For interfacing with the C library. + struct sockaddr* copy(struct sockaddr_in* s, uint16_t port) const + { + s->sin_family = AF_INET; + s->sin_port = htons(port); + memcpy(&(s->sin_addr.s_addr), addr, 4); + return (struct sockaddr*)s; + } + + + + //BEGIN // String functions //////////////////////////////////////// + + // implemented virtual + virtual std::wstring toString() const; + + + + /*! \brief Construct from string. + + \param str The string containing an IPv4 address to parse. + \throws ParseError if a parse error occurs. + \returns A new address object. + + This function parses an IPv4 address in dotted quad format, i.e. + a.b.c.d where a, b, c and d are all 8-bit unsigned values. + Any trailing or leading whitespace is ignored. + + */ + static NetAddressIPv4 fromString(const std::wstring& str); + + + + /*! \brief Look up via DNS (blocking). + + \param host The hostname to look up. + \throws DnsError If an error occurs. + \returns A new address object. + + This function looks up a specific host via DNS, returning its IPv4 + address. It is a blocking function. + + \todo std::wstring-capable version + + */ + static NetAddressIPv4 fromDNS(const std::string& host); +}; + + + +} diff --git a/src/liblw-support/Net/Address/IPv6.cpp b/src/liblw-support/Net/Address/IPv6.cpp new file mode 100644 index 0000000..fb26fea --- /dev/null +++ b/src/liblw-support/Net/Address/IPv6.cpp @@ -0,0 +1,104 @@ +/* lw-support/src/lib/Net/Address/IPv6.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +namespace { + +uint8_t ipv6_any_initialiser[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +uint8_t ipv6_loopback_initialiser[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 +}; + +} + + + +const NetAddressIPv6 NetAddressIPv6::any(ipv6_any_initialiser); +const NetAddressIPv6 NetAddressIPv6::loopback(ipv6_loopback_initialiser); + + + +std::wstring NetAddressIPv6::toString() const +{ + char buffer[50]; // should be enough for a full IPv6 address + if(!inet_ntop(AF_INET6, addr, buffer, sizeof(buffer))) { + throw lw::ProgramException().chain(L"NetAddressIPv6::toString()"); + } + + return lw::strToUcs4(buffer); +} + + + +NetAddressIPv6 NetAddressIPv6::fromString(const std::wstring& str) +{ + std::string ip; + try { + ip = ucs4ToAscii(stripWhitespace(str)); + } + catch(lw::Exception& e) { + e.chain(L"NetAddressIPv6::fromString()"); + throw; + } + + struct in6_addr addr; + + if(!inet_pton(AF_INET6, ip.c_str(), &addr)) + throw ParseError(L"Not an IPv6 address.", str, 0); + + return NetAddressIPv6(addr.s6_addr); +} + + + +NetAddressIPv6 NetAddressIPv6::fromDNS(const std::string& host) +{ + // attempt to convert from numeric form first + try { + return fromString(lw::asciiToUcs4(host)); + } + catch(...) { } + + // now perform blocking DNS lookup + struct hostent he, * hep = 0; + int err = 0, result = 0; + char buffer[4096]; + + result = gethostbyname2_r(host.c_str(), AF_INET6, + &he, buffer, sizeof(buffer), &hep, &err); + switch(result) { + case 0: + // success! + break; + + case HOST_NOT_FOUND: + throw DnsError(L"Host not found.", host); + + case TRY_AGAIN: + throw DnsError(L"Temporary failure in name resolution.", host); + + case NO_RECOVERY: + throw DnsError(L"Unrecoverable failure in name resolution.", host); + + case NO_ADDRESS: + throw DnsError(L"Host does not have an associated IPv6 address.", host); + + default: + throw DnsError(L"Unknown DNS error.", host); + } + + + return NetAddressIPv6(he.h_addr); +} + + + +} diff --git a/src/liblw-support/Net/Address/IPv6.h b/src/liblw-support/Net/Address/IPv6.h new file mode 100644 index 0000000..95cebcd --- /dev/null +++ b/src/liblw-support/Net/Address/IPv6.h @@ -0,0 +1,160 @@ +/* lw-support/src/lib/Net/Address/IPv6.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief IPv6 network address. + +This class is used to represent an IPv6 network address. It does not +have functions for converting names to IP addresses (DNS); this must be +done with the ... class. + +\todo mention DNS class + +*/ +class NetAddressIPv6 : public NetAddress { +private: + uint8_t addr[16]; + +public: + //BEGIN // Well-known addresses //////////////////////////////////// + + /// The address \c :: + static const NetAddressIPv6 any; + + /// The address \c ::1 + static const NetAddressIPv6 loopback; + + + + //BEGIN // Constructors //////////////////////////////////////////// + + /// Default constructor: address \c :: + NetAddressIPv6() + { + memset(addr, 0, 16); + } + + /// Copy constructor. + NetAddressIPv6(const NetAddressIPv6& other) + : NetAddress() + { + memcpy(addr, other.addr, 16); + } + + /// Constructor: from array. + NetAddressIPv6(const uint8_t* newAddr) + { + memcpy(addr, newAddr, 16); + } + + /// Constructor: from memory. + NetAddressIPv6(const char* newAddr) + { + memcpy(addr, newAddr, 16); + } + + // implemented virtual + virtual NetAddressIPv6* clone() const + { return new NetAddressIPv6(*this); } + + + + //BEGIN // Modifying functions ///////////////////////////////////// + + /// Assignment operator. + NetAddressIPv6& operator=(const NetAddressIPv6& other) + { + memcpy(addr, other.addr, 16); + return *this; + } + + /// Set from array. + NetAddressIPv6& set(const uint8_t* newAddr) + { + memcpy(addr, newAddr, 16); + return *this; + } + + /// Set from memory. + NetAddressIPv6& set(const char* newAddr) + { + memcpy(addr, newAddr, 16); + return *this; + } + + + + //BEGIN // Query functions ///////////////////////////////////////// + + /// Copy to array. + uint8_t* copy(uint8_t* dest) const + { + memcpy(dest, addr, 16); + return dest; + } + + /// Copy to memory. + char* copy(char* dest) const + { + memcpy(dest, addr, 16); + return dest; + } + + /// For interfacing with the C library. + struct sockaddr* copy(struct sockaddr_in6* s, uint16_t port) const + { + s->sin6_family = AF_INET6; + s->sin6_port = htons(port); + memcpy(s->sin6_addr.s6_addr, addr, 16); + return (struct sockaddr*)s; + } + + + + //BEGIN // String functions //////////////////////////////////////// + + // implemented virtual + virtual std::wstring toString() const; + + + + /*! \brief Construct from std::string. + + \param str The std::string containing an IPv6 address to parse. + \throws ParseError if a parse error occurs. + \returns A new address object. + + This function parses an IPv6 address in colon-delimited format, + e.g. \c "0123:4567:89ab:cdef:0123:4567:89ab:cdef". It understands + the compact form, e.g. \c "::1". Any traling or leading whitespace + is ignored. + + */ + static NetAddressIPv6 fromString(const std::wstring& str); + + + + /*! \brief Look up via DNS (blocking). + + \param host The hostname to look up. + \throws DnsError If an error occurs. + \returns A new address object. + + This function looks up a specific host via DNS, returning its IPv6 + address. It is a blocking function. + + \todo std::wstring-capable version + + */ + static NetAddressIPv6 fromDNS(const std::string& host); +}; + + + +} diff --git a/src/liblw-support/Net/Client.cpp b/src/liblw-support/Net/Client.cpp new file mode 100644 index 0000000..ca9336d --- /dev/null +++ b/src/liblw-support/Net/Client.cpp @@ -0,0 +1,57 @@ +/* lw-support/src/lib/Net/Client.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +void NetClient::checkErrors() +{ + int error; + socklen_t socklen = sizeof(error); + + if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &socklen) == -1) + throw SystemError().chain(L"NetClient::checkSocketErrors()") + .chain(L"getsockopt()"); + + if(error) + throw SystemError(error).chain(L"NetClient::checkSocketErrors()") + .chain(L"(pending error)"); +} + + + +void NetClient::shutdown(bool read, bool write) +{ + if(ioMode == IOClosed) + throw IOModeError(this, L"shutdown", IONone); + + IOMode newIoMode; + int shut; + + if(read && write) { + newIoMode = IONone; + shut = SHUT_RDWR; + } else if (read) { + if(ioMode == IOWrite || ioMode == IOReadWrite) newIoMode = IOWrite; + else newIoMode = IONone; + shut = SHUT_RD; + } else if (write) { + if(ioMode == IORead || ioMode == IOReadWrite) newIoMode = IORead; + else newIoMode = IONone; + shut = SHUT_WR; + } else { + return; + } + + if(TEMP_FAILURE_RETRY(::shutdown(fd, shut))) + throw SystemError().chain(L"NetClient::shutdown()"); + ioMode = newIoMode; +} + + + +} diff --git a/src/liblw-support/Net/Client.h b/src/liblw-support/Net/Client.h new file mode 100644 index 0000000..a1b4331 --- /dev/null +++ b/src/liblw-support/Net/Client.h @@ -0,0 +1,42 @@ +/* lw-support/src/lib/Net/Client.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base class for network clients. + +This class contains some common functionality shared by all network +socket clients. + +*/ +class NetClient : public IOPosixDevice { +public: + // implemented virtuals + virtual void checkErrors(); + + + + /*! \brief Shut down part of a connection. + + \param read Set to \a true if you want to shut down reading. + \param write Set to \a true if you want to shut down writing. + \throws IOModeError if the socket is not connected. + \throws SystemError if a system error occurs. + + This allows you to shut down part of a connection; either reading or + writing (or possibly both). Any data waiting to be received or + transmitted is discarded, and the read or write operation becomes + unavailable (the I/O mode is changed to reflect this). + + */ + virtual void shutdown(bool read, bool write); +}; + + + +} diff --git a/src/liblw-support/Net/Protocols/HTTP.cpp b/src/liblw-support/Net/Protocols/HTTP.cpp new file mode 100644 index 0000000..b173299 --- /dev/null +++ b/src/liblw-support/Net/Protocols/HTTP.cpp @@ -0,0 +1,372 @@ +/* lw-support/src/lib/Net/Protocols/HTTP.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +//BEGIN // HTTPHeaderParser //////////////////////////////////////////// + + + +HTTPHeaderParser::HTTPHeaderParser() + : state(StateBegin) +{ +} + + + +size_t HTTPHeaderParser::parse(const char* data, size_t amt) +{ + size_t parsed = 0; + char ch; + +begin: + if(parsed == amt || state == StateDone) return parsed; + if(state == StateError) throw HTTPMalformed(L"(previous error)"); + +#define ERROR(p) do { \ + state = StateError; \ + throw HTTPMalformed(p); \ +}while(0) + + // extract character (ASCII only) + ch = data[parsed++]; + if(ch & ~0x7F) { + state = StateError; + throw HTTPMalformed(L"Non-ASCII data encountered in HTTP header."); + } + + switch(state) { + case StateBegin: + if(ch == '\r') { + state = StateNewline2; + break; + } + if(!isgraph(ch) || ch == ':') ERROR(L"invalid field name"); + fieldName = tolower(ch); + state = StateFieldName; + break; + + case StateNewline: + if(ch != '\n') ERROR(L"expecting \\n after \\r"); + state = StateLine; + break; + + case StateLine: + if(ch == '\r') { + state = StateNewline2; + break; + } + if(isblank(ch)) { + state = StateFieldValue; + break; + } + if(!isgraph(ch) || ch == ':') ERROR(L"invalid field name"); + fieldName = tolower(ch); + state = StateFieldName; + break; + + case StateFieldName: + if(ch == ':') { + state = StateFieldValue; + break; + } + if(!isgraph(ch)) ERROR(L"invalid field name"); + fieldName += tolower(ch); + break; + + case StateFieldValue: + if(ch == '\r') { + state = StateNewline; + break; + } + if(!isprint(ch) && ch != '\t') ERROR(L"invalid field value"); + fields[fieldName] += ch; + break; + + case StateNewline2: + if(ch != '\n') ERROR(L"expecting \\n after \\r"); + state = StateDone; + break; + + case StateDone: + case StateError: + // should never get here! + throw lw::ProgramException().chain(L"HTTPHeaderParser::parse() [1]"); + } + + goto begin; +#undef ERROR +} + + + +//END // HTTPHeaderParser ////////////////////////////////////////////// +//BEGIN // HTTPResponseParser ////////////////////////////////////////// + + + +const char* HTTPResponseParser::HTTPSeq = "HTTP/"; + +HTTPResponseParser::HTTPResponseParser() + : state(StateHTTPSeq), HTTPSeqPos(HTTPSeq), + versionMajor(0), versionMinor(0), responseCode(0), fields(hp.fields) +{ +} + + + +size_t HTTPResponseParser::parse(const char* data, size_t amt) +{ + size_t parsed = 0; + char ch; + +begin: + if(parsed == amt || state == StateDone) return parsed; + if(state == StateError) throw HTTPMalformed(L"(previous error)"); + if(state == StateHeader) { + try { + parsed += hp.parse(data + parsed, amt - parsed); + if(hp.completed()) state = StateDone; + return parsed; + } + catch(...) { + state = StateError; + throw; + } + } + +#define ERROR(p) do { \ + state = StateError; \ + throw HTTPMalformed(p); \ +}while(0) + + // extract character (ASCII only) + ch = data[parsed++]; + if(ch & ~0x7F) { + state = StateError; + throw HTTPMalformed(L"Non-ASCII data encountered in HTTP response line."); + } + + switch(state) { + case StateHTTPSeq: + if(ch != *HTTPSeqPos) ERROR(L"expecting 'HTTP/major.minor'"); + if(!*(++HTTPSeqPos)) state = StateHTTPMajor1; + break; + + case StateHTTPMajor1: + if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'"); + versionMajor = ch - '0'; + state = StateHTTPMajor; + break; + + case StateHTTPMajor: + if(isdigit(ch)) { + versionMajor *= 10; + versionMajor += ch - '0'; + } else if(ch == '.') { + state = StateHTTPMinor1; + } else ERROR(L"expecting 'HTTP/major.minor'"); + break; + + case StateHTTPMinor1: + if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'"); + versionMinor = ch - '0'; + state = StateHTTPMinor; + break; + + case StateHTTPMinor: + if(isdigit(ch)) { + versionMinor *= 10; + versionMinor += ch - '0'; + } else if(ch == ' ') { + state = StateHTTPResp1; + } else ERROR(L"expecting 'HTTP/major.minor'"); + break; + + case StateHTTPResp1: + if(!isdigit(ch)) ERROR(L"invalid response code"); + responseCode = 100 * (ch - '0'); + state = StateHTTPResp2; + break; + + case StateHTTPResp2: + if(!isdigit(ch)) ERROR(L"invalid response code"); + responseCode += 10 * (ch - '0'); + state = StateHTTPResp3; + break; + + case StateHTTPResp3: + if(!isdigit(ch)) ERROR(L"invalid response code"); + responseCode += (ch - '0'); + state = StateHTTPResp4; + break; + + case StateHTTPResp4: + if(ch != ' ') ERROR(L"invalid response code"); + state = StateHTTPMsg; + break; + + case StateHTTPMsg: + if(ch == '\n') ERROR(L"\\n invalid on its own"); + else if(ch == '\r') state = StateHTTPNewline; + else status += ch; + break; + + case StateHTTPNewline: + if(ch != '\n') ERROR(L"expecting \\r after \\n"); + state = StateHeader; + break; + + case StateHeader: + case StateDone: + case StateError: + // should never get here! + throw lw::ProgramException().chain(L"HTTPResponseParser::parse() [1]"); + } + + goto begin; +#undef ERROR +} + + + +//END // HTTPResponseParser //////////////////////////////////////////// +//BEGIN // HTTPRequestParser /////////////////////////////////////////// + + + +const char* HTTPRequestParser::HTTPSeq = "HTTP/"; + +HTTPRequestParser::HTTPRequestParser() + : state(StateHTTPMethod1), HTTPSeqPos(HTTPSeq), fields(hp.fields) +{ +} + + + +size_t HTTPRequestParser::parse(const char* data, size_t amt) +{ + size_t parsed = 0; + char ch; + +begin: + if(parsed == amt || state == StateDone) return parsed; + if(state == StateError) throw HTTPMalformed(L"(previous error)"); + if(state == StateHeader) { + try { + parsed += hp.parse(data + parsed, amt - parsed); + if(hp.completed()) state = StateDone; + return parsed; + } + catch(...) { + state = StateError; + throw; + } + } + +#define ERROR(p) do { \ + state = StateError; \ + throw HTTPMalformed(p); \ +}while(0) + + // extract character (ASCII only) + ch = data[parsed++]; + if(ch & ~0x7F) { + state = StateError; + throw HTTPMalformed(L"Non-ASCII data encountered in HTTP request line."); + } + + switch(state) { + case StateHTTPMethod1: + if(!isgraph(ch)) ERROR(L"invalid HTTP method"); + method = ch; + state = StateHTTPMethod; + break; + + case StateHTTPMethod: + if(ch == ' ') { + state = StateHTTPUrl1; + break; + } + if(!isgraph(ch)) ERROR(L"invalid HTTP method"); + method += ch; + break; + + case StateHTTPUrl1: + if(!isgraph(ch)) ERROR(L"invalid HTTP URL"); + url = ch; + state = StateHTTPUrl; + break; + + case StateHTTPUrl: + if(ch == ' ') { + state = StateHTTPSeq; + break; + } + if(!isgraph(ch)) ERROR(L"invalid HTTP URL"); + url += ch; + break; + + case StateHTTPSeq: + if(ch != *HTTPSeqPos) ERROR(L"expecting 'HTTP/major.minor'"); + if(!*(++HTTPSeqPos)) state = StateHTTPMajor1; + break; + + case StateHTTPMajor1: + if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'"); + versionMajor = ch - '0'; + state = StateHTTPMajor; + break; + + case StateHTTPMajor: + if(isdigit(ch)) { + versionMajor *= 10; + versionMajor += ch - '0'; + } else if(ch == '.') { + state = StateHTTPMinor1; + } else ERROR(L"expecting 'HTTP/major.minor'"); + break; + + case StateHTTPMinor1: + if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'"); + versionMinor = ch - '0'; + state = StateHTTPMinor; + break; + + case StateHTTPMinor: + if(isdigit(ch)) { + versionMinor *= 10; + versionMinor += ch - '0'; + } else if(ch == '\r') { + state = StateHTTPNewline; + } else ERROR(L"expecting 'HTTP/major.minor'"); + break; + + case StateHTTPNewline: + if(ch != '\n') ERROR(L"\\r must be followed by \\n"); + state = StateHeader; + break; + + case StateHeader: + case StateDone: + case StateError: + // should never get here! + throw lw::ProgramException().chain(L"HTTPRequestParser::parse() [1]"); + } + + goto begin; +#undef ERROR +} + + + +//END // HTTPRequestParser ///////////////////////////////////////////// + + + +} diff --git a/src/liblw-support/Net/Protocols/HTTP.h b/src/liblw-support/Net/Protocols/HTTP.h new file mode 100644 index 0000000..90392b6 --- /dev/null +++ b/src/liblw-support/Net/Protocols/HTTP.h @@ -0,0 +1,212 @@ +/* lw-support/src/lib/Net/Protocols/HTTP.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Parser for HTTP header fields. + +This class is used to parse the HTTP header fields that occur after the +HTTP request or response line. It is a finite state machine parser that +only consumes as many characters as it needs (i.e. it stops at the final +\c "\r\n\r\n" sequence demanded by the standard). It exposes these +header fields as part of a map. + +\warning This isn't 100% RFC-compliant: it won't handle multiple + instances of the same field name in an RFC-compatible manner. + +*/ +class HTTPHeaderParser { +private: + enum State { + StateBegin, + StateNewline, + StateLine, + StateFieldName, + StateFieldValue, + StateNewline2, + StateDone, + StateError + }state; + + std::string fieldName; + +public: + /// Constructor. + HTTPHeaderParser(); + + /// Map of field name (key) to value. + std::map fields; + + + + /*! \brief Parse incoming data. + + \param data Pointer to character data. + \param amt Number of bytes to read. + \throws lw::HTTPMalformed if the HTTP is malformed in any way. + \returns Number of bytes parsed. If it returns less than the number + of bytes given, it has reached the end. + + */ + size_t parse(const char* data, size_t amt); + + /// Returns true if parsing is complete. + bool completed() const { return state == StateDone; } + + /// Returns true if parsing failed. + bool failed() const { return state == StateError; } +}; + + + +/*! \brief HTTP response parser. + +This class parses HTTP responses. It uses an HTTPHeaderParser internally +to deal with the header fields, and has code for dealing with the first +line of the HTTP response. + +\warning ASCII only; see also HTTPHeaderParser. + +*/ +class HTTPResponseParser { +private: + HTTPHeaderParser hp; + + enum State { + StateHTTPSeq, + StateHTTPMajor1, + StateHTTPMajor, + StateHTTPMinor1, + StateHTTPMinor, + StateHTTPResp1, + StateHTTPResp2, + StateHTTPResp3, + StateHTTPResp4, + StateHTTPMsg, + StateHTTPNewline, + StateHeader, + StateDone, + StateError + }state; + + static const char* HTTPSeq; + const char* HTTPSeqPos; + +public: + /// Constructor. + HTTPResponseParser(); + + /// HTTP major version number. + int versionMajor; + + /// HTTP minor version number. + int versionMinor; + + /// HTTP 3-digit response code. + int responseCode; + + /// HTTP status message. + std::string status; + + /// HTTP header fields. + std::map& fields; + + /*! \brief Parse incoming data. + + \param data Pointer to character data. + \param amt Number of bytes to read. + \throws lw::HTTPMalformed if the HTTP is malformed in any way. + \returns Number of bytes parsed. If it returns less than the number + of bytes given, it has reached the end. + + */ + size_t parse(const char* data, size_t amt); + + /// Returns true if parsing is complete. + bool completed() const { return state == StateDone; } + + /// Returns true if parsing failed. + bool failed() const { return state == StateError; } +}; + + + +/*! \brief HTTP request parser. + +This class parses HTTP requests. It uses an HTTPHeaderParser internally +to deal with the header fields, and has code for dealing with the first +line of the HTTP request. + +\warning ASCII only; see also HTTPHeaderParser. +\warning URIs are simply copied verbatim and are not validated in any + way whatsoever. + +*/ +class HTTPRequestParser { +private: + HTTPHeaderParser hp; + + enum State { + StateHTTPMethod1, + StateHTTPMethod, + StateHTTPUrl1, + StateHTTPUrl, + StateHTTPSeq, + StateHTTPMajor1, + StateHTTPMajor, + StateHTTPMinor1, + StateHTTPMinor, + StateHTTPNewline, + StateHeader, + StateDone, + StateError + }state; + + static const char* HTTPSeq; + const char* HTTPSeqPos; + +public: + /// Constructor. + HTTPRequestParser(); + + /// HTTP method (POST, GET, etc.). + std::string method; + + /// URL + std::string url; + + /// HTTP major version number. + int versionMajor; + + /// HTTP minor version number. + int versionMinor; + + /// HTTP header fields. + std::map& fields; + + /*! \brief Parse incoming data. + + \param data Pointer to character data. + \param amt Number of bytes to read. + \throws lw::HTTPMalformed if the HTTP is malformed in any way. + \returns Number of bytes parsed. If it returns less than the number + of bytes given, it has reached the end. + + */ + size_t parse(const char* data, size_t amt); + + /// Returns true if parsing is complete. + bool completed() const { return state == StateDone; } + + /// Returns true if parsing failed. + bool failed() const { return state == StateError; } +}; + + + +} diff --git a/src/liblw-support/Net/Protocols/SMTP.cpp b/src/liblw-support/Net/Protocols/SMTP.cpp new file mode 100644 index 0000000..f31b76a --- /dev/null +++ b/src/liblw-support/Net/Protocols/SMTP.cpp @@ -0,0 +1,629 @@ +/* lw-support/src/lib/Net/Protocols/SMTP.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +const char* const SMTPClient::dayName[8] = { + "(?) ", "Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat ", "Sun " +}; + +const char* const SMTPClient::monthName[13] = { + " (?) ", " Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ", + " Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec " +}; + + + +size_t SMTPClient::parserFeedData(const char* data, size_t amt) +{ + for(size_t used = 0; used < amt; ++used) { + char ch = data[used]; + + switch(parserState) { + case ParserStateDone: + throw std::wstring(L"Program logic error [1]"); + + case ParserStateNone: + if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code."); + parserCode = 100 * (ch - '0'); + parserState = ParserStateCode1; + break; + + case ParserStateCode1: + if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code."); + parserCode += 10 * (ch - '0'); + parserState = ParserStateCode2; + break; + + case ParserStateCode2: + if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code."); + parserCode += (ch - '0'); + parserState = ParserStateSP; + break; + + case ParserStateSP: + if(isspace(ch)) parserState = ParserStateData; + else if(ch == '-') parserState = ParserStateContData; + else throw std::wstring(L"Expecting ' ' or '-' after SMTP reply code."); + break; + + case ParserStateData: + if(ch == '\n') { // bad server! + parserState = ParserStateDone; + return used + 1; + } + if(ch == '\r') { + parserState = ParserStateLF; + break; + } + parserData.append(&ch, 1); + break; + + case ParserStateLF: + if(ch != '\n') throw std::wstring(L"Expecting \\r\\n sequence."); + parserState = ParserStateDone; + return used + 1; + + case ParserStateContData: + if(ch == '\n') parserState = ParserStateContCode0; + else if(ch == '\r') parserState = ParserStateContLF; + else parserData.append(&ch, 1); + break; + + case ParserStateContLF: + if(ch != '\n') throw std::wstring(L"Expecting \\r\\n sequence."); + parserState = ParserStateContCode0; + break; + + case ParserStateContCode0: + if((parserCode / 100) != (ch - '0')) + throw std::wstring(L"Mismatched multi-line SMTP reply code."); + parserState = ParserStateContCode1; + break; + + case ParserStateContCode1: + if(((parserCode % 100) / 10) != (ch - '0')) + throw std::wstring(L"Mismatched multi-line SMTP reply code."); + parserState = ParserStateContCode2; + break; + + case ParserStateContCode2: + if((parserCode % 10) != (ch - '0')) + throw std::wstring(L"Mismatched multi-line SMTP reply code."); + parserState = ParserStateSP; + break; + } + } + return amt; +} + + + +void SMTPClient::parserReset() +{ + parserState = ParserStateNone; + parserCode = 0; + parserData.clear(); +} + + + +bool SMTPClient::ioReady(uint32_t flags) +{ + if(flags & (IOEventUrgent | IOEventHUP)) { + state = StateDone; + logFail(L"Urgent data received / remote hangup"); + return IOListenResultDelete; + } + + try { + if(flags & IOEventError) netClient.checkErrors(); + + while(tryRead() | tryWrite()) + ; + } + catch(Exception& e) { + logFail(e); + delete this; + return false; + } + catch(std::wstring& e) { + logFail(e); + delete this; + return false; + } + + if(state == StateDone) { + delete this; + return false; + } + return true; +} + + + +bool SMTPClient::tryRead() // returns true if I/O happens +{ + bool ioHappened = false; + while(true) { + size_t amt = inBuffer.getRemaining(); + amt = netClient.read(inBuffer.getBuffer(amt), amt); + inBuffer.addedData(amt); + if(!amt) return ioHappened; + ioHappened = true; + logRead(inBuffer.getData(), inBuffer.getSize()); + + while(!inBuffer.isEmpty()) { + // parse data + inBuffer.getData(parserFeedData(inBuffer.getData(), + inBuffer.getSize())); + if(parserState != ParserStateDone) break; + +#define FAIL() do { \ + logFail(parserCode, parserData); \ + state = StateDone; \ + return true; \ +}while(0) + +#define WRITE(x) do { \ + writePtr = x.c_str(); \ + writeAmt = x.size(); \ +}while(0) + + switch(state) { + case StateDone: + throw std::wstring(L"Program logic error [2]."); + + case StateNone: + if((parserCode / 10) != 22) FAIL(); + else { + state = StateHelo; + outBuffer = "helo "; + outBuffer.append(hostName); + outBuffer.append("\r\n"); + WRITE(outBuffer); + } + break; + + case StateHelo: + if((parserCode / 10) != 25) FAIL(); + else { + state = StateMailFrom; + outBuffer = "mail from: "; + outBuffer.append(mailFrom); + outBuffer.append("\r\n"); + WRITE(outBuffer); + } + break; + + case StateMailFrom: + if((parserCode / 10) != 25) FAIL(); + else { + state = StateRcptTo; + outBuffer = "rcpt to: "; + outBuffer.append(rcptTo); + outBuffer.append("\r\n"); + WRITE(outBuffer); + } + break; + + case StateRcptTo: + if((parserCode / 10) != 25) FAIL(); + else { + state = StateData; + outBuffer = "data\r\n"; + WRITE(outBuffer); + } + break; + + case StateData: + if((parserCode / 10) != 35) FAIL(); + else { + state = StateQuit; + WRITE(data); + } + break; + + case StateQuit: + if((parserCode / 10) != 25) FAIL(); + else { + state = StateFinal; + outBuffer = "quit\r\n"; + WRITE(outBuffer); + } + break; + + case StateFinal: + if((parserCode / 10) != 22) FAIL(); + else { + state = StateDone; + logDone(); + return true; + } + break; + } + +#undef WRITE +#undef FAIL + + // reset parser and go again + parserReset(); + } + } +} + + + +bool SMTPClient::tryWrite() +{ + bool ioHappened = false; + if(!writePtr) return false; + + while(writeAmt) { + size_t amt = netClient.write(writePtr, writeAmt); + if(!amt) return ioHappened; + ioHappened = true; + + logWrite(writePtr, amt); + writeAmt -= amt; + writePtr += amt; + } + + writePtr = 0; + return true; +} + + + +bool SMTPClient::isWhitespace(char ch) +{ + return (ch >= 0x9 && ch <= 0xD) || ch == 0x20; +} + + + +std::string SMTPClient::wsFieldName(const std::string& fieldName) +{ + // strip whitespace from beginning and end + std::string::size_type pos, len = fieldName.length(); + for(pos = 0; pos < len; ++pos) if(!isWhitespace(fieldName[pos])) break; + if(pos == len) return std::string(); + + std::wstring::size_type endPos = len - 1; + while(isWhitespace(fieldName[endPos])) --endPos; + + std::string str = fieldName.substr(pos, endPos - pos + 1); + + // return empty string if there are any illegal chars, and convert + // to lowercase + for(pos = 0, len = str.length(); pos < len; ++pos) { + if(str[pos] <= 0x20 || str[pos] & ~0x7F) return std::string(); + str[pos] = tolower(str[pos]); + } + + // done + return str; +} + + + +std::string SMTPClient::wsFieldValue(const std::string& fieldValue) +{ + // strip whitespace from beginning and end + std::string::size_type pos, len = fieldValue.length(); + for(pos = 0; pos < len; ++pos) if(!isWhitespace(fieldValue[pos])) break; + if(pos == len) return std::string(); + + std::wstring::size_type endPos = len - 1; + while(isWhitespace(fieldValue[endPos])) --endPos; + + std::string str = fieldValue.substr(pos, endPos - pos + 1); + + // convert remaining whitespace + for(pos = 0, len = str.length(); pos < len; ++pos) + if(str[pos] == '\r' || str[pos] == '\n') + str[pos] = ' '; + + // done + return str; +} + + + +std::string SMTPClient::buildRFC2822(const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message) +{ + (void)hostName; // TODO: add message-id + std::ostringstream o; + + // first, deal with headers (sanitise as we go) + bool seenDate = false, seenFrom = false, seenTo = false; + + std::multimap::const_iterator + iter = headerLines.begin(), end = headerLines.end(); + for(; iter != end; ++iter) { + std::string fieldName = wsFieldName(iter->first); + std::string fieldValue = wsFieldValue(iter->second); + + if(fieldName.empty()) continue; + if(fieldName == "date") seenDate = true; + if(fieldName == "from") seenFrom = true; + if(fieldName == "to") seenTo = true; + if(fieldValue.empty()) continue; + + o << fieldName << ": " << fieldValue << "\r\n"; + } + + if(!seenDate) { + lw::DateTime date = lw::DateTime::now(); + o << std::setfill('0') + << "Date: " + << dayName[date.getDate().getWeekDay()] + << date.getDate().getDay() + << monthName[date.getDate().getMonth()] + << date.getDate().getYear() + << ' ' + << std::setw(2) << date.getTime().h() << ':' + << std::setw(2) << date.getTime().m() << ':' + << std::setw(2) << date.getTime().s() + << " +0000" // FIXME + << "\r\n" + << std::setfill(' '); + } + if(!seenFrom) o << "From: " << wsFieldValue(mailFrom) << "\r\n"; + if(!seenTo) o << "To: " << wsFieldValue(rcptTo) << "\r\n"; + + o << "\r\n"; + + // now output the escaped message + bool wasNewline = true; + for(std::string::size_type pos = 0, len = message.size(); pos < len; ++pos) { + char ch = message[pos]; + switch(ch) { + case '\r': + ch = ' '; + break; + + case '\n': + o << "\r\n"; + wasNewline = true; + continue; + + case '.': + if(wasNewline) o.put('.'); + break; + } + o.put(ch); + wasNewline = false; + } + + o << "\r\n.\r\n"; + + // done + return o.str(); +} + + + +SMTPClient::SMTPClient(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, CompletionNotifier* completionNotifier) + : completionNotifier(completionNotifier), + hostName(wsFieldValue(hostName)), + mailFrom(wsFieldValue(mailFrom)), + rcptTo(wsFieldValue(rcptTo)), + data(buildRFC2822(hostName, mailFrom, rcptTo, headerLines, message)), + state(StateNone), inBuffer(1024, 1024), writePtr(0), writeAmt(0), + parserState(ParserStateNone), parserCode(0), + port(port), netAddress(netAddress.clone()) +{ + // NOTE: we can't use any of the log* functions in the constructor, + // because the virtual functions won't be pointing to the derived + // class yet. We'll have to wait until we're in the I/O callback to + // log anything. + + // set up client + netClient.connect(netAddress, port); + eventManager.registerDevice(&netClient, this, ~0); +} + + + +SMTPClient::~SMTPClient() +{ + if(completionNotifier) completionNotifier->signal(); + delete netAddress; +} + + + +void SMTPClient::sendMail(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, CompletionNotifier* completionNotifier) +{ + new SMTPClient(netAddress, port, eventManager, hostName, + mailFrom, rcptTo, headerLines, message, completionNotifier); +} + + + +void SMTPClient::logStart(const std::string&, + const std::string&, const std::string&, + const std::string&) +{ +} + + + +void SMTPClient::logDone() +{ +} + + + +void SMTPClient::logRead(const char*, size_t) +{ +} + + + +void SMTPClient::logWrite(const char*, size_t) +{ +} + + + +void SMTPClient::logFail(int, const std::string&) +{ +} + + + +void SMTPClient::logFail(Exception&) +{ +} + + + +void SMTPClient::logFail(const std::wstring&) +{ +} + + + +int LoggingSMTPClient::instanceCount = 0; + + + +LoggingSMTPClient::LoggingSMTPClient(const NetAddress& netAddress, + uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, Log& logger, Log& logError, + CompletionNotifier* completionNotifier) + : SMTPClient(netAddress, port, eventManager, hostName, mailFrom, rcptTo, + headerLines, message, completionNotifier), + logger(logger), logError(logError) +{ + std::wostringstream o; + o << L"SMTP <" + << ++instanceCount + << L">, " + << netAddress.toString() + << L" port " + << port; + logID = o.str(); +} + + + +LoggingSMTPClient::~LoggingSMTPClient() +{ +} + + + +void LoggingSMTPClient::sendMail(const NetAddress& netAddress, + uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, Log& logger, Log& logError, + CompletionNotifier* completionNotifier) +{ + new LoggingSMTPClient(netAddress, port, eventManager, hostName, + mailFrom, rcptTo, headerLines, message, + logger, logError, completionNotifier); +} + + + +void LoggingSMTPClient::logStart(const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::string& data) +{ + logger << Log::start(logID) + << L"Sending a new email:" + << L"\n Host: " << hostName + << L"\n From: " << mailFrom + << L"\n To : " << rcptTo + << L"\n\n" + << Log::asciiDump(data.c_str(), data.size()) + << Log::end; +} + + + +void LoggingSMTPClient::logDone() +{ + logger << Log::start(logID) + << L"Email sent successfully." + << Log::end; +} + + + +void LoggingSMTPClient::logRead(const char* data, size_t amt) +{ + logger << Log::start(logID) + << L"Incoming data.\n\n" + << Log::asciiDump(data, amt) + << Log::end; +} + + + +void LoggingSMTPClient::logWrite(const char* data, size_t amt) +{ + logger << Log::start(logID) + << L"Wrote data.\n\n" + << Log::asciiDump(data, amt) + << Log::end; +} + + + +void LoggingSMTPClient::logFail(int smtpCode, const std::string& errorString) +{ + logError << Log::start(logID) + << L"SMTP error." + L"\n Code : " << smtpCode + << L"\n Reason: " << errorString + << Log::end; +} + + + +void LoggingSMTPClient::logFail(Exception& e) +{ + logError << Log::start(logID) + << L"System error.\n" + << e + << Log::end; +} + + + +void LoggingSMTPClient::logFail(const std::wstring& s) +{ + logError << Log::start(logID) + << L"Communication failed: " + << s + << Log::end; +} + + + +} diff --git a/src/liblw-support/Net/Protocols/SMTP.h b/src/liblw-support/Net/Protocols/SMTP.h new file mode 100644 index 0000000..cc71bf6 --- /dev/null +++ b/src/liblw-support/Net/Protocols/SMTP.h @@ -0,0 +1,287 @@ +/* lw-support/src/lib/Net/Protocols/SMTP.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief An SMTP (email) client class. + +This class can be used to perform an SMTP transaction. It will send one +email. It is used in an asynchronous manner, and can (optionally) signal +completion through a CompletionNotifier object. + +\warning You should only send ASCII characters through this client, but + it does not perform any checking to enforce this. + +\todo Timeout options. +\todo Split SMTP response parser into its own class. + +*/ +class SMTPClient : private EventCallbackIO { +private: + // for performing I/O + CompletionNotifier* completionNotifier; + NetClientTCP netClient; + virtual bool ioReady(uint32_t flags); + bool tryRead(); + bool tryWrite(); + + // data + const std::string hostName, mailFrom, rcptTo, data; + + // builds an RFC2822-compliant message + static const char* const dayName[8]; + static const char* const monthName[13]; + static std::string buildRFC2822(const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message); + + static bool isWhitespace(char ch); + static std::string wsFieldName(const std::string& fieldName); + static std::string wsFieldValue(const std::string& fieldValue); + + // transaction state + enum State { + StateNone, + StateHelo, + StateMailFrom, + StateRcptTo, + StateData, + StateQuit, + StateFinal, + StateDone + }state; + FIFO inBuffer; + std::string outBuffer; + const char* writePtr; + size_t writeAmt; + + // RFC 2821 parser + size_t parserFeedData(const char* data, size_t amt); + void parserReset(); + enum ParserState { + ParserStateNone, + ParserStateCode1, + ParserStateCode2, + ParserStateSP, + ParserStateData, + ParserStateLF, + ParserStateContData, + ParserStateContLF, + ParserStateContCode0, + ParserStateContCode1, + ParserStateContCode2, + ParserStateDone + }parserState; + int parserCode; + std::string parserData; + +protected: + /// TCP port to connect to. + uint16_t port; + + /// Host to connect to. + NetAddress* netAddress; + + /// Protected constructor for "fire-and-forget". + SMTPClient(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, CompletionNotifier* completionNotifier); + + /// Log the start of a new message. + virtual void logStart(const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::string& data); + + /// Log message complete. + virtual void logDone(); + + /// Log a data transfer (input). + virtual void logRead(const char* data, size_t amt); + + /// Log a data transfer (output). + virtual void logWrite(const char* data, size_t amt); + + /// Log an SMTP error. + virtual void logFail(int smtpCode, const std::string& errorString); + + /// Log a connection error. + virtual void logFail(Exception& e); + + /// Log a communications error. + virtual void logFail(const std::wstring& s); + +public: + /// Destructor. + virtual ~SMTPClient(); + + /*! \brief Begins sending email. + + \param netAddress The address of the host to connect to. + \param port The TCP port number of the SMTP service. + \param eventManager The event manager to use. + \param hostName The name of the host to present to the SMTP server. + \param mailFrom Email address of the (SMTP envelope) sender. + \param rcptTo Email address of the (SMTP envelope) receiver. + \param headerLines The header lines to use in the email. These will + be properly escaped, whitespace stripped, etc. + \param message The actual message to send. It will be properly + escaped. + \param completionNotifier The CompletionNotifier object to use to + signal when the process is complete. May be left as 0 in which + case no signal will be issued. + \throws Exception if a system error occurs. + + The constructor will create a properly-formatted email, and begin + connecting to the SMTP server. + + Several standard header lines will be automatically filled out for + you. You may override this by providing values for them in the + \a headerLines parameter. These headers are: + - \a orig-date (set to current date). + - \a from (set to the SMTP envelope \a mailFrom). + - \a to (set to the SMTP envelope \a rcptTo). + + Blank value strings in the \a headerLines array will cause the + corresponding header line \e not to be generated. Blank key strings + will be ignored. Whitespace will be stripped from all key strings. + Key strings are case insensitive. + + Rules for whitespace handling are: + - all ASCII CR characters are replaced with an ASCII space; + - all ASCII LF characters in header fields are replaced with an + ASCII space; + - any remaining whitespace in field names causes the field to be + skipped; + - otherwise, whitespace is left verbatim. + + */ + static void sendMail(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, CompletionNotifier* completionNotifier); +}; + + + +/*! \brief A logging SMTP (email) client class. + +This class can be used to perform an SMTP transaction. It will send one +email. It is used in a "fire and forget" manner, and there is no way to +determine the outcome of sending an email (except through the logging +functions). + +This class will log its actions to the specified log objects. + +\warning You should only send ASCII characters through this client, but + it does not perform any checking to enforce this. + +*/ +class LoggingSMTPClient : public SMTPClient { +private: + static int instanceCount; + +protected: + /// This email's unique ID, for logging. + std::wstring logID; + + /// Log object. + Log& logger, & logError; + + /// Protected constructor for "fire-and-forget". + LoggingSMTPClient(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, Log& logger, Log& logError, + CompletionNotifier* completionNotifier); + + /// Log the start of a new message. + virtual void logStart(const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::string& data); + + /// Log message complete. + virtual void logDone(); + + /// Log a data transfer (input). + virtual void logRead(const char* data, size_t amt); + + /// Log a data transfer (output). + virtual void logWrite(const char* data, size_t amt); + + /// Log an SMTP error. + virtual void logFail(int smtpCode, const std::string& errorString); + + /// Log a connection error. + virtual void logFail(Exception& e); + + /// Log a communications error. + virtual void logFail(const std::wstring& s); + +public: + /// Destructor. + virtual ~LoggingSMTPClient(); + + /*! \brief Begins sending email, with logging. + + \param netAddress The address of the host to connect to. + \param port The TCP port number of the SMTP service. + \param eventManager The event manager to use. + \param hostName The name of the host to present to the SMTP server. + \param mailFrom Email address of the (SMTP envelope) sender. + \param rcptTo Email address of the (SMTP envelope) receiver. + \param headerLines The header lines to use in the email. These will + be properly escaped, whitespace stripped, etc. + \param message The actual message to send. It will be properly + escaped. + \param logger The lw::Log object to use. + \param logError The lw::Log object to use to log errors. + \param completionNotifier The CompletionNotifier object to use to + signal when the process is complete. May be left as 0 in which + case no signal will be issued. + \throws Exception if a system error occurs. + + The constructor will create a properly-formatted email, and begin + connecting to the SMTP server. + + Several standard header lines will be automatically filled out for + you. You may override this by providing values for them in the + \a headerLines parameter. These headers are: + - \a orig-date (set to current date). + - \a from (set to the SMTP envelope \a mailFrom). + - \a to (set to the SMTP envelope \a rcptTo). + + Blank value strings in the \a headerLines array will cause the + corresponding header line \e not to be generated. Blank key strings + will be ignored. Whitespace will be stripped from all key strings. + Key strings are case insensitive. + + Rules for whitespace handling are: + - all ASCII CR characters are replaced with an ASCII space; + - all ASCII LF characters in header fields are replaced with an + ASCII space; + - any remaining whitespace in field names causes the field to be + skipped; + - otherwise, whitespace is left verbatim. + + */ + static void sendMail(const NetAddress& netAddress, uint16_t port, + EventManager& eventManager, const std::string& hostName, + const std::string& mailFrom, const std::string& rcptTo, + const std::multimap& headerLines, + const std::string& message, Log& logger, Log& logError, + CompletionNotifier* completionNotifier); +}; + + + +} diff --git a/src/liblw-support/Net/Server.cpp b/src/liblw-support/Net/Server.cpp new file mode 100644 index 0000000..0915682 --- /dev/null +++ b/src/liblw-support/Net/Server.cpp @@ -0,0 +1,121 @@ +/* lw-support/src/lib/Net/Server.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +NetServer::NetServer() + : nsCallback(0), eventManager(0) +{ +} + + + +NetServer::~NetServer() +{ +} + + + +NetServer::Protocol NetServer::getProtocol(const NetAddress& addr) +{ + const NetAddress* a = &addr; + if(dynamic_cast(a)) return IPv4; + if(dynamic_cast(a)) return IPv6; + throw UnknownProtocol(); +} + + + +void NetServer::registerCallback(EventManager& eventManager, + EventCallbackNetServer& callback) +{ + clearCallback(); + nsCallback = &callback; + this->eventManager = &eventManager; + eventManager.registerDevice(this, this, IOEventRead | IOEventError); +} + + + +void NetServer::clearCallback() +{ + if(eventManager) eventManager->ignoreDevice(this); + eventManager = 0; + nsCallback = 0; +} + + + +void NetServer::ioReady(uint32_t flags) throw() +{ + // TODO -- this really needs sorting somehow, since we no longer support + // error handling in the event loop +#if 0 + // deal with errors + try { + if(flags & IOEventError) if(!nsCallback->error()) return false; + } + catch(...) { } + + // deal with waiting connections + if(flags & IOEventRead) { + IODevice* netClient = 0; + while(true) { + if(!(netClient = accept())) break; + try { + if(!nsCallback->accept(netClient)) return false; + } + catch(...) { } + } + } + + return true; +#endif +} + + + +void NetServer::readBlock(int timeout, IOTimeoutMode timeoutMode) +{ + try { + int timeout_remaining = timeout; + struct pollfd ufd = { fd, POLLIN, 0 }; + IOCountdown countdown(this, L"readBlock()", timeout, timeoutMode); + + while(true) { + switch(poll(&ufd, 1, timeout_remaining)) { + case -1: + if(errno == EINTR) break; + throw SystemError().chain(L"poll()"); + + case 0: + throw IOTimeout(this, L"acceptBlocking", timeout, timeoutMode, 0); + + default: + // perform read + return; + } + + // update timeout + if(!countdown.update(timeout_remaining)) + throw IOTimeout(this, L"acceptBlocking", timeout, timeoutMode, 0); + } + } + catch(Exception& e) { + e.chain(L"IOPosixDevice::readBlock()"); + throw; + } +} + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Net/Server.h b/src/liblw-support/Net/Server.h new file mode 100644 index 0000000..db6cfe8 --- /dev/null +++ b/src/liblw-support/Net/Server.h @@ -0,0 +1,155 @@ +/* lw-support/src/lib/Net/Server.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base class for all network servers. + +This is the base class for any network server. It provides two main +functions: you can call accept() (non-blocking) to accept any waiting +connection; or you can register a callback with an EventManager. Your +callback will get error and new connection events. + +*/ +class NetServer + : protected IOPosixDevice, + private EventCallbackIO +{ +private: + // callback stuff + EventCallbackNetServer* nsCallback; + EventManager* eventManager; + virtual void ioReady(uint32_t flags) throw(); // from EventCallbackIO() + + //BEGIN // Protocol-specific implementation //////////////////////// + +public: + /// Exception used for an unknown protocol. + class UnknownProtocol : public ProgramException { }; + +protected: + /// Enumeration representing understood protocols. + enum Protocol { + IPv4, + IPv6 + }protocol; + + /*! \brief Get an address's protocol type. + + \param addr The address to inquire about. + \throws UnknownProtocol if the protocol is entirely unknown. + \returns The type of the address. + + This function can be used to ask what type of protocol an address + refers to. This isn't a virtual function in NetAddress because all + the behaviour changing functionality is (for the time being at + least) going to be in this class. + + */ + static Protocol getProtocol(const NetAddress& addr); + + /*! \brief Block until there's a connection available. + + \param timeout Timeout period, in milliseconds. 0 means immediate; + negative means forever. + \param timeoutMode The timeout mode. + \throws SystemError if a system error occurs. + \throws IOTimeout if a timeout occurs. + \returns A newly-allocated I/O device object. + + This function can be called from within acceptBlocking(). Its purpose is to aid the + implementation of that function. It will wait until there is data available to read on the + associated fd. + + */ + void readBlock(int timeout = -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull); + + + +public: + //BEGIN // Constructors etc. /////////////////////////////////////// + + /// Constructor. Does nothing. + NetServer(); + + /// Destructor. + virtual ~NetServer(); + + + + //BEGIN // Callback system ///////////////////////////////////////// + + /*! \brief Called to retrieve a connection (non-blocking). + + \throws SystemError if a system error occurs. + \returns A newly-allocated I/O device object. + \retval 0 if there is no connection waiting. + + This function is called whenever you want to accept a new + connection. It either creates a new I/O device object or returns 0 + if there are no connections waiting. + + */ + virtual lw::NetClient* accept() = 0; + + + + /*! \brief Called to retrieve a connection (blocking). + + \param timeout Timeout period, in milliseconds. 0 means immediate; + negative means forever. + \param timeoutMode The timeout mode. + \throws SystemError if a system error occurs. + \throws IOTimeout if a timeout occurs. + \returns A newly-allocated I/O device object. + + This function is called whenever you want to accept a new connection. It creates a new I/O + device object. + + */ + virtual lw::NetClient* acceptBlocking(int timeout = -1, + IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0; + + + + /*! \brief Registers object with an event manager. + + \param eventManager The event manager object. + \param callback The NetServerCallback object that will receive + events for this server. + \throws SystemError if a system error occurs. + + This function will register your callback object with an event + manager. Only one callback can be active at once; calling this + function with a callback already set will simply clear the old + callback. + + */ + void registerCallback(EventManager& eventManager, + EventCallbackNetServer& callback); + + + + /*! \brief Stops callbacks. + + \throws SystemError if a system error occurs. + + If you want to stop generating callback events, call this function. + Alternatively, you can return \a false in the callback + + */ + void clearCallback(); +}; + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Net/ServerCallback.cpp b/src/liblw-support/Net/ServerCallback.cpp new file mode 100644 index 0000000..2775ae1 --- /dev/null +++ b/src/liblw-support/Net/ServerCallback.cpp @@ -0,0 +1,18 @@ +/* lw-support/src/lib/Net/ServerCallback.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +bool EventCallbackNetServer::error() +{ + return true; +} + + + +} diff --git a/src/liblw-support/Net/ServerCallback.h b/src/liblw-support/Net/ServerCallback.h new file mode 100644 index 0000000..14488d7 --- /dev/null +++ b/src/liblw-support/Net/ServerCallback.h @@ -0,0 +1,70 @@ +/* lw-support/src/lib/Net/ServerCallback.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Base class for network server callback objects. + +This is the callback objected to be used with IOListen when you want to +listen for incoming connections on a new server object. You simply need +to override the accept() function (and possibly the error() function); +everything else is ready to work with any object derived from the +NetServer class. + +*/ +class EventCallbackNetServer { +public: + /// Destructor. Does nothing. + virtual ~EventCallbackNetServer() + { } + + + + /*! \brief Called when a new connection is accepted. + + \param client The new connection object. + \retval true if you want to continue listening. + \retval false if you want to stop listening. + + This function is called whenever the acceptance process for a new + socket is complete. This needs to be overridden in derived classes + to do something with \a client (such as add it to a global ready + list). + + \note Any exceptions you throw from here will be ignored, and the + callback will still be called in future for new connections. + + */ + virtual bool accept(IODevice* client) = 0; + + + + /*! \brief Called when an error is detected. + + \retval true if you want to continue listening. + \retval false if you want to stop listening. + + This function is called whenever \c epoll detects an error with the + server socket. The default behaviour is to ignore the error, but you + might have some more complicated error processing to do if you so + wish. + + \note Any exceptions you throw from here will be ignored, and the + callback will still be called in future for further errors. + + \todo Presumably this gets called whenever epoll returns the + EPOLLERR event; we should therefore find a way to retrieve the + error from the socket's fd. + + */ + virtual bool error(); +}; + + + +} diff --git a/src/liblw-support/Net/TCP/Client.cpp b/src/liblw-support/Net/TCP/Client.cpp new file mode 100644 index 0000000..a1b29a2 --- /dev/null +++ b/src/liblw-support/Net/TCP/Client.cpp @@ -0,0 +1,201 @@ +/* lw-support/src/lib/Net/TCP/Client.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +NetClientTCP::NetClientTCP() + : sockAddr(0), peerAddr(0) +{ +} + + + +void NetClientTCP::clearAddr() +{ + delete sockAddr; + sockAddr = 0; + delete peerAddr; + peerAddr = 0; +} + + + +NetClientTCP::~NetClientTCP() +{ + clearAddr(); +} + + + +NetClientTCP::NetClientTCP(int fd, NetAddress* sockAddr, uint16_t sockPort, + NetAddress* peerAddr, uint16_t peerPort) + : sockAddr(sockAddr), peerAddr(peerAddr), + sockPort(sockPort), peerPort(peerPort) +{ + this->fd = fd; + ioMode = IOReadWrite; + setNonBlocking(); +} + + + +void NetClientTCP::connect(const NetAddress& addr, uint16_t port, bool block) +{ + try { + // clear old connection + clearAddr(); + close(); + + // create a socket + fd = TEMP_FAILURE_RETRY(socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)); + if(fd == -1) + throw SystemError().chain(L"socket()"); + ioMode = IOReadWrite; + + // set non-blocking mode + if(!block) setNonBlocking(); + + // fill out addresses and connect + peerAddr = addr.clone(); + peerPort = port; + + // IPv4 + if(!sockAddr) { + const NetAddressIPv4* a = dynamic_cast(&addr); + if(a) { + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + // get address + if(TEMP_FAILURE_RETRY(getsockname( + fd, (struct sockaddr*)&sa, &sa_len))) + throw SystemError().chain(L"getsockname()"); + sockAddr = new NetAddressIPv4((char*)&(sa.sin_addr.s_addr)); + sockPort = sa.sin_port; + + // connect + int ret = TEMP_FAILURE_RETRY(::connect( + fd, a->copy(&sa, port), sa_len)); + + if(ret == -1 && (block || errno != EINPROGRESS)) + throw SystemError().chain(L"connect()"); + } + } + + // IPv6 + if(!sockAddr) { + const NetAddressIPv6* a = dynamic_cast(&addr); + if(a) { + struct sockaddr_in6 sa; + socklen_t sa_len = sizeof(sa); + + // get address + if(TEMP_FAILURE_RETRY(getsockname( + fd, (struct sockaddr*)&sa, &sa_len))) + throw SystemError().chain(L"getsockname()"); + sockAddr = new NetAddressIPv6(sa.sin6_addr.s6_addr); + sockPort = sa.sin6_port; + + // connect + int ret = TEMP_FAILURE_RETRY(::connect( + fd, a->copy(&sa, port), sa_len)); + + if(ret == -1 && (block || errno != EINPROGRESS)) + throw SystemError().chain(L"connect()"); + } + } + + // unknown + if(!sockAddr) { + throw NetServer::UnknownProtocol(); + } + + // if we were doing blocking I/O, set non-blocking mode now + setNonBlocking(); + + } + + // deal with errors gracefully + catch(Exception& e) { + try { + close(); + } + catch(...) { } + e.chain(L"NetClientTCP::connect()"); + throw; + } +} + + + +void NetClientTCP::connect(const NetAddress& addr, uint16_t port) +{ + connect(addr, port, false); +} + + + +void NetClientTCP::connectBlocking(const NetAddress& addr, uint16_t port) +{ + connect(addr, port, true); +} + + + +NetClientTCP::NetClientTCP(const NetAddress& addr, uint16_t port) + : sockAddr(0), peerAddr(0) +{ + connect(addr, port, true); +} + + + +uint16_t NetClientTCP::localPort() const +{ + checkOpen(L"NetClientTCP::localPort()"); + return sockPort; +} + + + +uint16_t NetClientTCP::remotePort() const +{ + checkOpen(L"NetClientTCP::remotePort()"); + return peerPort; +} + + + +const NetAddress& NetClientTCP::localAddr() const +{ + checkOpen(L"NetClientTCP::localPort()"); + return *sockAddr; +} + + + +const NetAddress& NetClientTCP::remoteAddr() const +{ + checkOpen(L"NetClientTCP::remotePort()"); + return *peerAddr; +} + + + +void NetClientTCP::setNoDelay(bool noDelay) +{ + int n = noDelay; + if(setsockopt(fd, SOL_TCP, TCP_NODELAY, &n, sizeof(n))) + throw SystemError() + .chain(L"setsockopt(..., SOL_TCP, TCP_NODELAY, ...)") + .chain(L"NetClientTCP::setNoDelay()"); +} + + + +} diff --git a/src/liblw-support/Net/TCP/Client.h b/src/liblw-support/Net/TCP/Client.h new file mode 100644 index 0000000..48dd069 --- /dev/null +++ b/src/liblw-support/Net/TCP/Client.h @@ -0,0 +1,148 @@ +/* lw-support/src/lib/Net/TCP/Client.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief TCP client socket class. + +\todo docs + +*/ +class NetClientTCP : public NetClient { +private: + void clearAddr(); + void connect(const NetAddress& addr, uint16_t port, bool block); + NetAddress* sockAddr, * peerAddr; + uint16_t sockPort, peerPort; + +public: + //BEGIN // Constructors etc. /////////////////////////////////////// + + /// Constructor. Does not connect client. + NetClientTCP(); + + /// Destructor. + virtual ~NetClientTCP(); + + + + /*! \brief Construct from an already-connected socket. + + \param fd The file descriptor of the socket. + \param sockAddr Local address of the socket. + \param sockPort Local port of the socket. + \param peerAddr Address of the remote socket. + \param peerPort Remote socket port number. + + This constructor is intended for use in e.g. NetServerTCP(), and is + for situations where you have an already-connected socket and want + to build a wrapper around it. It takes ownership of the \a sockAddr + and \a peerAddr objects. + + */ + NetClientTCP(int fd, NetAddress* sockAddr, uint16_t sockPort, + NetAddress* peerAddr, uint16_t peerPort); + + + + /*! \brief Construct and connect (blocking). + + \param addr The address of the machine to connect to. + \param port The TCP port to connect to. + \throws SystemError if a system error occurs. + \throws NetServer::UnknownProtocol if the protocol of \a addr is + not known. + + Similar to \a connect(), this function will establish a connection + to a remote host. However, this is a blocking function: it will + only return once the connection has been established. + + */ + NetClientTCP(const NetAddress& addr, uint16_t port); + + + + //BEGIN // Connection functions //////////////////////////////////// + + /*! \brief Connect to a remote host (non-blocking). + + \param addr The address of the machine to connect to. + \param port The TCP port to connect to. + \throws SystemError if a system error occurs. + \throws NetServer::UnknownProtocol if the protocol of \a addr is + not known. + + This function begins connecting to a remote host. Note that it + doesn't actually guarantee the connection will be established, + since it is a non-blocking function. You will know the connection + is ready when you receive an IOListen event. + + If the client is already connected, it will be disconnected first. + + \todo figure out what happens in the case of a connection error. + + */ + void connect(const NetAddress& addr, uint16_t port); + + + + /*! \brief Connect to a remote host (blocking). + + \param addr The address of the machine to connect to. + \param port The TCP port to connect to. + \throws SystemError if a system error occurs. + \throws NetServer::UnknownProtocol if the protocol of \a addr is + not known. + + Similar to \a connect(), this function will establish a connection + to a remote host. However, this is a blocking function: it will + only return once the connection has been established. + + \todo add some nicer exception classes for networking. + + */ + void connectBlocking(const NetAddress& addr, uint16_t port); + + + + //BEGIN // Query functions ///////////////////////////////////////// + + /// Query local port number; throws IOModeError if not connected. + uint16_t localPort() const; + + /// Query remote port number; throws IOModeError if not connected. + uint16_t remotePort() const; + + /// Query local host address; throws IOModeError if not connected. + const NetAddress& localAddr() const; + + /// Query remote host address; throws IOModeError if not connected. + const NetAddress& remoteAddr() const; + + + + //BEGIN // Misc. operations //////////////////////////////////////// + + /*! \brief Set no delay option. + + \param noDelay Set to \a true if you want to turn on the no delay + option; set to \a false if you want to use Nagle's algorithm. + \throws SystemError if a system error occurs. + + This function allows you to turn on/off Nagle's algorithm, whereby + outgoing packets are stored until the remote system returns an ACK. + This generally results in better usage of the network but does + increase latency. + + */ + void setNoDelay(bool noDelay); +}; + + + +} diff --git a/src/liblw-support/Net/TCP/Server.cpp b/src/liblw-support/Net/TCP/Server.cpp new file mode 100644 index 0000000..6e5ea4f --- /dev/null +++ b/src/liblw-support/Net/TCP/Server.cpp @@ -0,0 +1,205 @@ +/* lw-support/src/lib/Net/TCP/Server.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +void NetServerTCP::listen(const NetAddress& addr, + uint16_t port, int queueLen) +{ + try { + // shut down any previous server + stop(); + + // create a socket + fd = TEMP_FAILURE_RETRY(socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)); + if(fd == -1) + throw SystemError().chain(L"socket()"); + + // set SO_REUSEADDR, so that we can bind to a socket even if + // it's in the TCP TIME_WAIT state. + int reuse = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) + throw SystemError().chain(L"setsockopt(..., SOL_SOCKET, SO_REUSEADDR, ...)"); + + // bind that socket to the requested address + switch(protocol = getProtocol(addr)) { + case IPv4: { + const NetAddressIPv4& a = dynamic_cast(addr); + struct sockaddr_in sa; + if(TEMP_FAILURE_RETRY(bind(fd, a.copy(&sa, port), sizeof(sa)))) + throw SystemError().chain(L"bind()"); + break; + } + + case IPv6: { + const NetAddressIPv6& a = dynamic_cast(addr); + struct sockaddr_in6 sa; + if(TEMP_FAILURE_RETRY(bind(fd, a.copy(&sa, port), sizeof(sa)))) + throw SystemError().chain(L"bind()"); + break; + } + } + + // start listening + if(TEMP_FAILURE_RETRY(::listen(fd, queueLen))) + throw SystemError().chain(L"listen()"); + + // set non-blocking I/O mode + setNonBlocking(); + ioMode = IONone; + } + + // gracefully deal with any errors + catch(Exception& e) { + try { + stop(); + } + catch(...) { } + + e.chain(L"NetServerTCP::listen()"); + throw; + } +} + + + +void NetServerTCP::stop() +{ + try { + close(); + } + catch(Exception& e) { + e.chain(L"NetServerTCP::stop()"); + throw; + } +} + + + +NetClientTCP* NetServerTCP::accept() +{ + try { + int sockfd = -1; + + switch(protocol) { + case IPv4: { + while(true) { + // get connection and address + struct sockaddr_in sa, sa2; + socklen_t sa_len = sizeof(sa); + sockfd = TEMP_FAILURE_RETRY(::accept( + fd, (struct sockaddr*)(&sa), &sa_len)); + + // return 0 if there was nothing waiting + if(sockfd == -1) { + // see accept(2) man page: some errors are propagated + // from elsewhere in the TCP stack and we should + // handle them by retrying accept(). + switch(errno) { + case EAGAIN: + return 0; + + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: + case ENONET: + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + continue; + + default: + throw SystemError().chain(L"accept()"); + } + } + + // get local address + sa_len = sizeof(sa2); + if(TEMP_FAILURE_RETRY(getsockname( + sockfd, (struct sockaddr*)(&sa2), &sa_len))) + throw SystemError().chain(L"getsockname()"); + + // build client + return new NetClientTCP(sockfd, + new NetAddressIPv4((char*)&(sa2.sin_addr.s_addr)), ntohs(sa2.sin_port), + new NetAddressIPv4((char*)&(sa.sin_addr.s_addr)), ntohs(sa.sin_port)); + } + } + + case IPv6: { + while(true) { + // get connection and address + struct sockaddr_in6 sa, sa2; + socklen_t sa_len = sizeof(sa); + sockfd = TEMP_FAILURE_RETRY(::accept( + fd, (struct sockaddr*)(&sa), &sa_len)); + + // return 0 if there was nothing waiting + if(sockfd == -1) { + // see accept(2) man page: some errors are propagated + // from elsewhere in the TCP stack and we should + // handle them by retrying accept(). + // + // TODO: is this right for IPv6 as well? + switch(errno) { + case EAGAIN: + return 0; + + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: + case ENONET: + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + continue; + + default: + throw SystemError().chain(L"accept()"); + } + } + + // get local address + sa_len = sizeof(sa2); + if(TEMP_FAILURE_RETRY(getsockname( + sockfd, (struct sockaddr*)(&sa2), &sa_len))) + throw SystemError().chain(L"getsockname()"); + + // build client + return new NetClientTCP(sockfd, + new NetAddressIPv6(sa2.sin6_addr.s6_addr), ntohs(sa2.sin6_port), + new NetAddressIPv6(sa.sin6_addr.s6_addr), ntohs(sa.sin6_port)); + } + } + } + + throw ProgramException().chain(L"NetServerTCP::connect()"); + } + catch(Exception& e) { + e.chain(L"NetServerTCP::accept()"); + throw; + } +} + + + +NetClientTCP* NetServerTCP::acceptBlocking(int timeout, IOTimeoutMode timeoutMode) +{ + readBlock(timeout, timeoutMode); + NetClientTCP* client = accept(); + + // TODO: better error handling here would be appropriate... restart timeout somehow + if(!client) throw ProgramException().chain(L"NetServerTCP::acceptBlocking()"); + return client; +} + + + +} diff --git a/src/liblw-support/Net/TCP/Server.h b/src/liblw-support/Net/TCP/Server.h new file mode 100644 index 0000000..c227650 --- /dev/null +++ b/src/liblw-support/Net/TCP/Server.h @@ -0,0 +1,59 @@ +/* lw-support/src/lib/Net/TCP/Server.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief TCP server class. + +\todo docs + +*/ +class NetServerTCP : public NetServer { +public: + /// Constructor. Does not open connection. + NetServerTCP() + { } + + + + /*! \brief Begin listening on a specific address. + + \param address The address to listen on. + \param port The port number to listen on. + \param queueLen The length of the unaccepted connection queue. + \throws SystemError if a system error occurs. + + This function starts the server listening for new connections. The + server will listen on any address. + + */ + void listen(const NetAddress& address, uint16_t port, int queueLen); + + + + /*! \brief Stop listening to incoming connections. + + \throws SystemError if a system error occurs. + + This function will stop listening for incoming connections, and will + reject any connection requests that have not yet been accepted. + + */ + void stop(); + + + + //BEGIN // Implemented virtuals //////////////////////////////////// + + virtual lw::NetClientTCP* accept(); + virtual lw::NetClientTCP* acceptBlocking(int timeout, IOTimeoutMode timeoutMode); +}; + + + +} diff --git a/src/liblw-support/String/basics.cpp b/src/liblw-support/String/basics.cpp new file mode 100644 index 0000000..a0454ae --- /dev/null +++ b/src/liblw-support/String/basics.cpp @@ -0,0 +1,401 @@ +/* lw-support/src/lib/String/basics.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring stripWhitespace(const std::wstring& str) +{ + std::wstring::size_type pos, len = str.length(); + for(pos = 0; pos < len; ++pos) if(!isWhitespace(str[pos])) break; + if(pos == len) return std::wstring(); + + std::wstring::size_type endPos = len - 1; + while(isWhitespace(str[endPos])) --endPos; + + return str.substr(pos, endPos - pos + 1); +} + + + +std::string ucs4ToUtf8(const std::wstring& ucs4, bool force) +{ + // compute size of string, skip empty strings + std::wstring::size_type len = ucs4.length(), rd_pos, start; + if(!len) return std::string(); + + // skip BOM + start = (ucs4[0] == 0xFEFF) ? 1 : 0; + + // compute required size and check characters + int dlen = 0; + for(rd_pos = start; rd_pos < len; ++rd_pos) { + wchar_t ch = ucs4[rd_pos]; + if(!force && (ch & ~0x7FFFFFFF)) { + throw BadSourceChar((const char*)&ch, (const char*)(&ch + 4), + rd_pos * 4, L"UCS-4", L"Character out of range."); + } + + if(ch < 0x80) ++dlen; + else if(ch < 0x800) dlen += 2; + else if(ch < 0x10000) dlen += 3; + else if(ch < 0x200000) dlen += 4; + else if(ch < 0x4000000) dlen += 5; + else dlen += 6; + } + + // allocate memory + std::string dest(dlen, 0); + + // convert string + std::string::size_type wr_pos = 0; + for(rd_pos = start; rd_pos < len; ++rd_pos) { + uint32_t ch = ucs4[rd_pos]; + + if(ch & ~0x7FFFFFFF) continue; + + if(ch < 0x80) { + dest[wr_pos++] = ch; + + } else if(ch < 0x800) { + dest[wr_pos++] = 0xC0 | ((ch >> 6) & 0x1F); + dest[wr_pos++] = 0x80 | (ch & 0x3F); + + } else if(ch < 0x10000) { + dest[wr_pos++] = 0xE0 | ((ch >> 12) & 0xF); + dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F); + dest[wr_pos++] = 0x80 | (ch & 0x3F); + + } else if(ch < 0x200000) { + dest[wr_pos++] = 0xF0 | ((ch >> 18) & 0x7); + dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F); + dest[wr_pos++] = 0x80 | (ch & 0x3F); + + } else if(ch < 0x4000000) { + dest[wr_pos++] = 0xF8 | ((ch >> 24) & 0x3); + dest[wr_pos++] = 0x80 | ((ch >> 18) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F); + dest[wr_pos++] = 0x80 | (ch & 0x3F); + + } else { + dest[wr_pos++] = 0xFC | ((ch >> 30) & 0x1); + dest[wr_pos++] = 0x80 | ((ch >> 24) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 18) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F); + dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F); + dest[wr_pos++] = 0x80 | (ch & 0x3F); + + } + } + + return dest; +} + + + +std::string ucs4ToAscii(const std::wstring& ucs4, bool force) +{ + // compute size of string, skip empty strings + std::wstring::size_type len = ucs4.length(), rd_pos, start; + if(!len) return std::string(); + + // skip BOM + start = (ucs4[0] == 0xFEFF) ? 1 : 0; + + // allocate destination string + int skipped = 0; + std::string ascii(len - start, 0); + + // convert each char, checking values as we go + for(rd_pos = start; rd_pos < len; ++rd_pos) { + if(ucs4[rd_pos] & ~0x7F) { + ++skipped; + if(force) continue; + throw CannotConvertChar(ucs4[rd_pos], rd_pos, L"UCS-4", L"ASCII"); + } + + ascii[rd_pos] = ucs4[rd_pos]; + } + + // strip off any skipped characters + if(skipped) ascii.resize(len - skipped); + + // done + return ascii; +} + + + +namespace { + +void utf8AuxConsume(wchar_t& ch, const std::string& utf8, + std::string::size_type& rd_pos, std::string::size_type ch_start) +{ + uint8_t p = utf8[rd_pos++]; + if((p & 0xC0) != 0x80) { + throw BadSourceChar(utf8.c_str() + ch_start, + utf8.c_str() + rd_pos, ch_start, L"UTF-8", + L"Invalid character sequence (expecting byte 10xxxxxx)."); + } + + ch <<= 6; + ch |= p & 0x3F; +} + +std::wstring utf8ToUcs4Forced(const std::string& str) +{ + // compute size of std::string, skip empty std::strings + std::string::size_type len = str.length(), rd_pos = 0; + if(!len) return std::wstring(); + + // skip possible BOM + if(len >= 3 && str.substr(0, 3) == "\xEF\xBB\xBF") rd_pos = 3; + + // for FSM + enum CharType { + CTypeAscii, + CTypeContinue, + CType2, + CType3, + CType4, + CType5, + CType6 + }ctype; + int cremain = 0; + + // destination buffer + std::wstring d; + + // begin conversion using FSM + uint8_t ch; + wchar_t dch; + + while(rd_pos < len) { + ch = str[rd_pos++]; + + // classify + if(ch & 0x80) { + if((ch & 0xC0) == 0x80) ctype = CTypeContinue; + else if((ch & 0xE0) == 0xC0) ctype = CType2; + else if((ch & 0xF0) == 0xE0) ctype = CType3; + else if((ch & 0xF8) == 0xF0) ctype = CType4; + else if((ch & 0xFC) == 0xF8) ctype = CType5; + else if((ch & 0xFE) == 0xFC) ctype = CType6; + else { + continue; // must be 0xFE or 0xFF, both invalid + } + } else { + ctype = CTypeAscii; + } + + // decode multi-byte + if(cremain) { + if(ctype != CTypeContinue) { + --rd_pos; + cremain = 0; // was expecting multi-byte; abort conversion + continue; // of this char and restart + } + dch <<= 6; + dch |= ch & 0x3F; + if(!--cremain) d.append(&dch, 1); + + // decode start char + } else { + switch(ctype) { + case CTypeContinue: + // not valid here + continue; + + case CTypeAscii: + dch = ch; + d.append(&dch, 1); + continue; + + case CType2: + cremain = 1; + dch = ch & 0x1F; + continue; + + case CType3: + cremain = 2; + dch = ch & 0x0F; + continue; + + case CType4: + cremain = 3; + dch = ch & 0x07; + continue; + + case CType5: + cremain = 4; + dch = ch & 0x03; + continue; + + case CType6: + cremain = 5; + dch = ch & 0x01; + continue; + } + } + } + + // done + return d; +} + +} + + + +std::wstring utf8ToUcs4(const std::string& utf8, bool force) +{ + // when conversion is forced, we need to use a more hardy algorithm + if(force) return utf8ToUcs4Forced(utf8); + + // compute size of std::string, skip empty std::strings + std::string::size_type len = utf8.length(), rd_pos, ch_start = 0; + if(!len) return std::wstring(); + + // skip BOM + std::string::size_type start = 0; + if(len >= 3 && utf8.substr(0, 3) == "\xEF\xBB\xBF") start = 3; + + // compute required size + std::wstring::size_type dlen = 0; + rd_pos = start; + uint8_t ch; + while(rd_pos < len) { + ch_start = rd_pos; + ch = utf8[rd_pos]; + if((ch & 0x80) == 0) ++rd_pos; + else if((ch & 0xE0) == 0xC0) rd_pos += 2; + else if((ch & 0xF0) == 0xE0) rd_pos += 3; + else if((ch & 0xF8) == 0xF0) rd_pos += 4; + else if((ch & 0xFC) == 0xF8) rd_pos += 5; + else if((ch & 0xFE) == 0xFC) rd_pos += 6; + else { + throw BadSourceChar(utf8.c_str() + ch_start, + utf8.c_str() + ch_start + 1, ch_start, L"UTF-8", + L"Invalid character in std::string."); + } + ++dlen; + } + if(rd_pos != len) { + throw BadSourceChar(utf8.c_str() + ch_start, + utf8.c_str() + rd_pos, ch_start, L"UTF-8", + L"Last character appears to be truncated."); + } + + // allocate memory + std::wstring dest(dlen, 0); + + // helper macros +#define CONSUME() utf8AuxConsume(ch, utf8, rd_pos, ch_start) +#define CONSUME_FIRST(mask) ch = (p & mask) +#define CHECK(min) do \ + if(ch < min) { \ + throw BadSourceChar(utf8.c_str() + ch_start, \ + utf8.c_str() + rd_pos, ch_start, L"UTF-8", \ + L"Character encoded in an overlong sequence."); \ + } \ +while(0) + + // loop through each character in turn + rd_pos = start; + for(std::wstring::size_type wr_pos = 0; wr_pos < dlen; ++wr_pos) { + wchar_t ch = 0; + ch_start = rd_pos; + uint8_t p = utf8[rd_pos++]; + + if(!(p & 0x80)) { + ch = p; + + } else if((p & 0xFE) == 0xFC) { + CONSUME_FIRST(0x01); + CONSUME(); + CONSUME(); + CONSUME(); + CONSUME(); + CONSUME(); + CHECK(0x4000000); + + } else if((p & 0xFC) == 0xF8) { + CONSUME_FIRST(0x03); + CONSUME(); + CONSUME(); + CONSUME(); + CONSUME(); + CHECK(0x200000); + + } else if((p & 0xF8) == 0xF0) { + CONSUME_FIRST(0x07); + CONSUME(); + CONSUME(); + CONSUME(); + CHECK(0x10000); + + } else if((p & 0xF0) == 0xE0) { + CONSUME_FIRST(0x0F); + CONSUME(); + CONSUME(); + CHECK(0x800); + + } else if((p & 0xE0) == 0xC0) { + CONSUME_FIRST(0x1F); + CONSUME(); + CHECK(0x80); + + } + dest[wr_pos] = ch; + } + +#undef CONSUME +#undef CONSUME_FIRST +#undef CHECK + + return dest; +} + + + +std::wstring asciiToUcs4(const std::string& ascii, bool force) +{ + int skipped = 0; + std::string::size_type size = ascii.size(), pos; + std::wstring d(size, 0); + + for(pos = 0; pos < size; ++pos) { + if(d[pos] & ~0x7F) { + ++skipped; + if(!force) throw BadSourceChar(ascii.c_str() + pos, + ascii.c_str() + pos + 1, pos, L"ASCII", + L"Invalid character (out of range)."); + continue; + } + d[pos] = ascii[pos]; + } + + d.resize(size - skipped); + + return d; +} + + + +std::wstring strToUcs4(const std::string& str) +{ + std::string::size_type size = str.size(), pos; + std::wstring d(size, 0); + for(pos = 0; pos < size; ++pos) d[pos] = str[pos]; + return d; +} + + + +} diff --git a/src/liblw-support/String/basics.h b/src/liblw-support/String/basics.h new file mode 100644 index 0000000..1af67ac --- /dev/null +++ b/src/liblw-support/String/basics.h @@ -0,0 +1,140 @@ +/* lw-support/src/lib/String/basics.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Return true if character is whitespace. + +\param ch The character to classify. +\retval true if character represents whitespace. +\retval false if character does not represent whitespace. + +This function is identical to the C library's isspace() function, except +that it handles Unicode code points. + +*/ +inline bool isWhitespace(wchar_t ch) __attribute__((const)); +bool isWhitespace(wchar_t ch) +{ + return ((ch >= 0x9 && ch <= 0xD) || + ch == 0x20 || + ch == 0x85 || + ch == 0xA0 || + ch == 0x1680 || + ch == 0x180E || + (ch >= 0x2000 && ch <= 0x200A) || + ch == 0x2028 || + ch == 0x2029 || + ch == 0x202F || + ch == 0x205F || + ch == 0x3000); +} + + + +/*! \brief Strip leading and trailing whitespace. + +\param str The UCS-4 encoded string to strip whitespace from. +\returns A copy of the string without trailing/leading whitespace. + +*/ +std::wstring stripWhitespace(const std::wstring& str); + + + +/*! \brief Convert a UCS-4 string to UTF-8. + +\param ucs4 The (UCS-4 encoded) string to convert. +\param force Set this to \a true if you want to force the conversion to + happen even in the face of invalid characters. +\returns A UTF-8 representation of \a ucs4. +\throws BadSourceChar if a character in the string is outside the + Unicode range (i.e. 0-0x7FFFFFFF). + +This function can be used to convert a UCS-4 string into a UTF-8 string. +It will throw an exception if any error is encountered, unless \a force +is \a true, in which case invalid characters will be skipped. It will +strip a BOM (byte order mark) from the beginning of a string. + +*/ +std::string ucs4ToUtf8(const std::wstring& ucs4, bool force = false); + + + +/*! \brief Convert a UCS-4 string to ASCII. + +\param ucs4 The (UCS-4 encoded) string to convert. +\param force Set this to \a true if you want to force the conversion to + happen even in the face of invalid characters. +\returns A UTF-8 representation of \a ucs4. +\throws BadSourceChar if a character in the string is outside the + ASCII range (i.e. 0-0x7F). + +This function can be used to convert a UCS-4 string into an ASCII +string. It will throw an exception if any error is encountered, unless \a force +is \a true, in which case invalid characters will be skipped. It will +strip a BOM (byte order mark) from the beginning of a string. + +*/ +std::string ucs4ToAscii(const std::wstring& ucs4, bool force = false); + + + +/*! \brief Convert a UTF-8 string to UCS-4. + +\param utf8 The (UTF-8 encoded) string to convert. +\param force Set this to \a true if you want to force the conversion to + happen even in the face of invalid characters. +\returns A UCS-4 representation of \a utf8. +\throws BadSourceChar if an invalid byte sequence is encountered in the + UTF-8 string. + +This function can be used to convert a UTF-8 string into a UCS-4 string. +It will throw an exception if any error is encountered, unless \a force +is \a true, in which case invalid characters will be skipped. It will +strip a BOM (byte order mark) from the beginning of a string. + +*/ +std::wstring utf8ToUcs4(const std::string& utf8, bool force = false); + + + +/*! \brief Convert an ASCII string to UCS-4. + +\param ascii The ASCII string to convert. +\param force Set this to \a true if you want to force the conversion to + happen even in the face of invalid characters. +\returns A UCS-4 representation of \a ascii. +\throws BadSourceChar if an invalid byte (outside the range 0-x7F) is + encountered in the ASCII string. + +This function can be used to convert an ASCII string into a UCS-4 +string. It will throw an exception if any error is encountered, unless +\a force is \a true, in which case invalid characters will be skipped. + +*/ +std::wstring asciiToUcs4(const std::string& ascii, bool force = false); + + + +/*! \brief Convert an 8-bit string to UCS-4. + +\param str The 8-bit string to convert. +\returns A UCS-4 representation of \a str. + +This function is used to perform a direct conversion of an 8-bit string +into UCS-4. It doesn't take any Unicode issues into account. It always +succeeds. This function can be used if the resultant string will be +checked for validity elsewhere anyway. + +*/ +std::wstring strToUcs4(const std::string& str); + + + +} diff --git a/src/liblw-support/String/strToInt.cpp b/src/liblw-support/String/strToInt.cpp new file mode 100644 index 0000000..1ccd092 --- /dev/null +++ b/src/liblw-support/String/strToInt.cpp @@ -0,0 +1,530 @@ +/* lw-support/src/lib/String/strToInt.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +//BEGIN // Auxiliary conversion functions ////////////////////////////// +namespace { + +uint64_t strToIntDec(const std::wstring& str, + std::wstring::size_type pos, std::wstring::size_type strLen) +{ + uint64_t value = 0; + for(; pos < strLen; ++pos) { + int ch = str[pos]; + if(ch < '0' || ch > '9') + throw ParseError(L"Not a decimal number.", str, pos); + if(value > 1844674407370955161LL) throw NumericOverflow(); + value *= 10; + if(~value < (uint64_t)(ch - '0')) throw NumericOverflow(); + value += ch - '0'; + } + return value; +} + + + +uint64_t strToIntHex(const std::wstring& str, + std::wstring::size_type pos, std::wstring::size_type strLen) +{ + uint64_t value = 0; + int maxDigRemaining = 16; + for(; pos < strLen; ++pos) { + if(!maxDigRemaining--) throw NumericOverflow(); + value <<= 4; + + int ch = str[pos]; + switch(ch) { + case '0' ... '9': + value |= ch - '0'; + break; + + case 'a' ... 'f': + value |= ch - 'a' + 10; + break; + + case 'A' ... 'F': + value |= ch - 'A' + 10; + break; + + default: + throw ParseError(L"Not a decimal number.", str, pos); + } + } + return value; +} + +} +//END // Auxiliary conversion functions //////////////////////////////// + + + +int64_t strToInt64(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + bool negative = false; + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + negative = true; + ++pos; + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(negative && result == 0x8000000000000000LL) return -0x8000000000000000LL; + if(result & ~0x7FFFFFFFFFFFFFFFLL) throw NumericOverflow(); + + int64_t p = (int64_t)result; + if(negative) p *= -1; + return p; +} + + + +int32_t strToInt32(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + bool negative = false; + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + negative = true; + ++pos; + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(negative && result == 0x80000000) return -0x80000000; + if(result & ~0x7FFFFFFF) throw NumericOverflow(); + + int32_t p = (int32_t)result; + if(negative) p *= -1; + return p; +} + + + +int16_t strToInt16(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + bool negative = false; + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + negative = true; + ++pos; + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(negative && result == 0x8000) return -0x8000; + if(result & ~0x7FFF) throw NumericOverflow(); + + int16_t p = (int16_t)result; + if(negative) p *= -1; + return p; +} + + + +int8_t strToInt8(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + bool negative = false; + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + negative = true; + ++pos; + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(negative && result == 0x80) return -0x80; + if(result & ~0x7F) throw NumericOverflow(); + + int8_t p = (int8_t)result; + if(negative) p *= -1; + return p; +} + + + +uint64_t strToUInt64(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + std::wstring::size_type pos = 0; + + // check for sign + if(str[0] == '-') { + throw ParseError(L"Negative number found.", str, 0); + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + return strToIntHex(str, pos, strLen); + } + + if(pos == strLen) return 0; + return strToIntDec(str, pos, strLen); +} + + + +uint32_t strToUInt32(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + throw ParseError(L"Negative number found.", str, 0); + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(result & ~0xFFFFFFFF) throw NumericOverflow(); + + return uint32_t(result); +} + + + +uint16_t strToUInt16(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + throw ParseError(L"Negative number found.", str, 0); + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(result & ~0xFFFF) throw NumericOverflow(); + + return uint16_t(result); +} + + + +uint8_t strToUInt8(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + if(!strLen) throw ParseError(L"Empty string.", str, 0); + + std::wstring::size_type pos = 0; + uint64_t result; + + // check for sign + if(str[0] == '-') { + throw ParseError(L"Negative number found.", str, 0); + } else if(str[0] == '+') { + ++pos; + } + + if(pos == strLen) { + throw ParseError(L"String only contained sign character.", + str, 1); + } + + // check for hexadecimal + if(str[pos] == '0' && ++pos != strLen && + (str[pos] == 'x' || str[pos] == 'X')) + { + if(++pos == strLen) + throw ParseError(L"String only contained hex sequence.", str, pos); + result = strToIntHex(str, pos, strLen); + } else { + if(pos == strLen) return 0; + result = strToIntDec(str, pos, strLen); + } + + // check for overflow + if(result & ~0xFF) throw NumericOverflow(); + + return uint8_t(result); +} + + + +bool strToBool(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + + // get string length + std::wstring::size_type strLen = str.length(); + switch(strLen) { + case 0: + throw ParseError(L"Empty string.", str, 0); + + case 1: // "0" or "1" + // check for numeric values + if(str[0] == '0') return false; + if(str[0] == '1') return true; + throw ParseError(L"Unrecognised boolean value.", str, 0); + + case 4: // "true" + if(tolower(str[0]) != 't' || + tolower(str[1]) != 'r' || + tolower(str[2]) != 'u' || + tolower(str[3]) != 'e') + throw ParseError(L"Unrecognised boolean value.", str, 0); + return true; + + case 5: // "false" + if(tolower(str[0]) != 'f' || + tolower(str[1]) != 'a' || + tolower(str[2]) != 'l' || + tolower(str[3]) != 's' || + tolower(str[4]) != 'e') + throw ParseError(L"Unrecognised boolean value.", str, 0); + return false; + } + + throw ParseError(L"Unrecognised boolean value.", str, 0); +} + + + +long double strToLongDouble(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + if(str.empty()) throw ParseError(L"Empty string.", str, 0); + + // perform conversion + errno = 0; + wchar_t* tailPtr = 0; + long double d = wcstold(str.c_str(), &tailPtr); + + // check for errors + if(*tailPtr) { + throw ParseError(L"Not a valid floating point number.", str, + tailPtr - str.c_str()); + } + if(errno) throw NumericOverflow(); + + return d; +} + + + +double strToDouble(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + if(str.empty()) throw ParseError(L"Empty string.", str, 0); + + // perform conversion + errno = 0; + wchar_t* tailPtr = 0; + double d = wcstod(str.c_str(), &tailPtr); + + // check for errors + if(*tailPtr) { + throw ParseError(L"Not a valid floating point number.", str, + tailPtr - str.c_str()); + } + if(errno) throw NumericOverflow(); + + return d; +} + + + +float strToFloat(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + if(str.empty()) throw ParseError(L"Empty string.", str, 0); + + // perform conversion + errno = 0; + wchar_t* tailPtr = 0; + float d = wcstof(str.c_str(), &tailPtr); + + // check for errors + if(*tailPtr) { + throw ParseError(L"Not a valid floating point number.", str, + tailPtr - str.c_str()); + } + if(errno) throw NumericOverflow(); + + return d; +} + + + +} diff --git a/src/liblw-support/String/strToInt.h b/src/liblw-support/String/strToInt.h new file mode 100644 index 0000000..f7b31b3 --- /dev/null +++ b/src/liblw-support/String/strToInt.h @@ -0,0 +1,229 @@ +/* lw-support/src/lib/String/strToInt.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + -0xhhh, 0xhhh or +0xhhh), case insensitive. + +*/ +int64_t strToInt64(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + -0xhhh, 0xhhh or +0xhhh), case insensitive. + +*/ +int32_t strToInt32(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + -0xhhh, 0xhhh or +0xhhh), case insensitive. + +*/ +int16_t strToInt16(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + -0xhhh, 0xhhh or +0xhhh), case insensitive. + +*/ +int8_t strToInt8(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + 0xhhh or +0xhhh), case insensitive. + +*/ +uint64_t strToUInt64(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + 0xhhh or +0xhhh), case insensitive. + +*/ +uint32_t strToUInt32(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer, or + if the integer is negative. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + 0xhhh or +0xhhh), case insensitive. + +*/ +uint16_t strToUInt16(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to an integer. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid integer, or + if the integer is negative. +\throws NumericOverflow if the value represented in the string does not + fit into an integer. +\returns The value represented in the string. + +This function parses a string and returns it as an integer. Any trailing +or leading whitespace is ignored. It understands two types of number: + - a decimal, with optional leading sign (i.e. ddd or +ddd). + - a hexadecimal, with optional leading sign, in C notation (i.e. + 0xhhh or +0xhhh), case insensitive. + +*/ +uint8_t strToUInt8(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to a boolean. + +\param str The string to parse. +\throws ParseError if the string does not represent a valid boolean. +\returns The value represented in the string. + +This function parses a string and returns it as a boolean. Any trailing +or leading whitespace is ignored. It understands two notations: + - the strings "0" or "false" (case insensitive), for a false value. + - the strings "1" or "true" (case insensitive), for a true value. + +*/ +bool strToBool(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to a number. + +\param str The string to parse +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into a double-precision floating point number. +\returns The value represented in the string. + +This function parses a string and returns it as a quad-precision +floating point number. Any trailing or leading whitespace is ignored. + +*/ +long double strToLongDouble(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to a number. + +\param str The string to parse +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into a double-precision floating point number. +\returns The value represented in the string. + +This function parses a string and returns it as a double-precision +floating point number. Any trailing or leading whitespace is ignored. + +*/ +double strToDouble(const std::wstring& str); + + + +/*! \brief Parse a string, converting it to a number. + +\param str The string to parse +\throws ParseError if the string does not represent a valid integer. +\throws NumericOverflow if the value represented in the string does not + fit into a double-precision floating point number. +\returns The value represented in the string. + +This function parses a string and returns it as a single-precision +floating point number. Any trailing or leading whitespace is ignored. + +*/ +float strToFloat(const std::wstring& str); + + + +} diff --git a/src/liblw-support/Thread/CompletionNotifier.cpp b/src/liblw-support/Thread/CompletionNotifier.cpp new file mode 100644 index 0000000..f504333 --- /dev/null +++ b/src/liblw-support/Thread/CompletionNotifier.cpp @@ -0,0 +1,52 @@ +/* lw-support/src/lib/Thread/CompletionNotifier.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +CompletionNotifier::~CompletionNotifier() +{ + pthread_cond_broadcast(&cond); + while(true) { + pthread_mutex_lock(&mut); + if(!pthread_cond_destroy(&cond)) break; + pthread_mutex_unlock(&mut); + } + pthread_mutex_unlock(&mut); + pthread_mutex_destroy(&mut); +} + + + +void CompletionNotifier::wait(int maxTime) +{ + if(maxTime < 0) { + wait(); + return; + } + + struct timespec ts; + ts.tv_sec = maxTime / 1000; + ts.tv_nsec = (maxTime % 1000) * 1000000; + pthread_mutex_lock(&mut); + + while(!done) { + switch(pthread_cond_timedwait(&cond, &mut, &ts)) { + case ETIMEDOUT: { + pthread_mutex_unlock(&mut); + throw Timeout(); + } + + case 0: break; + } + } + pthread_mutex_unlock(&mut); +} + + + +} diff --git a/src/liblw-support/Thread/CompletionNotifier.h b/src/liblw-support/Thread/CompletionNotifier.h new file mode 100644 index 0000000..09a4d34 --- /dev/null +++ b/src/liblw-support/Thread/CompletionNotifier.h @@ -0,0 +1,99 @@ +/* lw-support/src/lib/Thread/CompletionNotifier.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Completion object. + +A completion object is a synchronisation device used in the following +manner: + - interested parties register for a notification, + - the task is begun, + - on completion, all registered parties are notified. + +*/ +class CompletionNotifier { +private: + pthread_mutex_t mut; + pthread_cond_t cond; + bool done; + +public: + /// Constructor. + CompletionNotifier() + : done(false) + { + pthread_mutex_init(&mut, 0); + pthread_cond_init(&cond, 0); + } + + /// Destructor. + ~CompletionNotifier(); + + + + /*! \brief Signal completion. + + This will wake all threads currently waiting on the completion + condition, and will record the object as completed. Any future calls + to the wait() function will automatically succeed. + + */ + inline void signal() + { + pthread_mutex_lock(&mut); + done = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mut); + } + + + + /*! \brief Wait for completion. + + Threads calling this function will block until the object is marked + as complete. + + */ + inline void wait() + { + if(done) return; // avoid lock contention if possible + + pthread_mutex_lock(&mut); + if(done) { // in case variable was changed before lock acquired + pthread_mutex_unlock(&mut); + return; + } + + pthread_cond_wait(&cond, &mut); + pthread_mutex_unlock(&mut); + return; + } + + + + /// Thrown if a timeout occurs. + class Timeout : public ProgramException { }; + + /*! \brief Wait for completion, with a timeout. + + \param maxTime The maximum amount of time to wait, in milliseconds. + \throws CompletionNotifier::Timeout if a timeout occurs. + + Threads calling this function will block until the object is marked + as complete. If you pass an negative value for \a maxTime, then it + will wait indefinitely. A zero value will cause it to return + immediately. + + */ + void wait(int maxTime); +}; + + + +} diff --git a/src/liblw-support/Thread/Mutex.cpp b/src/liblw-support/Thread/Mutex.cpp new file mode 100644 index 0000000..33f05ca --- /dev/null +++ b/src/liblw-support/Thread/Mutex.cpp @@ -0,0 +1,37 @@ +/* lw-support/src/lib/Thread/Mutex.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +Mutex::Mutex(bool recursive) +{ +// note the function calls here always succeed + // tell pthreads we want a "normal" (simple) mutex + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + if(recursive) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + else pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + + // initialise the mutex + pthread_mutex_init(&mut, &attr); + + // clean up + pthread_mutexattr_destroy(&attr); +} + + + +Mutex::~Mutex() +{ + // destroy the mutex + pthread_mutex_destroy(&mut); +} + + + +} diff --git a/src/liblw-support/Thread/Mutex.h b/src/liblw-support/Thread/Mutex.h new file mode 100644 index 0000000..21a9473 --- /dev/null +++ b/src/liblw-support/Thread/Mutex.h @@ -0,0 +1,67 @@ +/* lw-support/src/lib/Thread/Mutex.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Mutex. + +This object may be one of two types: fast or recursive. A fast mutex +cannot be locked twice in the same thread. Attempting to do so will +result in undefined behaviour (most likely deadlock). + +However, a recursive mutex can be locked twice in the same thread. It +must be unlocked an equal number of times. It will only become available +to other threads when each lock() call has been matched by an unlock() +call. + +\warning Behaviour is undefined if a locked mutex is destroyed. + Behaviour is undefined if a thread which does not own the lock + tries to unlock the mutex. + +*/ +class Mutex { +private: + pthread_mutex_t mut; + +public: + /*! \brief Constructor. + + \param recursive Indicates whether the mutex should be fast or + recursive. + + Creates the mutex object. Note that, once constructed, the type of + a mutex cannot be changed. Mutexes are always constructed unlocked. + + */ + Mutex(bool recursive = false); + + + + /// Destructor. + ~Mutex(); + + + + /// Lock the mutex. + inline void lock() + { + pthread_mutex_lock(&mut); + } + + + + /// Unlock the mutex. + inline void unlock() + { + pthread_mutex_unlock(&mut); + } +}; + + + +} diff --git a/src/liblw-support/Thread/MutexPadlock.h b/src/liblw-support/Thread/MutexPadlock.h new file mode 100644 index 0000000..e6e99db --- /dev/null +++ b/src/liblw-support/Thread/MutexPadlock.h @@ -0,0 +1,184 @@ +/* lw-support/src/lib/Thread/MutexPadlock.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Mutex utility class for safe locking/unlocking. + +A Mutex \e padlock will unlock its associated Mutex when it is destroyed. +This means that you can use it to lock a Mutex and then safely unlock it +regardless of how the function exit occurs. A padlock is constructed +unlocked, and you may call the lock and unlock functions at will. + +\warning Behaviour is undefined if you attempt to unlock an + already-unlocked Mutex. This type of padlock is not recursive; + calling lock twice in succession will probably result in + deadlock. But see MutexPadlockRecursive. + +\sa MutexHolder + +*/ +class MutexPadlock { +private: + Mutex& mut; + bool locked; + +public: + /*! \brief Constructor. + + \param m The Mutex to operate on. + + Constructs the padlock object, but does not lock the Mutex. + + */ + MutexPadlock(Mutex& m) + : mut(m), locked(false) + { } + + + + /*! \brief Destructor (unlocks padlock, if locked). + + If the padlock is locked, the destructor will unlock it. However, if + it is not locked, then nothing will happen. + + */ + ~MutexPadlock() + { + if(locked) mut.unlock(); + } + + + + /// Lock the Mutex. + inline void lock() + { + mut.lock(); + locked = true; + } + + + + /// Unlock the Mutex. + inline void unlock() + { + mut.unlock(); + locked = false; + } +}; + + + +/*! \brief Mutex utility class for safe, recursive locking/unlocking. + +A Mutex \e padlock will unlock its associated Mutex when it is destroyed. +This means that you can use it to lock a Mutex and then safely unlock it +regardless of how the function exit occurs. A padlock is constructed +unlocked, and you may call the lock and unlock functions at will. + +Note that this padlock allows recursive calls. It even works with fast +Mutexes, because it keeps count internally itself. + +\warning Behaviour is undefined if you attempt to unlock an + already-unlocked Mutex. + +\sa MutexHolder + +*/ +class MutexPadlockRecursive { +private: + Mutex& mut; + int count; + +public: + /*! \brief Constructor. + + \param m The Mutex to operate on. + + Constructs the padlock object, but does not lock the Mutex. + + */ + MutexPadlockRecursive(Mutex& m) + : mut(m), count(0) + { } + + + + /*! \brief Destructor (unlocks padlock, if locked). + + If the padlock is locked, the destructor will unlock it. However, if + it is not locked, then nothing will happen. + + */ + ~MutexPadlockRecursive() + { + if(count) mut.unlock(); + } + + + + /// Lock the Mutex. + inline void lock() + { + if(!count++) mut.lock(); + } + + + + /// Unlock the Mutex. + inline void unlock() + { + if(!--count) mut.unlock(); + } +}; + + + +/*! \brief Mutex utility class for safe locking/unlocking. + +On construction, this object locks its associated Mutex. On destruction, +it releases the lock. In essence, it is a less flexible (but less error +prone) Mutex padlock. + +\sa MutexPadlock + +*/ +class MutexHolder { +private: + Mutex& mut; + +public: + /*! \brief Constructor. + + \param m The Mutex to lock. + + Locks the Mutex. + + */ + MutexHolder(Mutex& m) + : mut(m) + { + mut.lock(); + } + + + + /*! \brief Destructor. + + Unlocks the Mutex. + + */ + ~MutexHolder() + { + mut.unlock(); + } +}; + + + +} diff --git a/src/liblw-support/Thread/Semaphore.cpp b/src/liblw-support/Thread/Semaphore.cpp new file mode 100644 index 0000000..129bf26 --- /dev/null +++ b/src/liblw-support/Thread/Semaphore.cpp @@ -0,0 +1,82 @@ +/* lw-support/src/lib/Thread/Semaphore.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +Semaphore::Semaphore(unsigned int init) + : val(init), waiting(0), err(false) +{ + pthread_mutex_init(&mut, 0); + pthread_cond_init(&cond, 0); +} + + + +Semaphore::~Semaphore() +{ + signalError(); + while(true) { + pthread_mutex_lock(&mut); + if(!waiting) break; + pthread_mutex_unlock(&mut); + pthread_yield(); + } + pthread_mutex_unlock(&mut); + + pthread_mutex_destroy(&mut); +} + + + +void Semaphore::wait(int maxTime) +{ + if(maxTime < 0) { + wait(); + return; + } + + // get current time + struct timeval tv; + gettimeofday(&tv, 0); + + // compute absolute time + struct timespec ts; + ts.tv_sec = tv.tv_sec + (maxTime / 1000); + ts.tv_nsec = (tv.tv_usec * 1000) + (maxTime % 1000) * tenExp6; + if(ts.tv_nsec > tenExp9) { + ++ts.tv_sec; + ts.tv_nsec -= tenExp9; + } + + pthread_mutex_lock(&mut); + ++waiting; + + while(!val && !err) { + switch(pthread_cond_timedwait(&cond, &mut, &ts)) { + case ETIMEDOUT: { + --waiting; + pthread_mutex_unlock(&mut); + throw Timeout(); + } + + case 0: break; + } + } + if(err) { + --waiting; + pthread_mutex_unlock(&mut); + throw Error(); + } + --val; + --waiting; + pthread_mutex_unlock(&mut); +} + + + +} diff --git a/src/liblw-support/Thread/Semaphore.h b/src/liblw-support/Thread/Semaphore.h new file mode 100644 index 0000000..143f14d --- /dev/null +++ b/src/liblw-support/Thread/Semaphore.h @@ -0,0 +1,148 @@ +/* lw-support/src/lib/Thread/Semaphore.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Semaphore object. + +Supports the usual signal and wait operations, as well as manipulation +of the value. In addition, there is an error handling mechanism which +can be used to signal every thread waiting on the semaphore that it has +become invalid. + +*/ +class Semaphore { +private: + pthread_mutex_t mut; + pthread_cond_t cond; + int val, waiting; + bool err; + +public: + /*! \brief Constructor. + + \param init The initial value of the semaphore. + + Creates the semaphore object, and sets its initial value to + \a init. + + */ + Semaphore(unsigned int init = 0); + + + + /*! \brief Destructor. + + Destroys the semaphore. Any threads currently waiting on the + semaphore receive an error signal, so it is safe to delete a + semaphore object while there are threads waiting on it. + + \warning If a semaphore is deleted in the time between a thread + deciding to call the wait() function and the time the + actual system call is executed, behaviour is undefined. + + */ + ~Semaphore(); + + + + /*! \brief Semaphore error signal. + + Thrown if a thread is waiting on the semaphore when an error + condition is signalled. This also occurs when the semaphore is + destroyed but some threads are waiting on it. + + */ + class Error : public ProgramException { }; + + + + /// Thrown if a semaphore wait operation times out. + class Timeout : public ProgramException { }; + + + + /// Increment / post / signal the semaphore. + inline void signal() + { + pthread_mutex_lock(&mut); + ++val; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mut); + } + + + + /*! \brief Decrement / wait on the semaphore. + + \throws Error if an error is signalled or the semaphore destroyed. + + Decrements the value of the semaphore. If the value is zero when + this function is called, the calling thread is instead put to sleep, + to be awoken when an error is signalled or the semaphore is posted. + + */ + inline void wait() + { + pthread_mutex_lock(&mut); + ++waiting; + while(!val && !err) pthread_cond_wait(&cond, &mut); + if(err) { + --waiting; + pthread_mutex_unlock(&mut); + throw Error(); + } + --val; + --waiting; + pthread_mutex_unlock(&mut); + } + + + + /*! \brief Decrement / wait on the semaphore, with a timeout. + + \param maxTime The maximum length of time to wait before timing + out, in milliseconds. + \throws Error if an error is signalled or the semaphore destroyed. + \throws Timeout if the timeout is reached before the thread can + decrement the semaphore. + + Decrements the value of the semaphore. If the value is zero when + this function is called, the calling thread is instead put to sleep, + to be awoken when an error is signalled or the semaphore is posted. + This version includes a timeout. If you pass a negative \a maxTime, + it will wait indefinitely; a zero value will cause the semaphore to + be decremented immediately or an error will be thrown. + + */ + void wait(int maxTime); + + + + /// Signal error. + inline void signalError() + { + pthread_mutex_lock(&mut); + err = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mut); + } + + + + /// Retrieve the semaphore's current value. + inline int value() + { + if(err) throw Error(); + return val; + } +}; + + + +} diff --git a/src/liblw-support/Thread/Thread.cpp b/src/liblw-support/Thread/Thread.cpp new file mode 100644 index 0000000..4750b52 --- /dev/null +++ b/src/liblw-support/Thread/Thread.cpp @@ -0,0 +1,108 @@ +/* lw-support/src/lib/Thread/Thread.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +const size_t Thread::minStackSize = PTHREAD_STACK_MIN; + + + +void* Thread::taskWrapper(void* v) +{ + Thread* self = (Thread*)v; + + try { + self->semStart.wait(); + self->run(); + } + catch(...) { + self->exception = true; + } + self->done = true; + self->sem.signalError(); + + return 0; +} + + + +Thread::Thread(size_t stackSize) + : done(false), exception(false) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if(stackSize) pthread_attr_setstacksize(&attr, std::min(stackSize, minStackSize)); + + int err = pthread_create(&thr, &attr, taskWrapper, this); + if(err) throw SystemError(err); + + pthread_attr_destroy(&attr); +} + + + +Thread::~Thread() +{ + wait(-1); +} + + + +void Thread::start() +{ + semStart.signal(); +} + + + +void Thread::signalQuit() +{ + semStart.signalError(); +} + + + +void Thread::wait(int timeout) const +{ + try { + sem.wait(timeout); + } + catch(Semaphore::Error&) { } +} + + + +void Thread::sleep(const ElapsedTime& time) +{ + struct timespec go, remain; + + lldiv_t div = lldiv(time.to_ns(), tenExp9); + go.tv_sec = div.quot; + go.tv_nsec = div.rem; + while(go.tv_sec || go.tv_nsec) { + if(!nanosleep(&go, &remain)) break; + if(errno == EINTR) go = remain; + else throw lw::SystemError(); + } +} + + + +void Thread::dbg_backtrace() +{ + void* array[200]; + size_t size; + + size = ::backtrace(array, sizeof(array) / sizeof(void*)); + ::backtrace_symbols_fd(array, size, 2); /* 2 = stderr */ +} + + + +} diff --git a/src/liblw-support/Thread/Thread.h b/src/liblw-support/Thread/Thread.h new file mode 100644 index 0000000..f497e03 --- /dev/null +++ b/src/liblw-support/Thread/Thread.h @@ -0,0 +1,153 @@ +/* lw-support/src/lib/Thread/Thread.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Thread object. + +This class provides multithreading facilities. You must override its +run() function. The thread is begun after the first call to the start() +function. The thread's execution finishes once run() returns. + +The Thread object also contains some static members, yield() and +sleep(), that affect the \em calling thread. + +*/ +class Thread : public Uncopyable { +private: + pthread_t thr; + mutable Semaphore sem; + bool done, exception; // to store the system state + + Semaphore semStart; + static void* taskWrapper(void*); + + + +protected: + /*! \brief Thread function (override this). + + This method must be overridden to provide the thread's behaviour. It + may either exit normally or by throwing an exception; this can be + determined through the isDone() and isException() functions. + + */ + virtual void run() = 0; + + + +public: + /*! \brief Constructor. + + \param stackSize The size of the stack, in bytes. Must be at least + lw::Thread::minStackSize, or 0 for the system default. + \throws SystemError if a system error occurs. + + Constructing a new thread object starts up a new thread. The thread + blocks on a semaphore until you call start() for the first time. + This is to avoid the situation where the thread calls a method or + refers to a variable in the derived class which may not have been + fully instantiated yet. + + */ + Thread(size_t stackSize = 0); + + + + /*! \brief Destructor. + + When deleted, the thread object's destructor will free up any thread + resources. It will wait for the thread to quit first. + + \warning The derived class's destructor will be called first, even + while the thread is still running. You should probably call + wait() in your derived class's destructor. + + */ + virtual ~Thread(); + + + + /// Start thread running. + void start(); + + + + /// Minimum stack size, in bytes. + static const size_t minStackSize; + + + + /// Reports whether the thread has finished executing. + bool isDone() const + { + return done; + } + + /// Reports whether the thread finished because of an exception. + bool isException() const + { + return exception; + } + + + + /*! \brief Wait for the thread to finish, with a timeout. + + \param timeout The maximum length of time to wait before timing + out, in milliseconds (negative means forever). + \throws Semaphore::Timeout if the timeout is reached before the + thread has finished. + + */ + void wait(int timeout) const; + + + + /*! \brief Signal that the thread needs to quit. + + This function, by default, does nothing. However, if you have some + specific way of signalling to your thread function that it should + quit, then you can use that method here. + + */ + virtual void signalQuit(); + + + + /*! \brief Yields CPU to another thread. + + Briefly yields the CPU to another thread, but does not put the + process to sleep. + + */ + static inline void yield() + { + pthread_yield(); + } + + + + /// Causes the current thread to sleep for a length of time. + static void sleep(const ElapsedTime& time); + + + + /*! \brief Prints a stack trace. + + This is a debugging function that prints a stack trace onto stderr. It assumes that fd 2 is + stderr. It will also be confused by compiler optimisations. See the `backtrace()' libc function + for details. + + */ + static void dbg_backtrace(); +}; + + + +} diff --git a/src/liblw-support/Time/Date.cpp b/src/liblw-support/Time/Date.cpp new file mode 100644 index 0000000..0fa36bf --- /dev/null +++ b/src/liblw-support/Time/Date.cpp @@ -0,0 +1,817 @@ +/* lw-support/src/lib/Time/Date.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + + +void Date::fixupDate() +{ + while(ord < 1) { + --year; + ord += isLeapYear(year) ? 366 : 365; + } + +while(ord > (isLeapYear(year) ? 366 : 365)) { +ord -= isLeapYear(year) ? 366 : 365; + ++year; +} +} + + + +/* This comes from the algorithm given on Wikipedia at +http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week */ +namespace { + +inline int weekDayJan0(int year) +{ + div_t cent = div(year, 100); + div_t leap = div(cent.quot, 4); + + int centOffset = (3 - leap.rem) * 2; + int yrOffset = cent.rem + ((cent.rem + 3) >> 2) - 1; + + return (centOffset + yrOffset) % 7; +} + +} + + + +Date::Date(int year, int month, int day) + : year(year) +{ + if(month < 1 || month > 12 || day < 1 || day > 31 || + !(ord = (isLeapYear(year) ? leapMDtoOrd : normalMDtoOrd)[month - 1][day - 1]) ) + { + throw BadDate(); + } +} + + + +Date::Date(int year, int ordinalDay) + : year(year), ord(ordinalDay) +{ + if(ord < 1 || ord > 366 || (ord == 366 && !isLeapYear(year))) { + throw BadDate(); + } +} + + + +Date Date::now() +{ + time_t t = time(0); + struct tm tt; + localtime_r(&t, &tt); + return Date(tt.tm_year + 1900, tt.tm_mon + 1, tt.tm_mday); +} + + + +Date Date::fromWeek(int year, int week, int weekDay) +{ + // sanity checks + if(weekDay < 1 || weekDay > 7 || week < 1 || week > 53) + throw BadDate(); + if(week == 53 && (weekDayJan0(year) != 3 && + (!isLeapYear(year) || weekDayJan0(year) != 2))) + throw BadDate(); + + // find date of Monday, week 1 + Date d(year, 1); + switch(weekDayJan0(year)) { + case 0: // Sunday + break; + + case 1: // Monday + d -= 1; + break; + + case 2: + d -= 2; + break; + + case 3: + d -= 3; + break; + + case 4: + d += 3; + break; + + case 5: + d += 2; + break; + + case 6: + d += 1; + break; + } + + // now move to correct week and day + d += (week - 1) * 7 + (weekDay - 1); + + return d; +} + + + +int Date::getMonth() const +{ +return (isLeapYear(year) ? leapOrdtoMD : normalOrdtoMD)[ord][0]; +} + + + +int Date::getDay() const +{ +return (isLeapYear(year) ? leapOrdtoMD : normalOrdtoMD)[ord][1]; +} + + + +int Date::getWeekDay() const +{ + int wd = (weekDayJan0(year) + ord) % 7; +return wd ?: 7; +} + + + +int Date::getWeek() const +{ + if(isLeapYear(year)) return leapOrdtoWeek[weekDayJan0(year)][ord]; + else return normalOrdtoWeek[weekDayJan0(year)][ord]; +} + + + +Date& Date::operator+=(int numDays) +{ + ord += numDays; + fixupDate(); + return *this; +} + + + +Date& Date::operator-=(int numDays) +{ + ord -= numDays; + fixupDate(); + return *this; +} + + + +Date& Date::operator++() +{ + ++ord; + fixupDate(); + return *this; +} + + + +Date Date::operator++(int) +{ + Date copy(*this); + ++ord; + fixupDate(); + return copy; +} + + + +Date& Date::operator--() +{ + --ord; + fixupDate(); + return *this; +} + + + +Date Date::operator--(int) +{ + Date copy(*this); + --ord; + fixupDate(); + return copy; +} + + + +int Date::operator-(const Date& sub) const +{ + int diff = ord - sub.ord; + + for(int xYear = year; xYear < sub.year; ++xYear) { + diff -= isLeapYear(xYear) ? 366 : 365; + } + for(int yYear = year; yYear > sub.year; --yYear) { + diff += isLeapYear(yYear - 1) ? 366 : 365; + } + + return diff; +} + + + +/* A good ISO8601 description is at Wikipedia: +http://en.wikipedia.org/wiki/ISO_8601 */ + +std::wstring Date::toString(bool extended, ISO8601Format format, + ISO8601Precision precision) const +{ + // sanity check arguments + if(format == ISO8601FormatDetect) throw BadFormat(); + switch(precision) { + case ISO8601PrecisionFull: + case ISO8601PrecisionDay: + break; + + case ISO8601PrecisionMonth: + case ISO8601PrecisionYear: + if(format != ISO8601FormatCalendar) throw BadFormat(); + break; + + case ISO8601PrecisionWeek: + if(format != ISO8601FormatWeekDate) throw BadFormat(); + break; + + default: + throw BadFormat(); + } + + // set up output stream + std::wostringstream o; + o << std::setfill(L'0'); + + // deal with the weekdate format + if(format == ISO8601FormatWeekDate) { + int wk = getWeek(); + if(!wk) { + o << std::setw(4) << (year + 1); + if(extended) o << '-'; + o << "W01"; + + } else if(wk < 0) { + o << std::setw(4) << (year - 1); + if(extended) o << '-'; + o << 'W' << std::setw(2) << (-wk); + + } else { + o << std::setw(4) << year; + if(extended) o << '-'; + o << 'W' << std::setw(2) << wk; + + } + + if(precision == ISO8601PrecisionWeek) return o.str(); + if(extended) o << '-'; + o << getWeekDay(); + return o.str(); + } + + // output the year + o << std::setw(4) << year; + + // deal with the ordinal format + if(format == ISO8601FormatOrdinal) { + if(extended) o << L'-'; + o << std::setw(3) << getOrdinal(); + return o.str(); + } + + // output the month and (possibly) day + switch(precision) { + case ISO8601PrecisionFull: + case ISO8601PrecisionDay: + if(extended) o << L'-'; + o << std::setw(2) << getMonth(); + if(extended) o << L'-'; + o << std::setw(2) << getDay(); + break; + + case ISO8601PrecisionMonth: + if(extended) o << L'-'; + o << std::setw(2) << getMonth(); + break; + + default: + break; + } + + return o.str(); +} + + + +namespace { + +/* The ISO8601 date parsing subsystem uses a bunch of parallel state +machines. Each active state machine is fed a character; if it can't cope +with that character, the machine becomes inactive. On completion, each +machine is asked for a probability. The machine with highest probability +wins. */ +class ISO8601DateParser { +public: + // Processes a char, returning false if the char doesn't fit the + // parser. + virtual bool input(int ch) = 0; + + // Probability system used to determine which state machine should + // 'win' if several complete + enum Probability { + Impossible, + Unlikely, + Likely, + Definite + }; + virtual Probability getProbability() = 0; + + // Return a date object. + virtual Date getDate(const std::wstring& str) = 0; + + virtual ~ISO8601DateParser() { } +}; + +class ISO8601DateParser_CalendarBasic : public ISO8601DateParser { +private: + int numDigits; + +public: + ISO8601DateParser_CalendarBasic() + : numDigits(0) + { } + + virtual bool input(int ch) + { + switch(ch) { + case '0' ... '9': + ++numDigits; + return true; + + case '-': + if(!numDigits) return true; + } + return false; + } + + virtual Probability getProbability() + { + return (numDigits < 8) ? Impossible : Likely; + } + + virtual Date getDate(const std::wstring& str) + { + int strLen = str.length(); + return Date(strToInt32(str.substr(0, strLen - 4)), + strToInt32(str.substr(strLen - 4, 2)), + strToInt32(str.substr(strLen - 2, 2))); + } + + virtual ~ISO8601DateParser_CalendarBasic() { } +}; + +class ISO8601DateParser_CalendarExtended : public ISO8601DateParser { +private: + int numDigits; + enum { + stateNothing, + stateYear, + stateMonth, + stateDay + }state; + int year, month, day; + bool yearNegative; + +public: + ISO8601DateParser_CalendarExtended() + : numDigits(0), state(stateNothing), + year(0), month(0), day(0), yearNegative(false) + { } + + virtual bool input(int ch) + { + switch(state) { + case stateNothing: + state = stateYear; + switch(ch) { + case '-': + yearNegative = true; + return true; + + case '0' ... '9': + break; + + default: + return false; + } + // fall through + + case stateYear: + if(ch == '-' && numDigits >= 4) { + state = stateMonth; + numDigits = 0; + return true; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + year *= 10; + year += ch - '0'; + return true; + + case stateMonth: + if(numDigits == 2) { + state = stateDay; + numDigits = 0; + return ch == '-'; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + month *= 10; + month += ch - '0'; + return true; + + case stateDay: + if(numDigits == 2) return false; + if(ch < '0' || ch > '9') return false; + ++numDigits; + day *= 10; + day += ch - '0'; + return true; + } + return false; + } + + virtual Probability getProbability() + { + return (state == stateDay && numDigits == 2) ? + Definite : Impossible; + } + + virtual Date getDate(const std::wstring&) + { + return Date(year, month, day); + } + + virtual ~ISO8601DateParser_CalendarExtended() { } +}; + +class ISO8601DateParser_OrdinalBasic : public ISO8601DateParser { +private: + int numDigits; + +public: + ISO8601DateParser_OrdinalBasic() + : numDigits(0) + { } + + virtual bool input(int ch) + { + switch(ch) { + case '0' ... '9': + ++numDigits; + return true; + + case '-': + if(!numDigits) return true; + } + return false; + } + + virtual Probability getProbability() + { + if(numDigits < 7) return Impossible; + return (numDigits == 7) ? Likely : Unlikely; + } + + virtual Date getDate(const std::wstring& str) + { + int strLen = str.length(); + return Date(strToInt32(str.substr(0, strLen - 3)), + strToInt32(str.substr(strLen - 3, 3))); + } + + virtual ~ISO8601DateParser_OrdinalBasic() { } +}; + +class ISO8601DateParser_OrdinalExtended : public ISO8601DateParser { +private: + int numDigits; + enum { + stateNothing, + stateYear, + stateDay + }state; + int year, day; + bool yearNegative; + +public: + ISO8601DateParser_OrdinalExtended() + : numDigits(0), state(stateNothing), + year(0), day(0), yearNegative(false) + { } + + virtual bool input(int ch) + { + switch(state) { + case stateNothing: + state = stateYear; + switch(ch) { + case '-': + yearNegative = true; + return true; + + case '0' ... '9': + break; + + default: + return false; + } + // fall through + + case stateYear: + if(ch == '-' && numDigits >= 4) { + state = stateDay; + numDigits = 0; + return true; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + year *= 10; + year += ch - '0'; + return true; + + case stateDay: + if(numDigits == 3) return false; + if(ch < '0' || ch > '9') return false; + ++numDigits; + day *= 10; + day += ch - '0'; + return true; + } + return false; + } + + virtual Probability getProbability() + { + return (state == stateDay && numDigits == 3) ? + Definite : Impossible; + } + + virtual Date getDate(const std::wstring&) + { + return Date(year, day); + } + + virtual ~ISO8601DateParser_OrdinalExtended() { } +}; + +class ISO8601DateParser_WeekDateBasic : public ISO8601DateParser { +private: + int numDigits; + enum { + stateNothing, + stateYear, + stateWeeks + }state; + +public: + ISO8601DateParser_WeekDateBasic() + : numDigits(0), state(stateNothing) + { } + + virtual bool input(int ch) + { + switch(state) { + case stateNothing: + state = stateYear; + switch(ch) { + case '-': + return true; + + case '0' ... '9': + break; + + default: + return false; + } + // fall through + + case stateYear: + if(ch == 'W' && numDigits >= 4) { + state = stateWeeks; + numDigits = 0; + return true; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + return true; + + case stateWeeks: + if(numDigits++ == 3) return false; + if(ch < '0' || ch > '9') return false; + return true; + } + return false; + } + + virtual Probability getProbability() + { + return (state == stateWeeks && numDigits == 3) ? + Definite : Impossible; + } + + virtual Date getDate(const std::wstring& str) + { + std::wstring::size_type strLen = str.length(); + return Date::fromWeek(strToInt32(str.substr(0, strLen - 4)), + strToInt32(str.substr(strLen - 3, 2)), + str[strLen - 1] - '0'); + } + + virtual ~ISO8601DateParser_WeekDateBasic() { } +}; + +class ISO8601DateParser_WeekDateExtended : public ISO8601DateParser { +private: + int numDigits; + enum { + stateNothing, + stateYear, + stateWeekDash, + stateWeek, + stateDay + }state; + +public: + ISO8601DateParser_WeekDateExtended() + : numDigits(0), state(stateNothing) + { } + + virtual bool input(int ch) + { + switch(state) { + case stateNothing: + state = stateYear; + switch(ch) { + case '-': + return true; + + case '0' ... '9': + break; + + default: + return false; + } + // fall through + + case stateYear: + if(ch == '-' && numDigits >= 4) { + state = stateWeekDash; + numDigits = 0; + return true; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + return true; + + case stateWeekDash: + state = stateWeek; + return ch == 'W'; + + case stateWeek: + if(numDigits == 2) { + state = stateDay; + numDigits = 0; + return ch == '-'; + } + if(ch < '0' || ch > '9') return false; + ++numDigits; + return true; + + case stateDay: + if(numDigits == 1) return false; + if(ch < '0' || ch > '9') return false; + ++numDigits; + return true; + } + return false; + } + + virtual Probability getProbability() + { + return (state == stateDay && numDigits == 1) ? + Definite : Impossible; + } + + virtual Date getDate(const std::wstring& str) + { + std::wstring::size_type strLen = str.length(); + return Date::fromWeek(strToInt32(str.substr(0, strLen - 6)), + strToInt32(str.substr(strLen - 4, 2)), + str[strLen - 1] - '0'); + } + + virtual ~ISO8601DateParser_WeekDateExtended() { } +}; + +} + +Date Date::fromString(const std::wstring& str, ISO8601Format format) +{ + // set up a std::list of participating state machines + ISO8601DateParser_CalendarBasic machineCalendarBasic; + ISO8601DateParser_CalendarExtended machineCalendarExtended; + ISO8601DateParser_OrdinalBasic machineOrdinalBasic; + ISO8601DateParser_OrdinalExtended machineOrdinalExtended; + ISO8601DateParser_WeekDateBasic machineWeekDateBasic; + ISO8601DateParser_WeekDateExtended machineWeekDateExtended; + + std::vector stateMachines; + switch(format) { + case ISO8601FormatDetect: + stateMachines.push_back(&machineCalendarBasic); + stateMachines.push_back(&machineCalendarExtended); + stateMachines.push_back(&machineOrdinalBasic); + stateMachines.push_back(&machineOrdinalExtended); + stateMachines.push_back(&machineWeekDateBasic); + stateMachines.push_back(&machineWeekDateExtended); + break; + + case ISO8601FormatCalendar: + stateMachines.push_back(&machineCalendarBasic); + stateMachines.push_back(&machineCalendarExtended); + break; + + case ISO8601FormatOrdinal: + stateMachines.push_back(&machineOrdinalBasic); + stateMachines.push_back(&machineOrdinalExtended); + break; + + case ISO8601FormatWeekDate: + stateMachines.push_back(&machineWeekDateBasic); + stateMachines.push_back(&machineWeekDateExtended); + break; + } + + // pass the std::string through all the state machines + int numMachines = stateMachines.size(); + std::wstring::size_type pos = 0, len = str.length(); + + for(pos = 0; pos < len; ++pos) { + int ch = str[pos]; + + for(int iter = 0; iter < numMachines; ++iter) { + if(!stateMachines[iter]->input(ch)) { + // this machine can't process the input any more + stateMachines.erase(stateMachines.begin() + iter); + --iter; + --numMachines; + } + } + } + + // choose the machine with the highest likelihood + ISO8601DateParser* bestMachine = 0; + ISO8601DateParser::Probability bestProbability = ISO8601DateParser::Impossible; + for(int iter = 0; iter < numMachines; ++iter) { + ISO8601DateParser::Probability p = + stateMachines[iter]->getProbability(); + if(p > bestProbability) { + bestProbability = p; + bestMachine = stateMachines[iter]; + } + } + + // return the date object or fail + if(bestMachine) return bestMachine->getDate(str); + + throw ParseError(L"Cannot interpet as ISO8601 date", str, 0); +} + + + +std::wostream& operator<<(std::wostream& ostr, const Date& date) +{ + ostr << date.toString(); + return ostr; +} + + + +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Time/Date.h b/src/liblw-support/Time/Date.h new file mode 100644 index 0000000..22dec35 --- /dev/null +++ b/src/liblw-support/Time/Date.h @@ -0,0 +1,288 @@ +/* lw-support/src/lib/Time/Date.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief A Gregorian date. + +This class holds a single date in the Gregorian calendar. It makes no +provision for dates in other formats (i.e. it uses the Proleptic +Gregorian Calendar). It also provides for conversion to/from ISO8601 +formats. It uses astronomical years. + +*/ +class Date { +private: + int year, ord; + void fixupDate(); // fix up after arithmetic + +public: + //BEGIN // Constructors etc. /////////////////////////////////////// + + /*! \brief Constructor (calendar date). + + \param year The astronimical year. + \param month Month of the year (1-12). + \param day Day of the month (1-31). + \throws BadDate if \a day or \a month are invalid. + + */ + Date(int year, int month, int day); + + /*! \brief Constructor (ordinal date). + + \param year The astronimical year. + \param ordinalDay Day of the year (1-366). + \throws BadDate if \a ordinalDay is invalid. + + */ + Date(int year, int ordinalDay); + + /*! \brief Make object from ISO8601 weekday form. + + \param year The week's year. + \param week The week number (1-53). + \param weekDay The week day (1-7). + \throws BadDate if \a week or \a weekDay are invalid. + + */ + static Date fromWeek(int year, int week, int weekDay); + + /// Copy constructor. + Date(const Date& date) + : year(date.year), ord(date.ord) + { } + + /// Assignment operator. + Date& operator=(const Date& date) + { + year = date.year; + ord = date.ord; + return *this; + } + + /// Create object from current calendar day. + static Date now(); + + + + //BEGIN // String functions //////////////////////////////////////// + + /// Thrown when format specifiers don't make sense, etc. + class BadFormat : public ProgramException { }; + + + + /*! \brief Convert to std::string (ISO8601) representation. + + \param format Which ISO8601 format to use. + \param precision Which precision to use. + \param extended Whether to use basic or extended notation. + \throws BadFormat if \a format and/or \a precision don't make sense + for this type. + \returns The ISO8601 representation of the date. + + This function converts the date into its ISO8601 notation, with a + variety of possible formatting options. + + */ + std::wstring toString(bool extended = false, + ISO8601Format format = ISO8601FormatCalendar, + ISO8601Precision precision = ISO8601PrecisionFull) const; + + + + /*! \brief Parse an ISO8601 date. + + \param str The ISO8601 std::string to parse. + \param format Which ISO8601 format to use. + \throws ParseError if the std::string cannot be parsed. + \throws BadDate if the date is parsed correctly but is not valid. + \returns A newly-created date object. + + \note There is some ambiguity between the ISO8601 basic forms, since + an 8-digit sequence could either be a basic calendar form or a + basic ordinal form with a 5-digit year. For this reason, all + sequences of 8 or more digits are interpreted as basic calendar + form if \a format is \a ISO8601FormatDetect. + + \note The year part is assumed to always be at least four digits. + + */ + static Date fromString(const std::wstring& str, + ISO8601Format format = ISO8601FormatDetect); + + + + //BEGIN // Access functions //////////////////////////////////////// + + /// Returns the year. + inline int getYear() const + { return year; } + + /// Returns the month. + int getMonth() const; + + /// Returns the day. + int getDay() const; + + /// Returns the ordinal day. + inline int getOrdinal() const + { + return ord; + } + + /// Returns the week day (1 = Monday, 7 = Sunday). + int getWeekDay() const; + + /*! \brief Returns the week number. + + \returns The ISO8601 week number. + \retval 0 if this is week 1 of next year. + \retval -n if this is week \a n of last year. + + Returns the day's week number within the year. There are a couple of + special return values; a zero means that this is in fact week 1 of + next year, and a negative number \a -n means that this is week \a n + of last year. + + */ + int getWeek() const; + + + + //BEGIN // Modify functions / operators //////////////////////////// + + /*! \brief Advance a number of days. + + \param numDays The number of days to advance by. Can be negative. + \returns A reference to the modified object. + + Moves the date forward a number of days, correctly accounting for + month and year boundaries. + + */ + Date& operator+=(int numDays); + + + + /*! \brief Roll back a number of days. + + \param numDays The number of days to reverse by. Can be negative. + \returns A reference to the modified object. + + Moves the date backward a number of days, correctly accounting for + month and year boundaries. + + */ + Date& operator-=(int numDays); + + + + /// Advance a day. + Date& operator++(); + + /// Advance a day. + Date operator++(int); + + /// Go back a day. + Date& operator--(); + + /// Go back a day. + Date operator--(int); + + + + //BEGIN // Comparison operators //////////////////////////////////// + + /*! \brief Get difference in days. + + \param date The date to subtract from this one. + \returns The difference, in days, between two dates. + + */ + int operator-(const Date& date) const; + + + + /// Returns true if two dates are equal. + inline bool operator==(const Date& date) const + { + return (year == date.year) && (ord == date.ord); + } + + /// Returns true if two dates are not equal. + inline bool operator!=(const Date& date) const + { + return (year != date.year) || (ord != date.ord); + } + + /// Returns true if \a date is less than \a this. + inline bool operator<(const Date& date) const + { + return (year < date.year) || (year == date.year && + (ord < date.ord)); + } + + /// Returns true if \a date is less than or equal to \a this. + inline bool operator<=(const Date& date) const + { + return (year < date.year) || (year == date.year && + (ord <= date.ord)); + } + + /// Returns true if \a date is greater than \a this. + inline bool operator>(const Date& date) const + { + return (year > date.year) || (year == date.year && + (ord > date.ord)); + } + + /// Returns true if \a date is greater than or equal to \a this. + inline bool operator>=(const Date& date) const + { + return (year > date.year) || (year == date.year && + (ord >= date.ord)); + } + + + + //BEGIN // Utility functions /////////////////////////////////////// + + /// Returns true if \a year is a leap year. + static inline bool isLeapYear(int year) + { + if(year < 0) year *= -1; + return !(year % 400) || ((year % 100) && !(year & 3)); + } + + /// Returns number of days in the given \a month and \a year. + static inline int daysInMonth(int year, int month) + { + switch(month) { + case 4: + case 6: + case 9: + case 11: + return 30; + + case 2: + return (isLeapYear(year) ? 29 : 28); + } + return 31; + } +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const Date&); + + + +} diff --git a/src/liblw-support/Time/DateTables.cpp b/src/liblw-support/Time/DateTables.cpp new file mode 100644 index 0000000..092db5e --- /dev/null +++ b/src/liblw-support/Time/DateTables.cpp @@ -0,0 +1,1841 @@ +/* lw-support/src/lib/Time/DateTables.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +/* This file contains various autogenerated tables used in Date.cpp */ + +//BEGIN // Data //////////////////////////////////////////////////////// + +namespace lw { +namespace { + + + + int normalMDtoOrd[12][31] = { + // January (31) + { 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31 }, + // February (28) + { 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, + 0, 0, 0 }, + // March (31) + { 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90 }, + // April (30) + { 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, + 119, 120, 0 }, + // May (31) + { 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, + 149, 150, 151 }, + // June (30) + { 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, + 166, 167, 168, 169, 170, 171, 172, + 173, 174, 175, 176, 177, 178, 179, + 180, 181, 0 }, + // July (31) + { 182, 183, 184, 185, 186, 187, 188, + 189, 190, 191, 192, 193, 194, 195, + 196, 197, 198, 199, 200, 201, 202, + 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212 }, + // August (31) + { 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, + 241, 242, 243 }, + // September (30) + { 244, 245, 246, 247, 248, 249, 250, + 251, 252, 253, 254, 255, 256, 257, + 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, + 272, 273, 0 }, + // October (31) + { 274, 275, 276, 277, 278, 279, 280, + 281, 282, 283, 284, 285, 286, 287, + 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298, 299, 300, 301, + 302, 303, 304 }, + // November (30) + { 305, 306, 307, 308, 309, 310, 311, + 312, 313, 314, 315, 316, 317, 318, + 319, 320, 321, 322, 323, 324, 325, + 326, 327, 328, 329, 330, 331, 332, + 333, 334, 0 }, + // December (31) + { 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, + 349, 350, 351, 352, 353, 354, 355, + 356, 357, 358, 359, 360, 361, 362, + 363, 364, 365 } + }; + + + + int leapMDtoOrd[12][31] = { + // January (31) + { 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31 }, + // February (29) + { 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, + 60, 0, 0 }, + // March (31) + { 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91 }, + // April (30) + { 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, + 120, 121, 0 }, + // May (31) + { 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152 }, + // June (30) + { 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, + 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 179, 180, + 181, 182, 0 }, + // July (31) + { 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, + 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 210, + 211, 212, 213 }, + // August (31) + { 214, 215, 216, 217, 218, 219, 220, + 221, 222, 223, 224, 225, 226, 227, + 228, 229, 230, 231, 232, 233, 234, + 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244 }, + // September (30) + { 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, + 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, + 273, 274, 0 }, + // October (31) + { 275, 276, 277, 278, 279, 280, 281, + 282, 283, 284, 285, 286, 287, 288, + 289, 290, 291, 292, 293, 294, 295, + 296, 297, 298, 299, 300, 301, 302, + 303, 304, 305 }, + // November (30) + { 306, 307, 308, 309, 310, 311, 312, + 313, 314, 315, 316, 317, 318, 319, + 320, 321, 322, 323, 324, 325, 326, + 327, 328, 329, 330, 331, 332, 333, + 334, 335, 0 }, + // December (31) + { 336, 337, 338, 339, 340, 341, 342, + 343, 344, 345, 346, 347, 348, 349, + 350, 351, 352, 353, 354, 355, 356, + 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366 } + }; + + + + int normalOrdtoMD[366][2] = { + { 0, 0 }, + { 1, 1 }, + { 1, 2 }, + { 1, 3 }, + { 1, 4 }, + { 1, 5 }, + { 1, 6 }, + { 1, 7 }, + { 1, 8 }, + { 1, 9 }, + { 1, 10 }, + { 1, 11 }, + { 1, 12 }, + { 1, 13 }, + { 1, 14 }, + { 1, 15 }, + { 1, 16 }, + { 1, 17 }, + { 1, 18 }, + { 1, 19 }, + { 1, 20 }, + { 1, 21 }, + { 1, 22 }, + { 1, 23 }, + { 1, 24 }, + { 1, 25 }, + { 1, 26 }, + { 1, 27 }, + { 1, 28 }, + { 1, 29 }, + { 1, 30 }, + { 1, 31 }, + { 2, 1 }, + { 2, 2 }, + { 2, 3 }, + { 2, 4 }, + { 2, 5 }, + { 2, 6 }, + { 2, 7 }, + { 2, 8 }, + { 2, 9 }, + { 2, 10 }, + { 2, 11 }, + { 2, 12 }, + { 2, 13 }, + { 2, 14 }, + { 2, 15 }, + { 2, 16 }, + { 2, 17 }, + { 2, 18 }, + { 2, 19 }, + { 2, 20 }, + { 2, 21 }, + { 2, 22 }, + { 2, 23 }, + { 2, 24 }, + { 2, 25 }, + { 2, 26 }, + { 2, 27 }, + { 2, 28 }, + { 3, 1 }, + { 3, 2 }, + { 3, 3 }, + { 3, 4 }, + { 3, 5 }, + { 3, 6 }, + { 3, 7 }, + { 3, 8 }, + { 3, 9 }, + { 3, 10 }, + { 3, 11 }, + { 3, 12 }, + { 3, 13 }, + { 3, 14 }, + { 3, 15 }, + { 3, 16 }, + { 3, 17 }, + { 3, 18 }, + { 3, 19 }, + { 3, 20 }, + { 3, 21 }, + { 3, 22 }, + { 3, 23 }, + { 3, 24 }, + { 3, 25 }, + { 3, 26 }, + { 3, 27 }, + { 3, 28 }, + { 3, 29 }, + { 3, 30 }, + { 3, 31 }, + { 4, 1 }, + { 4, 2 }, + { 4, 3 }, + { 4, 4 }, + { 4, 5 }, + { 4, 6 }, + { 4, 7 }, + { 4, 8 }, + { 4, 9 }, + { 4, 10 }, + { 4, 11 }, + { 4, 12 }, + { 4, 13 }, + { 4, 14 }, + { 4, 15 }, + { 4, 16 }, + { 4, 17 }, + { 4, 18 }, + { 4, 19 }, + { 4, 20 }, + { 4, 21 }, + { 4, 22 }, + { 4, 23 }, + { 4, 24 }, + { 4, 25 }, + { 4, 26 }, + { 4, 27 }, + { 4, 28 }, + { 4, 29 }, + { 4, 30 }, + { 5, 1 }, + { 5, 2 }, + { 5, 3 }, + { 5, 4 }, + { 5, 5 }, + { 5, 6 }, + { 5, 7 }, + { 5, 8 }, + { 5, 9 }, + { 5, 10 }, + { 5, 11 }, + { 5, 12 }, + { 5, 13 }, + { 5, 14 }, + { 5, 15 }, + { 5, 16 }, + { 5, 17 }, + { 5, 18 }, + { 5, 19 }, + { 5, 20 }, + { 5, 21 }, + { 5, 22 }, + { 5, 23 }, + { 5, 24 }, + { 5, 25 }, + { 5, 26 }, + { 5, 27 }, + { 5, 28 }, + { 5, 29 }, + { 5, 30 }, + { 5, 31 }, + { 6, 1 }, + { 6, 2 }, + { 6, 3 }, + { 6, 4 }, + { 6, 5 }, + { 6, 6 }, + { 6, 7 }, + { 6, 8 }, + { 6, 9 }, + { 6, 10 }, + { 6, 11 }, + { 6, 12 }, + { 6, 13 }, + { 6, 14 }, + { 6, 15 }, + { 6, 16 }, + { 6, 17 }, + { 6, 18 }, + { 6, 19 }, + { 6, 20 }, + { 6, 21 }, + { 6, 22 }, + { 6, 23 }, + { 6, 24 }, + { 6, 25 }, + { 6, 26 }, + { 6, 27 }, + { 6, 28 }, + { 6, 29 }, + { 6, 30 }, + { 7, 1 }, + { 7, 2 }, + { 7, 3 }, + { 7, 4 }, + { 7, 5 }, + { 7, 6 }, + { 7, 7 }, + { 7, 8 }, + { 7, 9 }, + { 7, 10 }, + { 7, 11 }, + { 7, 12 }, + { 7, 13 }, + { 7, 14 }, + { 7, 15 }, + { 7, 16 }, + { 7, 17 }, + { 7, 18 }, + { 7, 19 }, + { 7, 20 }, + { 7, 21 }, + { 7, 22 }, + { 7, 23 }, + { 7, 24 }, + { 7, 25 }, + { 7, 26 }, + { 7, 27 }, + { 7, 28 }, + { 7, 29 }, + { 7, 30 }, + { 7, 31 }, + { 8, 1 }, + { 8, 2 }, + { 8, 3 }, + { 8, 4 }, + { 8, 5 }, + { 8, 6 }, + { 8, 7 }, + { 8, 8 }, + { 8, 9 }, + { 8, 10 }, + { 8, 11 }, + { 8, 12 }, + { 8, 13 }, + { 8, 14 }, + { 8, 15 }, + { 8, 16 }, + { 8, 17 }, + { 8, 18 }, + { 8, 19 }, + { 8, 20 }, + { 8, 21 }, + { 8, 22 }, + { 8, 23 }, + { 8, 24 }, + { 8, 25 }, + { 8, 26 }, + { 8, 27 }, + { 8, 28 }, + { 8, 29 }, + { 8, 30 }, + { 8, 31 }, + { 9, 1 }, + { 9, 2 }, + { 9, 3 }, + { 9, 4 }, + { 9, 5 }, + { 9, 6 }, + { 9, 7 }, + { 9, 8 }, + { 9, 9 }, + { 9, 10 }, + { 9, 11 }, + { 9, 12 }, + { 9, 13 }, + { 9, 14 }, + { 9, 15 }, + { 9, 16 }, + { 9, 17 }, + { 9, 18 }, + { 9, 19 }, + { 9, 20 }, + { 9, 21 }, + { 9, 22 }, + { 9, 23 }, + { 9, 24 }, + { 9, 25 }, + { 9, 26 }, + { 9, 27 }, + { 9, 28 }, + { 9, 29 }, + { 9, 30 }, + { 10, 1 }, + { 10, 2 }, + { 10, 3 }, + { 10, 4 }, + { 10, 5 }, + { 10, 6 }, + { 10, 7 }, + { 10, 8 }, + { 10, 9 }, + { 10, 10 }, + { 10, 11 }, + { 10, 12 }, + { 10, 13 }, + { 10, 14 }, + { 10, 15 }, + { 10, 16 }, + { 10, 17 }, + { 10, 18 }, + { 10, 19 }, + { 10, 20 }, + { 10, 21 }, + { 10, 22 }, + { 10, 23 }, + { 10, 24 }, + { 10, 25 }, + { 10, 26 }, + { 10, 27 }, + { 10, 28 }, + { 10, 29 }, + { 10, 30 }, + { 10, 31 }, + { 11, 1 }, + { 11, 2 }, + { 11, 3 }, + { 11, 4 }, + { 11, 5 }, + { 11, 6 }, + { 11, 7 }, + { 11, 8 }, + { 11, 9 }, + { 11, 10 }, + { 11, 11 }, + { 11, 12 }, + { 11, 13 }, + { 11, 14 }, + { 11, 15 }, + { 11, 16 }, + { 11, 17 }, + { 11, 18 }, + { 11, 19 }, + { 11, 20 }, + { 11, 21 }, + { 11, 22 }, + { 11, 23 }, + { 11, 24 }, + { 11, 25 }, + { 11, 26 }, + { 11, 27 }, + { 11, 28 }, + { 11, 29 }, + { 11, 30 }, + { 12, 1 }, + { 12, 2 }, + { 12, 3 }, + { 12, 4 }, + { 12, 5 }, + { 12, 6 }, + { 12, 7 }, + { 12, 8 }, + { 12, 9 }, + { 12, 10 }, + { 12, 11 }, + { 12, 12 }, + { 12, 13 }, + { 12, 14 }, + { 12, 15 }, + { 12, 16 }, + { 12, 17 }, + { 12, 18 }, + { 12, 19 }, + { 12, 20 }, + { 12, 21 }, + { 12, 22 }, + { 12, 23 }, + { 12, 24 }, + { 12, 25 }, + { 12, 26 }, + { 12, 27 }, + { 12, 28 }, + { 12, 29 }, + { 12, 30 }, + { 12, 31 } + }; + + + + int leapOrdtoMD[367][2] = { + { 0, 0 }, + { 1, 1 }, + { 1, 2 }, + { 1, 3 }, + { 1, 4 }, + { 1, 5 }, + { 1, 6 }, + { 1, 7 }, + { 1, 8 }, + { 1, 9 }, + { 1, 10 }, + { 1, 11 }, + { 1, 12 }, + { 1, 13 }, + { 1, 14 }, + { 1, 15 }, + { 1, 16 }, + { 1, 17 }, + { 1, 18 }, + { 1, 19 }, + { 1, 20 }, + { 1, 21 }, + { 1, 22 }, + { 1, 23 }, + { 1, 24 }, + { 1, 25 }, + { 1, 26 }, + { 1, 27 }, + { 1, 28 }, + { 1, 29 }, + { 1, 30 }, + { 1, 31 }, + { 2, 1 }, + { 2, 2 }, + { 2, 3 }, + { 2, 4 }, + { 2, 5 }, + { 2, 6 }, + { 2, 7 }, + { 2, 8 }, + { 2, 9 }, + { 2, 10 }, + { 2, 11 }, + { 2, 12 }, + { 2, 13 }, + { 2, 14 }, + { 2, 15 }, + { 2, 16 }, + { 2, 17 }, + { 2, 18 }, + { 2, 19 }, + { 2, 20 }, + { 2, 21 }, + { 2, 22 }, + { 2, 23 }, + { 2, 24 }, + { 2, 25 }, + { 2, 26 }, + { 2, 27 }, + { 2, 28 }, + { 2, 29 }, + { 3, 1 }, + { 3, 2 }, + { 3, 3 }, + { 3, 4 }, + { 3, 5 }, + { 3, 6 }, + { 3, 7 }, + { 3, 8 }, + { 3, 9 }, + { 3, 10 }, + { 3, 11 }, + { 3, 12 }, + { 3, 13 }, + { 3, 14 }, + { 3, 15 }, + { 3, 16 }, + { 3, 17 }, + { 3, 18 }, + { 3, 19 }, + { 3, 20 }, + { 3, 21 }, + { 3, 22 }, + { 3, 23 }, + { 3, 24 }, + { 3, 25 }, + { 3, 26 }, + { 3, 27 }, + { 3, 28 }, + { 3, 29 }, + { 3, 30 }, + { 3, 31 }, + { 4, 1 }, + { 4, 2 }, + { 4, 3 }, + { 4, 4 }, + { 4, 5 }, + { 4, 6 }, + { 4, 7 }, + { 4, 8 }, + { 4, 9 }, + { 4, 10 }, + { 4, 11 }, + { 4, 12 }, + { 4, 13 }, + { 4, 14 }, + { 4, 15 }, + { 4, 16 }, + { 4, 17 }, + { 4, 18 }, + { 4, 19 }, + { 4, 20 }, + { 4, 21 }, + { 4, 22 }, + { 4, 23 }, + { 4, 24 }, + { 4, 25 }, + { 4, 26 }, + { 4, 27 }, + { 4, 28 }, + { 4, 29 }, + { 4, 30 }, + { 5, 1 }, + { 5, 2 }, + { 5, 3 }, + { 5, 4 }, + { 5, 5 }, + { 5, 6 }, + { 5, 7 }, + { 5, 8 }, + { 5, 9 }, + { 5, 10 }, + { 5, 11 }, + { 5, 12 }, + { 5, 13 }, + { 5, 14 }, + { 5, 15 }, + { 5, 16 }, + { 5, 17 }, + { 5, 18 }, + { 5, 19 }, + { 5, 20 }, + { 5, 21 }, + { 5, 22 }, + { 5, 23 }, + { 5, 24 }, + { 5, 25 }, + { 5, 26 }, + { 5, 27 }, + { 5, 28 }, + { 5, 29 }, + { 5, 30 }, + { 5, 31 }, + { 6, 1 }, + { 6, 2 }, + { 6, 3 }, + { 6, 4 }, + { 6, 5 }, + { 6, 6 }, + { 6, 7 }, + { 6, 8 }, + { 6, 9 }, + { 6, 10 }, + { 6, 11 }, + { 6, 12 }, + { 6, 13 }, + { 6, 14 }, + { 6, 15 }, + { 6, 16 }, + { 6, 17 }, + { 6, 18 }, + { 6, 19 }, + { 6, 20 }, + { 6, 21 }, + { 6, 22 }, + { 6, 23 }, + { 6, 24 }, + { 6, 25 }, + { 6, 26 }, + { 6, 27 }, + { 6, 28 }, + { 6, 29 }, + { 6, 30 }, + { 7, 1 }, + { 7, 2 }, + { 7, 3 }, + { 7, 4 }, + { 7, 5 }, + { 7, 6 }, + { 7, 7 }, + { 7, 8 }, + { 7, 9 }, + { 7, 10 }, + { 7, 11 }, + { 7, 12 }, + { 7, 13 }, + { 7, 14 }, + { 7, 15 }, + { 7, 16 }, + { 7, 17 }, + { 7, 18 }, + { 7, 19 }, + { 7, 20 }, + { 7, 21 }, + { 7, 22 }, + { 7, 23 }, + { 7, 24 }, + { 7, 25 }, + { 7, 26 }, + { 7, 27 }, + { 7, 28 }, + { 7, 29 }, + { 7, 30 }, + { 7, 31 }, + { 8, 1 }, + { 8, 2 }, + { 8, 3 }, + { 8, 4 }, + { 8, 5 }, + { 8, 6 }, + { 8, 7 }, + { 8, 8 }, + { 8, 9 }, + { 8, 10 }, + { 8, 11 }, + { 8, 12 }, + { 8, 13 }, + { 8, 14 }, + { 8, 15 }, + { 8, 16 }, + { 8, 17 }, + { 8, 18 }, + { 8, 19 }, + { 8, 20 }, + { 8, 21 }, + { 8, 22 }, + { 8, 23 }, + { 8, 24 }, + { 8, 25 }, + { 8, 26 }, + { 8, 27 }, + { 8, 28 }, + { 8, 29 }, + { 8, 30 }, + { 8, 31 }, + { 9, 1 }, + { 9, 2 }, + { 9, 3 }, + { 9, 4 }, + { 9, 5 }, + { 9, 6 }, + { 9, 7 }, + { 9, 8 }, + { 9, 9 }, + { 9, 10 }, + { 9, 11 }, + { 9, 12 }, + { 9, 13 }, + { 9, 14 }, + { 9, 15 }, + { 9, 16 }, + { 9, 17 }, + { 9, 18 }, + { 9, 19 }, + { 9, 20 }, + { 9, 21 }, + { 9, 22 }, + { 9, 23 }, + { 9, 24 }, + { 9, 25 }, + { 9, 26 }, + { 9, 27 }, + { 9, 28 }, + { 9, 29 }, + { 9, 30 }, + { 10, 1 }, + { 10, 2 }, + { 10, 3 }, + { 10, 4 }, + { 10, 5 }, + { 10, 6 }, + { 10, 7 }, + { 10, 8 }, + { 10, 9 }, + { 10, 10 }, + { 10, 11 }, + { 10, 12 }, + { 10, 13 }, + { 10, 14 }, + { 10, 15 }, + { 10, 16 }, + { 10, 17 }, + { 10, 18 }, + { 10, 19 }, + { 10, 20 }, + { 10, 21 }, + { 10, 22 }, + { 10, 23 }, + { 10, 24 }, + { 10, 25 }, + { 10, 26 }, + { 10, 27 }, + { 10, 28 }, + { 10, 29 }, + { 10, 30 }, + { 10, 31 }, + { 11, 1 }, + { 11, 2 }, + { 11, 3 }, + { 11, 4 }, + { 11, 5 }, + { 11, 6 }, + { 11, 7 }, + { 11, 8 }, + { 11, 9 }, + { 11, 10 }, + { 11, 11 }, + { 11, 12 }, + { 11, 13 }, + { 11, 14 }, + { 11, 15 }, + { 11, 16 }, + { 11, 17 }, + { 11, 18 }, + { 11, 19 }, + { 11, 20 }, + { 11, 21 }, + { 11, 22 }, + { 11, 23 }, + { 11, 24 }, + { 11, 25 }, + { 11, 26 }, + { 11, 27 }, + { 11, 28 }, + { 11, 29 }, + { 11, 30 }, + { 12, 1 }, + { 12, 2 }, + { 12, 3 }, + { 12, 4 }, + { 12, 5 }, + { 12, 6 }, + { 12, 7 }, + { 12, 8 }, + { 12, 9 }, + { 12, 10 }, + { 12, 11 }, + { 12, 12 }, + { 12, 13 }, + { 12, 14 }, + { 12, 15 }, + { 12, 16 }, + { 12, 17 }, + { 12, 18 }, + { 12, 19 }, + { 12, 20 }, + { 12, 21 }, + { 12, 22 }, + { 12, 23 }, + { 12, 24 }, + { 12, 25 }, + { 12, 26 }, + { 12, 27 }, + { 12, 28 }, + { 12, 29 }, + { 12, 30 }, + { 12, 31 } + }; + + + + int normalOrdtoWeek[7][366] = { + { 999, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0 + }, + { 999, + 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0, 0 + }, + { 999, + 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0, 0, 0 + }, + { 999, + 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 53, 53, 53, 53 + }, + { 999, + -53, -53, -53, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52 + }, + { 999, + -52, -52, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52 + }, + { 999, + -52, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52 + } + }; + + + + int leapOrdtoWeek[7][367] = { + { 999, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0, 0 + }, + { 999, + 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 53, 53, 53, 53 + }, + { 999, + -53, -53, -53, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52 + }, + { 999, + -52, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0 + }, + { 999, + 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 0, 0, 0 + }, + { 999, + 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52, + 53, 53, 53, 53, 53 + }, + { 999, + -52, -52, + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, + 14, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, + 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, + 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, + 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, 27, 27, 27, + 28, 28, 28, 28, 28, 28, 28, + 29, 29, 29, 29, 29, 29, 29, + 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, + 32, 32, 32, 32, 32, 32, 32, + 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, + 35, 35, 35, 35, 35, 35, 35, + 36, 36, 36, 36, 36, 36, 36, + 37, 37, 37, 37, 37, 37, 37, + 38, 38, 38, 38, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, + 40, 40, 40, 40, 40, 40, 40, + 41, 41, 41, 41, 41, 41, 41, + 42, 42, 42, 42, 42, 42, 42, + 43, 43, 43, 43, 43, 43, 43, + 44, 44, 44, 44, 44, 44, 44, + 45, 45, 45, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 46, 46, + 47, 47, 47, 47, 47, 47, 47, + 48, 48, 48, 48, 48, 48, 48, + 49, 49, 49, 49, 49, 49, 49, + 50, 50, 50, 50, 50, 50, 50, + 51, 51, 51, 51, 51, 51, 51, + 52, 52, 52, 52, 52, 52, 52 + } + }; + + + +} +} + + + +#if 0 +// gen.cpp + +#include +#include +using namespace std; + + + +char* monthNames[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + + + +//BEGIN // MDtoOrd ///////////////////////////////////////////////////// + +int MDtoOrd_normal[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; +int MDtoOrd_leap[12] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +void MDtoOrd(const char* varName, int* months) +{ + cout << " int " << varName << "[12][31] = {\n"; + int j = 0, k = 0; + for(int i = 0; i < 12; ++i) { + cout << " // " << monthNames[i] << " (" << months[i] << ")\n"; + cout << " { "; + for(j = 0; j < months[i]; ) { + if(j && !(j % 7)) { + cout << "\n "; + } + ++k; + if(++j == 31) { + cout << setw(3) << k << " }"; + if(i < 11) cout << ","; + cout << "\n"; + } else { + cout << setw(3) << k << ", "; + } + } + for(; j < 31; ) { + if(j && !(j % 7)) { + cout << "\n "; + } + if(++j == 31) { + cout << " 0 }"; + if(i < 11) cout << ","; + cout << "\n"; + } else { + cout << " 0, "; + } + } + } + cout << " };\n\n\n\n"; +} + +void OrdtoMD(const char* varName, int* months) +{ + int total = 0; + for(int i = 0; i < 12; ++i) total += months[i]; + cout << " int " << varName << "[" << (total + 1) << "][2] = {\n"; + cout << " { 0, 0 },\n"; + for(int i = 0; i < 12; ++i) { + for(int j = 0; j < months[i]; ) { + cout << " { " << (i + 1) << ", " << (++j) << " }"; + if(j == months[i] && i == 11) break; + cout << ",\n"; + } + } + cout << "\n };\n\n\n\n"; +} + + + +//BEGIN // OrdtoWeek /////////////////////////////////////////////////// + +void OrdtoWeek(const char* varName, bool isLeap) +{ + int week = 0; + + int iMax = (isLeap ? 366 : 365); + cout << " int " << varName << "[7][" << (iMax + 1) << "] = {\n"; + + for(int weekDay = 0; weekDay < 7; ++weekDay) { + switch(weekDay) { + case 0: + case 1: + case 2: + case 3: + week = 1; + break; + + case 4: + week = -53; + break; + + case 5: + case 6: + week = -52; + break; + } + + cout << " { 999,\n "; + for(int i = 0; i < iMax; ) { + cout << week; + if(++i < iMax) { + cout << ", "; + if(++weekDay == 7) { + weekDay = 0; + if(week < 0) week = 1; + else ++week; + if(week == 53 && (i != 361 && (!isLeap || i != 362))) week = 0; + cout << "\n "; + } + } + } + cout << "\n }"; + if(weekDay != 6) cout << ",\n"; + } + cout << "\n };\n\n\n\n"; +} + + + +//BEGIN // main() ////////////////////////////////////////////////////// + +int main() +{ + MDtoOrd("normalMDtoOrd", MDtoOrd_normal); + MDtoOrd("leapMDtoOrd", MDtoOrd_leap); + OrdtoMD("normalOrdtoMD", MDtoOrd_normal); + OrdtoMD("leapOrdtoMD", MDtoOrd_leap); + OrdtoWeek("normalOrdtoWeek", false); + OrdtoWeek("leapOrdtoWeek", true); +} +#endif + + diff --git a/src/liblw-support/Time/DateTime.cpp b/src/liblw-support/Time/DateTime.cpp new file mode 100644 index 0000000..0e8c187 --- /dev/null +++ b/src/liblw-support/Time/DateTime.cpp @@ -0,0 +1,108 @@ +/* lw-support/src/lib/Time/DateTime.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring DateTime::toString(bool extended, bool decimalTime, + ISO8601Format dateFormat, ISO8601Precision precision) const +{ + std::wostringstream o; + switch(precision) { + case ISO8601PrecisionYear: + case ISO8601PrecisionWeek: + case ISO8601PrecisionMonth: + case ISO8601PrecisionDay: + o << date.toString(extended, dateFormat, precision); + break; + + case ISO8601PrecisionHour: + case ISO8601PrecisionMinute: + case ISO8601PrecisionSecond: + case ISO8601PrecisionFull: + o << date.toString(extended, dateFormat, ISO8601PrecisionFull); + o << L'T'; + o << time.toString(extended, precision, decimalTime); + break; + } + return o.str(); +} + + + +DateTime DateTime::fromString(const std::wstring& str, + ISO8601Format dateFormat) +{ + std::wstring::size_type timeStart = str.find('T'); + if(timeStart == std::string::npos) { + return DateTime(Date::fromString(str, dateFormat)); + } + return DateTime( + Date::fromString(str.substr(0, timeStart), dateFormat), + DayTime::fromString(str.substr(timeStart + 1, std::wstring::npos)) + ); +} + + + +DateTime& DateTime::addNanoSeconds(int64_t ns) +{ + int dayDelta; + time.addNanoSeconds(ns, dayDelta); + date += dayDelta; + return *this; +} + + + +DateTime& DateTime::addSeconds(int s) +{ + int dayDelta; + time.addSeconds(s, dayDelta); + date += dayDelta; + return *this; +} + + + +DateTime& DateTime::addMinutes(int m) +{ + int dayDelta; + time.addMinutes(m, dayDelta); + date += dayDelta; + return *this; +} + + + +DateTime& DateTime::addHours(int h) +{ + int dayDelta; + time.addHours(h, dayDelta); + date += dayDelta; + return *this; +} + + + +DateTime& DateTime::addDays(int d) +{ + date += d; + return *this; +} + + + +std::wostream& operator<<(std::wostream& ostr, const DateTime& d) +{ + ostr << d.toString(); + return ostr; +} + + + +} diff --git a/src/liblw-support/Time/DateTime.h b/src/liblw-support/Time/DateTime.h new file mode 100644 index 0000000..7422446 --- /dev/null +++ b/src/liblw-support/Time/DateTime.h @@ -0,0 +1,222 @@ +/* lw-support/src/lib/Time/DateTime.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Stores a date and a time. + +This class uses (internally) a DayTime and a Date class. These allow it +to represent pretty much arbitrary timescales (well, about +/- 2e9 years +with the precision of a nanosecond). + +Altough you can individually operate on the internal Date and DayTime +instances, this isn't always a good idea. For instance, adding 24 hours +onto the DayTime object won't alter the Date. Instead, use the +addHours() function and equivalents. + +*/ +class DateTime { +protected: + /// The day. + Date date; + + /// The time. + DayTime time; + +public: + //BEGIN // Constructors, etc. ////////////////////////////////////// + + /// Construct from date and time. + DateTime(const Date& date, const DayTime& time) + : date(date), time(time) + { } + + /// Construct (at midnight) from date. + DateTime(const Date& date) + : date(date), time(0, 0, 0) + { } + + /// Copy constructor. + DateTime(const DateTime& dateTime) + : date(dateTime.date), time(dateTime.time) + { } + + /// Assignment operator. + DateTime& operator=(const DateTime& dateTime) + { + date = dateTime.date; + time = dateTime.time; + return *this; + } + + /// Construct from calendar time. + static DateTime now() + { return DateTime(Date::now(), DayTime::now()); } + + + + //BEGIN // String routines ///////////////////////////////////////// + + /*! \brief Convert to ISO8601 std::string. + + \param extended \a true if you want to use the extended form, + \a false if not. + \param decimalTime \a true if you want to use the decimal time + notation. + \param precision How precise you want the converted std::string to be. + \param dateFormat Which format to use for the date. + + Converts the entire date/time into an ISO8601-formatted std::string. See + documentation for Date and DayTime for more information about the + parameters and their results. + + \sa lw::Date::toString(), lw::DayTime::toString() + + */ + std::wstring toString(bool extended = false, bool decimalTime = false, + ISO8601Format dateFormat = ISO8601FormatCalendar, + ISO8601Precision precision = ISO8601PrecisionFull) const; + + + + /*! \brief Create from ISO8601 std::string. + + \param str The ISO8601 std::string to parse. + \param dateFormat Which ISO8601 format to use. + \throws ParseError if the std::string cannot be parsed. + \throws BadDate if the date is parsed correctly but is not valid. + \returns A newly-created date object. + + Attempts to parse \a str as an ISO8601 date/time std::string. If the time + part is not present, the time will be set to midnight. + + \note There is some ambiguity between the ISO8601 basic date forms, + since an 8-digit sequence could either be a basic calendar form + or a basic ordinal form with a 5-digit year. For this reason, + all sequences of 8 or more digits are interpreted as basic + calendar form if \a dateFormat is \a ISO8601FormatDetect. + + \note The year part is assumed to always be at least four digits. + + */ + static DateTime fromString(const std::wstring& str, + ISO8601Format dateFormat = ISO8601FormatDetect); + + + + //BEGIN // Access functions //////////////////////////////////////// + + /// Get date component. + const Date& getDate() const + { return date; } + + /// Get date component. + Date& getDate() + { return date; } + + /// Get time component. + const DayTime& getTime() const + { return time; } + + /// Get time component. + DayTime& getTime() + { return time; } + + + + //BEGIN // Modify functions //////////////////////////////////////// + + /// Set date component. + DateTime& setDate(const Date& date) + { + this->date = date; + return *this; + } + + /// Set time component. + DateTime& setTime(const DayTime& time) + { + this->time = time; + return *this; + } + + /// Set date and time. + DateTime& set(const Date& date, const DayTime& time) + { + this->date = date; + this->time = time; + return *this; + } + + + + /// Add a number of nanoseconds. + DateTime& addNanoSeconds(int64_t ns); + + /// Add a number of seconds. + DateTime& addSeconds(int s); + + /// Add a number of minutes. + DateTime& addMinutes(int m); + + /// Add a number of hours. + DateTime& addHours(int h); + + /// Add a number of days. + DateTime& addDays(int d); + + + + + //BEGIN // Operators /////////////////////////////////////////////// + + /// Returns true if the dates are equal. + bool operator==(const DateTime& dateTime) const + { return date == dateTime.date && time == dateTime.time; } + + /// Returns true if the dates are not equal. + bool operator!=(const DateTime& dateTime) const + { return date != dateTime.date || time != dateTime.time; } + + /// Returns true if less than \a dateTime. + bool operator<(const DateTime& dateTime) const + { + return date < dateTime.date || + (date == dateTime.date && time < dateTime.time); + } + + /// Returns true if less than or equal to \a dateTime. + bool operator<=(const DateTime& dateTime) const + { + return date < dateTime.date || + (date == dateTime.date && time <= dateTime.time); + } + + /// Returns true if greater than \a dateTime. + bool operator>(const DateTime& dateTime) const + { + return date > dateTime.date || + (date == dateTime.date && time > dateTime.time); + } + + /// Returns true if greater than or equal to \a dateTime. + bool operator>=(const DateTime& dateTime) const + { + return date > dateTime.date || + (date == dateTime.date && time >= dateTime.time); + } +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const DateTime&); + + + +} diff --git a/src/liblw-support/Time/DayTime.cpp b/src/liblw-support/Time/DayTime.cpp new file mode 100644 index 0000000..1b89c85 --- /dev/null +++ b/src/liblw-support/Time/DayTime.cpp @@ -0,0 +1,264 @@ +/* lw-support/src/lib/Time/DayTime.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +DayTime& DayTime::wrap(int& dayDelta) +{ + int d = 0; + dayDelta = 0; + if(_ns < 0) { + _ns += tenExp9; + _s--; + } + if(_ns >= tenExp9) { + _ns -= tenExp9; + _s++; + } + if(_s < 0) { + d = 1 + (_s / -60); + _s += d * 60; + _m -= d; + } + if(_s >= 60) { + d = _s / 60; + _s -= d * 60; + _m += d; + } + if(_m < 0) { + d = 1 + (_m / -60); + _m += d * 60; + _h -= d; + } + if(_m >= 60) { + d = _m / 60; + _m -= d * 60; + _h += d; + } + if(_h < 0) { + d = 1 + (_h / -24); + _h += d * 24; + dayDelta = -d; + } + if(_h >= 24) { + d = _h / 24; + _h -= d * 24; + dayDelta = d; + } + return *this; +} + + + +std::wstring DayTime::toString(bool extended, ISO8601Precision precision, + bool decimal) const +{ + std::wostringstream o, o2; + o << std::setfill(L'0'); + o2 << std::fixed; + + switch(precision) { + case ISO8601PrecisionHour: + o << std::setw(2) << _h; + if(decimal) { + double f = (_m / 60.0 + _s / 3600.0) + _ns / 3600e9; + o2 << f; + o << (o2.str().substr(1, std::wstring::npos)); + } + break; + + case ISO8601PrecisionMinute: + o << std::setw(2) << _h; + if(extended) o << ':'; + o << std::setw(2) << _m; + if(decimal) { + double f = (_s / 60.0) + _ns / 60e9; + o2 << f; + o << (o2.str().substr(1, std::wstring::npos)); + } + break; + + case ISO8601PrecisionSecond: + o << std::setw(2) << _h; + if(extended) o << ':'; + o << std::setw(2) << _m; + if(extended) o << ':'; + o << std::setw(2) << _s; + if(decimal) { + double f = _ns / 1e9; + o2 << f; + o << (o2.str().substr(1, std::wstring::npos)); + } + break; + + case ISO8601PrecisionFull: + o << std::setw(2) << _h; + if(extended) o << ':'; + o << std::setw(2) << _m; + if(extended) o << ':'; + o << std::setw(2) << _s; + if(_ns) { + double f = _ns / 1e9; + o2 << f; + o << (o2.str().substr(1, std::wstring::npos)); + } + break; + + default: + throw BadFormat(); + } + return o.str(); +} + + + +namespace { + +/* This function parses basic and extended format ISO8601 times (but not +the decimal notations, which are handled separately in fromString). It +returns the number of elements parsed (1, 2 or 3). This can be used to +determine which element a decimal fraction applies to. +*/ +int ISO8601TimeParser(const std::wstring& str, int& h, int& m, int& s) +{ + // The remaining ISO8601 formats can all be disambiguated on length. + switch(str.length()) { + case 8: // hh:mm:ss + if(str[2] != ':' || str[5] != ':') { + throw ParseError(L"Not a recognised ISO8601 format.", + str, str[2] == ':' ? 5 : 2); + } + h = strToInt32(str.substr(0, 2)); + m = strToInt32(str.substr(3, 2)); + s = strToInt32(str.substr(6, 2)); + return 3; + + case 6: // hhmmss + h = strToInt32(str.substr(0, 2)); + m = strToInt32(str.substr(2, 2)); + s = strToInt32(str.substr(4, 2)); + return 3; + + case 5: // hh:mm + if(str[2] != ':') { + throw ParseError(L"Not a recognised ISO8601 format.", + str, 2); + } + h = strToInt32(str.substr(0, 2)); + m = strToInt32(str.substr(3, 2)); + return 2; + + case 4: // hhmm + h = strToInt32(str.substr(0, 2)); + m = strToInt32(str.substr(2, 2)); + return 2; + + case 2: // hh + h = strToInt32(str); + return 1; + } + + throw ParseError(L"Not a recognised ISO8601 format.", str, 0); +} + +} + + + +DayTime DayTime::fromString(const std::wstring& str) +{ + int h = 0, m = 0, s = 0, ns = 0; + std::wstring::size_type decimal = std::wstring::npos; + + try { + // Check for decimal time. + if( (decimal = str.find('.')) != std::wstring::npos || + (decimal = str.find(',')) != std::wstring::npos) + { + // get fractional part + double fraction = strToDouble( + str.substr(decimal, std::wstring::npos)); + + switch(ISO8601TimeParser(str.substr(0, decimal), h, m, s)) { + case 1: + m = (int)(fraction * 60); + s = (int)fmod(fraction * 3600, 60); + ns = (int)fmod(fraction * 3600 * tenExp9, tenExp9); + break; + + case 2: + s = (int)(fraction * 60); + ns = (int)fmod(fraction * 60 * tenExp9, tenExp9); + break; + + case 3: + ns = (int)(fraction * tenExp9); + break; + } + + } else { + // Interpret as non-decimal time + ISO8601TimeParser(str, h, m, s); + } + + return DayTime(h, m, s, ns); + } + catch(Exception& ex) { + // build a suitable error message + std::wostringstream chainStr; + chainStr << "DayTime::fromString(\"" << str << "\")"; + + ex.chain(chainStr.str()); + throw; + } +} + + + +DayTime DayTime::now() +{ + struct timeval tv; + gettimeofday(&tv, 0); + time_t t = tv.tv_sec; + struct tm tt; + localtime_r(&t, &tt); + return DayTime(tt.tm_hour, tt.tm_min, tt.tm_sec, tv.tv_usec * 1000); +} + + + +DayTime& DayTime::addNanoSeconds(int64_t ns) +{ + lldiv_t dv = lldiv(ns, tenExp9); + _s += dv.quot; + _ns += dv.rem; + int dummy; + return wrap(dummy); +} + + + +DayTime& DayTime::addNanoSeconds(int64_t ns, int& dayDelta) +{ + lldiv_t dv = lldiv(ns, tenExp9); + _s += dv.quot; + _ns += dv.rem; + return wrap(dayDelta); +} + + + +std::wostream& operator<<(std::wostream& ostr, const DayTime& d) +{ + ostr << d.toString(); + return ostr; +} + + + +} diff --git a/src/liblw-support/Time/DayTime.h b/src/liblw-support/Time/DayTime.h new file mode 100644 index 0000000..e7ffdce --- /dev/null +++ b/src/liblw-support/Time/DayTime.h @@ -0,0 +1,331 @@ +/* lw-support/src/lib/Time/DayTime.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Represents a 24-hour day time. + +This class is used to represent a day time (i.e. between 00:00:00 and +23:59:59). It will "wrap around" if you modify it so that it goes +outside this range. The function forms will allow you to retrieve the +number of days difference in such cases. There is no support for +leap seconds. The class also has a fractional second member (in +nanoseconds). + +This class is intended for representing a specific time, not an elapsed +time. Use the ElapsedTime class for that. + +*/ +class DayTime { +private: + int _h, _m, _s, _ns; + + DayTime& wrap(int& dayDelta); + +public: + //BEGIN // Constructors //////////////////////////////////////////// + + /*! \brief Construct from time. + + \param h The hour (0-23). + \param m The minute (0-59). + \param s The second (0-59). + \param ns The nanosecond (a positive value < 1s). + \throws BadTimeValue if any value is out of range. + + */ + DayTime(int h, int m, int s = 0, int ns = 0) + : _h(h), _m(m), _s(s), _ns(ns) + { + if(_h < 0 || _h > 23 || _m < 0 || _m > 59 || _s < 0 || _s > 59 + || _ns < 0 || _ns >= tenExp9) + throw BadTimeValue(); + } + + /*! \brief Construct from elapsed time. + + \param time The ElapsedTime object to construct from. + \throws BadTimeValue if \a time represents a period >= 86400s. + + */ + DayTime(const ElapsedTime& time) + { + uint64_t p = time.to_s(); + if(p >= 86400) throw BadTimeValue(); + _h = p / 3600; + _m = (p / 60) % 60; + _s = p % 60; + _ns = time.to_ns() % tenExp9; + } + + /// Copy constructor. + DayTime(const DayTime& other) + : _h(other._h), _m(other._m), _s(other._s), _ns(other._ns) + { } + + /// Construct from calendar time. + static DayTime now(); + + + + //BEGIN // Setting functions /////////////////////////////////////// + + /*! \brief Set time. + + \param h The hour (0-23). + \param m The minute (0-59). + \param s The second (0-59). + \param ns The nanosecond (a positive value < 1s). + \throws BadTimeValue if any value is out of range. + \returns A reference to itself. + + */ + DayTime& set(int h, int m, int s = 0, int ns = 0) + { + if(h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59 + || ns < 0 || ns >= tenExp9) + throw BadTimeValue(); + _h = h; + _m = m; + _s = s; + _ns = ns; + return *this; + } + + /// Set time. + DayTime& operator=(const DayTime& other) + { + _h = other._h; + _m = other._m; + _s = other._s; + _ns = other._ns; + return *this; + } + + /*! \brief Set time. + + \param time The ElapsedTime object to construct from. + \throws BadTimeValue if \a time represents a period >= 86400s. + \returns A reference to itself. + + */ + DayTime& operator=(const ElapsedTime& time) + { + uint64_t p = time.to_s(); + if(p >= 86400) throw BadTimeValue(); + _h = p / 3600; + _m = (p / 60) % 60; + _s = p % 60; + _ns = time.to_ns() % tenExp9; + return *this; + } + + + + //BEGIN // Query functions ///////////////////////////////////////// + + /// Return hour (0-23). + int h() const + { return _h; } + + /// Return minute (0-59). + int m() const + { return _m; } + + /// Return second (0-59). + int s() const + { return _s; } + + /// Return nanosecond (<1s) + int ns() const + { return _ns; } + + /// Convert to an ElapsedTime object. + ElapsedTime toElapsedTime() const + { + if(_ns) return ElapsedTime(_h * 3600 + _m * 60 + _s, + _ns / 1000, _ns % 1000); + else return ElapsedTime(_h * 3600 + _m * 60 + _s); + } + + /// Convert into seconds since midnight. + int numSeconds() const + { return _h * 3600 + _m * 60 + _s; } + + /// Convert into nanoseconds since midnight. + int64_t numNanoSeconds() const + { return (_h * 3600 + _m * 60 + _s) * tenExp9 + _ns; } + + + + //BEGIN // Arithmetic functions //////////////////////////////////// + + /// Advance time. + DayTime& addNanoSeconds(int64_t ns); + + /// Advance time. + DayTime& addNanoSeconds(int64_t ns, int& dayDelta); + + /// Advance time. + DayTime& addSeconds(int s) + { + int dummy; + _s += s; + return wrap(dummy); + } + + /// Advance time. + DayTime& addSeconds(int s, int& dayDelta) + { + _s += s; + return wrap(dayDelta); + } + + /// Advance time. + DayTime& addMinutes(int m) + { + int dummy; + _m += m; + return wrap(dummy); + } + + /// Advance time. + DayTime& addMinutes(int m, int& dayDelta) + { + _m += m; + return wrap(dayDelta); + } + + /// Advance time. + DayTime& addHours(int h) + { + int dummy; + _h += h; + return wrap(dummy); + } + + /// Advance time. + DayTime& addHours(int h, int& dayDelta) + { + _h += h; + return wrap(dayDelta); + } + + + + //BEGIN // String routines ///////////////////////////////////////// + + /// Thrown when format specifiers don't make sense, etc. + class BadFormat : public ProgramException { }; + + + + /*! \brief Parse a std::string object to get a day time. + + \param str The std::string to parse. + \throws ParseError if a parsing error occurs. + \returns A new DayTime object constructed from the std::string data. + + Parses an ISO8601 std::string, accepting either basic or extended format + notations, at any precision. Handles decimal notation as well. + + */ + static DayTime fromString(const std::wstring& str); + + + + /*! \brief Convert to std::string. + + \param extended True if you want the extended ISO8601 + representation; false if not. + \param precision The precision to which you want to go. This takes + a different meaning if \a decimal is true. + \param decimal True if you want to use decimal time notation; false + if not. + \returns A UCS-4 std::string representation of the day time. + + Creates a std::string representation of the time in ISO8601 form. If you + choose to use decimal time, then the full precision will be printed, + but the \a precision argument specifies where to start the fraction. + If you use lw::ISO8601PrecisionFull, and \a decimal is set to false, + the decimal part will still be printed if and only if the number of + nanoseconds is non-zero. + + */ + std::wstring toString(bool extended = true, + ISO8601Precision precision = ISO8601PrecisionSecond, + bool decimal = false) const; + + + + //BEGIN // Operators (comparison) ////////////////////////////////// + + /// Returns true if \a this is equal to \a other. + bool operator==(const DayTime& other) const + { return _h == other._h && _m == other._m && _s == other._s; } + + /// Returns true if \a this is not equal to \a other. + bool operator!=(const DayTime& other) const + { return _h != other._h || _m != other._m || _s != other._s; } + + /// Returns true if \a this is earlier than \a other. + bool operator<(const DayTime& other) const + { return numNanoSeconds() < other.numNanoSeconds(); } + + /// Returns true if \a this is earlier than or equal to \a other. + bool operator<=(const DayTime& other) const + { return numNanoSeconds() <= other.numNanoSeconds(); } + + /// Returns true if \a this is later than \a other. + bool operator>(const DayTime& other) const + { return numNanoSeconds() > other.numNanoSeconds(); } + + /// Returns true if \a this is later than or equal to \a other. + bool operator>=(const DayTime& other) const + { return numNanoSeconds() >= other.numNanoSeconds(); } + + + + //BEGIN // Operators (arithmetic) ////////////////////////////////// + + /// Advance time. + DayTime& operator+=(int s) + { return addSeconds(s); } + + /// Retard time. + DayTime& operator-=(int s) + { return addSeconds(-s); } + + /// Advanced time. + DayTime operator+(int s) const + { + DayTime t(*this); + return t.addSeconds(s); + } + + /// Retarded time. + DayTime operator-(int s) const + { + DayTime t(*this); + return t.addSeconds(-s); + } + + /// Returns time difference in nanoseconds. + int64_t operator-(const DayTime& other) const + { return numNanoSeconds() - other.numNanoSeconds(); } + +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const DayTime& d); + + + +} diff --git a/src/liblw-support/Time/ElapsedTime.cpp b/src/liblw-support/Time/ElapsedTime.cpp new file mode 100644 index 0000000..f8ed0a9 --- /dev/null +++ b/src/liblw-support/Time/ElapsedTime.cpp @@ -0,0 +1,118 @@ +/* lw-support/src/lib/Time/ElapsedTime.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +std::wstring ElapsedTime::toString(int decPlaces, bool exp) const +{ + std::wostringstream o; + + o << std::setprecision(decPlaces); + + if(exp) { + o << std::scientific; + o << (time / 1e9) << 's'; + + } else { + o << std::fixed; + if(time < 100) { + o << time << "ns"; + + } else if(time < 1e5) { + o << (time / 1e3) << "us"; + + } else if(time < 1e8) { + o << (time / 1e6) << "ns"; + + } else { + o << (time / 1e9) << 's'; + + } + } + + return o.str(); +} + + + +ElapsedTime ElapsedTime::fromString(const std::wstring& str) +{ + std::wstring::size_type strSize = str.length(); + std::wistringstream istr(str); + double d; + + istr >> d >> std::ws; + int afterNumPos = (strSize - istr.rdbuf()->in_avail()); + if(istr.bad() || istr.fail()) { + throw ParseError(L"Not a number.", str, afterNumPos); + } + + if(d < 0 || !isfinite(d)) { + throw ParseError(L"Not a valid time period.", str, afterNumPos); + } + + if(!istr.eof()) { + std::wstring unit; + istr >> unit >> std::ws; + + if(unit == L"s") { + d *= 1e9; + + } else if(unit == L"ms") { + d *= 1e6; + + } else if(unit == L"us") { + d *= 1e3; + + } else if(unit == L"ns") { + // nothing to do + + } else { + throw ParseError(L"Unrecognised unit.", str, afterNumPos); + } + + if(!istr.eof()) { + throw ParseError(L"Data after unit.", str, + (strSize - istr.rdbuf()->in_avail())); + } + } + + return ElapsedTime((uint64_t)(d), true); +} + + + +ElapsedTime::ElapsedTime(struct timeval* tv) + : time(tv->tv_sec * tenExp9 + tv->tv_usec * tenExp3) +{ + if(tv->tv_sec < 0 || tv->tv_usec < 0 || tv->tv_usec >= tenExp6) { + throw BadTimeValue(); + } +} + + + +ElapsedTime::ElapsedTime(struct timespec* ts) + : time(ts->tv_sec * tenExp9 + ts->tv_nsec) +{ + if(ts->tv_sec < 0 || ts->tv_nsec < 0 || ts->tv_nsec >= tenExp9) { + throw BadTimeValue(); + } +} + + + +std::wostream& operator<<(std::wostream& ostr, const ElapsedTime& t) +{ + ostr << t.toString(); + return ostr; +} + + + +} diff --git a/src/liblw-support/Time/ElapsedTime.h b/src/liblw-support/Time/ElapsedTime.h new file mode 100644 index 0000000..188188b --- /dev/null +++ b/src/liblw-support/Time/ElapsedTime.h @@ -0,0 +1,394 @@ +/* lw-support/src/lib/Time/ElapsedTime.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Integer constant: 1e9. +const int64_t tenExp9 = 1000000000; + +/// Integer constant: 1e6. +const int64_t tenExp6 = 1000000; + +/// Integer constant: 1e3. +const int64_t tenExp3 = 1000; + + + +/*! \brief An elapsed time period. + +This class is used to represent an elapsed time period. It has a +resolution of nanoseconds, and uses a 64-bit integer (thus providing +a maximum representable period of 1.84e10 seconds, or about 584 years). +It is intended as a direct replacement for the C library's struct +timeval and struct timespec. It cannot represent negative +time periods. + +\sa lw::StopWatch, lw::DayTime + +*/ +class ElapsedTime { +private: + uint64_t time; + + // constructor with dummy argument to avoid collisions + explicit ElapsedTime(uint64_t ns, bool) + : time(ns) + { } + +public: + //BEGIN // Constructors //////////////////////////////////////////// + + /*! \brief Default constructor. + + \param s The number of seconds (must be >= 0) + \param ms The number of milliseconds (must be >= 0 and < 1000) + \param us The number of microseconds (must be >= 0 and < 1000) + \param ns The number of nanoseconds (must be >= 0 and < 1000) + \throws BadTimeValue if any one of the arguments is out of range. + + Constructs an elapsed time period. This constructor is designed for + intuitive use -- the more arguments you specify, the more accurate + the result. Seconds are the most significant argument. + + */ + ElapsedTime(int s = 0, int ms = 0, int us = 0, int ns = 0) + : time(s * tenExp9 + ms * tenExp6 + us * tenExp3 + ns) + { + if(s < 0 || ms < 0 || ms >= tenExp3 || us < 0 || us >= tenExp3 + || ns < 0 || ns >= tenExp3) + { + throw BadTimeValue(); + } + } + + /*! \brief Construct from floating point value in seconds. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + explicit ElapsedTime(long double s) + : time(uint64_t(s * tenExp9)) + { + if(s < 0) throw BadTimeValue(); + } + + /*! \brief Construct from floating point value in seconds. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + explicit ElapsedTime(double s) + : time(uint64_t(s * tenExp9)) + { + if(s < 0) throw BadTimeValue(); + } + + /*! \brief Construct from floating point value in seconds. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + explicit ElapsedTime(float s) + : time(uint64_t(s * tenExp9)) + { + if(s < 0) throw BadTimeValue(); + } + + + + /// Copy constructor. + ElapsedTime(const ElapsedTime& other) + : time(other.time) + { } + + + + /*! \brief Construct from a struct timeval. + + \param tv The timeval to construct from. + \throws BadTimeValue if \a tv's members are out of range. + + */ + ElapsedTime(struct timeval* tv); + + /*! \brief Construct from a struct spec. + + \param ts The timespec to construct from. + \throws BadTimeValue if \a ts's members are out of range. + + */ + ElapsedTime(struct timespec* ts); + + + + /// Construct from nanoseconds. + static ElapsedTime from_ns(uint64_t ns) + { return ElapsedTime(ns, true); } + + /// Explicitly construct from microseconds. + static ElapsedTime from_us(uint64_t us) + { return ElapsedTime(us * tenExp3, true); } + + /// Explicitly construct from milliseconds. + static ElapsedTime from_ms(uint64_t ms) + { return ElapsedTime(ms * tenExp6, true); } + + + + //BEGIN // Query functions ///////////////////////////////////////// + + /// Returns true if the time period is zero. + inline bool isZero() const { return !time; } + + /// Returns the time in nanoseconds. + uint64_t to_ns() const { return time; } + + /// Returns the time in microseconds. + uint64_t to_us() const { return time / tenExp3; } + + /// Returns the time in milliseconds. + uint64_t to_ms() const { return time / tenExp6; } + + /// Returns the time in seconds. + uint64_t to_s() const { return time / tenExp9; } + + /// Returns fractional time. + long double toLongDouble() const { return time / 1e9; } + + /// Returns fractional time. + double toDouble() const { return time / 1e9; } + + /// Returns fractional time. + float toFloat() const { return time / 1e9; } + + + + //BEGIN // Set functions /////////////////////////////////////////// + + /// Assignment operator. + ElapsedTime& operator=(const ElapsedTime& other) + { + time = other.time; + return *this; + } + + /// Clears the elapsed time. + ElapsedTime clear() + { + time = 0; + return *this; + } + + /// Sets the elapsed time (nanoseconds). + ElapsedTime set_ns(uint64_t ns) + { + time = ns; + return *this; + } + + /// Sets the elapsed time (microseconds). + ElapsedTime set_us(uint64_t us) + { + time = us * tenExp3; + return *this; + } + + /// Sets the elapsed time (milliseconds). + ElapsedTime set_ms(uint64_t ms) + { + time = ms * tenExp6; + return *this; + } + + /// Sets the elapsed time (seconds). + ElapsedTime set_s(uint64_t s) + { + time = s * tenExp9; + return *this; + } + + + + /*! \brief Sets the elapsed time from a fraction. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + ElapsedTime set(long double s) + { + if(s < 0) throw BadTimeValue(); + time = (uint64_t)s; + return *this; + } + + /*! \brief Sets the elapsed time from a fraction. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + ElapsedTime set(double s) + { + if(s < 0) throw BadTimeValue(); + time = (uint64_t)s; + return *this; + } + + /*! \brief Sets the elapsed time from a fraction. + + \param s The fractional number of seconds. + \throws BadTimeValue if \a s is negative. + + Rounds the fractional time to the nearest nanosecond. + + */ + ElapsedTime set(float s) + { + if(s < 0) throw BadTimeValue(); + time = (uint64_t)s; + return *this; + } + + + + //BEGIN // String Routines ///////////////////////////////////////// + + /*! \brief Returns a std::string representation of the elapsed time. + + \param decPlaces The number of decimal places. + \param exp True if you want exponential (scientific) notation. + + This function will return a std::string representation of an elapsed time + period. The formatting can be specified. Units (s, ms, us, + ns) will be printed after the number. In scientific notation, + the unit is always seconds. + + */ + std::wstring toString(int decPlaces = 1, bool exp = false) const; + + + + /*! \brief Constructs a time period from a std::string representation. + + \param str The std::string to interpret. + \throws ParseError if a parsing error occurs. + + This function will try to interpret a std::string as a time period. It + accepts an integral or floating point number (possibly in C's + exponential notation), possibly followed by a unit. Acceptable + units are s, ms, us, ns. If omitted, seconds are assumed. + Any whitespace in the std::string will be ignored. + + */ + static ElapsedTime fromString(const std::wstring& str); + + + + //BEGIN // Operators (comparison) ////////////////////////////////// + + /// Returns true if the time period is zero. + inline bool operator!() const { return !time; } + + /// Returns true if the time period is non-zero. + inline operator bool() const { return time; } + + /// Returns true if two time periods are equal. + inline bool operator==(const ElapsedTime& other) const + { return time == other.time; } + + /// Returns true if two time periods are not equal. + inline bool operator!=(const ElapsedTime& other) const + { return time != other.time; } + + /// Less than comparison. + inline bool operator<(const ElapsedTime& other) const + { return time < other.time; } + + /// Less than or equal to comparison. + inline bool operator<=(const ElapsedTime& other) const + { return time <= other.time; } + + /// Greater than comparison. + inline bool operator>(const ElapsedTime& other) const + { return time > other.time; } + + /// Greater than or equal to comparison. + inline bool operator>=(const ElapsedTime& other) const + { return time >= other.time; } + + + + //BEGIN // Operators (arithmetic) ////////////////////////////////// + + /// Addition operation. + inline ElapsedTime& operator+=(const ElapsedTime& other) + { + time += other.time; + return *this; + } + + /*! \brief Subtraction operation. + + \param other The time period to subtract from this one. + \throws BadTimeOverflow if \a other is a greater time period than + \a this + \returns A reference to the newly-modified object. + + */ + inline ElapsedTime& operator-=(const ElapsedTime& other) + { + if(other.time > time) + throw BadTimeOverflow(); + time -= other.time; + return *this; + } + + /// Addition operation. + inline ElapsedTime operator+(const ElapsedTime& other) const + { + return ElapsedTime(time + other.time, true); + } + + /*! \brief Subtraction operation. + + \param other The time period to subtract from this one. + \throws BadTimeOverflow if \a other is a greater time period than + \a this + \returns The time period difference. + + */ + inline ElapsedTime operator-(const ElapsedTime& other) const + { + if(other.time > time) + throw BadTimeOverflow(); + return ElapsedTime(time - other.time, true); + } +}; + + + +/// Stream insertion operator. +std::wostream& operator<<(std::wostream&, const ElapsedTime&); + + + +} diff --git a/src/liblw-support/Time/StopWatch.cpp b/src/liblw-support/Time/StopWatch.cpp new file mode 100644 index 0000000..9751dbf --- /dev/null +++ b/src/liblw-support/Time/StopWatch.cpp @@ -0,0 +1,45 @@ +/* lw-support/src/lib/Time/StopWatch.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +StopWatch::StopWatch() + : hasStarted(true) +{ + if(clock_gettime(CLOCK_REALTIME, &started)) throw SystemError(); +} + + + +void StopWatch::start() +{ + hasStarted = true; + if(clock_gettime(CLOCK_REALTIME, &started)) throw SystemError(); +} + + + +void StopWatch::stop() +{ + hasStarted = false; + if(clock_gettime(CLOCK_REALTIME, &stopped)) throw SystemError(); +} + + + +ElapsedTime StopWatch::elapsed() const +{ + if(hasStarted && clock_gettime(CLOCK_REALTIME, &stopped)) + throw SystemError(); + return ElapsedTime::from_ns((uint64_t)((stopped.tv_sec - started.tv_sec) * tenExp9 + + (stopped.tv_nsec - started.tv_nsec))); +} + + + +} diff --git a/src/liblw-support/Time/StopWatch.h b/src/liblw-support/Time/StopWatch.h new file mode 100644 index 0000000..c69e95a --- /dev/null +++ b/src/liblw-support/Time/StopWatch.h @@ -0,0 +1,50 @@ +/* lw-support/src/lib/Time/StopWatch.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Stop watch timer class. + +This is a simple stopwatch class, which uses the ElapsedTime class to +record elapsed time to the nearest nanosecond (ostensibly; this depends +on operating system support). + +\sa ElapsedTime + +*/ +class StopWatch { +private: + struct timespec started; + mutable struct timespec stopped; + bool hasStarted; + +public: + /// Constructor. Starts timer. + StopWatch(); + + /// Starts (or restarts) the timer. + void start(); + + /// Stops the timer. + void stop(); + + /*! \brief Retrieves elapsed time. + + \returns The elapsed time. + + If the timer is currently running, it returns the time elapsed since + it was started. If the timer is stopped, it returns the time elapsed + between starting (or restarting) and stopping the timer. + + */ + ElapsedTime elapsed() const; +}; + + + +} diff --git a/src/liblw-support/TopHeader.h b/src/liblw-support/TopHeader.h index 455072a..50f7a6e 100644 --- a/src/liblw-support/TopHeader.h +++ b/src/liblw-support/TopHeader.h @@ -1,16 +1,32 @@ -/* lw-support/src/liblw-support/TopHeader.h +/* lw-support/src/lib/TopHeader.h * - * (c)2006, Laurence Withers, . - * Released under the GNU GPLv2. See file COPYING or - * http://www.gnu.org/copyleft/gpl.html for details. + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. */ -#ifndef HEADER_liblw_support -#define HEADER_liblw_support +// This file contains the include guard, and also any includes that are +// needed for the header file itself. Includes that are only needed in +// the source go into TopSource.cpp + +#ifndef HEADER_LW_Support +#define HEADER_LW_Support + + // standard includes, or includes needed for type declarations +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -/* options for text editors -kate: replace-trailing-space-save true; space-indent true; tab-width 4; -vim: expandtab:ts=4:sw=4 -*/ +#include +#include + +#include diff --git a/src/liblw-support/TopSource.cpp b/src/liblw-support/TopSource.cpp index 8879a3c..45d1d1a 100644 --- a/src/liblw-support/TopSource.cpp +++ b/src/liblw-support/TopSource.cpp @@ -1,15 +1,36 @@ -/* lw-support/src/liblw-support/TopSource.cpp +/* lw-support/src/lib/TopSource.cpp * - * (c)2006, Laurence Withers, . - * Released under the GNU GPLv2. See file COPYING or - * http://www.gnu.org/copyleft/gpl.html for details. + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. */ #include "lw-support" // Below are all the includes used throughout the library. -/* options for text editors -kate: replace-trailing-space-save true; space-indent true; tab-width 4; -vim: expandtab:ts=4:sw=4 -*/ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// external library includes + +// Some useful, universal constants. + +#define pipeRead 0 +#define pipeWrite 1 diff --git a/src/liblw-support/Util/Completion.cpp b/src/liblw-support/Util/Completion.cpp new file mode 100644 index 0000000..49c8764 --- /dev/null +++ b/src/liblw-support/Util/Completion.cpp @@ -0,0 +1,159 @@ +/* lw-support/src/lib/Util/Completion.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +CompletionTask::CompletionTask(CompletionCallback* callback) + : callback(callback) +{ +} + + + +CompletionTask::~CompletionTask() +{ + if(callback) { + try { + callback->aborted(this, lw::Exception(L"Task still active when destructor called.")); + } + catch(...) { } + } +} + + + +void CompletionTask::taskCompleted() +{ + CompletionCallback* cb = callback; + if(!callback) throw TaskAlreadyCompleted(); + callback = 0; + cb->completed(this); +} + + + +void CompletionTask::taskAborted(const Exception& e) +{ + CompletionCallback* cb = callback; + if(!callback) throw TaskAlreadyCompleted(); + callback = 0; + cb->aborted(this, e); +} + + + +CompletionList::CompletionList() + : abortingTask(0) +{ +} + + + +CompletionList::~CompletionList() +{ + abortAll(); +} + + + +void CompletionList::abortAll() +{ + while(!tasks.empty()) { + abortingTask = tasks.front(); + tasks.pop_front(); + try { + abortingTask->taskAbort(lw::Exception(L"Task list shutdown.")); + } + catch(...) { } + if(abortingTask) dbg_taskNotAborted(abortingTask); + } +} + + + +void CompletionList::registerTask(CompletionTask* task) +{ + NullPointer::check(task, L"task", L"CompletionList::registerTask()"); + tasks.push_back(task); +} + + + +bool CompletionList::empty() +{ + return tasks.empty(); +} + + + +void CompletionList::completed(CompletionData* data) +{ + CompletionTask* task = dynamic_cast(data); + if(!task) { + dbg_dataNotTask(data); + return; + } + taskIter iter = find(tasks.begin(), tasks.end(), data); + if(iter == tasks.end()) { + dbg_taskNotActive(task); + return; + } + tasks.erase(iter); +} + + + +void CompletionList::aborted(CompletionData* data, const lw::Exception&) +{ + if(abortingTask == data) abortingTask = 0; + completed(data); +} + + + +void CompletionList::dbg_dataNotTask(CompletionData* data) +{ + stderr << lw::Log::start(L"[DEBUG] CompletionList") + << L"Data passed to CompletionList::completed() or aborted() was not derived from\n" + << L"CompletionTask, as required." + << L"\n this = " + << this + << L"\n data = " + << data + << lw::Log::end; +} + + + +void CompletionList::dbg_taskNotAborted(CompletionTask* task) +{ + stderr << lw::Log::start(L"[DEBUG] CompletionList") + << L"Task not aborted." + << L"\n this = " + << this + << L"\n task = " + << task + << lw::Log::end; +} + + + +void CompletionList::dbg_taskNotActive(CompletionTask* task) +{ + stderr << lw::Log::start(L"[DEBUG] CompletionList") + << L"Task passed to CompletionList::completed() or aborted() not active." + << L"\n this = " + << this + << L"\n task = " + << task + << lw::Log::end; +} + + + +} diff --git a/src/liblw-support/Util/Completion.h b/src/liblw-support/Util/Completion.h new file mode 100644 index 0000000..d06b065 --- /dev/null +++ b/src/liblw-support/Util/Completion.h @@ -0,0 +1,257 @@ +/* lw-support/src/lib/Util/Completion.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/// Task completion callback data base class. +class CompletionData { +public: + /// Destructor (does nothing). + virtual ~CompletionData() + { } +}; + + + +/*! \brief Completion callback interface. + +Certain tasks use a callback to signal when they have been completed. This interface supports this +by providing a common callback function. To implement this, you derive your signal's target class +from lw::CompletionCallback and override the two functions. + +The task class must take a pointer to a CompletionCallback instance as a parameter. When its task is +done, it will call the completed() function; if the task must be stopped early, it will call the +aborted() function. This function also provides an indication of what caused the task to abort. The +task class may be derived from CompletionTask, but this is not a requirement. + +It is possible to associate arbitrary data with the task. This must be generated in or passed to the +task object. It will then be passed as the \a data parameter to the callback function. The data +%class must be derived from the lw::CompletionData class. + +*/ +class CompletionCallback { +public: + /// Destructor (does nothing). + virtual ~CompletionCallback() + { } + + + + /*! \brief Task complete signal. + + \param data Arbitrary data associated with the task. + + This function is called to signal that the task has been completed, presumably successfully. + If any data was associated with the task, it will be passed in through the \a data pointer, + although it would be valid for this to be zero. + + */ + virtual void completed(CompletionData* data) = 0; + + + + /*! \brief Task aborted signal. + + \param data Arbitrary data associated with the task. + \param e The exception that caused the task to abort. + + This function is called to signal that the task has been aborted without completing. + If any data was associated with the task, it will be passed in through the \a data pointer, + although it would be valid for this to be zero. + + */ + virtual void aborted(CompletionData* data, const lw::Exception& e) = 0; +}; + + + +/// Task already completed. +class TaskAlreadyCompleted : public lw::ProgramException { }; + + + +/*! \brief Completion task suggested interface. + +This class is the suggested (but not required) interface for a task that uses the completion +notifier interface provided by CompletionCallback. It is a safe and effective way of ensuring that +all required completion events are generated and only one event is ever generated. It is also the +required interface for the CompletionList interface. + +Instances of this class will pass a pointer to themselves as the \a data parameter to the callback +functions. If the callback class throws an exception, this will be propagated back to the function +that generated the signal (or discarded if it was the destructor). + +*/ +class CompletionTask : public CompletionData { +private: + CompletionCallback* callback; + +protected: + /*! \brief Report task completed. + + \throws lw::TaskAlreadyCompleted if the task is already done. + + This function will generate the signal to the callback class. The data passed will be a pointer + to the instance itself. Once signalled, no further signal can be sent. Any attempt to send more + than one signal will result in an lw::TaskAlreadyCompleted exception. + + \sa lw::CompletionCallback::completed() + + */ + void taskCompleted(); + + + + /*! \brief Report task aborted. + + \param e Exception giving reason for task abort. + \throws lw::TaskAlreadyCompleted if the task is already done. + + This function will generate the signal to the callback class. The data passed will be a pointer + to the instance itself. Once signalled, no further signal can be sent. Any attempt to send more + than one signal will result in an lw::TaskAlreadyCompleted exception. + + \sa lw::CompletionCallback::completed() + + */ + void taskAborted(const lw::Exception& e); + + + +public: + /*! \brief Constructor. + + \param callback The callback object. + \throws lw::NullPointer if \a callback is zero. + + Stores the callback object for later signalling. + + */ + CompletionTask(CompletionCallback* callback); + + + + /*! \brief Destructor. Aborts task if not completed. + + The destructor will check to see if the task has been completed or not. If it has, then it will + take no action; if it has not, the task will be aborted with a TaskAborted exception. + + */ + virtual ~CompletionTask(); + + + + /*! \brief Abort the task. + + \param e Exception giving reason for task abort. + \throws lw::TaskAlreadyCompleted if the task is already done. + + This function is called by the destructor if the task is still in progress when the object is + destroyed. It may also be made available to other functions in your class and potentially users + of your class (lw::CompletionList for example). + + On calling this function, the task must be aborted and the taskAborted() signal must be sent + before returning from the function. + + */ + virtual void taskAbort(const lw::Exception& e) = 0; +}; + + + +/*! \brief List of tasks to be completed. + +This class is a useful utility which maintains a list of tasks in progress. As each task is +completed or aborted, it is removed from the list. When the object is destroyed, or when abortAll() +is called, it will call the lw::CompletionTask::taskAbort() function for every task still in the +list. + +This class is not thread-safe. A set of three debug functions are provided to cater for +unexpected conditions. dbg_taskNotAborted() is called if lw::CompletionTask::abortTask() does not +result in an immediate call to completed() or aborted(). dbg_dataNotTask() is called if completed() +or aborted() are passed a CompletionData pointer that is not a CompletionTask pointer. +dbg_taskNotActive() is called if completed() or aborted() are passed a CompletionTask pointer that +is not in the active task list. Their default behaviour is to print an error message through +lw::stderr, but they can be overridden. + +\todo We use an unsorted list here; sort it. + +*/ +class CompletionList : public CompletionCallback, + private Uncopyable +{ +private: + std::list tasks; + CompletionTask* abortingTask; + typedef std::list::iterator taskIter; + +protected: + /// Debug function. + virtual void dbg_taskNotAborted(CompletionTask* task); + /// Debug function. + virtual void dbg_dataNotTask(CompletionData* data); + /// Debug function. + virtual void dbg_taskNotActive(CompletionTask* task); + +public: + /// Constructor. + CompletionList(); + + + + /*! \brief Destructor. Aborts all active tasks. + + The destructor will abort all active tasks by calling abortAll() if necessary. + + */ + virtual ~CompletionList(); + + + + /*! \brief Register a new task. + + \param task The task to be completed. + \throws lw::NullPointer if \a task is zero. + + Registers a new task for completion. The task must have been instantiated to use this + list instance as its callback (or at least the callback signals must reach this object). If the + task is still active when the list instance is destroyed, or abortAll() is called, the task's + own abort function will be called. + + \note There is no guarding against adding the same task twice. Doing so will break things. + + */ + void registerTask(CompletionTask* task); + + + + /*! \brief Abort all active tasks. + + Attempts to abort all active tasks by calling lw::CompletionTask::abortTask() for each such + task. Any exception thrown from this function will be ignored; however, if there are any tasks + left unregistered at the end of the function, dbg_taskNotAborted() will be called for each + remaining task. + + */ + void abortAll(); + + + + /// Returns true if there are no active tasks. + bool empty(); + + + + // implemented virtuals from lw::CompletionCallback + virtual void completed(CompletionData* data); + virtual void aborted(CompletionData* data, const lw::Exception& e); +}; + + + +} diff --git a/src/liblw-support/Util/FIFO.cpp b/src/liblw-support/Util/FIFO.cpp new file mode 100644 index 0000000..8b8b7b9 --- /dev/null +++ b/src/liblw-support/Util/FIFO.cpp @@ -0,0 +1,43 @@ +/* lw-support/src/lib/Util/FIFO.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +void FIFO::reallocBuffer(size_t required) +{ + // sanity checks + if(required < bufferSize) return; + if(required > maxSize) throw FIFOOverflow(); + + // try to reduce frequency of small allocations + required = std::min(required + 128, maxSize); + + // try to allocate memory + char* newBuffer = 0; + try { + newBuffer = new char[required]; + } + catch(std::bad_alloc&) { + throw FIFOOutOfMemory(required); + } + + // copy across old data + if(data) { + memcpy(newBuffer, data, dataSize); + data = newBuffer; + } + + // free old memory + delete [] buffer; + buffer = newBuffer; + bufferSize = required; +} + + + +} diff --git a/src/liblw-support/Util/FIFO.h b/src/liblw-support/Util/FIFO.h new file mode 100644 index 0000000..a88e6fe --- /dev/null +++ b/src/liblw-support/Util/FIFO.h @@ -0,0 +1,329 @@ +/* lw-support/src/lib/Util/FIFO.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief FIFO memory buffer. + +This class is an implementation of a FIFO memory buffer. It has an +absolute maximum upper limit on how much memory has been consumed, and +you can specify how much of that limit is initially allocated. The +functions are all inline for speed. + +*/ +class FIFO { +private: + // These refer to the block of memory that is allocated. + char* buffer; + size_t bufferSize, maxSize; + + // These refer to the data in the buffer. + char* data; // set to zero if dataSize == 0 + size_t dataSize; + + // Reallocate buffer ('required' is the new total buffer size that + // is needed). + void reallocBuffer(size_t required); + + // Fast realloc check, also moves data if that would save an + // allocation. + inline void ensureBuffer(size_t required) + { + if(required > bufferSize) { + reallocBuffer(required); + return; + } + + if(data && (data + required) > (buffer + bufferSize)) { + memmove(buffer, data, dataSize); + data = buffer; + return; + } + } + +public: + //BEGIN // Constructors etc. /////////////////////////////////////// + + /*! \brief Constructor. + + \param hint This hints at how much memory should be allocated. + \param maxSize The absolute maximum number of bytes to allocate. + + Sets up the FIFO buffer. The absolute maximum size must be specified + here (it can be changed later). A hint as to how much memory to + initially allocate must also be provided. + + */ + FIFO(size_t hint, size_t maxSize) + : buffer((hint && maxSize) ? new char[std::min(hint, maxSize)] : 0), + bufferSize(hint), maxSize(maxSize), + data(0), dataSize(0) + { } + + /// Destructor (frees allocated memory). + ~FIFO() + { delete [] buffer; } + + + + /*! \brief Change absolute maximum size. + + \param newMaxSize The buffer's new absolute maximum size in bytes. + \throws FIFOOverflow if the buffer currently contains more than + \a newMaxSize bytes. + + This function will change the upper size limit of the FIFO memory + buffer. It can be used to make the buffer larger at any time. It can + also be used to make the buffer smaller (possibly freeing up memory + in the process); but you cannot make the buffer so small that its + contents no longer fit. + + */ + inline void setMaxSize(size_t newMaxSize) + { + if(newMaxSize < dataSize) throw FIFOOverflow(); + maxSize = newMaxSize; + + // might have to reallocate the buffer if it is too big + if(bufferSize > newMaxSize) reallocBuffer(newMaxSize); + } + + + + /// Query the absolute maximum size. + inline size_t getMaxSize() const + { + return maxSize; + } + + + + //BEGIN // Buffering functions ///////////////////////////////////// + + /*! \brief Gets ready to add some data. + + \param amt The maximum amount of data the buffer will be required to + accept. + \returns A pointer to the memory location at which the data should + be written. + \throws FIFOOverflow if the buffer is too small. + + This function ensures that the buffer has enough space to accept at + least \a amt bytes of additional data. This might require allocating + more memory or moving the current buffer data around. If you call + this twice in a row, then the first call is totally forgotten. It + doesn't change the object's internal state; it just makes sure that + you can add \a amt bytes if you want to, and tells you where. + + \sa addedData() + + */ + inline char* getBuffer(size_t amt) + { + ensureBuffer(amt + dataSize); + return data ? data + dataSize : buffer; + } + + + + /*! \brief Records how much data was added to the buffer. + + \param amt The amount of data that was actually added to the buffer. + + Once you have prepared the buffer with getBuffer(), you must add the + data. This function records how much data was added (so that new + data will be added after it). + + \warning No checking is done on \a amt, so make sure you put the + right amount. Also make sure that getBuffer() and addedData() + calls occur in pairs. + + \sa getBuffer(), addData() + + */ + inline void addedData(size_t amt) + { + if(!data) data = buffer; + dataSize += amt; + } + + + + /*! \brief Adds some data to the buffer. + + \param amt The number of bytes to add. + \param newData The data to add. + \throws FIFOOverflow if the buffer is too small. + + Adds a specific block of data to the buffer. This can only be used + if the data is coming from somewhere else in memory (e.g. an + mmap()ed file), and you know in advance how much there is. + + \sa addedData() + + */ + inline void addData(const char* newData, size_t amt) + { + ensureBuffer(amt + dataSize); + if(data) { + memcpy(data + dataSize, newData, amt); + dataSize += amt; + } else { + memcpy(data = buffer, newData, amt); + dataSize = amt; + } + } + + + + /*! \brief Adds a byte of data to the buffer. + + \param byte The byte of data to add. + \throws FIFOOverflow if the buffer is too small. + + Adds a single byte of data to the buffer. This is the most efficient + method for byte-oriented buffering. + + */ + inline void addByte(char byte) + { + ensureBuffer(dataSize + 1); + if(data) { + data[dataSize++] = byte; + } else { + *(data = buffer) = byte; + dataSize = 1; + } + } + + + + //BEGIN // Reading functions /////////////////////////////////////// + + /// Returns how many bytes of data there are in the buffer. + inline size_t getSize() const + { + return dataSize; + } + + /// Returns how many bytes of data there are in the buffer. + inline size_t size() const + { + return dataSize; + } + + + + /// Returns how many bytes are available to the buffer. + inline size_t getRemaining() const + { + return maxSize - dataSize; + } + + + + /// Returns how many (preallocated) bytes are available to the buffer. + inline size_t getRemainingPreallocated() const + { + return bufferSize - dataSize; + } + + + + /// Returns pointer to start of buffer (0 if empty). + inline const char* getData() const + { + return data; + } + + + + /*! \brief Retrieves some data from the buffer. + + \param amt The number of bytes to retrieve. + \throws FIFOUnderflow if the buffer contains less than \a amt bytes. + \returns A pointer to the start of the data (0 if \a amt = 0). + + This function allows you to get data out of the buffer. It will + record how many bytes were used, so that space will be free the next + time anyone wants to use it. + + \warning The pointer returned may become invalid as soon as you + perform any other operation on the FIFO, so you must finish + using the data it points to before calling any other functions. + + \sa getSize() + + */ + inline char* getData(size_t amt) + { + // sanity checking + if(!amt) return 0; + if(amt > dataSize) throw FIFOUnderflow(); + + // eat data + char* result = data; + if(!(dataSize -= amt)) data = 0; + else data += amt; + return result; + } + + + + /*! \brief Gets a single byte of data from the buffer. + + \returns The byte of data removed from the buffer. + \throws FIFOUnderflow if the buffer is empty. + \returns A single byte of data. + + Retrieves a single byte of data from the buffer. This is the most + efficient method for byte-oriented buffering. + + */ + inline char getByte() + { + if(!dataSize) throw FIFOUnderflow(); + char byte = *data; + if(!--dataSize) data = 0; + else ++data; + return byte; + } + + + + //BEGIN // Convenience functions /////////////////////////////////// + + /// Returns true if the buffer is empty. + inline bool isEmpty() const + { + return !dataSize; + } + + /// Returns true if the buffer is empty. + inline bool empty() const + { + return !dataSize; + } + + /// Returns true if the buffer is full. + inline bool isFull() const + { + return dataSize == maxSize; + } + + /// Empties the buffer. + inline void clear() + { + data = 0; + dataSize = 0; + } +}; + + + +} diff --git a/src/liblw-support/Util/Key.cpp b/src/liblw-support/Util/Key.cpp new file mode 100644 index 0000000..66c3d79 --- /dev/null +++ b/src/liblw-support/Util/Key.cpp @@ -0,0 +1,38 @@ +/* lw-support/src/lib/Util/Key.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + +Key::keyType Key::globalKey = 0; + + + +std::wstring Key::toString() const +{ + std::wostringstream str; + + str << L"{KEY{!" + << key + << L"}}"; + + return str.str(); +} + + + +lw::Key Key::fromString(const std::wstring& strIn) +{ + std::wstring str = stripWhitespace(strIn); + std::wstring::size_type end = str.size(); + + if(end < 9 || str.substr(0, 6) != L"!KEY{!" || str.substr(end - 2, 2) != L"}}") + throw ParseError(L"Not a valid key.", str, 0); + return Key(strToUInt64(str.substr(6, end - 8))); +} + + + +} diff --git a/src/liblw-support/Util/Key.h b/src/liblw-support/Util/Key.h new file mode 100644 index 0000000..46fa665 --- /dev/null +++ b/src/liblw-support/Util/Key.h @@ -0,0 +1,99 @@ +/* lw-support/src/lib/Util/Key.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \brief Key object. + +The key object is used to store some form of identifying key. Typically this will be used to store a +unique key number. + +\note We use a 64-bit integer to store the key. It is somewhat conceivable that this could be + wrapped around at some point. If you're really generating keys that frequently, don't use this + class and instead write a 128-bit or wider class. + +*/ +class Key { +public: + /// Constructor. Generates an invalid key. + Key() + : key(0) + { } + + /// Copy constructor. + Key(const Key& other) + : key(other.key) + { } + + /// Assignment operator. + Key& operator=(Key other) + { key = other.key; return *this; } + + /// Compares two keys for equality. + bool operator==(Key other) const + { return key == other.key; } + + /// Compares two keys for inequality. + bool operator!=(Key other) const + { return key != other.key; } + + /// Allows ordering by key. + bool operator<(Key other) const + { return key < other.key; } + + /// Returns status of key. + bool operator!() const + { return !key; } + + /// Returns status of key. + operator void*() const + { return key ? (void*)this : 0; } + + /// Returns status of key. + bool valid() const + { return key; } + + /// Makes the key invalid. + void clear() + { key = 0; } + + + + /*! \brief Represent the key as a string. + + \returns A string representing the value of the key. + + This function allows the key to be represented as a string. The string will always be started + with the sequence \c {KEY{ and it will always end with \c }} but its contents are implementation + defined. The string generated will consist only of ASCII characters and all characters will be + printable. + + */ + std::wstring toString() const; + + /// Construct a key from a string. + static lw::Key fromString(const std::wstring& str); + +private: + typedef uint_fast64_t keyType; + keyType key; + + // Explicitly construct a key from a value. + Key(keyType key) : key(key) { } + + static keyType globalKey; + +public: + /// Generate a program-wide unique key. + static Key get() + { ++globalKey; return Key(globalKey); } +}; + + + +} diff --git a/src/liblw-support/Util/Log.cpp b/src/liblw-support/Util/Log.cpp new file mode 100644 index 0000000..7adc574 --- /dev/null +++ b/src/liblw-support/Util/Log.cpp @@ -0,0 +1,355 @@ +/* lw-support/src/lib/Util/Log.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +Log stderr(std::wcerr, true); + + + +Log::Log(std::wostream& outStream, bool enabled) + : out(outStream), wstr(0), enabled(enabled), streamWidth(78), + dateExtended(true), dateDecimal(false), dateFormat(lw::ISO8601FormatCalendar), + datePrecision(lw::ISO8601PrecisionFull) +{ +} + + + +Log::Log(const std::string& fileName, bool enabled) + : out(file), wstr(0), enabled(enabled), streamWidth(78), + dateExtended(true), dateDecimal(false), dateFormat(lw::ISO8601FormatCalendar), + datePrecision(lw::ISO8601PrecisionFull) +{ + file.open(fileName.c_str()); + // FIXME: better exception required + if(!file) throw lw::FileNotFound(fileName.c_str()) + .chain(L"Log::Log()"); +} + + + +Log::~Log() +{ + delete wstr; +} + + + +void Log::checkBox() +{ + if(!wstr) throw BoxException(); +} + + + +Log& Log::setStreamWidth(size_t streamWidth) +{ + this->streamWidth = streamWidth; + return *this; +} + + + +Log& Log::setDateFormat(bool dateExtended, bool dateDecimal, + lw::ISO8601Format dateFormat, lw::ISO8601Precision datePrecision) +{ + this->dateExtended = dateExtended; + this->dateDecimal = dateDecimal; + this->dateFormat = dateFormat; + this->datePrecision = datePrecision; + return *this; +} + + + +Log& Log::startBox(const std::wstring& subject) +{ + if(wstr) throw BoxException(); + wstr = new std::wostringstream(); + + wstr->clear(); + *wstr << L"==== " + << DateTime::now().toString(dateExtended, dateDecimal, + dateFormat, datePrecision) + << L" ===="; + + int remaining = streamWidth - (wstr->str().length() + subject.length() + 6); + if(remaining > 0) *wstr << std::wstring(remaining, '='); + *wstr << L' ' + << subject + << L" ====\n\n"; + + return *this; +} + + + +Log& Log::enable(bool enabled) +{ + this->enabled = enabled; + return *this; +} + + + +Log& Log::disable() +{ + enabled = false; + return *this; +} + + + +Log& Log::endBox() +{ + checkBox(); + if(enabled) out << wstr->str() << L"\n\n" << std::endl; + delete wstr; + wstr = 0; + return *this; +} + + + +Log& Log::abortBox() +{ + delete wstr; + wstr = 0; + return *this; +} + + + +Log& Log::hexDumpData(const char* data, size_t amt) +{ + checkBox(); + + const size_t blocksPerLine = std::max(streamWidth / 34, size_t(1)), + charsPerLine = blocksPerLine * 8, + upperBlockSize = charsPerLine * ((amt + charsPerLine - 1) / charsPerLine); + size_t blocksThisLine = 0, lineStartPos = 0; + wchar_t ascii[charsPerLine + 1]; + ascii[charsPerLine] = 0; + + *wstr << L"Hex dump, " + << amt + << " bytes:\n" + << std::hex + << std::setfill(L'0'); + + for(size_t pos = 0; pos < amt; ++pos) { + uint8_t ch = data[pos]; + if(!(pos & 7)) { + *wstr << L' '; + if(blocksThisLine++ == blocksPerLine) { + blocksThisLine = 1; + *wstr << ascii << L"\n "; + lineStartPos = pos; + } + } + *wstr << std::setw(2) << uint(ch) << L' '; + ascii[pos - lineStartPos] = wchar_t(isprint(ch) ? ch : '.'); + } + + for(size_t pos = amt; pos < upperBlockSize; ++pos) { + if(!(pos & 7)) { + *wstr << L' '; + if(blocksThisLine++ == blocksPerLine) break; + } + *wstr << L" "; + ascii[pos - lineStartPos] = ' '; + } + + *wstr << L' ' << ascii << L'\n'; + return *this; +} + + + +Log& Log::asciiDumpData(const char* data, size_t amt) +{ + checkBox(); + + *wstr << L"- ASCII dump, " + << amt + << " bytes: -----------------\n" + << std::hex + << std::setfill(L'0'); + + for(size_t pos = 0; pos < amt; ++pos) { + switch(data[pos]) { + case '\r': + *wstr << L"\\r"; + continue; + + case '\n': + *wstr << L"\\n\n"; + continue; + + case '\t': + *wstr << L"\\t"; + continue; + } + + if(isprint(data[pos])) { + *wstr << wchar_t(data[pos]); + } else { + *wstr << L'<' + << std::setw(2) + << uint(uint8_t(data[pos])) + << L'>'; + } + } + + *wstr << L"\n- End of dump ---------------------\n"; + return *this; +} + + + +Log::LogManipArgStart Log::start(const std::wstring& subject) +{ + return LogManipArgStart(subject); +} + + + +Log::LogManipArgHex Log::hexDump(const char* data, size_t amt) +{ + return LogManipArgHex(data, amt); +} + + + +Log::LogManipArgAscii Log::asciiDump(const char* data, size_t amt) +{ + return LogManipArgAscii(data, amt); +} + + + +template<> + Log& operator<<(Log& log, const Log::LogManip& l) +{ + log.checkBox(); + + switch(l) { + case Log::end: + log.endBox(); + break; + + case Log::abort: + log.abortBox(); + break; + } + + return log; +} + + + +template<> + Log& operator<<(Log& log, const lw::Log::LogManipArgStart& l) +{ + log.startBox(l.subject); + return log; +} + + + + +template<> + Log& operator<<(Log& log, const lw::Log::LogManipArgHex& l) +{ + log.hexDumpData(l.data, l.amt); + return log; +} + + + +template<> + Log& operator<<(Log& log, const lw::Log::LogManipArgAscii& l) +{ + log.asciiDumpData(l.data, l.amt); + return log; +} + + + +template<> + Log& operator<<(Log& log, const char* const& str) +{ + if(!str) { + *(log.wstr) << L"{{{null}}}"; + return log; + } + + try { + // attempt UTF-8 conversion + *(log.wstr) << lw::utf8ToUcs4(str); + } + catch(...) { + // `safe' conversion + *(log.wstr) << L"{{{unknown encoding}}}"; + for(const char* q = str; *q; ++q) { + switch(*q) { + case '\n': + case '\t': + case ' ' ... '~': // 32 ... 126 + log.wstr->put(*q); + break; + + default: + log.wstr->put('?'); + } + } + } + + return log; +} + + + +template<> + Log& operator<<(Log& log, const std::string& str) +{ + return (log << str.c_str()); +} + + + +lw::Log& operator<<(lw::Log& log, const std::exception& e) +{ + return (log << e.what()); +} + + + +lw::Log& operator<<(lw::Log& log, const lw::Exception& e) +{ + std::cerr << L"[DEBUG] in lw::Exception-specific insertion operator\n"; + return (log << e.toString()); +} + + + +std::wostream& operator<<(std::wostream& ostr, const std::exception& e) +{ + ostr << lw::strToUcs4(e.what()); + return ostr; +} + + + +} + + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/liblw-support/Util/Log.h b/src/liblw-support/Util/Log.h new file mode 100644 index 0000000..6ffffc8 --- /dev/null +++ b/src/liblw-support/Util/Log.h @@ -0,0 +1,370 @@ +/* lw-support/src/lib/Util/Log.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +/*! \defgroup Log Logging functions. + +The logging functions provide a convenient way to log arbitrary data to an arbitrary output stream. +Log entries are broken into boxes and each entry is timestamped. The class that allows this is +lw::Log; an overloaded stream insertion operator allows you to treat it as an STL stream (and also +allows additional lw data types to be output to the stream). + +*/ +/*!@{*/ + + + +/*! \brief Overloaded stream insertion operator required for convenience. + +This operator will insert a std::exception into a std::ostream. Since it is safely ensconsed in the +lw namespace, it won't affect code outside that namespace. It is required because the template +function operator<< will call it when outputting a std::exception-derived class. + +*/ +std::wostream& operator<<(std::wostream&, const std::exception&); + + + +// forward declare of output functions +template + Log& operator<<(Log& log, const T& t); + + + +/*! \brief Log object. + +This object provides a simple and easy-to-use interface for logging to a +standard I/O stream. It uses the concept of a "message box". Each box +consists of a header (date, time and subject). Boxes are separated by +three blank lines. + +The object allows output of built-in types and any of the LW Support +objects that have a toString() method. It converts any const char* +or std::string objects into UCS-4 first -- it does this by first assuming that they are +UTF-8 and, if the conversion process fails, falls back to ASCII (substituting non-printable +characters with a question mark). + +Logs can be tied to any standard output stream. By default, logs are +turned off (the output is not produced); use enable() to turn on the +output, and disable() to turn it off again. You can have as many logs as +you like pointing to a single stream; messages are only printed once +they have been completely assembled. + +You must start a new box with startBox() (or the start manipulator). If +you are already in a box, a BoxException will be thrown. If you try to +log some output when not in a box, a BoxException will also be thrown. + +\note std::endl and std::flush are not supported. + +*/ +class Log : public lw::Uncopyable { +private: + // output streams + std::wofstream file; + std::wostream& out; + std::wostringstream* wstr; + bool enabled; + size_t streamWidth; + + // for checking if we are in a box + void checkBox(); + + // for formatting dates + bool dateExtended, dateDecimal; + lw::ISO8601Format dateFormat; + lw::ISO8601Precision datePrecision; + + // for STL-like output + template friend + Log& operator<<(Log& log, const T& t); + +public: + //BEGIN // Constructors etc. /////////////////////////////////////// + + + + /*! \brief Constructor (outputs to existing stream). + + \param outStream The output stream to send logs to. + \param enabled Whether or not the object is enabled as soon as it is + constructed; default is \a false. + + Sets up the logging object so that it will output to an existing + output stream. The default stream width is set to 78 characters. + + */ + Log(std::wostream& outStream, bool enabled = false); + + + + /*! \brief Constructor (opens a file). + + \param fileName The file to write to. + \param enabled Whether or not the object is enabled as soon as it is + constructed; default is \a false. + + \throws lw::Exception if the file cannot be opened. + + Sets up the logging object so that it will output to an existing + output stream. + + */ + Log(const std::string& fileName, bool enabled = false); + + + + /// Destructor. + ~Log(); + + + + //END // Constructors etc. ///////////////////////////////////////// + //BEGIN // Control functions /////////////////////////////////////// + + + + /// Thrown when a boxing error occurs. + class BoxException : public ProgramException { }; + + + + /// For funky STL-like manipulators (without arguments). + enum LogManip { + end, + abort + }; + + + + /// Set stream width (e.g. for screen). + Log& setStreamWidth(size_t streamWidth); + + /*! Set date conversion options. + + \param extended \a true (the default) selects ISO8601 extended + format; \a false selects ISO8601 basic. + \param decimalTime \a true if you want to print the time as a + decimal fraction; \a false (the default) if you don't. + Specifying full precision will get you decimal time anyway. + \param dateFormat The calendar format to use. See lw-support. + \param precision The precision to use. See lw-support. + + Sets options for formatting the date and time at the top of each + box. See lw::DateTime::toString() for full details. + + */ + Log& setDateFormat(bool extended = true, bool decimalTime = false, + lw::ISO8601Format dateFormat = lw::ISO8601FormatCalendar, + lw::ISO8601Precision precision = lw::ISO8601PrecisionFull); + + + + /// Enable output to stream. + Log& enable(bool enabled = true); + + /// Disable output to stream. + Log& disable(); + + /// Start a new box. + Log& startBox(const std::wstring& subject); + + /// End current box. + Log& endBox(); + + /// Abort current box. + Log& abortBox(); + + + + //END // Control functions ///////////////////////////////////////// + //BEGIN // Convenience functions /////////////////////////////////// + + + + /*! \brief Perform a hex dump. + + \param data Pointer to the data to write. + \param amt Number of bytes to write. + + Presents the data in a convenient, easy-to-read hex format. Prints + a newline after the data, but not before. Prints a header with the + block size in bytes. + + */ + Log& hexDumpData(const char* data, size_t amt); + + + + /*! \brief Perform an ASCII dump. + + \param data Pointer to the data to write. + \param amt Number of bytes to write. + + Prints ASCII data. It will show newlines and tabs with C-style \\r, + \\n and \\t sequences. Hex sequences are shown in angle brackets for + other non-printable characters. Prints + a newline after the data, but not before. Prints a header with the + block size in bytes. + + */ + Log& asciiDumpData(const char* data, size_t amt); + + + + //END // Convenience functions ///////////////////////////////////// + //BEGIN // Complex manipulators //////////////////////////////////// + + + + /// Manipulator object for starting a new box. + class LogManipArgStart { + const std::wstring& subject; + LogManipArgStart(const std::wstring& subject) + : subject(subject) + { } + + friend class Log; + template friend + Log& operator<<(Log& log, const T& t); + }; + + /// Start a new box (manipulator). + static LogManipArgStart start(const std::wstring& subject); + + + + /// Manipulator object for performing a hex dump. + class LogManipArgHex { + const char* data; + size_t amt; + LogManipArgHex(const char* data, size_t amt) + : data(data), amt(amt) + { } + + friend class Log; + template friend + Log& operator<<(Log& log, const T& t); + }; + + /*! \brief Perform a hex dump (manipulator). + + \param data Pointer to the data to write. + \param amt Number of bytes to write. + + Presents the data in a convenient, easy-to-read hex format. Prints + a newline after the data, but not before. Prints a header with the + block size in bytes. + + */ + static LogManipArgHex hexDump(const char* data, size_t amt); + + + + /// Manipulator object for performing an ASCII dump. + class LogManipArgAscii { + const char* data; + size_t amt; + LogManipArgAscii(const char* data, size_t amt) + : data(data), amt(amt) + { } + + friend class Log; + template friend + Log& operator<<(Log& log, const T& t); + }; + + /*! \brief Perform an ASCII dump (manipulator). + + \param data Pointer to the data to write. + \param amt Number of bytes to write. + + Prints ASCII data. It will show newlines and tabs with C-style \\r, + \\n and \\t sequences. Hex sequences are shown in angle brackets for + other non-printable characters. Prints + a newline after the data, but not before. Prints a header with the + block size in bytes. + + */ + static LogManipArgAscii asciiDump(const char* data, size_t amt); + + + + //END // Complex manipulators ////////////////////////////////////// +}; + + + +/*! \brief Standard error log. + +Some of the library functions may wish to log error conditions on stdout. They will do so through +this log object. This log object will point at std::wcerr and is on by default. It should only be +disabled if std::wcerr is closed. + +*/ +extern lw::Log stderr; + + + +/*! \brief STL-like output for lw::Log. + +This overloaded output operator provides STL-like output (stream insertion) for the lw::Log object. +Several specialised functions are provided to allow output of common lw objects. + +\param log The log object to output onto. +\param t The object to output. + +*/ +template + lw::Log& operator<<(lw::Log& log, const T& t) +{ + log.checkBox(); + *(log.wstr) << t; + return log; +} + + + +/// Stream insertion operator for lw::Log::LogManip. +template <> + lw::Log& operator<<(lw::Log&, const lw::Log::LogManip&); + + + +/// Stream insertion operator for lw::Log::start(). +template<> + lw::Log& operator<<(lw::Log&, const lw::Log::LogManipArgStart&); + + + +/// Stream insertion operator for lw::Log::hexDump(). +template<> + lw::Log& operator<<(lw::Log&, const lw::Log::LogManipArgHex&); + + + +/// Stream insertion operator for lw::Log::asciiDump(). +template<> + lw::Log& operator<<(lw::Log&, const lw::Log::LogManipArgAscii&); + + + +/// Stream insertion operator for an 8-bit string. +template<> + lw::Log& operator<<(lw::Log&, const char* const&); + + + +/// Stream insertion operator for an 8-bit string. +template<> + lw::Log& operator<<(lw::Log&, const std::string&); + + + +/*!@}*/ +} diff --git a/src/liblw-support/Util/Uncopyable.h b/src/liblw-support/Util/Uncopyable.h new file mode 100644 index 0000000..26c394e --- /dev/null +++ b/src/liblw-support/Util/Uncopyable.h @@ -0,0 +1,25 @@ +/* lw-support/src/lib/Util/Uncopyable.h + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +namespace lw { + + + +#ifndef DOXYGEN +class Uncopyable { +private: + Uncopyable(const Uncopyable&); + Uncopyable& operator=(const Uncopyable&); + +public: + Uncopyable() + { } +}; +#endif + + + +} diff --git a/src/liblw-support/build.monolithic b/src/liblw-support/build.monolithic index f748426..d74b57e 100644 --- a/src/liblw-support/build.monolithic +++ b/src/liblw-support/build.monolithic @@ -8,10 +8,35 @@ MONOLITHIC_TESTS="src/liblw-support/build.lib src/liblw-support/build.monolithic if [ -z "${liblw_support_MONOLITHIC}" ] then - MONOLITHIC_SOURCE="$(echo src/liblw-support/{TopHeader,BottomHeader}.h)" + MONOLITHIC_SOURCE="$(echo src/liblw-support/TopHeader.h) + $(echo src/liblw-support/Enums/{IOMode,IOTimeoutMode,ISO8601Format}.h) + $(echo src/liblw-support/ForwardDeclare.h) + $(echo src/liblw-support//{}.h) + $(echo src/liblw-support/Util/{Uncopyable,Key}.h) + $(echo src/liblw-support/Exceptions/{Exception,SystemError,Bad{Argument,String,Time},ParseError,IO,FIFO,HTTP,DNS,NullPointer}.h) + $(echo src/liblw-support/Util/{FIFO,Completion}.h) + $(echo src/liblw-support/String/{basics,strToInt}.h) + $(echo src/liblw-support/Time/{ElapsedTime,StopWatch,DayTime,Date,DateTime}.h) + $(echo src/liblw-support/Thread/{Mutex,MutexPadlock,Semaphore,CompletionNotifier,Thread}.h) + $(echo src/liblw-support/IO/{Interface,Filter,Device,PosixDevice,SerialPort,Pipe}.h) + $(echo src/liblw-support/Events/{Callback{IO,Timer,Update},EventManager}.h) + $(echo src/liblw-support/Net/{Server,ServerCallback,Client,Address/{Address,IPv4,IPv6},TCP/{Client,Server},Protocols/{HTTP,SMTP}}.h) + $(echo src/liblw-support/IO/Util/WriteTask.h) + $(echo src/liblw-support/lib/Util/Log.h) + $(echo src/liblw-support/BottomHeader.h)" make_monolithic ${HDR} C || return 1 - MONOLITHIC_SOURCE="$(echo src/liblw-support/TopSource.cpp)" + MONOLITHIC_SOURCE="$(echo src/liblw-support/TopSource.cpp) + $(echo src/liblw-support/Exceptions/{Exception,BadArgument,SystemError,ParseError,IO,FIFO,BadString,HTTP,DNS}.cpp) + $(echo src/liblw-support/String/{basics,strToInt}.cpp) + $(echo src/liblw-support/IO/{PosixDevice,Filter,SerialPort,Pipe}.cpp) + $(echo src/liblw-support/Time/{ElapsedTime,StopWatch,DayTime,DateTables,Date,DateTime}.cpp) + $(echo src/liblw-support/Thread/{Mutex,Semaphore,CompletionNotifier,Thread}.cpp) + $(echo src/liblw-support/Util/{FIFO,Log,Completion,Key}.cpp) + $(echo src/liblw-support/Net/{Server,ServerCallback,Client,Address/{Address,IPv4,IPv6},TCP/{Server,Client},Protocols/{HTTP,SMTP}}.cpp) + $(echo src/liblw-support/IO/Util/WriteTask.cpp) + $(echo src/liblw-support/Events/{EventManager,CallbackUpdate}.cpp) + " make_monolithic ${SRC} C || return 1 liblw_support_MONOLITHIC=1 diff --git a/src/tests/Event_Timer.cpp b/src/tests/Event_Timer.cpp new file mode 100644 index 0000000..e013945 --- /dev/null +++ b/src/tests/Event_Timer.cpp @@ -0,0 +1,183 @@ +/* lw-support/src/tests/Event_Timer.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" + +#include +#include + +// some log objects, for luck +lw::Log Log(std::wcout, true), Err(std::wcerr, true); + + + +// display callback object +class Display : private lw::EventCallbackTimer { +private: + class Counter : public lw::EventCallbackTimer { + private: + std::map counts; + lw::StopWatch stopWatch; + + std::wstring toHz(int counts, uint64_t ns_elapsed) + { + std::wostringstream str; + double freq = counts; + freq /= (ns_elapsed * 1e-9); + str << std::setprecision(3); + if(freq < 0.5e-3) str << (freq * 1e6) << "uHz"; + else if(freq < 0.5) str << (freq * 1e3) << "mHz"; + else if(freq < 0.5e3) str << (freq) << "Hz"; + else if(freq < 0.5e6) str << (freq / 1e3) << "kHz"; + else if(freq < 0.5e9) str << (freq / 1e6) << "MHz"; + else str << freq << "Hz"; + return str.str(); + } + + public: + virtual ~Counter() + { + } + + void dump() + { + Log << lw::Log::start(L"Counter") + << L"Counters:\n"; + + for(std::map::const_iterator iter = counts.begin(), + end = counts.end(); iter != end; ++iter) + { + Log << iter->first.toString() + << L", counts = " + << iter->second + << L", estimated frequency = " + << toHz(iter->second, stopWatch.elapsed().to_ns()) + << L'\n'; + } + + Log << lw::Log::end; + } + + virtual void timer(lw::Key key) throw() + { + if(!key) std::wcout << L"[DEBUG] Invalid key!\n"; + ++counts[key]; + // TODO de-register if we have over a million hits + } + }; + + Counter c; + + virtual void timer(lw::Key key) throw() + { + if(!key) std::wcout << L"[DEBUG] Invalid key!\n"; + c.dump(); + } + +public: + Display(lw::EventManager& eventManager) + { + eventManager.registerTimer(this, lw::ElapsedTime(0), lw::ElapsedTime(1)); + + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e+1)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e+0)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-1)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-2)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-3)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-4)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-5)); + eventManager.registerTimer(&c, lw::ElapsedTime(0), lw::ElapsedTime(1e-6)); + } + + virtual ~Display() + { + } + +}; + + + +// thread object to shut down event manager +class ShutdownThread : private lw::Thread { +private: + lw::EventManager& eventManager; + lw::Log Log; + + virtual void run() + { + Log << lw::Log::start(L"Thread") << L"Running.\n" << lw::Log::end; + lw::Thread::sleep(lw::ElapsedTime(100)); + Log << lw::Log::start(L"Thread") << L"Completed.\n" << lw::Log::end; + //eventManager.shutdown(); TODO + } + +public: + ShutdownThread(lw::EventManager& eventManager) + : eventManager(eventManager), Log(std::wcout, true) + { + start(); + } + + virtual ~ShutdownThread() + { + wait(-1); + } +}; + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + std::wcout << L"A test of the timer event subsystem.\n"; + return 0; + } + + if(argc != 1) { + std::wcerr << L"Not expecting any commandline arguments.\n"; + return 1; + } + + try { + // an initial delay, see ticket:6 for reason (basically we want to catch issues with + // registering events after the event manager thread has started) + //Log << lw::Log::start(L"main()") << L"Sleeping 5s..." << lw::Log::end; + //lw::Thread::sleep(lw::ElapsedTime::from_ms(5000)); + + // instantiate event manager + lw::EventManager eventManager(0, 10); + + // set up timer objects + Display display(eventManager); + ShutdownThread shutdownThread(eventManager); + + Log << lw::Log::start(L"main()") << L"Test started." << lw::Log::end; + + // event loop + try { + while(true) eventManager.processEvents(-1); + } + catch(lw::EventManager::Shutdown&) { } + + Log << lw::Log::start(L"main()") << L"Test completed." << lw::Log::end; + } + catch(lw::Exception& e) { + Err << lw::Log::start(L"main()") << e << lw::Log::end; + throw; + } + catch(...) { + Err << lw::Log::start(L"main()") + << L"Unknown exception caught.\n" + << lw::Log::end; + throw; + } + + return 0; +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +*/ diff --git a/src/tests/Log.cpp b/src/tests/Log.cpp new file mode 100644 index 0000000..8825459 --- /dev/null +++ b/src/tests/Log.cpp @@ -0,0 +1,96 @@ +/* lw-support/src/tests/Log.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +#include +#include +#include + + + +const char hexData[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 100, 101, 102, 103, 104, 105, 106, 107, + 200, 201, 202, 203, 204, 205, 206, 207, + 0 +}; + +const char asciiData[] = "Hello,\tworld!\r\nMore data on a new line\n"; + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + std::wcout << L"Tests the 'Log' object.\n"; + return 0; + } + + if(argc > 2) { + std::wcerr << L"Expecting no arguments (logs to screen) or one (logs to filename).\n"; + return 1; + } + + int ret = 0; + lw::Log* log = 0; + try { + if(argc == 1) log = new lw::Log(std::wcout, true); + else log = new lw::Log(argv[1], true); + + *log << lw::Log::start(L"Logging test") + << L"Logging object instantiated.\n" + << "This is some ASCII data.\n" + << L"And a number: " + << std::setfill(L'0') + << std::setw(4) + << std::hex + << 255 + << lw::Log::end; + + *log << lw::Log::start(L"Hex Dump") + << lw::Log::hexDump(hexData, sizeof(hexData)) + << lw::Log::end; + + *log << lw::Log::start(L"ASCII Dump") + << lw::Log::asciiDump(asciiData, sizeof(asciiData)) + << lw::Log::end; + + *log << lw::Log::start(L"Exceptions") + << L"std::out_of_bounds\n" + << std::out_of_range("exception test") + << L"\n\nlw::Exception\n" + << lw::Exception(L"A test of the log system") + << L"\n\nlw::ProgramException (derived from lw::Exception)\n" + << lw::ProgramException() + << lw::Log::end; + + *log << lw::Log::start(L"Network addresses") + << L"lw::NetAddressIPv4\n" + << lw::NetAddressIPv4::any + << L"\n\nlw::NetAddressIPv6\n" + << lw::NetAddressIPv6::any + << lw::Log::end; + } + catch(lw::Log::BoxException& e) { + std::wcerr << L"Boxing exception.\n"; + ret = 1; + } + catch(lw::Exception& e) { + std::wcerr << e.toString() << std::endl; + ret = 1; + } + catch(std::exception& e) { + std::wcerr << lw::utf8ToUcs4(e.what(), true) << std::endl; + ret = 1; + } + catch(...) { + std::wcerr << L"Unknown exception caught.\n"; + ret = 1; + } + + delete log; + return ret; +} diff --git a/src/tests/Net_EchoServerTCP.cpp b/src/tests/Net_EchoServerTCP.cpp new file mode 100644 index 0000000..9e77dbf --- /dev/null +++ b/src/tests/Net_EchoServerTCP.cpp @@ -0,0 +1,379 @@ +/* lw-support/src/tests/Net_EchoServerTCP.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" + +#include +#include +#include + + + +// Logging options +lw::Log Log(std::wcout, true), Err(std::wcerr, true); +int logRead, logWritten, logNew, logClosed; + + + +//BEGIN // Buffer machinery //////////////////////////////////////////// + +/// Maximum buffer size, in bytes. +int bufferSize = 0xFFFF; + +/// Already-allocated buffers are stored here, for re-use. +std::list freeBuffers; + +/// Allocate a buffer. +inline lw::FIFO* getBuffer() +{ + if(freeBuffers.empty()) { + Log << lw::Log::start(L"Buffer") + << L"Allocating a new buffer." + << lw::Log::end; + + return new lw::FIFO(bufferSize, bufferSize); + } + + lw::FIFO* p = freeBuffers.front(); + freeBuffers.pop_front(); + return p; +} + +/// Free a buffer. +inline void putBuffer(lw::FIFO* p) +{ + freeBuffers.push_back(p); +} + + + +//END // Buffer machinery ////////////////////////////////////////////// +//BEGIN // Echo client ///////////////////////////////////////////////// + + + +class EchoClient : private lw::EventCallbackIO { +private: + std::wstring name; + lw::IODevice* client; + + lw::FIFO* fifo; + bool eof; + + // from EventCallbackIO + virtual bool ioReady(uint32_t flags); + +public: + EchoClient(lw::EventManager& eventManager, lw::IODevice* client); + virtual ~EchoClient(); +}; + + + +EchoClient::EchoClient(lw::EventManager& eventManager, lw::IODevice* client) + : client(client), fifo(getBuffer()), eof(false) +{ + // build name for log + lw::NetClientTCP* tcp = dynamic_cast(client); + if(tcp) { + std::wostringstream o; + o << tcp->remoteAddr().toString() + << L", port " + << tcp->remotePort(); + name = o.str(); + } else { + name = L"(unknown client type)"; + } + + // listen to all events + eventManager.registerDevice(client, this, ~0); + + // log it + Log << lw::Log::start(name) << L"New connection." << lw::Log::end; + ++logNew; +} + + + +EchoClient::~EchoClient() +{ + Log << lw::Log::start(name) << L"Deleting device." << lw::Log::end; + ++logClosed; + putBuffer(fifo); + delete client; +} + + + +bool EchoClient::ioReady(uint32_t flags) +{ + bool ioRunning; + size_t amt; + + // check for errors + if(flags & lw::IOEventError) { + try { + client->checkErrors(); + } + catch(lw::Exception& e) { + Log << lw::Log::start(name) + << L"Detected client error:\n\n" + << e + << lw::Log::end; + + delete this; + return false; + } + + Err << lw::Log::start(name) + << L"epoll reports client error, but none detected." + << lw::Log::end; + } + + // check for HUP + if(flags & lw::IOEventHUP) { + Log << lw::Log::start(name) + << L"HUP detected. Closing connection." + << lw::Log::end; + + delete this; + return false; + } + + // check for urgent data + if(flags & lw::IOEventUrgent) { + Log << lw::Log::start(name) + << L"Urgent data available. Rejecting connection." + << lw::Log::end; + + delete this; + return false; + } + + // constantly try to drain the output buffer and fill the input buffer + do { + ioRunning = false; + + // write + while(true) { + amt = fifo->getSize(); + if(!amt) { + if(eof) { + Log << lw::Log::start(name) + << L"Closing client connection." + << lw::Log::end; + delete this; + return false; + } + break; + } + amt = client->write(fifo->getData(), amt); + if(!amt) break; + logWritten += amt; + ioRunning = true; + fifo->getData(amt); + } + + // read + while(!eof) { + amt = fifo->getRemaining(); + if(!amt) break; + try { + amt = client->read(fifo->getBuffer(amt), amt); + if(!amt) break; + logRead += amt; + fifo->addedData(amt); + } + catch(lw::EndOfFile&) { + eof = true; + } + ioRunning = true; + } + + }while(ioRunning); + + // done! + return true; +} + + + +//END // Echo client /////////////////////////////////////////////////// +//BEGIN // Echo server ///////////////////////////////////////////////// + + + +class EchoServer : private lw::EventCallbackNetServer { +private: + lw::EventManager& eventManager; + lw::NetServerTCP netServer; + + // from EventCallbackNetServer + virtual bool accept(lw::IODevice* client); + +public: + EchoServer(lw::EventManager& eventManager, + uint16_t port, lw::NetAddressIPv4& addr); + + virtual ~EchoServer() + { } +}; + + + +EchoServer::EchoServer(lw::EventManager& eventManager, + uint16_t port, lw::NetAddressIPv4& addr) + : eventManager(eventManager) +{ + netServer.listen(addr, port, 4); + netServer.registerCallback(eventManager, *this); +} + + + +bool EchoServer::accept(lw::IODevice* client) +{ + new EchoClient(eventManager, client); + return true; +} + + + +//END // Echo server /////////////////////////////////////////////////// +//BEGIN // Main and helpers //////////////////////////////////////////// + +void help(); +void parseArguments(int argc, char** argv, + uint16_t& port, + int& bufferSize, + lw::NetAddressIPv4& netAddress +); + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + std::wcout << L"A TCP echo service (--help for more).\n"; + return 0; + } + + if(argc == 1 || (argc == 2 && !strcmp(argv[1], "--help"))) { + // empty argument list + help(); + return 0; + } + + try { + // parse command line arguments + uint16_t port = 0; + lw::NetAddressIPv4 netAddress; + parseArguments(argc, argv, port, bufferSize, netAddress); + + // set up the event manager and server + lw::EventManager eventManager(5, 5); + EchoServer echoServer(eventManager, port, netAddress); + + // event loop + while(true) { + logRead = 0; + logWritten = 0; + logNew = 0; + logClosed = 0; + + eventManager.processEvents(-1); + + Log << lw::Log::start(L"main()") + << L"Bytes read this loop : " << logRead << L"\n" + L"Bytes written this loop : " << logWritten << L"\n" + L"New connections : " << logNew << L"\n" + L"Closed connections : " << logClosed + << lw::Log::end; + } + } + catch(lw::Exception& e) { + Err << lw::Log::start(L"main()") << e << lw::Log::end; + } + catch(...) { + Err << lw::Log::start(L"main()") + << L"Unknown exception caught." + << lw::Log::end; + } + + return 1; +} + + + +void help() +{ + std::wcout << L"Usage:\n" + L" Net_EchoServerTCP --port --buffer \n" + L" --bind \n" + L"\n" + L" : the TCP port to listen on\n" + L" : the internal buffer size (default 64k)\n" + L" : the IP address (dotted quad) to bind to (default any)\n" + L"\n"; +} + + + +void parseArguments(int argc, char** argv, + uint16_t& port, + int& bufferSize, + lw::NetAddressIPv4& netAddress) +{ + bool portSeen = false; + + for(int i = 1; i < argc; ++i) { + if(!strcmp(argv[i], "--port")) { + portSeen = true; + if(++i == argc) + throw lw::Exception(L"Expecting another argument after --port"); + + try { + port = lw::strToUInt16(lw::strToUcs4(argv[i])); + } + catch(lw::Exception& e) { + e.chain(L"parsing --port argument"); + throw; + } + + } else if(!strcmp(argv[i], "--buffer")) { + if(++i == argc) + throw lw::Exception(L"Expecting another argument after --buffer"); + + try { + bufferSize = lw::strToInt32(lw::strToUcs4(argv[i])); + } + catch(lw::Exception& e) { + e.chain(L"parsing --buffer argument"); + throw; + } + + } else if(!strcmp(argv[i], "--bind")) { + if(++i == argc) + throw lw::Exception(L"Expecting another argument after --bind"); + + try { + netAddress = lw::NetAddressIPv4::fromString(lw::strToUcs4(argv[i])); + } + catch(lw::Exception& e) { + e.chain(L"parsing --bind argument"); + throw; + } + + } else { + throw lw::Exception(L"Unknown parameter."); + + } + } +} + + + +//END // Main and helpers ////////////////////////////////////////////// diff --git a/src/tests/Net_SMTPClient.cpp b/src/tests/Net_SMTPClient.cpp new file mode 100644 index 0000000..432403e --- /dev/null +++ b/src/tests/Net_SMTPClient.cpp @@ -0,0 +1,106 @@ +/* lw-support/src/tests/Net_SMTPClient.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" + +#include + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + std::wcout << L"Tests the simple SMTP client.\n"; + return 0; + } + + if(argc != 5) { + std::wcout << L"Expecting arguments:\n" + L" SMTP server hostname\n" + L" SMTP server port\n" + L" mail from\n" + L" rcpt to\n" + L"and then pipe data into stdin.\n"; + return 1; + } + + // set up logs + lw::Log logger(std::wcout, true); + lw::Log logError(std::wcerr, true); + + int ret = 0; + try { + // process arguments + std::string hostName = argv[1]; + lw::NetAddressIPv4 hostAddr = lw::NetAddressIPv4::fromDNS(hostName); + uint16_t port = lw::strToUInt16(lw::strToUcs4(argv[2])); + std::string mailFrom = argv[3]; + std::string rcptTo = argv[4]; + + logger << lw::Log::start(L"DNS") + << L"Resolved host." + L"\n Host name : " << hostName + << L"\n IPv4 Address: " << (lw::NetAddress&)(hostAddr) + << lw::Log::end; + + // read input + std::string data, line; + while(std::cin) { + getline(std::cin, line); + data.append(line); + data.append("\n"); + } + + logger << lw::Log::start(L"Program") + << L"Sending " << data.size() + << L" bytes of data." + << lw::Log::end; + + // prepare header lines + std::multimap mailHeaders; + mailHeaders.insert(make_pair(std::string("subject"), + std::string("lw-support test message"))); + + // instantiate event manager + lw::EventManager eventManager(1, 1); + lw::CompletionNotifier notify; + + // send email + lw::LoggingSMTPClient::sendMail( + hostAddr, port, eventManager, "localhost", + mailFrom, rcptTo, mailHeaders, data, + logger, logError, ¬ify); + + while(true) { + try { + notify.wait(0); + break; + } + catch(lw::CompletionNotifier::Timeout&) { + eventManager.processEvents(-1); + continue; + } + } + + logger << lw::Log::start(L"Program") + << L"Process completed successfully." + << lw::Log::end; + } + catch(lw::Exception& e) { + logError << lw::Log::start(L"Program") + << e + << lw::Log::end; + ret = 1; + } + catch(...) { + logError << lw::Log::start(L"Program") + << L"Unknown exception caught.\n" + << lw::Log::end; + ret = 1; + } + + return ret; +} diff --git a/src/tests/ThreadedPipe.cpp b/src/tests/ThreadedPipe.cpp new file mode 100644 index 0000000..e7a49db --- /dev/null +++ b/src/tests/ThreadedPipe.cpp @@ -0,0 +1,135 @@ +/* lw-support/src/tests/ThreadedPipe.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +using namespace lw; + +#include + + + +const char* message = "Hello, world!\n"; +size_t messageLen = strlen(message) + 1; + + + +/// Convenience function: print the current time followed by a colon. +inline std::wostream& printTime() +{ + return std::wcout << DayTime::now() + .toString(true, ISO8601PrecisionFull, true) + << L": "; +} + + + +class ReaderThread : public Thread { +private: + EndPipe* read; + +protected: + virtual void run() + { + printTime() << L"Reading message\n"; + + char buffer[messageLen]; + read->readBlock(buffer, messageLen); + + if(memcmp(buffer, message, messageLen)) printTime() << L"Message mismatch!\n"; + else printTime() << L"Message received\n"; + } + +public: + ReaderThread(EndPipe* read) + : read(read) + { + start(); + } + + virtual ~ReaderThread() + { + printTime() << L"Deleting ReaderThread object [waiting]\n"; + wait(-1); + printTime() << L"Deleting ReaderThread object [now]\n"; + } +}; + + + +class WriterThread : public Thread { +private: + EndPipe* write; + +protected: + virtual void run() + { + sleep(ElapsedTime::from_ms(1000)); + printTime() << L"Writing message\n"; + + write->writeBlock(message, messageLen); + } + +public: + WriterThread(EndPipe* write) + : write(write) + { + start(); + } + + virtual ~WriterThread() + { + printTime() << L"Deleting WriterThread object [waiting]\n"; + wait(-1); + printTime() << L"Deleting WriterThread object [now]\n"; + } +}; + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + std::wcout << L"POSIX pipe and non-blocking threaded I/O regression test.\n"; + return 0; + } + + if(argc != 1) { + std::wcerr << L"Not expecting any arguments.\n"; + return 1; + } + + int ret = 0; + Pipe* p = 0; + Thread* thr1 = 0, * thr2 = 0; + try { + p = new Pipe; + thr1 = new ReaderThread(p->read); + thr2 = new WriterThread(p->write); + } + catch(Exception& e) { + std::wcerr << e.toString() << std::endl; + ret = 1; + } + catch(std::exception& e) { + try { + std::wcerr << utf8ToUcs4(e.what()) << std::endl; + } + catch(...) { + std::wcerr << L"std::exception caught but couldn't display.\n"; + } + ret = 1; + } + catch(...) { + std::wcerr << L"Unknown exception caught.\n"; + ret = 1; + } + + delete thr1; + delete thr2; + delete p; + + return ret; +} diff --git a/src/tests/Time_Date.cpp b/src/tests/Time_Date.cpp new file mode 100644 index 0000000..01422d3 --- /dev/null +++ b/src/tests/Time_Date.cpp @@ -0,0 +1,242 @@ +/* lw-support/src/tests/Time_Date.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +using namespace lw; + +#include +#include +using namespace std; + + + +const Date Epoch(1970, 1 ,1); + + + +void toString(const Date& d) +{ + wchar_t opt; + wcout << L"Extended (y/n)? "; + wcin >> opt; + bool extended = (opt == 'y'); + + wcout << L"\nFormat: [c]alendar, [o]rdinal or [w]eek date? "; + wcin >> opt; + ISO8601Format format = ISO8601FormatDetect; + switch(opt) { + case 'c': + format = ISO8601FormatCalendar; + break; + + case 'o': + format = ISO8601FormatOrdinal; + break; + + case 'w': + format = ISO8601FormatWeekDate; + break; + } + + wcout << L"Precision ([f]ull, [y]ear, [w]eek, m[o]nth, [d]ay, [h]our, [m]inute, [s]econd)? "; + wcin >> opt; + ISO8601Precision precision = ISO8601PrecisionFull; + switch(opt) { + case 'y': + precision = ISO8601PrecisionYear; + break; + + case 'w': + precision = ISO8601PrecisionWeek; + break; + + case 'o': + precision = ISO8601PrecisionMonth; + break; + + case 'd': + precision = ISO8601PrecisionDay; + break; + + case 'h': + precision = ISO8601PrecisionHour; + break; + + case 'm': + precision = ISO8601PrecisionMinute; + break; + + case 's': + precision = ISO8601PrecisionSecond; + break; + } + + wcout << d.toString(extended, format, precision) << endl; +} + + + +void add(Date& d) +{ + int amt; + wcout << L"How many? "; + wcin >> amt; + + d += amt; +} + + + +bool innerTest(Date& d) +{ + wcout << endl; + wcout << L"Year : " << d.getYear() << endl; + wcout << L"Month : " << d.getMonth() << endl; + wcout << L"Day : " << d.getDay() << endl; + wcout << L"Ordinal: " << d.getOrdinal() << endl; + wcout << L"Weekday: " << d.getWeekDay() << endl; + wcout << L"Week : " << d.getWeek() << endl; + wcout << L"dEpoch : " << (d - Epoch) << endl; + + wchar_t opt; + wcout << L"To [s]tring (ISO8601), [a]dd days, [q]uit? "; + wcin >> opt; + + switch(opt) { + case 's': + toString(d); + return true; + + case 'a': + add(d); + return true; + + case 'q': + return false; + } + + return true; +} + + + +Date fromString() +{ + wchar_t opt; + wcout << L"\nFormat: [d]etect, [c]alendar, [o]rdinal or [w]eek date? "; + wcin >> opt; + ISO8601Format format = ISO8601FormatDetect; + switch(opt) { + case 'c': + format = ISO8601FormatCalendar; + break; + + case 'o': + format = ISO8601FormatOrdinal; + break; + + case 'w': + format = ISO8601FormatWeekDate; + break; + } + + std::wstring str; + wcout << L"Enter ISO8601 std::string: "; + wcin >> str; + + return Date::fromString(str, format); +} + + + +Date specify() +{ + wchar_t opt; + wcout << L"\nFrom [o]rdinal, [w]eek or [m]onth+day? "; + wcin >> opt; + + if(opt == 'o') { + int year, ord; + wcout << L"Enter year and ordinal: "; + wcin >> year >> ord; + return Date(year, ord); + } + + if(opt == 'w') { + int year, week, weekDay; + wcout << L"Enter year, week and week day: "; + wcin >> year >> week >> weekDay; + return Date::fromWeek(year, week, weekDay); + } + + int year, month, day; + wcout << L"Enter year, month and day: "; + wcin >> year >> month >> day; + return Date(year, month, day); +} + + + +bool runTest() +{ + wchar_t opt; + wcout << L"\nFrom [s]tring (ISO8601), [c]urrent date, s[p]ecify or [q]uit? "; + wcin >> opt; + + Date d(1970, 1, 1); + + switch(opt) { + case 's': + d = fromString(); + break; + + case 'c': + d = Date::now(); + break; + + case 'p': + d = specify(); + break; + + case 'q': + return false; + + default: + return true; + } + + bool ok = true; + do { + try { + ok = innerTest(d); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); + + return true; +} + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + wcout << L"Test for the 'Date' object.\n"; + return 0; + } + + bool ok = true; + do { + try { + ok = runTest(); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); +} diff --git a/src/tests/Time_DateTime.cpp b/src/tests/Time_DateTime.cpp new file mode 100644 index 0000000..6129b95 --- /dev/null +++ b/src/tests/Time_DateTime.cpp @@ -0,0 +1,284 @@ +/* lw-support/src/tests/Time_DateTime.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +using namespace lw; + +#include +#include +using namespace std; + + + +void toString(const DateTime& dt) +{ + wchar_t opt; + wcout << L"Extended (y/n)? "; + wcin >> opt; + bool extended = (opt == 'y'); + + wcout << L"Decimal time (y/n)? "; + wcin >> opt; + bool decimal = (opt == 'y'); + + wcout << L"\nFormat: [c]alendar, [o]rdinal or [w]eek date? "; + wcin >> opt; + ISO8601Format format = ISO8601FormatDetect; + switch(opt) { + case 'c': + format = ISO8601FormatCalendar; + break; + + case 'o': + format = ISO8601FormatOrdinal; + break; + + case 'w': + format = ISO8601FormatWeekDate; + break; + } + + wcout << L"Precision ([f]ull, [y]ear, [w]eek, m[o]nth, [d]ay, [h]our, [m]inute, [s]econd)? "; + wcin >> opt; + ISO8601Precision precision = ISO8601PrecisionFull; + switch(opt) { + case 'y': + precision = ISO8601PrecisionYear; + break; + + case 'w': + precision = ISO8601PrecisionWeek; + break; + + case 'o': + precision = ISO8601PrecisionMonth; + break; + + case 'd': + precision = ISO8601PrecisionDay; + break; + + case 'h': + precision = ISO8601PrecisionHour; + break; + + case 'm': + precision = ISO8601PrecisionMinute; + break; + + case 's': + precision = ISO8601PrecisionSecond; + break; + } + + wcout << dt.toString(extended, decimal, format, precision) << endl; +} + + + +void add(DateTime& dt) +{ + wchar_t which; + wcout << L"Add [n]anoseconds, [s]econds, [m]inutes, [h]ours or [d]ays? "; + wcin >> which; + + int amt; + wcout << L"How many? "; + wcin >> amt; + + switch(which) { + case 'n': + dt.addNanoSeconds(amt); + break; + + case 's': + dt.addSeconds(amt); + break; + + case 'm': + dt.addMinutes(amt); + break; + + case 'h': + dt.addHours(amt); + break; + + case 'd': + dt.addDays(amt); + break; + + default: + wcout << L"Not sure what to add.\n"; + } +} + + + +bool innerTest(DateTime& dt) +{ + wcout << endl; + wcout << L"Year : " << dt.getDate().getYear() << endl; + wcout << L"Month : " << dt.getDate().getMonth() << endl; + wcout << L"Day : " << dt.getDate().getDay() << endl; + wcout << L"Ordinal: " << dt.getDate().getOrdinal() << endl; + wcout << L"Weekday: " << dt.getDate().getWeekDay() << endl; + wcout << L"Week : " << dt.getDate().getWeek() << endl; + wcout << L"Hour : " << dt.getTime().h() << endl; + wcout << L"Minute : " << dt.getTime().m() << endl; + wcout << L"Second : " << dt.getTime().s() << endl; + wcout << L"Nano-s : " << dt.getTime().ns() << endl; + + wchar_t opt; + wcout << L"To [s]tring (ISO8601), [a]dd, [q]uit? "; + wcin >> opt; + + switch(opt) { + case 's': + toString(dt); + return true; + + case 'a': + add(dt); + return true; + + case 'q': + return false; + } + + return true; +} + + + +DateTime fromString() +{ + wchar_t opt; + wcout << L"\nFormat: [d]etect, [c]alendar, [o]rdinal or [w]eek date? "; + wcin >> opt; + ISO8601Format format = ISO8601FormatDetect; + switch(opt) { + case 'c': + format = ISO8601FormatCalendar; + break; + + case 'o': + format = ISO8601FormatOrdinal; + break; + + case 'w': + format = ISO8601FormatWeekDate; + break; + } + + std::wstring str; + wcout << L"Enter ISO8601 std::string: "; + wcin >> str; + + return DateTime::fromString(str, format); +} + + + +DateTime specify() +{ + // get date + Date d(1970, 1, 1); + int year, week, weekDay, ord, month, day; + + wchar_t opt; + wcout << L"\nFrom [o]rdinal, [w]eek or [m]onth+day? "; + wcin >> opt; + + switch(opt) { + case 'o': + wcout << L"Enter year and ordinal: "; + wcin >> year >> ord; + d = Date(year, ord); + break; + + case 'w': + wcout << L"Enter year, week and week day: "; + wcin >> year >> week >> weekDay; + d = Date::fromWeek(year, week, weekDay); + break; + + default: + wcout << L"Enter year, month and day: "; + wcin >> year >> month >> day; + d = Date(year, month, day); + break; + } + + // get time + int h, m, s, ns; + wcout << L"\nEnter hour, minute, second, nanosecond: "; + wcin >> h >> m >> s >> ns; + + return DateTime(d, DayTime(h, m, s, ns)); +} + + + +bool runTest() +{ + wchar_t opt; + wcout << L"\nFrom [s]tring (ISO8601), [c]urrent date, s[p]ecify or [q]uit? "; + wcin >> opt; + + DateTime dt(Date(1970, 1, 1)); + + switch(opt) { + case 's': + dt = fromString(); + break; + + case 'c': + dt = DateTime::now(); + break; + + case 'p': + dt = specify(); + break; + + case 'q': + return false; + + default: + return true; + } + + bool ok = true; + do { + try { + ok = innerTest(dt); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); + + return true; +} + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + wcout << L"Test for the 'DateTime' object.\n"; + return 0; + } + + bool ok = true; + do { + try { + ok = runTest(); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); +} diff --git a/src/tests/Time_DayTime.cpp b/src/tests/Time_DayTime.cpp new file mode 100644 index 0000000..06d8806 --- /dev/null +++ b/src/tests/Time_DayTime.cpp @@ -0,0 +1,192 @@ +/* lw-support/src/tests/Time_DayTime.cpp + * + * (c)2005, Laurence Withers. Released under the GNU GPL. See file + * COPYING for more information / terms of license. +*/ + +#include "lw-support" +using namespace lw; + +#include +#include +using namespace std; + + + +void toString(const DayTime& t) +{ + wchar_t opt; + wcout << L"Extended (y/n)? "; + wcin >> opt; + bool extended = (opt == 'y'); + + wcout << L"Precision ([f]ull, [h]our, [m]inute, [s]econd)? "; + wcin >> opt; + ISO8601Precision precision = ISO8601PrecisionFull; + switch(opt) { + case 'h': + precision = ISO8601PrecisionHour; + break; + + case 'm': + precision = ISO8601PrecisionMinute; + break; + + case 's': + precision = ISO8601PrecisionSecond; + break; + } + + wcout << L"Decimal (y/n)? "; + wcin >> opt; + bool decimal = (opt == 'y'); + + wcout << t.toString(extended, precision, decimal) << endl; +} + + + +void add(DayTime& t, wchar_t which) +{ + int dayDelta, amt; + wcout << L"How many? "; + wcin >> amt; + + switch(which) { + case 'h': + t.addHours(amt, dayDelta); + break; + + case 'm': + t.addMinutes(amt, dayDelta); + break; + + case 'e': + t.addSeconds(amt, dayDelta); + break; + + case 'n': + t.addNanoSeconds(amt, dayDelta); + break; + } + + wcout << L"Change in days: " << dayDelta << endl; +} + + + +bool innerTest(DayTime& t) +{ + wcout << endl; + wcout << L"Hours : " << t.h() << endl; + wcout << L"Minutes: " << t.m() << endl; + wcout << L"Seconds: " << t.s() << endl; + wcout << L"ns : " << t.ns() << endl; + + wchar_t opt; + wcout << L"To [s]tring (ISO8601), add [h]ours, add [m]inutes, add s[e]conds, add [n]s, [q]uit? "; + wcin >> opt; + + switch(opt) { + case 's': + toString(t); + return true; + + case 'h': + case 'm': + case 'e': + case 'n': + add(t, opt); + return true; + + case 'q': + return false; + } + + return true; +} + + + +DayTime fromString() +{ + std::wstring str; + wcout << L"\nEnter ISO8601 std::string: "; + wcin >> str; + + return DayTime::fromString(str); +} + + + +DayTime specify() +{ + int h, m, s, ns; + wcout << L"\nEnter hour, minute, second, nanosecond: "; + wcin >> h >> m >> s >> ns; + + return DayTime(h, m, s, ns); +} + + + +bool runTest() +{ + wchar_t opt; + wcout << L"\nFrom [s]tring (ISO8601), [c]urrent time, s[p]ecify or [q]uit? "; + wcin >> opt; + + DayTime t(0, 0, 0, 0); + + switch(opt) { + case 's': + t = fromString(); + break; + + case 'c': + t = DayTime::now(); + break; + + case 'p': + t = specify(); + break; + + case 'q': + return false; + + default: + return true; + } + + bool ok = true; + do { + try { + ok = innerTest(t); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); + + return true; +} + + + +int main(int argc, char* argv[]) +{ + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + wcout << L"Test for the 'DayTime' object.\n"; + return 0; + } + + bool ok = true; + do { + try { + ok = runTest(); + } + catch(Exception& e) { + wcout << e.toString() << endl; + } + }while(ok); +}