libDwm-0.8.0
Dwm Class Library

Introduction

This class library is just a (somewhat random) collection of classes I've developed over the years for various applications. Since these are used in a variety of places, they're collected here to make re-use easy.

There are a few classes herein that I've used on nearly every software project in the last 20 years. One of those would be the Dwm::SysLogger class with the Syslog() macro, since syslogging is a common need. Another would be the Dwm::Assertions class with the UnitAssert() macro since I almost always write unit tests as part of my development process. And when I need to store data, I use the IO classes along with the various Readable and Writable interfaces.

Since much of the software I've written in the last 20 years has been multithreaded, I have also made heavy use of the older classes in the Dwm::Pthread namespace. Today I've replaced most of their use with std::mutex, std::thread and friends, and use instances of the Dwm::Thread::Queue class for inter-thread communication (usually as 'work queues').

History

I started this library in 1998. I continue to maintain it for my personal use. While it has grown over the years, the most common use features have been stable for a long time. To some extent they have to be stable; I have over 400,000 lines of code in my personal software repository, and a decent amount of it is dependent on this library. The good news is that those dependencies don't prevent me from refactoring, improving and growing libDwm as needed.

Platforms

I really only maintain support for 4 platforms: FreeBSD, macOS, desktop linux and Raspbian. FreeBSD is my operating system of choice for servers and macOS is my operating system of choice for desktops and laptops. I have several Raspberry Pis I utilize for simple small tasks, and Ubuntu VMs and an old Ubuntu workstation.

A small sample of what's in libDwm

I/O - Serialization

Dwm::DescriptorIO, Dwm::FileIO, Dwm::StreamIO, Dwm::ASIO, Dwm::GZIO and Dwm::BZ2IO provide serialization for intrinsic types, containers of intrinsic types, user types which implement the required interfaces, and containers of these user types.

Intrinsic integer types are written in network byte order. The library takes care of byte ordering so you need not be concerned with it. Float and double types are written in IEEE 754-1985 form (see RFC 1832). Some types are stored as (length,value) pairs, including the standard containers and C++ string objects.

For user types, the required interfaces are *Readable and *Writable, such as Dwm::StreamReadable and Dwm::StreamWritable. The required interfaces for Dwm::GZIO are Dwm::GZReadable and Dwm::GZWritable. The required interfaces for Dwm::BZ2IO are Dwm::BZ2Readable and Dwm::BZ2Writable.

Today, the C++ programmer has several options for serialization, and the possibility of reflection in a future version of the standard. However, I wrote most of this functionality in 2004 when we didn't have a lot of options to support serialization and finalized most of it in 2007 with the addition of cleaner code for std::tuple. I couldn't do this effectively until the compilers were up to speed.

Why did I spend time on this functionality? I frequently need to do things like this (error checking absent for the sake of brevity):

1 #include <fstream>
2 #include <map>
3 #include <string>
4 
5 #include "DwmIpv4Address.hh"
6 #include "DwmStreamIO.hh"
7 
8 int main(int argc, char *argv[])
9 {
10  std::map<Dwm::Ipv4Address, std::string> hosts;
11 
12  // Read hosts from an ifstream
13  std::ifstream is("/tmp/hosts");
14 
15  if (is) {
16  Dwm::StreamIO::Read(is, hosts);
17  is.close();
18  }
19 
20  // Do something with hosts, then...
21 
22  // Save hosts to an ofstream
23  std::ofstream os("/tmp/hosts");
24  if (os) {
25  Dwm::StreamIO::Write(os, hosts);
26  os.close();
27  }
28 
29  return 0;
30 }
31 
32 
static std::istream & Read(std::istream &is, char &c)
Reads c from is. Returns is.
static std::ostream & Write(std::ostream &os, char c)
Writes c to os. Returns os.
Dwm::Ipv4Address class definition.
Dwm::StreamIO class declaration.

While this is a trivial example, I've used this functionality in much more complex scenarios. Having this type of functionality in my toolbox has saved me large amounts of development time. It's reasonably flexible without a lot of extra work. No work at all if you're using types already supported. Containers of containers of supported types, for example:

