Initial revision

This commit is contained in:
Jordan Bancino 2022-07-22 20:19:12 -04:00
commit d102ba8676
22 changed files with 2763 additions and 0 deletions

1
.cvsignore Normal file
View file

@ -0,0 +1 @@
build

8
.idea/Telodendria.iml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Telodendria.iml" filepath="$PROJECT_DIR$/.idea/Telodendria.iml" />
</modules>
</component>
</project>

52
.idea/workspace.xml Normal file
View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ChangeListManager">
<list default="true" id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2CD2pGJ1okjE4N8lOPrGKmwbXGw" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;
}
}</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="fb1df1f2-2a47-495d-89b9-277de6049bd1" name="Changes" comment="" />
<created>1658323016759</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1658323016759</updated>
<workItem from="1658323018463" duration="25597000" />
<workItem from="1658450495358" duration="6933000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

62
Telodendria.css Normal file
View file

@ -0,0 +1,62 @@
body {
margin: auto;
max-width: 8.5in;
padding: 0.25in;
}
.code {
background-color: #eee;
border-radius: 5px;
display: block;
padding: 0.5em 1em 1.5em 1em;
font-family: monospace;
white-space: pre;
overflow-x: scroll;
}
kbd {
background-color: #eee;
border-radius: 3px;
border: 1px solid #b4b4b4;
box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
color: #333;
display: inline-block;
font-size: .85em;
font-weight: 700;
line-height: 1;
padding: 2px 4px;
white-space: nowrap;
}
h1, h4, h5, h6 {
border-bottom: 1px dashed gray;
}
h4, h5, h6 {
width: fit-content;
}
a {
color: #0969da;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
table {
width: 100%;
border-collapse: collapse;
}
td, th {
border: 1px solid #eee;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #eee;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #212121;
color: white;
}
.code, tr:nth-child(even) {
background-color: #333333;
}
}

541
Telodendria.html Normal file
View file

@ -0,0 +1,541 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Jordan Bancino">
<meta name="description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<meta property="og:title"
content="Telodendria | A Matrix Homeserver written in ANSI C.">
<meta property="og:type" content="website">
<meta property="og:url"
content="https://bancino.net/pub/telodendria/telodendria.html">
<meta property="og:description"
content="Telodendria, a Matrix homeserver written in ANSI C.">
<link rel="stylesheet" href="Telodendria.css">
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
</head>
<body>
<h1 id="telodendria">Telodendria</h1>
<p>
<b>Telodendria:</b> The terminal branches of an axon.
</p>
<p>
<b><i>Note:</i></b> <b>Telodendria</b> is under <i>heavy</i> development.
Please see the <a href="#project-status">Project Status</a>.
</p>
<p>
<b>Telodendria</b> is a Matrix homeserver implementation written from
scratch in ANSI C. It is designed to be lightweight and simple, yet
functional. <b>Telodendria</b> differentiates itself from other Matrix
homeserver implementations because it:
<ul>
<li>
Is written in C, a stable, low-level programming language with a long
history, low build and runtime overhead, and wide compatibility.
</li>
<li>
Is written with minimalism as a primary design goal. Whenever possible
and practical, no third-party libraries are pulled in to the source
code. Everything <b>Telodendria</b> needs is custom written. As a
result, <b>Telodendria</b> depends only on a standard C compiler and
library to be built, and only a web server with CGI capabilities to
run.
</li>
<li>
Uses a flat-file directory structure to store data instead of a
database. This has a number of advantages:
<ul>
<li>It makes setup and maintenance much easier.</li>
<li>
It allows <b>Telodendria</b> to run on systems with fewer resources.
</li>
<li>
It provides both runtime and data safety and stability. Since no
database is running, there's fewer things that could go wrong because
there's a lot less code running on the system.
</li>
</ul>
</li>
<li>
Runs as a CGI application. <b>Telodendria</b> is delivered as a single
small, highly-optimized binary that can be dropped in a web server's
web root to be executed. This allows it to consume very few resources
and be very easy to set up.
</li>
</ul>
<p>
<b>Telodendria</b> is on Matrix! Check out the official Matrix rooms:
</p>
<table>
<tr>
<th>Room</th>
<th>Description</th>
</tr>
<tr>
<td>
<code>#telodendria-releases:bancino.net</code>
</td>
<td>
Get notified of new releases.
</td>
</tr>
<tr>
<td>
<code>#telodendria-general:bancino.net</code>
</td>
<td>
General discussion and support for <b>Telodendria</b>.
</td>
</tr>
<tr>
<td>
<code>#telodendria-issues:bancino.net</code>
</td>
<td>
Report issues with <b>Telodendria</b>.
</td>
</tr>
<tr>
<td>
<code>#telodendria-patches:bancino.net</code>
</td>
<td>
Submit code patches to the <b>Telodendria</b> project.
</td>
</tr>
</table>
<h2 id="table-of-contents">Table of Contents</h2>
<ul>
<li>
<a href="#telodendria">Telodendria</a>
<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li>
<a href="#getting-started">Getting Started</a>
<ul>
<li>
<a href="#install">Install Telodendria</a>
<ul>
<li><a href="#openbsd">OpenBSD</a></li>
<li><a href="#building-from-source">Building From Source</a></li>
</ul>
</li>
<li><a href="#configure">Configure Telodendria</a></li>
</ul>
</li>
<li>
<a href="#project-status">Project Status</a>
<ul>
<li><a href="#phase-1">Phase 1: Getting Off The Ground</a></li>
<li><a href="#phase-2">Phase 2: Building A Foundation</a></li>
<li><a href="#phase-3">Phase 3: Welcome To Matrix</a></li>
<li><a href="#phase-4">Phase 4: A Real Homeserver</a></li>
</ul>
</li>
<li><a href="#rationale">Rationale</a></li>
<li><a href="#project-goals">Project Goals</a></li>
<li><a href="#getting-support">Getting Support</a></li>
<li>
<li>
<a href="#documentation-status">Documentation Status</a>
</li>
<a href="#contributing">Contributing</a>
<ul>
<li><a href="#reporting-issues">Reporting Issues</a></li>
<li>
<a href="#Developing">Developing</a>
<ul>
<li><a href="#getting-the-code">Getting The Code</a></li>
<li><a href="#code-style">Code Style</a></li>
<li><a href="#submitting-patches">Subitting Patches</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#license">License</a></li>
<li><a href="#change-log">Change Log</a></li>
</ul>
</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<h3 id="install">Install Telodendria</h3>
<p>
If your operating system has an official package or port of
<b>Telodendria</b>, you should prefer to use that. If your operating
system's package or port is too out of date for your tastes, please
contact the package's maintainers to notify them, or offer to update
the package yourself.
</p>
<p>
If your operating system does not have an official package, see below
for instructions on building from source and use them to create one.
</p>
<h4 id="openbsd">OpenBSD</h4>
<p>
<b>Telodendria</b> is available in the ports tree and as a binary
package. You can install it with the following command:
</p>
<div class="code">
$ pkg_add telodendria
</div>
<h4 id="building-from-source">Building From Source</h4>
<p>
<b>Telodendria</b> is designed to be light enough that it can be built
from source on just about any operating system. It only has the
following requirements, all of which should be already available to
you on a sufficiently complete operating system:
</p>
<ul>
<li>
A standards-compliant C compiler with the C standard library. Because
<b>Telodendria</b> is written in ANSI C, it should compile on just about
any compiler, but the following compilers are known to work:
<ul>
<li>GCC</li>
<li>Clang</li>
<li>
Tiny C Compiler (<b>Note:</b> must edit <code>make.sh</code> and remove
<code>-Wl,-static -Wl,-gc-sections</code> from <code>LDFLAGS</code>)
</li>
</ul>
Other compilers should work as well, but you may have to play with the
flags in <code>make.sh</code>.
</li>
<li>
POSIX base utilities, including <code>find</code>, <code>stat</code>,
<code>env</code>, and compliant <code>sh</code>-like shell.
</li>
</ul>
<div class="code">
$ ./make.sh
</div>
<p>
If everything went well, that will produce
<code>telodendria.cgi</code>, which you can then place under your web
root and configure your web server to execute. You'll need to make sure
<code>/.well-known/matrix</code> and <code>/_matrix</code> and all the
paths under them actually execute <code>telodendria.cgi</code>. See the
provided OpenBSD <code>httpd.conf</code> for reference. Even if you
aren't using OpenBSD's <code>httpd(8)</code>, you should find its
configuration syntax simple enough to adequately demonstrate the proper
configuration.
</p>
<h3 id="configure">Configure Telodendria</h3>
<p>
Once you get <b>Telodendria</b> built and hooked into your web server,
you will have to write a configuration file for it. The configuration
file is just JSON, and it should be called
<code>Telodendria.json</code>.
</p>
<h2 id="project-status">Project Status</h2>
<p>
<b>Telodendria</b> is a very ambitious project. There's a lot that needs
to happen yet before it is even remotely usable. At the moment, there's
nothing that even remotely resembles a Matrix homeserver here; we're still
getting off the ground and building a foundation.
</p>
<p>
Just because there's nothing here yet doesn't mean you should go away
though! We desparately need help, so you are more than welcome to help
out if you want things to go quicker. Please see the
<a href="#contributing">Contributing</a> section for details on how you
can get involved.
</p>
<h3 id="phase-1">Phase 1: Getting Off The Ground</h3>
<ul>
<li><s>Name this project</s></li>
<li><s>Set up a CVS repository</s></li>
<li><s>Make CVS repository public</s></li>
<li><s>Write a coding style guide</s></li>
<li><s>Write a build script</s></li>
<li><s>Add a license</s></li>
<li><s>Add support and issue reporting guide</s></li>
<li><s>Add table of contents to this document</s></li>
</ul>
<h3 id="phase-2">Phase 2: Building A Foundation</h3>
<ul>
<li><s>Implement an array</s></li>
<li><s>Implement a logging facility</s></li>
<li><s>Implement argument parsing (<code>-c file -Vh</code>)</s></li>
<li><s>Implement a hash map</s></li>
<li><s>Combine library code files</s></li>
<li><s>Implement configuration file parsing using the hash map</s></li>
<li>Implement a JSON library using the hash map and array</li>
<li>Figure out how to write unit tests for array/hashmap/etc</li>
<li>Implement a simple HTTP server</li>
<li>
Design the server architecture
<ul>
<li>Route requests</li>
<li>Handle requests</li>
<li>Data abstraction layer</li>
<li>Error generation</li>
</ul>
</li>
</ul>
<h3 id="phase-3">Phase 3: Welcome To Matrix</h3>
<ul>
<li>
Implement the Client-Server API
</li>
<li>
Implement the Server-Server API
</li>
<li>
Implement the other Matrix APIs
</li>
</ul>
<h3 id="phase-4">Phase 4: A Real Homeserver</h3>
<ul>
<li>
Create an OpenBSD package and get it submitted to ports
</li>
<li>
Create a command line tool to manage Telodendria
<ul>
<li>Configuration file generation</li>
<li>User management</li>
<li>Room management</li>
</ul>
</li>
<li>
Migrate from Synapse. I run a Synapse homeserver right now, so somehow
I have to get all my data into the Telodendria format.
</li>
</ul>
<h2 id="documentation-status">Documentation Status</h2>
<p>
This documentation needs just a little work. Here's the things
on my list for that:
</p>
<ul>
<li>Update Rationale section</li>
<li>Update Project description (no longer a CGI binary)</li>
<li>Update project code requirements (ANSI C, POSIX.1c)</li>
</ul>
<h2 id="rationale">Rationale</h2>
<p>
This section explains
</p>
<p>
I want a lightweight Matrix homeserver designed for OpenBSD. I want a
homeserver that can be developed in <code>vi(1)</code> and compiled
with a C compiler. I want it to function entirely on a base OpenBSD
install without having to install any extra packages whatsoever. I've
found that the existing homeserver implementations are way
over-engineered and written in such a way that many programs and
libraries have to be pulled in to use them. I also want to learn how
Matrix works, and I want to understand the code I'm running on my
server.
</p>
<p>
So I wrote Telodendria.
</p>
<p>
Telodendria is written entirely in portable ANSI C. It depends on no
third-party C libraries other than the standard C library. The only
thing you need to run it is a web server that supports executing CGI
programs, and a directory that data can be written to. Everything
Telodendria needs to run itself is compiled into a single static
binary, and the source code can be built anywhere, right out of the
box.
</p>
<p>
Telodendria doesn't use a database like all the other homeservers.
Instead, it operates more like email: it uses a flat-file data
structure similar to maildir to store data. The advantage of this is
that it saves server maintainers from also having to maintain a
database. It greatly simplifies the process of getting a Matrix
homeserver up and running, and it makes it highly portable. It also is
extremely easy to back up and restore with base tools; just
<code>tar(1)</code> up the directory, and you're good to go.
</p>
<p>
Telodendria is developed and tested on OpenBSD, but you'll find that it
should run under any web server that supports CGI. I chose to write
Telodendria as a CGI program because anyone running an existing Matrix
server is likely running a web server acting as a reverse proxy in
front of it anyway, so why not just hook the homeserver directly into
the web server? That's one less daemon to run, which means memory and
CPU savings. CGI also allows Telodendria to remain single-threaded.
Each request that comes in is handled as its own process, and
operations are entirely isolated.
</p>
<h2 id="project-goals">Project Goals</h2>
<p>
The goals of this project are as follows:
</p>
<ul>
<li>
To be a production-ready Matrix server capable of handling a lot of
users. Telodendria should have good performance in many diverse
environments.
</li>
<li>
To have as few external build and run dependencies as possible. It
should be possible to compile Telodendria on any operating system out
of the box, and have it be totally statically linked, ready to run
under a <code>chroot(8)</code>-ed web server. You'll even notice that
the documentation is written in HTML directly, not Markdown, to remove
the dependency on a Markdown parser and renderer.
</li>
<li>
To be written in clean, elegant, and well-documented code. The goal is
to build a Matrix homeserver from the ground up, not just because I
don't the way existing homeservers are implemented, but also so I can
learn how Matrix really works, and maybe even teach others along the
way.
</li>
</ul>
<h2 id="getting-support">Getting Support</h2>
<p>
<b>Telodendria</b> is designed to be fairly straightforward, but that
doesn't mean there won't be hiccups along the way. If you are struggling
to get <b>Telodendria</b> up and running, you're more than welcome to
reach out for support. Just join the
<code>#telodendria-general:bancino.net</code> Matrix channel. Before
you do though, make sure you're running the latest version of
<b>Telodendria</b> and you've thoroughly read through all the
relevant documentation.
</p>
<h2 id="contributing">Contributing</h2>
<p>
<b>Telodendria</b> is an open source project. As such, it welcomes
contributions. There are many ways you can contribute, and any way you
can is greatly appreciated.
</p>
<h3 id="reporting-issues">Reporting Issues</h3>
<p>
If&mdash;after you've reached out to
<code>#telodendria-general:bancino.net</code>&mdash;it has been
determined that there is a problem with <b>Telodendria</b>, it should
be reported to <code>#telodendria-issues:bancino.net</code>. There it
can be discussed further. The issues channel serves as the official
issue tracker of <b>Telodendria</b>; although issues may be copied
into a <code>TODO</code> file in the CVS repository just so they
don't get lost.
</p>
<h3 id="developing">Developing</h3>
<p>
The primary language used to write <b>Telodendria</b> code is ANSI C.
Yes, that's the original C standard from 1989. The reason this standard
is chosen, and the reason that it will not be changed, is because the
original C is the most portable. Other languages you'll find in the
<b>Telodendria</b> repository are shell scripts and HTML. If you have
any experience at all with any of these languages, your contributions
are valuable. Please follow the guidelines in this section to ensure
the contribution workflow goes as smoothly as possible.
</p>
<h4 id="getting-the-code">Getting The Code</h4>
<p>
There are multiple ways to get the source code for <b>Telodendria</b>.
You can download an official release tarball from
<a href="https://bancino.net/pub/telodendria">here</a> if you would like,
but the preferred way is to check out the source code from CVS. This
makes generating patches a lot easier. If you do not have CVS, consult
your operating system's package repository to install it. CVS was the
chosen version control system for this project primarily because it is
built into OpenBSD.
</p>
<div class="code">
$ export CVSROOT=anoncvs@bancino.net:/cvs
$ cvs checkout Telodendria
$ cd Telodendria
</div>
<p>
You should now have the latest <b>Telodendria</b> source code. Follow
the <a href="#code-style">Code Style</a> as you make your changes.
</p>
<h4 id="code-style">Code Style</h4>
<p>
<b>Telodendria</b>'s code style is very unique. In general, these are
the conventions used by the code base.
</p>
<ul>
<li>
All function, enumeration, structure, and header names are
<code>CamelCase</code>. This is preferred to <code>snake_case</code>
because it is more compact.
</li>
<li>
<code>enum</code>s and <code>struct</code>s are always
<code>typedef</code>-ed to their same name. The <code>typedef</code>
occurs in the public API header, and the actual declaration occurs in
the private implementation header.
</li>
<li>
Indentation is done with spaces. This ensures that files look the same
for everyone. It also makes line wrapping rules much easier because
the indentations are the same size. Please configure your editor to
make the <kbd>Tab</kbd> key insert spaces, and if it does automatic
indentation, make sure it indents with spaces. If you cannot configure
your editor to insert spaces, then you can try running the code files
you were working on through <code>expand</code>. A unit of indentation
is 4 spaces.
</li>
<li>
Lines should not exceed 72 characters, including indentations. Some
developers use <code>vi(1)</code> in an 80x24 terminal to write code.
</li>
</ul>
<p>
This guide may be subject to change. The source code is the absolute
source of truth, so as long as you make your code look like the
code surrounding it, you should be fine.
</p>
<h4 id="submitting-patches">Submitting Patches</h4>
<p>
Submitting patches is fairly easy to do if you've got the CVS sources
checked out. Once you have made your changes, just run
<code>cvs diff</code>:
</p>
<div class="code">
$ cvs diff -uNp > your-changes.patch
</div>
<p>
Then, send the resulting patches to
<code>#telodendria-patches:bancino.net</code>, where they will be
promptly reviewed by the community.
</p>
<h2 id="license">License</h2>
<p>
All of the code and documentation for <b>Telodendria</b> is licensed
under the following terms and conditions:
</p>
<div class="code">
Copyright (C) 2022 Jordan Bancino &lt;@jordan:bancino.net&gt;
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</div>
<h2 id="change-log">Change Log</h2>
<p>
At this time, Telodendria does not have any tagged releases because it
is not yet functional as a Matrix homeserver. Please check out the <a
href="#project-status">Project Status</a> to see where things are
currently at.
</p>
</body>
</html>

9
contrib/httpd.conf Normal file
View file

@ -0,0 +1,9 @@
#
# httpd.conf: An OpenBSD httpd(8) configuration file for running
# Telodendria. Note that this is a development configuration that
# should be adapted using the httpd.conf(5) man page for production
# use.
#
server "matrix" {
listen on localhost port http
}

13
contrib/telodendria.conf Normal file
View file

@ -0,0 +1,13 @@
# Telodendria configuration file
log "/var/log/telodendria.log" {
level "message";
timestampFormat "none";
color "true";
};
threads "4";
data-dir "/var/telodendria";
federation "true";

57
make.sh Normal file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env sh
TELODENDRIA_VERSION="0.0.1"
HEADERS="-D_POSIX_C_SOURCE=199506L -DTELODENDRIA_VERSION=\"$TELODENDRIA_VERSION\""
INCLUDES="-Isrc/include"
CC="${CC:-cc}"
CFLAGS="-Wall -Werror -pedantic -std=c89 -O3 $HEADERS $INCLUDES"
LDFLAGS="-static -flto -fdata-sections -ffunction-sections -s -Wl,-static -Wl,-gc-sections"
PROG="telodendria"
mod_time() {
if [ -n "$1" ] && [ -f "$1" ]; then
case "$(uname)" in
Linux)
stat -c %Y "$1"
;;
*BSD)
stat -f %m "$1"
;;
*)
echo "0"
;;
esac
else
echo "0"
fi
}
mkdir -p build
do_rebuild=0
objs=""
for src in $(find src -name '*.c'); do
obj=$(echo "$src" | sed -e 's/^src/build/' -e 's/\.c$/\.o/')
objs="$objs $obj"
if [ $(mod_time "$src") -gt $(mod_time "$obj") ]; then
echo "CC $obj"
obj_dir=$(dirname "$obj")
mkdir -p "$obj_dir"
if ! $CC $CFLAGS -c -o "$obj" "$src"; then
exit 1
fi
do_rebuild=1
fi
done
if [ $do_rebuild -eq 1 ] || [ ! -f "build/$PROG" ]; then
echo "LD build/$PROG"
$CC $LDFLAGS -o "build/$PROG" $objs
else
echo "Up to date."
fi
ls -lh "build/$PROG"

174
src/Array.c Normal file
View file

@ -0,0 +1,174 @@
#include <Array.h>
#ifndef ARRAY_BLOCK
#define ARRAY_BLOCK 16
#endif
#include <stddef.h>
#include <stdlib.h>
struct Array {
void **entries; /* An array of void pointers, to store any data */
size_t allocated; /* Elements allocated on the heap */
size_t size; /* Elements actually filled */
};
int
ArrayAdd(Array *array, void *value)
{
if (!array)
{
return 0;
}
return ArrayInsert(array, value, array->size);
}
Array *
ArrayCreate(void)
{
Array *array = malloc(sizeof(Array));
if (!array)
{
return NULL;
}
array->size = 0;
array->allocated = ARRAY_BLOCK;
array->entries = malloc(sizeof(void *) * ARRAY_BLOCK);
if (!array->entries)
{
free(array);
return NULL;
}
return array;
}
void *
ArrayDelete(Array *array, size_t index)
{
size_t i;
void *element;
if (!array)
{
return NULL;
}
element = array->entries[index];
for (i = index; i < array->size - 1; i++)
{
array->entries[i] = array->entries[i + 1];
}
array->size--;
return element;
}
void
ArrayFree(Array *array)
{
if (array)
{
free(array->entries);
free(array);
}
}
void *
ArrayGet(Array *array, size_t index)
{
if (!array)
{
return NULL;
}
if (index >= array->size)
{
return NULL;
}
return array->entries[index];
}
extern int
ArrayInsert(Array *array, void *value, size_t index)
{
size_t i;
if (!array || !value || index > array->size)
{
return 0;
}
if (array->size >= array->allocated)
{
void **tmp;
size_t newSize = array->allocated + ARRAY_BLOCK;
tmp = array->entries;
array->entries = realloc(array->entries,
sizeof(void *) * newSize);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
array->allocated = newSize;
}
for (i = array->size; i > index; i--)
{
array->entries[i] = array->entries[i - 1];
}
array->size++;
array->entries[index] = value;
return 1;
}
size_t
ArraySize(Array *array)
{
if (!array)
{
return 0;
}
return array->size;
}
int
ArrayTrim(Array *array)
{
void **tmp;
if (!array)
{
return 0;
}
tmp = array->entries;
array->entries = realloc(array->entries,
sizeof(void *) * array->size);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
return 1;
}

212
src/Base64.c Normal file
View file

@ -0,0 +1,212 @@
#include <Base64.h>
#include <stdlib.h>
static const char Base64EncodeMap[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int Base64DecodeMap[] = {
62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51
};
size_t
Base64EncodedSize(size_t inputSize)
{
size_t size = inputSize;
if (inputSize % 3)
{
size += 3 - (inputSize % 3);
}
size /= 3;
size *= 4;
return size;
}
size_t
Base64DecodedSize(const char *base64, size_t len)
{
size_t ret;
size_t i;
if (!base64)
{
return 0;
}
ret = len / 4 * 3;
for (i = len; i > 0; i--) {
if (base64[i] == '=') {
ret--;
} else {
break;
}
}
return ret;
}
char *
Base64Encode(const char *input, size_t len)
{
char *out;
size_t outLen;
size_t i, j, v;
if (!input || !len)
{
return NULL;
}
outLen = Base64EncodedSize(len);
out = malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4)
{
v = input[i];
v = i + 1 < len ? v << 8 | input[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | input[i + 2] : v << 8;
out[j] = Base64EncodeMap[(v >> 18) & 0x3F];
out[j + 1] = Base64EncodeMap[(v >> 12) & 0x3F];
if (i + 1 < len)
{
out[j + 2] = Base64EncodeMap[(v >> 6) & 0x3F];
} else {
out[j + 2] = '=';
}
if (i + 2 < len) {
out[j + 3] = Base64EncodeMap[v & 0x3F];
} else {
out[j + 3] = '=';
}
}
return out;
}
static int
Base64IsValidChar(char c)
{
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '+') ||
(c == '/') ||
(c == '=');
}
char *
Base64Decode(const char *input, size_t len)
{
size_t i, j;
int v;
size_t outLen;
char *out;
if (!input)
{
return NULL;
}
outLen = Base64DecodedSize(input, len);
if (len % 4)
{
/* Invalid length; must have incorrect padding */
return NULL;
}
/* Scan for invalid characters. */
for (i = 0; i < len; i++)
{
if (!Base64IsValidChar(input[i]))
{
return NULL;
}
}
out = malloc(outLen + 1);
if (!out)
{
return NULL;
}
out[outLen] = '\0';
for (i = 0, j = 0; i < len; i += 4, j += 3) {
v = Base64DecodeMap[input[i] - 43];
v = (v << 6) | Base64DecodeMap[input[i + 1] - 43];
v = input[i + 2] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 2] - 43];
v = input[i + 3] == '=' ? v << 6 : (v << 6) | Base64DecodeMap[input[i + 3] - 43];
out[j] = (v >> 16) & 0xFF;
if (input[i + 2] != '=')
out[j + 1] = (v >> 8) & 0xFF;
if (input[i + 3] != '=')
out[j + 2] = v & 0xFF;
}
return out;
}
extern void
Base64Unpad(char *base64, size_t length)
{
if (!base64)
{
return;
}
while (base64[length - 1] == '=')
{
length--;
}
base64[length] = '\0';
}
extern int
Base64Pad(char **base64Ptr, size_t length)
{
char *tmp;
size_t newSize;
size_t i;
if (length % 4 == 0)
{
return length; /* Success: no padding needed */
}
newSize = length + (4 - (length % 4));
tmp = realloc(*base64Ptr, newSize + 100);;
if (!tmp)
{
return 0; /* Memory error */
}
*base64Ptr = tmp;
for (i = length; i < newSize; i++)
{
(*base64Ptr)[i] = '=';
}
(*base64Ptr)[newSize] = '\0';
return newSize;
}

452
src/Config.c Normal file
View file

@ -0,0 +1,452 @@
#include <Config.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#ifndef CONFIG_BUFFER_BLOCK
#define CONFIG_BUFFER_BLOCK 32
#endif
struct ConfigDirective {
Array *values;
HashMap *children;
};
struct ConfigParseResult {
unsigned int ok : 1;
union {
size_t lineNumber;
HashMap *confMap;
} data;
};
typedef enum ConfigToken {
TOKEN_UNKNOWN,
TOKEN_NAME,
TOKEN_MACRO_ASSIGNMENT,
TOKEN_VALUE,
TOKEN_SEMICOLON,
TOKEN_BLOCK_OPEN,
TOKEN_BLOCK_CLOSE,
TOKEN_MACRO,
TOKEN_EOF
} ConfigToken;
typedef struct ConfigParserState {
FILE *stream;
unsigned int line;
char *token;
size_t tokenSize;
size_t tokenLen;
ConfigToken tokenType;
HashMap *macroMap;
} ConfigParserState;
unsigned int
ConfigParseResultOk(ConfigParseResult *result)
{
return result ? result->ok : 0;
}
size_t
ConfigParseResultLineNumber(ConfigParseResult *result)
{
return result && !result->ok ? result->data.lineNumber : 0;
}
HashMap *
ConfigParseResultGet(ConfigParseResult *result)
{
return result && result->ok ? result->data.confMap : NULL;
}
void
ConfigParseResultFree(ConfigParseResult *result)
{
/*
* Note that if the parse was valid, the hash map
* needs to be freed separately.
*/
free(result);
}
Array *
ConfigValuesGet(ConfigDirective *directive)
{
return directive ? directive->values : NULL;
}
HashMap *
ConfigChildrenGet(ConfigDirective *directive)
{
return directive ? directive->children : NULL;
}
/*
* Takes a void pointer because it is only used with
* HashMapIterate(), which requires a pointer to a function
* that takes a void pointer.
*/
static void
ConfigDirectiveFree(void *ptr)
{
ConfigDirective *directive = ptr;
size_t i;
if (!directive)
{
return;
}
for (i = 0; i < ArraySize(directive->values); i++)
{
free(ArrayGet(directive->values, i));
}
ArrayFree(directive->values);
ConfigFree(directive->children);
free(directive);
}
void
ConfigFree(HashMap *conf)
{
HashMapIterate(conf, ConfigDirectiveFree);
HashMapFree(conf);
}
static ConfigParserState *
ConfigParserStateCreate(FILE * stream)
{
ConfigParserState *state = malloc(sizeof(ConfigParserState));
if (!state)
{
return NULL;
}
state->macroMap = HashMapCreate();
if (!state->macroMap)
{
free(state);
return NULL;
}
state->stream = stream;
state->line = 1;
state->token = NULL;
state->tokenSize = 0;
state->tokenLen = 0;
state->tokenType = TOKEN_UNKNOWN;
return state;
}
static void
ConfigParserStateFree(ConfigParserState *state)
{
if (!state)
{
return;
}
free(state->token);
HashMapIterate(state->macroMap, free);
HashMapFree(state->macroMap);
free(state);
}
static int
ConfigIsNameChar(int c)
{
return isdigit(c) || isalpha(c) || (c == '-' || c == '_');
}
static char
ConfigConsumeWhitespace(ConfigParserState *state)
{
int c;
while (isspace(c = fgetc(state->stream)))
{
if (c == '\n')
{
state->line++;
}
}
return c;
}
static void
ConfigConsumeLine(ConfigParserState *state)
{
while (fgetc(state->stream) != '\n');
state->line++;
}
static void
ConfigTokenSeek(ConfigParserState *state)
{
int c;
/* If we already hit EOF, don't do anything */
if (state->tokenType == TOKEN_EOF) {
return;
}
while ((c = ConfigConsumeWhitespace(state)) == '#') {
ConfigConsumeLine(state);
}
/*
* After all whitespace and comments are consumed, identify the
* token by looking at the next character
*/
if (feof(state->stream)) {
state->tokenType = TOKEN_EOF;
return;
}
if (ConfigIsNameChar(c)) {
state->tokenLen = 0;
/* Read the key/macro into state->token */
if (!state->token) {
state->tokenSize = CONFIG_BUFFER_BLOCK;
state->token = malloc(CONFIG_BUFFER_BLOCK);
}
state->token[state->tokenLen] = c;
state->tokenLen++;
while (ConfigIsNameChar((c = fgetc(state->stream)))) {
state->token[state->tokenLen] = c;
state->tokenLen++;
if (state->tokenLen >= state->tokenSize) {
state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = realloc(state->token,
state->tokenSize);
}
}
state->token[state->tokenLen] = '\0';
state->tokenLen++;
if (!isspace(c)) {
state->tokenType = TOKEN_UNKNOWN;
} else {
state->tokenType = TOKEN_NAME;
if (c == '\n') {
state->line++;
}
}
} else {
switch (c) {
case '=':
state->tokenType = TOKEN_MACRO_ASSIGNMENT;
break;
case '"':
state->tokenLen = 0;
state->tokenType = TOKEN_VALUE;
/* read the value into state->curtok */
while ((c = fgetc(state->stream)) != '"') {
if (c == '\n') {
state->line++;
}
/*
* End of the stream reached without finding
* a closing quote
*/
if (feof(state->stream)) {
state->tokenType = TOKEN_EOF;
break;
}
state->token[state->tokenLen] = c;
state->tokenLen++;
if (state->tokenLen >= state->tokenSize) {
state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = realloc(state->token,
state->tokenSize);
}
}
state->token[state->tokenLen] = '\0';
state->tokenLen++;
break;
case ';':
state->tokenType = TOKEN_SEMICOLON;
break;
case '{':
state->tokenType = TOKEN_BLOCK_OPEN;
break;
case '}':
state->tokenType = TOKEN_BLOCK_CLOSE;
break;
case '$':
state->tokenLen = 0;
/* read the macro name into state->curtok */
while (ConfigIsNameChar(c = fgetc(state->stream))) {
state->token[state->tokenLen] = c;
state->tokenLen++;
if (state->tokenLen >= state->tokenSize) {
state->tokenSize += CONFIG_BUFFER_BLOCK;
state->token = realloc(state->token,
state->tokenSize);
}
}
state->token[state->tokenLen] = '\0';
state->tokenLen++;
state->tokenType = TOKEN_MACRO;
ungetc(c, state->stream);
break;
default:
state->tokenType = TOKEN_UNKNOWN;
break;
}
}
/* Resize curtok to only use the bytes it needs */
if (state->tokenLen) {
state->tokenSize = state->tokenLen;
state->token = realloc(state->token, state->tokenSize);
}
}
static int
ConfigExpect(ConfigParserState *state, ConfigToken tokenType)
{
return state->tokenType == tokenType;
}
static HashMap *
ConfigParseBlock(ConfigParserState *state, int level)
{
HashMap *block = HashMapCreate();
ConfigTokenSeek(state);
while (ConfigExpect(state, TOKEN_NAME)) {
char *name = malloc(state->tokenLen + 1);
strcpy(name, state->token);
ConfigTokenSeek(state);
if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) {
ConfigDirective *directive;
directive = malloc(sizeof(ConfigDirective));
directive->children = NULL;
directive->values = ArrayCreate();
while (ConfigExpect(state, TOKEN_VALUE) ||
ConfigExpect(state, TOKEN_MACRO)) {
char *dval;
char *dvalCpy;
if (ConfigExpect(state, TOKEN_VALUE)) {
dval = state->token;
} else if (ConfigExpect(state, TOKEN_MACRO)) {
dval = HashMapGet(state->macroMap, state->token);
if (!dval) {
goto error;
}
} else {
dval = NULL; /* Should never happen */
}
/* dval is a pointer which is overwritten with the next token. */
dvalCpy = malloc(strlen(dval) + 1);
strcpy(dvalCpy, dval);
ArrayAdd(directive->values, dvalCpy);
ConfigTokenSeek(state);
}
if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) {
/* token_seek(state); */
directive->children = ConfigParseBlock(state, level + 1);
if (!directive->children) {
goto error;
}
}
/*
* Append this directive to the current block,
* overwriting a directive at this level with the same name.
*
* Note that if a value already exists with this name, it is
* returned by HashMapSet() and then immediately passed to
* ConfigDirectiveFree(). If the value does not exist, then
* NULL is sent to ConfigDirectiveFree(), making it a no-op.
*/
ConfigDirectiveFree(HashMapSet(block, name, directive));
} else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) {
ConfigTokenSeek(state);
if (ConfigExpect(state, TOKEN_VALUE)) {
char * valueCopy = malloc(strlen(state->token) + 1);
strcpy(valueCopy, state->token);
free(HashMapSet(state->macroMap, name, state->token));
ConfigTokenSeek(state);
} else {
goto error;
}
} else {
goto error;
}
if (!ConfigExpect(state, TOKEN_SEMICOLON)) {
goto error;
}
ConfigTokenSeek(state);
}
if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) {
ConfigTokenSeek(state);
return block;
} else {
goto error;
}
error:
/* Only free the very top level, because this will recurse */
if (!level) {
ConfigFree(block);
}
return NULL;
}
ConfigParseResult *
ConfigParse(FILE * stream)
{
ConfigParseResult *result;
HashMap *conf;
ConfigParserState *state;
result = malloc(sizeof(ConfigParseResult));
state = ConfigParserStateCreate(stream);
conf = ConfigParseBlock(state, 0);
if (!conf) {
result->ok = 0;
result->data.lineNumber = state->line;
} else {
result->ok = 1;
result->data.confMap = conf;
}
ConfigParserStateFree(state);
return result;
}

300
src/HashMap.c Normal file
View file

@ -0,0 +1,300 @@
#include <HashMap.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
typedef struct HashMapBucket {
uint32_t hash;
void *value;
} HashMapBucket;
struct HashMap {
size_t count;
size_t capacity;
HashMapBucket **entries;
float maxLoad;
};
static uint32_t
HashMapHashKey(const char *key)
{
uint32_t hash = 2166136261u;
size_t i = 0;
while (key[i])
{
hash ^= (uint8_t) key[i];
hash *= 16777619;
i++;
}
return hash;
}
static int
HashMapGrow(HashMap *map)
{
size_t oldCapacity;
size_t i;
HashMapBucket **newEntries;
if (!map)
{
return 0;
}
oldCapacity = map->capacity;
map->capacity *= 2;
newEntries = calloc(map->capacity, sizeof(HashMapBucket *));
if (!newEntries)
{
return 0;
}
for (i = 0; i < oldCapacity; i++)
{
/* If there is a value here, and it isn't a tombstone */
if (map->entries[i] && map->entries[i]->hash)
{
/* Copy it to the new entries array */
size_t index = map->entries[i]->hash % map->capacity;
for (;;)
{
if (newEntries[index])
{
if (!newEntries[index]->hash)
{
free(newEntries[index]);
newEntries[index] = map->entries[i];
break;
}
}
else
{
newEntries[index] = map->entries[i];
break;
}
index = (index + 1) % map->capacity;
}
}
else
{
/* Either NULL or a tombstone */
free(map->entries[i]);
}
}
free(map->entries);
map->entries = newEntries;
return 1;
}
HashMap *
HashMapCreate(void)
{
HashMap *map = malloc(sizeof(HashMap));
if (!map)
{
return NULL;
}
map->maxLoad = 0.75;
map->count = 0;
map->capacity = 16;
map->entries = calloc(map->capacity, sizeof(HashMapBucket *));
if (!map->entries)
{
free(map);
return NULL;
}
return map;
}
void *
HashMapDelete(HashMap *map, const char *key)
{
uint32_t hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = HashMapHashKey(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
bucket->hash = 0;
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapFree(HashMap *map)
{
if (map)
{
size_t i;
for (i = 0; i < map->capacity; i++)
{
if (map->entries[i])
{
free(map->entries[i]);
}
}
}
free(map);
}
void *
HashMapGet(HashMap *map, const char *key)
{
uint32_t hash;
size_t index;
if (!map || !key)
{
return NULL;
}
hash = HashMapHashKey(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
break;
}
if (bucket->hash == hash)
{
return bucket->value;
}
index = (index + 1) % map->capacity;
}
return NULL;
}
void
HashMapIterate(HashMap *map, void (*iteratorFunc)(void *))
{
size_t i;
if (!map)
{
return;
}
for (i = 0; i < map->capacity; i++)
{
HashMapBucket *bucket = map->entries[i];
if (bucket)
{
iteratorFunc(bucket->value);
}
}
}
void
HashMapMaxLoadSet(HashMap *map, float load)
{
if (!map)
{
return;
}
map->maxLoad = load;
}
void *
HashMapSet(HashMap *map, const char *key, void *value)
{
uint32_t hash;
size_t index;
if (!map || !key || !value)
{
return NULL;
}
if (map->count + 1 > map->capacity * map->maxLoad)
{
HashMapGrow(map);
}
hash = HashMapHashKey(key);
index = hash % map->capacity;
for (;;)
{
HashMapBucket *bucket = map->entries[index];
if (!bucket)
{
bucket = malloc(sizeof(HashMapBucket));
if (!bucket)
{
break;
}
bucket->hash = hash;
bucket->value = value;
map->entries[index] = bucket;
map->count++;
break;
}
if (!bucket->hash)
{
bucket->hash = hash;
bucket->value = value;
break;
}
if (bucket->hash == hash)
{
void *oldValue = bucket->value;
bucket->value = value;
return oldValue;
}
index = (index + 1) % map->capacity;
}
return NULL;
}

310
src/Log.c Normal file
View file

@ -0,0 +1,310 @@
#include <Log.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#define LOG_TSBUFFER 64
struct LogConfig {
LogLevel level;
size_t indent;
FILE *out;
int flags;
char *tsFmt;
};
void
Log(LogConfig *config, LogLevel level, const char *msg, ...)
{
int i;
int doColor;
char indicator;
va_list argp;
/*
* Only proceed if we have a config and its log level is set to a
* value that permits us to log. This is as close as we can get
* to a no-op function if we aren't logging anything, without doing
* some crazy macro magic.
*/
if (!config || level > config->level)
{
return;
}
/* Misconfiguration */
if (!config->out)
{
return;
}
for (i = 0; i < config->indent; i++)
{
fputc(' ', config->out);
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(fileno(config->out));
if (doColor)
{
char *ansi;
switch (level)
{
case LOG_ERROR:
/* Bold Red */
ansi = "\033[1;31m";
break;
case LOG_WARNING:
/* Bold Yellow */
ansi = "\033[1;33m";
break;
case LOG_TASK:
/* Bold Magenta */
ansi = "\033[1;35m";
break;
case LOG_MESSAGE:
/* Bold Green */
ansi = "\033[1;32m";
break;
case LOG_DEBUG:
/* Bold Blue */
ansi = "\033[1;34m";
break;
default:
ansi = "";
break;
}
fputs(ansi, config->out);
}
fputc('[', config->out);
if (config->tsFmt)
{
time_t timer = time(NULL);
struct tm *timeInfo = localtime(&timer);
char tsBuffer[LOG_TSBUFFER];
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
timeInfo);
if (tsLength)
{
fputs(tsBuffer, config->out);
if (!isspace(tsBuffer[tsLength - 1]))
{
fputc(' ', config->out);
}
}
}
switch (level)
{
case LOG_ERROR:
indicator = 'x';
break;
case LOG_WARNING:
indicator = '!';
break;
case LOG_TASK:
indicator = '~';
break;
case LOG_MESSAGE:
indicator = '>';
break;
case LOG_DEBUG:
indicator = '*';
break;
default:
indicator = ' ';
break;
}
fprintf(config->out, "%c]", indicator);
if (doColor)
{
/* ANSI Reset */
fputs("\033[0m", config->out);
}
fputc(' ', config->out);
va_start(argp, msg);
vfprintf(config->out, msg, argp);
fputc('\n', config->out);
va_end(argp);
/* If we are debugging, there might be something that's
* going to segfault the program coming up, so flush the
* output stream immediately.
*/
if (config->level == LOG_DEBUG)
{
fflush(config->out);
}
}
LogConfig *
LogConfigCreate(void)
{
LogConfig *config;
config = calloc(1, sizeof(LogConfig));
if (!config)
{
return NULL;
}
LogConfigLevelSet(config, LOG_MESSAGE);
LogConfigIndentSet(config, 0);
LogConfigOutputSet(config, NULL); /* Will set to stdout */
LogConfigFlagSet(config, LOG_FLAG_COLOR);
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
return config;
}
void
LogConfigFlagClear(LogConfig *config, int flags)
{
if (!config)
{
return;
}
config->flags &= ~flags;
}
int
LogConfigFlagGet(LogConfig *config, int flags)
{
if (!config)
{
return 0;
}
return config->flags & flags;
}
void
LogConfigFlagSet(LogConfig *config, int flags)
{
if (!config)
{
return;
}
config->flags |= flags;
}
void
LogConfigFree(LogConfig *config)
{
free(config);
}
void
LogConfigIndent(LogConfig *config)
{
if (config)
{
config->indent += 2;
}
}
size_t
LogConfigIndentGet(LogConfig *config)
{
if (!config)
{
return 0;
}
return config->indent;
}
void
LogConfigIndentSet(LogConfig *config, size_t indent)
{
if (!config)
{
return;
}
config->indent = indent;
}
LogLevel
LogConfigLevelGet(LogConfig *config)
{
if (!config)
{
return -1;
}
return config->level;
}
void
LogConfigLevelSet(LogConfig *config, LogLevel level)
{
if (!config)
{
return;
}
switch (level)
{
case LOG_ERROR:
case LOG_WARNING:
case LOG_MESSAGE:
case LOG_DEBUG:
config->level = level;
default:
break;
}
}
void
LogConfigOutputSet(LogConfig *config, FILE *out)
{
if (!config)
{
return;
}
if (out)
{
config->out = out;
}
else
{
config->out = stdout;
}
}
void
LogConfigTimeStampFormatSet(LogConfig *config, char *tsFmt)
{
if (config)
{
config->tsFmt = tsFmt;
}
}
void
LogConfigUnindent(LogConfig *config)
{
if (config && config->indent >= 2)
{
config->indent -= 2;
}
}

157
src/Telodendria.c Normal file
View file

@ -0,0 +1,157 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <Log.h>
#include <HashMap.h>
#include <Config.h>
#include <Base64.h>
typedef enum ArgFlag
{
ARG_VERSION = (1 << 0),
ARG_USAGE = (1 << 1)
} ArgFlag;
static void
TelodendriaPrintHeader(LogConfig *lc)
{
Log(lc, LOG_MESSAGE,
" _____ _ _ _ _");
Log(lc, LOG_MESSAGE,
"|_ _|__| | ___ __| | ___ _ __ __| |_ __(_) __ _");
Log(lc, LOG_MESSAGE,
" | |/ _ \\ |/ _ \\ / _` |/ _ \\ '_ \\ / _` | '__| |/ _` |");
Log(lc, LOG_MESSAGE,
" | | __/ | (_) | (_| | __/ | | | (_| | | | | (_| |");
Log(lc, LOG_MESSAGE,
" |_|\\___|_|\\___/ \\__,_|\\___|_| |_|\\__,_|_| |_|\\__,_|");
Log(lc, LOG_MESSAGE, "Telodendria v" TELODENDRIA_VERSION);
Log(lc, LOG_MESSAGE, "");
Log(lc, LOG_MESSAGE,
"Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>");
Log(lc, LOG_MESSAGE,
"Documentation/Support: https://bancino.net/pub/Telodendria");
Log(lc, LOG_MESSAGE, "");
}
static void
TelodendriaPrintUsage(LogConfig *lc)
{
Log(lc, LOG_MESSAGE, "Usage:");
Log(lc, LOG_MESSAGE, " -c <file> Configuration file ('-' for stdin).");
Log(lc, LOG_MESSAGE, " -V Print the header, then exit.");
Log(lc, LOG_MESSAGE, " -h Print this usage, then exit.");
}
int main(int argc, char **argv)
{
LogConfig *lc;
int exit = EXIT_SUCCESS;
/* Arg parsing */
int opt;
int flags = 0;
char *configArg = NULL;
/* Config file */
FILE *configFile = NULL;
ConfigParseResult *configParseResult = NULL;
HashMap *config = NULL;
lc = LogConfigCreate();
/* TODO: Remove */
LogConfigLevelSet(lc, LOG_DEBUG);
if (!lc)
{
printf("Fatal error: unable to allocate memory for logger.\n");
return EXIT_FAILURE;
}
TelodendriaPrintHeader(lc);
while ((opt = getopt(argc, argv, "c:Vh")) != -1)
{
switch (opt)
{
case 'c':
configArg = optarg;
break;
case 'V':
flags |= ARG_VERSION;
break;
case 'h':
flags |= ARG_USAGE;
default:
break;
}
}
if (flags & ARG_VERSION)
{
goto finish;
}
if (flags & ARG_USAGE)
{
TelodendriaPrintUsage(lc);
goto finish;
}
if (!configArg)
{
Log(lc, LOG_ERROR, "No configuration file specified.");
TelodendriaPrintUsage(lc);
exit = EXIT_FAILURE;
goto finish;
}
if (strcmp(configArg, "-") == 0)
{
configFile = stdout;
}
else
{
configFile = fopen(configArg, "r");
if (!configFile)
{
Log(lc, LOG_ERROR, "Unable to open configuration file '%s' for reading.", configArg);
exit = EXIT_FAILURE;
goto finish;
}
}
Log(lc, LOG_MESSAGE, "Using configuration file '%s'.", configArg);
/* Read config here */
configParseResult = ConfigParse(configFile);
if (!ConfigParseResultOk(configParseResult))
{
Log(lc, LOG_ERROR, "Syntax error on line %d.",
ConfigParseResultLineNumber(configParseResult));
exit = EXIT_FAILURE;
goto finish;
}
config = ConfigParseResultGet(configParseResult);
ConfigParseResultFree(configParseResult);
Log(lc, LOG_DEBUG, "Closing configuration file.");
fclose(configFile);
/* Configure log file */
finish:
if (config)
{
Log(lc, LOG_DEBUG, "Freeing configuration structure.");
ConfigFree(config);
}
Log(lc, LOG_DEBUG, "Freeing log configuration and exiting with code '%d'.", exit);
LogConfigFree(lc);
return exit;
}

32
src/include/Array.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef TELODENDRIA_ARRAY_H
#define TELODENDRIA_ARRAY_H
#include <stddef.h>
typedef struct Array Array;
extern Array *
ArrayCreate(void);
extern size_t
ArraySize(Array *array);
extern void *
ArrayGet(Array *array, size_t index);
extern int
ArrayInsert(Array *, void *value, size_t index);
extern int
ArrayAdd(Array *array, void *value);
extern void *
ArrayDelete(Array *array, size_t index);
extern void
ArrayFree(Array *array);
extern int
ArrayTrim(Array *array);
#endif

25
src/include/Base64.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef TELODENDRIA_BASE64_H
#define TELODENDRIA_BASE64_H
#include <stddef.h>
extern size_t
Base64EncodedSize(size_t inputSize);
extern size_t
Base64DecodedSize(const char *base64, size_t len);
extern char *
Base64Encode(const char *input, size_t len);
extern char *
Base64Decode(const char *input, size_t len);
extern void
Base64Unpad(char *base64, size_t length);
extern int
Base64Pad(char **base64Ptr, size_t length);
#endif

37
src/include/Config.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef TELODENDRIA_CONFIG_H
#define TELODENDRIA_CONFIG_H
#include <stdio.h>
#include <HashMap.h>
#include <Array.h>
typedef struct ConfigDirective ConfigDirective;
typedef struct ConfigParseResult ConfigParseResult;
extern ConfigParseResult *
ConfigParse(FILE *stream);
extern unsigned int
ConfigParseResultOk(ConfigParseResult *result);
extern size_t
ConfigParseResultLineNumber(ConfigParseResult *result);
extern HashMap *
ConfigParseResultGet(ConfigParseResult *result);
extern void
ConfigParseResultFree(ConfigParseResult *result);
extern Array *
ConfigValuesGet(ConfigDirective *directive);
extern HashMap *
ConfigChildrenGet(ConfigDirective *directive);
extern void
ConfigFree(HashMap *conf);
#endif /* TELODENDRIA_CONFIG_H */

85
src/include/HashMap.h Normal file
View file

@ -0,0 +1,85 @@
/*
* HashMap.h: The public interface for Telodendria's hash map
* implementation. This hash map is designed to be simple, well
* documented, and generally readable and understandable, yet also
* performant enough to be useful, because it is used extensively in
* Telodendria.
*
* Fundamentally, this is an entirely generic map implementation. It
* can be used for many general purposes, but it is designed to only
* implement the features that Telodendria needs to function well.
*
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
*/
#ifndef TELODENDRIA_HASHMAP_H
#define TELODENDRIA_HASHMAP_H
#include <stddef.h>
/*
* HashMap: The primary data structure used by this hash map algorithm.
* All data necessary for the algorithm to function is stored inside
* it. This is an opaque structure; use the methods defined by this
* interface to manipulate it.
*/
typedef struct HashMap HashMap;
/*
* HashMapCreate: Create a new HashMap object.
*
* Returns: A HashMap object that is ready to be used by the rest of
* the HashMap functions, or NULL if memory could not be allocated on
* the heap.
*/
extern HashMap *
HashMapCreate(void);
extern void
HashMapMaxLoadSet(HashMap *map, float load);
/*
* HashMapSet: Set the given key in the HashMap to the given value. Note
* that the value is not copied into the HashMap's own memory space;
* only the pointer is stored. It is the caller's job to ensure that the
* value's memory remains valid for the life of the HashMap.
*
* Returns: The previous value at the given key, or NULL if the key did
* not previously exist or any of the parameters provided are NULL. All
* keys must have values; you can't set a key to NULL. To delete a key,
* use HashMapDelete.
*/
extern void *
HashMapSet(HashMap * map, const char *key, void *value);
/*
* HashMapGet: Get the value for the given key.
*
* Returns: The value at the given key, or NULL if the key does not
* exist, no map was provided, or no key was provided.
*/
extern void *
HashMapGet(HashMap * map, const char *key);
/*
* HashMapDelete: Delete the value for the given key.
*
* Returns: The value at the given key, or NULL if the key does not
* exist or the map or key was not provided.
*/
extern void *
HashMapDelete(HashMap *map, const char *key);
extern void
HashMapIterate(HashMap *map, void (*iteratorFunc)(void *));
/*
* HashMapFree: Free the hash map, returning its memory to the operating
* system. Note that this function does not free the values stored in
* the map since this hash map implementation has no way of knowing
* what actually is stored in it. You should use HashMapIterate to
* free the values using your own algorithm.
*/
extern void
HashMapFree(HashMap *map);
#endif /* TELODENDRIA_HASHMAP_H */

96
src/include/Http.h Normal file
View file

@ -0,0 +1,96 @@
#ifndef TELODENDRIA_HTTP_H
#define TELODENDRIA_HTTP_H
typedef enum HttpRequestMethod {
HTTP_GET,
HTTP_HEAD,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
HTTP_CONNECT,
HTTP_OPTIONS,
HTTP_TRACE,
HTTP_PATCH
} HttpRequestMethod;
typedef enum HttpStatus {
/* Informational responses */
HTTP_CONTINUE = 100,
HTTP_SWITCHING_PROTOCOLS = 101,
HTTP_EARLY_HINTS = 103,
/* Successful responses */
HTTP_OK = 200,
HTTP_CREATED = 201,
HTTP_ACCEPTED = 202,
HTTP_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_NO_CONTENT = 204,
HTTP_RESET_CONTENT = 205,
HTTP_PARTIAL_CONTENT = 206,
/* Redirection messages */
HTTP_MULTIPLE_CHOICES = 300,
HTTP_MOVED_PERMANENTLY = 301,
HTTP_FOUND = 302,
HTTP_SEE_OTHER = 303,
HTTP_NOT_MODIFIED = 304,
HTTP_TEMPORARY_REDIRECT = 307,
HTTP_PERMANENT_REDIRECT = 308,
/* Client error messages */
HTTP_BAD_REQUEST = 400,
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405,
HTTP_NOT_ACCEPTABLE = 406,
HTTP_PROXY_AUTH_REQUIRED = 407,
HTTP_REQUEST_TIMEOUT = 408,
HTTP_CONFLICT = 409,
HTTP_GONE = 410,
HTTP_LENGTH_REQUIRED = 411,
HTTP_PRECONDITION_FAILED = 412,
HTTP_PAYLOAD_TOO_LARGE = 413,
HTTP_URI_TOO_LONG = 414,
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_RANGE_NOT_SATISFIABLE = 416,
HTTP_EXPECTATION_FAILED = 417,
HTTP_TEAPOT = 418,
HTTP_UPGRADE_REQUIRED = 426,
HTTP_PRECONDITION_REQUIRED = 428,
HTTP_TOO_MANY_REQUESTS = 429,
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
/* Server error responses */
HTTP_INTERNAL_SERVER_ERROR = 500,
HTTP_NOT_IMPLEMENTED = 501,
HTTP_BAD_GATEWAY = 502,
HTTP_SERVICE_UNAVAILABLE = 503,
HTTP_GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_NOT_EXTENDED = 510,
HTTP_NETWORK_AUTH_REQUIRED = 511
} HttpStatus;
struct HttpRequest {
HttpRequestMethod method;
};
struct HttpResponse {
HttpStatus status;
};
extern char *
HttpGetStatusString(const HttpStatus httpStatus);
extern HttpRequestMethod
HttpRequestMethodFromString(const char *requestMethod);
typedef struct HttpRequest HttpRequest;
typedef struct HttpResponse HttpResponse;
typedef void (*HttpHandler)(HttpRequest *, HttpResponse *);
#endif

69
src/include/Json.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef TELODENDRIA_JSON_H
#define TELODENDRIA_JSON_H
#include <HashMap.h>
#include <Array.h>
typedef enum JsonType {
JSON_OBJECT,
JSON_ARRAY,
JSON_STRING,
JSON_INTEGER,
JSON_FLOAT,
JSON_BOOLEAN,
JSON_NULL
} JsonType;
typedef struct JsonValue {
JsonType type;
union as {
HashMap *object;
Array *array;
char *string;
int64_t integer;
double floating;
int boolean : 1;
};
} JsonValue;
extern JsonType
JsonValueType(JsonValue *value);
extern JsonValue *
JsonValueObject(HashMap *object);
extern HashMap *
JsonValueAsObject(JsonValue *value);
extern JsonValue *
JsonValueArray(Array *array);
extern Array *
JsonValueAsArray(JsonValue *value);
extern JsonValue *
JsonValueString(char *string);
extern JsonValue *
JsonValueInteger(int64_t integer);
extern JsonValue *
JsonValueFloat(double floating);
extern JsonValue *
JsonValueBoolean(int boolean);
extern JsonValue *
JsonValueNull(void);
extern void *
JsonValueFree(JsonValue *value);
extern char *
JsonEncode(HashMap *object);
extern HashMap *
JsonDecode(char *string);
#endif

63
src/include/Log.h Normal file
View file

@ -0,0 +1,63 @@
#ifndef TELODENDRIA_LOG_H
#define TELODENDRIA_LOG_H
#include <stdio.h>
#include <stddef.h>
typedef enum LogLevel {
LOG_ERROR,
LOG_WARNING,
LOG_TASK,
LOG_MESSAGE,
LOG_DEBUG
} LogLevel;
typedef enum LogFlag {
LOG_FLAG_COLOR = (1 << 0)
} LogFlag;
typedef struct LogConfig LogConfig;
extern LogConfig *
LogConfigCreate(void);
extern void
LogConfigFree(LogConfig *config);
extern void
LogConfigLevelSet(LogConfig *config, LogLevel level);
extern LogLevel
LogConfigLevelGet(LogConfig *config);
extern void
LogConfigIndentSet(LogConfig *config, size_t indent);
extern size_t
LogConfigIndentGet(LogConfig *config);
extern void
LogConfigIndent(LogConfig *config);
extern void
LogConfigUnindent(LogConfig *config);
extern void
LogConfigOutputSet(LogConfig *config, FILE *out);
extern void
LogConfigFlagSet(LogConfig *config, int flags);
extern void
LogConfigFlagClear(LogConfig *config, int flags);
extern int
LogConfigFlagGet(LogConfig *config, int flags);
extern void
LogConfigTimeStampFormatSet(LogConfig *config, char *tsFmt);
extern void
Log(LogConfig *config, LogLevel level, const char *msg, ...);
#endif