Import old svn repository.

This commit is contained in:
Laurence Withers 2006-08-25 00:09:46 +01:00
parent f81b32b830
commit 432db441a8
111 changed files with 17149 additions and 39 deletions

48
src/docs/Exceptions.dox Normal file
View File

@ -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.
*/

View File

@ -1,15 +1,22 @@
/* lw-support/src/docs/MainPage.dox /* lw-support/src/docs/MainPage.txt
* *
* (c)2006, Laurence Withers, <l@lwithers.me.uk>. * (c)2005, Laurence Withers. Released under the GNU GPL. See file
* Released under the GNU GPLv2. See file COPYING or * COPYING for more information / terms of license.
* http://www.gnu.org/copyleft/gpl.html for details.
*/ */
/*! \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
*/

33
src/docs/TimeDate.dox Normal file
View File

@ -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 <a href="http://en.wikipedia.org/wiki/ISO_8601">
ISO8601</a> 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.
*/

3
src/docs/docs.in Normal file
View File

@ -0,0 +1,3 @@
src/docs/MainPage.txt
src/docs/Exceptions.txt
src/docs/TimeDate.txt

View File

@ -1,13 +1,18 @@
/* lw-support/src/liblw-support/BottomHeader.h /* lw-support/src/lib/BottomHeader.h
* *
* (c)2006, Laurence Withers, <l@lwithers.me.uk>. * (c)2005, Laurence Withers. Released under the GNU GPL. See file
* Released under the GNU GPLv2. See file COPYING or * COPYING for more information / terms of license.
* http://www.gnu.org/copyleft/gpl.html for details.
*/ */
// 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 #endif
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -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.
};
}

View File

@ -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;
*/

View File

@ -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;
*/

View File

@ -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;
*/

View File

@ -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<lw::EventManager*> managers;
};
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
*/

View File

@ -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<TimerEntry> 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<EventCallbackUpdate*> 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<EventCallbackUpdate*>::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<EventCallbackUpdate*>::iterator iter =
std::lower_bound(p->updateList.begin(), p->updateList.end(), updateCallback);
if(*iter != updateCallback) return;
// remove it, unregister with it
std::vector<EventManager*>& 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;
*/

View File

@ -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;
*/

View File

@ -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;
*/

View File

@ -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<class T> 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<class T> 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;
*/

View File

@ -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)
{
}
}

View File

@ -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<uint8_t> 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<uint8_t>& 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; }
};
}

View File

@ -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.")
{ }
};
}

View File

@ -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)
{
}
}

View File

@ -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; }
};
}

View File

@ -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;
}
}

View File

@ -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:
<pre>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;
}</pre>
This would give the following output:
<pre>errorMessage
From: b()</pre>
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&);
}

View File

@ -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();
}
}

View File

@ -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).")
{ }
};
}

View File

@ -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");
}
}

View File

@ -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);
};
}

View File

@ -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()
{
}
}

View File

@ -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;
};
}

View File

@ -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);
}
}
};
}

View File

@ -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)
{
}
}

View File

@ -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; }
};
}

View File

@ -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))
{
}
}

View File

@ -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);
};
}

View File

@ -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;
}

View File

@ -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; }
};
}

View File

@ -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;
}
}
}

View File

@ -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();
};
}

View File

@ -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;
};
}

View File

@ -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.
}
}

View File

@ -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();
};
}

View File

@ -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<IOPosixDevice*>(this), L"read", IORead);
}
void IOPosixDevice::checkWrite() const
{
IOMode mode = getIOMode();
if(mode == IOWrite || mode == IOReadWrite) return;
throw IOModeError(const_cast<IOPosixDevice*>(this), L"write", IOWrite);
}
void IOPosixDevice::checkOpen(const std::wstring& op) const
{
IOMode mode = getIOMode();
if(mode != IOClosed) return;
throw IOModeError(const_cast<IOPosixDevice*>(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()
{
}
}

View File

@ -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();
};
}

View File

@ -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()");
}
}
}

View File

@ -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();
};
}

View File

@ -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;
}
}

View File

@ -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);
};
}

View File

@ -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;
}
}

View File

@ -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&);
}

View File

@ -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);
}
}

View File

@ -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.
<tt>a.b.c.d</tt> 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);
};
}

View File

@ -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);
}
}

View File

@ -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);
};
}

View File

@ -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;
}
}

View File

@ -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);
};
}

View File

@ -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 /////////////////////////////////////////////
}

View File

@ -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<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string>& 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; }
};
}

View File

@ -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<std::string, std::string>& 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<std::string, std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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;
}
}

View File

@ -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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& headerLines,
const std::string& message, Log& logger, Log& logError,
CompletionNotifier* completionNotifier);
};
}

View File

@ -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<const NetAddressIPv4*>(a)) return IPv4;
if(dynamic_cast<const NetAddressIPv6*>(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;
*/

View File

@ -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;
*/

View File

@ -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;
}
}

View File

@ -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();
};
}

View File

@ -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<const NetAddressIPv4*>(&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<const NetAddressIPv6*>(&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()");
}
}

View File

@ -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);
};
}

View File

@ -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<const NetAddressIPv4&>(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<const NetAddressIPv6&>(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;
}
}

View File

@ -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);
};
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
};
}

View File

@ -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);
}
}

View File

@ -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);
}
};
}

View File

@ -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();
}
};
}

View File

@ -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);
}
}

View File

@ -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;
}
};
}

View File

@ -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 */
}
}

View File

@ -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();
};
}

View File

@ -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<ISO8601DateParser*> 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;
*/

View File

@ -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&);
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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&);
}

View File

@ -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;
}
}

View File

@ -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 &lt; 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 &lt; 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 (&lt;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);
}

View File

@ -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;
}
}

View File

@ -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 <tt>struct
timeval</tt> and <tt>struct timespec</tt>. 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 &gt;= 0)
\param ms The number of milliseconds (must be &gt;= 0 and &lt; 1000)
\param us The number of microseconds (must be &gt;= 0 and &lt; 1000)
\param ns The number of nanoseconds (must be &gt;= 0 and &lt; 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 (<tt>s, ms, us,
ns</tt>) 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 <tt>s, ms, us, ns</tt>. 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&);
}

View File

@ -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)));
}
}

View File

@ -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;
};
}

View File

@ -1,16 +1,32 @@
/* lw-support/src/liblw-support/TopHeader.h /* lw-support/src/lib/TopHeader.h
* *
* (c)2006, Laurence Withers, <l@lwithers.me.uk>. * (c)2005, Laurence Withers. Released under the GNU GPL. See file
* Released under the GNU GPLv2. See file COPYING or * COPYING for more information / terms of license.
* http://www.gnu.org/copyleft/gpl.html for details.
*/ */
#ifndef HEADER_liblw_support // This file contains the include guard, and also any includes that are
#define HEADER_liblw_support // 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 // standard includes, or includes needed for type declarations
#include <string>
#include <exception>
#include <vector>
#include <list>
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <algorithm>
#include <pthread.h>
#include <stdint.h>
/* options for text editors #include <sys/epoll.h>
kate: replace-trailing-space-save true; space-indent true; tab-width 4; #include <sys/socket.h>
vim: expandtab:ts=4:sw=4
*/ #include <netinet/in.h>

View File

@ -1,15 +1,36 @@
/* lw-support/src/liblw-support/TopSource.cpp /* lw-support/src/lib/TopSource.cpp
* *
* (c)2006, Laurence Withers, <l@lwithers.me.uk>. * (c)2005, Laurence Withers. Released under the GNU GPL. See file
* Released under the GNU GPLv2. See file COPYING or * COPYING for more information / terms of license.
* http://www.gnu.org/copyleft/gpl.html for details.
*/ */
#include "lw-support" #include "lw-support"
// Below are all the includes used throughout the library. // Below are all the includes used throughout the library.
/* options for text editors #include <iostream>
kate: replace-trailing-space-save true; space-indent true; tab-width 4; #include <iomanip>
vim: expandtab:ts=4:sw=4
*/ #include <errno.h>
#include <string.h>
#include <wchar.h>
#include <math.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <netdb.h>
#include <execinfo.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
// external library includes
// Some useful, universal constants.
#define pipeRead 0
#define pipeWrite 1

View File

@ -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<CompletionTask*>(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;
}
}

View File

@ -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<CompletionTask*> tasks;
CompletionTask* abortingTask;
typedef std::list<CompletionTask*>::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);
};
}

View File

@ -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;
}
}

View File

@ -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;
}
};
}

View File

@ -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)));
}
}

View File

@ -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); }
};
}

View File

@ -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;
*/

Some files were not shown because too many files have changed in this diff Show More