Compare commits

..

108 commits

Author SHA1 Message Date
LDA
d5941986ba Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-09-27 15:48:14 +02:00
71491ab6fc
Update to release v2. 2024-09-21 17:03:14 -04:00
5041e0c991
Let's try again. 2024-09-21 16:59:42 -04:00
66c237727b
Apparently on: [tags] doesn't work. 2024-09-21 16:55:55 -04:00
505d01cea1
Hopefully finalize the release logic. 2024-09-21 16:52:41 -04:00
c7433bb745
Order actions properly. 2024-09-21 16:36:26 -04:00
8546ddb909
Get the ref name properly. 2024-09-21 16:35:18 -04:00
0e946b2422
Fix Release artifact 2024-09-21 16:32:15 -04:00
12e11f74a1
Fix typo in Git command. 2024-09-21 16:26:57 -04:00
3304f8c08f
Add release job. 2024-09-21 16:23:19 -04:00
59e3a6d3e2 Okay nevermind, that breaks CI. 2024-09-21 15:54:33 -04:00
46dcf8ab59 Fix submodule url. 2024-09-21 15:49:59 -04:00
21b830e514 Cytoplasm submodule now clones over SSH. 2024-09-21 15:49:06 -04:00
42223c94bb Basic work toward compiling on Darwin. 2024-09-21 15:47:25 -04:00
e083ece2ed Fast-forward Cytoplasm to latest master. 2024-09-21 15:21:11 -04:00
2fcd51e810 Use Forgejo action runner. 2024-09-21 15:20:05 -04:00
lda
775b1d9571 Use hashtypes(c.f Cytoplasm#47) (#57)
This makes Telodendria compatible with Telodendria/Cytoplasm#47.

---

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.

- [x] 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.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Co-authored-by: LDA <lda@ari.lt>
Reviewed-on: Telodendria/Telodendria#57
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-08-27 09:47:25 -04:00
LDA
1e544a4927 [MOD] Use hashtypes 2024-08-25 21:47:44 +02:00
LDA
4bc7ae92d6 Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-08-25 21:36:20 +02:00
6dd4440125 remove use of install in Makefile (#56)
The current make `install` target uses `install -D` to create leading components when install into a directory.
This doesn't work on some variants of `install(1)`.

Also see Telodendria/Cytoplasm#45 and Telodendria/Cytoplasm#13 and 08166dd3a7.

---

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&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] 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.

Reviewed-on: Telodendria/Telodendria#56
Co-authored-by: LevitatingBusinessMan (Rein Fernhout) <me@levitati.ng>
Co-committed-by: LevitatingBusinessMan (Rein Fernhout) <me@levitati.ng>
2024-08-23 16:16:33 -04:00
lda
e263eca5dc Fix build issues with #28 (#55)
Just here to fix old code issues with #28. (we really need CI back, don't we?)

---

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&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] 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.

Co-authored-by: LDA <lda@ari.lt>
Reviewed-on: Telodendria/Telodendria#55
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-08-21 14:32:42 -04:00
lda
ac9372a30a User Directory Patch #70 (#28)
Reviewed-on: Telodendria/Telodendria#28
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-08-18 19:31:14 -04:00
a8924b8437
Update Cytoplasm to v0.4.1. 2024-05-18 10:55:15 -04:00
lda
b72f18538d Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-05-01 21:02:08 +02:00
lda
ff85b72899 Fix IPv6 issue in parser (#52)
Fixes compilation issue in the parser (and checks IPv6 slightly more).
---

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&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] 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.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/Telodendria#52
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-03-29 11:48:25 -05:00
lda
bccbb3bcac Fix other double-free issue with router. (#53)
Similar issue to #33.
---

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&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] 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.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/Telodendria#53
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-03-29 11:48:04 -05:00
lda
f815f653b0 Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-03-20 15:29:16 +01:00
dede82ad33 Update docs/dev/hosting.md 2024-01-14 14:13:53 -05:00
fde2b26857 Update docs/dev/hosting.md 2024-01-14 09:43:47 -05:00
6305f5d76e Update docs/dev/hosting.md 2024-01-14 09:36:57 -05:00
95a5f6f087 Fix compile warnings on 32-bit systems. 2024-01-13 20:41:29 -05:00
129802fe94 Fix ordering of LDFLAGS. 2024-01-13 20:31:49 -05:00
c7d44866c3 Put -L before -l in LDFLAGS. 2024-01-13 20:26:46 -05:00
85672985eb Fix compiler warnings on 32-bit platforms in json.c. 2024-01-13 20:25:16 -05:00
19443a1c24 Fix unused argument error on Clang. 2024-01-13 20:25:03 -05:00
15fb6d8c2a Make sure CI checks out submodules. 2024-01-13 20:14:42 -05:00
cd22aea772 Require Cytoplasm to be compiled separately.
Also add the Gitea CI runner jobs.
2024-01-13 20:11:58 -05:00
ae0724f01c Support building a local copy of Cytoplasm.
Previously, one would have to install Cytoplasm globally to compile
Telodendria. Now, Telodendria builds and links against its own copy
unless --cytoplasm is set to nothing.
2024-01-13 20:02:57 -05:00
e62389aa14 Make Telodendria compatible with latest Cytoplasm.
This also brings Telodendria to C99 compliance.
2024-01-13 20:02:07 -05:00
f2a4a64b27 Add Cytoplasm as a submodule of Telodendria. 2024-01-13 18:05:00 -05:00
258f509413 Update docs/dev/hosting.md 2024-01-13 10:09:39 -05:00
2e92daeb00 Update docs/dev/hosting.md 2024-01-13 10:06:06 -05:00
ac7ea4dec1 Add docs/dev/hosting.md 2024-01-13 09:51:14 -05:00
54420f0036 Improvements to #44: Implement #22 and #9 (#51)
This pull request makes a very small commit on top of #44.

Closes #44.
Closes #9.
Closes #22.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: Telodendria/Telodendria#51
2024-01-11 19:33:50 -05:00
lda
243de4f1a0 Add new entry to CONTRIBUTORS.txt (#50)
Reviewed-on: Telodendria/Telodendria#50
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-01-06 10:02:53 -05:00
35b9ef51f9 PID File: Properly initialize variables in Main(). 2024-01-05 21:02:45 -05:00
83eb69f2cd Use j2s for the Config API. (#49)
Closes #7.
Closes #46.
Closes #47.

This pull request makes some minor on top of #46.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: lda <lda@noreply.git.telodendria.io>
Reviewed-on: Telodendria/Telodendria#49
2024-01-05 21:00:27 -05:00
22d0e88dde Update copyright year in code. 2024-01-05 20:23:27 -05:00
f83be63d53 Add CONTRIBUTORS.txt and make a note of it in the LICENSE.txt and documentation. 2024-01-05 18:57:19 -05:00
lda
af4fd5dceb Update licensing text for 2024 (#48)
Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/Telodendria#48
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2024-01-04 19:32:03 -05:00
lda
8231e8a078
[LICENSE] Update licensing for 2024 2024-01-05 01:13:56 +01:00
lda
12061afe62 Merge branch 'master' of https://git.telodendria.io/Telodendria/Telodendria 2024-01-05 01:09:13 +01:00
lda
2c6d5194d2 Set an error message with MatrixErrorCreate whenever applicable (#45)
Closes #6.

Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/Telodendria#45
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2023-12-02 10:24:08 -05:00
lda
18488d463e
[FIX] Remove Cytoplasm. 2023-11-30 19:25:04 +01:00
lda
184a0ef1de Merge branch 'master' of https://git.telodendria.io/lda/Telodendria 2023-11-30 19:10:06 +01:00
lda
e8d6f0f857 [FIX] Start fixing merging issue 2023-11-30 19:08:44 +01:00
lda
829b9bd6b4 Merge remote-tracking branch 'upstream/master' 2023-11-30 19:07:18 +01:00
0a91a0c40b Remove site files & update README with current site.
The website is now managed in Grav.
2023-11-21 09:16:50 -05:00
d3dcf334f0 Update change log. 2023-11-21 09:14:03 -05:00
lda
3dae19e82d Refactor code to comply with #8 (#39)
Reviewed-on: Telodendria/Telodendria#39
Co-authored-by: lda <lda@freetards.xyz>
Co-committed-by: lda <lda@freetards.xyz>
2023-11-21 09:11:45 -05:00
2d30719be4 Implement Registration Token Administrator API (#43)
This pull request fully implements and documents all of the registration token administrator API endpoints.

**NOTE:** There is a memory leak when listing all of the registration tokens. Debug this before merging.
 
~~Closes~~ Supersedes #37.

Closes #26.

This pull request is based off of #37, which addresses #26. This pull makes a number of improvements to the logic, organization, and behavior of the API endpoints.

Co-authored-by: hatkid <daichi93100@gmail.com>
Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-authored-by: lda <lda@freetards.xyz>
Co-authored-by: Load Accumulator <lda@freetards.xyz>
Reviewed-on: Telodendria/Telodendria#43
2023-11-10 09:30:53 -05:00
503bfb6104 Suppress warnings of unused variables.
I will use them eventually when I implement all these functions.

I'm probably going to start CI work here soon, so I don't want warnings showing up in the logs unless they're important.
2023-11-08 15:17:27 -05:00
77800e4117 Housekeeping tasks with documentation and versioning. 2023-11-08 14:30:45 -05:00
db4ae0408c Fix admin endpoint names in documentation 2023-11-08 11:21:20 -05:00
edee1288d8 Remove unused global variables. 2023-11-08 09:45:49 -05:00
4b90800a2b Fix leak in RouteConfig, update documentation.
Closes #15.
2023-11-07 01:26:26 -05:00
6e7f170768 Start working on #15. 2023-11-06 20:42:39 -05:00
1f02f3c2a2 Allow customization of compiler in configure. 2023-11-04 15:56:57 -04:00
582c79b608 Schema are now generated out-of-tree.
Closes #40.
2023-11-04 15:28:20 -04:00
42a901b7f5 Update docs/dev/rationale.md 2023-11-01 20:35:48 -04:00
1fee47a628 Use Makefiles instead of a custom script (#38)
This pull request also requires the use of the external [Cytoplasm](/Telodendria/Cytoplasm) repository by removing the in-tree copy of Cytoplasm. The increased modularity requires a little more complex build process, but is overall better. Closes #19

The appropriate documentation has been updated. Closes #18

---

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&mdash;including all
personal information I submit with it&mdash;is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.

- [x] 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.

Reviewed-on: Telodendria/Telodendria#38
2023-11-01 12:27:45 -04:00
6377689a83 Update docs/CHANGELOG.md 2023-11-01 12:16:03 -04:00
23237f97b5 Format source code and apply license. 2023-09-28 19:16:45 -04:00
82ae2d4e41 Update 'docs/CHANGELOG.md' 2023-09-25 16:18:35 -04:00
7d3d84d969 Move Admin routes to /_telodendria/admin/v1 (with version number)
Closes #16.
2023-09-25 16:10:44 -04:00
bf1ce839d0 If HOST_NAME_MAX is undefined, use _POSIX_HOST_NAME_MAX.
Probably good enough. Closes #2
2023-09-25 16:02:09 -04:00
f8d3e54fec Use int instead of char in EvalExpr.
Closes #5
2023-09-25 15:58:43 -04:00
LoaD Accumulator
cb41716bf3 Implement #27 (#36)
Implements #27.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Co-authored-by: Jordan Bancino <jordan@bancino.net>
Reviewed-on: Telodendria/telodendria#36
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-committed-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
2023-09-25 09:39:21 -04:00
f4cb10804a Add change log entry for #35. 2023-09-11 19:53:31 +02:00
3f69954ca7 Convert configuration documentation. 2023-09-11 19:53:31 +02:00
LoaD Accumulator
afc9e0e5dc Fixes issue #33 related to a memory issue, and format some code. (#35)
Fixes #33.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Reviewed-on: Telodendria/telodendria#35
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-committed-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
2023-09-11 19:53:31 +02:00
5067b5bcf0 Remove send-patch and tp. See #20. 2023-09-11 19:53:31 +02:00
36c07ed17d Put the finishing touches on CONTRIBUTING.md. 2023-09-11 19:53:31 +02:00
d7afd6285d Add contributing documentation. 2023-09-11 19:53:31 +02:00
69e7837fb4 Fix issue config. 2023-09-11 19:53:31 +02:00
6f9ac042b5 Don't allow blank issues. 2023-09-11 19:53:31 +02:00
9b65558a5a Fix a few small bugs in Gitea templates. 2023-09-11 19:53:31 +02:00
16a45e67e6 Add Gitea issue templates. 2023-09-11 19:53:31 +02:00
afde23df70 Fix table rendering in stats.md 2023-09-11 19:53:31 +02:00
b8c99a2b1f Finish moving over current Administrator API documentation. 2023-09-11 19:53:31 +02:00
c781950fff Another typo. 2023-09-11 19:53:30 +02:00
60ebbccf7d Fix typo in privileges.md. 2023-09-11 19:53:30 +02:00
73dd12ef7e Add privileges documentation. 2023-09-11 19:53:30 +02:00
20c4a4d0fb Fix typo in docs/user/admin/README.md 2023-09-11 19:53:30 +02:00
4168557c4b Add admin documentation home page. 2023-09-11 19:53:30 +02:00
6247085df1 Add change log entry for #35. 2023-09-11 11:01:50 -04:00
LoaD Accumulator
0172fa083b Fixes issue #33 related to a memory issue, and format some code. (#35)
Fixes #33.

Co-authored-by: LoaD Accumulator <lda@freetards.xyz>
Reviewed-on: Telodendria/telodendria#35
Co-authored-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
Co-committed-by: LoaD Accumulator <lda@noreply.git.telodendria.io>
2023-09-11 10:57:16 -04:00
5ad199353b Add logo and center title. 2023-09-08 22:54:05 +02:00
b32aababb3 Add technical rationale document. 2023-09-08 22:54:05 +02:00
82d4711f7f Add a nice README which will serve as the basis for the website. 2023-09-08 22:54:05 +02:00
66537d1b0c Add repository structure documentation. 2023-09-08 22:54:05 +02:00
6eb16af9b5 Add setup documentation. 2023-09-08 22:54:05 +02:00
cb6963299c Add porting instructions. 2023-09-08 22:54:05 +02:00
c2294723e6 Add usage and install documentation. 2023-09-08 22:54:05 +02:00
5c7ce30b15 Add documentation home page. 2023-09-08 22:54:05 +02:00
63a6bcce1a Remove old change log. 2023-09-08 22:54:05 +02:00
6f14d0eef3 Add CHANGELOG.md 2023-09-08 22:54:05 +02:00
d9217946f3
[FIX] Fix commit f61009a423's mistake 2023-09-08 16:54:07 +02:00
177 changed files with 3529 additions and 23316 deletions

View file

@ -0,0 +1,27 @@
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

View file

@ -0,0 +1,44 @@
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

10
.gitignore vendored
View file

@ -1,13 +1,17 @@
# Telodendria .gitignore
build build
out
data data
Makefile
*-leaked.txt
.env .env
*.patch *.patch
*.orig
*.log *.log
vgcore.* vgcore.*
*.core *.core
Cytoplasm/build
Cytoplasm/out
Cytoplasm/*-leaked.txt
contrib/.vagrant contrib/.vagrant
src/Schema src/Schema
src/include/Schema src/include/Schema

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "Cytoplasm"]
path = Cytoplasm
url = https://git.telodendria.io/Telodendria/Cytoplasm.git

16
CONTRIBUTORS.txt Normal file
View file

@ -0,0 +1,16 @@
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
Cytoplasm Submodule

@ -0,0 +1 @@
Subproject commit 4f316ff7b3a955b831ca4aefb8679ddf3396a7d0

28
Cytoplasm/.indent.pro vendored
View file

@ -1,28 +0,0 @@
-bad
-bap
-bbb
-nbc
-bl
-c36
-cd36
-ncdb
-nce
-ci8
-cli1
-d0
-di1
-ndj
-ei
-fc1
-i4
-ip
-l72
-lc72
-lp
-npcs
-psl
-sc
-nsob
-nut
-nv

View file

@ -1,23 +0,0 @@
/*
* Copyright (C) 2022-2023 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.
*/

View file

@ -1,78 +0,0 @@
# Cytoplasm (libcytoplasm)
Cytoplasm is a general-purpose C library and runtime stub 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 perform various complex tasks. Cytoplasm provides high-level data structures, a basic logging facility, an HTTP client and server, and more.
Cytoplasm aims not to only do one thing well, but to do many things good enough. The primary target of Cytoplasm is simple, yet higher level C applications that have to perform relatively complex tasks, but don't want to pull in a large number of dependencies.
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 C89, and depends only on a POSIX environment. This differentiates it from other general-purpose libraries that often require 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.
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 safetly, this inevitably adds a little overhead.
Originally a part of Telodendria (https://telodendria.io), 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 a Telodendria project, and is maintained along side of Telodendria itself, even living in the same CVS module, but it is designed specifically to be distributed and used totally independent of Telodendria.
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.
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.
## Building
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.
Cytoplasm aims to have zero dependencies beyond what is mandated by POSIX. You only need the standard math and pthread libraries to build it. TLS support can optionally be enabled by setting the TLS_IMPL environment variable. The supported TLS implementations are as follows:
- OpenSSL
- LibreSSL
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. Cytoplasm uses a custom build script instead of Make, for the sake of portability. To build everything, just run the script:
$ sh make.sh
This will produce the following out/ directory:
```
out/
lib/
libcytoplasm.so - The Cytoplasm shared library.
libcytoplasm.a - The Cytoplasm static library.
cytoplasm.o - The Cytoplasm runtime stub.
bin/
hdoc - The documentation generator tool.
man/ - All Cytoplasm API documentation.
```
## Usage
Cytoplasm provides the typical .so and .a files, which can be used to link programs with it in the usual way. However, Cytoplasm also provides a minimal runtime environment that is intended to be used with the library. As such, it also provides a runtime stub, which is intended to be linked in with programs using Cytoplasm. This stub is responsible for setting up and tearing down some Cytoplasm APIs. While it isn't required by any means, it makes Cytoplasm a lot easier to use for programmers by abstracting out all of the common logic that most programs will want to use.
Here is the canonical Hello World written with Cytoplasm:
```c
#include <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 something like this:
$ cc -o hello Hello.c cytoplasm.o -lcytoplasm
This command assumes that the runtime stub resides in the current working directory, and that libcytoplasm.so is in your library path. If you're using the version of Cytoplasm installed by your operating system or software distribution, consult the documentation for the location of the runtime stub. It may be located in /usr/local/libexec or some other similar location. If you've built Cytoplasm from source and wish to link to it from there, you may wish to do something like this:
$ export CYTOPLASM=/path/to/Cytoplasm/out/lib
$ cc -o hello Hello.c "${CYTOPLASM}/cytoplasm.o" \
"-L${CYTOPLASM}" -lcytoplasm
As you may have noticed, C programs using Cytoplasm's runtime stub don't write the main() function. Instead, they use Main(). The main() function is provided by the runtime stub. 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.
Note that both arguments to Main may be treated like any other array or hash map. However, do not invoke ArrayFree() or HashMapFree() on the passed pointers, because memory is cleaned up after Main() returns.

View file

@ -1,28 +0,0 @@
#include <Array.h>
#include <HashMap.h>
#include <Log.h>
int
Main(Array * args, HashMap * env)
{
size_t i;
char *key;
char *val;
Log(LOG_INFO, "Hello World!");
Log(LOG_INFO, "Arguments: %lu", ArraySize(args));
for (i = 0; i < ArraySize(args); i++)
{
Log(LOG_INFO, " [%ld] %s", i, ArrayGet(args, i));
}
Log(LOG_INFO, "Environment:");
while (HashMapIterate(env, &key, (void **) &val))
{
Log(LOG_INFO, " %s = %s", key, val);
}
return 0;
}

View file

@ -1,328 +0,0 @@
#!/usr/bin/env sh
addprefix() {
prefix="$1"
shift
for thing in "$@"; do
echo "${prefix}${thing}"
done
unset prefix
unset thing
}
: "${NAME:=Cytoplasm}"
: "${LIB_NAME:=$(echo ${NAME} | tr '[A-Z]' '[a-z]')}"
: "${VERSION:=0.4.0}"
: "${CVS_TAG:=${NAME}-$(echo ${VERSION} | sed 's/\./_/g')}"
: "${SRC:=src}"
: "${TOOLS:=tools}"
: "${EXAMPLES:=examples}"
: "${BUILD:=build}"
: "${OUT:=out}"
: "${STUB:=RtStub}"
: "${LICENSE:=LICENSE.txt}"
: "${CC:=cc}"
: "${AR:=ar}"
: "${DEFINES:=-D_DEFAULT_SOURCE -DLIB_NAME=\"${NAME}\" -DLIB_VERSION=\"${VERSION}\"}"
: "${INCLUDE:=${SRC}/include}"
: "${CFLAGS:=-Wall -Wextra -pedantic -std=c89 -O3 -pipe}"
: "${LD_EXTRA:=-flto -fdata-sections -ffunction-sections -s -Wl,-gc-sections}"
: "${LDFLAGS:=-lm -pthread}"
if [ "${DEBUG}" = "1" ]; then
CFLAGS="${CFLAGS} -O0 -g"
LD_EXTRA=""
fi
if [ -n "${TLS_IMPL}" ]; then
case "${TLS_IMPL}" in
"LIBRESSL")
TLS_LIBS="-ltls -lcrypto -lssl"
;;
"OPENSSL")
TLS_LIBS="-lcrypto -lssl"
;;
*)
echo "Unrecognized TLS implementation: ${TLS_IMPL}"
echo "Consult Tls.h for supported implementations."
echo "Note that the TLS_ prefix is omitted in TLS_IMPL."
exit 1
;;
esac
DEFINES="${DEFINES} -DTLS_IMPL=TLS_${TLS_IMPL}"
LDFLAGS="${LDFLAGS} ${TLS_LIBS}"
fi
CFLAGS="${CFLAGS} ${DEFINES} $(addprefix -I$(pwd)/ ${INCLUDE})"
LDFLAGS="${LDFLAGS} ${LD_EXTRA}"
# Check the modificiation time of a file. This is used to do
# incremental builds; we only want to rebuild files that have
# have changed.
mod_time() {
if [ -n "$1" ] && [ -f "$1" ]; then
case "$(uname)" in
Linux | CYGWIN_NT* | Haiku)
stat -c %Y "$1"
;;
*BSD | DragonFly | Minix)
stat -f %m "$1"
;;
*)
# Platform unknown, force rebuilding the whole
# project every time.
echo "0"
;;
esac
else
echo "0"
fi
}
# Substitute shell variables in a stream with their actual value
# in this shell.
setsubst() {
SED="/tmp/sed-$RANDOM.txt"
(
set | while IFS='=' read -r var val; do
val=$(echo "$val" | cut -d "'" -f 2- | rev | cut -d "'" -f 2- | rev)
echo "s|\\\${$var}|$val|g"
done
echo "s|\\\${[a-zA-Z_]*}||g"
echo "s|'''|'|g"
) >"$SED"
sed -f "$SED" $@
rm "$SED"
}
recipe_docs() {
export LD_LIBRARY_PATH="${OUT}/lib"
mkdir -p "${OUT}/man/man3"
for header in $(find ${INCLUDE} -name '*.h'); do
basename=$(basename "$header")
man=$(echo "${OUT}/man/man3/$basename" | sed -e 's/\.h$/\.3/')
if [ $(mod_time "$header") -ge $(mod_time "$man") ]; then
echo "DOC $basename"
if ! "${OUT}/bin/hdoc" -D "Os=${NAME}" -i "$header" -o "$man"; then
rm "$man"
exit 1
fi
fi
done
if which makewhatis 2>&1 > /dev/null; then
makewhatis "${OUT}/man"
fi
}
recipe_libs() {
echo "-lm -pthread ${TLS_LIBS}"
}
recipe_build() {
mkdir -p "${BUILD}" "${OUT}/bin" "${OUT}/lib"
cd "${SRC}"
echo "CC = ${CC}"
echo "CFLAGS = ${CFLAGS}"
echo "LDFLAGS = ${LDFLAGS}"
echo
do_rebuild=0
objs=""
for src in $(find . -name '*.c' | cut -d '/' -f 2-); do
obj=$(echo "${BUILD}/$src" | sed -e 's/\.c$/\.o/')
if [ $(basename "$obj" .o) != "${STUB}" ]; then
objs="$objs $obj"
fi
if [ $(mod_time "$src") -ge $(mod_time "../$obj") ]; then
echo "CC $(basename $obj)"
obj_dir=$(dirname "../$obj")
mkdir -p "$obj_dir"
if ! $CC $CFLAGS -fPIC -c -o "../$obj" "$src"; then
exit 1
fi
do_rebuild=1
fi
done
cd ..
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.a" ]; then
echo "AR lib${LIB_NAME}.a"
if ! $AR rcs "${OUT}/lib/lib${LIB_NAME}.a" $objs; then
exit 1
fi
fi
if [ $do_rebuild -eq 1 ] || [ ! -f "${OUT}/lib/lib${LIB_NAME}.so" ]; then
echo "LD lib${LIB_NAME}.so"
if ! $CC -shared -o "${OUT}/lib/lib${LIB_NAME}.so" $objs ${LDFLAGS}; then
exit 1
fi
fi
cp "${BUILD}/${STUB}.o" "${OUT}/lib/${LIB_NAME}.o"
for src in $(find "${TOOLS}" -name '*.c'); do
out=$(basename "$src" .c)
out="${OUT}/bin/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ] || [ $do_rebuild -eq 1 ]; then
echo "CC $(basename $out)"
mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
exit 1
fi
fi
done
recipe_docs
}
recipe_examples() {
for src in $(find "${EXAMPLES}" -name '*.c'); do
out=$(basename "$src" .c)
out="${OUT}/bin/$out"
if [ $(mod_time "$src") -ge $(mod_time "$out") ]; then
echo "CC $(basename $out)"
mkdir -p "$(dirname $out)"
if ! $CC $CFLAGS -o "$out" "$src" "${OUT}/lib/${LIB_NAME}.o" "-L${OUT}/lib" "-l${LIB_NAME}" ${LDFLAGS}; then
exit 1
fi
fi
done
}
recipe_clean() {
rm -r "${BUILD}" "${OUT}"
}
# Update copyright comments in sources and header files.
recipe_license() {
find . -name '*.[ch]' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "LICENSE %s%*c\r" $(basename "$src") "16" " "
fi
srcHeader=$(grep -n -m 1 '^ \*/' "$src" | cut -d ':' -f 1)
head "-n$srcHeader" "$src" |
diff -u -p - "${LICENSE}" |
patch "$src" | grep -v "^Hmm"
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Format source code files by running indent(1) on them.
recipe_format() {
find . -name '*.c' | while IFS= read -r src; do
if [ -t 1 ]; then
printf "FMT %s%*c\r" $(basename "$src") "16" " "
fi
if indent "$src"; then
rm $(basename "$src").BAK
fi
done
if [ -t 1 ]; then
printf "%*c\n" "50" " "
fi
}
# Generate a release tarball, checksum and sign it, and push it to
# the web root.
recipe_release() {
# Tag the release at this point in time.
cvs tag "$CVS_TAG"
mkdir -p "${OUT}/release"
cd "${OUT}/release"
# Generate the release tarball.
cvs export "-r$CVS_TAG" "${NAME}"
mv "${NAME}" "${NAME}-v${VERSION}"
tar -czf "${NAME}-v${VERSION}.tar.gz" "${NAME}-v${VERSION}"
rm -r "${NAME}-v${VERSION}"
# Checksum the release tarball.
sha256 "${NAME}-v${VERSION}.tar.gz" > "${NAME}-v${VERSION}.tar.gz.sha256"
# Sign the release tarball.
if [ ! -z "${SIGNIFY_SECRET}" ]; then
signify -S -s "${SIGNIFY_SECRET}" \
-m "${NAME}-v${VERSION}.tar.gz" \
-x "${NAME}-v${VERSION}.tar.gz.sig"
else
echo "Warning: SIGNIFY_SECRET not net."
echo "The built tarball will not be signed."
fi
}
recipe_patch() {
# If the user has not set their MXID, try to deduce one from
# their system.
if [ -z "$MXID" ]; then
MXID="@${USER}:$(hostname)"
fi
# If the user has not set their EDITOR, use a safe default.
# (vi should be available on any POSIX system)
if [ -z "$EDITOR" ]; then
EDITOR=vi
fi
NORMALIZED_MXID=$(echo "$MXID" | sed -e 's/@//g' -e 's/:/-/g')
PATCH_FILE="${NORMALIZED_MXID}_$(date +%s).patch"
# Generate the actual patch file
# Here we write some nice headers, and then do a cvs diff.
(
printf 'From: "%s" <%s>\n' "$DISPLAY_NAME" "$MXID"
echo "Date: $(date)"
echo "Subject: "
echo
cvs -q diff -uNp $PATCHSET | grep -v '^\? '
) >"$PATCH_FILE"
"$EDITOR" "$PATCH_FILE"
echo "$PATCH_FILE"
}
recipe_diff() {
tmp_patch="/tmp/${NAME}-$(date +%s).patch"
cvs -q diff -uNp $PATCHSET >"$tmp_patch"
if [ -z "$PAGER" ]; then
PAGER="less -F"
fi
$PAGER "$tmp_patch"
rm "$tmp_patch"
}
# Execute the user-specified recipes.
for recipe in $@; do
recipe_$recipe
done
# If no recipe was provided, run a build.
if [ -z "$1" ]; then
recipe_build
fi

View file

@ -1,51 +0,0 @@
.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

View file

@ -1,195 +0,0 @@
.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

View file

@ -1,118 +0,0 @@
/*
* Copyright (C) 2022-2023 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;
}
}
}

View file

@ -1,423 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */
};
int
ArrayAdd(Array * array, void *value)
{
if (!array)
{
return 0;
}
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];
}
extern int
ArrayInsert(Array * array, size_t index, void *value)
{
size_t i;
if (!array || !value || index > array->size)
{
return 0;
}
if (array->size >= array->allocated)
{
void **tmp;
size_t newSize = array->allocated + ARRAY_BLOCK;
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * newSize);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
array->allocated = newSize;
}
for (i = array->size; i > index; i--)
{
array->entries[i] = array->entries[i - 1];
}
array->size++;
array->entries[index] = value;
return 1;
}
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;
}
int
ArrayTrim(Array * array)
{
void **tmp;
if (!array)
{
return 0;
}
tmp = array->entries;
array->entries = Realloc(array->entries,
sizeof(void *) * array->size);
if (!array->entries)
{
array->entries = tmp;
return 0;
}
return 1;
}
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 (!array)
{
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;
}

View file

@ -1,244 +0,0 @@
/*
* Copyright (C) 2022-2023 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';
}
extern int
Base64Pad(char **base64Ptr, size_t length)
{
char *tmp;
size_t newSize;
size_t i;
if (length % 4 == 0)
{
return length; /* Success: no padding needed */
}
newSize = length + (4 - (length % 4));
tmp = Realloc(*base64Ptr, newSize + 100);;
if (!tmp)
{
return 0; /* Memory error */
}
*base64Ptr = tmp;
for (i = length; i < newSize; i++)
{
(*base64Ptr)[i] = '=';
}
(*base64Ptr)[newSize] = '\0';
return newSize;
}

View file

@ -1,252 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <UInt64.h>
#include <Array.h>
#include <Memory.h>
#include <Util.h>
#include <pthread.h>
struct Cron
{
UInt64 tick;
Array *jobs;
pthread_mutex_t lock;
volatile unsigned int stop:1;
pthread_t thread;
};
typedef struct Job
{
UInt64 interval;
UInt64 lastExec;
JobFunc *func;
void *args;
} Job;
static Job *
JobCreate(UInt32 interval, JobFunc * func, void *args)
{
Job *job;
if (!func)
{
return NULL;
}
job = Malloc(sizeof(Job));
if (!job)
{
return NULL;
}
job->interval = UInt64Create(0, interval);
job->lastExec = UInt64Create(0, 0);
job->func = func;
job->args = args;
return job;
}
static void *
CronThread(void *args)
{
Cron *cron = args;
while (!cron->stop)
{
size_t i;
UInt64 ts; /* tick start */
UInt64 te; /* tick end */
pthread_mutex_lock(&cron->lock);
ts = UtilServerTs();
for (i = 0; i < ArraySize(cron->jobs); i++)
{
Job *job = ArrayGet(cron->jobs, i);
if (UInt64Gt(UInt64Sub(ts, job->lastExec), job->interval))
{
job->func(job->args);
job->lastExec = ts;
}
if (UInt64Eq(job->interval, UInt64Create(0, 0)))
{
ArrayDelete(cron->jobs, i);
Free(job);
}
}
te = UtilServerTs();
pthread_mutex_unlock(&cron->lock);
/* Only sleep if the jobs didn't overrun the tick */
if (UInt64Gt(cron->tick, UInt64Sub(te, ts)))
{
const UInt64 microTick = UInt64Create(0, 100);
UInt64 remainingTick = UInt64Sub(cron->tick, UInt64Sub(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 (UInt64Geq(remainingTick, microTick) && !cron->stop)
{
UtilSleepMillis(microTick);
remainingTick = UInt64Sub(remainingTick, microTick);
}
if (UInt64Neq(remainingTick, UInt64Create(0, 0)) && !cron->stop)
{
UtilSleepMillis(remainingTick);
}
}
}
return NULL;
}
Cron *
CronCreate(UInt32 tick)
{
Cron *cron = Malloc(sizeof(Cron));
if (!cron)
{
return NULL;
}
cron->jobs = ArrayCreate();
if (!cron->jobs)
{
Free(cron);
return NULL;
}
cron->tick = UInt64Create(0, tick);
cron->stop = 1;
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, unsigned long 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 = 0;
pthread_create(&cron->thread, NULL, CronThread, cron);
}
void
CronStop(Cron * cron)
{
if (!cron || cron->stop)
{
return;
}
cron->stop = 1;
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);
}

View file

@ -1,969 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <UInt64.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>
struct Db
{
char *dir;
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;
};
struct DbRef
{
HashMap *json;
UInt64 ts;
size_t size;
Array *name;
DbRef *prev;
DbRef *next;
int fd;
Stream *stream;
};
static 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 char *
DbDirName(Db * db, Array * args, size_t strip)
{
size_t i;
char *str = StrConcat(2, db->dir, "/");
for (i = 0; i < ArraySize(args) - strip; i++)
{
char *tmp;
tmp = StrConcat(3, str, ArrayGet(args, i), "/");
Free(str);
str = tmp;
}
return str;
}
static char *
DbFileName(Db * 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])
{
switch (arg[j])
{
case '/':
arg[j] = '_';
break;
case '.':
arg[j] = '-';
break;
default:
break;
}
j++;
}
tmp = StrConcat(3, str, arg,
(i < ArraySize(args) - 1) ? "/" : ".json");
Free(arg);
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;
}
}
Db *
DbOpen(char *dir, size_t cache)
{
Db *db;
pthread_mutexattr_t attr;
if (!dir)
{
return NULL;
}
db = Malloc(sizeof(Db));
if (!db)
{
return NULL;
}
db->dir = dir;
db->maxCache = cache;
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;
if (db->maxCache)
{
db->cache = HashMapCreate();
if (!db->cache)
{
return NULL;
}
}
else
{
db->cache = NULL;
}
return db;
}
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);
DbMaxCacheSet(db, 0);
DbCacheEvict(db);
HashMapFree(db->cache);
pthread_mutex_unlock(&db->lock);
pthread_mutex_destroy(&db->lock);
Free(db);
}
static DbRef *
DbLockFromArr(Db * db, Array * args)
{
char *file;
char *hash;
DbRef *ref;
struct flock lock;
int fd;
Stream *stream;
if (!db || !args)
{
return NULL;
}
ref = NULL;
hash = NULL;
pthread_mutex_lock(&db->lock);
/* Check if the item is in the cache */
hash = DbHashKey(args);
ref = HashMapGet(db->cache, hash);
file = DbFileName(db, args);
fd = open(file, O_RDWR);
if (fd == -1)
{
if (ref)
{
HashMapDelete(db->cache, hash);
JsonFree(ref->json);
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;
}
if (!db->leastRecent)
{
db->leastRecent = db->mostRecent;
}
Free(ref);
}
ref = NULL;
goto finish;
}
stream = StreamFd(fd);
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
/* Lock the file on the disk */
if (fcntl(fd, F_SETLK, &lock) < 0)
{
StreamClose(stream);
ref = NULL;
goto finish;
}
if (ref) /* In cache */
{
UInt64 diskTs = UtilLastModified(file);
ref->fd = fd;
ref->stream = stream;
if (UInt64Gt(diskTs, ref->ts))
{
/* File was modified on disk since it was cached */
HashMap *json = JsonDecode(ref->stream);
if (!json)
{
StreamClose(ref->stream);
ref = NULL;
goto finish;
}
JsonFree(ref->json);
ref->json = json;
ref->ts = diskTs;
ref->size = DbComputeSize(ref->json);
}
/* Float this ref to mostRecent */
if (ref->next)
{
ref->next->prev = ref->prev;
if (!ref->prev)
{
db->leastRecent = ref->next;
}
else
{
ref->prev->next = ref->next;
}
ref->prev = db->mostRecent;
ref->next = NULL;
if (db->mostRecent)
{
db->mostRecent->next = ref;
}
db->mostRecent = ref;
}
/* If there is no least recent, this is the only thing in the
* cache, so it is also least recent. */
if (!db->leastRecent)
{
db->leastRecent = ref;
}
/* The file on disk may be larger than what we have in memory,
* which may require items in cache to be evicted. */
DbCacheEvict(db);
}
else
{
Array *name;
size_t i;
/* Not in cache; load from disk */
ref = Malloc(sizeof(DbRef));
if (!ref)
{
StreamClose(stream);
goto finish;
}
ref->json = JsonDecode(stream);
if (!ref->json)
{
Free(ref);
StreamClose(stream);
ref = NULL;
goto finish;
}
ref->fd = fd;
ref->stream = stream;
name = ArrayCreate();
for (i = 0; i < ArraySize(args); i++)
{
ArrayAdd(name, StrDuplicate(ArrayGet(args, i)));
}
ref->name = name;
if (db->cache)
{
ref->ts = UtilServerTs();
ref->size = DbComputeSize(ref->json);
HashMapSet(db->cache, hash, ref);
db->cacheSize += ref->size;
ref->next = NULL;
ref->prev = db->mostRecent;
if (db->mostRecent)
{
db->mostRecent->next = ref;
}
db->mostRecent = ref;
if (!db->leastRecent)
{
db->leastRecent = ref;
}
/* Adding this item to the cache may case it to grow too
* large, requiring some items to be evicted */
DbCacheEvict(db);
}
}
finish:
if (!ref)
{
pthread_mutex_unlock(&db->lock);
}
Free(file);
Free(hash);
return ref;
}
DbRef *
DbCreate(Db * db, size_t nArgs,...)
{
Stream *fp;
char *file;
char *dir;
va_list ap;
Array *args;
DbRef *ret;
if (!db)
{
return NULL;
}
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return NULL;
}
pthread_mutex_lock(&db->lock);
file = DbFileName(db, args);
if (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
{
Free(file);
ArrayFree(args);
pthread_mutex_unlock(&db->lock);
return NULL;
}
dir = DbDirName(db, args, 1);
if (UtilMkdir(dir, 0750) < 0)
{
Free(file);
ArrayFree(args);
Free(dir);
pthread_mutex_unlock(&db->lock);
return NULL;
}
Free(dir);
fp = StreamOpen(file, "w");
Free(file);
if (!fp)
{
ArrayFree(args);
pthread_mutex_unlock(&db->lock);
return NULL;
}
StreamPuts(fp, "{}");
StreamClose(fp);
/* DbLockFromArr() will lock again for us */
pthread_mutex_unlock(&db->lock);
ret = DbLockFromArr(db, args);
ArrayFree(args);
return ret;
}
int
DbDelete(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
char *hash;
int ret = 1;
DbRef *ref;
if (!db)
{
return 0;
}
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
pthread_mutex_lock(&db->lock);
hash = DbHashKey(args);
file = DbFileName(db, args);
ref = HashMapGet(db->cache, hash);
if (ref)
{
HashMapDelete(db->cache, hash);
JsonFree(ref->json);
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;
}
if (!db->leastRecent)
{
db->leastRecent = db->mostRecent;
}
Free(ref);
}
Free(hash);
if (UInt64Neq(UtilLastModified(file), UInt64Create(0, 0)))
{
ret = remove(file) == 0;
}
pthread_mutex_unlock(&db->lock);
ArrayFree(args);
Free(file);
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)
{
return NULL;
}
ret = DbLockFromArr(db, args);
ArrayFree(args);
return ret;
}
int
DbUnlock(Db * db, DbRef * ref)
{
int destroy;
if (!db || !ref)
{
return 0;
}
lseek(ref->fd, 0L, SEEK_SET);
if (ftruncate(ref->fd, 0) < 0)
{
pthread_mutex_unlock(&db->lock);
Log(LOG_ERR, "Failed to truncate file on disk.");
Log(LOG_ERR, "Error on fd %d: %s", ref->fd, strerror(errno));
return 0;
}
JsonEncode(ref->json, ref->stream, JSON_DEFAULT);
StreamClose(ref->stream);
if (db->cache)
{
char *key = DbHashKey(ref->name);
if (HashMapGet(db->cache, key))
{
db->cacheSize -= ref->size;
ref->size = DbComputeSize(ref->json);
db->cacheSize += ref->size;
/* If this ref has grown significantly since we last
* computed its size, it may have filled the cache and
* require some items to be evicted. */
DbCacheEvict(db);
destroy = 0;
}
else
{
destroy = 1;
}
Free(key);
}
else
{
destroy = 1;
}
if (destroy)
{
JsonFree(ref->json);
StringArrayFree(ref->name);
Free(ref);
}
pthread_mutex_unlock(&db->lock);
return 1;
}
int
DbExists(Db * db, size_t nArgs,...)
{
va_list ap;
Array *args;
char *file;
int ret;
va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap);
va_end(ap);
if (!args)
{
return 0;
}
pthread_mutex_lock(&db->lock);
file = DbFileName(db, args);
ret = UInt64Neq(UtilLastModified(file), UInt64Create(0, 0));
pthread_mutex_unlock(&db->lock);
Free(file);
ArrayFree(args);
return ret;
}
Array *
DbList(Db * db, size_t nArgs,...)
{
Array *result;
Array *path;
DIR *files;
struct dirent *file;
char *dir;
va_list ap;
if (!db || !nArgs)
{
return NULL;
}
result = ArrayCreate();
if (!result)
{
return NULL;
}
va_start(ap, nArgs);
path = ArrayFromVarArgs(nArgs, ap);
dir = DbDirName(db, path, 0);
pthread_mutex_lock(&db->lock);
files = opendir(dir);
if (!files)
{
ArrayFree(path);
ArrayFree(result);
Free(dir);
pthread_mutex_unlock(&db->lock);
return NULL;
}
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';
ArrayAdd(result, StrDuplicate(file->d_name));
}
}
}
closedir(files);
ArrayFree(path);
Free(dir);
pthread_mutex_unlock(&db->lock);
return result;
}
void
DbListFree(Array * arr)
{
StringArrayFree(arr);
}
HashMap *
DbJson(DbRef * ref)
{
return ref ? ref->json : NULL;
}
int
DbJsonSet(DbRef * ref, HashMap * json)
{
if (!ref || !json)
{
return 0;
}
JsonFree(ref->json);
ref->json = JsonDuplicate(json);
return 1;
}

View file

@ -1,347 +0,0 @@
/*
* Copyright (C) 2022-2023 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;
}

View file

@ -1,456 +0,0 @@
/*
* Copyright (C) 2022-2023 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;
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;
}
int
HashMapIterateReentrant(HashMap * map, char **key, void **value, size_t * i)
{
if (!map)
{
return 0;
}
if (*i >= map->capacity)
{
*i = 0;
*key = NULL;
*value = NULL;
return 0;
}
while (*i < map->capacity)
{
HashMapBucket *bucket = map->entries[*i];
*i = *i + 1;
if (bucket && bucket->hash)
{
*key = bucket->key;
*value = bucket->value;
return 1;
}
}
*i = 0;
return 0;
}
int
HashMapIterate(HashMap * map, char **key, void **value)
{
if (!map)
{
return 0;
}
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;
}

View file

@ -1,665 +0,0 @@
/*
* Copyright (C) 2022-2023 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);
}
}

View file

@ -1,642 +0,0 @@
/*
* Copyright (C) 2022-2023 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);
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;
}

View file

@ -1,302 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <HttpClient.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <Http.h>
#include <Memory.h>
#include <Util.h>
#include <Tls.h>
struct HttpClientContext
{
HashMap *responseHeaders;
Stream *stream;
};
HttpClientContext *
HttpRequest(HttpRequestMethod method, int flags, unsigned short port, char *host, char *path)
{
HttpClientContext *context;
int sd = -1;
struct addrinfo hints, *res, *res0;
int error;
char serv[8];
if (!method || !host || !path)
{
return NULL;
}
#ifndef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
return NULL;
}
#endif
if (!port)
{
if (flags & HTTP_FLAG_TLS)
{
strcpy(serv, "https");
}
else
{
strcpy(serv, "www");
}
}
else
{
snprintf(serv, sizeof(serv), "%hu", port);
}
context = Malloc(sizeof(HttpClientContext));
if (!context)
{
return NULL;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, serv, &hints, &res0);
if (error)
{
Free(context);
return NULL;
}
for (res = res0; res; res = res->ai_next)
{
sd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sd < 0)
{
continue;
}
if (connect(sd, res->ai_addr, res->ai_addrlen) < 0)
{
close(sd);
sd = -1;
continue;
}
break;
}
if (sd < 0)
{
Free(context);
return NULL;
}
freeaddrinfo(res0);
#ifdef TLS_IMPL
if (flags & HTTP_FLAG_TLS)
{
context->stream = TlsClientStream(sd, host);
}
else
{
context->stream = StreamFd(sd);
}
#else
context->stream = StreamFd(sd);
#endif
if (!context->stream)
{
Free(context);
close(sd);
return NULL;
}
StreamPrintf(context->stream, "%s %s HTTP/1.0\r\n",
HttpRequestMethodToString(method), path);
HttpRequestHeader(context, "Connection", "close");
HttpRequestHeader(context, "User-Agent", LIB_NAME "/" LIB_VERSION);
HttpRequestHeader(context, "Host", host);
return context;
}
void
HttpRequestHeader(HttpClientContext * context, char *key, char *val)
{
if (!context || !key || !val)
{
return;
}
StreamPrintf(context->stream, "%s: %s\r\n", key, val);
}
void
HttpRequestSendHeaders(HttpClientContext * context)
{
if (!context)
{
return;
}
StreamPuts(context->stream, "\r\n");
StreamFlush(context->stream);
}
HttpStatus
HttpRequestSend(HttpClientContext * context)
{
HttpStatus status = HTTP_STATUS_UNKNOWN;
char *line = NULL;
ssize_t lineLen;
size_t lineSize = 0;
char *tmp;
if (!context)
{
goto finish;
}
StreamFlush(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
while (lineLen == -1 && errno == EAGAIN)
{
StreamClearError(context->stream);
lineLen = UtilGetLine(&line, &lineSize, context->stream);
}
if (lineLen == -1)
{
goto finish;
}
/* Line must contain at least "HTTP/x.x xxx" */
if (lineLen < 12)
{
goto finish;
}
if (!(strncmp(line, "HTTP/1.0", 8) == 0 ||
strncmp(line, "HTTP/1.1", 8) == 0))
{
goto finish;
}
tmp = line + 9;
while (isspace((unsigned char) *tmp) && *tmp != '\0')
{
tmp++;
}
if (!*tmp)
{
goto finish;
}
status = atoi(tmp);
if (!status)
{
status = HTTP_STATUS_UNKNOWN;
goto finish;
}
context->responseHeaders = HttpParseHeaders(context->stream);
if (!context->responseHeaders)
{
status = HTTP_STATUS_UNKNOWN;
goto finish;
}
finish:
Free(line);
return status;
}
HashMap *
HttpResponseHeaders(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->responseHeaders;
}
Stream *
HttpClientStream(HttpClientContext * context)
{
if (!context)
{
return NULL;
}
return context->stream;
}
void
HttpClientContextFree(HttpClientContext * context)
{
char *key;
void *val;
if (!context)
{
return;
}
while (HashMapIterate(context->responseHeaders, &key, &val))
{
Free(val);
}
HashMapFree(context->responseHeaders);
StreamClose(context->stream);
Free(context);
}

View file

@ -1,300 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <HttpRouter.h>
#include <Memory.h>
#include <HashMap.h>
#include <Str.h>
#include <regex.h>
#include <string.h>
#define REG_FLAGS (REG_EXTENDED)
#define REG_MAX_SUB 8
typedef struct RouteNode
{
HttpRouteFunc *exec;
HashMap *children;
regex_t regex;
} RouteNode;
struct HttpRouter
{
RouteNode *root;
};
static RouteNode *
RouteNodeCreate(char *regex, HttpRouteFunc * exec)
{
RouteNode *node;
if (!regex)
{
return NULL;
}
node = Malloc(sizeof(RouteNode));
if (!node)
{
return NULL;
}
node->children = HashMapCreate();
if (!node->children)
{
Free(node);
return NULL;
}
/* Force the regex to match the entire path part exactly. */
regex = StrConcat(3, "^", regex, "$");
if (!regex)
{
Free(node);
return NULL;
}
if (regcomp(&node->regex, regex, REG_FLAGS) != 0)
{
HashMapFree(node->children);
Free(node);
Free(regex);
return NULL;
}
node->exec = exec;
Free(regex);
return node;
}
static void
RouteNodeFree(RouteNode * node)
{
char *key;
RouteNode *val;
if (!node)
{
return;
}
while (HashMapIterate(node->children, &key, (void **) &val))
{
RouteNodeFree(val);
}
HashMapFree(node->children);
regfree(&node->regex);
Free(node);
}
HttpRouter *
HttpRouterCreate(void)
{
HttpRouter *router = Malloc(sizeof(HttpRouter));
if (!router)
{
return NULL;
}
router->root = RouteNodeCreate("/", NULL);
return router;
}
void
HttpRouterFree(HttpRouter * router)
{
if (!router)
{
return;
}
RouteNodeFree(router->root);
Free(router);
}
int
HttpRouterAdd(HttpRouter * router, char *regPath, HttpRouteFunc * exec)
{
RouteNode *node;
char *pathPart;
char *tmp;
if (!router || !regPath || !exec)
{
return 0;
}
if (StrEquals(regPath, "/"))
{
router->root->exec = exec;
return 1;
}
regPath = StrDuplicate(regPath);
if (!regPath)
{
return 0;
}
tmp = regPath;
node = router->root;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
RouteNode *tNode = HashMapGet(node->children, pathPart);
if (!tNode)
{
tNode = RouteNodeCreate(pathPart, NULL);
RouteNodeFree(HashMapSet(node->children, pathPart, tNode));
}
node = tNode;
}
node->exec = exec;
Free(regPath);
return 1;
}
int
HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
{
RouteNode *node;
char *pathPart;
char *tmp;
HttpRouteFunc *exec = NULL;
Array *matches = NULL;
size_t i;
int retval;
if (!router || !path)
{
return 0;
}
matches = ArrayCreate();
if (!matches)
{
return 0;
}
node = router->root;
if (StrEquals(path, "/"))
{
exec = node->exec;
}
else
{
path = StrDuplicate(path);
tmp = path;
while ((pathPart = strtok_r(tmp, "/", &tmp)))
{
char *key;
RouteNode *val = NULL;
regmatch_t pmatch[REG_MAX_SUB];
i = 0;
while (HashMapIterateReentrant(node->children, &key, (void **) &val, &i))
{
if (regexec(&val->regex, pathPart, REG_MAX_SUB, pmatch, 0) == 0)
{
break;
}
val = NULL;
}
if (!val)
{
exec = NULL;
break;
}
node = val;
exec = node->exec;
/* If we want to pass an arg, the match must be in parens */
if (val->regex.re_nsub)
{
/* pmatch[0] is the whole string, not the first
* subexpression */
char * substr;
regmatch_t cpmatch;
for (i = 1; i < REG_MAX_SUB; i++)
{
cpmatch = pmatch[i];
substr = StrSubstr(pathPart, cpmatch.rm_so, cpmatch.rm_eo);
if (pmatch[i].rm_so == -1)
{
break;
}
ArrayAdd(matches, substr);
}
}
}
Free(path);
}
if (!exec)
{
retval = 0;
goto finish;
}
if (ret)
{
*ret = exec(matches, args);
}
else
{
exec(matches, args);
}
retval = 1;
finish:
for (i = 0; i < ArraySize(matches); i++)
{
Free(ArrayGet(matches, i));
}
ArrayFree(matches);
return retval;
}

View file

@ -1,755 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <HttpServer.h>
#include <Memory.h>
#include <Queue.h>
#include <Array.h>
#include <Util.h>
#include <Tls.h>
#include <Log.h>
#include <Str.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
static const char ENABLE = 1;
struct HttpServer
{
HttpServerConfig config;
int sd;
pthread_t socketThread;
volatile unsigned int stop:1;
volatile unsigned int isRunning:1;
Queue *connQueue;
pthread_mutex_t connQueueMutex;
Array *threadPool;
};
struct HttpServerContext
{
HashMap *requestHeaders;
HttpRequestMethod requestMethod;
char *requestPath;
HashMap *requestParams;
HashMap *responseHeaders;
HttpStatus responseStatus;
Stream *stream;
};
typedef struct HttpServerWorkerThreadArgs
{
HttpServer *server;
pthread_t thread;
} HttpServerWorkerThreadArgs;
static HttpServerContext *
HttpServerContextCreate(HttpRequestMethod requestMethod,
char *requestPath, HashMap * requestParams, Stream * stream)
{
HttpServerContext *c;
c = Malloc(sizeof(HttpServerContext));
if (!c)
{
return NULL;
}
c->responseHeaders = HashMapCreate();
if (!c->responseHeaders)
{
Free(c->requestHeaders);
Free(c);
return NULL;
}
c->requestMethod = requestMethod;
c->requestPath = requestPath;
c->requestParams = requestParams;
c->stream = stream;
c->responseStatus = HTTP_OK;
return c;
}
static void
HttpServerContextFree(HttpServerContext * c)
{
char *key;
void *val;
if (!c)
{
return;
}
while (HashMapIterate(c->requestHeaders, &key, &val))
{
Free(val);
}
HashMapFree(c->requestHeaders);
while (HashMapIterate(c->responseHeaders, &key, &val))
{
/*
* These are generated by code. As such, they may be either
* on the heap, or on the stack, depending on how they were
* added.
*
* Basically, if the memory API knows about a pointer, then
* it can be freed. If it doesn't know about a pointer, skip
* freeing it because it's probably a stack pointer.
*/
if (MemoryInfoGet(val))
{
Free(val);
}
}
HashMapFree(c->responseHeaders);
while (HashMapIterate(c->requestParams, &key, &val))
{
Free(val);
}
HashMapFree(c->requestParams);
Free(c->requestPath);
StreamClose(c->stream);
Free(c);
}
HashMap *
HttpRequestHeaders(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestHeaders;
}
HttpRequestMethod
HttpRequestMethodGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_METHOD_UNKNOWN;
}
return c->requestMethod;
}
char *
HttpRequestPath(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestPath;
}
HashMap *
HttpRequestParams(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->requestParams;
}
char *
HttpResponseHeader(HttpServerContext * c, char *key, char *val)
{
if (!c)
{
return NULL;
}
return HashMapSet(c->responseHeaders, key, val);
}
void
HttpResponseStatus(HttpServerContext * c, HttpStatus status)
{
if (!c)
{
return;
}
c->responseStatus = status;
}
HttpStatus
HttpResponseStatusGet(HttpServerContext * c)
{
if (!c)
{
return HTTP_STATUS_UNKNOWN;
}
return c->responseStatus;
}
Stream *
HttpServerStream(HttpServerContext * c)
{
if (!c)
{
return NULL;
}
return c->stream;
}
void
HttpSendHeaders(HttpServerContext * c)
{
Stream *fp = c->stream;
char *key;
char *val;
StreamPrintf(fp, "HTTP/1.0 %d %s\n", c->responseStatus, HttpStatusToString(c->responseStatus));
while (HashMapIterate(c->responseHeaders, &key, (void **) &val))
{
StreamPrintf(fp, "%s: %s\n", key, val);
}
StreamPuts(fp, "\n");
}
static Stream *
DequeueConnection(HttpServer * server)
{
Stream *fp;
if (!server)
{
return NULL;
}
pthread_mutex_lock(&server->connQueueMutex);
fp = QueuePop(server->connQueue);
pthread_mutex_unlock(&server->connQueueMutex);
return fp;
}
HttpServer *
HttpServerCreate(HttpServerConfig * config)
{
HttpServer *server;
struct sockaddr_in sa;
if (!config)
{
errno = EINVAL;
return NULL;
}
if (!config->handler)
{
errno = EINVAL;
return NULL;
}
#ifndef TLS_IMPL
if (config->flags & HTTP_FLAG_TLS)
{
Log(LOG_ERR, "TLS support is disabled.");
errno = EINVAL;
return NULL;
}
#endif
server = Malloc(sizeof(HttpServer));
if (!server)
{
goto error;
}
memset(server, 0, sizeof(HttpServer));
server->config = *config;
server->config.tlsCert = StrDuplicate(config->tlsCert);
server->config.tlsKey = StrDuplicate(config->tlsKey);
server->sd = -1;
server->threadPool = ArrayCreate();
if (!server->threadPool)
{
goto error;
}
server->connQueue = QueueCreate(config->maxConnections);
if (!server->connQueue)
{
goto error;
}
if (pthread_mutex_init(&server->connQueueMutex, NULL) != 0)
{
goto error;
}
server->sd = socket(AF_INET, SOCK_STREAM, 0);
if (server->sd < 0)
{
goto error;
}
if (fcntl(server->sd, F_SETFL, O_NONBLOCK) == -1)
{
goto error;
}
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#ifdef SO_REUSEPORT
if (setsockopt(server->sd, SOL_SOCKET, SO_REUSEPORT, &ENABLE, sizeof(int)) < 0)
{
goto error;
}
#endif
memset(&sa, 0, sizeof(struct sockaddr_in));
sa.sin_family = AF_INET;
sa.sin_port = htons(config->port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server->sd, (struct sockaddr *) & sa, sizeof(sa)) < 0)
{
goto error;
}
if (listen(server->sd, config->maxConnections) < 0)
{
goto error;
}
server->stop = 0;
server->isRunning = 0;
return server;
error:
if (server)
{
if (server->connQueue)
{
QueueFree(server->connQueue);
}
pthread_mutex_destroy(&server->connQueueMutex);
if (server->threadPool)
{
ArrayFree(server->threadPool);
}
if (server->sd > -1)
{
close(server->sd);
}
Free(server);
}
return NULL;
}
HttpServerConfig *
HttpServerConfigGet(HttpServer * server)
{
if (!server)
{
return NULL;
}
return &server->config;
}
void
HttpServerFree(HttpServer * server)
{
if (!server)
{
return;
}
close(server->sd);
QueueFree(server->connQueue);
pthread_mutex_destroy(&server->connQueueMutex);
ArrayFree(server->threadPool);
Free(server->config.tlsCert);
Free(server->config.tlsKey);
Free(server);
}
static void *
HttpServerWorkerThread(void *args)
{
HttpServerWorkerThreadArgs *wArgs = (HttpServerWorkerThreadArgs *) args;
HttpServer *server = wArgs->server;
while (!server->stop)
{
Stream *fp;
HttpServerContext *context;
char *line = NULL;
size_t lineSize = 0;
ssize_t lineLen = 0;
char *requestMethodPtr;
char *pathPtr;
char *requestPath;
char *requestProtocol;
HashMap *requestParams;
ssize_t requestPathLen;
ssize_t i = 0;
HttpRequestMethod requestMethod;
UInt64 firstRead;
fp = DequeueConnection(server);
if (!fp)
{
/* Block for 1 millisecond before continuing so we don't
* murder the CPU if the queue is empty. */
UtilSleepMillis(UInt64Create(0, 1));
continue;
}
/* Get the first line of the request.
*
* Every once in a while, we're too fast for the client. When this
* happens, UtilGetLine() sets errno to EAGAIN. If we get
* EAGAIN, then clear the error on the stream and try again
* after a few ms. This is typically more than enough time for
* the client to send data. */
firstRead = UtilServerTs();
while ((lineLen = UtilGetLine(&line, &lineSize, fp)) == -1
&& errno == EAGAIN)
{
StreamClearError(fp);
/* If the server is stopped, or it's been a while, just
* give up so we aren't wasting a thread on this client. */
if (server->stop || UInt64Gt(UInt64Sub(UtilServerTs(), firstRead), UInt64Create(0, 1000 * 30)))
{
goto finish;
}
UtilSleepMillis(UInt64Create(0, 5));
}
if (lineLen == -1)
{
goto bad_request;
}
requestMethodPtr = line;
for (i = 0; i < lineLen; i++)
{
if (line[i] == ' ')
{
line[i] = '\0';
break;
}
}
if (i == lineLen)
{
goto bad_request;
}
requestMethod = HttpRequestMethodFromString(requestMethodPtr);
if (requestMethod == HTTP_METHOD_UNKNOWN)
{
goto bad_request;
}
pathPtr = line + i + 1;
for (i = 0; i < (line + lineLen) - pathPtr; i++)
{
if (pathPtr[i] == ' ')
{
pathPtr[i] = '\0';
break;
}
}
requestPathLen = i;
requestPath = Malloc(((requestPathLen + 1) * sizeof(char)));
strncpy(requestPath, pathPtr, requestPathLen + 1);
requestProtocol = &pathPtr[i + 1];
line[lineLen - 2] = '\0'; /* Get rid of \r and \n */
if (!StrEquals(requestProtocol, "HTTP/1.1") && !StrEquals(requestProtocol, "HTTP/1.0"))
{
Free(requestPath);
goto bad_request;
}
/* Find request params */
for (i = 0; i < requestPathLen; i++)
{
if (requestPath[i] == '?')
{
break;
}
}
requestPath[i] = '\0';
requestParams = (i == requestPathLen) ? NULL : HttpParamDecode(requestPath + i + 1);
context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp);
if (!context)
{
Free(requestPath);
goto internal_error;
}
context->requestHeaders = HttpParseHeaders(fp);
if (!context->requestHeaders)
{
goto internal_error;
}
server->config.handler(context, server->config.handlerArgs);
HttpServerContextFree(context);
fp = NULL; /* The above call will close this
* Stream */
goto finish;
internal_error:
StreamPuts(fp, "HTTP/1.0 500 Internal Server Error\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
bad_request:
StreamPuts(fp, "HTTP/1.0 400 Bad Request\n");
StreamPuts(fp, "Connection: close\n");
goto finish;
finish:
Free(line);
if (fp)
{
StreamClose(fp);
}
}
return NULL;
}
static void *
HttpServerEventThread(void *args)
{
HttpServer *server = (HttpServer *) args;
struct pollfd pollFds[1];
Stream *fp;
size_t i;
server->isRunning = 1;
server->stop = 0;
pollFds[0].fd = server->sd;
pollFds[0].events = POLLIN;
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = Malloc(sizeof(HttpServerWorkerThreadArgs));
if (!workerThread)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
workerThread->server = server;
if (pthread_create(&workerThread->thread, NULL, HttpServerWorkerThread, workerThread) != 0)
{
/* TODO: Make the event thread return an error to the main
* thread */
return NULL;
}
ArrayAdd(server->threadPool, workerThread);
}
while (!server->stop)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
int connFd;
int pollResult;
pollResult = poll(pollFds, 1, 500);
if (pollResult < 0)
{
/* The poll either timed out, or was interrupted. */
continue;
}
pthread_mutex_lock(&server->connQueueMutex);
/* Don't even accept connections if the queue is full. */
if (!QueueFull(server->connQueue))
{
connFd = accept(server->sd, (struct sockaddr *) & addr, &addrLen);
if (connFd < 0)
{
pthread_mutex_unlock(&server->connQueueMutex);
continue;
}
#ifdef TLS_IMPL
if (server->config.flags & HTTP_FLAG_TLS)
{
fp = TlsServerStream(connFd, server->config.tlsCert, server->config.tlsKey);
}
else
{
fp = StreamFd(connFd);
}
#else
fp = StreamFd(connFd);
#endif
if (!fp)
{
close(connFd);
pthread_mutex_unlock(&server->connQueueMutex);
continue;
}
QueuePush(server->connQueue, fp);
}
pthread_mutex_unlock(&server->connQueueMutex);
}
for (i = 0; i < server->config.threads; i++)
{
HttpServerWorkerThreadArgs *workerThread = ArrayGet(server->threadPool, i);
pthread_join(workerThread->thread, NULL);
Free(workerThread);
}
while ((fp = DequeueConnection(server)))
{
StreamClose(fp);
}
server->isRunning = 0;
return NULL;
}
int
HttpServerStart(HttpServer * server)
{
if (!server)
{
return 0;
}
if (server->isRunning)
{
return 1;
}
if (pthread_create(&server->socketThread, NULL, HttpServerEventThread, server) != 0)
{
return 0;
}
return 1;
}
void
HttpServerJoin(HttpServer * server)
{
if (!server)
{
return;
}
pthread_join(server->socketThread, NULL);
}
void
HttpServerStop(HttpServer * server)
{
if (!server)
{
return;
}
server->stop = 1;
}

View file

@ -1,399 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Int64.h>
#include <stddef.h>
#include <signal.h>
#include <Log.h>
#ifdef INT64_NATIVE
#define Int64Sign(x) ((int) (((UInt64) (x)) >> 63))
#else
#define Int64Sign(x) ((int) ((x).i[1] >> 31))
#endif
size_t
Int64Str(Int64 x, int base, char *out, size_t len)
{
static const char symbols[] = "0123456789ABCDEF";
size_t i = len - 1;
size_t j = 0;
int neg = Int64Sign(x);
Int64 base64 = Int64Create(0, base);
/* We only have symbols up to base 16 */
if (base < 2 || base > 16)
{
return 0;
}
/*
* This algorithm doesn't work on INT64_MIN.
*
* But it works on all other integers in the range, so we
* just scoot the range in by one for now. It's a hack and
* I'm not a huge fan of it, but this function is mostly
* used in Json, which shouldn't have a range this large
* anyway (Json is limited to -2^53 -> 2^53-1).
*
* Proper fixes are always welcome.
*/
if (Int64Eq(x, Int64Create(0x80000000, 0x00000000)))
{
x = Int64Add(x, Int64Create(0, 1));
}
#if 0
else if (Int64Eq(x, Int64Create(0x7FFFFFFF, 0xFFFFFFFF)))
{
x = Int64Sub(x, Int64Create(0, 1));
}
#endif
if (base != 2 && base != 8 && base != 16 && neg)
{
x = Int64Neg(x);
}
do
{
Int64 mod = Int64Rem(x, base64);
Int32 low = Int64Low(mod);
out[i] = symbols[low];
i--;
x = Int64Div(x, base64);
} while (Int64Gt(x, Int64Create(0, 0)));
if (base != 2 && base != 8 && base != 16)
{
/*
* Binary, octal, and hexadecimal are known to
* be bit representations. Everything else (notably
* decimal) should include the negative sign.
*/
if (neg)
{
out[i] = '-';
i--;
}
}
while (++i < len)
{
out[j++] = out[i];
}
out[j] = '\0';
return j;
}
#ifndef INT64_NATIVE
/* No native 64-bit support, add our own */
Int64
Int64Create(UInt32 high, UInt32 low)
{
Int64 x;
x.i[0] = low;
x.i[1] = high;
return x;
}
Int64
Int64Add(Int64 x, Int64 y)
{
Int64 z = Int64Create(0, 0);
int carry;
z.i[0] = x.i[0] + y.i[0];
carry = z.i[0] < x.i[0];
z.i[1] = x.i[1] + y.i[1] + carry;
return z;
}
Int64
Int64Sub(Int64 x, Int64 y)
{
return Int64Add(x, Int64Neg(y));
}
Int64
Int64Mul(Int64 x, Int64 y)
{
Int64 z = Int64Create(0, 0);
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg)
{
x = Int64Neg(x);
}
if (yneg)
{
y = Int64Neg(y);
}
/* while (y > 0) */
while (Int64Gt(y, Int64Create(0, 0)))
{
/* if (y & 1 != 0) */
if (Int64Neq(Int64And(y, Int64Create(0, 1)), Int64Create(0, 0)))
{
z = Int64Add(z, x);
}
x = Int64Sll(x, 1);
y = Int64Sra(y, 1);
}
if (xneg != yneg)
{
z = Int64Neg(z);
}
return z;
}
typedef struct
{
Int64 q;
Int64 r;
} Int64Ldiv;
static Int64Ldiv
Int64LongDivision(Int64 n, Int64 d)
{
Int64Ldiv o;
int i;
int nneg = Int64Sign(n);
int dneg = Int64Sign(d);
o.q = Int64Create(0, 0);
o.r = Int64Create(0, 0);
if (Int64Eq(d, Int64Create(0, 0)))
{
raise(SIGFPE);
return o;
}
if (nneg)
{
n = Int64Neg(n);
}
if (dneg)
{
d = Int64Neg(d);
}
for (i = 63; i >= 0; i--)
{
Int64 bit = Int64And(Int64Sra(n, i), Int64Create(0, 1));
o.r = Int64Sll(o.r, 1);
o.r = Int64Or(o.r, bit);
if (Int64Geq(o.r, d))
{
o.r = Int64Sub(o.r, d);
o.q = Int64Or(o.q, Int64Sll(Int64Create(0, 1), i));
}
}
if (nneg != dneg)
{
o.r = Int64Neg(o.r);
o.q = Int64Neg(o.q);
}
return o;
}
Int64
Int64Div(Int64 x, Int64 y)
{
return Int64LongDivision(x, y).q;
}
Int64
Int64Rem(Int64 x, Int64 y)
{
return Int64LongDivision(x, y).r;
}
Int64
Int64Sll(Int64 x, int y)
{
Int64 z;
if (!y)
{
return x;
}
z = Int64Create(0, 0);
if (y < 32)
{
z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y);
z.i[0] = x.i[0] << y;
}
else
{
z.i[1] = x.i[0] << (y - 32);
}
return z;
}
Int64
Int64Sra(Int64 x, int y)
{
Int64 z;
int neg = Int64Sign(x);
if (!y)
{
return x;
}
z = Int64Create(0, 0);
if (y < 32)
{
z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y);
z.i[1] = x.i[1] >> y;
}
else
{
z.i[0] = x.i[1] >> (y - 32);
}
if (neg)
{
Int64 mask = Int64Create(0xFFFFFFFF, 0xFFFFFFFF);
z = Int64Or(Int64Sll(mask, (64 - y)), z);
}
return z;
}
Int64
Int64And(Int64 x, Int64 y)
{
return Int64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]);
}
Int64
Int64Or(Int64 x, Int64 y)
{
return Int64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]);
}
Int64
Int64Xor(Int64 x, Int64 y)
{
return Int64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]);
}
Int64
Int64Not(Int64 x)
{
return Int64Create(~(x.i[1]), ~(x.i[0]));
}
int
Int64Eq(Int64 x, Int64 y)
{
return x.i[0] == y.i[0] && x.i[1] == y.i[1];
}
int
Int64Lt(Int64 x, Int64 y)
{
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg != yneg)
{
return xneg > yneg;
}
else
{
if (xneg)
{
/* Both negative */
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
}
else
{
/* Both positive */
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
}
}
}
int
Int64Gt(Int64 x, Int64 y)
{
int xneg = Int64Sign(x);
int yneg = Int64Sign(y);
if (xneg != yneg)
{
return xneg < yneg;
}
else
{
if (xneg)
{
/* Both negative */
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
}
else
{
/* Both positive */
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
}
}
}
#endif

View file

@ -1,212 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Io.h>
#include <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
struct Io
{
IoFunctions io;
void *cookie;
};
Io *
IoCreate(void *cookie, IoFunctions funcs)
{
Io *io;
/* Must have at least read or write */
if (!funcs.read && !funcs.write)
{
return NULL;
}
io = Malloc(sizeof(Io));
if (!io)
{
return NULL;
}
io->cookie = cookie;
io->io.read = funcs.read;
io->io.write = funcs.write;
io->io.seek = funcs.seek;
io->io.close = funcs.close;
return io;
}
ssize_t
IoRead(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.read)
{
errno = EBADF;
return -1;
}
return io->io.read(io->cookie, buf, nBytes);
}
ssize_t
IoWrite(Io * io, void *buf, size_t nBytes)
{
if (!io || !io->io.write)
{
errno = EBADF;
return -1;
}
return io->io.write(io->cookie, buf, nBytes);
}
off_t
IoSeek(Io * io, off_t offset, int whence)
{
if (!io)
{
errno = EBADF;
return -1;
}
if (!io->io.seek)
{
errno = EINVAL;
return -1;
}
return io->io.seek(io->cookie, offset, whence);
}
int
IoClose(Io * io)
{
int ret;
if (!io)
{
errno = EBADF;
return -1;
}
if (io->io.close)
{
ret = io->io.close(io->cookie);
}
else
{
ret = 0;
}
Free(io);
return ret;
}
int
IoVprintf(Io * io, const char *fmt, va_list ap)
{
char *buf = NULL;
size_t bufSize = 0;
FILE *fp;
int ret;
if (!io || !fmt)
{
return -1;
}
fp = open_memstream(&buf, &bufSize);
if (!fp)
{
return -1;
}
ret = vfprintf(fp, fmt, ap);
fclose(fp);
if (ret >= 0)
{
ret = IoWrite(io, buf, bufSize);
}
free(buf); /* Allocated by stdlib, not Memory
* API */
return ret;
}
int
IoPrintf(Io * io, const char *fmt,...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = IoVprintf(io, fmt, ap);
va_end(ap);
return ret;
}
ssize_t
IoCopy(Io * in, Io * out)
{
ssize_t nBytes = 0;
char buf[IO_BUFFER];
ssize_t rRes;
ssize_t wRes;
if (!in || !out)
{
errno = EBADF;
return -1;
}
while ((rRes = IoRead(in, &buf, IO_BUFFER)) != 0)
{
if (rRes == -1)
{
return -1;
}
wRes = IoWrite(out, &buf, rRes);
if (wRes == -1)
{
return -1;
}
nBytes += wRes;
}
return nBytes;
}

View file

@ -1,95 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Io.h>
#include <Memory.h>
#include <fcntl.h>
static ssize_t
IoReadFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return read(fd, buf, nBytes);
}
static ssize_t
IoWriteFd(void *cookie, void *buf, size_t nBytes)
{
int fd = *((int *) cookie);
return write(fd, buf, nBytes);
}
static off_t
IoSeekFd(void *cookie, off_t offset, int whence)
{
int fd = *((int *) cookie);
return lseek(fd, offset, whence);
}
static int
IoCloseFd(void *cookie)
{
int fd = *((int *) cookie);
Free(cookie);
return close(fd);
}
Io *
IoFd(int fd)
{
int *cookie = Malloc(sizeof(int));
IoFunctions f;
if (!cookie)
{
return NULL;
}
*cookie = fd;
f.read = IoReadFd;
f.write = IoWriteFd;
f.seek = IoSeekFd;
f.close = IoCloseFd;
return IoCreate(cookie, f);
}
Io *
IoOpen(const char *path, int flags, mode_t mode)
{
int fd = open(path, flags, mode);
if (fd == -1)
{
return NULL;
}
return IoFd(fd);
}

View file

@ -1,91 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Io.h>
#include <stdio.h>
static ssize_t
IoReadFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
return fread(buf, 1, nBytes, fp);
}
static ssize_t
IoWriteFile(void *cookie, void *buf, size_t nBytes)
{
FILE *fp = cookie;
size_t res = fwrite(buf, 1, nBytes, fp);
/*
* fwrite() may be buffered on some platforms, but at this low level,
* it should not be; buffering happens in Stream, not Io.
*/
fflush(fp);
return res;
}
static off_t
IoSeekFile(void *cookie, off_t offset, int whence)
{
FILE *fp = cookie;
off_t ret = fseeko(fp, offset, whence);
if (ret > -1)
{
return ftello(fp);
}
else
{
return ret;
}
}
static int
IoCloseFile(void *cookie)
{
FILE *fp = cookie;
return fclose(fp);
}
Io *
IoFile(FILE * fp)
{
IoFunctions f;
if (!fp)
{
return NULL;
}
f.read = IoReadFile;
f.write = IoWriteFile;
f.seek = IoSeekFile;
f.close = IoCloseFile;
return IoCreate(fp, f);
}

File diff suppressed because it is too large Load diff

View file

@ -1,393 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Log.h>
#include <Memory.h>
#include <Util.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#define LOG_TSBUFFER 64
struct LogConfig
{
int level;
size_t indent;
Stream *out;
int flags;
char *tsFmt;
pthread_mutex_t lock;
};
LogConfig *globalConfig = NULL;
LogConfig *
LogConfigCreate(void)
{
LogConfig *config;
config = Malloc(sizeof(LogConfig));
if (!config)
{
return NULL;
}
memset(config, 0, sizeof(LogConfig));
pthread_mutex_init(&config->lock, NULL);
LogConfigLevelSet(config, LOG_INFO);
LogConfigIndentSet(config, 0);
LogConfigOutputSet(config, NULL); /* Will set to stdout */
LogConfigFlagSet(config, LOG_FLAG_COLOR);
LogConfigTimeStampFormatSet(config, "%y-%m-%d %H:%M:%S");
return config;
}
LogConfig *
LogConfigGlobal(void)
{
if (!globalConfig)
{
globalConfig = LogConfigCreate();
}
return globalConfig;
}
void
LogConfigFlagClear(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags &= ~flags;
}
static int
LogConfigFlagGet(LogConfig * config, int flags)
{
if (!config)
{
return 0;
}
return config->flags & flags;
}
void
LogConfigFlagSet(LogConfig * config, int flags)
{
if (!config)
{
return;
}
config->flags |= flags;
}
void
LogConfigFree(LogConfig * config)
{
if (!config)
{
return;
}
pthread_mutex_destroy(&config->lock);
Free(config);
if (config == globalConfig)
{
globalConfig = NULL;
}
}
void
LogConfigIndent(LogConfig * config)
{
if (config)
{
config->indent += 2;
}
}
void
LogConfigIndentSet(LogConfig * config, size_t indent)
{
if (!config)
{
return;
}
config->indent = indent;
}
int
LogConfigLevelGet(LogConfig * config)
{
if (!config)
{
return -1;
}
return config->level;
}
void
LogConfigLevelSet(LogConfig * config, int level)
{
if (!config)
{
return;
}
switch (level)
{
case LOG_ERR:
case LOG_WARNING:
case LOG_INFO:
case LOG_DEBUG:
config->level = level;
default:
break;
}
}
void
LogConfigOutputSet(LogConfig * config, Stream * out)
{
if (!config)
{
return;
}
if (out)
{
config->out = out;
}
else
{
config->out = StreamStdout();
}
}
void
LogConfigTimeStampFormatSet(LogConfig * config, char *tsFmt)
{
if (config)
{
config->tsFmt = tsFmt;
}
}
void
LogConfigUnindent(LogConfig * config)
{
if (config && config->indent >= 2)
{
config->indent -= 2;
}
}
void
Logv(LogConfig * config, int level, const char *msg, va_list argp)
{
size_t i;
int doColor;
char indicator;
/*
* Only proceed if we have a config and its log level is set to a
* value that permits us to log. This is as close as we can get
* to a no-op function if we aren't logging anything, without doing
* some crazy macro magic.
*/
if (!config || level > config->level)
{
return;
}
/* Misconfiguration */
if (!config->out)
{
return;
}
pthread_mutex_lock(&config->lock);
if (LogConfigFlagGet(config, LOG_FLAG_SYSLOG))
{
/* No further print logic is needed; syslog will handle it all
* for us. */
vsyslog(level, msg, argp);
pthread_mutex_unlock(&config->lock);
return;
}
doColor = LogConfigFlagGet(config, LOG_FLAG_COLOR)
&& isatty(StreamFileno(config->out));
if (doColor)
{
char *ansi;
switch (level)
{
case LOG_EMERG:
case LOG_ALERT:
case LOG_CRIT:
case LOG_ERR:
/* Bold Red */
ansi = "\033[1;31m";
break;
case LOG_WARNING:
/* Bold Yellow */
ansi = "\033[1;33m";
break;
case LOG_NOTICE:
/* Bold Magenta */
ansi = "\033[1;35m";
break;
case LOG_INFO:
/* Bold Green */
ansi = "\033[1;32m";
break;
case LOG_DEBUG:
/* Bold Blue */
ansi = "\033[1;34m";
break;
default:
ansi = "";
break;
}
StreamPuts(config->out, ansi);
}
StreamPutc(config->out, '[');
if (config->tsFmt)
{
time_t timer = time(NULL);
struct tm *timeInfo = localtime(&timer);
char tsBuffer[LOG_TSBUFFER];
int tsLength = strftime(tsBuffer, LOG_TSBUFFER, config->tsFmt,
timeInfo);
if (tsLength)
{
StreamPuts(config->out, tsBuffer);
if (!isspace((unsigned char) tsBuffer[tsLength - 1]))
{
StreamPutc(config->out, ' ');
}
}
}
StreamPrintf(config->out, "(%lu) ", UtilThreadNo());
switch (level)
{
case LOG_EMERG:
indicator = '#';
break;
case LOG_ALERT:
indicator = '@';
break;
case LOG_CRIT:
indicator = 'X';
break;
case LOG_ERR:
indicator = 'x';
break;
case LOG_WARNING:
indicator = '!';
break;
case LOG_NOTICE:
indicator = '~';
break;
case LOG_INFO:
indicator = '>';
break;
case LOG_DEBUG:
indicator = '*';
break;
default:
indicator = '?';
break;
}
StreamPrintf(config->out, "%c]", indicator);
if (doColor)
{
/* ANSI Reset */
StreamPuts(config->out, "\033[0m");
}
StreamPutc(config->out, ' ');
for (i = 0; i < config->indent; i++)
{
StreamPutc(config->out, ' ');
}
StreamVprintf(config->out, msg, argp);
StreamPutc(config->out, '\n');
StreamFlush(config->out);
pthread_mutex_unlock(&config->lock);
}
void
LogTo(LogConfig * config, int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(config, level, fmt, argp);
va_end(argp);
}
extern void
Log(int level, const char *fmt,...)
{
va_list argp;
va_start(argp, fmt);
Logv(LogConfigGlobal(), level, fmt, argp);
va_end(argp);
}

View file

@ -1,625 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Memory.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <Int.h>
#ifndef MEMORY_TABLE_CHUNK
#define MEMORY_TABLE_CHUNK 256
#endif
#ifndef MEMORY_HEXDUMP_WIDTH
#define MEMORY_HEXDUMP_WIDTH 16
#endif
struct MemoryInfo
{
size_t size;
const char *file;
int line;
void *pointer;
};
#define MEM_BOUND_TYPE UInt32
#define MEM_BOUND 0xDEADBEEF
#define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p)
#define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((UInt8 *) p) + x)) + 1)
#define MEM_SIZE_ACTUAL(x) (((x) * sizeof(UInt8)) + (2 * sizeof(MEM_BOUND_TYPE)))
static pthread_mutex_t lock;
static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook;
static void *hookArgs = NULL;
static MemoryInfo **allocations = NULL;
static size_t allocationsSize = 0;
static size_t allocationsLen = 0;
int
MemoryRuntimeInit(void)
{
pthread_mutexattr_t attr;
int ret = 0;
if (pthread_mutexattr_init(&attr) != 0)
{
goto finish;
}
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
ret = pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
ret = (ret == 0);
finish:
return ret;
}
int
MemoryRuntimeDestroy(void)
{
MemoryFreeAll();
return pthread_mutex_destroy(&lock) == 0;
}
static size_t
MemoryHash(void *p)
{
return (((size_t) p) >> 2 * 7) % allocationsSize;
}
static int
MemoryInsert(MemoryInfo * a)
{
size_t hash;
if (!allocations)
{
allocationsSize = MEMORY_TABLE_CHUNK;
allocations = calloc(allocationsSize, sizeof(void *));
if (!allocations)
{
return 0;
}
}
/* If the next insertion would cause the table to be at least 3/4
* full, re-allocate and re-hash. */
if ((allocationsLen + 1) >= ((allocationsSize * 3) >> 2))
{
size_t i;
size_t tmpAllocationsSize = allocationsSize;
MemoryInfo **tmpAllocations;
allocationsSize += MEMORY_TABLE_CHUNK;
tmpAllocations = calloc(allocationsSize, sizeof(void *));
if (!tmpAllocations)
{
return 0;
}
for (i = 0; i < tmpAllocationsSize; i++)
{
if (allocations[i])
{
hash = MemoryHash(allocations[i]->pointer);
while (tmpAllocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
tmpAllocations[hash] = allocations[i];
}
}
free(allocations);
allocations = tmpAllocations;
}
hash = MemoryHash(a->pointer);
while (allocations[hash])
{
hash = (hash + 1) % allocationsSize;
}
allocations[hash] = a;
allocationsLen++;
return 1;
}
static void
MemoryDelete(MemoryInfo * a)
{
size_t hash = MemoryHash(a->pointer);
size_t count = 0;
while (count <= allocationsSize)
{
if (allocations[hash] && allocations[hash] == a)
{
allocations[hash] = NULL;
allocationsLen--;
return;
}
else
{
hash = (hash + 1) % allocationsSize;
count++;
}
}
}
static int
MemoryCheck(MemoryInfo * a)
{
if (MEM_BOUND_LOWER(a->pointer) != MEM_BOUND ||
MEM_BOUND_UPPER(a->pointer, a->size - (2 * sizeof(MEM_BOUND_TYPE))) != MEM_BOUND)
{
if (hook)
{
hook(MEMORY_CORRUPTED, a, hookArgs);
}
return 0;
}
return 1;
}
void *
MemoryAllocate(size_t size, const char *file, int line)
{
void *p;
MemoryInfo *a;
MemoryIterate(NULL, NULL);
pthread_mutex_lock(&lock);
a = malloc(sizeof(MemoryInfo));
if (!a)
{
pthread_mutex_unlock(&lock);
return NULL;
}
p = malloc(MEM_SIZE_ACTUAL(size));
if (!p)
{
free(a);
pthread_mutex_unlock(&lock);
return NULL;
}
memset(p, 0, MEM_SIZE_ACTUAL(size));
MEM_BOUND_LOWER(p) = MEM_BOUND;
MEM_BOUND_UPPER(p, size) = MEM_BOUND;
a->size = MEM_SIZE_ACTUAL(size);
a->file = file;
a->line = line;
a->pointer = p;
if (!MemoryInsert(a))
{
free(a);
free(p);
pthread_mutex_unlock(&lock);
return NULL;
}
if (hook)
{
hook(MEMORY_ALLOCATE, a, hookArgs);
}
pthread_mutex_unlock(&lock);
return ((MEM_BOUND_TYPE *) p) + 1;
}
void *
MemoryReallocate(void *p, size_t size, const char *file, int line)
{
MemoryInfo *a;
void *new = NULL;
MemoryIterate(NULL, NULL);
if (!p)
{
return MemoryAllocate(size, file, line);
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
new = realloc(a->pointer, MEM_SIZE_ACTUAL(size));
if (new)
{
MemoryDelete(a);
a->size = MEM_SIZE_ACTUAL(size);
a->file = file;
a->line = line;
a->pointer = new;
MemoryInsert(a);
MEM_BOUND_LOWER(a->pointer) = MEM_BOUND;
MEM_BOUND_UPPER(a->pointer, size) = MEM_BOUND;
if (hook)
{
hook(MEMORY_REALLOCATE, a, hookArgs);
}
}
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->size = 0;
a->file = file;
a->line = line;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
return ((MEM_BOUND_TYPE *) new) + 1;
}
void
MemoryFree(void *p, const char *file, int line)
{
MemoryInfo *a;
MemoryIterate(NULL, NULL);
if (!p)
{
return;
}
a = MemoryInfoGet(p);
if (a)
{
pthread_mutex_lock(&lock);
if (hook)
{
a->file = file;
a->line = line;
hook(MEMORY_FREE, a, hookArgs);
}
MemoryDelete(a);
free(a->pointer);
free(a);
pthread_mutex_unlock(&lock);
}
else if (hook)
{
a = malloc(sizeof(MemoryInfo));
if (a)
{
a->file = file;
a->line = line;
a->size = 0;
a->pointer = p;
hook(MEMORY_BAD_POINTER, a, hookArgs);
free(a);
}
}
}
size_t
MemoryAllocated(void)
{
size_t i;
size_t total = 0;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
total += MemoryInfoGetSize(allocations[i]);
}
}
pthread_mutex_unlock(&lock);
return total;
}
void
MemoryFreeAll(void)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
free(allocations[i]->pointer);
free(allocations[i]);
}
}
free(allocations);
allocations = NULL;
allocationsSize = 0;
allocationsLen = 0;
pthread_mutex_unlock(&lock);
}
MemoryInfo *
MemoryInfoGet(void *p)
{
size_t hash, count;
pthread_mutex_lock(&lock);
p = ((MEM_BOUND_TYPE *) p) - 1;
hash = MemoryHash(p);
count = 0;
while (count <= allocationsSize)
{
if (!allocations[hash] || allocations[hash]->pointer != p)
{
hash = (hash + 1) % allocationsSize;
count++;
}
else
{
MemoryCheck(allocations[hash]);
pthread_mutex_unlock(&lock);
return allocations[hash];
}
}
pthread_mutex_unlock(&lock);
return NULL;
}
size_t
MemoryInfoGetSize(MemoryInfo * a)
{
if (!a)
{
return 0;
}
return a->size ? a->size - (2 * sizeof(MEM_BOUND_TYPE)) : 0;
}
const char *
MemoryInfoGetFile(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return a->file;
}
int
MemoryInfoGetLine(MemoryInfo * a)
{
if (!a)
{
return -1;
}
return a->line;
}
void *
MemoryInfoGetPointer(MemoryInfo * a)
{
if (!a)
{
return NULL;
}
return ((MEM_BOUND_TYPE *) a->pointer) + 1;
}
void
MemoryIterate(void (*iterFunc) (MemoryInfo *, void *), void *args)
{
size_t i;
pthread_mutex_lock(&lock);
for (i = 0; i < allocationsSize; i++)
{
if (allocations[i])
{
MemoryCheck(allocations[i]);
if (iterFunc)
{
iterFunc(allocations[i], args);
}
}
}
pthread_mutex_unlock(&lock);
}
void
MemoryHook(void (*memHook) (MemoryAction, MemoryInfo *, void *), void *args)
{
pthread_mutex_lock(&lock);
hook = memHook;
hookArgs = args;
pthread_mutex_unlock(&lock);
}
static size_t
HexPtr(unsigned long ptr, char *out, size_t len)
{
size_t i = len - 1;
size_t j = 0;
do
{
out[i] = "0123456789abcdef"[ptr % 16];
i--;
ptr /= 16;
} while (ptr > 0);
while (++i < len)
{
out[j++] = out[i];
}
out[j] = '\0';
return j;
}
void
MemoryDefaultHook(MemoryAction a, MemoryInfo * i, void *args)
{
char buf[64];
unsigned long ptr = (unsigned long) MemoryInfoGetPointer(i);
size_t len = HexPtr(ptr, buf, sizeof(buf));
(void) args;
switch (a)
{
case MEMORY_BAD_POINTER:
write(STDERR_FILENO, "Bad pointer: 0x", 15);
break;
case MEMORY_CORRUPTED:
write(STDERR_FILENO, "Corrupted block: 0x", 19);
break;
default:
return;
}
write(STDERR_FILENO, buf, len);
write(STDERR_FILENO, " to 0x", 6);
len = HexPtr(MemoryInfoGetSize(i), buf, sizeof(buf));
write(STDERR_FILENO, buf, len);
write(STDERR_FILENO, " bytes at ", 10);
write(STDERR_FILENO, MemoryInfoGetFile(i), strlen(MemoryInfoGetFile(i)));
write(STDERR_FILENO, ":0x", 3);
len = HexPtr(MemoryInfoGetLine(i), buf, sizeof(buf));
write(STDERR_FILENO, buf, len);
write(STDERR_FILENO, "\n", 1);
raise(SIGSEGV);
}
void
MemoryHexDump(MemoryInfo * info, void (*printFunc) (size_t, char *, char *, void *), void *args)
{
char hexBuf[(MEMORY_HEXDUMP_WIDTH * 2) + MEMORY_HEXDUMP_WIDTH + 1];
char asciiBuf[MEMORY_HEXDUMP_WIDTH + 1];
size_t pI = 0;
size_t hI = 0;
size_t aI = 0;
const unsigned char *pc;
if (!info || !printFunc)
{
return;
}
pc = MemoryInfoGetPointer(info);
for (pI = 0; pI < MemoryInfoGetSize(info); pI++)
{
if (pI > 0 && pI % MEMORY_HEXDUMP_WIDTH == 0)
{
hexBuf[hI - 1] = '\0';
asciiBuf[aI] = '\0';
printFunc(pI - MEMORY_HEXDUMP_WIDTH, hexBuf, asciiBuf, args);
snprintf(hexBuf, 4, "%02x ", pc[pI]);
hI = 3;
asciiBuf[0] = isprint(pc[pI]) ? pc[pI] : '.';
asciiBuf[1] = '\0';
aI = 1;
}
else
{
asciiBuf[aI] = isprint(pc[pI]) ? pc[pI] : '.';
aI++;
snprintf(hexBuf + hI, 4, "%02x ", pc[pI]);
hI += 3;
}
}
hexBuf[hI] = '\0';
hI--;
while (hI < sizeof(hexBuf) - 2)
{
hexBuf[hI] = ' ';
hI++;
}
while (aI < sizeof(asciiBuf) - 1)
{
asciiBuf[aI] = ' ';
aI++;
}
hexBuf[hI] = '\0';
asciiBuf[aI] = '\0';
printFunc(pI - ((pI % MEMORY_HEXDUMP_WIDTH) ?
(pI % MEMORY_HEXDUMP_WIDTH) : MEMORY_HEXDUMP_WIDTH),
hexBuf, asciiBuf, args);
printFunc(pI, NULL, NULL, args);
}

View file

@ -1,176 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Queue.h>
#include <Memory.h>
struct Queue
{
void **items;
size_t size;
size_t front;
size_t rear;
};
Queue *
QueueCreate(size_t size)
{
Queue *q;
if (!size)
{
/* Can't have a queue of length zero */
return NULL;
}
q = Malloc(sizeof(Queue));
if (!q)
{
return NULL;
}
q->items = Malloc(size * sizeof(void *));
if (!q->items)
{
Free(q);
return NULL;
}
q->size = size;
q->front = size + 1;
q->rear = size + 1;
return q;
}
void
QueueFree(Queue * q)
{
if (q)
{
Free(q->items);
}
Free(q);
}
int
QueueFull(Queue * q)
{
if (!q)
{
return 0;
}
return ((q->front == q->rear + 1) || (q->front == 0 && q->rear == q->size - 1));
}
int
QueueEmpty(Queue * q)
{
if (!q)
{
return 0;
}
return q->front == q->size + 1;
}
int
QueuePush(Queue * q, void *element)
{
if (!q || !element)
{
return 0;
}
if (QueueFull(q))
{
return 0;
}
if (q->front == q->size + 1)
{
q->front = 0;
}
if (q->rear == q->size + 1)
{
q->rear = 0;
}
else
{
q->rear = (q->rear + 1) % q->size;
}
q->items[q->rear] = element;
return 1;
}
void *
QueuePop(Queue * q)
{
void *element;
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
element = q->items[q->front];
if (q->front == q->rear)
{
q->front = q->size + 1;
q->rear = q->size + 1;
}
else
{
q->front = (q->front + 1) % q->size;
}
return element;
}
void *
QueuePeek(Queue * q)
{
if (!q)
{
return NULL;
}
if (QueueEmpty(q))
{
return NULL;
}
return q->items[q->front];
}

View file

@ -1,174 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Rand.h>
#include <Int.h>
#include <UInt64.h>
#include <Util.h>
#include <Memory.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define RAND_STATE_VECTOR_LENGTH 624
#define RAND_STATE_VECTOR_M 397
#define RAND_UPPER_MASK 0x80000000
#define RAND_LOWER_MASK 0x7FFFFFFF
#define RAND_TEMPER_B 0x9D2C5680
#define RAND_TEMPER_C 0xEFC60000
typedef struct RandState
{
UInt32 mt[RAND_STATE_VECTOR_LENGTH];
int index;
} RandState;
static void
RandSeed(RandState * state, UInt32 seed)
{
state->mt[0] = seed & 0xFFFFFFFF;
for (state->index = 1; state->index < RAND_STATE_VECTOR_LENGTH; state->index++)
{
state->mt[state->index] = (6069 * state->mt[state->index - 1]) & 0xFFFFFFFF;
}
}
static UInt32
RandGenerate(RandState * state)
{
static const UInt32 mag[2] = {0x0, 0x9908B0DF};
UInt32 result;
if (state->index >= RAND_STATE_VECTOR_LENGTH || state->index < 0)
{
int kk;
if (state->index >= RAND_STATE_VECTOR_LENGTH + 1 || state->index < 0)
{
RandSeed(state, 4357);
}
for (kk = 0; kk < RAND_STATE_VECTOR_LENGTH - RAND_STATE_VECTOR_M; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + RAND_STATE_VECTOR_M] ^ (result >> 1) ^ mag[result & 0x1];
}
for (; kk < RAND_STATE_VECTOR_LENGTH - 1; kk++)
{
result = (state->mt[kk] & RAND_UPPER_MASK) | (state->mt[kk + 1] & RAND_LOWER_MASK);
state->mt[kk] = state->mt[kk + (RAND_STATE_VECTOR_M - RAND_STATE_VECTOR_LENGTH)] ^ (result >> 1) ^ mag[result & 0x1];
}
result = (state->mt[RAND_STATE_VECTOR_LENGTH - 1] & RAND_UPPER_MASK) | (state->mt[0] & RAND_LOWER_MASK);
state->mt[RAND_STATE_VECTOR_LENGTH - 1] = state->mt[RAND_STATE_VECTOR_M - 1] ^ (result >> 1) ^ mag[result & 0x1];
state->index = 0;
}
result = state->mt[state->index++];
result ^= (result >> 11);
result ^= (result << 7) & RAND_TEMPER_B;
result ^= (result << 15) & RAND_TEMPER_C;
result ^= (result >> 18);
return result;
}
static void
RandDestructor(void *p)
{
Free(p);
}
/* Generate random numbers using rejection sampling. The basic idea is
* to "reroll" if a number happens to be outside the range. However
* this could be extremely inefficient.
*
* Another idea would just be to "reroll" if the generated number ends up
* in the previously "biased" range, and THEN do a modulo.
*
* This would be far more efficient for small values of max, and fixes the
* bias issue. */
/* This algorithm therefore computes N random numbers generally in O(N)
* time, while being less biased. */
void
RandIntN(int *buf, size_t size, unsigned int max)
{
static pthread_key_t stateKey;
static int createdKey = 0;
/* Limit the range to banish all previously biased results */
const int allowed = RAND_MAX - RAND_MAX % max;
RandState *state;
int tmp;
size_t i;
if (!createdKey)
{
pthread_key_create(&stateKey, RandDestructor);
createdKey = 1;
}
state = pthread_getspecific(stateKey);
if (!state)
{
/* Generate a seed from the system time, PID, and TID */
UInt64 ts = UtilServerTs();
UInt32 seed = UInt64Low(ts) ^ getpid() ^ (unsigned long) pthread_self();
state = Malloc(sizeof(RandState));
RandSeed(state, seed);
pthread_setspecific(stateKey, state);
}
/* Generate {size} random numbers. */
for (i = 0; i < size; i++)
{
/* Most of the time, this will take about 1 loop */
do
{
tmp = RandGenerate(state);
} while (tmp > allowed);
buf[i] = tmp % max;
}
}
/* Generate just 1 random number */
int
RandInt(unsigned int max)
{
int val = 0;
RandIntN(&val, 1, max);
return val;
}

View file

@ -1,191 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Runtime.h>
#include <Array.h>
#include <HashMap.h>
#include <Stream.h>
#include <Log.h>
#include <Memory.h>
#include <Str.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define ERR_BUF_SIZE 128
/* Specified by POSIX to contain environment variables */
extern char **environ;
/* The linking program is expected to provide Main */
extern int Main(Array *, HashMap *);
/* Functions in the Memory API that are not exported via header */
extern int MemoryRuntimeInit(void);
extern int MemoryRuntimeDestroy(void);
typedef struct MainArgs
{
Array *args;
HashMap *env;
int ret;
} MainArgs;
static void *
MainThread(void *argp)
{
MainArgs *args = argp;
args = argp;
args->ret = Main(args->args, args->env);
return NULL;
}
int
main(int argc, char **argv)
{
pthread_t mainThread;
size_t i;
char *key;
char *val;
char **envp;
MainArgs args;
char errBuf[ERR_BUF_SIZE];
int errLen;
args.args = NULL;
args.env = NULL;
args.ret = EXIT_FAILURE;
if (!MemoryRuntimeInit())
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to initialize Memory runtime.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
args.args = ArrayCreate();
if (!args.args)
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to allocate heap memory for program arguments.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
args.env = HashMapCreate();
if (!args.env)
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to allocate heap memory for program environment.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
for (i = 0; i < (size_t) argc; i++)
{
char *arg = StrDuplicate(argv[i]);
if (!arg || !ArrayAdd(args.args, arg))
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to allocate heap memory for program argument.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
}
envp = environ;
while (*envp)
{
size_t valInd;
/* It is unclear whether or not envp strings are writable, so
* we make our own copy to manipulate it */
key = StrDuplicate(*envp);
if (!key)
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to allocate heap memory for program environment variable.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
valInd = strcspn(key, "=");
key[valInd] = '\0';
val = key + valInd + 1;
HashMapSet(args.env, key, StrDuplicate(val));
Free(key);
envp++;
}
if (pthread_create(&mainThread, NULL, MainThread, &args) != 0)
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to create main thread.\n");
write(STDERR_FILENO, errBuf, errLen);
goto finish;
}
if (pthread_join(mainThread, NULL) != 0)
{
errLen = snprintf(errBuf, ERR_BUF_SIZE, "Fatal: Unable to join main thread.\n");
write(STDERR_FILENO, errBuf, errLen);
args.ret = EXIT_FAILURE;
goto finish;
}
finish:
if (args.args)
{
for (i = 0; i < ArraySize(args.args); i++)
{
Free(ArrayGet(args.args, i));
}
ArrayFree(args.args);
}
if (args.env)
{
while (HashMapIterate(args.env, &key, (void **) &val))
{
Free(val);
}
HashMapFree(args.env);
}
LogConfigFree(LogConfigGlobal());
StreamClose(StreamStdout());
StreamClose(StreamStdin());
StreamClose(StreamStderr());
GenerateMemoryReport(argc, argv);
MemoryRuntimeDestroy();
return args.ret;
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Runtime.h>
#include <Array.h>
#include <Stream.h>
#include <Log.h>
#include <Memory.h>
#include <stdlib.h>
#include <time.h>
#include <libgen.h>
static void
HexDump(size_t off, char *hexBuf, char *asciiBuf, void *args)
{
FILE *report = args;
if (hexBuf && asciiBuf)
{
fprintf(report, "%04lx: %s | %s |\n", off, hexBuf, asciiBuf);
}
else
{
fprintf(report, "%04lx\n", off);
}
}
static void
MemoryIterator(MemoryInfo * i, void *args)
{
FILE *report = args;
fprintf(report, "%s:%d: %lu bytes at %p\n",
MemoryInfoGetFile(i), MemoryInfoGetLine(i),
MemoryInfoGetSize(i), MemoryInfoGetPointer(i));
MemoryHexDump(i, HexDump, report);
fprintf(report, "\n");
}
void
GenerateMemoryReport(int argc, char **argv)
{
char reportName[128];
char *namePtr;
/*
* Use C standard IO instead of the Stream or Io APIs, because
* those use the Memory API, and that's exactly what we're trying
* to assess, so using it would generate false positives. None of
* this code should leak memory.
*/
FILE *report;
time_t currentTime;
struct tm *timeInfo;
char tsBuffer[1024];
int i;
if (!MemoryAllocated())
{
/* No memory leaked, no need to write the report. This is the
* ideal situation; we only want the report to show up if leaks
* occurred. */
return;
}
snprintf(reportName, sizeof(reportName), "%s-leaked.txt", argv[0]);
/* Make sure the report goes in the current working directory. */
namePtr = basename(reportName);
report = fopen(namePtr, "a");
if (!report)
{
return;
}
currentTime = time(NULL);
timeInfo = localtime(&currentTime);
strftime(tsBuffer, sizeof(tsBuffer), "%c", timeInfo);
fprintf(report, "---------- Memory Report ----------\n");
fprintf(report, "Program:");
for (i = 0; i < argc; i++)
{
fprintf(report, " '%s'", argv[i]);
}
fprintf(report, "\nDate: %s\n", tsBuffer);
fprintf(report, "Total Bytes: %lu\n", MemoryAllocated());
fprintf(report, "\n");
MemoryIterate(MemoryIterator, report);
fclose(report);
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Sha.h>
#include <Memory.h>
#include <stdio.h>
#include <string.h>
char *
ShaToHex(unsigned char *bytes)
{
size_t i = 0;
char *str = Malloc(((strlen((char *) bytes) * 2) + 1) * sizeof(char));
if (!str)
{
return NULL;
}
while (bytes[i] != '\0')
{
snprintf(str + (2 * i), 3, "%02x", bytes[i]);
i++;
}
return str;
}

View file

@ -1,267 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Sha.h>
#include <Memory.h>
#include <Int.h>
#include <string.h>
#include <limits.h>
#define LOAD32H(x, y) \
{ \
x = ((UInt32)((y)[0] & 255) << 24) | \
((UInt32)((y)[1] & 255) << 16) | \
((UInt32)((y)[2] & 255) << 8) | \
((UInt32)((y)[3] & 255)); \
}
#define ROL(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
#define BLK(i) (block->l[i & 15] = ROL(block->l[(i+13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
#define R0(v, w, x, y, z, i) z += ((w & (x ^ y)) ^ y) + block->l[i] + 0x5A827999 + ROL(v, 5); w = ROL(w, 30);
#define R1(v, w, x, y, z, i) z += ((w & (x ^ y)) ^ y) + BLK(i) + 0x5A827999 + ROL(v, 5); w = ROL(w, 30);
#define R2(v, w, x, y, z, i) z += (w ^ x ^ y) + BLK(i) + 0x6ED9EBA1 + ROL(v, 5); w = ROL(w, 30);
#define R3(v, w, x, y, z, i) z += (((w | x) & y) | (w & x)) + BLK(i) + 0x8F1BBCDC + ROL(v, 5); w = ROL(w, 30);
#define R4(v, w, x, y, z, i) z += (w ^ x ^ y) + BLK(i) + 0xCA62C1D6 + ROL(v, 5); w = ROL(w, 30);
typedef union
{
UInt8 c[64];
UInt32 l[16];
} Char64Long16;
typedef struct
{
UInt32 state[5];
UInt32 count[2];
UInt8 buffer[64];
} Sha1Context;
static void
Sha1Transform(UInt32 state[5], const UInt8 buffer[64])
{
UInt32 a, b, c, d, e, i;
UInt8 workspace[64];
Char64Long16 *block = (Char64Long16 *) workspace;
for (i = 0; i < 16; i++)
{
LOAD32H(block->l[i], buffer + (i * 4));
}
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
R0(a, b, c, d, e, 0);
R0(e, a, b, c, d, 1);
R0(d, e, a, b, c, 2);
R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4);
R0(a, b, c, d, e, 5);
R0(e, a, b, c, d, 6);
R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8);
R0(b, c, d, e, a, 9);
R0(a, b, c, d, e, 10);
R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12);
R0(c, d, e, a, b, 13);
R0(b, c, d, e, a, 14);
R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16);
R1(d, e, a, b, c, 17);
R1(c, d, e, a, b, 18);
R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20);
R2(e, a, b, c, d, 21);
R2(d, e, a, b, c, 22);
R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24);
R2(a, b, c, d, e, 25);
R2(e, a, b, c, d, 26);
R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28);
R2(b, c, d, e, a, 29);
R2(a, b, c, d, e, 30);
R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32);
R2(c, d, e, a, b, 33);
R2(b, c, d, e, a, 34);
R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36);
R2(d, e, a, b, c, 37);
R2(c, d, e, a, b, 38);
R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40);
R3(e, a, b, c, d, 41);
R3(d, e, a, b, c, 42);
R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44);
R3(a, b, c, d, e, 45);
R3(e, a, b, c, d, 46);
R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48);
R3(b, c, d, e, a, 49);
R3(a, b, c, d, e, 50);
R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52);
R3(c, d, e, a, b, 53);
R3(b, c, d, e, a, 54);
R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56);
R3(d, e, a, b, c, 57);
R3(c, d, e, a, b, 58);
R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60);
R4(e, a, b, c, d, 61);
R4(d, e, a, b, c, 62);
R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64);
R4(a, b, c, d, e, 65);
R4(e, a, b, c, d, 66);
R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68);
R4(b, c, d, e, a, 69);
R4(a, b, c, d, e, 70);
R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72);
R4(c, d, e, a, b, 73);
R4(b, c, d, e, a, 74);
R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76);
R4(d, e, a, b, c, 77);
R4(c, d, e, a, b, 78);
R4(b, c, d, e, a, 79);
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
}
static void
Sha1Init(Sha1Context * ctx)
{
ctx->state[0] = 0x67452301;
ctx->state[1] = 0xEFCDAB89;
ctx->state[2] = 0x98BADCFE;
ctx->state[3] = 0x10325476;
ctx->state[4] = 0xC3D2E1F0;
ctx->count[0] = 0;
ctx->count[1] = 0;
}
static void
Sha1Update(Sha1Context * ctx, const void *buf, UInt32 size)
{
UInt32 i, j;
j = (ctx->count[0] >> 3) & 63;
if ((ctx->count[0] += size << 3) < (size << 3))
{
ctx->count[1]++;
}
ctx->count[1] += (size >> 29);
if ((j + size) > 63)
{
i = 64 - j;
memcpy(&ctx->buffer[j], buf, i);
Sha1Transform(ctx->state, ctx->buffer);
for (; i + 63 < size; i += 64)
{
Sha1Transform(ctx->state, (UInt8 *) buf + i);
}
j = 0;
}
else
{
i = 0;
}
memcpy(&ctx->buffer[j], &((UInt8 *) buf)[i], size - i);
}
static void
Sha1Calculate(Sha1Context * ctx, unsigned char *out)
{
UInt32 i;
UInt8 count[8];
for (i = 0; i < 8; i++)
{
count[i] = (unsigned char) ((ctx->count[(i >= 4 ? 0 : 1)]
>> ((3 - (i & 3)) * 8)) & 255);
}
Sha1Update(ctx, (UInt8 *) "\x80", 1);
while ((ctx->count[0] & 504) != 448)
{
Sha1Update(ctx, (UInt8 *) "\0", 1);
}
Sha1Update(ctx, count, 8);
for (i = 0; i < (160 / 8); i++)
{
out[i] = (UInt8) ((ctx->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
}
unsigned char *
Sha1(char *str)
{
Sha1Context ctx;
unsigned char *out;
if (!str)
{
return NULL;
}
out = Malloc(((160 / 8) + 1) * sizeof(unsigned char));
if (!out)
{
return NULL;
}
Sha1Init(&ctx);
Sha1Update(&ctx, str, strlen(str));
Sha1Calculate(&ctx, out);
out[160 / 8] = '\0';
return out;
}

View file

@ -1,233 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Sha.h>
#include <Memory.h>
#include <Int.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#define GET_UINT32(x) \
(((UInt32)(x)[0] << 24) | \
((UInt32)(x)[1] << 16) | \
((UInt32)(x)[2] << 8) | \
((UInt32)(x)[3]))
#define PUT_UINT32(dst, x) { \
(dst)[0] = (x) >> 24; \
(dst)[1] = (x) >> 16; \
(dst)[2] = (x) >> 8; \
(dst)[3] = (x); \
}
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ (x >> 3))
#define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ (x >> 10))
#define T0(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
#define T1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
#define CH(a, b, c) (((a) & (b)) ^ ((~(a)) & (c)))
#define MAJ(a, b, c) (((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c)))
#define WW(i) (w[i] = w[i - 16] + S0(w[i - 15]) + w[i - 7] + S1(w[i - 2]))
#define ROUND(a, b, c, d, e, f, g, h, k, w) { \
UInt32 tmp0 = h + T0(e) + CH(e, f, g) + k + w; \
UInt32 tmp1 = T1(a) + MAJ(a, b, c); \
h = tmp0 + tmp1; \
d += tmp0; \
}
typedef struct Sha256Context
{
size_t length;
UInt32 state[8];
size_t bufLen;
unsigned char buffer[64];
} Sha256Context;
static void
Sha256Chunk(Sha256Context * context, unsigned char chunk[64])
{
const UInt32 rk[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
UInt32 w[64];
UInt32 a, b, c, d, e, f, g, h;
int i;
for (i = 0; i < 16; i++)
{
w[i] = GET_UINT32(&chunk[4 * i]);
}
a = context->state[0];
b = context->state[1];
c = context->state[2];
d = context->state[3];
e = context->state[4];
f = context->state[5];
g = context->state[6];
h = context->state[7];
for (i = 0; i < 16; i += 8)
{
ROUND(a, b, c, d, e, f, g, h, rk[i], w[i]);
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], w[i + 1]);
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], w[i + 2]);
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], w[i + 3]);
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], w[i + 4]);
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], w[i + 5]);
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], w[i + 6]);
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], w[i + 7]);
}
for (i = 16; i < 64; i += 8)
{
ROUND(a, b, c, d, e, f, g, h, rk[i], WW(i));
ROUND(h, a, b, c, d, e, f, g, rk[i + 1], WW(i + 1));
ROUND(g, h, a, b, c, d, e, f, rk[i + 2], WW(i + 2));
ROUND(f, g, h, a, b, c, d, e, rk[i + 3], WW(i + 3));
ROUND(e, f, g, h, a, b, c, d, rk[i + 4], WW(i + 4));
ROUND(d, e, f, g, h, a, b, c, rk[i + 5], WW(i + 5));
ROUND(c, d, e, f, g, h, a, b, rk[i + 6], WW(i + 6));
ROUND(b, c, d, e, f, g, h, a, rk[i + 7], WW(i + 7));
}
context->state[0] += a;
context->state[1] += b;
context->state[2] += c;
context->state[3] += d;
context->state[4] += e;
context->state[5] += f;
context->state[6] += g;
context->state[7] += h;
}
static void
Sha256Process(Sha256Context * context, unsigned char *data, size_t length)
{
context->length += length;
if (context->bufLen && context->bufLen + length >= 64)
{
int len = 64 - context->bufLen;
memcpy(context->buffer + context->bufLen, data, len);
Sha256Chunk(context, context->buffer);
data += len;
length -= len;
context->bufLen = 0;
}
while (length >= 64)
{
Sha256Chunk(context, data);
data += 64;
length -= 64;
}
if (length)
{
memcpy(context->buffer + context->bufLen, data, length);
context->bufLen += length;
}
}
unsigned char *
Sha256(char *str)
{
Sha256Context context;
size_t i;
unsigned char *out;
unsigned char fill[64];
UInt32 fillLen;
unsigned char buf[8];
UInt32 hiLen;
UInt32 loLen;
if (!str)
{
return NULL;
}
out = Malloc(33 * sizeof(unsigned char));
if (!out)
{
return NULL;
}
context.state[0] = 0x6a09e667;
context.state[1] = 0xbb67ae85;
context.state[2] = 0x3c6ef372;
context.state[3] = 0xa54ff53a;
context.state[4] = 0x510e527f;
context.state[5] = 0x9b05688c;
context.state[6] = 0x1f83d9ab;
context.state[7] = 0x5be0cd19;
context.bufLen = 0;
context.length = 0;
memset(context.buffer, 0, 64);
Sha256Process(&context, (unsigned char *) str, strlen(str));
memset(fill, 0, 64);
fill[0] = 0x80;
fillLen = (context.bufLen < 56) ? 56 - context.bufLen : 120 - context.bufLen;
hiLen = (UInt32) (context.length >> 29);
loLen = (UInt32) (context.length << 3);
PUT_UINT32(&buf[0], hiLen);
PUT_UINT32(&buf[4], loLen);
Sha256Process(&context, fill, fillLen);
Sha256Process(&context, buf, 8);
for (i = 0; i < 8; i++)
{
PUT_UINT32(&out[4 * i], context.state[i]);
}
out[32] = '\0';
return out;
}

View file

@ -1,343 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Str.h>
#include <Memory.h>
#include <Util.h>
#include <Rand.h>
#include <Int.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <pthread.h>
#include <unistd.h>
UInt32
StrUtf16Decode(UInt16 high, UInt16 low)
{
if (high <= 0xD7FF)
{
return high;
}
else if (high <= 0xDBFF)
{
unsigned short hS = (high - 0xD800) * 0x400;
unsigned short lS = low - 0xDC00;
return (lS | hS) + 0x10000;
}
else
{
return 0;
}
}
char *
StrUtf8Encode(UInt32 codepoint)
{
char *str;
str = Malloc(5 * sizeof(char));
if (!str)
{
return NULL;
}
if (codepoint <= 0x7F && codepoint != 0) /* Plain ASCII */
{
str[0] = (char) codepoint;
str[1] = '\0';
}
else if (codepoint <= 0x07FF) /* 2-byte */
{
str[0] = (char) (((codepoint >> 6) & 0x1F) | 0xC0);
str[1] = (char) (((codepoint >> 0) & 0x3F) | 0x80);
str[2] = '\0';
}
else if (codepoint <= 0xFFFF) /* 3-byte */
{
str[0] = (char) (((codepoint >> 12) & 0x0F) | 0xE0);
str[1] = (char) (((codepoint >> 6) & 0x3F) | 0x80);
str[2] = (char) (((codepoint >> 0) & 0x3F) | 0x80);
str[3] = '\0';
}
else if (codepoint <= 0x10FFFF)/* 4-byte */
{
str[0] = (char) (((codepoint >> 18) & 0x07) | 0xF0);
str[1] = (char) (((codepoint >> 12) & 0x3F) | 0x80);
str[2] = (char) (((codepoint >> 6) & 0x3F) | 0x80);
str[3] = (char) (((codepoint >> 0) & 0x3F) | 0x80);
str[4] = '\0';
}
else
{
/* Send replacement character */
str[0] = (char) 0xEF;
str[1] = (char) 0xBF;
str[2] = (char) 0xBD;
str[3] = '\0';
}
return str;
}
char *
StrDuplicate(const char *inStr)
{
size_t len;
char *outStr;
if (!inStr)
{
return NULL;
}
len = strlen(inStr);
outStr = Malloc(len + 1); /* For the null terminator */
if (!outStr)
{
return NULL;
}
strncpy(outStr, inStr, len + 1);
return outStr;
}
char *
StrSubstr(const char *inStr, size_t start, size_t end)
{
size_t len;
size_t i;
size_t j;
char *outStr;
if (!inStr)
{
return NULL;
}
if (start >= end)
{
return NULL;
}
len = end - start;
outStr = Malloc(len + 1);
if (!outStr)
{
return NULL;
}
j = 0;
for (i = start; i < end; i++)
{
if (inStr[i] == '\0')
{
break;
}
outStr[j] = inStr[i];
j++;
}
outStr[j] = '\0';
return outStr;
}
char *
StrConcat(size_t nStr,...)
{
va_list argp;
char *str;
char *strp;
size_t strLen = 0;
size_t i;
va_start(argp, nStr);
for (i = 0; i < nStr; i++)
{
char *argStr = va_arg(argp, char *);
if (argStr)
{
strLen += strlen(argStr);
}
}
va_end(argp);
str = Malloc(strLen + 1);
strp = str;
va_start(argp, nStr);
for (i = 0; i < nStr; i++)
{
/* Manually copy chars instead of using strcopy() so we don't
* have to call strlen() on the strings again, and we aren't
* writing useless null chars. */
char *argStr = va_arg(argp, char *);
if (argStr)
{
while (*argStr)
{
*strp = *argStr;
strp++;
argStr++;
}
}
}
va_end(argp);
str[strLen] = '\0';
return str;
}
int
StrBlank(const char *str)
{
int blank = 1;
size_t i = 0;
while (str[i])
{
blank &= isspace((unsigned char) str[i]);
/* No need to continue if we don't have a blank */
if (!blank)
{
break;
}
i++;
}
return blank;
}
char *
StrRandom(size_t len)
{
static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *str;
int *nums;
size_t i;
if (!len)
{
return NULL;
}
str = Malloc(len + 1);
if (!str)
{
return NULL;
}
nums = Malloc(len * sizeof(int));
if (!nums)
{
Free(str);
return NULL;
}
/* TODO: This seems slow. */
RandIntN(nums, len, sizeof(charset) - 1);
for (i = 0; i < len; i++)
{
str[i] = charset[nums[i]];
}
Free(nums);
str[len] = '\0';
return str;
}
char *
StrInt(long i)
{
char *str;
int len;
len = snprintf(NULL, 0, "%ld", i);
str = Malloc(len + 1);
if (!str)
{
return NULL;
}
snprintf(str, len + 1, "%ld", i);
return str;
}
char *
StrLower(char *str)
{
char *ret;
size_t len;
size_t i;
if (!str)
{
return NULL;
}
len = strlen(str);
ret = Malloc(len + 1);
for (i = 0; i < len; i++)
{
ret[i] = tolower(str[i]);
}
ret[len] = '\0';
return ret;
}
int
StrEquals(const char *str1, const char *str2)
{
/* Both strings are NULL, they're equal */
if (!str1 && !str2)
{
return 1;
}
/* One or the other is NULL, they're not equal */
if (!str1 || !str2)
{
return 0;
}
/* Neither are NULL, do a regular string comparison */
return strcmp(str1, str2) == 0;
}

View file

@ -1,658 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Stream.h>
#include <Io.h>
#include <Memory.h>
#include <Util.h>
#include <Int.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#ifndef STREAM_RETRIES
#define STREAM_RETRIES 10
#endif
#ifndef STREAM_DELAY
#define STREAM_DELAY 2
#endif
#define STREAM_EOF (1 << 0)
#define STREAM_ERR (1 << 1)
#define STREAM_TTY (1 << 2)
struct Stream
{
Io *io;
UInt8 *rBuf;
size_t rLen;
size_t rOff;
UInt8 *wBuf;
size_t wLen;
char *ugBuf;
size_t ugSize;
size_t ugLen;
int flags;
int fd;
};
Stream *
StreamIo(Io * io)
{
Stream *stream;
if (!io)
{
return NULL;
}
stream = Malloc(sizeof(Stream));
if (!stream)
{
return NULL;
}
memset(stream, 0, sizeof(Stream));
stream->io = io;
stream->fd = -1;
return stream;
}
Stream *
StreamFd(int fd)
{
Io *io = IoFd(fd);
Stream *stream;
if (!io)
{
return NULL;
}
stream = StreamIo(io);
if (!stream)
{
return NULL;
}
stream->fd = fd;
if (isatty(stream->fd))
{
stream->flags |= STREAM_TTY;
}
return stream;
}
Stream *
StreamFile(FILE * fp)
{
Io *io = IoFile(fp);
Stream *stream;
if (!io)
{
return NULL;
}
stream = StreamIo(io);
if (!stream)
{
return NULL;
}
stream->fd = fileno(fp);
if (isatty(stream->fd))
{
stream->flags |= STREAM_TTY;
}
return stream;
}
Stream *
StreamOpen(const char *path, const char *mode)
{
FILE *fp = fopen(path, mode);
if (!fp)
{
return NULL;
}
return StreamFile(fp);
}
Stream *
StreamStdout(void)
{
static Stream *stdOut = NULL;
if (!stdOut)
{
stdOut = StreamFd(STDOUT_FILENO);
}
return stdOut;
}
Stream *
StreamStderr(void)
{
static Stream *stdErr = NULL;
if (!stdErr)
{
stdErr = StreamFd(STDERR_FILENO);
}
return stdErr;
}
Stream *
StreamStdin(void)
{
static Stream *stdIn = NULL;
if (!stdIn)
{
stdIn = StreamFd(STDIN_FILENO);
}
return stdIn;
}
int
StreamClose(Stream * stream)
{
int ret = 0;
if (!stream)
{
errno = EBADF;
return EOF;
}
if (stream->rBuf)
{
Free(stream->rBuf);
}
if (stream->wBuf)
{
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
Free(stream->wBuf);
if (writeRes == -1)
{
ret = EOF;
}
}
if (stream->ugBuf)
{
Free(stream->ugBuf);
}
ret = IoClose(stream->io);
Free(stream);
return ret;
}
int
StreamVprintf(Stream * stream, const char *fmt, va_list ap)
{
/* This might look like very similar code to IoVprintf(), but I
* chose not to defer to IoVprintf() because that would require us
* to immediately flush the buffer, since the Io API is unbuffered.
* StreamPuts() uses StreamPutc() under the hood, which is
* buffered. It therefore allows us to finish filling the buffer
* and then only flush it when necessary, preventing superfluous
* writes. */
char *buf = NULL;
size_t bufSize = 0;
FILE *fp;
int ret;
if (!fmt)
{
return -1;
}
fp = open_memstream(&buf, &bufSize);
if (!fp)
{
return -1;
}
ret = vfprintf(fp, fmt, ap);
fclose(fp);
if (ret >= 0 && stream)
{
if (StreamPuts(stream, buf) < 0)
{
ret = -1;
};
}
free(buf); /* Allocated by stdlib, not Memory
* API */
return ret;
}
int
StreamPrintf(Stream * stream, const char *fmt,...)
{
int ret;
va_list ap;
va_start(ap, fmt);
ret = StreamVprintf(stream, fmt, ap);
va_end(ap);
return ret;
}
int
StreamGetc(Stream * stream)
{
int c;
if (!stream)
{
errno = EBADF;
return EOF;
}
/* Empty the ungetc stack first */
if (stream->ugLen)
{
c = stream->ugBuf[stream->ugLen - 1];
stream->ugLen--;
return c;
}
if (stream->flags & STREAM_EOF)
{
return EOF;
}
if (!stream->rBuf)
{
/* No buffer allocated yet */
stream->rBuf = Malloc(IO_BUFFER);
if (!stream->rBuf)
{
stream->flags |= STREAM_ERR;
return EOF;
}
stream->rOff = 0;
stream->rLen = 0;
}
if (stream->rOff >= stream->rLen)
{
/* We read through the entire buffer; get a new one */
ssize_t readRes = IoRead(stream->io, stream->rBuf, IO_BUFFER);
if (readRes == 0)
{
stream->flags |= STREAM_EOF;
return EOF;
}
if (readRes == -1)
{
stream->flags |= STREAM_ERR;
return EOF;
}
stream->rOff = 0;
stream->rLen = readRes;
}
/* Read the character in the buffer and advance the offset */
c = stream->rBuf[stream->rOff];
stream->rOff++;
return c;
}
int
StreamUngetc(Stream * stream, int c)
{
if (!stream)
{
errno = EBADF;
return EOF;
}
if (!stream->ugBuf)
{
stream->ugSize = IO_BUFFER;
stream->ugBuf = Malloc(stream->ugSize);
if (!stream->ugBuf)
{
stream->flags |= STREAM_ERR;
return EOF;
}
}
if (stream->ugLen >= stream->ugSize)
{
char *new;
stream->ugSize += IO_BUFFER;
new = Realloc(stream->ugBuf, stream->ugSize);
if (!new)
{
stream->flags |= STREAM_ERR;
Free(stream->ugBuf);
stream->ugBuf = NULL;
return EOF;
}
Free(stream->ugBuf);
stream->ugBuf = new;
}
stream->ugBuf[stream->ugLen] = c;
stream->ugLen++;
return c;
}
int
StreamPutc(Stream * stream, int c)
{
if (!stream)
{
errno = EBADF;
return EOF;
}
if (!stream->wBuf)
{
stream->wBuf = Malloc(IO_BUFFER);
if (!stream->wBuf)
{
stream->flags |= STREAM_ERR;
return EOF;
}
}
if (stream->wLen == IO_BUFFER)
{
/* Buffer full; write it */
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
if (writeRes == -1)
{
stream->flags |= STREAM_ERR;
return EOF;
}
stream->wLen = 0;
}
stream->wBuf[stream->wLen] = c;
stream->wLen++;
if (stream->flags & STREAM_TTY && c == '\n')
{
/* Newline encountered on a TTY; write now. This fixes some
* strange behavior on certain TTYs where a newline is written
* to the screen upon flush even when no newline exists in the
* stream. We just flush on newlines, but only if we're
* directly writing to a TTY. */
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
if (writeRes == -1)
{
stream->flags |= STREAM_ERR;
return EOF;
}
stream->wLen = 0;
}
return c;
}
int
StreamPuts(Stream * stream, char *str)
{
int ret = 0;
if (!stream)
{
errno = EBADF;
return -1;
}
while (*str)
{
if (StreamPutc(stream, *str) == EOF)
{
ret = -1;
break;
}
str++;
}
return ret;
}
char *
StreamGets(Stream * stream, char *str, int size)
{
int i;
if (!stream)
{
errno = EBADF;
return NULL;
}
if (size <= 0)
{
errno = EINVAL;
return NULL;
}
for (i = 0; i < size - 1; i++)
{
int c = StreamGetc(stream);
if (StreamEof(stream) || StreamError(stream))
{
break;
}
str[i] = c;
if (c == '\n')
{
i++;
break;
}
}
str[i] = '\0';
return str;
}
off_t
StreamSeek(Stream * stream, off_t offset, int whence)
{
off_t result;
if (!stream)
{
errno = EBADF;
return -1;
}
result = IoSeek(stream->io, offset, whence);
if (result < 0)
{
return result;
}
/* Successful seek; clear the buffers */
stream->rOff = 0;
stream->wLen = 0;
stream->ugLen = 0;
return result;
}
int
StreamEof(Stream * stream)
{
return stream && (stream->flags & STREAM_EOF);
}
int
StreamError(Stream * stream)
{
return stream && (stream->flags & STREAM_ERR);
}
void
StreamClearError(Stream * stream)
{
if (stream)
{
stream->flags &= ~STREAM_ERR;
}
}
int
StreamFlush(Stream * stream)
{
if (!stream)
{
errno = EBADF;
return EOF;
}
if (stream->wLen)
{
ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen);
if (writeRes == -1)
{
stream->flags |= STREAM_ERR;
return EOF;
}
stream->wLen = 0;
}
return 0;
}
ssize_t
StreamCopy(Stream * in, Stream * out)
{
ssize_t nBytes = 0;
int c;
int tries = 0;
int readFlg = 0;
while (1)
{
c = StreamGetc(in);
if (StreamEof(in))
{
break;
}
if (StreamError(in))
{
if (errno == EAGAIN)
{
StreamClearError(in);
tries++;
if (tries >= STREAM_RETRIES || readFlg)
{
break;
}
else
{
UtilSleepMillis(UInt64Create(0, STREAM_DELAY));
continue;
}
}
else
{
break;
}
}
/* As soon as we've successfully read a byte, treat future
* EAGAINs as EOF, because somebody might have forgotten to
* close their stream. */
readFlg = 1;
tries = 0;
StreamPutc(out, c);
nBytes++;
}
StreamFlush(out);
return nBytes;
}
int
StreamFileno(Stream * stream)
{
return stream ? stream->fd : -1;
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Tls.h>
#ifdef TLS_IMPL
#include <Io.h>
#include <Stream.h>
Stream *
TlsClientStream(int fd, const char *serverName)
{
Io *io;
void *cookie;
IoFunctions funcs;
cookie = TlsInitClient(fd, serverName);
if (!cookie)
{
return NULL;
}
funcs.read = TlsRead;
funcs.write = TlsWrite;
funcs.seek = NULL;
funcs.close = TlsClose;
io = IoCreate(cookie, funcs);
if (!io)
{
return NULL;
}
return StreamIo(io);
}
Stream *
TlsServerStream(int fd, const char *crt, const char *key)
{
Io *io;
void *cookie;
IoFunctions funcs;
cookie = TlsInitServer(fd, crt, key);
if (!cookie)
{
return NULL;
}
funcs.read = TlsRead;
funcs.write = TlsWrite;
funcs.seek = NULL;
funcs.close = TlsClose;
io = IoCreate(cookie, funcs);
if (!io)
{
return NULL;
}
return StreamIo(io);
}
#endif

View file

@ -1,247 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Tls.h>
#if TLS_IMPL == TLS_LIBRESSL
#include <Memory.h>
#include <Log.h>
#include <errno.h>
#include <tls.h> /* LibreSSL TLS */
typedef struct LibreSSLCookie
{
int fd;
struct tls *ctx;
struct tls *cctx;
struct tls_config *cfg;
} LibreSSLCookie;
void *
TlsInitClient(int fd, const char *serverName)
{
LibreSSLCookie *cookie = Malloc(sizeof(LibreSSLCookie));
if (!cookie)
{
return NULL;
}
cookie->ctx = tls_client();
cookie->cctx = NULL;
cookie->cfg = tls_config_new();
cookie->fd = fd;
if (!cookie->ctx || !cookie->cfg)
{
goto error;
}
if (tls_config_set_ca_file(cookie->cfg, tls_default_ca_cert_file()) == -1)
{
goto error;
}
if (tls_configure(cookie->ctx, cookie->cfg) == -1)
{
goto error;
}
if (tls_connect_socket(cookie->ctx, fd, serverName) == -1)
{
goto error;
}
if (tls_handshake(cookie->ctx) == -1)
{
goto error;
}
return cookie;
error:
if (cookie->ctx)
{
if (tls_error(cookie->ctx))
{
Log(LOG_ERR, "TlsInitClient(): %s", tls_error(cookie->ctx));
}
tls_free(cookie->ctx);
}
if (cookie->cfg)
{
tls_config_free(cookie->cfg);
}
Free(cookie);
return NULL;
}
void *
TlsInitServer(int fd, const char *crt, const char *key)
{
LibreSSLCookie *cookie = Malloc(sizeof(LibreSSLCookie));
if (!cookie)
{
return NULL;
}
cookie->ctx = tls_server();
cookie->cctx = NULL;
cookie->cfg = tls_config_new();
cookie->fd = fd;
if (!cookie->ctx || !cookie->cfg)
{
goto error;
}
if (tls_config_set_cert_file(cookie->cfg, crt) == -1)
{
goto error;
}
if (tls_config_set_key_file(cookie->cfg, key) == -1)
{
goto error;
}
if (tls_configure(cookie->ctx, cookie->cfg) == -1)
{
goto error;
}
if (tls_accept_fds(cookie->ctx, &cookie->cctx, fd, fd) == -1)
{
goto error;
}
if (tls_handshake(cookie->cctx) == -1)
{
goto error;
}
return cookie;
error:
if (cookie->ctx)
{
if (tls_error(cookie->ctx))
{
Log(LOG_ERR, "TlsInitServer(): %s", tls_error(cookie->ctx));
}
tls_free(cookie->ctx);
}
if (cookie->cctx)
{
if (tls_error(cookie->cctx))
{
Log(LOG_ERR, "TlsInitServer(): %s", tls_error(cookie->cctx));
}
tls_free(cookie->cctx);
}
if (cookie->cfg)
{
tls_config_free(cookie->cfg);
}
Free(cookie);
return NULL;
}
ssize_t
TlsRead(void *cookie, void *buf, size_t nBytes)
{
LibreSSLCookie *tls = cookie;
struct tls *ctx = tls->cctx ? tls->cctx : tls->ctx;
ssize_t ret = tls_read(ctx, buf, nBytes);
if (ret == -1)
{
errno = EIO;
}
else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
{
errno = EAGAIN;
ret = -1;
}
return ret;
}
ssize_t
TlsWrite(void *cookie, void *buf, size_t nBytes)
{
LibreSSLCookie *tls = cookie;
struct tls *ctx = tls->cctx ? tls->cctx : tls->ctx;
ssize_t ret = tls_write(ctx, buf, nBytes);
if (ret == -1)
{
errno = EIO;
}
else if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
{
errno = EAGAIN;
ret = -1;
}
return ret;
}
int
TlsClose(void *cookie)
{
LibreSSLCookie *tls = cookie;
int tlsRet = tls_close(tls->cctx ? tls->cctx : tls->ctx);
int sdRet;
if (tls->cctx)
{
tls_free(tls->cctx);
}
tls_free(tls->ctx);
tls_config_free(tls->cfg);
sdRet = close(tls->fd);
Free(tls);
return (tlsRet == -1 || sdRet == -1) ? -1 : 0;
}
#endif

View file

@ -1,307 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Tls.h>
#if TLS_IMPL == TLS_OPENSSL
#include <Memory.h>
#include <Log.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
typedef struct OpenSSLCookie
{
int fd;
const SSL_METHOD *method;
SSL_CTX *ctx;
SSL *ssl;
} OpenSSLCookie;
static char *
SSLErrorString(int err)
{
switch (err)
{
case SSL_ERROR_NONE:
return "No error.";
case SSL_ERROR_ZERO_RETURN:
return "The TLS/SSL connection has been closed.";
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
return "The operation did not complete.";
case SSL_ERROR_WANT_X509_LOOKUP:
return "X509 lookup failed.";
case SSL_ERROR_SYSCALL:
return "I/O Error.";
case SSL_ERROR_SSL:
return "SSL library error.";
}
return NULL;
}
void *
TlsInitClient(int fd, const char *serverName)
{
OpenSSLCookie *cookie;
char errorStr[256];
/*
* TODO: Seems odd that this isn't needed to make the
* connection... we should figure out how to verify the
* certificate matches the server we think we're
* connecting to.
*/
(void) serverName;
cookie = Malloc(sizeof(OpenSSLCookie));
if (!cookie)
{
return NULL;
}
memset(cookie, 0, sizeof(OpenSSLCookie));
cookie->method = TLS_client_method();
cookie->ctx = SSL_CTX_new(cookie->method);
if (!cookie->ctx)
{
goto error;
}
cookie->ssl = SSL_new(cookie->ctx);
if (!cookie->ssl)
{
goto error;
}
if (!SSL_set_fd(cookie->ssl, fd))
{
goto error;
}
if (SSL_connect(cookie->ssl) <= 0)
{
goto error;
}
return cookie;
error:
Log(LOG_ERR, "TlsClientInit(): %s", ERR_error_string(ERR_get_error(), errorStr));
if (cookie->ssl)
{
SSL_shutdown(cookie->ssl);
SSL_free(cookie->ssl);
}
close(cookie->fd);
if (cookie->ctx)
{
SSL_CTX_free(cookie->ctx);
}
return NULL;
}
void *
TlsInitServer(int fd, const char *crt, const char *key)
{
OpenSSLCookie *cookie;
char errorStr[256];
int acceptRet = 0;
cookie = Malloc(sizeof(OpenSSLCookie));
if (!cookie)
{
return NULL;
}
memset(cookie, 0, sizeof(OpenSSLCookie));
cookie->method = TLS_server_method();
cookie->ctx = SSL_CTX_new(cookie->method);
if (!cookie->ctx)
{
Log(LOG_ERR, "TlsInitServer(): Unable to create SSL Context.");
goto error;
}
if (SSL_CTX_use_certificate_file(cookie->ctx, crt, SSL_FILETYPE_PEM) <= 0)
{
Log(LOG_ERR, "TlsInitServer(): Unable to set certificate file: %s", crt);
goto error;
}
if (SSL_CTX_use_PrivateKey_file(cookie->ctx, key, SSL_FILETYPE_PEM) <= 0)
{
Log(LOG_ERR, "TlsInitServer(): Unable to set key file: %s", key);
goto error;
}
cookie->ssl = SSL_new(cookie->ctx);
if (!cookie->ssl)
{
Log(LOG_ERR, "TlsInitServer(): Unable to create SSL object.");
goto error;
}
if (!SSL_set_fd(cookie->ssl, fd))
{
Log(LOG_ERR, "TlsInitServer(): Unable to set file descriptor.");
goto error;
}
while ((acceptRet = SSL_accept(cookie->ssl)) <= 0)
{
switch (SSL_get_error(cookie->ssl, acceptRet))
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
continue;
default:
Log(LOG_ERR, "TlsInitServer(): Unable to accept connection.");
goto error;
}
}
return cookie;
error:
if (SSL_get_error(cookie->ssl, acceptRet) == SSL_ERROR_SYSCALL)
{
Log(LOG_ERR, "TlsServerInit(): System error: %s", strerror(errno));
}
Log(LOG_ERR, "TlsServerInit(): %s", SSLErrorString(SSL_get_error(cookie->ssl, acceptRet)));
Log(LOG_ERR, "TlsServerInit(): %s", ERR_error_string(ERR_get_error(), errorStr));
if (cookie->ssl)
{
SSL_shutdown(cookie->ssl);
SSL_free(cookie->ssl);
}
close(cookie->fd);
if (cookie->ctx)
{
SSL_CTX_free(cookie->ctx);
}
Free(cookie);
return NULL;
}
ssize_t
TlsRead(void *cookie, void *buf, size_t nBytes)
{
OpenSSLCookie *ssl = cookie;
int ret;
ret = SSL_read(ssl->ssl, buf, nBytes);
if (ret <= 0)
{
switch (SSL_get_error(ssl->ssl, ret))
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
errno = EAGAIN;
break;
case SSL_ERROR_ZERO_RETURN:
ret = 0;
break;
default:
errno = EIO;
break;
}
ret = -1;
}
return ret;
}
ssize_t
TlsWrite(void *cookie, void *buf, size_t nBytes)
{
OpenSSLCookie *ssl = cookie;
int ret;
ret = SSL_write(ssl->ssl, buf, nBytes);
if (ret <= 0)
{
switch (SSL_get_error(ssl->ssl, ret))
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
errno = EAGAIN;
break;
case SSL_ERROR_ZERO_RETURN:
ret = 0;
break;
default:
errno = EIO;
break;
}
ret = -1;
}
return ret;
}
int
TlsClose(void *cookie)
{
OpenSSLCookie *ssl = cookie;
while (SSL_shutdown(ssl->ssl) == 0);
SSL_free(ssl->ssl);
SSL_CTX_free(ssl->ctx);
#if 0
close(ssl->fd);
#endif
Free(ssl);
return 0;
}
#endif

View file

@ -1,265 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <UInt64.h>
#include <stddef.h>
#include <signal.h>
size_t
UInt64Str(UInt64 x, int base, char *out, size_t len)
{
static const char symbols[] = "0123456789ABCDEF";
size_t i = len - 1;
size_t j = 0;
UInt64 base64 = UInt64Create(0, base);
/* We only have symbols up to base 16 */
if (base < 2 || base > 16)
{
return 0;
}
do
{
UInt64 mod = UInt64Rem(x, base64);
UInt32 low = UInt64Low(mod);
out[i] = symbols[low];
i--;
x = UInt64Div(x, base64);
} while (UInt64Gt(x, UInt64Create(0, 0)));
while (++i < len)
{
out[j++] = out[i];
}
out[j] = '\0';
return j;
}
#ifndef UINT64_NATIVE
/* No native 64-bit support, add our own */
UInt64
UInt64Create(UInt32 high, UInt32 low)
{
UInt64 x;
x.i[0] = low;
x.i[1] = high;
return x;
}
UInt64
UInt64Add(UInt64 x, UInt64 y)
{
UInt64 z = UInt64Create(0, 0);
int carry;
z.i[0] = x.i[0] + y.i[0];
carry = z.i[0] < x.i[0];
z.i[1] = x.i[1] + y.i[1] + carry;
return z;
}
UInt64
UInt64Sub(UInt64 x, UInt64 y)
{
UInt64 twosCompl = UInt64Add(UInt64Not(y), UInt64Create(0, 1));
return UInt64Add(x, twosCompl);
}
UInt64
UInt64Mul(UInt64 x, UInt64 y)
{
UInt64 z = UInt64Create(0, 0);
/* while (y > 0) */
while (UInt64Gt(y, UInt64Create(0, 0)))
{
/* if (y & 1 != 0) */
if (UInt64Neq(UInt64And(y, UInt64Create(0, 1)), UInt64Create(0, 0)))
{
z = UInt64Add(z, x);
}
x = UInt64Sll(x, 1);
y = UInt64Srl(y, 1);
}
return z;
}
typedef struct
{
UInt64 q;
UInt64 r;
} UInt64Ldiv;
static UInt64Ldiv
UInt64LongDivision(UInt64 n, UInt64 d)
{
UInt64Ldiv o;
int i;
o.q = UInt64Create(0, 0);
o.r = UInt64Create(0, 0);
if (UInt64Eq(d, UInt64Create(0, 0)))
{
raise(SIGFPE);
return o;
}
for (i = 63; i >= 0; i--)
{
UInt64 bit = UInt64And(UInt64Srl(n, i), UInt64Create(0, 1));
o.r = UInt64Sll(o.r, 1);
o.r = UInt64Or(o.r, bit);
if (UInt64Geq(o.r, d))
{
o.r = UInt64Sub(o.r, d);
o.q = UInt64Or(o.q, UInt64Sll(UInt64Create(0, 1), i));
}
}
return o;
}
UInt64
UInt64Div(UInt64 x, UInt64 y)
{
return UInt64LongDivision(x, y).q;
}
UInt64
UInt64Rem(UInt64 x, UInt64 y)
{
return UInt64LongDivision(x, y).r;
}
UInt64
UInt64Sll(UInt64 x, int y)
{
UInt64 z;
if (!y)
{
return x;
}
z = UInt64Create(0, 0);
if (y < 32)
{
z.i[1] = (x.i[0] >> (32 - y)) | (x.i[1] << y);
z.i[0] = x.i[0] << y;
}
else
{
z.i[1] = x.i[0] << (y - 32);
}
return z;
}
UInt64
UInt64Srl(UInt64 x, int y)
{
UInt64 z;
if (!y)
{
return x;
}
z = UInt64Create(0, 0);
if (y < 32)
{
z.i[0] = (x.i[1] << (32 - y)) | (x.i[0] >> y);
z.i[1] = x.i[1] >> y;
}
else
{
z.i[0] = x.i[1] >> (y - 32);
}
return z;
}
UInt64
UInt64And(UInt64 x, UInt64 y)
{
return UInt64Create(x.i[1] & y.i[1], x.i[0] & y.i[0]);
}
UInt64
UInt64Or(UInt64 x, UInt64 y)
{
return UInt64Create(x.i[1] | y.i[1], x.i[0] | y.i[0]);
}
UInt64
UInt64Xor(UInt64 x, UInt64 y)
{
return UInt64Create(x.i[1] ^ y.i[1], x.i[0] ^ y.i[0]);
}
UInt64
UInt64Not(UInt64 x)
{
return UInt64Create(~(x.i[1]), ~(x.i[0]));
}
int
UInt64Eq(UInt64 x, UInt64 y)
{
return x.i[0] == y.i[0] && x.i[1] == y.i[1];
}
int
UInt64Lt(UInt64 x, UInt64 y)
{
return x.i[1] < y.i[1] || (x.i[1] == y.i[1] && x.i[0] < y.i[0]);
}
int
UInt64Gt(UInt64 x, UInt64 y)
{
return x.i[1] > y.i[1] || (x.i[1] == y.i[1] && x.i[0] > y.i[0]);
}
#endif

View file

@ -1,73 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Uri.h>
#include <stdio.h>
#include <string.h>
#include <Memory.h>
Uri *
UriParse(const char *str)
{
Uri *uri;
int res;
if (!str)
{
return NULL;
}
uri = Malloc(sizeof(Uri));
if (!uri)
{
return NULL;
}
memset(uri, 0, sizeof(Uri));
res = sscanf(str, "%7[^:]://%127[^:]:%hu%255[^\n]", uri->proto, uri->host, &uri->port, uri->path) == 4
|| sscanf(str, "%7[^:]://%127[^/]%255[^\n]", uri->proto, uri->host, uri->path) == 3
|| sscanf(str, "%7[^:]://%127[^:]:%hu[^\n]", uri->proto, uri->host, &uri->port) == 3
|| sscanf(str, "%7[^:]://%127[^\n]", uri->proto, uri->host) == 2;
if (!res)
{
Free(uri);
return NULL;
}
if (!uri->path[0])
{
strcpy(uri->path, "/");
}
return uri;
}
void
UriFree(Uri * uri)
{
Free(uri);
}

View file

@ -1,339 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Util.h>
#include <Memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <limits.h>
#include <pthread.h>
#include <UInt64.h>
#ifndef PATH_MAX
#define PATH_MAX 256
#endif
#ifndef SSIZE_MAX
#define SSIZE_MAX LONG_MAX
#endif
UInt64
UtilServerTs(void)
{
struct timeval tv;
UInt64 ts;
UInt64 sec;
UInt64 usec;
gettimeofday(&tv, NULL);
/*
* POSIX defines struct timeval to be:
*
* struct timeval {
* time_t tv_sec;
* suseconds_t tv_usec;
* };
*
* Note: Although the C standard does not require that
* time_t be an integer type (it may be floating point),
* according to POSIX, time_t shall be an integer type.
* As we are targeting POSIX, not ANSI/ISO C, we can
* rely on this.
*
* The same goes for suseconds_t.
*/
if (sizeof(time_t) == sizeof(UInt64))
{
/* 64 bit time_t: convert it to a 64 bit integer */
time_t ms = tv.tv_sec * 1000;
UInt32 high = (UInt32) (ms >> 32);
UInt32 low = (UInt32) ms;
sec = UInt64Create(high, low);
}
else
{
/* Assume 32 bit time_t: promote to 64 bit, then multiply, in
* case multiplication overflows 32 bits. */
sec = UInt64Create(0, tv.tv_sec);
sec = UInt64Mul(sec, UInt64Create(0, 1000));
}
usec = UInt64Create(0, tv.tv_usec / 1000);
ts = UInt64Add(sec, usec);
return ts;
}
UInt64
UtilLastModified(char *path)
{
struct stat st;
UInt64 ts = UInt64Create(0, 0);
if (stat(path, &st) == 0)
{
if (sizeof(time_t) == sizeof(UInt64))
{
/* 64 bit time_t: convert it to a 64 bit integer */
time_t ms = st.st_mtim.tv_sec * 1000;
UInt32 high = (UInt32) (ms >> 32);
UInt32 low = (UInt32) ms;
ts = UInt64Create(high, low);
}
else
{
ts = UInt64Create(0, st.st_mtim.tv_sec);
ts = UInt64Mul(ts, UInt64Create(0, 1000));
}
/* nsec gauanteed to fit in 32 bits */
ts = UInt64Add(ts, UInt64Create(0, st.st_mtim.tv_nsec / 1000000));
}
return ts;
}
int
UtilMkdir(const char *dir, const mode_t mode)
{
char tmp[PATH_MAX];
char *p = NULL;
struct stat st;
size_t len;
len = strnlen(dir, PATH_MAX);
if (!len || len == PATH_MAX)
{
errno = ENAMETOOLONG;
return -1;
}
memcpy(tmp, dir, len);
tmp[len] = '\0';
if (tmp[len - 1] == '/')
{
tmp[len - 1] = '\0';
}
if (stat(tmp, &st) == 0 && S_ISDIR(st.st_mode))
{
return 0;
}
for (p = tmp + 1; *p; p++)
{
if (*p == '/')
{
*p = 0;
if (stat(tmp, &st) != 0)
{
if (mkdir(tmp, mode) < 0)
{
/* errno already set by mkdir() */
return -1;
}
}
else if (!S_ISDIR(st.st_mode))
{
errno = ENOTDIR;
return -1;
}
*p = '/';
}
}
if (stat(tmp, &st) != 0)
{
if (mkdir(tmp, mode) < 0)
{
/* errno already set by mkdir() */
return -1;
}
}
else if (!S_ISDIR(st.st_mode))
{
errno = ENOTDIR;
return -1;
}
return 0;
}
int
UtilSleepMillis(UInt64 ms)
{
struct timespec ts;
int res;
if (sizeof(time_t) == sizeof(UInt64))
{
ts.tv_sec = ((time_t) UInt64High(ms) << 32 | UInt64Low(ms)) / 1000;
}
else
{
ts.tv_sec = UInt64Low(ms) / 1000;
}
ts.tv_nsec = UInt64Low(UInt64Rem(ms, UInt64Create(0, 1000))) * 1000000;
res = nanosleep(&ts, &ts);
return res;
}
ssize_t
UtilGetDelim(char **linePtr, size_t * n, int delim, Stream * stream)
{
char *curPos, *newLinePtr;
size_t newLinePtrLen;
int c;
if (!linePtr || !n || !stream)
{
errno = EINVAL;
return -1;
}
if (*linePtr == NULL)
{
*n = 128;
if (!(*linePtr = Malloc(*n)))
{
errno = ENOMEM;
return -1;
}
}
curPos = *linePtr;
while (1)
{
c = StreamGetc(stream);
if (StreamError(stream) || (c == EOF && curPos == *linePtr))
{
return -1;
}
if (c == EOF)
{
break;
}
if ((*linePtr + *n - curPos) < 2)
{
if (SSIZE_MAX / 2 < *n)
{
#ifdef EOVERFLOW
errno = EOVERFLOW;
#else
errno = ERANGE;
#endif
return -1;
}
newLinePtrLen = *n * 2;
if (!(newLinePtr = Realloc(*linePtr, newLinePtrLen)))
{
errno = ENOMEM;
return -1;
}
curPos = newLinePtr + (curPos - *linePtr);
*linePtr = newLinePtr;
*n = newLinePtrLen;
}
*curPos++ = (char) c;
if (c == delim)
{
break;
}
}
*curPos = '\0';
return (ssize_t) (curPos - *linePtr);
}
ssize_t
UtilGetLine(char **linePtr, size_t * n, Stream * stream)
{
return UtilGetDelim(linePtr, n, '\n', stream);
}
static void
ThreadNoDestructor(void *p)
{
free(p);
}
UInt32
UtilThreadNo(void)
{
static pthread_key_t key;
static int createdKey = 0;
static unsigned long count = 0;
UInt32 *no;
if (!createdKey)
{
pthread_key_create(&key, ThreadNoDestructor);
createdKey = 1;
}
no = pthread_getspecific(key);
if (!no)
{
no = malloc(sizeof(UInt32));
*no = count++;
pthread_setspecific(key, no);
}
return *no;
}

View file

@ -1,82 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,187 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* 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 int 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 int 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 entires. 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 int 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 */

View file

@ -1,99 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* 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 int
Base64Pad(char **, size_t);
#endif /* CYTOPLASM_BASE64_H */

View file

@ -1,140 +0,0 @@
/*
* Copyright (C) 2022-2023 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
/***
* @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.
*/
#include <Int.h>
/**
* 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(UInt32);
/**
* 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 *, unsigned long, 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 */

View file

@ -1,169 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <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;
/**
* 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);
/**
* 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,...);
/**
* Immediately and permanently remove an object from the database.
* This function assumes the object is not locked, otherwise undefined
* behavior will result.
*/
extern int 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 int 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 int 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 int DbJsonSet(DbRef *, HashMap *);
#endif

View file

@ -1,175 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,185 +0,0 @@
/*
* Copyright (C) 2022-2023 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 int 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 int
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 */

View file

@ -1,125 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,211 +0,0 @@
/*
* Copyright (C) 2022-2023 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

View file

@ -1,104 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,91 +0,0 @@
/*
* Copyright (C) 2022-2023 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 int 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 int HttpRouterRoute(HttpRouter *, char *, void *, void **);
#endif /* CYTOPLASM_HTTPROUTER_H */

View file

@ -1,234 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Http.h>
#include <stdio.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 int 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 */

View file

@ -1,122 +0,0 @@
/*
* Copyright (C) 2022-2023 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_INT_H
#define CYTOPLASM_INT_H
/***
* @Nm Int
* @Nd Fixed-width integer types.
* @Dd April 27 2023
*
* This header provides cross-platform, fixed-width integer types.
* Specifically, it uses preprocessor magic to define the following
* types:
* .Bl -bullet -offset indent
* .It
* Int8 and UInt8
* .It
* Int16 and UInt16
* .It
* Int32 and UInt32
* .El
* .Pp
* Note that there is no 64-bit integer type, because the ANSI C
* standard makes no guarantee that such a type will exist, even
* though it does on most platforms.
* .Pp
* The reason Cytoplasm provides its own header for this is
* because ANSI C does not define fixed-width types, and while it
* should be safe to rely on C99 fixed-width types in most cases,
* there may be cases where even that is not possible.
*
* @ignore-typedefs
*/
#include <limits.h>
#define BIT32_MAX 4294967295UL
#define BIT16_MAX 65535UL
#define BIT8_MAX 255UL
#ifndef UCHAR_MAX
#error Size of char data type is unknown. Define UCHAR_MAX.
#endif
#ifndef USHRT_MAX
#error Size of short data type is unknown. Define USHRT_MAX.
#endif
#ifndef UINT_MAX
#error Size of int data type is unknown. Define UINT_MAX.
#endif
#ifndef ULONG_MAX
#error Size of long data type is unknown. Define ULONG_MAX.
#endif
#if UCHAR_MAX == BIT8_MAX
typedef signed char Int8;
typedef unsigned char UInt8;
#else
#error Unable to determine suitable data type for 8-bit integers.
#endif
#if UINT_MAX == BIT16_MAX
typedef signed int Int16;
typedef unsigned int UInt16;
#elif USHRT_MAX == BIT16_MAX
typedef signed short Int16;
typedef unsigned short UInt16;
#elif UCHAR_MAX == BIT16_MAX
typedef signed char Int16;
typedef unsigned char UInt16;
#else
#error Unable to determine suitable data type for 16-bit integers.
#endif
#if ULONG_MAX == BIT32_MAX
typedef signed long Int32;
typedef unsigned long UInt32;
#elif UINT_MAX == BIT32_MAX
typedef signed int Int32;
typedef unsigned int UInt32;
#elif USHRT_MAX == BIT32_MAX
typedef signed short Int32;
typedef unsigned short UInt32;
#elif UCHAR_MAX == BIT32_MAX
typedef signed char Int32;
typedef unsigned char UInt32;
#else
#error Unable to determine suitable data type for 32-bit integers.
#endif
#endif /* CYTOPLASM_INT_H */

View file

@ -1,252 +0,0 @@
/*
* Copyright (C) 2022-2023 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_INT64_H
#define CYTOPLASM_INT64_H
/***
* @Nm Int64
* @Nd Fixed-width 64 bit integers.
* @Dd August 11, 2023
*
* .Pp
* ANSI C89 (or C99 for that matter) provides no required mechanism
* for 64 bit integers. Nevertheless, many compilers provide them as
* extensions. However, since it is not a gaurantee, and to be fully
* standards-compliant and thus portable, a platform-agnostic interface
* is required. This header provides such an interface. If the platform
* has a 64 bit integer type, that is used, and native operations are
* performed by C preprocessor macro expansion. Otherwise, a
* compatibility layer is provided, which implements 64-bit
* arithmetic on an array of 2 32-bit numbers which are provided by
* .Xr Int 3 .
* .Pp
* Note that 64-bit emulation is certainly not as performant as using
* native 64-bit operations, so whenever possible, the native
* operations should be preferred. However, since C provides no required
* 64 bit integer on 32-bit and less platforms, this API can be used as
* a "good enough" fallback mechanism.
* .Pp
* Also note that this implementation, both in the native and
* non-native forms, makes some assumptions:
* .Bl -bullet -width Ds
* .It
* When a cast from a larger integer to a smaller integer is performed,
* the upper bits are truncated, not the lower bits.
* .It
* Negative numbers are represented in memory and in registers in two's
* compliment form.
* .El
* .Pp
* This API may provide unexpected output if these assumptions are
* false for a given platform.
*
* @ignore-typedefs
*/
#include <Int.h>
#include <UInt64.h>
#include <stddef.h>
#ifndef INT64_FORCE_EMULATED
#define BIT64_MAX 18446744073709551615UL
#if UINT_MAX == BIT64_MAX
typedef signed int Int64;
#define INT64_NATIVE
#elif ULONG_MAX == BIT64_MAX
typedef signed long Int64;
#define INT64_NATIVE
#endif
#endif /* ifndef INT64_FORCE_EMULATED */
#ifdef INT64_NATIVE
#define Int64Create(high, low) ((Int64) (((UInt64) (high) << 32) | (low)))
#define Int64Neg(x) (-(x))
#define Int64Low(a) ((UInt32) (a))
#define Int64High(a) ((UInt32) ((a) >> 32))
#define Int64Add(a, b) ((a) + (b))
#define Int64Sub(a, b) ((a) - (b))
#define Int64Mul(a, b) ((a) * (b))
#define Int64Div(a, b) ((a) / (b))
#define Int64Rem(a, b) ((a) % (b))
#define Int64Sll(a, b) ((a) << (b))
#define Int64Sra(a, b) ((a) >> (b))
#define Int64And(a, b) ((a) & (b))
#define Int64Or(a, b) ((a) | (b))
#define Int64Xor(a, b) ((a) ^ (b))
#define Int64Not(a) (~(a))
#define Int64Eq(a, b) ((a) == (b))
#define Int64Lt(a, b) ((a) < (b))
#define Int64Gt(a, b) ((a) > (b))
#define Int64Neq(a, b) ((a) != (b))
#define Int64Leq(a, b) ((a) <= (b))
#define Int64Geq(a, b) ((a) >= (b))
#else
#define Int64Neg(x) (Int64Add(Int64Not(x), Int64Create(0, 1)))
/**
* The internal bit representation of a signed integer is identical
* to an unsigned integer, the difference is in the algorithms and
* the way the bits are interpreted.
*/
typedef UInt64 Int64;
/**
* Create a new signed 64 bit integer using the given high and low
* bits.
*/
extern Int64 Int64Create(UInt32, UInt32);
/**
* Add two signed 64 bit integers together.
*/
extern Int64 Int64Add(Int64, Int64);
/**
* Subtract the second 64 bit integer from the first.
*/
extern Int64 Int64Sub(Int64, Int64);
/**
* Multiply two 64 bit integers together. The non-native version of
* this function uses the Russian Peasant method of multiplication,
* which should afford more performance than a naive multiplication by
* addition, but it is still rather slow and depends on the size of
* the integers being multiplied.
*/
extern Int64 Int64Mul(Int64, Int64);
/**
* Divide the first 64 bit integer by the second and return the
* quotient. The non-native version of this function uses naive binary
* long division, which is slow, but gauranteed to finish in constant
* time.
*/
extern Int64 Int64Div(Int64, Int64);
/**
* Divide the first 64 bit integer by the second and return the
* remainder. The non-native version of this function uses naive binary
* long division, which is slow, but gauranteed to finish in constant
* time.
*/
extern Int64 Int64Rem(Int64, Int64);
/**
* Perform a left logical bit shift of a 64 bit integer. The second
* parameter is how many places to shift, and is declared as a regular
* integer because anything more than 64 does not make sense.
*/
extern Int64 Int64Sll(Int64, int);
/**
* Perform a right arithmetic bit shift of a 64 bit integer. The second
* parameter is how many places to shift, and is declared as a regular
* integer because anything more than 64 does not make sense.
* .Pp
* Note that on platforms that use the native 64-bit implementation,
* this is technically implementation-defined, and may in fact be a
* logical shift instead of an arithmetic shift. Note that typically
* this operation is not performed on signed integers.
*/
extern Int64 Int64Sra(Int64, int);
/**
* Perform a bitwise AND (&) of the provided 64 bit integers.
*/
extern Int64 Int64And(Int64, Int64);
/**
* Perform a bitwise OR (|) of the provided 64 bit integers.
*/
extern Int64 Int64Or(Int64, Int64);
/**
* Perform a bitwise XOR (^) of the provided 64 bit integers.
*/
extern Int64 Int64Xor(Int64, Int64);
/**
* Perform a bitwise NOT (~) of the provided 64 bit integer.
*/
extern Int64 Int64Not(Int64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if they are equal.
*/
extern int Int64Eq(Int64, Int64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if the second operand is strictly
* less than the first.
*/
extern int Int64Lt(Int64, Int64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if the second operand is strictly
* greater than the first.
*/
extern int Int64Gt(Int64, Int64);
#define Int64Low(a) ((a).i[0])
#define Int64High(a) ((a).i[1])
#define Int64Neq(a, b) (!Int64Eq(a, b))
#define Int64Leq(a, b) (Int64Eq(a, b) || Int64Lt(a, b))
#define Int64Geq(a, b) (Int64Eq(a, b) || Int64Gt(a, b))
#endif
#define INT64_STRBUF 65 /* Base 2 representation with '\0' */
/**
* Convert a 64 bit integer to a string in an arbitrary base
* representation specified by the second parameter, using the provided
* buffer and length specified by the third and fourth parameters. To
* guarantee that the string will fit in the buffer, allocate it of
* size INT64_STRBUF or larger. Note that a buffer size smaller than
* INT64_STRBUF will invoke undefined behavior.
*/
extern size_t Int64Str(Int64, int, char *, size_t);
#endif /* CYTOPLASM_INT64_H */

View file

@ -1,222 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,323 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Int64.h>
#include <stdio.h>
#include <stddef.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(Int64);
/**
* 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 Int64 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(int);
/**
* 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 int 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 int 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 int 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 int 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,...);
#endif /* CYTOPLASM_JSON_H */

View file

@ -1,196 +0,0 @@
/*
* Copyright (C) 2022-2023 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

View file

@ -1,235 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* 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

View file

@ -1,105 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* 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 int 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 int QueueFull(Queue *);
/**
* Determine whether or not the queue is empty.
*/
extern int QueueEmpty(Queue *);
#endif

View file

@ -1,81 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* Generate a single random integer between 0 and the passed value.
*/
extern int RandInt(unsigned int);
/**
* 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(int *, size_t, unsigned int);
#endif /* CYTOPLASM_RAND_H */

View file

@ -1,53 +0,0 @@
/*
* Copyright (C) 2022-2023 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_RUNTIME_H
#define CYTOPLASM_RUNTIME_H
/***
* @Nm Runtime
* @Nd Supporting functions for the Cytoplasm runtime.
* @Dd May 23 2023
* @Xr Memory
*
* .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.
*/
#include <Array.h>
/**
* 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 void GenerateMemoryReport(int argc, char **argv);
#endif /* CYTOPLASM_RUNTIME_H */

View file

@ -1,76 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,129 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <Int.h>
#include <stddef.h>
/**
* Convert UTF-16 into a Unicode codepoint.
*/
extern UInt32 StrUtf16Decode(UInt16, UInt16);
/**
* 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);
/**
* 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 int 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 int StrEquals(const char *, const char *);
#endif /* CYTOPLASM_STR_H */

View file

@ -1,223 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
/**
* 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 int 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 int 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 */

View file

@ -1,109 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,252 +0,0 @@
/*
* Copyright (C) 2022-2023 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_UINT64_H
#define CYTOPLASM_UINT64_H
/***
* @Nm UInt64
* @Nd Fixed-width 64 bit integers.
* @Dd August 11, 2023
*
* .Pp
* ANSI C89 (or C99 for that matter) provides no required mechanism
* for 64 bit integers. Nevertheless, many compilers provide them as
* extensions. However, since it is not a gaurantee, and to be fully
* standards-compliant and thus portable, a platform-agnostic interface
* is required. This header provides such an interface. If the platform
* has a 64 bit integer type, that is used, and native operations are
* performed by C preprocessor macro expansion. Otherwise, a
* compatibility layer is provided, which implements 64-bit
* arithmetic on an array of 2 32-bit numbers which are provided by
* .Xr Int 3 .
* .Pp
* Note that 64-bit emulation is certainly not as performant as using
* native 64-bit operations, so whenever possible, the native
* operations should be preferred. However, since C provides no required
* 64 bit integer on 32-bit and less platforms, this API can be used as
* a "good enough" fallback mechanism.
* .Pp
* Also note that this implementation, both in the native and
* non-native forms, makes some assumptions:
* .Bl -bullet -width Ds
* .It
* When a cast from a larger integer to a smaller integer is performed,
* the upper bits are truncated, not the lower bits.
* .It
* Negative numbers are represented in memory and in registers in two's
* compliment form.
* .El
* .Pp
* This API may provide unexpected output if these assumptions are
* false for a given platform.
*
* @ignore-typedefs
*/
#include <Int.h>
#include <stddef.h>
#ifndef INT64_FORCE_EMULATED
#define BIT64_MAX 18446744073709551615UL
#if UINT_MAX == BIT64_MAX
/* typedef signed int Int64; */
typedef unsigned int UInt64;
#define UINT64_NATIVE
#elif ULONG_MAX == BIT64_MAX
/* typedef signed int Int64; */
typedef unsigned long UInt64;
#define UINT64_NATIVE
#endif
#endif /* ifndef INT64_FORCE_EMULATED */
#ifdef UINT64_NATIVE
#define UInt64Create(high, low) (((UInt64) (high) << 32) | (low))
#define UInt64Low(a) ((UInt32) ((a) & 0x00000000FFFFFFFF))
#define UInt64High(a) ((UInt32) ((a) >> 32))
#define UInt64Add(a, b) ((a) + (b))
#define UInt64Sub(a, b) ((a) - (b))
#define UInt64Mul(a, b) ((a) * (b))
#define UInt64Div(a, b) ((a) / (b))
#define UInt64Rem(a, b) ((a) % (b))
#define UInt64Sll(a, b) ((a) << (b))
#define UInt64Srl(a, b) ((a) >> (b))
#define UInt64And(a, b) ((a) & (b))
#define UInt64Or(a, b) ((a) | (b))
#define UInt64Xor(a, b) ((a) ^ (b))
#define UInt64Not(a) (~(a))
#define UInt64Eq(a, b) ((a) == (b))
#define UInt64Lt(a, b) ((a) < (b))
#define UInt64Gt(a, b) ((a) > (b))
#define UInt64Neq(a, b) ((a) != (b))
#define UInt64Leq(a, b) ((a) <= (b))
#define UInt64Geq(a, b) ((a) >= (b))
#else
/**
* For platforms that do not have a native integer large enough to
* store a 64 bit integer, this struct is used. i[0] contains the low
* bits of integer, and i[1] contains the high bits of the integer.
* .Pp
* This struct should not be accessed directly, because UInt64 may not
* actually be this struct, it might be an actual integer type. For
* maximum portability, only use the functions defined here to
* manipulate 64 bit integers.
*/
typedef struct
{
UInt32 i[2];
} UInt64;
/**
* Create a new unsigned 64 bit integer using the given high and low
* bits.
*/
extern UInt64 UInt64Create(UInt32, UInt32);
/**
* Add two unsigned 64 bit integers together.
*/
extern UInt64 UInt64Add(UInt64, UInt64);
/**
* Subtract the second 64 bit integer from the first.
*/
extern UInt64 UInt64Sub(UInt64, UInt64);
/**
* Multiply two 64 bit integers together. The non-native version of
* this function uses the Russian Peasant method of multiplication,
* which should afford more performance than a naive multiplication by
* addition, but it is still rather slow and depends on the size of
* the integers being multiplied.
*/
extern UInt64 UInt64Mul(UInt64, UInt64);
/**
* Divide the first 64 bit integer by the second and return the
* quotient. The non-native version of this function uses naive binary
* long division, which is slow, but gauranteed to finish in constant
* time.
*/
extern UInt64 UInt64Div(UInt64, UInt64);
/**
* Divide the first 64 bit integer by the second and return the
* remainder. The non-native version of this function uses naive binary
* long division, which is slow, but gauranteed to finish in constant
* time.
*/
extern UInt64 UInt64Rem(UInt64, UInt64);
/**
* Perform a left logical bit shift of a 64 bit integer. The second
* parameter is how many places to shift, and is declared as a regular
* integer because anything more than 64 does not make sense.
*/
extern UInt64 UInt64Sll(UInt64, int);
/**
* Perform a right logical bit shift of a 64 bit integer. The second
* parameter is how many places to shift, and is declared as a regular
* integer because anything more than 64 does not make sense.
*/
extern UInt64 UInt64Srl(UInt64, int);
/**
* Perform a bitwise AND (&) of the provided 64 bit integers.
*/
extern UInt64 UInt64And(UInt64, UInt64);
/**
* Perform a bitwise OR (|) of the provided 64 bit integers.
*/
extern UInt64 UInt64Or(UInt64, UInt64);
/**
* Perform a bitwise XOR (^) of the provided 64 bit integers.
*/
extern UInt64 UInt64Xor(UInt64, UInt64);
/**
* Perform a bitwise NOT (~) of the provided 64 bit integer.
*/
extern UInt64 UInt64Not(UInt64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if they are equal.
*/
extern int UInt64Eq(UInt64, UInt64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if the second operand is strictly
* less than the first.
*/
extern int UInt64Lt(UInt64, UInt64);
/**
* Perform a comparison of the provided 64 bit integers and return a C
* boolean that is true if and only if the second operand is strictly
* greater than the first.
*/
extern int UInt64Gt(UInt64, UInt64);
#define UInt64Low(a) ((a).i[0])
#define UInt64High(a) ((a).i[1])
#define UInt64Neq(a, b) (!UInt64Eq(a, b))
#define UInt64Leq(a, b) (UInt64Eq(a, b) || UInt64Lt(a, b))
#define UInt64Geq(a, b) (UInt64Eq(a, b) || UInt64Gt(a, b))
#endif
#define UINT64_STRBUF 65 /* Base 2 representation with '\0' */
/**
* Convert a 64 bit integer to a string in an arbitrary base
* representation specified by the second parameter, using the provided
* buffer and length specified by the third and fourth parameters. To
* guarantee that the string will fit in the buffer, allocate it of
* size UINT64_STRBUF or larger. Note that a buffer size smaller than
* UINT64_STRBUF will invoke undefined behavior.
*/
extern size_t UInt64Str(UInt64, int, char *, size_t);
#endif /* CYTOPLASM_UINT64_H */

View file

@ -1,69 +0,0 @@
/*
* Copyright (C) 2022-2023 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 */

View file

@ -1,117 +0,0 @@
/*
* Copyright (C) 2022-2023 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>
#include <UInt64.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 UtilServerTs(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 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);
/**
* 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 UtilThreadNo(void);
#endif /* CYTOPLASM_UTIL_H */

View file

@ -1,568 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <HashMap.h>
#include <Str.h>
#include <Memory.h>
#include <Args.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
typedef struct DocDecl
{
char docs[HEADER_EXPR_MAX];
HeaderDeclaration decl;
} DocDecl;
typedef struct DocTypedef
{
char docs[HEADER_EXPR_MAX];
char text[HEADER_EXPR_MAX];
} DocTypedef;
typedef struct DocGlobal
{
char docs[HEADER_EXPR_MAX];
HeaderGlobal global;
} DocGlobal;
static void
ParseMainBlock(HashMap * registers, Array * descr, char *comment)
{
char *line = strtok(comment, "\n");
while (line)
{
while (*line && (isspace(*line) || *line == '*'))
{
line++;
}
if (!*line)
{
goto next;
}
if (*line == '@')
{
int i = 0;
line++;
while (!isspace(line[i]))
{
i++;
}
line[i] = '\0';
Free(HashMapSet(registers, line, StrDuplicate(line + i + 1)));
}
else
{
ArrayAdd(descr, StrDuplicate(line));
}
next:
line = strtok(NULL, "\n");
}
}
int
Main(Array * args)
{
HeaderExpr expr;
size_t i;
char *key;
char *val;
int exit = EXIT_SUCCESS;
ArgParseState arg;
HashMap *registers = HashMapCreate();
Array *descr = ArrayCreate();
Array *declarations = ArrayCreate();
DocDecl *decl = NULL;
Array *typedefs = ArrayCreate();
DocTypedef *type = NULL;
Array *globals = ArrayCreate();
DocGlobal *global = NULL;
char comment[HEADER_EXPR_MAX];
int isDocumented = 0;
Stream *in = NULL;
Stream *out = NULL;
int opt;
ArgParseStateInit(&arg);
while ((opt = ArgParse(&arg, args, "i:o:D:")) != -1)
{
switch (opt)
{
case 'i':
if (in)
{
break;
}
if (StrEquals(arg.optArg, "-"))
{
in = StreamStdin();
}
else
{
int len = strlen(arg.optArg);
in = StreamOpen(arg.optArg, "r");
if (!in)
{
StreamPrintf(StreamStderr(), "Error: %s:%s",
arg.optArg, strerror(errno));
exit = EXIT_FAILURE;
goto finish;
}
while (arg.optArg[len - 1] != '.')
{
arg.optArg[len - 1] = '\0';
len--;
}
arg.optArg[len - 1] = '\0';
len--;
HashMapSet(registers, "Nm", StrDuplicate(arg.optArg));
}
break;
case 'o':
if (out)
{
break;
}
if (StrEquals(arg.optArg, "-"))
{
out = StreamStdout();
}
else
{
out = StreamOpen(arg.optArg, "w");
if (!out)
{
StreamPrintf(StreamStderr(), "Error: %s:%s",
arg.optArg, strerror(errno));
exit = EXIT_FAILURE;
goto finish;
}
}
break;
case 'D':
val = arg.optArg;
while (*val && *val != '=')
{
val++;
}
if (!*val || *val != '=')
{
StreamPrintf(StreamStderr(), "Bad register definition: %s",
arg.optArg);
exit = EXIT_FAILURE;
goto finish;
}
*val = '\0';
val++;
HashMapSet(registers, arg.optArg, StrDuplicate(val));
break;
}
}
if (!in)
{
in = StreamStdin();
}
if (!out)
{
out = StreamStdout();
}
memset(&expr, 0, sizeof(expr));
while (1)
{
HeaderParse(in, &expr);
switch (expr.type)
{
case HP_PREPROCESSOR_DIRECTIVE:
/* Ignore */
break;
case HP_EOF:
/* Done parsing */
goto last;
case HP_PARSE_ERROR:
case HP_SYNTAX_ERROR:
StreamPrintf(StreamStderr(), "Parse Error: (line %d) %s\n",
expr.data.error.lineNo, expr.data.error.msg);
exit = EXIT_FAILURE;
goto finish;
case HP_COMMENT:
if (expr.data.text[0] != '*')
{
break;
}
if (strncmp(expr.data.text, "**", 2) == 0)
{
ParseMainBlock(registers, descr, expr.data.text);
}
else
{
strncpy(comment, expr.data.text, sizeof(comment));
isDocumented = 1;
}
break;
case HP_TYPEDEF:
if (HashMapGet(registers, "ignore-typedefs"))
{
break;
}
if (!isDocumented)
{
StreamPrintf(StreamStderr(),
"Error: Undocumented typedef:\n%s\n",
expr.data.text);
exit = EXIT_FAILURE;
goto finish;
}
else
{
type = Malloc(sizeof(DocTypedef));
strncpy(type->docs, comment, sizeof(type->docs));
strncpy(type->text, expr.data.text, sizeof(type->text));
ArrayAdd(typedefs, type);
isDocumented = 0;
}
break;
case HP_DECLARATION:
if (!isDocumented)
{
StreamPrintf(StreamStderr(),
"Error: %s() is undocumented.\n",
expr.data.declaration.name);
exit = EXIT_FAILURE;
goto finish;
}
else
{
decl = Malloc(sizeof(DocDecl));
decl->decl = expr.data.declaration;
decl->decl.args = ArrayCreate();
strncpy(decl->docs, comment, sizeof(decl->docs));
for (i = 0; i < ArraySize(expr.data.declaration.args); i++)
{
ArrayAdd(decl->decl.args, StrDuplicate(ArrayGet(expr.data.declaration.args, i)));
}
ArrayAdd(declarations, decl);
isDocumented = 0;
}
break;
case HP_GLOBAL:
if (!isDocumented)
{
StreamPrintf(StreamStderr(),
"Error: Global %s is undocumented.\n",
expr.data.global.name);
exit = EXIT_FAILURE;
goto finish;
}
else
{
global = Malloc(sizeof(DocGlobal));
global->global = expr.data.global;
strncpy(global->docs, comment, sizeof(global->docs));
ArrayAdd(globals, global);
isDocumented = 0;
}
break;
case HP_UNKNOWN:
if (HashMapGet(registers, "suppress-warnings"))
{
break;
}
StreamPrintf(StreamStderr(), "Warning: Unknown expression: %s\n",
expr.data.text);
break;
default:
StreamPrintf(StreamStderr(), "Unknown header type: %d\n", expr.type);
StreamPrintf(StreamStderr(), "This is a programming error.\n");
exit = EXIT_FAILURE;
goto finish;
}
}
last:
val = HashMapGet(registers, "Nm");
if (!val)
{
HashMapSet(registers, "Nm", StrDuplicate("Unnamed"));
}
val = HashMapGet(registers, "Dd");
if (!val)
{
time_t currentTime;
struct tm *timeInfo;
char tsBuf[1024];
currentTime = time(NULL);
timeInfo = localtime(&currentTime);
strftime(tsBuf, sizeof(tsBuf), "%B %d %Y", timeInfo);
val = tsBuf;
}
StreamPrintf(out, ".Dd $%s: %s $\n", "Mdocdate", val);
val = HashMapGet(registers, "Os");
if (val)
{
StreamPrintf(out, ".Os %s\n", val);
}
val = HashMapGet(registers, "Nm");
StreamPrintf(out, ".Dt %s 3\n", val);
StreamPrintf(out, ".Sh NAME\n");
StreamPrintf(out, ".Nm %s\n", val);
val = HashMapGet(registers, "Nd");
if (!val)
{
val = "No Description.";
}
StreamPrintf(out, ".Nd %s\n", val);
StreamPrintf(out, ".Sh SYNOPSIS\n");
val = HashMapGet(registers, "Nm");
StreamPrintf(out, ".In %s.h\n", val);
for (i = 0; i < ArraySize(declarations); i++)
{
size_t j;
decl = ArrayGet(declarations, i);
StreamPrintf(out, ".Ft %s\n", decl->decl.returnType);
StreamPrintf(out, ".Fn %s ", decl->decl.name);
for (j = 0; j < ArraySize(decl->decl.args); j++)
{
StreamPrintf(out, "\"%s\" ", ArrayGet(decl->decl.args, j));
}
StreamPutc(out, '\n');
}
if (ArraySize(globals))
{
StreamPrintf(out, ".Sh GLOBALS\n");
for (i = 0; i < ArraySize(globals); i++)
{
char *line;
global = ArrayGet(globals, i);
StreamPrintf(out, ".Ss %s %s\n", global->global.type, global->global.name);
line = strtok(global->docs, "\n");
while (line)
{
while (*line && (isspace(*line) || *line == '*'))
{
line++;
}
if (*line)
{
StreamPrintf(out, "%s\n", line);
}
line = strtok(NULL, "\n");
}
}
}
if (ArraySize(typedefs))
{
StreamPrintf(out, ".Sh TYPE DECLARATIONS\n");
for (i = 0; i < ArraySize(typedefs); i++)
{
char *line;
type = ArrayGet(typedefs, i);
StreamPrintf(out, ".Bd -literal -offset indent\n");
StreamPrintf(out, "%s\n", type->text);
StreamPrintf(out, ".Ed\n.Pp\n");
line = strtok(type->docs, "\n");
while (line)
{
while (*line && (isspace(*line) || *line == '*'))
{
line++;
}
if (*line)
{
StreamPrintf(out, "%s\n", line);
}
line = strtok(NULL, "\n");
}
}
}
StreamPrintf(out, ".Sh DESCRIPTION\n");
for (i = 0; i < ArraySize(descr); i++)
{
StreamPrintf(out, "%s\n", ArrayGet(descr, i));
}
for (i = 0; i < ArraySize(declarations); i++)
{
size_t j;
char *line;
decl = ArrayGet(declarations, i);
StreamPrintf(out, ".Ss %s %s(",
decl->decl.returnType, decl->decl.name);
for (j = 0; j < ArraySize(decl->decl.args); j++)
{
StreamPrintf(out, "%s", ArrayGet(decl->decl.args, j));
if (j < ArraySize(decl->decl.args) - 1)
{
StreamPuts(out, ", ");
}
}
StreamPuts(out, ")\n");
line = strtok(decl->docs, "\n");
while (line)
{
while (*line && (isspace(*line) || *line == '*'))
{
line++;
}
if (*line)
{
StreamPrintf(out, "%s\n", line);
}
line = strtok(NULL, "\n");
}
}
val = HashMapGet(registers, "Xr");
if (val)
{
char *xr = strtok(val, " ");
StreamPrintf(out, ".Sh SEE ALSO\n");
while (xr)
{
if (*xr)
{
StreamPrintf(out, ".Xr %s 3 ", xr);
}
xr = strtok(NULL, " ");
if (xr)
{
StreamPutc(out, ',');
}
StreamPutc(out, '\n');
}
}
finish:
if (in != StreamStdin())
{
StreamClose(in);
}
if (out != StreamStdout())
{
StreamClose(out);
}
for (i = 0; i < ArraySize(declarations); i++)
{
size_t j;
decl = ArrayGet(declarations, i);
for (j = 0; j < ArraySize(decl->decl.args); j++)
{
Free(ArrayGet(decl->decl.args, j));
}
ArrayFree(decl->decl.args);
Free(decl);
}
ArrayFree(declarations);
for (i = 0; i < ArraySize(typedefs); i++)
{
Free(ArrayGet(typedefs, i));
}
ArrayFree(typedefs);
for (i = 0; i < ArraySize(globals); i++)
{
Free(ArrayGet(globals, i));
}
ArrayFree(globals);
for (i = 0; i < ArraySize(descr); i++)
{
Free(ArrayGet(descr, i));
}
ArrayFree(descr);
while (HashMapIterate(registers, &key, (void **) &val))
{
Free(val);
}
HashMapFree(registers);
return exit;
}

View file

@ -1,145 +0,0 @@
#include <Int64.h>
#include <Log.h>
/* AssertEquals(actual, expected) */
int
AssertEquals(char *msg, Int64 x, Int64 y)
{
if (!Int64Eq(x, y))
{
Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg,
Int64High(y), Int64Low(y),
Int64High(x), Int64Low(x));
return 0;
}
return 1;
}
int
Main(void)
{
Int64 x, y;
Log(LOG_INFO, "sizeof(Int64) = %lu", sizeof(Int64));
#ifdef INT64_NATIVE
Log(LOG_INFO, "Using native 64-bit integers.");
#else
Log(LOG_INFO, "Using emulated 64-bit integers.");
#endif
/* BSR Tests */
x = Int64Create(0x000000FF, 0x00000000);
y = Int64Sra(x, 4);
AssertEquals("x >> 4", y, Int64Create(0x0000000F, 0xF0000000));
y = Int64Sra(x, 8);
AssertEquals("x >> 8", y, Int64Create(0x00000000, 0xFF000000));
y = Int64Sra(x, 36);
AssertEquals("x >> 36", y, Int64Create(0x00000000, 0x0000000F));
x = Int64Create(0xFF000000, 0x00000000);
y = Int64Sra(x, 4);
AssertEquals("x >> 4", y, Int64Create(0xFFF00000, 0x00000000));
y = Int64Sra(x, 8);
AssertEquals("x >> 8", y, Int64Create(0xFFFF0000, 0x00000000));
y = Int64Sra(x, 63);
AssertEquals("x >> 63", y, Int64Create(0xFFFFFFFF, 0xFFFFFFFF));
/* BSL Tests */
x = Int64Create(0x00000000, 0xFF000000);
y = Int64Sll(x, 4);
AssertEquals("x << 4", y, Int64Create(0x0000000F, 0xF0000000));
y = Int64Sll(x, 8);
AssertEquals("x << 8", y, Int64Create(0x000000FF, 0x00000000));
y = Int64Sll(x, 36);
AssertEquals("x << 36", y, Int64Create(0xF0000000, 0x00000000));
/* ADD Tests */
x = Int64Create(0x00000000, 0xF0000001);
y = Int64Create(0x00000000, 0x00000002);
AssertEquals("0xF0000001 + 0x00000002", Int64Add(x, y), Int64Create(0x00000000, 0xF0000003));
x = Int64Create(0x00000000, 0xF0000000);
y = Int64Create(0x00000000, 0x10000000);
AssertEquals("0xF0000000 + 0x10000000", Int64Add(x, y), Int64Create(0x00000001, 0x00000000));
x = Int64Create(0, 5);
y = Int64Neg(Int64Create(0, 10));
AssertEquals("5 + (-10)", Int64Add(x, y), Int64Neg(Int64Create(0, 5)));
/* SUB Tests */
x = Int64Create(0x00000000, 0x00000005);
y = Int64Create(0x00000000, 0x00000002);
AssertEquals("0x00000005 - 0x00000002", Int64Sub(x, y), Int64Create(0x00000000, 0x00000003));
x = Int64Create(0x00000001, 0x00000000);
y = Int64Create(0x00000000, 0x00000001);
AssertEquals("0x00000001 0x00000000 - 0x00000001", Int64Sub(x, y), Int64Create(0x00000000, 0xFFFFFFFF));
x = Int64Create(0, 5);
y = Int64Create(0, 10);
AssertEquals("5 - 10", Int64Sub(x, y), Int64Neg(Int64Create(0, 5)));
x = Int64Create(0, 5);
y = Int64Neg(Int64Create(0, 10));
AssertEquals("5 - (-10)", Int64Sub(x, y), Int64Create(0, 15));
/* MUL Tests */
x = Int64Create(0, 18);
y = Int64Create(0, 1);
AssertEquals("18 * 1", Int64Mul(x, y), Int64Create(0, 18));
x = Int64Create(0, 20);
y = Int64Create(0, 12);
AssertEquals("20 * 12", Int64Mul(x, y), Int64Create(0, 240));
x = Int64Create(0x00000000, 0x00000005);
y = Int64Create(0x00000000, 0x00000005);
AssertEquals("0x00000005 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000000, 0x00000019));
x = Int64Create(0x00000001, 0x00000000);
y = Int64Create(0x00000000, 0x00000005);
AssertEquals("0x00000001 0x00000000 * 0x00000005", Int64Mul(x, y), Int64Create(0x00000005, 0x00000000));
/* DIV Tests */
x = Int64Create(0, 12);
y = Int64Create(0, 4);
AssertEquals("12 / 4", Int64Div(x, y), Int64Create(0, 3));
/* MOD Tests */
x = Int64Create(0x000000FF, 0x00000000);
y = Int64Create(0x00000000, 0x00000010);
AssertEquals("0x000000FF 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0));
x = Int64Create(0x00000000, 0xFF000000);
y = Int64Create(0x00000000, 0x00000010);
AssertEquals("0x00000000 0xFF000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0));
x = Int64Create(0xFF000000, 0x00000000);
y = Int64Create(0x00000000, 0x00000010);
AssertEquals("0xFF000000 0x00000000 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0));
x = Int64Create(0x00000000, 0x000000F0);
y = Int64Create(0x00000000, 0x00000010);
AssertEquals("0x00000000 0x000000F0 mod 0x00000010", Int64Rem(x, y), Int64Create(0, 0));
/* TODO: Add more tests for negative multiplication, division, and
* mod */
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -1,787 +0,0 @@
/*
* Copyright (C) 2022-2023 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 <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <Args.h>
#include <Memory.h>
#include <Array.h>
#include <HashMap.h>
#include <Str.h>
#include <Stream.h>
#define BLOCK 64
/*
* Anything that can be done in the language itself should; to
* keep the interpreter minimal. Note that this has the side effect
* of allowing the runtime to become corrupted or undefined which
* may lead to confusing behavior, but the language primitives
* cannot be corrupted, so things can always be corrected.
*/
static char runtime[] =
"[def let; name, value; [eval [(]def [name][;][;][value][)]]]"
"[def not; cond; [if [cond];;true]]"
"[def (); str; [(][str][)]]"
"[def #;;]"
"[def //;;]"
"[def day;; [time %d]]"
"[def month;; [time %m]]"
"[def year;; [time %Y]]"
"[def day-name;; [time %A]]"
"[def month-name;; [time %B]]"
"[def hour;; [time %I]]"
"[def minute;; [time %M]]"
"[def second;; [time %S]]"
"[def am/pm;; [time %p]]"
"[def timezone;; [time %Z]]"
"[def date;; [day-name], [month-name] [day], [year]]"
"[def timestamp;; [hour]:[minute]:[second] [am/pm] [timezone]]"
;
static Stream *err;
static int debug;
typedef struct Definition
{
Array *args;
char *body;
} Definition;
static char *
EvalExpr(char *expr, Array * stack);
static char *
ReadExpr(Stream * in)
{
size_t len = 0;
size_t size = 0;
size_t level = 1;
int c;
char *expr;
char *tmp;
size += BLOCK;
expr = Malloc(size * sizeof(char));
if (!expr)
{
return NULL;
}
while ((c = StreamGetc(in)) != EOF)
{
if (len + 1 >= size)
{
size += BLOCK;
tmp = Realloc(expr, size * sizeof(char));
if (!tmp)
{
Free(expr);
return NULL;
}
expr = tmp;
}
if (c == ']')
{
if (level > 0)
{
level--;
}
if (!level)
{
break;
}
}
if (c == '[')
{
level++;
}
expr[len] = c;
len++;
}
expr[len] = '\0';
return expr;
}
static char *
Eval(char **argp, Array * stack)
{
size_t len;
size_t size;
char *res;
char *tmp;
char *arg = *argp;
len = 0;
size = BLOCK;
res = Malloc(size * sizeof(char));
if (!res)
{
return NULL;
}
while (*arg)
{
if (len + 1 >= size)
{
size += BLOCK;
tmp = Realloc(res, size * sizeof(char));
if (!tmp)
{
Free(res);
return NULL;
}
res = tmp;
}
if (*arg == ';')
{
res[len] = '\0';
break;
}
else if (*arg == '[')
{
size_t level = 1;
char *endp;
char *evalRes;
char *base;
arg++;
endp = arg;
while (level && *endp)
{
if (*endp == ']')
{
if (level > 0)
{
level--;
}
if (!level)
{
break;
}
}
else if (*endp == '[')
{
level++;
}
endp++;
}
*endp = '\0';
evalRes = EvalExpr(arg, stack);
base = evalRes;
while (*evalRes)
{
if (len + 1 >= size)
{
size += BLOCK;
tmp = Realloc(res, size * sizeof(char));
if (!tmp)
{
Free(base);
Free(res);
return NULL;
}
res = tmp;
}
res[len] = *evalRes;
len++;
evalRes++;
}
Free(base);
*endp = ']';
arg = endp;
}
else
{
res[len] = *arg;
len++;
}
arg++;
}
if (*arg == ';')
{
arg++;
while (isspace(*arg))
{
arg++;
}
}
res[len] = '\0';
*argp = arg;
return res;
}
static char *
EvalExpr(char *expr, Array * stack)
{
char *argv = expr;
char *op = expr;
char ops;
char *opi;
char *res;
while (*argv && !isspace(*argv))
{
argv++;
}
ops = *argv;
opi = argv;
*argv = '\0';
argv++;
if (StrEquals(op, "("))
{
res = StrDuplicate("[");
}
else if (StrEquals(op, ")"))
{
res = StrDuplicate("]");
}
else if (StrEquals(op, ";"))
{
res = StrDuplicate(";");
}
else if (StrEquals(op, "env"))
{
char *env = Eval(&argv, stack);
char *val = getenv(env);
Free(env);
if (!val)
{
val = "";
}
res = StrDuplicate(val);
}
else if (StrEquals(op, "time"))
{
char *fmt = Eval(&argv, stack);
time_t currentTime = time(NULL);
struct tm *timeInfo = localtime(&currentTime);
char *timestamp = Malloc(128 * sizeof(char));
if (strftime(timestamp, 128 * sizeof(char), fmt, timeInfo))
{
res = timestamp;
}
else
{
res = StrDuplicate("");
Free(timestamp);
}
Free(fmt);
}
else if (StrEquals(op, "eval"))
{
char *expr = Eval(&argv, stack);
char *tmp = expr;
res = Eval(&tmp, stack);
Free(expr);
}
else if (StrEquals(op, "if"))
{
char *cond = Eval(&argv, stack);
/*
* If the condition is false, seek past the true argument
* without evaluating it.
*/
if (StrBlank(cond))
{
size_t level = 0;
while (*argv)
{
switch (*argv)
{
case '[':
level++;
break;
case ']':
if (level > 0)
{
level--;
}
break;
case ';':
if (!level)
{
argv++;
while (isspace(*argv))
{
argv++;
}
goto end_loop;
}
break;
}
argv++;
}
}
end_loop:
res = Eval(&argv, stack);
Free(cond);
}
else if (StrEquals(op, "eq?"))
{
char *s1 = Eval(&argv, stack);
char *s2 = Eval(&argv, stack);
if (StrEquals(s1, s2))
{
res = StrDuplicate("true");
}
else
{
res = StrDuplicate("");
}
Free(s1);
Free(s2);
}
else if (StrEquals(op, "def?"))
{
Definition *def;
size_t i;
char *directive = Eval(&argv, stack);
for (i = 0; i < ArraySize(stack); i++)
{
HashMap *stackFrame = ArrayGet(stack, i);
def = HashMapGet(stackFrame, directive);
if (def)
{
break;
}
}
if (def)
{
res = StrDuplicate("true");
}
else
{
res = StrDuplicate("");
}
Free(directive);
}
else if (StrEquals(op, "print"))
{
char *msg = Eval(&argv, stack);
StreamPrintf(err, msg);
Free(msg);
res = StrDuplicate("");
}
else if (StrEquals(op, "def"))
{
char *name = Eval(&argv, stack);
char *args = Eval(&argv, stack);
char *body = argv;
char *tok;
HashMap *stackFrame;
size_t i;
Definition *def = Malloc(sizeof(Definition));
def->body = StrDuplicate(body);
def->args = ArrayCreate();
if (debug)
{
StreamPrintf(err, "(def op = '%s', args = [", name);
}
tok = strtok(args, ",");
while (tok)
{
while (isspace(*tok))
{
tok++;
}
ArrayAdd(def->args, StrDuplicate(tok));
if (debug)
{
StreamPrintf(err, "'%s', ", tok);
}
tok = strtok(NULL, ",");
}
if (debug)
{
StreamPrintf(err, "], body = '%s')\n", def->body);
}
Free(args);
stackFrame = ArrayGet(stack, ArraySize(stack) - 1);
def = HashMapSet(stackFrame, name, def);
if (def)
{
for (i = 0; i < ArraySize(def->args); i++)
{
Free(ArrayGet(def->args, i));
}
ArrayFree(def->args);
Free(def->body);
Free(def);
}
Free(name);
res = StrDuplicate("");
}
else if (StrEquals(op, "undef"))
{
char *name = Eval(&argv, stack);
HashMap *stackFrame = ArrayGet(stack, ArraySize(stack) - 1);
Definition *def = HashMapDelete(stackFrame, name);
Free(name);
if (def)
{
size_t i;
for (i = 0; i < ArraySize(def->args); i++)
{
Free(ArrayGet(def->args, i));
}
ArrayFree(def->args);
Free(def->body);
Free(def);
}
res = StrDuplicate("");
}
else if (StrEquals(op, "include"))
{
char *fileName = Eval(&argv, stack);
Stream *file = StreamOpen(fileName, "r");
size_t size;
size_t len;
char c;
Free(fileName);
if (!file)
{
res = StrDuplicate("");
goto finish;
}
size = BLOCK;
len = 0;
res = Malloc(size * sizeof(char));
while ((c = StreamGetc(file)) != EOF)
{
if (len + 1 >= size)
{
size += BLOCK;
res = Realloc(res, size * sizeof(char));
}
res[len] = c;
len++;
}
res[len] = '\0';
StreamClose(file);
}
else
{
/* Locate macro on stack and execute it */
Definition *def;
char *body;
HashMap *stackFrame;
size_t i;
for (i = 0; i < ArraySize(stack); i++)
{
stackFrame = ArrayGet(stack, i);
def = HashMapGet(stackFrame, op);
if (def)
{
break;
}
}
if (!def)
{
res = StrDuplicate("");
goto finish;
}
stackFrame = HashMapCreate();
for (i = 0; i < ArraySize(def->args); i++)
{
char *argName = ArrayGet(def->args, i);
char *argVal = Eval(&argv, stack);
Definition *argDef = Malloc(sizeof(Definition));
argDef->args = NULL;
argDef->body = argVal;
HashMapSet(stackFrame, argName, argDef);
}
ArrayInsert(stack, 0, stackFrame);
body = def->body;
res = Eval(&body, stack);
ArrayDelete(stack, 0);
while (HashMapIterate(stackFrame, &body, (void **) &def))
{
for (i = 0; i < ArraySize(def->args); i++)
{
Free(ArrayGet(def->args, i));
}
ArrayFree(def->args);
Free(def->body);
Free(def);
}
HashMapFree(stackFrame);
}
finish:
*opi = ops;
return res;
}
static int
Process(Stream * in, Stream * out, Array * stack)
{
int c;
while ((c = StreamGetc(in)) != EOF)
{
if (c == '[')
{
char *expr;
char *res;
expr = ReadExpr(in);
res = EvalExpr(expr, stack);
StreamPuts(out, res);
StreamFlush(out);
Free(expr);
Free(res);
}
else
{
StreamPutc(out, c);
}
StreamFlush(out);
}
return 1;
}
int
Main(Array * args)
{
ArgParseState arg;
int opt;
int ret = EXIT_FAILURE;
size_t i;
Array *stack;
char *rt;
Stream *in;
Stream *out;
Array *eval = ArrayCreate();
debug = 0;
in = StreamStdin();
out = StreamStdout();
err = StreamStderr();
ArgParseStateInit(&arg);
while ((opt = ArgParse(&arg, args, "i:o:e:d")) != -1)
{
switch (opt)
{
case 'i':
if (StrEquals(arg.optArg, "-"))
{
in = StreamStdin();
}
in = StreamOpen(arg.optArg, "r");
if (!in)
{
StreamPrintf(err, "Unable to open '%s' for reading\n", arg.optArg);
goto finish;
}
break;
case 'o':
if (StrEquals(arg.optArg, "-"))
{
out = StreamStdout();
}
out = StreamOpen(arg.optArg, "w");
if (!out)
{
StreamPrintf(err, "Unable to open '%s' for writing.\n", arg.optArg);
goto finish;
}
break;
case 'e':
ArrayAdd(eval, StrDuplicate(arg.optArg));
break;
case 'd':
debug = 1;
break;
default:
goto finish;
}
}
stack = ArrayCreate();
ArrayAdd(stack, HashMapCreate());
/*
* Evaluate the runtime environment, discarding any
* output because the purpose is only to set the initial
* state, not to produce extra output.
*/
rt = runtime;
Free(Eval(&rt, stack));
/*
* Evaluate any expressions specified on the command line,
* interpolating them in the output if they produce anything
* because that is the expected behavior.
*/
for (i = 0; i < ArraySize(eval); i++)
{
char *expr = ArrayGet(eval, i);
char *res = Eval(&expr, stack);
StreamPuts(out, res);
Free(res);
}
Process(in, out, stack);
for (i = 0; i < ArraySize(stack); i++)
{
HashMap *stackFrame = ArrayGet(stack, i);
char *key;
Definition *val;
while (HashMapIterate(stackFrame, &key, (void **) &val))
{
size_t j;
for (j = 0; j < ArraySize(val->args); j++)
{
Free(ArrayGet(val->args, j));
}
ArrayFree(val->args);
Free(val->body);
Free(val);
}
HashMapFree(stackFrame);
}
ArrayFree(stack);
ret = EXIT_SUCCESS;
finish:
for (i = 0; i < ArraySize(eval); i++)
{
Free(ArrayGet(eval, i));
}
ArrayFree(eval);
if (in != StreamStdin())
{
StreamClose(in);
}
if (out != StreamStdout())
{
StreamClose(out);
}
return ret;
}

View file

@ -1,119 +0,0 @@
#include <UInt64.h>
#include <Log.h>
/* AssertEquals(actual, expected) */
int
AssertEquals(char *msg, UInt64 x, UInt64 y)
{
if (!UInt64Eq(x, y))
{
Log(LOG_ERR, "%s: Expected 0x%X 0x%X, got 0x%X 0x%X", msg,
UInt64High(y), UInt64Low(y),
UInt64High(x), UInt64Low(x));
return 0;
}
return 1;
}
int
Main(void)
{
UInt64 x, y;
Log(LOG_INFO, "sizeof(UInt64) = %lu", sizeof(UInt64));
#ifdef UINT64_NATIVE
Log(LOG_INFO, "Using native 64-bit integers.");
#else
Log(LOG_INFO, "Using emulated 64-bit integers.");
#endif
/* BSR Tests */
x = UInt64Create(0x000000FF, 0x00000000);
y = UInt64Srl(x, 4);
AssertEquals("x >> 4", y, UInt64Create(0x0000000F, 0xF0000000));
y = UInt64Srl(x, 8);
AssertEquals("x >> 8", y, UInt64Create(0x00000000, 0xFF000000));
y = UInt64Srl(x, 36);
AssertEquals("x >> 36", y, UInt64Create(0x00000000, 0x0000000F));
/* BSL Tests */
x = UInt64Create(0x00000000, 0xFF000000);
y = UInt64Sll(x, 4);
AssertEquals("x << 4", y, UInt64Create(0x0000000F, 0xF0000000));
y = UInt64Sll(x, 8);
AssertEquals("x << 8", y, UInt64Create(0x000000FF, 0x00000000));
y = UInt64Sll(x, 36);
AssertEquals("x << 36", y, UInt64Create(0xF0000000, 0x00000000));
/* ADD Tests */
x = UInt64Create(0x00000000, 0xF0000001);
y = UInt64Create(0x00000000, 0x00000002);
AssertEquals("0xF0000001 + 0x00000002", UInt64Add(x, y), UInt64Create(0x00000000, 0xF0000003));
x = UInt64Create(0x00000000, 0xF0000000);
y = UInt64Create(0x00000000, 0x10000000);
AssertEquals("0xF0000000 + 0x10000000", UInt64Add(x, y), UInt64Create(0x00000001, 0x00000000));
/* SUB Tests */
x = UInt64Create(0x00000000, 0x00000005);
y = UInt64Create(0x00000000, 0x00000002);
AssertEquals("0x00000005 - 0x00000002", UInt64Sub(x, y), UInt64Create(0x00000000, 0x00000003));
x = UInt64Create(0x00000001, 0x00000000);
y = UInt64Create(0x00000000, 0x00000001);
AssertEquals("0x00000001 0x00000000 - 0x00000001", UInt64Sub(x, y), UInt64Create(0x00000000, 0xFFFFFFFF));
/* MUL Tests */
x = UInt64Create(0, 18);
y = UInt64Create(0, 1);
AssertEquals("18 * 1", UInt64Mul(x, y), UInt64Create(0, 18));
x = UInt64Create(0, 20);
y = UInt64Create(0, 12);
AssertEquals("20 * 12", UInt64Mul(x, y), UInt64Create(0, 240));
x = UInt64Create(0x00000000, 0x00000005);
y = UInt64Create(0x00000000, 0x00000005);
AssertEquals("0x00000005 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000000, 0x00000019));
x = UInt64Create(0x00000001, 0x00000000);
y = UInt64Create(0x00000000, 0x00000005);
AssertEquals("0x00000001 0x00000000 * 0x00000005", UInt64Mul(x, y), UInt64Create(0x00000005, 0x00000000));
/* DIV Tests */
x = UInt64Create(0, 12);
y = UInt64Create(0, 4);
AssertEquals("12 / 4", UInt64Div(x, y), UInt64Create(0, 3));
/* MOD Tests */
x = UInt64Create(0x000000FF, 0x00000000);
y = UInt64Create(0x00000000, 0x00000010);
AssertEquals("0x000000FF 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0));
x = UInt64Create(0x00000000, 0xFF000000);
y = UInt64Create(0x00000000, 0x00000010);
AssertEquals("0x00000000 0xFF000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0));
x = UInt64Create(0xFF000000, 0x00000000);
y = UInt64Create(0x00000000, 0x00000010);
AssertEquals("0xFF000000 0x00000000 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0));
x = UInt64Create(0x00000000, 0x000000F0);
y = UInt64Create(0x00000000, 0x00000010);
AssertEquals("0x00000000 0x000000F0 mod 0x00000010", UInt64Rem(x, y), UInt64Create(0, 0));
return 0;
}

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> * 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 * Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files * obtaining a copy of this software and associated documentation files

View file

@ -1,7 +1,6 @@
<p align="center"><img src="https://telodendria.io/assets/Telodendria-500x500.png"></p> <h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
<h1 align="center">Telodendria</h1>
Telodendria is an extremely powerful, yet lightweight and portable **Telodendria** is an extremely powerful, yet lightweight and portable
chat server designed to be easy to install and configure. Powered by chat server designed to be easy to install and configure. Powered by
the [Matrix](https://matrix.org) protocol, Telodendria empowers the [Matrix](https://matrix.org) protocol, Telodendria empowers
everyone to run their own chat server on ordinary hardware, including everyone to run their own chat server on ordinary hardware, including
@ -12,7 +11,7 @@ hosting a complicated, high-maintenance homeserver or joining an
existing homeserver for privacy or other reasons, then Telodendria existing homeserver for privacy or other reasons, then Telodendria
might be for you. might be for you.
> **Note:** Telodendria still in development. See [Status](#status). !!!! **Note:** Telodendria still in development. See **Status** below.
## What is Matrix? ## What is Matrix?
@ -64,12 +63,13 @@ incredibly outdated. Telodendria, on the other hand, aims to be stable.
It should *just work* for long periods of time between upgrades, and It should *just work* for long periods of time between upgrades, and
you should never feel like Telodendria is going to change significantly you should never feel like Telodendria is going to change significantly
between upgrades. 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.
[Read Technical Rationale &rightarrow;](docs/dev/rationale.md) [Read Technical Rationale &rightarrow;](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
## Get Started ## Get Started
Check out the [Documentation](docs/README.md) to get started with Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
Telodendria. Telodendria.
## Status ## Status
@ -79,13 +79,13 @@ not yet deliver on all of its promises. Currently, Telodendria is not
ready for end-users yet. While it features very basic user ready for end-users yet. While it features very basic user
authentication, it does not actually work as a chat server yet. authentication, it does not actually work as a chat server yet.
We are hoping to ship Telodendria `v0.4.0` by May of 2024. This We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
release should be usable for communication between **local users** release should be usable for communication between **local users**
only. Additional features, including federation with other Matrix only. Additional features, including federation with other Matrix
homeservers will be added in future releases. homeservers will be added in future releases.
You can help speed up development by [sponsoring](#sponsorship) You can help speed up development by **sponsoring**
Telodendria or [getting involved](docs/CONTRIBUTING.md). Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
## Sponsorship ## Sponsorship
@ -96,7 +96,7 @@ Telodendria's long-term success, please consider sponsoring the
project. project.
You can make a recurring donation to Telodendria using You can make a recurring donation to Telodendria using
[LiberaPay](https://bancino.net/Telodendria/donate). You can also make [LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
one-time donations using one-time donations using
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would [Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
like to make a recurring donation larger than that allowed by like to make a recurring donation larger than that allowed by
@ -108,7 +108,7 @@ LiberaPay, please contact Jordan Bancino over Matrix at
While there are no set sponsorship tiers at this time, sponsoring While there are no set sponsorship tiers at this time, sponsoring
Telodendria is a mutually beneficial relationship. Depending on the Telodendria is a mutually beneficial relationship. Depending on the
amount you donate, you can get your name, logo, and website links amount you donate, you can get your name, logo, and website links
on the [Sponsors](docs/SPONSORS.md) page, the project `README`, or the on the [Sponsors](../sponsors) page, the project `README`, or the
main website. main website.
## License ## License
@ -116,7 +116,7 @@ main website.
All of the code and documentation for Telodendria is licensed under a All of the code and documentation for Telodendria is licensed under a
modified MIT license. The MIT license is an extremely permissive modified MIT license. The MIT license is an extremely permissive
license that has very few restrictions. Please consult the license that has very few restrictions. Please consult the
[`LICENSE.txt`](LICENSE.txt) file for the actual license text. It is [`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 important to note that the Telodendria license text differs from the
original MIT license in the following ways: original MIT license in the following ways:
@ -133,4 +133,3 @@ 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 official project. If Telodendria is forked, the logo must be removed
completely from the project, and optionally replaced by a different completely from the project, and optionally replaced by a different
one. one.

88
Schema/Config.json Normal file
View file

@ -0,0 +1,88 @@
{
"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"
}
}
}

View file

@ -1,4 +1,5 @@
{ {
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
"header": "Schema\/Filter.h", "header": "Schema\/Filter.h",
"types": { "types": {
"FilterRoom": { "FilterRoom": {
@ -116,6 +117,5 @@
}, },
"type": "struct" "type": "struct"
} }
}, }
"guard": "TELODENDRIA_SCHEMA_FILTER_H"
} }

38
Schema/LoginRequest.json Normal file
View file

@ -0,0 +1,38 @@
{
"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"
}

49
Schema/RegToken.json Normal file
View file

@ -0,0 +1,49 @@
{
"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"
}
}
}

17
Schema/Registration.json Normal file
View file

@ -0,0 +1,17 @@
{
"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"
}

21
Schema/RequestToken.json Normal file
View file

@ -0,0 +1,21 @@
{
"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"
}

View file

@ -0,0 +1,13 @@
{
"header": "Schema\/UserDirectoryRequest.h",
"types": {
"UserDirectoryRequest": {
"fields": {
"search_term": { "type": "string" },
"limit": { "type": "integer" }
},
"type": "struct"
}
},
"guard": "TELODENDRIA_SCHEMA_USERDIRECTORYREQUEST_H"
}

321
configure vendored Executable file
View file

@ -0,0 +1,321 @@
#!/usr/bin/env sh
#
# Argument Parsing
#
echo "Build Configuration"
echo "-------------------"
BUILD="build"
OUT="out"
SRC="src"
INCLUDE="src/include"
TOOLS="tools/src"
SCHEMA="Schema"
CYTOPLASM="Cytoplasm"
CFLAGS="-O1 -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
LIBS="-lm -pthread -lCytoplasm"
# 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 compiler depending on the platform.
case "$(uname)" in
Linux|NetBSD)
# These systems typically use GCC.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
;;
OpenBSD|FreeBSD|Darwin)
# These systems typically use Clang.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
;;
*)
# Use default compiler which is required to be present on
# all POSIX-compliant systems.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=c99"
;;
esac
# Append any additional args specified by user
SCRIPT_ARGS="${SCRIPT_ARGS} $@"
echo "Processing options..."
echo "Ran with arguments: $SCRIPT_ARGS"
# Process all arguments
for arg in $SCRIPT_ARGS; do
case "$arg" in
--cc=*)
CC=$(echo "$arg" | cut -d '=' -f 2-)
case "${CC}" in
gcc*|clang*)
# "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"
;;
esac
;;
--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-)
;;
--enable-debug)
DEBUG="-O0 -g"
;;
--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
;;
esac
done
CFLAGS="${CFLAGS} '-DTELODENDRIA_VERSION=\"${VERSION}\"' ${DEBUG}"
LDFLAGS="${LDFLAGS} ${LIBS}"
#
# Makefile generation
#
collect() {
from="$1"
orig_ext="$2"
new_ext="$3"
prefix="$4"
exec="$5"
find "${from}" -name "*${orig_ext}" -type f | while IFS= read -r src; do
src=$(echo "$src" | sed -e "s|^${from}||g")
obj=$(echo "$src" | sed -e "s|${orig_ext}\$|${new_ext}|g")
obj="${prefix}${obj}"
src="${from}${src}"
"${exec}" "${src}" "${obj}"
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"
}
print_obj() {
printf '%s ' "$2"
}
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}/" \
| while IFS= read -r dep; do
printf "%s " "$dep"
done
}
compile_obj() {
src="$1"
obj="$2"
pref="${obj}: $(get_deps ${src})"
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
echo "${TAB}@mkdir -p $(dirname ${obj})"
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
}
compile_bin() {
src="$1"
out="$2"
depObjs=$(prefix ${BUILD}/ CanonicalJson.o Telodendria.o)
echo "${out}: ${src}"
echo "${TAB}@mkdir -p ${OUT}/bin"
echo "${TAB}\$(CC) \$(CFLAGS) -o \"${out}\" \"${src}\" $depObjs \$(LDFLAGS)"
}
compile_doc() {
src="$1"
out="$2"
if echo "${src}" | grep "Schema" > /dev/null; then
return
fi
echo "${out}: ${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\""
}
install_out() {
src="$1"
out="$2"
dir=$(dirname "$out")
echo "${TAB}mkdir -p \"$dir\""
echo "${TAB}cp \"$src\" \"$out\""
}
install_man() {
src="${OUT}/man/man3/${BIN_NAME}-$(basename $1 .h).3"
out="$2"
dir=$(dirname "$out")
echo "${TAB}mkdir -p \"$dir\""
echo "${TAB}cp \"$src\" \"$out\""
}
uninstall_out() {
src="$1"
out="$2"
echo "${TAB}rm \"$out\""
}
echo "Generating Makefile..."
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
TAB=$(printf '\t')
cat << EOF > Makefile
.POSIX:
# Generated by '$0' on $(date).
# This file should generally not be manually edited.
CC = ${CC}
PREFIX = ${PREFIX}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS}
all: ${BIN_NAME} docs tools
docs: $(collect ${INCLUDE}/ .h .3 ${OUT}/man/man3/${BIN_NAME}- print_doc)
tools: $(collect ${TOOLS}/ .c '' ${OUT}/bin/ print_obj)
format:
${TAB}find . -name '*.c' | while IFS= read -r src; do \\
${TAB} if indent "\$\$src"; then \\
${TAB} rm \$\$(basename "\$\$src").BAK; \\
${TAB} fi \\
${TAB}done
license:
${TAB}find . -name '*.[ch]' | while IFS= read -r src; do \\
${TAB} srcHeader=\$\$(grep -n -m 1 '^ \*/' "\$\$src" | cut -d ':' -f 1); \\
${TAB} head -n\$\$srcHeader \$\$src | \\
${TAB} diff -u -p - "LICENSE.txt" | \\
${TAB} patch "\$\$src" | grep -v "^Hmm"; \\
${TAB}done
${BIN_NAME}: ${OUT}/bin/${BIN_NAME}
install: ${BIN_NAME}
${TAB}mkdir -p \$(PREFIX)/bin
${TAB}cp ${OUT}/bin/${BIN_NAME} \$(PREFIX)/bin/${BIN_NAME}
uninstall:
${TAB}rm \$(PREFIX)/bin/${BIN_NAME}
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)
$(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
)
EOF
echo "Done. Run 'make' to build ${BIN_NAME}."

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> * 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 * Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files * obtaining a copy of this software and associated documentation files

View file

@ -5,12 +5,22 @@ It is intended to be updated with every commit that makes a user-facing change w
reporting in the change log. As such, it changes frequently between releases. Final reporting in the change log. As such, it changes frequently between releases. Final
change log entries are published as [Releases](releases). change log entries are published as [Releases](releases).
## v0.4.0 ## v1.7.0-alpha4
**Not Released Yet.** **Not Released Yet.**
This release brings filters, rooms, and events! The core of the Matrix protocol architecture This release brings filters, rooms, and events! The core of the Matrix
is not in place. 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&mdash;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 ### Matrix Specification
@ -19,6 +29,42 @@ The following endpoints were added:
- **POST** `/_matrix/client/v3/user/{userId}/filter` - **POST** `/_matrix/client/v3/user/{userId}/filter`
- **GET** `/_matrix/client/v3/user/{userId}/filter/{filterId}` - **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 ## v0.3.0
**Saturday, June 10, 2023** **Saturday, June 10, 2023**

View file

@ -64,6 +64,9 @@ them do&mdash;pick your favorite, and if you find it doesn't work,
open an issue!). open an issue!).
- `make` for building the project. - `make` for building the project.
- `git` for managing your changes. - `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: Optionally, you may also find these tools helpful:
@ -81,8 +84,8 @@ request to ask us to pull your changes into our repo.
1. Fork this repository. 1. Fork this repository.
1. In your development environment, clone your fork: 1. In your development environment, clone your fork:
```shell ```shell
git clone https://git.telodendria.io/[YOUR_USERNAME]/telodendria.git git clone https://git.telodendria.io/[YOUR_USERNAME]/Telodendria.git
cd telodendria cd Telodendria
``` ```
Please base your changes on the `master` branch. If you need help Please base your changes on the `master` branch. If you need help
@ -91,12 +94,61 @@ request to ask us to pull your changes into our repo.
### Building &amp; Running ### Building &amp; Running
Telodendria uses the `make` build system. 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.
**TODO:** Update this section before #19 is closed. Provide quick Please follow the build and installation directions for
make, run, and install directions (maybe just redirect to Porting for [Cytoplasm](/Telodendria/Cytoplasm) first before attempting to build
install directions), then list all the `make` recipes. Shouldn't be Telodendria, because Telodendria depends on Cytoplasm and assumes it is
as many as were in `td`. 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 ### Pull Requests
@ -161,3 +213,44 @@ comments to the appropriate header.
If your pull request does not also include proper documentation, it If your pull request does not also include proper documentation, it
will likely be rejected. 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.

94
docs/dev/hosting.md Normal file
View file

@ -0,0 +1,94 @@
# 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.

View file

@ -30,12 +30,7 @@ for general build instructions.
To package Telodendria, you should collect the following files, and To package Telodendria, you should collect the following files, and
figure out where they should be installed on your system: figure out where they should be installed on your system:
- The `telodendria` server binary itself, located at - The `telodendria` server binary itself.
`build/telodendria`.
- All manual pages in the `man/` directory. These should be prefixed
with `telodendria-`. 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.
- An init script. People that wish to install Telodendria on their - An init script. People that wish to install Telodendria on their
system using your package are going to expect it to be integrated system using your package are going to expect it to be integrated
enough that Telodendria can easily be started at boot and otherwise enough that Telodendria can easily be started at boot and otherwise
@ -43,6 +38,9 @@ managed by the system's daemon tools, be it `systemd` or another
init system. Consult your system's documentation for writing an init init system. Consult your system's documentation for writing an init
script. **Note:** Telodendria *does not* fork itself to the background; script. **Note:** Telodendria *does not* fork itself to the background;
the init script should do that. 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 You may wish to optionally create a dedicated user under which
Telodendria should run. Telodendria can be directly started as that Telodendria should run. Telodendria can be directly started as that
@ -51,7 +49,7 @@ user. Additionally, it might be helpful to provide a default
configuration, which can be placed in the samples directory on your configuration, which can be placed in the samples directory on your
platform, or in a default location that Telodendria will load from. platform, or in a default location that Telodendria will load from.
A good default directory that you may wish to provide for configuration, A good default directory that you may wish to provide for configuration,
data, and logs could perhaps be `/var/telodendria` on Unix-like systems. 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 Once you have collected the necessary files and directories that need
to be installed, make sure your package performs the following tasks to be installed, make sure your package performs the following tasks

View file

@ -39,12 +39,71 @@ Telodendria that Conduit lacks.
### Small Dependency Chain ### Small Dependency Chain
**TODO:** See #30. 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 ### Standardized
**TODO:** See #30. 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&mdash;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&mdash;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 ### Portable
**TODO:** See #30. 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.

Some files were not shown because too many files have changed in this diff Show more