Compare commits

..

49 Commits

Author SHA1 Message Date
Karchnu 282cc59c18 version bump 2020-07-12 22:00:18 +02:00
Karchnu 045b1433f5 closing sockets (but not the client in a switch) 2020-07-12 20:35:55 +02:00
Karchnu 2813bd2695 fixed segfault 2020-07-11 10:44:47 +02:00
Karchnu 02c25c150d fixing a memory leak 2020-07-10 23:43:11 +02:00
Karchnu ff21ff42cb almost usable version 2020-07-10 20:18:17 +02:00
Karchnu c897edf9e7 a lot of printf 2020-07-10 12:19:03 +02:00
Karchnu 58aab88dbf callbacks kinda ok 2020-07-08 18:51:25 +02:00
Karchnu 553a550d97 default cb_out OK 2020-07-08 13:51:32 +02:00
Karchnu 0a8680a7e8 usock doesn't handle memory allocation anymore 2020-07-08 13:38:48 +02:00
Karchnu 8ccefcc40f lacking default cb_out 2020-07-08 12:40:24 +02:00
Karchnu 74fea778f7 more consistant connection types (and their use) 2020-07-08 09:57:33 +02:00
Karchnu 12427217c6 Compilation fixed for examples. 2020-07-08 01:15:18 +02:00
Karchnu 90a51a7ed7 buffers don't require malloc anymore 2020-07-08 01:08:00 +02:00
Karchnu 98a1389376 callbacks 2020-07-08 00:07:49 +02:00
Karchnu ff9d6ed2b8 Correction examples. 2020-07-06 14:38:06 +02:00
Karchnu 47408ef653 fixed 2020-07-06 09:02:13 +02:00
Karchnu 4c45fb7d12 s/events_loop/wait_event/ 2020-07-06 08:43:06 +02:00
Karchnu 8439cc0f18 Things. 2020-07-06 08:42:05 +02:00
Karchnu f1790cf314 switching ok 2020-07-04 19:29:59 +02:00
Karchnu 6243b818f1 corrections 2020-07-04 19:03:21 +02:00
Karchnu bef3b28656 simple-tcp: corrections and debug print 2020-07-04 19:02:43 +02:00
Karchnu 9a83993795 better ipcd 2020-07-04 14:41:21 +02:00
Karchnu e8cd339622 better simple-tcpd 2020-07-04 14:40:55 +02:00
Karchnu df373aea6a simple-tcp: improvments. 2020-07-04 14:21:02 +02:00
Karchnu fe87f31c34 pongspam OK 2020-07-04 14:06:51 +02:00
Karchnu e98b544539 pongd OK 2020-07-04 11:45:09 +02:00
Karchnu 1377e6fd28 pongd 2020-07-04 11:38:11 +02:00
Karchnu 36cdda36e0 pong client OK 2020-07-04 11:12:12 +02:00
Karchnu fbb7394f31 tests now compile again 2020-07-04 11:01:24 +02:00
Karchnu f89ab91917 timer works as expected 2020-07-04 10:11:19 +02:00
Karchnu 3b1aaf36c2 timer 2020-07-03 22:27:56 +02:00
Karchnu 0f46858e78 print context 2020-07-02 21:33:59 +02:00
Karchnu b6f32819e5 networkd -> ipcd 2020-07-01 14:24:45 +02:00
Karchnu 2bba5cb232 Removing printf. 2020-07-01 13:26:22 +02:00
Karchnu 71db43802e Multiple clients: ok. 2020-07-01 12:47:04 +02:00
Karchnu a5877101eb WIP: no mem leaks on simple use. switch still to test. 2020-07-01 09:53:24 +02:00
Karchnu f4ffd7386f No leaks, no error whatsoever so far. 2020-06-30 13:49:21 +02:00
Karchnu 292cca45e0 WIP: messages are sent and received OK 2020-06-30 12:59:05 +02:00
Karchnu a6ca358847 WIP!! 2020-06-29 21:07:19 +02:00
Karchnu 2a8e9a3707 WIP: connection OK, timer OK 2020-06-29 19:35:52 +02:00
Karchnu 9cdf868755 WIP 2020-06-29 16:49:54 +02:00
Karchnu 9429be80b3 Buffered writings: WIP. 2020-06-29 16:22:14 +02:00
Karchnu 0cd6f5bebd Compiles, not tested and no write buffer. 2020-06-29 00:53:40 +02:00
Karchnu 89d8881a40 Compile again, but lacks poll(2) call. 2020-06-28 18:05:28 +02:00
Karchnu 1dc5b4bcbe WIP 2020-06-28 17:22:58 +02:00
Karchnu 44992e940c WIP 2020-06-28 15:40:02 +02:00
Karchnu 05d483a712 WIP 2020-06-28 01:13:48 +02:00
Karchnu 91793c45cc WIP 2020-06-27 19:16:07 +02:00
Karchnu 85b54b0db6 poll(2): WIP. 2020-06-27 04:45:43 +02:00
119 changed files with 162 additions and 8624 deletions

1
.gitignore vendored
View File

@ -6,4 +6,3 @@
*.bin
*.dSYM
drop/
pres/

View File

@ -1,5 +1,5 @@
PACKAGE = 'libipc'
VERSION = '0.7.2'
VERSION = '0.6.0'
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 ' IN > $(LIBDIR)/libipc.so.0.7.2'
@echo ' IN > $(LIBDIR)/libipc.so.0.6.0'
$(Q)mkdir -p '$(DESTDIR)$(LIBDIR)'
$(Q)install -m0755 libipc.so $(DESTDIR)$(LIBDIR)/libipc.so.0.7.2
@echo ' LN > $(LIBDIR)/libipc.so.0.7'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so.0.7'
$(Q)install -m0755 libipc.so $(DESTDIR)$(LIBDIR)/libipc.so.0.6.0
@echo ' LN > $(LIBDIR)/libipc.so.0.6'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so.0.6'
@echo ' LN > $(LIBDIR)/libipc.so.0'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
@echo ' LN > $(LIBDIR)/libipc.so'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so'
libipc.so.clean:
@echo ' RM > libipc.so'
$(Q)rm -f libipc.so
libipc.so.uninstall:
@echo ' RM > $(LIBDIR)/libipc.so.0.7.2'
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.7.2'
@echo ' RM > $(LIBDIR)/libipc.so.0.7'
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.7'
@echo ' RM > $(LIBDIR)/libipc.so.0.6.0'
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.6.0'
@echo ' RM > $(LIBDIR)/libipc.so.0.6'
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0.6'
@echo ' RM > $(LIBDIR)/libipc.so.0'
$(Q)rm -f '$(DESTDIR)$(LIBDIR)/libipc.so.0'
@echo ' RM > $(LIBDIR)/libipc.so'
@ -327,7 +327,7 @@ $(PACKAGE)-$(VERSION).tar.bz2: distdir
$(PACKAGE)-$(VERSION)/src/utils.h
help:
@echo ' :: libipc-0.7.2'
@echo ' :: libipc-0.6.0'
@echo ''
@echo 'Generic targets:'
@echo ' - help  Prints this help message.'

View File

@ -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 in `man/libipc.7`.
See the presentation in [docs/libipc.md](docs/libipc.md).
See the introductory [man page](man/libipc.7.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,19 +25,23 @@ See the presentation in [docs/libipc.md](docs/libipc.md).
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 `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 use `libevent` for performance improvments
- `libipc` should be thread-safe
# Planning for 1.0
- `libipc` *may* be written in Zig
- `libipc` should have usable bindings in several languages
[zigimpl]: https://git.baguette.netlib.re/Baguette/libipc
# Implementation design
## Memory management
1. Prefer stack over mallocs.
2. Basic functions (such as *usock_*) should not handle memory allocation.

View File

@ -1,34 +0,0 @@
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;
}
}

2
docs/.gitignore vendored
View File

@ -1,2 +0,0 @@
catpoint
pointtools

View File

@ -1,151 +0,0 @@
#!/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
}' -

View File

@ -1,34 +0,0 @@
#!/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

View File

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

View File

@ -1,380 +0,0 @@
## 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`

View File

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

View File

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

View File

@ -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;

View File

@ -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);

View File