1 #include <fstream>
2 #include <map>
3 #include <set>
4 #include <string>
5 #include <vector>
6 
7 #include "DwmStreamIO.hh"
8 
9 using namespace std;
10 
11 int main(int argc, char *argv[])
12 {
13  if (argc > 2) {
14  vector<map<string,set<string>>> data;
15 
16  // Read data from an ifstream
17  ifstream is(argv[1]);
18  if (is) {
19  Dwm::StreamIO::Read(is, data);
20  is.close();
21  }
22 
23  // Do something with the data...
24 
25  // Then save to an ofstream
26  ofstream os(argv[2]);
27  if (os) {
28  Dwm::StreamIO::Write(os, data);
29  os.close();
30  }
31  }
32 
33  return 0;
34 }
static std::istream & Read(std::istream &is, char &c)
Reads c from is. Returns is.
static std::ostream & Write(std::ostream &os, char c)
Writes c to os. Returns os.
Dwm::StreamIO class declaration.

Since the library handles C++ <tuple>, you can create new data classes and easily add serialization by keeping all of your class's data in a tuple. Below is a trivial contrived example of a phone contact data store. The serialization members required are lines 43 to 49 and 111 to 117. These are easy to implement, since they each require only a single call to a member of an I/O class. Note that I never put  using namespace std in header files. It's here just to reduce clutter on your screen.

1 #include "DwmDescriptorIO.hh"
2 #include "DwmFileIO.hh"
3 #include "DwmStreamIO.hh"
4 
5 using namespace std;
6 
7 //----------------------------------------------------------------------------
8 //----------------------------------------------------------------------------
9 class PhoneContact
11  public Dwm::FileReadable, public Dwm::FileWritable,
13 {
14 public:
15  PhoneContact() : _data() { }
16 
17  const string & FirstName() const { return get<0>(_data); }
18  const string & FirstName(const string & firstName)
19  {
20  get<0>(_data) = firstName;
21  return get<0>(_data);
22  }
23 
24  const string & LastName() const { return get<1>(_data); }
25  const string & LastName(const string & lastName)
26  {
27  get<1>(_data) = lastName;
28  return get<1>(_data);
29  }
30 
31  const set<pair<string,string> > & PhoneNumbers() const
32  { return get<2>(_data); }
33  bool AddPhoneNumber(const string & phoneName,
34  const string & phoneNumber)
35  {
36  pair<string,string> phone(phoneName, phoneNumber);
37  return get<2>(_data).insert(phone).second;
38  }
39 
40  bool RemovePhoneNumber(const string & phoneName,
41  const string & phoneNumber)
42  {
43  pair<string,string> phone(phoneName, phoneNumber);
44  return (get<2>(_data).erase(phone) == 1);
45  }
46 
47  istream & Read(istream & is) { return Dwm::StreamIO::Read(is, _data); }
48  ostream & Write(ostream & os) const { return Dwm::StreamIO::Write(os, _data); }
49  ssize_t Read(int fd) { return Dwm::DescriptorIO::Read(fd, _data); }
50  ssize_t Write(int fd) const { return Dwm::DescriptorIO::Write(fd, _data); }
51  size_t Read(FILE *f) { return Dwm::FileIO::Read(f, _data); }
52  size_t Write(FILE *f) const { return Dwm::FileIO::Write(f, _data); }
53 
54 private:
55  tuple<string, // first name
56  string, // last name
57  set<pair<string,string> > // phone numbers
58  > _data;
59 };
60 
61 //----------------------------------------------------------------------------
62 //----------------------------------------------------------------------------
63 class PhoneContacts
65  public Dwm::FileReadable, public Dwm::FileWritable,
67 {
68 public:
69  PhoneContacts() : _contacts() { }
70 
71  bool AddContact(const PhoneContact & contact)
72  {
73  bool rc = false;
74  string fullName(contact.FirstName() + " " + contact.LastName());
75  auto it = _contacts.find(fullName);
76  if (it == _contacts.end()) {
77  _contacts[fullName] = contact;
78  rc = true;
79  }
80  return rc;
81  }
82 
83  bool MatchesByEitherName(const string & name,
84  vector<PhoneContact> & matches) const
85  {
86  if (! matches.empty()) {
87  matches.clear();
88  }
89  for (auto i : _contacts) {
90  if ((i.second.FirstName() == name)
91  || (i.second.LastName() == name)) {
92  matches.push_back(i.second);
93  }
94  }
95  return (! matches.empty());
96  }
97 
98  bool MatchByFullName(const string & firstName, const string & lastName,
99  PhoneContact & match)
100  {
101  bool rc = false;
102  for (auto i : _contacts) {
103  if ((i.second.FirstName() == firstName)
104  && (i.second.LastName() == lastName)) {
105  match = i.second;
106  rc = true;
107  break;
108  }
109  }
110  return rc;
111  }
112 
113  const map<string,PhoneContact> & Contacts() const { return _contacts; }
114  map<string,PhoneContact> & Contacts() { return _contacts; }
115 
116  istream & Read(istream & is) { return Dwm::StreamIO::Read(is, _contacts); }
117  ostream & Write(ostream & os) const { return Dwm::StreamIO::Write(os, _contacts); }
118  ssize_t Read(int fd) { return Dwm::DescriptorIO::Read(fd, _contacts); }
119  ssize_t Write(int fd) const { return Dwm::DescriptorIO::Write(fd, _contacts); }
120  size_t Read(FILE *f) { return Dwm::FileIO::Read(f, _contacts); }
121  size_t Write(FILE *f) const { return Dwm::FileIO::Write(f, _contacts); }
122 
123 private:
124  map<string,PhoneContact> _contacts;
125 };
126 
This class defines an interface for classes that can write their contents to a file descriptor.
Definition: DwmDescriptorWritable.hh:52
static std::istream & Read(std::istream &is, char &c)
Reads c from is. Returns is.
This class defines an interface for classes that can read their contents from a FILE pointer.
Definition: DwmFileReadable.hh:53
Dwm::FileIO class declaration.
This class defines an interface for classes that can read their contents from an istream.
Definition: DwmStreamReadable.hh:53
This class defines an interface for classes that can read their contents from a file descriptor.
Definition: DwmDescriptorReadable.hh:54
static ssize_t Write(int fd, char c)
Writes c to fd.
static size_t Read(FILE *f, char &c)
Reads c from f.
static std::ostream & Write(std::ostream &os, char c)
Writes c to os. Returns os.
Dwm::StreamIO class declaration.
static size_t Write(FILE *f, char c)
Writes c to f.
This class defines an interface for classes that can write their contents to a FILE pointer.
Definition: DwmFileWritable.hh:53
Dwm::DescriptorIO class declaration.
static ssize_t Read(int fd, char &c)
Reads c from fd.
This class defines an interface for classes that can write their contents to an ostream.
Definition: DwmStreamWritable.hh:52

The astute observer will notice that the PhoneContact class is just a wrapper around a std::tuple, to avoid having to use std::get<>() directly from application code outside of this class. If you're willing to deal directly with std::get<>(), you can just do this:

typedef std::tuple<std::string,std::string,std::pair<std::string,std::string> > > PhoneContact;
typedef std::map<std::string,PhoneContact> PhoneContacts;

UnitAssert - A trivial unit testing framework

In DwmUnitAssert.hh you will find the UnitAssert() macro and a handful of support classes for unit testing. This trivial framework makes simple unit testing easy to accomplish for all exposed interfaces. This framework is used for all of the unit tests in the tests subdirectory of the libDwm source distribution, and you can look there for many examples. But its basic usage is trivial and typically boils down to just three calls: the UnitAssert() macro to assert conditions that must be true (just like assert() from the standard C library), and a call to Dwm::Assertions::Print() at the end of your test program that is typically wrapped in test of the return of Dwm::Assertions::Total().Failed().

Below is the entire contents of TestGroup.cc from the tests directory of the libDwm source distribution. You will see calls to UnitAssert() on lines 16, 21, 26, 27 and 28. On line 30, we check if there were any failed tests via Dwm::Assertions::Total().Failed(). If there were, we print them with Dwm::Assertions::Print(). If there were no failed tests, we pring the total number of tests (which all passed).

1 extern "C" {
2  #include <unistd.h>
3 }
4 
5 #include "DwmGroup.hh"
6 #include "DwmPassword.hh"
7 #include "DwmUnitAssert.hh"
8 
9 //----------------------------------------------------------------------------
11 //----------------------------------------------------------------------------
12 int main(int argc, char *argv[])
13 {
14  gid_t mygid = getegid();
15  Dwm::Group mygroup(mygid);
16  UnitAssert(mygid == mygroup.Id());
17 
18  Dwm::Password mypasswd(geteuid());
19  auto gmit = std::find(mygroup.Members().begin(), mygroup.Members().end(),
20  mypasswd.UserName());
21  UnitAssert(gmit != mygroup.Members().end());
22 
23  struct group *grp = getgrgid(mygid);
24  Dwm::Group setGroup(getgid());
25  setGroup.Set(*grp);
26  UnitAssert(setGroup.Id() == grp->gr_gid);
27  UnitAssert(setGroup.Name() == grp->gr_name);
28  UnitAssert(setGroup.Password() == grp->gr_passwd);
29 
30  if (Dwm::Assertions::Total().Failed() > 0) {
31  Dwm::Assertions::Print(std::cerr, true);
32  exit(1);
33  }
34  else {
35  std::cout << Dwm::Assertions::Total() << " passed" << std::endl;
36  }
37  exit(0);
38 }
#define UnitAssert(e)
This macro is used just like assert(), but populates Assertions so we can report results at the end o...
Definition: DwmUnitAssert.hh:283
Encapsulates an /etc/group entry.
Definition: DwmGroup.hh:61
Encapsulates an /etc/passwd entry.
Definition: DwmPassword.hh:61
static std::ostream & Print(std::ostream &os, bool onlyFailed=false)
Prints to an ostream.
Dwm::Assertions class definition and UnitAssert() macro for unit tests.
static AssertionCounter Total()
Returns the total passed and failed counter.
uint64_t Failed() const
Returns the failed counter.
Dwm::Password class definition.
Dwm::Group class definition.

Dwm::SysLogger - decorated syslogging

The intent of Dwm::SysLogger and the Syslog() macro is to allow the automatic addition of filename, line number and syslog priority to syslog messages. No one in their right mind wants to have to add "(%s:%d)", __FILE__, __LINE__ to every syslog call in their source code. It's inane work and prone to mistakes. Syslog() can do this for you if you enable it with Dwm::SysLogger::ShowFileLocation(bool). It can also put three-character tags on each log message if you enable it with Dwm::SysLogger::ShowPriorities(bool). You can also change the minimum priority of messages to be logged via Dwm::SysLogger::MinimumPriority(int) or Dwm::SysLogger::MinimumPriority(const std::string &). This is handy when you want to toggle debug logging while a program is running, perhaps via a signal.

Dwm::Pacer - pacing repetitive calls

Documentation forthcoming

Networking classes

Documentation forthcoming for Dwm::HostPinger, ICMP classes, TCP classes, Dwm::Pcap, etc.

Dwm::Pcap - simple pcap wrapper class

Dwm::Pcap is a simple wrapper class for pcap (packet capture library). It doesn't do anything beyond what libpcap provides, it simply wraps in in a C++ interface.

Dwm::HostPinger - pinging IP hosts

Dwm::HostPinger is a class that allows the 'pinging' of one or more IP hosts using ICMP or TCP. The overall packet rate may be controlled, and an object may be registered for the reception of responding host, receive time and round-trip time.

ICMP classes

Dwm::ThreadQueue - inter-thread communication

Documentation forthcoming.