forked from lda/telodendria
Initial revision
This commit is contained in:
commit
d102ba8676
22 changed files with 2763 additions and 0 deletions
1
.cvsignore
Normal file
1
.cvsignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build
|
8
.idea/Telodendria.iml
Normal file
8
.idea/Telodendria.iml
Normal 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
8
.idea/modules.xml
Normal 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
52
.idea/workspace.xml
Normal 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">{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"WebServerToolWindowFactoryState": "false",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true"
|
||||||
|
}
|
||||||
|
}</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
62
Telodendria.css
Normal 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
541
Telodendria.html
Normal 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—after you've reached out to
|
||||||
|
<code>#telodendria-general:bancino.net</code>—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 <@jordan:bancino.net>
|
||||||
|
|
||||||
|
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
9
contrib/httpd.conf
Normal 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
13
contrib/telodendria.conf
Normal 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
57
make.sh
Normal 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
174
src/Array.c
Normal 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
212
src/Base64.c
Normal 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
452
src/Config.c
Normal 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
300
src/HashMap.c
Normal 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
310
src/Log.c
Normal 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
157
src/Telodendria.c
Normal 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
32
src/include/Array.h
Normal 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
25
src/include/Base64.h
Normal 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
37
src/include/Config.h
Normal 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
85
src/include/HashMap.h
Normal 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
96
src/include/Http.h
Normal 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
69
src/include/Json.h
Normal 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
63
src/include/Log.h
Normal 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
|
Loading…
Reference in a new issue