@ -30,7 +30,7 @@ void non_interactive (int verbosity, size_t nb_msg, char *msg_str)
SECURE_DECLARATION (struct ipc_message, m);
// init service
TEST_IPC_QUIT_ON_ERROR (ipc_connection (ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_QUIT_ON_ERROR (ipc_connection (ctx, SERVICE_NAME), EXIT_FAILURE);
if (verbosity > 1) {
printf ("msg to send (%ld): %.*s\n", (ssize_t) strlen (MSG) + 1, (int)strlen (MSG), MSG);
@ -39,9 +39,6 @@ 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);
@ -57,7 +54,7 @@ void non_interactive (int verbosity, size_t nb_msg, char *msg_str)
void interactive ()
{
// init service
TEST_IPC_QUIT_ON_ERROR (ipc_connection (ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_QUIT_ON_ERROR (ipc_connection (ctx, SERVICE_NAME), EXIT_FAILURE);
SECURE_DECLARATION (struct ipc_error, ret);
SECURE_DECLARATION (struct ipc_event, event);
@ -113,16 +110,16 @@ void interactive ()
fprintf (stderr, "%s", ret.error_message);
exit (EXIT_FAILURE);
}
#if 1
#if 0
printf ("\n");
printf ("right before sending a message\n");
#endif
ret = ipc_write_fd (ctx->pollfd[1].fd, m);
ret = ipc_write (ctx, m);
if (ret.error_code != IPC_ERROR_NONE) {
fprintf (stderr, "%s", ret.error_message);
exit (EXIT_FAILURE);
}
#if 1
#if 0
printf ("right after sending a message\n");
#endif
@ -148,7 +145,7 @@ void interactive ()
int main (int argc, char *argv[])
{
printf("usage: %s [verbosity #messages message]\n", argv[0]);
printf("usage: %s [verbosity #messages message]", argv[0]);
ctx = malloc (sizeof (struct ipc_ctx));
memset (ctx, 0, sizeof (struct ipc_ctx));

View File

@ -136,7 +136,6 @@ 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]);
}

View File

@ -30,7 +30,7 @@ void non_interactive ()
SECURE_DECLARATION (struct ipc_message, m);
// init service
TEST_IPC_Q (ipc_connection (ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (ctx, SERVICE_NAME), EXIT_FAILURE);
TEST_IPC_Q (ipc_message_format_data (&m, 42, MSG, (ssize_t) strlen (MSG) + 1), EXIT_FAILURE);
printf ("msg to send (%ld): %.*s\n", (ssize_t) strlen (MSG) + 1, (int)strlen (MSG), MSG);
@ -48,7 +48,7 @@ void non_interactive ()
void interactive ()
{
// init service
TEST_IPC_Q (ipc_connection (ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (ctx, SERVICE_NAME), EXIT_FAILURE);
SECURE_DECLARATION (struct ipc_event, event);

View File

@ -90,11 +90,14 @@ void tcp_connection (int fd)
T_PERROR_Q ((send (fd, "OK", 2, 0) <= 0), "sending a message", EXIT_FAILURE);
printf ("connection to %s\n", buf);
struct ipc_error ret = ipc_connection_switched (ctx, buf, fd, NULL);
struct ipc_error ret = ipc_connection_switched (ctx, buf);
if (ret.error_code != IPC_ERROR_NONE) {
fprintf (stderr, "%s\n", ret.error_message);
exit (EXIT_FAILURE);
}
ipc_switching_add (&ctx->switchdb, fd, ctx->pollfd[ctx->size-1].fd);
ipc_ctx_fd_type (ctx, fd, IPC_CONNECTION_TYPE_SWITCHED);
}
int accept_new_client (int serverfd)

View File

@ -26,7 +26,7 @@ int main (void)
/** TODO: contact the service */
printf ("WARNING: currently this program only ask for pong service %d\n", ret.error_code);
TEST_IPC_Q (ipc_connection (&ctx, "pong", NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (&ctx, "pong"), EXIT_FAILURE);
ipc_provide_fd (client_fd, ctx.pollfd[ctx.size-1].fd);

View File

@ -38,7 +38,7 @@ void interactive ()
}
// init service
TEST_IPC_Q (ipc_connection (ctx, service_name, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (ctx, service_name), EXIT_FAILURE);
ipc_add_fd (ctx, 0); // add STDIN

View File

@ -1,183 +0,0 @@
.\" 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

View File

@ -17,15 +17,15 @@ It provides both client and server code.
// server initialization
_enum ipc_errors_ **ipc_server_init** (*char* \*\*env, *const char* \*sname);
_enum ipc_errors_ **ipc_server_init** (*char* \*\*env , *struct ipc_connection_info* \*srv, *const char* \*sname);
// connection establishement to a server
_enum ipc_errors_ **ipc_connection** (*char* \*\*env, *const char* \*, int \*serverfd);
_enum ipc_errors_ **ipc_connection** (*char* \*\*env, *struct ipc_connection_info* \*, *const char* \*);
// closing a server
_enum ipc_errors_ **ipc_close** (*struct ipc_connection_info* \*srv);
_enum ipc_errors_ **ipc_server_close** (*struct ipc_connection_info* \*srv);
// closing a connection

View File

@ -1,6 +1,6 @@
package=libipc # Package name.
version=0.7.2 # Package version.
version=0.7.0 # Package version.
# Our targets are the library and its documentation.
targets=(libipc man/libipc.7)

View File

@ -58,11 +58,9 @@ 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?
@ -75,6 +73,7 @@ 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;
}
@ -96,24 +95,7 @@ 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 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;
}
TEST_IPC_RR (ipc_write_fd (ipcd_fd, &msg), "cannot send a message to networkd");
struct ipc_error ret = ipc_receive_fd (ipcd_fd, pfd);
if (ret.error_code == IPC_ERROR_NONE) {
@ -134,7 +116,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 ipcd connection");
TEST_IPC_P (ipc_contact_ipcd (&pollfd.fd, sname), "error during networkd connection");
// if ipcd did not initiate the connection
if (pollfd.fd <= 0) {
@ -169,10 +151,10 @@ int ipc_ctx_fd_type (struct ipc_ctx *ctx, int fd, enum ipc_connection_type type)
return -1;
}
struct ipc_error ipc_connection (struct ipc_ctx *ctx, const char *sname, int *serverfd)
struct ipc_error ipc_connection (struct ipc_ctx *ctx, const char *sname)
{
// Data received on the socket = messages, not new clients, and not switched (no callbacks).
return ipc_connection_ (ctx, sname, IPC_CONNECTION_TYPE_IPC, serverfd);
return ipc_connection_ (ctx, sname, IPC_CONNECTION_TYPE_IPC, NULL);
}
struct ipc_error ipc_connection_switched (struct ipc_ctx *ctx, const char *sname, int clientfd, int *serverfd)
@ -204,30 +186,12 @@ 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 ipc_close_all");
TEST_IPC_P (ipc_close (ctx, i), "cannot close a connection in handle_message");
}
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);
@ -235,14 +199,27 @@ struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index)
SECURE_DECLARATION (struct ipc_error, ret);
int fd = ctx->pollfd[index].fd;
#if 0
if (fd_is_valid (fd)) {
printf ("ipc_close: fd is valid ==> %d\n", fd);
if (ctx->cinfos[index].type == IPC_CONNECTION_TYPE_EXTERNAL) {
printf ("=== === === external fd: not closing %d\n", fd);
}
else {
ret = usock_close (fd);
}
}
else {
printf ("!!!!!!!!!!! !! !! !! IPC_CLOSE: fd is not valid!! ==> %d\n", fd);
}
#else
// Closing the file descriptor only if it is not an external connection,
// this should be handled by the libipc user application.
if (ctx->cinfos[index].type != IPC_CONNECTION_TYPE_EXTERNAL) {
ret = usock_close (fd);
}
// Remove all messages for this fd.
ipc_remove_messages_for_fd (ctx, fd);
#endif
// Verify that the close was OK.
if (ret.error_code != IPC_ERROR_NONE) {
@ -284,7 +261,7 @@ struct ipc_error ipc_accept_add (struct ipc_event *event, struct ipc_ctx *ctx, u
}
// receive then format in an ipc_message structure
struct ipc_error ipc_read_fd (int32_t fd, struct ipc_message *m)
struct ipc_error ipc_read (const struct ipc_ctx *ctx, uint32_t index, struct ipc_message *m)
{
T_R ((m == NULL), IPC_ERROR_READ__NO_MESSAGE_PARAM);
@ -293,17 +270,12 @@ struct ipc_error ipc_read_fd (int32_t fd, struct ipc_message *m)
char *pbuf = buf;
// On error or closed recipient, the buffer already freed.
TEST_IPC_RETURN_ON_ERROR (usock_recv (fd, &pbuf, &msize));
TEST_IPC_RETURN_ON_ERROR (usock_recv (ctx->pollfd[index].fd, &pbuf, &msize));
TEST_IPC_RETURN_ON_ERROR (ipc_message_format_read (m, buf, msize));
IPC_RETURN_NO_ERROR; // propagates ipc_message_format return
}
struct ipc_error ipc_read (const struct ipc_ctx *ctx, uint32_t index, struct ipc_message *m)
{
return ipc_read_fd (ctx->pollfd[index].fd, m);
}
struct ipc_error ipc_write_fd (int fd, const struct ipc_message *m)
{
size_t msize = 0;
@ -369,8 +341,6 @@ struct ipc_error ipc_del (struct ipc_ctx *ctx, uint32_t index)
ctx->cinfos[index].spath = NULL;
}
ipc_remove_messages_for_fd (ctx, ctx->pollfd[index].fd);
ctx->size--;
if (ctx->size == 0) {
@ -444,9 +414,7 @@ struct ipc_error handle_writing_message (struct ipc_event *event, struct ipc_ctx
m = &ctx->tx.messages[i];
mfd = m->fd;
if (txfd == mfd) {
// 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");
TEST_IPC_RR (ipc_write_fd (txfd, m), "cannot send a message to the client");
// Freeing the message structure.
ipc_message_empty (m);
@ -474,24 +442,14 @@ 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, fd, NULL);
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, index, ctx->pollfd[index].fd, NULL);
return rvalue;
}
@ -500,12 +458,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);
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");
ipc_message_empty (m);
free (m);
// warning: do not forget to free the ipc_client structure
IPC_RETURN_NO_ERROR;
}
@ -536,29 +494,13 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
IPC_EVENT_CLEAN (event);
// By default, everything is alright.
SECURE_DECLARATION(struct ipc_error, final_return);
final_return.error_code = IPC_ERROR_NONE;
int32_t n;
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;
break;
}
}
}
@ -571,18 +513,7 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
gettimeofday(&tv_1, NULL);
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) {
if ((n = poll(ctx->pollfd, ctx->size, *timer)) < 0) {
IPC_RETURN_ERROR (IPC_ERROR_WAIT_EVENT__POLL);
}
@ -601,44 +532,38 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
}
// Timeout.
if (n == 0 && timer_ != 0) {
if (n == 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++) {
// Whatever happens, we have the fd and the index in event.
event->index = i;
event->origin = ctx->pollfd[i].fd;
for (size_t i = 0; i <= ctx->size; i++) {
// Something to read or connection.
if (ctx->pollfd[i].revents & POLLIN || ctx->cinfos[i].more_to_read == 1) {
// Avoiding loops.
ctx->cinfos[i].more_to_read = 0;
if (ctx->pollfd[i].revents & POLLIN) {
// In case there is something to read for the server socket: new client.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SERVER) {
final_return = ipc_accept_add (event, ctx, i);
goto wait_event_exit;
return ipc_accept_add (event, ctx, i);
}
// fd is switched: using callbacks for IO operations.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SWITCHED) {
final_return = handle_switched_message (event, ctx, i);
goto wait_event_exit;
#if 0
int fd_validity = fd_is_valid (ctx->pollfd[i].fd);
if (! fd_validity) {
printf ("switch happening in C: %d FD IS INVALID:::::::::: IM BROKEN INSIIIIIIIDE\n", ctx->pollfd[i].fd);
}
#endif
return handle_switched_message (event, ctx, i);
}
// 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);
// Default: return no error.
goto wait_event_exit;
IPC_RETURN_NO_ERROR;
}
final_return = handle_new_message (event, ctx, i);
goto wait_event_exit;
return handle_new_message (event, ctx, i);
}
// Something can be sent.
@ -647,50 +572,20 @@ 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) {
final_return = handle_writing_switched_message (event, ctx, i);
goto wait_event_exit;
return handle_writing_switched_message (event, ctx, i);
}
final_return = handle_writing_message (event, ctx, i);
goto wait_event_exit;
return handle_writing_message (event, ctx, i);
}
// 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);
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;
return ipc_close (ctx, i);
}
} /** for loop: end of the message handling */
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;
IPC_RETURN_NO_ERROR;
}

View File

@ -18,7 +18,6 @@
#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
@ -42,7 +41,7 @@ enum msg_types {
, MSG_TYPE_ERR = 1
, MSG_TYPE_DATA = 2
, MSG_TYPE_NETWORK_LOOKUP = 3
};
} message_types;
/**
* Event types.
@ -232,7 +231,6 @@ enum ipc_connection_type {
struct ipc_connection_info {
enum ipc_connection_type type;
short int more_to_read;
char *spath; // max size: PATH_MAX
};
@ -252,9 +250,9 @@ struct ipc_messages {
struct ipc_switching {
int orig;
int dest;
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m, short int *more_to_read);
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m);
enum ipccb (*orig_out) (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_in) (int origin_fd, struct ipc_message *m);
enum ipccb (*dest_out) (int origin_fd, struct ipc_message *m);
};
@ -317,8 +315,6 @@ 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);\
@ -333,7 +329,7 @@ struct ipc_event {
struct ipc_error ipc_wait_event (struct ipc_ctx *, struct ipc_event *, int *timer);
struct ipc_error ipc_server_init (struct ipc_ctx *ctx, const char *sname);
struct ipc_error ipc_connection (struct ipc_ctx *ctx, const char *sname, int *fd);
struct ipc_error ipc_connection (struct ipc_ctx *ctx, const char *sname);
struct ipc_error ipc_connection_switched (struct ipc_ctx *ctx, const char *sname, int clientfd, int *serverfd);
struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index);
@ -341,8 +337,7 @@ struct ipc_error ipc_close_all (struct ipc_ctx *ctx);
void ipc_ctx_free (struct ipc_ctx *ctx);
struct ipc_error ipc_read (const struct ipc_ctx *, uint32_t index, struct ipc_message *m);
struct ipc_error ipc_read_fd (int32_t fd, struct ipc_message *m);
struct ipc_error ipc_read (const struct ipc_ctx *, uint32_t index, struct ipc_message *m);
struct ipc_error ipc_write (struct ipc_ctx *, const struct ipc_message *m);
struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx, int index);
@ -379,7 +374,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\n", f, m); } break;
#define ERROR_CASE(e,f,m) case e : { fprintf (stderr, "function %s: %s", f, m); } break;
/***
* non public functions
@ -403,17 +398,16 @@ 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, short int *more_to_read));
, enum ipccb (*cb_in )(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, short int *more_to_read)
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
, 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);
@ -526,7 +520,6 @@ 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) {\
@ -534,8 +527,6 @@ 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);\
}\
}

View File

@ -88,10 +88,14 @@ 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));
}
@ -110,11 +114,8 @@ 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;
}
int ipc_ctx_switching_del (struct ipc_ctx *ctx, int fd)
{
return ipc_switching_del (&ctx->switchdb, fd);
// printf ("ipc_switching_add END: switchdb has %ld entries\n", is->size);
}
int ipc_switching_del (struct ipc_switchings *is, int fd)
@ -196,12 +197,11 @@ void ipc_switching_free (struct ipc_switchings *is)
}
enum ipccb
default_cb_in(int fd, struct ipc_message *m, short int *more_to_read)
default_cb_in(int fd, struct ipc_message *m)
{
*more_to_read = 0;
size_t msize = IPC_MAX_MESSAGE_SIZE;
SECURE_BUFFER_DECLARATION (char, buf, msize);
// TODO: fix buffer size for switching messages
size_t msize = 4096;
char buf[msize];
char *pbuf = buf;
// By default, usock_read (a wrapper around read(2)) is used.
@ -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, short int *more_to_read))
, enum ipccb (*cb_in )(int fd, struct ipc_message *m))
{
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, short int *more_to_read)
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
, enum ipccb (*cb_out)(int fd, struct ipc_message *m))
{
struct ipc_switching *sw = NULL;
@ -287,7 +287,6 @@ 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;
@ -298,7 +297,6 @@ 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);
@ -307,24 +305,22 @@ 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, &more_to_read);
r = default_cb_in (talkingfd, &m);
}
else {
r = (*sw->orig_in)(talkingfd, &m, &more_to_read);
r = (*sw->orig_in)(talkingfd, &m);
}
}
else {
dest_fd = sw->orig;
if (sw->dest_in == NULL) {
r = default_cb_in (talkingfd, &m, &more_to_read);
r = default_cb_in (talkingfd, &m);
}
else {
r = (*sw->dest_in)(talkingfd, &m, &more_to_read);
r = (*sw->dest_in)(talkingfd, &m);
}
}
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:
@ -343,7 +339,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);
@ -355,14 +351,13 @@ 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. remove both fd from switchdb
// Client and servers should be closed by the libipc user application.
// close (sw->dest);
// close (talkingfd);
// 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.
// close (talkingfd);
ipc_del_fd (ctx, talkingfd);
ipc_switching_del (&ctx->switchdb, talkingfd);
// 2. set event (either error or disconnection)
@ -382,6 +377,8 @@ 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);

View File

@ -23,73 +23,8 @@
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);
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);
T_R ((ret <= 0), IPC_ERROR_USOCK_SEND);
*sent = ret;
IPC_RETURN_NO_ERROR;
}
@ -100,57 +35,37 @@ 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);
if (ret_recv > 0) {
#ifdef IPC_DEBUG
if (ret_recv > 0) {
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);
#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);
// }
msize = ntohl (msize);
if (msize > IPC_MAX_MESSAGE_SIZE) {
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;
msize_read += ret_recv - IPC_HEADER_SIZE;
} else if (ret_recv < 0) {
*len = 0;
@ -196,17 +111,11 @@ struct ipc_error usock_recv (const int32_t fd, char **buf, size_t * len)
, "usock_recv: recv < 0, is the message size malformed?");
}
// if (msize > msize_read) {
// printf ("USOCKET: loop again for %d (read %u/%u)\n", fd, msize_read, msize);
// }
} while (msize > msize_read);
// In case msize still is 0, recv didn't worked as expected.
} while (msize > 0 && msize > msize_read - IPC_HEADER_SIZE);
*len = msize + IPC_HEADER_SIZE;
// 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
// 1 on none byte received, indicates a closed recipient
if (ret_recv == 0) {
*len = 0;
IPC_RETURN_ERROR (IPC_ERROR_CLOSED_RECIPIENT);
@ -226,7 +135,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_STREAM, 0)) == -1), "socket", IPC_ERROR_USOCK_CONNECT__SOCKET);
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 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));
@ -260,7 +169,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_STREAM, 0)) == -1)
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1)
, "socket", IPC_ERROR_USOCK_INIT__WRONG_FILE_DESCRIPTOR);
// delete the unix socket if already created

View File

@ -15,7 +15,7 @@ int main(int argc, char * argv[])
SECURE_DECLARATION(struct ipc_ctx, ctx);
SECURE_DECLARATION(struct ipc_event, event);
ret = ipc_connection (&ctx, SERVICE_NAME, NULL);
ret = ipc_connection (&ctx, SERVICE_NAME);
if (ret.error_code != IPC_ERROR_NONE) {
printf ("error: %s\n", ipc_errors_get(ret.error_code));
return EXIT_FAILURE;
@ -32,7 +32,6 @@ 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;
}

View File

@ -11,7 +11,6 @@ int main(int argc, char * argv[])
argc = (int) argc;
argv = (char **) argv;
SECURE_DECLARATION(struct ipc_ctx, ctx);
int timer = 10000; // 10 seconds timer

View File

@ -19,7 +19,7 @@ void non_interactive ()
SECURE_DECLARATION(struct ipc_ctx, ctx);
// init service
TEST_IPC_Q(ipc_connection (&ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q(ipc_connection (&ctx, SERVICE_NAME), EXIT_FAILURE);
int server_fd = ctx.pollfd[0].fd;

View File

@ -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
}
@ -69,20 +69,18 @@ int main(void)
SECURE_DECLARATION(struct ipc_ctx, ctx2);
SECURE_DECLARATION(struct ipc_event, event);
TEST_IPC_Q (ipc_connection (&ctx1, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (&ctx2, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (&ctx1, SERVICE_NAME), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (&ctx2, SERVICE_NAME), EXIT_FAILURE);
send_message (&ctx1);
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;
}

View File

@ -6,20 +6,6 @@
#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[])
{
@ -27,7 +13,6 @@ 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");
@ -39,13 +24,7 @@ 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);
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");
}
TEST_IPC_WAIT_EVENT_Q (ipc_wait_event (&ctx, &event, &timer), EXIT_FAILURE);
switch (event.type) {
case IPC_EVENT_TYPE_TIMER : {
@ -62,12 +41,8 @@ int main_loop(int argc, char * argv[])
};
break;
case IPC_EVENT_TYPE_MESSAGE : {
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);
printf ("received message: %s\n", ((struct ipc_message*) event.m)->payload);
ipc_write (&ctx, (struct ipc_message*) event.m);
}
break;
case IPC_EVENT_TYPE_TX : {
@ -79,6 +54,7 @@ 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;
}
}
@ -92,17 +68,21 @@ 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;
}

View File

@ -47,13 +47,12 @@ int main(void)
{
SECURE_DECLARATION(struct ipc_ctx, ctx);
TEST_IPC_Q(ipc_connection (&ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q(ipc_connection (&ctx, SERVICE_NAME), EXIT_FAILURE);
send_message (&ctx);
read_message (&ctx);
TEST_IPC_Q(ipc_close_all (&ctx), EXIT_FAILURE);
ipc_ctx_free(&ctx);
return EXIT_SUCCESS;
}

View File

@ -47,7 +47,7 @@ int main(int argc, char * argv[])
memcpy(message_str, DEFAULT_MSG, strlen(DEFAULT_MSG));
}
TEST_IPC_Q (ipc_connection (&ctx, SERVICE_NAME, NULL), EXIT_FAILURE);
TEST_IPC_Q (ipc_connection (&ctx, SERVICE_NAME), EXIT_FAILURE);
SECURE_MALLOC (write_m.payload, strlen(message_str), exit(EXIT_FAILURE));
memcpy (write_m.payload, message_str, strlen(message_str));

8
zig-impl/.gitignore vendored
View File

@ -1,8 +0,0 @@
zig-cache
zig-out
docs
build
ipcd
tcpd
pongd
pong

View File

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

View File

@ -1,38 +0,0 @@
### 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)

View File

@ -1,226 +0,0 @@
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;
}

View File

@ -1,71 +0,0 @@
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;
}

View File

@ -1,111 +0,0 @@
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;
}

View File

@ -1,260 +0,0 @@
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;
}

View File

@ -1,75 +0,0 @@
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);
}

View File

@ -1,8 +0,0 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
old
shard.lock
*.log

View File

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

View File

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

View File

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

View File

@ -1,261 +0,0 @@
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…
# itll 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…
# itll 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

View File

@ -1,13 +0,0 @@
module AuthD
class Exception < ::Exception
end
class UserNotFound < ::Exception
end
class AuthenticationInfoLacking < ::Exception
end
class AdminAuthorizationException < ::Exception
end
end

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +0,0 @@
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/*"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +0,0 @@
class AuthD::Response
IPC::JSON.message Error, 0 do
property reason : String? = nil
def initialize(@reason)
end
end
AuthD.responses << Error
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
require "./bindings.cr"
require "./message.cr"
require "./high-level-bindings.cr"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +0,0 @@
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);
}

View File

@ -1,24 +0,0 @@
#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;
}

View File

@ -1,33 +0,0 @@
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();
}

View File

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

View File

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

View File

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

View File

@ -1,63 +0,0 @@
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));
}

View File

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

View File

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

View File

@ -1,104 +0,0 @@
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;
}

View File

@ -1,208 +0,0 @@
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;
}

View File

@ -1,56 +0,0 @@
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;
}

View File

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

View File

@ -1,86 +0,0 @@
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;
}

View File

@ -1,54 +0,0 @@
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;
}

View File

@ -1,86 +0,0 @@
# 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|{

View File

@ -1,11 +0,0 @@
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;
}

View File

@ -1,8 +0,0 @@
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()});

View File

@ -1,57 +0,0 @@
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;
}

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