forked from Telodendria/Telodendria
Compare commits
No commits in common. "master" and "fix-configure" have entirely different histories.
master
...
fix-config
176 changed files with 19204 additions and 13364 deletions
2
.exrc
2
.exrc
|
@ -1,2 +0,0 @@
|
|||
set tabstop=4
|
||||
set expandtab
|
|
@ -1,49 +0,0 @@
|
|||
name: Bug Report
|
||||
about: File a bug report regarding Telodendria, its website, or its documentation.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
You are submitting a bug report. Please be sure to fill out the
|
||||
title with a brief description of the bug.
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
description: Select the type of issue.
|
||||
options:
|
||||
- Memory Leak
|
||||
- Crash
|
||||
- Unexpected Error Message
|
||||
- Documentation
|
||||
- Website
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please give a thorough and detailed description of the bug you
|
||||
are reporting. Provide all the information you have, and do
|
||||
some investigating to ensure you are providing a legitimate
|
||||
issue report that is well thought out. **Include details on
|
||||
how to reproduce the issue, or explicitly state that you were
|
||||
unable to reproduce it.**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log Output
|
||||
description: |
|
||||
Please copy and paste the relevant sections of the log output,
|
||||
or the entire log if it is not unreasonably large. The logs
|
||||
will be automatically formatted, no code block is necessary.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please attach any additional files that may aid in our
|
||||
investigation of this issue, including screenshots, debugging
|
||||
session stack traces and dumps, etc.
|
|
@ -1,7 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: General Matrix Room
|
||||
url: "https://matrix.to/#/#telodendria-general:bancino.net"
|
||||
about: |
|
||||
General discussion on Telodendria happens in this Matrix room. You
|
||||
may get quicker feedback from there.
|
|
@ -1,18 +0,0 @@
|
|||
name: Feature Request
|
||||
about: Request a new feature or enhancement be added to Telodendria.
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
You are submitting a feature request. Please be sure to fill
|
||||
out the title with a brief description of the feature you are
|
||||
requesting.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please give a thorough and detailed description of the feature
|
||||
you are requesting.
|
||||
validations:
|
||||
required: true
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
---
|
||||
|
||||
Please review the developer certificate of origin:
|
||||
|
||||
1. The contribution was created in whole or in part by me, and I have
|
||||
the right to submit it under the open source licenses of the
|
||||
Telodendria project; or
|
||||
1. The contribution is based upon a previous work that, to the best of
|
||||
my knowledge, is covered under an appropriate open source license and
|
||||
I have the right under that license to submit that work with
|
||||
modifications, whether created in whole or in part by me, under the
|
||||
Telodendria project license; or
|
||||
1. The contribution was provided directly to me by some other person
|
||||
who certified (1), (2), or (3), and I have not modified it.
|
||||
1. I understand and agree that this project and the contribution are
|
||||
made public and that a record of the contribution—including all
|
||||
personal information I submit with it—is maintained indefinitely
|
||||
and may be redistributed consistent with this project or the open
|
||||
source licenses involved.
|
||||
|
||||
- [ ] I have read the Telodendria Project development certificate of
|
||||
origin, and I certify that I have permission to submit this patch
|
||||
under the conditions specified in it.
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
name: Compile Telodendria
|
||||
run-name: Compile Telodendria on ${{ forgejo.actor }}
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'ma*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
"Compile Telodendria":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [alpine]
|
||||
arch: [aarch64]
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Configure Telodendria
|
||||
run: ./configure
|
||||
- name: Configure & Build Cytoplasm
|
||||
run: make cytoplasm
|
||||
- name: Build Telodendria
|
||||
run: make
|
|
@ -1,44 +0,0 @@
|
|||
name: Release Telodendria
|
||||
run-name: Release Telodendria on ${{ forgejo.actor }}
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
"Release Telodendria":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [alpine]
|
||||
arch: [aarch64]
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Archive submodules
|
||||
run: git submodule foreach --recursive 'git archive --format tar --prefix=$displaypath/ -o submodule.tar HEAD'
|
||||
- name: Archive repository
|
||||
run: git archive --format tar -o release.tar HEAD
|
||||
- name: Produce release archive
|
||||
run: |
|
||||
TOPDIR=$(pwd) git submodule --quiet foreach --recursive 'cd $TOPDIR; tar --concatenate --file=release.tar $displaypath/submodule.tar; rm -fv $displaypath/submodule.tar'
|
||||
gzip release.tar
|
||||
mkdir release
|
||||
mv release.tar.gz release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
path: release/Telodendria-$GITHUB_REF_NAME.tar.gz
|
||||
- name: Publish release
|
||||
uses: actions/forgejo-release@v2
|
||||
with:
|
||||
tag: $GITHUB_REF_NAME
|
||||
title: "Telodendria $GITHUB_REF_NAME"
|
||||
release-dir: release/
|
||||
release-notes: "docs/CHANGELOG.md"
|
||||
direction: upload
|
||||
prerelease: true
|
25
.gitea/workflows/compile.yaml
Normal file
25
.gitea/workflows/compile.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: Compile Cytoplasm
|
||||
run-name: Compile Cytoplasm on ${{ gitea.actor }}
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
"Compile Cytoplasm":
|
||||
strategy:
|
||||
matrix:
|
||||
os: [debian-v12.4, alpine-v3.19, openbsd-v7.4, freebsd-v14.0, netbsd-v9.3]
|
||||
arch: [x86, x86_64]
|
||||
exclude:
|
||||
# 32-bit OpenBSD does not behave well in QEMU. Even when using
|
||||
# QEMU to emulate i386, it utilizes 100% of its CPU core and is
|
||||
# still extremely sluggish. Thus, we don't have a working 32-bit
|
||||
# OpenBSD runner, so exclude it from the matrix configuration.
|
||||
- os: openbsd-v7.4
|
||||
arch: x86
|
||||
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Configure Cytoplasm
|
||||
run: ./configure
|
||||
- name: Build Cytoplasm
|
||||
run: make
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,2 +0,0 @@
|
|||
liberapay: "Telodendria"
|
||||
custom: "https://donate.stripe.com/8wM29AfF5bRJc48eUU"
|
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,18 +1,8 @@
|
|||
# Telodendria .gitignore
|
||||
# Cytoplasm .gitignore
|
||||
|
||||
build
|
||||
out
|
||||
data
|
||||
Makefile
|
||||
|
||||
*-leaked.txt
|
||||
.env
|
||||
*.patch
|
||||
*.orig
|
||||
*.log
|
||||
vgcore.*
|
||||
*.core
|
||||
contrib/.vagrant
|
||||
src/Schema
|
||||
src/include/Schema
|
||||
man/mandoc.db
|
||||
|
||||
Makefile
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "Cytoplasm"]
|
||||
path = Cytoplasm
|
||||
url = https://git.telodendria.io/Telodendria/Cytoplasm.git
|
35
CHANGELOG.md
Normal file
35
CHANGELOG.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Cytoplasm Change Log
|
||||
|
||||
This document contains the complete change log for every official release of
|
||||
Cytoplasm. It is intended to be updated with every commit that makes a user-facing
|
||||
change worth reporting in the change log. As such, it changes frequently between
|
||||
releases. Final change log entries are published as [Releases](releases).
|
||||
|
||||
## v0.4.1
|
||||
|
||||
Cytoplasm is now a C99 library! Upgrading from C89 to C99 makes Cytoplasm more portable
|
||||
than ever.
|
||||
|
||||
### New Features
|
||||
|
||||
- Added an option to `j2s` to allow additional fields in structures and ignore them in
|
||||
encoding and decoding. Note that additional fields are totally untouched—they
|
||||
are not even initialized to a default value.
|
||||
- Fixed a memory leak that would occur in code generated by `j2s` under
|
||||
specific circumstances.
|
||||
- Added `JsonMerge()` to the JSON API to merge two JSON objects together.
|
||||
- Make `HttpRouter` decode path parts before matching them on regular expressions.
|
||||
- Fixed a bug in `ArraySort()` that would crash programs if the passed array has no
|
||||
elements.
|
||||
|
||||
## v0.4.0
|
||||
|
||||
**Released on November 1, 2023**
|
||||
|
||||
This is the first independent release of Cytoplasm! Last month, Cytoplasm was
|
||||
split off of [Telodendria](/Telodendria/Telodendria) to become its own independent
|
||||
project with its own independent releases. This allows it to develop at a much more
|
||||
rapid pace than Telodendria.
|
||||
|
||||
Changes in future releases will be reported here. Since this is the first release,
|
||||
there are no changes to show.
|
|
@ -1,16 +0,0 @@
|
|||
N: Jordan Bancino
|
||||
E: jordan@bancino.net
|
||||
M: @jordan:bancino.net
|
||||
W: https://bancino.net
|
||||
D: Project Lead
|
||||
L: United States
|
||||
|
||||
N: LDA
|
||||
E: marie@doskel.net
|
||||
E: ldasta@tedomum.fr
|
||||
E: lda@freetards.xyz
|
||||
M: @lda:a.freetards.xyz
|
||||
M: @lda:pain.agency
|
||||
M: @fourier:ari.lt
|
||||
D: Developer
|
||||
L: France
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 4f316ff7b3a955b831ca4aefb8679ddf3396a7d0
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022-2024 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
|
||||
|
|
203
README.md
203
README.md
|
@ -1,135 +1,116 @@
|
|||
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
||||
<p align="center"><img src="https://telodendria.io/user/themes/bancino/images/logo/Cytoplasm.png"></p>
|
||||
<h1 align="center">Cytoplasm (<code>libcytoplasm</code>)</h1>
|
||||
|
||||
**Telodendria** is an extremely powerful, yet lightweight and portable
|
||||
chat server designed to be easy to install and configure. Powered by
|
||||
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
||||
everyone to run their own chat server on ordinary hardware, including
|
||||
old and embedded devices. Whether you want a simple chat server just
|
||||
for you and your friends and family, or want to talk to users on other
|
||||
Matrix homeservers but don't want to go through all the hastle of
|
||||
hosting a complicated, high-maintenance homeserver or joining an
|
||||
existing homeserver for privacy or other reasons, then Telodendria
|
||||
might be for you.
|
||||
Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications. It allows applications to take advantage of the speed, flexibility, and simplicity of the C programming language, while providing helpful code to allow applications to perform various complex tasks with minimal effort. Cytoplasm provides high-level data structures, a basic logging facility, an HTTP client and server, and more. It also reports memory leaks, which can aid in debugging, particularly on systems that don't have advanced tools like `valgrind`.
|
||||
|
||||
!!!! **Note:** Telodendria still in development. See **Status** below.
|
||||
Cytoplasm aims not to only do one thing well, but to do many things good enough. This is in contrast to other libraries, which only do one thing and thus require the developer to pull in many different libraries for a broad range of functionality. The primary target of Cytoplasm is simple yet higher level C applications that have to perform relatively complex tasks, but don't want to depend on a large number of dependencies.
|
||||
|
||||
## What is Matrix?
|
||||
Cytoplasm is extremely opinionated on the way programs using it are written. It strives to create a comprehensive and tightly-integrated programming environment, while also maintaining C programming correctness. It doesn't do any macro magic or make C look like anything other than C. It is written entirely in C99, and depends only on a POSIX environment. This differentiates it from other general-purpose libraries that often require more modern compilers and non-standard language and environment features. Cytoplasm is intended to be extremely portable and simple, while still providing some of the functionality expected in higher-level programming languages in a platform-agnostic manner. In the case of TLS, Cytoplasm wraps low-level TLS libraries to offer a single, unified interface to TLS so that programs do not have to care about the underlying implementation.
|
||||
|
||||
Matrix is an **open standard** for *interoperable*, *decentralized*,
|
||||
*secure*, and *real-time* communication over the internet.
|
||||
Cytoplasm is probably not suitable for embedded programming. It makes liberal use of the heap, and while data structures are designed to conserve memory where possible and practical, minimal memory usage is not really a design goal for Cytoplasm, although Cytoplasm takes care not to use any more memory than it absolutely needs. Cytoplasm also wraps a few standard libraries with additional logic and checking. While this ensures better runtime safety, this inevitably adds a little overhead, which may be unsuitable for time- or space-critical tasks.
|
||||
|
||||
Matrix can be thought of as the successor to email, but it works
|
||||
very similar to iMessage, Discord, or direct messaging on most
|
||||
social media networks. The primary difference between Matrix and these
|
||||
other services, however, is that Matrix doesn't depend on one central
|
||||
authority, and is designed in such a way to respect your privacy.
|
||||
Matrix has proven itself over the last few
|
||||
years to be a reliable communication tool, and has only gotten more
|
||||
user-friendly over the course of its development. Matrix is capable
|
||||
enough that it can—and should—totally replace any other
|
||||
means of digital communication, and it offers a much higher degree
|
||||
of security, simplicity, and functionality.
|
||||
Originally a part of Telodendria ([Website](https://telodendria.io), [Repo](/Telodendria/Telodendria)), a Matrix homeserver written in C, Cytoplasm was split off into its own project due to the desire of some Telodendria developers to use Telodendria's code in other projects. Cytoplasm is still an official Telodendria project, but it is designed specifically to be distributed and used totally independent of Telodendria.
|
||||
|
||||
Strictly speaking, Matrix itself is just the *protocol* by which
|
||||
clients and servers communicate. In order to use Matrix, we need
|
||||
implementations of both clients and servers. Telodendria is a server
|
||||
implementation of the Matrix protocol.
|
||||
The name "Cytoplasm" was chosen for a few reasons. It plays off the precedent set up by the Matrix organization in naming projects after the parts of a neuron. It also speaks to the function of Cytoplasm. The cytoplasm of a cell is the supporting material. It is what gives the cell its shape, and it facilitates the movement of materials to the other cell parts. Likewise, Cytoplasm aims to provide a support mechanism for C applications that have to perform complex tasks beyond what the C standard library provides.
|
||||
|
||||
## Why Telodendria?
|
||||
Cytoplasm also starts with a C, which I think is a nice touch for C libraries. It's also fun to say and unique enough that searching for "libcytoplasm" should bring you to this project and not some other one.
|
||||
|
||||
- **Lightweight:** Written in the C programming language, Telodendria
|
||||
is automatically lighter and faster than other self-hosted chat servers.
|
||||
It has very few external dependencies and is as self-contained as
|
||||
possible.
|
||||
- **Fully-Featured:** Most lightweight chat solutions compromise on
|
||||
features. Telodendria is built on the fully-featured Matrix protocol,
|
||||
which provides a chat experience that most normal users are familiar
|
||||
with.
|
||||
- **Portable:** You can run Telodendria on just about everything,
|
||||
including more traditional options like a personal home server or VPS,
|
||||
but also more obscure platforms like Raspberry Pis or retro computers.
|
||||
Telodendria can run on a broad number of operating systems, which means
|
||||
that no matter which platform and OS you prefer, there is a good chance
|
||||
you can add Telodendria without much difficulty. It is also extremely
|
||||
easy to migrate a Telodendria instance between platforms; just copy the
|
||||
data directory to a new device.
|
||||
- **Simple:** Telodendria is designed to be a simple, no-frills
|
||||
chat server. It is easy to install, easy to configure, and easy to
|
||||
maintain.
|
||||
- **Stable:** Other Matrix homeservers develop at the pace of the
|
||||
Matrix specification itself, which is to say quite rapidly. Changes are
|
||||
always being made, and a version shipped 6 months ago is already
|
||||
incredibly outdated. Telodendria, on the other hand, aims to be stable.
|
||||
It should *just work* for long periods of time between upgrades, and
|
||||
you should never feel like Telodendria is going to change significantly
|
||||
between upgrades.
|
||||
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
|
||||
## Requirements
|
||||
|
||||
[Read Technical Rationale →](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
|
||||
Cytoplasm aims to have zero software dependencies beyond what is mandated by POSIX. You only need a standard C99 compiler, and the standard `math` and `pthread` libraries to build Cytoplasm. TLS support can optionally be enabled with the configuration script. The supported TLS implementations are as follows:
|
||||
|
||||
## Get Started
|
||||
- OpenSSL
|
||||
- LibreSSL
|
||||
|
||||
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
|
||||
Telodendria.
|
||||
If TLS support is not enabled, all APIs that use it should fall back to non-TLS behavior in a sensible manner. For example, if TLS support is not enabled, then the HTTP client API will simply return an error if a TLS connection is requested.
|
||||
|
||||
## Status
|
||||
## Building
|
||||
|
||||
Telodendria is in the very early stages of development. As such, it may
|
||||
not yet deliver on all of its promises. Currently, Telodendria is not
|
||||
ready for end-users yet. While it features very basic user
|
||||
authentication, it does not actually work as a chat server yet.
|
||||
If your operating system or software distribution provides a pre-built package of Cytoplasm, you should prefer to use that instead of building it from source.
|
||||
|
||||
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
|
||||
release should be usable for communication between **local users**
|
||||
only. Additional features, including federation with other Matrix
|
||||
homeservers will be added in future releases.
|
||||
Cytoplasm uses the standard C library build procedure. Just run these commands:
|
||||
|
||||
You can help speed up development by **sponsoring**
|
||||
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
|
||||
```
|
||||
./configure
|
||||
make
|
||||
```
|
||||
|
||||
## Sponsorship
|
||||
This will produce the following out/ directory:
|
||||
|
||||
Telodendria is maintained by a loosely-knit band of volunteers. The
|
||||
project currently has no sponsors and thus no source of income to
|
||||
pay for infrastructure costs and developer time. To ensure
|
||||
Telodendria's long-term success, please consider sponsoring the
|
||||
project.
|
||||
```
|
||||
out/
|
||||
lib/
|
||||
libcytoplasm.so - The Cytoplasm shared library.
|
||||
libcytoplasm.a - The Cytoplasm static archive.
|
||||
bin/ - A few useful tools build with Cytoplasm.
|
||||
man/ - All Cytoplasm API documentation.
|
||||
```
|
||||
|
||||
You can make a recurring donation to Telodendria using
|
||||
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
|
||||
one-time donations using
|
||||
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
||||
like to make a recurring donation larger than that allowed by
|
||||
LiberaPay, please contact Jordan Bancino over Matrix at
|
||||
`@jordan:bancino.net` or email at `jordan@bancino.net`.
|
||||
You can also run `make install` as `root` to install Cytoplasm to the system. This will install the libraries, tools, and `man` pages.
|
||||
|
||||
### Benefits
|
||||
The `configure` script has a number of optional flags, which are as follows:
|
||||
|
||||
While there are no set sponsorship tiers at this time, sponsoring
|
||||
Telodendria is a mutually beneficial relationship. Depending on the
|
||||
amount you donate, you can get your name, logo, and website links
|
||||
on the [Sponsors](../sponsors) page, the project `README`, or the
|
||||
main website.
|
||||
- `--with-(openssl|libressl)`: Select the TLS implementation to use. OpenSSL is selected by default.
|
||||
- `--disable-tls`: Disable TLS altogether.
|
||||
- `--prefix=<path>`: Set the install prefix to set by default in the `Makefile`. This defaults to `/usr/local`, which should be appropriate for most Unix-like systems.
|
||||
- `--(enable|disable)-debug`: Control whether or not to enable debug mode. This sets the optimization level to 0 and builds with debug symbols. Useful for running with a debugger.
|
||||
|
||||
Cytoplasm can be customized with the following options:
|
||||
|
||||
- `--lib-name=<name>`: The output name of the library. This defaults to `Cytoplasm` and should in most cases not be changed.
|
||||
|
||||
The following recipes are available in the generated `Makefile`:
|
||||
|
||||
- `all`: This is the default target. It builds everything.
|
||||
- `Cytoplasm`: Build the `libCytoplasm.(so|a)` binaries. If you specified an alternative `--lib-name`, then this target will be named after that.
|
||||
- `docs`: Generate the header documentation as `man` pages.
|
||||
- `tools`: Build the supplemental tools which may be useful for development.
|
||||
- `clean`: Remove the build and output directories. Cytoplasm builds are out-of-tree, which greatly simplifies this recipe compared to in-tree builds.
|
||||
|
||||
If you're developing Cytoplasm, these recipes may also be helpful:
|
||||
|
||||
- `format`: Format the source code using `indent`. This may require a BSD `indent` because last time I tried GNU `indent`, it didn't like the flags in `indent.pro`. Your mileage may vary.
|
||||
- `license`: Update the license headers in all source code files with the contents of the `LICENSE.txt`.
|
||||
|
||||
To install Telodendria to your system, the following recipes are available:
|
||||
|
||||
- `install`: This installs Cytoplasm under the prefix set with `./configure --prefix=<dir>` or with `make PREFIX=<dir>`. By default, the `make` `PREFIX` is set to whatever was set with `configure --prefix`.
|
||||
- `uninstall`: Uninstall Cytoplasm from the same prefix as specified above.
|
||||
|
||||
After a build, you can find the object files in `build/` and the output binaries in `out/lib/`.
|
||||
|
||||
## Usage
|
||||
|
||||
Cytoplasm provides the typical .so and .a files, which can be used to link programs with it in the usual way. Somewhat *unusually* for C libraries, however, it provides its own `main()` function, so programs written with Cytoplasm provide `Main()` instead, which is called by Cytoplasm. Cytoplasm works this way because it needs to perform some setup logic before user code runs and some teardown logic after user code returns.
|
||||
|
||||
Here is the canonical Hello World written with Cytoplasm:
|
||||
|
||||
```c
|
||||
#include <Cytoplasm/Log.h>
|
||||
|
||||
int Main(void)
|
||||
{
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
If this file is `Hello.c`, then you can compile it by doing this:
|
||||
|
||||
$ cc -o hello Hello.c -lCytoplasm
|
||||
|
||||
The full form of `Main()` expected by the stub is as follows:
|
||||
|
||||
```c
|
||||
int Main(Array *args, HashMap *env);
|
||||
```
|
||||
|
||||
The first argument is a Cytoplasm array of the command line arguments, and the second is a Cytoplasm hash map of environment variables. Most linkers will let programs omit the `env` argument, or both arguments if you don't need either. The return value of `Main()` is returned to the operating system, as would be expected.
|
||||
|
||||
Note that both arguments to Main may be treated like any other Cytoplasm array or hash map. However, do not invoke `ArrayFree()` or `HashMapFree()` on the passed pointers, because memory is cleaned up after `Main()` returns.
|
||||
|
||||
## License
|
||||
|
||||
All of the code and documentation for Telodendria is licensed under a
|
||||
modified MIT license. The MIT license is an extremely permissive
|
||||
license that has very few restrictions. Please consult the
|
||||
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
|
||||
important to note that the Telodendria license text differs from the
|
||||
original MIT license in the following ways:
|
||||
All of the code and documentation for Cytoplasm is licensed under the same license as Telodendria itself. Please refer to [Telodendria → License](/Telodendria/Telodendria#license) for details.
|
||||
|
||||
- Where the MIT license states that the copyright notice and permission
|
||||
notice shall be included in all copies or *substantial* portions of the
|
||||
software, the Telodendria requires the copyright notice and
|
||||
permission notice be included with *all* portions, regardless of the
|
||||
size, by omitting the word *substantial*.
|
||||
|
||||
The Telodendria logo in all its forms, including the ASCII
|
||||
representation, belongs solely to the Telodendria project. It must be
|
||||
used only to represent the official Telodendria project. You are free
|
||||
to use the logo in any way as long as it represents or links to the
|
||||
official project. If Telodendria is forked, the logo must be removed
|
||||
completely from the project, and optionally replaced by a different
|
||||
one.
|
||||
The Cytoplasm logo was designed by [Tobskep](https://tobskep.com) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_CLIENTEVENT_H",
|
||||
"header": "Schema/ClientEvent.h",
|
||||
"types": {
|
||||
"ClientEventUnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"prev_content": {
|
||||
"type": "object"
|
||||
},
|
||||
"redacted_because": {
|
||||
"type": "object"
|
||||
},
|
||||
"transaction_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientEvent": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "ClientEventUnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_CONFIG_H",
|
||||
"header": "Schema\/Config.h",
|
||||
"include": [ "Cytoplasm\/Db.h", "Cytoplasm/HttpServer.h" ],
|
||||
|
||||
"types": {
|
||||
"ConfigTls": {
|
||||
"fields": {
|
||||
"cert": { "type": "string", "required": true },
|
||||
"key": { "type": "string", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
|
||||
"ConfigListener": {
|
||||
"fields": {
|
||||
"port": { "type": "integer", "required": true },
|
||||
"threads": { "type": "integer", "required": false },
|
||||
"maxConnections": { "type": "integer", "required": false },
|
||||
"tls": { "type": "ConfigTls", "required": false }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"ConfigRunAs": {
|
||||
"fields": {
|
||||
"uid": { "type": "string", "required": false },
|
||||
"gid": { "type": "string", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"ConfigLogOutput": {
|
||||
"fields": {
|
||||
"stdout": { "name": "CONFIG_LOG_OUTPUT_STDOUT" },
|
||||
"file": { "name": "CONFIG_LOG_OUTPUT_FILE" },
|
||||
"syslog": { "name": "CONFIG_LOG_OUTPUT_SYSLOG" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"ConfigLogLevel": {
|
||||
"fields": {
|
||||
"message": { "name": "CONFIG_LOG_LEVEL_MESSAGE" },
|
||||
"debug": { "name": "CONFIG_LOG_LEVEL_DEBUG" },
|
||||
"notice": { "name": "CONFIG_LOG_LEVEL_NOTICE" },
|
||||
"warning": { "name": "CONFIG_LOG_LEVEL_WARNING" },
|
||||
"error": { "name": "CONFIG_LOG_LEVEL_ERROR" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"ConfigLogConfig": {
|
||||
"fields": {
|
||||
"output": { "type": "ConfigLogOutput", "required": true },
|
||||
"level": { "type": "ConfigLogLevel", "required": false },
|
||||
"timestampFormat":{ "type": "string", "required": false },
|
||||
"color": { "type": "boolean", "required": false }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
|
||||
"Db *": { "type": "extern" },
|
||||
"DbRef *": { "type": "extern" },
|
||||
"char *": { "type": "extern" },
|
||||
|
||||
"Config": {
|
||||
"fields": {
|
||||
"db": { "type": "Db *", "ignore": true },
|
||||
"ref": { "type": "DbRef *", "ignore": true },
|
||||
|
||||
"ok": { "type": "boolean", "ignore": true },
|
||||
"err": { "type": "char *", "ignore": true },
|
||||
|
||||
"listen": { "type": "[ConfigListener]", "required": true },
|
||||
"runAs": { "type": "ConfigRunAs", "required": false },
|
||||
"log": { "type": "ConfigLogConfig", "required": true },
|
||||
|
||||
"serverName": { "type": "string", "required": true },
|
||||
"baseUrl": { "type": "string", "required": false },
|
||||
"identityServer": { "type": "string", "required": false },
|
||||
"pid": { "type": "string", "required": false },
|
||||
|
||||
"maxCache": { "type": "integer", "required": false },
|
||||
|
||||
"federation": { "type": "boolean", "required": true },
|
||||
"registration": { "type": "boolean", "required": true }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
|
||||
"header": "Schema\/Filter.h",
|
||||
"types": {
|
||||
"FilterRoom": {
|
||||
"fields": {
|
||||
"not_rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"state": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"include_leave": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"timeline": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"account_data": {
|
||||
"type": "FilterRoomEvent"
|
||||
},
|
||||
"rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"ephemeral": {
|
||||
"type": "FilterRoomEvent"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"FilterEventFormat": {
|
||||
"fields": {
|
||||
"federation": {
|
||||
"name": "FILTER_FORMAT_FEDERATION"
|
||||
},
|
||||
"client": {
|
||||
"name": "FILTER_FORMANT_CLIENT"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"FilterEvent": {
|
||||
"fields": {
|
||||
"not_senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"not_types": {
|
||||
"type": "[string]"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"Filter": {
|
||||
"fields": {
|
||||
"event_format": {
|
||||
"type": "FilterEventFormat"
|
||||
},
|
||||
"presence": {
|
||||
"type": "FilterEvent"
|
||||
},
|
||||
"account_data": {
|
||||
"type": "FilterEvent"
|
||||
},
|
||||
"room": {
|
||||
"type": "FilterRoom"
|
||||
},
|
||||
"event_fields": {
|
||||
"type": "[string]"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"FilterRoomEvent": {
|
||||
"fields": {
|
||||
"not_rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"not_senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"senders": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"include_redundant_members": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"rooms": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"lazy_load_members": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"not_types": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"contains_url": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"unread_thread_notifications": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"header": "Schema\/LoginRequest.h",
|
||||
"types": {
|
||||
"LoginRequestType": {
|
||||
"fields": {
|
||||
"m.login.password": { "name": "REQUEST_TYPE_PASSWORD" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"LoginRequestUserIdentifier": {
|
||||
"fields": {
|
||||
"type": { "type": "string" },
|
||||
"user": { "type": "string" }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"LoginRequest": {
|
||||
"fields": {
|
||||
"type": { "type": "LoginRequestType" },
|
||||
|
||||
"identifier": { "type": "object" },
|
||||
|
||||
"password": { "type": "string" },
|
||||
"address": { "type": "string" },
|
||||
"user": { "type": "string" },
|
||||
"device_id": { "type": "string" },
|
||||
"initial_device_display_name": { "type": "string" },
|
||||
"medium": { "type": "string" },
|
||||
"token": { "type": "string" },
|
||||
|
||||
"refresh_token": { "type": "boolean" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_LOGIN_REQUEST_H"
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_PDUV1_H",
|
||||
"header": "Schema/PduV1.h",
|
||||
"types": {
|
||||
"PduV1EventHash": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV1UnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV1": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"auth_events": {
|
||||
"type": "array",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"event_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"hashes": {
|
||||
"type": "PduV1EventHash",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"prev_events": {
|
||||
"type": "array",
|
||||
"required": true
|
||||
},
|
||||
"redacts": {
|
||||
"type": "string"
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"signatures": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "PduV1UnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_PDUV3_H",
|
||||
"header": "Schema/PduV3.h",
|
||||
"types": {
|
||||
"PduV3EventHash": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"sha256": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV3UnsignedData": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PduV3": {
|
||||
"type": "struct",
|
||||
"fields": {
|
||||
"auth_events": {
|
||||
"type": "[string]",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"depth": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"hashes": {
|
||||
"type": "PduV3EventHash",
|
||||
"required": true
|
||||
},
|
||||
"origin_server_ts": {
|
||||
"type": "integer",
|
||||
"required": true
|
||||
},
|
||||
"prev_events": {
|
||||
"type": "[string]",
|
||||
"required": true
|
||||
},
|
||||
"redacts": {
|
||||
"type": "string"
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"sender": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"signatures": {
|
||||
"type": "object",
|
||||
"required": true
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"_unsigned": {
|
||||
"type": "PduV3UnsignedData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_REGTOKEN_H",
|
||||
"header": "Schema\/RegToken.h",
|
||||
"include": [
|
||||
"Cytoplasm\/Db.h"
|
||||
],
|
||||
"types": {
|
||||
"Db *": {
|
||||
"type": "extern"
|
||||
},
|
||||
"DbRef *": {
|
||||
"type": "extern"
|
||||
},
|
||||
"RegTokenInfo": {
|
||||
"fields": {
|
||||
"db": {
|
||||
"type": "Db *",
|
||||
"ignore": true
|
||||
},
|
||||
"ref": {
|
||||
"type": "DbRef *",
|
||||
"ignore": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_on": {
|
||||
"type": "integer"
|
||||
},
|
||||
"expires_on": {
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "integer"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"grants": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"header": "Schema\/Registration.h",
|
||||
"types": {
|
||||
"RegistrationRequest": {
|
||||
"fields": {
|
||||
"username": { "type": "string" },
|
||||
"password": { "type": "string" },
|
||||
"device_id": { "type": "string" },
|
||||
"inhibit_login": { "type": "boolean" },
|
||||
"initial_device_display_name": { "type": "string" },
|
||||
"refresh_token": { "type": "boolean" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_REGISTRATION_H"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"header": "Schema\/RequestToken.h",
|
||||
"types": {
|
||||
"RequestToken": {
|
||||
"fields": {
|
||||
"client_secret": { "type": "string" },
|
||||
"send_attempt": { "type": "integer" },
|
||||
"next_link": { "type": "string" },
|
||||
"id_access_token": { "type": "string" },
|
||||
"id_server": { "type": "string" },
|
||||
|
||||
"email": { "type": "string" },
|
||||
|
||||
"country": { "type": "string" },
|
||||
"phone_number": { "type": "string" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_REQUESTTOKEN_H"
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_ROOMCREATE_H",
|
||||
"header": "Schema/RoomCreateRequest.h",
|
||||
"types": {
|
||||
"RoomVisibility": {
|
||||
"fields": {
|
||||
"public": {
|
||||
"name": "ROOM_PUBLIC"
|
||||
},
|
||||
"private": {
|
||||
"name": "ROOM_PRIVATE"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomCreateRequest": {
|
||||
"fields": {
|
||||
"invite": {
|
||||
"type": "[string]"
|
||||
},
|
||||
"room_version": {
|
||||
"type": "string"
|
||||
},
|
||||
"invite_3pid": {
|
||||
"type": "[RoomInvite3Pid]"
|
||||
},
|
||||
"topic": {
|
||||
"type": "string"
|
||||
},
|
||||
"visibility": {
|
||||
"type": "RoomVisibility"
|
||||
},
|
||||
"creation_content": {
|
||||
"type": "object"
|
||||
},
|
||||
"is_direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"initial_state": {
|
||||
"type": "[RoomStateEvent]"
|
||||
},
|
||||
"power_level_content_override": {
|
||||
"type": "object"
|
||||
},
|
||||
"room_alias_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"preset": {
|
||||
"type": "RoomCreatePreset"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"RoomInvite3Pid": {
|
||||
"fields": {
|
||||
"id_access_token": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"address": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"medium": {
|
||||
"required": true,
|
||||
"type": "Room3PidMedium"
|
||||
},
|
||||
"id_server": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"Room3PidMedium": {
|
||||
"fields": {
|
||||
"msisdn": {
|
||||
"name": "ROOM_3PID_MSISDN"
|
||||
},
|
||||
"email": {
|
||||
"name": "ROOM_3PID_EMAIL"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomCreatePreset": {
|
||||
"fields": {
|
||||
"public_chat": {
|
||||
"name": "ROOM_CREATE_PUBLIC"
|
||||
},
|
||||
"trusted_private_chat": {
|
||||
"name": "ROOM_CREATE_TRUSTED"
|
||||
},
|
||||
"private_chat": {
|
||||
"name": "ROOM_CREATE_PRIVATE"
|
||||
}
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"RoomStateEvent": {
|
||||
"fields": {
|
||||
"content": {
|
||||
"required": true,
|
||||
"type": "object"
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"header": "Schema\/UserDirectoryRequest.h",
|
||||
"types": {
|
||||
"UserDirectoryRequest": {
|
||||
"fields": {
|
||||
"search_term": { "type": "string" },
|
||||
"limit": { "type": "integer" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_USERDIRECTORYREQUEST_H"
|
||||
}
|
230
configure
vendored
230
configure
vendored
|
@ -10,23 +10,26 @@ echo "-------------------"
|
|||
BUILD="build"
|
||||
OUT="out"
|
||||
SRC="src"
|
||||
INCLUDE="src/include"
|
||||
TOOLS="tools/src"
|
||||
SCHEMA="Schema"
|
||||
CYTOPLASM="Cytoplasm"
|
||||
INCLUDE="include/Cytoplasm"
|
||||
TOOLS="tools"
|
||||
|
||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||
LIBS="-lm -pthread -lCytoplasm"
|
||||
# Default compiler flags. These must be supported by all POSIX C compilers.
|
||||
# "Fancy" compilers that have additional options must be detected and set below.
|
||||
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${SRC}";
|
||||
LIBS="-lm -lpthread"
|
||||
|
||||
# Default args for all platforms.
|
||||
SCRIPT_ARGS="--prefix=/usr/local --lib-name=Cytoplasm"
|
||||
|
||||
# Set default args for all platforms
|
||||
SCRIPT_ARGS="--prefix=/usr/local --bin-name=telodendria --version=1.7.0-alpha4"
|
||||
|
||||
if [ -f "${CYTOPLASM}/configure" ]; then
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=${CYTOPLASM}"
|
||||
else
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cytoplasm=" # No cytoplasm path.
|
||||
fi
|
||||
# Set SSL flags depending on the platform.
|
||||
case "$(uname)" in
|
||||
OpenBSD)
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --with-libressl --disable-lmdb"
|
||||
;;
|
||||
*)
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --with-openssl --disable-lmdb"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Set compiler depending on the platform.
|
||||
case "$(uname)" in
|
||||
|
@ -34,7 +37,7 @@ case "$(uname)" in
|
|||
# These systems typically use GCC.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
|
||||
;;
|
||||
OpenBSD|FreeBSD|Darwin)
|
||||
OpenBSD|FreeBSD)
|
||||
# These systems typically use Clang.
|
||||
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
|
||||
;;
|
||||
|
@ -61,18 +64,35 @@ for arg in $SCRIPT_ARGS; do
|
|||
# "Fancy" compilers that support a plethora of additional flags we
|
||||
# want to enable if present.
|
||||
CFLAGS="-Wall -Wextra -Werror -pedantic -std=c99 -O3 ${CFLAGS}"
|
||||
LDFLAGS="${LDFLAGS} -flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
|
||||
LDFLAGS="-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--with-openssl)
|
||||
TLS_IMPL="TLS_OPENSSL"
|
||||
TLS_LIBS="-lcrypto -lssl"
|
||||
;;
|
||||
--with-libressl)
|
||||
TLS_IMPL="TLS_LIBRESSL"
|
||||
TLS_LIBS="-ltls -lcrypto -lssl"
|
||||
;;
|
||||
--disable-tls)
|
||||
TLS_IMPL=""
|
||||
TLS_LIBS=""
|
||||
;;
|
||||
--with-lmdb)
|
||||
EDB_IMPL="EDB_LMDB"
|
||||
EDB_LIBS="-llmdb"
|
||||
;;
|
||||
--disable-lmdb)
|
||||
EDB_IMPL=""
|
||||
EDB_LIBS=""
|
||||
;;
|
||||
--prefix=*)
|
||||
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--bin-name=*)
|
||||
BIN_NAME=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--version=*)
|
||||
VERSION=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
--lib-name=*)
|
||||
LIB_NAME=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--enable-debug)
|
||||
DEBUG="-O0 -g"
|
||||
|
@ -80,19 +100,6 @@ for arg in $SCRIPT_ARGS; do
|
|||
--disable-debug)
|
||||
DEBUG=""
|
||||
;;
|
||||
--cytoplasm=*)
|
||||
CYTOPLASM=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
if [ ! -f "${CYTOPLASM}/configure" ]; then
|
||||
echo "Path for Cytoplasm does not appear to actually contain Cytoplasm source:"
|
||||
echo "${CYTOPLASM}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CFLAGS="${CFLAGS} -I${CYTOPLASM}/include"
|
||||
LDFLAGS="-L${CYTOPLASM}/out/lib ${LDFLAGS}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $arg"
|
||||
exit 1
|
||||
|
@ -100,8 +107,18 @@ for arg in $SCRIPT_ARGS; do
|
|||
esac
|
||||
done
|
||||
|
||||
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
|
||||
LDFLAGS="${LDFLAGS} ${LIBS}"
|
||||
if [ -n "$TLS_IMPL" ]; then
|
||||
CFLAGS="${CFLAGS} -DTLS_IMPL=${TLS_IMPL}"
|
||||
LIBS="${LIBS} ${TLS_LIBS}"
|
||||
fi
|
||||
|
||||
if [ -n "$EDB_IMPL" ]; then
|
||||
CFLAGS="${CFLAGS} -D${EDB_IMPL}"
|
||||
LIBS="${LIBS} ${EDB_LIBS}"
|
||||
fi
|
||||
|
||||
CFLAGS="${CFLAGS} '-DLIB_NAME=\"${LIB_NAME}\"' ${DEBUG}"
|
||||
LDFLAGS="${LIBS} ${LDFLAGS}"
|
||||
|
||||
#
|
||||
# Makefile generation
|
||||
|
@ -125,25 +142,6 @@ collect() {
|
|||
done
|
||||
}
|
||||
|
||||
prefix() {
|
||||
prefix="$1"
|
||||
shift
|
||||
|
||||
for thing in $@; do
|
||||
printf "${prefix}${thing} "
|
||||
done
|
||||
}
|
||||
|
||||
cytoplasm_tool() {
|
||||
tool="$1"
|
||||
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
echo "LD_LIBRARY_PATH=${CYTOPLASM}/out/lib ${CYTOPLASM}/out/bin/$tool"
|
||||
else
|
||||
echo "$tool"
|
||||
fi
|
||||
}
|
||||
|
||||
print_src() {
|
||||
printf '%s ' "$1"
|
||||
}
|
||||
|
@ -155,14 +153,14 @@ print_obj() {
|
|||
get_deps() {
|
||||
src="$1"
|
||||
|
||||
${CC} -I${INCLUDE} -I${BUILD} $(if [ -n "${CYTOPLASM}" ]; then echo "-I${CYTOPLASM}/include"; fi) -E "$src" \
|
||||
| grep '^#' \
|
||||
| awk '{print $3}' \
|
||||
| cut -d '"' -f 2 \
|
||||
| sort \
|
||||
| uniq \
|
||||
| grep -v '^[/<]' \
|
||||
| grep -e "^${SRC}/" -e "^${BUILD}/" \
|
||||
${CC} -I${SRC} -I${INCLUDE} -E "$src" \
|
||||
| grep '^#' \
|
||||
| awk '{print $3}' \
|
||||
| cut -d '"' -f 2 \
|
||||
| sort \
|
||||
| uniq \
|
||||
| grep -v '^[/<]' \
|
||||
| grep "^${SRC}/" \
|
||||
| while IFS= read -r dep; do
|
||||
printf "%s " "$dep"
|
||||
done
|
||||
|
@ -172,8 +170,7 @@ compile_obj() {
|
|||
src="$1"
|
||||
obj="$2"
|
||||
|
||||
pref="${obj}: $(get_deps ${src})"
|
||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
|
||||
echo "${obj}: $(get_deps ${src})"
|
||||
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
||||
}
|
||||
|
@ -182,50 +179,19 @@ compile_bin() {
|
|||
src="$1"
|
||||
out="$2"
|
||||
|
||||
depObjs=$(prefix ${BUILD}/ CanonicalJson.o Telodendria.o)
|
||||
|
||||
echo "${out}: ${src}"
|
||||
echo "${out}: ${OUT}/lib/lib${LIB_NAME}.a ${OUT}/lib/lib${LIB_NAME}.so ${src}"
|
||||
echo "${TAB}@mkdir -p ${OUT}/bin"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS)"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" -L${OUT}/lib \$(LDFLAGS) -l${LIB_NAME}"
|
||||
}
|
||||
|
||||
compile_doc() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
pref="LD_LIBRARY_PATH=${OUT}/lib "
|
||||
|
||||
if echo "${src}" | grep "Schema" > /dev/null; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "${out}: ${src}"
|
||||
echo "${out}: ${OUT}/bin/hdoc ${src}"
|
||||
echo "${TAB}@mkdir -p ${OUT}/man/man3"
|
||||
echo "${TAB}$(cytoplasm_tool hdoc) -D \"Os=${BIN_NAME}\" -i \"${src}\" -o \"${out}\""
|
||||
}
|
||||
|
||||
print_doc() {
|
||||
if echo "${src}" | grep "Schema" > /dev/null; then
|
||||
return
|
||||
fi
|
||||
printf '%s ' "$2"
|
||||
}
|
||||
|
||||
compile_schema() {
|
||||
src="$1"
|
||||
out="$2"
|
||||
|
||||
obj="${BUILD}/Schema/${out}.o"
|
||||
|
||||
echo "${BUILD}/Schema/${out}.h:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${BUILD}/Schema/${out}.c:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}$(cytoplasm_tool j2s) -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${obj}: ${src} ${BUILD}/Schema/${out}.c"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${BUILD}/Schema/${out}.c\""
|
||||
echo "${TAB}${pref}${OUT}/bin/hdoc -D \"Os=${LIB_NAME}\" -i \"${src}\" -o \"${out}\""
|
||||
}
|
||||
|
||||
install_out() {
|
||||
|
@ -238,9 +204,18 @@ install_out() {
|
|||
}
|
||||
|
||||
install_man() {
|
||||
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
|
||||
src="${OUT}/man/man3/${LIB_NAME}-$(basename $1 .h).3"
|
||||
out="$2"
|
||||
dir=$(dirname "$out")
|
||||
dir=$(dirname "$out")
|
||||
|
||||
echo "${TAB}mkdir -p \"$dir\""
|
||||
echo "${TAB}cp \"$src\" \"$out\""
|
||||
}
|
||||
|
||||
install_tool() {
|
||||
src=${OUT}/bin/$(basename "$1" .c)
|
||||
out="$2"
|
||||
dir=$(dirname "$out")
|
||||
|
||||
echo "${TAB}mkdir -p \"$dir\""
|
||||
echo "${TAB}cp \"$src\" \"$out\""
|
||||
|
@ -255,7 +230,7 @@ uninstall_out() {
|
|||
|
||||
echo "Generating Makefile..."
|
||||
|
||||
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
||||
OBJS=$(collect ${SRC}/ .c .o ${BUILD}/ print_obj)
|
||||
TAB=$(printf '\t')
|
||||
|
||||
cat << EOF > Makefile
|
||||
|
@ -269,10 +244,13 @@ PREFIX = ${PREFIX}
|
|||
CFLAGS = ${CFLAGS}
|
||||
LDFLAGS = ${LDFLAGS}
|
||||
|
||||
all: ${BIN_NAME} docs tools
|
||||
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- print_doc)
|
||||
all: ${LIB_NAME} docs tools
|
||||
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${LIB_NAME}- print_obj)
|
||||
tools: $(collect ${TOOLS}/ .c '' ${OUT}/bin/ print_obj)
|
||||
|
||||
print-libs:
|
||||
${TAB}@echo ${LIBS}
|
||||
|
||||
format:
|
||||
${TAB}find . -name '*.c' | while IFS= read -r src; do \\
|
||||
${TAB} if indent "\$\$src"; then \\
|
||||
|
@ -288,34 +266,38 @@ ${TAB} diff -u -p - "LICENSE.txt" | \\
|
|||
${TAB} patch "\$\$src" | grep -v "^Hmm"; \\
|
||||
${TAB}done
|
||||
|
||||
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
|
||||
${LIB_NAME}: ${OUT}/lib/lib${LIB_NAME}.a ${OUT}/lib/lib${LIB_NAME}.so
|
||||
|
||||
install: ${BIN_NAME}
|
||||
${TAB}mkdir -p \$(PREFIX)/bin
|
||||
${TAB}cp ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
|
||||
install: ${LIB_NAME}
|
||||
${TAB}mkdir -p \$(PREFIX)/${OUT}/lib
|
||||
${TAB}mkdir -p \$(PREFIX)/lib
|
||||
${TAB}cp ${OUT}/lib/lib${LIB_NAME}.a \$(PREFIX)/lib/lib${LIB_NAME}.a
|
||||
${TAB}cp ${OUT}/lib/lib${LIB_NAME}.so \$(PREFIX)/lib/lib${LIB_NAME}.so
|
||||
$(collect ${INCLUDE}/ '' '' \$\(PREFIX\)/include/${LIB_NAME}/ install_out)
|
||||
$(collect ${INCLUDE}/ .h .3 \$\(PREFIX\)/man/man3/${LIB_NAME}- install_man)
|
||||
$(collect ${TOOLS}/ '.c' '' \$\(PREFIX\)/bin/ install_tool)
|
||||
|
||||
uninstall:
|
||||
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
|
||||
${TAB}rm -r \$(PREFIX)/lib/lib${LIB_NAME}.*
|
||||
${TAB}rm -r \$(PREFIX)/include/${LIB_NAME}
|
||||
${TAB}rm -r \$(PREFIX)/man/man3/${LIB_NAME}-*
|
||||
$(collect ${TOOLS}/ '.c' '' \$\(PREFIX\)/bin/ uninstall_out)
|
||||
|
||||
clean:
|
||||
${TAB}rm -r "${BUILD}" "${OUT}"
|
||||
|
||||
${OUT}/bin/${BIN_NAME}: ${OBJS}
|
||||
${TAB}@mkdir -p "${OUT}/bin"
|
||||
${TAB}\$(CC) -o "${OUT}/bin/${BIN_NAME}" ${OBJS} \$(CFLAGS) \$(LDFLAGS)
|
||||
${OUT}/lib/lib${LIB_NAME}.a: ${OBJS}
|
||||
${TAB}@mkdir -p ${OUT}/lib
|
||||
${TAB}\$(AR) rcs ${OUT}/lib/lib${LIB_NAME}.a ${OBJS}
|
||||
|
||||
${OUT}/lib/lib${LIB_NAME}.so: ${OBJS}
|
||||
${TAB}@mkdir -p ${OUT}/lib
|
||||
${TAB}\$(CC) -shared -o ${OUT}/lib/lib${LIB_NAME}.so ${OBJS} ${LDFLAGS}
|
||||
|
||||
$(collect ${SCHEMA}/ .json '' '' compile_schema)
|
||||
$(collect ${SRC}/ .c .o ${BUILD}/ compile_obj)
|
||||
$(collect ${TOOLS}/ .c '' ${OUT}/bin/ compile_bin)
|
||||
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- compile_doc)
|
||||
|
||||
$(
|
||||
if [ -n "${CYTOPLASM}" ]; then
|
||||
echo "cytoplasm:"
|
||||
echo "${TAB}cd ${CYTOPLASM} && ./configure && \$(MAKE)"
|
||||
fi
|
||||
)
|
||||
$(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${LIB_NAME}- compile_doc)
|
||||
|
||||
EOF
|
||||
|
||||
echo "Done. Run 'make' to build ${BIN_NAME}."
|
||||
echo "Done. Run 'make' to build ${LIB_NAME}."
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
all:
|
||||
sh tools/bin/td
|
||||
|
||||
install:
|
||||
install build/telodendria $(PREFIX)/bin/telodendria
|
||||
find man -name 'telodendria*\.[1-8]' -exec install {} $(PREFIX)/{} \;
|
34
contrib/Vagrantfile
vendored
34
contrib/Vagrantfile
vendored
|
@ -1,34 +0,0 @@
|
|||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "generic/openbsd7"
|
||||
config.vm.network "forwarded_port", guest: 80, host: 80
|
||||
config.vm.network "forwarded_port", guest: 443, host: 443
|
||||
config.vm.network "forwarded_port", guest: 8008, host: 8008
|
||||
# NOTE: This address is not within the allowed ranges.
|
||||
# To allow this address, simply allow all ranges by specifying
|
||||
# this in /etc/vbox/networks.conf (if you use the Virtualbox provider, or change the configured address):
|
||||
# * 0.0.0.0/0 ::/0
|
||||
config.vm.network "private_network", ip: "172.17.0.101"
|
||||
# File watcher which syncs the project directory to /vagrant on the vm
|
||||
config.vm.synced_folder "../", "/vagrant"
|
||||
config.vm.provision "shell", inline: <<-EOF
|
||||
cp /vagrant/contrib/relayd.conf /etc/relayd.conf
|
||||
sed -i s/127.0.0.1/0.0.0.0/ /etc/relayd.conf
|
||||
mkdir -p -m 0700 /etc/ssl/private
|
||||
openssl req -x509 -newkey rsa:4096 \
|
||||
-days 365 -nodes \
|
||||
-subj '/CN=telodendria' \
|
||||
-keyout /etc/ssl/private/telodendria.key \
|
||||
-out /etc/ssl/telodendria.crt
|
||||
relayd -n
|
||||
rcctl enable relayd
|
||||
rcctl restart relayd
|
||||
cat /vagrant/tools/env.sh >> /home/vagrant/.bash_profile
|
||||
sed -i 's#$(pwd)#/vagrant#' /home/vagrant/.bash_profile
|
||||
sed -i 's#find tools/bin#find /vagrant/tools/bin#' /home/vagrant/.bash_profile
|
||||
mkdir /vagrant/data
|
||||
cp /vagrant/contrib/development.conf /vagrant/contrib/development.conf.bak
|
||||
sed -i 's/"localhost"/"vagrant"/' /vagrant/contrib/development.conf
|
||||
### If you changed the address in the config above you might want to change it here as well:
|
||||
sed -i s#http://localhost:8008#https://172.17.0.101:443# /vagrant/contrib/development.conf
|
||||
EOF
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"color": true,
|
||||
"timestampFormat": "none",
|
||||
"level": "debug"
|
||||
},
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008,
|
||||
"tls": false
|
||||
}
|
||||
],
|
||||
"registration": true,
|
||||
"serverName": "localhost",
|
||||
"baseUrl": "http:\/\/localhost:8008",
|
||||
"federation": true
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"log": {
|
||||
"output": "file"
|
||||
},
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008,
|
||||
"tls": false
|
||||
},
|
||||
{
|
||||
"port": 8448,
|
||||
"tls": {
|
||||
"cert": "telodendria.crt",
|
||||
"key": "telodendria.key"
|
||||
}
|
||||
}
|
||||
],
|
||||
"serverName": "example.com",
|
||||
"identityServer": "https://identity.example.com",
|
||||
"baseUrl": "https://matrix.example.com",
|
||||
"registration": false,
|
||||
"federation": true,
|
||||
"maxCache": 512000000
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
table <telodendria> { localhost }
|
||||
|
||||
http protocol httpproxy {
|
||||
pass request quick path "/_matrix/*" forward to <telodendria>
|
||||
pass request quick path "/.well-known/matrix/*" forward to <telodendria>
|
||||
|
||||
# You'll have to generate the following for this to work:
|
||||
# /etc/ssl/telodendria.crt /etc/ssl/private/telodendria.key
|
||||
tls keypair "telodendria"
|
||||
|
||||
block
|
||||
return error
|
||||
}
|
||||
|
||||
relay proxy {
|
||||
listen on 127.0.0.1 port https tls
|
||||
|
||||
protocol httpproxy
|
||||
|
||||
forward to <telodendria> port 8008
|
||||
}
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
# Telodendria Change Log
|
||||
|
||||
This document contains the complete change log for every official release of Telodendria.
|
||||
It is intended to be updated with every commit that makes a user-facing change worth
|
||||
reporting in the change log. As such, it changes frequently between releases. Final
|
||||
change log entries are published as [Releases](releases).
|
||||
|
||||
## v1.7.0-alpha4
|
||||
|
||||
**Not Released Yet.**
|
||||
|
||||
This release brings filters, rooms, and events! The core of the Matrix
|
||||
protocol architecture is now in place.
|
||||
|
||||
Note that the versioning scheme has changed from `v0.X.0` to
|
||||
`v1.7.0-alphaX`. This is so that Telodendria releases correspond to the
|
||||
Matrix specification that they implement, in accordance with
|
||||
[this blog post](https://telodendria.io/blog/on-matrixs-release-cadence-and-state-resolution-v1).
|
||||
This versioning scheme change does not indicate a drastic leap forward
|
||||
in Telodendria's development—the `-alpha4` suffix indicates that
|
||||
this is the 4th pre-release, with the target being a stable `v1.7.0`.
|
||||
Note also that we still have a *long* way to go before we reach that
|
||||
stable release.
|
||||
|
||||
### Matrix Specification
|
||||
|
||||
The following endpoints were added:
|
||||
|
||||
- **POST** `/_matrix/client/v3/user/{userId}/filter`
|
||||
- **GET** `/_matrix/client/v3/user/{userId}/filter/{filterId}`
|
||||
|
||||
### Bug Fixes & General Improvements
|
||||
|
||||
- Use `j2s` for parsing the configuration
|
||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors
|
||||
with certain Matrix clients. (#35)
|
||||
- Improved compatibility with NetBSD on various platforms.
|
||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
|
||||
will now be maintained separately and have its own releases as well.
|
||||
- Use a `configure` script and `make` to build Telodendria instead of
|
||||
custom scripts.
|
||||
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
|
||||
parsing request bodies.
|
||||
- Create a parser API for grammars found in Matrix, and refactor the
|
||||
User API to use it.
|
||||
|
||||
### New Features
|
||||
|
||||
- Implemented a `"pid"` option in the configuration, allowing Telodendria
|
||||
to write its process ID to a specified file.
|
||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
|
||||
because later revisions of the administrator API may break clients, so
|
||||
we want a way to give those breaking revisions new endpoints.
|
||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
|
||||
to be able to deactivate users.
|
||||
- Added a **PUT** option to `/_telodendria/admin/v1/config` that gives
|
||||
the ability to change only a subset of the configuration.
|
||||
- Implemented the following APIs for managing registration tokens:
|
||||
- **GET** `/_telodendria/admin/tokens`
|
||||
- **GET** `/_telodendria/admin/tokens/[token]`
|
||||
- **POST** `/_telodendria/admin/tokens`
|
||||
- **DELETE** `/_telodendria/admin/tokens/[token]`
|
||||
- **GET** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **PUT** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **DELETE** `/_matrix/client/v3/directory/room/[alias]`
|
||||
- **GET** `/_matrix/client/v3/rooms/[id]/aliases`
|
||||
|
||||
## v0.3.0
|
||||
|
||||
**Saturday, June 10, 2023**
|
||||
|
||||
Introducing a new configuration API and Cytoplasm, a general-purpose C library that
|
||||
supports source/sink-agnostic I/O, TLS, an HTTP client, and more! The third major
|
||||
release of Telodendria packs a lot of architectural improvements on top of supporting
|
||||
more of the Matrix specification.
|
||||
|
||||
### Matrix Specification
|
||||
|
||||
Added support for the following endpoints:
|
||||
|
||||
- `/_matrix/client/v3/account/whoami`
|
||||
- `/_matrix/client/v3/account/password`
|
||||
- `/_matrix/client/v3/account/deactivate`
|
||||
- `/_matrix/client/v3/profile/*`
|
||||
- `/_matrix/client/v3/capabilities`
|
||||
- `/_matrix/client/v3/auth/*/fallback/web`
|
||||
|
||||
There is also support for token-based user registration. Note that there is as of
|
||||
yet no admin-facing way to create these registration tokens, but the APIs are in
|
||||
place.
|
||||
|
||||
### New Features
|
||||
|
||||
- Added a new `HttpClient` API for making HTTP requests. This will eventually be
|
||||
used for federating with other Matrix homeservers.
|
||||
- Added support for pretty-printing JSON in `Json`. Telodendria itself does not
|
||||
pretty-print JSON, but this is useful for debugging and building useful tools.
|
||||
- Added a handful of new development tools built on the Telodendria APIs. New
|
||||
tools include `http`, a command line tool for making HTTP requests, similar to
|
||||
`curl`, `json`, a command line tool for working with JSON, similar to `jq`, and
|
||||
`http-debug-server`, a simple HTTP server that just prints requests out to standard
|
||||
output and returns an empty JSON object. `http` and `json` are replacements for
|
||||
`curl` and `jq` that build on the `HttpClient` and `Json` APIs. They exist mainly
|
||||
to test those APIs, but also to reduce the number of dependencies that Telodendria
|
||||
has. `http-debug-server` exists to test the `HttpServer` and `HttpClient` APIs.
|
||||
- Replaced all usage of `jq` with the new `json` tool. `jq` is no longer a development
|
||||
dependency.
|
||||
- Replaced all usage of `curl` with the new `http` tool. `curl` is no longer a
|
||||
required development dependency.
|
||||
- Added a new `tt` script for easily making Matrix requests against Telodendria
|
||||
in development.
|
||||
- Added TLS support to both the HTTP client and server. Currently, Telodendria
|
||||
supports LibreSSL and OpenSSL, but other TLS libraries should be extremely easy
|
||||
to add support for.
|
||||
- Added support for spinning up multiple HTTP servers. This is useful for having
|
||||
a TLS port and a non-TLS port, for example.
|
||||
- Moved all program configuration to the data directory and added an administrator
|
||||
API endpoint to manage it. It is now no longer recommended to manually update the
|
||||
configuration file. Consult the [Administrator API](user/admin/README.md) documentation
|
||||
and the [Configuration](user/config.md) documentation.
|
||||
- Added an administrator API endpoint for process control. Telodendria can now be
|
||||
restarted or shutdown via API endpoint.
|
||||
- Added an administrator API endpoint for getting statistics about the running
|
||||
Telodendria process.
|
||||
- Added support for user privileges, a way to have fine-grained control over what
|
||||
users are allowed to do with the administrator API. Administrator APIs for setting
|
||||
and getting privileges is now supported, and registration tokens have privileges
|
||||
associated with them so that users created with a token will automatically be given
|
||||
the specified privileges.
|
||||
|
||||
### Fixes & General Improvements
|
||||
|
||||
- Fixed a few warnings that were generated on some obscure compilers.
|
||||
- Moved the `main()` function to its own file to make it easier to link other
|
||||
programs with the Telodendria APIs.
|
||||
- Fixed the development tools environment script. Apparently using a hyphen as a bullet
|
||||
point is not very portable, because some shell implementations of `printf` interpret it
|
||||
as a flag. Switched to an asterisk.
|
||||
- Fixed some intermittent I/O errors that would occur as a result of race conditions in
|
||||
`JsonConsomeWhitespace()`. This function, and a few others, expect I/O to be blocking,
|
||||
but the `HttpServer` sets up I/O to be non-blocking, leading to occasional failures in
|
||||
JSON parsing.
|
||||
- Abstracted all I/O into the new `Io` and `Stream` APIs, which provide an input- and
|
||||
output- agnostic stream processing interface. This allows for a simple implementation of
|
||||
proxies, TLS, and other stream filters without having to change any of the existing
|
||||
code.
|
||||
- Remove all non-POSIX function calls, including the call to `chroot()` and, on
|
||||
OpenBSD, `pledge()` and `unveil()`. This may seem like a downgrade in security, but
|
||||
these are platform-specific system calls that should be patched in by package maintainers
|
||||
if they are desired. They also caused problems when implementing other features, because
|
||||
some library calls need to be able to access files on the filesystem.
|
||||
- Fixed the build script to supply `LDFLAGS` after the object files when linking.
|
||||
Apparently the order in which libraries are passed matters to some compilers.
|
||||
- Added the response status of a request to the log output. This means that requests are
|
||||
logged after they have completed, not before they are started.
|
||||
- Memory allocations, reallocations, and frees are no longer loged when the log level
|
||||
is set to debug in the configuration file. To enable the logging of memory operations,
|
||||
pass the `-v` flag.
|
||||
- Implemented a proper HTTP request router with POSIX regular expression support.
|
||||
Previously, a series of nested `if`-statements were used to route requests, but this
|
||||
approach quickly becamse very messy. While the HTTP request router incurs a small memory
|
||||
and runtime speed penalty, the code is now much more maintainable and easier to follow.
|
||||
- Fixed some memory bugs in `Db` that were related to caching data. Caching should
|
||||
now work as expected.
|
||||
- Fixed a major design flaw in `Db` that would cause deadlock when multiple threads
|
||||
request access to the same object. Database locking is now in a per-thread basis,
|
||||
instead of a per-reference basis.
|
||||
- Telodendria now shuts down cleanly in response to `SIGTERM`.
|
||||
- Did some general refactoring to make the source code more readable and easier
|
||||
to maintain.
|
||||
Fixed a number of memory-related issues, including switching out some unsafe
|
||||
functions for safer versions, per the recommendations of the OpenBSD linker.
|
||||
- Moved all code documentation into the C header files to make it more likely
|
||||
that it will get updated. A simple header file parser and documentation generator
|
||||
have been added to the code base. See the `hdoc` man pages for documentation.
|
||||
- Updated the build script to provide static and shared libraries containing
|
||||
the code for Telodendria to make it easier to statically and dynamically link to
|
||||
other programs. The idea is that these libraries should be shipped with Telodendria,
|
||||
or as a separate package, and can be used to provide a high-level programming
|
||||
environment.
|
||||
- Updated the `Json` API to calculate the length of a JSON object. This is
|
||||
used to set the `Content-Length` header in HTTP requests and reponses.
|
||||
- Added some string functions, including `StrEquals()`, which replaced almost all
|
||||
uses of `strcmp()`, since `strcmp()` is used almost exclusively for equality
|
||||
checking. `StrEquals()` provides a standard way to do so, because previously,
|
||||
multiple different conventions could be found throughout the code base (for example:
|
||||
`!strcmp(str1, str2)` vs `strcmp(str1, str2) == 0`).
|
||||
|
||||
... And many more!
|
||||
|
||||
## v0.2.1
|
||||
|
||||
**Monday, March 6, 2023**
|
||||
|
||||
This is a patch release that fixes a few typos and other minor issues.
|
||||
|
||||
## v0.2.0
|
||||
|
||||
**Monday, March 6, 2023**
|
||||
|
||||
This release is focused on providing a decent amount of the client authentication
|
||||
API. You can now create accounts on a Telodendria homeserver, and log in to
|
||||
get access tokens.
|
||||
|
||||
### New
|
||||
|
||||
- Added the basic form of the user registration API. If registration is enabled
|
||||
in the configuration file, clients can now register for Matrix accounts.
|
||||
- Added the basic form of the user login API. Clients can now log in to
|
||||
their accounts and generate access tokens to be used to authenticate requests.
|
||||
- Added the basic form of the user interactive authentication API, which can be used
|
||||
by endpoints that the spec says requires it. Currently, it only implements the
|
||||
dummy and password stages, but more stages, such as the registration token stage,
|
||||
will be added in future releases.
|
||||
- Added a simple landing page that allows those setting up Telodendria to
|
||||
quickly verify that it is accessible where it needs to be.
|
||||
- Added the static login page for clients that don't support regular login.
|
||||
|
||||
### Changes
|
||||
|
||||
- Improved HTTP request logging by removing unnecessary log entries and making
|
||||
errors more specific.
|
||||
- Leaked memory is now hexdump-ed out to the log if the log level is set to debug.
|
||||
This greatly simplifies debugging, because developers can now see exactly what the
|
||||
contents of the leaked memory are. Note that in some circumstances, this memory
|
||||
may contain sensitive data, such as access tokens, usernames, or passwords. However,
|
||||
Telodendria should not be leaking memory at all, so if you encounter any leaks,
|
||||
please report them.
|
||||
- Refactored a lot of the code and accompanying documentation to be more readable and
|
||||
maintainable.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed a memory leak that would occur when parsing an invalid JSON object.
|
||||
- Fixed an edge case where HTTP response headers were being sent before they were
|
||||
properly set, causing the server to report a status of 200 even when that wasn't the
|
||||
desired status.
|
||||
- Fixed a few memory leaks in the HTTP parameter decoder that would occur in some
|
||||
edge cases.
|
||||
- Fixed an "off-by-one" error in the HTTP server request parser that would prevent
|
||||
`GET` parameters from being parsed.
|
||||
- Fixed the database file name descriptor to prevent directory traversal attacks
|
||||
by replacing special characters with safer ones.
|
||||
- Fixed a memory leak that would occur when closing a database that contains
|
||||
cached objects.
|
||||
- Fixed a memory leak that would occur when deleting database objects.
|
||||
- Fixed a few non-fatal memory warnings that would show up as a result of passing a
|
||||
constant string into certain functions.
|
||||
|
||||
### Misc.
|
||||
|
||||
- Fixed a bug in `td` that caused `cvs` to be invoked in the wrong directory when
|
||||
tagging a new release.
|
||||
- Added support for environment variable substitution in all site files. This
|
||||
makes it easier to release Telodendria versions.
|
||||
- Fix whitespace issues in various shell scripts.
|
||||
- Fixed the debug log output so that it only shows the file name, not the
|
||||
entire file path in the repository.
|
||||
- Updated the copyright year in the source code and compiled output.
|
||||
- Switched the `-std=c89` flag to `-ansi`, as `-ansi` might be more supported.
|
||||
- Fixed the `-v` flag. It now sets the log level to debug as soon as possible to
|
||||
allow debugging configuration file parsing if necessary.
|
||||
|
||||
... And many more bug fixes and feature additions! Too much has changed to make a
|
||||
comprehensive change log. A lot of things have been done under the hood to make
|
||||
Telodendria easier to develop in the future. Please test the current functionality,
|
||||
and report bugs.
|
||||
|
||||
The following platforms have been known to compile and run Telodendria:
|
||||
|
||||
- OpenBSD
|
||||
- Linux (GNU and non-GNU)
|
||||
- Windows (via Cygwin)
|
||||
- FreeBSD
|
||||
- NetBSD
|
||||
- DragonFlyBSD
|
||||
- Haiku OS
|
||||
- Android (via Termux)
|
||||
|
||||
Telodendria is about being portable; if you compile it on an obscure operating system,
|
||||
do let us know about it!
|
||||
|
||||
## v0.1.0
|
||||
|
||||
**Tuesday, December 13, 2022**
|
||||
|
||||
This is the first public release of Telodendria so there are no changes to report.
|
||||
Future releases will have a complete change log entry here.
|
||||
|
||||
This is a symbolic release targeted at developers, so there's nothing useful to
|
||||
ordinary users yet. Stay tuned for future releases though!
|
|
@ -1,256 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
Telodendria is a fully open source project. As such, it welcomes
|
||||
contributions. There are many ways you can contribute, and any way you
|
||||
can is greatly appreciated. This document details the ways you can
|
||||
contribute, and how to go about contributing.
|
||||
|
||||
## Sponsoring Telodendria
|
||||
|
||||
If you would like to sponsor Telodendria, see the
|
||||
[Sponsorship](../README.md#sponsorship) section on the main project
|
||||
page. Donations of any size are greatly appreciated.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
An important way to get involved is to just report issues you find with
|
||||
Telodendria during experimentation or normal use. To report an issue,
|
||||
go to [Issues](/Telodendria/telodendria/issues) →
|
||||
[New Issue](/Telodendria/telodendria/issues/new/choose) and follow the
|
||||
instructions.
|
||||
|
||||
> **Note:** GitHub issues are not accepted. Issues may only be
|
||||
> submitted to the official [Gitea](https://git.telodendria.io)
|
||||
> instance.
|
||||
|
||||
### Feature Requests
|
||||
|
||||
Feature requests are allowed, but note that they are low-priority in
|
||||
comparison to existing issues and features. That being said, don't
|
||||
hesitate to submit feature requests. Just select the "Feature Request"
|
||||
option when submitting an issue.
|
||||
|
||||
## Developing
|
||||
|
||||
If you want to write code for Telodendria, either to fix an issue or
|
||||
add a new feature, you're in the right place. Please follow all the
|
||||
guidelines in this document to ensure the contribution workflow goes
|
||||
as smoothly as possible.
|
||||
|
||||
### Who can develop Telodendria?
|
||||
|
||||
Everyone is welcome to contribute code to Telodendria, provided that
|
||||
they are willing to license their contributions under the same license
|
||||
as the project itself.
|
||||
|
||||
The primary language used to write Telodendria code is ANSI C. Other
|
||||
languages you'll find in the Telodendria repository include shell
|
||||
scripts, `mdoc`, a little bit of HTML and CSS, and `Makefiles`.
|
||||
Experience with any of these is preferred, but if you want to use
|
||||
Telodendria to learn, that's okay too! Telodendria's code base should
|
||||
hopefully be a good learning tool, and if you are serious about
|
||||
submitting quality work, we'll guide you through the process and
|
||||
offer suggestions.
|
||||
|
||||
### What do I need?
|
||||
|
||||
You'll need a couple of things to develop Telodendria:
|
||||
|
||||
- A Unix-like operating system that provides standard POSIX behavior,
|
||||
or the Windows Subsystem for Linux (WSL), Cygwin, or Msys2 if you are
|
||||
running Windows.
|
||||
- A C compiler capable of compiling ANSI C89 code (pretty much all of
|
||||
them do—pick your favorite, and if you find it doesn't work,
|
||||
open an issue!).
|
||||
- `make` for building the project.
|
||||
- `git` for managing your changes.
|
||||
- [Cytoplasm](/Telodendria/Cytoplasm), a simple C library written by
|
||||
the Telodendria developers for the purpose of supporting Telodendria
|
||||
in a modular way.
|
||||
|
||||
Optionally, you may also find these tools helpful:
|
||||
|
||||
- `indent` for formatting code.
|
||||
- `valgrind` for debugging particularly nasty issues.
|
||||
|
||||
### Getting The Code
|
||||
|
||||
Telodendria is developed using Git. The easiest way to contribute
|
||||
changes is to fork the main repository, and then creating a pull
|
||||
request to ask us to pull your changes into our repo.
|
||||
|
||||
1. If you don't have an account on the
|
||||
[Gitea instance](https://git.telodendria.io), create one and sign in.
|
||||
1. Fork this repository.
|
||||
1. In your development environment, clone your fork:
|
||||
```shell
|
||||
git clone https://git.telodendria.io/[YOUR_USERNAME]/Telodendria.git
|
||||
cd Telodendria
|
||||
```
|
||||
|
||||
Please base your changes on the `master` branch. If you need help
|
||||
getting started with Git, that is beyond the scope of this
|
||||
document, but you can find many good tutorials on the web.
|
||||
|
||||
### Building & Running
|
||||
|
||||
Telodendria uses the `make` build system. Because it aims at maximum
|
||||
portability, it targets POSIX `make` and should thus run on any POSIX
|
||||
system that provides a `make`, be it GNU, BSD, or something different
|
||||
entirely. To facilitate this, Telodendria provides a `configure` script
|
||||
which generates the `Makefile`, because the `Makefile` would be far too
|
||||
verbose and tedious to maintain in a POSIX-compatible way otherwise.
|
||||
This is similar to how other C programs and libraries are built, although
|
||||
note that Telodendria's `configure` script is not nearly as advanced as
|
||||
an `autoconf` script, for example.
|
||||
|
||||
Please follow the build and installation directions for
|
||||
[Cytoplasm](/Telodendria/Cytoplasm) first before attempting to build
|
||||
Telodendria, because Telodendria depends on Cytoplasm and assumes it is
|
||||
installed in the standard location for your system. For the best results,
|
||||
it is recommended to take the time to enable TLS, unless you plan on
|
||||
running Telodendria behind a reverse proxy.
|
||||
|
||||
To build Telodendria, simply run `configure`, then `make`:
|
||||
|
||||
```
|
||||
$ ./configure
|
||||
$ make
|
||||
```
|
||||
|
||||
You may find some of the following options for `configure` helpful:
|
||||
|
||||
- `--prefix=<path>`: Set the install prefix to set by default in the `Makefile`. This defaults to `/usr/local`, which should be appropriate for most Unix-like systems.
|
||||
- `--(enable|disable)-ld-extra`: Control whether or not to enable additional linking flags that create a more optimized binary. For large compilers such as GCC and Clang, these flags should be enabled. However, if you are using a small or more obscure compiler, then these flags may not be supported, so you can disable them with this option.
|
||||
- `--(enable|disable)-debug`: Control whether or not to enable debug mode. This sets the optimization level to 0 and builds with debug symbols. Useful for running with a debugger.
|
||||
- `--static` and `--no-static`: Controls whether static binaries are built by default. On BSD systems, `--static` is perfectly acceptable, but on GNU systems, `--no-static` is often desirable to silence warnings about static binaries emitted by the GNU linker.
|
||||
|
||||
Telodendria can be customized with the following options:
|
||||
|
||||
- `--bin-name=<name>`: The output name of the server binary. This defaults to `telodendria`. Common alternatives are `matrix-telodendria` or `telodendria-server`.
|
||||
- `--version=<version>`: The version string to embed in the binary. This can be used to indicate build customizations or non-release versions of Telodendria.
|
||||
|
||||
The following recipes are available in the generated `Makefile`:
|
||||
|
||||
- `all`: This is the default target. It builds everything.
|
||||
- `telodendria`: Build the `telodendria` binary. If you specified an alternative `--bin-name`, then this target will be named after that.
|
||||
- `docs`: Generate the header documentation as `man` pages.
|
||||
- `tools`: Build the supplemental tools which may be useful for development.
|
||||
- `clean`: Remove the build and output directories. Telodendria builds are out-of-tree, which greatly simplifies this recipe compared to in-tree builds.
|
||||
|
||||
If you're developing Telodendria, these recipes may also be helpful:
|
||||
|
||||
- `format`: Format the source code using `indent`. This may require a BSD `indent` because last time I tried GNU `indent`, it didn't like the flags in `indent.pro`. Your mileage may vary.
|
||||
- `license`: Update the license headers in all source code files with the contents of the `LICENSE.txt`.
|
||||
|
||||
To install Telodendria to your system, the following recipes are available:
|
||||
|
||||
- `install`: This installs Telodendria under the prefix set with `./configure --prefix=<dir>` or with `make PREFIX=<dir>`. By default, the `make` `PREFIX` is set to whatever was set with `configure --prefix`.
|
||||
- `uninstall`: Uninstall Telodendria from the same prefix as specified above.
|
||||
|
||||
After a build, you can find the object files in `build/` and the output binary in `out/bin/`.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
> **Note:** Telodendria does not accept GitHub pull requests at this
|
||||
> time. Please submit your pull requests via Gitea.
|
||||
|
||||
Telodendria follows the standard pull request procedures. Once you have
|
||||
made your changes, committed them, and pushed to your fork, you should
|
||||
be able to open a pull request on the main repository. When you do, you
|
||||
will be prompted to write a description. Be sure to include the
|
||||
related issue that you are closing in your description.
|
||||
|
||||
### Code Style
|
||||
|
||||
In general, these are the conventions used by the code base. This
|
||||
guide may be slightly outdated or subject to change, but it should be
|
||||
a good start. The source code itself is always the absolute source of
|
||||
truth, so as long as you make your code look like the code surrounding
|
||||
it, you should be fine.
|
||||
|
||||
- All function, enumeration, structure, and header names are
|
||||
`CamelCase`. This is preferred to `snake_case` because it is more
|
||||
compact.
|
||||
- All variable names are `lowerCamelCase`. This is preferred to
|
||||
`snake_case` because it is more compact. One exception to this rule is
|
||||
if a variable name, such as a member of a struct, directly represents
|
||||
a JSON key in an object specified by the Matrix specification, which
|
||||
may be in `snake_case`.
|
||||
- Enumerations and structures are always `typedef`-ed to their same
|
||||
name. The `typedef` should occur in the public API header, and the
|
||||
actual declaration should live in the implementation file, unless
|
||||
the enumeration or structure is intended to be made fully public.
|
||||
- A feature of the code base lives in a single C source file that has a
|
||||
matching header. The header file should only export public symbols;
|
||||
everything else in the C source should be static.
|
||||
- Except where absolutely necessary, global variables are forbidden
|
||||
to prevent problems with threads and whatnot. Every variable a
|
||||
function needs should be passed to it either through a structure, or
|
||||
as a separate argument.
|
||||
- Anywhere that C allows curly braces to be optional, there still must
|
||||
be curly braces. This makes it easier to read the code by making it
|
||||
less ambiguous, and it makes it easier to add on to the code later.
|
||||
|
||||
As far as actually formatting the code goes, such as where to put
|
||||
brackets, and whether or not to use tabs or spaces, use `indent` to
|
||||
take care of that. The repository contains a `.indent.pro` that should
|
||||
automatically be loaded by `indent` to set the correct rules. If you
|
||||
don't have a working `indent`, then just indicate in your pull
|
||||
request that I should run my `indent` on the code.
|
||||
|
||||
### Documentation
|
||||
|
||||
This project places a strong emphasis on documentation. Well-documented
|
||||
code is fundamental to a successful project, so when you are writing
|
||||
code, please also make sure that it is documented appropriately.
|
||||
|
||||
- If you are adding a header, make sure you add the necessary comments
|
||||
detailing the header and the functions in it.
|
||||
- If you are adding a function, make sure you add the necessary
|
||||
comments to the appropriate header.
|
||||
|
||||
If your pull request does not also include proper documentation, it
|
||||
will likely be rejected.
|
||||
|
||||
### Be Recognized!
|
||||
|
||||
If your pull request gets approved, you should be recognized for your
|
||||
contributions to the project!
|
||||
|
||||
To have your work recognized, add your information to the `CONTRIBUTORS.txt`
|
||||
file in the root of the Telodendria repository if it isn't there already.
|
||||
You should do this as a part of your pull request so that when it is merged,
|
||||
your information will be automatically added to the repository.
|
||||
|
||||
The `CONTRIBUTORS.txt` file loosely follows the Linux kernel's
|
||||
[CREDITS](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/CREDITS)
|
||||
file format. It is designed to be human-readable, but also parsable by
|
||||
scripts.
|
||||
|
||||
The following fields are available:
|
||||
|
||||
```
|
||||
(N) Name
|
||||
(E) Email
|
||||
(M) Matrix ID
|
||||
(W) Website
|
||||
(D) Description of contribution
|
||||
(L) Physical location
|
||||
```
|
||||
|
||||
Here are the rules:
|
||||
|
||||
* All fields are optional. If you don't want to include a field, that's
|
||||
okay, simply omit it.
|
||||
* All fields identify you however you wish. The goal is to recognize you for
|
||||
your contribution, but if you wish to remain anonymous, you don't have to
|
||||
use your real information.
|
||||
* All fields can be specified multiple times. For example, if you have
|
||||
multiple email addresses, websites, or Matrix IDs and you want to include
|
||||
all of them, you absolutely may. Likewise, if you have made multiple
|
||||
contributions, you can add multiple description entries.
|
||||
* You can make up your own fields if you want. Just add their description
|
||||
above.
|
||||
* Leave exactly one blank like between entries in this file.
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Telodendria Documentation
|
||||
|
||||
Here you will find all of the documentation for Telodendria. If you
|
||||
find that some documentation is missing or incorrect, please open an
|
||||
issue, and, even better, a pull request to fix the issue.
|
||||
|
||||
You are also welcome to join the
|
||||
[`#telodendria-general:bancino.net`](https://matrix.to/#/#telodendria-general:bancino.net)
|
||||
matrix room, where you can discuss Telodnedria with others and ask
|
||||
questions.
|
||||
|
||||
## User Documentation
|
||||
|
||||
- [System Requirements](user/requirements.md)
|
||||
- [Install](user/install.md)
|
||||
- [Usage & Running](user/usage.md)
|
||||
- [Initial Set Up](user/setup.md)
|
||||
- [Configuration Options](user/config.md)
|
||||
- [Administrator API](user/admin/README.md)
|
||||
|
||||
## Developer Documentation
|
||||
|
||||
- [Repository Structure](dev/repo.md)
|
||||
- [Contributing Guidelines](CONTRIBUTING.md)
|
||||
- [Porting Guidelines](dev/ports.md)
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
- [Matrix Specification](https://spec.matrix.org) ([Mirror](https://telodendria.io/spec.matrix.org))
|
||||
- [Change Log](CHANGELOG.md)
|
||||
- [Project Road Map](ROADMAP.md)
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
# Telodendria Matrix Specification Roadmap
|
||||
|
||||
This document provides a high-level overview of Telodendria's roadmap as it pertains to implementing the Matrix specification. Essentially, the Matrix specification is divided up into manageable portions amongst Telodendria releases, so that each release up until the first stable release implements a small portion of it.
|
||||
|
||||
**Note:** The first stable release of Telodendria will implement Matrix v1.7, no newer version. The Matrix specification changes too frequently, so I had to just pick a version in order to make this project manageable. Once v1.7 is complete, then we can move on to later specs.
|
||||
|
||||
This document will be updated to include more implementation details as they come up. It contains the big picture for far-out releases, and more relevant implementation details for near releases.
|
||||
|
||||
## Milestone v0.4.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] **7:** Events
|
||||
- [x] Compute size of JSON object in `CanonicalJson`
|
||||
- [x] Rename `Sha2.h` to just `Sha.h`; add `Sha1()` function
|
||||
- [x] Make `Sha256()` just return raw bytes; add function to convert to hex string.
|
||||
- [ ] **8:** Rooms
|
||||
- [ ] **9:** User Data
|
||||
- [x] Profiles
|
||||
- [ ] Directory
|
||||
|
||||
## Milestone v0.5.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] Modules
|
||||
- [ ] Content Repository
|
||||
|
||||
## Milestone v0.6.0
|
||||
|
||||
- [ ] Client-Server API
|
||||
- [ ] Modules
|
||||
- [ ] Instant Messaging
|
||||
- [ ] Voice over IP
|
||||
- [ ] Receipts
|
||||
- [ ] Fully Read Markers
|
||||
- [ ] Send-To-Device Messaging
|
||||
- [ ] Security (Rate Limiting)
|
||||
|
||||
## Milestone v0.7.0
|
||||
|
||||
- [ ] Server-Server API
|
||||
|
||||
## Milestone v0.8.0
|
||||
|
||||
- [ ] Application Service API
|
||||
- [ ] YAML parser?
|
||||
|
||||
## Milestone v0.9.0
|
||||
|
||||
- [ ] Identity Service API
|
||||
|
||||
## Milestone v1.7.0 (Stable Release!)
|
||||
|
||||
- [ ] Push Gateway API
|
||||
- [ ] Room Versions
|
|
@ -1,94 +0,0 @@
|
|||
# Hosting Telodendria
|
||||
|
||||
These are just my own personal notes for hosting Telodendria's code infrastructure. This document is not intended to be used by normal Telodendria users or developers. It may be useful if you are *forking* Telodendria, but I sincerely hope you'll contribute to the upstream project instead. I'm writing this document solely for my own reference, but I am placing it into Telodendria's code repository in the name of transparency.
|
||||
|
||||
## Runners
|
||||
|
||||
The general sequence of steps required for setting up a CI runner is as follows:
|
||||
|
||||
1. Install the runner OS with all the defaults. I typically install my runners in virtual machines with 1 vcpu and 512mb RAM. Only Debian complained about this configuration, but since I didn't install a desktop environment, it worked out fine.
|
||||
2. Install the packages required to build and execute the runner. These are:
|
||||
- Git for checking out the source code.
|
||||
- NodeJS for running `actions/checkout`, I think. Not really sure, all I know is that the runner will fail all jobs without NodeJS.
|
||||
- Go for compiling the runner itself.
|
||||
|
||||
Run these commands to install the packages:
|
||||
- **OpenBSD:** `pkg_add git go node`
|
||||
- **FreeBSD:** `pkg install git go node`
|
||||
- **NetBSD:** `pkgin install git go nodejs openssl mozilla-rootcerts-openssl`
|
||||
(Note that the `go` executable is `go121` or whatever version was installed. and that NetBSD has no root certificates installed by default)
|
||||
- **Debian:** `apt install git golang nodejs`
|
||||
- **Alpine:** `apk add git go nodejs`
|
||||
|
||||
3. Install any development packages required to build Telodendria. For the BSDs, all development tools are built in so no additional packages are necessary. For the Linux distributions I've messed with, install these additional packages:
|
||||
- **Debian:** `apt install make gcc libssl-dev`
|
||||
- **Alpine:** `apk add make gcc musl-dev openssl-dev`
|
||||
4. Clone `https://git.telodendria.io/Telodendria/act_runner.git`.
|
||||
5. Run `go build` in the `act_runner` directory. On NetBSD, you may have to `umount /tmp` first because `/tmp` is by default very small. Otherwise, make `/tmp` larger during installation. 2GB should be plenty.
|
||||
6. Run `./act_runner register` to register the runner. When prompted for the tags, follow following convention:
|
||||
- **Linux Distros:** `linux`, `<distro>-v<version>`, `<arch>`
|
||||
- **BSD Derivatives:** `bsd`, `<osname>-v<version>`, `<arch>`
|
||||
- **Windows:** `windows`, `windows-v<version>`, `<arch>`
|
||||
- **MacOS:** `macos`, `macos-v<version>`, `<arch>`
|
||||
- **Others:** `other`, `<osname>-v<version>`, `<arch>`
|
||||
|
||||
Where `<arch>` is one of `x86` or `x64` for now. ARM runners will be a future project.
|
||||
7. Run `./act_runner daemon`.
|
||||
|
||||
### Startup Scripts
|
||||
|
||||
We will obviously want `act_runner` to execute on bootup. Here are the start scripts I used:
|
||||
|
||||
#### Alpine
|
||||
|
||||
In `/etc/init.d/act_runner`:
|
||||
|
||||
```shell
|
||||
#!/sbin/openrc-run
|
||||
|
||||
directory="/home/runner/act_runner"
|
||||
command="/home/runner/act_runner/act_runner"
|
||||
command_args="daemon"
|
||||
command_user="runner:runner"
|
||||
command_background="true"
|
||||
pidfile="/run/act_runner.pid"
|
||||
```
|
||||
|
||||
Don't forget to `chmod +x /etc/init.d/act_runner`.
|
||||
|
||||
Then just `rc-update add act_runner` and `rc-service act_runner start`.
|
||||
|
||||
#### Debian
|
||||
|
||||
In `/etc/systemd/system/act_runner.service`:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Gitea Actions runner
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/runner/act_runner/act_runner daemon
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
WorkingDirectory=/home/runner/act_runner
|
||||
TimeoutSec=0
|
||||
RestartSec=10
|
||||
Restart=always
|
||||
User=runner
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Then just `systemctl enable act_runner` and `systemctl start act_runner`.
|
||||
|
||||
#### Other
|
||||
|
||||
Eventually I got sick of writing init scripts for all the various operating systems.
|
||||
|
||||
Just put this in `runner`'s `crontab`:
|
||||
|
||||
```
|
||||
@reboot cd /home/runner/act_runner && ./act_runner daemon
|
||||
```
|
||||
|
||||
That seems to do the job good enough, and it's cross platform.
|
|
@ -1,92 +0,0 @@
|
|||
## Ports
|
||||
|
||||
Telodendria is distributed primarily as source code, and the project
|
||||
itself does not offer a convenient install process such as in the form
|
||||
of a shell script. This is intentional; the Telodendria project is
|
||||
primarily concerned with developing Telodendria itself, not packaging
|
||||
it for the hundreds of different operating systems and linux
|
||||
distributions that exist. It is my firm belief that distributing an
|
||||
open source project is not the job of the open source developer; that
|
||||
is the reason software distributions exist: to collect and
|
||||
*distribute* software.
|
||||
|
||||
It would be impossible to single-handedly package Telodendria for
|
||||
every platform, because each platform has very different expectations
|
||||
and conventions for software. Even different Linux distributions have
|
||||
different conventions for where manual pages, binaries, and
|
||||
configuration files go.
|
||||
|
||||
That being said, this document aims to assist those who want to
|
||||
package Telodendria for their operating system or software
|
||||
distribution.
|
||||
|
||||
---
|
||||
|
||||
Before attempting to package Telodendria, make sure that you can build
|
||||
it and that it builds cleanly on your target platform. See
|
||||
[Install → From Source](../user/install.md#from-source)
|
||||
for general build instructions.
|
||||
|
||||
To package Telodendria, you should collect the following files, and
|
||||
figure out where they should be installed on your system:
|
||||
|
||||
- The `telodendria` server binary itself.
|
||||
- An init script. People that wish to install Telodendria on their
|
||||
system using your package are going to expect it to be integrated
|
||||
enough that Telodendria can easily be started at boot and otherwise
|
||||
managed by the system's daemon tools, be it `systemd` or another
|
||||
init system. Consult your system's documentation for writing an init
|
||||
script. **Note:** Telodendria *does not* fork itself to the background;
|
||||
the init script should do that.
|
||||
- You may also wish to ship the `docs/` directory
|
||||
so that the user can read the documentation offline, and ensure that
|
||||
they are reading the correct documentation for the installed version.
|
||||
|
||||
You may wish to optionally create a dedicated user under which
|
||||
Telodendria should run. Telodendria can be directly started as that
|
||||
user, or start as root and be configured to automatically drop to that
|
||||
user. Additionally, it might be helpful to provide a default
|
||||
configuration, which can be placed in the samples directory on your
|
||||
platform, or in a default location that Telodendria will load from.
|
||||
A good default directory that you may wish to provide for configuration,
|
||||
data, and logs could perhaps be `/var/telodendria` or `/var/db/telodendria` on Unix-like systems.
|
||||
|
||||
Once you have collected the necessary files and directories that need
|
||||
to be installed, make sure your package performs the following tasks
|
||||
on install:
|
||||
|
||||
- If necessary and depending on the configuration used, create a new
|
||||
system user for the Telodnedria daemon to run as.
|
||||
- If conventional for your system, enable the Telodendria init script
|
||||
so that Telodendria is started on system boot.
|
||||
- Instruct the user to carefully read the [Setup](../user/setup.md)
|
||||
(`docs/user/setup.md`) instructions and the
|
||||
[Configuration](../user/config.md) (`docs/user/config.md`) instructions
|
||||
before starting Telodendria.
|
||||
|
||||
The goal of a package should be to get everything as ready-to-run as
|
||||
possible. The user should be able to start Telodendria right away and
|
||||
begin configuring it.
|
||||
|
||||
Remember to publicly document the setup of Telodendria on your platform
|
||||
if there are additional steps required that are not mentioned in the
|
||||
official Telodendria documentation. This ensures that users can get
|
||||
up and running quickly and easily. If you're packaging Telodendria
|
||||
for a container system such as Docker, you can omit the things that
|
||||
containers typically do not have, such as the init scripts and
|
||||
documentation.
|
||||
|
||||
Also remember that your port should feel like it belongs on your target
|
||||
system. Follow all of your system's conventions when placing files
|
||||
on the filesystem, so your users know what to expect. The goal is not
|
||||
necessarily to have a unified experience across all operating systems,
|
||||
rather, you should cater to the opinions of your operating system.
|
||||
Telodendria is architected in such a way that it does not impose the
|
||||
developer's opinions of where things should go, and since the
|
||||
configuration lives in the database, it is fairly self contained.
|
||||
|
||||
If there are any changes necessary to the upstream code or build
|
||||
system that would make your job in porting Telodendria easier, do not
|
||||
hesitate to get involved by opening an issue and/or submitting a pull
|
||||
request.
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
# Rationale
|
||||
|
||||
This document seeks to answer the question of "why Telodendria?" from
|
||||
a technical perspective by comparing it to existing Matrix homservers.
|
||||
Telodendria is written entirely from scratch in ANSI C. It is designed
|
||||
to be lightweight, simple, and functional. Telodendria differentiates
|
||||
itself from other homeserver implementations because it:
|
||||
|
||||
- Is written C, a stable, low-level programming language with a long
|
||||
history, low build and runtime overhead, and wide compatibility.
|
||||
- Is written with minimalism as a primary design goal. Whenever possible
|
||||
and practical, no third-party libraries are pulled into the code.
|
||||
Everything Telodnedria needs is custom written. As a result, Telodendria
|
||||
depends only on a standard C compiler and a POSIX C library to be
|
||||
built, both of which should come with any good Unix-style operating
|
||||
system already, which means you shouldn't have to install anything
|
||||
additional to use Telodendria.
|
||||
- Uses a flat-file directory structure to store data instead of a
|
||||
real database. This has a number of advantages:
|
||||
- It make setup and mainenance much easier.
|
||||
- It allows Telodendria to run on systems with fewer resources.
|
||||
- Is packaged as a single small, statically-linked and highly-optimized
|
||||
binary that can be run just about anywhere. It is designed to be
|
||||
extremely easy to set up and consume as few resources as possible.
|
||||
- Is permissively licensed. Telodendria is licensed under a modified
|
||||
MIT license, which imposes very few restrictions on what you can do
|
||||
with it.
|
||||
|
||||
## What about [Conduit](https://conduit.rs)?
|
||||
|
||||
At this point, you may be wondering why one would prefer Telodendria
|
||||
over Conduit, a Matrix homeserver that could also say pretty much
|
||||
everything this document has said so far. After all, Conduit is older
|
||||
and thus better established, and written in Rust, a Memory Safe™
|
||||
programming language.
|
||||
|
||||
In this section, we will discuss some additional advantages of
|
||||
Telodendria that Conduit lacks.
|
||||
|
||||
### Small Dependency Chain
|
||||
|
||||
Conduit's dependency chain is quite large. What this means is that
|
||||
Conduit depends on a lot of code that it does not control, making it
|
||||
vulnerable to supply chain attacks. A problem with Rust Crates
|
||||
is that they are developer-published, so they don't go through any sort
|
||||
of auditing process like a Debian package would, for example.
|
||||
If any one of the dependencies is
|
||||
hijacked or otherwise compromised, then Conduit itself is compromised
|
||||
and it is likely that this would go unnoticed for quite a while. While
|
||||
one could argue that this is extremely unlikely to happen, sometimes you
|
||||
just don't want to take that risk, especially not if you're deploying a
|
||||
Matrix homeserver, likely for the purpose of secure, private chat.
|
||||
|
||||
Telodendria doesn't pull in any packages from developer repositories, so
|
||||
the risk of supply chain attacks is much lower. It
|
||||
only uses its own code and code provided by the operating system it is running
|
||||
on, which has been vetted by a large number of developers and can be trusted
|
||||
due to the sheer scope of an operating system. A supply chain attack against
|
||||
Telodendria would be a supply chain attack against the entire operating system;
|
||||
at that point, end users have much bigger problems.
|
||||
|
||||
Minimal dependencies doesn't only mitigate supply chain attacks. It also makes
|
||||
maintenance much easier. Telodendria can spend more time writing code than
|
||||
Conduit because Conduit developers have to ensure dependencies stay up to date and
|
||||
when they inevitably break things, Conduit must pause development to fix those.
|
||||
Telodendria doesn't suffer from this problem: because most of the code is developed
|
||||
along side of Telodendria, it can remain as stable or become as volatile as the
|
||||
developers choose. Additionally, because Telodendria is so low-level, the code on
|
||||
which it depends is extremely unlikely to be changed in any significant way,
|
||||
since so many other programs depend on that code.
|
||||
|
||||
### Standardized
|
||||
|
||||
Conduit is written in Rust, which has no formal standard. This makes it less than
|
||||
ideal for long-lived software projects, because it changes frequently and often
|
||||
breaks existing code. Telodendria is written in C, a stable, mature, and standardized
|
||||
language that will always compile the same code the same way, making it more
|
||||
portable and sustainable for the future because we don't ever have to worry about
|
||||
upgrading our toolchain—using standard tools built into most operating systems
|
||||
will suffice.
|
||||
|
||||
Because the language in which Telodendria is written never changes, Telodendria can
|
||||
continually optimize and improve the code, instead of having to fix breaking changes.
|
||||
This ensures that Telodendria's code will last. Rust code becomes obsolete with in a
|
||||
few years at best—programs written in Rust last year probably won't compile or run
|
||||
properly on the latest Rust toolchain. Telodendria, on the other hand, is written in C89,
|
||||
which compiled and ran the same way in 1989 as it does today and will continue to for the
|
||||
foreseeable future.
|
||||
|
||||
### Fast Compile Times
|
||||
|
||||
Rust is well-known for taking an extremely long time to compile moderately-sized
|
||||
programs. Since a Matrix homeserver is such a large project, the compile times would
|
||||
be prohibitively large for rapid development. By writing Telodendria in C, we can take
|
||||
advantage of decades worth of compiler optimizations and speed improvements, resulting
|
||||
in extremely fast builds.
|
||||
|
||||
### Portable
|
||||
|
||||
One does not typically think of C as more portable than something like Rust, but
|
||||
Telodendria is written in such a way that it is. Rust relies on LLVM, which doesn't
|
||||
support some strange or exotic architectures in the same way that a specialized C
|
||||
compiler for those architectures will. This allows users to run Telodendria on the
|
||||
hardware of their choice, even if that hardware is so strange that the modern world
|
||||
has totally left it behind.
|
||||
|
||||
Telodendria doesn't just aim at being lightweight and portable, it aims to empower
|
||||
people to use common hardware that they already have, even if it is typically thought
|
||||
of as underpowered.
|
|
@ -1,17 +0,0 @@
|
|||
# Repository Structure
|
||||
|
||||
This document describes the filesystem layout of the Telodendria source
|
||||
code repository.
|
||||
|
||||
- `Telodendria/`
|
||||
- `Cytoplasm/`: The source code for Cytoplasm, Telodendria's
|
||||
general-purpose support library that provides core functionality.
|
||||
- `contrib/`: Supplemental files, such as example configurations.
|
||||
- `docs/`: All user and developer documentation as Markdown.
|
||||
- `site/`: The official website source code as HTML.
|
||||
- `src/': The C source code and headers for Telodendria.
|
||||
- `Routes/`: Where the Matrix API endpoints are implemented.
|
||||
- `Static/`: Endpoints that just generate static HTML pages.
|
||||
- `include/`: Header files.
|
||||
- `tools/`: Development environment and tools.
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# Administrator API
|
||||
|
||||
Telodendria provides an administrator API as an extension to the
|
||||
Matrix specification that allows for administrator control over the
|
||||
homeserver. This includes profiling and examining the state of
|
||||
running instances, as well as managing users and media.
|
||||
|
||||
Like Synapse, Telodendria supports designating specific local users as
|
||||
administrators. However, unlike Synapse, Telodendria uses a more
|
||||
fine-grained privilege model that allows a server administrator to
|
||||
delegate specific administration tasks to other users while not
|
||||
compromising and granting them full administrative access to the server.
|
||||
|
||||
To authenticate with the administrator API, simply use your login
|
||||
access token just like you would authenticate any other Matrix client
|
||||
request.
|
||||
|
||||
- [Privileges](privileges.md)
|
||||
- [Configuration](config.md)
|
||||
- [Server Statistics](stats.md)
|
||||
- [Process Control](proc.md)
|
||||
- [Registration Tokens](tokens.md)
|
||||
|
||||
## API Conventions
|
||||
|
||||
Unless otherwise indicated, HTTP response codes that are not `200 Ok`
|
||||
will be accompanied by a standard Matrix API error. Consult the Matrix
|
||||
specification for the format of these errors. The following error
|
||||
conditions are assumed to be possible for all API endpoints listed
|
||||
in the Administrator API documentation:
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 400 | The user is not authenticated, did not provide a valid JSON object, or provided a JSON object with invalid or missing parameters.|
|
||||
| 403 | The user does not have the privileges necessary to carry out the requested action.|
|
||||
| 500 | A fatal server error occurred. Check the logs for more information.|
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
# Administrator API: Configuration
|
||||
|
||||
As mentioned in [Setup](../setup.md), Telodendria's configuration is
|
||||
intended to be managed via the configuration API. Consult the
|
||||
[Configuration](../config.md) document for a complete list of supported
|
||||
configuration options. This document simply describes the API used to
|
||||
update the configuration described in that document.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/config`
|
||||
|
||||
Retrieve the current configuration.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The current configuration was successfully retrieved.|
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/config`
|
||||
|
||||
Installs a new configuration. This endpoint validates the request body,
|
||||
ensuring it is a proper configuration, then it replaces the existing
|
||||
configuration with the new one.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The new configuration was successfully installed.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||
|
||||
### **PUT** `/_telodendria/admin/v1/config`
|
||||
|
||||
Update the currently installed configuration instead of completely replacing it. This endpoint
|
||||
validates the request body, merges it on top of the current configuration, validates the resulting
|
||||
configuration, then updates it in the database. This is useful when only one or two properties
|
||||
in the configuration needs to be changed.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The new configuration was successfully installed.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
|
@ -1,141 +0,0 @@
|
|||
# Administrator API: Privileges
|
||||
|
||||
This document describes the privilege model and the API endpoints that
|
||||
allow administrators to modify privileges for users.
|
||||
|
||||
## List Of Privileges
|
||||
|
||||
A local user can have any of the following privileges. Unless otherwise
|
||||
indicated, these privileges only grant access to certain parts of the
|
||||
administrator API; the regular Matrix API is unaffected.
|
||||
|
||||
- **DEACTIVATE:** Allows a user to deactivate any other local users.
|
||||
- **ISSUE_TOKENS:** Allows a user to create, modify, and delete
|
||||
registration tokens.
|
||||
- **CONFIG:** Allows a user to modify the Telodendria server daemon's
|
||||
configuration.
|
||||
- **GRANT_PRIVILEGES:** Allows a user to modify his or her own
|
||||
privileges or the privileges of other local users.
|
||||
- **ALIAS:** Allows a user to modify and see room aliases created by
|
||||
other users. By default, users can only manage their own room aliases,
|
||||
but an administrator may wish to take over an alias or remove an
|
||||
offensive alias.
|
||||
- **PROC_CONTROL:** Allows a user to get statistics on the running
|
||||
process, as well as shutdown and resetart the Telodendria daemon
|
||||
itself. Typically this will pair well with **CONFIG**, because there
|
||||
are certain configuration options that require the process to be
|
||||
restarted to take full effect.
|
||||
|
||||
There is also a special "pseudo-privilege":
|
||||
|
||||
- **ALL:** Grants a user all of the aforementioned privileges, as well
|
||||
as privileges that do not yet exist. That is, if an update to
|
||||
Telodendria adds more privileges, users with this privilege will
|
||||
automatically gain those new privileges in addition to having all the
|
||||
existing privileges. This privilege should only be used with
|
||||
fully-trusted users. It is typical for a server administrator to not
|
||||
fully trust anyone else, and be the only one that holds an account with
|
||||
this privilege level.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The following API endpoints are implemented for managing privileges.
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Retrieve the permissions for a user. If the localpart is omitted, then
|
||||
retrieve the privileges for the user that owns the access token being
|
||||
used. Note that the owner of the access token must have the
|
||||
**GRANT_PRIVILEGES** privilege to use this endpoint.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully retrieved.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by replacing the privileges array
|
||||
with the one specified in the request. Like the **GET** version of this
|
||||
endpoint, the localpart can be omitted to operate on the user that
|
||||
owns the access token.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully replaced.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by adding the privileges
|
||||
specified in the request to the users existing privileges.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully added.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by removing the privileges
|
||||
specified in the request from the user's existing privileges.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The privileges were successfully removed.|
|
||||
|
||||
#### Request Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# Administrator API: Process Control
|
||||
|
||||
This document describes the administrator APIs that allow a server
|
||||
administrator to manage the Telodendria process itself.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/restart`
|
||||
|
||||
Restart the Telodendria daemon cleanly. This endpoint will respond
|
||||
immediately after signaling to the daemon that it should be restarted
|
||||
as soon as possible. Note that the restart wmay not happen
|
||||
instantaneously, as Telodendria will finish processing all current
|
||||
requests before restarting. Also note that this is not a true restart;
|
||||
the process does not exit and restart, rather, Telodendria simply tears
|
||||
down all its state and then jumps back to the beginning of its code and
|
||||
starts over.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The restart request was successfully sent.|
|
||||
|
||||
On success, this endpoint simply returns an empty JSON object.
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/shutdown`
|
||||
|
||||
Shut down the Telodendria process cleanly. This endpoint will respond
|
||||
immediately after signalling to the daemon that it should be shut
|
||||
down as soon as possible. Note that the shutdown may not happen
|
||||
instantaneously, as Telodendria will finish processing all current
|
||||
requests before shutting down. Also note that once shut down, Telodendria
|
||||
may be automatically restarted by the system's service manager.
|
||||
Otherwise, it will have to be manually restarted. This is a true
|
||||
shutdown; the Telodendria process exits as soon as possible.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The shutdown request was successfully sent.|
|
||||
|
||||
On success, this endpoint simply returns an empty JSON object.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Administrator API: Server Statistics
|
||||
|
||||
The administrator API allows users with the proper privileges to get
|
||||
information about how the server process is performing.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/stats`
|
||||
|
||||
Retrieve basic statistics about the currently running Telodendria
|
||||
process.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The server statistics were successfully retrieved.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `memory_allocated` | `Integer` | The total amount of memory allocated, measured in bytes.|
|
||||
| `version` | `String` | The current version of Telodendria.|
|
|
@ -1,106 +0,0 @@
|
|||
# Administrator API: Registration Tokens
|
||||
|
||||
Telodendria implements registration tokens as specified by the Matrix
|
||||
specification. These tokens can be used for registration using the
|
||||
`m.login.registration_token` login type. This API provides a Telodendria
|
||||
administrator with a mechanism for generating and managing these tokens,
|
||||
which allows controlled registration on the homeserver.
|
||||
|
||||
It is generally safer than completely open registration to use
|
||||
registration tokens that either expire after a short period of time, or
|
||||
have a limited number of uses.
|
||||
|
||||
## Registration Token
|
||||
|
||||
A registration token is represented by the following `RegToken` JSON
|
||||
object:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The token identifier; what is used when registering. |
|
||||
| `created_by` | `String` | The localpart of the user that created this token. |
|
||||
| `created_on` | `Integer` | A timestamp of when the token was created. |
|
||||
| `expires_on` | `Integer` | An expiration stamp, or 0 if the token never expires. |
|
||||
| `used` | `Integer` | The number of times the token has been used. |
|
||||
| `uses` | `Integer` | The total number of allowed uses, or -1 for unlimited. |
|
||||
| `grants` | `[String]` | An array of privileges to grant users that register with this token as described in [Privileges](privileges.md). |
|
||||
|
||||
All endpoints in this API will operate on some variation of this
|
||||
structure. The remaining number of uses can be computed by performing
|
||||
the subtraction: `uses - used`. `used` should never be greater than
|
||||
`uses` or less than `0`.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "q34jgapo8uq34hg",
|
||||
"created_by": "admin",
|
||||
"created_on": 1699467640000,
|
||||
"expires_on": 0,
|
||||
"used": 3,
|
||||
"uses": 5
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/tokens`
|
||||
|
||||
Get a list of all registration tokens and information about them.
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `tokens` | `[RegToken]` | An array of registration tokens. |
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/tokens/[name]`
|
||||
|
||||
Get information about the specified registration token.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
This endpoint returns a `RegToken` object that represents the server's
|
||||
record of the registration token.
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/tokens`
|
||||
|
||||
Create a new registration token.
|
||||
|
||||
#### Request Format
|
||||
|
||||
This endpoint accepts a `RegToken` object, as described above. If no
|
||||
`name` is provided, one will be randomly generated. Note that the fields
|
||||
`created_by`, `created_on`, and `used` are ignored and set by the server
|
||||
when this request is made. All other fields may be set by the request
|
||||
body.
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
If the creation of the registration token was successful, a `RegToken`
|
||||
that represents the server's record of it is returned.
|
||||
|
||||
### **DELETE** `/_telodendria/admin/v1/tokens/[name]`
|
||||
|
||||
Delete the specified registration token. It will no longer be usable for
|
||||
the registration of users. Any users that have completed the
|
||||
`m.login.registration_token` step but have not yet created their account
|
||||
should still be able to do so until their user-interactive auth session
|
||||
expires.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
On success, this endpoint returns an empty JSON object.
|
|
@ -1,248 +0,0 @@
|
|||
# Configuration
|
||||
|
||||
Telodendria is designed to be configurable. It is configured using
|
||||
JSON, which is intended to be submitted to the [Administrator
|
||||
API](admin/README.md). This document details Telodendria's configuration
|
||||
JSON format, which is used in both the administrator API and on-disk
|
||||
in the database. The configuration file on the disk in the databsae
|
||||
is `config.json`, though that file should not be edited by hand.
|
||||
Use the API described in
|
||||
[Administrator API → Configuration](admin/config.md).
|
||||
|
||||
## JSON Format
|
||||
|
||||
Telodendria's configuration is just a JSON object in the standard
|
||||
key-value form:
|
||||
|
||||
```json
|
||||
{
|
||||
"serverName": "telodendria.io",
|
||||
"listen": [
|
||||
{
|
||||
"port": 8008
|
||||
}
|
||||
]
|
||||
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
Some keys, called *directives* in this document, have values that are
|
||||
objects themselves.
|
||||
|
||||
## Directives
|
||||
|
||||
Here are the top-level directives:
|
||||
|
||||
- **listen:** `Array`
|
||||
|
||||
An array of listener description objects. Telodendria supports
|
||||
listening on multiple ports, and each port is configured
|
||||
independently of the others. A listener object looks like this:
|
||||
|
||||
- **port:** `integer`
|
||||
|
||||
The port to listen on. Telodendria will bind to all interfaces,
|
||||
so it is recommended to configure your firewall to only allow
|
||||
access on the desired interfaces. Note that Telodendria offers all
|
||||
APIs over each port, including the administrator APIs; there is no
|
||||
way to control which APIs are made available over which ports. If
|
||||
this is a concern, a reverse-proxy such as `relayd` can be placed
|
||||
in front of Telodendria to block access to undesired APIs.
|
||||
|
||||
- **tls:** `Object`
|
||||
|
||||
Telodendria can be compiled with TLS support. If it is, then a
|
||||
particular listener can be set to use TLS for connections. If
|
||||
**tls** is not `null` or `false`, then it can be an object with
|
||||
the following directives:
|
||||
|
||||
- **cert:** `String`
|
||||
|
||||
The full path—or path relative to the data
|
||||
directory—of the certificate file to load. The certificate
|
||||
file should be in the format expected by the platform's TLS
|
||||
library.
|
||||
|
||||
- **key:** `String`
|
||||
|
||||
Same as **cert**, but this should be the private key that matches
|
||||
the certificate being used.
|
||||
|
||||
- **threads:** `Integer`
|
||||
|
||||
How many worker threads to spin up to handle requests for this
|
||||
listener. This should generally be less than the total CPU core
|
||||
count, to prevent overloading the system. The most efficient number
|
||||
of threads ultimately depends on the configuration of the machine
|
||||
running Telodendria, so you may just have to play around with
|
||||
different values here to see which gives the best performance.
|
||||
|
||||
Note that this can be set as low as 0; in that case, the listener
|
||||
will never respond to requests. Each listener needs to have at
|
||||
least one thread to be useful. Also note that Telodendria may spin
|
||||
up additional threads for background work, so the actual total
|
||||
thread count at any given time may exceed the sum of threads
|
||||
specified in the configuration.
|
||||
|
||||
This directive is optional. The default value is `4` in the upstream
|
||||
code, but your software distribution may have patched this to be
|
||||
different.
|
||||
|
||||
- **maxConnections:** `Integer`
|
||||
|
||||
The maximum number of simultanious connections to allow to the
|
||||
daemon. This option prevents the daemon from allocating large
|
||||
amounts of memory in the event that it undergoes a denial of
|
||||
service attack. It is optional, defaults to `32`, and typically
|
||||
does not need to be adjusted.
|
||||
|
||||
- **serverName:** `String`
|
||||
|
||||
Configure the domain name of your homeserver. Note that Matrix
|
||||
servers cannot be migrated to other domains, so once this is set,
|
||||
it should never change unless you want unexpected things to happen
|
||||
or you want to start over. **serverName** should be a DNS name that
|
||||
can be publicly resolved. This directive is required.
|
||||
|
||||
- **pid:** `String`
|
||||
Configure the file Telodendria writes its PID to.
|
||||
|
||||
- **baseUrl:** `String`
|
||||
|
||||
Set the server's base URL. **baseUrl** should be a valid URL,
|
||||
complete with the protocol. It does not need to be the same as the
|
||||
server name; in fact, it is common for a subdomain of the server name
|
||||
to be the base URL for the Matrix homeserver.
|
||||
|
||||
This URL is the URL at which Matrix clients will connect to the
|
||||
server, and is thus served as a part of the `.well-known`
|
||||
manifest.
|
||||
|
||||
This directive is optional. If unspecified, it is automatically
|
||||
deduced from the server name.
|
||||
|
||||
- **identityServer:** `String`
|
||||
|
||||
The identity server that clients should use to perform identity
|
||||
lookups. **identityServer** folows the same rules as **baseUrl**.
|
||||
It also is optional, and is set to be the same as the **baseUrl**
|
||||
if left unspecified.
|
||||
|
||||
- **runAs:** `Object`
|
||||
|
||||
The effective Unix user and group to drop to after binding to the
|
||||
socket and completing any setup that may potentially require
|
||||
elevated privileges. This directive only takes effect if
|
||||
Telodendria is started as the root user, and is used as a security
|
||||
mechanism. If this option is set and Telodendria is started as a
|
||||
non-privileged user, then a warning is printed to the log if that
|
||||
user and group do not match what's specified here. This directive
|
||||
is optional, but should be used as a sanity check even if not
|
||||
running as `root`, just to make sure you have your permissions
|
||||
working properly.
|
||||
|
||||
This directive takes an object with the following directives:
|
||||
|
||||
- **uid:** `String`
|
||||
|
||||
The Unix username to switch to. If **runAs** is specified, this
|
||||
directive is required.
|
||||
|
||||
- **gid:** `String`
|
||||
|
||||
The Unix group to switch to. This directive is optional; if left
|
||||
unspecified, then the value of **uid** is copied.
|
||||
|
||||
- **federation:** `Boolean`
|
||||
|
||||
Whether or not to enable federation with other Matrix homeservers.
|
||||
Matrix by its very nature is a federated protocol, but if you just
|
||||
want to rn your own internal chat server with no contact with the
|
||||
outside, then you can use this option to disable federation. It is
|
||||
highly recommended to set this to `true`, however, if you wish to
|
||||
be able to communicate with users on other Matrix servers. This
|
||||
directive is required.
|
||||
|
||||
- **registration:** `Boolean`
|
||||
|
||||
Whether or not to enable new user registration or not. For security
|
||||
and anti-spam reasons, you can set this to `false`. If you do, you
|
||||
can still allow only certain users to be registered using
|
||||
registration tokens, which can be managed via the administrator API.
|
||||
This directive is required.
|
||||
|
||||
In an ideal world, everyone would run their own Matrix homeserver,
|
||||
so no public registration would ever be required. Unfortunately,
|
||||
not everyone has the means to run their own homeserver, especially
|
||||
because of the fact that IPv4 addresses are becoming increasingly
|
||||
hard to come by. If you would like to provide a service to those
|
||||
that are unable to run their homeserver, then set this to `true`,
|
||||
thereby allowing anyone to create an account.
|
||||
|
||||
Telodendria *should* be capable of handling a large amount of users
|
||||
without difficulty, but it is targetted at smaller deployments.
|
||||
|
||||
- **log:** `Object`
|
||||
|
||||
The logging configuration. Telodendria uses its own logging
|
||||
facility, which can output logs to standard output, a file, or the
|
||||
syslog. This directive is required, and it takes an object with the
|
||||
following directives:
|
||||
|
||||
- **output:** `Enum`
|
||||
|
||||
The log output destination. This can either be `stdout`, `file`,
|
||||
or `syslog`. If set to `file`, Telodendria will log to
|
||||
`telodendria.log` inside the data directory.
|
||||
|
||||
- **level:** `Enum`
|
||||
|
||||
The level of messages to log. Each level shows all the levels above
|
||||
it. The levels are as follows:
|
||||
|
||||
- `error`
|
||||
- `warning`
|
||||
- `notice`
|
||||
- `message`
|
||||
- `debug`
|
||||
|
||||
For example, setting the level to `error` will show only errors,
|
||||
while setting the level to `warning` will show both warnings
|
||||
*and* errors. The `debug` level shows all messages.
|
||||
|
||||
- **timestampFormat:** `Enum`
|
||||
|
||||
If you want to customize the timestamp format shown in the log,
|
||||
or disable it altogether, you can do so via this option. Acceptable
|
||||
values are `none`, `default`, or a formatter string as described
|
||||
by your system's `strftime()` documentation. This option only
|
||||
applies if **log** is `stdout` or `file`.
|
||||
|
||||
- **color:** `Boolean`
|
||||
|
||||
Whether or not to enable colored output on TTYs. Note that ANSI
|
||||
color sequences will not be written to a log file, only a real
|
||||
terminal, so this option only applies if the log is being written
|
||||
to a standard output which is connected to a terminal.
|
||||
|
||||
- **maxCache:** `Integer`
|
||||
|
||||
The maximum size of the cache. Telodendria relies heavily on caching
|
||||
for performance reasons. The cache grows as data is loaded from the
|
||||
data directory. All cache is stored in memory. This option limits the
|
||||
size of the memory cache. If you have a system with a lot of memory
|
||||
to spare, you'll get better performance if this option is set higher.
|
||||
Otherwise, this value should be lowered on systems that have a
|
||||
minimal amount of memory available.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
A number of example configuration files are shipped with Telodendria's
|
||||
source code. They can be found in the `contrib/` directory if you are
|
||||
viewing the source code directly. Otherwise, if you installed
|
||||
Telodendria from a package, it is possible that the example
|
||||
configurations were placed in the default locations for such files on
|
||||
your operating system.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# Installation
|
||||
|
||||
There are multiple methods of installing Telodendria. Choose the one
|
||||
best suited to your use case.
|
||||
|
||||
## Package Manager Or System Ports
|
||||
|
||||
This is the recommended way to install Telodendria. If your operating
|
||||
system has an official package or port of Telodendria, you should
|
||||
prefer to use that instead of the other methods documented here,
|
||||
because your operating system or software distribution will have
|
||||
already figured out how to best integrate Telodendria with your system.
|
||||
|
||||
Consult your operating system or software distribution's system
|
||||
manual for instructions on how to install packages. Also consult the
|
||||
official repository of your distribution to see if a package is
|
||||
available. If a package exists but it is too out of date for your
|
||||
tastes, please contact the package's maintainer to notify them, or
|
||||
offer to update the package yourself using the
|
||||
[porting instructions](../dev/ports.md).
|
||||
|
||||
If you are maintaining a port or package for an operating system or
|
||||
software distribution, open a pull request to include your
|
||||
platform-specific instructions as a subheader of this section.
|
||||
Eventually, this section should contain basic instructions for the
|
||||
operating systems that have packages or ports.
|
||||
See [Ports](../dev/ports.md) for the project's distribution
|
||||
philosophy.
|
||||
|
||||
## Container
|
||||
|
||||
At this time, Telodendria does not have any officially recommended
|
||||
procedure for running in a container such as Docker or Vagrant. You
|
||||
may find helpful files in the [`contrib/`](../../contrib) directory,
|
||||
however.
|
||||
|
||||
If you are publishing container images, please open a pull request to
|
||||
add your source files to `contrib/`, as well as to add documentation
|
||||
under this section explaining how to get set started.
|
||||
|
||||
## Release Binary
|
||||
|
||||
At this time, Telodendria does not publish any official binaries that
|
||||
can be downloaded. The tentative plan is to eventually provide binaries
|
||||
with each release for a number of supported platforms. When that
|
||||
happens, instructions will be provided here for dealing with the
|
||||
binaries.
|
||||
|
||||
## From Source
|
||||
|
||||
If you would like to build Telodendria from source, you can download
|
||||
the latest release code from the
|
||||
[Releases](/Telodendria/telodendria/releases) page. After extracting
|
||||
the tarball, read
|
||||
[Contributing → Developing → Building & Running](../CONTRIBUTING.md#building-amp-running)
|
||||
for details on how to build Telodendria.
|
|
@ -1,60 +0,0 @@
|
|||
# Initial Set Up
|
||||
|
||||
While Telodendria strives to be extremely simple to deploy and run,
|
||||
in most circumstances a few basic setup steps will be necessary.
|
||||
Telodendria does not have a traditional configuration file like most
|
||||
daemons. Instead, its configuration lives in its database; as such,
|
||||
all configuration happens through the administrator API. This design
|
||||
decision makes Telodendria extremely flexible, because it is possible
|
||||
to re-configure Telodendria without having to manually edit files on
|
||||
the filesystem, thus allowing administrators to secure their server
|
||||
better.
|
||||
|
||||
Please follow the instructions followed here carefully in the order
|
||||
they are presented for the best results.
|
||||
|
||||
This document assumes that you have installed Telodendria using any
|
||||
of the instructions found in [Install](install.md). After installation,
|
||||
follow these steps:
|
||||
|
||||
1. Start Telodendria. If you installed it via a package or container,
|
||||
consult your operating system or container system's documentation. If
|
||||
you are running Telodendria from a release binary or have built it from
|
||||
source, execute the binary directly. If needed, consult the
|
||||
[Usage](usage.md) page for details on how to run Telodendria.
|
||||
1. Assuming that Telodendria started properly, it will spin up and
|
||||
initialize its database directly with a simple—and, importantly,
|
||||
safe—default configuration, as well as a randomly generated,
|
||||
single-use registration token that grants a user all privileges
|
||||
documented in the [Administrator API](admin/README.md) documentation.
|
||||
Consult the log file for this administrator registration token. By
|
||||
default, the log file is located in the data directory, and is named
|
||||
`telodendria.log`.
|
||||
1. Use the registration token to register for an account on the
|
||||
server. This account will be the administrator account. You can do this
|
||||
using the client of your choice, or using tools such as `curl` or
|
||||
`http`, following the Matrix specification for registering accounts.
|
||||
The administrator account behaves just like a normal local account
|
||||
that an ordinary user would have registered on the server, except that
|
||||
it also has all privileges granted to it, so it can make full use of
|
||||
the Administrator API.
|
||||
1. Using the access token granted for the administrator account via
|
||||
the login process, configure Telodendria as descibed in
|
||||
[Configuration](config.md). See the [Administrator API](admin/README.md)
|
||||
documentation for the configuration endpoint details.
|
||||
|
||||
This is the recommended way to set up Telodendria. However, if you
|
||||
wish to bypass the account creation step and want to configure
|
||||
Telodendria by directly writing a configuration file instead of using
|
||||
the administrator API, you can manually create the configuration file
|
||||
in the database before starting Telodendria. Simply create `config.json`
|
||||
following the description in [Configuration](config.md), then start
|
||||
Telodendria.
|
||||
|
||||
While this alternative method may seem simpler and more convenient
|
||||
to some administrators, it is only so in the short-term. Note that this
|
||||
method is not supported, because it gives no access to the
|
||||
administrator API whatsoever, unless you manually modify the database
|
||||
further to give a user admin privileges, which is error-prone and
|
||||
bypasses some of Telodendria's safety mechanisms.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Usage
|
||||
|
||||
This document provides general documentation on how to use the
|
||||
`telodendria` server binary, as well as details on how it behaves.
|
||||
The details here will be useful for setting up init systems, running
|
||||
Telodendria in a container, or manually executing the binary for
|
||||
testing or debugging purposes.
|
||||
|
||||
## Command Line Options
|
||||
|
||||
Typically, Telodendria is controlled via the
|
||||
[Administrator API](admin/README.md), but the Telodendria binary does include
|
||||
a few command line options, which can be used in init scripts or for
|
||||
debugging purposes.
|
||||
|
||||
The command line arguments are as follows:
|
||||
|
||||
- **`-d <dir>`** Specify the data directory to use. All persistent
|
||||
storage that Telodendria requires is saved to and loaded from here.
|
||||
- **`-V`** Only print the version information header and then quit
|
||||
with a success exit code.
|
||||
- **`-v`** Verbose mode. This overrides the configuration and sets the
|
||||
log level to `debug`. It also enables additional logging of memory
|
||||
operations, which can be useful for debugging.
|
||||
|
||||
Before proposing additional command line arguments, consider whether or
|
||||
not the functionality requested can be provided via a (potentially new
|
||||
and as of yet uncreated) administrator API endpoint.
|
||||
|
||||
## Environment
|
||||
|
||||
Telodendria does not read any environment variables. All configuration
|
||||
should be done via the [Configuration API](config.md).
|
||||
|
||||
## Signals
|
||||
|
||||
Telodendria recognizes and responds to a number of signals:
|
||||
|
||||
- **`PIPE`:** This signal is ignored, because all I/O errors should
|
||||
already be handled properly.
|
||||
- **`USR1`:** Perform a soft restart by shutting down the HTTP servers
|
||||
and resetting the program state. Note that the daemon process does
|
||||
not exit.
|
||||
- **`TERM`:** Perform a clean shutdown after all existing connections
|
||||
are closed.
|
||||
- **`INT`:** Same as `TERM`.
|
||||
|
||||
Any other signals are not explicitly handled, so they have the
|
||||
default behavior as defind by the operating system.
|
||||
|
||||
## Exit Status
|
||||
|
||||
Telodendria exits with a non-0 exit code if the configuration file is
|
||||
invalid, or one or more of required paths or files is inaccessible.
|
||||
Telodendria will print an error to the log and then terminate
|
||||
abnormally.
|
||||
|
||||
Telodendria exits with a code of 0 if the configuration file is valid,
|
||||
all paths and files required are accessible, and the HTTP listener
|
||||
starts as intended. If Telodendria is sent a signal that it catches
|
||||
after it begins servicing requests, it will still exit normally after
|
||||
it safely shuts down, because the bootstrap process completed
|
||||
successfully, and by all accounts, it ran normally and exitted
|
||||
normally.
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022-2024 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
|
||||
|
@ -22,36 +21,31 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Array.h>
|
||||
#include <HashMap.h>
|
||||
|
||||
#ifndef TELODENDRIA_STATE_H
|
||||
#define TELODENDRIA_STATE_H
|
||||
#include <Log.h>
|
||||
|
||||
/***
|
||||
* @Nm State
|
||||
* @Nd The Matrix state resolution algorithms.
|
||||
* @Dd July 6 2023
|
||||
*
|
||||
* .Nm implements the state resolution algorithms required
|
||||
* to maintain room state.
|
||||
*/
|
||||
int
|
||||
Main(Array * args, HashMap * env)
|
||||
{
|
||||
size_t i;
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
Log(LOG_INFO, "Hello World!");
|
||||
Log(LOG_INFO, "Arguments: %lu", ArraySize(args));
|
||||
|
||||
#include <Room.h>
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
Log(LOG_INFO, " [%ld] %s", i, ArrayGet(args, i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value of a state tuple.
|
||||
*/
|
||||
extern char *StateGet(HashMap *, char *, char *);
|
||||
Log(LOG_INFO, "Environment:");
|
||||
while (HashMapIterate(env, &key, (void **) &val))
|
||||
{
|
||||
Log(LOG_INFO, " %s = %s", key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a state tuple to a value.
|
||||
*/
|
||||
extern char *StateSet(HashMap *, char *, char *, char *);
|
||||
|
||||
/**
|
||||
* Compute the room state before the specified event was sent.
|
||||
*/
|
||||
extern HashMap * StateResolve(Room *, HashMap *);
|
||||
|
||||
#endif /* TELODENDRIA_STATE_H */
|
||||
return 0;
|
||||
}
|
82
include/Cytoplasm/Args.h
Normal file
82
include/Cytoplasm/Args.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_ARGS_H
|
||||
#define CYTOPLASM_ARGS_H
|
||||
|
||||
/***
|
||||
* @Nm Args
|
||||
* @Nd Getopt-style argument parser that operates on arrays.
|
||||
* @Dd May 12 2023
|
||||
* @Xr Array
|
||||
*
|
||||
* .Nm
|
||||
* provides a simple argument parser in the style of
|
||||
* .Xr getopt 3 .
|
||||
* It exists because the runtime passes the program arguments as
|
||||
* an Array, and it is often useful to parse the arguments to
|
||||
* provide the standard command line interface.
|
||||
*/
|
||||
|
||||
#include "Array.h"
|
||||
|
||||
/**
|
||||
* All state is stored in this structure, instead of global
|
||||
* variables. This makes
|
||||
* .Nm
|
||||
* thread-safe and easy to reset.
|
||||
*/
|
||||
typedef struct ArgParseState
|
||||
{
|
||||
int optInd;
|
||||
int optErr;
|
||||
int optOpt;
|
||||
char *optArg;
|
||||
|
||||
int optPos;
|
||||
} ArgParseState;
|
||||
|
||||
/**
|
||||
* Initialize the variables in the given parser state structure
|
||||
* to their default values. This should be called before
|
||||
* .Fn ArgParse
|
||||
* is called with the parser state. It should also be called if
|
||||
* .Fn ArgParse
|
||||
* will be used again on a different array, or the same array all
|
||||
* over again.
|
||||
*/
|
||||
extern void ArgParseStateInit(ArgParseState *);
|
||||
|
||||
/**
|
||||
* Parse command line arguments stored in the given array, using
|
||||
* the given state and option string. This function behaves
|
||||
* identically to the POSIX
|
||||
* .Fn getopt
|
||||
* function, and should be used in exactly the same way. Refer to
|
||||
* your system's
|
||||
* .Xr getopt 3
|
||||
* page for details.
|
||||
*/
|
||||
extern int ArgParse(ArgParseState *, Array *, const char *);
|
||||
|
||||
#endif /* CYTOPLASM_ARGS_H */
|
188
include/Cytoplasm/Array.h
Normal file
188
include/Cytoplasm/Array.h
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_ARRAY_H
|
||||
#define CYTOPLASM_ARRAY_H
|
||||
|
||||
/***
|
||||
* @Nm Array
|
||||
* @Nd A simple dynamic array data structure.
|
||||
* @Dd November 24 2022
|
||||
* @Xr HashMap Queue
|
||||
*
|
||||
* These functions implement a simple array data structure that is
|
||||
* automatically resized as necessary when new values are added. This
|
||||
* implementation does not actually store the values of the items in it;
|
||||
* it only stores pointers to the data. As such, you will still have to
|
||||
* manually maintain all your data. The advantage of this is that these
|
||||
* functions don't have to copy data, and thus don't care how big the
|
||||
* data is. Furthermore, arbitrary data can be stored in the array.
|
||||
* .Pp
|
||||
* This array implementation is optimized for storage space and
|
||||
* appending. Deletions are expensive in that all the items of the list
|
||||
* above a deletion are moved down to fill the hole where the deletion
|
||||
* occurred. Insertions are also expensive in that all the elements
|
||||
* above the given index must be shifted up to make room for the new
|
||||
* element.
|
||||
* .Pp
|
||||
* Due to these design choices, this array implementation is best
|
||||
* suited to linear writing, and then linear or random reading.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* The functions in this API operate on an array structure which is
|
||||
* opaque to the caller.
|
||||
*/
|
||||
typedef struct Array Array;
|
||||
|
||||
/**
|
||||
* Allocate a new array. This function returns a pointer that can be
|
||||
* used with the other functions in this API, or NULL if there was an
|
||||
* error allocating memory for the array.
|
||||
*/
|
||||
extern Array *ArrayCreate(void);
|
||||
|
||||
/**
|
||||
* Deallocate an array. Note that this function does not free any of
|
||||
* the values stored in the array; it is the caller's job to manage the
|
||||
* memory for each item. Typically, the caller would iterate over all
|
||||
* the items in the array and free them before freeing the array.
|
||||
*/
|
||||
extern void ArrayFree(Array *);
|
||||
|
||||
/**
|
||||
* Get the size, in number of elements, of the given array.
|
||||
*/
|
||||
extern size_t ArraySize(Array *);
|
||||
|
||||
/**
|
||||
* Get the element at the specified index from the specified array.
|
||||
* This function will return NULL if the array is NULL, or the index
|
||||
* is out of bounds. Otherwise, it will return a pointer to a value
|
||||
* put into the array using
|
||||
* .Fn ArrayInsert
|
||||
* or
|
||||
* .Fn ArraySet .
|
||||
*/
|
||||
extern void *ArrayGet(Array *, size_t);
|
||||
|
||||
/**
|
||||
* Insert the specified element at the specified index in the specified
|
||||
* array. This function will shift the element currently at that index,
|
||||
* and any elements after it before inserting the given element.
|
||||
* .Pp
|
||||
* This function returns a boolean value indicating whether or not it
|
||||
* suceeded.
|
||||
*/
|
||||
extern bool ArrayInsert(Array *, size_t, void *);
|
||||
|
||||
/**
|
||||
* Set the value at the specified index in the specified array to the
|
||||
* specified value. This function will return the old value at that
|
||||
* index, if any.
|
||||
*/
|
||||
extern void *ArraySet(Array *, size_t, void *);
|
||||
|
||||
/**
|
||||
* Append the specified element to the end of the specified array. This
|
||||
* function uses
|
||||
* .Fn ArrayInsert
|
||||
* under the hood to insert an element at the end. It thus has the same
|
||||
* return value as
|
||||
* .Fn ArrayInsert .
|
||||
*/
|
||||
extern bool ArrayAdd(Array *, void *);
|
||||
|
||||
/**
|
||||
* Remove the element at the specified index from the specified array.
|
||||
* This function returns the element removed, if any.
|
||||
*/
|
||||
extern void *ArrayDelete(Array *, size_t);
|
||||
|
||||
/**
|
||||
* Sort the specified array using the specified sort function. The
|
||||
* sort function compares two elements. It takes two void pointers as
|
||||
* parameters, and returns an integer. The return value indicates to
|
||||
* the sorting algorithm how the elements relate to each other. A
|
||||
* return value of 0 indicates that the elements are identical. A
|
||||
* return value greater than 0 indicates that the first item is
|
||||
* ``bigger'' than the second item and should thus appear after it in
|
||||
* the array. A return value less than 0 indicates the opposite: the
|
||||
* second element should appear after the first in the array.
|
||||
*/
|
||||
extern void ArraySort(Array *, int (*) (void *, void *));
|
||||
|
||||
/**
|
||||
* Remove all duplicates from an array by using the given comparison
|
||||
* function to sort the array, then remove matching values. This
|
||||
* function returns a new array with all duplicates removed. The
|
||||
* original array is unaffected. Note that arrays only store pointers
|
||||
* to values, usually values on the heap. Thus, it is possible to lose
|
||||
* pointers to duplicate values and have them leak.
|
||||
* .P
|
||||
* This is a relatively expensive operation. The array must first be
|
||||
* duplicated. Then it is sorted, then it is iterated from beginning
|
||||
* to end to remove duplicate entries. Note that the comparison
|
||||
* function is executed on each element at least twice.
|
||||
*/
|
||||
extern Array *ArrayUnique(Array *, int (*) (void *, void *));
|
||||
|
||||
|
||||
/**
|
||||
* Reverses the order of all elements in the array into a new array on
|
||||
* the heap, to be freed using
|
||||
* .Fn ArrayFree .
|
||||
*/
|
||||
extern Array *ArrayReverse(Array *);
|
||||
|
||||
/**
|
||||
* If possible, reduce the amount of memory allocated to this array
|
||||
* by calling
|
||||
* .Fn Realloc
|
||||
* on the internal structure to perfectly fit the elements in the
|
||||
* array. This function is intended to be used by functions that return
|
||||
* relatively read-only arrays that will be long-lived.
|
||||
*/
|
||||
extern bool ArrayTrim(Array *);
|
||||
|
||||
/**
|
||||
* Convert a variadic arguments list into an Array. In most cases, the
|
||||
* Array API is much easier to work with than
|
||||
* .Fn va_arg
|
||||
* and friends.
|
||||
*/
|
||||
extern Array *ArrayFromVarArgs(size_t, va_list);
|
||||
|
||||
/**
|
||||
* Duplicate an existing array. Note that arrays only hold pointers to
|
||||
* their data, not the data itself, so the duplicated array will point
|
||||
* to the same places in memory as the original array.
|
||||
*/
|
||||
extern Array *ArrayDuplicate(Array *);
|
||||
|
||||
#endif /* CYTOPLASM_ARRAY_H */
|
100
include/Cytoplasm/Base64.h
Normal file
100
include/Cytoplasm/Base64.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_BASE64_H
|
||||
#define CYTOPLASM_BASE64_H
|
||||
|
||||
/***
|
||||
* @Nm Base64
|
||||
* @Nd A simple base64 encoder/decoder with unpadded base64 support.
|
||||
* @Dd September 30 2022
|
||||
* @Xr Sha2
|
||||
*
|
||||
* This is an efficient yet simple base64 encoding and decoding API
|
||||
* that supports regular base64, as well as the Matrix specification's
|
||||
* extension to base64, called ``unpadded base64.'' This API provides
|
||||
* the ability to convert between the two, instead of just implementing
|
||||
* unpadded base64.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* This function computes the amount of bytes needed to store a message
|
||||
* of the specified number of bytes as base64.
|
||||
*/
|
||||
extern size_t
|
||||
Base64EncodedSize(size_t);
|
||||
|
||||
/**
|
||||
* This function computes the amount of bytes needed to store a decoded
|
||||
* representation of the encoded message. It takes a pointer to the
|
||||
* encoded string because it must read a few bytes off the end in order
|
||||
* to accurately compute the size.
|
||||
*/
|
||||
extern size_t
|
||||
Base64DecodedSize(const char *, size_t);
|
||||
|
||||
/**
|
||||
* Encode the specified number of bytes from the specified buffer as
|
||||
* base64. This function returns a string on the heap that should be
|
||||
* freed with
|
||||
* .Fn Free ,
|
||||
* or NULL if a memory allocation error ocurred.
|
||||
*/
|
||||
extern char *
|
||||
Base64Encode(const char *, size_t);
|
||||
|
||||
/**
|
||||
* Decode the specified number of bytes from the specified buffer of
|
||||
* base64. This function returns a string on the heap that should be
|
||||
* freed with
|
||||
* .Fn Free ,
|
||||
* or NULL if a memory allocation error occured.
|
||||
*/
|
||||
extern char *
|
||||
Base64Decode(const char *, size_t);
|
||||
|
||||
/**
|
||||
* Remove the padding from a specified base64 string. This function
|
||||
* modifies the specified string in place. It thus has no return value
|
||||
* because it cannot fail. If the passed pointer is invalid, the
|
||||
* behavior is undefined.
|
||||
*/
|
||||
extern void
|
||||
Base64Unpad(char *, size_t);
|
||||
|
||||
/**
|
||||
* Add padding to an unpadded base64 string. This function takes a
|
||||
* pointer to a pointer because it may be necessary to grow the memory
|
||||
* allocated to the string. This function returns a boolean value
|
||||
* indicating whether the pad operation was successful. In practice,
|
||||
* this means it will only fail if a bigger string is necessary, but it
|
||||
* could not be automatically allocated on the heap.
|
||||
*/
|
||||
extern bool
|
||||
Base64Pad(char **, size_t);
|
||||
|
||||
#endif /* CYTOPLASM_BASE64_H */
|
140
include/Cytoplasm/Cron.h
Normal file
140
include/Cytoplasm/Cron.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_CRON_H
|
||||
#define CYTOPLASM_CRON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/***
|
||||
* @Nm Cron
|
||||
* @Nd Basic periodic job scheduler.
|
||||
* @Dd December 24 2022
|
||||
*
|
||||
* This is an extremely basic job scheduler. So basic, in fact, that
|
||||
* it currently runs all jobs on a single thread, which means that it
|
||||
* is intended for short-lived jobs. In the future, it might be
|
||||
* extended to support a one-thread-per-job model, but for now, jobs
|
||||
* should take into consideration the fact that they are sharing their
|
||||
* thread, so they should not be long-running.
|
||||
* .Pp
|
||||
* .Nm
|
||||
* works by ``ticking'' at an interval defined by the caller of
|
||||
* .Fn CronCreate .
|
||||
* At each tick, all the registered jobs are queried, and if they are
|
||||
* due to run again, their function is executed. As much as possible,
|
||||
* .Nm
|
||||
* tries to tick at constant intervals, however it is possible that one
|
||||
* or more jobs may overrun the tick duration. If this happens,
|
||||
* .Nm
|
||||
* ticks again immediately after all the jobs for the previous tick
|
||||
* have completed. This is in an effort to compensate for lost time,
|
||||
* however it is important to note that when jobs overrun the tick
|
||||
* interval, the interval is pushed back by the amount that it was
|
||||
* overrun. Because of this,
|
||||
* .Nm
|
||||
* is best suited for scheduling jobs that should happen
|
||||
* ``aproximately'' every so often; it is not a real-time scheduler
|
||||
* by any means.
|
||||
*/
|
||||
|
||||
/**
|
||||
* All functions defined here operate on a structure opaque to the
|
||||
* caller.
|
||||
*/
|
||||
typedef struct Cron Cron;
|
||||
|
||||
/**
|
||||
* A job function is a function that takes a void pointer and returns
|
||||
* nothing. The pointer is passed when the job is scheduled, and
|
||||
* is expected to remain valid until the job is no longer registered.
|
||||
* The pointer is passed each time the job executes.
|
||||
*/
|
||||
typedef void (JobFunc) (void *);
|
||||
|
||||
/**
|
||||
* Create a new
|
||||
* .Nm
|
||||
* object that all other functions operate on. Like most of the other
|
||||
* APIs in this project, it must be freed with
|
||||
* .Fn CronFree
|
||||
* when it is no longer needed.
|
||||
* .Pp
|
||||
* This function takes the tick interval in milliseconds.
|
||||
*/
|
||||
extern Cron * CronCreate(uint64_t);
|
||||
|
||||
/**
|
||||
* Schedule a one-off job to be executed only at the next tick, and
|
||||
* then immediately discarded. This is useful for scheduling tasks that
|
||||
* only have to happen once, or very infrequently depending on
|
||||
* conditions other than the current time, but don't have to happen
|
||||
* immediately. The caller simply indicates that it wishes for the task
|
||||
* to execute at some time in the future. How far into the future this
|
||||
* practically ends up being is determined by how long it takes for
|
||||
* other registered jobs to finish, and what the tick interval is.
|
||||
* .Pp
|
||||
* This function takes a job function and a pointer to pass to that
|
||||
* function when it is executed.
|
||||
*/
|
||||
extern void
|
||||
CronOnce(Cron *, JobFunc *, void *);
|
||||
|
||||
/**
|
||||
* Schedule a repetitive task to be executed at aproximately the given
|
||||
* interval. As stated above, this is as fuzzy interval; depending on
|
||||
* the jobs being run and the tick interval, tasks may not execute at
|
||||
* exactly the scheduled time, but they will eventually execute.
|
||||
* .Pp
|
||||
* This function takes an interval in milliseconds, a job function,
|
||||
* and a pointer to pass to that function when it is executed.
|
||||
*/
|
||||
extern void
|
||||
CronEvery(Cron *, uint64_t, JobFunc *, void *);
|
||||
|
||||
/**
|
||||
* Start ticking the clock and executing registered jobs.
|
||||
*/
|
||||
extern void
|
||||
CronStart(Cron *);
|
||||
|
||||
/**
|
||||
* Stop ticking the clock. Jobs that are already executing will
|
||||
* continue to execute, but when they are finished, no new jobs will
|
||||
* be executed until
|
||||
* .Fn CronStart
|
||||
* is called.
|
||||
*/
|
||||
extern void
|
||||
CronStop(Cron *);
|
||||
|
||||
/**
|
||||
* Discard all job references and free all memory associated with the
|
||||
* given
|
||||
* .Nm Cron
|
||||
* instance.
|
||||
*/
|
||||
extern void
|
||||
CronFree(Cron *);
|
||||
|
||||
#endif /* CYTOPLASM_CRON_H */
|
62
include/Cytoplasm/Cytoplasm.h
Normal file
62
include/Cytoplasm/Cytoplasm.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_CYTOPLASM_H
|
||||
#define CYTOPLASM_CYTOPLASM_H
|
||||
|
||||
#define CYTOPLASM_VERSION_MAJOR 0
|
||||
#define CYTOPLASM_VERSION_MINOR 4
|
||||
#define CYTOPLASM_VERSION_PATCH 1
|
||||
#define CYTOPLASM_VERSION ((CYTOPLASM_VERSION_MAJOR * 10000) + (CYTOPLASM_VERSION_MINOR * 100) + (CYTOPLASM_VERSION_PATCH))
|
||||
|
||||
#define CYTOPLASM_VERSION_ALPHA 1
|
||||
#define CYTOPLASM_VERSION_BETA 0
|
||||
#define CYTOPLASM_VERSION_STABLE (!CYTOPLASM_VERSION_ALPHA && !CYTOPLASM_VERSION_BETA)
|
||||
|
||||
#define XSTRINGIFY(x) #x
|
||||
#define STRINGIFY(x) XSTRINGIFY(x)
|
||||
|
||||
/***
|
||||
* @Nm Cytoplasm
|
||||
* @Nd A simple API that provides metadata on the library itself.
|
||||
* @Dd September 30 2022
|
||||
* @Xr Sha2
|
||||
*
|
||||
* This API simply provides name and versioning information for the
|
||||
* currently loaded library.
|
||||
*/
|
||||
|
||||
/** */
|
||||
extern int CytoplasmGetVersion(void);
|
||||
|
||||
/**
|
||||
* Get the library version. This will be useful mostly for printing
|
||||
* to log files, but it may also be used by a program to verify that
|
||||
* the version is new enough.
|
||||
*
|
||||
* This function returns a string, which should usually be able to be
|
||||
* parsed using sscanf() if absolutely necessary.
|
||||
*/
|
||||
extern const char * CytoplasmGetVersionStr(void);
|
||||
|
||||
#endif /* CYTOPLASM_CYTOPLASM_H */
|
199
include/Cytoplasm/Db.h
Normal file
199
include/Cytoplasm/Db.h
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_DB_H
|
||||
#define CYTOPLASM_DB_H
|
||||
|
||||
/***
|
||||
* @Nm Db
|
||||
* @Nd A minimal flat-file database with mutex locking and cache.
|
||||
* @Dd April 27 2023
|
||||
* @Xr Json
|
||||
*
|
||||
* Cytoplasm operates on a flat-file database instead of a
|
||||
* traditional relational database. This greatly simplifies the
|
||||
* persistent storage code, and creates a relatively basic API,
|
||||
* described here.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "HashMap.h"
|
||||
#include "Array.h"
|
||||
|
||||
/**
|
||||
* All functions in this API operate on a database structure that is
|
||||
* opaque to the caller.
|
||||
*/
|
||||
typedef struct Db Db;
|
||||
|
||||
/**
|
||||
* Some "hints" for the database backend for operations.
|
||||
* Hints are a way for the program to declare what to except of it
|
||||
* (and the program MUST adhere to these hints, but the backend
|
||||
* MAY adhere).
|
||||
*/
|
||||
typedef enum DbHint {
|
||||
DB_HINT_READONLY, /* The database reference is treated as read-only */
|
||||
DB_HINT_WRITE /* The database reference is treated as read/write */
|
||||
} DbHint;
|
||||
|
||||
/**
|
||||
* When an object is locked, a reference is returned. This reference
|
||||
* is owned by the current thread, and the database is inaccessible to
|
||||
* other threads until all references have been returned to the
|
||||
* database.
|
||||
* .Pp
|
||||
* This reference is opaque, but can be manipulated by the functions
|
||||
* defined here.
|
||||
*/
|
||||
typedef struct DbRef DbRef;
|
||||
|
||||
/**
|
||||
* Open a data directory. This function takes a path to open, and a
|
||||
* cache size in bytes. If the cache size is 0, then caching is
|
||||
* disabled and objects are loaded off the disk every time they are
|
||||
* locked. Otherwise, objects are stored in the cache, and they are
|
||||
* evicted in a least-recently-used manner.
|
||||
*/
|
||||
extern Db * DbOpen(char *, size_t);
|
||||
|
||||
/**
|
||||
* Open a LMDB data directory. This function is similar to
|
||||
* .Fn DbOpen ,
|
||||
* but works with a LMDB-based backend, with the second argument
|
||||
* being the maximum filesize. This function fails with NULL if ever
|
||||
* called without LMDB enabled at compiletime.
|
||||
*/
|
||||
extern Db * DbOpenLMDB(char *, size_t);
|
||||
|
||||
/**
|
||||
* Close the database. This function will flush anything in the cache
|
||||
* to the disk, and then close the data directory. It assumes that
|
||||
* all references have been unlocked. If a reference has not been
|
||||
* unlocked, undefined behavior results.
|
||||
*/
|
||||
extern void DbClose(Db *);
|
||||
|
||||
/**
|
||||
* Set the maximum cache size allowed before
|
||||
* .Nm
|
||||
* starts evicting old objects. If this is set to 0, everything in the
|
||||
* cache is immediately evicted and caching is disabled. If the
|
||||
* database was opened with a cache size of 0, setting this will
|
||||
* initialize the cache, and subsequent calls to
|
||||
* .Fn DbLock
|
||||
* will begin caching objects.
|
||||
*/
|
||||
extern void DbMaxCacheSet(Db *, size_t);
|
||||
|
||||
/**
|
||||
* Create a new object in the database with the specified name. This
|
||||
* function will fail if the object already exists in the database. It
|
||||
* takes a variable number of C strings, with the exact number being
|
||||
* specified by the second parameter. These C strings are used to
|
||||
* generate a filesystem path at which to store the object. These paths
|
||||
* ensure each object is uniquely identifiable, and provides semantic
|
||||
* meaning to an object.
|
||||
*/
|
||||
extern DbRef * DbCreate(Db *, size_t,...);
|
||||
|
||||
/**
|
||||
* Lock an existing object in the database. This function will fail
|
||||
* if the object does not exist. It takes a variable number of C
|
||||
* strings, with the exact number being specified by the second
|
||||
* parameter. These C strings are used to generate the filesystem path
|
||||
* at which to load the object. These paths ensure each object is
|
||||
* uniquely identifiable, and provides semantic meaning to an object.
|
||||
*/
|
||||
extern DbRef * DbLock(Db *, size_t,...);
|
||||
|
||||
/**
|
||||
* Behaves like
|
||||
* .Fn DbLock ,
|
||||
* but with hints on the reference itself, as
|
||||
* .Fn DbLock
|
||||
* itself is read/write.
|
||||
*/
|
||||
extern DbRef * DbLockIntent(Db *, DbHint, size_t,...);
|
||||
|
||||
/**
|
||||
* Immediately and permanently remove an object from the database.
|
||||
* This function assumes the object is not locked, otherwise undefined
|
||||
* behavior will result.
|
||||
*/
|
||||
extern bool DbDelete(Db *, size_t,...);
|
||||
|
||||
/**
|
||||
* Unlock an object and return it back to the database. This function
|
||||
* immediately syncs the object to the filesystem. The cache is a
|
||||
* read cache; writes are always immediate to ensure data integrity in
|
||||
* the event of a system failure.
|
||||
*/
|
||||
extern bool DbUnlock(Db *, DbRef *);
|
||||
|
||||
/**
|
||||
* Check the existence of the given database object in a more efficient
|
||||
* manner than attempting to lock it with
|
||||
* .Fn DbLock .
|
||||
* This function does not lock the object, nor does it load it into
|
||||
* memory if it exists.
|
||||
*/
|
||||
extern bool DbExists(Db *, size_t,...);
|
||||
|
||||
/**
|
||||
* List all of the objects at a given path. Unlike the other varargs
|
||||
* functions, this one does not take a path to a specific object; it
|
||||
* takes a directory to be iterated, where each path part is its own
|
||||
* C string. Note that the resulting list only contains the objects
|
||||
* in the specified directory, it does not list any subdirectories.
|
||||
* .Pp
|
||||
* The array returned is an array of C strings containing the object
|
||||
* name.
|
||||
*/
|
||||
extern Array * DbList(Db *, size_t,...);
|
||||
|
||||
/**
|
||||
* Free the list returned by
|
||||
* .Fn DbListFree .
|
||||
*/
|
||||
extern void DbListFree(Array *);
|
||||
|
||||
/**
|
||||
* Convert a database reference into JSON that can be manipulated.
|
||||
* At this time, the database actually stores objects as JSON on the
|
||||
* disk, so this function just returns an internal pointer, but in the
|
||||
* future it may have to be generated by decompressing a binary blob,
|
||||
* or something of that nature.
|
||||
*/
|
||||
extern HashMap * DbJson(DbRef *);
|
||||
|
||||
/**
|
||||
* Free the existing JSON associated with the given reference, and
|
||||
* replace it with new JSON. This is more efficient than duplicating
|
||||
* a separate object into the database reference.
|
||||
*/
|
||||
extern bool DbJsonSet(DbRef *, HashMap *);
|
||||
|
||||
#endif
|
175
include/Cytoplasm/Graph.h
Normal file
175
include/Cytoplasm/Graph.h
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_GRAPH_H
|
||||
#define CYTOPLASM_GRAPH_H
|
||||
|
||||
/***
|
||||
* @Nm Graph
|
||||
* @Nd Extremely simple graph, implemented as an adjacency matrix.
|
||||
* @Dd July 15 2023
|
||||
*
|
||||
* .Nm
|
||||
* is a basic graph data structure originally written for a computer
|
||||
* science class on data structures and algorithms, in which it
|
||||
* received full credit. This is an adaptation of the original
|
||||
* implementation that follows the Cytoplasm style and uses Cytoplasm
|
||||
* APIs when convenient.
|
||||
* .P
|
||||
* .Nm
|
||||
* stores data in an adjacency matrix, which means the storage
|
||||
* complexity is O(N^2), where N is the number of vertices (called
|
||||
* Nodes in this implementation) in the graph. However, this makes the
|
||||
* algorithms fast and efficient.
|
||||
* .P
|
||||
* Nodes are identified by index, so the first node is 0, the second
|
||||
* is 1, and so on. This data structure does not support storing
|
||||
* arbitrary data as nodes; rather, the intended use case is to add
|
||||
* all your node data to an Array, thus giving each node an index,
|
||||
* and then manipulating the graph with that index. This allows access
|
||||
* to node data in O(1) time in call cases, and is the most memory
|
||||
* efficient.
|
||||
* .P
|
||||
* .Nm
|
||||
* can be used to store a variety of types of graphs, although it is
|
||||
* primarily suited to directed and weighted graphs.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* The functions provided here operate on an opaque graph structure.
|
||||
* This structure really just stores a matrix in a contiguous block of
|
||||
* memory, as well as the number of nodes in the graph, but the
|
||||
* structure is kept opaque so that it remains internally consistent.
|
||||
* It also maintains the style of the Cytoplasm library.
|
||||
*/
|
||||
typedef struct Graph Graph;
|
||||
|
||||
/**
|
||||
* An Edge is really just a weight, which is easily represented by an
|
||||
* integer. However, it makes sense to alias this to Edge for clarity,
|
||||
* both in the documentation and in the implementation.
|
||||
*/
|
||||
typedef int Edge;
|
||||
|
||||
/**
|
||||
* A Node is really just a row or column in the matrix, which is easily
|
||||
* represented by an unsigned integer. However, it makes sense to alias
|
||||
* this to Node for clarity, both in the documentation and the
|
||||
* implementation.
|
||||
*/
|
||||
typedef size_t Node;
|
||||
|
||||
/**
|
||||
* Create a new graph structure with the given number of vertices.
|
||||
*/
|
||||
extern Graph *GraphCreate(size_t);
|
||||
|
||||
/**
|
||||
* Create a new graph data structure with the given number of vertices
|
||||
* and the given adjacency matrix. The adjacency matrix is copied
|
||||
* verbatim into the graph data structure without any validation.
|
||||
*/
|
||||
extern Graph *GraphCreateWithEdges(size_t, Edge *);
|
||||
|
||||
/**
|
||||
* Free all memory associated with the given graph. Since graphs are
|
||||
* just a collection of numbers, they do not depend on each other in
|
||||
* any way.
|
||||
*/
|
||||
extern void GraphFree(Graph *);
|
||||
|
||||
/**
|
||||
* Get the weight of the edge connecting the node specified first to
|
||||
* the node specified second. If this is a directed graph, it does not
|
||||
* necessarily follow that there is an edge from the node specified
|
||||
* second to the node specified first. It also does not follow that
|
||||
* such an edge, if it exists, has the same weight.
|
||||
* .P
|
||||
* This function will return -1 if the graph is invalid or either node
|
||||
* is out of bounds. It will return 0 if there is no such edge from the
|
||||
* node specified first to the node specified second.
|
||||
*/
|
||||
extern Edge GraphEdgeGet(Graph *, Node, Node);
|
||||
|
||||
/**
|
||||
* Set the weight of the edge connecting the node specified first to
|
||||
* the node specified second. If this is not a directed graph, this
|
||||
* function will have to be called twice, the second time reversing the
|
||||
* order of the nodes. To remove the edge, specify a weight of 0.
|
||||
*/
|
||||
extern Edge GraphEdgeSet(Graph *, Node, Node, Edge);
|
||||
|
||||
/**
|
||||
* Get the number of nodes in the given graph. This operation is a
|
||||
* simple memory access that happens in O(1) time.
|
||||
*/
|
||||
extern size_t GraphCountNodes(Graph *);
|
||||
|
||||
/**
|
||||
* Perform a breadth-first search on the given graph, starting at the
|
||||
* specified node. This function returns a list of nodes in the order
|
||||
* they were touched. The size of the list is stored in the unsigned
|
||||
* integer pointed to by the last argument.
|
||||
* .P
|
||||
* If an error occurs, NULL will be returned. Otherwise, the returned
|
||||
* pointer should be freed with the Memory API when it is no longer
|
||||
* needed.
|
||||
*/
|
||||
extern Node * GraphBreadthFirstSearch(Graph *, Node, size_t *);
|
||||
|
||||
/**
|
||||
* Perform a depth-first search on the given graph, starting at the
|
||||
* specified node. This function returns a list of nodes in the order
|
||||
* they were touched. The size of the list is stored in the unsigned
|
||||
* integer pointed to by the last argument.
|
||||
* .P
|
||||
* If an error occurs, NULL will be returned. Otherwise the returned
|
||||
* pointer should be freed with the Memory API when it is no longer
|
||||
* needed.
|
||||
*/
|
||||
extern Node *GraphDepthFirstSearch(Graph *, Node, size_t *);
|
||||
|
||||
/**
|
||||
* Perform a topological sort on the given graph. This function returns
|
||||
* a list of nodes in topological ordering, though note that this is
|
||||
* probably not the only topological ordering that exists for the
|
||||
* graph. The size of the list is stored in the unsigned integer
|
||||
* pointed to by the last argument. It should always be the number of
|
||||
* nodes in the graph, but is provided for consistency and convenience.
|
||||
* .P
|
||||
* If an error occurs, NULL will be returned. Otherwise the returned
|
||||
* pointer should be freed with the Memory API when it is no longer
|
||||
* needed.
|
||||
*/
|
||||
extern Node *GraphTopologicalSort(Graph *, size_t *);
|
||||
|
||||
/**
|
||||
* Transpose the given graph, returning a brand new graph that is the
|
||||
* result of the transposition.
|
||||
*/
|
||||
extern Graph * GraphTranspose(Graph *);
|
||||
|
||||
#endif /* CYTOPLASM_GRAPH_H */
|
185
include/Cytoplasm/HashMap.h
Normal file
185
include/Cytoplasm/HashMap.h
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_HASHMAP_H
|
||||
#define CYTOPLASM_HASHMAP_H
|
||||
|
||||
/***
|
||||
* @Nm HashMap
|
||||
* @Nd A simple hash map implementation.
|
||||
* @Dd October 11 2022
|
||||
* @Xr Array Queue
|
||||
*
|
||||
* This is the public interface for Cytoplasm'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
|
||||
* throughout the project.
|
||||
* .Pp
|
||||
* Fundamentally, this is an entirely generic map implementation. It
|
||||
* can be used for many general purposes, but it is designed only to
|
||||
* implement the features Cytoplasm needs to be functional. One
|
||||
* example of a Cytoplasm-specific feature is that keys cannot be
|
||||
* arbitrary data; they are NULL-terminated C strings.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "Array.h"
|
||||
|
||||
/**
|
||||
* These functions operate on an opaque structure, which the caller
|
||||
* has no knowledge about.
|
||||
*/
|
||||
typedef struct HashMap HashMap;
|
||||
|
||||
/**
|
||||
* Create a new hash map that is ready to be used with the rest of the
|
||||
* functions defined here.
|
||||
*/
|
||||
extern HashMap * HashMapCreate(void);
|
||||
|
||||
/**
|
||||
* Free the specified hash map such that it becomes invalid and any
|
||||
* future use results in undefined behavior. Note that this function
|
||||
* does not free the values stored in the hash map, but since it stores
|
||||
* the keys internally, it will free the keys. You should use
|
||||
* .Fn HashMapIterate
|
||||
* to free the values stored in this map appropriately before calling
|
||||
* this function.
|
||||
*/
|
||||
extern void HashMapFree(HashMap *);
|
||||
|
||||
/**
|
||||
* Control the maximum load of the hash map before it is expanded.
|
||||
* When the hash map reaches the given capacity, it is grown. You
|
||||
* don't want to only grow hash maps when they are full, because that
|
||||
* makes them perform very poorly. The maximum load value is a
|
||||
* percentage of how full the hash map is, and it should be between
|
||||
* 0 and 1, where 0 means that no elements will cause the map to be
|
||||
* expanded, and 1 means that the hash map must be completely full
|
||||
* before it is expanded. The default maximum load on a new hash map
|
||||
* is 0.75, which should be good enough for most purposes, however,
|
||||
* this function exists specifically so that the maximum load can be
|
||||
* fine-tuned.
|
||||
*/
|
||||
extern void HashMapMaxLoadSet(HashMap *, float);
|
||||
|
||||
/**
|
||||
* Use a custom hashing function with the given hash map. New hash
|
||||
* maps have a sane hashing function that should work okay for most
|
||||
* use cases, but if you have a better hashing function, it can be
|
||||
* specified this way. Do not change the hash function after keys have
|
||||
* been added; doing so results in undefined behavior. Only set a new
|
||||
* hash function immediately after constructing a new hash map, before
|
||||
* anything has been added to it.
|
||||
* .Pp
|
||||
* The hash function takes a pointer to a C string, and is expected
|
||||
* to return a fairly unique numerical hash value which will be
|
||||
* converted into an array index.
|
||||
*/
|
||||
extern void
|
||||
HashMapFunctionSet(HashMap *, unsigned long (*) (const char *));
|
||||
|
||||
/**
|
||||
* Set the given string key to the given value. Note that the key is
|
||||
* copied into the hash map's own memory space, but the value is not.
|
||||
* It is the caller's job to ensure that the value pointer remains
|
||||
* valid for the life of the hash map, and are freed when no longer
|
||||
* needed.
|
||||
*/
|
||||
extern void * HashMapSet(HashMap *, char *, void *);
|
||||
|
||||
/**
|
||||
* Retrieve the value for the given key, or return NULL if no such
|
||||
* key exists in the hash map.
|
||||
*/
|
||||
extern void * HashMapGet(HashMap *, const char *);
|
||||
|
||||
/**
|
||||
* Remove a value specified by the given key from the hash map, and
|
||||
* return it to the caller to deal with. This function returns NULL
|
||||
* if no such key exists.
|
||||
*/
|
||||
extern void * HashMapDelete(HashMap *, const char *);
|
||||
|
||||
/**
|
||||
* Iterate over all the keys and values of a hash map. This function
|
||||
* works very similarly to
|
||||
* .Xr getopt 3 ,
|
||||
* where calls are repeatedly made in a while loop until there are no
|
||||
* more items to go over. The difference is that this function does not
|
||||
* rely on globals; it takes pointer pointers, and stores all
|
||||
* necessary state inside the hash map itself.
|
||||
* .Pp
|
||||
* Note that this function is not thread-safe; two threads cannot be
|
||||
* iterating over any given hash map at the same time, though they
|
||||
* can each be iterating over different hash maps.
|
||||
* .Pp
|
||||
* This function can be tricky to use in some scenarios, as it
|
||||
* continues where it left off on each call, until there are no more
|
||||
* elements to go through in the hash map. If you are not iterating
|
||||
* over the entire map in one go, and happen to break the loop, then
|
||||
* the next time you attempt to iterate the hash map, you'll start
|
||||
* somewhere in the middle, which is most likely not the intended
|
||||
* behavior. Thus, it is always recommended to iterate over the entire
|
||||
* hash map if you're going to use this function.
|
||||
* .Pp
|
||||
* Also note that the behavior of this function is undefined if
|
||||
* insertions or deletions occur during the iteration. This
|
||||
* functionality has not been tested, and will likely not work.
|
||||
*/
|
||||
extern bool HashMapIterate(HashMap *, char **, void **);
|
||||
|
||||
/**
|
||||
* A reentrant version of
|
||||
* .Fn HashMapIterate
|
||||
* that allows the caller to overcome the flaws of that function by
|
||||
* storing the cursor outside of the hash map structure itself. This
|
||||
* allows multiple threads to iterate over the same hash map at the
|
||||
* same time, and it allows the iteration to be halted midway through
|
||||
* without causing any unintended side effects.
|
||||
* .Pp
|
||||
* The cursor should be initialized to 0 at the start of iteration.
|
||||
*/
|
||||
extern bool
|
||||
HashMapIterateReentrant(HashMap *, char **, void **, size_t *);
|
||||
|
||||
/**
|
||||
* Collect the string keys of a hash map and return them as an array.
|
||||
* The returned array holds pointers to the strings stored in the
|
||||
* hash map, so the strings should NOT be freed; it is sufficient to
|
||||
* free the array itself. Likewise, once the hash map is freed, the
|
||||
* array elements are invalid and the array should be freed.
|
||||
*/
|
||||
extern Array * HashMapKeys(HashMap *);
|
||||
|
||||
/**
|
||||
* Collect the values of a hash map and return them as an array. The
|
||||
* returned array holds the same pointers to the values as the hash
|
||||
* map.
|
||||
*/
|
||||
extern Array * HashMapValues(HashMap *);
|
||||
|
||||
#endif /* CYTOPLASM_HASHMAP_H */
|
125
include/Cytoplasm/HeaderParser.h
Normal file
125
include/Cytoplasm/HeaderParser.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_HEADERPARSER_H
|
||||
#define CYTOPLASM_HEADERPARSER_H
|
||||
|
||||
/***
|
||||
* @Nm HeaderParser
|
||||
* @Nd Parse simple C header files.
|
||||
* @Dd April 29 2023
|
||||
*
|
||||
* .Nm
|
||||
* is an extremely simple parser that lacks most of the functionality
|
||||
* one would expect from a C code parser. It simply maps a stream
|
||||
* of tokens into a few categories, parsing major ``milestones'' in
|
||||
* a header, without actually understanding any of the details.
|
||||
* .Pp
|
||||
* This exists because it is used to generate man pages from headers.
|
||||
* See
|
||||
* .Xr hdoc 1
|
||||
* for example usage of this parser.
|
||||
*/
|
||||
|
||||
#include "Stream.h"
|
||||
#include "Array.h"
|
||||
|
||||
#define HEADER_EXPR_MAX 4096
|
||||
|
||||
/**
|
||||
* Headers are parsed as expressions. These are the expressions that
|
||||
* this parser recognizes.
|
||||
*/
|
||||
typedef enum HeaderExprType
|
||||
{
|
||||
HP_COMMENT,
|
||||
HP_PREPROCESSOR_DIRECTIVE,
|
||||
HP_TYPEDEF,
|
||||
HP_DECLARATION,
|
||||
HP_GLOBAL,
|
||||
HP_UNKNOWN,
|
||||
HP_SYNTAX_ERROR,
|
||||
HP_PARSE_ERROR,
|
||||
HP_EOF
|
||||
} HeaderExprType;
|
||||
|
||||
/**
|
||||
* A representation of a function declaration.
|
||||
*/
|
||||
typedef struct HeaderDeclaration
|
||||
{
|
||||
char returnType[64];
|
||||
char name[32]; /* Enforced by ANSI C */
|
||||
Array *args;
|
||||
} HeaderDeclaration;
|
||||
|
||||
/**
|
||||
* A global variable declaration. The type must be of the same size
|
||||
* as the function declaration's return type due to the way parsing
|
||||
* them is implemented.
|
||||
*/
|
||||
typedef struct HeaderGlobal
|
||||
{
|
||||
char type[64];
|
||||
char name[HEADER_EXPR_MAX - 64];
|
||||
} HeaderGlobal;
|
||||
|
||||
/**
|
||||
* A representation of a single header expression. Note that that state
|
||||
* structure is entirely internally managed, so it should not be
|
||||
* accessed or manipulated by functions outside the functions defined
|
||||
* here.
|
||||
* .Pp
|
||||
* The type field should be used to determine which field in the data
|
||||
* union is valid.
|
||||
*/
|
||||
typedef struct HeaderExpr
|
||||
{
|
||||
HeaderExprType type;
|
||||
union
|
||||
{
|
||||
char text[HEADER_EXPR_MAX];
|
||||
HeaderDeclaration declaration;
|
||||
HeaderGlobal global;
|
||||
struct
|
||||
{
|
||||
int lineNo;
|
||||
char *msg;
|
||||
} error;
|
||||
} data;
|
||||
|
||||
struct
|
||||
{
|
||||
Stream *stream;
|
||||
int lineNo;
|
||||
} state;
|
||||
} HeaderExpr;
|
||||
|
||||
/**
|
||||
* Parse the next expression into the given header expression structure.
|
||||
* To parse an entire C header, this function should be called in a
|
||||
* loop until the type of the expression is HP_EOF.
|
||||
*/
|
||||
extern void HeaderParse(Stream *, HeaderExpr *);
|
||||
|
||||
#endif /* CYTOPLASM_HEADERPARSER_H */
|
211
include/Cytoplasm/Http.h
Normal file
211
include/Cytoplasm/Http.h
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_HTTP_H
|
||||
#define CYTOPLASM_HTTP_H
|
||||
|
||||
/***
|
||||
* @Nm Http
|
||||
* @Nd Encode and decode various parts of the HTTP protocol.
|
||||
* @Dd March 12 2023
|
||||
* @Xr HttpClient HttpServer HashMap Queue Memory
|
||||
*
|
||||
* .Nm
|
||||
* is a collection of utility functions and type definitions that are
|
||||
* useful for dealing with HTTP. HTTP is not a complex protocol, but
|
||||
* this API makes it a lot easier to work with.
|
||||
* .Pp
|
||||
* Note that this API doesn't target any particular HTTP version, but
|
||||
* it is currently used with HTTP 1.0 clients and servers, and
|
||||
* therefore may be lacking functionality added in later HTTP versions.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "HashMap.h"
|
||||
#include "Stream.h"
|
||||
|
||||
#define HTTP_FLAG_NONE 0
|
||||
#define HTTP_FLAG_TLS (1 << 0)
|
||||
|
||||
/**
|
||||
* The request methods defined by the HTTP standard. These numeric
|
||||
* constants should be preferred to strings when building HTTP APIs
|
||||
* because they are more efficient.
|
||||
*/
|
||||
typedef enum HttpRequestMethod
|
||||
{
|
||||
HTTP_METHOD_UNKNOWN,
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
HTTP_DELETE,
|
||||
HTTP_CONNECT,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_TRACE,
|
||||
HTTP_PATCH
|
||||
} HttpRequestMethod;
|
||||
|
||||
/**
|
||||
* An enumeration that corresponds to the actual integer values of the
|
||||
* valid HTTP response codes.
|
||||
*/
|
||||
typedef enum HttpStatus
|
||||
{
|
||||
HTTP_STATUS_UNKNOWN = 0,
|
||||
|
||||
/* 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;
|
||||
|
||||
/**
|
||||
* Convert an HTTP status enumeration value into a string description
|
||||
* of the status, which is to be used in server response to a client,
|
||||
* or a client response to a user. For example, calling
|
||||
* .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT"
|
||||
* (or
|
||||
* .Fn HttpStatusToString "504" )
|
||||
* produces the string "Gateway Timeout". Note that the returned
|
||||
* pointers point to static space, so their manipulation is forbidden.
|
||||
*/
|
||||
extern const char * HttpStatusToString(const HttpStatus);
|
||||
|
||||
/**
|
||||
* Convert a string into a numeric code that can be used throughout
|
||||
* the code of a program in an efficient manner. See the definition
|
||||
* of HttpRequestMethod. This function does case-sensitive matching,
|
||||
* and does not trim or otherwise process the input string.
|
||||
*/
|
||||
extern HttpRequestMethod HttpRequestMethodFromString(const char *);
|
||||
|
||||
/**
|
||||
* Convert a numeric code as defined by HttpRequestMethod into a
|
||||
* string that can be sent to a server. Note that the returned pointers
|
||||
* point to static space, so their manipulation is forbidden.
|
||||
*/
|
||||
extern const char * HttpRequestMethodToString(const HttpRequestMethod);
|
||||
|
||||
/**
|
||||
* Encode a C string such that it can safely appear in a URL by
|
||||
* performing the necessary percent escaping. A new string on the
|
||||
* heap is returned. It should be freed with
|
||||
* .Fn Free ,
|
||||
* defined in the
|
||||
* .Xr Memory 3
|
||||
* API.
|
||||
*/
|
||||
extern char * HttpUrlEncode(char *);
|
||||
|
||||
/**
|
||||
* Decode a percent-encoded string into a C string, ignoring encoded
|
||||
* null characters entirely, because those would do nothing but cause
|
||||
* problems.
|
||||
*/
|
||||
extern char * HttpUrlDecode(char *);
|
||||
|
||||
/**
|
||||
* Decode an encoded parameter string in the form of
|
||||
* ``key=val&key2=val2'' into a hash map whose values are C strings.
|
||||
* This function properly decodes keys and values using the functions
|
||||
* defined above.
|
||||
*/
|
||||
extern HashMap * HttpParamDecode(char *);
|
||||
|
||||
/**
|
||||
* Encode a hash map whose values are strings as an HTTP parameter
|
||||
* string suitable for GET or POST requests.
|
||||
*/
|
||||
extern char * HttpParamEncode(HashMap *);
|
||||
|
||||
/**
|
||||
* Read HTTP headers from a stream and return a hash map whose values
|
||||
* are strings. All keys are lowercased to make querying them
|
||||
* consistent and not dependent on the case that was read from the
|
||||
* stream. This is useful for both client and server code, since the
|
||||
* headers are in the same format. This function should be used after
|
||||
* parsing the HTTP status line, because it does not parse that line.
|
||||
* It will stop when it encounters the first blank line, which
|
||||
* indicates that the body is beginning. After this function completes,
|
||||
* the body may be immediately read from the stream without any
|
||||
* additional processing.
|
||||
*/
|
||||
extern HashMap * HttpParseHeaders(Stream *);
|
||||
|
||||
#endif
|
104
include/Cytoplasm/HttpClient.h
Normal file
104
include/Cytoplasm/HttpClient.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_HTTPCLIENT_H
|
||||
#define CYTOPLASM_HTTPCLIENT_H
|
||||
|
||||
/***
|
||||
* @Nm HttpClient
|
||||
* @Nd Extremely simple HTTP client.
|
||||
* @Dd April 29 2023
|
||||
* @Xr Http HttpServer Tls
|
||||
*
|
||||
* .Nm
|
||||
* HttpClient
|
||||
* builds on the HTTP API to provide a simple yet functional HTTP
|
||||
* client. It aims at being easy to use and minimal, yet also
|
||||
* efficient.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "HashMap.h"
|
||||
#include "Http.h"
|
||||
|
||||
/**
|
||||
* A server response is represented by a client context. It is
|
||||
* opaque, so the functions defined in this API should be used to
|
||||
* fetch data from and manipulate it.
|
||||
*/
|
||||
typedef struct HttpClientContext HttpClientContext;
|
||||
|
||||
/**
|
||||
* Make an HTTP request. This function takes the request method,
|
||||
* any flags defined in the HTTP API, the port, hostname, and path,
|
||||
* all in that order. It returns NULL if there was an error making
|
||||
* the request. Otherwise it returns a client context. Note that this
|
||||
* function does not actually send any data, it simply makes the
|
||||
* connection. Use
|
||||
* .Fn HttpRequestHeader
|
||||
* to add headers to the request. Then, send headers with
|
||||
* .Fn HttpRequestSendHeaders .
|
||||
* Finally, the request body, if any, can be written to the output
|
||||
* stream, and then the request can be fully sent using
|
||||
* .Fn HttpRequestSend .
|
||||
*/
|
||||
extern HttpClientContext *
|
||||
HttpRequest(HttpRequestMethod, int, unsigned short, char *, char *);
|
||||
|
||||
/**
|
||||
* Set a request header to send to the server when making the
|
||||
* request.
|
||||
*/
|
||||
extern void HttpRequestHeader(HttpClientContext *, char *, char *);
|
||||
|
||||
/**
|
||||
* Send the request headers to the server. This must be called before
|
||||
* the request body can be written or a response body can be read.
|
||||
*/
|
||||
extern void HttpRequestSendHeaders(HttpClientContext *);
|
||||
|
||||
/**
|
||||
* Flush the request stream to the server. This function should be
|
||||
* called before the response body is read.
|
||||
*/
|
||||
extern HttpStatus HttpRequestSend(HttpClientContext *);
|
||||
|
||||
/**
|
||||
* Get the headers returned by the server.
|
||||
*/
|
||||
extern HashMap * HttpResponseHeaders(HttpClientContext *);
|
||||
|
||||
/**
|
||||
* Get the stream used to write the request body and read the
|
||||
* response body.
|
||||
*/
|
||||
extern Stream * HttpClientStream(HttpClientContext *);
|
||||
|
||||
/**
|
||||
* Free all memory associated with the client context. This closes the
|
||||
* connection, if it was still open.
|
||||
*/
|
||||
extern void HttpClientContextFree(HttpClientContext *);
|
||||
|
||||
#endif /* CYTOPLASM_HTTPCLIENT_H */
|
91
include/Cytoplasm/HttpRouter.h
Normal file
91
include/Cytoplasm/HttpRouter.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_HTTPROUTER_H
|
||||
#define CYTOPLASM_HTTPROUTER_H
|
||||
|
||||
/***
|
||||
* @Nm HttpRouter
|
||||
* @Nd Simple HTTP request router with regular expression support.
|
||||
* @Dd April 29 2023
|
||||
* @Xr HttpServer Http
|
||||
*
|
||||
* .Nm
|
||||
* provides a simple mechanism for assigning functions to an HTTP
|
||||
* request path. It is a simple tree data structure that parses the
|
||||
* registered request paths and maps functions onto each part of the
|
||||
* path. Then, requests can be easily routed to their appropriate
|
||||
* handler functions.
|
||||
*/
|
||||
|
||||
#include "Array.h"
|
||||
|
||||
/**
|
||||
* The router structure is opaque and thus managed entirely by the
|
||||
* functions defined in this API.
|
||||
*/
|
||||
typedef struct HttpRouter HttpRouter;
|
||||
|
||||
/**
|
||||
* A function written to handle an HTTP request takes an array
|
||||
* consisting of the matched path parts in the order they appear in
|
||||
* the path, and a pointer to caller-provided arguments, if any.
|
||||
* It returns a pointer that the caller is assumed to know how to
|
||||
* handle.
|
||||
*/
|
||||
typedef void *(HttpRouteFunc) (Array *, void *);
|
||||
|
||||
/**
|
||||
* Create a new empty routing tree.
|
||||
*/
|
||||
extern HttpRouter * HttpRouterCreate(void);
|
||||
|
||||
/**
|
||||
* Free all the memory associated with the given routing tree.
|
||||
*/
|
||||
extern void HttpRouterFree(HttpRouter *);
|
||||
|
||||
/**
|
||||
* Register the specified route function to be executed upon requests
|
||||
* for the specified HTTP path. The path is parsed by splitting at
|
||||
* each path separator. Each part of the path is a regular expression
|
||||
* that matches the entire path part. A regular expression cannot
|
||||
* match more than one path part. This allows for paths like
|
||||
* .Pa /some/path/(.*)/parts
|
||||
* to work as one would expect.
|
||||
*/
|
||||
extern bool HttpRouterAdd(HttpRouter *, char *, HttpRouteFunc *);
|
||||
|
||||
/**
|
||||
* Route the specified request path using the specified routing
|
||||
* tree. This function will parse the path and match it to the
|
||||
* appropriate route handler function. The return value is a boolean
|
||||
* value that indicates whether or not an appropriate route function
|
||||
* was found. If an appropriate function was found, then the void
|
||||
* pointer is passed to it as arguments that it is expected to know
|
||||
* how to handle, and the pointer to a void pointer is where the
|
||||
* route function's response will be placed.
|
||||
*/
|
||||
extern bool HttpRouterRoute(HttpRouter *, char *, void *, void **);
|
||||
|
||||
#endif /* CYTOPLASM_HTTPROUTER_H */
|
234
include/Cytoplasm/HttpServer.h
Normal file
234
include/Cytoplasm/HttpServer.h
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_HTTPSERVER_H
|
||||
#define CYTOPLASM_HTTPSERVER_H
|
||||
|
||||
/***
|
||||
* @Nm HttpServer
|
||||
* @Nd Extremely simple HTTP server.
|
||||
* @Dd December 13 2022
|
||||
* @Xr Http HttpClient
|
||||
*
|
||||
* .Nm
|
||||
* builds on the
|
||||
* .Xr Http 3
|
||||
* API, and provides a very simple, yet very functional API for
|
||||
* creating an HTTP server. It aims at being easy to use and minimal,
|
||||
* yet also efficient. It uses non-blocking I/O, is fully
|
||||
* multi-threaded, and is very configurable. It can be set up in just
|
||||
* two function calls and minimal supporting code.
|
||||
* .Pp
|
||||
* This API should be familar to those that have dealt with the HTTP
|
||||
* server libraries of other programming languages, particularly Java.
|
||||
* In fact, much of the terminology used in this API came from Java,
|
||||
* and you'll notice that the way responses are sent and received very
|
||||
* closely resembles Java.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "Http.h"
|
||||
#include "HashMap.h"
|
||||
#include "Stream.h"
|
||||
|
||||
/**
|
||||
* The functions on this API operate on an opaque structure.
|
||||
*/
|
||||
typedef struct HttpServer HttpServer;
|
||||
|
||||
/**
|
||||
* Each request receives a context structure. It is opaque, so the
|
||||
* functions defined in this API should be used to fetch data from
|
||||
* it. These functions allow the handler to figure out the context of
|
||||
* the request, which includes the path requested, any parameters,
|
||||
* and the headers and method used to make the request. The context
|
||||
* also provides the means by which the handler responds to the
|
||||
* request, allowing it to set the status code, headers, and body.
|
||||
*/
|
||||
typedef struct HttpServerContext HttpServerContext;
|
||||
|
||||
/**
|
||||
* The request handler function is executed when an HTTP request is
|
||||
* received. It takes a request context, and a pointer as specified
|
||||
* in the server configuration.
|
||||
*/
|
||||
typedef void (HttpHandler) (HttpServerContext *, void *);
|
||||
|
||||
/**
|
||||
* The number of arguments to
|
||||
* .Fn HttpServerCreate
|
||||
* has grown so large that arguments are now stuffed into a
|
||||
* configuration structure, which is in turn passed to
|
||||
* .Fn HttpServerCreate .
|
||||
* This configuration is copied by value into the internal
|
||||
* structures of the server. It is copied with very minimal
|
||||
* validation, so ensure that all values are sensible. It may
|
||||
* make sense to use
|
||||
* .Fn memset
|
||||
* to zero out everything in here before assigning values.
|
||||
*/
|
||||
typedef struct HttpServerConfig
|
||||
{
|
||||
unsigned short port;
|
||||
unsigned int threads;
|
||||
unsigned int maxConnections;
|
||||
|
||||
int flags; /* Http(3) flags */
|
||||
char *tlsCert; /* File path */
|
||||
char *tlsKey; /* File path */
|
||||
|
||||
HttpHandler *handler;
|
||||
void *handlerArgs;
|
||||
} HttpServerConfig;
|
||||
|
||||
/**
|
||||
* Create a new HTTP server using the specified configuration.
|
||||
* This will set up all internal structures used by the server,
|
||||
* and bind the socket and start listening for connections. However,
|
||||
* it will not start accepting connections.
|
||||
*/
|
||||
extern HttpServer * HttpServerCreate(HttpServerConfig *);
|
||||
|
||||
/**
|
||||
* Retrieve the configuration that was used to instantiate the given
|
||||
* server. Note that this configuration is not necessarily the exact
|
||||
* one that was provided; even though its values are the same, it
|
||||
* should be treated as an entirely separate configuration with no
|
||||
* connection to the original.
|
||||
*/
|
||||
extern HttpServerConfig * HttpServerConfigGet(HttpServer *);
|
||||
|
||||
/**
|
||||
* Free the resources associated with the given HTTP server. Note that
|
||||
* the server can only be freed after it has been stopped. Calling this
|
||||
* function while the server is still running results in undefined
|
||||
* behavior.
|
||||
*/
|
||||
extern void HttpServerFree(HttpServer *);
|
||||
|
||||
/**
|
||||
* Attempt to start the HTTP server, and return immediately with the
|
||||
* status. This API is fully multi-threaded and asynchronous, so the
|
||||
* caller can continue working while the HTTP server is running in a
|
||||
* separate thread and managing a pool of threads to handle responses.
|
||||
*/
|
||||
extern bool HttpServerStart(HttpServer *);
|
||||
|
||||
/**
|
||||
* Typically, at some point after calling
|
||||
* .Fn HttpServerStart ,
|
||||
* the program will have no more work to do, so it will want to wait
|
||||
* for the HTTP server to finish. This is accomplished via this
|
||||
* function, which joins the HTTP worker thread to the calling thread,
|
||||
* pausing the calling thread until the HTTP server has stopped.
|
||||
*/
|
||||
extern void HttpServerJoin(HttpServer *);
|
||||
|
||||
/**
|
||||
* Stop the HTTP server. Only the execution of this function will
|
||||
* cause the proper shutdown of the HTTP server. If the main program
|
||||
* is joined to the HTTP thread, then either another thread or a
|
||||
* signal handler will have to stop the server using this function.
|
||||
* The typical use case is to install a signal handler that executes
|
||||
* this function on a global HTTP server.
|
||||
*/
|
||||
extern void HttpServerStop(HttpServer *);
|
||||
|
||||
/**
|
||||
* Get the request headers for the request represented by the given
|
||||
* context. The data in the returned hash map should be treated as
|
||||
* read only and should not be freed; it is managed entirely by the
|
||||
* server.
|
||||
*/
|
||||
extern HashMap * HttpRequestHeaders(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Get the request method used to make the request represented by
|
||||
* the given context.
|
||||
*/
|
||||
extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Get the request path for the request represented by the given
|
||||
* context. The return value of this function should be treated as
|
||||
* read-only, and should not be freed; it is managed entirely by the
|
||||
* server.
|
||||
*/
|
||||
extern char * HttpRequestPath(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Retrieve the parsed GET parameters for the request represented by
|
||||
* the given context. The returned hash map should be treated as
|
||||
* read-only, and should not be freed; it is managed entirely by the
|
||||
* server.
|
||||
*/
|
||||
extern HashMap * HttpRequestParams(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Set a response header to return to the client. The old value for
|
||||
* the given header is returned, if any, otherwise NULL is returned.
|
||||
*/
|
||||
extern char * HttpResponseHeader(HttpServerContext *, char *, char *);
|
||||
|
||||
/**
|
||||
* Set the response status to return to the client.
|
||||
*/
|
||||
extern void HttpResponseStatus(HttpServerContext *, HttpStatus);
|
||||
|
||||
/**
|
||||
* Get the current response status that will be sent to the client
|
||||
* making the request represented by the given context.
|
||||
*/
|
||||
extern HttpStatus HttpResponseStatusGet(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Send the response headers to the client that made the request
|
||||
* represented by the specified context. This function must be called
|
||||
* before the response body can be written, otherwise a malformed
|
||||
* response will be sent.
|
||||
*/
|
||||
extern void HttpSendHeaders(HttpServerContext *);
|
||||
|
||||
/**
|
||||
* Get a stream that is both readable and writable. Reading from the
|
||||
* stream reads the request body that the client sent, if there is one.
|
||||
* Note that the rquest headers have already been read, so the stream
|
||||
* is correctly positioned at the beginning of the body of the request.
|
||||
* .Fn HttpSendHeaders
|
||||
* must be called before the stream is written, otherwise a malformed
|
||||
* HTTP response will be sent. An HTTP handler should properly set all
|
||||
* the headers it itends to send, send those headers, and then write
|
||||
* the response body to this stream.
|
||||
* .Pp
|
||||
* Note that the stream does not need to be closed by the HTTP
|
||||
* handler; in fact doing so results in undefined behavior. The stream
|
||||
* is managed entirely by the server itself, so it will close it when
|
||||
* necessary. This allows the underlying protocol to differ: for
|
||||
* instance, an HTTP/1.1 connection may stay for multiple requests and
|
||||
* responses.
|
||||
*/
|
||||
extern Stream * HttpServerStream(HttpServerContext *);
|
||||
|
||||
#endif /* CYTOPLASM_HTTPSERVER_H */
|
222
include/Cytoplasm/Io.h
Normal file
222
include/Cytoplasm/Io.h
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_IO_H
|
||||
#define CYTOPLASM_IO_H
|
||||
|
||||
/***
|
||||
* @Nm Io
|
||||
* @Nd Source/sink-agnostic I/O for implementing custom streams.
|
||||
* @Dd April 29 2023
|
||||
* @Xr Stream Tls
|
||||
*
|
||||
* Many systems provide platform-specific means of implementing custom
|
||||
* streams using file pointers. However, POSIX does not define a way
|
||||
* of programmatically creating custom streams.
|
||||
* .Nm
|
||||
* therefore fills this gap in POSIX by mimicking all of the
|
||||
* functionality of these platform-specific functions, but in pure
|
||||
* POSIX C. It defines a number of callback funtions to be executed
|
||||
* in place of the standard POSIX I/O functions, which are used to
|
||||
* implement arbitrary streams that may not be to a file or socket.
|
||||
* Additionally, streams can now be pipelined; the sink of one stream
|
||||
* may be the source of another lower-level stream. Additionally, all
|
||||
* streams, regardless of their source or sink, share the same API, so
|
||||
* streams can be handled in a much more generic manner. This allows
|
||||
* the HTTP client and server libraries to seemlessly support TLS and
|
||||
* plain connections without having to handle each separately.
|
||||
* .Pp
|
||||
* .Nm
|
||||
* was heavily inspired by GNU's
|
||||
* .Fn fopencookie
|
||||
* and BSD's
|
||||
* .Fn funopen .
|
||||
* It aims to combine the best of both of these functions into a single
|
||||
* API that is intuitive and easy to use.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef IO_BUFFER
|
||||
#define IO_BUFFER 4096
|
||||
#endif
|
||||
|
||||
/**
|
||||
* An opaque structure analogous to a POSIX file descriptor.
|
||||
*/
|
||||
typedef struct Io Io;
|
||||
|
||||
/**
|
||||
* Read input from the source of a stream. This function should
|
||||
* attempt to read the specified number of bytes of data from the
|
||||
* given cookie into the given buffer. It should behave identically
|
||||
* to the POSIX
|
||||
* .Xr read 2
|
||||
* system call, except instead of using an integer descriptor as the
|
||||
* first parameter, a pointer to an implementation-defined cookie
|
||||
* stores any information the function needs to read from the source.
|
||||
*/
|
||||
typedef ssize_t (IoReadFunc) (void *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Write output to a sink. This function should attempt to write the
|
||||
* specified number of bytes of data from the given buffer into the
|
||||
* stream described by the given cookie. It should behave identically
|
||||
* to the POSIX
|
||||
* .Xr write 2
|
||||
* system call, except instead of using an integer descriptor as the
|
||||
* first parameter, a pointer to an implementation-defined cookie
|
||||
* stores any information the function needs to write to the sink.
|
||||
*/
|
||||
typedef ssize_t (IoWriteFunc) (void *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Repositions the offset of the stream described by the specified
|
||||
* cookie. This function should behave identically to the POSIX
|
||||
* .Xr lseek 2
|
||||
* system call, except instead of using an integer descriptor as the
|
||||
* first parameter, a pointer to an implementation-defined cookie
|
||||
* stores any information the function needs to seek the stream.
|
||||
*/
|
||||
typedef off_t (IoSeekFunc) (void *, off_t, int);
|
||||
|
||||
/**
|
||||
* Close the given stream, making future reads or writes result in
|
||||
* undefined behavior. This function should also free all memory
|
||||
* associated with the cookie. It should behave identically to the
|
||||
* .Xr close 2
|
||||
* system call, except instead of using an integer descriptor for the
|
||||
* parameter, a pointer to an implementation-defined cookie stores any
|
||||
* information the function needs to close the stream.
|
||||
*/
|
||||
typedef int (IoCloseFunc) (void *);
|
||||
|
||||
/**
|
||||
* A simple mechanism for grouping together a set of stream functions,
|
||||
* to be passed to
|
||||
* .Fn IoCreate .
|
||||
*/
|
||||
typedef struct IoFunctions
|
||||
{
|
||||
IoReadFunc *read;
|
||||
IoWriteFunc *write;
|
||||
IoSeekFunc *seek;
|
||||
IoCloseFunc *close;
|
||||
} IoFunctions;
|
||||
|
||||
/**
|
||||
* Create a new stream using the specified cookie and the specified
|
||||
* I/O functions.
|
||||
*/
|
||||
extern Io * IoCreate(void *, IoFunctions);
|
||||
|
||||
/**
|
||||
* Read the specified number of bytes from the specified stream into
|
||||
* the specified buffer. This calls the stream's underlying IoReadFunc,
|
||||
* which should behave identically to the POSIX
|
||||
* .Xr read 2
|
||||
* system call.
|
||||
*/
|
||||
extern ssize_t IoRead(Io *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Write the specified number of bytes from the specified stream into
|
||||
* the specified buffer. This calls the stream's underlying
|
||||
* IoWriteFunc, which should behave identically to the POSIX
|
||||
* .Xr write 2
|
||||
* system call.
|
||||
*/
|
||||
extern ssize_t IoWrite(Io *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Seek the specified stream using the specified offset and whence
|
||||
* value. This calls the stream's underlying IoSeekFunc, which should
|
||||
* behave identically to the POSIX
|
||||
* .Xr lseek 2
|
||||
* system call.
|
||||
*/
|
||||
extern off_t IoSeek(Io *, off_t, int);
|
||||
|
||||
/**
|
||||
* Close the specified stream. This calls the stream's underlying
|
||||
* IoCloseFunc, which should behave identically to the POSIX
|
||||
* .Xr close 2
|
||||
* system call.
|
||||
*/
|
||||
extern int IoClose(Io *);
|
||||
|
||||
/**
|
||||
* Print a formatted string to the given stream. This is a
|
||||
* re-implementation of the standard library function
|
||||
* .Xr vfprintf 3 ,
|
||||
* and behaves identically.
|
||||
*/
|
||||
extern int IoVprintf(Io *, const char *, va_list);
|
||||
|
||||
/**
|
||||
* Print a formatted string to the given stream. This is a
|
||||
* re-implementation of the standard library function
|
||||
* .Xr fprintf 3 ,
|
||||
* and behaves identically.
|
||||
*/
|
||||
extern int IoPrintf(Io *, const char *,...);
|
||||
|
||||
/**
|
||||
* Read all the bytes from the first stream and write them to the
|
||||
* second stream. Neither stream is closed upon the completion of this
|
||||
* function. This can be used for quick and convenient buffered
|
||||
* copying of data from one stream into another.
|
||||
*/
|
||||
extern ssize_t IoCopy(Io *, Io *);
|
||||
|
||||
/**
|
||||
* Wrap a POSIX file descriptor to take advantage of this API. The
|
||||
* common use case for this function is when a regular file descriptor
|
||||
* needs to be accessed by an application that uses this API to also
|
||||
* access non-POSIX streams.
|
||||
*/
|
||||
extern Io * IoFd(int);
|
||||
|
||||
/**
|
||||
* Open or create a file for reading or writing. The specified file
|
||||
* name is opened for reading or writing as specified by the given
|
||||
* flags and mode. This function is a simple convenience wrapper around
|
||||
* the POSIX
|
||||
* .Xr open 2
|
||||
* system call that passes the opened file descriptor into
|
||||
* .Fn IoFd .
|
||||
*/
|
||||
extern Io * IoOpen(const char *, int, mode_t);
|
||||
|
||||
/**
|
||||
* Wrap a standard C file pointer to take advantage of this API. The
|
||||
* common use case for this function is when a regular C file pointer
|
||||
* needs to be accessed by an application that uses this API to also
|
||||
* access custom streams.
|
||||
*/
|
||||
extern Io * IoFile(FILE *);
|
||||
|
||||
#endif /* CYTOPLASM_IO_H */
|
332
include/Cytoplasm/Json.h
Normal file
332
include/Cytoplasm/Json.h
Normal file
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_JSON_H
|
||||
#define CYTOPLASM_JSON_H
|
||||
|
||||
/***
|
||||
* @Nm Json
|
||||
* @Nd A fully-featured JSON API.
|
||||
* @Dd March 12 2023
|
||||
* @Xr HashMap Array Stream
|
||||
*
|
||||
* .Nm
|
||||
* is a fully-featured JSON API for C using the array and hash map
|
||||
* APIs. It can parse JSON, ans serialize an in-memory structure to
|
||||
* JSON. It build on the foundation of Array and HashMap because that's
|
||||
* all JSON really is, just arrays and maps.
|
||||
* .Nm
|
||||
* also provides a structure for encapsulating an arbitrary value and
|
||||
* identifying its type, making it easy for a strictly-typed language
|
||||
* like C to work with loosely-typed JSON data.
|
||||
* .Nm
|
||||
* is very strict and tries to adhere as closely as possible to the
|
||||
* proper definition of JSON. It will fail on syntax errors of any
|
||||
* kind, which is fine for a Matrix homeserver that can just return
|
||||
* M_BAD_JSON if anything in here fails, but this behavior may not be
|
||||
* suitable for other purposes.
|
||||
* .Pp
|
||||
* This JSON implementation focuses primarily on serialization and
|
||||
* deserialization to and from streams. It does not provide facilities
|
||||
* for handling JSON strings; it only writes JSON to output streams,
|
||||
* and reads them from input streams. Of course, you can use the
|
||||
* POSIX
|
||||
* .Xr fmemopen 3
|
||||
* and
|
||||
* .Xr open_memstream 3
|
||||
* functions if you want to deal with JSON strings, but JSON is
|
||||
* intended to be an exchange format. Data should be converted to JSON
|
||||
* right when it is leaving the program, and converted from JSON to the
|
||||
* in-memory format as soon as it is coming in.
|
||||
* .Pp
|
||||
* JSON objects are represented as hash maps consisting entirely of
|
||||
* JsonValue structures, and arrays are represented as arrays
|
||||
* consisting entirely of JsonValue structures. When generating a
|
||||
* JSON object, any attempt to stuff a value into a hash map or array
|
||||
* without first encoding it as a JsonValue will result in undefined
|
||||
* behavior.
|
||||
*/
|
||||
|
||||
#include "HashMap.h"
|
||||
#include "Array.h"
|
||||
#include "Stream.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define JSON_DEFAULT -1
|
||||
#define JSON_PRETTY 0
|
||||
|
||||
/**
|
||||
* This opaque structure encapsulates all the possible types that can
|
||||
* be stored in JSON. It is managed entirely by the functions defined
|
||||
* in this API. It is important to note that strings, integers, floats,
|
||||
* booleans, and the NULL value are all stored by value, but objects
|
||||
* and arrays are stored by reference. That is, it doesn't store these
|
||||
* itself, just pointers to them, however, the data
|
||||
* .Em is
|
||||
* freed when using
|
||||
* .Fn JsonFree .
|
||||
*/
|
||||
typedef struct JsonValue JsonValue;
|
||||
|
||||
/**
|
||||
* These are the types that can be used to identify a JsonValue
|
||||
* and act on it accordingly.
|
||||
*/
|
||||
typedef enum JsonType
|
||||
{
|
||||
JSON_NULL, /* Maps to a C NULL */
|
||||
JSON_OBJECT, /* Maps to a HashMap of JsonValues */
|
||||
JSON_ARRAY, /* Maps to an Array of JsonValues */
|
||||
JSON_STRING, /* Maps to a null-terminated C string */
|
||||
JSON_INTEGER, /* Maps to an Int64 */
|
||||
JSON_FLOAT, /* Maps to a C double */
|
||||
JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */
|
||||
} JsonType;
|
||||
|
||||
/**
|
||||
* Determine the type of the specified JSON value.
|
||||
*/
|
||||
extern JsonType JsonValueType(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a JSON object as a JSON value that can be added to another
|
||||
* object, or an array.
|
||||
*/
|
||||
extern JsonValue * JsonValueObject(HashMap *);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents an object. This function will
|
||||
* return NULL if the value is not actually an object.
|
||||
*/
|
||||
extern HashMap * JsonValueAsObject(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a JSON array as a JSON value that can be added to an object
|
||||
* or another array.
|
||||
*/
|
||||
extern JsonValue * JsonValueArray(Array *);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents an array. This function will
|
||||
* return NULL if the value is not actually an array.
|
||||
*/
|
||||
extern Array * JsonValueAsArray(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a C string as a JSON value that can be added to an object or
|
||||
* an array.
|
||||
*/
|
||||
extern JsonValue * JsonValueString(char *);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents a string. This function will
|
||||
* return NULL if the value is not actually a string.
|
||||
*/
|
||||
extern char * JsonValueAsString(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a number as a JSON value that can be added to an object or
|
||||
* an array.
|
||||
*/
|
||||
extern JsonValue * JsonValueInteger(uint64_t);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents a number. This function will
|
||||
* return 0 if the value is not actually a number, which may be
|
||||
* misleading. Check the type of the value before making assumptions
|
||||
* about its value.
|
||||
*/
|
||||
extern uint64_t JsonValueAsInteger(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a floating point number as a JSON value that can be added
|
||||
* to an object or an array.
|
||||
*/
|
||||
extern JsonValue * JsonValueFloat(double);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents a floating point number. This
|
||||
* function will return 0 if the value is not actually a floating
|
||||
* point number, which may be misleading. Check the type of the value
|
||||
* before making assumptions about its type.
|
||||
*/
|
||||
extern double JsonValueAsFloat(JsonValue *);
|
||||
|
||||
/**
|
||||
* Encode a C integer according to the way C treats integers in boolean
|
||||
* expressions as a JSON value that can be added to an object or an
|
||||
* array.
|
||||
*/
|
||||
extern JsonValue * JsonValueBoolean(bool);
|
||||
|
||||
/**
|
||||
* Unwrap a JSON value that represents a boolean. This function will
|
||||
* return 0 if the value is not actually a boolean, which may be
|
||||
* misleading. Check the type of the value before making assumptions
|
||||
* about its type.
|
||||
*/
|
||||
extern bool JsonValueAsBoolean(JsonValue *);
|
||||
|
||||
/**
|
||||
* This is a special case that represents a JSON null. Because the
|
||||
* Array and HashMap APIs do not accept NULL values, this function
|
||||
* should be used to represent NULL in JSON. Even though a small
|
||||
* amount of memory is allocated just to be a placeholder for nothing,
|
||||
* this keeps the APIs clean.
|
||||
*/
|
||||
extern JsonValue * JsonValueNull(void);
|
||||
|
||||
/**
|
||||
* Free the memory being used by a JSON value. Note that this will
|
||||
* recursively free all Arrays, HashMaps, and other JsonValues that are
|
||||
* reachable from the given value, including any strings attached to
|
||||
* this value.
|
||||
*/
|
||||
extern void JsonValueFree(JsonValue *);
|
||||
|
||||
/**
|
||||
* Recursively duplicate the given JSON value. This returns a new
|
||||
* JSON value that is completely identical to the specified value, but
|
||||
* in no way connected to it.
|
||||
*/
|
||||
extern JsonValue * JsonValueDuplicate(JsonValue *);
|
||||
|
||||
/**
|
||||
* Recursively duplicate the given JSON object. This returns a new
|
||||
* JSON object that is completely identical to the specified object,
|
||||
* but in no way connect to it.
|
||||
*/
|
||||
extern HashMap * JsonDuplicate(HashMap *);
|
||||
|
||||
/**
|
||||
* Recursively free a JSON object by iterating over all of its values
|
||||
* and freeing them using
|
||||
* .Fn JsonValueFree .
|
||||
*/
|
||||
extern void JsonFree(HashMap *);
|
||||
|
||||
/**
|
||||
* Encode the given string in such a way that it can be safely
|
||||
* embedded in a JSON stream. This entails:
|
||||
* .Bl -bullet -offset indent
|
||||
* .It
|
||||
* Escaping quotes, backslashes, and other special characters using
|
||||
* their backslash escape.
|
||||
* .It
|
||||
* Encoding bytes that are not UTF-8 using escapes.
|
||||
* .It
|
||||
* Wrapping the entire string in double quotes.
|
||||
* .El
|
||||
* .Pp
|
||||
* This function is only provided via the public
|
||||
* .Nm
|
||||
* API so that it is accessible to custom JSON encoders, such as the
|
||||
* CanonicalJson encoder. This will typically be used for encoding
|
||||
* object keys; to encode values, just use
|
||||
* .Fn JsonEncodeValue .
|
||||
* .Pp
|
||||
* This function returns the number of bytes written to the stream,
|
||||
* or if the stream is NULL, the number of bytes that would have
|
||||
* been written.
|
||||
*/
|
||||
extern size_t JsonEncodeString(const char *, Stream *);
|
||||
|
||||
/**
|
||||
* Serialize a JSON value as it would appear in JSON output. This is
|
||||
* a recursive function that also encodes all child values reachable
|
||||
* from the given value. This function is exposed via the public
|
||||
* .Nm
|
||||
* API so that it is accessible to custom JSON encoders. Normal users
|
||||
* that are not writing custom encoders should in most cases just use
|
||||
* .Fn JsonEncode
|
||||
* to encode an entire object.
|
||||
* .Pp
|
||||
* The third parameter is an integer that represents the indent level
|
||||
* of the value to be printed, or a negative number if pretty-printing
|
||||
* should be disabled and JSON should be printed as minimized as
|
||||
* possible. To pretty-print a JSON object, set this to
|
||||
* .Va JSON_PRETTY .
|
||||
* To get minified output, set it to
|
||||
* .Va JSON_DEFAULT .
|
||||
* .Pp
|
||||
* This function returns the number of bytes written to the stream,
|
||||
* or if the stream is NULL, the number of bytes that would have
|
||||
* been written.
|
||||
*/
|
||||
extern size_t JsonEncodeValue(JsonValue *, Stream *, int);
|
||||
|
||||
/**
|
||||
* Encode a JSON object as it would appear in JSON output, writing it
|
||||
* to the given output stream. This function is recursive; it will
|
||||
* serialize everything accessible from the passed object. The third
|
||||
* parameter has the same behavior as described above.
|
||||
* .Pp
|
||||
* This function returns the number of bytes written to the stream,
|
||||
* or if the stream is NULL, the number of bytes that would have
|
||||
* been written.
|
||||
*/
|
||||
extern size_t JsonEncode(HashMap *, Stream *, int);
|
||||
|
||||
/**
|
||||
* Decode a JSON object from the given input stream and parse it into
|
||||
* a hash map of JSON values.
|
||||
*/
|
||||
extern HashMap * JsonDecode(Stream *);
|
||||
|
||||
/**
|
||||
* A convenience function that allows the caller to retrieve and
|
||||
* arbitrarily deep keys within a JSON object. It takes a root JSON
|
||||
* object, the number of levels deep to go, and then that number of
|
||||
* keys as a varargs list. All keys must have objects as values, with
|
||||
* the exception of the last one, which is the value that will be
|
||||
* returned. Otherwise, NULL indicates the specified path doas not
|
||||
* exist.
|
||||
*/
|
||||
extern JsonValue * JsonGet(HashMap *, size_t,...);
|
||||
|
||||
/**
|
||||
* A convenience function that allows the caller to set arbitrarily
|
||||
* deep keys within a JSON object. It takes a root JSON object, the
|
||||
* number of levels deep to go, and then that number of keys as a
|
||||
* varargs list. All keys must have object as values, with the
|
||||
* exception of the last one, which is the value that will be set.
|
||||
* The value currently at that key, if any, will be returned.
|
||||
* This function will create any intermediate objects as necessary to
|
||||
* set the proper key.
|
||||
*/
|
||||
extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...);
|
||||
|
||||
/**
|
||||
* Recursively merge two JSON objects. The second object is merged
|
||||
* on top of the first; any keys present in the first object that are
|
||||
* also present in the second object are replaced with those in the
|
||||
* second object, and any keys present in the second object that are
|
||||
* not present in the first object are copied to the first object.
|
||||
*/
|
||||
extern void JsonMerge(HashMap *, HashMap *);
|
||||
|
||||
#endif /* CYTOPLASM_JSON_H */
|
196
include/Cytoplasm/Log.h
Normal file
196
include/Cytoplasm/Log.h
Normal file
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_LOG_H
|
||||
#define CYTOPLASM_LOG_H
|
||||
|
||||
/***
|
||||
* @Nm Log
|
||||
* @Nd A simple logging framework for logging to multiple destinations.
|
||||
* @Dd April 27 2023
|
||||
* @Xr Stream
|
||||
*
|
||||
* .Nm
|
||||
* is a simple C logging library that allows for colorful outputs,
|
||||
* timestamps, and custom log levels. It also features the ability to
|
||||
* have multiple logs open at one time, although Cytoplasm primarily
|
||||
* utilizes the global log. All logs are thread safe.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
#define LOG_FLAG_COLOR (1 << 0)
|
||||
#define LOG_FLAG_SYSLOG (1 << 1)
|
||||
|
||||
/**
|
||||
* A log is defined as a configuration that describes the properties
|
||||
* of the log. This opaque structure can be manipulated by the
|
||||
* functions defined in this API.
|
||||
*/
|
||||
typedef struct LogConfig LogConfig;
|
||||
|
||||
/**
|
||||
* Create a new log configuration with sane defaults that can be used
|
||||
* immediately with the logging functions.
|
||||
*/
|
||||
extern LogConfig * LogConfigCreate(void);
|
||||
|
||||
/**
|
||||
* Get the global log configuration, creating a new one with
|
||||
* .Fn LogConfigCreate
|
||||
* if necessary.
|
||||
*/
|
||||
extern LogConfig * LogConfigGlobal(void);
|
||||
|
||||
/**
|
||||
* Free the given log configuration. Note that this does not close the
|
||||
* underlying stream associated with the log, if there is one. Also
|
||||
* note that to avoid memory leaks, the global log configuration must
|
||||
* also be freed, but it cannot be used after it is freed.
|
||||
*/
|
||||
extern void LogConfigFree(LogConfig *);
|
||||
|
||||
/**
|
||||
* Set the current log level on the specified log configuration.
|
||||
* This indicates that only messages at or above this level should be
|
||||
* logged; all others are silently discarded. The passed log level
|
||||
* should be one of the log levels defined by
|
||||
* .Xr syslog 3 .
|
||||
* Refer to that page for a complete list of acceptable log levels,
|
||||
* and note that passing an invalid log level will result in undefined
|
||||
* behavior.
|
||||
*/
|
||||
extern void LogConfigLevelSet(LogConfig *, int);
|
||||
|
||||
/**
|
||||
* Cause the log output to be indented two more spaces than it was
|
||||
* previously. This can be helpful when generating stack traces or
|
||||
* other hierarchical output. This is a simple convenience wrapper
|
||||
* around
|
||||
* .Fn LogConfigIndentSet .
|
||||
*/
|
||||
extern void LogConfigIndent(LogConfig *);
|
||||
|
||||
/**
|
||||
* Cause the log output to be indented two less spaces than it was
|
||||
* previously. This is a simple convenience wrapper around
|
||||
* .Fn LogConfigIndentSet .
|
||||
*/
|
||||
extern void LogConfigUnindent(LogConfig *);
|
||||
|
||||
/**
|
||||
* Indent future log output using the specified config by some
|
||||
* arbitrary amount.
|
||||
*/
|
||||
extern void LogConfigIndentSet(LogConfig *, size_t);
|
||||
|
||||
/**
|
||||
* Set the file stream that logging output should be written to. This
|
||||
* defaults to standard output, but it can be set to standard error,
|
||||
* or any other arbitrary stream. Passing a NULL value for the stream
|
||||
* pointer sets the log output to the standard output. Note that the
|
||||
* output stream is only used if
|
||||
* .Va LOG_FLAG_SYSLOG
|
||||
* is not set.
|
||||
*/
|
||||
extern void LogConfigOutputSet(LogConfig *, Stream *);
|
||||
|
||||
/**
|
||||
* Set a number of boolean options on a log configuration. This
|
||||
* function uses bitwise operators, so multiple options can be set with
|
||||
* a single function call using bitwise OR operators. The flags are
|
||||
* defined as preprocessor macros, and are as follows:
|
||||
* .Bl -tag -width Ds
|
||||
* .It LOG_FLAG_COLOR
|
||||
* When set, enable color-coded output on TTYs. Note that colors are
|
||||
* implemented as ANSI escape sequences, and are not written to file
|
||||
* streams that are not actually connected to a TTY, to prevent those
|
||||
* sequences from being written to a file.
|
||||
* .Xr isatty 3
|
||||
* is checked before writing any terminal sequences.
|
||||
* .It LOG_FLAG_SYSLOG
|
||||
* When set, log output to the syslog using
|
||||
* .Xr syslog 3 ,
|
||||
* instead of logging to the file set by
|
||||
* .Fn LogConfigOutputSet .
|
||||
* This flag always overrides the stream set by that function,
|
||||
* regardless of when it was set, even if it was set after this flag
|
||||
* was set.
|
||||
* .El
|
||||
*/
|
||||
extern void LogConfigFlagSet(LogConfig *, int);
|
||||
|
||||
/**
|
||||
* Clear a boolean flag from the specified log format. See above for
|
||||
* the list of flags.
|
||||
*/
|
||||
extern void LogConfigFlagClear(LogConfig *, int);
|
||||
|
||||
/**
|
||||
* Set a custom timestamp to be prepended to each message if the
|
||||
* output is not going to the system log. Consult your system's
|
||||
* documentation for
|
||||
* .Xr strftime 3 .
|
||||
* A value of NULL disables the timestamp output before messages.
|
||||
*/
|
||||
extern void LogConfigTimeStampFormatSet(LogConfig *, char *);
|
||||
|
||||
/**
|
||||
* This function does the actual logging of messages using a
|
||||
* specified configuration. It takes the configuration, the log
|
||||
* level, a format string, and then a list of arguments, all in that
|
||||
* order. This function only logs messages if their level is above
|
||||
* or equal to the currently configured log level, making it easy to
|
||||
* turn some messages on or off.
|
||||
* .Pp
|
||||
* This function has the same usage as
|
||||
* .Xr vprintf 3 .
|
||||
* Consult that page for the list of format specifiers and their
|
||||
* arguments. This function is typically not used directly, see the
|
||||
* other log functions for the most common use cases.
|
||||
*/
|
||||
extern void Logv(LogConfig *, int, const char *, va_list);
|
||||
|
||||
/**
|
||||
* Log a message using
|
||||
* .Fn Logv .
|
||||
* with the specified configuration. This function has the same usage
|
||||
* as
|
||||
* .Xr printf 3 .
|
||||
*/
|
||||
extern void LogTo(LogConfig *, int, const char *, ...);
|
||||
|
||||
/**
|
||||
* Log a message to the global log using
|
||||
* .Fn Logv .
|
||||
* This function has the same usage as
|
||||
* .Xr printf 3 .
|
||||
*/
|
||||
extern void Log(int, const char *, ...);
|
||||
|
||||
#endif
|
236
include/Cytoplasm/Memory.h
Normal file
236
include/Cytoplasm/Memory.h
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_MEMORY_H
|
||||
#define CYTOPLASM_MEMORY_H
|
||||
|
||||
/***
|
||||
* @Nm Memory
|
||||
* @Nd Smart memory management.
|
||||
* @Dd January 9 2023
|
||||
*
|
||||
* .Nm
|
||||
* is an API that allows for smart memory management and profiling. It
|
||||
* wraps the standard library functions
|
||||
* .Xr malloc 3 ,
|
||||
* .Xr realloc 3 ,
|
||||
* and
|
||||
* .Xr free 3 ,
|
||||
* and offers identical semantics, while providing functionality that
|
||||
* the standard library doesn't have, such as getting statistics on the
|
||||
* total memory allocated on the heap, and getting the size of a block
|
||||
* given a pointer. Additionally, thanks to preprocessor macros, the
|
||||
* exact file and line number at which an allocation, re-allocation, or
|
||||
* free occured can be obtained given a pointer. Finally, all the
|
||||
* blocks allocated on the heap can be iterated and evaluated, and a
|
||||
* callback function can be executed every time a memory operation
|
||||
* occurs.
|
||||
* .Pp
|
||||
* In the future, this API could include a garbage collector that
|
||||
* automatically frees memory it detects as being no longer in use.
|
||||
* However, this feature does not yet exist.
|
||||
* .Pp
|
||||
* A number of macros are available, which make the
|
||||
* .Nm
|
||||
* API much easier to use. They are as follows:
|
||||
* .Bl -bullet -offset indent
|
||||
* .It
|
||||
* .Fn Malloc "x"
|
||||
* .It
|
||||
* .Fn Realloc "x" "y"
|
||||
* .It
|
||||
* .Fn Free "x"
|
||||
* .El
|
||||
* .Pp
|
||||
* These macros expand to
|
||||
* .Fn MemoryAllocate ,
|
||||
* .Fn MemoryReallocate ,
|
||||
* and
|
||||
* .Fn MemoryFree
|
||||
* with the second and third parameters set to __FILE__ and __LINE__.
|
||||
* This allows
|
||||
* .Nm
|
||||
* to be used exactly how the standard library functions would be
|
||||
* used. In fact, the functions to which these macros expand are not
|
||||
* intended to be used directly; for the best results, use these
|
||||
* macros.
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* These values are passed into the memory hook function to indicate
|
||||
* the action that just happened.
|
||||
*/
|
||||
typedef enum MemoryAction
|
||||
{
|
||||
MEMORY_ALLOCATE,
|
||||
MEMORY_REALLOCATE,
|
||||
MEMORY_FREE,
|
||||
MEMORY_BAD_POINTER,
|
||||
MEMORY_CORRUPTED
|
||||
} MemoryAction;
|
||||
|
||||
#define Malloc(x) MemoryAllocate(x, __FILE__, __LINE__)
|
||||
#define Realloc(x, s) MemoryReallocate(x, s, __FILE__, __LINE__)
|
||||
#define Free(x) MemoryFree(x, __FILE__, __LINE__)
|
||||
|
||||
/**
|
||||
* The memory information is opaque, but can be accessed using the
|
||||
* functions defined by this API.
|
||||
*/
|
||||
typedef struct MemoryInfo MemoryInfo;
|
||||
|
||||
/**
|
||||
* Allocate the specified number of bytes on the heap. This function
|
||||
* has the same semantics as
|
||||
* .Xr malloc 3 ,
|
||||
* except that it takes the file name and line number at which the
|
||||
* allocation occurred.
|
||||
*/
|
||||
extern void * MemoryAllocate(size_t, const char *, int);
|
||||
|
||||
/**
|
||||
* Change the size of the object pointed to by the given pointer
|
||||
* to the given number of bytes. This function has the same semantics
|
||||
* as
|
||||
* .Xr realloc 3 ,
|
||||
* except that it takes the file name and line number at which the
|
||||
* reallocation occurred.
|
||||
*/
|
||||
extern void * MemoryReallocate(void *, size_t, const char *, int);
|
||||
|
||||
/**
|
||||
* Free the memory at the given pointer. This function has the same
|
||||
* semantics as
|
||||
* .Xr free 3 ,
|
||||
* except that it takes the file name and line number at which the
|
||||
* free occurred.
|
||||
*/
|
||||
extern void MemoryFree(void *, const char *, int);
|
||||
|
||||
/**
|
||||
* Get the total number of bytes that the program has allocated on the
|
||||
* heap. This operation iterates over all heap allocations made with
|
||||
* .Fn MemoryAllocate
|
||||
* and then returns a total count, in bytes.
|
||||
*/
|
||||
extern size_t MemoryAllocated(void);
|
||||
|
||||
/**
|
||||
* Iterate over all heap allocations made with
|
||||
* .Fn MemoryAllocate
|
||||
* and call
|
||||
* .Fn MemoryFree
|
||||
* on them. This function immediately invalidates all pointers to
|
||||
* blocks on the heap, and any subsequent attempt to read or write to
|
||||
* data on the heap will result in undefined behavior. This is
|
||||
* typically called at the end of the program, just before exit.
|
||||
*/
|
||||
extern void MemoryFreeAll(void);
|
||||
|
||||
/**
|
||||
* Fetch information about an allocation. This function takes a raw
|
||||
* pointer, and if
|
||||
* . Nm
|
||||
* knows about the pointer, it returns a structure that can be used
|
||||
* to obtain information about the block of memory that the pointer
|
||||
* points to.
|
||||
*/
|
||||
extern MemoryInfo * MemoryInfoGet(void *);
|
||||
|
||||
/**
|
||||
* Get the size in bytes of the block of memory represented by the
|
||||
* specified memory info structure.
|
||||
*/
|
||||
extern size_t MemoryInfoGetSize(MemoryInfo *);
|
||||
|
||||
/**
|
||||
* Get the file name in which the block of memory represented by the
|
||||
* specified memory info structure was allocated.
|
||||
*/
|
||||
extern const char * MemoryInfoGetFile(MemoryInfo *);
|
||||
|
||||
/**
|
||||
* Get the line number on which the block of memory represented by the
|
||||
* specified memory info structure was allocated.
|
||||
*/
|
||||
extern int MemoryInfoGetLine(MemoryInfo *);
|
||||
|
||||
/**
|
||||
* Get a pointer to the block of memory represented by the specified
|
||||
* memory info structure.
|
||||
*/
|
||||
extern void * MemoryInfoGetPointer(MemoryInfo *);
|
||||
|
||||
/**
|
||||
* This function takes a pointer to a function that takes the memory
|
||||
* info structure, as well as a void pointer for caller-provided
|
||||
* arguments. It iterates over all the heap memory currently allocated
|
||||
* at the time of calling, executing the function on each allocation.
|
||||
*/
|
||||
extern void MemoryIterate(void (*) (MemoryInfo *, void *), void *);
|
||||
|
||||
/**
|
||||
* Specify a function to be executed whenever a memory operation
|
||||
* occurs. The MemoryAction argument specifies the operation that
|
||||
* occurred on the block of memory represented by the memory info
|
||||
* structure. The function also takes a void pointer to caller-provided
|
||||
* arguments.
|
||||
*/
|
||||
extern void MemoryHook(void (*) (MemoryAction, MemoryInfo *, void *), void *);
|
||||
|
||||
/**
|
||||
* The default memory hook, which has sane behavior and is installed
|
||||
* at runtime. This function does not use any memory on the heap,
|
||||
* except for the MemoryInfo passed to it, which it assumes to be
|
||||
* valid. Everything else happens on the stack only, to ensure that
|
||||
* the hook doesn't make any memory problems worse.
|
||||
*/
|
||||
extern void MemoryDefaultHook(MemoryAction, MemoryInfo *, void *);
|
||||
|
||||
/**
|
||||
* Read over the block of memory represented by the given memory info
|
||||
* structure and generate a hexadecimal and ASCII string for each
|
||||
* chunk of the block. This function takes a callback function that
|
||||
* takes the following parameters in order:
|
||||
* .Bl -bullet -offset indent
|
||||
* .It
|
||||
* The current offset from the beginning of the block of memory in
|
||||
* bytes.
|
||||
* .It
|
||||
* A null-terminated string containing the next 16 bytes of the block
|
||||
* encoded as space-separated hex values.
|
||||
* .It
|
||||
* A null-terminated string containing the ASCII representation of the
|
||||
* same 16 bytes of memory. This ASCII representation is safe to print
|
||||
* to a terminal or other text device, because non-printable characters
|
||||
* are encoded as a . (period).
|
||||
* .It
|
||||
* Caller-passed pointer.
|
||||
* .El
|
||||
*/
|
||||
extern void
|
||||
MemoryHexDump(MemoryInfo *, void (*) (size_t, char *, char *, void *), void *);
|
||||
|
||||
#endif
|
106
include/Cytoplasm/Queue.h
Normal file
106
include/Cytoplasm/Queue.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_QUEUE_H
|
||||
#define CYTOPLASM_QUEUE_H
|
||||
|
||||
/***
|
||||
* @Nm Queue
|
||||
* @Nd A simple static queue data structure.
|
||||
* @Dd November 25 2022
|
||||
* @Xr Array HashMap
|
||||
*
|
||||
* .Nm
|
||||
* implements a simple queue data structure that is statically sized.
|
||||
* This implementation does not actually store the values of the items
|
||||
* in it; it only stores pointers to the data. As such, you will have
|
||||
* to manually maintain data and make sure it remains valid as long as
|
||||
* it is in the queue. The advantage of this is that
|
||||
* .Nm
|
||||
* doesn't have to copy data, and thus doesn't care how big the data
|
||||
* is. Furthermore, any arbitrary data can be stored in the queue.
|
||||
* .Pp
|
||||
* This queue implementation operates on the heap. It is a circular
|
||||
* queue, and it does not grow as it is used. Once the size is set,
|
||||
* the queue never gets any bigger.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* These functions operate on a queue structure that is opaque to the
|
||||
* caller.
|
||||
*/
|
||||
typedef struct Queue Queue;
|
||||
|
||||
/**
|
||||
* Allocate a new queue that is able to store the specified number of
|
||||
* items in it.
|
||||
*/
|
||||
extern Queue * QueueCreate(size_t);
|
||||
|
||||
/**
|
||||
* Free the memory associated with the specified queue structure. Note
|
||||
* that this function does not free any of the values stored in the
|
||||
* queue; it is the caller's job to manage memory for each item.
|
||||
* Typically, the caller would dequeue all the items in the queue and
|
||||
* deal with them before freeing the queue itself.
|
||||
*/
|
||||
extern void QueueFree(Queue *);
|
||||
|
||||
/**
|
||||
* Push an element into the queue. This function returns a boolean
|
||||
* value indicating whether or not the push succeeded. Pushing items
|
||||
* into the queue will fail if the queue is full.
|
||||
*/
|
||||
extern bool QueuePush(Queue *, void *);
|
||||
|
||||
/**
|
||||
* Pop an element out of the queue. This function returns NULL if the
|
||||
* queue is empty. Otherwise, it returns a pointer to the item that is
|
||||
* next up in the queue.
|
||||
*/
|
||||
extern void * QueuePop(Queue *);
|
||||
|
||||
/**
|
||||
* Retrieve a pointer to the item that is next up in the queue without
|
||||
* actually discarding it, such that the next call to
|
||||
* .Fn QueuePeek
|
||||
* or
|
||||
* .Fn QueuePop
|
||||
* will return the same pointer.
|
||||
*/
|
||||
extern void * QueuePeek(Queue *);
|
||||
|
||||
/**
|
||||
* Determine whether or not the queue is full.
|
||||
*/
|
||||
extern bool QueueFull(Queue *);
|
||||
|
||||
/**
|
||||
* Determine whether or not the queue is empty.
|
||||
*/
|
||||
extern bool QueueEmpty(Queue *);
|
||||
|
||||
#endif
|
83
include/Cytoplasm/Rand.h
Normal file
83
include/Cytoplasm/Rand.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_RAND_H
|
||||
#define CYTOPLASM_RAND_H
|
||||
|
||||
/***
|
||||
* @Nm Rand
|
||||
* @Nd Thread-safe random numbers.
|
||||
* @Dd February 16 2023
|
||||
* @Xr Util
|
||||
*
|
||||
* .Nm
|
||||
* is used for generating random numbers in a thread-safe way.
|
||||
* Currently, one generator state is shared across all threads, which
|
||||
* means that only one thread can generate random numbers at a time.
|
||||
* This state is protected with a mutex to guarantee this behavior.
|
||||
* In the future, a seed pool may be maintained to allow multiple
|
||||
* threads to generate random numbers at the same time.
|
||||
* .Pp
|
||||
* The generator state is seeded on the first call to a function that
|
||||
* needs it. The seed is determined by the current timestamp, the ID
|
||||
* of the process, and the thread ID. These should all be sufficiently
|
||||
* random sources, so the seed should be secure enough.
|
||||
* .Pp
|
||||
* .Nm
|
||||
* currently uses a simple Mersenne Twister algorithm to generate
|
||||
* random numbers. This algorithm was chosen because it is extremely
|
||||
* popular and widespread. While it is likely not cryptographically
|
||||
* secure, and does suffer some unfortunate pitfalls, this algorithm
|
||||
* has stood the test of time and is simple enough to implement, so
|
||||
* it was chosen over the alternatives.
|
||||
* .Pp
|
||||
* .Nm
|
||||
* does not use any random number generator functions from the C
|
||||
* standard library, since these are often flawed.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Generate a single random 32-bit integer between 0 and the
|
||||
* passed value.
|
||||
*/
|
||||
extern uint32_t RandInt(uint32_t);
|
||||
|
||||
/**
|
||||
* Generate the number of integers specified by the second argument
|
||||
* storing them into the buffer pointed to in the first argument.
|
||||
* Ensure that each number is between 0 and the third argument.
|
||||
* .Pp
|
||||
* This function allows a caller to get multiple random numbers at once
|
||||
* in a more efficient manner than repeatedly calling
|
||||
* .Fn RandInt ,
|
||||
* since each call to these functions
|
||||
* has to lock and unlock a mutex. It is therefore better to obtain
|
||||
* multiple random numbers in one pass if multiple are needed.
|
||||
*/
|
||||
extern void RandIntN(uint32_t *, size_t, uint32_t);
|
||||
|
||||
#endif /* CYTOPLASM_RAND_H */
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022-2024 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
|
||||
|
@ -22,29 +21,31 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#ifndef TELODENDRIA_FILTER_H
|
||||
#define TELODENDRIA_FILTER_H
|
||||
|
||||
#include <Schema/Filter.h>
|
||||
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#ifndef CYTOPLASM_RUNTIME_H
|
||||
#define CYTOPLASM_RUNTIME_H
|
||||
|
||||
/***
|
||||
* @Nm Filter
|
||||
* @Nd Validate JSON filters and apply them to events.
|
||||
* @Dd June 17 2023
|
||||
* @Nm Runtime
|
||||
* @Nd Supporting functions for the Cytoplasm runtime.
|
||||
* @Dd May 23 2023
|
||||
* @Xr Memory
|
||||
*
|
||||
* The Matrix Client-Server API defines a mechanism for defining
|
||||
* filters and applying them to certain endpoints. This API allows
|
||||
* those filters to be validated and applied to events.
|
||||
* .Nm
|
||||
* provides supporting functions for the Cytoplasm runtime. These
|
||||
* functions are not intended to be called directly by programs,
|
||||
* but are used internally. They're exposed via a header because
|
||||
* the runtime stub needs to know their definitions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Apply the given filter to the given event, returning a
|
||||
* new event JSON with the filter applied, or NULL if the event
|
||||
* is excluded totally by the rules of the filter.
|
||||
* Write a memory report to a file in the current directory, using
|
||||
* the provided program arguments, including the program name that
|
||||
* executed. This function is to be called after all memory is
|
||||
* supposed to have been freed. It iterates over all remaining
|
||||
* memory and generates a text file containing all of the
|
||||
* recorded information about each block, including a hex dump of
|
||||
* the data stored in them.
|
||||
*/
|
||||
extern HashMap *
|
||||
FilterApply(Filter *, HashMap *);
|
||||
extern void GenerateMemoryReport(int argc, char **argv);
|
||||
|
||||
#endif /* TELODENDRIA_FILTER_H */
|
||||
#endif /* CYTOPLASM_RUNTIME_H */
|
76
include/Cytoplasm/Sha.h
Normal file
76
include/Cytoplasm/Sha.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_SHA_H
|
||||
#define CYTOPLASM_SHA_H
|
||||
|
||||
/***
|
||||
* @Nm Sha
|
||||
* @Nd A simple implementation of a few SHA hashing functions.
|
||||
* @Dd December 19 2022
|
||||
* @Xr Memory Base64
|
||||
*
|
||||
* This API defines simple functions for computing SHA hashes.
|
||||
* At the moment, it only defines
|
||||
* .Fn Sha256
|
||||
* and
|
||||
* .Fn Sha1 ,
|
||||
* which compute the SHA-256 and SHA-1 hashes of the given C string,
|
||||
* respectively. It is not trivial to implement SHA-512 in ANSI C
|
||||
* due to the lack of a 64-bit integer type, so that hash
|
||||
* function has been omitted.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function takes a pointer to a NULL-terminated C string, and
|
||||
* returns a NULL-terminated byte buffer allocated on the heap using
|
||||
* the Memory API, or NULL if there was an error allocating memory.
|
||||
* The returned byte buffer should be freed when it is no longer
|
||||
* needed. It is important to note that the returned buffer is not
|
||||
* a printable string; to get a printable string, use
|
||||
* .Fn ShaToHex .
|
||||
*/
|
||||
extern unsigned char * Sha256(char *);
|
||||
|
||||
/**
|
||||
* This function takes a pointer to a NULL-terminated C string, and
|
||||
* returns a NULL-terminated byte buffer allocated on the heap using
|
||||
* the Memory API, or NULL if there was an error allocating memory.
|
||||
* The returned byte buffer should be freed when it is no longer
|
||||
* needed. It is important to note that the returned buffer is not
|
||||
* a printable string; to get a printable string, use
|
||||
* .Fn ShaToHex .
|
||||
*/
|
||||
extern unsigned char * Sha1(char *);
|
||||
|
||||
/**
|
||||
* Convert a SHA byte buffer into a hex string. These hex strings
|
||||
* are typically what is transmitted, stored, and compared, however
|
||||
* there may be times when it is necessary to work with the raw
|
||||
* bytes directly, which is why the conversion to a hex string is
|
||||
* a separate step.
|
||||
*/
|
||||
extern char * ShaToHex(unsigned char *);
|
||||
|
||||
#endif /* CYTOPLASM_SHA_H */
|
129
include/Cytoplasm/Str.h
Normal file
129
include/Cytoplasm/Str.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_STR_H
|
||||
#define CYTOPLASM_STR_H
|
||||
|
||||
/***
|
||||
* @Nm Str
|
||||
* @Nd Functions for creating and manipulating strings.
|
||||
* @Dd February 15 2023
|
||||
* @Xr Memory
|
||||
*
|
||||
* .Nm
|
||||
* provides string-related functions. It is called
|
||||
* .Nm ,
|
||||
* not String, because some platforms (Windows) do not have
|
||||
* case-sensitive filesystems, which poses a problem since
|
||||
* .Pa string.h
|
||||
* is a standard library header.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Convert UTF-16 into a Unicode codepoint.
|
||||
*/
|
||||
extern uint32_t StrUtf16Decode(uint16_t, uint16_t);
|
||||
|
||||
/**
|
||||
* Take a Unicode codepoint and encode it into a string buffer containing
|
||||
* between 1 and 4 bytes. The string buffer is allocated on the heap,
|
||||
* so it should be freed when it is no longer needed.
|
||||
*/
|
||||
extern char * StrUtf8Encode(uint32_t);
|
||||
|
||||
/**
|
||||
* Duplicate a null-terminated string, returning a new string on the
|
||||
* heap. This is useful when a function takes in a string that it needs
|
||||
* to store for long amounts of time, even perhaps after the original
|
||||
* string is gone.
|
||||
*/
|
||||
extern char * StrDuplicate(const char *);
|
||||
|
||||
/**
|
||||
* Extract part of a null-terminated string, returning a new string on
|
||||
* the heap containing only the requested subsection. Like the
|
||||
* substring functions included with most programming languages, the
|
||||
* starting index is inclusive, and the ending index is exclusive.
|
||||
*/
|
||||
extern char * StrSubstr(const char *, size_t, size_t);
|
||||
|
||||
/**
|
||||
* A varargs function that takes a number of null-terminated strings
|
||||
* specified by the first argument, and returns a new string that
|
||||
* contains their concatenation. It works similarly to
|
||||
* .Xr strcat 3 ,
|
||||
* but it takes care of allocating memory big enough to hold all the
|
||||
* strings. Any string in the list may be NULL. If a NULL pointer is
|
||||
* passed, it is treated like an empty string.
|
||||
*/
|
||||
extern char * StrConcat(size_t,...);
|
||||
|
||||
/**
|
||||
* Return a boolean value indicating whether or not the null-terminated
|
||||
* string consists only of blank characters, as determined by
|
||||
* .Xr isblank 3 .
|
||||
*/
|
||||
extern bool StrBlank(const char *str);
|
||||
|
||||
/**
|
||||
* Generate a string of the specified length, containing random
|
||||
* lowercase and uppercase letters.
|
||||
*/
|
||||
extern char * StrRandom(size_t);
|
||||
|
||||
/**
|
||||
* Convert the specified integer into a string, returning the string
|
||||
* on the heap, or NULL if there was a memory allocation error. The
|
||||
* returned string should be freed by the caller after it is no longer
|
||||
* needed.
|
||||
*/
|
||||
extern char * StrInt(long);
|
||||
|
||||
/**
|
||||
* Converts a string into a lowercase version of it using
|
||||
* .Xr tolower 3 ,
|
||||
* returning the lowercase version on the heap, or NULL if there was
|
||||
* a memory allocation error. The returned string should be freed by
|
||||
* the caller after it is no longer needed.
|
||||
*/
|
||||
extern char * StrLower(char *);
|
||||
|
||||
/**
|
||||
* Compare two strings and determine whether or not they are equal.
|
||||
* This is the most common use case of strcmp() in Cytoplasm, but
|
||||
* strcmp() doesn't like NULL pointers, so these have to be checked
|
||||
* explicitly and can cause problems if they aren't. This function,
|
||||
* on the other hand, makes NULL pointers special cases. If both
|
||||
* arguments are NULL, then they are considered equal. If only one
|
||||
* argument is NULL, they are considered not equal. Otherwise, if
|
||||
* no arguments are NULL, a regular strcmp() takes place and this
|
||||
* function returns a boolean value indicating whether or not
|
||||
* strcmp() returned 0.
|
||||
*/
|
||||
extern bool StrEquals(const char *, const char *);
|
||||
|
||||
#endif /* CYTOPLASM_STR_H */
|
225
include/Cytoplasm/Stream.h
Normal file
225
include/Cytoplasm/Stream.h
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_STREAM_H
|
||||
#define CYTOPLASM_STREAM_H
|
||||
|
||||
/***
|
||||
* @Nm Stream
|
||||
* @Nd An abstraction over the Io API that implements standard C I/O.
|
||||
* @Dd April 29 2023
|
||||
* @Xr Io
|
||||
*
|
||||
* .Nm
|
||||
* implements an abstraction layer over the Io API. This layer buffers
|
||||
* I/O and makes it much easier to work with, mimicking the standard
|
||||
* C library and offering some more convenience features.
|
||||
*/
|
||||
|
||||
#include "Io.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* An opaque structure analogous to C's FILE pointers.
|
||||
*/
|
||||
typedef struct Stream Stream;
|
||||
|
||||
/**
|
||||
* Create a new stream using the specified Io for underlying I/O
|
||||
* operations.
|
||||
*/
|
||||
extern Stream * StreamIo(Io * io);
|
||||
|
||||
/**
|
||||
* Create a new stream using the specified POSIX file descriptor.
|
||||
* This is a convenience function for calling
|
||||
* .Fn IoFd
|
||||
* and then passing the result into
|
||||
* .Fn StreamIo .
|
||||
*/
|
||||
extern Stream * StreamFd(int);
|
||||
|
||||
/**
|
||||
* Create a new stream using the specified C FILE pointer. This is a
|
||||
* convenience function for calling
|
||||
* .Fn IoFile
|
||||
* and then passing the result into
|
||||
* .Fn StreamIo .
|
||||
*/
|
||||
extern Stream * StreamFile(FILE *);
|
||||
|
||||
/**
|
||||
* Create a new stream using the specified path and mode. This is a
|
||||
* convenience function for calling
|
||||
* .Xr fopen 3
|
||||
* and then passing the result into
|
||||
* .Fn StreamFile .
|
||||
*/
|
||||
extern Stream * StreamOpen(const char *, const char *);
|
||||
|
||||
/**
|
||||
* Get a stream that writes to the standard output.
|
||||
*/
|
||||
extern Stream * StreamStdout(void);
|
||||
|
||||
/**
|
||||
* Get a stream that writes to the standard error.
|
||||
*/
|
||||
extern Stream * StreamStderr(void);
|
||||
|
||||
/**
|
||||
* Get a stream that reads from the standard input.
|
||||
*/
|
||||
extern Stream * StreamStdin(void);
|
||||
|
||||
/**
|
||||
* Close the stream. This flushes the buffers and closes the underlying
|
||||
* Io. It is analogous to the standard
|
||||
* .Xr fclose 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamClose(Stream *);
|
||||
|
||||
/**
|
||||
* Print a formatted string. This function is analogous to the standard
|
||||
* .Xr vfprintf 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamVprintf(Stream *, const char *, va_list);
|
||||
|
||||
/**
|
||||
* Print a formatted string. This function is analogous to the
|
||||
* standard
|
||||
* .Xr fprintf 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamPrintf(Stream *, const char *,...);
|
||||
|
||||
/**
|
||||
* Get a single character from a stream. This function is analogous to
|
||||
* the standard
|
||||
* .Xr fgetc 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamGetc(Stream *);
|
||||
|
||||
/**
|
||||
* Push a character back onto the input stream. This function is
|
||||
* analogous to the standard
|
||||
* .Xr ungetc 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamUngetc(Stream *, int);
|
||||
|
||||
/**
|
||||
* Write a single character to the stream. This function is analogous
|
||||
* to the standard
|
||||
* .Xr fputc 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamPutc(Stream *, int);
|
||||
|
||||
/**
|
||||
* Write a null-terminated string to the stream. This function is
|
||||
* analogous to the standard
|
||||
* .Xr fputs 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamPuts(Stream *, char *);
|
||||
|
||||
/**
|
||||
* Read at most the specified number of characters minus 1 from the
|
||||
* specified stream and store them at the memory located at the
|
||||
* specified pointer. This function is analogous to the standard
|
||||
* .Xr fgets 3
|
||||
* function.
|
||||
*/
|
||||
extern char * StreamGets(Stream *, char *, int);
|
||||
|
||||
/**
|
||||
* Set the file position indicator for the specified stream. This
|
||||
* function is analogous to the standard
|
||||
* .Xr fseeko
|
||||
* function.
|
||||
*/
|
||||
extern off_t StreamSeek(Stream *, off_t, int);
|
||||
|
||||
/**
|
||||
* Test the end-of-file indicator for the given stream, returning a
|
||||
* boolean value indicating whether or not it is set. This is analogous
|
||||
* to the standard
|
||||
* .Xr feof 3
|
||||
* function.
|
||||
*/
|
||||
extern bool StreamEof(Stream *);
|
||||
|
||||
/**
|
||||
* Test the stream for an error condition, returning a boolean value
|
||||
* indicating whether or not one is present. This is analogous to the
|
||||
* standard
|
||||
* .Xr ferror 3
|
||||
* function.
|
||||
*/
|
||||
extern bool StreamError(Stream *);
|
||||
|
||||
/**
|
||||
* Clear the error condition associated with the given stream, allowing
|
||||
* future reads or writes to potentially be successful. This functio
|
||||
* is analogous to the standard
|
||||
* .Xr clearerr 3
|
||||
* function.
|
||||
*/
|
||||
extern void StreamClearError(Stream *);
|
||||
|
||||
/**
|
||||
* Flush all buffered data using the streams underlying write function.
|
||||
* This function is analogous to the standard
|
||||
* .Xr fflush 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamFlush(Stream *);
|
||||
|
||||
/**
|
||||
* Read all the bytes from the first stream and write them to the
|
||||
* second stream. This is analogous to
|
||||
* .Fn IoCopy ,
|
||||
* but it uses the internal buffers of the streams. It is probably
|
||||
* less efficient than doing a
|
||||
* .Fn IoCopy
|
||||
* instead, but it is more convenient.
|
||||
*/
|
||||
extern ssize_t StreamCopy(Stream *, Stream *);
|
||||
|
||||
/**
|
||||
* Get the file descriptor associated with the given stream, or -1 if
|
||||
* the stream is not associated with any file descriptor. This function
|
||||
* is analogous to the standard
|
||||
* .Xr fileno 3
|
||||
* function.
|
||||
*/
|
||||
extern int StreamFileno(Stream *);
|
||||
|
||||
#endif /* CYTOPLASM_STREAM_H */
|
109
include/Cytoplasm/Tls.h
Normal file
109
include/Cytoplasm/Tls.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_TLS_H
|
||||
#define CYTOPLASM_TLS_H
|
||||
|
||||
/***
|
||||
* @Nm Tls
|
||||
* @Nd Interface to platform-dependent TLS libraries.
|
||||
* @Dd April 29 2023
|
||||
* @Xr Stream Io
|
||||
*
|
||||
* .Nm
|
||||
* provides an interface to platform-dependent TLS libraries. It allows
|
||||
* Cytoplasm to support any TLS library with no changes to existing
|
||||
* code. Support for additional TLS libraries is added by creating a
|
||||
* new compilation unit that implements all the functions here, with
|
||||
* the exception of a few, which are noted.
|
||||
* .Pp
|
||||
* Currently, Cytoplasm has support for the following TLS libraries:
|
||||
* .Bl -bullet -offset indent
|
||||
* .It
|
||||
* LibreSSL
|
||||
* .It
|
||||
* OpenSSL
|
||||
* .El
|
||||
*/
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
#define TLS_LIBRESSL 2
|
||||
#define TLS_OPENSSL 3
|
||||
|
||||
/**
|
||||
* Create a new TLS client stream using the given file descriptor and
|
||||
* the given server hostname. The hostname should be used to verify
|
||||
* that the server actually is who it says it is.
|
||||
* .Pp
|
||||
* This function does not need to be implemented by the individual
|
||||
* TLS support stubs.
|
||||
*/
|
||||
extern Stream * TlsClientStream(int, const char *);
|
||||
|
||||
/**
|
||||
* Create a new TLS server stream using the given certificate and key
|
||||
* file, in the format natively supported by the TLS library.
|
||||
* .Pp
|
||||
* This function does not need to be implemented by the individual
|
||||
* TLS support stubs.
|
||||
*/
|
||||
extern Stream * TlsServerStream(int, const char *, const char *);
|
||||
|
||||
/**
|
||||
* Initialize a cookie that stores information about the given client
|
||||
* connection. This cookie will be passed into the other functions
|
||||
* defined by this API.
|
||||
*/
|
||||
extern void * TlsInitClient(int, const char *);
|
||||
|
||||
/**
|
||||
* Initialize a cookie that stores information about the given
|
||||
* server connection. This cookie will be passed into the other
|
||||
* functions defined by this API.
|
||||
*/
|
||||
extern void * TlsInitServer(int, const char *, const char *);
|
||||
|
||||
/**
|
||||
* Read from a TLS stream, decrypting it and storing the result in the
|
||||
* specified buffer. This function takes the cookie, buffer, and
|
||||
* number of decrypted bytes to read into it. See the documentation for
|
||||
* .Fn IoRead .
|
||||
*/
|
||||
extern ssize_t TlsRead(void *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Write to a TLS stream, encrypting the buffer. This function takes
|
||||
* the cookie, buffer, and number of unencrypted bytes to write to
|
||||
* the stream. See the documentation for
|
||||
* .Fn IoWrite .
|
||||
*/
|
||||
extern ssize_t TlsWrite(void *, void *, size_t);
|
||||
|
||||
/**
|
||||
* Close the TLS stream, also freeing all memory associated with the
|
||||
* cookie.
|
||||
*/
|
||||
extern int TlsClose(void *);
|
||||
|
||||
#endif /* CYTOPLASM_TLS_H */
|
69
include/Cytoplasm/Uri.h
Normal file
69
include/Cytoplasm/Uri.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_URI_H
|
||||
#define CYTOPLASM_URI_H
|
||||
|
||||
/***
|
||||
* @Nm Uri
|
||||
* @Nd Parse a URI. Typically used to parse HTTP(s) URLs.
|
||||
* @Dd April 29 2023
|
||||
* @Xr Http
|
||||
*
|
||||
* .Nm
|
||||
* provides a simple mechanism for parsing URIs. This is an extremely
|
||||
* basic parser that (ab)uses
|
||||
* .Xr sscanf 3
|
||||
* to parse URIs, so it may not be the most reliable, but it should
|
||||
* work in most cases and on reasonable URIs that aren't too long, as
|
||||
* the _MAX definitions are modest.
|
||||
*/
|
||||
|
||||
#define URI_PROTO_MAX 8
|
||||
#define URI_HOST_MAX 128
|
||||
#define URI_PATH_MAX 256
|
||||
|
||||
/**
|
||||
* The parsed URI is stored in this structure.
|
||||
*/
|
||||
typedef struct Uri
|
||||
{
|
||||
char proto[URI_PROTO_MAX];
|
||||
char host[URI_HOST_MAX];
|
||||
char path[URI_PATH_MAX];
|
||||
unsigned short port;
|
||||
} Uri;
|
||||
|
||||
/**
|
||||
* Parse a URI string into the Uri structure as described above, or
|
||||
* return NULL if there was a parsing error.
|
||||
*/
|
||||
extern Uri * UriParse(const char *);
|
||||
|
||||
/**
|
||||
* Free the memory associated with a Uri structure returned by
|
||||
* .Fn UriParse .
|
||||
*/
|
||||
extern void UriFree(Uri *);
|
||||
|
||||
#endif /* CYTOPLASM_URI_H */
|
116
include/Cytoplasm/Util.h
Normal file
116
include/Cytoplasm/Util.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#ifndef CYTOPLASM_UTIL_H
|
||||
#define CYTOPLASM_UTIL_H
|
||||
|
||||
/***
|
||||
* @Nm Util
|
||||
* @Nd Some misc. helper functions that don't need their own headers.
|
||||
* @Dd February 15 2023
|
||||
*
|
||||
* This header holds a number of random functions related to strings,
|
||||
* time, the filesystem, and other simple tasks that don't require a
|
||||
* full separate API. For the most part, the functions here are
|
||||
* entirely standalone, depending only on POSIX functions, however
|
||||
* there are a few that depend explicitly on a few other APIs. Those
|
||||
* are noted.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
/**
|
||||
* Get the current timestamp in milliseconds since the Unix epoch. This
|
||||
* uses
|
||||
* .Xr gettimeofday 2
|
||||
* and time_t, and converts it to a single number, which is then
|
||||
* returned to the caller.
|
||||
* .Pp
|
||||
* A note on the 2038 problem: as long as sizeof(time_t) >= 8, that is,
|
||||
* as long as the time_t type is 64 bits or more, then everything
|
||||
* should be fine. On most, if not, all, 64-bit systems, time_t is 64
|
||||
* bits. time_t is promoted to a 64-bit integer before it is converted
|
||||
* to milliseconds, so there is no risk of overflue due to the
|
||||
* multiplication by 1000. However, if time_t is only 32 bits, it will
|
||||
* overflow before it even gets to this function, which will cause this
|
||||
* function to produce unexpected results.
|
||||
*/
|
||||
extern uint64_t UtilTsMillis(void);
|
||||
|
||||
/**
|
||||
* Use
|
||||
* .Xr stat 2
|
||||
* to get the last modified time of the given file, or zero if there
|
||||
* was an error getting the last modified time of a file. This is
|
||||
* primarily useful for caching file data.
|
||||
*/
|
||||
extern uint64_t UtilLastModified(char *);
|
||||
|
||||
/**
|
||||
* This function behaves just like the system call
|
||||
* .Xr mkdir 2 ,
|
||||
* but it creates any intermediate directories as necessary, unlike
|
||||
* .Xr mkdir 2 .
|
||||
*/
|
||||
extern int UtilMkdir(const char *, const mode_t);
|
||||
|
||||
/**
|
||||
* Sleep the calling thread for the given number of milliseconds.
|
||||
* POSIX does not have a very friendly way to sleep, so this wraps
|
||||
* .Xr nanosleep 2
|
||||
* to make its usage much, much simpler.
|
||||
*/
|
||||
extern int UtilSleepMillis(uint64_t);
|
||||
|
||||
/**
|
||||
* This function works identically to the POSIX
|
||||
* .Xr getdelim 3 ,
|
||||
* except that it assumes pointers were allocated with the Memory API
|
||||
* and it reads from a Stream instead of a file pointer.
|
||||
*/
|
||||
extern ssize_t UtilGetDelim(char **, size_t *, int, Stream *);
|
||||
|
||||
/**
|
||||
* This function is just a special case of
|
||||
* .Fn UtilGetDelim
|
||||
* that sets the delimiter to the newline character.
|
||||
*/
|
||||
extern ssize_t UtilGetLine(char **, size_t *, Stream *);
|
||||
|
||||
/**
|
||||
* Get a unique number associated with the current thread.
|
||||
* Numbers are assigned in the order that threads call this
|
||||
* function, but are guaranteed to be unique in identifying
|
||||
* the thread in a more human-readable way than just casting
|
||||
* the return value of
|
||||
* .Fn pthread_self
|
||||
* to a number.
|
||||
*/
|
||||
extern uint32_t UtilThreadNo(void);
|
||||
|
||||
#endif /* CYTOPLASM_UTIL_H */
|
51
man/man1/hdoc.1
Normal file
51
man/man1/hdoc.1
Normal file
|
@ -0,0 +1,51 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 1
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd Generate documentation from a C header file.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl i Ar file
|
||||
.Op Fl o Ar file
|
||||
.Op Fl D Ar key=value
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an extremely simple documentation generator that generates
|
||||
a BSD man page from a specially-formatted C header file.
|
||||
See
|
||||
.Xr hdoc 5
|
||||
for details on the format of this header file.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl i Ar file
|
||||
The input C header file to read from. If this option is omitted,
|
||||
then the standard input is read.
|
||||
.It Fl o Ar file
|
||||
The output BSD man page file to write. If this option is omitted,
|
||||
then the standard output is written.
|
||||
.It Fl D Ar key=value
|
||||
Set the register
|
||||
.Ar key
|
||||
to
|
||||
.Ar value .
|
||||
.Nm
|
||||
registers are used in various places, and can be set in either
|
||||
the header file, or on the command line. Setting registers that
|
||||
should be the same in all headers is best done from the command
|
||||
line for maintainability purposes, but header-specific values
|
||||
should be set in the header file itself.
|
||||
.Pp
|
||||
Registers are explained in more detail in
|
||||
.Xr hdoc 5 .
|
||||
.El
|
||||
.Sh RETURN VALUE
|
||||
.Pp
|
||||
.Nm
|
||||
returns a success value if the header is well-formed and the
|
||||
man page is successfully generated. It returns an error code in
|
||||
all other scenarios.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
|
@ -1,18 +0,0 @@
|
|||
.Dd $Mdocdate: May 6 2023 $
|
||||
.Dt HTTP-DEBUG-SERVER 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm http-debug-server
|
||||
.Nd A simple HTTP server that logs requests to the standard output.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
spins up an HTTP server, listening on port 8008, in the exact same
|
||||
manner as Telodendria itself. Any request it receives is written to
|
||||
the standard output, and an empty JSON object is returned to the
|
||||
client.
|
||||
.Pp
|
||||
This command exists just to test the HTTP server API during
|
||||
development. It probably serves no other practical purpose.
|
||||
.Sh SEE ALSO
|
||||
.Xr HttpServer 3
|
70
man/man1/http.1
Normal file
70
man/man1/http.1
Normal file
|
@ -0,0 +1,70 @@
|
|||
.Dd $Mdocdate: March 12 2023 $
|
||||
.Dt HTTP 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm http
|
||||
.Nd A simple command line utility for making HTTP requests.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl i
|
||||
.Op Fl X Ar method
|
||||
.Op Fl H Ar header
|
||||
.Op Fl d Ar data
|
||||
.Op URL
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
Is a command line HTTP client. It is very heavily inspired by
|
||||
.Xr curl 1 ,
|
||||
and even uses the same flag names when possible. However,
|
||||
.Nm
|
||||
is designed to be much simpler than
|
||||
.Xr curl 1 ,
|
||||
and is built on Telodendria's own
|
||||
.Xr HttpClient 3
|
||||
API. It primarily exists to test
|
||||
.Xr HttpClient 3
|
||||
and
|
||||
.Xr HttpServer 3 ,
|
||||
and make development of Telodendria possible without having
|
||||
to install any external tools.
|
||||
.sp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl i
|
||||
Display the response headers before writing the body.
|
||||
.It Fl X Ar method
|
||||
Set the request method. This can be any of the options
|
||||
allowed by the
|
||||
.Xr Http 3
|
||||
API; unlike
|
||||
.Xr curl 1 ,
|
||||
it cannot be any arbitrary string.
|
||||
.It Fl H Ar header
|
||||
Set a request header, in the form of ``Header: value''. This option
|
||||
can be set multiple times to add multiple request headers.
|
||||
.It Fl d Ar data
|
||||
Send data to the server in the request body. If
|
||||
.Ar data
|
||||
starts with ``@'', then the file specified after is opened
|
||||
and read in. If it is ``@-'', then standard input is used.
|
||||
Otherwise, the string is passed to the server as-is.
|
||||
.El
|
||||
.Pp
|
||||
.Nm
|
||||
also requires a
|
||||
.Ar URL
|
||||
to make the request to. The URL is parsed by the
|
||||
.Xr Uri 3
|
||||
API, so consult that page for the syntax of URLs.
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with
|
||||
.Va EXIT_SUCCESS
|
||||
if all command line options were valid, the request was
|
||||
made successfully, and the server returns an HTTP code
|
||||
that indicates success. It exits with
|
||||
.Va EXIT_FAILURE
|
||||
in all other scenarios.
|
||||
.Sh SEE ALSO
|
||||
.Xr HttpClient 3 ,
|
||||
.Xr Uri 3
|
111
man/man1/json.1
111
man/man1/json.1
|
@ -1,111 +0,0 @@
|
|||
.Dd $Mdocdate: March 12 2023 $
|
||||
.Dt JSON 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm json
|
||||
.Nd A simple command line utility for parsing and generating JSON.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl s Ar query
|
||||
.Op Fl e Ar str
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a simple command line utility for dealing with JSON. It is
|
||||
somewhat inspired by
|
||||
.Xr jq 1 ,
|
||||
but is not compatible in any way with it.
|
||||
.Nm
|
||||
is designed to be much simpler than
|
||||
.Xr jq 1 ,
|
||||
and is built on Telodendria's own
|
||||
.Xr Json 3
|
||||
API. It primarily exists to ease development of Telodendria, and
|
||||
to make development possible without having to install any external
|
||||
tools.
|
||||
.Pp
|
||||
The options are as follows. Unless stated otherwise, these options
|
||||
are mutually exclusive, and the last one specified takes precedence.
|
||||
All positional parameters are ignored.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl s Ar query
|
||||
Use
|
||||
.Va query
|
||||
to query a field from a JSON object given on the standard input.
|
||||
The query syntax very vaguely resembles C code, but it is much
|
||||
more primitive. Multiple queries are separated by an arrow
|
||||
(``->''). This makes it trivial to drill down into nested
|
||||
objects and arrays.
|
||||
.Pp
|
||||
To select a value from an object, just specify the key. To select
|
||||
an element from an array specify the key whose value is the array,
|
||||
and then use the C square bracket syntax to select an element.
|
||||
.Pp
|
||||
A number of ``functions'' exist to make
|
||||
.Nm
|
||||
more versatile. Functions are called by prefacing the key with
|
||||
a ``@'' symbol. Functions can appear anywhere in the query, provided
|
||||
they make sense within the context of the JSON object being processed.
|
||||
The available functions are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It keys
|
||||
When applied to an object, outputs an array of keys.
|
||||
.It length
|
||||
When applied to an array, outputs the number of elements in the
|
||||
array. When applied to a string, returns the number of bytes
|
||||
needed to store the decoded version of the string.
|
||||
.It decode
|
||||
When applied to a string, outputs the decoded version of the
|
||||
string.
|
||||
.El
|
||||
.Pp
|
||||
When a key is prefaced with the ``^'' symbol, then instead of getting
|
||||
the value at that key, it is removed from the object, and the new
|
||||
object is passed along.
|
||||
.It Fl e Ar str
|
||||
Encode
|
||||
.Va str
|
||||
as a JSON string and print it to standard output. This is useful for
|
||||
generating JSON with shell scripts.
|
||||
.El
|
||||
.Pp
|
||||
If no options are specified, then the default behavior of
|
||||
.Nm
|
||||
is to read a JSON object given on the standard input and pretty-print
|
||||
it to the standard output, or print an error to standard error if
|
||||
the given input is invalid.
|
||||
.Sh EXAMPLES
|
||||
.Pp
|
||||
Get the error string of an error returned by a Matrix API endpoint:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'error->@decode'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the number of stages in the first flow listed in a list
|
||||
of user-interactive authentication flows:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'flows[0]->stages->@length'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the first stage of the first flow listed in a list
|
||||
of user-interactive authentication flows:
|
||||
.Bd -literal -offset indent
|
||||
json -s 'flows[0]->stages[0]->@decode'
|
||||
.Ed
|
||||
.Pp
|
||||
List the keys in a JSON object:
|
||||
.Bd -literal -offset indent
|
||||
json -s '@keys'
|
||||
.Ed
|
||||
.Pp
|
||||
Get the number of keys in a JSON object:
|
||||
.Bd -literal -offset indent
|
||||
json -s '@keys->@length'
|
||||
.Ed
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with
|
||||
.Va EXIT_SUCCESS
|
||||
if all command line options were valid and the given JSON object
|
||||
parses successfully. It exits with
|
||||
.Va EXIT_FAILURE
|
||||
in all other scenarios.
|
|
@ -1,34 +0,0 @@
|
|||
.Dd $Mdocdate: April 29 2023 $
|
||||
.Dt TT 1
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
.Nm tt
|
||||
.Nd Make authenticated Matrix requests.
|
||||
.Sh SYNPOSIS
|
||||
.Nm
|
||||
.Op request-path
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is an extremely simple wrapper over
|
||||
.Xr http 1
|
||||
and
|
||||
.Xr json 1
|
||||
that automatically registers a user and continues to log in as that
|
||||
user. It obtains an access token that it uses to authenticate the
|
||||
given request, and then logs out when the request has completed. It
|
||||
also doesn't require the whole URL to be typed; only the path name
|
||||
is needed.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag
|
||||
.It Ev METH
|
||||
The request method to use for the user-specified request.
|
||||
.It Ev DATA
|
||||
The data to pass to
|
||||
.Xr http 1 .
|
||||
This can either be a string, or a file. Consult the
|
||||
.Xr http 1
|
||||
page for details.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr http 1 ,
|
||||
.Xr json 1
|
195
man/man5/hdoc.5
Normal file
195
man/man5/hdoc.5
Normal file
|
@ -0,0 +1,195 @@
|
|||
.Dd $Mdocdate: May 21 2023 $
|
||||
.Dt HDOC 5
|
||||
.Os Cytoplasm
|
||||
.Sh NAME
|
||||
.Nm hdoc
|
||||
.Nd C header file format accepted by the documentation generator.
|
||||
.Sh DESCRIPTION
|
||||
.Pp
|
||||
.Nm
|
||||
uses an extremely primitive parser to generate documentation from
|
||||
C header files. As such, the format accepted by
|
||||
.Nm
|
||||
is rather strict and may not be suitable for other projects beyond
|
||||
of Cytoplasm. This document describes what
|
||||
.Nm
|
||||
considers to be a valid C header file, and how that header file can
|
||||
be annotated to produce a nicely-formatted man page.
|
||||
.Pp
|
||||
At the very top level, a C header is a sequences of tokens that
|
||||
represent the following:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
An ANSI C89 comment.
|
||||
.It
|
||||
A preprocessor directive.
|
||||
.It
|
||||
A typedef declaration.
|
||||
.It
|
||||
A constant or other global variable declaration.
|
||||
.It
|
||||
A function declaration.
|
||||
.El
|
||||
.Pp
|
||||
Note that global variables and functions
|
||||
.Em must
|
||||
be marked with
|
||||
.Ar extern ,
|
||||
otherwise the parser will fail to recognize them. This is by
|
||||
design; a header should make everything extern by default,
|
||||
because it does not actually implement or declare anything.
|
||||
.Pp
|
||||
Preprocessor directives are completely ignored. Regular C
|
||||
comments are also ignored.
|
||||
.Nm
|
||||
is primarily concerned with type declarations, global
|
||||
variables, and functions. It also inspects specially-formatted
|
||||
C comments, which are used to annotate these elements of the
|
||||
header. The format of these comments is described here.
|
||||
.Pp
|
||||
There are two types of special comments recognized, the first
|
||||
of which is called the ``main'' comment block, as it documents
|
||||
the header itself, not the declarations contained in it. Main
|
||||
comment blocks also control the behavior of the parser and the
|
||||
resulting man page by setting registers. The format of the
|
||||
main comment block, which typically appears only once at the
|
||||
top of the header, although this is not a requirement, is as
|
||||
follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``triple star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
Any lines that start with a ``@'' are parser directives that
|
||||
set registers. The name of the register to set follows
|
||||
immediately after the ``@'' sigil, and continues until the first
|
||||
whitespace. The rest of the line is the value of the register.
|
||||
A list of registers recognized by
|
||||
.Nm
|
||||
is as follows. These registers control the man page output,
|
||||
and the last value set is the one that is used:
|
||||
.Bl -tag -width Ds
|
||||
.It \&Nm
|
||||
The name register. The name of the man page will be set to the
|
||||
contents of this register. It defaults to ``Unnmamed.''
|
||||
.It \&Dd
|
||||
The date register. The date of the man page will be set to the
|
||||
contents of this register. If left unset, it defaults to the
|
||||
current date.
|
||||
.It \&Os
|
||||
The operating system register. The Os value, which appears in
|
||||
the bottom left and right corners of the man page, will be set
|
||||
to the contents of this register. If left unset, it is not
|
||||
output.
|
||||
.It \&Nd
|
||||
The description register. The description of the man page will
|
||||
be set to the contents of this register. It defaults to
|
||||
``No description.''
|
||||
.It \&Xr
|
||||
The cross reference register. The SEE ALSO section of the man
|
||||
page will list the specified cross references, which are to be
|
||||
separated by a single space. The section shall be omitted,
|
||||
because it is set automatically to the same section that the
|
||||
current man page will belong to. This limitation may be removed
|
||||
in the future.
|
||||
.El
|
||||
.Pp
|
||||
These registers control the parser itself, modifying its
|
||||
behavior as soon as the appear in the file:
|
||||
.Bl -tag -width Ds
|
||||
.It ignore-typedefs
|
||||
Don't throw an error for an undocumented type declaration.
|
||||
The value doesn't matter; as soon as this register shows
|
||||
up, it's set. In most cases, it should not be used, however,
|
||||
it may be helpful in a few scenarios, such as when there are
|
||||
multiple typedefs that do the same thing, but are controlled
|
||||
by preprocessor macros.
|
||||
.It suppress-warnings
|
||||
Don't issue a warning for invalid or unrecognized top-level
|
||||
tokens. They will instead be ignored until the next
|
||||
recognized top-level token is found. The value doesn't
|
||||
matter; as soon as this register shows up, it is set. In most
|
||||
cases, it should not be used, however it may be helpful in a
|
||||
few scenarios, such as when function declarations are generated
|
||||
by preprocessor directives and thus don't follow the standard
|
||||
form.
|
||||
.El
|
||||
.It
|
||||
Any other lines in the main block are output to the DESCRIPTION
|
||||
section of the main page. This description may contain mdoc
|
||||
directives in it, as the lines are copied verbatim. If multiple
|
||||
main comment blocks appear in a single header, their description
|
||||
lines are appended in the order they appear.
|
||||
.El
|
||||
.Pp
|
||||
Declaration documentation comments are created as follows:
|
||||
.Bl -bullet -offset indent
|
||||
.It
|
||||
The comment should start with a ``double star,'' like this:
|
||||
.Bd -literal -offset indent
|
||||
/**
|
||||
*
|
||||
*/
|
||||
.Ed
|
||||
.It
|
||||
The contents of the comment are copied verbatim into the output,
|
||||
so the comment may contain mdoc directives.
|
||||
.It
|
||||
The comment must appear before a declaration. If multiple
|
||||
documentation comments appear before a declaration, the last
|
||||
one before the declaration is used.
|
||||
.El
|
||||
.Pp
|
||||
The generated man page includes the name and description of the
|
||||
header, a synopsis section that lists all of the functions in
|
||||
the header, a description section that contains all the non-register
|
||||
lines of the main comment blocks, and then all of the documentation
|
||||
for each function, with the function prototype displayed as a
|
||||
subsection header, and the documentation displayed under it.
|
||||
.Sh EXAMPLES
|
||||
.Pp
|
||||
Consider the following simple C header:
|
||||
.Bd -literal -offset indent
|
||||
#include <stdio.h>
|
||||
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
To annotate this header in the manner
|
||||
.Nm
|
||||
expects, do something like this:
|
||||
.Bd -literal -offset indent
|
||||
/***
|
||||
* @Nm Hello
|
||||
* @Nd Say hello.
|
||||
* @Dd May 17 2023
|
||||
*
|
||||
* .Nm
|
||||
* provides functionality to write hello world messages
|
||||
* into standard C file descriptors.
|
||||
*
|
||||
* @Xr fputs fprintf
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* This function writes "hello world" to the given file
|
||||
* descriptor.
|
||||
* .Pp
|
||||
* There really isn't much more to be said about it.
|
||||
*/
|
||||
extern void SayHello(FILE *);
|
||||
.Ed
|
||||
.Pp
|
||||
This example shows how mdoc directives can be placed in
|
||||
documentation comments. Note that the triple-star comment
|
||||
documents the header itself, and the double-star comment
|
||||
documents the type declaration or function definition
|
||||
below it.
|
||||
.Sh SEE ALSO
|
||||
.Xr hdoc 5 ,
|
||||
.Xr HeaderParser 3
|
118
src/Args.c
Normal file
118
src/Args.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Args.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Log.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
ArgParseStateInit(ArgParseState * state)
|
||||
{
|
||||
state->optPos = 1;
|
||||
state->optErr = 1;
|
||||
state->optInd = 1;
|
||||
state->optOpt = 0;
|
||||
state->optArg = NULL;
|
||||
}
|
||||
|
||||
int
|
||||
ArgParse(ArgParseState * state, Array * args, const char *optStr)
|
||||
{
|
||||
const char *arg;
|
||||
|
||||
arg = ArrayGet(args, state->optInd);
|
||||
|
||||
if (arg && StrEquals(arg, "--"))
|
||||
{
|
||||
state->optInd++;
|
||||
return -1;
|
||||
}
|
||||
else if (!arg || arg[0] != '-' || !isalnum((unsigned char) arg[1]))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *opt = strchr(optStr, arg[state->optPos]);
|
||||
|
||||
state->optOpt = arg[state->optPos];
|
||||
|
||||
if (!opt)
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Illegal option: %c", ArrayGet(args, 0), state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
else if (opt[1] == ':')
|
||||
{
|
||||
if (arg[state->optPos + 1])
|
||||
{
|
||||
state->optArg = (char *) arg + state->optPos + 1;
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else if (ArrayGet(args, state->optInd + 1))
|
||||
{
|
||||
state->optArg = (char *) ArrayGet(args, state->optInd + 1);
|
||||
state->optInd += 2;
|
||||
state->optPos = 1;
|
||||
return state->optOpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->optErr && *optStr != ':')
|
||||
{
|
||||
Log(LOG_ERR, "Option requires an argument: %c", state->optOpt);
|
||||
}
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return *optStr == ':' ? ':' : '?';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!arg[++state->optPos])
|
||||
{
|
||||
state->optInd++;
|
||||
state->optPos = 1;
|
||||
}
|
||||
return state->optOpt;
|
||||
}
|
||||
}
|
||||
}
|
424
src/Array.c
Normal file
424
src/Array.c
Normal file
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Array.h>
|
||||
|
||||
#ifndef ARRAY_BLOCK
|
||||
#define ARRAY_BLOCK 16
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <Memory.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 */
|
||||
};
|
||||
|
||||
bool
|
||||
ArrayAdd(Array * array, void *value)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ArrayInsert(array, array->size, value);
|
||||
}
|
||||
|
||||
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 || array->size <= index)
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ArrayInsert(Array * array, size_t index, void *value)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!array || !value || index > array->size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
array->allocated = newSize;
|
||||
}
|
||||
|
||||
for (i = array->size; i > index; i--)
|
||||
{
|
||||
array->entries[i] = array->entries[i - 1];
|
||||
}
|
||||
|
||||
array->size++;
|
||||
|
||||
array->entries[index] = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern void *
|
||||
ArraySet(Array * array, size_t index, void *value)
|
||||
{
|
||||
void *oldValue;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
return ArrayDelete(array, index);
|
||||
}
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (index >= array->size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
oldValue = array->entries[index];
|
||||
array->entries[index] = value;
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
size_t
|
||||
ArraySize(Array * array)
|
||||
{
|
||||
if (!array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return array->size;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayTrim(Array * array)
|
||||
{
|
||||
void **tmp;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp = array->entries;
|
||||
|
||||
array->entries = Realloc(array->entries,
|
||||
sizeof(void *) * array->size);
|
||||
|
||||
if (!array->entries)
|
||||
{
|
||||
array->entries = tmp;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
ArraySwap(Array * array, size_t i, size_t j)
|
||||
{
|
||||
void *p = array->entries[i];
|
||||
|
||||
array->entries[i] = array->entries[j];
|
||||
array->entries[j] = p;
|
||||
}
|
||||
|
||||
static size_t
|
||||
ArrayPartition(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
void *pivot = array->entries[high];
|
||||
size_t i = low - 1;
|
||||
size_t j;
|
||||
|
||||
for (j = low; j <= high - 1; j++)
|
||||
{
|
||||
if (compare(array->entries[j], pivot) < 0)
|
||||
{
|
||||
i++;
|
||||
ArraySwap(array, i, j);
|
||||
}
|
||||
}
|
||||
ArraySwap(array, i + 1, high);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void
|
||||
ArrayQuickSort(Array * array, size_t low, size_t high, int (*compare) (void *, void *))
|
||||
{
|
||||
if (low < high)
|
||||
{
|
||||
size_t pi = ArrayPartition(array, low, high, compare);
|
||||
|
||||
ArrayQuickSort(array, low, pi ? pi - 1 : 0, compare);
|
||||
ArrayQuickSort(array, pi + 1, high, compare);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArraySort(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
if (!ArraySize(array))
|
||||
{
|
||||
// If a NULL ptr was given, or the array has no elements, do nothing.
|
||||
return;
|
||||
}
|
||||
ArrayQuickSort(array, 0, array->size - 1, compare);
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayUnique(Array * array, int (*compare) (void *, void *))
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ArrayDuplicate(array);
|
||||
if (!ret)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ArraySize(ret) == 1)
|
||||
{
|
||||
/* There can't be any duplicates when there's only 1 value */
|
||||
return ret;
|
||||
}
|
||||
|
||||
ArraySort(ret, compare);
|
||||
|
||||
for (i = 1; i < ArraySize(ret); i++)
|
||||
{
|
||||
void *cur = ret->entries[i];
|
||||
void *prev = ret->entries[i - 1];
|
||||
|
||||
if (compare(cur, prev) == 0)
|
||||
{
|
||||
/* Remove the duplicate, and put i back where it was. */
|
||||
ArrayDelete(ret, i--);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayTrim(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Even though the following operations could be done using only the
|
||||
* public Array API defined above, I opted for low-level struct
|
||||
* manipulation because it allows much more efficient copying; we only
|
||||
* allocate what we for sure need upfront, and don't have to
|
||||
* re-allocate during the operation. */
|
||||
|
||||
Array *
|
||||
ArrayReverse(Array * array)
|
||||
{
|
||||
Array *ret;
|
||||
|
||||
size_t i;
|
||||
size_t size;
|
||||
|
||||
if (!array)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!array->size)
|
||||
{
|
||||
return ArrayCreate();
|
||||
}
|
||||
|
||||
ret = Malloc(sizeof(Array));
|
||||
|
||||
size = array->size;
|
||||
|
||||
ret->size = size;
|
||||
ret->allocated = size;
|
||||
ret->entries = Malloc(sizeof(void *) * size);
|
||||
|
||||
if (!ret->entries)
|
||||
{
|
||||
Free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
ret->entries[i] = array->entries[size - i - 1];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayFromVarArgs(size_t n, va_list ap)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr->size = n;
|
||||
arr->allocated = n;
|
||||
arr->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr->entries)
|
||||
{
|
||||
Free(arr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
arr->entries[i] = va_arg(ap, void *);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
ArrayDuplicate(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
Array *arr2 = Malloc(sizeof(Array));
|
||||
|
||||
if (!arr2)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr2->size = arr->size;
|
||||
arr2->allocated = arr->size;
|
||||
arr2->entries = Malloc(sizeof(void *) * arr->allocated);
|
||||
|
||||
if (!arr2->entries)
|
||||
{
|
||||
Free(arr2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < arr2->size; i++)
|
||||
{
|
||||
arr2->entries[i] = arr->entries[i];
|
||||
}
|
||||
|
||||
return arr2;
|
||||
}
|
244
src/Base64.c
Normal file
244
src/Base64.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Base64.h>
|
||||
|
||||
#include <Memory.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';
|
||||
}
|
||||
|
||||
bool
|
||||
Base64Pad(char **base64Ptr, size_t length)
|
||||
{
|
||||
char *tmp;
|
||||
size_t newSize;
|
||||
size_t i;
|
||||
|
||||
if (length % 4 == 0)
|
||||
{
|
||||
return true; /* Success: no padding needed */
|
||||
}
|
||||
|
||||
newSize = length + (4 - (length % 4));
|
||||
|
||||
tmp = Realloc(*base64Ptr, newSize + 100);;
|
||||
if (!tmp)
|
||||
{
|
||||
return false; /* Memory error */
|
||||
}
|
||||
*base64Ptr = tmp;
|
||||
|
||||
for (i = length; i < newSize; i++)
|
||||
{
|
||||
(*base64Ptr)[i] = '=';
|
||||
}
|
||||
|
||||
(*base64Ptr)[newSize] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <CanonicalJson.h>
|
||||
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int
|
||||
CanonicalJsonKeyCompare(void *k1, void *k2)
|
||||
{
|
||||
return strcmp((char *) k1, (char *) k2);
|
||||
}
|
||||
|
||||
int
|
||||
CanonicalJsonEncodeValue(JsonValue * value, Stream * out)
|
||||
{
|
||||
Array *arr;
|
||||
size_t i, len;
|
||||
|
||||
int length = 0;
|
||||
|
||||
/* Override object type to encode using the canonical functions */
|
||||
switch (JsonValueType(value))
|
||||
{
|
||||
case JSON_OBJECT:
|
||||
length += CanonicalJsonEncode(JsonValueAsObject(value), out);
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
arr = JsonValueAsArray(value);
|
||||
len = ArraySize(arr);
|
||||
|
||||
StreamPutc(out, '[');
|
||||
length++;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
JsonValue *aVal = ArrayGet(arr, i);
|
||||
|
||||
if (JsonValueType(aVal) == JSON_FLOAT)
|
||||
{
|
||||
/* See comment in CanonicalJsonEncode() */
|
||||
continue;
|
||||
}
|
||||
|
||||
length += CanonicalJsonEncodeValue(aVal, out);
|
||||
if (i < len - 1)
|
||||
{
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
StreamPutc(out, ']');
|
||||
length++;
|
||||
break;
|
||||
default:
|
||||
length += JsonEncodeValue(value, out, JSON_DEFAULT);
|
||||
break;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int
|
||||
CanonicalJsonEncode(HashMap * object, Stream * out)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *value;
|
||||
Array *keys;
|
||||
size_t i;
|
||||
size_t keyCount;
|
||||
int length;
|
||||
|
||||
if (!object)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
keys = ArrayCreate();
|
||||
if (!keys)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (HashMapIterate(object, &key, (void **) &value))
|
||||
{
|
||||
ArrayAdd(keys, key);
|
||||
}
|
||||
|
||||
ArraySort(keys, CanonicalJsonKeyCompare);
|
||||
|
||||
/* The total number of bytes written */
|
||||
length = 0;
|
||||
|
||||
StreamPutc(out, '{');
|
||||
length++;
|
||||
|
||||
keyCount = ArraySize(keys);
|
||||
for (i = 0; i < keyCount; i++)
|
||||
{
|
||||
key = (char *) ArrayGet(keys, i);
|
||||
value = (JsonValue *) HashMapGet(object, key);
|
||||
|
||||
if (JsonValueType(value) == JSON_FLOAT)
|
||||
{
|
||||
/*
|
||||
* "INFO: Float values are not permitted by this encoding."
|
||||
*
|
||||
* The spec doesn't say how a canonical JSON generator should
|
||||
* handle float values, but given that it is highly unlikely
|
||||
* that we will ever be using float values, it shouldn't be
|
||||
* an issue if we just skip keys that have float values
|
||||
* altogether.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
length += JsonEncodeString(key, out);
|
||||
StreamPutc(out, ':');
|
||||
length++;
|
||||
length += CanonicalJsonEncodeValue(value, out);
|
||||
|
||||
if (i < keyCount - 1)
|
||||
{
|
||||
StreamPutc(out, ',');
|
||||
length++;
|
||||
}
|
||||
}
|
||||
|
||||
StreamPutc(out, '}');
|
||||
length++;
|
||||
|
||||
ArrayFree(keys);
|
||||
return length;
|
||||
}
|
232
src/Config.c
232
src/Config.c
|
@ -1,232 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <Schema/Config.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Array.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Db.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#ifndef HOST_NAME_MAX
|
||||
#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
|
||||
#endif
|
||||
|
||||
void
|
||||
ConfigParse(HashMap * config, Config *tConfig)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!config)
|
||||
{
|
||||
tConfig->ok = 0;
|
||||
tConfig->err = "Invalid object given as config.";
|
||||
return;
|
||||
}
|
||||
|
||||
memset(tConfig, 0, sizeof(Config));
|
||||
|
||||
tConfig->maxCache = 0;
|
||||
|
||||
if (!ConfigFromJson(config, tConfig, &tConfig->err))
|
||||
{
|
||||
ConfigFree(tConfig);
|
||||
goto error;
|
||||
}
|
||||
if (!tConfig->baseUrl)
|
||||
{
|
||||
size_t len = strlen(tConfig->serverName) + 10;
|
||||
|
||||
tConfig->baseUrl = Malloc(len);
|
||||
if (!tConfig->baseUrl)
|
||||
{
|
||||
tConfig->err = "Couldn't allocate enough memory for 'baseUrl'.";
|
||||
goto error;
|
||||
}
|
||||
snprintf(tConfig->baseUrl, len, "https://%s/", tConfig->serverName);
|
||||
}
|
||||
if (!tConfig->log.timestampFormat)
|
||||
{
|
||||
tConfig->log.timestampFormat = StrDuplicate("default");
|
||||
}
|
||||
for (i = 0; i < ArraySize(tConfig->listen); i++)
|
||||
{
|
||||
ConfigListener *listener = ArrayGet(tConfig->listen, i);
|
||||
if (!listener->maxConnections)
|
||||
{
|
||||
listener->maxConnections = 32;
|
||||
}
|
||||
if (!listener->threads)
|
||||
{
|
||||
listener->threads = 4;
|
||||
}
|
||||
if (!listener->port)
|
||||
{
|
||||
listener->port = 8008;
|
||||
}
|
||||
}
|
||||
tConfig->ok = 1;
|
||||
tConfig->err = NULL;
|
||||
return;
|
||||
|
||||
error:
|
||||
tConfig->ok = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
ConfigExists(Db * db)
|
||||
{
|
||||
return DbExists(db, 1, "config");
|
||||
}
|
||||
|
||||
int
|
||||
ConfigCreateDefault(Db * db)
|
||||
{
|
||||
Config config;
|
||||
ConfigListener *listener;
|
||||
|
||||
HashMap *json;
|
||||
JsonValue *val;
|
||||
|
||||
DbRef *ref;
|
||||
|
||||
size_t len;
|
||||
|
||||
memset(&config, 0, sizeof(Config));
|
||||
|
||||
|
||||
config.log.output = CONFIG_LOG_OUTPUT_FILE;
|
||||
|
||||
config.runAs.gid = StrDuplicate(getgrgid(getgid())->gr_name);
|
||||
config.runAs.uid = StrDuplicate(getpwuid(getuid())->pw_name);
|
||||
|
||||
config.registration = 0;
|
||||
config.federation = 1;
|
||||
|
||||
/* Create serverName and baseUrl. */
|
||||
config.serverName = Malloc(HOST_NAME_MAX + 1);
|
||||
memset(config.serverName, 0, HOST_NAME_MAX + 1);
|
||||
gethostname(config.serverName, HOST_NAME_MAX);
|
||||
len = strlen(config.serverName) + 10;
|
||||
config.baseUrl = Malloc(len);
|
||||
snprintf(config.baseUrl, len, "https://%s/", config.serverName);
|
||||
|
||||
/* Add simple listener without TLS. */
|
||||
config.listen = ArrayCreate();
|
||||
listener = Malloc(sizeof(ConfigListener));
|
||||
listener->maxConnections = 32;
|
||||
listener->port = 8008;
|
||||
listener->threads = 4;
|
||||
|
||||
ArrayAdd(config.listen, listener);
|
||||
|
||||
/* Write it all out to the configuration file. */
|
||||
json = ConfigToJson(&config);
|
||||
val = JsonGet(json, 1, "listen");
|
||||
val = ArrayGet(JsonValueAsArray(val), 0);
|
||||
JsonValueFree(HashMapDelete(JsonValueAsObject(val), "tls"));
|
||||
|
||||
ref = DbCreate(db, 1, "config");
|
||||
if (!ref)
|
||||
{
|
||||
ConfigFree(&config);
|
||||
return 0;
|
||||
}
|
||||
DbJsonSet(ref, json);
|
||||
DbUnlock(db, ref);
|
||||
|
||||
ConfigFree(&config);
|
||||
JsonFree(json);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigLock(Db * db, Config *config)
|
||||
{
|
||||
DbRef *ref = DbLock(db, 1, "config");
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
config->ok = 0;
|
||||
config->err = "Couldn't lock configuration.";
|
||||
}
|
||||
|
||||
ConfigParse(DbJson(ref), config);
|
||||
if (config->ok)
|
||||
{
|
||||
config->db = db;
|
||||
config->ref = ref;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ConfigUnlock(Config *config)
|
||||
{
|
||||
Db *db;
|
||||
DbRef *dbRef;
|
||||
|
||||
if (!config->ok)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
db = config->db;
|
||||
dbRef = config->ref;
|
||||
|
||||
ConfigFree(config);
|
||||
config->ok = 0;
|
||||
|
||||
return DbUnlock(db, dbRef);
|
||||
}
|
||||
int
|
||||
ConfigLogLevelToSyslog(ConfigLogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case CONFIG_LOG_LEVEL_NOTICE:
|
||||
return LOG_NOTICE;
|
||||
case CONFIG_LOG_LEVEL_ERROR:
|
||||
return LOG_ERR;
|
||||
case CONFIG_LOG_LEVEL_MESSAGE:
|
||||
return LOG_INFO;
|
||||
case CONFIG_LOG_LEVEL_DEBUG:
|
||||
return LOG_DEBUG;
|
||||
case CONFIG_LOG_LEVEL_WARNING:
|
||||
return LOG_WARNING;
|
||||
}
|
||||
return LOG_INFO;
|
||||
}
|
253
src/Cron.c
Normal file
253
src/Cron.c
Normal file
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Cron.h>
|
||||
|
||||
#include <Array.h>
|
||||
#include <Memory.h>
|
||||
#include <Util.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct Cron
|
||||
{
|
||||
uint64_t tick;
|
||||
Array *jobs;
|
||||
pthread_mutex_t lock;
|
||||
pthread_t thread;
|
||||
volatile bool stop;
|
||||
};
|
||||
|
||||
typedef struct Job
|
||||
{
|
||||
uint64_t interval;
|
||||
uint64_t lastExec;
|
||||
JobFunc *func;
|
||||
void *args;
|
||||
} Job;
|
||||
|
||||
static Job *
|
||||
JobCreate(uint64_t interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!func)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job = Malloc(sizeof(Job));
|
||||
if (!job)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
job->interval = interval;
|
||||
job->lastExec = 0;
|
||||
job->func = func;
|
||||
job->args = args;
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static void *
|
||||
CronThread(void *args)
|
||||
{
|
||||
Cron *cron = args;
|
||||
|
||||
while (!cron->stop)
|
||||
{
|
||||
size_t i;
|
||||
uint64_t ts; /* tick start */
|
||||
uint64_t te; /* tick end */
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
|
||||
ts = UtilTsMillis();
|
||||
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Job *job = ArrayGet(cron->jobs, i);
|
||||
|
||||
if ((ts - job->lastExec) > job->interval)
|
||||
{
|
||||
job->func(job->args);
|
||||
job->lastExec = ts;
|
||||
}
|
||||
|
||||
if (!job->interval)
|
||||
{
|
||||
ArrayDelete(cron->jobs, i);
|
||||
Free(job);
|
||||
}
|
||||
}
|
||||
te = UtilTsMillis();
|
||||
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
|
||||
// Only sleep if the jobs didn't overrun the tick
|
||||
if (cron->tick > (te - ts))
|
||||
{
|
||||
const uint64_t microTick = 100;
|
||||
|
||||
uint64_t remainingTick = cron->tick - (te - ts);
|
||||
|
||||
/* Only sleep for microTick ms at a time because if the job
|
||||
* scheduler is supposed to stop before the tick is up, we
|
||||
* don't want to be stuck in a long sleep */
|
||||
while (remainingTick >= microTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(microTick);
|
||||
|
||||
remainingTick -= microTick;
|
||||
}
|
||||
|
||||
if (remainingTick && !cron->stop)
|
||||
{
|
||||
UtilSleepMillis(remainingTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Cron *
|
||||
CronCreate(uint64_t tick)
|
||||
{
|
||||
Cron *cron = Malloc(sizeof(Cron));
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->jobs = ArrayCreate();
|
||||
if (!cron->jobs)
|
||||
{
|
||||
Free(cron);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cron->tick = tick;
|
||||
cron->stop = true;
|
||||
|
||||
pthread_mutex_init(&cron->lock, NULL);
|
||||
|
||||
return cron;
|
||||
}
|
||||
|
||||
void
|
||||
CronOnce(Cron * cron, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(0, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronEvery(Cron * cron, uint64_t interval, JobFunc * func, void *args)
|
||||
{
|
||||
Job *job;
|
||||
|
||||
if (!cron || !func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
job = JobCreate(interval, func, args);
|
||||
if (!job)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
ArrayAdd(cron->jobs, job);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
}
|
||||
|
||||
void
|
||||
CronStart(Cron * cron)
|
||||
{
|
||||
if (!cron || !cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = false;
|
||||
|
||||
pthread_create(&cron->thread, NULL, CronThread, cron);
|
||||
}
|
||||
|
||||
void
|
||||
CronStop(Cron * cron)
|
||||
{
|
||||
if (!cron || cron->stop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cron->stop = true;
|
||||
|
||||
pthread_join(cron->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
CronFree(Cron * cron)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!cron)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CronStop(cron);
|
||||
|
||||
pthread_mutex_lock(&cron->lock);
|
||||
for (i = 0; i < ArraySize(cron->jobs); i++)
|
||||
{
|
||||
Free(ArrayGet(cron->jobs, i));
|
||||
}
|
||||
|
||||
ArrayFree(cron->jobs);
|
||||
pthread_mutex_unlock(&cron->lock);
|
||||
pthread_mutex_destroy(&cron->lock);
|
||||
|
||||
Free(cron);
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
* Copyright (C) 2022-2024 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
|
||||
|
@ -22,44 +21,25 @@
|
|||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Tls.h>
|
||||
|
||||
#if TLS_IMPL == TLS_TEMPLATE /* Set your TLS_* implementation
|
||||
* flag here */
|
||||
|
||||
/*
|
||||
* #include statements and any implementation structures
|
||||
* needed should go here.
|
||||
*/
|
||||
|
||||
void *
|
||||
TlsInitClient(int fd, const char *serverName)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *
|
||||
TlsInitServer(int fd, const char *crt, const char *key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
TlsRead(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
TlsWrite(void *cookie, void *buf, size_t nBytes)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#include <Cytoplasm.h>
|
||||
|
||||
int
|
||||
TlsClose(void *cookie)
|
||||
CytoplasmGetVersion(void)
|
||||
{
|
||||
return -1;
|
||||
return CYTOPLASM_VERSION;
|
||||
}
|
||||
|
||||
const char *
|
||||
CytoplasmGetVersionStr(void)
|
||||
{
|
||||
return "v" STRINGIFY(CYTOPLASM_VERSION_MAJOR)
|
||||
"." STRINGIFY(CYTOPLASM_VERSION_MINOR)
|
||||
"." STRINGIFY(CYTOPLASM_VERSION_PATCH)
|
||||
#if CYTOPLASM_VERSION_ALPHA
|
||||
"-alpha" STRINGIFY(CYTOPLASM_VERSION_ALPHA)
|
||||
#elif CYTOPLASM_VERSION_BETA
|
||||
"-beta" STRINGIFY(CYTOPLASM_VERSION_BETA)
|
||||
#endif
|
||||
;
|
||||
}
|
499
src/Db/Db.c
Normal file
499
src/Db/Db.c
Normal file
|
@ -0,0 +1,499 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Db.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Json.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
#include <Stream.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Db/Internal.h"
|
||||
|
||||
void
|
||||
StringArrayFree(Array * arr)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
{
|
||||
Free(ArrayGet(arr, i));
|
||||
}
|
||||
|
||||
ArrayFree(arr);
|
||||
}
|
||||
|
||||
static ssize_t DbComputeSize(HashMap *);
|
||||
|
||||
static ssize_t
|
||||
DbComputeSizeOfValue(JsonValue * val)
|
||||
{
|
||||
MemoryInfo *a;
|
||||
ssize_t total = 0;
|
||||
|
||||
size_t i;
|
||||
|
||||
union
|
||||
{
|
||||
char *str;
|
||||
Array *arr;
|
||||
} u;
|
||||
|
||||
if (!val)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
a = MemoryInfoGet(val);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
switch (JsonValueType(val))
|
||||
{
|
||||
case JSON_OBJECT:
|
||||
total += DbComputeSize(JsonValueAsObject(val));
|
||||
break;
|
||||
case JSON_ARRAY:
|
||||
u.arr = JsonValueAsArray(val);
|
||||
a = MemoryInfoGet(u.arr);
|
||||
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(u.arr); i++)
|
||||
{
|
||||
total += DbComputeSizeOfValue(ArrayGet(u.arr, i));
|
||||
}
|
||||
break;
|
||||
case JSON_STRING:
|
||||
u.str = JsonValueAsString(val);
|
||||
a = MemoryInfoGet(u.str);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
break;
|
||||
case JSON_NULL:
|
||||
case JSON_INTEGER:
|
||||
case JSON_FLOAT:
|
||||
case JSON_BOOLEAN:
|
||||
default:
|
||||
/* These don't use any extra heap space */
|
||||
break;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
DbComputeSize(HashMap * json)
|
||||
{
|
||||
char *key;
|
||||
JsonValue *val;
|
||||
MemoryInfo *a;
|
||||
size_t total;
|
||||
|
||||
if (!json)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
total = 0;
|
||||
|
||||
a = MemoryInfoGet(json);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
while (HashMapIterate(json, &key, (void **) &val))
|
||||
{
|
||||
a = MemoryInfoGet(key);
|
||||
if (a)
|
||||
{
|
||||
total += MemoryInfoGetSize(a);
|
||||
}
|
||||
|
||||
total += DbComputeSizeOfValue(val);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbHashKey(Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = NULL;
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp = StrConcat(2, str, ArrayGet(args, i));
|
||||
|
||||
Free(str);
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
DbCacheEvict(Db * db)
|
||||
{
|
||||
DbRef *ref = db->leastRecent;
|
||||
DbRef *tmp;
|
||||
|
||||
while (ref && db->cacheSize > db->maxCache)
|
||||
{
|
||||
char *hash;
|
||||
|
||||
JsonFree(ref->json);
|
||||
|
||||
hash = DbHashKey(ref->name);
|
||||
HashMapDelete(db->cache, hash);
|
||||
Free(hash);
|
||||
|
||||
StringArrayFree(ref->name);
|
||||
|
||||
db->cacheSize -= ref->size;
|
||||
|
||||
if (ref->next)
|
||||
{
|
||||
ref->next->prev = ref->prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->mostRecent = ref->prev;
|
||||
}
|
||||
|
||||
if (ref->prev)
|
||||
{
|
||||
ref->prev->next = ref->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
db->leastRecent = ref->next;
|
||||
}
|
||||
|
||||
tmp = ref->next;
|
||||
Free(ref);
|
||||
|
||||
ref = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DbMaxCacheSet(Db * db, size_t cache)
|
||||
{
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
|
||||
db->maxCache = cache;
|
||||
if (db->maxCache && !db->cache)
|
||||
{
|
||||
db->cache = HashMapCreate();
|
||||
db->cacheSize = 0;
|
||||
}
|
||||
|
||||
DbCacheEvict(db);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
}
|
||||
|
||||
void
|
||||
DbClose(Db * db)
|
||||
{
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&db->lock);
|
||||
if (db->close)
|
||||
{
|
||||
db->close(db);
|
||||
}
|
||||
DbMaxCacheSet(db, 0);
|
||||
DbCacheEvict(db);
|
||||
HashMapFree(db->cache);
|
||||
|
||||
pthread_mutex_unlock(&db->lock);
|
||||
pthread_mutex_destroy(&db->lock);
|
||||
|
||||
Free(db);
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbCreate(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
if (!db || !db->create)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = db->create(db, args);
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
DbDelete(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
bool ret = true;
|
||||
|
||||
if (!db)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = db->delete(db, args);
|
||||
|
||||
ArrayFree(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbLock(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args || !db->lockFunc)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = db->lockFunc(db, DB_HINT_WRITE, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DbRef *
|
||||
DbLockIntent(Db * db, DbHint hint, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
DbRef *ret;
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args || !db->lockFunc)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = db->lockFunc(db, hint, args);
|
||||
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
DbUnlock(Db * db, DbRef * ref)
|
||||
{
|
||||
if (!db || !ref || !db->unlock)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return db->unlock(db, ref);
|
||||
}
|
||||
|
||||
bool
|
||||
DbExists(Db * db, size_t nArgs,...)
|
||||
{
|
||||
va_list ap;
|
||||
Array *args;
|
||||
bool ret;
|
||||
if (!db || !nArgs || !db->exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
args = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = db->exists(db, args);
|
||||
ArrayFree(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Array *
|
||||
DbList(Db * db, size_t nArgs,...)
|
||||
{
|
||||
Array *result;
|
||||
Array *path;
|
||||
va_list ap;
|
||||
|
||||
if (!db || !nArgs || !db->list)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
va_start(ap, nArgs);
|
||||
path = ArrayFromVarArgs(nArgs, ap);
|
||||
va_end(ap);
|
||||
|
||||
result = db->list(db, path);
|
||||
|
||||
ArrayFree(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
DbListFree(Array * arr)
|
||||
{
|
||||
StringArrayFree(arr);
|
||||
}
|
||||
|
||||
HashMap *
|
||||
DbJson(DbRef * ref)
|
||||
{
|
||||
return ref ? ref->json : NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
DbJsonSet(DbRef * ref, HashMap * json)
|
||||
{
|
||||
if (!ref || !json)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonFree(ref->json);
|
||||
ref->json = JsonDuplicate(json);
|
||||
return true;
|
||||
}
|
||||
void
|
||||
DbInit(Db *db)
|
||||
{
|
||||
pthread_mutexattr_t attr;
|
||||
if (!db)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&db->lock, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
db->mostRecent = NULL;
|
||||
db->leastRecent = NULL;
|
||||
db->cacheSize = 0;
|
||||
db->maxCache = 0;
|
||||
|
||||
if (db->maxCache)
|
||||
{
|
||||
db->cache = HashMapCreate();
|
||||
}
|
||||
else
|
||||
{
|
||||
db->cache = NULL;
|
||||
}
|
||||
}
|
||||
void
|
||||
DbRefInit(Db *db, DbRef *ref)
|
||||
{
|
||||
if (!db || !ref)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ref->prev = db->mostRecent;
|
||||
ref->next = NULL;
|
||||
ref->json = NULL;
|
||||
ref->name = NULL;
|
||||
|
||||
/* As default values to be overwritten by impls */
|
||||
ref->ts = UtilTsMillis();
|
||||
ref->size = 0;
|
||||
|
||||
/* TODO: Append the ref to the cache list.
|
||||
* I removed it because it broke everything and crashed all the time.
|
||||
* My bad! */
|
||||
}
|
||||
void
|
||||
StringArrayAppend(Array *arr, char *str)
|
||||
{
|
||||
if (!arr || !str)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArrayAdd(arr, StrDuplicate(str));
|
||||
}
|
374
src/Db/Flat.c
Normal file
374
src/Db/Flat.c
Normal file
|
@ -0,0 +1,374 @@
|
|||
#include <Db.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Json.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
#include <Stream.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Db/Internal.h"
|
||||
|
||||
typedef struct FlatDb {
|
||||
Db base;
|
||||
char *dir;
|
||||
/* Theres not much to do here. */
|
||||
} FlatDb;
|
||||
typedef struct FlatDbRef {
|
||||
DbRef base;
|
||||
|
||||
Stream *stream;
|
||||
int fd;
|
||||
} FlatDbRef;
|
||||
|
||||
static char
|
||||
DbSanitiseChar(char input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case '/':
|
||||
return '_';
|
||||
case '.':
|
||||
return '-';
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
static char *
|
||||
DbDirName(FlatDb * db, Array * args, size_t strip)
|
||||
{
|
||||
size_t i, j;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args) - strip; i++)
|
||||
{
|
||||
char *tmp;
|
||||
char *sanitise = StrDuplicate(ArrayGet(args, i));
|
||||
for (j = 0; j < strlen(sanitise); j++)
|
||||
{
|
||||
sanitise[j] = DbSanitiseChar(sanitise[j]);
|
||||
}
|
||||
|
||||
tmp = StrConcat(3, str, sanitise, "/");
|
||||
|
||||
Free(str);
|
||||
Free(sanitise);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
static char *
|
||||
DbFileName(FlatDb * db, Array * args)
|
||||
{
|
||||
size_t i;
|
||||
char *str = StrConcat(2, db->dir, "/");
|
||||
|
||||
for (i = 0; i < ArraySize(args); i++)
|
||||
{
|
||||
char *tmp;
|
||||
char *arg = StrDuplicate(ArrayGet(args, i));
|
||||
size_t j = 0;
|
||||
|
||||
/* Sanitize name to prevent directory traversal attacks */
|
||||
while (arg[j])
|
||||
{
|
||||
arg[j] = DbSanitiseChar(arg[j]);
|
||||
j++;
|
||||
}
|
||||
|
||||
tmp = StrConcat(3, str, arg,
|
||||
(i < ArraySize(args) - 1) ? "/" : ".json");
|
||||
|
||||
Free(arg);
|
||||
Free(str);
|
||||
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static DbRef *
|
||||
FlatLock(Db *d, DbHint hint, Array *dir)
|
||||
{
|
||||
FlatDb *db = (FlatDb *) d;
|
||||
FlatDbRef *ref = NULL;
|
||||
size_t i;
|
||||
char *path = NULL;
|
||||
if (!d || !dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
path = DbFileName(db, dir);
|
||||
/* TODO: Caching */
|
||||
{
|
||||
int fd = open(path, O_RDWR);
|
||||
Stream *stream;
|
||||
struct flock lock;
|
||||
if (fd == -1)
|
||||
{
|
||||
ref = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
stream = StreamFd(fd);
|
||||
if (!stream)
|
||||
{
|
||||
ref = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0;
|
||||
lock.l_type = F_WRLCK;
|
||||
lock.l_whence = SEEK_SET;
|
||||
|
||||
if (fcntl(fd, F_SETLK, &lock) < 0)
|
||||
{
|
||||
StreamClose(stream);
|
||||
ref = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ref = Malloc(sizeof(*ref));
|
||||
DbRefInit(d, (DbRef *) ref);
|
||||
/* TODO: Hints */
|
||||
ref->base.hint = hint;
|
||||
ref->base.ts = UtilLastModified(path);
|
||||
ref->base.json = JsonDecode(stream);
|
||||
ref->stream = stream;
|
||||
ref->fd = fd;
|
||||
if (!ref->base.json)
|
||||
{
|
||||
Free(ref);
|
||||
StreamClose(stream);
|
||||
ref = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
||||
ref->base.name = ArrayCreate();
|
||||
for (i = 0; i < ArraySize(dir); i++)
|
||||
{
|
||||
StringArrayAppend(ref->base.name, ArrayGet(dir, i));
|
||||
}
|
||||
}
|
||||
end:
|
||||
Free(path);
|
||||
if (!ref)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
}
|
||||
return (DbRef *) ref;
|
||||
}
|
||||
|
||||
static bool
|
||||
FlatUnlock(Db *d, DbRef *r)
|
||||
{
|
||||
FlatDbRef *ref = (FlatDbRef *) r;
|
||||
|
||||
if (!d || !r)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lseek(ref->fd, 0L, SEEK_SET);
|
||||
if (ftruncate(ref->fd, 0) < 0)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
Log(LOG_ERR, "Failed to truncate file on disk.");
|
||||
Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonEncode(ref->base.json, ref->stream, JSON_DEFAULT);
|
||||
StreamClose(ref->stream);
|
||||
|
||||
JsonFree(ref->base.json);
|
||||
StringArrayFree(ref->base.name);
|
||||
Free(ref);
|
||||
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return true;
|
||||
}
|
||||
static DbRef *
|
||||
FlatCreate(Db *d, Array *dir)
|
||||
{
|
||||
FlatDb *db = (FlatDb *) d;
|
||||
char *path, *dirPath;
|
||||
Stream *fp;
|
||||
DbRef *ret;
|
||||
|
||||
if (!d || !dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_lock(&d->lock);
|
||||
|
||||
path = DbFileName(db, dir);
|
||||
if (UtilLastModified(path))
|
||||
{
|
||||
Free(path);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dirPath = DbDirName(db, dir, 1);
|
||||
if (UtilMkdir(dirPath, 0750) < 0)
|
||||
{
|
||||
Free(path);
|
||||
Free(dirPath);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return NULL;
|
||||
}
|
||||
Free(dirPath);
|
||||
|
||||
fp = StreamOpen(path, "w");
|
||||
Free(path);
|
||||
if (!fp)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
StreamPuts(fp, "{}");
|
||||
StreamClose(fp);
|
||||
|
||||
/* FlatLock() will lock again for us */
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
ret = FlatLock(d, DB_HINT_WRITE, dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
FlatDelete(Db *d, Array *dir)
|
||||
{
|
||||
bool ret = false;
|
||||
char *file;
|
||||
FlatDb *db = (FlatDb *) d;
|
||||
if (!d || !dir)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
file = DbFileName(db, dir);
|
||||
|
||||
/* TODO: Unlink the entry from the linkedlist */
|
||||
if (UtilLastModified(file))
|
||||
{
|
||||
ret = remove(file) == 0;
|
||||
}
|
||||
|
||||
Free(file);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Array *
|
||||
FlatList(Db *d, Array *dir)
|
||||
{
|
||||
FlatDb *db = (FlatDb *) d;
|
||||
struct dirent *file;
|
||||
Array *ret;
|
||||
DIR *files;
|
||||
char *path;
|
||||
|
||||
if (!d || !dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
|
||||
path = DbDirName(db, dir, 0);
|
||||
files = opendir(path);
|
||||
if (!files)
|
||||
{
|
||||
Free(path);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ArrayCreate();
|
||||
while ((file = readdir(files)))
|
||||
{
|
||||
size_t namlen = strlen(file->d_name);
|
||||
|
||||
if (namlen > 5)
|
||||
{
|
||||
int nameOffset = namlen - 5;
|
||||
|
||||
if (StrEquals(file->d_name + nameOffset, ".json"))
|
||||
{
|
||||
file->d_name[nameOffset] = '\0';
|
||||
StringArrayAppend(ret, file->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(files);
|
||||
Free(path);
|
||||
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return ret;
|
||||
}
|
||||
static bool
|
||||
FlatExists(Db *d, Array *dir)
|
||||
{
|
||||
FlatDb *db = (FlatDb *) d;
|
||||
char *path;
|
||||
bool ret;
|
||||
if (!d || !dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
|
||||
path = DbFileName(db, dir);
|
||||
ret = UtilLastModified(path) != 0;
|
||||
Free(path);
|
||||
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Db *
|
||||
DbOpen(char *dir, size_t cache)
|
||||
{
|
||||
FlatDb *db;
|
||||
if (!dir)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
db = Malloc(sizeof(*db));
|
||||
DbInit((Db *) db);
|
||||
db->dir = dir;
|
||||
db->base.cacheSize = cache;
|
||||
|
||||
db->base.lockFunc = FlatLock;
|
||||
db->base.unlock = FlatUnlock;
|
||||
db->base.create = FlatCreate;
|
||||
db->base.delete = FlatDelete;
|
||||
db->base.exists = FlatExists;
|
||||
db->base.list = FlatList;
|
||||
db->base.close = NULL;
|
||||
|
||||
return (Db *) db;
|
||||
}
|
101
src/Db/Internal.h
Normal file
101
src/Db/Internal.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#ifndef CYTOPLASM_DB_INTERNAL_H
|
||||
#define CYTOPLASM_DB_INTERNAL_H
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Db.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/* TODO: Define the base structures to define a database internally.
|
||||
* All "implementations" shall have them as a first element, so that
|
||||
* basic, general functions can work on them properly. */
|
||||
|
||||
/* The base structure of a database */
|
||||
struct Db
|
||||
{
|
||||
pthread_mutex_t lock;
|
||||
|
||||
size_t cacheSize;
|
||||
size_t maxCache;
|
||||
HashMap *cache;
|
||||
|
||||
/*
|
||||
* The cache uses a double linked list (see DbRef
|
||||
* below) to know which objects are most and least
|
||||
* recently used. The following diagram helps me
|
||||
* know what way all the pointers go, because it
|
||||
* can get very confusing sometimes. For example,
|
||||
* there's nothing stopping "next" from pointing to
|
||||
* least recent, and "prev" from pointing to most
|
||||
* recent, so hopefully this clarifies the pointer
|
||||
* terminology used when dealing with the linked
|
||||
* list:
|
||||
*
|
||||
* mostRecent leastRecent
|
||||
* | prev prev | prev
|
||||
* +---+ ---> +---+ ---> +---+ ---> NULL
|
||||
* |ref| |ref| |ref|
|
||||
* NULL <--- +---+ <--- +---+ <--- +---+
|
||||
* next next next
|
||||
*/
|
||||
DbRef *mostRecent;
|
||||
DbRef *leastRecent;
|
||||
|
||||
/* Functions for implementation-specific operations
|
||||
* (opening a ref, closing a db, removing an entry, ...) */
|
||||
DbRef * (*lockFunc)(Db *, DbHint, Array *);
|
||||
DbRef * (*create)(Db *, Array *);
|
||||
Array * (*list)(Db *, Array *);
|
||||
bool (*unlock)(Db *, DbRef *);
|
||||
bool (*delete)(Db *, Array *);
|
||||
bool (*exists)(Db *, Array *);
|
||||
void (*close)(Db *);
|
||||
|
||||
/* Implementation-specific constructs */
|
||||
};
|
||||
|
||||
struct DbRef
|
||||
{
|
||||
HashMap *json;
|
||||
|
||||
uint64_t ts;
|
||||
size_t size;
|
||||
|
||||
Array *name;
|
||||
|
||||
DbRef *prev;
|
||||
DbRef *next;
|
||||
|
||||
DbHint hint;
|
||||
/* Implementation-specific constructs */
|
||||
};
|
||||
|
||||
extern void DbInit(Db *);
|
||||
extern void DbRefInit(Db *, DbRef *);
|
||||
extern void StringArrayFree(Array *);
|
||||
extern void StringArrayAppend(Array *, char *);
|
||||
|
||||
#endif
|
590
src/Db/LMDB.c
Normal file
590
src/Db/LMDB.c
Normal file
|
@ -0,0 +1,590 @@
|
|||
#include <Memory.h>
|
||||
#include <Json.h>
|
||||
#include <Log.h>
|
||||
#include <Db.h>
|
||||
|
||||
#include "Db/Internal.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef EDB_LMDB
|
||||
|
||||
#include <lmdb.h>
|
||||
|
||||
typedef struct LMDB {
|
||||
Db base; /* The base implementation required to pass */
|
||||
|
||||
MDB_env *environ;
|
||||
MDB_dbi dbi;
|
||||
} LMDB;
|
||||
typedef struct LMDBRef {
|
||||
DbRef base;
|
||||
|
||||
/* TODO: LMDB-dependent stuff */
|
||||
MDB_txn *transaction;
|
||||
MDB_dbi dbi;
|
||||
} LMDBRef;
|
||||
|
||||
/* Helper functions */
|
||||
static MDB_val
|
||||
LMDBTranslateKey(Array *key)
|
||||
{
|
||||
MDB_val ret = { 0 };
|
||||
char *data = NULL;
|
||||
size_t length = 0;
|
||||
size_t i;
|
||||
if (!key || ArraySize(key) > 255)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
data = Realloc(data, ++length);
|
||||
data[0] = ArraySize(key);
|
||||
|
||||
/* Now, let's push every item */
|
||||
for (i = 0; i < ArraySize(key); i++)
|
||||
{
|
||||
char *entry = ArrayGet(key, i);
|
||||
size_t offset = length;
|
||||
|
||||
data = Realloc(data, (length += strlen(entry) + 1));
|
||||
memcpy(data + offset, entry, strlen(entry));
|
||||
data[length - 1] = '\0';
|
||||
}
|
||||
|
||||
/* We now have every key */
|
||||
ret.mv_size = length;
|
||||
ret.mv_data = data;
|
||||
return ret;
|
||||
}
|
||||
static void
|
||||
LMDBKillKey(MDB_val key)
|
||||
{
|
||||
if (!key.mv_data || !key.mv_size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Free(key.mv_data);
|
||||
}
|
||||
static HashMap *
|
||||
LMDBDecode(MDB_val val)
|
||||
{
|
||||
FILE *fakefile;
|
||||
Stream *fakestream;
|
||||
HashMap *ret;
|
||||
if (!val.mv_data || !val.mv_size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fakefile = fmemopen(val.mv_data, val.mv_size, "r");
|
||||
fakestream = StreamFile(fakefile);
|
||||
ret = JsonDecode(fakestream);
|
||||
StreamClose(fakestream);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static bool
|
||||
LMDBKeyStartsWith(MDB_val key, MDB_val starts)
|
||||
{
|
||||
size_t i;
|
||||
if (!key.mv_size || !starts.mv_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (key.mv_size < starts.mv_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < starts.mv_size; i++)
|
||||
{
|
||||
char keyC = ((char *) key.mv_data)[i];
|
||||
char startC = ((char *) starts.mv_data)[i];
|
||||
|
||||
if (keyC != startC)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static char *
|
||||
LMDBKeyHead(MDB_val key)
|
||||
{
|
||||
char *end;
|
||||
if (!key.mv_size || !key.mv_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/* -2 because we have a NULL byte in there */
|
||||
end = ((char *) key.mv_data) + key.mv_size - 1;
|
||||
if ((void *) end < key.mv_data)
|
||||
{
|
||||
/* Uh oh. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while ((void *) (end - 1) >= key.mv_data && *(end - 1))
|
||||
{
|
||||
end--;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
static DbRef *
|
||||
LMDBLock(Db *d, DbHint hint, Array *k)
|
||||
{
|
||||
LMDB *db = (LMDB *) d;
|
||||
MDB_txn *transaction;
|
||||
LMDBRef *ret = NULL;
|
||||
MDB_val key, json_val;
|
||||
int code, flags;
|
||||
if (!d || !k)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
key = LMDBTranslateKey(k);
|
||||
|
||||
/* Create a transaction, honoring hints. */
|
||||
/* TODO: Do we want to create a "main" transaction that everyone inherits
|
||||
* from? */
|
||||
flags = hint == DB_HINT_READONLY ? MDB_RDONLY : 0;
|
||||
if ((code = mdb_txn_begin(db->environ, NULL, flags, &transaction)) != 0)
|
||||
{
|
||||
/* Very bad! */
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
goto end;
|
||||
}
|
||||
|
||||
json_val.mv_size = 0;
|
||||
json_val.mv_data = NULL;
|
||||
code = mdb_get(transaction, db->dbi, &key, &json_val);
|
||||
if (code == MDB_NOTFOUND)
|
||||
{
|
||||
mdb_txn_abort(transaction);
|
||||
goto end;
|
||||
}
|
||||
else if (code != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: mdb_get failure: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
mdb_txn_abort(transaction);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = Malloc(sizeof(*ret));
|
||||
DbRefInit(d, (DbRef *) ret);
|
||||
/* TODO: Timestamp */
|
||||
{
|
||||
size_t i;
|
||||
ret->base.name = ArrayCreate();
|
||||
for (i = 0; i < ArraySize(k); i++)
|
||||
{
|
||||
char *ent = ArrayGet(k, i);
|
||||
StringArrayAppend(ret->base.name, ent);
|
||||
}
|
||||
}
|
||||
ret->base.json = LMDBDecode(json_val);
|
||||
ret->base.hint = hint;
|
||||
ret->transaction = NULL;
|
||||
|
||||
if (hint == DB_HINT_WRITE)
|
||||
{
|
||||
ret->transaction = transaction;
|
||||
}
|
||||
else
|
||||
{
|
||||
mdb_txn_abort(transaction);
|
||||
}
|
||||
end:
|
||||
if (!ret || hint == DB_HINT_READONLY)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
}
|
||||
LMDBKillKey(key);
|
||||
return (DbRef *) ret;
|
||||
}
|
||||
static bool
|
||||
LMDBExists(Db *d, Array *k)
|
||||
{
|
||||
MDB_val key, empty;
|
||||
LMDB *db = (LMDB *) d;
|
||||
MDB_txn *transaction;
|
||||
int code;
|
||||
bool ret = false;
|
||||
if (!d || !k)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
key = LMDBTranslateKey(k);
|
||||
|
||||
/* create a txn */
|
||||
if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0)
|
||||
{
|
||||
/* Very bad! */
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = mdb_get(transaction, db->dbi, &key, &empty) == 0;
|
||||
mdb_txn_abort(transaction);
|
||||
end:
|
||||
LMDBKillKey(key);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return ret;
|
||||
}
|
||||
static bool
|
||||
LMDBDelete(Db *d, Array *k)
|
||||
{
|
||||
MDB_val key, empty;
|
||||
LMDB *db = (LMDB *) d;
|
||||
MDB_txn *transaction;
|
||||
int code;
|
||||
bool ret = false;
|
||||
if (!d || !k)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
key = LMDBTranslateKey(k);
|
||||
|
||||
/* create a txn */
|
||||
if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0)
|
||||
{
|
||||
/* Very bad! */
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = mdb_del(transaction, db->dbi, &key, &empty) == 0;
|
||||
mdb_txn_commit(transaction);
|
||||
end:
|
||||
LMDBKillKey(key);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
LMDBUnlock(Db *d, DbRef *r)
|
||||
{
|
||||
LMDBRef *ref = (LMDBRef *) r;
|
||||
LMDB *db = (LMDB *) d;
|
||||
FILE *fakestream;
|
||||
Stream *stream;
|
||||
MDB_val key, val;
|
||||
bool ret = true;
|
||||
|
||||
if (!d || !r)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
val.mv_data = NULL;
|
||||
val.mv_size = 0;
|
||||
if (ref->transaction && r->hint == DB_HINT_WRITE)
|
||||
{
|
||||
key = LMDBTranslateKey(r->name);
|
||||
|
||||
fakestream = open_memstream((char **) &val.mv_data, &val.mv_size);
|
||||
stream = StreamFile(fakestream);
|
||||
JsonEncode(r->json, stream, JSON_DEFAULT);
|
||||
StreamFlush(stream);
|
||||
StreamClose(stream);
|
||||
|
||||
ret = mdb_put(ref->transaction, db->dbi, &key, &val, 0) == 0;
|
||||
|
||||
mdb_txn_commit(ref->transaction);
|
||||
LMDBKillKey(key);
|
||||
}
|
||||
StringArrayFree(ref->base.name);
|
||||
JsonFree(ref->base.json);
|
||||
Free(ref);
|
||||
|
||||
if (val.mv_data)
|
||||
{
|
||||
free(val.mv_data);
|
||||
}
|
||||
if (ret && r->hint == DB_HINT_WRITE)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static DbRef *
|
||||
LMDBCreate(Db *d, Array *k)
|
||||
{
|
||||
LMDB *db = (LMDB *) d;
|
||||
MDB_txn *transaction;
|
||||
LMDBRef *ret = NULL;
|
||||
MDB_val key, empty_json;
|
||||
int code;
|
||||
if (!d || !k)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
key = LMDBTranslateKey(k);
|
||||
|
||||
/* create a txn */
|
||||
if ((code = mdb_txn_begin(db->environ, NULL, 0, &transaction)) != 0)
|
||||
{
|
||||
/* Very bad! */
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
goto end;
|
||||
}
|
||||
|
||||
empty_json.mv_size = 2;
|
||||
empty_json.mv_data = "{}";
|
||||
/* put data in it */
|
||||
code = mdb_put(transaction, db->dbi, &key, &empty_json, MDB_NOOVERWRITE);
|
||||
if (code == MDB_KEYEXIST)
|
||||
{
|
||||
mdb_txn_abort(transaction);
|
||||
goto end;
|
||||
}
|
||||
else if (code == MDB_MAP_FULL)
|
||||
{
|
||||
Log(LOG_ERR, "%s: db is full", __func__);
|
||||
mdb_txn_abort(transaction);
|
||||
goto end;
|
||||
}
|
||||
else if (code != 0)
|
||||
{
|
||||
Log(LOG_ERR, "%s: mdb_put failure: %s", __func__, mdb_strerror(code));
|
||||
mdb_txn_abort(transaction);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = Malloc(sizeof(*ret));
|
||||
DbRefInit(d, (DbRef *) ret);
|
||||
/* TODO: Timestamp */
|
||||
{
|
||||
size_t i;
|
||||
ret->base.name = ArrayCreate();
|
||||
for (i = 0; i < ArraySize(k); i++)
|
||||
{
|
||||
char *ent = ArrayGet(k, i);
|
||||
StringArrayAppend(ret->base.name, ent);
|
||||
}
|
||||
}
|
||||
ret->base.hint = DB_HINT_WRITE;
|
||||
ret->base.json = HashMapCreate();
|
||||
ret->transaction = transaction;
|
||||
end:
|
||||
if (!ret)
|
||||
{
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
}
|
||||
LMDBKillKey(key);
|
||||
return (DbRef *) ret;
|
||||
}
|
||||
|
||||
static Array *
|
||||
LMDBList(Db *d, Array *k)
|
||||
{
|
||||
LMDB *db = (LMDB *) d;
|
||||
MDB_val key = { 0 };
|
||||
MDB_val subKey;
|
||||
MDB_val val;
|
||||
Array *ret = NULL;
|
||||
|
||||
MDB_cursor *cursor;
|
||||
MDB_cursor_op op = MDB_SET_RANGE;
|
||||
MDB_txn *txn;
|
||||
int code;
|
||||
|
||||
if (!d || !k)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&d->lock);
|
||||
|
||||
/* Marked as read-only, as we just don't need to write anything
|
||||
* when listing */
|
||||
if ((code = mdb_txn_begin(db->environ, NULL, MDB_RDONLY, &txn)) != 0)
|
||||
{
|
||||
/* Very bad! */
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
goto end;
|
||||
}
|
||||
if ((code = mdb_cursor_open(txn, db->dbi, &cursor)) != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not get cursor: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
mdb_txn_abort(txn);
|
||||
goto end;
|
||||
}
|
||||
|
||||
key = LMDBTranslateKey(k);
|
||||
|
||||
/* Small hack to get it to list subitems */
|
||||
((char *) key.mv_data)[0]++;
|
||||
|
||||
ret = ArrayCreate();
|
||||
subKey = key;
|
||||
while (mdb_cursor_get(cursor, &subKey, &val, op) == 0)
|
||||
{
|
||||
/* This searches by *increasing* order. The problem is that it may
|
||||
* extend to unwanted points. Since the values are sorted, we can
|
||||
* just exit if the subkey is incorrect. */
|
||||
char *head = LMDBKeyHead(subKey);
|
||||
if (!LMDBKeyStartsWith(subKey, key))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
StringArrayAppend(ret, head);
|
||||
op = MDB_NEXT;
|
||||
}
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
end:
|
||||
LMDBKillKey(key);
|
||||
pthread_mutex_unlock(&d->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Implementation functions */
|
||||
static void
|
||||
LMDBClose(Db *d)
|
||||
{
|
||||
LMDB *db = (LMDB *) d;
|
||||
if (!d)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mdb_dbi_close(db->environ, db->dbi);
|
||||
mdb_env_close(db->environ);
|
||||
}
|
||||
|
||||
Db *
|
||||
DbOpenLMDB(char *dir, size_t size)
|
||||
{
|
||||
/* TODO */
|
||||
MDB_env *env = NULL;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
int code;
|
||||
LMDB *db;
|
||||
if (!dir || !size)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Try initialising LMDB */
|
||||
if ((code = mdb_env_create(&env)) != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not create LMDB env: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
if ((code = mdb_env_set_mapsize(env, size) != 0))
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not set mapsize to %lu: %s",
|
||||
__func__, (unsigned long) size,
|
||||
mdb_strerror(code)
|
||||
);
|
||||
mdb_env_close(env);
|
||||
return NULL;
|
||||
}
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
if ((code = mdb_env_open(env, dir, MDB_NOTLS, 0644)) != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not open LMDB env: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
mdb_env_close(env);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Initialise a DBI */
|
||||
{
|
||||
if ((code = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not begin transaction: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
mdb_env_close(env);
|
||||
return NULL;
|
||||
}
|
||||
if ((code = mdb_dbi_open(txn, "db", MDB_CREATE, &dbi)) != 0)
|
||||
{
|
||||
Log(LOG_ERR,
|
||||
"%s: could not get transaction dbi: %s",
|
||||
__func__, mdb_strerror(code)
|
||||
);
|
||||
mdb_txn_abort(txn);
|
||||
mdb_env_close(env);
|
||||
return NULL;
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
|
||||
db = Malloc(sizeof(*db));
|
||||
DbInit((Db *) db);
|
||||
db->environ = env;
|
||||
db->dbi = dbi;
|
||||
|
||||
db->base.lockFunc = LMDBLock;
|
||||
db->base.create = LMDBCreate;
|
||||
db->base.unlock = LMDBUnlock;
|
||||
db->base.delete = LMDBDelete;
|
||||
db->base.exists = LMDBExists;
|
||||
db->base.close = LMDBClose;
|
||||
db->base.list = LMDBList;
|
||||
|
||||
return (Db *) db;
|
||||
}
|
||||
|
||||
#else
|
||||
Db *
|
||||
DbOpenLMDB(char *dir, size_t size)
|
||||
{
|
||||
/* Unimplemented function */
|
||||
Log(LOG_ERR,
|
||||
"LMDB support is not enabled. Please compile with --use-lmdb"
|
||||
);
|
||||
(void) size;
|
||||
(void) dir;
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
347
src/Graph.c
Normal file
347
src/Graph.c
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
|
||||
#include <Graph.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct Graph
|
||||
{
|
||||
size_t n;
|
||||
Edge *matrix;
|
||||
};
|
||||
|
||||
Graph *
|
||||
GraphCreate(size_t n)
|
||||
{
|
||||
Graph *g;
|
||||
|
||||
if (!n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g = Malloc(sizeof(Graph));
|
||||
if (!g)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g->n = n;
|
||||
|
||||
g->matrix = Malloc((n * n) * sizeof(Edge));
|
||||
if (!g->matrix)
|
||||
{
|
||||
Free(g);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(g->matrix, 0, (n * n) * sizeof(Edge));
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
Graph *
|
||||
GraphCreateWithEdges(size_t n, Edge * matrix)
|
||||
{
|
||||
Graph *g = GraphCreate(n);
|
||||
|
||||
if (!g)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(g->matrix, matrix, (n * n) * sizeof(Edge));
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
void
|
||||
GraphFree(Graph * g)
|
||||
{
|
||||
if (!g)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Free(g->matrix);
|
||||
Free(g);
|
||||
}
|
||||
|
||||
Edge
|
||||
GraphEdgeGet(Graph * g, Node n1, Node n2)
|
||||
{
|
||||
if (n1 >= g->n || n2 >= g->n)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return g->matrix[(g->n * n1) + n2];
|
||||
}
|
||||
|
||||
Edge
|
||||
GraphEdgeSet(Graph * g, Node n1, Node n2, Edge e)
|
||||
{
|
||||
int oldVal;
|
||||
|
||||
if (n1 >= g->n || n2 >= g->n)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (e < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldVal = g->matrix[(g->n * n1) + n2];
|
||||
|
||||
g->matrix[(g->n * n1) + n2] = e;
|
||||
|
||||
return oldVal;
|
||||
}
|
||||
|
||||
size_t
|
||||
GraphCountNodes(Graph * g)
|
||||
{
|
||||
return g ? g->n : 0;
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphBreadthFirstSearch(Graph * G, Node s, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *queue;
|
||||
Node *result;
|
||||
size_t queueSize;
|
||||
Node i;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (s >= G->n)
|
||||
{
|
||||
Free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
queue = Malloc(G->n * sizeof(Node));
|
||||
queueSize = 0;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
queueSize++;
|
||||
queue[queueSize - 1] = s;
|
||||
|
||||
while (queueSize)
|
||||
{
|
||||
s = queue[queueSize - 1];
|
||||
queueSize--;
|
||||
|
||||
result[*n] = s;
|
||||
(*n)++;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
visited[i] = 1;
|
||||
|
||||
queueSize++;
|
||||
queue[queueSize - 1] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Free(visited);
|
||||
Free(queue);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
GraphDepthFirstSearchRecursive(Graph * G, Node s, Node * result, size_t * n,
|
||||
Node * visited)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
result[*n] = s;
|
||||
(*n)++;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
GraphDepthFirstSearchRecursive(G, i, result, n, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphDepthFirstSearch(Graph * G, Node s, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *result;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
if (s >= G->n)
|
||||
{
|
||||
Free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
|
||||
GraphDepthFirstSearchRecursive(G, s, result, n, visited);
|
||||
|
||||
Free(visited);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
GraphTopologicalSortRecursive(Graph * G, Node s, Node * visited,
|
||||
Node * stack, size_t * stackSize)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
visited[s] = 1;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (GraphEdgeGet(G, s, i) && !visited[i])
|
||||
{
|
||||
GraphTopologicalSortRecursive(G, i, visited, stack, stackSize);
|
||||
}
|
||||
}
|
||||
|
||||
stack[*stackSize] = s;
|
||||
(*stackSize)++;
|
||||
}
|
||||
|
||||
Node *
|
||||
GraphTopologicalSort(Graph * G, size_t * n)
|
||||
{
|
||||
Node *visited;
|
||||
Node *stack;
|
||||
Node *result;
|
||||
|
||||
size_t i;
|
||||
size_t stackSize;
|
||||
|
||||
if (!G || !n)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*n = 0;
|
||||
|
||||
result = Malloc(G->n * sizeof(Node));
|
||||
if (!result)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
visited = Malloc(G->n * sizeof(Node));
|
||||
memset(visited, 0, G->n * sizeof(Node));
|
||||
stack = Malloc(G->n * sizeof(Node));
|
||||
memset(stack, 0, G->n * sizeof(Node));
|
||||
|
||||
stackSize = 0;
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
if (!visited[i])
|
||||
{
|
||||
GraphTopologicalSortRecursive(G, i, visited, stack, &stackSize);
|
||||
}
|
||||
}
|
||||
|
||||
Free(visited);
|
||||
|
||||
while (stackSize)
|
||||
{
|
||||
stackSize--;
|
||||
result[*n] = stack[stackSize];
|
||||
(*n)++;
|
||||
}
|
||||
|
||||
Free(stack);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Graph *
|
||||
GraphTranspose(Graph * G)
|
||||
{
|
||||
Graph *T = Malloc(sizeof(Graph));
|
||||
size_t i, j;
|
||||
|
||||
T->n = G->n;
|
||||
T->matrix = Malloc((G->n * G->n) * sizeof(Edge));
|
||||
|
||||
memset(T->matrix, 0, (T->n * T->n) * sizeof(Edge));
|
||||
|
||||
for (i = 0; i < G->n; i++)
|
||||
{
|
||||
for (j = 0; j < G->n; j++)
|
||||
{
|
||||
if (GraphEdgeGet(G, i, j))
|
||||
{
|
||||
GraphEdgeSet(T, j, i, GraphEdgeGet(G, i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return T;
|
||||
}
|
458
src/HashMap.c
Normal file
458
src/HashMap.c
Normal file
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <HashMap.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
#include <Array.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct HashMapBucket
|
||||
{
|
||||
unsigned long hash;
|
||||
char *key;
|
||||
void *value;
|
||||
} HashMapBucket;
|
||||
|
||||
struct HashMap
|
||||
{
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
HashMapBucket **entries;
|
||||
|
||||
unsigned long (*hashFunc) (const char *);
|
||||
|
||||
float maxLoad;
|
||||
size_t iterator;
|
||||
};
|
||||
|
||||
static unsigned long
|
||||
HashMapHashKey(const char *key)
|
||||
{
|
||||
unsigned long hash = 2166136261u;
|
||||
size_t i = 0;
|
||||
|
||||
while (key[i])
|
||||
{
|
||||
hash ^= (unsigned char) 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 = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!newEntries)
|
||||
{
|
||||
map->capacity /= 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(newEntries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
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->iterator = 0;
|
||||
map->hashFunc = HashMapHashKey;
|
||||
|
||||
map->entries = Malloc(map->capacity * sizeof(HashMapBucket *));
|
||||
if (!map->entries)
|
||||
{
|
||||
Free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(map->entries, 0, map->capacity * sizeof(HashMapBucket *));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapDelete(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
bucket->hash = 0;
|
||||
Free(bucket->key);
|
||||
bucket->key = NULL;
|
||||
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]->key);
|
||||
Free(map->entries[i]);
|
||||
}
|
||||
}
|
||||
Free(map->entries);
|
||||
Free(map);
|
||||
}
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapGet(HashMap * map, const char *key)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = map->hashFunc(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;
|
||||
}
|
||||
|
||||
bool
|
||||
HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*i >= map->capacity)
|
||||
{
|
||||
*i = 0;
|
||||
*key = NULL;
|
||||
*value = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (*i < map->capacity)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[*i];
|
||||
|
||||
*i = *i + 1;
|
||||
|
||||
if (bucket && bucket->hash)
|
||||
{
|
||||
*key = bucket->key;
|
||||
*value = bucket->value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*i = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
HashMapIterate(HashMap * map, char **key, void **value)
|
||||
{
|
||||
if (!map)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HashMapIterateReentrant(map, key, value, &map->iterator);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HashMapMaxLoadSet(HashMap * map, float load)
|
||||
{
|
||||
if (!map || (load > 1.0 || load <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->maxLoad = load;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapFunctionSet(HashMap * map, unsigned long (*hashFunc) (const char *))
|
||||
{
|
||||
if (!map || !hashFunc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->hashFunc = hashFunc;
|
||||
}
|
||||
|
||||
void *
|
||||
HashMapSet(HashMap * map, char *key, void *value)
|
||||
{
|
||||
unsigned long hash;
|
||||
size_t index;
|
||||
|
||||
if (!map || !key || !value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
key = StrDuplicate(key);
|
||||
if (!key)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (map->count + 1 > map->capacity * map->maxLoad)
|
||||
{
|
||||
HashMapGrow(map);
|
||||
}
|
||||
|
||||
hash = map->hashFunc(key);
|
||||
index = hash % map->capacity;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
HashMapBucket *bucket = map->entries[index];
|
||||
|
||||
if (!bucket)
|
||||
{
|
||||
bucket = Malloc(sizeof(HashMapBucket));
|
||||
if (!bucket)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bucket->hash = hash;
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
map->entries[index] = bucket;
|
||||
map->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bucket->hash)
|
||||
{
|
||||
bucket->hash = hash;
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
bucket->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bucket->hash == hash)
|
||||
{
|
||||
void *oldValue = bucket->value;
|
||||
|
||||
Free(bucket->key);
|
||||
bucket->key = key;
|
||||
|
||||
bucket->value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = (index + 1) % map->capacity;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
HashMapIterateFree(char *key, void *value)
|
||||
{
|
||||
if (key)
|
||||
{
|
||||
Free(key);
|
||||
}
|
||||
|
||||
if (value)
|
||||
{
|
||||
Free(value);
|
||||
}
|
||||
}
|
||||
|
||||
Array *
|
||||
HashMapKeys(HashMap * map)
|
||||
{
|
||||
Array *arr;
|
||||
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr = ArrayCreate();
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(map, &key, &val))
|
||||
{
|
||||
ArrayAdd(arr, key);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
Array *
|
||||
HashMapValues(HashMap * map)
|
||||
{
|
||||
Array *arr;
|
||||
|
||||
char *key;
|
||||
void *val;
|
||||
|
||||
if (!map)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arr = ArrayCreate();
|
||||
if (!arr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(map, &key, &val))
|
||||
{
|
||||
ArrayAdd(arr, val);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
665
src/HeaderParser.c
Normal file
665
src/HeaderParser.c
Normal file
|
@ -0,0 +1,665 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <HeaderParser.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <Str.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static int
|
||||
HeaderConsumeWhitespace(HeaderExpr * expr)
|
||||
{
|
||||
int c;
|
||||
|
||||
while (1)
|
||||
{
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_EOF;
|
||||
expr->data.error.msg = "End of stream reached.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isspace(c))
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeWord(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (!isspace(c = StreamGetc(expr->state.stream)))
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeAlnum(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (isalnum(c = StreamGetc(expr->state.stream)))
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *
|
||||
HeaderConsumeArg(HeaderExpr * expr)
|
||||
{
|
||||
char *str = Malloc(16 * sizeof(char));
|
||||
int len = 16;
|
||||
int i;
|
||||
int c;
|
||||
int block = 0;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
i = 0;
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
while (((c = StreamGetc(expr->state.stream)) != ',' && c != ')') || block > 0)
|
||||
{
|
||||
if (i >= len)
|
||||
{
|
||||
len *= 2;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '(')
|
||||
{
|
||||
block++;
|
||||
}
|
||||
else if (c == ')')
|
||||
{
|
||||
block--;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= len)
|
||||
{
|
||||
len++;
|
||||
str = Realloc(str, len * sizeof(char));
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
if (c != EOF)
|
||||
{
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void
|
||||
HeaderParse(Stream * stream, HeaderExpr * expr)
|
||||
{
|
||||
int c;
|
||||
|
||||
if (!expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "NULL pointer to stream.";
|
||||
expr->data.error.lineNo = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (expr->type == HP_DECLARATION && expr->data.declaration.args)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ArraySize(expr->data.declaration.args); i++)
|
||||
{
|
||||
Free(ArrayGet(expr->data.declaration.args, i));
|
||||
}
|
||||
|
||||
ArrayFree(expr->data.declaration.args);
|
||||
}
|
||||
|
||||
expr->state.stream = stream;
|
||||
if (!expr->state.lineNo)
|
||||
{
|
||||
expr->state.lineNo = 1;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
if (StreamEof(stream) || StreamError(stream))
|
||||
{
|
||||
expr->type = HP_EOF;
|
||||
expr->data.error.msg = "End of stream reached.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '/')
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
if (c != '*')
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected comment opening.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
expr->type = HP_COMMENT;
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing comment.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated comment.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '*')
|
||||
{
|
||||
c = StreamGetc(expr->state.stream);
|
||||
if (c == '/')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.text[i] = '*';
|
||||
i++;
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '#')
|
||||
{
|
||||
int i = 0;
|
||||
char *word;
|
||||
|
||||
expr->type = HP_PREPROCESSOR_DIRECTIVE;
|
||||
expr->data.text[i] = '#';
|
||||
i++;
|
||||
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
strncpy(expr->data.text + i, word, HEADER_EXPR_MAX - i - 1);
|
||||
i += strlen(word);
|
||||
|
||||
if (StrEquals(word, "include") ||
|
||||
StrEquals(word, "undef") ||
|
||||
StrEquals(word, "ifdef") ||
|
||||
StrEquals(word, "ifndef"))
|
||||
{
|
||||
/* Read one more word */
|
||||
Free(word);
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
if (i + strlen(word) + 1 >= HEADER_EXPR_MAX)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(expr->data.text + i + 1, word, HEADER_EXPR_MAX - i - 1);
|
||||
expr->data.text[i] = ' ';
|
||||
}
|
||||
|
||||
Free(word);
|
||||
}
|
||||
else if (StrEquals(word, "define") ||
|
||||
StrEquals(word, "if") ||
|
||||
StrEquals(word, "elif") ||
|
||||
StrEquals(word, "error"))
|
||||
{
|
||||
int pC = 0;
|
||||
|
||||
Free(word);
|
||||
expr->data.text[i] = ' ';
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit reached parsing preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
if (pC != '\\')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
pC = c;
|
||||
}
|
||||
}
|
||||
else if (StrEquals(word, "else") ||
|
||||
StrEquals(word, "endif"))
|
||||
{
|
||||
/* Read no more words, that's the whole directive */
|
||||
Free(word);
|
||||
}
|
||||
else
|
||||
{
|
||||
Free(word);
|
||||
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unknown preprocessor directive.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char *word;
|
||||
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
word = HeaderConsumeWord(expr);
|
||||
|
||||
if (StrEquals(word, "typedef"))
|
||||
{
|
||||
int block = 0;
|
||||
int i = 0;
|
||||
|
||||
expr->type = HP_TYPEDEF;
|
||||
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
|
||||
i += strlen(word);
|
||||
expr->data.text[i] = ' ';
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - 1)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing typedef.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated typedef.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
expr->data.text[i] = c;
|
||||
i++;
|
||||
|
||||
if (c == '{')
|
||||
{
|
||||
block++;
|
||||
}
|
||||
else if (c == '}')
|
||||
{
|
||||
block--;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
expr->state.lineNo++;
|
||||
}
|
||||
|
||||
if (block <= 0 && c == ';')
|
||||
{
|
||||
expr->data.text[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (StrEquals(word, "extern"))
|
||||
{
|
||||
int wordLimit = sizeof(expr->data.declaration.returnType) - 8;
|
||||
int wordLen;
|
||||
|
||||
Free(word);
|
||||
|
||||
word = HeaderConsumeWord(expr);
|
||||
wordLen = strlen(word);
|
||||
if (wordLen > wordLimit)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Return of declaration exceeds length limit.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = wordLen;
|
||||
|
||||
expr->type = HP_GLOBAL;
|
||||
strncpy(expr->data.global.type, word, wordLimit);
|
||||
|
||||
if (StrEquals(word, "struct") ||
|
||||
StrEquals(word, "enum") ||
|
||||
StrEquals(word, "const") ||
|
||||
StrEquals(word, "unsigned"))
|
||||
{
|
||||
Free(word);
|
||||
word = HeaderConsumeWord(expr);
|
||||
wordLen = strlen(word);
|
||||
expr->data.global.type[i] = ' ';
|
||||
|
||||
strncpy(expr->data.global.type + i + 1, word, wordLen + 1);
|
||||
i += wordLen + 1;
|
||||
}
|
||||
|
||||
Free(word);
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
if (c == '*')
|
||||
{
|
||||
expr->data.global.type[i] = ' ';
|
||||
|
||||
i++;
|
||||
expr->data.global.type[i] = '*';
|
||||
|
||||
i++;
|
||||
while ((c = HeaderConsumeWhitespace(expr)) == '*')
|
||||
{
|
||||
expr->data.global.type[i] = c;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
StreamUngetc(expr->state.stream, c);
|
||||
word = HeaderConsumeAlnum(expr);
|
||||
|
||||
wordLen = strlen(word);
|
||||
wordLimit = sizeof(expr->data.declaration.name) - 1;
|
||||
|
||||
if (wordLen > wordLimit)
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Function name too long.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(expr->data.global.name, word, wordLimit);
|
||||
Free(word);
|
||||
word = NULL;
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
|
||||
if (c == ';')
|
||||
{
|
||||
/* That's the end of the global. */
|
||||
}
|
||||
else if (c == '[')
|
||||
{
|
||||
/* Looks like we have an array. Slurp all the
|
||||
* dimensions */
|
||||
int i = wordLen;
|
||||
|
||||
expr->data.global.name[i] = '[';
|
||||
|
||||
i++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (i >= HEADER_EXPR_MAX - wordLen)
|
||||
{
|
||||
expr->type = HP_PARSE_ERROR;
|
||||
expr->data.error.msg = "Memory limit exceeded while parsing global array.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = StreamGetc(expr->state.stream);
|
||||
|
||||
if (StreamEof(expr->state.stream) || StreamError(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Unterminated global array.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == ';')
|
||||
{
|
||||
expr->data.global.name[i] = '\0';
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->data.global.name[i] = c;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '(')
|
||||
{
|
||||
expr->type = HP_DECLARATION;
|
||||
expr->data.declaration.args = ArrayCreate();
|
||||
do
|
||||
{
|
||||
word = HeaderConsumeArg(expr);
|
||||
ArrayAdd(expr->data.declaration.args, word);
|
||||
word = NULL;
|
||||
}
|
||||
while ((!StreamEof(expr->state.stream)) && ((c = HeaderConsumeWhitespace(expr)) != ')'));
|
||||
|
||||
if (StreamEof(expr->state.stream))
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "End of file reached before ')'.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
c = HeaderConsumeWhitespace(expr);
|
||||
if (c != ';')
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected ';'.";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expr->type = HP_SYNTAX_ERROR;
|
||||
expr->data.error.msg = "Expected ';', '[', or '('";
|
||||
expr->data.error.lineNo = expr->state.lineNo;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Cope with preprocessor macro expansions at the top
|
||||
* level. */
|
||||
expr->type = HP_UNKNOWN;
|
||||
strncpy(expr->data.text, word, HEADER_EXPR_MAX - 1);
|
||||
}
|
||||
|
||||
Free(word);
|
||||
}
|
||||
}
|
73
src/Html.c
73
src/Html.c
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with
|
||||
* other valuable contributors. See CONTRIBUTORS.txt for the full list.
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#include <Html.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <Telodendria.h>
|
||||
|
||||
void
|
||||
HtmlBegin(Stream * stream, char *title)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StreamPrintf(stream,
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<meta charset=\"utf-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
||||
"<title>%s | Telodendria</title>"
|
||||
"<link rel=\"stylesheet\" href=\"/_matrix/static/telodendria.css\">"
|
||||
"<script src=\"/_matrix/static/telodendria.js\"></script>"
|
||||
"</head>"
|
||||
"<body>"
|
||||
"<pre class=\"logo\">"
|
||||
,title
|
||||
);
|
||||
|
||||
for (i = 0; i < TELODENDRIA_LOGO_HEIGHT; i++)
|
||||
{
|
||||
StreamPrintf(stream, "%s\n", TelodendriaLogo[i]);
|
||||
}
|
||||
|
||||
StreamPrintf(stream,
|
||||
"</pre>"
|
||||
"<h1>%s</h1>"
|
||||
,title);
|
||||
}
|
||||
|
||||
void
|
||||
HtmlEnd(Stream * stream)
|
||||
{
|
||||
StreamPuts(stream,
|
||||
"</body>"
|
||||
"</html>");
|
||||
}
|
642
src/Http.c
Normal file
642
src/Http.c
Normal file
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2024 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 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.
|
||||
*/
|
||||
#include <Http.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <Memory.h>
|
||||
#include <HashMap.h>
|
||||
#include <Util.h>
|
||||
#include <Str.h>
|
||||
|
||||
#ifndef CYTOPLASM_STRING_CHUNK
|
||||
#define CYTOPLASM_STRING_CHUNK 64
|
||||
#endif
|
||||
|
||||
const char *
|
||||
HttpRequestMethodToString(const HttpRequestMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
return "GET";
|
||||
case HTTP_HEAD:
|
||||
return "HEAD";
|
||||
case HTTP_POST:
|
||||
return "POST";
|
||||
case HTTP_PUT:
|
||||
return "PUT";
|
||||
case HTTP_DELETE:
|
||||
return "DELETE";
|
||||
case HTTP_CONNECT:
|
||||
return "CONNECT";
|
||||
case HTTP_OPTIONS:
|
||||
return "OPTIONS";
|
||||
case HTTP_TRACE:
|
||||
return "TRACE";
|
||||
case HTTP_PATCH:
|
||||
return "PATCH";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestMethod
|
||||
HttpRequestMethodFromString(const char *str)
|
||||
{
|
||||
if (StrEquals(str, "GET"))
|
||||
{
|
||||
return HTTP_GET;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "HEAD"))
|
||||
{
|
||||
return HTTP_HEAD;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "POST"))
|
||||
{
|
||||
return HTTP_POST;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "PUT"))
|
||||
{
|
||||
return HTTP_PUT;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "DELETE"))
|
||||
{
|
||||
return HTTP_DELETE;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "CONNECT"))
|
||||
{
|
||||
return HTTP_CONNECT;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "OPTIONS"))
|
||||
{
|
||||
return HTTP_OPTIONS;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "TRACE"))
|
||||
{
|
||||
return HTTP_TRACE;
|
||||
}
|
||||
|
||||
if (StrEquals(str, "PATCH"))
|
||||
{
|
||||
return HTTP_PATCH;
|
||||
}
|
||||
|
||||
return HTTP_METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *
|
||||
HttpStatusToString(const HttpStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HTTP_CONTINUE:
|
||||
return "Continue";
|
||||
case HTTP_SWITCHING_PROTOCOLS:
|
||||
return "Switching Protocols";
|
||||
case HTTP_EARLY_HINTS:
|
||||
return "Early Hints";
|
||||
case HTTP_OK:
|
||||
return "Ok";
|
||||
case HTTP_CREATED:
|
||||
return "Created";
|
||||
case HTTP_ACCEPTED:
|
||||
return "Accepted";
|
||||
case HTTP_NON_AUTHORITATIVE_INFORMATION:
|
||||
return "Non-Authoritative Information";
|
||||
case HTTP_NO_CONTENT:
|
||||
return "No Content";
|
||||
case HTTP_RESET_CONTENT:
|
||||
return "Reset Content";
|
||||
case HTTP_PARTIAL_CONTENT:
|
||||
return "Partial Content";
|
||||
case HTTP_MULTIPLE_CHOICES:
|
||||
return "Multiple Choices";
|
||||
case HTTP_MOVED_PERMANENTLY:
|
||||
return "Moved Permanently";
|
||||
case HTTP_FOUND:
|
||||
return "Found";
|
||||
case HTTP_SEE_OTHER:
|
||||
return "See Other";
|
||||
case HTTP_NOT_MODIFIED:
|
||||
return "Not Modified";
|
||||
case HTTP_TEMPORARY_REDIRECT:
|
||||
return "Temporary Redirect";
|
||||
case HTTP_PERMANENT_REDIRECT:
|
||||
return "Permanent Redirect";
|
||||
case HTTP_BAD_REQUEST:
|
||||
return "Bad Request";
|
||||
case HTTP_UNAUTHORIZED:
|
||||
return "Unauthorized";
|
||||
case HTTP_FORBIDDEN:
|
||||
return "Forbidden";
|
||||
case HTTP_NOT_FOUND:
|
||||
return "Not Found";
|
||||
case HTTP_METHOD_NOT_ALLOWED:
|
||||
return "Method Not Allowed";
|
||||
case HTTP_NOT_ACCEPTABLE:
|
||||
return "Not Acceptable";
|
||||
case HTTP_PROXY_AUTH_REQUIRED:
|
||||
return "Proxy Authentication Required";
|
||||
case HTTP_REQUEST_TIMEOUT:
|
||||
return "Request Timeout";
|
||||
case HTTP_CONFLICT:
|
||||
return "Conflict";
|
||||
case HTTP_GONE:
|
||||
return "Gone";
|
||||
case HTTP_LENGTH_REQUIRED:
|
||||
return "Length Required";
|
||||
case HTTP_PRECONDITION_FAILED:
|
||||
return "Precondition Failed";
|
||||
case HTTP_PAYLOAD_TOO_LARGE:
|
||||
return "Payload Too Large";
|
||||
case HTTP_URI_TOO_LONG:
|
||||
return "URI Too Long";
|
||||
case HTTP_UNSUPPORTED_MEDIA_TYPE:
|
||||
return "Unsupported Media Type";
|
||||
case HTTP_RANGE_NOT_SATISFIABLE:
|
||||
return "Range Not Satisfiable";
|
||||
case HTTP_EXPECTATION_FAILED:
|
||||
return "Expectation Failed";
|
||||
case HTTP_TEAPOT:
|
||||
return "I'm a Teapot";
|
||||
case HTTP_UPGRADE_REQUIRED:
|
||||
return "Upgrade Required";
|
||||
case HTTP_PRECONDITION_REQUIRED:
|
||||
return "Precondition Required";
|
||||
case HTTP_TOO_MANY_REQUESTS:
|
||||
return "Too Many Requests";
|
||||
case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
|
||||
return "Request Header Fields Too Large";
|
||||
case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
|
||||
return "Unavailable For Legal Reasons";
|
||||
case HTTP_INTERNAL_SERVER_ERROR:
|
||||
return "Internal Server Error";
|
||||
case HTTP_NOT_IMPLEMENTED:
|
||||
return "Not Implemented";
|
||||
case HTTP_BAD_GATEWAY:
|
||||
return "Bad Gateway";
|
||||
case HTTP_SERVICE_UNAVAILABLE:
|
||||
return "Service Unavailable";
|
||||
case HTTP_GATEWAY_TIMEOUT:
|
||||
return "Gateway Timeout";
|
||||
case HTTP_VERSION_NOT_SUPPORTED:
|
||||
return "Version Not Supported";
|
||||
case HTTP_VARIANT_ALSO_NEGOTIATES:
|
||||
return "Variant Also Negotiates";
|
||||
case HTTP_NOT_EXTENDED:
|
||||
return "Not Extended";
|
||||
case HTTP_NETWORK_AUTH_REQUIRED:
|
||||
return "Network Authentication Required";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlEncode(char *str)
|
||||
{
|
||||
size_t size;
|
||||
size_t len;
|
||||
char *encoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = CYTOPLASM_STRING_CHUNK;
|
||||
len = 0;
|
||||
encoded = Malloc(size);
|
||||
if (!encoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (len >= size - 4)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
size += CYTOPLASM_STRING_CHUNK;
|
||||
tmp = Realloc(encoded, size);
|
||||
if (!tmp)
|
||||
{
|
||||
Free(encoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoded = tmp;
|
||||
}
|
||||
|
||||
/* Control characters and extended characters */
|
||||
if (c <= 0x1F || c >= 0x7F)
|
||||
{
|
||||
goto percentEncode;
|
||||
}
|
||||
|
||||
/* Reserved and unsafe characters */
|
||||
switch (c)
|
||||
{
|
||||
case '$':
|
||||
case '&':
|
||||
case '+':
|
||||
case ',':
|
||||
case '/':
|
||||
case ':':
|
||||
case ';':
|
||||
case '=':
|
||||
case '?':
|
||||
case '@':
|
||||
case ' ':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '#':
|
||||
case '%':
|
||||
case '{':
|
||||
case '}':
|
||||
case '|':
|
||||
case '\\':
|
||||
case '^':
|
||||
case '~':
|
||||
case '[':
|
||||
case ']':
|
||||
case '`':
|
||||
goto percentEncode;
|
||||
break;
|
||||
default:
|
||||
encoded[len] = c;
|
||||
len++;
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
percentEncode:
|
||||
encoded[len] = '%';
|
||||
len++;
|
||||
snprintf(encoded + len, 3, "%2X", c);
|
||||
len += 2;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
encoded[len] = '\0';
|
||||
return encoded;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpUrlDecode(char *str)
|
||||
{
|
||||
size_t i;
|
||||
size_t inputLen;
|
||||
char *decoded;
|
||||
|
||||
if (!str)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
inputLen = strlen(str);
|
||||
decoded = Malloc(inputLen + 1);
|
||||
|
||||
if (!decoded)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*str)
|
||||
{
|
||||
char c = *str;
|
||||
|
||||
if (c == '%')
|
||||
{
|
||||
unsigned int d;
|
||||
|
||||
str++;
|
||||
|
||||
if (sscanf(str, "%2X", &d) != 1)
|
||||
{
|
||||
/* Decoding error */
|
||||
Free(decoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!d)
|
||||
{
|
||||
/* Null character given, don't put that in the string. */
|
||||
continue;
|
||||
}
|
||||
|
||||
c = (char) d;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = c;
|
||||
i++;
|
||||
|
||||
str++;
|
||||
}
|
||||
|
||||
decoded[i] = '\0';
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParamDecode(char *in)
|
||||
{
|
||||
HashMap *params;
|
||||
|
||||
if (!in)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = HashMapCreate();
|
||||
if (!params)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*in)
|
||||
{
|
||||
char *buf;
|
||||
size_t allocated;
|
||||
size_t len;
|
||||
|
||||
char *decKey;
|
||||
char *decVal;
|
||||
|
||||
/* Read in key */
|
||||
|
||||
allocated = CYTOPLASM_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '=')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += CYTOPLASM_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Sanity check */
|
||||
if (*in != '=')
|
||||
{
|
||||
/* Malformed param */
|
||||
Free(buf);
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
in++;
|
||||
|
||||
/* Decode key */
|
||||
decKey = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decKey)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read in value */
|
||||
allocated = CYTOPLASM_STRING_CHUNK;
|
||||
buf = Malloc(allocated);
|
||||
len = 0;
|
||||
|
||||
while (*in && *in != '&')
|
||||
{
|
||||
if (len >= allocated - 1)
|
||||
{
|
||||
allocated += CYTOPLASM_STRING_CHUNK;
|
||||
buf = Realloc(buf, allocated);
|
||||
}
|
||||
|
||||
buf[len] = *in;
|
||||
len++;
|
||||
in++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
|
||||
/* Decode value */
|
||||
decVal = HttpUrlDecode(buf);
|
||||
Free(buf);
|
||||
|
||||
if (!decVal)
|
||||
{
|
||||
/* Decoding error */
|
||||
HashMapFree(params);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = HashMapSet(params, decKey, decVal);
|
||||
if (buf)
|
||||
{
|
||||
Free(buf);
|
||||
}
|
||||
Free(decKey);
|
||||
|
||||
if (*in == '&')
|
||||
{
|
||||
in++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
char *
|
||||
HttpParamEncode(HashMap * params)
|
||||
{
|
||||
char *key;
|
||||
char *val;
|
||||
char *out = NULL;
|
||||
|
||||
if (!params || !out)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (HashMapIterate(params, &key, (void *) &val))
|
||||
{
|
||||
char *encKey;
|
||||
char *encVal;
|
||||
|
||||
encKey = HttpUrlEncode(key);
|
||||
encVal = HttpUrlEncode(val);
|
||||
|
||||
if (!encKey || !encVal)
|
||||
{
|
||||
/* Memory error */
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
|
||||
Free(encKey);
|
||||
Free(encVal);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
HttpParseHeaders(Stream * fp)
|
||||
{
|
||||
HashMap *headers;
|
||||
|
||||
char *line;
|
||||
ssize_t lineLen;
|
||||
size_t lineSize;
|
||||
|
||||
char *headerKey;
|
||||
char *headerValue;
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
headers = HashMapCreate();
|
||||
if (!headers)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
line = NULL;
|
||||
lineLen = 0;
|
||||
|
||||
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1)
|
||||
{
|
||||
char *headerPtr;
|
||||
|
||||
ssize_t i;
|
||||
size_t len;
|
||||
|
||||
if (StrEquals(line, "\r\n") || StrEquals(line, "\n"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < lineLen; i++)
|
||||
{
|
||||
if (line[i] == ':')
|
||||
{
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
line[i] = tolower((unsigned char) line[i]);
|
||||
}
|
||||
|
||||
len = i + 1;
|
||||
headerKey = Malloc(len * sizeof(char));
|
||||
if (!headerKey)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
strncpy(headerKey, line, len);
|
||||
|
||||
headerPtr = line + i + 1;
|
||||
|
||||
while (isspace((unsigned char) *headerPtr))
|
||||
{
|
||||
headerPtr++;
|
||||
}
|
||||
|
||||
for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--)
|
||||
{
|
||||
if (!isspace((unsigned char) line[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
line[i] = '\0';
|
||||
}
|
||||
|
||||
len = strlen(headerPtr) + 1;
|
||||
headerValue = Malloc(len * sizeof(char));
|
||||
if (!headerValue)
|
||||
{
|
||||
Free(headerKey);
|
||||
goto error;
|
||||
}
|
||||
|
||||
strncpy(headerValue, headerPtr, len);
|
||||
|
||||
Free(HashMapSet(headers, headerKey, headerValue));
|
||||
Free(headerKey);
|
||||
}
|
||||
|
||||
Free(line);
|
||||
return headers;
|
||||
|
||||
error:
|
||||
Free(line);
|
||||
|
||||
while (HashMapIterate(headers, &headerKey, (void **) &headerValue))
|
||||
{
|
||||
Free(headerValue);
|
||||
}
|
||||
|
||||
HashMapFree(headers);
|
||||
|
||||
return NULL;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue