Compare commits
226 Commits
more_to_re
...
master
Author | SHA1 | Date |
---|---|---|
Philippe Pittoli | 83d5374586 | |
Philippe Pittoli | 9d875062f6 | |
Karchnu | b75e7dbbdc | |
Philippe Pittoli | d26d6de759 | |
Philippe Pittoli | 7ab2fccfd6 | |
Philippe Pittoli | 0b96aaafeb | |
Philippe Pittoli | 136b294aac | |
Philippe Pittoli | 6a28fa8ac7 | |
Philippe Pittoli | 7d96ec3c28 | |
Philippe Pittoli | 7ef4c43d60 | |
Philippe Pittoli | 49e0430fb5 | |
Philippe Pittoli | 1479d4e243 | |
Philippe Pittoli | f0e41455be | |
Philippe Pittoli | 81cf462e10 | |
Philippe Pittoli | c394c854f4 | |
Philippe Pittoli | 9d69afe3ae | |
Philippe Pittoli | a6e1609fe6 | |
Philippe Pittoli | 48a727bf19 | |
Philippe Pittoli | 94dfefe198 | |
Philippe Pittoli | 4ed4981e42 | |
Philippe Pittoli | 0c8fc284d3 | |
Philippe Pittoli | 745b8c12ca | |
Philippe Pittoli | 007d329f83 | |
Philippe Pittoli | 6564f934e1 | |
Philippe Pittoli | 9fabcbdc3c | |
Philippe Pittoli | 247134b178 | |
Philippe Pittoli | d72d85294a | |
Philippe Pittoli | 37b460d52d | |
Philippe Pittoli | 8c168fdbf8 | |
Philippe Pittoli | bdff3156f4 | |
Philippe Pittoli | 61186c9ea9 | |
Philippe Pittoli | bf4d5c803f | |
Philippe Pittoli | 2b99e18046 | |
Philippe Pittoli | 6a912202e4 | |
Philippe Pittoli | 1c850be4cf | |
Philippe Pittoli | ddc1d65bef | |
Philippe Pittoli | 08523fa90a | |
Philippe Pittoli | dda7514a61 | |
Philippe Pittoli | afd8bbec18 | |
Philippe Pittoli | db5cf0cbf0 | |
Philippe Pittoli | e849fdafda | |
Philippe Pittoli | 49698f11bd | |
Philippe Pittoli | f79938f441 | |
Philippe Pittoli | 7a1e66423f | |
Philippe Pittoli | 0ad272d10a | |
Philippe Pittoli | 2192f44ffc | |
Philippe Pittoli | bfdbacb981 | |
Philippe Pittoli | db561d99b5 | |
Philippe Pittoli | 79752068c6 | |
Philippe Pittoli | 4b0ca0a59d | |
Philippe Pittoli | 32fa98bc76 | |
Philippe Pittoli | d2ab260255 | |
Philippe Pittoli | 7904d5eef6 | |
Philippe Pittoli | af709a665c | |
Philippe Pittoli | 1269b55c05 | |
Philippe Pittoli | 23de66befc | |
Philippe Pittoli | 5ee0a6e6e0 | |
Philippe Pittoli | 0a836c56cd | |
Philippe Pittoli | 0356540e7b | |
Philippe Pittoli | a958666060 | |
Philippe Pittoli | ae99353c19 | |
Philippe Pittoli | 12123b0cb4 | |
Philippe Pittoli | b0cf9638ec | |
Philippe Pittoli | 51cad2c07d | |
Philippe Pittoli | 21ecf157d1 | |
Philippe Pittoli | 368513bca5 | |
Philippe Pittoli | 9696190e99 | |
Philippe Pittoli | e6ad0ce65c | |
Philippe Pittoli | 6fa8f31dd4 | |
Philippe Pittoli | eb99ba6cb2 | |
Philippe Pittoli | 3e3d996e7b | |
Philippe Pittoli | da27ce33dd | |
Philippe Pittoli | 0eb93c805d | |
Philippe Pittoli | ae62d300ff | |
Philippe Pittoli | 4651430ef8 | |
Philippe Pittoli | fd63139157 | |
Philippe Pittoli | 82f81a7fb7 | |
Philippe Pittoli | 06a01f2b4b | |
Philippe Pittoli | 132743442b | |
Philippe Pittoli | a0c446ca28 | |
Philippe Pittoli | b2bf66c436 | |
Philippe Pittoli | 0feb31b4c7 | |
Philippe Pittoli | cab07003ad | |
Philippe Pittoli | 273204af6d | |
Philippe Pittoli | b59ad86e41 | |
Philippe Pittoli | b111982a0f | |
Philippe Pittoli | 3123051ef0 | |
Philippe Pittoli | 9dc4cfa003 | |
Philippe Pittoli | 2befab21e2 | |
Philippe Pittoli | 9462224255 | |
Philippe Pittoli | 3695a8ec82 | |
Philippe Pittoli | 6601eb61b0 | |
Philippe Pittoli | f07b915124 | |
Philippe Pittoli | eafdc3749a | |
Philippe Pittoli | 66ddeb2207 | |
Philippe Pittoli | caf255d6c4 | |
Philippe Pittoli | 3514b4fe96 | |
Philippe Pittoli | 13a60d0158 | |
Philippe Pittoli | e80e99d47f | |
Philippe Pittoli | ed9cd24b22 | |
Philippe Pittoli | 178f205d44 | |
Philippe Pittoli | 5b73186353 | |
Philippe Pittoli | c8f34ef3c2 | |
Philippe Pittoli | 472dd1f1ab | |
Philippe Pittoli | 71aa496501 | |
Philippe Pittoli | 125a960816 | |
Philippe Pittoli | a8224c1cf9 | |
Philippe Pittoli | a6cfa88f79 | |
Philippe Pittoli | c5e3b7b901 | |
Philippe Pittoli | 8ea70b51ee | |
Philippe Pittoli | d585ffb8ee | |
Philippe Pittoli | 32fd31934c | |
Philippe Pittoli | aca3f2183d | |
Philippe Pittoli | 614e972b95 | |
Philippe Pittoli | f3c7695462 | |
Philippe Pittoli | bb9d397a40 | |
Philippe Pittoli | 1034b1aa5c | |
Philippe Pittoli | ba6671d902 | |
Philippe Pittoli | 28d7dd8fc2 | |
Philippe Pittoli | 58320cbb46 | |
Philippe Pittoli | 1e3c1b2625 | |
Philippe Pittoli | 871b2b249c | |
Philippe Pittoli | b73550bdcf | |
Philippe Pittoli | a0e9515600 | |
Philippe Pittoli | 5ad00d0675 | |
Philippe Pittoli | 45f3fa1860 | |
Philippe Pittoli | 506bd21d57 | |
Philippe Pittoli | 0cf2e5ef1f | |
Philippe Pittoli | 978676051a | |
Philippe Pittoli | ec787d7496 | |
Philippe Pittoli | fb12f65218 | |
Philippe Pittoli | 49089b910b | |
Philippe Pittoli | 2a714cd064 | |
Philippe Pittoli | d99a8d13e3 | |
Philippe Pittoli | 4bbd5fc686 | |
Philippe Pittoli | 49b1b3bab2 | |
Philippe Pittoli | 1a6c13c85d | |
Philippe Pittoli | ce28b72f3b | |
Philippe Pittoli | 9ea49087bd | |
Philippe Pittoli | d72aac1a50 | |
Philippe Pittoli | 1b19118701 | |
Philippe Pittoli | 54fc5aa9e0 | |
Philippe Pittoli | 6b8d659319 | |
Philippe Pittoli | 3097dca06a | |
Philippe Pittoli | fc0c28fb7e | |
Philippe Pittoli | 4421ee31c4 | |
Philippe Pittoli | bbf7e669ff | |
Philippe Pittoli | 78670e5b71 | |
Philippe Pittoli | b43fd57704 | |
Philippe Pittoli | 14509b8d28 | |
Philippe Pittoli | 4fac81b143 | |
Philippe Pittoli | b50d910906 | |
Philippe Pittoli | 8012cff4bf | |
Philippe Pittoli | 266f1daaad | |
Philippe Pittoli | 2ad505b305 | |
Philippe Pittoli | 79f9fdc3e2 | |
Philippe Pittoli | 8a347f9ece | |
Philippe Pittoli | c77ae35751 | |
Philippe Pittoli | 05a47b8473 | |
Philippe Pittoli | 4b0778e37d | |
Philippe Pittoli | de1d221881 | |
Philippe Pittoli | 98eede6814 | |
Karchnu | 1a161d1b14 | |
Philippe Pittoli | e7c1c8b96d | |
Philippe Pittoli | c7f48d21e4 | |
Philippe Pittoli | 9eea1dbc07 | |
Philippe Pittoli | 727de2988f | |
Philippe Pittoli | d52fbdf61d | |
Philippe Pittoli | 6819de1da5 | |
Philippe Pittoli | 9f214180a7 | |
Philippe Pittoli | 1f5ac951cb | |
Philippe Pittoli | bc0fe07990 | |
Philippe Pittoli | 1a83b3c824 | |
Philippe Pittoli | 168bea7e78 | |
Philippe Pittoli | 8b10612456 | |
Philippe Pittoli | ca0d6adbc6 | |
Philippe Pittoli | ceafe4c84f | |
Philippe Pittoli | 200219d2fe | |
Philippe Pittoli | 9d1fef34a6 | |
Philippe Pittoli | 9321158a22 | |
Philippe Pittoli | 72ad635874 | |
Philippe Pittoli | 91995657dd | |
Philippe Pittoli | 0eb5dc57f5 | |
Philippe Pittoli | 3b7203c58d | |
Philippe Pittoli | 6038a277f3 | |
Philippe Pittoli | 0e2043c5e6 | |
Philippe Pittoli | a39ce64b7b | |
Philippe Pittoli | 2bb06db137 | |
Philippe Pittoli | 51e10d7f1e | |
Philippe Pittoli | 29d18e8ca1 | |
Philippe Pittoli | 03b1222ff0 | |
Philippe Pittoli | 1c26a69acd | |
Philippe Pittoli | 0fddc05576 | |
Philippe Pittoli | 1c8be3390e | |
Philippe Pittoli | 7204ade9e3 | |
Philippe Pittoli | 444078fcc6 | |
Philippe Pittoli | 382dc06f85 | |
Philippe Pittoli | 382dcc07d7 | |
Philippe Pittoli | a0dbd66fd2 | |
Philippe Pittoli | 9011578d8b | |
Philippe Pittoli | 1762f50100 | |
Philippe Pittoli | 69732ccad8 | |
Philippe Pittoli | 87f6f9071b | |
Philippe Pittoli | c14148ef35 | |
Philippe Pittoli | 9d16d6f2b8 | |
Philippe Pittoli | fc4899a26f | |
Philippe Pittoli | 13e7619899 | |
Philippe Pittoli | 33f7c9ccfb | |
Philippe Pittoli | e6edfd0e43 | |
Karchnu | 513348652e | |
Karchnu | bf600e0889 | |
Karchnu | f9b9000a3f | |
Karchnu | 1439bb78ca | |
Karchnu | c8757d0fb0 | |
Karchnu | 7cb5d2669c | |
Karchnu | 7caa934753 | |
Karchnu | 62db8ff7fd | |
Karchnu | d32e26b848 | |
Karchnu | a99d5317b0 | |
Karchnu | 825e0c1b2c | |
Karchnu | c69ce64273 | |
Karchnu | 7659766fc0 | |
Karchnu | b4cc1814cd | |
Karchnu | ec33e6086e | |
Karchnu | 556652418a | |
Karchnu | 7eeda65cd9 |
|
@ -6,3 +6,4 @@
|
|||
*.bin
|
||||
*.dSYM
|
||||
drop/
|
||||
pres/
|
||||
|
|
24
Makefile
24
Makefile
|
@ -1,5 +1,5 @@
|
|||
PACKAGE = 'libipc'
|
||||
VERSION = '0.6.0'
|
||||
VERSION = '0.7.2'
|
||||
|
||||
PREFIX := /usr/local
|
||||
BINDIR := $(PREFIX)/bin
|
||||
|
@ -50,25 +50,25 @@ libipc.so: src/communication.o src/context.o src/error.o src/fs.o src/message.o
|
|||
$(Q)$(CC) -o libipc.so -shared $(LDFLAGS) src/communication.o src/context.o src/error.o src/fs.o src/message.o src/network.o src/print.o src/service_path.o src/usocket.o src/utils.o
|
||||
|
||||
libipc.so.install: libipc.so
|
||||
@echo '[01;31m IN > [01;37m$(LIBDIR)/libipc.so.0.6.0[00m'
|
||||
@echo '[01;31m IN > [01;37m$(LIBDIR)/libipc.so.0.7.2[00m'
|
||||
$(Q)mkdir -p '$(DESTDIR)$(LIBDIR)'
|
||||
$(Q)install -m0755 libipc.so $(DESTDIR)$(LIBDIR)/libipc.so.0.6.0
|
||||
@echo '[01;35m LN > [01;37m$(LIBDIR)/libipc.so.0.6[00m'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so.0.6'
|
||||
$(Q)install -m0755 libipc.so $(DESTDIR)$(LIBDIR)/libipc.so.0.7.2
|
||||
@echo '[01;35m LN > [01;37m$(LIBDIR)/libipc.so.0.7[00m'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so.0.7'
|
||||
@echo '[01;35m LN > [01;37m$(LIBDIR)/libipc.so.0[00m'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
|
||||
@echo '[01;35m LN > [01;37m$(LIBDIR)/libipc.so[00m'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so'
|
||||
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so'
|
||||
|
||||
libipc.so.clean:
|
||||
@echo '[01;37m RM > [01;37mlibipc.so[00m'
|
||||
$(Q)rm -f libipc.so
|
||||
|
||||
libipc.so.uninstall:
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so.0.6.0[00m'
|
||||
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.6.0'
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so.0.6[00m'
|
||||
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.6'
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so.0.7.2[00m'
|
||||
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.7.2'
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so.0.7[00m'
|
||||
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.7'
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so.0[00m'
|
||||
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0'
|
||||
@echo '[01;37m RM > [01;37m$(LIBDIR)/libipc.so[00m'
|
||||
|
@ -327,7 +327,7 @@ $(PACKAGE)-$(VERSION).tar.bz2: distdir
|
|||
$(PACKAGE)-$(VERSION)/src/utils.h
|
||||
|
||||
help:
|
||||
@echo '[01;37m :: libipc-0.6.0[00m'
|
||||
@echo '[01;37m :: libipc-0.7.2[00m'
|
||||
@echo ''
|
||||
@echo '[01;37mGeneric targets:[00m'
|
||||
@echo '[00m - [01;32mhelp [37m Prints this help message.[00m'
|
||||
|
|
32
README.md
32
README.md
|
@ -1,22 +1,22 @@
|
|||
|
||||
# OBSOLETED BY
|
||||
|
||||
This project was obsoleted by the [new Zig implementation][zigimpl].
|
||||
Code is smaller, simpler and safer to use.
|
||||
Packet format and API are a bit simpler, too.
|
||||
|
||||
# libipc
|
||||
|
||||
libipc - Simple, easy-to-use IPC library
|
||||
|
||||
See the introductory [man page](man/libipc.7.md).
|
||||
See the introductory man page in `man/libipc.7`.
|
||||
|
||||
See the presentation in [docs/libipc.md](docs/libipc.md).
|
||||
|
||||
# Compilation
|
||||
|
||||
`make`
|
||||
|
||||
|
||||
# logging system
|
||||
|
||||
Logs are in one of the following directories: `$XDG_DATA_HOME/ipc/` or `$HOME/.local/share/ipc/`.
|
||||
The log file can be indicated with the `IPC_LOGFILE` environment variable, too.
|
||||
|
||||
To remove logs: `make LDFLAGS=-DIPC_WITHOUT_ERRORS`
|
||||
|
||||
# Since 0.7
|
||||
|
||||
- `libipc` have callbacks to use along with switching capabilities, making easier to implement proxies for different communication protocols
|
||||
|
@ -25,23 +25,19 @@ To remove logs: `make LDFLAGS=-DIPC_WITHOUT_ERRORS`
|
|||
|
||||
For performance improvements within `libipc`:
|
||||
|
||||
- `libipc` will be rewritten in Zig -- **DONE!**
|
||||
- `libipc` shouldn't use realloc for each event (new client, new message, etc.) but by batch of a few thousand elements
|
||||
- `libipc` should use better internal structures, unrequiring the use of loops (over the whole list of messages or connections) for each action
|
||||
|
||||
# Planning for 0.9
|
||||
|
||||
- `libipc` should use `libevent` for performance improvments
|
||||
- `libipc` should use `epoll/kqueue` for performance improvements
|
||||
* new functions will be added to the API
|
||||
* **but** we'll keep the same API for applications with no need for threading (way simpler implementation)
|
||||
- `libipc` should be thread-safe
|
||||
|
||||
# Planning for 1.0
|
||||
|
||||
- `libipc` *may* be written in Zig
|
||||
- `libipc` should have usable bindings in several languages
|
||||
|
||||
|
||||
# Implementation design
|
||||
|
||||
## Memory management
|
||||
|
||||
1. Prefer stack over mallocs.
|
||||
2. Basic functions (such as *usock_*) should not handle memory allocation.
|
||||
[zigimpl]: https://git.baguette.netlib.re/Baguette/libipc
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
const std = @import("std");
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
const args = try std.process.argsAlloc(std.heap.page_allocator);
|
||||
|
||||
if (args.len <= 1) {
|
||||
try print_input();
|
||||
}
|
||||
|
||||
for (args[1..]) |f| {
|
||||
if (std.mem.eql(u8, f, "-")) { try print_input(); }
|
||||
else { try print_file (f); }
|
||||
}
|
||||
}
|
||||
|
||||
fn print_input() !void {
|
||||
try print_all (std.io.getStdIn());
|
||||
}
|
||||
|
||||
fn print_file(dest: []const u8) !void {
|
||||
var file = try std.fs.cwd().openFile(dest, .{ .mode = .read_only });
|
||||
defer file.close();
|
||||
try print_all (file);
|
||||
}
|
||||
|
||||
fn print_all(reader: std.fs.File) !void {
|
||||
var buffer: [4096]u8 = undefined;
|
||||
while (true) {
|
||||
const nbytes = try reader.read(&buffer);
|
||||
try stdout.print("{s}", .{buffer[0..nbytes]});
|
||||
if (nbytes == 0) break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
catpoint
|
||||
pointtools
|
|
@ -0,0 +1,151 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Extract layers from a FIG file
|
||||
#
|
||||
# History
|
||||
# 2002/04/23 : pda : design
|
||||
# 2013/02/12 : pda : extension to intervals
|
||||
#
|
||||
|
||||
usage ()
|
||||
{
|
||||
echo "usage: $0 layer[-layer] ... < fig-file > fig-file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ $# = 0 ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
LAYERS="$*"
|
||||
|
||||
for i
|
||||
do
|
||||
if expr "$i" : "^[0-9][0-9]*-[0-9][0-9]*$" > /dev/null
|
||||
then
|
||||
MIN=`expr "$i" : "\([0-9]*\)-"`
|
||||
MAX=`expr "$i" : ".*-\([0-9]*\)"`
|
||||
if [ $MIN -gt $MAX ]
|
||||
then usage
|
||||
fi
|
||||
while [ $MIN -le $MAX ]
|
||||
do
|
||||
LAYERS="$LAYERS $MIN"
|
||||
MIN=`expr $MIN + 1`
|
||||
done
|
||||
elif expr "$i" : "^[0-9][0-9]*$" > /dev/null
|
||||
then
|
||||
LAYERS="$LAYERS $i"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
done
|
||||
|
||||
awk -v "layers_string=$LAYERS" -F" " '
|
||||
BEGIN {
|
||||
split (layers_string, layers, "[ \t]")
|
||||
}
|
||||
/^#FIG/ {
|
||||
version = $2
|
||||
if (version != 3.2)
|
||||
{
|
||||
print "Invalid FIG version ($0)" > "/dev/stderr"
|
||||
}
|
||||
print
|
||||
afficher = 1
|
||||
next
|
||||
}
|
||||
/^0 / {
|
||||
# color pseudo object
|
||||
afficher = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
/^1 / {
|
||||
# ellipse
|
||||
if (layerok($7))
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
}
|
||||
else afficher = 0
|
||||
next
|
||||
}
|
||||
/^2 / {
|
||||
# polyline
|
||||
if (layerok($7))
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
}
|
||||
else afficher = 0
|
||||
next
|
||||
}
|
||||
/^3 / {
|
||||
# spline
|
||||
if (layerok($7))
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
}
|
||||
else afficher = 0
|
||||
next
|
||||
}
|
||||
/^4 / {
|
||||
# text
|
||||
if (layerok($4))
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
}
|
||||
else afficher = 0
|
||||
next
|
||||
}
|
||||
/^5 / {
|
||||
# arc
|
||||
if (layerok($7))
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
}
|
||||
else afficher = 0
|
||||
next
|
||||
}
|
||||
/^6 / {
|
||||
# compound
|
||||
afficher = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
/^-6 / {
|
||||
# end of compound
|
||||
afficher = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
/^ / {
|
||||
# ligne de continuation
|
||||
if (afficher)
|
||||
{
|
||||
print
|
||||
}
|
||||
next
|
||||
}
|
||||
{
|
||||
afficher = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
function layerok (l, ok, n)
|
||||
{
|
||||
ok = 0
|
||||
for (n in layers)
|
||||
{
|
||||
if (l == layers [n])
|
||||
ok = 1
|
||||
}
|
||||
return ok
|
||||
}' -
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
for i in *.fig
|
||||
do
|
||||
# latin1 accents
|
||||
grep -E "[ôéèàîÉê]" $i 2>/dev/null 1>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo $i matches a latin1 accent
|
||||
sed -r -i "s/É/\\\311/g" $i
|
||||
sed -r -i "s/à/\\\340/g" $i
|
||||
sed -r -i "s/è/\\\350/g" $i
|
||||
sed -r -i "s/é/\\\351/g" $i
|
||||
sed -r -i "s/ê/\\\352/g" $i
|
||||
sed -r -i "s/î/\\\356/g" $i
|
||||
sed -r -i "s/ô/\\\364/g" $i
|
||||
# sed -r -i "s/°/\\\176/g" $i
|
||||
fi
|
||||
|
||||
PDF=$(echo ${i} | sed "s/fig$/pdf/")
|
||||
|
||||
if [ ! -f ${PDF} ] || [ $(stat -c "%X" ${PDF}) -lt $(stat -c "%X" ${i}) ]
|
||||
then
|
||||
|
||||
echo "fig2ps ${i}"
|
||||
fig2dev -L pdf ${i} > ${PDF}
|
||||
|
||||
echo "touch ${PDF}"
|
||||
touch ${PDF}
|
||||
|
||||
# echo "make and touch ${PDF}"
|
||||
# pdf2ps ${PDF}
|
||||
# touch ${PDF}
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,109 @@
|
|||
#FIG 3.2 Produced by xfig version 3.2.7a
|
||||
Landscape
|
||||
Center
|
||||
Metric
|
||||
A4
|
||||
100.00
|
||||
Single
|
||||
-2
|
||||
1200 2
|
||||
5 1 0 3 0 7 30 -1 -1 0.000 0 0 1 0 3646.767 3431.191 2160 4950 1530 3240 2340 1755
|
||||
1 1 2.00 90.00 150.00
|
||||
2 2 0 1 0 7 30 -1 -1 0.000 0 0 -1 0 0 5
|
||||
8955 1395 11115 1395 11115 1755 8955 1755 8955 1395
|
||||
2 1 0 3 0 7 30 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 2.00 75.00 150.00
|
||||
1 1 2.00 75.00 150.00
|
||||
4230 1530 8955 1530
|
||||
2 1 0 3 0 7 30 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 2.00 75.00 150.00
|
||||
1 1 2.00 75.00 150.00
|
||||
4230 1665 8955 3150
|
||||
2 1 0 3 0 7 15 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 2.00 75.00 150.00
|
||||
1 1 2.00 75.00 150.00
|
||||
9990 3420 9990 4950
|
||||
2 2 0 1 0 7 5 -1 -1 0.000 0 0 -1 0 0 5
|
||||
8955 4950 11115 4950 11115 5310 8955 5310 8955 4950
|
||||
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
|
||||
8955 3060 11115 3060 11115 3420 8955 3420 8955 3060
|
||||
2 2 0 1 0 7 5 -1 -1 0.000 0 0 -1 0 0 5
|
||||
2070 4950 4230 4950 4230 5310 2070 5310 2070 4950
|
||||
2 1 0 3 0 7 10 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
4230 5130 8955 5130
|
||||
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
|
||||
2070 3060 2880 3060 2880 3420 2070 3420 2070 3060
|
||||
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
|
||||
3375 3060 4185 3060 4185 3420 3375 3420 3375 3060
|
||||
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 1
|
||||
6435 2295
|
||||
2 2 0 1 0 7 30 -1 -1 0.000 0 0 -1 0 0 5
|
||||
2070 1395 4230 1395 4230 1755 2070 1755 2070 1395
|
||||
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
9360 6345 10620 6345
|
||||
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
9225 4950 9225 3420
|
||||
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
4185 3105 8955 3105
|
||||
2 1 1 3 1 7 25 -1 -1 2.000 0 0 -1 1 1 2
|
||||
1 1 1.00 60.00 90.00
|
||||
1 1 1.00 60.00 90.00
|
||||
2880 3105 3375 3105
|
||||
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
2430 4950 2430 3420
|
||||
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
9360 6075 10620 6075
|
||||
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
3780 4950 3780 3420
|
||||
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
|
||||
1 1 3.00 90.00 150.00
|
||||
1 1 3.00 90.00 150.00
|
||||
4185 3375 8955 3375
|
||||
2 2 0 0 0 7 999 -1 -1 0.000 0 0 -1 0 0 5
|
||||
0 0 13365 0 13365 7830 0 7830 0 0
|
||||
4 1 0 30 -1 16 20 0.0000 4 240 5625 6705 540 LibIPC communications with a browser\001
|
||||
4 1 0 30 -1 16 16 0.0000 4 210 1620 3105 1665 Web Browser\001
|
||||
4 1 0 30 -1 16 16 0.0000 4 210 1545 3105 5220 JS or WASM\001
|
||||
4 1 0 30 -1 16 16 0.0000 4 210 1425 9990 1665 Web Server\001
|
||||
4 1 0 30 -1 16 16 5.9865 4 270 2460 6615 2295 dynamic interactions\001
|
||||
4 1 0 30 -1 16 14 5.9865 4 225 3465 6435 2700 (the webserver could be a proxy)\001
|
||||
4 1 0 30 -1 16 16 0.0000 4 210 1485 9990 3330 Websocketd\001
|
||||
4 1 0 30 -1 16 16 0.0000 4 210 1185 6705 1395 static files\001
|
||||
4 1 0 5 -1 16 16 0.0000 4 210 795 9990 5220 Server\001
|
||||
4 1 0 20 -1 16 20 0.0000 4 240 4560 6705 540 Remote libIPC communications\001
|
||||
4 1 0 10 -1 16 20 0.0000 4 240 4200 6705 540 Local libIPC communications\001
|
||||
4 0 0 15 -1 16 16 0.0000 4 210 2475 10170 4275 IPC communications\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 150 855 2115 5895 10 = local\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 150 1305 2115 6135 15 = all remote\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 135 1050 2115 6375 20 = remote\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 150 795 2115 6615 30 = web\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 150 645 2115 5670 05 = all\001
|
||||
4 1 0 10 -1 16 16 0.0000 4 255 1710 6570 5490 (Unix sockets)\001
|
||||
4 1 0 10 -1 16 16 0.0000 4 210 2595 6570 4950 Direct communication\001
|
||||
4 1 0 11 -1 16 16 0.0000 4 210 690 3060 5220 Client\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 150 1890 3375 5895 12 = local and remote\001
|
||||
4 1 0 20 -1 16 16 0.0000 4 210 690 3780 3330 TCPd\001
|
||||
4 1 0 20 -1 16 16 0.0000 4 210 600 2475 3330 IPCd\001
|
||||
4 0 0 50 -1 16 12 0.0000 4 195 2040 3375 6375 21 = IPCd explanations\001
|
||||
4 1 0 20 -1 16 16 0.0000 4 210 690 9990 3330 TCPd\001
|
||||
4 2 0 25 -1 16 10 0.0000 4 150 1395 2205 4140 tcp://remote/server\001
|
||||
4 2 0 25 -1 16 10 0.0000 4 120 675 9090 6390 Data flow\001
|
||||
4 2 0 25 -1 16 10 0.0000 4 120 1875 9090 6120 Connection establishment\001
|
||||
4 0 0 21 -1 16 14 0.0000 4 225 3825 4455 1620 contacts the right "protocol deamon"\001
|
||||
4 0 0 21 -1 16 14 0.0000 4 225 6690 4455 1935 after connection establishment, provides the socket to the client\001
|
||||
4 0 0 21 -1 16 14 0.0000 4 225 3600 4455 2250 therefore, never handles data flow\001
|
||||
4 2 18 21 -1 18 14 0.0000 4 180 630 4275 1620 IPCd:\001
|
|
@ -0,0 +1,380 @@
|
|||
## Before starting
|
||||
|
||||
This file is a presentation based on the point tools:
|
||||
https://git.baguette.netlib.re/Baguette/pointtools
|
||||
|
||||
To see it, type 'make'.
|
||||
|
||||
TODO: Explain the problem
|
||||
TODO: Explain the solution
|
||||
TODO: Explain why LibIPC
|
||||
TODO: Explain how LibIPC
|
||||
TODO: Explain other possible implementations
|
||||
TODO: Explain future of LibIPC
|
||||
TODO: Explain what can be done right now
|
||||
TODO: Explain what I actually do with it
|
||||
TODO: Explain LibIPC isn't a silver bullet
|
||||
|
||||
Have fun!
|
||||
|
||||
## Programming problems
|
||||
|
||||
Libraries
|
||||
* change often = hard to follow
|
||||
* don't often provide a high-level interface
|
||||
* each library is coded in its own way
|
||||
* availability vary depending on the language
|
||||
|
||||
Example: libraries to access databases
|
||||
* languages often have their own implementation
|
||||
* functions may vary from a language to another
|
||||
|
||||
## Infrastructure problems
|
||||
|
||||
Infrastructure
|
||||
* Always need to install all required libraries
|
||||
* No clear way to sandbox part of an application
|
||||
|
||||
## What solution?
|
||||
|
||||
MAKE APPLICATIONS, NOT LIBRARIES
|
||||
* apps talking to apps
|
||||
|
||||
Create an abstraction for libraries
|
||||
* languages, implementations, code modifications in general
|
||||
|
||||
Create an abstraction for network code
|
||||
* applications think communications are local
|
||||
* networking is performed by dedicated services
|
||||
* examples: TCPd, UDPd, TLSd, HTTPd...
|
||||
* apps are independant from protocols and formats
|
||||
(unless they are fundamentaly network-related)
|
||||
|
||||
## In practice
|
||||
|
||||
usage must be simple
|
||||
|
||||
1. init connection or service
|
||||
2. loop over events
|
||||
|
||||
#pause
|
||||
events are simple and high level
|
||||
|
||||
1. connection and disconnection
|
||||
2. message received and sent
|
||||
|
||||
#pause
|
||||
message have a simple format: length + value
|
||||
|
||||
#pause
|
||||
And that's it.
|
||||
## When
|
||||
|
||||
LibIPC is useful when the app:
|
||||
|
||||
- cannot be a simple shell command
|
||||
- needs a bidirectional communication
|
||||
- is an abstraction over a library
|
||||
|
||||
## Available libraries
|
||||
|
||||
* DBUS
|
||||
* libevent
|
||||
* even more complicated stuff
|
||||
* RPC-style, like Corba
|
||||
|
||||
#pause
|
||||
* ... or bare libc api
|
||||
* shared memory
|
||||
* pipes
|
||||
* sockets (unix, inet, inet6)
|
||||
|
||||
## DBUS
|
||||
|
||||
* not well suited for our needs
|
||||
(a polite way to say: what a bloody mess)
|
||||
|
||||
Is it designed *NOT* to be used?
|
||||
* over-engineered
|
||||
* complex
|
||||
* documentation isn't great
|
||||
* no code example
|
||||
|
||||
## DBUS (bonus page!)
|
||||
|
||||
DBUS feels obsolete: a big chunk of the documentation is
|
||||
about message format. Just use CBOR already!
|
||||
|
||||
#pause
|
||||
They even admit they did a poor job on the C part:
|
||||
> There is a low-level C binding, but that is probably too detailed
|
||||
> and cumbersome for anything but writing other bindings.
|
||||
|
||||
#pause
|
||||
Oh. And C++. YOU SHALL NOT PASS!
|
||||
|
||||
This is a Linux requirement nowadays, wth?
|
||||
|
||||
## libevent
|
||||
|
||||
* works with epoll and kqueue
|
||||
* great performances
|
||||
* works on Linux and *BSD
|
||||
|
||||
* a bit complicated
|
||||
|
||||
## Bare libc api
|
||||
|
||||
shared memory and semaphores
|
||||
|
||||
* (kinda) complicated api
|
||||
* not about exchanging messages
|
||||
|
||||
|
||||
pipes, sockets
|
||||
|
||||
* lack a conventional message format
|
||||
... but that's about it
|
||||
* Great to start with!
|
||||
|
||||
All have great performances to exchange data.
|
||||
|
||||
What is slow is the function to _wait_ for new events.
|
||||
|
||||
## LibIPC's choice
|
||||
|
||||
Unix sockets
|
||||
- fast, simple, reliable, bidirectional
|
||||
- remote connections will have their own service (ex: TCPd)
|
||||
|
||||
Dumbest possible message format
|
||||
- length + value
|
||||
- build your own format on top of it!
|
||||
|
||||
Wait on file descriptors with poll(2)
|
||||
- slow, but available everywhere
|
||||
- may upgrade to libevent
|
||||
|
||||
## LibIPC history (1/3)
|
||||
|
||||
1. based on pipes
|
||||
* because we gotta go fast!
|
||||
* ... but implementation was a bit of a mess
|
||||
|
||||
#pause
|
||||
2. rewrite to work with unix sockets
|
||||
* performances are excellent, no need for _absolute best_
|
||||
* way nicer implementation
|
||||
* select(2) for listening on file descriptors
|
||||
#pause
|
||||
* ... wait, does select(2) support more than 1024 connections?
|
||||
|
||||
## LibIPC history (2/3)
|
||||
|
||||
3. rewrite using poll(2)
|
||||
* many bugfixes later, way more tested than before
|
||||
* implementation was (kinda) production-ready
|
||||
* implementation was simple: < 2000 lines of C code
|
||||
|
||||
Still wasn't as simple as I wanted
|
||||
|
||||
## LibIPC history (3/3)
|
||||
|
||||
4. rewrite in Zig
|
||||
* still uses poll(2) (at least for now)
|
||||
* C-compatible bindings are available
|
||||
|
||||
## Why Zig? (1/2)
|
||||
|
||||
error management is built-in and mandatory
|
||||
|
||||
simpler to read and write
|
||||
* nicer data structures (contain functions)
|
||||
* less code redundancy (defer, more generic functions)
|
||||
* no more C's pitfalls
|
||||
* fully qualified names
|
||||
|
||||
## Why Zig? (2/2)
|
||||
|
||||
better standard library
|
||||
* usual structures: lists, hashtables
|
||||
* log system
|
||||
|
||||
memory management is simpler, more secure and more flexible
|
||||
|
||||
better at exposing bugs (better type system)
|
||||
|
||||
simpler to cross-compile: same standard library for all OSs
|
||||
|
||||
## Current implementation of libIPC
|
||||
|
||||
bindings available in Crystal
|
||||
* as well as fancy mappings: JSON and CBOR class serialization
|
||||
|
||||
#pause
|
||||
epoll (Linux) and kqueue (*BSD) were avoided
|
||||
* because callbacks hell => harder to read and to write code
|
||||
#pause
|
||||
* still a possibility for someday, not the priority right now
|
||||
|
||||
#pause
|
||||
LibIPC doesn't handle parallelism, yet
|
||||
|
||||
## How libIPC works (in Zig)
|
||||
|
||||
LibIPC has a high level API
|
||||
|
||||
var context = try Context.init(allocator);
|
||||
defer context.deinit();
|
||||
|
||||
#pause
|
||||
|
||||
var pong_fd = try context.connect_service ("pong");
|
||||
var message = try Message.init (pong_fd, allocator, "hello");
|
||||
try context.schedule (message);
|
||||
|
||||
## How libIPC works (in Zig)
|
||||
|
||||
var event = try context.wait_event();
|
||||
|
||||
switch (event.t) {
|
||||
...
|
||||
}
|
||||
|
||||
## How libIPC works (in Zig)
|
||||
|
||||
var event = try context.wait_event();
|
||||
|
||||
switch (event.t) {
|
||||
|
||||
.CONNECTION => {
|
||||
print ("New client!\n", .{});
|
||||
},
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
## How libIPC works (in Zig)
|
||||
|
||||
var event = try context.wait_event();
|
||||
|
||||
switch (event.t) {
|
||||
|
||||
.CONNECTION => {
|
||||
print ("New client!\n", .{});
|
||||
},
|
||||
|
||||
.MESSAGE_RX => {
|
||||
if (event.m) |m| {
|
||||
print ("a message has been received: {s}\n", .{m});
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
## How libIPC works (bindings)
|
||||
|
||||
1. init a connection (client) or create an unix socket (service)
|
||||
|
||||
ipc_connect_service (context, &fd, service_name, service_len)
|
||||
ipc_service_init (context, &fd, service_name, service_len)
|
||||
|
||||
#pause
|
||||
2. loop, wait for events
|
||||
listening to file descriptors (libIPC ones or not)
|
||||
|
||||
example:
|
||||
|
||||
while(1) {
|
||||
ipc_wait_event (context, &type, &index, &fd, buffer, &buffer_len)
|
||||
switch (type) {
|
||||
case IPC_CONNECTION : ...
|
||||
case IPC_DISCONNECTION : ...
|
||||
case IPC_MESSAGE: ...
|
||||
}
|
||||
}
|
||||
|
||||
## How libIPC works
|
||||
|
||||
3. send messages
|
||||
|
||||
```c
|
||||
ipc_schedule (context, fd, buffer, buffer_len)
|
||||
or
|
||||
ipc_write (context, fd, buffer, buffer_len)
|
||||
```
|
||||
|
||||
#pause
|
||||
4. add a file descriptor to listen to
|
||||
|
||||
ipc_add_external (context, fd)
|
||||
|
||||
## How libIPC works
|
||||
|
||||
LibIPC also helps to create "protocol daemons" like TCPd with
|
||||
automatic switching between file descriptors
|
||||
|
||||
LibIPC takes callbacks to obtain libipc payloads inside arbitrary message structure
|
||||
|
||||
Example: websocketd.
|
||||
Clients exchange data with a libipc service through websockets messages.
|
||||
|
||||
websocketd binds both the client and its service file descriptors,
|
||||
then provides the libipc a callback to extract libipc messages from
|
||||
the websocket messages sent by the client.
|
||||
|
||||
Same thing the other way.
|
||||
|
||||
|
||||
ipc_switching_callbacks (context, client_fd, cb_in, cb_out)
|
||||
|
||||
## libIPC internal structures (1/2)
|
||||
|
||||
Main goal: simplest possible structures
|
||||
|
||||
Examples (nothing hidden):
|
||||
|
||||
Message {
|
||||
fd: i32 => File descriptor concerned about this message.
|
||||
payload: []u8 => Actual payload.
|
||||
allocator: std.mem.Allocator => Memory management.
|
||||
};
|
||||
|
||||
Event {
|
||||
t: Event.Type => Example: connection, message tx, ...
|
||||
m: ?Message => Message, if there is one.
|
||||
index: usize => (Internal stuff).
|
||||
originfd: i32 => File descriptor related to the event.
|
||||
};
|
||||
|
||||
## libIPC internal structures (2/2)
|
||||
|
||||
Context structure is slightly more complicated, but _reasonable_.
|
||||
|
||||
Context {
|
||||
rundir: [] u8, // Where the UNIX sockets are.
|
||||
pollfd: PollFD, // File descriptors to manage.
|
||||
tx: Messages, // Messages to send, once their fd is available.
|
||||
...
|
||||
};
|
||||
|
||||
The rest is implementation details (and more advanced usage of LibIPC).
|
||||
|
||||
## Future of libIPC
|
||||
|
||||
## Why not use it?
|
||||
|
||||
Current limitations
|
||||
|
||||
* performances (libIPC is based on poll(2), not epoll nor kqueue)
|
||||
* it really isn't an issue until you have hundreds of clients
|
||||
* LibIPC could someday use libevent
|
||||
|
||||
* nothing in libIPC is thread-safe
|
||||
|
||||
These limitations are the price for a simple implementation.
|
||||
|
||||
## Questions?
|
||||
|
||||
Ask! `karchnu at karchnu.fr`
|
|
@ -0,0 +1,30 @@
|
|||
all: get-point-tools clean generate display
|
||||
|
||||
BAGUETTE=https://git.baguette.netlib.re/Baguette
|
||||
get-point-tools:
|
||||
@test -d catpoint || (git clone $(BAGUETTE)/catpoint.git && cd catpoint && make)
|
||||
@test -d pointtools || (git clone $(BAGUETTE)/pointtools.git && cd pointtools && make)
|
||||
|
||||
CATPOINT = $(PWD)/catpoint/catpoint
|
||||
MD2POINT = $(PWD)/pointtools/bin/md2point
|
||||
PRESENTATION = $(PWD)/libipc.md
|
||||
|
||||
BDIR=build
|
||||
clean:
|
||||
@-rm -r $(BDIR) 2>/dev/null || true
|
||||
|
||||
generate:
|
||||
@test -d $(BDIR) || mkdir $(BDIR)
|
||||
@cd $(BDIR) && (cat $(PRESENTATION) | $(MD2POINT))
|
||||
|
||||
display:
|
||||
@cd $(BDIR) && $(CATPOINT) *.txt
|
||||
|
||||
help:
|
||||
@echo "get-point-tools: get all relevant point tools (catpoint and md2point)"
|
||||
@echo
|
||||
@echo "generate: convert markdown into 'point' documents"
|
||||
@echo "display: run catpoint on all 'point' document"
|
||||
@echo "clean: remove all 'point' files"
|
||||
@echo
|
||||
@echo "By default: get point tools, generate then display the presentation"
|
|
@ -0,0 +1,21 @@
|
|||
all: allfigures
|
||||
|
||||
FIGS=$(shell ls figs/*.fig)
|
||||
FIGL=./figlayers
|
||||
|
||||
LIBIPC=figs/libipc
|
||||
|
||||
figlibipc: $(LIBIPC).fig
|
||||
echo "libipc"
|
||||
$(FIGL) 999 5 10-12 < $(LIBIPC).fig > $(LIBIPC)-1.fig # local
|
||||
$(FIGL) 999 5 11-12 15 20-25 < $(LIBIPC).fig > $(LIBIPC)-2.fig # remote
|
||||
$(FIGL) 999 5 15 30 < $(LIBIPC).fig > $(LIBIPC)-3.fig # web remote
|
||||
|
||||
allfigures: figlibipc
|
||||
echo "make ps"
|
||||
cd figs/ ; ./graph-this.sh
|
||||
# cd diag/ ; ./graph-this.sh
|
||||
|
||||
clean:
|
||||
echo "rm figs/*.ps"
|
||||
rm figs/*.ps
|
|
@ -24,7 +24,7 @@ int main (int argc, char *argv[])
|
|||
|
||||
T_PERROR_R (((fd = open (argv[1], O_CREAT | O_RDWR)) < 0), "cannot open the file", EXIT_FAILURE);
|
||||
|
||||
TEST_IPC_Q (usock_connect (&sock, "SOCKET_FD_EXCHANGE_TEST"), EXIT_FAILURE);
|
||||
TEST_IPC_Q (usock_connect (&sock, "./SOCKET_FD_EXCHANGE_TEST"), EXIT_FAILURE);
|
||||
TEST_IPC_Q (ipc_provide_fd (sock, fd), EXIT_FAILURE);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
|
|
@ -24,7 +24,7 @@ int main (int argc, char *argv[])
|
|||
int usockclient = 0;
|
||||
int fd = 0;
|
||||
|
||||
TEST_IPC_Q (usock_init (&usock, "SOCKET_FD_EXCHANGE_TEST"), EXIT_FAILURE);
|
||||
TEST_IPC_Q (usock_init (&usock, "./SOCKET_FD_EXCHANGE_TEST"), EXIT_FAILURE);
|
||||
TEST_IPC_Q (usock_accept (usock, &usockclient), EXIT_FAILURE);
|
||||
TEST_IPC_Q (ipc_receive_fd (usockclient, &fd), EXIT_FAILURE);
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ void non_interactive (int verbosity, size_t nb_msg, char *msg_str)
|
|||
for (size_t i = 0 ; i < nb_msg ; i++) {
|
||||
TEST_IPC_QUIT_ON_ERROR (ipc_message_format_data (&m, 42, msg_str, (ssize_t) strlen (msg_str) + 1), EXIT_FAILURE);
|
||||
TEST_IPC_QUIT_ON_ERROR (ipc_write_fd (ctx->pollfd[0].fd, &m), EXIT_FAILURE);
|
||||
if (verbosity > 1) {
|
||||
printf ("msg written (type: %u): %s\n", m.user_type, m.payload);
|
||||
}
|
||||
ipc_message_empty (&m);
|
||||
TEST_IPC_QUIT_ON_ERROR (ipc_read (ctx, 0 /* read from the only valid index */, &m), EXIT_FAILURE);
|
||||
|
||||
|
@ -110,16 +113,16 @@ void interactive ()
|
|||
fprintf (stderr, "%s", ret.error_message);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
#if 0
|
||||
#if 1
|
||||
printf ("\n");
|
||||
printf ("right before sending a message\n");
|
||||
#endif
|
||||
ret = ipc_write (ctx, m);
|
||||
ret = ipc_write_fd (ctx->pollfd[1].fd, m);
|
||||
if (ret.error_code != IPC_ERROR_NONE) {
|
||||
fprintf (stderr, "%s", ret.error_message);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
#if 0
|
||||
#if 1
|
||||
printf ("right after sending a message\n");
|
||||
#endif
|
||||
|
||||
|
@ -145,7 +148,7 @@ void interactive ()
|
|||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
printf("usage: %s [verbosity #messages message]", argv[0]);
|
||||
printf("usage: %s [verbosity #messages message]\n", argv[0]);
|
||||
|
||||
ctx = malloc (sizeof (struct ipc_ctx));
|
||||
memset (ctx, 0, sizeof (struct ipc_ctx));
|
||||
|
|
|
@ -136,6 +136,7 @@ void exit_program (int signal)
|
|||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
printf ("Usage: %s [verbosity]\n", argv[0]);
|
||||
if (argc > 1) {
|
||||
verbosity = atoi(argv[1]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
.\" Generated by scdoc 1.9.6
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "libipc" "7" "2020-12-08"
|
||||
.P
|
||||
.SH NAME
|
||||
.P
|
||||
libipc - Simple, easy-to-use IPC library
|
||||
.P
|
||||
.SH DESCRIPTION
|
||||
.P
|
||||
\fB\fRlibipc\fB\fR is a library that provides interprocess communication medium between applications.
|
||||
It provides both client and server code.
|
||||
.P
|
||||
.SH SYNOPSIS
|
||||
.P
|
||||
\fB\fR#include <ipc.h>\fB\fR
|
||||
.P
|
||||
.SS Initialization, exchanges, disconnection
|
||||
.P
|
||||
// server initialization
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_server_init\fB\fR (\fBchar\fR **env, \fBconst char\fR *sname);
|
||||
.P
|
||||
// connection establishement to a server
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_connection\fB\fR (\fBchar\fR **env, \fBconst char\fR *, int *serverfd);
|
||||
.P
|
||||
// closing a server
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_close\fB\fR (\fBstruct ipc_connection_info\fR *srv);
|
||||
.P
|
||||
// closing a connection
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_close\fB\fR (\fBstruct ipc_connection_info\fR *p);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_accept\fB\fR (\fBstruct ipc_connection_info\fR *srv, \fBstruct ipc_connection_info\fR *p);
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_read\fB\fR (\fBconst struct ipc_connection_info\fR *, \fBstruct ipc_message\fR *m);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_write\fB\fR (\fBconst struct ipc_connection_info\fR *, \fBconst struct ipc_message\fR *m);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_wait_event\fB\fR (\fBstruct ipc_ctx\fR *clients, \fBstruct ipc_connection_info\fR *srv, \fBstruct ipc_event\fR *event);
|
||||
.P
|
||||
.P
|
||||
// store and remove only pointers on allocated structures
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_add\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBstruct ipc_connection_info\fR *cinfo);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_del\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBstruct ipc_connection_info\fR *cinfo);
|
||||
.P
|
||||
// add an arbitrary file descriptor to read
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_add_fd\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBint\fR fd);
|
||||
.P
|
||||
.P
|
||||
.SS Message functions
|
||||
.P
|
||||
// create msg structure from buffer
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_format_read\fB\fR (\fBstruct ipc_message\fR *m, \fBconst char\fR *buf, \fBssize_t\fR msize);
|
||||
.P
|
||||
// create buffer from msg structure
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_format_write\fB\fR (\fBconst struct ipc_message\fR *m, \fBchar\fR **buf, \fBssize_t\fR *msize);
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_format\fB\fR (\fBstruct ipc_message\fR *m, \fBchar\fR type, \fBconst char\fR *payload, \fBssize_t\fR length);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_format_data\fB\fR (\fBstruct ipc_message\fR *m, \fBconst char\fR *payload, \fBssize_t\fR length);
|
||||
.br
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_format_server_close\fB\fR (\fBstruct ipc_message\fR *m);
|
||||
.P
|
||||
\fIenum ipc_errors\fR \fB\fRipc_message_empty\fB\fR (\fBstruct ipc_message\fR *m);
|
||||
.P
|
||||
.P
|
||||
.SH STRUCTURES
|
||||
.P
|
||||
.nf
|
||||
.RS 4
|
||||
struct ipc_connection_info {
|
||||
uint32_t version;
|
||||
uint32_t index;
|
||||
int32_t fd;
|
||||
char type; // may be an arbitrary fd
|
||||
char *spath; // max size: PATH_MAX, used to store unix socket path
|
||||
};
|
||||
|
||||
struct ipc_ctx {
|
||||
struct ipc_connection_info ** cinfos;
|
||||
int32_t size;
|
||||
};
|
||||
|
||||
struct ipc_message {
|
||||
char type;
|
||||
uint32_t length;
|
||||
char *payload;
|
||||
};
|
||||
|
||||
struct ipc_event {
|
||||
enum ipc_event_type type;
|
||||
void* origin; // currently used as an client or service pointer
|
||||
void* m; // message pointer
|
||||
};
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
.P
|
||||
.SH ENUMERATIONS
|
||||
.P
|
||||
.nf
|
||||
.RS 4
|
||||
enum msg_types {
|
||||
MSG_TYPE_SERVER_CLOSE = 0
|
||||
, MSG_TYPE_ERR
|
||||
, MSG_TYPE_DATA
|
||||
} message_types;
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
Function \fB\fRipc_wait_event\fB\fR returns an \fBevent type\fR structure.
|
||||
The event may be a (dis)connection, received data or an error.
|
||||
It also can be \fBIPC_EVENT_TYPE_EXTRA_SOCKET\fR since an arbitrary file descriptor can be added to the \fBipc_ctx\fR structure with \fB\fRipc_add_fd\fB\fR.
|
||||
.P
|
||||
.nf
|
||||
.RS 4
|
||||
enum ipc_event_type {
|
||||
IPC_EVENT_TYPE_NOT_SET
|
||||
, IPC_EVENT_TYPE_ERROR
|
||||
, IPC_EVENT_TYPE_EXTRA_SOCKET
|
||||
, IPC_EVENT_TYPE_CONNECTION
|
||||
, IPC_EVENT_TYPE_DISCONNECTION
|
||||
, IPC_EVENT_TYPE_MESSAGE
|
||||
};
|
||||
|
||||
enum ipc_errors {
|
||||
\&.\&.\&.
|
||||
};
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
.P
|
||||
.SH EXAMPLES
|
||||
.P
|
||||
Examples are available in the \fB/examples\fR directory.
|
||||
.P
|
||||
.SH NOTES
|
||||
.P
|
||||
.SH SEE ALSO
|
||||
.P
|
||||
.SH BUGS & LIMITATIONS
|
||||
.P
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.IP \(bu 4
|
||||
.\}
|
||||
Documentation is currently limited.
|
||||
.RE
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.IP \(bu 4
|
||||
.\}
|
||||
Tests are currently limited.
|
||||
.RE
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.IP \(bu 4
|
||||
.\}
|
||||
No code audit has been made.
|
||||
|
||||
.RE
|
||||
.P
|
|
@ -17,15 +17,15 @@ It provides both client and server code.
|
|||
|
||||
// server initialization
|
||||
|
||||
_enum ipc_errors_ **ipc_server_init** (*char* \*\*env , *struct ipc_connection_info* \*srv, *const char* \*sname);
|
||||
_enum ipc_errors_ **ipc_server_init** (*char* \*\*env, *const char* \*sname);
|
||||
|
||||
// connection establishement to a server
|
||||
|
||||
_enum ipc_errors_ **ipc_connection** (*char* \*\*env, *struct ipc_connection_info* \*, *const char* \*);
|
||||
_enum ipc_errors_ **ipc_connection** (*char* \*\*env, *const char* \*, int \*serverfd);
|
||||
|
||||
// closing a server
|
||||
|
||||
_enum ipc_errors_ **ipc_server_close** (*struct ipc_connection_info* \*srv);
|
||||
_enum ipc_errors_ **ipc_close** (*struct ipc_connection_info* \*srv);
|
||||
|
||||
// closing a connection
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
package=libipc # Package name.
|
||||
version=0.7.0 # Package version.
|
||||
version=0.7.2 # Package version.
|
||||
|
||||
# Our targets are the library and its documentation.
|
||||
targets=(libipc man/libipc.7)
|
||||
|
|
|
@ -58,9 +58,11 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
|
|||
T_R ((pfd == NULL), IPC_ERROR_CONTACT_IPCD__NO_FD_PARAM);
|
||||
T_R ((sname == NULL), IPC_ERROR_CONTACT_IPCD__NO_SERVICE_NAME_PARAM);
|
||||
|
||||
// In case there is a problem with ipcd.
|
||||
*pfd = 0;
|
||||
|
||||
char *ipcd_var = getenv ("IPC_NETWORK");
|
||||
if (ipcd_var == NULL) {
|
||||
*pfd = 0;
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
// TODO: is there another, more interesting way to do this?
|
||||
|
@ -73,7 +75,6 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
|
|||
memcpy (columnthensname + 1, sname, strlen (sname));
|
||||
|
||||
if (strncmp (ipcd_var, sname, strlen (sname)) != 0 && strstr (ipcd_var, columnthensname) == NULL) {
|
||||
*pfd = 0;
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
|
||||
|
@ -95,7 +96,24 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
|
|||
msg.length = strlen (content);
|
||||
msg.payload = content;
|
||||
|
||||
TEST_IPC_RR (ipc_write_fd (ipcd_fd, &msg), "cannot send a message to networkd");
|
||||
TEST_IPC_RR (ipc_write_fd (ipcd_fd, &msg), "cannot send a message to ipcd");
|
||||
|
||||
memset (&msg, 0, sizeof(struct ipc_message));
|
||||
|
||||
// ipcd successfully contacted the service or failed.
|
||||
// ipcd will tell either OK or NOT OK.
|
||||
TEST_IPC_RR (ipc_read_fd (ipcd_fd, &msg), "cannot read the ipcd response");
|
||||
|
||||
// In case ipcd failed.
|
||||
if (msg.length != 2) {
|
||||
printf ("ipcd failed to contact service: (%d bytes) %s\n"
|
||||
, msg.length
|
||||
, msg.payload);
|
||||
SECURE_DECLARATION(struct ipc_error, ret);
|
||||
ret.error_code = IPC_ERROR_CLOSED_RECIPIENT;
|
||||
usock_close (ipcd_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct ipc_error ret = ipc_receive_fd (ipcd_fd, pfd);
|
||||
if (ret.error_code == IPC_ERROR_NONE) {
|
||||
|
@ -116,7 +134,7 @@ struct ipc_error ipc_connection_ (struct ipc_ctx *ctx, const char *sname, enum i
|
|||
SECURE_DECLARATION(struct pollfd, pollfd);
|
||||
pollfd.events = POLLIN;
|
||||
|
||||
TEST_IPC_P (ipc_contact_ipcd (&pollfd.fd, sname), "error during networkd connection");
|
||||
TEST_IPC_P (ipc_contact_ipcd (&pollfd.fd, sname), "error during ipcd connection");
|
||||
|
||||
// if ipcd did not initiate the connection
|
||||
if (pollfd.fd <= 0) {
|
||||
|
@ -186,12 +204,30 @@ struct ipc_error ipc_close_all (struct ipc_ctx *ctx)
|
|||
T_R ((ctx == NULL), IPC_ERROR_CLOSE_ALL__NO_CTX_PARAM);
|
||||
|
||||
for (size_t i = 0 ; i < ctx->size ; i++) {
|
||||
TEST_IPC_P (ipc_close (ctx, i), "cannot close a connection in handle_message");
|
||||
TEST_IPC_P (ipc_close (ctx, i), "cannot close a connection in ipc_close_all");
|
||||
}
|
||||
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
|
||||
// Removing all messages for this fd.
|
||||
void ipc_remove_messages_for_fd (struct ipc_ctx *ctx, int fd)
|
||||
{
|
||||
struct ipc_message *m = NULL;
|
||||
size_t looping_count = ctx->tx.size;
|
||||
for (size_t i = 0; i < looping_count; i++) {
|
||||
m = &ctx->tx.messages[i];
|
||||
if (m->fd == fd) {
|
||||
// Freeing the message structure.
|
||||
ipc_message_empty (m);
|
||||
ipc_messages_del (&ctx->tx, i); // remove the message indexed by i
|
||||
// Let restart this round
|
||||
i--;
|
||||
looping_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index)
|
||||
{
|
||||
T_R ((ctx == NULL), IPC_ERROR_CLOSE__NO_CTX_PARAM);
|
||||
|
@ -205,6 +241,9 @@ struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index)
|
|||
ret = usock_close (fd);
|
||||
}
|
||||
|
||||
// Remove all messages for this fd.
|
||||
ipc_remove_messages_for_fd (ctx, fd);
|
||||
|
||||
// Verify that the close was OK.
|
||||
if (ret.error_code != IPC_ERROR_NONE) {
|
||||
return ret;
|
||||
|
@ -330,18 +369,7 @@ struct ipc_error ipc_del (struct ipc_ctx *ctx, uint32_t index)
|
|||
ctx->cinfos[index].spath = NULL;
|
||||
}
|
||||
|
||||
struct ipc_message *m = NULL;
|
||||
// Removing all messages for this fd.
|
||||
size_t looping_count = ctx->tx.size;
|
||||
for (size_t i = 0; i < looping_count; i++) {
|
||||
m = &ctx->tx.messages[i];
|
||||
if (m->fd == ctx->pollfd[index].fd) {
|
||||
ipc_messages_del (&ctx->tx, i); // remove the message indexed by i
|
||||
// Let restart this round
|
||||
i--;
|
||||
looping_count--;
|
||||
}
|
||||
}
|
||||
ipc_remove_messages_for_fd (ctx, ctx->pollfd[index].fd);
|
||||
|
||||
ctx->size--;
|
||||
|
||||
|
@ -416,7 +444,9 @@ struct ipc_error handle_writing_message (struct ipc_event *event, struct ipc_ctx
|
|||
m = &ctx->tx.messages[i];
|
||||
mfd = m->fd;
|
||||
if (txfd == mfd) {
|
||||
TEST_IPC_RR (ipc_write_fd (txfd, m), "cannot send a message to the client");
|
||||
// In case the writing is compromised, do not return right away,
|
||||
// just print the result.
|
||||
TEST_IPC_P(ipc_write_fd (txfd, m), "cannot send a message to the client");
|
||||
|
||||
// Freeing the message structure.
|
||||
ipc_message_empty (m);
|
||||
|
@ -444,14 +474,24 @@ struct ipc_error handle_new_message (struct ipc_event *event, struct ipc_ctx *ct
|
|||
ret = ipc_read (ctx, index, m);
|
||||
if (ret.error_code != IPC_ERROR_NONE && ret.error_code != IPC_ERROR_CLOSED_RECIPIENT) {
|
||||
struct ipc_error rvalue = ret; // store the final return value
|
||||
|
||||
ipc_message_empty (m);
|
||||
free (m);
|
||||
|
||||
int fd = ctx->pollfd[index].fd;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf ("error when ipc_read: index %d fd %d error num %d, message: %s\n"
|
||||
, index, fd
|
||||
, ret.error_code
|
||||
, ret.error_message);
|
||||
#endif
|
||||
|
||||
// if there is a problem, just remove the client
|
||||
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection in handle_message");
|
||||
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection in handle_message");
|
||||
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, index, ctx->pollfd[index].fd, NULL);
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, index, fd, NULL);
|
||||
return rvalue;
|
||||
}
|
||||
|
||||
|
@ -460,12 +500,12 @@ struct ipc_error handle_new_message (struct ipc_event *event, struct ipc_ctx *ct
|
|||
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_DISCONNECTION, index, ctx->pollfd[index].fd, NULL);
|
||||
|
||||
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection on closed recipient in handle_message");
|
||||
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection on closed recipient in handle_message");
|
||||
|
||||
ipc_message_empty (m);
|
||||
free (m);
|
||||
|
||||
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection on closed recipient in handle_message");
|
||||
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection on closed recipient in handle_message");
|
||||
|
||||
// warning: do not forget to free the ipc_client structure
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
|
@ -496,16 +536,28 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
|
|||
|
||||
IPC_EVENT_CLEAN (event);
|
||||
|
||||
int32_t n;
|
||||
// By default, everything is alright.
|
||||
SECURE_DECLARATION(struct ipc_error, final_return);
|
||||
final_return.error_code = IPC_ERROR_NONE;
|
||||
|
||||
int32_t n = 0;
|
||||
|
||||
for (size_t i = 0; i < ctx->size; i++) {
|
||||
// We assume that any fd in the list has to be listen to.
|
||||
#ifdef DEBUG
|
||||
printf ("reading fd: %d index %lu\n", ctx->pollfd[i].fd, i);
|
||||
#endif
|
||||
ctx->pollfd[i].events = POLLIN;
|
||||
}
|
||||
|
||||
// For each message to send…
|
||||
for (size_t i = 0; i < ctx->tx.size; i++) {
|
||||
// … verify that its destination is available for message exchange.
|
||||
for (size_t y = 0; y < ctx->size; y++) {
|
||||
if (ctx->pollfd[y].fd == ctx->tx.messages[i].fd) {
|
||||
#ifdef DEBUG
|
||||
printf ("writing fd: %d\n", ctx->pollfd[y].fd);
|
||||
#endif
|
||||
ctx->pollfd[y].events |= POLLOUT;
|
||||
}
|
||||
}
|
||||
|
@ -519,7 +571,18 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
|
|||
|
||||
gettimeofday(&tv_1, NULL);
|
||||
|
||||
if ((n = poll(ctx->pollfd, ctx->size, *timer)) < 0) {
|
||||
int timer_ = *timer;
|
||||
|
||||
/* In case there is a file descriptor that requires more to read. */
|
||||
for (size_t i = 0; i < ctx->size; i++) {
|
||||
if (ctx->cinfos[i].more_to_read == 1) {
|
||||
// printf ("There is more to read for _at least_ fd %d\n", ctx->pollfd[i].fd);
|
||||
timer_ = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((n = poll(ctx->pollfd, ctx->size, timer_)) < 0) {
|
||||
IPC_RETURN_ERROR (IPC_ERROR_WAIT_EVENT__POLL);
|
||||
}
|
||||
|
||||
|
@ -538,31 +601,44 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
|
|||
}
|
||||
|
||||
// Timeout.
|
||||
if (n == 0) {
|
||||
if (n == 0 && timer_ != 0) {
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_TIMER, 0, 0, NULL);
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i <= ctx->size; i++) {
|
||||
for (size_t i = 0; i < ctx->size; i++) {
|
||||
|
||||
// Whatever happens, we have the fd and the index in event.
|
||||
event->index = i;
|
||||
event->origin = ctx->pollfd[i].fd;
|
||||
|
||||
// Something to read or connection.
|
||||
if (ctx->pollfd[i].revents & POLLIN) {
|
||||
if (ctx->pollfd[i].revents & POLLIN || ctx->cinfos[i].more_to_read == 1) {
|
||||
|
||||
// Avoiding loops.
|
||||
ctx->cinfos[i].more_to_read = 0;
|
||||
|
||||
// In case there is something to read for the server socket: new client.
|
||||
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SERVER) {
|
||||
return ipc_accept_add (event, ctx, i);
|
||||
final_return = ipc_accept_add (event, ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
// fd is switched: using callbacks for IO operations.
|
||||
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SWITCHED) {
|
||||
return handle_switched_message (event, ctx, i);
|
||||
final_return = handle_switched_message (event, ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
// No treatment of the socket if external socket: the libipc user should handle IO operations.
|
||||
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_EXTERNAL) {
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_EXTRA_SOCKET, i, ctx->pollfd[i].fd, NULL);
|
||||
IPC_RETURN_NO_ERROR;
|
||||
// Default: return no error.
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
return handle_new_message (event, ctx, i);
|
||||
final_return = handle_new_message (event, ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
// Something can be sent.
|
||||
|
@ -571,20 +647,50 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
|
|||
|
||||
// fd is switched: using callbacks for IO operations.
|
||||
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SWITCHED) {
|
||||
return handle_writing_switched_message (event, ctx, i);
|
||||
final_return = handle_writing_switched_message (event, ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
return handle_writing_message (event, ctx, i);
|
||||
final_return = handle_writing_message (event, ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
// Disconnection.
|
||||
if (ctx->pollfd[i].revents & POLLHUP) {
|
||||
/** IPC_EVENT_SET: event, type, index, fd, message */
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_DISCONNECTION, i, ctx->pollfd[i].fd, NULL);
|
||||
return ipc_close (ctx, i);
|
||||
final_return = ipc_close (ctx, i);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
if (ctx->pollfd[i].revents & POLLERR) {
|
||||
#ifdef DEBUG
|
||||
printf ("pollerr: problem with fd %d\n", ctx->pollfd[i].fd);
|
||||
#endif
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, i, ctx->pollfd[i].fd, NULL);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
if (ctx->pollfd[i].revents & POLLNVAL) {
|
||||
#ifdef DEBUG
|
||||
printf ("pollnval: invalid fd %d\n", ctx->pollfd[i].fd);
|
||||
#endif
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, i, ctx->pollfd[i].fd, NULL);
|
||||
goto wait_event_exit;
|
||||
}
|
||||
|
||||
} /** for loop: end of the message handling */
|
||||
|
||||
IPC_RETURN_NO_ERROR;
|
||||
printf ("END OF THE LOOP WITHOUT GOTO!\n");
|
||||
|
||||
wait_event_exit:
|
||||
|
||||
/** TODO: tests on event, it has to be filled. */
|
||||
if (event->type == 0) {
|
||||
printf ("EVENT TYPE NOT FILLED! code: %d, error_message: %s\n"
|
||||
, final_return.error_code
|
||||
, final_return.error_message);
|
||||
}
|
||||
|
||||
return final_return;
|
||||
}
|
||||
|
|
20
src/ipc.h
20
src/ipc.h
|
@ -18,6 +18,7 @@
|
|||
#define RUNDIR "/run/ipc/"
|
||||
#define PATH_MAX 4096
|
||||
#define IPC_HEADER_SIZE 6
|
||||
// #define __IPC_BASE_SIZE 500000 // 500 KB
|
||||
#define __IPC_BASE_SIZE 2000000 // 2 MB, plenty enough space for messages
|
||||
#define IPC_MAX_MESSAGE_SIZE __IPC_BASE_SIZE-IPC_HEADER_SIZE
|
||||
|
||||
|
@ -41,7 +42,7 @@ enum msg_types {
|
|||
, MSG_TYPE_ERR = 1
|
||||
, MSG_TYPE_DATA = 2
|
||||
, MSG_TYPE_NETWORK_LOOKUP = 3
|
||||
} message_types;
|
||||
};
|
||||
|
||||
/**
|
||||
* Event types.
|
||||
|
@ -231,6 +232,7 @@ enum ipc_connection_type {
|
|||
|
||||
struct ipc_connection_info {
|
||||
enum ipc_connection_type type;
|
||||
short int more_to_read;
|
||||
char *spath; // max size: PATH_MAX
|
||||
};
|
||||
|
||||
|
@ -250,9 +252,9 @@ struct ipc_messages {
|
|||
struct ipc_switching {
|
||||
int orig;
|
||||
int dest;
|
||||
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m);
|
||||
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m, short int *more_to_read);
|
||||
enum ipccb (*orig_out) (int origin_fd, struct ipc_message *m);
|
||||
enum ipccb (*dest_in) (int origin_fd, struct ipc_message *m);
|
||||
enum ipccb (*dest_in) (int origin_fd, struct ipc_message *m, short int *more_to_read);
|
||||
enum ipccb (*dest_out) (int origin_fd, struct ipc_message *m);
|
||||
};
|
||||
|
||||
|
@ -315,6 +317,8 @@ struct ipc_event {
|
|||
|
||||
#define IPC_EVENT_CLEAN(pevent) {\
|
||||
pevent->type = IPC_EVENT_TYPE_NOT_SET;\
|
||||
pevent->origin = 0;\
|
||||
pevent->index = 0;\
|
||||
if (pevent->m != NULL) {\
|
||||
ipc_message_empty (pevent->m);\
|
||||
free(pevent->m);\
|
||||
|
@ -375,7 +379,7 @@ void ipc_messages_free (struct ipc_messages *);
|
|||
|
||||
// Switch cases macros
|
||||
// print on error
|
||||
#define ERROR_CASE(e,f,m) case e : { fprintf (stderr, "function %s: %s", f, m); } break;
|
||||
#define ERROR_CASE(e,f,m) case e : { fprintf (stderr, "function %s: %s\n", f, m); } break;
|
||||
|
||||
/***
|
||||
* non public functions
|
||||
|
@ -399,16 +403,17 @@ struct ipc_error service_path (char *path, const char *sname);
|
|||
**/
|
||||
|
||||
void ipc_ctx_switching_add (struct ipc_ctx *ctx, int orig, int dest);
|
||||
int ipc_ctx_switching_del (struct ipc_ctx *ctx, int fd);
|
||||
void ipc_switching_add (struct ipc_switchings *is, int orig, int dest);
|
||||
int ipc_switching_del (struct ipc_switchings *is, int fd);
|
||||
int ipc_switching_get (struct ipc_switchings *is, int fd);
|
||||
void ipc_switching_free (struct ipc_switchings *is);
|
||||
void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m));
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read));
|
||||
void ipc_switching_callbacks (
|
||||
struct ipc_ctx *ctx
|
||||
, int fd
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read)
|
||||
, enum ipccb (*cb_out)(int fd, struct ipc_message *m));
|
||||
|
||||
int ipc_ctx_fd_type (struct ipc_ctx *ctx, int fd, enum ipc_connection_type type);
|
||||
|
@ -521,6 +526,7 @@ struct ipc_error ipc_provide_fd (int sock, int fd);
|
|||
TEST_IPC_RR_F(function_to_test, "%s", err_message)
|
||||
|
||||
// same as TEST_IPC_RR but do not return
|
||||
// Also: print the error right away.
|
||||
#define TEST_IPC_P(function_to_test, err_message) {\
|
||||
struct ipc_error ret = function_to_test;\
|
||||
if (ret.error_code != IPC_ERROR_NONE) {\
|
||||
|
@ -528,6 +534,8 @@ struct ipc_error ipc_provide_fd (int sock, int fd);
|
|||
, "non blocking error" \
|
||||
, ":" __FILE__ "%s:%s" \
|
||||
, err_message );\
|
||||
\
|
||||
fprintf (stderr, "%s\n", ret.error_message);\
|
||||
}\
|
||||
}
|
||||
|
||||
|
|
|
@ -88,14 +88,10 @@ void ipc_ctx_switching_add (struct ipc_ctx *ctx, int orig, int dest)
|
|||
|
||||
void ipc_switching_add (struct ipc_switchings *is, int orig, int dest)
|
||||
{
|
||||
// printf ("ipc_switching_add START: switchdb has %ld entries\n", is->size);
|
||||
|
||||
if (is->collection == NULL) {
|
||||
// printf ("switchdb collection is null\n");
|
||||
is->collection = malloc (sizeof (struct ipc_switching) * (is->size + 1));
|
||||
}
|
||||
else {
|
||||
// printf ("switchdb collection isn't null\n");
|
||||
is->collection = realloc (is->collection, sizeof (struct ipc_switching) * (is->size + 1));
|
||||
}
|
||||
|
||||
|
@ -114,8 +110,11 @@ void ipc_switching_add (struct ipc_switchings *is, int orig, int dest)
|
|||
is->collection[is->size - 1].dest_in = NULL;
|
||||
is->collection[is->size - 1].orig_out = NULL;
|
||||
is->collection[is->size - 1].dest_out = NULL;
|
||||
}
|
||||
|
||||
// printf ("ipc_switching_add END: switchdb has %ld entries\n", is->size);
|
||||
int ipc_ctx_switching_del (struct ipc_ctx *ctx, int fd)
|
||||
{
|
||||
return ipc_switching_del (&ctx->switchdb, fd);
|
||||
}
|
||||
|
||||
int ipc_switching_del (struct ipc_switchings *is, int fd)
|
||||
|
@ -197,9 +196,10 @@ void ipc_switching_free (struct ipc_switchings *is)
|
|||
}
|
||||
|
||||
enum ipccb
|
||||
default_cb_in(int fd, struct ipc_message *m)
|
||||
default_cb_in(int fd, struct ipc_message *m, short int *more_to_read)
|
||||
{
|
||||
// TODO: fix buffer size for switching messages
|
||||
*more_to_read = 0;
|
||||
|
||||
size_t msize = IPC_MAX_MESSAGE_SIZE;
|
||||
SECURE_BUFFER_DECLARATION (char, buf, msize);
|
||||
char *pbuf = buf;
|
||||
|
@ -251,7 +251,7 @@ default_cb_out(int fd, struct ipc_message *m)
|
|||
}
|
||||
|
||||
void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m))
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read))
|
||||
{
|
||||
ipc_switching_callbacks (ctx, fd, cb_in, NULL);
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
|
|||
void ipc_switching_callbacks (
|
||||
struct ipc_ctx *ctx
|
||||
, int fd
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
|
||||
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read)
|
||||
, enum ipccb (*cb_out)(int fd, struct ipc_message *m))
|
||||
{
|
||||
struct ipc_switching *sw = NULL;
|
||||
|
@ -287,6 +287,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
|
||||
// If the socket is associated to another one for ipcd:
|
||||
// read and write automatically and provide a new IPC_EVENT_TYPE indicating the switch.
|
||||
|
||||
T_R ((ctx->switchdb.size == 0), IPC_ERROR_FD_SWITCHING__NO_FD_RECORD);
|
||||
|
||||
int talkingfd = ctx->pollfd[index].fd;
|
||||
|
@ -297,6 +298,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
|
||||
enum ipccb r;
|
||||
int is_valid = 0;
|
||||
short int more_to_read = 0;
|
||||
|
||||
is_valid = ipc_switching_get_ (&ctx->switchdb, talkingfd, &sw);
|
||||
|
||||
|
@ -305,22 +307,24 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
if (sw->orig == talkingfd) {
|
||||
dest_fd = sw->dest;
|
||||
if (sw->orig_in == NULL) {
|
||||
r = default_cb_in (talkingfd, &m);
|
||||
r = default_cb_in (talkingfd, &m, &more_to_read);
|
||||
}
|
||||
else {
|
||||
r = (*sw->orig_in)(talkingfd, &m);
|
||||
r = (*sw->orig_in)(talkingfd, &m, &more_to_read);
|
||||
}
|
||||
}
|
||||
else {
|
||||
dest_fd = sw->orig;
|
||||
if (sw->dest_in == NULL) {
|
||||
r = default_cb_in (talkingfd, &m);
|
||||
r = default_cb_in (talkingfd, &m, &more_to_read);
|
||||
}
|
||||
else {
|
||||
r = (*sw->dest_in)(talkingfd, &m);
|
||||
r = (*sw->dest_in)(talkingfd, &m, &more_to_read);
|
||||
}
|
||||
}
|
||||
|
||||
ctx->cinfos[index].more_to_read = more_to_read;
|
||||
|
||||
// Message reception OK: reading the message and put it in the list of messages to send.
|
||||
if (r == IPC_CB_NO_ERROR) {
|
||||
// In case of message reception:
|
||||
|
@ -339,7 +343,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
// This is applied to protocol-specific messages, for example when the client
|
||||
// has to communicate with the proxy, not the service.
|
||||
if (r == IPC_CB_IGNORE) {
|
||||
printf ("IGNORING REQUEST\n");
|
||||
// printf ("IGNORING REQUEST\n");
|
||||
// In case of message reception:
|
||||
// 1. set event IPC_EVENT_TYPE_SWITCH, inform ipcd of a successful reception.
|
||||
IPC_EVENT_SET (event, IPC_EVENT_TYPE_SWITCH, index, ctx->pollfd[index].fd, NULL);
|
||||
|
@ -351,13 +355,14 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
* NOTE: In any other case, the fd is, or should be closed.
|
||||
*/
|
||||
|
||||
// 1. close and remove both fd from switchdb
|
||||
close (sw->dest);
|
||||
ipc_del_fd (ctx, sw->dest);
|
||||
// Should not close the client: it's the job of the libipc user application.
|
||||
// XXX: this may be normal, but should be documented.
|
||||
// 1. remove both fd from switchdb
|
||||
// Client and servers should be closed by the libipc user application.
|
||||
// close (sw->dest);
|
||||
// close (talkingfd);
|
||||
|
||||
ipc_del_fd (ctx, sw->dest);
|
||||
ipc_del_fd (ctx, talkingfd);
|
||||
|
||||
ipc_switching_del (&ctx->switchdb, talkingfd);
|
||||
|
||||
// 2. set event (either error or disconnection)
|
||||
|
@ -377,8 +382,6 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
|
|||
*/
|
||||
struct ipc_error fd_switching_write (struct ipc_event *event, struct ipc_ctx *ctx, int index)
|
||||
{
|
||||
// printf ("fd_switching_write\n");
|
||||
|
||||
// If the socket is associated to another one for ipcd:
|
||||
// read and write automatically and provide a new IPC_EVENT_TYPE indicating the switch.
|
||||
T_R ((ctx->switchdb.size == 0), IPC_ERROR_FD_SWITCHING__NO_FD_RECORD);
|
||||
|
|
119
src/usocket.c
119
src/usocket.c
|
@ -23,8 +23,73 @@
|
|||
struct ipc_error usock_send (const int32_t fd, const char *buf, size_t len, size_t * sent)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
|
||||
#ifdef __PRINT_MSG_SIZES
|
||||
fprintf (stderr, "a %10lu-byte message should be sent to %d\n", len, fd);
|
||||
#endif
|
||||
|
||||
ret = send (fd, buf, len, MSG_NOSIGNAL);
|
||||
T_R ((ret <= 0), IPC_ERROR_USOCK_SEND);
|
||||
if (ret == -1)
|
||||
{
|
||||
// Some choice could be made.
|
||||
switch (errno) {
|
||||
|
||||
// The receive buffer pointer(s) point outside the process's address space.
|
||||
ERROR_CASE (EACCES, "usock_send", "write permission is denied");
|
||||
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
// POSIX.1-2001 allows either error to be returned for this case, and does not
|
||||
// require these constants to have the same value, so a portable application
|
||||
// should check for both possibilities.
|
||||
ERROR_CASE (EWOULDBLOCK, "usock_send", "socket marked as nonblocking, but requested operation would block");
|
||||
|
||||
// ERROR_CASE (EAGAIN, "usock_send", "socket not previously bound to an address and all ports are in use");
|
||||
|
||||
ERROR_CASE (EALREADY, "usock_send", "another Fast Open is in progress");
|
||||
|
||||
ERROR_CASE (EBADF, "usock_send", "sockfd is not a valid open file descriptor");
|
||||
|
||||
ERROR_CASE (ECONNRESET, "usock_send", "Connection reset by peer.");
|
||||
|
||||
ERROR_CASE (EDESTADDRREQ, "usock_send", "socket not connection-mode, and no peer address is set.");
|
||||
|
||||
ERROR_CASE (EFAULT, "usock_send", "an invalid user space address was specified for an argument");
|
||||
|
||||
// See signal(7).
|
||||
ERROR_CASE (EINTR, "usock_send", "a signal occurred before any data was transmitted");
|
||||
|
||||
ERROR_CASE (EINVAL, "usock_send", "invalid argument passed");
|
||||
|
||||
// This error should not happen, and the recipient specification may be ignored.
|
||||
ERROR_CASE (EISCONN, "usock_send", "connection-mode socket was already connected but a recipient was specified");
|
||||
|
||||
// The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible.
|
||||
ERROR_CASE (EMSGSIZE, "usock_send", "cannot send a message of that size");
|
||||
|
||||
// This generally indicates that the interface has stopped sending, but
|
||||
// may be caused by transient congestion. (Normally, this does not occur in Linux.
|
||||
// Packets are just silently dropped when a device queue overflows.)
|
||||
ERROR_CASE (ENOBUFS, "usock_send", "the output queue for the network interface was full");
|
||||
|
||||
ERROR_CASE (ENOMEM, "usock_send", "no memory available");
|
||||
|
||||
ERROR_CASE (ENOTCONN, "usock_send", "the socket is not connected, and no target has been given");
|
||||
|
||||
// Should not happen in libipc (watch out for libipc user application).
|
||||
ERROR_CASE (ENOTSOCK, "usock_send", "the file descriptor sockfd does not refer to a socket");
|
||||
|
||||
// Should not happen in libipc.
|
||||
ERROR_CASE (EOPNOTSUPP, "usock_send", "some bit in the flags argument is inappropriate for the socket type");
|
||||
|
||||
// In this case, the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
|
||||
ERROR_CASE (EPIPE, "usock_send", "the local end has been shut down on a connection oriented socket");
|
||||
|
||||
default:
|
||||
fprintf (stderr, "usock_send: unrecognized error after send(2), num: %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
T_R ((ret == -1), IPC_ERROR_USOCK_SEND);
|
||||
*sent = ret;
|
||||
IPC_RETURN_NO_ERROR;
|
||||
}
|
||||
|
@ -35,37 +100,57 @@ struct ipc_error usock_recv (const int32_t fd, char **buf, size_t * len)
|
|||
T_R ((buf == NULL), IPC_ERROR_USOCK_RECV__NO_BUFFER);
|
||||
T_R ((len == NULL), IPC_ERROR_USOCK_RECV__NO_LENGTH);
|
||||
|
||||
// printf("USOCKET: listen to %d (up to %lu bytes)\n", fd, *len);
|
||||
int32_t ret_recv = 0;
|
||||
|
||||
if (*len == 0)
|
||||
*len = IPC_MAX_MESSAGE_SIZE;
|
||||
|
||||
// msize_read: size of the message (without the header).
|
||||
uint32_t msize = 0;
|
||||
|
||||
// msize_read: size sum of the packets received.
|
||||
uint32_t msize_read = 0;
|
||||
|
||||
do {
|
||||
/**
|
||||
* recv:
|
||||
* ret > 0: message receveid
|
||||
* ret == 0: fd is closing
|
||||
* ret < 0: error
|
||||
*/
|
||||
ret_recv = recv (fd, *buf, *len, 0);
|
||||
#ifdef IPC_DEBUG
|
||||
|
||||
if (ret_recv > 0) {
|
||||
#ifdef IPC_DEBUG
|
||||
print_hexa ("msg recv", (uint8_t *) * buf, ret_recv);
|
||||
fflush (stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ret_recv > 0) {
|
||||
if (msize == 0) {
|
||||
memcpy (&msize, *buf + 1, sizeof msize);
|
||||
}
|
||||
msize = ntohl (msize);
|
||||
msize = ntohl (msize);
|
||||
|
||||
if (msize >= IPC_MAX_MESSAGE_SIZE) {
|
||||
#ifdef __PRINT_MSG_SIZES
|
||||
fprintf (stderr, "a %10u-byte message should be received on %d\n"
|
||||
, msize + IPC_HEADER_SIZE
|
||||
, fd);
|
||||
#endif
|
||||
}
|
||||
// else {
|
||||
// printf ("USOCKET: We received a message in (at least) two packets (receveid %u bytes).\n", msize_read);
|
||||
// }
|
||||
|
||||
if (msize > IPC_MAX_MESSAGE_SIZE) {
|
||||
#ifdef IPC_DEBUG
|
||||
print_hexa ("msg recv", (uint8_t *) * buf, ret_recv);
|
||||
fflush (stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Do not allow messages with a longer size than expected.
|
||||
T_R ((msize > IPC_MAX_MESSAGE_SIZE), IPC_ERROR_USOCK_RECV__MESSAGE_SIZE);
|
||||
msize_read += ret_recv - IPC_HEADER_SIZE;
|
||||
|
||||
msize_read += ret_recv;
|
||||
} else if (ret_recv < 0) {
|
||||
*len = 0;
|
||||
|
||||
|
@ -111,11 +196,17 @@ struct ipc_error usock_recv (const int32_t fd, char **buf, size_t * len)
|
|||
, "usock_recv: recv < 0, is the message size malformed?");
|
||||
}
|
||||
|
||||
} while (msize > msize_read);
|
||||
// if (msize > msize_read) {
|
||||
// printf ("USOCKET: loop again for %d (read %u/%u)\n", fd, msize_read, msize);
|
||||
// }
|
||||
|
||||
*len = msize + IPC_HEADER_SIZE;
|
||||
// In case msize still is 0, recv didn't worked as expected.
|
||||
} while (msize > 0 && msize > msize_read - IPC_HEADER_SIZE);
|
||||
|
||||
// 1 on none byte received, indicates a closed recipient
|
||||
// printf("USOCKET: end of the loop for client %d -- %u bytes read\n", fd, msize_read);
|
||||
*len = msize_read;
|
||||
|
||||
// none bytes received, indicates a closed recipient
|
||||
if (ret_recv == 0) {
|
||||
*len = 0;
|
||||
IPC_RETURN_ERROR (IPC_ERROR_CLOSED_RECIPIENT);
|
||||
|
@ -135,7 +226,7 @@ struct ipc_error usock_connect (int32_t * fd, const char *path)
|
|||
int32_t sfd;
|
||||
socklen_t peer_addr_size = sizeof (struct sockaddr_un);
|
||||
|
||||
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1), "socket", IPC_ERROR_USOCK_CONNECT__SOCKET);
|
||||
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_STREAM, 0)) == -1), "socket", IPC_ERROR_USOCK_CONNECT__SOCKET);
|
||||
strncpy (my_addr.sun_path, path, (strlen (path) < PATH_MAX) ? strlen (path) : PATH_MAX);
|
||||
|
||||
TEST_IPC_RETURN_ON_ERROR(directory_setup_ (path));
|
||||
|
@ -169,7 +260,7 @@ struct ipc_error usock_init (int32_t * fd, const char *path)
|
|||
|
||||
TEST_IPC_RETURN_ON_ERROR(directory_setup_ (path));
|
||||
|
||||
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1)
|
||||
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
|
||||
, "socket", IPC_ERROR_USOCK_INIT__WRONG_FILE_DESCRIPTOR);
|
||||
|
||||
// delete the unix socket if already created
|
||||
|
|
|
@ -32,6 +32,7 @@ int main(int argc, char * argv[])
|
|||
printf ("error: %s\n", ipc_errors_get(ret.error_code));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
ipc_ctx_free (&ctx);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ int main(int argc, char * argv[])
|
|||
argc = (int) argc;
|
||||
argv = (char **) argv;
|
||||
|
||||
|
||||
SECURE_DECLARATION(struct ipc_ctx, ctx);
|
||||
int timer = 10000; // 10 seconds timer
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ void read_message (struct ipc_ctx *ctx)
|
|||
SECURE_DECLARATION (struct ipc_message, m);
|
||||
|
||||
TEST_IPC_Q(ipc_read (ctx, 0 /* only one server here */, &m), EXIT_FAILURE);
|
||||
printf ("received message: %*.s\n", m.length, m.payload);
|
||||
printf ("received message: %.*s\n", m.length, m.payload);
|
||||
free (m.payload);
|
||||
#endif
|
||||
}
|
||||
|
@ -76,11 +76,13 @@ int main(void)
|
|||
read_message (&ctx1);
|
||||
|
||||
TEST_IPC_Q (ipc_close_all (&ctx1), EXIT_FAILURE);
|
||||
ipc_ctx_free (&ctx1);
|
||||
|
||||
send_message (&ctx2);
|
||||
read_message (&ctx2);
|
||||
|
||||
TEST_IPC_Q (ipc_close_all (&ctx2), EXIT_FAILURE);
|
||||
ipc_ctx_free (&ctx2);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,20 @@
|
|||
#include <string.h>
|
||||
|
||||
#define SERVICE_NAME "pong"
|
||||
struct ipc_ctx *pctx = NULL;
|
||||
|
||||
void exit_program(int signal)
|
||||
{
|
||||
printf("Quitting, signal: %d\n", signal);
|
||||
|
||||
// the application will shut down, and close the service
|
||||
TEST_IPC_Q(ipc_close_all (pctx), EXIT_FAILURE);
|
||||
|
||||
// free remaining ctx
|
||||
ipc_ctx_free (pctx);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int main_loop(int argc, char * argv[])
|
||||
{
|
||||
|
@ -13,6 +27,7 @@ int main_loop(int argc, char * argv[])
|
|||
argv = argv;
|
||||
|
||||
SECURE_DECLARATION (struct ipc_ctx, ctx);
|
||||
pctx = &ctx;
|
||||
int timer = 10000; // in ms
|
||||
|
||||
printf ("func 03 - server init...\n");
|
||||
|
@ -24,7 +39,13 @@ int main_loop(int argc, char * argv[])
|
|||
printf ("func 01 - service polling...\n");
|
||||
// listen only for a single client
|
||||
while (1) {
|
||||
TEST_IPC_WAIT_EVENT_Q (ipc_wait_event (&ctx, &event, &timer), EXIT_FAILURE);
|
||||
// TEST_IPC_WAIT_EVENT_Q (ipc_wait_event (&ctx, &event, &timer), EXIT_FAILURE);
|
||||
struct ipc_error ret = ipc_wait_event (&ctx, &event, &timer);
|
||||
|
||||
if (ret.error_code != IPC_ERROR_NONE &&
|
||||
ret.error_code != IPC_ERROR_CLOSED_RECIPIENT) {
|
||||
printf ("An error happened :(\n");
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case IPC_EVENT_TYPE_TIMER : {
|
||||
|
@ -41,8 +62,12 @@ int main_loop(int argc, char * argv[])
|
|||
};
|
||||
break;
|
||||
case IPC_EVENT_TYPE_MESSAGE : {
|
||||
printf ("received message: %s\n", ((struct ipc_message*) event.m)->payload);
|
||||
ipc_write (&ctx, (struct ipc_message*) event.m);
|
||||
struct ipc_message *m = (struct ipc_message*) event.m;
|
||||
printf ("received message (%d bytes): %.*s\n"
|
||||
, m->length
|
||||
, m->length
|
||||
, m->payload);
|
||||
ipc_write (&ctx, m);
|
||||
}
|
||||
break;
|
||||
case IPC_EVENT_TYPE_TX : {
|
||||
|
@ -54,7 +79,6 @@ int main_loop(int argc, char * argv[])
|
|||
case IPC_EVENT_TYPE_EXTRA_SOCKET :
|
||||
default :
|
||||
printf ("not ok - should not happen\n");
|
||||
exit (EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -68,21 +92,17 @@ int main_loop(int argc, char * argv[])
|
|||
return 0;
|
||||
}
|
||||
|
||||
void exit_program(int signal)
|
||||
{
|
||||
printf("Quitting, signal: %d\n", signal);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
// In case we want to quit the program, do it cleanly.
|
||||
signal (SIGHUP, exit_program);
|
||||
signal (SIGALRM, exit_program);
|
||||
signal (SIGUSR1, exit_program);
|
||||
signal (SIGUSR2, exit_program);
|
||||
signal (SIGTERM, exit_program);
|
||||
signal (SIGINT, exit_program);
|
||||
|
||||
main_loop (argc, argv);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ int main(void)
|
|||
read_message (&ctx);
|
||||
|
||||
TEST_IPC_Q(ipc_close_all (&ctx), EXIT_FAILURE);
|
||||
ipc_ctx_free(&ctx);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
zig-cache
|
||||
zig-out
|
||||
docs
|
||||
build
|
||||
ipcd
|
||||
tcpd
|
||||
pongd
|
||||
pong
|
|
@ -0,0 +1,44 @@
|
|||
# Why rewrite in Zig
|
||||
|
||||
I did a library called `libipc` then I wanted to rewrite it in Zig.
|
||||
|
||||
The problems with the C-based version:
|
||||
|
||||
- libipc cannot easily be ported on other plateforms
|
||||
* cannot compile under musl
|
||||
* cannot compile for any OS
|
||||
* different libc = different error codes for functions
|
||||
- cannot change the code easily without risking to break stuff
|
||||
- behavior isn't the same accross OSs
|
||||
* glibc-based and musl-based Linux have different behavior
|
||||
* Linux and OpenBSD have different behavior
|
||||
- hard to debug
|
||||
* OpenBSD has a buggy valgrind
|
||||
|
||||
For all these reasons, this library took a lot of time to dev (~ 2 months), and I'm still not entirely confident in its reliability.
|
||||
Tests are required to gain confidence.
|
||||
The mere compilation isn't a proof of anything.
|
||||
And the code size (2kloc) for something that simple can be seen as a mess, despite being rather fair.
|
||||
|
||||
What Zig brings to the table:
|
||||
|
||||
- OS and libc-agnostic, all quirks are handled in the std lib by the Zig dev team
|
||||
- same source code for all OSs
|
||||
- can compile **from and to** any OS and libc
|
||||
- errors already are verified in the std lib
|
||||
- compiling `foo() catch |e| switch(e) {}` shows all possible errors
|
||||
- std lib provides
|
||||
* basic structures and functions, no need to add deps or write functions to have simple lists and such
|
||||
* memory allocators & checks, no need for valgrind and other memory checking tools
|
||||
* basic network structures (address & stuff)
|
||||
- structures can be printed
|
||||
- code and tests in the same file
|
||||
- (in a near future) async functions, to handle networking operations in a very elegant manner
|
||||
|
||||
All these features provide a **massive gain of time**.
|
||||
|
||||
And in general, just having the same standard library for all OSs is great just not to have to rely on different implementations and even locations of code and header files.
|
||||
|
||||
What isn't *that* great with Zig:
|
||||
|
||||
- structures can be printed, but they also provide the format themselves, leading to an unreliable way of discovering structure's content.
|
|
@ -0,0 +1,38 @@
|
|||
### MISC
|
||||
|
||||
- create the unix socket directory
|
||||
- close the connection and log when we receive too big messages
|
||||
- decide then explicitely document what the max message size should be
|
||||
- rx message buffer should be small but should grow if required
|
||||
- some functions are not exposed in the bindings, mostly functions related to switching
|
||||
|
||||
### makefile
|
||||
|
||||
- release
|
||||
- distribution
|
||||
|
||||
### documentation
|
||||
|
||||
- document the two ways to use LibIPC: either within some Zig code or through the bindings
|
||||
- manpages for ipcd, tcpd, pong, pongd
|
||||
|
||||
### src/exchange-fd.zig
|
||||
|
||||
- still very WIP, even though it works as expected
|
||||
- recvmsg is a very *stupid* copy of the sendmsg fn, **EXPECT ERRORS** (if used outside libipc)
|
||||
- at least one memory error when using Cmsghdr (see below)
|
||||
|
||||
==32374== Syscall param sendmsg(msg.msg_control) points to uninitialised byte(s)
|
||||
==32374== at 0x40554A3: ??? (in /lib/ld-musl-x86_64.so.1)
|
||||
==32374== by 0x40526F9: ??? (in /lib/ld-musl-x86_64.so.1)
|
||||
==32374== by 0x4096B83: ???
|
||||
==32374== Address 0x1ffefff384 is on thread 1's stack
|
||||
==32374== Uninitialised value was created by a client request
|
||||
==32374== at 0x289769: exchange-fd.Cmsghdr(i32).init (exchange-fd.zig:39)
|
||||
==32374== by 0x2808F0: exchange-fd.send_fd (exchange-fd.zig:86)
|
||||
==32374== by 0x27EA97: ipcd.create_service (ipcd.zig:178)
|
||||
==32374== by 0x28117C: ipcd.main (ipcd.zig:224)
|
||||
==32374== by 0x28161E: callMain (start.zig:614)
|
||||
==32374== by 0x28161E: initEventLoopAndCallMain (start.zig:548)
|
||||
==32374== by 0x28161E: callMainWithArgs (start.zig:498)
|
||||
==32374== by 0x28161E: main (start.zig:513)
|
|
@ -0,0 +1,226 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const ipc = @import("ipc");
|
||||
const hexdump = ipc.hexdump;
|
||||
const Message = ipc.Message;
|
||||
|
||||
// Import send_fd this way in order to produce docs for exchange-fd functions.
|
||||
const exchange_fd = ipc.exchangefd;
|
||||
const send_fd = exchange_fd.send_fd;
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.target.os.tag;
|
||||
const print = std.debug.print;
|
||||
const testing = std.testing;
|
||||
const print_eq = ipc.util.print_eq;
|
||||
const URI = ipc.util.URI;
|
||||
|
||||
// Standard library is unecessary complex regarding networking.
|
||||
// libipc drops it and uses plain old file descriptors instead.
|
||||
|
||||
// API should completely obfuscate the inner structures.
|
||||
// Only libipc structures should be necessary to write any networking code,
|
||||
// users should only work with Context and Message, mostly.
|
||||
|
||||
// QUESTION: should libipc use std.fs.path and not simple [] const u8?
|
||||
|
||||
fn create_service() !void {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var ctx = try ipc.Context.init(allocator);
|
||||
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
|
||||
|
||||
// SERVER SIDE: creating a service.
|
||||
_ = try ctx.server_init("ipc");
|
||||
|
||||
// signal handler, to quit when asked
|
||||
const S = struct {
|
||||
var should_quit: bool = false;
|
||||
|
||||
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
|
||||
print ("A signal has been received: {}\n", .{sig});
|
||||
// Check that we received the correct signal.
|
||||
switch (native_os) {
|
||||
.netbsd => {
|
||||
if (sig != os.SIG.HUP or sig != info.info.signo)
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
if (sig != os.SIG.HUP and sig != info.signo)
|
||||
return;
|
||||
},
|
||||
}
|
||||
should_quit = true;
|
||||
}
|
||||
};
|
||||
|
||||
var sa = os.Sigaction{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = os.empty_sigset, // Do not mask any signal.
|
||||
.flags = os.SA.SIGINFO,
|
||||
};
|
||||
|
||||
// Quit on SIGHUP (kill -1).
|
||||
try os.sigaction(os.SIG.HUP, &sa, null);
|
||||
|
||||
var some_event: ipc.Event = undefined;
|
||||
ctx.timer = 1000; // 1 second
|
||||
var count: u32 = 0;
|
||||
while(! S.should_quit) {
|
||||
some_event = try ctx.wait_event();
|
||||
switch (some_event.t) {
|
||||
.TIMER => {
|
||||
print("\rTimer! ({})", .{count});
|
||||
count += 1;
|
||||
},
|
||||
|
||||
.CONNECTION => {
|
||||
print("New connection: {} so far!\n", .{ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.DISCONNECTION => {
|
||||
print("User {} disconnected, {} remainaing.\n"
|
||||
, .{some_event.origin, ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.EXTERNAL => {
|
||||
print("Message received from a non IPC socket.\n", .{});
|
||||
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
|
||||
break;
|
||||
},
|
||||
|
||||
.SWITCH_RX => {
|
||||
print("Message has been received (SWITCH).\n", .{});
|
||||
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
|
||||
break;
|
||||
},
|
||||
|
||||
.SWITCH_TX => {
|
||||
print("Message has been sent (SWITCH).\n", .{});
|
||||
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
|
||||
break;
|
||||
},
|
||||
|
||||
.MESSAGE_RX => {
|
||||
print("Client asking for a service through ipcd.\n", .{});
|
||||
defer ctx.close_fd (some_event.origin) catch {};
|
||||
if (some_event.m) |m| {
|
||||
print("{}\n", .{m});
|
||||
defer m.deinit(); // Do not forget to free the message payload.
|
||||
|
||||
// 1. split message
|
||||
var iterator = std.mem.split(u8, m.payload, ";");
|
||||
var service_to_contact = iterator.first();
|
||||
// print("service to contact: {s}\n", .{service_to_contact});
|
||||
var final_destination: ?[]const u8 = null;
|
||||
|
||||
// 2. find relevant part of the message
|
||||
while (iterator.next()) |next| {
|
||||
// print("next part: {s}\n", .{next});
|
||||
var iterator2 = std.mem.split(u8, next, " ");
|
||||
var sname = iterator2.first();
|
||||
var target = iterator2.next();
|
||||
if (target) |t| {
|
||||
// print ("sname: {s} - target: {s}\n", .{sname, t});
|
||||
if (std.mem.eql(u8, service_to_contact, sname)) {
|
||||
final_destination = t;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print("ERROR: no target in: {s}\n", .{next});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. connect whether asked to and send a message
|
||||
if (final_destination) |dest| {
|
||||
print("Connecting to {s} (service requested: {s})\n"
|
||||
, .{dest, service_to_contact});
|
||||
|
||||
var uri = URI.read(dest);
|
||||
|
||||
// 1. in case there is no URI
|
||||
if (std.mem.eql(u8, uri.protocol, dest)) {
|
||||
var newfd = try ctx.connect_service (dest);
|
||||
send_fd (some_event.origin, "ok", newfd);
|
||||
try ctx.close_fd (newfd);
|
||||
}
|
||||
else if (std.mem.eql(u8, uri.protocol, "unix")) {
|
||||
var newfd = try ctx.connect_service (uri.address);
|
||||
send_fd (some_event.origin, "ok", newfd);
|
||||
try ctx.close_fd (newfd);
|
||||
}
|
||||
// 2. else, contact <protocol>d or directly the dest in case there is none.
|
||||
else {
|
||||
var servicefd = try ctx.connect_service (uri.protocol);
|
||||
defer ctx.close_fd (servicefd) catch {};
|
||||
// TODO: make a simple protocol between IPCd and <protocol>d
|
||||
// NEED inform about the connection (success or fail)
|
||||
// FIRST DRAFT:
|
||||
// - IPCd: send a message containing the destination
|
||||
// - PROTOCOLd: send "ok" to inform the connection is established
|
||||
// - PROTOCOLd: send "no" in case there was an error
|
||||
|
||||
var message = try Message.init(servicefd, allocator, dest);
|
||||
defer message.deinit();
|
||||
try ctx.write(message);
|
||||
var response_from_service = try ctx.read_fd(servicefd);
|
||||
if (response_from_service) |r| {
|
||||
defer r.deinit();
|
||||
if (std.mem.eql(u8, r.payload, "ok")) {
|
||||
// OK
|
||||
// print("service has established the connection\n", .{});
|
||||
send_fd (some_event.origin, "ok", servicefd);
|
||||
}
|
||||
else if (std.mem.eql(u8, r.payload, "ne")) {
|
||||
// PROBLEM
|
||||
print("service cannot establish the connection\n", .{});
|
||||
// TODO
|
||||
}
|
||||
else {
|
||||
print("service isn't working properly, its response is: {s}\n", .{r.payload});
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No message = should be handled as a disconnection.
|
||||
print("No response from service: let's drop everything\n", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// There is a problem: ipcd was contacted without providing
|
||||
// a message, meaning there is nothing to do. This should be
|
||||
// explicitely warned about.
|
||||
var response = try Message.init(some_event.origin
|
||||
, allocator
|
||||
, "lookup message without data");
|
||||
defer response.deinit();
|
||||
try ctx.write(response);
|
||||
}
|
||||
},
|
||||
|
||||
.MESSAGE_TX => {
|
||||
print("Message sent.\n", .{});
|
||||
},
|
||||
|
||||
.ERROR => {
|
||||
print("A problem occured, event: {}, let's suicide\n", .{some_event});
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
print("Goodbye\n", .{});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
try create_service();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const print = std.debug.print;
|
||||
|
||||
const ipc = @import("ipc");
|
||||
const hexdump = ipc.hexdump;
|
||||
const Message = ipc.Message;
|
||||
|
||||
pub fn main() !u8 {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var ctx = try ipc.Context.init(allocator);
|
||||
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
|
||||
|
||||
// The service to contact, either provided with the SERVICE envvar
|
||||
// or simply using "pong".
|
||||
var should_free_service_to_contact: bool = true;
|
||||
var service_to_contact = std.process.getEnvVarOwned(allocator, "SERVICE") catch blk: {
|
||||
should_free_service_to_contact = false;
|
||||
break :blk "pong";
|
||||
};
|
||||
defer {
|
||||
if (should_free_service_to_contact)
|
||||
allocator.free(service_to_contact);
|
||||
}
|
||||
|
||||
var pongfd = try ctx.connect_ipc(service_to_contact);
|
||||
var message = try Message.init(pongfd, allocator, "bounce me");
|
||||
try ctx.schedule(message);
|
||||
|
||||
var some_event: ipc.Event = undefined;
|
||||
ctx.timer = 2000; // 2 seconds
|
||||
while(true) {
|
||||
some_event = try ctx.wait_event();
|
||||
switch (some_event.t) {
|
||||
.TIMER => {
|
||||
print("Timer!\n", .{});
|
||||
},
|
||||
|
||||
.MESSAGE_RX => {
|
||||
if (some_event.m) |m| {
|
||||
print("message has been bounced: {}\n", .{m});
|
||||
m.deinit();
|
||||
break;
|
||||
}
|
||||
else {
|
||||
print("Received empty message, ERROR.\n", .{});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
.MESSAGE_TX => {
|
||||
print("Message sent.\n", .{});
|
||||
},
|
||||
|
||||
else => {
|
||||
print("Unexpected event: {}, let's suicide\n", .{some_event});
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
print("Goodbye\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
const std = @import("std");
|
||||
const os = std.os;
|
||||
|
||||
const ipc = @import("ipc");
|
||||
const hexdump = ipc.hexdump;
|
||||
const Message = ipc.Message;
|
||||
const util = ipc.util;
|
||||
|
||||
// Import send_fd this way in order to produce docs for exchange-fd functions.
|
||||
const exchange_fd = ipc.exchangefd;
|
||||
const send_fd = exchange_fd.send_fd;
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.target.os.tag;
|
||||
const print = std.debug.print;
|
||||
const testing = std.testing;
|
||||
|
||||
fn create_service() !void {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var ctx = try ipc.Context.init(allocator);
|
||||
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
|
||||
|
||||
// SERVER SIDE: creating a service.
|
||||
_ = try ctx.server_init("pong");
|
||||
|
||||
// signal handler, to quit when asked
|
||||
const S = struct {
|
||||
var should_quit: bool = false;
|
||||
|
||||
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
|
||||
print ("A signal has been received: {}\n", .{sig});
|
||||
// Check that we received the correct signal.
|
||||
switch (native_os) {
|
||||
.netbsd => {
|
||||
if (sig != os.SIG.HUP or sig != info.info.signo)
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
if (sig != os.SIG.HUP and sig != info.signo)
|
||||
return;
|
||||
},
|
||||
}
|
||||
should_quit = true;
|
||||
}
|
||||
};
|
||||
|
||||
var sa = os.Sigaction{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = os.empty_sigset, // Do not mask any signal.
|
||||
.flags = os.SA.SIGINFO,
|
||||
};
|
||||
|
||||
// Quit on SIGHUP (kill -1).
|
||||
try os.sigaction(os.SIG.HUP, &sa, null);
|
||||
|
||||
var some_event: ipc.Event = undefined;
|
||||
ctx.timer = 20000; // 2 seconds
|
||||
var count: u32 = 0;
|
||||
while(! S.should_quit) {
|
||||
some_event = try ctx.wait_event();
|
||||
switch (some_event.t) {
|
||||
.TIMER => {
|
||||
print("\rTimer! ({})", .{count});
|
||||
count += 1;
|
||||
},
|
||||
|
||||
.CONNECTION => {
|
||||
print("New connection: {} so far!\n", .{ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.DISCONNECTION => {
|
||||
print("User {} disconnected, {} remainaing.\n"
|
||||
, .{some_event.origin, ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.MESSAGE_RX => {
|
||||
if (some_event.m) |m| {
|
||||
print("New message ({} bytes)\n", .{m.payload.len});
|
||||
util.print_message ("RECEIVED MESSAGE", m);
|
||||
print("Echoing it...\n", .{});
|
||||
try ctx.schedule(m);
|
||||
}
|
||||
else {
|
||||
print("Error while receiving new message.\n", .{});
|
||||
print("Ignoring...\n", .{});
|
||||
}
|
||||
},
|
||||
|
||||
.MESSAGE_TX => {
|
||||
print("Message sent.\n", .{});
|
||||
},
|
||||
|
||||
else => {
|
||||
print("Error: unexpected event: {}\n", .{some_event});
|
||||
print("Let's suicide.\n", .{});
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
print("Goodbye\n", .{});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
try create_service();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
const testing = std.testing;
|
||||
const print = std.debug.print;
|
||||
|
||||
const ipc = @import("ipc");
|
||||
const hexdump = ipc.hexdump;
|
||||
const Message = ipc.Message;
|
||||
|
||||
// Import send_fd this way in order to produce docs for exchange-fd functions.
|
||||
const exchange_fd = ipc.exchangefd;
|
||||
const send_fd = exchange_fd.send_fd;
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.target.os.tag;
|
||||
const print_eq = ipc.util.print_eq;
|
||||
const URI = ipc.util.URI;
|
||||
|
||||
fn init_tcp_server(allocator: std.mem.Allocator, server: *net.StreamServer) !i32 {
|
||||
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
|
||||
error.EnvironmentVariableNotFound => blk: {
|
||||
print ("no ADDRESS envvar: TCPd will listen on 127.0.0.1:9000\n", .{});
|
||||
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
|
||||
},
|
||||
else => { return err; },
|
||||
};
|
||||
defer allocator.free(address);
|
||||
|
||||
var iterator = std.mem.split(u8, address, ":");
|
||||
var real_tcp_address = iterator.first();
|
||||
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
|
||||
|
||||
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
|
||||
|
||||
server.* = net.StreamServer.init(.{.reuse_address = true});
|
||||
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
|
||||
try server.listen(socket_addr);
|
||||
|
||||
const newfd = server.sockfd orelse return error.SocketLOL; // TODO
|
||||
return newfd;
|
||||
}
|
||||
|
||||
fn create_service() !void {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var ctx = try ipc.Context.init(allocator);
|
||||
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
|
||||
|
||||
// SERVER SIDE: creating a service.
|
||||
var service_name = std.process.getEnvVarOwned(allocator, "IPC_SERVICE_NAME") catch |err| switch(err) {
|
||||
error.EnvironmentVariableNotFound => blk: {
|
||||
print ("no IPC_SERVICE_NAME envvar: TCPd will be named 'tcp'\n", .{});
|
||||
break :blk try allocator.dupe(u8, "tcp");
|
||||
},
|
||||
else => { return err; },
|
||||
};
|
||||
defer allocator.free(service_name);
|
||||
|
||||
_ = try ctx.server_init(service_name);
|
||||
|
||||
// signal handler, to quit when asked
|
||||
const S = struct {
|
||||
var should_quit: bool = false;
|
||||
|
||||
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
|
||||
print ("A signal has been received: {}\n", .{sig});
|
||||
// Check that we received the correct signal.
|
||||
switch (native_os) {
|
||||
.netbsd => {
|
||||
if (sig != os.SIG.HUP or sig != info.info.signo)
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
if (sig != os.SIG.HUP and sig != info.signo)
|
||||
return;
|
||||
},
|
||||
}
|
||||
should_quit = true;
|
||||
}
|
||||
};
|
||||
|
||||
var sa = os.Sigaction{
|
||||
.handler = .{ .sigaction = &S.handler },
|
||||
.mask = os.empty_sigset, // Do not mask any signal.
|
||||
.flags = os.SA.SIGINFO,
|
||||
};
|
||||
|
||||
// Quit on SIGHUP (kill -1).
|
||||
try os.sigaction(os.SIG.HUP, &sa, null);
|
||||
|
||||
var server: net.StreamServer = undefined;
|
||||
var serverfd = try init_tcp_server(allocator, &server);
|
||||
try ctx.add_external (serverfd);
|
||||
|
||||
var some_event: ipc.Event = undefined;
|
||||
var previous_event: ipc.Event.Type = ipc.Event.Type.ERROR;
|
||||
ctx.timer = 1000; // 1 second
|
||||
var count: u32 = 0;
|
||||
while(! S.should_quit) {
|
||||
some_event = try ctx.wait_event();
|
||||
|
||||
// For clarity in the output.
|
||||
if (some_event.t != .TIMER and previous_event == .TIMER ) { print("\n", .{}); }
|
||||
previous_event = some_event.t;
|
||||
|
||||
switch (some_event.t) {
|
||||
.TIMER => {
|
||||
print ("\rTimer! ({})", .{count});
|
||||
count += 1;
|
||||
},
|
||||
|
||||
.CONNECTION => {
|
||||
print ("New connection: {} so far!\n", .{ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.DISCONNECTION => {
|
||||
print ("User {} disconnected, {} remaining.\n"
|
||||
, .{some_event.origin, ctx.pollfd.items.len});
|
||||
},
|
||||
|
||||
.EXTERNAL => {
|
||||
print ("Message received from a non IPC socket.\n", .{});
|
||||
var client = try server.accept(); // net.StreamServer.Connection
|
||||
errdefer client.stream.close();
|
||||
// Receiving a new client from the EXTERNAL socket.
|
||||
// New client = new switch from a distant TCP connection to a
|
||||
// local libipc service.
|
||||
|
||||
var buffer: [10000]u8 = undefined;
|
||||
var size = try client.stream.read(&buffer);
|
||||
var service_to_contact = buffer[0..size];
|
||||
|
||||
if (service_to_contact.len == 0) {
|
||||
print("Error, no service provided, closing the connection.\n", .{});
|
||||
client.stream.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
print ("Ask to connect to service [{s}].\n", .{service_to_contact});
|
||||
var servicefd = ctx.connect_service (service_to_contact) catch |err| {
|
||||
print("Error while connecting to the service {s}: {}.\n"
|
||||
, .{service_to_contact, err});
|
||||
print ("Closing the connection.\n", .{});
|
||||
client.stream.close();
|
||||
continue;
|
||||
};
|
||||
errdefer ctx.close_fd (servicefd) catch {};
|
||||
|
||||
print ("Send a message to inform remote TCPd that the connection is established.\n", .{});
|
||||
_ = try client.stream.write("ok");
|
||||
|
||||
print ("Add current client as external connection (for now).\n", .{});
|
||||
try ctx.add_external (client.stream.handle);
|
||||
|
||||
print ("Message sent, switching.\n", .{});
|
||||
try ctx.add_switch(client.stream.handle, servicefd);
|
||||
|
||||
print ("DONE.\n", .{});
|
||||
|
||||
// Some protocols will require to change the default functions
|
||||
// to read and to write on the client socket.
|
||||
// Function to call: ctx.set_switch_callbacks(clientfd, infn, outfn);
|
||||
},
|
||||
|
||||
.SWITCH_RX => {
|
||||
print ("Message has been received (SWITCH fd {}).\n", .{some_event.origin});
|
||||
// if (some_event.m) |m| {
|
||||
// var hexbuf: [4000]u8 = undefined;
|
||||
// var hexfbs = std.io.fixedBufferStream(&hexbuf);
|
||||
// var hexwriter = hexfbs.writer();
|
||||
// try hexdump.hexdump(hexwriter, "Received", m.payload);
|
||||
// print("{s}\n", .{hexfbs.getWritten()});
|
||||
// }
|
||||
// else {
|
||||
// print ("Message received without actually a message?! {}", .{some_event});
|
||||
// }
|
||||
},
|
||||
|
||||
.SWITCH_TX => {
|
||||
print ("Message has been sent (SWITCH fd {}).\n", .{some_event.origin});
|
||||
},
|
||||
|
||||
.MESSAGE_RX => {
|
||||
print ("Client asking for a service through TCPd.\n", .{});
|
||||
errdefer ctx.close (some_event.index) catch {};
|
||||
if (some_event.m) |m| {
|
||||
defer m.deinit(); // Do not forget to free the message payload.
|
||||
|
||||
print ("URI to contact {s}\n", .{m.payload});
|
||||
var uri = URI.read(m.payload);
|
||||
print ("proto [{s}] address [{s}] path [{s}]\n"
|
||||
, .{uri.protocol, uri.address, uri.path});
|
||||
|
||||
var iterator = std.mem.split(u8, uri.address, ":");
|
||||
var real_tcp_address = iterator.first();
|
||||
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
|
||||
|
||||
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
|
||||
var stream = try net.tcpConnectToAddress(socket_addr);
|
||||
errdefer stream.close();
|
||||
|
||||
print ("Writing URI PATH: {s}\n", .{uri.path});
|
||||
_ = try stream.write(uri.path);
|
||||
|
||||
print ("Writing URI PATH - written, waiting for the final 'ok'.\n", .{});
|
||||
var buffer: [10000]u8 = undefined;
|
||||
var size = try stream.read(&buffer);
|
||||
if (! std.mem.eql(u8, buffer[0..size], "ok")) {
|
||||
print ("didn't receive 'ok', let's kill the connection\n", .{});
|
||||
stream.close();
|
||||
try ctx.close(some_event.index);
|
||||
continue;
|
||||
}
|
||||
print ("Final 'ok' received, sending 'ok' to IPCd.\n", .{});
|
||||
|
||||
// Connection is established, inform IPCd.
|
||||
var response = try Message.init(some_event.origin, allocator, "ok");
|
||||
defer response.deinit();
|
||||
try ctx.write(response);
|
||||
|
||||
print ("Add current client as external connection (for now).\n", .{});
|
||||
try ctx.add_external (stream.handle);
|
||||
|
||||
print ("Finally, add switching\n", .{});
|
||||
try ctx.add_switch(some_event.origin, stream.handle);
|
||||
// Could invoke ctx.set_switch_callbacks but TCP sockets are
|
||||
// working pretty well with default functions.
|
||||
}
|
||||
else {
|
||||
// TCPd was contacted without providing a message, nothing to do.
|
||||
var response = try Message.init(some_event.origin, allocator, "no");
|
||||
defer response.deinit();
|
||||
try ctx.write(response);
|
||||
try ctx.close(some_event.index);
|
||||
}
|
||||
},
|
||||
|
||||
.MESSAGE_TX => {
|
||||
print ("Message sent.\n", .{});
|
||||
},
|
||||
|
||||
.ERROR => {
|
||||
print ("A problem occured, event: {}, let's suicide.\n", .{some_event});
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
print ("Goodbye\n", .{});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
try create_service();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
const std = @import("std");
|
||||
|
||||
const VERSION = "0.1.0";
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
const is_release = b.option(bool, "release", "Compile a release build.") orelse false;
|
||||
|
||||
if (is_release) {
|
||||
std.log.err("hello, this is the release stuff", .{});
|
||||
}
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const static_lib = b.addStaticLibrary(.{
|
||||
.name = "ipc",
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = .{ .path = "src/bindings.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Link with the libc of the target system since the C allocator
|
||||
// is required in the bindings.
|
||||
static_lib.linkLibC();
|
||||
|
||||
// This declares intent for the library to be installed into the standard
|
||||
// location when the user invokes the "install" step (the default step when
|
||||
// running `zig build`).
|
||||
static_lib.install();
|
||||
|
||||
const shared_lib = b.addSharedLibrary(.{
|
||||
.name = "ipc",
|
||||
.root_source_file = .{ .path = "src/bindings.zig" },
|
||||
.version = comptime (try std.builtin.Version.parse(VERSION)),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
shared_lib.linkLibC();
|
||||
shared_lib.install();
|
||||
|
||||
// Creates a step for unit testing.
|
||||
const main_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
main_tests.linkLibC();
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build test`
|
||||
// This will evaluate the `test` step rather than the default, which is "install".
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&main_tests.step);
|
||||
|
||||
const install_static_lib = b.addInstallArtifact(static_lib);
|
||||
const static_lib_step = b.step("static", "Compile LibIPC as a static library.");
|
||||
static_lib_step.dependOn(&install_static_lib.step);
|
||||
|
||||
const install_shared_lib = b.addInstallArtifact(shared_lib);
|
||||
// b.getInstallStep().dependOn(&install_shared_lib.step);
|
||||
const shared_lib_step = b.step("shared", "Compile LibIPC as a shared library.");
|
||||
shared_lib_step.dependOn(&install_shared_lib.step);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/docs/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
||||
old
|
||||
shard.lock
|
||||
*.log
|
|
@ -0,0 +1,231 @@
|
|||
require "option_parser"
|
||||
|
||||
require "../src/main.cr"
|
||||
require "yaml"
|
||||
|
||||
require "baguette-crystal-base"
|
||||
|
||||
require "../authd/libauth.cr"
|
||||
|
||||
# require "./altideal-client.cr"
|
||||
# require "./yaml_uuid.cr" # YAML UUID parser
|
||||
# require "./authd_api.cr" # Authd interface functions
|
||||
|
||||
|
||||
class Context
|
||||
class_property simulation = false # do not perform the action
|
||||
|
||||
class_property authd_login = "undef" # undef authd user
|
||||
class_property authd_pass = "undef" # undef authd user password
|
||||
class_property shared_key = "undef" # undef authd user password
|
||||
|
||||
# # Properties to select what to display when printing a deal.
|
||||
# class_property print_title = true
|
||||
# class_property print_description = true
|
||||
# class_property print_owner = true
|
||||
# class_property print_nb_comments = true
|
||||
|
||||
class_property command = "not-implemented"
|
||||
|
||||
class_property user_profile : Hash(String,JSON::Any)?
|
||||
class_property phone : String?
|
||||
class_property email : String?
|
||||
|
||||
# Will be parsed later, with a specific parser.
|
||||
class_property args : Array(String)? = nil
|
||||
end
|
||||
|
||||
# require "./parse-me"
|
||||
require "./better-parser"
|
||||
|
||||
class Actions
|
||||
|
||||
def self.ask_password
|
||||
STDOUT << "password: "
|
||||
STDOUT << `stty -echo`
|
||||
STDOUT.flush
|
||||
password = STDIN.gets.try &.chomp
|
||||
|
||||
STDOUT << '\n'
|
||||
STDOUT << `stty echo`
|
||||
|
||||
password
|
||||
end
|
||||
|
||||
def self.ask_something(str : String) : String?
|
||||
STDOUT << "#{str} "
|
||||
STDOUT.flush
|
||||
answer = STDIN.gets.try &.chomp
|
||||
answer
|
||||
end
|
||||
|
||||
|
||||
property the_call = {} of String => Proc(Nil)
|
||||
property authd : AuthD::Client
|
||||
|
||||
def initialize(@authd)
|
||||
@the_call["user-add"] = ->user_add
|
||||
@the_call["user-mod"] = ->user_mod
|
||||
@the_call["user-registration"] = ->user_registration # Do not require admin priviledges.
|
||||
@the_call["user-delete"] = ->user_deletion # Do not require admin priviledges.
|
||||
@the_call["user-get"] = ->user_get # Do not require authentication.
|
||||
@the_call["user-validation"] = ->user_validation # Do not require authentication.
|
||||
@the_call["user-recovery"] = ->user_recovery # Do not require authentication.
|
||||
@the_call["user-search"] = ->user_search # Do not require authentication.
|
||||
|
||||
@the_call["permission-set"] = ->permission_set
|
||||
@the_call["permission-check"] = ->permission_check
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# For all functions: the number of arguments is already tested.
|
||||
#
|
||||
|
||||
def user_add
|
||||
puts "User add!!!"
|
||||
args = Context.args.not_nil!
|
||||
login, email, phone = args[0..2]
|
||||
profile = Context.user_profile
|
||||
|
||||
password = Actions.ask_password
|
||||
exit 1 unless password
|
||||
|
||||
pp! authd.add_user login, password.not_nil!, email, phone, profile: profile
|
||||
rescue e : AuthD::Exception
|
||||
puts "error: #{e.message}"
|
||||
end
|
||||
|
||||
def user_registration
|
||||
args = Context.args.not_nil!
|
||||
login, email, phone = args[0..2]
|
||||
profile = Context.user_profile
|
||||
|
||||
password = Actions.ask_password
|
||||
exit 1 unless password
|
||||
|
||||
res = authd.register login, password.not_nil!, email, phone, profile: profile
|
||||
puts res
|
||||
rescue e
|
||||
puts "error: #{e.message}"
|
||||
end
|
||||
|
||||
# TODO
|
||||
def user_mod
|
||||
args = Context.args.not_nil!
|
||||
userid = args[0]
|
||||
|
||||
password : String? = nil
|
||||
|
||||
should_ask_password = Actions.ask_something "Should we change the password (Yn) ?" || "n"
|
||||
case should_ask_password
|
||||
when /y/i
|
||||
Baguette::Log.debug "Ok let's change the password!"
|
||||
password = Actions.ask_password
|
||||
exit 1 unless password
|
||||
else
|
||||
Baguette::Log.debug "Ok no change in password."
|
||||
end
|
||||
|
||||
email = Context.email
|
||||
phone = Context.phone
|
||||
|
||||
Baguette::Log.error "This function shouldn't be used for now."
|
||||
Baguette::Log.error "It is way too cumbersome."
|
||||
|
||||
# res = authd.add_user login, password, email, phone, profile: profile
|
||||
# puts res
|
||||
end
|
||||
|
||||
def user_deletion
|
||||
args = Context.args.not_nil!
|
||||
userid = args[0].to_i
|
||||
|
||||
# Check if the request comes from an admin or the user.
|
||||
res = if Context.shared_key.nil?
|
||||
authd.delete userid, Context.authd_login, Context.authd_pass
|
||||
else
|
||||
authd.delete userid, Context.shared_key
|
||||
end
|
||||
|
||||
puts res
|
||||
end
|
||||
|
||||
def user_validation
|
||||
args = Context.args.not_nil!
|
||||
login, activation_key = args[0..1]
|
||||
pp! authd.validate_user login, activation_key
|
||||
end
|
||||
def user_search
|
||||
args = Context.args.not_nil!
|
||||
login = args[0]
|
||||
pp! authd.search_user login
|
||||
end
|
||||
def user_get
|
||||
args = Context.args.not_nil!
|
||||
login = args[0]
|
||||
pp! authd.get_user? login
|
||||
end
|
||||
def user_recovery
|
||||
args = Context.args.not_nil!
|
||||
login, email = args[0..1]
|
||||
pp! authd.ask_password_recovery login, email
|
||||
end
|
||||
|
||||
def permission_check
|
||||
args = Context.args.not_nil!
|
||||
user, application, resource = args[0..2]
|
||||
# pp! user, application, resource
|
||||
|
||||
res = @authd.check_permission user.to_i, application, resource
|
||||
puts res
|
||||
end
|
||||
|
||||
def permission_set
|
||||
args = Context.args.not_nil!
|
||||
user, application, resource, permission = args[0..3]
|
||||
# pp! user, application, resource, permission
|
||||
|
||||
perm = AuthD::User::PermissionLevel.parse(permission)
|
||||
res = @authd.set_permission user.to_i, application, resource, perm
|
||||
puts res
|
||||
end
|
||||
end
|
||||
|
||||
def main
|
||||
|
||||
# Authd connection.
|
||||
authd = AuthD::Client.new
|
||||
authd.key = Context.shared_key if Context.shared_key != "undef"
|
||||
|
||||
# Authd token.
|
||||
# FIXME: not sure about getting the token, it seems not used elsewhere.
|
||||
# If login == pass == "undef": do not even try.
|
||||
#unless Context.authd_login == Context.authd_pass && Context.authd_login == "undef"
|
||||
# login = Context.authd_login
|
||||
# pass = Context.authd_pass
|
||||
# token = authd.get_token? login, pass
|
||||
# raise "cannot get a token" if token.nil?
|
||||
# # authd.login token
|
||||
#end
|
||||
|
||||
actions = Actions.new authd
|
||||
|
||||
# Now we did read the intent, we should proceed doing what was asked.
|
||||
begin
|
||||
actions.the_call[Context.command].call
|
||||
rescue e
|
||||
Baguette::Log.info "The command is not recognized (or implemented)."
|
||||
end
|
||||
|
||||
# authd disconnection
|
||||
authd.close
|
||||
rescue e
|
||||
Baguette::Log.info "Exception: #{e}"
|
||||
end
|
||||
|
||||
|
||||
# Command line:
|
||||
# tool [options] command [options-for-command]
|
||||
|
||||
main
|
|
@ -0,0 +1,230 @@
|
|||
require "option_parser"
|
||||
|
||||
opt_authd_admin = -> (parser : OptionParser) {
|
||||
parser.on "-k file", "--key-file file", "Read the authd shared key from a file." do |file|
|
||||
Context.shared_key = File.read(file).chomp
|
||||
Baguette::Log.info "Key for admin operations: #{Context.shared_key}."
|
||||
end
|
||||
}
|
||||
|
||||
# frequently used functions
|
||||
opt_authd_login = -> (parser : OptionParser) {
|
||||
parser.on "-l LOGIN", "--login LOGIN", "Authd user login." do |login|
|
||||
Context.authd_login = login
|
||||
Baguette::Log.info "User login for authd: #{Context.authd_login}."
|
||||
end
|
||||
parser.on "-p PASSWORD", "--password PASSWORD", "Authd user password." do |password|
|
||||
Context.authd_pass = password
|
||||
Baguette::Log.info "User password for authd: #{Context.authd_pass}."
|
||||
end
|
||||
}
|
||||
|
||||
opt_help = -> (parser : OptionParser) {
|
||||
parser.on "help", "Prints this help message." do
|
||||
puts parser
|
||||
exit 0
|
||||
end
|
||||
}
|
||||
|
||||
opt_profile = -> (parser : OptionParser) {
|
||||
parser.on "-P file", "--profile file", "Read the user profile from a file." do |file|
|
||||
Context.user_profile = JSON.parse(File.read file).as_h
|
||||
Baguette::Log.info "Reading the user profile: #{Context.user_profile}."
|
||||
end
|
||||
}
|
||||
|
||||
opt_phone = -> (parser : OptionParser) {
|
||||
parser.on "-n phone", "--phone-number num", "Phone number." do |phone|
|
||||
Context.phone = phone
|
||||
Baguette::Log.info "Reading the user phone number: #{Context.phone}."
|
||||
end
|
||||
}
|
||||
|
||||
opt_email = -> (parser : OptionParser) {
|
||||
parser.on "-e email", "--email address", "Email address." do |email|
|
||||
Context.email = email
|
||||
Baguette::Log.info "Reading the user email address: #{Context.email}."
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
# Unrecognized parameters are used to create commands with multiple arguments.
|
||||
# Example: user add _login email phone_
|
||||
# Here, login, email and phone are unrecognized arguments.
|
||||
# Still, the "user add" command expect them.
|
||||
unrecognized_args_to_context_args = -> (parser : OptionParser, n_expected_args : Int32) {
|
||||
# With the right args, these will be interpreted as serialized data.
|
||||
parser.unknown_args do |args|
|
||||
if args.size != n_expected_args
|
||||
Baguette::Log.error "expected number of arguments: #{n_expected_args}, received: #{args.size}"
|
||||
Baguette::Log.error "args: #{args}"
|
||||
Baguette::Log.error "#{parser}"
|
||||
exit 1
|
||||
end
|
||||
args.each do |arg|
|
||||
Baguette::Log.debug "Unrecognized argument: #{arg} (adding to Context.args)"
|
||||
if Context.args.nil?
|
||||
Context.args = Array(String).new
|
||||
end
|
||||
Context.args.not_nil! << arg
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
parser = OptionParser.new do |parser|
|
||||
parser.banner = "usage: #{PROGRAM_NAME} command help"
|
||||
parser.on "-v verbosity", "--verbosity v", "Verbosity. From 0 to 4 (debug)." do |v|
|
||||
Baguette::Context.verbosity = v.to_i
|
||||
Baguette::Log.info "verbosity = #{v}"
|
||||
end
|
||||
parser.on "-h", "--help", "Prints this help message." do
|
||||
puts "usage: #{PROGRAM_NAME} command help"
|
||||
puts parser
|
||||
exit 0
|
||||
end
|
||||
|
||||
parser.on "user", "Operations on users." do
|
||||
parser.banner = "Usage: user [add | mod | delete | validate | search | get | recover | register ]"
|
||||
|
||||
parser.on "add", "Adding a user to the DB." do
|
||||
parser.banner = "usage: user add login email phone [-P profile] [opt]"
|
||||
Baguette::Log.info "Adding a user to the DB."
|
||||
Context.command = "user-add"
|
||||
opt_authd_admin.call parser
|
||||
opt_profile.call parser
|
||||
opt_email.call parser
|
||||
opt_phone.call parser
|
||||
opt_help.call parser
|
||||
# login email phone
|
||||
unrecognized_args_to_context_args.call parser, 3
|
||||
end
|
||||
|
||||
parser.on "mod", "Modify a user account." do
|
||||
parser.banner = "Usage: user mod userid [-e email|-n phone|-P profile] [opt]"
|
||||
Baguette::Log.info "Modify a user account."
|
||||
Context.command = "user-mod"
|
||||
opt_authd_admin.call parser
|
||||
opt_email.call parser
|
||||
opt_phone.call parser
|
||||
opt_profile.call parser
|
||||
opt_help.call parser
|
||||
# userid
|
||||
unrecognized_args_to_context_args.call parser, 1
|
||||
end
|
||||
|
||||
parser.on "delete", "Remove user." do
|
||||
parser.banner = "Usage: user delete userid [opt]"
|
||||
Baguette::Log.info "Remove user."
|
||||
Context.command = "user-delete"
|
||||
# You can either be the owner of the account, or an admin.
|
||||
opt_authd_login.call parser
|
||||
opt_authd_admin.call parser
|
||||
opt_help.call parser
|
||||
# userid
|
||||
unrecognized_args_to_context_args.call parser, 1
|
||||
end
|
||||
|
||||
parser.on "validate", "Validate user." do
|
||||
parser.banner = "Usage: user validate login activation-key [opt]"
|
||||
Baguette::Log.info "Validate user."
|
||||
Context.command = "user-validate"
|
||||
# No need to be authenticated.
|
||||
opt_help.call parser
|
||||
# login activation-key
|
||||
unrecognized_args_to_context_args.call parser, 2
|
||||
end
|
||||
|
||||
parser.on "get", "Get user info." do
|
||||
parser.banner = "Usage: user get login [opt]"
|
||||
Baguette::Log.info "Get user info."
|
||||
Context.command = "user-get"
|
||||
# No need to be authenticated.
|
||||
opt_help.call parser
|
||||
# login
|
||||
unrecognized_args_to_context_args.call parser, 1
|
||||
end
|
||||
|
||||
parser.on "search", "Search user." do
|
||||
parser.banner = "Usage: user recover login [opt]"
|
||||
Baguette::Log.info "Search user."
|
||||
Context.command = "user-search"
|
||||
# No need to be authenticated.
|
||||
opt_help.call parser
|
||||
# login
|
||||
unrecognized_args_to_context_args.call parser, 1
|
||||
end
|
||||
|
||||
parser.on "recover", "Recover user password." do
|
||||
parser.banner = "Usage: user recover login email [opt]"
|
||||
Baguette::Log.info "Recover user password."
|
||||
Context.command = "user-recovery"
|
||||
# No need to be authenticated.
|
||||
opt_help.call parser
|
||||
# login email
|
||||
unrecognized_args_to_context_args.call parser, 2
|
||||
end
|
||||
|
||||
|
||||
# Do not require to be admin.
|
||||
parser.on "register", "Register a user (requires activation)." do
|
||||
parser.banner = "Usage: user register login email phone [-P profile] [opt]"
|
||||
Baguette::Log.info "Register a user (requires activation)."
|
||||
Context.command = "user-registration"
|
||||
# These options shouldn't be used here,
|
||||
# email and phone parameters are mandatory.
|
||||
# opt_email.call parser
|
||||
# opt_phone.call parser
|
||||
opt_profile.call parser
|
||||
opt_help.call parser
|
||||
# login email phone
|
||||
unrecognized_args_to_context_args.call parser, 3
|
||||
end
|
||||
end
|
||||
|
||||
parser.on "permission", "Permissions management." do
|
||||
parser.banner = "Usage: permissions [check | set]"
|
||||
parser.on "set", "Set permissions." do
|
||||
parser.banner = <<-END
|
||||
usage: permission set user application resource permission
|
||||
example: permission set 1002 my-application chat read
|
||||
|
||||
permission list: none read edit admin
|
||||
END
|
||||
Baguette::Log.info "Set permissions."
|
||||
Context.command = "permission-set"
|
||||
opt_authd_admin.call parser
|
||||
opt_help.call parser
|
||||
# userid application resource permission
|
||||
unrecognized_args_to_context_args.call parser, 4
|
||||
end
|
||||
|
||||
parser.on "check", "Check permissions." do
|
||||
parser.banner = <<-END
|
||||
usage: permission check user application resource
|
||||
example: permission check 1002 my-application chat
|
||||
|
||||
permission list: none read edit admin
|
||||
END
|
||||
Baguette::Log.info "Check permissions."
|
||||
Context.command = "permission-check"
|
||||
opt_authd_admin.call parser
|
||||
opt_help.call parser
|
||||
# userid application resource
|
||||
unrecognized_args_to_context_args.call parser, 3
|
||||
end
|
||||
end
|
||||
|
||||
parser.unknown_args do |args|
|
||||
if args.size > 0
|
||||
Baguette::Log.warning "Unknown args: #{args}"
|
||||
end
|
||||
end
|
||||
|
||||
# parser.on "-X user-password", "--user-password pass", "Read the new user password." do |pass|
|
||||
# password = pass
|
||||
# end
|
||||
|
||||
end
|
||||
|
||||
|
||||
parser.parse
|
|
@ -0,0 +1,239 @@
|
|||
extend AuthD
|
||||
|
||||
class Baguette::Configuration
|
||||
class Auth < IPC
|
||||
property recreate_indexes : Bool = false
|
||||
property storage : String = "storage"
|
||||
property registrations : Bool = false
|
||||
property require_email : Bool = false
|
||||
property activation_template : String = "email-activation"
|
||||
property recovery_template : String = "email-recovery"
|
||||
property mailer_exe : String = "mailer"
|
||||
property read_only_profile_keys : Array(String) = Array(String).new
|
||||
|
||||
property print_password_recovery_parameters : Bool = false
|
||||
end
|
||||
end
|
||||
|
||||
# Provides a JWT-based authentication scheme for service-specific users.
|
||||
class AuthD::Service < IPC
|
||||
property configuration : Baguette::Configuration::Auth
|
||||
|
||||
# DB and its indexes.
|
||||
property users : DODB::DataBase(User)
|
||||
property users_per_uid : DODB::Index(User)
|
||||
property users_per_login : DODB::Index(User)
|
||||
|
||||
# #{@configuration.storage}/last_used_uid
|
||||
property last_uid_file : String
|
||||
|
||||
def initialize(@configuration)
|
||||
super()
|
||||
|
||||
@users = DODB::DataBase(User).new @configuration.storage
|
||||
@users_per_uid = @users.new_index "uid", &.uid.to_s
|
||||
@users_per_login = @users.new_index "login", &.login
|
||||
|
||||
@last_uid_file = "#{@configuration.storage}/last_used_uid"
|
||||
|
||||
if @configuration.recreate_indexes
|
||||
@users.reindex_everything!
|
||||
end
|
||||
|
||||
self.timer @configuration.ipc_timer
|
||||
self.service_init "auth"
|
||||
end
|
||||
|
||||
def hash_password(password : String) : String
|
||||
digest = OpenSSL::Digest.new "sha256"
|
||||
digest << password
|
||||
digest.hexfinal
|
||||
end
|
||||
|
||||
# new_uid reads the last given UID and returns it incremented.
|
||||
# Splitting the retrieval and record of new user ids allows to
|
||||
# only increment when an user fully registers, thus avoiding a
|
||||
# Denial of Service attack.
|
||||
#
|
||||
# WARNING: to record this new UID, new_uid_commit must be called.
|
||||
# WARNING: new_uid isn't thread safe.
|
||||
def new_uid
|
||||
begin
|
||||
uid = File.read(@last_uid_file).to_i
|
||||
rescue
|
||||
uid = 999
|
||||
end
|
||||
|
||||
uid += 1
|
||||
end
|
||||
|
||||
# new_uid_commit records the new UID.
|
||||
# WARNING: new_uid_commit isn't thread safe.
|
||||
def new_uid_commit(uid : Int)
|
||||
File.write @last_uid_file, uid.to_s
|
||||
end
|
||||
|
||||
def handle_request(event : IPC::Event)
|
||||
request_start = Time.utc
|
||||
|
||||
array = event.message.not_nil!
|
||||
slice = Slice.new array.to_unsafe, array.size
|
||||
message = IPCMessage::TypedMessage.deserialize slice
|
||||
request = AuthD.requests.parse_ipc_json message.not_nil!
|
||||
|
||||
if request.nil?
|
||||
raise "unknown request type"
|
||||
end
|
||||
|
||||
request_name = request.class.name.sub /^AuthD::Request::/, ""
|
||||
Baguette::Log.debug "<< #{request_name}"
|
||||
|
||||
response = begin
|
||||
request.handle self
|
||||
rescue e : UserNotFound
|
||||
Baguette::Log.error "#{request_name} user not found"
|
||||
AuthD::Response::Error.new "authorization error"
|
||||
rescue e : AuthenticationInfoLacking
|
||||
Baguette::Log.error "#{request_name} lacking authentication info"
|
||||
AuthD::Response::Error.new "authorization error"
|
||||
rescue e : AdminAuthorizationException
|
||||
Baguette::Log.error "#{request_name} admin authentication failed"
|
||||
AuthD::Response::Error.new "authorization error"
|
||||
rescue e
|
||||
Baguette::Log.error "#{request_name} generic error #{e}"
|
||||
AuthD::Response::Error.new "unknown error"
|
||||
end
|
||||
|
||||
# If clients sent requests with an “id” field, it is copied
|
||||
# in the responses. Allows identifying responses easily.
|
||||
response.id = request.id
|
||||
|
||||
schedule event.fd, response
|
||||
|
||||
duration = Time.utc - request_start
|
||||
|
||||
response_name = response.class.name.sub /^AuthD::Response::/, ""
|
||||
|
||||
if response.is_a? AuthD::Response::Error
|
||||
Baguette::Log.warning ">> #{response_name} (#{response.reason})"
|
||||
else
|
||||
Baguette::Log.debug ">> #{response_name} (Total duration: #{duration})"
|
||||
end
|
||||
end
|
||||
|
||||
def get_user_from_token(token : String)
|
||||
token_payload = Token.from_s(@configuration.shared_key, token)
|
||||
|
||||
@users_per_uid.get? token_payload.uid.to_s
|
||||
end
|
||||
|
||||
def run
|
||||
Baguette::Log.title "Starting authd"
|
||||
|
||||
Baguette::Log.info "(mailer) Email activation template: #{@configuration.activation_template}"
|
||||
Baguette::Log.info "(mailer) Email recovery template: #{@configuration.recovery_template}"
|
||||
|
||||
self.loop do |event|
|
||||
case event.type
|
||||
when LibIPC::EventType::Timer
|
||||
Baguette::Log.debug "Timer" if @configuration.print_ipc_timer
|
||||
|
||||
when LibIPC::EventType::MessageRx
|
||||
Baguette::Log.debug "Received message from #{event.fd}" if @configuration.print_ipc_message_received
|
||||
begin
|
||||
handle_request event
|
||||
rescue e
|
||||
Baguette::Log.error "#{e.message}"
|
||||
# send event.fd, Response::Error.new e.message
|
||||
end
|
||||
|
||||
when LibIPC::EventType::MessageTx
|
||||
Baguette::Log.debug "Message sent to #{event.fd}" if @configuration.print_ipc_message_sent
|
||||
|
||||
when LibIPC::EventType::Connection
|
||||
Baguette::Log.debug "Connection from #{event.fd}" if @configuration.print_ipc_connection
|
||||
when LibIPC::EventType::Disconnection
|
||||
Baguette::Log.debug "Disconnection from #{event.fd}" if @configuration.print_ipc_disconnection
|
||||
else
|
||||
Baguette::Log.error "Not implemented behavior for event: #{event}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
begin
|
||||
simulation, no_configuration, configuration_file = Baguette::Configuration.option_parser
|
||||
|
||||
configuration = if no_configuration
|
||||
Baguette::Log.info "do not load a configuration file."
|
||||
Baguette::Configuration::Auth.new
|
||||
else
|
||||
Baguette::Configuration::Auth.get(configuration_file) ||
|
||||
Baguette::Configuration::Auth.new
|
||||
end
|
||||
|
||||
Baguette::Context.verbosity = configuration.verbosity
|
||||
|
||||
if key_file = configuration.shared_key_file
|
||||
configuration.shared_key = File.read(key_file).chomp
|
||||
end
|
||||
|
||||
OptionParser.parse do |parser|
|
||||
parser.banner = "usage: authd [options]"
|
||||
|
||||
parser.on "--storage directory", "Directory in which to store users." do |directory|
|
||||
configuration.storage = directory
|
||||
end
|
||||
|
||||
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
|
||||
configuration.shared_key = File.read(file_name).chomp
|
||||
end
|
||||
|
||||
parser.on "-R", "--allow-registrations", "Allow user registration." do
|
||||
configuration.registrations = true
|
||||
end
|
||||
|
||||
parser.on "-E", "--require-email", "Require an email." do
|
||||
configuration.require_email = true
|
||||
end
|
||||
|
||||
parser.on "-t activation-template-name", "--activation-template name", "Email activation template." do |opt|
|
||||
configuration.activation_template = opt
|
||||
end
|
||||
|
||||
parser.on "-r recovery-template-name", "--recovery-template name", "Email recovery template." do |opt|
|
||||
configuration.recovery_template = opt
|
||||
end
|
||||
|
||||
parser.on "-m mailer-exe", "--mailer mailer-exe", "Application to send registration emails." do |opt|
|
||||
configuration.mailer_exe = opt
|
||||
end
|
||||
|
||||
|
||||
parser.on "-x key", "--read-only-profile-key key", "Marks a user profile key as being read-only." do |key|
|
||||
configuration.read_only_profile_keys.push key
|
||||
end
|
||||
|
||||
parser.on "-h", "--help", "Show this help" do
|
||||
puts parser
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
||||
if simulation
|
||||
pp! configuration
|
||||
exit 0
|
||||
end
|
||||
|
||||
AuthD::Service.new(configuration).run
|
||||
|
||||
rescue e : OptionParser::Exception
|
||||
Baguette::Log.error e.message
|
||||
rescue e
|
||||
Baguette::Log.error "exception raised: #{e.message}"
|
||||
e.backtrace.try &.each do |line|
|
||||
STDERR << " - " << line << '\n'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,261 @@
|
|||
require "../../src/json"
|
||||
require "json"
|
||||
|
||||
module AuthD
|
||||
class Client < IPC
|
||||
property key : String
|
||||
property server_fd : Int32 = -1
|
||||
|
||||
def initialize
|
||||
super
|
||||
@key = ""
|
||||
fd = self.connect "auth"
|
||||
if fd.nil?
|
||||
raise "couldn't connect to 'auth' IPC service"
|
||||
end
|
||||
@server_fd = fd
|
||||
end
|
||||
|
||||
def read
|
||||
slice = self.read @server_fd
|
||||
m = IPCMessage::TypedMessage.deserialize slice
|
||||
m.not_nil!
|
||||
end
|
||||
|
||||
def get_token?(login : String, password : String) : String?
|
||||
send_now Request::GetToken.new login, password
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
if response.is_a?(Response::Token)
|
||||
response.token
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_user?(login : String, password : String) : AuthD::User::Public?
|
||||
send_now Request::GetUserByCredentials.new login, password
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
if response.is_a? Response::User
|
||||
response.user
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_user?(uid_or_login : Int32 | String) : ::AuthD::User::Public?
|
||||
send_now Request::GetUser.new uid_or_login
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
if response.is_a? Response::User
|
||||
response.user
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def send_now(msg : IPC::JSON)
|
||||
m = IPCMessage::TypedMessage.new msg.type.to_u8, msg.to_json
|
||||
write @server_fd, m
|
||||
end
|
||||
|
||||
def send_now(type : Request::Type, payload)
|
||||
m = IPCMessage::TypedMessage.new type.value.to_u8, payload
|
||||
write @server_fd, m
|
||||
end
|
||||
|
||||
def decode_token(token)
|
||||
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
|
||||
|
||||
user = ::AuthD::User::Public.from_json user.to_json
|
||||
|
||||
{user, meta}
|
||||
end
|
||||
|
||||
# FIXME: Extra options may be useful to implement here.
|
||||
def add_user(login : String, password : String,
|
||||
email : String?,
|
||||
phone : String?,
|
||||
profile : Hash(String, ::JSON::Any)?) : ::AuthD::User::Public | Exception
|
||||
|
||||
send_now Request::AddUser.new @key, login, password, email, phone, profile
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::UserAdded
|
||||
response.user
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
# Should not happen in serialized connections, but…
|
||||
# it’ll happen if you run several requests at once.
|
||||
Exception.new
|
||||
end
|
||||
end
|
||||
|
||||
def validate_user(login : String, activation_key : String) : ::AuthD::User::Public | Exception
|
||||
send_now Request::ValidateUser.new login, activation_key
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::UserValidated
|
||||
response.user
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
# Should not happen in serialized connections, but…
|
||||
# it’ll happen if you run several requests at once.
|
||||
Exception.new
|
||||
end
|
||||
end
|
||||
|
||||
def ask_password_recovery(uid_or_login : String | Int32, email : String)
|
||||
send_now Request::AskPasswordRecovery.new uid_or_login, email
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::PasswordRecoverySent
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
Exception.new
|
||||
end
|
||||
end
|
||||
|
||||
def change_password(uid_or_login : String | Int32, new_pass : String, renew_key : String)
|
||||
send_now Request::PasswordRecovery.new uid_or_login, renew_key, new_pass
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::PasswordRecovered
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
Exception.new
|
||||
end
|
||||
end
|
||||
|
||||
def register(login : String,
|
||||
password : String,
|
||||
email : String?,
|
||||
phone : String?,
|
||||
profile : Hash(String, ::JSON::Any)?) : ::AuthD::User::Public?
|
||||
|
||||
send_now Request::Register.new login, password, email, phone, profile
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::UserAdded
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
end
|
||||
end
|
||||
|
||||
def mod_user(uid_or_login : Int32 | String, password : String? = nil, email : String? = nil, phone : String? = nil, avatar : String? = nil) : Bool | Exception
|
||||
request = Request::ModUser.new @key, uid_or_login
|
||||
|
||||
request.password = password if password
|
||||
request.email = email if email
|
||||
request.phone = phone if phone
|
||||
request.avatar = avatar if avatar
|
||||
|
||||
send_now request
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::UserEdited
|
||||
true
|
||||
when Response::Error
|
||||
Exception.new response.reason
|
||||
else
|
||||
Exception.new "???"
|
||||
end
|
||||
end
|
||||
|
||||
def check_permission(user : Int32, service_name : String, resource_name : String) : User::PermissionLevel
|
||||
request = Request::CheckPermission.new @key, user, service_name, resource_name
|
||||
|
||||
send_now request
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::PermissionCheck
|
||||
response.permission
|
||||
when Response
|
||||
raise Exception.new "unexpected response: #{response.type}"
|
||||
else
|
||||
raise Exception.new "unexpected response"
|
||||
end
|
||||
end
|
||||
|
||||
def set_permission(uid : Int32, service : String, resource : String, permission : User::PermissionLevel)
|
||||
request = Request::SetPermission.new @key, uid, service, resource, permission
|
||||
|
||||
send_now request
|
||||
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::PermissionSet
|
||||
true
|
||||
when Response
|
||||
raise Exception.new "unexpected response: #{response.type}"
|
||||
else
|
||||
raise Exception.new "unexpected response"
|
||||
end
|
||||
end
|
||||
|
||||
def search_user(user_login : String)
|
||||
send_now Request::SearchUser.new user_login
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::MatchingUsers
|
||||
response.users
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
Exception.new
|
||||
end
|
||||
end
|
||||
|
||||
def edit_profile_content(user : Int32 | String, new_values)
|
||||
send_now Request::EditProfileContent.new key, user, new_values
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
|
||||
case response
|
||||
when Response::User
|
||||
response.user
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
else
|
||||
raise Exception.new "unexpected response"
|
||||
end
|
||||
end
|
||||
|
||||
def delete(user : Int32 | String, key : String)
|
||||
send_now Request::Delete.new user, key
|
||||
delete_
|
||||
end
|
||||
def delete(user : Int32 | String, login : String, pass : String)
|
||||
send_now Request::Delete.new user, login, pass
|
||||
delete_
|
||||
end
|
||||
def delete_
|
||||
response = AuthD.responses.parse_ipc_json read
|
||||
case response
|
||||
when Response::Error
|
||||
raise Exception.new response.reason
|
||||
end
|
||||
response
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module AuthD
|
||||
class Exception < ::Exception
|
||||
end
|
||||
|
||||
class UserNotFound < ::Exception
|
||||
end
|
||||
|
||||
class AuthenticationInfoLacking < ::Exception
|
||||
end
|
||||
|
||||
class AdminAuthorizationException < ::Exception
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
require "json"
|
||||
|
||||
class AuthD::Token
|
||||
include JSON::Serializable
|
||||
|
||||
property login : String
|
||||
property uid : Int32
|
||||
|
||||
def initialize(@login, @uid)
|
||||
end
|
||||
|
||||
def to_h
|
||||
{
|
||||
:login => login,
|
||||
:uid => uid
|
||||
}
|
||||
end
|
||||
|
||||
def to_s(key)
|
||||
JWT.encode to_h, key, JWT::Algorithm::HS256
|
||||
end
|
||||
|
||||
def self.from_s(key, str)
|
||||
payload, meta = JWT.decode str, key, JWT::Algorithm::HS256
|
||||
|
||||
self.new payload["login"].as_s, payload["uid"].as_i
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
require "json"
|
||||
require "uuid"
|
||||
|
||||
class AuthD::User
|
||||
include JSON::Serializable
|
||||
|
||||
enum PermissionLevel
|
||||
None
|
||||
Read
|
||||
Edit
|
||||
Admin
|
||||
|
||||
def to_json(o)
|
||||
to_s.downcase.to_json o
|
||||
end
|
||||
end
|
||||
|
||||
class Contact
|
||||
include JSON::Serializable
|
||||
|
||||
# the activation key is removed once the user is validated
|
||||
property activation_key : String?
|
||||
property email : String?
|
||||
property phone : String?
|
||||
|
||||
def initialize(@email = nil, @phone = nil)
|
||||
@activation_key = UUID.random.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Public.
|
||||
property login : String
|
||||
property uid : Int32
|
||||
property profile : Hash(String, JSON::Any)?
|
||||
|
||||
# Private.
|
||||
property contact : Contact
|
||||
property password_hash : String
|
||||
property password_renew_key : String?
|
||||
# service => resource => permission level
|
||||
property permissions : Hash(String, Hash(String, PermissionLevel))
|
||||
property configuration : Hash(String, Hash(String, JSON::Any))
|
||||
property date_last_connection : Time? = nil
|
||||
property date_registration : Time? = nil
|
||||
|
||||
def to_token
|
||||
Token.new @login, @uid
|
||||
end
|
||||
|
||||
def initialize(@uid, @login, @password_hash)
|
||||
@contact = Contact.new
|
||||
@permissions = Hash(String, Hash(String, PermissionLevel)).new
|
||||
@configuration = Hash(String, Hash(String, JSON::Any)).new
|
||||
end
|
||||
|
||||
class Public
|
||||
include JSON::Serializable
|
||||
|
||||
property login : String
|
||||
property uid : Int32
|
||||
property profile : Hash(String, JSON::Any)?
|
||||
|
||||
property date_registration : Time?
|
||||
|
||||
def initialize(@uid, @login, @profile, @date_registration)
|
||||
end
|
||||
end
|
||||
|
||||
def to_public : Public
|
||||
Public.new @uid, @login, @profile, @date_registration
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
require "json"
|
||||
require "jwt"
|
||||
require "../src/main.cr"
|
||||
|
||||
require "baguette-crystal-base"
|
||||
|
||||
# Allows get configuration from a provided file.
|
||||
# See Baguette::Configuration::Base.get
|
||||
class Baguette::Configuration
|
||||
class Auth < IPC
|
||||
include YAML::Serializable
|
||||
|
||||
property login : String? = nil
|
||||
property pass : String? = nil
|
||||
property shared_key : String = "nico-nico-nii" # Default authd key, as per the specs. :eyes:
|
||||
property shared_key_file : String? = nil
|
||||
|
||||
def initialize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Token and user classes.
|
||||
require "./authd/token.cr"
|
||||
require "./authd/user.cr"
|
||||
|
||||
# Requests and responses.
|
||||
require "./authd/exceptions"
|
||||
|
||||
# Requests and responses.
|
||||
require "./network"
|
||||
|
||||
# Functions to request the authd server.
|
||||
require "./authd/client.cr"
|
|
@ -0,0 +1,13 @@
|
|||
require "uuid"
|
||||
require "option_parser"
|
||||
require "openssl"
|
||||
require "colorize"
|
||||
require "jwt"
|
||||
require "grok"
|
||||
|
||||
require "dodb"
|
||||
require "baguette-crystal-base"
|
||||
|
||||
require "../src/main.cr"
|
||||
require "./libauth.cr"
|
||||
require "./authd.cr"
|
|
@ -0,0 +1,22 @@
|
|||
require "../src/main.cr"
|
||||
require "../src/json"
|
||||
|
||||
class IPC::JSON
|
||||
def handle(service : AuthD::Service)
|
||||
raise "unimplemented"
|
||||
end
|
||||
end
|
||||
|
||||
module AuthD
|
||||
class_getter requests = [] of IPC::JSON.class
|
||||
class_getter responses = [] of IPC::JSON.class
|
||||
end
|
||||
|
||||
class IPC
|
||||
def schedule(fd, m : (AuthD::Request | AuthD::Response))
|
||||
schedule fd, m.type.to_u8, m.to_json
|
||||
end
|
||||
end
|
||||
|
||||
require "./requests/*"
|
||||
require "./responses/*"
|
|
@ -0,0 +1,100 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message AddUser, 1 do
|
||||
# Only clients that have the right shared key will be allowed
|
||||
# to create users.
|
||||
property shared_key : String
|
||||
|
||||
property login : String
|
||||
property password : String
|
||||
property email : String? = nil
|
||||
property phone : String? = nil
|
||||
property profile : Hash(String, JSON::Any)? = nil
|
||||
|
||||
def initialize(@shared_key, @login, @password, @email, @phone, @profile)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
# No verification of the users' informations when an admin adds it.
|
||||
# No mail address verification.
|
||||
if @shared_key != authd.configuration.shared_key
|
||||
return Response::Error.new "invalid authentication key"
|
||||
end
|
||||
|
||||
if authd.users_per_login.get? @login
|
||||
return Response::Error.new "login already used"
|
||||
end
|
||||
|
||||
if authd.configuration.require_email && @email.nil?
|
||||
return Response::Error.new "email required"
|
||||
end
|
||||
|
||||
password_hash = authd.hash_password @password
|
||||
|
||||
uid = authd.new_uid
|
||||
|
||||
user = User.new uid, @login, password_hash
|
||||
user.contact.email = @email unless @email.nil?
|
||||
user.contact.phone = @phone unless @phone.nil?
|
||||
|
||||
@profile.try do |profile|
|
||||
user.profile = profile
|
||||
end
|
||||
|
||||
# We consider adding the user as a registration
|
||||
user.date_registration = Time.local
|
||||
|
||||
authd.users << user
|
||||
authd.new_uid_commit uid
|
||||
Response::UserAdded.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << AddUser
|
||||
|
||||
|
||||
IPC::JSON.message ModUser, 5 do
|
||||
property shared_key : String
|
||||
|
||||
property user : Int32 | String
|
||||
property password : String? = nil
|
||||
property email : String? = nil
|
||||
property phone : String? = nil
|
||||
property avatar : String? = nil
|
||||
|
||||
def initialize(@shared_key, @user)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
if @shared_key != authd.configuration.shared_key
|
||||
return Response::Error.new "invalid authentication key"
|
||||
end
|
||||
|
||||
uid_or_login = @user
|
||||
user = if uid_or_login.is_a? Int32
|
||||
authd.users_per_uid.get? uid_or_login.to_s
|
||||
else
|
||||
authd.users_per_login.get? uid_or_login
|
||||
end
|
||||
|
||||
unless user
|
||||
return Response::Error.new "user not found"
|
||||
end
|
||||
|
||||
@password.try do |s|
|
||||
user.password_hash = authd.hash_password s
|
||||
end
|
||||
|
||||
@email.try do |email|
|
||||
user.contact.email = email
|
||||
end
|
||||
|
||||
@phone.try do |phone|
|
||||
user.contact.phone = phone
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::UserEdited.new user.uid
|
||||
end
|
||||
end
|
||||
AuthD.requests << ModUser
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message EditContacts, 16 do
|
||||
property token : String
|
||||
|
||||
property email : String? = nil
|
||||
property phone : String? = nil
|
||||
|
||||
def initialize(@token)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.get_user_from_token @token
|
||||
|
||||
return Response::Error.new "invalid user" unless user
|
||||
|
||||
if email = @email
|
||||
# FIXME: This *should* require checking the new mail, with
|
||||
# a new activation key and everything else.
|
||||
user.contact.email = email
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user
|
||||
|
||||
Response::UserEdited.new user.uid
|
||||
end
|
||||
end
|
||||
AuthD.requests << EditContacts
|
||||
|
||||
IPC::JSON.message GetContacts, 18 do
|
||||
property token : String
|
||||
|
||||
def initialize(@token)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.get_user_from_token @token
|
||||
|
||||
return Response::Error.new "invalid user" unless user
|
||||
|
||||
_c = user.contact
|
||||
|
||||
Response::Contacts.new user.uid, _c.email, _c.phone
|
||||
end
|
||||
end
|
||||
AuthD.requests << GetContacts
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message Delete, 17 do
|
||||
# Deletion can be triggered by either an admin or the user.
|
||||
property shared_key : String? = nil
|
||||
|
||||
property login : String? = nil
|
||||
property password : String? = nil
|
||||
|
||||
property user : String | Int32
|
||||
|
||||
def initialize(@user, @login, @password)
|
||||
end
|
||||
def initialize(@user, @shared_key)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
uid_or_login = @user
|
||||
user_to_delete = if uid_or_login.is_a? Int32
|
||||
authd.users_per_uid.get? uid_or_login.to_s
|
||||
else
|
||||
authd.users_per_login.get? uid_or_login
|
||||
end
|
||||
|
||||
if user_to_delete.nil?
|
||||
return Response::Error.new "invalid user"
|
||||
end
|
||||
|
||||
# Either the request comes from an admin or the user.
|
||||
# Shared key == admin, check the key.
|
||||
if key = @shared_key
|
||||
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
|
||||
else
|
||||
login = @login
|
||||
pass = @password
|
||||
if login.nil? || pass.nil?
|
||||
return Response::Error.new "authentication failed (no shared key, no login)"
|
||||
end
|
||||
|
||||
# authenticate the user
|
||||
begin
|
||||
user = authd.users_per_login.get login
|
||||
rescue e : DODB::MissingEntry
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if user.password_hash != authd.hash_password pass
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
# Is the user to delete the requesting user?
|
||||
if user.uid != user_to_delete.uid
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
end
|
||||
|
||||
# User or admin is now verified: let's proceed with the user deletion.
|
||||
authd.users_per_login.delete user_to_delete.login
|
||||
|
||||
# TODO: better response
|
||||
Response::User.new user_to_delete.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << Delete
|
||||
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message ListUsers, 8 do
|
||||
property token : String? = nil
|
||||
property key : String? = nil
|
||||
|
||||
def initialize(@token, @key)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
|
||||
@token.try do |token|
|
||||
user = authd.get_user_from_token token
|
||||
|
||||
return Response::Error.new "unauthorized (user not found from token)" unless user
|
||||
|
||||
# Test if the user is a moderator.
|
||||
if permissions = user.permissions["authd"]?
|
||||
if rights = permissions["*"]?
|
||||
if rights >= User::PermissionLevel::Read
|
||||
else
|
||||
raise AdminAuthorizationException.new "unauthorized (insufficient rights on '*')"
|
||||
end
|
||||
else
|
||||
raise AdminAuthorizationException.new "unauthorized (no rights on '*')"
|
||||
end
|
||||
else
|
||||
raise AdminAuthorizationException.new "unauthorized (user not in authd group)"
|
||||
end
|
||||
end
|
||||
|
||||
@key.try do |key|
|
||||
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
|
||||
end
|
||||
|
||||
return Response::Error.new "unauthorized (no key nor token)" unless @key || @token
|
||||
|
||||
Response::UsersList.new authd.users.to_h.map &.[1].to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << ListUsers
|
||||
end
|
|
@ -0,0 +1,122 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message UpdatePassword, 7 do
|
||||
property login : String
|
||||
property old_password : String
|
||||
property new_password : String
|
||||
|
||||
def initialize(@login, @old_password, @new_password)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.users_per_login.get? @login
|
||||
|
||||
unless user
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if authd.hash_password(@old_password) != user.password_hash
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
user.password_hash = authd.hash_password @new_password
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::UserEdited.new user.uid
|
||||
end
|
||||
end
|
||||
AuthD.requests << UpdatePassword
|
||||
|
||||
IPC::JSON.message PasswordRecovery, 11 do
|
||||
property user : Int32 | String
|
||||
property password_renew_key : String
|
||||
property new_password : String
|
||||
|
||||
def initialize(@user, @password_renew_key, @new_password)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
uid_or_login = @user
|
||||
user = if uid_or_login.is_a? Int32
|
||||
authd.users_per_uid.get? uid_or_login.to_s
|
||||
else
|
||||
authd.users_per_login.get? uid_or_login
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "user not found"
|
||||
end
|
||||
|
||||
if user.password_renew_key == @password_renew_key
|
||||
user.password_hash = authd.hash_password @new_password
|
||||
else
|
||||
return Response::Error.new "renew key not valid"
|
||||
end
|
||||
|
||||
user.password_renew_key = nil
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::PasswordRecovered.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << PasswordRecovery
|
||||
|
||||
IPC::JSON.message AskPasswordRecovery, 12 do
|
||||
property user : Int32 | String
|
||||
property email : String
|
||||
|
||||
def initialize(@user, @email)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
uid_or_login = @user
|
||||
user = if uid_or_login.is_a? Int32
|
||||
authd.users_per_uid.get? uid_or_login.to_s
|
||||
else
|
||||
authd.users_per_login.get? uid_or_login
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "no such user"
|
||||
end
|
||||
|
||||
if user.contact.email != @email
|
||||
# Same error as when users are not found.
|
||||
return Response::Error.new "no such user"
|
||||
end
|
||||
|
||||
user.password_renew_key = UUID.random.to_s
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
# Once the user is created and stored, we try to contact him
|
||||
if authd.configuration.print_password_recovery_parameters
|
||||
pp! user.login,
|
||||
user.contact.email.not_nil!,
|
||||
user.password_renew_key.not_nil!
|
||||
end
|
||||
|
||||
mailer_exe = authd.configuration.mailer_exe
|
||||
template_name = authd.configuration.recovery_template
|
||||
|
||||
u_login = user.login
|
||||
u_email = user.contact.email.not_nil!
|
||||
u_token = user.password_renew_key.not_nil!
|
||||
|
||||
# Once the user is created and stored, we try to contact him.
|
||||
unless Process.run(mailer_exe,
|
||||
# PARAMETERS
|
||||
[ "send", template_name, u_email ],
|
||||
# ENV
|
||||
{ "LOGIN" => u_login, "TOKEN" => u_token },
|
||||
true # clear environment
|
||||
).success?
|
||||
raise "cannot contact user #{u_login} address #{u_email}"
|
||||
end
|
||||
|
||||
Response::PasswordRecoverySent.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << AskPasswordRecovery
|
||||
end
|
|
@ -0,0 +1,113 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message CheckPermission, 9 do
|
||||
property shared_key : String? = nil
|
||||
property token : String? = nil
|
||||
|
||||
property user : Int32 | String
|
||||
property service : String
|
||||
property resource : String
|
||||
|
||||
def initialize(@shared_key, @user, @service, @resource)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
authorized = false
|
||||
|
||||
if key = @shared_key
|
||||
if key == authd.configuration.shared_key
|
||||
authorized = true
|
||||
else
|
||||
return Response::Error.new "invalid key provided"
|
||||
end
|
||||
end
|
||||
|
||||
if token = @token
|
||||
user = authd.get_user_from_token token
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "token does not match user"
|
||||
end
|
||||
|
||||
if user.login != @user && user.uid != @user
|
||||
return Response::Error.new "token does not match user"
|
||||
end
|
||||
|
||||
authorized = true
|
||||
end
|
||||
|
||||
unless authorized
|
||||
return Response::Error.new "unauthorized"
|
||||
end
|
||||
|
||||
user = case u = @user
|
||||
when .is_a? Int32
|
||||
authd.users_per_uid.get? u.to_s
|
||||
else
|
||||
authd.users_per_login.get? u
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "no such user"
|
||||
end
|
||||
|
||||
service = @service
|
||||
service_permissions = user.permissions[service]?
|
||||
|
||||
if service_permissions.nil?
|
||||
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
|
||||
end
|
||||
|
||||
resource_permissions = service_permissions[@resource]?
|
||||
|
||||
if resource_permissions.nil?
|
||||
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
|
||||
end
|
||||
|
||||
return Response::PermissionCheck.new service, @resource, user.uid, resource_permissions
|
||||
end
|
||||
end
|
||||
AuthD.requests << CheckPermission
|
||||
|
||||
IPC::JSON.message SetPermission, 10 do
|
||||
property shared_key : String
|
||||
|
||||
property user : Int32 | String
|
||||
property service : String
|
||||
property resource : String
|
||||
property permission : ::AuthD::User::PermissionLevel
|
||||
|
||||
def initialize(@shared_key, @user, @service, @resource, @permission)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
unless @shared_key == authd.configuration.shared_key
|
||||
return Response::Error.new "unauthorized"
|
||||
end
|
||||
|
||||
user = authd.users_per_uid.get? @user.to_s
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "no such user"
|
||||
end
|
||||
|
||||
service = @service
|
||||
service_permissions = user.permissions[service]?
|
||||
|
||||
if service_permissions.nil?
|
||||
service_permissions = Hash(String, User::PermissionLevel).new
|
||||
user.permissions[service] = service_permissions
|
||||
end
|
||||
|
||||
if @permission.none?
|
||||
service_permissions.delete @resource
|
||||
else
|
||||
service_permissions[@resource] = @permission
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::PermissionSet.new user.uid, service, @resource, @permission
|
||||
end
|
||||
end
|
||||
AuthD.requests << SetPermission
|
||||
end
|
|
@ -0,0 +1,93 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message EditProfile, 14 do
|
||||
property token : String
|
||||
property new_profile : Hash(String, JSON::Any)
|
||||
|
||||
def initialize(@token, @new_profile)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.get_user_from_token @token
|
||||
|
||||
return Response::Error.new "invalid user" unless user
|
||||
|
||||
new_profile = @new_profile
|
||||
|
||||
profile = user.profile || Hash(String, JSON::Any).new
|
||||
|
||||
authd.configuration.read_only_profile_keys.each do |key|
|
||||
if new_profile[key]? != profile[key]?
|
||||
return Response::Error.new "tried to edit read only key"
|
||||
end
|
||||
end
|
||||
|
||||
user.profile = new_profile
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::User.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << EditProfile
|
||||
|
||||
# Same as above, but doesn’t reset the whole profile, only resets elements
|
||||
# for which keys are present in `new_profile`.
|
||||
IPC::JSON.message EditProfileContent, 15 do
|
||||
property token : String? = nil
|
||||
|
||||
property shared_key : String? = nil
|
||||
property user : Int32 | String | Nil
|
||||
|
||||
property new_profile : Hash(String, JSON::Any)
|
||||
|
||||
def initialize(@shared_key, @user, @new_profile)
|
||||
end
|
||||
def initialize(@token, @new_profile)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = if token = @token
|
||||
u = authd.get_user_from_token token
|
||||
raise UserNotFound.new unless u
|
||||
u
|
||||
elsif shared_key = @shared_key
|
||||
raise AdminAuthorizationException.new if shared_key != authd.configuration.shared_key
|
||||
|
||||
u = @user
|
||||
raise UserNotFound.new unless u
|
||||
|
||||
u = if u.is_a? Int32
|
||||
authd.users_per_uid.get? u.to_s
|
||||
else
|
||||
authd.users_per_login.get? u
|
||||
end
|
||||
raise UserNotFound.new unless u
|
||||
|
||||
u
|
||||
else
|
||||
raise AuthenticationInfoLacking.new
|
||||
end
|
||||
|
||||
new_profile = user.profile || Hash(String, JSON::Any).new
|
||||
|
||||
unless @shared_key
|
||||
authd.configuration.read_only_profile_keys.each do |key|
|
||||
if @new_profile.has_key? key
|
||||
return Response::Error.new "tried to edit read only key"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@new_profile.each do |key, value|
|
||||
new_profile[key] = value
|
||||
end
|
||||
|
||||
user.profile = new_profile
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::User.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << EditProfileContent
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message Register, 6 do
|
||||
property login : String
|
||||
property password : String
|
||||
property email : String? = nil
|
||||
property phone : String? = nil
|
||||
property profile : Hash(String, JSON::Any)? = nil
|
||||
|
||||
def initialize(@login, @password, @email, @phone, @profile)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
if ! authd.configuration.registrations
|
||||
return Response::Error.new "registrations not allowed"
|
||||
end
|
||||
|
||||
if authd.users_per_login.get? @login
|
||||
return Response::Error.new "login already used"
|
||||
end
|
||||
|
||||
if authd.configuration.require_email && @email.nil?
|
||||
return Response::Error.new "email required"
|
||||
end
|
||||
|
||||
if ! @email.nil?
|
||||
# Test on the email address format.
|
||||
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
|
||||
result = grok.parse @email.not_nil!
|
||||
email = result["email"]?
|
||||
|
||||
if email.nil?
|
||||
return Response::Error.new "invalid email format"
|
||||
end
|
||||
end
|
||||
|
||||
# In this case we should not accept its registration.
|
||||
if @password.size < 4
|
||||
return Response::Error.new "password too short"
|
||||
end
|
||||
|
||||
uid = authd.new_uid
|
||||
password = authd.hash_password @password
|
||||
|
||||
user = User.new uid, @login, password
|
||||
user.contact.email = @email unless @email.nil?
|
||||
user.contact.phone = @phone unless @phone.nil?
|
||||
|
||||
@profile.try do |profile|
|
||||
user.profile = profile
|
||||
end
|
||||
|
||||
user.date_registration = Time.local
|
||||
|
||||
begin
|
||||
mailer_exe = authd.configuration.mailer_exe
|
||||
template_name = authd.configuration.activation_template
|
||||
|
||||
u_login = user.login
|
||||
u_email = user.contact.email.not_nil!
|
||||
u_activation_key = user.contact.activation_key.not_nil!
|
||||
|
||||
# Once the user is created and stored, we try to contact him.
|
||||
unless Process.run(mailer_exe,
|
||||
# PARAMETERS
|
||||
[ "send", template_name, u_email ],
|
||||
# ENV
|
||||
{ "LOGIN" => u_login, "TOKEN" => u_activation_key },
|
||||
true # clear environment
|
||||
).success?
|
||||
raise "cannot contact user #{u_login} address #{u_email}"
|
||||
end
|
||||
rescue e
|
||||
Baguette::Log.error "mailer: #{e}"
|
||||
return Response::Error.new "cannot contact the user (not registered)"
|
||||
end
|
||||
|
||||
# add the user only if we were able to send the confirmation mail
|
||||
authd.users << user
|
||||
authd.new_uid_commit uid
|
||||
Response::UserAdded.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << Register
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message SearchUser, 13 do
|
||||
property user : String
|
||||
|
||||
def initialize(@user)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
pattern = Regex.new @user, Regex::Options::IGNORE_CASE
|
||||
|
||||
matching_users = Array(AuthD::User::Public).new
|
||||
|
||||
users = authd.users.to_a
|
||||
users.each do |u|
|
||||
if pattern =~ u.login || u.profile.try do |profile|
|
||||
full_name = profile["full_name"]?
|
||||
if full_name.nil?
|
||||
false
|
||||
else
|
||||
pattern =~ full_name.as_s
|
||||
end
|
||||
end
|
||||
Baguette::Log.debug "#{u.login} matches #{pattern}"
|
||||
matching_users << u.to_public
|
||||
else
|
||||
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
|
||||
end
|
||||
end
|
||||
|
||||
Response::MatchingUsers.new matching_users
|
||||
end
|
||||
end
|
||||
AuthD.requests << SearchUser
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message GetToken, 0 do
|
||||
property login : String
|
||||
property password : String
|
||||
|
||||
def initialize(@login, @password)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
begin
|
||||
user = authd.users_per_login.get @login
|
||||
rescue e : DODB::MissingEntry
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if user.password_hash != authd.hash_password @password
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
user.date_last_connection = Time.local
|
||||
token = user.to_token
|
||||
|
||||
# change the date of the last connection
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::Token.new (token.to_s authd.configuration.shared_key), user.uid
|
||||
end
|
||||
end
|
||||
AuthD.requests << GetToken
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
class AuthD::Request
|
||||
IPC::JSON.message ValidateUser, 2 do
|
||||
property login : String
|
||||
property activation_key : String
|
||||
|
||||
def initialize(@login, @activation_key)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.users_per_login.get? @login
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "user not found"
|
||||
end
|
||||
|
||||
if user.contact.activation_key.nil?
|
||||
return Response::Error.new "user already validated"
|
||||
end
|
||||
|
||||
# remove the user contact activation key: the email is validated
|
||||
if user.contact.activation_key == @activation_key
|
||||
user.contact.activation_key = nil
|
||||
else
|
||||
return Response::Error.new "wrong activation key"
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::UserValidated.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << ValidateUser
|
||||
|
||||
IPC::JSON.message GetUser, 3 do
|
||||
property user : Int32 | String
|
||||
|
||||
def initialize(@user)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
uid_or_login = @user
|
||||
user = if uid_or_login.is_a? Int32
|
||||
authd.users_per_uid.get? uid_or_login.to_s
|
||||
else
|
||||
authd.users_per_login.get? uid_or_login
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
return Response::Error.new "user not found"
|
||||
end
|
||||
|
||||
Response::User.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << GetUser
|
||||
|
||||
IPC::JSON.message GetUserByCredentials, 4 do
|
||||
property login : String
|
||||
property password : String
|
||||
|
||||
def initialize(@login, @password)
|
||||
end
|
||||
|
||||
def handle(authd : AuthD::Service)
|
||||
user = authd.users_per_login.get? @login
|
||||
|
||||
unless user
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
if authd.hash_password(@password) != user.password_hash
|
||||
return Response::Error.new "invalid credentials"
|
||||
end
|
||||
|
||||
user.date_last_connection = Time.local
|
||||
|
||||
# change the date of the last connection
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
|
||||
Response::User.new user.to_public
|
||||
end
|
||||
end
|
||||
AuthD.requests << GetUserByCredentials
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message Contacts, 12 do
|
||||
property user : Int32
|
||||
property email : String? = nil
|
||||
property phone : String? = nil
|
||||
def initialize(@user, @email, @phone)
|
||||
end
|
||||
end
|
||||
AuthD.responses << Contacts
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message Error, 0 do
|
||||
property reason : String? = nil
|
||||
def initialize(@reason)
|
||||
end
|
||||
end
|
||||
AuthD.responses << Error
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message PasswordRecoverySent, 9 do
|
||||
property user : ::AuthD::User::Public
|
||||
def initialize(@user)
|
||||
end
|
||||
end
|
||||
AuthD.responses << PasswordRecoverySent
|
||||
|
||||
IPC::JSON.message PasswordRecovered, 10 do
|
||||
property user : ::AuthD::User::Public
|
||||
def initialize(@user)
|
||||
end
|
||||
end
|
||||
AuthD.responses << PasswordRecovered
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message PermissionCheck, 7 do
|
||||
property user : Int32
|
||||
property service : String
|
||||
property resource : String
|
||||
property permission : ::AuthD::User::PermissionLevel
|
||||
def initialize(@service, @resource, @user, @permission)
|
||||
end
|
||||
end
|
||||
AuthD.responses << PermissionCheck
|
||||
|
||||
IPC::JSON.message PermissionSet, 8 do
|
||||
property user : Int32
|
||||
property service : String
|
||||
property resource : String
|
||||
property permission : ::AuthD::User::PermissionLevel
|
||||
def initialize(@user, @service, @resource, @permission)
|
||||
end
|
||||
end
|
||||
AuthD.responses << PermissionSet
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message Token, 1 do
|
||||
property uid : Int32
|
||||
property token : String
|
||||
def initialize(@token, @uid)
|
||||
end
|
||||
end
|
||||
AuthD.responses << Token
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
class AuthD::Response
|
||||
IPC::JSON.message User, 2 do
|
||||
property user : ::AuthD::User::Public
|
||||
def initialize(@user)
|
||||
end
|
||||
end
|
||||
AuthD.responses << User
|
||||
|
||||
IPC::JSON.message UserAdded, 3 do
|
||||
property user : ::AuthD::User::Public
|
||||
def initialize(@user)
|
||||
end
|
||||
end
|
||||
AuthD.responses << UserAdded
|
||||
|
||||
IPC::JSON.message UserEdited, 4 do
|
||||
property uid : Int32
|
||||
def initialize(@uid)
|
||||
end
|
||||
end
|
||||
AuthD.responses << UserEdited
|
||||
|
||||
IPC::JSON.message UserValidated, 5 do
|
||||
property user : ::AuthD::User::Public
|
||||
def initialize(@user)
|
||||
end
|
||||
end
|
||||
AuthD.responses << UserValidated
|
||||
|
||||
IPC::JSON.message UsersList, 6 do
|
||||
property users : Array(::AuthD::User::Public)
|
||||
def initialize(@users)
|
||||
end
|
||||
end
|
||||
AuthD.responses << UsersList
|
||||
|
||||
IPC::JSON.message MatchingUsers, 11 do
|
||||
property users : Array(::AuthD::User::Public)
|
||||
def initialize(@users)
|
||||
end
|
||||
end
|
||||
AuthD.responses << MatchingUsers
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
LDPATH ?= /tmp/libipc/zig-impl/build
|
||||
SRC ?= ./bin/some-crystal-app
|
||||
|
||||
VG_OPTS = --leak-check=full -v
|
||||
VG_OPTS += --show-leak-kinds=all
|
||||
VG_OPTS += --suppressions=valgrind.suppressions
|
||||
VG_OPTS += --gen-suppressions=all
|
||||
|
||||
build:
|
||||
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build
|
||||
|
||||
valgrind:
|
||||
LD_LIBRARY_PATH=$(LDPATH) valgrind $(VG_OPTS) $(SRC)
|
||||
|
||||
run:
|
||||
LD_LIBRARY_PATH=$(LDPATH) $(SRC)
|
||||
|
||||
build-pongd:
|
||||
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build pongd
|
||||
|
||||
run-pongd:
|
||||
LD_LIBRARY_PATH=$(LDPATH) ./bin/pongd
|
||||
|
||||
build-authd:
|
||||
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build authd
|
||||
|
||||
run-authd:
|
||||
@-rm /tmp/libipc-run/auth 2>/dev/null || true
|
||||
# LD_LIBRARY_PATH=$(LDPATH) ./bin/authd -R -E
|
||||
LD_LIBRARY_PATH=$(LDPATH) ./bin/authd --allow-registrations --require-email $(PARAMS)
|
||||
|
||||
build-authc:
|
||||
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build authc
|
||||
|
||||
run-authc:
|
||||
LD_LIBRARY_PATH=$(LDPATH) ./bin/authc
|
||||
|
||||
run-test:
|
||||
crystal run src/libauth.cr
|
||||
|
||||
doc:
|
||||
crystal docs
|
||||
|
||||
ACCESS_LOGS ?= ./access.log
|
||||
serve-doc:
|
||||
darkhttpd docs/ --addr 127.0.0.1 --port 35001 --log $(ACCESS_LOGS)
|
|
@ -0,0 +1,38 @@
|
|||
name: some-crystal-app
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Philippe Pittoli <karchnu@karchnu.fr>
|
||||
|
||||
targets:
|
||||
main:
|
||||
main: src/main.cr
|
||||
pongd:
|
||||
main: tests/pongd.cr
|
||||
authd:
|
||||
main: authd/main.cr
|
||||
authc:
|
||||
main: authc/authc.cr
|
||||
|
||||
crystal: 1.7.1
|
||||
|
||||
dependencies:
|
||||
grok:
|
||||
github: spinscale/grok.cr
|
||||
passwd:
|
||||
git: https://git.baguette.netlib.re/Baguette/passwd.cr
|
||||
branch: master
|
||||
jwt:
|
||||
github: crystal-community/jwt
|
||||
branch: master
|
||||
baguette-crystal-base:
|
||||
git: https://git.baguette.netlib.re/Baguette/baguette-crystal-base
|
||||
branch: master
|
||||
dodb:
|
||||
git: https://git.baguette.netlib.re/Baguette/dodb.cr
|
||||
branch: master
|
||||
cbor:
|
||||
git: https://git.baguette.netlib.re/Baguette/crystal-cbor
|
||||
branch: master
|
||||
|
||||
license: MIT
|
|
@ -0,0 +1,38 @@
|
|||
@[Link("ipc")]
|
||||
lib LibIPC
|
||||
enum EventType
|
||||
Error # Self explanatory.
|
||||
Connection # New user.
|
||||
Disconnection # User disconnected.
|
||||
MessageRx # Message received.
|
||||
MessageTx # Message sent.
|
||||
Timer # Timeout in the poll(2) function.
|
||||
External # Message received from a non IPC socket.
|
||||
SwitchRx # Switch subsystem: message received.
|
||||
SwitchTx # Switch subsystem: message send.
|
||||
end
|
||||
|
||||
fun init = ipc_context_init (Void**) : LibC::Int
|
||||
fun deinit = ipc_context_deinit (Void**) : Void
|
||||
|
||||
fun service_init = ipc_service_init (Void*, LibC::Int*, LibC::Char*, LibC::UInt16T) : LibC::Int
|
||||
fun connect_service = ipc_connect_service(Void*, LibC::Int*, LibC::Char*, LibC::UInt16T) : LibC::Int
|
||||
|
||||
# Context EventType index fd buffer buflen
|
||||
fun wait = ipc_wait_event(Void*, UInt8*, LibC::UInt64T*, LibC::Int*, UInt8*, LibC::UInt64T*) : LibC::Int
|
||||
|
||||
# Sending a message NOW.
|
||||
# WARNING: doesn't wait the fd to become available.
|
||||
fun write = ipc_write(Void*, LibC::Int, UInt8*, LibC::UInt64T) : LibC::Int
|
||||
# Sending a message (will wait the fd to become available for IO operations).
|
||||
fun schedule = ipc_schedule(Void*, LibC::Int, UInt8*, LibC::UInt64T) : LibC::Int
|
||||
|
||||
fun read = ipc_read_fd (Void*, LibC::Int, UInt8*, LibC::UInt64T*);
|
||||
|
||||
fun timer = ipc_context_timer (Void*, LibC::Int)
|
||||
|
||||
# Closing connections.
|
||||
fun close = ipc_close(Void*, LibC::UInt64T) : LibC::Int
|
||||
fun close_fd = ipc_close_fd(Void*, LibC::Int) : LibC::Int
|
||||
fun close_all = ipc_close_all(Void*) : LibC::Int
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
require "cbor"
|
||||
require "./main.cr"
|
||||
|
||||
# IPC::CBOR is the root class for all exchanged messages (using CBOR).
|
||||
# IPC::CBOR inherited classes have a common 'type' class attribute,
|
||||
# which enables to find the right IPC::CBOR+ class given a TypedMessage's type.
|
||||
|
||||
# All transfered messages are typed messages.
|
||||
# TypedMessage = u8 type (= IPC::CBOR+ class type) + CBOR content.
|
||||
# 'CBOR content' being a serialized IPC::CBOR+ class.
|
||||
|
||||
# Conventionally, IPC::CBOR+ classes have a 'handle' method to process incoming messages.
|
||||
class IPC::CBOR
|
||||
include ::CBOR::Serializable
|
||||
|
||||
class_property type = -1
|
||||
property id : ::CBOR::Any?
|
||||
|
||||
def type
|
||||
@@type
|
||||
end
|
||||
|
||||
macro message(id, type, &block)
|
||||
class {{id}} < ::IPC::CBOR
|
||||
include ::CBOR::Serializable
|
||||
|
||||
@@type = {{type}}
|
||||
|
||||
{{yield}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IPC
|
||||
# Schedule messages contained into IPC::CBOR+.
|
||||
def schedule(fd : Int32, message : IPC::CBOR)
|
||||
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_cbor
|
||||
schedule fd, typed_msg
|
||||
end
|
||||
def write(fd : Int32, message : IPC::CBOR)
|
||||
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_cbor
|
||||
write fd, typed_msg
|
||||
end
|
||||
end
|
||||
|
||||
# CAUTION: only use this method on an Array(IPC::CBOR.class).
|
||||
class Array(T)
|
||||
def parse_ipc_cbor(message : IPCMessage::TypedMessage) : IPC::CBOR?
|
||||
message_type = find &.type.==(message.type)
|
||||
|
||||
payload = message.payload
|
||||
|
||||
if message_type.nil?
|
||||
raise "invalid message type (#{message.type})"
|
||||
end
|
||||
|
||||
message_type.from_cbor payload
|
||||
end
|
||||
end
|
|
@ -0,0 +1,134 @@
|
|||
class IPC
|
||||
# Reception buffer with a big capacity.
|
||||
# Allocated once.
|
||||
@reception_buffer = Array(UInt8).new 2_000_000
|
||||
@reception_buffer_len : LibC::UInt64T = 2_000_000
|
||||
|
||||
class Event
|
||||
property type : LibIPC::EventType
|
||||
property index : LibC::UInt64T
|
||||
property fd : Int32
|
||||
property message : Array(UInt8)? = nil
|
||||
|
||||
def initialize(t : UInt8, @index, @fd, buffer, buflen)
|
||||
@type = LibIPC::EventType.new t
|
||||
if buflen > 0
|
||||
# Array -> Pointer -> Slice -> Array
|
||||
@message = buffer.to_unsafe.to_slice(buflen).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize()
|
||||
@context = Pointer(Void).null
|
||||
LibIPC.init(pointerof(@context))
|
||||
at_exit { deinit }
|
||||
end
|
||||
|
||||
# Closes all connections then remove the structure from memory.
|
||||
def deinit
|
||||
LibIPC.deinit(pointerof(@context))
|
||||
end
|
||||
|
||||
def service_init(name : String) : Int
|
||||
fd = uninitialized Int32
|
||||
if LibIPC.service_init(@context, pointerof(fd), name, name.size) != 0
|
||||
raise "oh noes, 'service_init' iz brkn"
|
||||
end
|
||||
fd
|
||||
end
|
||||
|
||||
def connect(name : String) : Int32
|
||||
fd = uninitialized Int32
|
||||
if LibIPC.connect_service(@context, pointerof(fd), name, name.size) != 0
|
||||
raise "oh noes, 'connect_service' iz brkn"
|
||||
end
|
||||
fd
|
||||
end
|
||||
|
||||
def timer(value : LibC::Int)
|
||||
LibIPC.timer(@context, value)
|
||||
end
|
||||
|
||||
def write(fd : Int, string : String)
|
||||
self.write(fd, string.to_unsafe, string.size.to_u64)
|
||||
end
|
||||
|
||||
def write(fd : Int, buffer : UInt8*, buflen : UInt64)
|
||||
if LibIPC.write(@context, fd, buffer, buflen) != 0
|
||||
raise "oh noes, 'write' iz brkn"
|
||||
end
|
||||
end
|
||||
|
||||
def write(fd : Int32, buffer : Bytes)
|
||||
self.write(fd, buffer.to_unsafe, buffer.size.to_u64)
|
||||
end
|
||||
|
||||
def read(fd : Int32) : Slice(UInt8)
|
||||
buffer : Bytes = Bytes.new 2000000
|
||||
size = buffer.size.to_u64
|
||||
LibIPC.read(@context, fd, buffer.to_unsafe, pointerof(size))
|
||||
buffer[0..size - 1]
|
||||
end
|
||||
|
||||
def schedule(fd : Int32, string : String)
|
||||
self.schedule(fd, string.to_unsafe, string.size.to_u64)
|
||||
end
|
||||
|
||||
def schedule(fd : Int32, buffer : Array(UInt8), buflen : Int32)
|
||||
self.schedule(fd, buffer.to_unsafe, buflen.to_u64)
|
||||
end
|
||||
|
||||
def schedule(fd : Int32, buffer : Bytes)
|
||||
self.schedule(fd, buffer.to_unsafe, buffer.size.to_u64)
|
||||
end
|
||||
|
||||
def schedule(fd : Int32, buffer : UInt8*, buflen : UInt64)
|
||||
if LibIPC.schedule(@context, fd, buffer, buflen) != 0
|
||||
raise "oh noes, 'schedule' iz brkn"
|
||||
end
|
||||
end
|
||||
|
||||
def close(index : LibC::UInt64T)
|
||||
if LibIPC.close(@context, index) != 0
|
||||
raise "Oh noes, 'close index' iz brkn"
|
||||
end
|
||||
end
|
||||
|
||||
def close(fd : LibC::Int)
|
||||
if LibIPC.close_fd(@context, fd) != 0
|
||||
raise "Oh noes, 'close fd' iz brkn"
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
if LibIPC.close_all(@context) != 0
|
||||
raise "Oh noes, 'close all' iz brkn"
|
||||
end
|
||||
end
|
||||
|
||||
def wait() : IPC::Event
|
||||
eventtype : UInt8 = 0
|
||||
index : LibC::UInt64T = 0
|
||||
fd : Int32 = 0
|
||||
buflen = @reception_buffer_len
|
||||
ret = LibIPC.wait(@context,
|
||||
pointerof(eventtype),
|
||||
pointerof(index),
|
||||
pointerof(fd),
|
||||
@reception_buffer.to_unsafe,
|
||||
pointerof(buflen))
|
||||
|
||||
if ret != 0
|
||||
raise "Oh noes, 'wait' iz brkn"
|
||||
end
|
||||
|
||||
Event.new(eventtype, index, fd, @reception_buffer, buflen)
|
||||
end
|
||||
|
||||
def loop(&block : Proc(IPC::Event, Nil))
|
||||
::loop do
|
||||
yield wait
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
require "json"
|
||||
|
||||
# IPC::JSON is the root class for all exchanged messages (using JSON).
|
||||
# IPC::JSON inherited classes have a common 'type' class attribute,
|
||||
# which enables to find the right IPC::JSON+ class given a TypedMessage's type.
|
||||
|
||||
# All transfered messages are typed messages.
|
||||
# TypedMessage = u8 type (= IPC::JSON+ class type) + JSON content.
|
||||
# 'JSON content' being a serialized IPC::JSON+ class.
|
||||
|
||||
# Conventionally, IPC::JSON+ classes have a 'handle' method to process incoming messages.
|
||||
class IPC::JSON
|
||||
include ::JSON::Serializable
|
||||
|
||||
class_property type = -1
|
||||
property id : ::JSON::Any?
|
||||
|
||||
def type
|
||||
@@type
|
||||
end
|
||||
|
||||
macro message(id, type, &block)
|
||||
class {{id}} < ::IPC::JSON
|
||||
include ::JSON::Serializable
|
||||
|
||||
@@type = {{type}}
|
||||
|
||||
{{yield}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IPC
|
||||
# Schedule messages contained into IPC::JSON+.
|
||||
def schedule(fd : Int32, message : IPC::JSON)
|
||||
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_json
|
||||
schedule fd, typed_msg
|
||||
end
|
||||
def write(fd : Int32, message : IPC::JSON)
|
||||
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_json
|
||||
write fd, typed_msg
|
||||
end
|
||||
end
|
||||
|
||||
# CAUTION: only use this method on an Array(IPC::JSON.class).
|
||||
class Array(T)
|
||||
def parse_ipc_json(message : IPCMessage::TypedMessage) : IPC::JSON?
|
||||
message_type = find &.type.==(message.type)
|
||||
|
||||
payload = String.new message.payload
|
||||
|
||||
if message_type.nil?
|
||||
raise "invalid message type (#{message.type})"
|
||||
end
|
||||
|
||||
message_type.from_json payload
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
require "./bindings.cr"
|
||||
require "./message.cr"
|
||||
require "./high-level-bindings.cr"
|
|
@ -0,0 +1,69 @@
|
|||
# TODO: tests.
|
||||
|
||||
# Serialization (and deserialization) doesn't refer to IPC format.
|
||||
# IPC serialization format: 'length + value'
|
||||
# IPCMessage serialization: 'value'
|
||||
# 'Value' is:
|
||||
# - simply the message payload for UntypedMessage
|
||||
# - type (u8) + payload for TypedMessage
|
||||
module IPCMessage
|
||||
class UntypedMessage
|
||||
property payload : Bytes
|
||||
def initialize(string : String)
|
||||
@payload = Bytes.new string.to_unsafe, string.size
|
||||
end
|
||||
def initialize(@payload)
|
||||
end
|
||||
|
||||
def self.deserialize(payload : Bytes) : UntypedMessage
|
||||
IPCMessage::UntypedMessage.new payload
|
||||
end
|
||||
|
||||
def serialize
|
||||
@payload
|
||||
end
|
||||
end
|
||||
|
||||
# WARNING: you can only have up to 256 types.
|
||||
class TypedMessage < UntypedMessage
|
||||
property type : UInt8? = nil
|
||||
def initialize(@type, string : String)
|
||||
super string
|
||||
end
|
||||
def initialize(@type, payload)
|
||||
super payload
|
||||
end
|
||||
def initialize(payload)
|
||||
super payload
|
||||
end
|
||||
|
||||
def self.deserialize(bytes : Bytes) : TypedMessage?
|
||||
if bytes.size == 0
|
||||
nil
|
||||
else
|
||||
type = bytes[0]
|
||||
IPCMessage::TypedMessage.new type, bytes[1..]
|
||||
end
|
||||
end
|
||||
|
||||
def serialize
|
||||
bytes = Bytes.new (1 + @payload.size)
|
||||
type = @type
|
||||
bytes[0] = type.nil? ? 0.to_u8 : type
|
||||
bytes[1..].copy_from @payload
|
||||
bytes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Send both typed and untyped messages.
|
||||
class IPC
|
||||
def schedule(fd : Int32, m : (IPCMessage::TypedMessage | IPCMessage::UntypedMessage))
|
||||
payload = m.serialize
|
||||
schedule fd, payload
|
||||
end
|
||||
def write(fd : Int32, m : (IPCMessage::TypedMessage | IPCMessage::UntypedMessage))
|
||||
payload = m.serialize
|
||||
write fd, payload
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
require "../src/main.cr"
|
||||
|
||||
# In 5 messages: quit
|
||||
count = 5
|
||||
|
||||
ipc = IPC.new
|
||||
fd = ipc.service_init("pong")
|
||||
|
||||
ipc.loop do |event|
|
||||
case event.type
|
||||
when LibIPC::EventType::MessageRx
|
||||
m = event.message
|
||||
if m.nil?
|
||||
puts "No message"
|
||||
else
|
||||
received = String.new(m.to_unsafe, m.size)
|
||||
pp! received
|
||||
ipc.schedule event.fd, m, m.size
|
||||
end
|
||||
|
||||
when LibIPC::EventType::MessageTx
|
||||
puts "A message has been sent"
|
||||
count -= 1
|
||||
if count == 0
|
||||
exit
|
||||
end
|
||||
|
||||
when LibIPC::EventType::Connection
|
||||
puts "A client just connected #JOY"
|
||||
|
||||
when LibIPC::EventType::Disconnection
|
||||
puts "A client just disconnected #SAD"
|
||||
|
||||
else
|
||||
puts "Unexpected: #{event.type}"
|
||||
exit 1
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
require "../main.cr"
|
||||
|
||||
def test_high_level
|
||||
ipc = IPC.new
|
||||
fd = ipc.connect("pong")
|
||||
ipc.write(fd, "hello this is some value")
|
||||
event = ipc.wait()
|
||||
|
||||
m = event.message
|
||||
if m.nil?
|
||||
puts "No message"
|
||||
else
|
||||
pp! String.new(m.to_unsafe, m.size)
|
||||
end
|
||||
end
|
||||
|
||||
def test_loop
|
||||
ipc = IPC.new
|
||||
fd = ipc.connect("pong")
|
||||
ipc.schedule(fd, "hello this is some value")
|
||||
ipc.loop do |event|
|
||||
case event.type
|
||||
when LibIPC::EventType::MessageRx
|
||||
m = event.message
|
||||
if m.nil?
|
||||
puts "No message"
|
||||
else
|
||||
pp! String.new(m.to_unsafe, m.size)
|
||||
end
|
||||
exit 0
|
||||
when LibIPC::EventType::MessageTx
|
||||
puts "A message has been sent"
|
||||
else
|
||||
puts "Unexpected: #{event.type}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Write documentation for `Some::Crystal::App`
|
||||
module Some::Crystal::App
|
||||
VERSION = "0.1.0"
|
||||
|
||||
test_high_level
|
||||
test_loop
|
||||
end
|
|
@ -0,0 +1,229 @@
|
|||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Addr1
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_init_linux_data_start
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_push_all_eager
|
||||
fun:GC_with_callee_saves_pushed
|
||||
fun:GC_push_roots
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_push_all_eager
|
||||
fun:GC_with_callee_saves_pushed
|
||||
fun:GC_push_roots
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:memalign
|
||||
fun:posix_memalign
|
||||
obj:/tmp/libipc/zig-impl/build/libipc.so.0.1.0
|
||||
fun:*Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
fun:*Signal::setup_segfault_handler:(Int32 | Nil)
|
||||
fun:*Exception::CallStack::setup_crash_handler:(Int32 | Nil)
|
||||
fun:__crystal_main
|
||||
fun:*Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_push_all_eager
|
||||
fun:GC_with_callee_saves_pushed
|
||||
fun:GC_push_roots
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Addr1
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_init_linux_data_start
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_push_all_eager
|
||||
fun:GC_with_callee_saves_pushed
|
||||
fun:GC_push_roots
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
fun:GC_mark_from
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_mark_some
|
||||
obj:/usr/lib/libgc.so.1.5.0
|
||||
fun:GC_try_to_collect_inner
|
||||
fun:GC_init
|
||||
fun:*GC::init:Nil
|
||||
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
|
||||
fun:main
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
ZIGC=zig
|
||||
|
||||
CC=gcc
|
||||
CFLAGS=-Wall -Wextra
|
||||
LDFLAGS=-I build/ -L build/ -lipc
|
||||
|
||||
all: zigcompilation compilation
|
||||
|
||||
ifeq ($(SRC),)
|
||||
test-src:
|
||||
@echo SRC must be set via command line.
|
||||
@exit 1
|
||||
else
|
||||
test-src:
|
||||
endif
|
||||
|
||||
list-obj-files: test-src
|
||||
@# List all .o included in a .a archive.
|
||||
ar t $(SRC)
|
||||
list-symbols: test-src
|
||||
@# List all symbols in a .so.
|
||||
nm -D $(SRC)
|
||||
list-symbols-alt: test-src
|
||||
@# Alternative: grep .text section in an objdump output.
|
||||
objdump -T $(SRC) | grep text
|
||||
|
||||
zigcompilation: build.zig src/*.zig
|
||||
$(ZIGC) build
|
||||
|
||||
compilation: src/main.c
|
||||
@echo the following compilation will produce errors despite actually working
|
||||
$(CC) -o main build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
|
||||
|
||||
run:
|
||||
LD_LIBRARY_PATH=build ./main
|
||||
|
||||
valgrind:
|
||||
LD_LIBRARY_PATH=build valgrind --suppressions=./suppress-stuff.suppr --gen-suppressions=all -v --leak-check=full ./main
|
|
@ -0,0 +1,25 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.build.Builder) void {
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const lib = b.addStaticLibrary("ipc", "src/main.zig");
|
||||
lib.setOutputDir("build");
|
||||
lib.linkLibC();
|
||||
lib.setBuildMode(mode);
|
||||
lib.install();
|
||||
|
||||
const solib = b.addSharedLibrary("ipc", "src/main.zig", b.version(0, 0, 1));
|
||||
solib.setOutputDir("build");
|
||||
solib.linkLibC();
|
||||
solib.setBuildMode(mode);
|
||||
solib.install();
|
||||
|
||||
const main_tests = b.addTest("src/main.zig");
|
||||
main_tests.setBuildMode(mode);
|
||||
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&main_tests.step);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
int ret = 0;
|
||||
|
||||
printf ("Init context.\n");
|
||||
void *ctx = NULL;
|
||||
ret = ipc_context_init (&ctx);
|
||||
|
||||
if (ret != 0) {
|
||||
printf ("Cannot init context.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO: do stuff
|
||||
|
||||
printf ("Deinit context\n");
|
||||
ipc_context_deinit (ctx);
|
||||
printf ("Context deinit.\n");
|
||||
free(ctx);
|
||||
printf ("Context completely freed.\n");
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
const std = @import("std");
|
||||
const fmt = std.fmt;
|
||||
|
||||
const print = std.debug.print;
|
||||
|
||||
pub const Context = struct {
|
||||
rundir: [] u8,
|
||||
allocator: std.mem.Allocator,
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Self {
|
||||
var rundir = try allocator.dupeZ(u8, "/tmp/libipc-run/");
|
||||
return Self { .rundir = rundir, .allocator = allocator };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.allocator.free(self.rundir);
|
||||
}
|
||||
};
|
||||
|
||||
export fn ipc_context_init (ptr: **Context) callconv(.C) i32 {
|
||||
ptr.* = std.heap.c_allocator.create(Context) catch return 1;
|
||||
|
||||
ptr.*.* = Context.init(std.heap.c_allocator) catch |err| {
|
||||
print ("libipc: error while init context: {}\n", .{err});
|
||||
return 1;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
export fn ipc_context_deinit (ctx: *Context) callconv(.C) void {
|
||||
ctx.deinit();
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef LIBIPC
|
||||
#define LIBIPC
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum event_types {
|
||||
ERROR = 0 // A problem occured.
|
||||
, CONNECTION = 1 // New user.
|
||||
, DISCONNECTION = 2 // User disconnected.
|
||||
, MESSAGE_RX = 3 // New message.
|
||||
, MESSAGE_TX = 4 // Message sent.
|
||||
, TIMER = 5 // Timeout in the poll(2) function.
|
||||
, EXTERNAL = 6 // Message received from a non IPC socket.
|
||||
, SWITCH_RX = 7 // Message received from a switched FD.
|
||||
, SWITCH_TX = 8 // Message sent to a switched fd.
|
||||
};
|
||||
|
||||
// Return type of callback functions when switching.
|
||||
enum cb_event_types {
|
||||
CB_NO_ERROR = 0 // No error. A message was generated.
|
||||
, CB_ERROR = 1 // Generic error.
|
||||
, CB_FD_CLOSING = 2 // The fd is closing.
|
||||
, CB_IGNORE = 3 // The message should be ignored (protocol specific).
|
||||
};
|
||||
|
||||
int ipc_context_init (void** ptr);
|
||||
int ipc_service_init (void* ctx, int* servicefd, const char* service_name, uint16_t service_name_len);
|
||||
int ipc_connect_service (void* ctx, int* servicefd, const char* service_name, uint16_t service_name_len);
|
||||
void ipc_context_deinit (void** ctx);
|
||||
int ipc_write (void* ctx, int servicefd, char* mcontent, uint32_t mlen);
|
||||
int ipc_schedule (void* ctx, int servicefd, const char* mcontent, uint32_t mlen);
|
||||
int ipc_read_fd (void* ctx, int fd, char* buffer, size_t* buflen);
|
||||
int ipc_read (void* ctx, size_t index, char* buffer, size_t* buflen);
|
||||
int ipc_wait_event(void* ctx, char* t, size_t* index, int* originfd, char* buffer, size_t* buflen);
|
||||
void ipc_context_timer (void* ctx, int timer);
|
||||
int ipc_close_fd (void* ctx, int fd);
|
||||
int ipc_close (void* ctx, size_t index);
|
||||
int ipc_close_all (void* ctx);
|
||||
|
||||
// Switch functions (for "protocol" services, such as TCPd).
|
||||
int ipc_add_external (void* ctx, int newfd);
|
||||
int ipc_add_switch (void* ctx, int fd1, int fd2);
|
||||
|
||||
int ipc_set_switch_callbacks (void* ctx, int fd
|
||||
, enum cb_event_types (*in (int orig, const char *payload, uint32_t *mlen))
|
||||
, enum cb_event_types (*out(int dest, char *payload, uint32_t mlen)));
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
all:
|
||||
|
||||
ZIGMAKEDOC = -femit-docs -fno-emit-bin
|
||||
ZIGOPTIM ?= Debug
|
||||
# Linking against libc is almost mandatory, C allocator is used
|
||||
# for switching (default reception and emission functions).
|
||||
ZIGBUSEOPTS ?= -O$(ZIGOPTIM) -freference-trace -lc
|
||||
ZIGUSROPTS ?=
|
||||
ZIGC ?= zig
|
||||
ZIGOPTS ?= $(ZIGBUSEOPTS) $(ZIGUSROPTS)
|
||||
|
||||
# Debug with valgrind.
|
||||
ifdef VG_SUPPRESS_WARNINGS
|
||||
VALGRIND_SUPPRESS_WARNINGS ?= --suppressions=./valgrind.suppr
|
||||
endif
|
||||
ifdef VG_GENERATE_SUPPRESSION
|
||||
VALGRIND_GEN_SUPPRESSION ?= --gen-suppressions=all
|
||||
endif
|
||||
VALGRIND_OPTS=-v --leak-check=full --track-origins=yes
|
||||
ifdef USE_VALGRIND
|
||||
VALGRIND ?= valgrind $(VALGRIND_SUPPRESS_WARNINGS) \
|
||||
$(VALGRIND_GEN_SUPPRESSION) \
|
||||
$(VALGRIND_OPTS)
|
||||
endif
|
||||
# Optional parameters (copied here to help with autocompletion).
|
||||
VG_SUPPRESS_WARNINGS ?=
|
||||
VG_GENERATE_SUPPRESSION ?=
|
||||
USE_VALGRIND ?=
|
||||
|
||||
TO_CLEAN != ls misc/*.zig | sed 's/.zig$\//' | sed 's_misc/__'
|
||||
TO_CLEAN += bin/ipcd bin/tcpd bin/pong bin/pongd
|
||||
TO_CLEAN += bin/*.o
|
||||
clean:
|
||||
@-rm $(TO_CLEAN) 2>/dev/null
|
||||
|
||||
mrproper: clean
|
||||
@-rm -r docs build zig-cache zig-out 2>/dev/null
|
||||
|
||||
doc: src/ipcd.zig
|
||||
$(ZIGC) build-exe $(ZIGOPTS) $(ZIGMAKEDOC) $^
|
||||
|
||||
ACCESS_LOGS ?= ./access.log
|
||||
serve-doc:
|
||||
darkhttpd docs/ --addr 127.0.0.1 --port 35000 --log $(ACCESS_LOGS)
|
||||
|
||||
# You can add your specific instructions there.
|
||||
-include makefile.user
|
|
@ -0,0 +1,92 @@
|
|||
CC=gcc
|
||||
CFLAGS=-Wall -Wextra #-Wno-implicit-function-declaration
|
||||
LDFLAGS=-I build/ -L build/ -lipc
|
||||
|
||||
bin: build.zig apps/*.zig src/*.zig
|
||||
$(ZIGC) build
|
||||
|
||||
lib: build.zig src/*.zig
|
||||
$(ZIGC) build
|
||||
|
||||
stop-ipcd:
|
||||
-pkill -1 ipcd
|
||||
|
||||
stop-tcpd:
|
||||
-pkill -1 tcpd
|
||||
|
||||
run-ipcd:
|
||||
-rm /tmp/libipc-run/ipc 2>/dev/null || true
|
||||
$(VALGRIND) ./bin/ipcd
|
||||
|
||||
run-pongd:
|
||||
-rm /tmp/libipc-run/pong 2>/dev/null || true
|
||||
$(VALGRIND) ./bin/pongd
|
||||
|
||||
run-tcpd:
|
||||
@-rm /tmp/libipc-run/tcp 2>/dev/null || true
|
||||
$(VALGRIND) ./bin/tcpd
|
||||
|
||||
TCP_SERVICE_ALT ?= 127.0.0.1:9898
|
||||
run-tcpd-alternative:
|
||||
-rm /tmp/libipc-run/tcpdup 2>/dev/null || true
|
||||
IPC_SERVICE_NAME=tcpdup ADDRESS=$(TCP_SERVICE_ALT) $(VALGRIND) ./tcpd
|
||||
|
||||
SERVICE_NAME ?= p
|
||||
IPC_NETWORK ?= p unix://pong
|
||||
run-pong:
|
||||
@#Force pong to contact IPCd.
|
||||
@#SERVICE is the service to contact and IPC_NETWORK is the IPCd
|
||||
@#configuration to translate "p" into "pong" (still using UNIX
|
||||
@#sockets on the same computer).
|
||||
SERVICE="$(SERVICE_NAME)" IPC_NETWORK="$(IPC_NETWORK)" $(VALGRIND) ./bin/pong
|
||||
|
||||
run-pong-test-tcpd:
|
||||
SERVICE="pong" IPC_NETWORK="pong tcp://$(TCP_SERVICE_ALT)/pong" $(VALGRIND) ./pong
|
||||
|
||||
ifeq ($(SRC),)
|
||||
test-src:
|
||||
@echo SRC must be set via command line.
|
||||
@exit 1
|
||||
else
|
||||
test-src:
|
||||
endif
|
||||
|
||||
comp: bin test-bindings-pong test-bindings-pongd
|
||||
|
||||
list-obj-files: test-src
|
||||
@# List all .o included in a .a archive.
|
||||
ar t $(SRC)
|
||||
list-symbols: test-src
|
||||
@# List all symbols in a .so.
|
||||
nm -D $(SRC)
|
||||
list-symbols-alt: test-src
|
||||
@# Alternative: grep .text section in an objdump output.
|
||||
objdump -T $(SRC) | grep text
|
||||
|
||||
bindings-compile-pong: test-bindings/pong.c
|
||||
@-mkdir bin-bindings 2>/dev/null || true
|
||||
$(CC) -o bin-bindings/pong build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
|
||||
|
||||
bindings-compile-pongd: test-bindings/pongd.c
|
||||
@-mkdir bin-bindings 2>/dev/null || true
|
||||
$(CC) -o bin-bindings/pongd build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
|
||||
|
||||
bindings-test-pong:
|
||||
LD_LIBRARY_PATH=build/ $(VALGRIND) ./bin-bindings/pong
|
||||
|
||||
bindings-test-pongd:
|
||||
-rm /tmp/libipc-run/pong 2>/dev/null || true
|
||||
LD_LIBRARY_PATH=build/ $(VALGRIND) ./bin-bindings/pongd
|
||||
|
||||
WS_SERVICE ?= 127.0.0.1:8080
|
||||
TCP_SERVICE ?= 127.0.0.1:9000
|
||||
init-websocket-tcpd:
|
||||
@# '-b' binary, '-E' quit on end-of-file, 'ws-l' websocket URI to listen
|
||||
@# each connection is redirected to last parameter
|
||||
websocat -b -E ws-l:$(WS_SERVICE) tcp:$(TCP_SERVICE)
|
||||
|
||||
init-websocket-client:
|
||||
@# websocat -b -E tcp-l:127.0.0.1:9000 ws://127.0.0.1:9999
|
||||
websocat -b -E ws://$(WS_SERVICE)
|
||||
|
||||
.PHONY: bin lib
|
|
@ -0,0 +1,63 @@
|
|||
const std = @import("std");
|
||||
|
||||
/// TODO: move this to std
|
||||
|
||||
/// This definition enables the use of Zig types with a cmsghdr structure.
|
||||
/// The oddity of this layout is that the data must be aligned to @sizeOf(usize)
|
||||
/// rather than its natural alignment.
|
||||
pub fn Cmsghdr(comptime T: type) type {
|
||||
const Header = extern struct {
|
||||
len: usize,
|
||||
level: c_int,
|
||||
@"type": c_int,
|
||||
};
|
||||
|
||||
const data_align = @sizeOf(usize);
|
||||
const data_offset = std.mem.alignForward(@sizeOf(Header), data_align);
|
||||
|
||||
return extern struct {
|
||||
const Self = @This();
|
||||
|
||||
bytes: [data_offset + @sizeOf(T)]u8 align(@alignOf(Header)),
|
||||
|
||||
pub fn init(args: struct {
|
||||
level: c_int,
|
||||
@"type": c_int,
|
||||
data: T,
|
||||
}) Self {
|
||||
var self: Self = undefined;
|
||||
self.headerPtr().* = .{
|
||||
.len = data_offset + @sizeOf(T),
|
||||
.level = args.level,
|
||||
.@"type" = args.@"type",
|
||||
};
|
||||
self.dataPtr().* = args.data;
|
||||
return self;
|
||||
}
|
||||
|
||||
// TODO: include this version if we submit a PR to add this to std
|
||||
pub fn initNoData(args: struct {
|
||||
level: c_int,
|
||||
@"type": c_int,
|
||||
}) Self {
|
||||
var self: Self = undefined;
|
||||
self.headerPtr().* = .{
|
||||
.len = data_offset + @sizeOf(T),
|
||||
.level = args.level,
|
||||
.@"type" = args.@"type",
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn headerPtr(self: *Self) *Header {
|
||||
return @ptrCast(*Header, self);
|
||||
}
|
||||
pub fn dataPtr(self: *Self) *align(data_align) T {
|
||||
return @ptrCast(*T, self.bytes[data_offset..]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(Cmsghdr([3]std.os.fd_t));
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const print = std.debug.print;
|
||||
|
||||
|
||||
fn print_current_dir() !void {
|
||||
const buffer_size = 10000;
|
||||
var buffer: [buffer_size]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&buffer);
|
||||
var allocator = fba.allocator();
|
||||
|
||||
print("Print current directory\n", .{});
|
||||
var current_dir = try std.fs.cwd().openIterableDir(".", .{});
|
||||
var walker = try current_dir.walk(allocator);
|
||||
while (try walker.next()) |entry| {
|
||||
print("content: {s}\n", .{entry.basename});
|
||||
}
|
||||
walker.deinit();
|
||||
print("DONE\n", .{});
|
||||
}
|
||||
|
||||
fn add_line_in_file() !void {
|
||||
var cwd = std.fs.cwd();
|
||||
var f = try cwd.createFile("some-file.log", .{.read = true});
|
||||
// defer f.close(); // closed in add_line_from_fd
|
||||
|
||||
var writer = f.writer();
|
||||
|
||||
try writer.print("hello\n", .{});
|
||||
|
||||
try add_line_from_fd(f.handle);
|
||||
}
|
||||
|
||||
fn add_line_from_fd(fd: i32) !void {
|
||||
// var f = std.fs.File {.handle = fd};
|
||||
// defer f.close();
|
||||
|
||||
_ = try std.os.write(fd, "hello this is another line\n");
|
||||
std.os.close(fd);
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
// var path = "/tmp/.TEST_USOCK";
|
||||
// print("Opening the file '{s}'.\n", .{path});
|
||||
//try print_current_dir();
|
||||
try add_line_in_file();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
const std = @import("std");
|
||||
const hexdump = @import("./hexdump.zig");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
|
||||
const Timer = std.time.Timer;
|
||||
|
||||
const print = std.debug.print;
|
||||
const P = std.ArrayList(std.os.pollfd);
|
||||
|
||||
fn arraylist_test() !void {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var p = P.init(allocator);
|
||||
defer p.deinit();
|
||||
|
||||
try p.append(.{.fd = 8, .events = 0, .revents = 0});
|
||||
|
||||
for(p.items) |i| { print("fd: {}\n", .{i.fd}); }
|
||||
}
|
||||
|
||||
fn timer_test() !void {
|
||||
var timer = try Timer.start();
|
||||
|
||||
var count: u64 = 0;
|
||||
while (count < 100000) {
|
||||
count += 1;
|
||||
print("\rcount = {}", .{count});
|
||||
}
|
||||
print("\n", .{});
|
||||
|
||||
var duration = timer.read();
|
||||
print("took {} us\n", .{duration / 1000});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
// try arraylist_test();
|
||||
try timer_test();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
const std = @import("std");
|
||||
const hexdump = @import("./hexdump.zig");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const os = std.os;
|
||||
const fmt = std.fmt;
|
||||
|
||||
const print = std.debug.print;
|
||||
|
||||
// const config = .{.safety = true};
|
||||
// var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
// defer _ = gpa.deinit();
|
||||
// const allocator = gpa.allocator();
|
||||
|
||||
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
|
||||
|
||||
fn server_init() net.StreamServer {
|
||||
// no reuse_address and default kernel_backlog
|
||||
return net.StreamServer.init(.{});
|
||||
}
|
||||
|
||||
fn remove_unix_socket(path: []const u8) void {
|
||||
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
|
||||
else => { print("error: {}\n", .{err}); }
|
||||
};
|
||||
}
|
||||
|
||||
fn waiting_for_connection(stream: *net.StreamServer
|
||||
, path: []const u8) !net.StreamServer.Connection {
|
||||
var address = try net.Address.initUnix(path);
|
||||
try stream.listen(address);
|
||||
|
||||
// const linux = os.linux;
|
||||
// var tfd = try os.timerfd_create(linux.CLOCK.MONOTONIC, linux.TFD.CLOEXEC);
|
||||
// defer os.close(tfd);
|
||||
// // Fire event 10_000_000ns = 10s after the os.timerfd_settime call.
|
||||
// var sit: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }
|
||||
// , .it_value = .{ .tv_sec = 0, .tv_nsec = 10 * (1000 * 1000 * 1000) } };
|
||||
// print("before os.timerfd_settime\n", .{});
|
||||
// try os.timerfd_settime(tfd, 0, &sit, null);
|
||||
|
||||
// var fds: [2]os.pollfd =
|
||||
// .{ .{ .fd = tfd, .events = os.linux.POLL.IN, .revents = 0 }
|
||||
// , .{ .fd = lfd, .events = os.linux.POLL.IN, .revents = 0 }};
|
||||
// var count = try os.poll(&fds, -1); // -1 => infinite waiting
|
||||
|
||||
var waiting_duration: i32 = 10 * 1000; // in ms
|
||||
print("waiting for 10 seconds, tops\n", .{});
|
||||
var ssockfd = stream.sockfd; // actual listener (server)
|
||||
var lfd: os.socket_t = undefined;
|
||||
|
||||
if (ssockfd) |sfd| { lfd = sfd; }
|
||||
else { return error.Undefined; }
|
||||
|
||||
print("Let's wait for an event (either stdin or unix socket)\n", .{});
|
||||
var count: usize = undefined;
|
||||
while(true) {
|
||||
var fds: [2]os.pollfd =
|
||||
.{.{ .fd = 0, .events = os.linux.POLL.IN, .revents = 0 }
|
||||
, .{ .fd = lfd, .events = os.linux.POLL.IN, .revents = 0 }};
|
||||
print("fds: {any}\n", .{fds});
|
||||
count = try os.poll(&fds, waiting_duration);
|
||||
if (count == 0) { print("no client, still waiting\n", .{}); continue; }
|
||||
print("fds NOW: {any}\n", .{fds});
|
||||
break;
|
||||
}
|
||||
|
||||
return stream.accept();
|
||||
}
|
||||
|
||||
fn receive_msg(stream: net.Stream) !void {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var reader = fbs.reader();
|
||||
|
||||
_ = try stream.read(buffer[0..]);
|
||||
|
||||
const msg_type = try reader.readByte();
|
||||
const msg_len = try reader.readIntBig(u32);
|
||||
const msg_payload = buffer[5..5+msg_len];
|
||||
print ("type: {}, len {}, content: {s}\n"
|
||||
, .{msg_type, msg_len, msg_payload});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
print("Init UNIX server to {s}...\n", .{path});
|
||||
var stream = server_init();
|
||||
defer stream.deinit();
|
||||
defer remove_unix_socket(path);
|
||||
|
||||
// TODO
|
||||
print("Waiting for a connection...\n", .{});
|
||||
var connection = try waiting_for_connection(&stream, path);
|
||||
print("Someone is connected! Receiving a message...\n", .{});
|
||||
try receive_msg(connection.stream);
|
||||
|
||||
print("Disconnection...\n", .{});
|
||||
disconnect(&stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
const mem = std.mem;
|
||||
const print = std.debug.print;
|
||||
|
||||
const Cmsghdr = @import("./cmsghdr.zig").Cmsghdr;
|
||||
|
||||
const system = std.os.system;
|
||||
const socket_t = std.os.socket_t;
|
||||
const msghdr = system.msghdr;
|
||||
const builtin = @import("builtin");
|
||||
const windows = std.os.windows;
|
||||
const errno = std.os.errno;
|
||||
const unexpectedErrno = std.os.unexpectedErrno;
|
||||
const SendMsgError = std.os.SendMsgError;
|
||||
|
||||
pub fn recvmsg(
|
||||
/// The file descriptor of the sending socket.
|
||||
sockfd: socket_t,
|
||||
/// Message header and iovecs
|
||||
msg: msghdr,
|
||||
flags: u32,
|
||||
) SendMsgError!usize {
|
||||
while (true) {
|
||||
var m = msg;
|
||||
const rc = system.recvmsg(sockfd, @ptrCast(*std.x.os.Socket.Message, &m), @intCast(c_int, flags));
|
||||
if (builtin.os.tag == .windows) {
|
||||
if (rc == windows.ws2_32.SOCKET_ERROR) {
|
||||
switch (windows.ws2_32.WSAGetLastError()) {
|
||||
.WSAEACCES => return error.AccessDenied,
|
||||
.WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
|
||||
.WSAECONNRESET => return error.ConnectionResetByPeer,
|
||||
.WSAEMSGSIZE => return error.MessageTooBig,
|
||||
.WSAENOBUFS => return error.SystemResources,
|
||||
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
|
||||
.WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||||
.WSAEDESTADDRREQ => unreachable, // A destination address is required.
|
||||
.WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
|
||||
.WSAEHOSTUNREACH => return error.NetworkUnreachable,
|
||||
// TODO: WSAEINPROGRESS, WSAEINTR
|
||||
.WSAEINVAL => unreachable,
|
||||
.WSAENETDOWN => return error.NetworkSubsystemFailed,
|
||||
.WSAENETRESET => return error.ConnectionResetByPeer,
|
||||
.WSAENETUNREACH => return error.NetworkUnreachable,
|
||||
.WSAENOTCONN => return error.SocketNotConnected,
|
||||
.WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
|
||||
.WSAEWOULDBLOCK => return error.WouldBlock,
|
||||
.WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
|
||||
else => |err| return windows.unexpectedWSAError(err),
|
||||
}
|
||||
} else {
|
||||
return @intCast(usize, rc);
|
||||
}
|
||||
} else {
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return @intCast(usize, rc),
|
||||
|
||||
.ACCES => return error.AccessDenied,
|
||||
.AGAIN => return error.WouldBlock,
|
||||
.ALREADY => return error.FastOpenAlreadyInProgress,
|
||||
.BADF => unreachable, // always a race condition
|
||||
.CONNRESET => return error.ConnectionResetByPeer,
|
||||
.DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
|
||||
.FAULT => unreachable, // An invalid user space address was specified for an argument.
|
||||
.INTR => continue,
|
||||
.INVAL => unreachable, // Invalid argument passed.
|
||||
.ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
|
||||
.MSGSIZE => return error.MessageTooBig,
|
||||
.NOBUFS => return error.SystemResources,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
|
||||
.OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
|
||||
.PIPE => return error.BrokenPipe,
|
||||
.AFNOSUPPORT => return error.AddressFamilyNotSupported,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.HOSTUNREACH => return error.NetworkUnreachable,
|
||||
.NETUNREACH => return error.NetworkUnreachable,
|
||||
.NOTCONN => return error.SocketNotConnected,
|
||||
.NETDOWN => return error.NetworkSubsystemFailed,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
|
||||
|
||||
fn server_init() net.StreamServer {
|
||||
// no reuse_address and default kernel_backlog
|
||||
return net.StreamServer.init(.{});
|
||||
}
|
||||
|
||||
fn waiting_for_connection(stream: *net.StreamServer
|
||||
, path: []const u8) !net.StreamServer.Connection {
|
||||
var address = try net.Address.initUnix(path);
|
||||
try stream.listen(address);
|
||||
return stream.accept();
|
||||
}
|
||||
|
||||
fn remove_unix_socket(path: []const u8) void {
|
||||
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
|
||||
else => { print("error: {}\n", .{err}); }
|
||||
};
|
||||
}
|
||||
|
||||
const SCM_RIGHTS: c_int = 1;
|
||||
|
||||
fn send_msg(sock: os.socket_t, msg: []const u8, fd: os.fd_t) void {
|
||||
var iov = [_]os.iovec_const{
|
||||
.{
|
||||
.iov_base = msg.ptr,
|
||||
.iov_len = msg.len,
|
||||
},
|
||||
};
|
||||
|
||||
var cmsg = Cmsghdr(os.fd_t).init(.{
|
||||
.level = os.SOL.SOCKET,
|
||||
.@"type" = SCM_RIGHTS,
|
||||
.data = fd,
|
||||
});
|
||||
|
||||
const len = os.sendmsg(sock, .{
|
||||
.name = undefined,
|
||||
.namelen = 0,
|
||||
.iov = &iov,
|
||||
.iovlen = iov.len,
|
||||
.control = &cmsg,
|
||||
.controllen = @sizeOf(@TypeOf(cmsg)),
|
||||
.flags = 0,
|
||||
}, 0) catch |err| {
|
||||
print("error sendmsg failed with {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
|
||||
if (len != msg.len) {
|
||||
// we don't have much choice but to exit here
|
||||
// log.err(@src(), "expected sendmsg to return {} but got {}", .{msg.len, len});
|
||||
print("expected sendmsg to return {} but got {}", .{msg.len, len});
|
||||
os.exit(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
fn receive_msg(sock: os.socket_t) !os.fd_t {
|
||||
var buffer: [100]u8 = undefined;
|
||||
|
||||
var iov = [1]os.iovec{
|
||||
.{
|
||||
.iov_base = buffer[0..],
|
||||
.iov_len = buffer.len,
|
||||
},
|
||||
};
|
||||
|
||||
var cmsg = Cmsghdr(os.fd_t).init(.{
|
||||
.level = os.SOL.SOCKET,
|
||||
.@"type" = SCM_RIGHTS,
|
||||
.data = undefined,
|
||||
});
|
||||
|
||||
var msg: std.os.msghdr = .{
|
||||
.name = undefined,
|
||||
.namelen = 0,
|
||||
.iov = &iov,
|
||||
.iovlen = iov.len,
|
||||
.control = &cmsg,
|
||||
.controllen = @sizeOf(@TypeOf(cmsg)),
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
const len = recvmsg(sock, msg, 0) catch |err| {
|
||||
print("error sendmsg failed with {s}", .{@errorName(err)});
|
||||
return 0;
|
||||
};
|
||||
|
||||
print("received {} bytes, fd is {}\n", .{len, @as(i32, cmsg.dataPtr().*)});
|
||||
print("iov base {s}\n", .{iov[0].iov_base[0..iov[0].iov_len - 1]});
|
||||
return @as(i32, cmsg.dataPtr().*);
|
||||
}
|
||||
|
||||
fn add_line_from_fd(fd: i32) !void {
|
||||
// var f = std.fs.File {.handle = fd};
|
||||
// defer f.close();
|
||||
_ = try std.os.write(fd, "hello this is another line\n");
|
||||
std.os.close(fd);
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
print("Init UNIX server to {s}...\n", .{path});
|
||||
var stream = server_init();
|
||||
defer stream.deinit();
|
||||
print("Waiting for a connection...\n", .{});
|
||||
var connection = try waiting_for_connection(&stream, path);
|
||||
defer remove_unix_socket(path);
|
||||
print("Someone is connected! Receiving a file descriptor...\n", .{});
|
||||
var fd = try receive_msg(connection.stream.handle);
|
||||
print("FD received, writing a line into the file...\n", .{});
|
||||
try add_line_from_fd(fd);
|
||||
print("Disconnection...\n", .{});
|
||||
disconnect(&stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const print = std.debug.print;
|
||||
|
||||
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
|
||||
|
||||
fn server_init() net.StreamServer {
|
||||
// no reuse_address and default kernel_backlog
|
||||
return net.StreamServer.init(.{});
|
||||
}
|
||||
|
||||
fn waiting_for_connection(stream: *net.StreamServer
|
||||
, path: []const u8) !net.StreamServer.Connection {
|
||||
var address = try net.Address.initUnix(path);
|
||||
try stream.listen(address);
|
||||
return stream.accept();
|
||||
}
|
||||
|
||||
fn remove_unix_socket(path: []const u8) void {
|
||||
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
|
||||
else => { print("error: {}\n", .{err}); }
|
||||
};
|
||||
}
|
||||
|
||||
fn receive_msg(stream: net.Stream) !void {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var reader = fbs.reader();
|
||||
|
||||
_ = try stream.read(buffer[0..]);
|
||||
|
||||
const msg_type = try reader.readByte();
|
||||
const msg_len = try reader.readIntBig(u32);
|
||||
const msg_payload = buffer[5..5+msg_len];
|
||||
print ("type: {}, len {}, content: {s}\n"
|
||||
, .{msg_type, msg_len, msg_payload});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
print("Init UNIX server to {s}...\n", .{path});
|
||||
var stream = server_init();
|
||||
defer stream.deinit();
|
||||
print("Waiting for a connection...\n", .{});
|
||||
var connection = try waiting_for_connection(&stream, path);
|
||||
defer remove_unix_socket(path);
|
||||
print("Someone is connected! Receiving a message...\n", .{});
|
||||
try receive_msg(connection.stream);
|
||||
print("Disconnection...\n", .{});
|
||||
disconnect(&stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const print = std.debug.print;
|
||||
|
||||
fn disconnect(stream: net.Stream) void { stream.close(); }
|
||||
|
||||
fn connect(path: []const u8) !net.Stream {
|
||||
return try net.connectUnixSocket(path);
|
||||
}
|
||||
|
||||
fn send_msg(stream: net.Stream) !usize {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
|
||||
try writer.writeByte(2); // DATA
|
||||
|
||||
const message = "hello everyone";
|
||||
try writer.writeIntBig(u32, message.len);
|
||||
_ = try writer.write(message);
|
||||
|
||||
return stream.write (fbs.getWritten());
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
print("Connection to {s}...\n", .{path});
|
||||
var stream = try connect(path);
|
||||
print("Connected! Sending a message...\n", .{});
|
||||
const bytecount = try send_msg(stream);
|
||||
print("Sent {} bytes! Disconnection...\n", .{bytecount});
|
||||
disconnect(stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const os = std.os;
|
||||
const print = std.debug.print;
|
||||
|
||||
const Cmsghdr = @import("./cmsghdr.zig").Cmsghdr;
|
||||
|
||||
fn disconnect(stream: net.Stream) void { stream.close(); }
|
||||
|
||||
fn connect(path: []const u8) !net.Stream {
|
||||
return try net.connectUnixSocket(path);
|
||||
}
|
||||
|
||||
const SCM_RIGHTS: c_int = 1;
|
||||
|
||||
fn send_msg(sock: os.socket_t, msg: []const u8, fd: os.fd_t) void {
|
||||
var iov = [_]os.iovec_const{
|
||||
.{
|
||||
.iov_base = msg.ptr,
|
||||
.iov_len = msg.len,
|
||||
},
|
||||
};
|
||||
|
||||
var cmsg = Cmsghdr(os.fd_t).init(.{
|
||||
.level = os.SOL.SOCKET,
|
||||
.@"type" = SCM_RIGHTS,
|
||||
.data = fd,
|
||||
});
|
||||
|
||||
const len = os.sendmsg(sock, .{
|
||||
.name = undefined,
|
||||
.namelen = 0,
|
||||
.iov = &iov,
|
||||
.iovlen = iov.len,
|
||||
.control = &cmsg,
|
||||
.controllen = @sizeOf(@TypeOf(cmsg)),
|
||||
.flags = 0,
|
||||
}, 0) catch |err| {
|
||||
print("error sendmsg failed with {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
|
||||
if (len != msg.len) {
|
||||
// we don't have much choice but to exit here
|
||||
// log.err(@src(), "expected sendmsg to return {} but got {}", .{msg.len, len});
|
||||
print("expected sendmsg to return {} but got {}", .{msg.len, len});
|
||||
os.exit(0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// const buffer_size = 10000;
|
||||
// var buffer: [buffer_size]u8 = undefined;
|
||||
// var fba = std.heap.fixedBufferAllocator(&buffer);
|
||||
|
||||
fn add_line_in_file() !void {
|
||||
var cwd = std.fs.cwd();
|
||||
var f = try cwd.createFile("some-file.log", .{.read = true});
|
||||
defer f.close();
|
||||
|
||||
var writer = f.writer();
|
||||
try writer.print("hello\n", .{});
|
||||
}
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
|
||||
print("Connection to {s}...\n", .{path});
|
||||
var stream = try connect(path);
|
||||
|
||||
print("Connected! Opening a file...\n", .{});
|
||||
var file = try std.fs.cwd().createFile("some-file.log", .{.read = true});
|
||||
defer file.close();
|
||||
|
||||
print("File opened! Writing some data into it...\n", .{});
|
||||
var writer = file.writer();
|
||||
try writer.print("hello this is the first process\n", .{});
|
||||
|
||||
print("Data written! Sending its fd...\n", .{});
|
||||
send_msg(stream.handle, "hello", file.handle);
|
||||
print("Sent fd! Disconnection...\n", .{});
|
||||
disconnect(stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const print = std.debug.print;
|
||||
|
||||
fn disconnect(stream: net.Stream) void { stream.close(); }
|
||||
|
||||
fn connect(path: []const u8) !net.Stream {
|
||||
return try net.connectUnixSocket(path);
|
||||
}
|
||||
|
||||
fn receive_msg(stream: net.Stream) !void {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var reader = fbs.reader();
|
||||
|
||||
_ = try stream.read(buffer[0..]);
|
||||
|
||||
const msg_type = try reader.readByte();
|
||||
const msg_len = try reader.readIntBig(u32);
|
||||
const msg_payload = buffer[5..5+msg_len];
|
||||
print ("type: {}, len {}, content: {s}\n"
|
||||
, .{msg_type, msg_len, msg_payload});
|
||||
}
|
||||
|
||||
fn send_msg(stream: net.Stream) !usize {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
|
||||
try writer.writeByte(2); // DATA
|
||||
|
||||
const message = "hello everyone";
|
||||
try writer.writeIntBig(u32, message.len);
|
||||
_ = try writer.write(message);
|
||||
|
||||
return stream.write (fbs.getWritten());
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/.TEST_USOCK";
|
||||
print("Connection to {s}...\n", .{path});
|
||||
var stream = try connect(path);
|
||||
print("Connected! Sending a message...\n", .{});
|
||||
const bytecount = try send_msg(stream);
|
||||
print("Sent {} bytes! Waiting a message...\n", .{bytecount});
|
||||
try receive_msg(stream);
|
||||
print("Received a message! Disconnection...\n", .{});
|
||||
disconnect(stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
# functions
|
||||
|
||||
var received_fd = @as(i32, cmsg.dataPtr().*);
|
||||
|
||||
std.mem.copy(u8, buffer, &msg_buffer);
|
||||
|
||||
@ptrCast(*std.x.os.Socket.Message, &m)
|
||||
|
||||
os.exit(0xff);
|
||||
|
||||
var network_envvar = std.process.getEnvVarOwned(fba, "IPC_NETWORK") catch |err| switch(err) {
|
||||
// error{ OutOfMemory, EnvironmentVariableNotFound, InvalidUtf8 } (ErrorSet)
|
||||
.EnvironmentVariableNotFound => { return; }, // no need to contact IPCd
|
||||
else => { return err; },
|
||||
};
|
||||
|
||||
|
||||
const log = std.log.scoped(.libipc_context);
|
||||
log.err("context.deinit(): IndexOutOfBounds", .{});
|
||||
log.debug("stuff(): IndexOutOfBounds", .{});
|
||||
|
||||
# Functions done
|
||||
|
||||
receive_fd
|
||||
send_fd
|
||||
|
||||
# switch
|
||||
|
||||
An example of `catch |err| switch(err)`.
|
||||
|
||||
# Test stuff
|
||||
|
||||
zig test src/main.zig
|
||||
|
||||
# Documentation
|
||||
|
||||
zig build-exe -femit-docs -fno-emit-bin src/main.zig
|
||||
|
||||
ACCESS_LOGS ?= ./access.log
|
||||
servedoc:
|
||||
darkhttpd docs/ --addr 127.0.0.1 --port 35000 --log $(ACCESS_LOGS)
|
||||
|
||||
### Frustration
|
||||
|
||||
Searching for a type, this type depends on a sub-type, which depends on the OS, which ultimately... cannot be documented automatically.
|
||||
|
||||
Example:
|
||||
|
||||
std.fs.File.Mode => const Mode: "mode_t" = os.mode_t;
|
||||
os.mode_t => const mode_t: "mode_t" = system.mode_t;
|
||||
|
||||
### anytype
|
||||
|
||||
// create a server path for the UNIX socket based on the service name
|
||||
pub fn server_path(self: *Self, service_name: []const u8, writer: anytype) !void {
|
||||
try writer.print("{s}/{s}", .{ self.rundir, service_name });
|
||||
}
|
||||
|
||||
From
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
try ctx.server_path("simple-context-test", writer);
|
||||
var path = fbs.getWritten();
|
||||
To
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var path = try std.fmt.bufPrint(&buffer, "{s}/{s}", .{ ctx.rundir, "simple-context-test" });
|
||||
|
||||
|
||||
# Errors
|
||||
|
||||
Double returning type => no need for specific return structures.
|
||||
|
||||
# Timer
|
||||
|
||||
const Timer = std.time.Timer;
|
||||
var timer = try Timer.start();
|
||||
var duration = timer.read() / 1000000; // ns -> ms
|
||||
|
||||
var value = db.get(key) orelse return error.notHere;
|
||||
|
||||
# There is still room for improvement.
|
||||
|
||||
When the result actually is a single value (anonymous hash).
|
||||
src/switch.zig:125:37: error: binary operator `|` has whitespace on one side, but not the other.
|
||||
self.db.fetchSwapRemove(fd) |k,v|{
|
|
@ -0,0 +1,11 @@
|
|||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
|
||||
pub fn main() !u8 {
|
||||
print("First close!\n", .{});
|
||||
std.os.close(1);
|
||||
print("SECOND close!\n", .{});
|
||||
std.os.close(1);
|
||||
print("Will it be printed?\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
const msg_type = @intToEnum(Message.Type, try reader.readByte());
|
||||
try writer.writeByte(@enumToInt(self.t));
|
||||
|
||||
var hexbuf: [4000]u8 = undefined;
|
||||
var hexfbs = std.io.fixedBufferStream(&hexbuf);
|
||||
var hexwriter = hexfbs.writer();
|
||||
try hexdump.hexdump(hexwriter, "Message.read input buffer", buffer);
|
||||
print("{s}\n", .{hexfbs.getWritten()});
|
|
@ -0,0 +1,57 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const print = std.debug.print;
|
||||
|
||||
const receive_fd = @import("./exchange-fd.zig").receive_fd;
|
||||
|
||||
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
|
||||
|
||||
fn server_init() net.StreamServer {
|
||||
// no reuse_address and default kernel_backlog
|
||||
return net.StreamServer.init(.{});
|
||||
}
|
||||
|
||||
fn waiting_for_connection(stream: *net.StreamServer
|
||||
, path: []const u8) !net.StreamServer.Connection {
|
||||
var address = try net.Address.initUnix(path);
|
||||
try stream.listen(address);
|
||||
return stream.accept();
|
||||
}
|
||||
|
||||
fn remove_unix_socket(path: []const u8) void {
|
||||
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
|
||||
else => { print("error: {}\n", .{err}); }
|
||||
};
|
||||
}
|
||||
|
||||
fn add_line_from_fd(fd: i32) !void {
|
||||
_ = try std.os.write(fd, "SECOND LINE\n");
|
||||
std.os.close(fd);
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/TEST_EXCHANGE_FD";
|
||||
|
||||
print("Init UNIX server to {s}...\n", .{path});
|
||||
var stream = server_init();
|
||||
defer stream.deinit();
|
||||
|
||||
print("Waiting for a connection...\n", .{});
|
||||
var connection = try waiting_for_connection(&stream, path);
|
||||
defer remove_unix_socket(path);
|
||||
|
||||
print("Someone is connected! Receiving a file descriptor...\n", .{});
|
||||
var msgbuffer: [1500]u8 = undefined;
|
||||
var msgsize: usize = 0;
|
||||
var fd = try receive_fd(connection.stream.handle, msgbuffer[0..], &msgsize);
|
||||
print("received fd: {}, payload: {s}\n", .{fd, msgbuffer[0..msgsize - 1]});
|
||||
|
||||
print("FD received, writing a line into the file...\n", .{});
|
||||
try add_line_from_fd(fd);
|
||||
|
||||
print("Disconnection...\n", .{});
|
||||
disconnect(&stream);
|
||||
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
|
||||
const send_fd = @import("./exchange-fd.zig").send_fd;
|
||||
|
||||
const print = std.debug.print;
|
||||
|
||||
fn disconnect(stream: net.Stream) void { stream.close(); }
|
||||
|
||||
fn connect(path: []const u8) !net.Stream {
|
||||
return try net.connectUnixSocket(path);
|
||||
}
|
||||
|
||||
fn add_line_in_file() !void {
|
||||
var cwd = std.fs.cwd();
|
||||
var f = try cwd.createFile("some-file.log", .{.read = true});
|
||||
defer f.close();
|
||||
|
||||
var writer = f.writer();
|
||||
try writer.print("hello\n", .{});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var path = "/tmp/TEST_EXCHANGE_FD";
|
||||
|
||||
print("Connection to {s}...\n", .{path});
|
||||
var stream = try connect(path);
|
||||
|
||||
print("Connected! Opening a file...\n", .{});
|
||||
var file = try std.fs.cwd().createFile("some-file.log", .{.read = true});
|
||||
defer file.close();
|
||||
|
||||
print("File opened! Writing some data into it...\n", .{});
|
||||
var writer = file.writer();
|
||||
try writer.print("hello this is the first process\n", .{});
|
||||
|
||||
print("Data written! Sending its fd...\n", .{});
|
||||
send_fd(stream.handle, "hello this is the payload", file.handle);
|
||||
print("Sent fd! Disconnection...\n", .{});
|
||||
disconnect(stream);
|
||||
print("Disconnected!\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
const std = @import("std");
|
||||
const hexdump = @import("./hexdump.zig");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const print = std.debug.print;
|
||||
|
||||
fn say_hello(some_number: i32) void {
|
||||
print ("hello number {}\n", .{some_number});
|
||||
}
|
||||
|
||||
fn say_coucou(some_number: i32) void {
|
||||
print ("coucou number {}\n", .{some_number});
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var fp: *const fn (i32) void = say_hello;
|
||||
// print ("currently fp {any}\n", .{fp});
|
||||
fp(10);
|
||||
fp = say_coucou;
|
||||
// print ("currently fp {p}\n", .{fp.?});
|
||||
fp(20);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const print = std.debug.print;
|
||||
const testing = std.testing;
|
||||
const print_eq = @import("./util.zig").print_eq;
|
||||
const URI = @import("./util.zig").URI;
|
||||
|
||||
fn connect_tcp(allocator: std.mem.Allocator) !net.Stream {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
|
||||
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
|
||||
error.EnvironmentVariableNotFound => blk: {
|
||||
print("no ADDRESS envvar: connecting on 127.0.0.1:9000\n", .{});
|
||||
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
|
||||
},
|
||||
else => { return err; },
|
||||
};
|
||||
defer allocator.free(address);
|
||||
|
||||
try writer.print("{s}", .{address});
|
||||
var tcp_address = fbs.getWritten();
|
||||
|
||||
var iterator = std.mem.split(u8, tcp_address, ":");
|
||||
var real_tcp_address = iterator.first();
|
||||
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
|
||||
|
||||
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
|
||||
|
||||
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
|
||||
var stream = try net.tcpConnectToAddress(socket_addr);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
var stream = try connect_tcp(allocator);
|
||||
_ = try stream.write("coucou");
|
||||
stream.close();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
const std = @import("std");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const print = std.debug.print;
|
||||
const testing = std.testing;
|
||||
const print_eq = @import("./util.zig").print_eq;
|
||||
const URI = @import("./util.zig").URI;
|
||||
|
||||
fn init_tcp_server(allocator: std.mem.Allocator) !net.StreamServer {
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
|
||||
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
|
||||
error.EnvironmentVariableNotFound => blk: {
|
||||
print("no ADDRESS envvar: TCPd will listen on 127.0.0.1:9000\n", .{});
|
||||
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
|
||||
},
|
||||
else => { return err; },
|
||||
};
|
||||
defer allocator.free(address);
|
||||
|
||||
try writer.print("{s}", .{address});
|
||||
var tcp_address = fbs.getWritten();
|
||||
|
||||
var iterator = std.mem.split(u8, tcp_address, ":");
|
||||
var real_tcp_address = iterator.first();
|
||||
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
|
||||
|
||||
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
|
||||
|
||||
var server = net.StreamServer.init(.{.reuse_address = true});
|
||||
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
|
||||
try server.listen(socket_addr);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
fn accept_read(server: *net.StreamServer) !void {
|
||||
var client = try server.accept(); // net.StreamServer.Connection
|
||||
var buffer: [1000]u8 = undefined;
|
||||
var size = try client.stream.read(&buffer);
|
||||
print ("received: {s}\n", .{buffer[0..size]});
|
||||
client.stream.close();
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
var streamserver = try init_tcp_server(allocator);
|
||||
try accept_read(&streamserver);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const std = @import("std");
|
||||
const hexdump = @import("./hexdump.zig");
|
||||
const net = std.net;
|
||||
const fmt = std.fmt;
|
||||
const os = std.os;
|
||||
|
||||
const print = std.debug.print;
|
||||
const ipc = @import("./main.zig");
|
||||
const Message = ipc.Message;
|
||||
|
||||
pub fn main() !u8 {
|
||||
const config = .{.safety = true};
|
||||
var gpa = std.heap.GeneralPurposeAllocator(config){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var ctx = try ipc.Context.init(allocator);
|
||||
ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
|
||||
|
||||
print("Goodbye\n", .{});
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
const std = @import("std");
|
||||
const ipc = @import("./ipc.zig");
|
||||
const hexdump = @import("./hexdump.zig");
|
||||
const print = std.debug.print;
|
||||
|
||||
|
||||
pub fn main() !void {
|
||||
var allocator = std.heap.c_allocator;
|
||||
|
||||
var buffer = [_]u8{0} ** 10000;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
var writer = fbs.writer();
|
||||
|
||||
var m = try ipc.Message.init (9, allocator, "hello this is me!");
|
||||
defer m.deinit();
|
||||
|
||||
_ = try m.write(writer);
|
||||
|
||||
var msg_bytes = fbs.getWritten();
|
||||
|
||||
// var hexbuf: [4000]u8 = undefined;
|
||||
// var hexfbs = std.io.fixedBufferStream(&hexbuf);
|
||||
// var hexwriter = hexfbs.writer();
|
||||
// try hexdump.hexdump(hexwriter, "What should be written in output", msg_bytes);
|
||||
// print("{s}\n", .{hexfbs.getWritten()});
|
||||
|
||||
var out = std.io.getStdOut();
|
||||
_ = try out.write("pong");
|
||||
std.time.sleep(1_000_000_000); // wait for 1 seconds
|
||||
_ = try out.write(msg_bytes);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue