finish doxygen docs.

This also includes a lot of code cleanups along the way.
This commit is contained in:
Kyle Isom 2023-10-10 02:35:43 -07:00
parent 386869df44
commit a8b09001f7
19 changed files with 596 additions and 234 deletions

33
.clang-tidy Normal file
View File

@ -0,0 +1,33 @@
Checks: >-
bugprone-*,
cppcoreguidelines-*,
google-*,
misc-*,
modernize-*,
performance-*,
readability-*,
-bugprone-lambda-function-name,
-bugprone-reserved-identifier,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-type-vararg,
-google-readability-braces-around-statements,
-google-readability-function-size,
-misc-no-recursion,
-modernize-return-braced-init-list,
-modernize-use-nodiscard,
-modernize-use-trailing-return-type,
-performance-unnecessary-value-param,
-readability-magic-numbers,
CheckOptions:
- key: readability-function-cognitive-complexity.Threshold
value: 100
- key: readability-function-cognitive-complexity.IgnoreMacros
value: true
# Set naming conventions for your style below (there are dozens of naming settings possible):
# See https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html
- key: readability-identifier-naming.ClassCase
value: CamelCase

2
.gitignore vendored
View File

@ -1,10 +1,12 @@
.idea .idea
.trunk
.vc .vc
.vscode .vscode
*.a *.a
*.o *.o
*.bin *.bin
*.pc
build build
core core
core.* core.*

View File

@ -26,7 +26,7 @@ namespace klib {
Arena::Arena() Arena::Arena()
: store(nullptr), size(0), fd(0), arenaType(ARENA_UNINIT) : store(nullptr), size(0), fd(0), arenaType(ArenaType::Uninit)
{} {}
@ -39,20 +39,20 @@ Arena::~Arena()
void void
Arena::Initialize() Arena::Initialize()
{ {
assert(this->arenaType != ARENA_UNINIT); assert(this->arenaType != ArenaType::Uninit);
this->store = nullptr; this->store = nullptr;
this->size = 0; this->size = 0;
this->arenaType = ARENA_UNINIT; this->arenaType = ArenaType::Uninit;
this->fd = 0; this->fd = 0;
} }
int int
Arena::SetStatic(uint8_t *mem, size_t allocSize) Arena::SetStatic(uint8_t *mem, size_t memSize)
{ {
this->store = mem; this->store = mem;
this->size = allocSize; this->size = memSize;
this->arenaType = ARENA_STATIC; this->arenaType = ArenaType::Static;
return 0; return 0;
} }
@ -64,7 +64,7 @@ Arena::SetAlloc(size_t allocSize)
this->Destroy(); this->Destroy();
} }
this->arenaType = ARENA_ALLOC; this->arenaType = ArenaType::Alloc;
this->size = allocSize; this->size = allocSize;
this->store = new uint8_t[allocSize]; this->store = new uint8_t[allocSize];
if (this->store == nullptr) { if (this->store == nullptr) {
@ -85,7 +85,7 @@ Arena::MemoryMap(int memFileDes, size_t memSize)
this->Destroy(); this->Destroy();
} }
this->arenaType = ARENA_MMAP; this->arenaType = ArenaType::MemoryMapped;
this->size = memSize; this->size = memSize;
this->store = (uint8_t *) mmap(NULL, memSize, PROT_RW, MAP_SHARED, this->store = (uint8_t *) mmap(NULL, memSize, PROT_RW, MAP_SHARED,
memFileDes, 0); memFileDes, 0);
@ -224,18 +224,18 @@ Arena::Clear()
void void
Arena::Destroy() Arena::Destroy()
{ {
if (this->arenaType == ARENA_UNINIT) { if (this->arenaType == ArenaType::Uninit) {
return; return;
} }
switch (this->arenaType) { switch (this->arenaType) {
case ARENA_STATIC: case ArenaType::Static:
break; break;
case ARENA_ALLOC: case ArenaType::Alloc:
delete this->store; delete this->store;
break; break;
#if defined(__linux__) #if defined(__linux__)
case ARENA_MMAP: case ArenaType::MemoryMapped:
if (munmap(this->store, this->size) == -1) { if (munmap(this->store, this->size) == -1) {
abort(); abort();
return; return;
@ -257,7 +257,7 @@ Arena::Destroy()
} }
this->arenaType = ARENA_UNINIT; this->arenaType = ArenaType::Uninit;
this->size = 0; this->size = 0;
this->store = nullptr; this->store = nullptr;
return; return;
@ -266,24 +266,24 @@ Arena::Destroy()
std::ostream & std::ostream &
operator<<(std::ostream &os, Arena &arena) operator<<(std::ostream &os, Arena &arena)
{ {
auto cursor = arena.Store(); auto cursor = arena.NewCursor();
char cursorString[33] = {0}; char cursorString[33] = {0};
snprintf(cursorString, 32, "%#016llx", snprintf(cursorString, 32, "%#016llx",
(long long unsigned int)cursor); (long long unsigned int)cursor);
os << "Arena<"; os << "Arena<";
switch (arena.Type()) { switch (arena.Type()) {
case ARENA_UNINIT: case ArenaType::Uninit:
os << "uninitialized"; os << "uninitialized";
break; break;
case ARENA_STATIC: case ArenaType::Static:
os << "static"; os << "static";
break; break;
case ARENA_ALLOC: case ArenaType::Alloc:
os << "allocated"; os << "allocated";
break; break;
#if defined(__linux__) #if defined(__linux__)
case ARENA_MMAP: case ArenaType::MemoryMapped:
os << "mmap/file"; os << "mmap/file";
break; break;
#endif #endif
@ -328,5 +328,18 @@ Arena::Write(const char *path)
return retc; return retc;
} }
uint8_t &
Arena::operator[](size_t index)
{
if (index > this->size) {
#if defined(DESKTOP_BUILD) and !defined(KLIB_NO_ASSERT)
throw std::range_error("index out of range");
#else
abort();
#endif
}
return this->store[index];
}
} // namespace klib } // namespace klib

172
Arena.h
View File

@ -4,24 +4,13 @@
/// \date 2023-10-06 /// \date 2023-10-06
/// \brief Memory management using an arena. /// \brief Memory management using an arena.
/// ///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// \section DESCRIPTION
/// Arena defines a memory management backend for pre-allocating memory. /// Arena defines a memory management backend for pre-allocating memory.
///
/// \section PLATFORM SUPPORT
///
/// Arena will build on the major platforms, but memory-mapped files are only
/// supported on Unix-like systems. File I/O on Windows, for example, reads the
/// file into an allocated arena. See Arena::Open for more details.
#ifndef KIMODEM_ARENA_H #ifndef KIMODEM_ARENA_H
@ -37,71 +26,156 @@
namespace klib { namespace klib {
/// \enum ArenaType describes the type of #Arena. /// \enum ArenaType
enum ArenaType : uint8_t { ///
/// ARENA_UNINIT is an unintialized arena. /// ArenaType describes the type of \class Arena.
ARENA_UNINIT, enum class ArenaType : uint8_t {
/// ARENA_STATIC is an arena backed by a static block of memory. /// Uninit is an unintialized arena.
ARENA_STATIC, Uninit,
/// ARENA_ALLOC is an arena backed by allocated memory. /// Static is an arena backed by a static block of memory.
ARENA_ALLOC, Static,
/// ARENA_MMAP is an arena backed by a memory-mapped file. /// Alloc is an arena backed by allocated memory.
ARENA_MMAP, Alloc,
/// MemoryMapped is an arena backed by a memory-mapped file.
MemoryMapped,
}; };
/// Arena is the class that implements a memory arena. /// Arena is the class that implements a memory arena.
///
/// The Arena uses the concept of a cursor to point to memory in the arena. The
/// #NewCursor and #End methods return pointers to the start and end of the
/// arena memory.
///
/// The arena should be initalized with one of the Set methods (SetStatic,
/// SetAlloc) or one of the file-based options (Create, Open, MemoryMap). At
/// this point, no further memory management should be done until the end of the
/// arena's life, at which point Destroy should be called.
class Arena { class Arena {
public: public:
/// An Arena is initialized with no backing memory.
Arena(); Arena();
~Arena(); ~Arena();
/* /// Initialize is intended for use only with systems that do not
* InitializeArena is intended for use only with systems that /// initialize new variables to zero. It should be called exactly once,
* do not initialize new variables to zero. It should be called /// at the start of the program. Any other time the arena needs to be
* exactly once, at the start of the program. Any other time the /// reset, it should be called with #Clear or #Destroy.
* arena needs to be reset, it should be called with Clear or
* Destroy.
*/
void Initialize(); void Initialize();
int SetStatic(uint8_t *, size_t); /// SetStatic points the arena to a chunk of memory. That memory is
/// intended to be statically allocated, e.g. via a global `static
/// uint8_t memory[memSize];`. If the arena is already backed, then
/// #Destroy will be called first.
///
/// \param mem A pointer to a section of memory, preferably statically
/// allocated.
/// \param memSize The size of the memory section.
/// \return Returns 0 on success and -1 on error.
int SetStatic(uint8_t *mem, size_t memSize);
/// SetAlloc allocates a chunk of memory for the arena; the arena takes
/// ownership. If the arena is already backed, then #Destroy will be
/// called first.
///
/// \param allocSize The size of memory to allocate.
/// \return Returns 0 on success and -1 on error.
int SetAlloc(size_t allocSize); int SetAlloc(size_t allocSize);
#if defined(__linux__) #if defined(__linux__)
/// MemoryMap points the arena to a memory-mapped file. This is
/// currently only supported on Linux. If the arena is already backed,
/// then #Destroy will be called first.
///
/// \param memFileDes File descriptor to map into memory.
/// \param memSize The size of memory to map.
/// \return Returns 0 on success and -1 on error.
int MemoryMap(int memFileDes, size_t memSize); // Arena will own fd. int MemoryMap(int memFileDes, size_t memSize); // Arena will own fd.
/// Create creates a new file, truncating it if it already exists. On
/// Unix-based platforms, the arena will be backed by a memory via
/// #MemoryMap. On other platforms (e.g. Windows), the arena will read
/// the file into an allocated arena, calling #SetAlloc.
///
/// \param path The path to the file that should be created.
/// \param fileSize The size of the file to create.
/// \param mode The permissions to load.
/// \return Returns 0 on success and -1 on error.
int Create(const char *path, size_t fileSize, mode_t mode); int Create(const char *path, size_t fileSize, mode_t mode);
/// Open reads a file into the arena; the file must already exist. On
/// Unix-based platforms, the arena will be backed by a memory via
/// #MemoryMap. On other platforms (e.g. Windows), the arena will read
/// the file into an allocated arena, calling #SetAlloc. On these
/// platforms, in order to persist changes, #Write must be called to
/// sync changes to disk.
///
/// \param path The path to the file to be loaded.
/// \return Returns 0 on success and -1 on error.
int Open(const char *path); int Open(const char *path);
#elif defined(__WIN64__) || defined(__WIN32__) #elif defined(__WIN64__) || defined(__WIN32__)
int Open(const char *path); int Open(const char *path);
#endif #endif
/// NewCursor returns a pointer to the start of the memory in the arena.
///
/// \return A pointer to the start of the arena memory.
uint8_t *NewCursor() const { return this->store; } uint8_t *NewCursor() const { return this->store; }
/// End returns a pointer to the end of the arena memory.
///
/// \return A pointer to the end of the arena memory.
uint8_t *End() { return this->store + this->size; } uint8_t *End() { return this->store + this->size; }
/// CursorInArena checks whether the cursor is still in the arena.
///
/// \param cursor A pointer that ostensibly points to the arena's
/// memory.
/// \return True if the cursor is still in the arena.
bool CursorInArena(const uint8_t *cursor); bool CursorInArena(const uint8_t *cursor);
/// Returns the current size of the arena.
///
/// \return The size of the arena.
size_t Size() const size_t Size() const
{ return this->size; } { return this->size; }
uint8_t Type() const /// Type returns an ArenaType describing the arena.
///
/// \return An ArenaType describing the backing memory for the arena.
ArenaType Type() const
{ return this->arenaType; } { return this->arenaType; }
uintptr_t Store() { return (uintptr_t)this->store; } /// Ready returns whether the arena is initialized.
bool Ready() const { return this->Type() != ArenaType::Uninit; };
bool Ready() const { return this->Type() != ARENA_UNINIT; }; /// Clear zeroizes the memory in the arena.
void Clear(); void Clear();
void Destroy(); /* dispose of any memory used by arena */
/* /// Destroy removes any backing memory (e.g. from SetAlloc or
* DANGER: if arena is file backed (mmap or open), DO NOT WRITE TO THE /// MemoryMap). This does not call Clear; if the arena was backed by a
* BACKING FILE! /// file that should be persisted, it would wipe out the file.
*/ void Destroy();
/// Write dumps the arena to a file suitable for loading by Open.
///
/// \warning DANGER: if arena is memory-mapped, DO NOT WRITE TO THE
/// BACKING FILE!
///
/// \param path
/// \return Returns 0 on success and -1 on error.
int Write(const char *path); int Write(const char *path);
uint8_t &operator[](size_t index) /// This operator allows the data in the arena to be accessed
{ return this->store[index]; } /// as if it were an array. If the index is out of bounds, it
/// will throw a range_error.
///
/// \throws std::range_error.
///
/// \param index The index to retrieve.
/// \return
uint8_t &operator[](size_t index);
private: private:
uint8_t *store; uint8_t *store;
@ -111,6 +185,14 @@ private:
}; };
/// Write an Arena out to the output stream.
///
/// The resulting output looks something like
/// Arena<allocated>@0x7fff91dfad70,store<128B>@0x0055d6c5881ec0.
///
/// \param os
/// \param arena
/// \return
std::ostream &operator<<(std::ostream& os, Arena &arena); std::ostream &operator<<(std::ostream& os, Arena &arena);

View File

@ -3,22 +3,7 @@
/// \author K. Isom <kyle@imap.cc> /// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09 /// \date 2023-10-09
/// ///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
@ -31,7 +16,11 @@
namespace klib { namespace klib {
/// The defaultCapacity for a new Buffer is a reasonably arbitrary starting
/// point.
constexpr size_t defaultCapacity = 32; constexpr size_t defaultCapacity = 32;
/// maxReasonableLine is the longest a reasonable line could be. It assumes
/// something like a long, unprettified JSON strong or the like.
constexpr size_t maxReasonableLine = 8192; constexpr size_t maxReasonableLine = 8192;
@ -323,7 +312,11 @@ uint8_t &
Buffer::operator[](size_t index) Buffer::operator[](size_t index)
{ {
if (index > this->length) { if (index > this->length) {
#if defined(DESKTOP_BUILD) and !defined(KLIB_NO_ASSERT)
throw std::range_error("array index out of bounds"); throw std::range_error("array index out of bounds");
#else
abort();
#endif
} }
return this->contents[index]; return this->contents[index];
} }

View File

@ -1,26 +1,9 @@
/// ///
/// \file Buffer.hcc /// \file Buffer.h
/// \author K. Isom <kyle@imap.cc> /// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09 /// \date 2023-10-09
/// \brief Buffer implements basic line buffers. /// \brief Buffer implements basic line buffers.
/// ///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// \section DESCRIPTION
/// Buffer implements a basic uint8_t line buffer that is intended for use in text /// Buffer implements a basic uint8_t line buffer that is intended for use in text
/// editing. It allocates memory in powers of two, and will grow or shrink /// editing. It allocates memory in powers of two, and will grow or shrink
/// as needed. /// as needed.
@ -221,8 +204,11 @@ private:
bool autoTrim; bool autoTrim;
}; };
/// The << operator is overloaded to write out the contents of the Buffer.
std::ostream &operator<<(std::ostream &os, const Buffer &buf); std::ostream &operator<<(std::ostream &os, const Buffer &buf);
/// Two Buffers are not equal if their lengths differ or if their contents
/// differ.
inline bool operator!=(const Buffer &lhs, const Buffer &rhs) { return !(lhs == rhs); }; inline bool operator!=(const Buffer &lhs, const Buffer &rhs) { return !(lhs == rhs); };
} // namespace klib } // namespace klib

View File

@ -2,6 +2,9 @@
find_package(Doxygen) find_package(Doxygen)
if (${DOXYGEN_FOUND}) if (${DOXYGEN_FOUND})
set(DOXYGEN_GENERATE_MAN YES)
set(DOXYGEN_GENERATE_LATEX YES)
#set(DOXYGEN_EXTRACT_ALL YES)
doxygen_add_docs(klib_docs doxygen_add_docs(klib_docs
${HEADER_FILES} ${SOURCE_FILES}) ${HEADER_FILES} ${SOURCE_FILES})

View File

@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
project(klib LANGUAGES CXX VERSION 0.0.1) project(klib LANGUAGES CXX
VERSION 0.0.1
DESCRIPTION "Kyle's C++ library")
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
@ -16,6 +18,7 @@ endif()
add_compile_options("-DDESKTOP_BUILD") add_compile_options("-DDESKTOP_BUILD")
set(HEADER_FILES set(HEADER_FILES
klib.h
Arena.h Arena.h
Buffer.h Buffer.h
Dictionary.h Dictionary.h
@ -33,11 +36,7 @@ add_library(klib STATIC
Arena.cc Arena.cc
Buffer.cc Buffer.cc
Dictionary.cc Dictionary.cc
TLV.cc TLV.cc)
)
install(TARGETS klib LIBRARY DESTINATION ${PREFIX}/lib)
install(FILES ${HEADER_FILES} DESTINATION include/klib)
install(FILES klibConfig.cmake DESTINATION share/klib/cmake)
include(CTest) include(CTest)
enable_testing() enable_testing()
@ -61,5 +60,17 @@ write_basic_package_version_file(
COMPATIBILITY AnyNewerVersion COMPATIBILITY AnyNewerVersion
) )
add_custom_target(cloc
COMMAND cloc ${SOURCE_FILES} ${HEADER_FILES}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
configure_file(klib.pc.in klib.pc @ONLY)
install(TARGETS klib LIBRARY DESTINATION ${PREFIX}/lib)
install(FILES ${HEADER_FILES} DESTINATION include/{klib})
install(FILES klibConfig.cmake DESTINATION share/klib/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/klib.pc DESTINATION lib/pkgconfig)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/klib)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man DESTINATION share/man)
include(CMakePack.txt) include(CMakePack.txt)
include(CMakeDocs.txt) include(CMakeDocs.txt)

View File

@ -7,7 +7,6 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
# Debian settings # Debian settings
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "K. Isom") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "K. Isom")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity C++ library") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity C++ library")

View File

@ -6,13 +6,17 @@
#include <iostream> #include <iostream>
#endif #endif
namespace klib {
bool bool
Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res) Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
{ {
res.Tag = this->kTag; res.Tag = this->kTag;
uint8_t *cursor = TLV::FindTag(this->arena, NULL, res); uint8_t *cursor = TLV::FindTag(this->arena, nullptr, res);
while (cursor != NULL) { while (cursor != nullptr) {
if ((klen == res.Len) && if ((klen == res.Len) &&
(memcmp(res.Val, key, klen) == 0)) { (memcmp(res.Val, key, klen) == 0)) {
TLV::ReadFromMemory(res, cursor); TLV::ReadFromMemory(res, cursor);
@ -33,11 +37,11 @@ int
Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen) Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen)
{ {
TLV::Record rec; TLV::Record rec;
uint8_t *cursor = NULL; uint8_t *cursor = nullptr;
SetRecord(rec, this->kTag, klen, key); SetRecord(rec, this->kTag, klen, key);
cursor = this->seek(key, klen); cursor = this->seek(key, klen);
if (cursor != NULL) { if (cursor != nullptr) {
TLV::DeleteRecord(this->arena, cursor); TLV::DeleteRecord(this->arena, cursor);
TLV::DeleteRecord(this->arena, cursor); TLV::DeleteRecord(this->arena, cursor);
} }
@ -46,13 +50,13 @@ Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen)
return -1; return -1;
} }
cursor = TLV::WriteToMemory(this->arena, NULL, rec); cursor = TLV::WriteToMemory(this->arena, nullptr, rec);
if (cursor == NULL) { if (cursor == nullptr) {
return -1; return -1;
} }
SetRecord(rec, this->vTag, vlen, val); SetRecord(rec, this->vTag, vlen, val);
if (TLV::WriteToMemory(this->arena, NULL, rec) == NULL) { if (TLV::WriteToMemory(this->arena, nullptr, rec) == nullptr) {
return -1; return -1;
} }
@ -67,9 +71,9 @@ Dictionary::seek(const char *key, uint8_t klen)
TLV::Record rec; TLV::Record rec;
rec.Tag = this->kTag; rec.Tag = this->kTag;
uint8_t *cursor = TLV::LocateTag(this->arena, NULL, rec); uint8_t *cursor = TLV::LocateTag(this->arena, nullptr, rec);
while (cursor != NULL) { while (cursor != nullptr) {
if ((klen == rec.Len) && (this->kTag == rec.Tag)) { if ((klen == rec.Len) && (this->kTag == rec.Tag)) {
if (memcmp(rec.Val, key, klen) == 0) { if (memcmp(rec.Val, key, klen) == 0) {
return cursor; return cursor;
@ -79,14 +83,14 @@ Dictionary::seek(const char *key, uint8_t klen)
cursor = TLV::LocateTag(this->arena, cursor, rec); cursor = TLV::LocateTag(this->arena, cursor, rec);
} }
return NULL; return nullptr;
} }
bool bool
Dictionary::Has(const char *key, uint8_t klen) Dictionary::Contains(const char *key, uint8_t klen)
{ {
return this->seek(key, klen) != NULL; return this->seek(key, klen) != nullptr;
} }
@ -95,10 +99,10 @@ Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen)
{ {
size_t required = 0; size_t required = 0;
uintptr_t remaining = 0; uintptr_t remaining = 0;
uint8_t *cursor = NULL; uint8_t *cursor = nullptr;
cursor = TLV::FindEmpty(this->arena, NULL); cursor = TLV::FindEmpty(this->arena, nullptr);
if (cursor == NULL) { if (cursor == nullptr) {
return false; return false;
} }
@ -111,41 +115,39 @@ Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen)
} }
#if defined(DESKTOP_BUILD) std::ostream &
void operator<<(std::ostream &os, const Dictionary &dictionary)
Dictionary::DumpKVPairs()
{ {
uint8_t *cursor = (this->arena).NewCursor(); #if defined(DESKTOP_BUILD)
uint8_t *cursor = (dictionary.arena).NewCursor();
TLV::Record rec; TLV::Record rec;
TLV::ReadFromMemory(rec, cursor); TLV::ReadFromMemory(rec, cursor);
std::cout << "Dictionary KV pairs" << std::endl; os << "Dictionary KV pairs" << std::endl;
if (rec.Tag == TAG_EMPTY) { if (rec.Tag == TLV::TAG_EMPTY) {
std::cout << "\t(NONE)" << std::endl; os << "\t(NONE)" << std::endl;
return; return os;
} }
while ((cursor != NULL) && (rec.Tag != TAG_EMPTY)) { while ((cursor != nullptr) && (rec.Tag != TLV::TAG_EMPTY)) {
std::cout << "\t" << rec.Val << "->"; os << "\t" << rec.Val << "->";
cursor = TLV::SkipRecord(rec, cursor); cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor); TLV::ReadFromMemory(rec, cursor);
std::cout << rec.Val << std::endl; os << rec.Val << std::endl;
cursor = TLV::SkipRecord(rec, cursor); cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor); TLV::ReadFromMemory(rec, cursor);
} }
}
#else
void
Dictionary::DumpKVPairs()
{
}
#endif #endif
return os;
}
void
int
Dictionary::DumpToFile(const char *path) Dictionary::DumpToFile(const char *path)
{ {
this->arena.Write(path); return this->arena.Write(path);
} }
} // namespace klib

View File

@ -1,3 +1,10 @@
///
/// \file klib.h
/// \author kyle
/// \date 2023-10-06
///
#ifndef KLIB_DICTIONARY_H #ifndef KLIB_DICTIONARY_H
#define KLIB_DICTIONARY_H #define KLIB_DICTIONARY_H
@ -6,33 +13,102 @@
#include "TLV.h" #include "TLV.h"
#define DICTIONARY_TAG_KEY 1 static constexpr uint8_t DICTIONARY_TAG_KEY = 1;
#define DICTIONARY_TAG_VAL 2 static constexpr uint8_t DICTIONARY_TAG_VAL = 2;
namespace klib {
/* /*
* A Dictionary is a collection of key-value pairs, similar to how * A Dictionary is a collection of key-value pairs, similar to how
* a dictionary is a mapping of names to definitions. * a dictionary is a mapping of names to definitions.
*/ */
/// Dictionary implements a key-value store on top of Arena and TLV::Record.
///
/// phonebook of SSIDs and WPA keys on a microcontroller. This phonebook had to
/// be stored in persistent NVRAM storage, preëmpting the use of std::map or
/// similar. The hardware in use was also not conducive to more expensive
/// options. It was originally named Phonebook until it was adapted to a more
/// general-purpose data structure.
///
/// Keys and vales are stored as sequential pairs of TLV records; they are
/// expected to contain string values but this isn't necessarily the case. The
/// tag values default to a tag of DICTIONARY_TAG_KEY, and values to a tag of
/// DICTIONARY_TAG_VAL.
class Dictionary { class Dictionary {
public: public:
/// A Dictionary can be initialized with just a backing Arena.
///
/// \param arena The backing arena for the Dictionary.
Dictionary(Arena &arena) : Dictionary(Arena &arena) :
arena(arena), arena(arena),
kTag(DICTIONARY_TAG_KEY), kTag(DICTIONARY_TAG_KEY),
vTag(DICTIONARY_TAG_VAL) {} ; vTag(DICTIONARY_TAG_VAL)
{};
/// A Dictionary can also be configured with custom key and value types.
///
/// \param arena The backing arena for the Dictionary.
/// \param kt The value to use for key tags.
/// \param vt The value to use for val tags.
Dictionary(Arena &arena, uint8_t kt, uint8_t vt) : Dictionary(Arena &arena, uint8_t kt, uint8_t vt) :
arena(arena), arena(arena),
kTag(kt), kTag(kt),
vTag(vt) {}; vTag(vt)
{};
/// Lookup checks to see if the Dictionary has a value under key.
///
/// \param key The key to search for.
/// \param klen The length of the key.
/// \param res The TLV::Record to store the value in;
/// \return True if the key was found, false otherwise.
bool Lookup(const char *key, uint8_t klen, TLV::Record &res); bool Lookup(const char *key, uint8_t klen, TLV::Record &res);
/// Set adds a pairing for key → value in the Dictionary.
///
/// If the key is already present in the dictionary, both the
/// key and value are deleted, and a new pair is insert.
///
/// \warning If the key is present, but there isn't enough space to
/// store the new Val, the Dictionary will not contain either key
/// or value.
///
///
/// \param key The key to associate.
/// \param klen The length of the key.
/// \param val The value to associate.
/// \param vlen The length of the value.
/// \return Returns 0 on success and -1 on failure.
int Set(const char *key, uint8_t klen, const char *val, int Set(const char *key, uint8_t klen, const char *val,
uint8_t vlen); uint8_t vlen);
bool Has(const char *key, uint8_t klen);
void DumpKVPairs(); /// Contains checks the dictionary to see if it contains a given key.
void DumpToFile(const char *path); ///
/// \param key The key to look up.
/// \param klen The length of the key.
/// \return True if the key is in the Dictionary, otherwise false.
bool Contains(const char *key, uint8_t klen);
/// DumpToFile is a wrapper aorund a call to Arena::Write on the
/// underlying Arena.
///
/// \param path The path to the dumped file.
/// \return 0 on success, -1 on failure.
int DumpToFile(const char *path);
/// operator<< writes the key pairs to the output stream.
///
/// \param os The output stream to write to.
/// \param dictionary The dictionary to write out.
/// \return The output stream is returned.
friend std::ostream &operator<<(std::ostream &os,
const Dictionary &dictionary);
private: private:
uint8_t *seek(const char *key, uint8_t klen); uint8_t *seek(const char *key, uint8_t klen);
bool spaceAvailable(uint8_t klen, uint8_t vlen); bool spaceAvailable(uint8_t klen, uint8_t vlen);
Arena &arena; Arena &arena;
@ -41,4 +117,6 @@ private:
}; };
} // namespace klib
#endif #endif

28
TLV.cc
View File

@ -14,7 +14,7 @@ namespace TLV {
static bool static bool
spaceAvailable(Arena &arena, uint8_t *cursor, uint8_t len) spaceAvailable(Arena &arena, uint8_t *cursor, uint8_t len)
{ {
if (cursor == NULL) { if (cursor == nullptr) {
return false; return false;
} }
@ -33,21 +33,25 @@ clearUnused(Record &rec)
uint8_t * uint8_t *
WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec) WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec)
{ {
// If cursor is NULL, the user needs us to select an empty // If cursor is nullptr, the user needs us to select an empty
// slot for the record. If we can't find one, that's an // slot for the record. If we can't find one, that's an
// error. // error.
// //
// If, however, the user gives us a cursor, we'll trust it // If, however, the user gives us a cursor, we'll trust it
// (though spaceAvailable will sanity check that cursor). // (though spaceAvailable will sanity check that cursor).
if (cursor == NULL) { if (cursor == nullptr) {
cursor = FindEmpty(arena, cursor); cursor = FindEmpty(arena, cursor);
if (cursor == NULL) { if (cursor == nullptr) {
return NULL; return nullptr;
} }
} }
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (!spaceAvailable(arena, cursor, rec.Len)) { if (!spaceAvailable(arena, cursor, rec.Len)) {
return NULL; return nullptr;
} }
memcpy(cursor, &rec, REC_SIZE(rec)); memcpy(cursor, &rec, REC_SIZE(rec));
@ -98,21 +102,21 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{ {
uint8_t tag, len; uint8_t tag, len;
if (cursor == NULL) { if (cursor == nullptr) {
cursor = arena.NewCursor(); cursor = arena.NewCursor();
} }
while ((tag = cursor[0]) != rec.Tag) { while ((tag = cursor[0]) != rec.Tag) {
len = cursor[1]; len = cursor[1];
if (!spaceAvailable(arena, cursor, len)) { if (!spaceAvailable(arena, cursor, len)) {
return NULL; return nullptr;
} }
cursor += len; cursor += len;
cursor += 2; cursor += 2;
} }
if (tag != rec.Tag) { if (tag != rec.Tag) {
return NULL; return nullptr;
} }
if (tag != TAG_EMPTY) { if (tag != TAG_EMPTY) {
@ -142,7 +146,11 @@ SkipRecord(Record &rec, uint8_t *cursor)
void void
DeleteRecord(Arena &arena, uint8_t *cursor) DeleteRecord(Arena &arena, uint8_t *cursor)
{ {
if (cursor == NULL) { if (cursor == nullptr) {
return;
}
if (!arena.CursorInArena(cursor)) {
return; return;
} }

116
TLV.h
View File

@ -1,35 +1,76 @@
///
/// \file TLV.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief TLV.h implements basic tag-length-value records.
///
/// TLV implements tag-length-value (TLV) records. Each record can have
/// a maximum length of 253 bytes; each TLV record occupies a fixed 255
/// bytes in memory. TLV records don't allocate memory.
///
/// This system uses an Arena as a backing store.
///
#ifndef KIMODEM_TLV_H #ifndef KIMODEM_TLV_H
#define KIMODEM_TLV_H #define KIMODEM_TLV_H
#include <cstdint> #include <cstdint>
#include <array>
#include "Arena.h" #include "Arena.h"
using namespace klib;
#ifndef TLV_MAX_LEN
#define TLV_MAX_LEN 253
#endif
#define TAG_EMPTY 0
namespace klib { namespace klib {
namespace TLV { namespace TLV {
#ifndef TLV_MAX_LEN
static constexpr size_t TLV_MAX_LEN = 253;
#endif
static constexpr uint8_t TAG_EMPTY = 0;
/// Record describes a tag-length-value record.
///
/// TLV records occupy a fixed size in memory, which can be controlled with the
/// TLV_MAX_LEN define. If this isn't defined, it defaults to a size of 253.
/// When writen to an Arena, it occupies Len + 2 bytes. The strings
/// are not null-terminated in the arena.
struct Record { struct Record {
/// A Tag is used to identify the type of this record.
uint8_t Tag; uint8_t Tag;
/// Len describes the number of bytes stored in #Val.
uint8_t Len; uint8_t Len;
char Val[TLV_MAX_LEN]; /// Val contains the data in the record.
uint8_t Val[TLV_MAX_LEN];
}; };
uint8_t *WriteToMemory(Arena &, uint8_t *, Record &); /// WriteToMemory writes the TLV record into the arena at the location pointed
void ReadFromMemory(Record &, uint8_t *); /// to in the arena.
void SetRecord(Record &, uint8_t, uint8_t, const char *); ///
void DeleteRecord(Arena &, uint8_t *); /// \param arena The backing memory store.
/// \param cursor Pointer into the arena's memory.
/// \param rec A TLV record to be serialized.
/// \return A pointer the memory after the record.
uint8_t *WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec);
/// ReadFromMemory reads a record from the memory pointed to by the cursor.
///
/// \param rec The TLV record to be filled in.
/// \param cursor A pointer into an arena's memory store.
void ReadFromMemory(Record &rec, uint8_t *cursor);
/// SetRecord sets a record.
///
/// \param rec The record to be set.
/// \param tag The record's tag.
/// \param length The record's length.
/// \param data The data to fill the record with.
void SetRecord(Record &rec, uint8_t tag, uint8_t length, const char *data);
/// DeleteRecord removes the record from the arena. All records ahead of this
/// record are shifted backwards so that there are no gaps.
void DeleteRecord(Arena &arena, uint8_t *cursor);
/* /*
* returns a pointer to memory where the record was found, * returns a pointer to memory where the record was found,
@ -37,11 +78,48 @@ void DeleteRecord(Arena &, uint8_t *);
* FindTag will call LocateTag and then SkipRecord if the * FindTag will call LocateTag and then SkipRecord if the
* tag was found. * tag was found.
*/ */
uint8_t *FindTag(Arena &, uint8_t *, Record &); /// FindTag finds the next occurrence of the record's tag.
uint8_t *LocateTag(Arena &, uint8_t *, Record &); ///
/// The record must have a tag set, which tells FindTag which tag to look for.
/// If found, it fills the record. \see LocateTag.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the next record is
/// returned; otherwise nullptr is returned.
uint8_t *FindTag(Arena &arena, uint8_t *cursor, Record &rec);
uint8_t *FindEmpty(Arena &, uint8_t *); /// LocateTag operates similarly to FindTag, but the cursor points to the
uint8_t *SkipRecord(Record &, uint8_t *); /// beginning of the found record.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the record is
/// returned; otherwise nullptr is returned.
uint8_t *LocateTag(Arena &arena, uint8_t *cursor, Record &rec);
/// FindEmpty finds a pointer the next available empty space.
///
/// \return A cursor to the start of empty space in the arena, or nullptr
/// if there is no more empty space available.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \return If the arena has space available, a cursor pointing the start
/// of empty space; otherwise, nullptr is returned.
uint8_t *FindEmpty(Arena &arena, uint8_t *cursor);
/// SkipRecord skips the cursor to the next record.
///
/// \param rec The record that should be skipped.
/// \param cursor A pointer to the record in the arena.
/// \return The pointer to the next record in the arena.
uint8_t *SkipRecord(Record &rec, uint8_t *cursor);
} // namespace TLV } // namespace TLV

View File

@ -15,7 +15,7 @@ namespace klib {
void void
TestAssert(bool condition, std::string message = "Assertion failed.") TestAssert(bool condition, std::string message = "Assertion failed.")
{ {
#if defined(NDEBUG) #if defined(NDEBUG) || defined(KLIB_NO_ASSERT)
if (!condition) { if (!condition) {
throw AssertionFailed(message); throw AssertionFailed(message);
} }

34
Test.h
View File

@ -1,27 +1,43 @@
// ///
// Created by kyle on 2023-10-09. /// \file Test.h
// /// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
#include <string> /// \brief Test.h implements basic testing tools.
///
#ifndef KLIB_TEST_H #ifndef KLIB_TEST_H
#define KLIB_TEST_H #define KLIB_TEST_H
#include <string>
namespace klib { namespace klib {
void /// TestAssert is a variant on the assert macro.
TestAssert(bool condition, std::string message); ///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// In addition to NDEBUG, KLIB_NO_ASSERT will suppress assertions.
///
/// \throws AssertionFailed
///
/// \param condition The condition to assert.
/// \param message The message that should be displayed if condition is false.
inline void TestAssert(bool condition, std::string message);
/// AssertionFailed indicates that some invariant didn't hold.
class AssertionFailed : public std::exception { class AssertionFailed : public std::exception {
public: public:
/// AssertionFailed is constructed with a message describing what
/// failed.
explicit AssertionFailed(std::string message); explicit AssertionFailed(std::string message);
/// what returns a message describing the exception.
char *what(); char *what();
public: private:
std::string msg; std::string msg;
}; };

View File

@ -4,6 +4,7 @@
#include "Arena.h" #include "Arena.h"
#include "Dictionary.h" #include "Dictionary.h"
#include "testFixtures.h" #include "testFixtures.h"
using namespace klib;
constexpr char TEST_KVSTR1[] = "foo"; constexpr char TEST_KVSTR1[] = "foo";
@ -56,18 +57,18 @@ main(int argc, const char *argv[])
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN3, TEST_KVSTR3); TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN3, TEST_KVSTR3);
Dictionary dict(arena); Dictionary dict(arena);
assert(!dict.Has(TEST_KVSTR2, TEST_KVSTRLEN2)); assert(!dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
assert(testSetKV(dict, TEST_KVSTR1, TEST_KVSTRLEN1, TEST_KVSTR3, assert(testSetKV(dict, TEST_KVSTR1, TEST_KVSTRLEN1, TEST_KVSTR3,
TEST_KVSTRLEN3)); TEST_KVSTRLEN3));
dict.DumpKVPairs(); std::cout << dict;
assert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR3, assert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR3,
TEST_KVSTRLEN3)); TEST_KVSTRLEN3));
dict.DumpKVPairs(); std::cout << dict;
assert(dict.Has(TEST_KVSTR2, TEST_KVSTRLEN2)); assert(dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
assert(testSetKV(dict, TEST_KVSTR4, TEST_KVSTRLEN4, TEST_KVSTR5, assert(testSetKV(dict, TEST_KVSTR4, TEST_KVSTRLEN4, TEST_KVSTR5,
TEST_KVSTRLEN5)); TEST_KVSTRLEN5));
dict.DumpKVPairs(); std::cout << dict;
assert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value)); assert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
assert(cmpRecord(value, expect)); assert(cmpRecord(value, expect));
@ -75,7 +76,7 @@ main(int argc, const char *argv[])
std::cout << "test overwriting key" << std::endl; std::cout << "test overwriting key" << std::endl;
assert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR6, assert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR6,
TEST_KVSTRLEN6)); TEST_KVSTRLEN6));
dict.DumpKVPairs(); std::cout << dict;
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN6, TEST_KVSTR6); TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN6, TEST_KVSTR6);
std::cout << "\tlookup" << std::endl; std::cout << "\tlookup" << std::endl;
assert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value)); assert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
@ -85,7 +86,7 @@ main(int argc, const char *argv[])
std::cout << "\tadd new key to dictionary" << std::endl; std::cout << "\tadd new key to dictionary" << std::endl;
assert(testSetKV(dict, TEST_KVSTR3, TEST_KVSTRLEN3, TEST_KVSTR5, assert(testSetKV(dict, TEST_KVSTR3, TEST_KVSTRLEN3, TEST_KVSTR5,
TEST_KVSTRLEN5)); TEST_KVSTRLEN5));
dict.DumpKVPairs(); std::cout << dict;
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN5, TEST_KVSTR5); TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN5, TEST_KVSTR5);
assert(dict.Lookup(TEST_KVSTR4, TEST_KVSTRLEN4, value)); assert(dict.Lookup(TEST_KVSTR4, TEST_KVSTRLEN4, value));
@ -100,5 +101,5 @@ main(int argc, const char *argv[])
#endif #endif
arena.Clear(); arena.Clear();
dict.DumpKVPairs(); std::cout << dict;
} }

39
klib.h Normal file
View File

@ -0,0 +1,39 @@
///
/// \file klib.h
/// \author kyle
/// \created 2023-10-10
/// \brief klib is my collection of C++ data structures and code.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// \mainpage klib documentation
/// Hello, world.
///
/// \section Introduction
///
/// This is a collection of data structures and subroutines that I find
/// useful in building things.
#ifndef KLIB_KLIB_H
#define KLIB_KLIB_H
/// klib is the top-level namespace containing all the code in this library.
namespace klib {}
#endif // KLIB_KLIB_H

10
klib.pc.in Normal file
View File

@ -0,0 +1,10 @@
prefix="@CMAKE_INSTALL_PREFIX@"
exec_prefix="${prefix}"
libdir="${prefix}/lib"
includedir="${prefix}/include"
Name: @PROJECT_NAME@
Description: @CMAKE_PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -l@PROJECT_NAME@

View File

@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include "TLV.h"
#define ARENA_SIZE 128 #define ARENA_SIZE 128
@ -20,6 +21,10 @@
#define TEST_STR4 "How is a raven like a writing desk?" #define TEST_STR4 "How is a raven like a writing desk?"
#define TEST_STRLEN4 35 #define TEST_STRLEN4 35
namespace klib {
static bool static bool
cmpRecord(TLV::Record &a, TLV::Record &b) cmpRecord(TLV::Record &a, TLV::Record &b)
{ {
@ -39,4 +44,7 @@ cmpRecord(TLV::Record &a, TLV::Record &b)
} }
#endif } // namespace klib
#endif // KLIB_TESTFIXTURES_H