Compare commits

...

226 Commits

Author SHA1 Message Date
Philippe Pittoli 83d5374586 Merge branch 'master' of ssh://192.168.122.132:22/Baguette/libipc-old 2023-02-15 15:56:33 +01:00
Philippe Pittoli 9d875062f6 Next video: show fmt.bufPrint. 2023-02-15 15:56:14 +01:00
Karchnu b75e7dbbdc Documentation: fig document + makefile. 2023-02-15 10:59:52 +01:00
Philippe Pittoli d26d6de759 Next video: new ideas. 2023-02-14 19:15:31 +01:00
Philippe Pittoli 7ab2fccfd6 Presentation: fix online display. 2023-02-07 05:58:14 +01:00
Philippe Pittoli 0b96aaafeb Zig implementation: TODO.md. 2023-02-06 10:36:25 +01:00
Philippe Pittoli 136b294aac Zig implementation: TODO. 2023-02-06 06:50:07 +01:00
Philippe Pittoli 6a28fa8ac7 Zig implementation: remove useless empty line. 2023-02-06 06:09:01 +01:00
Philippe Pittoli 7d96ec3c28 Zig implementation: fix a test. 2023-02-06 06:08:32 +01:00
Philippe Pittoli 7ef4c43d60 Zig implementation: only build static or shared library. 2023-02-06 06:06:48 +01:00
Philippe Pittoli 49e0430fb5 Zig implementation: change libipc version. 2023-02-05 09:37:54 +01:00
Philippe Pittoli 1479d4e243 Zig implementation: update to current zig API. 2023-02-05 09:34:36 +01:00
Philippe Pittoli f0e41455be Ignore catpoint and pointtools directories. 2023-02-05 07:10:39 +01:00
Philippe Pittoli 81cf462e10 Presentation: compile the presentation in a build directory. 2023-02-05 07:07:35 +01:00
Philippe Pittoli c394c854f4 Presentation: explain how to display it. 2023-02-05 07:03:58 +01:00
Philippe Pittoli 9d69afe3ae Readme: fix typo. 2023-02-05 06:07:08 +01:00
Philippe Pittoli a6e1609fe6 Readme: link to the new Zig implementation. 2023-02-05 06:00:30 +01:00
Philippe Pittoli 48a727bf19 Crystal bindings: remove useless 'puts'. 2023-02-04 10:30:10 +01:00
Philippe Pittoli 94dfefe198 next video: std.log 2023-02-04 09:23:48 +01:00
Philippe Pittoli 4ed4981e42 pongd: grooming. 2023-02-04 09:22:38 +01:00
Philippe Pittoli 0c8fc284d3 Zig implementation: use std.log + grooming. 2023-02-04 09:20:57 +01:00
Philippe Pittoli 745b8c12ca Authd: code restructured, more obvious naming. 2023-02-04 04:47:20 +01:00
Philippe Pittoli 007d329f83 Authd: new_uid split into two fn. 2023-02-04 01:18:58 +01:00
Philippe Pittoli 6564f934e1 Crystal bindings: fix authc import. 2023-02-03 16:05:50 +01:00
Philippe Pittoli 9fabcbdc3c Crystal bindings: fix rx buffer in 'read' fn. 2023-02-03 16:05:01 +01:00
Philippe Pittoli 247134b178 Crystal bindings: makefile: allow to pass parameters to authd. 2023-02-03 10:24:05 +01:00
Philippe Pittoli d72d85294a Crystal bindings: authd: right calls to the mailer. 2023-02-03 10:23:24 +01:00
Philippe Pittoli 37b460d52d Makefile grooming. 2023-02-03 05:48:24 +01:00
Philippe Pittoli 8c168fdbf8 Bindings: add ipc_close_all in libipc.h. 2023-02-03 05:46:18 +01:00
Philippe Pittoli bdff3156f4 Crystal bindings: git ignore logs. 2023-02-03 05:42:11 +01:00
Philippe Pittoli 61186c9ea9 Crystal bindings: pongd. 2023-02-03 05:41:25 +01:00
Philippe Pittoli bf4d5c803f Crystal bindings: makefile: handle documentation. 2023-02-03 05:37:42 +01:00
Philippe Pittoli 2b99e18046 Crystal bindings: add 'cbor' dependency. 2023-02-03 05:36:59 +01:00
Philippe Pittoli 6a912202e4 Crystal bindings: removing a useless CBOR function. 2023-02-03 05:36:04 +01:00
Philippe Pittoli 1c850be4cf Crystal bindings: compile authc. 2023-02-03 04:07:56 +01:00
Philippe Pittoli ddc1d65bef Crystal bindings: CBOR utils. 2023-02-03 04:06:52 +01:00
Philippe Pittoli 08523fa90a Crystal bindings: restructuration. 2023-02-03 04:06:18 +01:00
Philippe Pittoli dda7514a61 Crystal bindings: json.cr: s/mother class/root class/ 2023-02-03 04:02:36 +01:00
Philippe Pittoli afd8bbec18 Crystal bindings: authc. 2023-02-03 02:55:54 +01:00
Philippe Pittoli db5cf0cbf0 Crystal bindings: git ignore. 2023-02-03 02:54:40 +01:00
Philippe Pittoli e849fdafda Crystal bindings: authd/authc better parser. 2023-02-03 02:51:23 +01:00
Philippe Pittoli 49698f11bd Crystal bindings: fix Authd parsing options. 2023-02-03 02:50:33 +01:00
Philippe Pittoli f79938f441 Crystal bindings: authc kinda works. 2023-02-03 02:09:03 +01:00
Philippe Pittoli 7a1e66423f Zig bindings: close_all. 2023-02-03 02:08:31 +01:00
Philippe Pittoli 0ad272d10a Crystal bindings: git ignore. 2023-02-01 20:32:51 +01:00
Philippe Pittoli 2192f44ffc Crystal bindings: authd compiles. 2023-02-01 20:32:21 +01:00
Philippe Pittoli bfdbacb981 Crystal bindings: write & schedule (fd, Bytes). 2023-02-01 20:31:44 +01:00
Philippe Pittoli db561d99b5 Crystal bindings: authd: no need for events in 'handle' methods. 2023-02-01 11:17:18 +01:00
Philippe Pittoli 79752068c6 Crystal bindings: add authd full WIP code. 2023-02-01 11:10:30 +01:00
Philippe Pittoli 4b0ca0a59d Crystal bindings: removing useless method. 2023-02-01 11:09:41 +01:00
Philippe Pittoli 32fa98bc76 Crystal bindings: IPC::JSON comments. 2023-02-01 11:09:06 +01:00
Philippe Pittoli d2ab260255 Crystal bindings: (typed|untyped) message serialization. 2023-02-01 11:08:05 +01:00
Philippe Pittoli 7904d5eef6 Crystal bindings: add JSON message creation examples. 2023-02-01 10:04:38 +01:00
Philippe Pittoli af709a665c Crystal bindings: add support for JSON messages. 2023-02-01 10:03:33 +01:00
Philippe Pittoli 1269b55c05 Crystal bindings: build stuff. 2023-02-01 04:45:08 +01:00
Philippe Pittoli 23de66befc Crystal bindings: tests. 2023-02-01 02:06:17 +01:00
Philippe Pittoli 5ee0a6e6e0 Crystal bindings: code structure. 2023-02-01 02:06:00 +01:00
Philippe Pittoli 0a836c56cd Test application for crystal bindings. 2023-01-25 04:52:27 +01:00
Philippe Pittoli 0356540e7b Pongd test code for Crystal bindings. 2023-01-25 04:47:47 +01:00
Philippe Pittoli a958666060 Crystal library: uncluttered by test code. 2023-01-25 04:47:17 +01:00
Philippe Pittoli ae99353c19 IPC::loop. 2023-01-24 15:22:51 +01:00
Philippe Pittoli 12123b0cb4 Remove useless prints. 2023-01-24 00:50:46 +01:00
Philippe Pittoli b0cf9638ec Crystal bindings: allow bigger messages. 2023-01-21 19:08:24 +01:00
Philippe Pittoli 51cad2c07d API change: ipc_context_deinit now frees context memory. 2023-01-21 19:06:44 +01:00
Philippe Pittoli 21ecf157d1 Crystal bindings: most functions done. Can be used for real. 2023-01-21 07:32:44 +01:00
Philippe Pittoli 368513bca5 Crystal bindings are in good shape. 2023-01-21 05:52:18 +01:00
Philippe Pittoli 9696190e99 Bindings Crystal: working! Pong client works as expected. 2023-01-21 05:10:55 +01:00
Philippe Pittoli e6ad0ce65c Some Crystal code to work on bindings. 2023-01-21 03:26:56 +01:00
Philippe Pittoli 6fa8f31dd4 Libipc presentation update. 2023-01-21 01:47:58 +01:00
Philippe Pittoli eb99ba6cb2 Bindings: update API (following previous naming consistency changes). 2023-01-20 23:14:02 +01:00
Philippe Pittoli 3e3d996e7b Naming consistency fixed (MESSAGE_TX and MESSAGE_RX). 2023-01-20 23:12:04 +01:00
Philippe Pittoli da27ce33dd TODO: build.zig now creates binaries. 2023-01-20 23:11:28 +01:00
Philippe Pittoli 0eb93c805d Presentation: smol update 2023-01-20 22:35:19 +01:00
Philippe Pittoli ae62d300ff Presentation makefile: split different operations. 2023-01-20 22:15:22 +01:00
Philippe Pittoli 4651430ef8 Ignore presentation files. 2023-01-20 22:13:16 +01:00
Philippe Pittoli fd63139157 Add a makefile to compile the presentation. 2023-01-20 04:50:23 +01:00
Philippe Pittoli 82f81a7fb7 Presentation of LibIPC: a few improvements. 2023-01-20 04:49:45 +01:00
Philippe Pittoli 06a01f2b4b Add several example programs. 2023-01-20 04:04:12 +01:00
Philippe Pittoli 132743442b makefile.user: simpler, build.zig handles many operations now. 2023-01-20 03:50:47 +01:00
Philippe Pittoli a0c446ca28 Use build.zig instead of the makefile to build binaries. 2023-01-20 02:02:47 +01:00
Philippe Pittoli b2bf66c436 Hexdump: complete rewrite, fixes all known problems. 2023-01-19 02:58:31 +01:00
Philippe Pittoli 0feb31b4c7 hexdump: fix last byte not printed and last line space shift. 2023-01-19 01:44:26 +01:00
Philippe Pittoli cab07003ad Add specific TODO for zig implementation. 2023-01-19 01:22:37 +01:00
Philippe Pittoli 273204af6d Makefile: add new bin to compile in build-all, valgrind opts. 2023-01-19 01:04:29 +01:00
Philippe Pittoli b59ad86e41 Exchange-fd: replace a few "undefined". 2023-01-19 01:03:19 +01:00
Philippe Pittoli b111982a0f Makefile: start app with right IPC_NETWORK envvar. 2023-01-18 23:56:23 +01:00
Philippe Pittoli 3123051ef0 Fix switch read message + add print debug. 2023-01-18 23:55:48 +01:00
Philippe Pittoli 9dc4cfa003 Remove debug print. 2023-01-18 22:38:17 +01:00
Philippe Pittoli 2befab21e2 FD exchange: fix two minor memory problems. 2023-01-18 22:36:14 +01:00
Philippe Pittoli 9462224255 Bindings: use uint16_t and uint32_t types. 2023-01-18 22:35:22 +01:00
Philippe Pittoli 3695a8ec82 Makefile: add debug options (valgrind). 2023-01-18 20:09:38 +01:00
Philippe Pittoli 6601eb61b0 Bindings: fixing libipc.h. 2023-01-18 17:26:06 +01:00
Philippe Pittoli f07b915124 API change introduced bugs in switch: fixed. 2023-01-18 17:22:56 +01:00
Philippe Pittoli eafdc3749a Add a few entries in .gitignore. 2023-01-18 14:02:11 +01:00
Philippe Pittoli 66ddeb2207 Makefile: add files and directories to remove on 'clean'. 2023-01-18 14:01:42 +01:00
Philippe Pittoli caf255d6c4 Code example for iterating on directory entries. 2023-01-18 13:59:11 +01:00
Philippe Pittoli 3514b4fe96 Switch: tests pass. 2023-01-18 01:46:42 +01:00
Philippe Pittoli 13a60d0158 Bindings: simpler in and out fn for switchs, leading to simpler bindings. 2023-01-18 01:34:30 +01:00
Philippe Pittoli e80e99d47f Bindings: no more memory errors, fix typing. 2023-01-17 07:51:41 +01:00
Philippe Pittoli ed9cd24b22 Bindings: libipc.h almost complete, correct types, fix callconv. 2023-01-17 05:47:14 +01:00
Philippe Pittoli 178f205d44 Minor fix for the drop/Makefile (two '-o' options). 2023-01-17 04:42:05 +01:00
Philippe Pittoli 5b73186353 Bindings: pong client and service. 2023-01-17 04:33:09 +01:00
Philippe Pittoli c8f34ef3c2 Bindings: switch functions. 2023-01-17 04:32:24 +01:00
Philippe Pittoli 472dd1f1ab Bindings: wait_event + schedule. 2023-01-17 01:27:55 +01:00
Philippe Pittoli 71aa496501 Bindings: read from fd. 2023-01-17 00:16:58 +01:00
Philippe Pittoli 125a960816 Bindings: context init+deinit, connect+init service, write msg ok. 2023-01-16 23:58:30 +01:00
Philippe Pittoli a8224c1cf9 Bindings: first draft (context init & deinit). 2023-01-16 22:26:15 +01:00
Philippe Pittoli a6cfa88f79 Context uses an allocator, currently a simple c_allocator. 2023-01-16 21:58:22 +01:00
Philippe Pittoli c5e3b7b901 Binding example: works as expected (with c_allocator for now). 2023-01-16 20:34:23 +01:00
Philippe Pittoli 8ea70b51ee test-bindings: pong.c 2023-01-14 01:09:03 +01:00
Philippe Pittoli d585ffb8ee Example code: C code working with Zig functions. 2023-01-13 02:06:19 +01:00
Philippe Pittoli 32fd31934c Example code: first steps towards C bindings. 2023-01-13 02:05:17 +01:00
Philippe Pittoli aca3f2183d TCPd: code simplification & (most important) error management. 2023-01-12 21:02:05 +01:00
Philippe Pittoli 614e972b95 An example of working function exports. 2023-01-12 17:31:51 +01:00
Philippe Pittoli f3c7695462 Change a simple comment. 2023-01-12 00:26:10 +01:00
Philippe Pittoli bb9d397a40 TCPd works. TODO: proper error management. 2023-01-11 19:58:45 +01:00
Philippe Pittoli 1034b1aa5c TCPd: preliminary work. Lacks actual TCP connection but switching is okay. 2023-01-11 16:34:43 +01:00
Philippe Pittoli ba6671d902 Less debug messages. 2023-01-11 16:30:08 +01:00
Philippe Pittoli 28d7dd8fc2 Next video: orelse + some possible error improvements. 2023-01-11 16:05:39 +01:00
Philippe Pittoli 58320cbb46 Makefile: tcpd, zig links against libc. 2023-01-11 16:04:16 +01:00
Philippe Pittoli 1e3c1b2625 Switch, IPCd and TCPd are working. 2023-01-11 15:59:35 +01:00
Philippe Pittoli 871b2b249c Switch: almost there. 2023-01-11 15:05:16 +01:00
Philippe Pittoli b73550bdcf Add a very limited URI parser. 2023-01-11 14:16:01 +01:00
Philippe Pittoli a0e9515600 Switch code mostly done, needs testing. 2023-01-10 17:09:34 +01:00
Philippe Pittoli 5ad00d0675 SwitchDB: nuke 'em. 2023-01-08 20:45:35 +01:00
Philippe Pittoli 45f3fa1860 Next video: minimal timer library. 2023-01-08 20:06:59 +01:00
Philippe Pittoli 506bd21d57 SwitchDB: in/out should be fine. 2023-01-08 20:06:22 +01:00
Philippe Pittoli 0cf2e5ef1f Next video: talk about error management. 2023-01-08 12:46:56 +01:00
Philippe Pittoli 978676051a Switch: first solid draft, lacks some tests. 2023-01-08 12:46:21 +01:00
Philippe Pittoli ec787d7496 Switch: fn draft for read/write switched ipc messages. 2023-01-07 19:04:05 +01:00
Philippe Pittoli fb12f65218 Switch: WIP. 2023-01-07 16:46:39 +01:00
Philippe Pittoli 49089b910b Grooming. 2023-01-07 16:46:17 +01:00
Philippe Pittoli 2a714cd064 Service name from ipcd to ipc. Some comments changes. 2023-01-05 17:28:24 +01:00
Philippe Pittoli d99a8d13e3 Remove message type. It was useless: msg to IPCd is LOOKUP. 2023-01-05 11:49:33 +01:00
Philippe Pittoli 4bbd5fc686 Pong client: code is fine. 2023-01-05 11:07:29 +01:00
Philippe Pittoli 49b1b3bab2 Grooming and fixing iov length (big WIP). 2023-01-04 13:29:40 +01:00
Philippe Pittoli 1a6c13c85d Grooming and fix reported message length. 2023-01-04 13:06:29 +01:00
Philippe Pittoli ce28b72f3b Pongd. 2023-01-04 11:55:47 +01:00
Philippe Pittoli 9ea49087bd Fix mem leak (forgot a message deinit). 2023-01-04 11:46:34 +01:00
Philippe Pittoli d72aac1a50 IPCd is working for aliases. No error management, WIP. 2023-01-04 11:34:49 +01:00
Philippe Pittoli 1b19118701 Remove done TODOs. 2023-01-04 08:34:30 +01:00
Philippe Pittoli 54fc5aa9e0 Fix mem leaks in ipcd. 2023-01-03 18:18:16 +01:00
Philippe Pittoli 6b8d659319 Fix socket options. 2023-01-03 12:10:08 +01:00
Philippe Pittoli 3097dca06a fd-exchange tests in C: fix socket names. 2023-01-03 12:09:28 +01:00
Philippe Pittoli fc0c28fb7e Fixed many errors. 2023-01-03 12:07:55 +01:00
Philippe Pittoli 4421ee31c4 Stuff to say for the next video. 2023-01-03 10:56:48 +01:00
Philippe Pittoli bbf7e669ff Stuff. 2023-01-03 10:56:01 +01:00
Philippe Pittoli 78670e5b71 Add library in src/ to exchange file descriptor. 2023-01-03 01:31:20 +01:00
Philippe Pittoli b43fd57704 Receiving fd with Zig! 2023-01-02 08:36:14 +01:00
Philippe Pittoli 14509b8d28 Sending a file descriptor through a UNIX socket. 2023-01-01 20:50:08 +01:00
Philippe Pittoli 4fac81b143 Grooming. 2023-01-01 18:20:57 +01:00
Philippe Pittoli b50d910906 Some simplifications. 2023-01-01 18:19:39 +01:00
Philippe Pittoli 8012cff4bf makefile: add access logs file 2022-12-31 05:00:01 +01:00
Philippe Pittoli 266f1daaad Pong client. 2022-12-31 04:59:06 +01:00
Philippe Pittoli 2ad505b305 Clients are working. 2022-12-31 04:58:45 +01:00
Philippe Pittoli 79f9fdc3e2 Context: slowly add client-related functions. 2022-12-29 15:31:15 +01:00
Philippe Pittoli 8a347f9ece makefile (clean rule): removing *.o 2022-12-29 12:19:07 +01:00
Philippe Pittoli c77ae35751 ipcd: handling SIGHUP 2022-12-29 12:16:12 +01:00
Philippe Pittoli 05a47b8473 makefile: add servedoc rule (serving doc through darkhttpd) 2022-12-29 12:15:20 +01:00
Philippe Pittoli 4b0778e37d Mrproper 2022-12-29 10:33:02 +01:00
Philippe Pittoli de1d221881 Grooming. 2022-12-29 10:12:40 +01:00
Philippe Pittoli 98eede6814 Tests are passing. Simpler makefile. 2022-12-27 22:39:23 +01:00
Karchnu 1a161d1b14 Do not quit after timeout. 2022-12-26 08:21:29 +01:00
Philippe Pittoli e7c1c8b96d Misc program, fix a wrong value (message type ERROR -> DATA). 2022-12-25 22:18:33 +01:00
Philippe Pittoli c7f48d21e4 Add a new misc program. 2022-12-25 22:18:14 +01:00
Philippe Pittoli 9eea1dbc07 move misc program 2022-12-25 22:14:50 +01:00
Philippe Pittoli 727de2988f Echoing stuff. 2022-12-25 21:45:51 +01:00
Philippe Pittoli d52fbdf61d Move some temporary utilities in another dir. 2022-12-25 20:44:20 +01:00
Philippe Pittoli 6819de1da5 Messages can be received. 2022-12-25 06:26:38 +01:00
Philippe Pittoli 9f214180a7 libipc can now add new clients 2022-12-25 05:05:41 +01:00
Philippe Pittoli 1f5ac951cb wait_event function: first draft okay 2022-12-24 23:09:25 +01:00
Philippe Pittoli bc0fe07990 Add some test/example programs. 2022-12-24 18:57:51 +01:00
Philippe Pittoli 1a83b3c824 PollFD structure and poll syscall seem working. WIP 2022-12-24 18:57:00 +01:00
Philippe Pittoli 168bea7e78 src/context.zig: add a waiting_event TODO list. 2022-12-24 02:47:20 +01:00
Philippe Pittoli 8b10612456 Makefile++. 2022-12-23 02:36:10 +01:00
Philippe Pittoli ca0d6adbc6 Testing basic message exchange on a UNIX socket. 2022-12-23 02:35:38 +01:00
Philippe Pittoli ceafe4c84f Split source code into different files. 2022-12-23 01:53:07 +01:00
Philippe Pittoli 200219d2fe Makefile. 2022-12-23 00:53:47 +01:00
Philippe Pittoli 9d1fef34a6 hexdump library 2022-12-23 00:53:25 +01:00
Philippe Pittoli 9321158a22 Message: .read & .write okay. 2022-12-23 00:39:12 +01:00
Philippe Pittoli 72ad635874 Proper tests. 2022-12-22 09:58:40 +01:00
Philippe Pittoli 91995657dd s/@"type"/t/ and many new tests. 2022-12-22 08:30:32 +01:00
Philippe Pittoli 0eb5dc57f5 Makefile: add test. 2022-12-22 08:30:15 +01:00
Philippe Pittoli 3b7203c58d Receive msg example: remove a debug message. 2022-12-21 23:55:06 +01:00
Philippe Pittoli 6038a277f3 Simple makefile. 2022-12-21 23:53:54 +01:00
Philippe Pittoli 0e2043c5e6 Receive msg example: fixing buffer offset. 2022-12-21 19:26:03 +01:00
Philippe Pittoli a39ce64b7b Receive msg example: rm unix socket path. 2022-12-21 19:05:58 +01:00
Philippe Pittoli 2bb06db137 Add some standard library examples. 2022-12-21 10:00:40 +01:00
Philippe Pittoli 51e10d7f1e user_type is dropped. Rundir is now handled in Context. 2022-12-20 23:56:50 +01:00
Philippe Pittoli 29d18e8ca1 Update to Zig v0.11-ish. 2022-12-20 07:16:55 +01:00
Philippe Pittoli 03b1222ff0 Lot of simplifications. 2022-05-08 02:33:02 +02:00
Philippe Pittoli 1c26a69acd Renaming, grooming. 2022-05-07 13:13:40 +02:00
Philippe Pittoli 0fddc05576 main.zig: work in progress. 2022-05-01 00:42:17 +02:00
Philippe Pittoli 1c8be3390e Readme: what's NOT THAT great with Zig (but still cool). 2022-05-01 00:41:50 +02:00
Philippe Pittoli 7204ade9e3 cat.zig: simplification. 2022-05-01 00:41:11 +02:00
Philippe Pittoli 444078fcc6 cat 2022-04-14 15:52:01 +02:00
Philippe Pittoli 382dc06f85 Readme on the rewrite in Zig perks. 2022-02-08 15:48:15 +01:00
Philippe Pittoli 382dcc07d7 Storing inner structures, adding TODOs. 2022-02-08 05:59:18 +01:00
Philippe Pittoli a0dbd66fd2 Actual Unix socket involved (create, listen, accept, connect, close). 2022-02-07 18:30:06 +01:00
Philippe Pittoli 9011578d8b Some more functions (close, close_all, write, read, read_fd). 2022-02-07 04:35:21 +01:00
Philippe Pittoli 1762f50100 pollfd structure (draft), a few more mockup functions (server_init, wait_event...) 2022-02-07 03:24:39 +01:00
Philippe Pittoli 69732ccad8 Deinit() some stuff. 2022-02-07 01:21:46 +01:00
Philippe Pittoli 87f6f9071b Test leaks, but compiles! 2022-02-06 18:11:01 +01:00
Philippe Pittoli c14148ef35 Print the different strucutures. 2022-02-06 16:57:23 +01:00
Philippe Pittoli 9d16d6f2b8 Some corrections for stuff. 2022-02-06 00:32:16 +01:00
Philippe Pittoli fc4899a26f Zigimpl: draft. 2022-02-05 13:16:44 +01:00
Philippe Pittoli 13e7619899 Fixed tests (3 and 4). 2022-02-04 03:22:26 +01:00
Philippe Pittoli 33f7c9ccfb Wrong pointer read. 2022-02-04 02:43:49 +01:00
Philippe Pittoli e6edfd0e43 Fixing some test functions: no leaks when ending these programs. 2022-02-04 00:53:22 +01:00
Karchnu 513348652e Better presentation, for the ones only reading it online. 2020-12-09 15:58:31 +01:00
Karchnu bf600e0889 Readme fix. 2020-12-09 15:34:53 +01:00
Karchnu f9b9000a3f Add libIPC presentation. 2020-12-09 15:33:54 +01:00
Karchnu 1439bb78ca README: again, getting rid of old stuff. 2020-12-08 23:49:18 +01:00
Karchnu c8757d0fb0 Readme: less outdated stuff. 2020-12-08 23:39:25 +01:00
Karchnu 7cb5d2669c Man page added. 2020-12-08 22:49:12 +01:00
Karchnu 7caa934753 Man page: functions renamed. 2020-12-08 22:47:27 +01:00
Karchnu 62db8ff7fd Add and remove printf. 2020-11-08 06:07:28 +01:00
Karchnu d32e26b848 libipc: better error detection at message send. 2020-11-08 01:19:02 +01:00
Karchnu a99d5317b0 Bugfix (<= => <), better error message (send) and error management. 2020-11-06 21:17:01 +01:00
Karchnu 825e0c1b2c v0.7.2: ipcd indicates if it successfully contacted the service. 2020-11-05 14:15:39 +01:00
Karchnu c69ce64273 removing message_types 2020-10-14 14:08:09 +02:00
Karchnu 7659766fc0 Minor changes in examples. 2020-10-01 03:19:52 +02:00
Karchnu b4cc1814cd Fix memory leaks and print messages right away when non critical. 2020-10-01 02:47:08 +02:00
Karchnu ec33e6086e Fixing a broken loop condition. 2020-10-01 01:27:33 +02:00
Karchnu 556652418a Version 0.7.1. Small fix in usock_recv function. 2020-09-01 00:16:43 +02:00
Karchnu 7eeda65cd9 libipc now allows buffered readings in switched context. 2020-08-03 00:42:34 +02:00
113 changed files with 8592 additions and 127 deletions

1
.gitignore vendored
View File

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

View File

@ -1,5 +1,5 @@
PACKAGE = 'libipc'
VERSION = '0.6.0'
VERSION = '0.7.2'
PREFIX := /usr/local
BINDIR := $(PREFIX)/bin
@ -50,25 +50,25 @@ libipc.so: src/communication.o src/context.o src/error.o src/fs.o src/message.o
$(Q)$(CC) -o libipc.so -shared $(LDFLAGS) src/communication.o src/context.o src/error.o src/fs.o src/message.o src/network.o src/print.o src/service_path.o src/usocket.o src/utils.o
libipc.so.install: libipc.so
@echo ' IN > $(LIBDIR)/libipc.so.0.6.0'
@echo ' IN > $(LIBDIR)/libipc.so.0.7.2'
$(Q)mkdir -p '$(DESTDIR)$(LIBDIR)'
$(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'
$(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'
@echo ' LN > $(LIBDIR)/libipc.so.0'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so.0'
@echo ' LN > $(LIBDIR)/libipc.so'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.6.0' '$(DESTDIR)/$(LIBDIR)/libipc.so'
$(Q)ln -sf '$(LIBDIR)/libipc.so.0.7.2' '$(DESTDIR)/$(LIBDIR)/libipc.so'
libipc.so.clean:
@echo ' RM > libipc.so'
$(Q)rm -f libipc.so
libipc.so.uninstall:
@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.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'
$(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.6.0'
@echo ' :: libipc-0.7.2'
@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](man/libipc.7.md).
See the introductory man page in `man/libipc.7`.
See the presentation in [docs/libipc.md](docs/libipc.md).
# Compilation
`make`
# logging system
Logs are in one of the following directories: `$XDG_DATA_HOME/ipc/` or `$HOME/.local/share/ipc/`.
The log file can be indicated with the `IPC_LOGFILE` environment variable, too.
To remove logs: `make LDFLAGS=-DIPC_WITHOUT_ERRORS`
# Since 0.7
- `libipc` have callbacks to use along with switching capabilities, making easier to implement proxies for different communication protocols
@ -25,23 +25,19 @@ To remove logs: `make LDFLAGS=-DIPC_WITHOUT_ERRORS`
For performance improvements within `libipc`:
- `libipc` will be rewritten in Zig -- **DONE!**
- `libipc` shouldn't use realloc for each event (new client, new message, etc.) but by batch of a few thousand elements
- `libipc` should use better internal structures, unrequiring the use of loops (over the whole list of messages or connections) for each action
# Planning for 0.9
- `libipc` should use `libevent` for performance improvments
- `libipc` should use `epoll/kqueue` for performance improvements
* new functions will be added to the API
* **but** we'll keep the same API for applications with no need for threading (way simpler implementation)
- `libipc` should be thread-safe
# Planning for 1.0
- `libipc` *may* be written in Zig
- `libipc` should have usable bindings in several languages
# Implementation design
## Memory management
1. Prefer stack over mallocs.
2. Basic functions (such as *usock_*) should not handle memory allocation.
[zigimpl]: https://git.baguette.netlib.re/Baguette/libipc

34
cat/cat.zig Normal file
View File

@ -0,0 +1,34 @@
const std = @import("std");
const stdout = std.io.getStdOut().writer();
pub fn main() anyerror!void {
const args = try std.process.argsAlloc(std.heap.page_allocator);
if (args.len <= 1) {
try print_input();
}
for (args[1..]) |f| {
if (std.mem.eql(u8, f, "-")) { try print_input(); }
else { try print_file (f); }
}
}
fn print_input() !void {
try print_all (std.io.getStdIn());
}
fn print_file(dest: []const u8) !void {
var file = try std.fs.cwd().openFile(dest, .{ .mode = .read_only });
defer file.close();
try print_all (file);
}
fn print_all(reader: std.fs.File) !void {
var buffer: [4096]u8 = undefined;
while (true) {
const nbytes = try reader.read(&buffer);
try stdout.print("{s}", .{buffer[0..nbytes]});
if (nbytes == 0) break;
}
}

2
docs/.gitignore vendored Normal file
View File

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

151
docs/figlayers Executable file
View File

@ -0,0 +1,151 @@
#!/bin/sh
#
# Extract layers from a FIG file
#
# History
# 2002/04/23 : pda : design
# 2013/02/12 : pda : extension to intervals
#
usage ()
{
echo "usage: $0 layer[-layer] ... < fig-file > fig-file" >&2
exit 1
}
if [ $# = 0 ]
then
usage
fi
LAYERS="$*"
for i
do
if expr "$i" : "^[0-9][0-9]*-[0-9][0-9]*$" > /dev/null
then
MIN=`expr "$i" : "\([0-9]*\)-"`
MAX=`expr "$i" : ".*-\([0-9]*\)"`
if [ $MIN -gt $MAX ]
then usage
fi
while [ $MIN -le $MAX ]
do
LAYERS="$LAYERS $MIN"
MIN=`expr $MIN + 1`
done
elif expr "$i" : "^[0-9][0-9]*$" > /dev/null
then
LAYERS="$LAYERS $i"
else
usage
fi
done
awk -v "layers_string=$LAYERS" -F" " '
BEGIN {
split (layers_string, layers, "[ \t]")
}
/^#FIG/ {
version = $2
if (version != 3.2)
{
print "Invalid FIG version ($0)" > "/dev/stderr"
}
print
afficher = 1
next
}
/^0 / {
# color pseudo object
afficher = 1
print
next
}
/^1 / {
# ellipse
if (layerok($7))
{
afficher = 1
print
}
else afficher = 0
next
}
/^2 / {
# polyline
if (layerok($7))
{
afficher = 1
print
}
else afficher = 0
next
}
/^3 / {
# spline
if (layerok($7))
{
afficher = 1
print
}
else afficher = 0
next
}
/^4 / {
# text
if (layerok($4))
{
afficher = 1
print
}
else afficher = 0
next
}
/^5 / {
# arc
if (layerok($7))
{
afficher = 1
print
}
else afficher = 0
next
}
/^6 / {
# compound
afficher = 1
print
next
}
/^-6 / {
# end of compound
afficher = 1
print
next
}
/^ / {
# ligne de continuation
if (afficher)
{
print
}
next
}
{
afficher = 1
print
next
}
function layerok (l, ok, n)
{
ok = 0
for (n in layers)
{
if (l == layers [n])
ok = 1
}
return ok
}' -

34
docs/figs/graph-this.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
for i in *.fig
do
# latin1 accents
grep -E "[ôéèàîÉê]" $i 2>/dev/null 1>/dev/null
if [ $? -eq 0 ]; then
echo $i matches a latin1 accent
sed -r -i "s/É/\\\311/g" $i
sed -r -i "s/à/\\\340/g" $i
sed -r -i "s/è/\\\350/g" $i
sed -r -i "s/é/\\\351/g" $i
sed -r -i "s/ê/\\\352/g" $i
sed -r -i "s/î/\\\356/g" $i
sed -r -i "s/ô/\\\364/g" $i
# sed -r -i "s/°/\\\176/g" $i
fi
PDF=$(echo ${i} | sed "s/fig$/pdf/")
if [ ! -f ${PDF} ] || [ $(stat -c "%X" ${PDF}) -lt $(stat -c "%X" ${i}) ]
then
echo "fig2ps ${i}"
fig2dev -L pdf ${i} > ${PDF}
echo "touch ${PDF}"
touch ${PDF}
# echo "make and touch ${PDF}"
# pdf2ps ${PDF}
# touch ${PDF}
fi
done

109
docs/libipc.fig Normal file
View File

@ -0,0 +1,109 @@
#FIG 3.2 Produced by xfig version 3.2.7a
Landscape
Center
Metric
A4
100.00
Single
-2
1200 2
5 1 0 3 0 7 30 -1 -1 0.000 0 0 1 0 3646.767 3431.191 2160 4950 1530 3240 2340 1755
1 1 2.00 90.00 150.00
2 2 0 1 0 7 30 -1 -1 0.000 0 0 -1 0 0 5
8955 1395 11115 1395 11115 1755 8955 1755 8955 1395
2 1 0 3 0 7 30 -1 -1 0.000 0 0 -1 1 1 2
1 1 2.00 75.00 150.00
1 1 2.00 75.00 150.00
4230 1530 8955 1530
2 1 0 3 0 7 30 -1 -1 0.000 0 0 -1 1 1 2
1 1 2.00 75.00 150.00
1 1 2.00 75.00 150.00
4230 1665 8955 3150
2 1 0 3 0 7 15 -1 -1 0.000 0 0 -1 1 1 2
1 1 2.00 75.00 150.00
1 1 2.00 75.00 150.00
9990 3420 9990 4950
2 2 0 1 0 7 5 -1 -1 0.000 0 0 -1 0 0 5
8955 4950 11115 4950 11115 5310 8955 5310 8955 4950
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
8955 3060 11115 3060 11115 3420 8955 3420 8955 3060
2 2 0 1 0 7 5 -1 -1 0.000 0 0 -1 0 0 5
2070 4950 4230 4950 4230 5310 2070 5310 2070 4950
2 1 0 3 0 7 10 -1 -1 0.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
4230 5130 8955 5130
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
2070 3060 2880 3060 2880 3420 2070 3420 2070 3060
2 2 0 1 0 7 15 -1 -1 0.000 0 0 -1 0 0 5
3375 3060 4185 3060 4185 3420 3375 3420 3375 3060
2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 1
6435 2295
2 2 0 1 0 7 30 -1 -1 0.000 0 0 -1 0 0 5
2070 1395 4230 1395 4230 1755 2070 1755 2070 1395
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
9360 6345 10620 6345
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
9225 4950 9225 3420
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
4185 3105 8955 3105
2 1 1 3 1 7 25 -1 -1 2.000 0 0 -1 1 1 2
1 1 1.00 60.00 90.00
1 1 1.00 60.00 90.00
2880 3105 3375 3105
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
2430 4950 2430 3420
2 1 1 3 1 7 25 -1 -1 8.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
9360 6075 10620 6075
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
3780 4950 3780 3420
2 1 0 3 0 7 25 -1 -1 0.000 0 0 -1 1 1 2
1 1 3.00 90.00 150.00
1 1 3.00 90.00 150.00
4185 3375 8955 3375
2 2 0 0 0 7 999 -1 -1 0.000 0 0 -1 0 0 5
0 0 13365 0 13365 7830 0 7830 0 0
4 1 0 30 -1 16 20 0.0000 4 240 5625 6705 540 LibIPC communications with a browser\001
4 1 0 30 -1 16 16 0.0000 4 210 1620 3105 1665 Web Browser\001
4 1 0 30 -1 16 16 0.0000 4 210 1545 3105 5220 JS or WASM\001
4 1 0 30 -1 16 16 0.0000 4 210 1425 9990 1665 Web Server\001
4 1 0 30 -1 16 16 5.9865 4 270 2460 6615 2295 dynamic interactions\001
4 1 0 30 -1 16 14 5.9865 4 225 3465 6435 2700 (the webserver could be a proxy)\001
4 1 0 30 -1 16 16 0.0000 4 210 1485 9990 3330 Websocketd\001
4 1 0 30 -1 16 16 0.0000 4 210 1185 6705 1395 static files\001
4 1 0 5 -1 16 16 0.0000 4 210 795 9990 5220 Server\001
4 1 0 20 -1 16 20 0.0000 4 240 4560 6705 540 Remote libIPC communications\001
4 1 0 10 -1 16 20 0.0000 4 240 4200 6705 540 Local libIPC communications\001
4 0 0 15 -1 16 16 0.0000 4 210 2475 10170 4275 IPC communications\001
4 0 0 50 -1 16 12 0.0000 4 150 855 2115 5895 10 = local\001
4 0 0 50 -1 16 12 0.0000 4 150 1305 2115 6135 15 = all remote\001
4 0 0 50 -1 16 12 0.0000 4 135 1050 2115 6375 20 = remote\001
4 0 0 50 -1 16 12 0.0000 4 150 795 2115 6615 30 = web\001
4 0 0 50 -1 16 12 0.0000 4 150 645 2115 5670 05 = all\001
4 1 0 10 -1 16 16 0.0000 4 255 1710 6570 5490 (Unix sockets)\001
4 1 0 10 -1 16 16 0.0000 4 210 2595 6570 4950 Direct communication\001
4 1 0 11 -1 16 16 0.0000 4 210 690 3060 5220 Client\001
4 0 0 50 -1 16 12 0.0000 4 150 1890 3375 5895 12 = local and remote\001
4 1 0 20 -1 16 16 0.0000 4 210 690 3780 3330 TCPd\001
4 1 0 20 -1 16 16 0.0000 4 210 600 2475 3330 IPCd\001
4 0 0 50 -1 16 12 0.0000 4 195 2040 3375 6375 21 = IPCd explanations\001
4 1 0 20 -1 16 16 0.0000 4 210 690 9990 3330 TCPd\001
4 2 0 25 -1 16 10 0.0000 4 150 1395 2205 4140 tcp://remote/server\001
4 2 0 25 -1 16 10 0.0000 4 120 675 9090 6390 Data flow\001
4 2 0 25 -1 16 10 0.0000 4 120 1875 9090 6120 Connection establishment\001
4 0 0 21 -1 16 14 0.0000 4 225 3825 4455 1620 contacts the right "protocol deamon"\001
4 0 0 21 -1 16 14 0.0000 4 225 6690 4455 1935 after connection establishment, provides the socket to the client\001
4 0 0 21 -1 16 14 0.0000 4 225 3600 4455 2250 therefore, never handles data flow\001
4 2 18 21 -1 18 14 0.0000 4 180 630 4275 1620 IPCd:\001

380
docs/libipc.md Normal file
View File

@ -0,0 +1,380 @@
## Before starting
This file is a presentation based on the point tools:
https://git.baguette.netlib.re/Baguette/pointtools
To see it, type 'make'.
TODO: Explain the problem
TODO: Explain the solution
TODO: Explain why LibIPC
TODO: Explain how LibIPC
TODO: Explain other possible implementations
TODO: Explain future of LibIPC
TODO: Explain what can be done right now
TODO: Explain what I actually do with it
TODO: Explain LibIPC isn't a silver bullet
Have fun!
## Programming problems
Libraries
* change often = hard to follow
* don't often provide a high-level interface
* each library is coded in its own way
* availability vary depending on the language
Example: libraries to access databases
* languages often have their own implementation
* functions may vary from a language to another
## Infrastructure problems
Infrastructure
* Always need to install all required libraries
* No clear way to sandbox part of an application
## What solution?
MAKE APPLICATIONS, NOT LIBRARIES
* apps talking to apps
Create an abstraction for libraries
* languages, implementations, code modifications in general
Create an abstraction for network code
* applications think communications are local
* networking is performed by dedicated services
* examples: TCPd, UDPd, TLSd, HTTPd...
* apps are independant from protocols and formats
(unless they are fundamentaly network-related)
## In practice
usage must be simple
1. init connection or service
2. loop over events
#pause
events are simple and high level
1. connection and disconnection
2. message received and sent
#pause
message have a simple format: length + value
#pause
And that's it.
## When
LibIPC is useful when the app:
- cannot be a simple shell command
- needs a bidirectional communication
- is an abstraction over a library
## Available libraries
* DBUS
* libevent
* even more complicated stuff
* RPC-style, like Corba
#pause
* ... or bare libc api
* shared memory
* pipes
* sockets (unix, inet, inet6)
## DBUS
* not well suited for our needs
(a polite way to say: what a bloody mess)
Is it designed *NOT* to be used?
* over-engineered
* complex
* documentation isn't great
* no code example
## DBUS (bonus page!)
DBUS feels obsolete: a big chunk of the documentation is
about message format. Just use CBOR already!
#pause
They even admit they did a poor job on the C part:
> There is a low-level C binding, but that is probably too detailed
> and cumbersome for anything but writing other bindings.
#pause
Oh. And C++. YOU SHALL NOT PASS!
This is a Linux requirement nowadays, wth?
## libevent
* works with epoll and kqueue
* great performances
* works on Linux and *BSD
* a bit complicated
## Bare libc api
shared memory and semaphores
* (kinda) complicated api
* not about exchanging messages
pipes, sockets
* lack a conventional message format
... but that's about it
* Great to start with!
All have great performances to exchange data.
What is slow is the function to _wait_ for new events.
## LibIPC's choice
Unix sockets
- fast, simple, reliable, bidirectional
- remote connections will have their own service (ex: TCPd)
Dumbest possible message format
- length + value
- build your own format on top of it!
Wait on file descriptors with poll(2)
- slow, but available everywhere
- may upgrade to libevent
## LibIPC history (1/3)
1. based on pipes
* because we gotta go fast!
* ... but implementation was a bit of a mess
#pause
2. rewrite to work with unix sockets
* performances are excellent, no need for _absolute best_
* way nicer implementation
* select(2) for listening on file descriptors
#pause
* ... wait, does select(2) support more than 1024 connections?
## LibIPC history (2/3)
3. rewrite using poll(2)
* many bugfixes later, way more tested than before
* implementation was (kinda) production-ready
* implementation was simple: < 2000 lines of C code
Still wasn't as simple as I wanted
## LibIPC history (3/3)
4. rewrite in Zig
* still uses poll(2) (at least for now)
* C-compatible bindings are available
## Why Zig? (1/2)
error management is built-in and mandatory
simpler to read and write
* nicer data structures (contain functions)
* less code redundancy (defer, more generic functions)
* no more C's pitfalls
* fully qualified names
## Why Zig? (2/2)
better standard library
* usual structures: lists, hashtables
* log system
memory management is simpler, more secure and more flexible
better at exposing bugs (better type system)
simpler to cross-compile: same standard library for all OSs
## Current implementation of libIPC
bindings available in Crystal
* as well as fancy mappings: JSON and CBOR class serialization
#pause
epoll (Linux) and kqueue (*BSD) were avoided
* because callbacks hell => harder to read and to write code
#pause
* still a possibility for someday, not the priority right now
#pause
LibIPC doesn't handle parallelism, yet
## How libIPC works (in Zig)
LibIPC has a high level API
var context = try Context.init(allocator);
defer context.deinit();
#pause
var pong_fd = try context.connect_service ("pong");
var message = try Message.init (pong_fd, allocator, "hello");
try context.schedule (message);
## How libIPC works (in Zig)
var event = try context.wait_event();
switch (event.t) {
...
}
## How libIPC works (in Zig)
var event = try context.wait_event();
switch (event.t) {
.CONNECTION => {
print ("New client!\n", .{});
},
...
}
## How libIPC works (in Zig)
var event = try context.wait_event();
switch (event.t) {
.CONNECTION => {
print ("New client!\n", .{});
},
.MESSAGE_RX => {
if (event.m) |m| {
print ("a message has been received: {s}\n", .{m});
}
}
...
}
## How libIPC works (bindings)
1. init a connection (client) or create an unix socket (service)
ipc_connect_service (context, &fd, service_name, service_len)
ipc_service_init (context, &fd, service_name, service_len)
#pause
2. loop, wait for events
listening to file descriptors (libIPC ones or not)
example:
while(1) {
ipc_wait_event (context, &type, &index, &fd, buffer, &buffer_len)
switch (type) {
case IPC_CONNECTION : ...
case IPC_DISCONNECTION : ...
case IPC_MESSAGE: ...
}
}
## How libIPC works
3. send messages
```c
ipc_schedule (context, fd, buffer, buffer_len)
or
ipc_write (context, fd, buffer, buffer_len)
```
#pause
4. add a file descriptor to listen to
ipc_add_external (context, fd)
## How libIPC works
LibIPC also helps to create "protocol daemons" like TCPd with
automatic switching between file descriptors
LibIPC takes callbacks to obtain libipc payloads inside arbitrary message structure
Example: websocketd.
Clients exchange data with a libipc service through websockets messages.
websocketd binds both the client and its service file descriptors,
then provides the libipc a callback to extract libipc messages from
the websocket messages sent by the client.
Same thing the other way.
ipc_switching_callbacks (context, client_fd, cb_in, cb_out)
## libIPC internal structures (1/2)
Main goal: simplest possible structures
Examples (nothing hidden):
Message {
fd: i32 => File descriptor concerned about this message.
payload: []u8 => Actual payload.
allocator: std.mem.Allocator => Memory management.
};
Event {
t: Event.Type => Example: connection, message tx, ...
m: ?Message => Message, if there is one.
index: usize => (Internal stuff).
originfd: i32 => File descriptor related to the event.
};
## libIPC internal structures (2/2)
Context structure is slightly more complicated, but _reasonable_.
Context {
rundir: [] u8, // Where the UNIX sockets are.
pollfd: PollFD, // File descriptors to manage.
tx: Messages, // Messages to send, once their fd is available.
...
};
The rest is implementation details (and more advanced usage of LibIPC).
## Future of libIPC
## Why not use it?
Current limitations
* performances (libIPC is based on poll(2), not epoll nor kqueue)
* it really isn't an issue until you have hundreds of clients
* LibIPC could someday use libevent
* nothing in libIPC is thread-safe
These limitations are the price for a simple implementation.
## Questions?
Ask! `karchnu at karchnu.fr`

30
docs/makefile Normal file
View File

@ -0,0 +1,30 @@
all: get-point-tools clean generate display
BAGUETTE=https://git.baguette.netlib.re/Baguette
get-point-tools:
@test -d catpoint || (git clone $(BAGUETTE)/catpoint.git && cd catpoint && make)
@test -d pointtools || (git clone $(BAGUETTE)/pointtools.git && cd pointtools && make)
CATPOINT = $(PWD)/catpoint/catpoint
MD2POINT = $(PWD)/pointtools/bin/md2point
PRESENTATION = $(PWD)/libipc.md
BDIR=build
clean:
@-rm -r $(BDIR) 2>/dev/null || true
generate:
@test -d $(BDIR) || mkdir $(BDIR)
@cd $(BDIR) && (cat $(PRESENTATION) | $(MD2POINT))
display:
@cd $(BDIR) && $(CATPOINT) *.txt
help:
@echo "get-point-tools: get all relevant point tools (catpoint and md2point)"
@echo
@echo "generate: convert markdown into 'point' documents"
@echo "display: run catpoint on all 'point' document"
@echo "clean: remove all 'point' files"
@echo
@echo "By default: get point tools, generate then display the presentation"

21
docs/makefile.fig Normal file
View File

@ -0,0 +1,21 @@
all: allfigures
FIGS=$(shell ls figs/*.fig)
FIGL=./figlayers
LIBIPC=figs/libipc
figlibipc: $(LIBIPC).fig
echo "libipc"
$(FIGL) 999 5 10-12 < $(LIBIPC).fig > $(LIBIPC)-1.fig # local
$(FIGL) 999 5 11-12 15 20-25 < $(LIBIPC).fig > $(LIBIPC)-2.fig # remote
$(FIGL) 999 5 15 30 < $(LIBIPC).fig > $(LIBIPC)-3.fig # web remote
allfigures: figlibipc
echo "make ps"
cd figs/ ; ./graph-this.sh
# cd diag/ ; ./graph-this.sh
clean:
echo "rm figs/*.ps"
rm figs/*.ps

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

@ -39,6 +39,9 @@ void non_interactive (int verbosity, size_t nb_msg, char *msg_str)
for (size_t i = 0 ; i < nb_msg ; i++) {
TEST_IPC_QUIT_ON_ERROR (ipc_message_format_data (&m, 42, msg_str, (ssize_t) strlen (msg_str) + 1), EXIT_FAILURE);
TEST_IPC_QUIT_ON_ERROR (ipc_write_fd (ctx->pollfd[0].fd, &m), EXIT_FAILURE);
if (verbosity > 1) {
printf ("msg written (type: %u): %s\n", m.user_type, m.payload);
}
ipc_message_empty (&m);
TEST_IPC_QUIT_ON_ERROR (ipc_read (ctx, 0 /* read from the only valid index */, &m), EXIT_FAILURE);
@ -110,16 +113,16 @@ void interactive ()
fprintf (stderr, "%s", ret.error_message);
exit (EXIT_FAILURE);
}
#if 0
#if 1
printf ("\n");
printf ("right before sending a message\n");
#endif
ret = ipc_write (ctx, m);
ret = ipc_write_fd (ctx->pollfd[1].fd, m);
if (ret.error_code != IPC_ERROR_NONE) {
fprintf (stderr, "%s", ret.error_message);
exit (EXIT_FAILURE);
}
#if 0
#if 1
printf ("right after sending a message\n");
#endif
@ -145,7 +148,7 @@ void interactive ()
int main (int argc, char *argv[])
{
printf("usage: %s [verbosity #messages message]", argv[0]);
printf("usage: %s [verbosity #messages message]\n", argv[0]);
ctx = malloc (sizeof (struct ipc_ctx));
memset (ctx, 0, sizeof (struct ipc_ctx));

View File

@ -136,6 +136,7 @@ void exit_program (int signal)
int main (int argc, char *argv[])
{
printf ("Usage: %s [verbosity]\n", argv[0]);
if (argc > 1) {
verbosity = atoi(argv[1]);
}

183
man/libipc.7 Normal file
View File

@ -0,0 +1,183 @@
.\" Generated by scdoc 1.9.6
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "libipc" "7" "2020-12-08"
.P
.SH NAME
.P
libipc - Simple, easy-to-use IPC library
.P
.SH DESCRIPTION
.P
\fB\fRlibipc\fB\fR is a library that provides interprocess communication medium between applications.
It provides both client and server code.
.P
.SH SYNOPSIS
.P
\fB\fR#include <ipc.h>\fB\fR
.P
.SS Initialization, exchanges, disconnection
.P
// server initialization
.P
\fIenum ipc_errors\fR \fB\fRipc_server_init\fB\fR (\fBchar\fR **env, \fBconst char\fR *sname);
.P
// connection establishement to a server
.P
\fIenum ipc_errors\fR \fB\fRipc_connection\fB\fR (\fBchar\fR **env, \fBconst char\fR *, int *serverfd);
.P
// closing a server
.P
\fIenum ipc_errors\fR \fB\fRipc_close\fB\fR (\fBstruct ipc_connection_info\fR *srv);
.P
// closing a connection
.P
\fIenum ipc_errors\fR \fB\fRipc_close\fB\fR (\fBstruct ipc_connection_info\fR *p);
.br
\fIenum ipc_errors\fR \fB\fRipc_accept\fB\fR (\fBstruct ipc_connection_info\fR *srv, \fBstruct ipc_connection_info\fR *p);
.P
\fIenum ipc_errors\fR \fB\fRipc_read\fB\fR (\fBconst struct ipc_connection_info\fR *, \fBstruct ipc_message\fR *m);
.br
\fIenum ipc_errors\fR \fB\fRipc_write\fB\fR (\fBconst struct ipc_connection_info\fR *, \fBconst struct ipc_message\fR *m);
.br
\fIenum ipc_errors\fR \fB\fRipc_wait_event\fB\fR (\fBstruct ipc_ctx\fR *clients, \fBstruct ipc_connection_info\fR *srv, \fBstruct ipc_event\fR *event);
.P
.P
// store and remove only pointers on allocated structures
.P
\fIenum ipc_errors\fR \fB\fRipc_add\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBstruct ipc_connection_info\fR *cinfo);
.br
\fIenum ipc_errors\fR \fB\fRipc_del\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBstruct ipc_connection_info\fR *cinfo);
.P
// add an arbitrary file descriptor to read
.P
\fIenum ipc_errors\fR \fB\fRipc_add_fd\fB\fR (\fBstruct ipc_ctx\fR *cinfos, \fBint\fR fd);
.P
.P
.SS Message functions
.P
// create msg structure from buffer
.P
\fIenum ipc_errors\fR \fB\fRipc_message_format_read\fB\fR (\fBstruct ipc_message\fR *m, \fBconst char\fR *buf, \fBssize_t\fR msize);
.P
// create buffer from msg structure
.P
\fIenum ipc_errors\fR \fB\fRipc_message_format_write\fB\fR (\fBconst struct ipc_message\fR *m, \fBchar\fR **buf, \fBssize_t\fR *msize);
.P
\fIenum ipc_errors\fR \fB\fRipc_message_format\fB\fR (\fBstruct ipc_message\fR *m, \fBchar\fR type, \fBconst char\fR *payload, \fBssize_t\fR length);
.br
\fIenum ipc_errors\fR \fB\fRipc_message_format_data\fB\fR (\fBstruct ipc_message\fR *m, \fBconst char\fR *payload, \fBssize_t\fR length);
.br
\fIenum ipc_errors\fR \fB\fRipc_message_format_server_close\fB\fR (\fBstruct ipc_message\fR *m);
.P
\fIenum ipc_errors\fR \fB\fRipc_message_empty\fB\fR (\fBstruct ipc_message\fR *m);
.P
.P
.SH STRUCTURES
.P
.nf
.RS 4
struct ipc_connection_info {
uint32_t version;
uint32_t index;
int32_t fd;
char type; // may be an arbitrary fd
char *spath; // max size: PATH_MAX, used to store unix socket path
};
struct ipc_ctx {
struct ipc_connection_info ** cinfos;
int32_t size;
};
struct ipc_message {
char type;
uint32_t length;
char *payload;
};
struct ipc_event {
enum ipc_event_type type;
void* origin; // currently used as an client or service pointer
void* m; // message pointer
};
.fi
.RE
.P
.P
.SH ENUMERATIONS
.P
.nf
.RS 4
enum msg_types {
MSG_TYPE_SERVER_CLOSE = 0
, MSG_TYPE_ERR
, MSG_TYPE_DATA
} message_types;
.fi
.RE
.P
Function \fB\fRipc_wait_event\fB\fR returns an \fBevent type\fR structure.
The event may be a (dis)connection, received data or an error.
It also can be \fBIPC_EVENT_TYPE_EXTRA_SOCKET\fR since an arbitrary file descriptor can be added to the \fBipc_ctx\fR structure with \fB\fRipc_add_fd\fB\fR.
.P
.nf
.RS 4
enum ipc_event_type {
IPC_EVENT_TYPE_NOT_SET
, IPC_EVENT_TYPE_ERROR
, IPC_EVENT_TYPE_EXTRA_SOCKET
, IPC_EVENT_TYPE_CONNECTION
, IPC_EVENT_TYPE_DISCONNECTION
, IPC_EVENT_TYPE_MESSAGE
};
enum ipc_errors {
\&.\&.\&.
};
.fi
.RE
.P
.P
.SH EXAMPLES
.P
Examples are available in the \fB/examples\fR directory.
.P
.SH NOTES
.P
.SH SEE ALSO
.P
.SH BUGS & LIMITATIONS
.P
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.IP \(bu 4
.\}
Documentation is currently limited.
.RE
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.IP \(bu 4
.\}
Tests are currently limited.
.RE
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.IP \(bu 4
.\}
No code audit has been made.
.RE
.P

View File

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

View File

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

View File

@ -58,9 +58,11 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
T_R ((pfd == NULL), IPC_ERROR_CONTACT_IPCD__NO_FD_PARAM);
T_R ((sname == NULL), IPC_ERROR_CONTACT_IPCD__NO_SERVICE_NAME_PARAM);
// In case there is a problem with ipcd.
*pfd = 0;
char *ipcd_var = getenv ("IPC_NETWORK");
if (ipcd_var == NULL) {
*pfd = 0;
IPC_RETURN_NO_ERROR;
}
// TODO: is there another, more interesting way to do this?
@ -73,7 +75,6 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
memcpy (columnthensname + 1, sname, strlen (sname));
if (strncmp (ipcd_var, sname, strlen (sname)) != 0 && strstr (ipcd_var, columnthensname) == NULL) {
*pfd = 0;
IPC_RETURN_NO_ERROR;
}
@ -95,7 +96,24 @@ struct ipc_error ipc_contact_ipcd (int *pfd, const char *sname)
msg.length = strlen (content);
msg.payload = content;
TEST_IPC_RR (ipc_write_fd (ipcd_fd, &msg), "cannot send a message to networkd");
TEST_IPC_RR (ipc_write_fd (ipcd_fd, &msg), "cannot send a message to ipcd");
memset (&msg, 0, sizeof(struct ipc_message));
// ipcd successfully contacted the service or failed.
// ipcd will tell either OK or NOT OK.
TEST_IPC_RR (ipc_read_fd (ipcd_fd, &msg), "cannot read the ipcd response");
// In case ipcd failed.
if (msg.length != 2) {
printf ("ipcd failed to contact service: (%d bytes) %s\n"
, msg.length
, msg.payload);
SECURE_DECLARATION(struct ipc_error, ret);
ret.error_code = IPC_ERROR_CLOSED_RECIPIENT;
usock_close (ipcd_fd);
return ret;
}
struct ipc_error ret = ipc_receive_fd (ipcd_fd, pfd);
if (ret.error_code == IPC_ERROR_NONE) {
@ -116,7 +134,7 @@ struct ipc_error ipc_connection_ (struct ipc_ctx *ctx, const char *sname, enum i
SECURE_DECLARATION(struct pollfd, pollfd);
pollfd.events = POLLIN;
TEST_IPC_P (ipc_contact_ipcd (&pollfd.fd, sname), "error during networkd connection");
TEST_IPC_P (ipc_contact_ipcd (&pollfd.fd, sname), "error during ipcd connection");
// if ipcd did not initiate the connection
if (pollfd.fd <= 0) {
@ -186,12 +204,30 @@ struct ipc_error ipc_close_all (struct ipc_ctx *ctx)
T_R ((ctx == NULL), IPC_ERROR_CLOSE_ALL__NO_CTX_PARAM);
for (size_t i = 0 ; i < ctx->size ; i++) {
TEST_IPC_P (ipc_close (ctx, i), "cannot close a connection in handle_message");
TEST_IPC_P (ipc_close (ctx, i), "cannot close a connection in ipc_close_all");
}
IPC_RETURN_NO_ERROR;
}
// Removing all messages for this fd.
void ipc_remove_messages_for_fd (struct ipc_ctx *ctx, int fd)
{
struct ipc_message *m = NULL;
size_t looping_count = ctx->tx.size;
for (size_t i = 0; i < looping_count; i++) {
m = &ctx->tx.messages[i];
if (m->fd == fd) {
// Freeing the message structure.
ipc_message_empty (m);
ipc_messages_del (&ctx->tx, i); // remove the message indexed by i
// Let restart this round
i--;
looping_count--;
}
}
}
struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index)
{
T_R ((ctx == NULL), IPC_ERROR_CLOSE__NO_CTX_PARAM);
@ -205,6 +241,9 @@ struct ipc_error ipc_close (struct ipc_ctx *ctx, uint32_t index)
ret = usock_close (fd);
}
// Remove all messages for this fd.
ipc_remove_messages_for_fd (ctx, fd);
// Verify that the close was OK.
if (ret.error_code != IPC_ERROR_NONE) {
return ret;
@ -330,18 +369,7 @@ struct ipc_error ipc_del (struct ipc_ctx *ctx, uint32_t index)
ctx->cinfos[index].spath = NULL;
}
struct ipc_message *m = NULL;
// Removing all messages for this fd.
size_t looping_count = ctx->tx.size;
for (size_t i = 0; i < looping_count; i++) {
m = &ctx->tx.messages[i];
if (m->fd == ctx->pollfd[index].fd) {
ipc_messages_del (&ctx->tx, i); // remove the message indexed by i
// Let restart this round
i--;
looping_count--;
}
}
ipc_remove_messages_for_fd (ctx, ctx->pollfd[index].fd);
ctx->size--;
@ -416,7 +444,9 @@ struct ipc_error handle_writing_message (struct ipc_event *event, struct ipc_ctx
m = &ctx->tx.messages[i];
mfd = m->fd;
if (txfd == mfd) {
TEST_IPC_RR (ipc_write_fd (txfd, m), "cannot send a message to the client");
// In case the writing is compromised, do not return right away,
// just print the result.
TEST_IPC_P(ipc_write_fd (txfd, m), "cannot send a message to the client");
// Freeing the message structure.
ipc_message_empty (m);
@ -444,14 +474,24 @@ struct ipc_error handle_new_message (struct ipc_event *event, struct ipc_ctx *ct
ret = ipc_read (ctx, index, m);
if (ret.error_code != IPC_ERROR_NONE && ret.error_code != IPC_ERROR_CLOSED_RECIPIENT) {
struct ipc_error rvalue = ret; // store the final return value
ipc_message_empty (m);
free (m);
int fd = ctx->pollfd[index].fd;
#ifdef DEBUG
printf ("error when ipc_read: index %d fd %d error num %d, message: %s\n"
, index, fd
, ret.error_code
, ret.error_message);
#endif
// if there is a problem, just remove the client
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection in handle_message");
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection in handle_message");
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, index, ctx->pollfd[index].fd, NULL);
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, index, fd, NULL);
return rvalue;
}
@ -460,12 +500,12 @@ struct ipc_error handle_new_message (struct ipc_event *event, struct ipc_ctx *ct
IPC_EVENT_SET (event, IPC_EVENT_TYPE_DISCONNECTION, index, ctx->pollfd[index].fd, NULL);
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection on closed recipient in handle_message");
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection on closed recipient in handle_message");
ipc_message_empty (m);
free (m);
TEST_IPC_P (ipc_close (ctx, index), "cannot close a connection on closed recipient in handle_message");
TEST_IPC_P (ipc_del (ctx, index), "cannot delete a connection on closed recipient in handle_message");
// warning: do not forget to free the ipc_client structure
IPC_RETURN_NO_ERROR;
}
@ -496,16 +536,28 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
IPC_EVENT_CLEAN (event);
int32_t n;
// By default, everything is alright.
SECURE_DECLARATION(struct ipc_error, final_return);
final_return.error_code = IPC_ERROR_NONE;
int32_t n = 0;
for (size_t i = 0; i < ctx->size; i++) {
// We assume that any fd in the list has to be listen to.
#ifdef DEBUG
printf ("reading fd: %d index %lu\n", ctx->pollfd[i].fd, i);
#endif
ctx->pollfd[i].events = POLLIN;
}
// For each message to send…
for (size_t i = 0; i < ctx->tx.size; i++) {
// … verify that its destination is available for message exchange.
for (size_t y = 0; y < ctx->size; y++) {
if (ctx->pollfd[y].fd == ctx->tx.messages[i].fd) {
#ifdef DEBUG
printf ("writing fd: %d\n", ctx->pollfd[y].fd);
#endif
ctx->pollfd[y].events |= POLLOUT;
}
}
@ -519,7 +571,18 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
gettimeofday(&tv_1, NULL);
if ((n = poll(ctx->pollfd, ctx->size, *timer)) < 0) {
int timer_ = *timer;
/* In case there is a file descriptor that requires more to read. */
for (size_t i = 0; i < ctx->size; i++) {
if (ctx->cinfos[i].more_to_read == 1) {
// printf ("There is more to read for _at least_ fd %d\n", ctx->pollfd[i].fd);
timer_ = 0;
break;
}
}
if ((n = poll(ctx->pollfd, ctx->size, timer_)) < 0) {
IPC_RETURN_ERROR (IPC_ERROR_WAIT_EVENT__POLL);
}
@ -538,31 +601,44 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
}
// Timeout.
if (n == 0) {
if (n == 0 && timer_ != 0) {
IPC_EVENT_SET (event, IPC_EVENT_TYPE_TIMER, 0, 0, NULL);
IPC_RETURN_NO_ERROR;
}
for (size_t i = 0; i <= ctx->size; i++) {
for (size_t i = 0; i < ctx->size; i++) {
// Whatever happens, we have the fd and the index in event.
event->index = i;
event->origin = ctx->pollfd[i].fd;
// Something to read or connection.
if (ctx->pollfd[i].revents & POLLIN) {
if (ctx->pollfd[i].revents & POLLIN || ctx->cinfos[i].more_to_read == 1) {
// Avoiding loops.
ctx->cinfos[i].more_to_read = 0;
// In case there is something to read for the server socket: new client.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SERVER) {
return ipc_accept_add (event, ctx, i);
final_return = ipc_accept_add (event, ctx, i);
goto wait_event_exit;
}
// fd is switched: using callbacks for IO operations.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SWITCHED) {
return handle_switched_message (event, ctx, i);
final_return = handle_switched_message (event, ctx, i);
goto wait_event_exit;
}
// No treatment of the socket if external socket: the libipc user should handle IO operations.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_EXTERNAL) {
IPC_EVENT_SET (event, IPC_EVENT_TYPE_EXTRA_SOCKET, i, ctx->pollfd[i].fd, NULL);
IPC_RETURN_NO_ERROR;
// Default: return no error.
goto wait_event_exit;
}
return handle_new_message (event, ctx, i);
final_return = handle_new_message (event, ctx, i);
goto wait_event_exit;
}
// Something can be sent.
@ -571,20 +647,50 @@ struct ipc_error ipc_wait_event (struct ipc_ctx *ctx, struct ipc_event *event, i
// fd is switched: using callbacks for IO operations.
if (ctx->cinfos[i].type == IPC_CONNECTION_TYPE_SWITCHED) {
return handle_writing_switched_message (event, ctx, i);
final_return = handle_writing_switched_message (event, ctx, i);
goto wait_event_exit;
}
return handle_writing_message (event, ctx, i);
final_return = handle_writing_message (event, ctx, i);
goto wait_event_exit;
}
// Disconnection.
if (ctx->pollfd[i].revents & POLLHUP) {
/** IPC_EVENT_SET: event, type, index, fd, message */
IPC_EVENT_SET (event, IPC_EVENT_TYPE_DISCONNECTION, i, ctx->pollfd[i].fd, NULL);
return ipc_close (ctx, i);
final_return = ipc_close (ctx, i);
goto wait_event_exit;
}
if (ctx->pollfd[i].revents & POLLERR) {
#ifdef DEBUG
printf ("pollerr: problem with fd %d\n", ctx->pollfd[i].fd);
#endif
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, i, ctx->pollfd[i].fd, NULL);
goto wait_event_exit;
}
if (ctx->pollfd[i].revents & POLLNVAL) {
#ifdef DEBUG
printf ("pollnval: invalid fd %d\n", ctx->pollfd[i].fd);
#endif
IPC_EVENT_SET (event, IPC_EVENT_TYPE_ERROR, i, ctx->pollfd[i].fd, NULL);
goto wait_event_exit;
}
} /** for loop: end of the message handling */
IPC_RETURN_NO_ERROR;
printf ("END OF THE LOOP WITHOUT GOTO!\n");
wait_event_exit:
/** TODO: tests on event, it has to be filled. */
if (event->type == 0) {
printf ("EVENT TYPE NOT FILLED! code: %d, error_message: %s\n"
, final_return.error_code
, final_return.error_message);
}
return final_return;
}

View File

@ -18,6 +18,7 @@
#define RUNDIR "/run/ipc/"
#define PATH_MAX 4096
#define IPC_HEADER_SIZE 6
// #define __IPC_BASE_SIZE 500000 // 500 KB
#define __IPC_BASE_SIZE 2000000 // 2 MB, plenty enough space for messages
#define IPC_MAX_MESSAGE_SIZE __IPC_BASE_SIZE-IPC_HEADER_SIZE
@ -41,7 +42,7 @@ enum msg_types {
, MSG_TYPE_ERR = 1
, MSG_TYPE_DATA = 2
, MSG_TYPE_NETWORK_LOOKUP = 3
} message_types;
};
/**
* Event types.
@ -231,6 +232,7 @@ enum ipc_connection_type {
struct ipc_connection_info {
enum ipc_connection_type type;
short int more_to_read;
char *spath; // max size: PATH_MAX
};
@ -250,9 +252,9 @@ struct ipc_messages {
struct ipc_switching {
int orig;
int dest;
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m);
enum ipccb (*orig_in) (int origin_fd, struct ipc_message *m, short int *more_to_read);
enum ipccb (*orig_out) (int origin_fd, struct ipc_message *m);
enum ipccb (*dest_in) (int origin_fd, struct ipc_message *m);
enum ipccb (*dest_in) (int origin_fd, struct ipc_message *m, short int *more_to_read);
enum ipccb (*dest_out) (int origin_fd, struct ipc_message *m);
};
@ -315,6 +317,8 @@ struct ipc_event {
#define IPC_EVENT_CLEAN(pevent) {\
pevent->type = IPC_EVENT_TYPE_NOT_SET;\
pevent->origin = 0;\
pevent->index = 0;\
if (pevent->m != NULL) {\
ipc_message_empty (pevent->m);\
free(pevent->m);\
@ -375,7 +379,7 @@ void ipc_messages_free (struct ipc_messages *);
// Switch cases macros
// print on error
#define ERROR_CASE(e,f,m) case e : { fprintf (stderr, "function %s: %s", f, m); } break;
#define ERROR_CASE(e,f,m) case e : { fprintf (stderr, "function %s: %s\n", f, m); } break;
/***
* non public functions
@ -399,16 +403,17 @@ struct ipc_error service_path (char *path, const char *sname);
**/
void ipc_ctx_switching_add (struct ipc_ctx *ctx, int orig, int dest);
int ipc_ctx_switching_del (struct ipc_ctx *ctx, int fd);
void ipc_switching_add (struct ipc_switchings *is, int orig, int dest);
int ipc_switching_del (struct ipc_switchings *is, int fd);
int ipc_switching_get (struct ipc_switchings *is, int fd);
void ipc_switching_free (struct ipc_switchings *is);
void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
, enum ipccb (*cb_in )(int fd, struct ipc_message *m));
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read));
void ipc_switching_callbacks (
struct ipc_ctx *ctx
, int fd
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read)
, enum ipccb (*cb_out)(int fd, struct ipc_message *m));
int ipc_ctx_fd_type (struct ipc_ctx *ctx, int fd, enum ipc_connection_type type);
@ -521,6 +526,7 @@ struct ipc_error ipc_provide_fd (int sock, int fd);
TEST_IPC_RR_F(function_to_test, "%s", err_message)
// same as TEST_IPC_RR but do not return
// Also: print the error right away.
#define TEST_IPC_P(function_to_test, err_message) {\
struct ipc_error ret = function_to_test;\
if (ret.error_code != IPC_ERROR_NONE) {\
@ -528,6 +534,8 @@ struct ipc_error ipc_provide_fd (int sock, int fd);
, "non blocking error" \
, ":" __FILE__ "%s:%s" \
, err_message );\
\
fprintf (stderr, "%s\n", ret.error_message);\
}\
}

View File

@ -88,14 +88,10 @@ void ipc_ctx_switching_add (struct ipc_ctx *ctx, int orig, int dest)
void ipc_switching_add (struct ipc_switchings *is, int orig, int dest)
{
// printf ("ipc_switching_add START: switchdb has %ld entries\n", is->size);
if (is->collection == NULL) {
// printf ("switchdb collection is null\n");
is->collection = malloc (sizeof (struct ipc_switching) * (is->size + 1));
}
else {
// printf ("switchdb collection isn't null\n");
is->collection = realloc (is->collection, sizeof (struct ipc_switching) * (is->size + 1));
}
@ -114,8 +110,11 @@ void ipc_switching_add (struct ipc_switchings *is, int orig, int dest)
is->collection[is->size - 1].dest_in = NULL;
is->collection[is->size - 1].orig_out = NULL;
is->collection[is->size - 1].dest_out = NULL;
}
// printf ("ipc_switching_add END: switchdb has %ld entries\n", is->size);
int ipc_ctx_switching_del (struct ipc_ctx *ctx, int fd)
{
return ipc_switching_del (&ctx->switchdb, fd);
}
int ipc_switching_del (struct ipc_switchings *is, int fd)
@ -197,9 +196,10 @@ void ipc_switching_free (struct ipc_switchings *is)
}
enum ipccb
default_cb_in(int fd, struct ipc_message *m)
default_cb_in(int fd, struct ipc_message *m, short int *more_to_read)
{
// TODO: fix buffer size for switching messages
*more_to_read = 0;
size_t msize = IPC_MAX_MESSAGE_SIZE;
SECURE_BUFFER_DECLARATION (char, buf, msize);
char *pbuf = buf;
@ -251,7 +251,7 @@ default_cb_out(int fd, struct ipc_message *m)
}
void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
, enum ipccb (*cb_in )(int fd, struct ipc_message *m))
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read))
{
ipc_switching_callbacks (ctx, fd, cb_in, NULL);
}
@ -259,7 +259,7 @@ void ipc_switching_callbacks_ (struct ipc_ctx *ctx, int fd
void ipc_switching_callbacks (
struct ipc_ctx *ctx
, int fd
, enum ipccb (*cb_in )(int fd, struct ipc_message *m)
, enum ipccb (*cb_in )(int fd, struct ipc_message *m, short int *more_to_read)
, enum ipccb (*cb_out)(int fd, struct ipc_message *m))
{
struct ipc_switching *sw = NULL;
@ -287,6 +287,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
// If the socket is associated to another one for ipcd:
// read and write automatically and provide a new IPC_EVENT_TYPE indicating the switch.
T_R ((ctx->switchdb.size == 0), IPC_ERROR_FD_SWITCHING__NO_FD_RECORD);
int talkingfd = ctx->pollfd[index].fd;
@ -297,6 +298,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
enum ipccb r;
int is_valid = 0;
short int more_to_read = 0;
is_valid = ipc_switching_get_ (&ctx->switchdb, talkingfd, &sw);
@ -305,22 +307,24 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
if (sw->orig == talkingfd) {
dest_fd = sw->dest;
if (sw->orig_in == NULL) {
r = default_cb_in (talkingfd, &m);
r = default_cb_in (talkingfd, &m, &more_to_read);
}
else {
r = (*sw->orig_in)(talkingfd, &m);
r = (*sw->orig_in)(talkingfd, &m, &more_to_read);
}
}
else {
dest_fd = sw->orig;
if (sw->dest_in == NULL) {
r = default_cb_in (talkingfd, &m);
r = default_cb_in (talkingfd, &m, &more_to_read);
}
else {
r = (*sw->dest_in)(talkingfd, &m);
r = (*sw->dest_in)(talkingfd, &m, &more_to_read);
}
}
ctx->cinfos[index].more_to_read = more_to_read;
// Message reception OK: reading the message and put it in the list of messages to send.
if (r == IPC_CB_NO_ERROR) {
// In case of message reception:
@ -339,7 +343,7 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
// This is applied to protocol-specific messages, for example when the client
// has to communicate with the proxy, not the service.
if (r == IPC_CB_IGNORE) {
printf ("IGNORING REQUEST\n");
// printf ("IGNORING REQUEST\n");
// In case of message reception:
// 1. set event IPC_EVENT_TYPE_SWITCH, inform ipcd of a successful reception.
IPC_EVENT_SET (event, IPC_EVENT_TYPE_SWITCH, index, ctx->pollfd[index].fd, NULL);
@ -351,13 +355,14 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
* NOTE: In any other case, the fd is, or should be closed.
*/
// 1. close and remove both fd from switchdb
close (sw->dest);
ipc_del_fd (ctx, sw->dest);
// Should not close the client: it's the job of the libipc user application.
// XXX: this may be normal, but should be documented.
// 1. remove both fd from switchdb
// Client and servers should be closed by the libipc user application.
// close (sw->dest);
// close (talkingfd);
ipc_del_fd (ctx, sw->dest);
ipc_del_fd (ctx, talkingfd);
ipc_switching_del (&ctx->switchdb, talkingfd);
// 2. set event (either error or disconnection)
@ -377,8 +382,6 @@ struct ipc_error fd_switching_read (struct ipc_event *event, struct ipc_ctx *ctx
*/
struct ipc_error fd_switching_write (struct ipc_event *event, struct ipc_ctx *ctx, int index)
{
// printf ("fd_switching_write\n");
// If the socket is associated to another one for ipcd:
// read and write automatically and provide a new IPC_EVENT_TYPE indicating the switch.
T_R ((ctx->switchdb.size == 0), IPC_ERROR_FD_SWITCHING__NO_FD_RECORD);

View File

@ -23,8 +23,73 @@
struct ipc_error usock_send (const int32_t fd, const char *buf, size_t len, size_t * sent)
{
ssize_t ret = 0;
#ifdef __PRINT_MSG_SIZES
fprintf (stderr, "a %10lu-byte message should be sent to %d\n", len, fd);
#endif
ret = send (fd, buf, len, MSG_NOSIGNAL);
T_R ((ret <= 0), IPC_ERROR_USOCK_SEND);
if (ret == -1)
{
// Some choice could be made.
switch (errno) {
// The receive buffer pointer(s) point outside the process's address space.
ERROR_CASE (EACCES, "usock_send", "write permission is denied");
// The socket is marked nonblocking and the requested operation would block.
// POSIX.1-2001 allows either error to be returned for this case, and does not
// require these constants to have the same value, so a portable application
// should check for both possibilities.
ERROR_CASE (EWOULDBLOCK, "usock_send", "socket marked as nonblocking, but requested operation would block");
// ERROR_CASE (EAGAIN, "usock_send", "socket not previously bound to an address and all ports are in use");
ERROR_CASE (EALREADY, "usock_send", "another Fast Open is in progress");
ERROR_CASE (EBADF, "usock_send", "sockfd is not a valid open file descriptor");
ERROR_CASE (ECONNRESET, "usock_send", "Connection reset by peer.");
ERROR_CASE (EDESTADDRREQ, "usock_send", "socket not connection-mode, and no peer address is set.");
ERROR_CASE (EFAULT, "usock_send", "an invalid user space address was specified for an argument");
// See signal(7).
ERROR_CASE (EINTR, "usock_send", "a signal occurred before any data was transmitted");
ERROR_CASE (EINVAL, "usock_send", "invalid argument passed");
// This error should not happen, and the recipient specification may be ignored.
ERROR_CASE (EISCONN, "usock_send", "connection-mode socket was already connected but a recipient was specified");
// The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible.
ERROR_CASE (EMSGSIZE, "usock_send", "cannot send a message of that size");
// This generally indicates that the interface has stopped sending, but
// may be caused by transient congestion. (Normally, this does not occur in Linux.
// Packets are just silently dropped when a device queue overflows.)
ERROR_CASE (ENOBUFS, "usock_send", "the output queue for the network interface was full");
ERROR_CASE (ENOMEM, "usock_send", "no memory available");
ERROR_CASE (ENOTCONN, "usock_send", "the socket is not connected, and no target has been given");
// Should not happen in libipc (watch out for libipc user application).
ERROR_CASE (ENOTSOCK, "usock_send", "the file descriptor sockfd does not refer to a socket");
// Should not happen in libipc.
ERROR_CASE (EOPNOTSUPP, "usock_send", "some bit in the flags argument is inappropriate for the socket type");
// In this case, the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
ERROR_CASE (EPIPE, "usock_send", "the local end has been shut down on a connection oriented socket");
default:
fprintf (stderr, "usock_send: unrecognized error after send(2), num: %d\n", errno);
}
}
T_R ((ret == -1), IPC_ERROR_USOCK_SEND);
*sent = ret;
IPC_RETURN_NO_ERROR;
}
@ -35,37 +100,57 @@ struct ipc_error usock_recv (const int32_t fd, char **buf, size_t * len)
T_R ((buf == NULL), IPC_ERROR_USOCK_RECV__NO_BUFFER);
T_R ((len == NULL), IPC_ERROR_USOCK_RECV__NO_LENGTH);
// printf("USOCKET: listen to %d (up to %lu bytes)\n", fd, *len);
int32_t ret_recv = 0;
if (*len == 0)
*len = IPC_MAX_MESSAGE_SIZE;
// msize_read: size of the message (without the header).
uint32_t msize = 0;
// msize_read: size sum of the packets received.
uint32_t msize_read = 0;
do {
/**
* recv:
* ret > 0: message receveid
* ret == 0: fd is closing
* ret < 0: error
*/
ret_recv = recv (fd, *buf, *len, 0);
#ifdef IPC_DEBUG
if (ret_recv > 0) {
#ifdef IPC_DEBUG
print_hexa ("msg recv", (uint8_t *) * buf, ret_recv);
fflush (stdout);
}
#endif
if (ret_recv > 0) {
if (msize == 0) {
memcpy (&msize, *buf + 1, sizeof msize);
}
msize = ntohl (msize);
msize = ntohl (msize);
if (msize >= IPC_MAX_MESSAGE_SIZE) {
#ifdef __PRINT_MSG_SIZES
fprintf (stderr, "a %10u-byte message should be received on %d\n"
, msize + IPC_HEADER_SIZE
, fd);
#endif
}
// else {
// printf ("USOCKET: We received a message in (at least) two packets (receveid %u bytes).\n", msize_read);
// }
if (msize > IPC_MAX_MESSAGE_SIZE) {
#ifdef IPC_DEBUG
print_hexa ("msg recv", (uint8_t *) * buf, ret_recv);
fflush (stdout);
#endif
}
// Do not allow messages with a longer size than expected.
T_R ((msize > IPC_MAX_MESSAGE_SIZE), IPC_ERROR_USOCK_RECV__MESSAGE_SIZE);
msize_read += ret_recv - IPC_HEADER_SIZE;
msize_read += ret_recv;
} else if (ret_recv < 0) {
*len = 0;
@ -111,11 +196,17 @@ struct ipc_error usock_recv (const int32_t fd, char **buf, size_t * len)
, "usock_recv: recv < 0, is the message size malformed?");
}
} while (msize > msize_read);
// if (msize > msize_read) {
// printf ("USOCKET: loop again for %d (read %u/%u)\n", fd, msize_read, msize);
// }
*len = msize + IPC_HEADER_SIZE;
// In case msize still is 0, recv didn't worked as expected.
} while (msize > 0 && msize > msize_read - IPC_HEADER_SIZE);
// 1 on none byte received, indicates a closed recipient
// printf("USOCKET: end of the loop for client %d -- %u bytes read\n", fd, msize_read);
*len = msize_read;
// none bytes received, indicates a closed recipient
if (ret_recv == 0) {
*len = 0;
IPC_RETURN_ERROR (IPC_ERROR_CLOSED_RECIPIENT);
@ -135,7 +226,7 @@ struct ipc_error usock_connect (int32_t * fd, const char *path)
int32_t sfd;
socklen_t peer_addr_size = sizeof (struct sockaddr_un);
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1), "socket", IPC_ERROR_USOCK_CONNECT__SOCKET);
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_STREAM, 0)) == -1), "socket", IPC_ERROR_USOCK_CONNECT__SOCKET);
strncpy (my_addr.sun_path, path, (strlen (path) < PATH_MAX) ? strlen (path) : PATH_MAX);
TEST_IPC_RETURN_ON_ERROR(directory_setup_ (path));
@ -169,7 +260,7 @@ struct ipc_error usock_init (int32_t * fd, const char *path)
TEST_IPC_RETURN_ON_ERROR(directory_setup_ (path));
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) == -1)
T_PERROR_RIPC (((sfd = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
, "socket", IPC_ERROR_USOCK_INIT__WRONG_FILE_DESCRIPTOR);
// delete the unix socket if already created

View File

@ -32,6 +32,7 @@ int main(int argc, char * argv[])
printf ("error: %s\n", ipc_errors_get(ret.error_code));
return EXIT_FAILURE;
}
ipc_ctx_free (&ctx);
return EXIT_SUCCESS;
}

View File

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

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
}
@ -76,11 +76,13 @@ int main(void)
read_message (&ctx1);
TEST_IPC_Q (ipc_close_all (&ctx1), EXIT_FAILURE);
ipc_ctx_free (&ctx1);
send_message (&ctx2);
read_message (&ctx2);
TEST_IPC_Q (ipc_close_all (&ctx2), EXIT_FAILURE);
ipc_ctx_free (&ctx2);
return EXIT_SUCCESS;
}

View File

@ -6,6 +6,20 @@
#include <string.h>
#define SERVICE_NAME "pong"
struct ipc_ctx *pctx = NULL;
void exit_program(int signal)
{
printf("Quitting, signal: %d\n", signal);
// the application will shut down, and close the service
TEST_IPC_Q(ipc_close_all (pctx), EXIT_FAILURE);
// free remaining ctx
ipc_ctx_free (pctx);
exit(EXIT_SUCCESS);
}
int main_loop(int argc, char * argv[])
{
@ -13,6 +27,7 @@ int main_loop(int argc, char * argv[])
argv = argv;
SECURE_DECLARATION (struct ipc_ctx, ctx);
pctx = &ctx;
int timer = 10000; // in ms
printf ("func 03 - server init...\n");
@ -24,7 +39,13 @@ int main_loop(int argc, char * argv[])
printf ("func 01 - service polling...\n");
// listen only for a single client
while (1) {
TEST_IPC_WAIT_EVENT_Q (ipc_wait_event (&ctx, &event, &timer), EXIT_FAILURE);
// TEST_IPC_WAIT_EVENT_Q (ipc_wait_event (&ctx, &event, &timer), EXIT_FAILURE);
struct ipc_error ret = ipc_wait_event (&ctx, &event, &timer);
if (ret.error_code != IPC_ERROR_NONE &&
ret.error_code != IPC_ERROR_CLOSED_RECIPIENT) {
printf ("An error happened :(\n");
}
switch (event.type) {
case IPC_EVENT_TYPE_TIMER : {
@ -41,8 +62,12 @@ int main_loop(int argc, char * argv[])
};
break;
case IPC_EVENT_TYPE_MESSAGE : {
printf ("received message: %s\n", ((struct ipc_message*) event.m)->payload);
ipc_write (&ctx, (struct ipc_message*) event.m);
struct ipc_message *m = (struct ipc_message*) event.m;
printf ("received message (%d bytes): %.*s\n"
, m->length
, m->length
, m->payload);
ipc_write (&ctx, m);
}
break;
case IPC_EVENT_TYPE_TX : {
@ -54,7 +79,6 @@ int main_loop(int argc, char * argv[])
case IPC_EVENT_TYPE_EXTRA_SOCKET :
default :
printf ("not ok - should not happen\n");
exit (EXIT_FAILURE);
break;
}
}
@ -68,21 +92,17 @@ int main_loop(int argc, char * argv[])
return 0;
}
void exit_program(int signal)
{
printf("Quitting, signal: %d\n", signal);
exit(EXIT_SUCCESS);
}
int main(int argc, char * argv[])
{
// In case we want to quit the program, do it cleanly.
signal (SIGHUP, exit_program);
signal (SIGALRM, exit_program);
signal (SIGUSR1, exit_program);
signal (SIGUSR2, exit_program);
signal (SIGTERM, exit_program);
signal (SIGINT, exit_program);
main_loop (argc, argv);
return EXIT_SUCCESS;
}

View File

@ -53,6 +53,7 @@ int main(void)
read_message (&ctx);
TEST_IPC_Q(ipc_close_all (&ctx), EXIT_FAILURE);
ipc_ctx_free(&ctx);
return EXIT_SUCCESS;
}

8
zig-impl/.gitignore vendored Normal file
View File

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

44
zig-impl/README.md Normal file
View File

@ -0,0 +1,44 @@
# Why rewrite in Zig
I did a library called `libipc` then I wanted to rewrite it in Zig.
The problems with the C-based version:
- libipc cannot easily be ported on other plateforms
* cannot compile under musl
* cannot compile for any OS
* different libc = different error codes for functions
- cannot change the code easily without risking to break stuff
- behavior isn't the same accross OSs
* glibc-based and musl-based Linux have different behavior
* Linux and OpenBSD have different behavior
- hard to debug
* OpenBSD has a buggy valgrind
For all these reasons, this library took a lot of time to dev (~ 2 months), and I'm still not entirely confident in its reliability.
Tests are required to gain confidence.
The mere compilation isn't a proof of anything.
And the code size (2kloc) for something that simple can be seen as a mess, despite being rather fair.
What Zig brings to the table:
- OS and libc-agnostic, all quirks are handled in the std lib by the Zig dev team
- same source code for all OSs
- can compile **from and to** any OS and libc
- errors already are verified in the std lib
- compiling `foo() catch |e| switch(e) {}` shows all possible errors
- std lib provides
* basic structures and functions, no need to add deps or write functions to have simple lists and such
* memory allocators & checks, no need for valgrind and other memory checking tools
* basic network structures (address & stuff)
- structures can be printed
- code and tests in the same file
- (in a near future) async functions, to handle networking operations in a very elegant manner
All these features provide a **massive gain of time**.
And in general, just having the same standard library for all OSs is great just not to have to rely on different implementations and even locations of code and header files.
What isn't *that* great with Zig:
- structures can be printed, but they also provide the format themselves, leading to an unreliable way of discovering structure's content.

38
zig-impl/TODO.md Normal file
View File

@ -0,0 +1,38 @@
### MISC
- create the unix socket directory
- close the connection and log when we receive too big messages
- decide then explicitely document what the max message size should be
- rx message buffer should be small but should grow if required
- some functions are not exposed in the bindings, mostly functions related to switching
### makefile
- release
- distribution
### documentation
- document the two ways to use LibIPC: either within some Zig code or through the bindings
- manpages for ipcd, tcpd, pong, pongd
### src/exchange-fd.zig
- still very WIP, even though it works as expected
- recvmsg is a very *stupid* copy of the sendmsg fn, **EXPECT ERRORS** (if used outside libipc)
- at least one memory error when using Cmsghdr (see below)
==32374== Syscall param sendmsg(msg.msg_control) points to uninitialised byte(s)
==32374== at 0x40554A3: ??? (in /lib/ld-musl-x86_64.so.1)
==32374== by 0x40526F9: ??? (in /lib/ld-musl-x86_64.so.1)
==32374== by 0x4096B83: ???
==32374== Address 0x1ffefff384 is on thread 1's stack
==32374== Uninitialised value was created by a client request
==32374== at 0x289769: exchange-fd.Cmsghdr(i32).init (exchange-fd.zig:39)
==32374== by 0x2808F0: exchange-fd.send_fd (exchange-fd.zig:86)
==32374== by 0x27EA97: ipcd.create_service (ipcd.zig:178)
==32374== by 0x28117C: ipcd.main (ipcd.zig:224)
==32374== by 0x28161E: callMain (start.zig:614)
==32374== by 0x28161E: initEventLoopAndCallMain (start.zig:548)
==32374== by 0x28161E: callMainWithArgs (start.zig:498)
==32374== by 0x28161E: main (start.zig:513)

226
zig-impl/apps/ipcd.zig Normal file
View File

@ -0,0 +1,226 @@
const std = @import("std");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const ipc = @import("ipc");
const hexdump = ipc.hexdump;
const Message = ipc.Message;
// Import send_fd this way in order to produce docs for exchange-fd functions.
const exchange_fd = ipc.exchangefd;
const send_fd = exchange_fd.send_fd;
const builtin = @import("builtin");
const native_os = builtin.target.os.tag;
const print = std.debug.print;
const testing = std.testing;
const print_eq = ipc.util.print_eq;
const URI = ipc.util.URI;
// Standard library is unecessary complex regarding networking.
// libipc drops it and uses plain old file descriptors instead.
// API should completely obfuscate the inner structures.
// Only libipc structures should be necessary to write any networking code,
// users should only work with Context and Message, mostly.
// QUESTION: should libipc use std.fs.path and not simple [] const u8?
fn create_service() !void {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try ipc.Context.init(allocator);
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
// SERVER SIDE: creating a service.
_ = try ctx.server_init("ipc");
// signal handler, to quit when asked
const S = struct {
var should_quit: bool = false;
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
print ("A signal has been received: {}\n", .{sig});
// Check that we received the correct signal.
switch (native_os) {
.netbsd => {
if (sig != os.SIG.HUP or sig != info.info.signo)
return;
},
else => {
if (sig != os.SIG.HUP and sig != info.signo)
return;
},
}
should_quit = true;
}
};
var sa = os.Sigaction{
.handler = .{ .sigaction = &S.handler },
.mask = os.empty_sigset, // Do not mask any signal.
.flags = os.SA.SIGINFO,
};
// Quit on SIGHUP (kill -1).
try os.sigaction(os.SIG.HUP, &sa, null);
var some_event: ipc.Event = undefined;
ctx.timer = 1000; // 1 second
var count: u32 = 0;
while(! S.should_quit) {
some_event = try ctx.wait_event();
switch (some_event.t) {
.TIMER => {
print("\rTimer! ({})", .{count});
count += 1;
},
.CONNECTION => {
print("New connection: {} so far!\n", .{ctx.pollfd.items.len});
},
.DISCONNECTION => {
print("User {} disconnected, {} remainaing.\n"
, .{some_event.origin, ctx.pollfd.items.len});
},
.EXTERNAL => {
print("Message received from a non IPC socket.\n", .{});
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
break;
},
.SWITCH_RX => {
print("Message has been received (SWITCH).\n", .{});
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
break;
},
.SWITCH_TX => {
print("Message has been sent (SWITCH).\n", .{});
print("NOT IMPLEMENTED, YET. It's a suicide, then.\n", .{});
break;
},
.MESSAGE_RX => {
print("Client asking for a service through ipcd.\n", .{});
defer ctx.close_fd (some_event.origin) catch {};
if (some_event.m) |m| {
print("{}\n", .{m});
defer m.deinit(); // Do not forget to free the message payload.
// 1. split message
var iterator = std.mem.split(u8, m.payload, ";");
var service_to_contact = iterator.first();
// print("service to contact: {s}\n", .{service_to_contact});
var final_destination: ?[]const u8 = null;
// 2. find relevant part of the message
while (iterator.next()) |next| {
// print("next part: {s}\n", .{next});
var iterator2 = std.mem.split(u8, next, " ");
var sname = iterator2.first();
var target = iterator2.next();
if (target) |t| {
// print ("sname: {s} - target: {s}\n", .{sname, t});
if (std.mem.eql(u8, service_to_contact, sname)) {
final_destination = t;
}
}
else {
print("ERROR: no target in: {s}\n", .{next});
}
}
// 3. connect whether asked to and send a message
if (final_destination) |dest| {
print("Connecting to {s} (service requested: {s})\n"
, .{dest, service_to_contact});
var uri = URI.read(dest);
// 1. in case there is no URI
if (std.mem.eql(u8, uri.protocol, dest)) {
var newfd = try ctx.connect_service (dest);
send_fd (some_event.origin, "ok", newfd);
try ctx.close_fd (newfd);
}
else if (std.mem.eql(u8, uri.protocol, "unix")) {
var newfd = try ctx.connect_service (uri.address);
send_fd (some_event.origin, "ok", newfd);
try ctx.close_fd (newfd);
}
// 2. else, contact <protocol>d or directly the dest in case there is none.
else {
var servicefd = try ctx.connect_service (uri.protocol);
defer ctx.close_fd (servicefd) catch {};
// TODO: make a simple protocol between IPCd and <protocol>d
// NEED inform about the connection (success or fail)
// FIRST DRAFT:
// - IPCd: send a message containing the destination
// - PROTOCOLd: send "ok" to inform the connection is established
// - PROTOCOLd: send "no" in case there was an error
var message = try Message.init(servicefd, allocator, dest);
defer message.deinit();
try ctx.write(message);
var response_from_service = try ctx.read_fd(servicefd);
if (response_from_service) |r| {
defer r.deinit();
if (std.mem.eql(u8, r.payload, "ok")) {
// OK
// print("service has established the connection\n", .{});
send_fd (some_event.origin, "ok", servicefd);
}
else if (std.mem.eql(u8, r.payload, "ne")) {
// PROBLEM
print("service cannot establish the connection\n", .{});
// TODO
}
else {
print("service isn't working properly, its response is: {s}\n", .{r.payload});
// TODO
}
}
else {
// No message = should be handled as a disconnection.
print("No response from service: let's drop everything\n", .{});
}
}
}
}
else {
// There is a problem: ipcd was contacted without providing
// a message, meaning there is nothing to do. This should be
// explicitely warned about.
var response = try Message.init(some_event.origin
, allocator
, "lookup message without data");
defer response.deinit();
try ctx.write(response);
}
},
.MESSAGE_TX => {
print("Message sent.\n", .{});
},
.ERROR => {
print("A problem occured, event: {}, let's suicide\n", .{some_event});
break;
},
}
}
print("Goodbye\n", .{});
}
pub fn main() !u8 {
try create_service();
return 0;
}

71
zig-impl/apps/pong.zig Normal file
View File

@ -0,0 +1,71 @@
const std = @import("std");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const print = std.debug.print;
const ipc = @import("ipc");
const hexdump = ipc.hexdump;
const Message = ipc.Message;
pub fn main() !u8 {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try ipc.Context.init(allocator);
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
// The service to contact, either provided with the SERVICE envvar
// or simply using "pong".
var should_free_service_to_contact: bool = true;
var service_to_contact = std.process.getEnvVarOwned(allocator, "SERVICE") catch blk: {
should_free_service_to_contact = false;
break :blk "pong";
};
defer {
if (should_free_service_to_contact)
allocator.free(service_to_contact);
}
var pongfd = try ctx.connect_ipc(service_to_contact);
var message = try Message.init(pongfd, allocator, "bounce me");
try ctx.schedule(message);
var some_event: ipc.Event = undefined;
ctx.timer = 2000; // 2 seconds
while(true) {
some_event = try ctx.wait_event();
switch (some_event.t) {
.TIMER => {
print("Timer!\n", .{});
},
.MESSAGE_RX => {
if (some_event.m) |m| {
print("message has been bounced: {}\n", .{m});
m.deinit();
break;
}
else {
print("Received empty message, ERROR.\n", .{});
break;
}
},
.MESSAGE_TX => {
print("Message sent.\n", .{});
},
else => {
print("Unexpected event: {}, let's suicide\n", .{some_event});
break;
},
}
}
print("Goodbye\n", .{});
return 0;
}

111
zig-impl/apps/pongd.zig Normal file
View File

@ -0,0 +1,111 @@
const std = @import("std");
const os = std.os;
const ipc = @import("ipc");
const hexdump = ipc.hexdump;
const Message = ipc.Message;
const util = ipc.util;
// Import send_fd this way in order to produce docs for exchange-fd functions.
const exchange_fd = ipc.exchangefd;
const send_fd = exchange_fd.send_fd;
const builtin = @import("builtin");
const native_os = builtin.target.os.tag;
const print = std.debug.print;
const testing = std.testing;
fn create_service() !void {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try ipc.Context.init(allocator);
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
// SERVER SIDE: creating a service.
_ = try ctx.server_init("pong");
// signal handler, to quit when asked
const S = struct {
var should_quit: bool = false;
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
print ("A signal has been received: {}\n", .{sig});
// Check that we received the correct signal.
switch (native_os) {
.netbsd => {
if (sig != os.SIG.HUP or sig != info.info.signo)
return;
},
else => {
if (sig != os.SIG.HUP and sig != info.signo)
return;
},
}
should_quit = true;
}
};
var sa = os.Sigaction{
.handler = .{ .sigaction = &S.handler },
.mask = os.empty_sigset, // Do not mask any signal.
.flags = os.SA.SIGINFO,
};
// Quit on SIGHUP (kill -1).
try os.sigaction(os.SIG.HUP, &sa, null);
var some_event: ipc.Event = undefined;
ctx.timer = 20000; // 2 seconds
var count: u32 = 0;
while(! S.should_quit) {
some_event = try ctx.wait_event();
switch (some_event.t) {
.TIMER => {
print("\rTimer! ({})", .{count});
count += 1;
},
.CONNECTION => {
print("New connection: {} so far!\n", .{ctx.pollfd.items.len});
},
.DISCONNECTION => {
print("User {} disconnected, {} remainaing.\n"
, .{some_event.origin, ctx.pollfd.items.len});
},
.MESSAGE_RX => {
if (some_event.m) |m| {
print("New message ({} bytes)\n", .{m.payload.len});
util.print_message ("RECEIVED MESSAGE", m);
print("Echoing it...\n", .{});
try ctx.schedule(m);
}
else {
print("Error while receiving new message.\n", .{});
print("Ignoring...\n", .{});
}
},
.MESSAGE_TX => {
print("Message sent.\n", .{});
},
else => {
print("Error: unexpected event: {}\n", .{some_event});
print("Let's suicide.\n", .{});
break;
},
}
}
print("Goodbye\n", .{});
}
pub fn main() !u8 {
try create_service();
return 0;
}

260
zig-impl/apps/tcpd.zig Normal file
View File

@ -0,0 +1,260 @@
const std = @import("std");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const testing = std.testing;
const print = std.debug.print;
const ipc = @import("ipc");
const hexdump = ipc.hexdump;
const Message = ipc.Message;
// Import send_fd this way in order to produce docs for exchange-fd functions.
const exchange_fd = ipc.exchangefd;
const send_fd = exchange_fd.send_fd;
const builtin = @import("builtin");
const native_os = builtin.target.os.tag;
const print_eq = ipc.util.print_eq;
const URI = ipc.util.URI;
fn init_tcp_server(allocator: std.mem.Allocator, server: *net.StreamServer) !i32 {
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
error.EnvironmentVariableNotFound => blk: {
print ("no ADDRESS envvar: TCPd will listen on 127.0.0.1:9000\n", .{});
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
},
else => { return err; },
};
defer allocator.free(address);
var iterator = std.mem.split(u8, address, ":");
var real_tcp_address = iterator.first();
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
server.* = net.StreamServer.init(.{.reuse_address = true});
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
try server.listen(socket_addr);
const newfd = server.sockfd orelse return error.SocketLOL; // TODO
return newfd;
}
fn create_service() !void {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try ipc.Context.init(allocator);
defer ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
// SERVER SIDE: creating a service.
var service_name = std.process.getEnvVarOwned(allocator, "IPC_SERVICE_NAME") catch |err| switch(err) {
error.EnvironmentVariableNotFound => blk: {
print ("no IPC_SERVICE_NAME envvar: TCPd will be named 'tcp'\n", .{});
break :blk try allocator.dupe(u8, "tcp");
},
else => { return err; },
};
defer allocator.free(service_name);
_ = try ctx.server_init(service_name);
// signal handler, to quit when asked
const S = struct {
var should_quit: bool = false;
fn handler(sig: i32, info: *const os.siginfo_t, _: ?*const anyopaque) callconv(.C) void {
print ("A signal has been received: {}\n", .{sig});
// Check that we received the correct signal.
switch (native_os) {
.netbsd => {
if (sig != os.SIG.HUP or sig != info.info.signo)
return;
},
else => {
if (sig != os.SIG.HUP and sig != info.signo)
return;
},
}
should_quit = true;
}
};
var sa = os.Sigaction{
.handler = .{ .sigaction = &S.handler },
.mask = os.empty_sigset, // Do not mask any signal.
.flags = os.SA.SIGINFO,
};
// Quit on SIGHUP (kill -1).
try os.sigaction(os.SIG.HUP, &sa, null);
var server: net.StreamServer = undefined;
var serverfd = try init_tcp_server(allocator, &server);
try ctx.add_external (serverfd);
var some_event: ipc.Event = undefined;
var previous_event: ipc.Event.Type = ipc.Event.Type.ERROR;
ctx.timer = 1000; // 1 second
var count: u32 = 0;
while(! S.should_quit) {
some_event = try ctx.wait_event();
// For clarity in the output.
if (some_event.t != .TIMER and previous_event == .TIMER ) { print("\n", .{}); }
previous_event = some_event.t;
switch (some_event.t) {
.TIMER => {
print ("\rTimer! ({})", .{count});
count += 1;
},
.CONNECTION => {
print ("New connection: {} so far!\n", .{ctx.pollfd.items.len});
},
.DISCONNECTION => {
print ("User {} disconnected, {} remaining.\n"
, .{some_event.origin, ctx.pollfd.items.len});
},
.EXTERNAL => {
print ("Message received from a non IPC socket.\n", .{});
var client = try server.accept(); // net.StreamServer.Connection
errdefer client.stream.close();
// Receiving a new client from the EXTERNAL socket.
// New client = new switch from a distant TCP connection to a
// local libipc service.
var buffer: [10000]u8 = undefined;
var size = try client.stream.read(&buffer);
var service_to_contact = buffer[0..size];
if (service_to_contact.len == 0) {
print("Error, no service provided, closing the connection.\n", .{});
client.stream.close();
continue;
}
print ("Ask to connect to service [{s}].\n", .{service_to_contact});
var servicefd = ctx.connect_service (service_to_contact) catch |err| {
print("Error while connecting to the service {s}: {}.\n"
, .{service_to_contact, err});
print ("Closing the connection.\n", .{});
client.stream.close();
continue;
};
errdefer ctx.close_fd (servicefd) catch {};
print ("Send a message to inform remote TCPd that the connection is established.\n", .{});
_ = try client.stream.write("ok");
print ("Add current client as external connection (for now).\n", .{});
try ctx.add_external (client.stream.handle);
print ("Message sent, switching.\n", .{});
try ctx.add_switch(client.stream.handle, servicefd);
print ("DONE.\n", .{});
// Some protocols will require to change the default functions
// to read and to write on the client socket.
// Function to call: ctx.set_switch_callbacks(clientfd, infn, outfn);
},
.SWITCH_RX => {
print ("Message has been received (SWITCH fd {}).\n", .{some_event.origin});
// if (some_event.m) |m| {
// var hexbuf: [4000]u8 = undefined;
// var hexfbs = std.io.fixedBufferStream(&hexbuf);
// var hexwriter = hexfbs.writer();
// try hexdump.hexdump(hexwriter, "Received", m.payload);
// print("{s}\n", .{hexfbs.getWritten()});
// }
// else {
// print ("Message received without actually a message?! {}", .{some_event});
// }
},
.SWITCH_TX => {
print ("Message has been sent (SWITCH fd {}).\n", .{some_event.origin});
},
.MESSAGE_RX => {
print ("Client asking for a service through TCPd.\n", .{});
errdefer ctx.close (some_event.index) catch {};
if (some_event.m) |m| {
defer m.deinit(); // Do not forget to free the message payload.
print ("URI to contact {s}\n", .{m.payload});
var uri = URI.read(m.payload);
print ("proto [{s}] address [{s}] path [{s}]\n"
, .{uri.protocol, uri.address, uri.path});
var iterator = std.mem.split(u8, uri.address, ":");
var real_tcp_address = iterator.first();
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
var stream = try net.tcpConnectToAddress(socket_addr);
errdefer stream.close();
print ("Writing URI PATH: {s}\n", .{uri.path});
_ = try stream.write(uri.path);
print ("Writing URI PATH - written, waiting for the final 'ok'.\n", .{});
var buffer: [10000]u8 = undefined;
var size = try stream.read(&buffer);
if (! std.mem.eql(u8, buffer[0..size], "ok")) {
print ("didn't receive 'ok', let's kill the connection\n", .{});
stream.close();
try ctx.close(some_event.index);
continue;
}
print ("Final 'ok' received, sending 'ok' to IPCd.\n", .{});
// Connection is established, inform IPCd.
var response = try Message.init(some_event.origin, allocator, "ok");
defer response.deinit();
try ctx.write(response);
print ("Add current client as external connection (for now).\n", .{});
try ctx.add_external (stream.handle);
print ("Finally, add switching\n", .{});
try ctx.add_switch(some_event.origin, stream.handle);
// Could invoke ctx.set_switch_callbacks but TCP sockets are
// working pretty well with default functions.
}
else {
// TCPd was contacted without providing a message, nothing to do.
var response = try Message.init(some_event.origin, allocator, "no");
defer response.deinit();
try ctx.write(response);
try ctx.close(some_event.index);
}
},
.MESSAGE_TX => {
print ("Message sent.\n", .{});
},
.ERROR => {
print ("A problem occured, event: {}, let's suicide.\n", .{some_event});
break;
},
}
}
print ("Goodbye\n", .{});
}
pub fn main() !u8 {
try create_service();
return 0;
}

75
zig-impl/build.zig Normal file
View File

@ -0,0 +1,75 @@
const std = @import("std");
const VERSION = "0.1.0";
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
const is_release = b.option(bool, "release", "Compile a release build.") orelse false;
if (is_release) {
std.log.err("hello, this is the release stuff", .{});
}
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const static_lib = b.addStaticLibrary(.{
.name = "ipc",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/bindings.zig" },
.target = target,
.optimize = optimize,
});
// Link with the libc of the target system since the C allocator
// is required in the bindings.
static_lib.linkLibC();
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
static_lib.install();
const shared_lib = b.addSharedLibrary(.{
.name = "ipc",
.root_source_file = .{ .path = "src/bindings.zig" },
.version = comptime (try std.builtin.Version.parse(VERSION)),
.target = target,
.optimize = optimize,
});
shared_lib.linkLibC();
shared_lib.install();
// Creates a step for unit testing.
const main_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
main_tests.linkLibC();
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build test`
// This will evaluate the `test` step rather than the default, which is "install".
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
const install_static_lib = b.addInstallArtifact(static_lib);
const static_lib_step = b.step("static", "Compile LibIPC as a static library.");
static_lib_step.dependOn(&install_static_lib.step);
const install_shared_lib = b.addInstallArtifact(shared_lib);
// b.getInstallStep().dependOn(&install_shared_lib.step);
const shared_lib_step = b.step("shared", "Compile LibIPC as a shared library.");
shared_lib_step.dependOn(&install_shared_lib.step);
}

View File

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

View File

@ -0,0 +1,231 @@
require "option_parser"
require "../src/main.cr"
require "yaml"
require "baguette-crystal-base"
require "../authd/libauth.cr"
# require "./altideal-client.cr"
# require "./yaml_uuid.cr" # YAML UUID parser
# require "./authd_api.cr" # Authd interface functions
class Context
class_property simulation = false # do not perform the action
class_property authd_login = "undef" # undef authd user
class_property authd_pass = "undef" # undef authd user password
class_property shared_key = "undef" # undef authd user password
# # Properties to select what to display when printing a deal.
# class_property print_title = true
# class_property print_description = true
# class_property print_owner = true
# class_property print_nb_comments = true
class_property command = "not-implemented"
class_property user_profile : Hash(String,JSON::Any)?
class_property phone : String?
class_property email : String?
# Will be parsed later, with a specific parser.
class_property args : Array(String)? = nil
end
# require "./parse-me"
require "./better-parser"
class Actions
def self.ask_password
STDOUT << "password: "
STDOUT << `stty -echo`
STDOUT.flush
password = STDIN.gets.try &.chomp
STDOUT << '\n'
STDOUT << `stty echo`
password
end
def self.ask_something(str : String) : String?
STDOUT << "#{str} "
STDOUT.flush
answer = STDIN.gets.try &.chomp
answer
end
property the_call = {} of String => Proc(Nil)
property authd : AuthD::Client
def initialize(@authd)
@the_call["user-add"] = ->user_add
@the_call["user-mod"] = ->user_mod
@the_call["user-registration"] = ->user_registration # Do not require admin priviledges.
@the_call["user-delete"] = ->user_deletion # Do not require admin priviledges.
@the_call["user-get"] = ->user_get # Do not require authentication.
@the_call["user-validation"] = ->user_validation # Do not require authentication.
@the_call["user-recovery"] = ->user_recovery # Do not require authentication.
@the_call["user-search"] = ->user_search # Do not require authentication.
@the_call["permission-set"] = ->permission_set
@the_call["permission-check"] = ->permission_check
end
#
# For all functions: the number of arguments is already tested.
#
def user_add
puts "User add!!!"
args = Context.args.not_nil!
login, email, phone = args[0..2]
profile = Context.user_profile
password = Actions.ask_password
exit 1 unless password
pp! authd.add_user login, password.not_nil!, email, phone, profile: profile
rescue e : AuthD::Exception
puts "error: #{e.message}"
end
def user_registration
args = Context.args.not_nil!
login, email, phone = args[0..2]
profile = Context.user_profile
password = Actions.ask_password
exit 1 unless password
res = authd.register login, password.not_nil!, email, phone, profile: profile
puts res
rescue e
puts "error: #{e.message}"
end
# TODO
def user_mod
args = Context.args.not_nil!
userid = args[0]
password : String? = nil
should_ask_password = Actions.ask_something "Should we change the password (Yn) ?" || "n"
case should_ask_password
when /y/i
Baguette::Log.debug "Ok let's change the password!"
password = Actions.ask_password
exit 1 unless password
else
Baguette::Log.debug "Ok no change in password."
end
email = Context.email
phone = Context.phone
Baguette::Log.error "This function shouldn't be used for now."
Baguette::Log.error "It is way too cumbersome."
# res = authd.add_user login, password, email, phone, profile: profile
# puts res
end
def user_deletion
args = Context.args.not_nil!
userid = args[0].to_i
# Check if the request comes from an admin or the user.
res = if Context.shared_key.nil?
authd.delete userid, Context.authd_login, Context.authd_pass
else
authd.delete userid, Context.shared_key
end
puts res
end
def user_validation
args = Context.args.not_nil!
login, activation_key = args[0..1]
pp! authd.validate_user login, activation_key
end
def user_search
args = Context.args.not_nil!
login = args[0]
pp! authd.search_user login
end
def user_get
args = Context.args.not_nil!
login = args[0]
pp! authd.get_user? login
end
def user_recovery
args = Context.args.not_nil!
login, email = args[0..1]
pp! authd.ask_password_recovery login, email
end
def permission_check
args = Context.args.not_nil!
user, application, resource = args[0..2]
# pp! user, application, resource
res = @authd.check_permission user.to_i, application, resource
puts res
end
def permission_set
args = Context.args.not_nil!
user, application, resource, permission = args[0..3]
# pp! user, application, resource, permission
perm = AuthD::User::PermissionLevel.parse(permission)
res = @authd.set_permission user.to_i, application, resource, perm
puts res
end
end
def main
# Authd connection.
authd = AuthD::Client.new
authd.key = Context.shared_key if Context.shared_key != "undef"
# Authd token.
# FIXME: not sure about getting the token, it seems not used elsewhere.
# If login == pass == "undef": do not even try.
#unless Context.authd_login == Context.authd_pass && Context.authd_login == "undef"
# login = Context.authd_login
# pass = Context.authd_pass
# token = authd.get_token? login, pass
# raise "cannot get a token" if token.nil?
# # authd.login token
#end
actions = Actions.new authd
# Now we did read the intent, we should proceed doing what was asked.
begin
actions.the_call[Context.command].call
rescue e
Baguette::Log.info "The command is not recognized (or implemented)."
end
# authd disconnection
authd.close
rescue e
Baguette::Log.info "Exception: #{e}"
end
# Command line:
# tool [options] command [options-for-command]
main

View File

@ -0,0 +1,230 @@
require "option_parser"
opt_authd_admin = -> (parser : OptionParser) {
parser.on "-k file", "--key-file file", "Read the authd shared key from a file." do |file|
Context.shared_key = File.read(file).chomp
Baguette::Log.info "Key for admin operations: #{Context.shared_key}."
end
}
# frequently used functions
opt_authd_login = -> (parser : OptionParser) {
parser.on "-l LOGIN", "--login LOGIN", "Authd user login." do |login|
Context.authd_login = login
Baguette::Log.info "User login for authd: #{Context.authd_login}."
end
parser.on "-p PASSWORD", "--password PASSWORD", "Authd user password." do |password|
Context.authd_pass = password
Baguette::Log.info "User password for authd: #{Context.authd_pass}."
end
}
opt_help = -> (parser : OptionParser) {
parser.on "help", "Prints this help message." do
puts parser
exit 0
end
}
opt_profile = -> (parser : OptionParser) {
parser.on "-P file", "--profile file", "Read the user profile from a file." do |file|
Context.user_profile = JSON.parse(File.read file).as_h
Baguette::Log.info "Reading the user profile: #{Context.user_profile}."
end
}
opt_phone = -> (parser : OptionParser) {
parser.on "-n phone", "--phone-number num", "Phone number." do |phone|
Context.phone = phone
Baguette::Log.info "Reading the user phone number: #{Context.phone}."
end
}
opt_email = -> (parser : OptionParser) {
parser.on "-e email", "--email address", "Email address." do |email|
Context.email = email
Baguette::Log.info "Reading the user email address: #{Context.email}."
end
}
# Unrecognized parameters are used to create commands with multiple arguments.
# Example: user add _login email phone_
# Here, login, email and phone are unrecognized arguments.
# Still, the "user add" command expect them.
unrecognized_args_to_context_args = -> (parser : OptionParser, n_expected_args : Int32) {
# With the right args, these will be interpreted as serialized data.
parser.unknown_args do |args|
if args.size != n_expected_args
Baguette::Log.error "expected number of arguments: #{n_expected_args}, received: #{args.size}"
Baguette::Log.error "args: #{args}"
Baguette::Log.error "#{parser}"
exit 1
end
args.each do |arg|
Baguette::Log.debug "Unrecognized argument: #{arg} (adding to Context.args)"
if Context.args.nil?
Context.args = Array(String).new
end
Context.args.not_nil! << arg
end
end
}
parser = OptionParser.new do |parser|
parser.banner = "usage: #{PROGRAM_NAME} command help"
parser.on "-v verbosity", "--verbosity v", "Verbosity. From 0 to 4 (debug)." do |v|
Baguette::Context.verbosity = v.to_i
Baguette::Log.info "verbosity = #{v}"
end
parser.on "-h", "--help", "Prints this help message." do
puts "usage: #{PROGRAM_NAME} command help"
puts parser
exit 0
end
parser.on "user", "Operations on users." do
parser.banner = "Usage: user [add | mod | delete | validate | search | get | recover | register ]"
parser.on "add", "Adding a user to the DB." do
parser.banner = "usage: user add login email phone [-P profile] [opt]"
Baguette::Log.info "Adding a user to the DB."
Context.command = "user-add"
opt_authd_admin.call parser
opt_profile.call parser
opt_email.call parser
opt_phone.call parser
opt_help.call parser
# login email phone
unrecognized_args_to_context_args.call parser, 3
end
parser.on "mod", "Modify a user account." do
parser.banner = "Usage: user mod userid [-e email|-n phone|-P profile] [opt]"
Baguette::Log.info "Modify a user account."
Context.command = "user-mod"
opt_authd_admin.call parser
opt_email.call parser
opt_phone.call parser
opt_profile.call parser
opt_help.call parser
# userid
unrecognized_args_to_context_args.call parser, 1
end
parser.on "delete", "Remove user." do
parser.banner = "Usage: user delete userid [opt]"
Baguette::Log.info "Remove user."
Context.command = "user-delete"
# You can either be the owner of the account, or an admin.
opt_authd_login.call parser
opt_authd_admin.call parser
opt_help.call parser
# userid
unrecognized_args_to_context_args.call parser, 1
end
parser.on "validate", "Validate user." do
parser.banner = "Usage: user validate login activation-key [opt]"
Baguette::Log.info "Validate user."
Context.command = "user-validate"
# No need to be authenticated.
opt_help.call parser
# login activation-key
unrecognized_args_to_context_args.call parser, 2
end
parser.on "get", "Get user info." do
parser.banner = "Usage: user get login [opt]"
Baguette::Log.info "Get user info."
Context.command = "user-get"
# No need to be authenticated.
opt_help.call parser
# login
unrecognized_args_to_context_args.call parser, 1
end
parser.on "search", "Search user." do
parser.banner = "Usage: user recover login [opt]"
Baguette::Log.info "Search user."
Context.command = "user-search"
# No need to be authenticated.
opt_help.call parser
# login
unrecognized_args_to_context_args.call parser, 1
end
parser.on "recover", "Recover user password." do
parser.banner = "Usage: user recover login email [opt]"
Baguette::Log.info "Recover user password."
Context.command = "user-recovery"
# No need to be authenticated.
opt_help.call parser
# login email
unrecognized_args_to_context_args.call parser, 2
end
# Do not require to be admin.
parser.on "register", "Register a user (requires activation)." do
parser.banner = "Usage: user register login email phone [-P profile] [opt]"
Baguette::Log.info "Register a user (requires activation)."
Context.command = "user-registration"
# These options shouldn't be used here,
# email and phone parameters are mandatory.
# opt_email.call parser
# opt_phone.call parser
opt_profile.call parser
opt_help.call parser
# login email phone
unrecognized_args_to_context_args.call parser, 3
end
end
parser.on "permission", "Permissions management." do
parser.banner = "Usage: permissions [check | set]"
parser.on "set", "Set permissions." do
parser.banner = <<-END
usage: permission set user application resource permission
example: permission set 1002 my-application chat read
permission list: none read edit admin
END
Baguette::Log.info "Set permissions."
Context.command = "permission-set"
opt_authd_admin.call parser
opt_help.call parser
# userid application resource permission
unrecognized_args_to_context_args.call parser, 4
end
parser.on "check", "Check permissions." do
parser.banner = <<-END
usage: permission check user application resource
example: permission check 1002 my-application chat
permission list: none read edit admin
END
Baguette::Log.info "Check permissions."
Context.command = "permission-check"
opt_authd_admin.call parser
opt_help.call parser
# userid application resource
unrecognized_args_to_context_args.call parser, 3
end
end
parser.unknown_args do |args|
if args.size > 0
Baguette::Log.warning "Unknown args: #{args}"
end
end
# parser.on "-X user-password", "--user-password pass", "Read the new user password." do |pass|
# password = pass
# end
end
parser.parse

View File

@ -0,0 +1,239 @@
extend AuthD
class Baguette::Configuration
class Auth < IPC
property recreate_indexes : Bool = false
property storage : String = "storage"
property registrations : Bool = false
property require_email : Bool = false
property activation_template : String = "email-activation"
property recovery_template : String = "email-recovery"
property mailer_exe : String = "mailer"
property read_only_profile_keys : Array(String) = Array(String).new
property print_password_recovery_parameters : Bool = false
end
end
# Provides a JWT-based authentication scheme for service-specific users.
class AuthD::Service < IPC
property configuration : Baguette::Configuration::Auth
# DB and its indexes.
property users : DODB::DataBase(User)
property users_per_uid : DODB::Index(User)
property users_per_login : DODB::Index(User)
# #{@configuration.storage}/last_used_uid
property last_uid_file : String
def initialize(@configuration)
super()
@users = DODB::DataBase(User).new @configuration.storage
@users_per_uid = @users.new_index "uid", &.uid.to_s
@users_per_login = @users.new_index "login", &.login
@last_uid_file = "#{@configuration.storage}/last_used_uid"
if @configuration.recreate_indexes
@users.reindex_everything!
end
self.timer @configuration.ipc_timer
self.service_init "auth"
end
def hash_password(password : String) : String
digest = OpenSSL::Digest.new "sha256"
digest << password
digest.hexfinal
end
# new_uid reads the last given UID and returns it incremented.
# Splitting the retrieval and record of new user ids allows to
# only increment when an user fully registers, thus avoiding a
# Denial of Service attack.
#
# WARNING: to record this new UID, new_uid_commit must be called.
# WARNING: new_uid isn't thread safe.
def new_uid
begin
uid = File.read(@last_uid_file).to_i
rescue
uid = 999
end
uid += 1
end
# new_uid_commit records the new UID.
# WARNING: new_uid_commit isn't thread safe.
def new_uid_commit(uid : Int)
File.write @last_uid_file, uid.to_s
end
def handle_request(event : IPC::Event)
request_start = Time.utc
array = event.message.not_nil!
slice = Slice.new array.to_unsafe, array.size
message = IPCMessage::TypedMessage.deserialize slice
request = AuthD.requests.parse_ipc_json message.not_nil!
if request.nil?
raise "unknown request type"
end
request_name = request.class.name.sub /^AuthD::Request::/, ""
Baguette::Log.debug "<< #{request_name}"
response = begin
request.handle self
rescue e : UserNotFound
Baguette::Log.error "#{request_name} user not found"
AuthD::Response::Error.new "authorization error"
rescue e : AuthenticationInfoLacking
Baguette::Log.error "#{request_name} lacking authentication info"
AuthD::Response::Error.new "authorization error"
rescue e : AdminAuthorizationException
Baguette::Log.error "#{request_name} admin authentication failed"
AuthD::Response::Error.new "authorization error"
rescue e
Baguette::Log.error "#{request_name} generic error #{e}"
AuthD::Response::Error.new "unknown error"
end
# If clients sent requests with an “id” field, it is copied
# in the responses. Allows identifying responses easily.
response.id = request.id
schedule event.fd, response
duration = Time.utc - request_start
response_name = response.class.name.sub /^AuthD::Response::/, ""
if response.is_a? AuthD::Response::Error
Baguette::Log.warning ">> #{response_name} (#{response.reason})"
else
Baguette::Log.debug ">> #{response_name} (Total duration: #{duration})"
end
end
def get_user_from_token(token : String)
token_payload = Token.from_s(@configuration.shared_key, token)
@users_per_uid.get? token_payload.uid.to_s
end
def run
Baguette::Log.title "Starting authd"
Baguette::Log.info "(mailer) Email activation template: #{@configuration.activation_template}"
Baguette::Log.info "(mailer) Email recovery template: #{@configuration.recovery_template}"
self.loop do |event|
case event.type
when LibIPC::EventType::Timer
Baguette::Log.debug "Timer" if @configuration.print_ipc_timer
when LibIPC::EventType::MessageRx
Baguette::Log.debug "Received message from #{event.fd}" if @configuration.print_ipc_message_received
begin
handle_request event
rescue e
Baguette::Log.error "#{e.message}"
# send event.fd, Response::Error.new e.message
end
when LibIPC::EventType::MessageTx
Baguette::Log.debug "Message sent to #{event.fd}" if @configuration.print_ipc_message_sent
when LibIPC::EventType::Connection
Baguette::Log.debug "Connection from #{event.fd}" if @configuration.print_ipc_connection
when LibIPC::EventType::Disconnection
Baguette::Log.debug "Disconnection from #{event.fd}" if @configuration.print_ipc_disconnection
else
Baguette::Log.error "Not implemented behavior for event: #{event}"
end
end
end
end
begin
simulation, no_configuration, configuration_file = Baguette::Configuration.option_parser
configuration = if no_configuration
Baguette::Log.info "do not load a configuration file."
Baguette::Configuration::Auth.new
else
Baguette::Configuration::Auth.get(configuration_file) ||
Baguette::Configuration::Auth.new
end
Baguette::Context.verbosity = configuration.verbosity
if key_file = configuration.shared_key_file
configuration.shared_key = File.read(key_file).chomp
end
OptionParser.parse do |parser|
parser.banner = "usage: authd [options]"
parser.on "--storage directory", "Directory in which to store users." do |directory|
configuration.storage = directory
end
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
configuration.shared_key = File.read(file_name).chomp
end
parser.on "-R", "--allow-registrations", "Allow user registration." do
configuration.registrations = true
end
parser.on "-E", "--require-email", "Require an email." do
configuration.require_email = true
end
parser.on "-t activation-template-name", "--activation-template name", "Email activation template." do |opt|
configuration.activation_template = opt
end
parser.on "-r recovery-template-name", "--recovery-template name", "Email recovery template." do |opt|
configuration.recovery_template = opt
end
parser.on "-m mailer-exe", "--mailer mailer-exe", "Application to send registration emails." do |opt|
configuration.mailer_exe = opt
end
parser.on "-x key", "--read-only-profile-key key", "Marks a user profile key as being read-only." do |key|
configuration.read_only_profile_keys.push key
end
parser.on "-h", "--help", "Show this help" do
puts parser
exit 0
end
end
if simulation
pp! configuration
exit 0
end
AuthD::Service.new(configuration).run
rescue e : OptionParser::Exception
Baguette::Log.error e.message
rescue e
Baguette::Log.error "exception raised: #{e.message}"
e.backtrace.try &.each do |line|
STDERR << " - " << line << '\n'
end
end

View File

@ -0,0 +1,261 @@
require "../../src/json"
require "json"
module AuthD
class Client < IPC
property key : String
property server_fd : Int32 = -1
def initialize
super
@key = ""
fd = self.connect "auth"
if fd.nil?
raise "couldn't connect to 'auth' IPC service"
end
@server_fd = fd
end
def read
slice = self.read @server_fd
m = IPCMessage::TypedMessage.deserialize slice
m.not_nil!
end
def get_token?(login : String, password : String) : String?
send_now Request::GetToken.new login, password
response = AuthD.responses.parse_ipc_json read
if response.is_a?(Response::Token)
response.token
else
nil
end
end
def get_user?(login : String, password : String) : AuthD::User::Public?
send_now Request::GetUserByCredentials.new login, password
response = AuthD.responses.parse_ipc_json read
if response.is_a? Response::User
response.user
else
nil
end
end
def get_user?(uid_or_login : Int32 | String) : ::AuthD::User::Public?
send_now Request::GetUser.new uid_or_login
response = AuthD.responses.parse_ipc_json read
if response.is_a? Response::User
response.user
else
nil
end
end
def send_now(msg : IPC::JSON)
m = IPCMessage::TypedMessage.new msg.type.to_u8, msg.to_json
write @server_fd, m
end
def send_now(type : Request::Type, payload)
m = IPCMessage::TypedMessage.new type.value.to_u8, payload
write @server_fd, m
end
def decode_token(token)
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
user = ::AuthD::User::Public.from_json user.to_json
{user, meta}
end
# FIXME: Extra options may be useful to implement here.
def add_user(login : String, password : String,
email : String?,
phone : String?,
profile : Hash(String, ::JSON::Any)?) : ::AuthD::User::Public | Exception
send_now Request::AddUser.new @key, login, password, email, phone, profile
response = AuthD.responses.parse_ipc_json read
case response
when Response::UserAdded
response.user
when Response::Error
raise Exception.new response.reason
else
# Should not happen in serialized connections, but…
# 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

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

View File

@ -0,0 +1,29 @@
require "json"
class AuthD::Token
include JSON::Serializable
property login : String
property uid : Int32
def initialize(@login, @uid)
end
def to_h
{
:login => login,
:uid => uid
}
end
def to_s(key)
JWT.encode to_h, key, JWT::Algorithm::HS256
end
def self.from_s(key, str)
payload, meta = JWT.decode str, key, JWT::Algorithm::HS256
self.new payload["login"].as_s, payload["uid"].as_i
end
end

View File

@ -0,0 +1,73 @@
require "json"
require "uuid"
class AuthD::User
include JSON::Serializable
enum PermissionLevel
None
Read
Edit
Admin
def to_json(o)
to_s.downcase.to_json o
end
end
class Contact
include JSON::Serializable
# the activation key is removed once the user is validated
property activation_key : String?
property email : String?
property phone : String?
def initialize(@email = nil, @phone = nil)
@activation_key = UUID.random.to_s
end
end
# Public.
property login : String
property uid : Int32
property profile : Hash(String, JSON::Any)?
# Private.
property contact : Contact
property password_hash : String
property password_renew_key : String?
# service => resource => permission level
property permissions : Hash(String, Hash(String, PermissionLevel))
property configuration : Hash(String, Hash(String, JSON::Any))
property date_last_connection : Time? = nil
property date_registration : Time? = nil
def to_token
Token.new @login, @uid
end
def initialize(@uid, @login, @password_hash)
@contact = Contact.new
@permissions = Hash(String, Hash(String, PermissionLevel)).new
@configuration = Hash(String, Hash(String, JSON::Any)).new
end
class Public
include JSON::Serializable
property login : String
property uid : Int32
property profile : Hash(String, JSON::Any)?
property date_registration : Time?
def initialize(@uid, @login, @profile, @date_registration)
end
end
def to_public : Public
Public.new @uid, @login, @profile, @date_registration
end
end

View File

@ -0,0 +1,34 @@
require "json"
require "jwt"
require "../src/main.cr"
require "baguette-crystal-base"
# Allows get configuration from a provided file.
# See Baguette::Configuration::Base.get
class Baguette::Configuration
class Auth < IPC
include YAML::Serializable
property login : String? = nil
property pass : String? = nil
property shared_key : String = "nico-nico-nii" # Default authd key, as per the specs. :eyes:
property shared_key_file : String? = nil
def initialize
end
end
end
# Token and user classes.
require "./authd/token.cr"
require "./authd/user.cr"
# Requests and responses.
require "./authd/exceptions"
# Requests and responses.
require "./network"
# Functions to request the authd server.
require "./authd/client.cr"

View File

@ -0,0 +1,13 @@
require "uuid"
require "option_parser"
require "openssl"
require "colorize"
require "jwt"
require "grok"
require "dodb"
require "baguette-crystal-base"
require "../src/main.cr"
require "./libauth.cr"
require "./authd.cr"

View File

@ -0,0 +1,22 @@
require "../src/main.cr"
require "../src/json"
class IPC::JSON
def handle(service : AuthD::Service)
raise "unimplemented"
end
end
module AuthD
class_getter requests = [] of IPC::JSON.class
class_getter responses = [] of IPC::JSON.class
end
class IPC
def schedule(fd, m : (AuthD::Request | AuthD::Response))
schedule fd, m.type.to_u8, m.to_json
end
end
require "./requests/*"
require "./responses/*"

View File

@ -0,0 +1,100 @@
class AuthD::Request
IPC::JSON.message AddUser, 1 do
# Only clients that have the right shared key will be allowed
# to create users.
property shared_key : String
property login : String
property password : String
property email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@shared_key, @login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service)
# No verification of the users' informations when an admin adds it.
# No mail address verification.
if @shared_key != authd.configuration.shared_key
return Response::Error.new "invalid authentication key"
end
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
password_hash = authd.hash_password @password
uid = authd.new_uid
user = User.new uid, @login, password_hash
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
# We consider adding the user as a registration
user.date_registration = Time.local
authd.users << user
authd.new_uid_commit uid
Response::UserAdded.new user.to_public
end
end
AuthD.requests << AddUser
IPC::JSON.message ModUser, 5 do
property shared_key : String
property user : Int32 | String
property password : String? = nil
property email : String? = nil
property phone : String? = nil
property avatar : String? = nil
def initialize(@shared_key, @user)
end
def handle(authd : AuthD::Service)
if @shared_key != authd.configuration.shared_key
return Response::Error.new "invalid authentication key"
end
uid_or_login = @user
user = if uid_or_login.is_a? Int32
authd.users_per_uid.get? uid_or_login.to_s
else
authd.users_per_login.get? uid_or_login
end
unless user
return Response::Error.new "user not found"
end
@password.try do |s|
user.password_hash = authd.hash_password s
end
@email.try do |email|
user.contact.email = email
end
@phone.try do |phone|
user.contact.phone = phone
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << ModUser
end

View File

@ -0,0 +1,46 @@
class AuthD::Request
IPC::JSON.message EditContacts, 16 do
property token : String
property email : String? = nil
property phone : String? = nil
def initialize(@token)
end
def handle(authd : AuthD::Service)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
if email = @email
# FIXME: This *should* require checking the new mail, with
# a new activation key and everything else.
user.contact.email = email
end
authd.users_per_uid.update user
Response::UserEdited.new user.uid
end
end
AuthD.requests << EditContacts
IPC::JSON.message GetContacts, 18 do
property token : String
def initialize(@token)
end
def handle(authd : AuthD::Service)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
_c = user.contact
Response::Contacts.new user.uid, _c.email, _c.phone
end
end
AuthD.requests << GetContacts
end

View File

@ -0,0 +1,69 @@
class AuthD::Request
IPC::JSON.message Delete, 17 do
# Deletion can be triggered by either an admin or the user.
property shared_key : String? = nil
property login : String? = nil
property password : String? = nil
property user : String | Int32
def initialize(@user, @login, @password)
end
def initialize(@user, @shared_key)
end
def handle(authd : AuthD::Service)
uid_or_login = @user
user_to_delete = if uid_or_login.is_a? Int32
authd.users_per_uid.get? uid_or_login.to_s
else
authd.users_per_login.get? uid_or_login
end
if user_to_delete.nil?
return Response::Error.new "invalid user"
end
# Either the request comes from an admin or the user.
# Shared key == admin, check the key.
if key = @shared_key
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
else
login = @login
pass = @password
if login.nil? || pass.nil?
return Response::Error.new "authentication failed (no shared key, no login)"
end
# authenticate the user
begin
user = authd.users_per_login.get login
rescue e : DODB::MissingEntry
return Response::Error.new "invalid credentials"
end
if user.nil?
return Response::Error.new "invalid credentials"
end
if user.password_hash != authd.hash_password pass
return Response::Error.new "invalid credentials"
end
# Is the user to delete the requesting user?
if user.uid != user_to_delete.uid
return Response::Error.new "invalid credentials"
end
end
# User or admin is now verified: let's proceed with the user deletion.
authd.users_per_login.delete user_to_delete.login
# TODO: better response
Response::User.new user_to_delete.to_public
end
end
AuthD.requests << Delete
end

View File

@ -0,0 +1,41 @@
class AuthD::Request
IPC::JSON.message ListUsers, 8 do
property token : String? = nil
property key : String? = nil
def initialize(@token, @key)
end
def handle(authd : AuthD::Service)
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
@token.try do |token|
user = authd.get_user_from_token token
return Response::Error.new "unauthorized (user not found from token)" unless user
# Test if the user is a moderator.
if permissions = user.permissions["authd"]?
if rights = permissions["*"]?
if rights >= User::PermissionLevel::Read
else
raise AdminAuthorizationException.new "unauthorized (insufficient rights on '*')"
end
else
raise AdminAuthorizationException.new "unauthorized (no rights on '*')"
end
else
raise AdminAuthorizationException.new "unauthorized (user not in authd group)"
end
end
@key.try do |key|
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
end
return Response::Error.new "unauthorized (no key nor token)" unless @key || @token
Response::UsersList.new authd.users.to_h.map &.[1].to_public
end
end
AuthD.requests << ListUsers
end

View File

@ -0,0 +1,122 @@
class AuthD::Request
IPC::JSON.message UpdatePassword, 7 do
property login : String
property old_password : String
property new_password : String
def initialize(@login, @old_password, @new_password)
end
def handle(authd : AuthD::Service)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@old_password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.password_hash = authd.hash_password @new_password
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << UpdatePassword
IPC::JSON.message PasswordRecovery, 11 do
property user : Int32 | String
property password_renew_key : String
property new_password : String
def initialize(@user, @password_renew_key, @new_password)
end
def handle(authd : AuthD::Service)
uid_or_login = @user
user = if uid_or_login.is_a? Int32
authd.users_per_uid.get? uid_or_login.to_s
else
authd.users_per_login.get? uid_or_login
end
if user.nil?
return Response::Error.new "user not found"
end
if user.password_renew_key == @password_renew_key
user.password_hash = authd.hash_password @new_password
else
return Response::Error.new "renew key not valid"
end
user.password_renew_key = nil
authd.users_per_uid.update user.uid.to_s, user
Response::PasswordRecovered.new user.to_public
end
end
AuthD.requests << PasswordRecovery
IPC::JSON.message AskPasswordRecovery, 12 do
property user : Int32 | String
property email : String
def initialize(@user, @email)
end
def handle(authd : AuthD::Service)
uid_or_login = @user
user = if uid_or_login.is_a? Int32
authd.users_per_uid.get? uid_or_login.to_s
else
authd.users_per_login.get? uid_or_login
end
if user.nil?
return Response::Error.new "no such user"
end
if user.contact.email != @email
# Same error as when users are not found.
return Response::Error.new "no such user"
end
user.password_renew_key = UUID.random.to_s
authd.users_per_uid.update user.uid.to_s, user
# Once the user is created and stored, we try to contact him
if authd.configuration.print_password_recovery_parameters
pp! user.login,
user.contact.email.not_nil!,
user.password_renew_key.not_nil!
end
mailer_exe = authd.configuration.mailer_exe
template_name = authd.configuration.recovery_template
u_login = user.login
u_email = user.contact.email.not_nil!
u_token = user.password_renew_key.not_nil!
# Once the user is created and stored, we try to contact him.
unless Process.run(mailer_exe,
# PARAMETERS
[ "send", template_name, u_email ],
# ENV
{ "LOGIN" => u_login, "TOKEN" => u_token },
true # clear environment
).success?
raise "cannot contact user #{u_login} address #{u_email}"
end
Response::PasswordRecoverySent.new user.to_public
end
end
AuthD.requests << AskPasswordRecovery
end

View File

@ -0,0 +1,113 @@
class AuthD::Request
IPC::JSON.message CheckPermission, 9 do
property shared_key : String? = nil
property token : String? = nil
property user : Int32 | String
property service : String
property resource : String
def initialize(@shared_key, @user, @service, @resource)
end
def handle(authd : AuthD::Service)
authorized = false
if key = @shared_key
if key == authd.configuration.shared_key
authorized = true
else
return Response::Error.new "invalid key provided"
end
end
if token = @token
user = authd.get_user_from_token token
if user.nil?
return Response::Error.new "token does not match user"
end
if user.login != @user && user.uid != @user
return Response::Error.new "token does not match user"
end
authorized = true
end
unless authorized
return Response::Error.new "unauthorized"
end
user = case u = @user
when .is_a? Int32
authd.users_per_uid.get? u.to_s
else
authd.users_per_login.get? u
end
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
resource_permissions = service_permissions[@resource]?
if resource_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
return Response::PermissionCheck.new service, @resource, user.uid, resource_permissions
end
end
AuthD.requests << CheckPermission
IPC::JSON.message SetPermission, 10 do
property shared_key : String
property user : Int32 | String
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@shared_key, @user, @service, @resource, @permission)
end
def handle(authd : AuthD::Service)
unless @shared_key == authd.configuration.shared_key
return Response::Error.new "unauthorized"
end
user = authd.users_per_uid.get? @user.to_s
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
service_permissions = Hash(String, User::PermissionLevel).new
user.permissions[service] = service_permissions
end
if @permission.none?
service_permissions.delete @resource
else
service_permissions[@resource] = @permission
end
authd.users_per_uid.update user.uid.to_s, user
Response::PermissionSet.new user.uid, service, @resource, @permission
end
end
AuthD.requests << SetPermission
end

View File

@ -0,0 +1,93 @@
class AuthD::Request
IPC::JSON.message EditProfile, 14 do
property token : String
property new_profile : Hash(String, JSON::Any)
def initialize(@token, @new_profile)
end
def handle(authd : AuthD::Service)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
new_profile = @new_profile
profile = user.profile || Hash(String, JSON::Any).new
authd.configuration.read_only_profile_keys.each do |key|
if new_profile[key]? != profile[key]?
return Response::Error.new "tried to edit read only key"
end
end
user.profile = new_profile
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << EditProfile
# Same as above, but 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

@ -0,0 +1,84 @@
class AuthD::Request
IPC::JSON.message Register, 6 do
property login : String
property password : String
property email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service)
if ! authd.configuration.registrations
return Response::Error.new "registrations not allowed"
end
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
if ! @email.nil?
# Test on the email address format.
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
result = grok.parse @email.not_nil!
email = result["email"]?
if email.nil?
return Response::Error.new "invalid email format"
end
end
# In this case we should not accept its registration.
if @password.size < 4
return Response::Error.new "password too short"
end
uid = authd.new_uid
password = authd.hash_password @password
user = User.new uid, @login, password
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
user.date_registration = Time.local
begin
mailer_exe = authd.configuration.mailer_exe
template_name = authd.configuration.activation_template
u_login = user.login
u_email = user.contact.email.not_nil!
u_activation_key = user.contact.activation_key.not_nil!
# Once the user is created and stored, we try to contact him.
unless Process.run(mailer_exe,
# PARAMETERS
[ "send", template_name, u_email ],
# ENV
{ "LOGIN" => u_login, "TOKEN" => u_activation_key },
true # clear environment
).success?
raise "cannot contact user #{u_login} address #{u_email}"
end
rescue e
Baguette::Log.error "mailer: #{e}"
return Response::Error.new "cannot contact the user (not registered)"
end
# add the user only if we were able to send the confirmation mail
authd.users << user
authd.new_uid_commit uid
Response::UserAdded.new user.to_public
end
end
AuthD.requests << Register
end

View File

@ -0,0 +1,34 @@
class AuthD::Request
IPC::JSON.message SearchUser, 13 do
property user : String
def initialize(@user)
end
def handle(authd : AuthD::Service)
pattern = Regex.new @user, Regex::Options::IGNORE_CASE
matching_users = Array(AuthD::User::Public).new
users = authd.users.to_a
users.each do |u|
if pattern =~ u.login || u.profile.try do |profile|
full_name = profile["full_name"]?
if full_name.nil?
false
else
pattern =~ full_name.as_s
end
end
Baguette::Log.debug "#{u.login} matches #{pattern}"
matching_users << u.to_public
else
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
end
end
Response::MatchingUsers.new matching_users
end
end
AuthD.requests << SearchUser
end

View File

@ -0,0 +1,34 @@
class AuthD::Request
IPC::JSON.message GetToken, 0 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service)
begin
user = authd.users_per_login.get @login
rescue e : DODB::MissingEntry
return Response::Error.new "invalid credentials"
end
if user.nil?
return Response::Error.new "invalid credentials"
end
if user.password_hash != authd.hash_password @password
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
token = user.to_token
# change the date of the last connection
authd.users_per_uid.update user.uid.to_s, user
Response::Token.new (token.to_s authd.configuration.shared_key), user.uid
end
end
AuthD.requests << GetToken
end

View File

@ -0,0 +1,84 @@
class AuthD::Request
IPC::JSON.message ValidateUser, 2 do
property login : String
property activation_key : String
def initialize(@login, @activation_key)
end
def handle(authd : AuthD::Service)
user = authd.users_per_login.get? @login
if user.nil?
return Response::Error.new "user not found"
end
if user.contact.activation_key.nil?
return Response::Error.new "user already validated"
end
# remove the user contact activation key: the email is validated
if user.contact.activation_key == @activation_key
user.contact.activation_key = nil
else
return Response::Error.new "wrong activation key"
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserValidated.new user.to_public
end
end
AuthD.requests << ValidateUser
IPC::JSON.message GetUser, 3 do
property user : Int32 | String
def initialize(@user)
end
def handle(authd : AuthD::Service)
uid_or_login = @user
user = if uid_or_login.is_a? Int32
authd.users_per_uid.get? uid_or_login.to_s
else
authd.users_per_login.get? uid_or_login
end
if user.nil?
return Response::Error.new "user not found"
end
Response::User.new user.to_public
end
end
AuthD.requests << GetUser
IPC::JSON.message GetUserByCredentials, 4 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
# change the date of the last connection
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << GetUserByCredentials
end

View File

@ -0,0 +1,10 @@
class AuthD::Response
IPC::JSON.message Contacts, 12 do
property user : Int32
property email : String? = nil
property phone : String? = nil
def initialize(@user, @email, @phone)
end
end
AuthD.responses << Contacts
end

View File

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

View File

@ -0,0 +1,15 @@
class AuthD::Response
IPC::JSON.message PasswordRecoverySent, 9 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
AuthD.responses << PasswordRecoverySent
IPC::JSON.message PasswordRecovered, 10 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
AuthD.responses << PasswordRecovered
end

View File

@ -0,0 +1,21 @@
class AuthD::Response
IPC::JSON.message PermissionCheck, 7 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@service, @resource, @user, @permission)
end
end
AuthD.responses << PermissionCheck
IPC::JSON.message PermissionSet, 8 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@user, @service, @resource, @permission)
end
end
AuthD.responses << PermissionSet
end

View File

@ -0,0 +1,9 @@
class AuthD::Response
IPC::JSON.message Token, 1 do
property uid : Int32
property token : String
def initialize(@token, @uid)
end
end
AuthD.responses << Token
end

View File

@ -0,0 +1,43 @@
class AuthD::Response
IPC::JSON.message User, 2 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
AuthD.responses << User
IPC::JSON.message UserAdded, 3 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
AuthD.responses << UserAdded
IPC::JSON.message UserEdited, 4 do
property uid : Int32
def initialize(@uid)
end
end
AuthD.responses << UserEdited
IPC::JSON.message UserValidated, 5 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
AuthD.responses << UserValidated
IPC::JSON.message UsersList, 6 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
AuthD.responses << UsersList
IPC::JSON.message MatchingUsers, 11 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
AuthD.responses << MatchingUsers
end

View File

@ -0,0 +1,46 @@
LDPATH ?= /tmp/libipc/zig-impl/build
SRC ?= ./bin/some-crystal-app
VG_OPTS = --leak-check=full -v
VG_OPTS += --show-leak-kinds=all
VG_OPTS += --suppressions=valgrind.suppressions
VG_OPTS += --gen-suppressions=all
build:
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build
valgrind:
LD_LIBRARY_PATH=$(LDPATH) valgrind $(VG_OPTS) $(SRC)
run:
LD_LIBRARY_PATH=$(LDPATH) $(SRC)
build-pongd:
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build pongd
run-pongd:
LD_LIBRARY_PATH=$(LDPATH) ./bin/pongd
build-authd:
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build authd
run-authd:
@-rm /tmp/libipc-run/auth 2>/dev/null || true
# LD_LIBRARY_PATH=$(LDPATH) ./bin/authd -R -E
LD_LIBRARY_PATH=$(LDPATH) ./bin/authd --allow-registrations --require-email $(PARAMS)
build-authc:
CRYSTAL_LIBRARY_PATH=$(LDPATH) shards build authc
run-authc:
LD_LIBRARY_PATH=$(LDPATH) ./bin/authc
run-test:
crystal run src/libauth.cr
doc:
crystal docs
ACCESS_LOGS ?= ./access.log
serve-doc:
darkhttpd docs/ --addr 127.0.0.1 --port 35001 --log $(ACCESS_LOGS)

View File

@ -0,0 +1,38 @@
name: some-crystal-app
version: 0.1.0
authors:
- Philippe Pittoli <karchnu@karchnu.fr>
targets:
main:
main: src/main.cr
pongd:
main: tests/pongd.cr
authd:
main: authd/main.cr
authc:
main: authc/authc.cr
crystal: 1.7.1
dependencies:
grok:
github: spinscale/grok.cr
passwd:
git: https://git.baguette.netlib.re/Baguette/passwd.cr
branch: master
jwt:
github: crystal-community/jwt
branch: master
baguette-crystal-base:
git: https://git.baguette.netlib.re/Baguette/baguette-crystal-base
branch: master
dodb:
git: https://git.baguette.netlib.re/Baguette/dodb.cr
branch: master
cbor:
git: https://git.baguette.netlib.re/Baguette/crystal-cbor
branch: master
license: MIT

View File

@ -0,0 +1,38 @@
@[Link("ipc")]
lib LibIPC
enum EventType
Error # Self explanatory.
Connection # New user.
Disconnection # User disconnected.
MessageRx # Message received.
MessageTx # Message sent.
Timer # Timeout in the poll(2) function.
External # Message received from a non IPC socket.
SwitchRx # Switch subsystem: message received.
SwitchTx # Switch subsystem: message send.
end
fun init = ipc_context_init (Void**) : LibC::Int
fun deinit = ipc_context_deinit (Void**) : Void
fun service_init = ipc_service_init (Void*, LibC::Int*, LibC::Char*, LibC::UInt16T) : LibC::Int
fun connect_service = ipc_connect_service(Void*, LibC::Int*, LibC::Char*, LibC::UInt16T) : LibC::Int
# Context EventType index fd buffer buflen
fun wait = ipc_wait_event(Void*, UInt8*, LibC::UInt64T*, LibC::Int*, UInt8*, LibC::UInt64T*) : LibC::Int
# Sending a message NOW.
# WARNING: doesn't wait the fd to become available.
fun write = ipc_write(Void*, LibC::Int, UInt8*, LibC::UInt64T) : LibC::Int
# Sending a message (will wait the fd to become available for IO operations).
fun schedule = ipc_schedule(Void*, LibC::Int, UInt8*, LibC::UInt64T) : LibC::Int
fun read = ipc_read_fd (Void*, LibC::Int, UInt8*, LibC::UInt64T*);
fun timer = ipc_context_timer (Void*, LibC::Int)
# Closing connections.
fun close = ipc_close(Void*, LibC::UInt64T) : LibC::Int
fun close_fd = ipc_close_fd(Void*, LibC::Int) : LibC::Int
fun close_all = ipc_close_all(Void*) : LibC::Int
end

View File

@ -0,0 +1,59 @@
require "cbor"
require "./main.cr"
# IPC::CBOR is the root class for all exchanged messages (using CBOR).
# IPC::CBOR inherited classes have a common 'type' class attribute,
# which enables to find the right IPC::CBOR+ class given a TypedMessage's type.
# All transfered messages are typed messages.
# TypedMessage = u8 type (= IPC::CBOR+ class type) + CBOR content.
# 'CBOR content' being a serialized IPC::CBOR+ class.
# Conventionally, IPC::CBOR+ classes have a 'handle' method to process incoming messages.
class IPC::CBOR
include ::CBOR::Serializable
class_property type = -1
property id : ::CBOR::Any?
def type
@@type
end
macro message(id, type, &block)
class {{id}} < ::IPC::CBOR
include ::CBOR::Serializable
@@type = {{type}}
{{yield}}
end
end
end
class IPC
# Schedule messages contained into IPC::CBOR+.
def schedule(fd : Int32, message : IPC::CBOR)
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_cbor
schedule fd, typed_msg
end
def write(fd : Int32, message : IPC::CBOR)
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_cbor
write fd, typed_msg
end
end
# CAUTION: only use this method on an Array(IPC::CBOR.class).
class Array(T)
def parse_ipc_cbor(message : IPCMessage::TypedMessage) : IPC::CBOR?
message_type = find &.type.==(message.type)
payload = message.payload
if message_type.nil?
raise "invalid message type (#{message.type})"
end
message_type.from_cbor payload
end
end

View File

@ -0,0 +1,134 @@
class IPC
# Reception buffer with a big capacity.
# Allocated once.
@reception_buffer = Array(UInt8).new 2_000_000
@reception_buffer_len : LibC::UInt64T = 2_000_000
class Event
property type : LibIPC::EventType
property index : LibC::UInt64T
property fd : Int32
property message : Array(UInt8)? = nil
def initialize(t : UInt8, @index, @fd, buffer, buflen)
@type = LibIPC::EventType.new t
if buflen > 0
# Array -> Pointer -> Slice -> Array
@message = buffer.to_unsafe.to_slice(buflen).to_a
end
end
end
def initialize()
@context = Pointer(Void).null
LibIPC.init(pointerof(@context))
at_exit { deinit }
end
# Closes all connections then remove the structure from memory.
def deinit
LibIPC.deinit(pointerof(@context))
end
def service_init(name : String) : Int
fd = uninitialized Int32
if LibIPC.service_init(@context, pointerof(fd), name, name.size) != 0
raise "oh noes, 'service_init' iz brkn"
end
fd
end
def connect(name : String) : Int32
fd = uninitialized Int32
if LibIPC.connect_service(@context, pointerof(fd), name, name.size) != 0
raise "oh noes, 'connect_service' iz brkn"
end
fd
end
def timer(value : LibC::Int)
LibIPC.timer(@context, value)
end
def write(fd : Int, string : String)
self.write(fd, string.to_unsafe, string.size.to_u64)
end
def write(fd : Int, buffer : UInt8*, buflen : UInt64)
if LibIPC.write(@context, fd, buffer, buflen) != 0
raise "oh noes, 'write' iz brkn"
end
end
def write(fd : Int32, buffer : Bytes)
self.write(fd, buffer.to_unsafe, buffer.size.to_u64)
end
def read(fd : Int32) : Slice(UInt8)
buffer : Bytes = Bytes.new 2000000
size = buffer.size.to_u64
LibIPC.read(@context, fd, buffer.to_unsafe, pointerof(size))
buffer[0..size - 1]
end
def schedule(fd : Int32, string : String)
self.schedule(fd, string.to_unsafe, string.size.to_u64)
end
def schedule(fd : Int32, buffer : Array(UInt8), buflen : Int32)
self.schedule(fd, buffer.to_unsafe, buflen.to_u64)
end
def schedule(fd : Int32, buffer : Bytes)
self.schedule(fd, buffer.to_unsafe, buffer.size.to_u64)
end
def schedule(fd : Int32, buffer : UInt8*, buflen : UInt64)
if LibIPC.schedule(@context, fd, buffer, buflen) != 0
raise "oh noes, 'schedule' iz brkn"
end
end
def close(index : LibC::UInt64T)
if LibIPC.close(@context, index) != 0
raise "Oh noes, 'close index' iz brkn"
end
end
def close(fd : LibC::Int)
if LibIPC.close_fd(@context, fd) != 0
raise "Oh noes, 'close fd' iz brkn"
end
end
def close
if LibIPC.close_all(@context) != 0
raise "Oh noes, 'close all' iz brkn"
end
end
def wait() : IPC::Event
eventtype : UInt8 = 0
index : LibC::UInt64T = 0
fd : Int32 = 0
buflen = @reception_buffer_len
ret = LibIPC.wait(@context,
pointerof(eventtype),
pointerof(index),
pointerof(fd),
@reception_buffer.to_unsafe,
pointerof(buflen))
if ret != 0
raise "Oh noes, 'wait' iz brkn"
end
Event.new(eventtype, index, fd, @reception_buffer, buflen)
end
def loop(&block : Proc(IPC::Event, Nil))
::loop do
yield wait
end
end
end

View File

@ -0,0 +1,58 @@
require "json"
# IPC::JSON is the root class for all exchanged messages (using JSON).
# IPC::JSON inherited classes have a common 'type' class attribute,
# which enables to find the right IPC::JSON+ class given a TypedMessage's type.
# All transfered messages are typed messages.
# TypedMessage = u8 type (= IPC::JSON+ class type) + JSON content.
# 'JSON content' being a serialized IPC::JSON+ class.
# Conventionally, IPC::JSON+ classes have a 'handle' method to process incoming messages.
class IPC::JSON
include ::JSON::Serializable
class_property type = -1
property id : ::JSON::Any?
def type
@@type
end
macro message(id, type, &block)
class {{id}} < ::IPC::JSON
include ::JSON::Serializable
@@type = {{type}}
{{yield}}
end
end
end
class IPC
# Schedule messages contained into IPC::JSON+.
def schedule(fd : Int32, message : IPC::JSON)
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_json
schedule fd, typed_msg
end
def write(fd : Int32, message : IPC::JSON)
typed_msg = IPCMessage::TypedMessage.new message.type.to_u8, message.to_json
write fd, typed_msg
end
end
# CAUTION: only use this method on an Array(IPC::JSON.class).
class Array(T)
def parse_ipc_json(message : IPCMessage::TypedMessage) : IPC::JSON?
message_type = find &.type.==(message.type)
payload = String.new message.payload
if message_type.nil?
raise "invalid message type (#{message.type})"
end
message_type.from_json payload
end
end

View File

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

View File

@ -0,0 +1,69 @@
# TODO: tests.
# Serialization (and deserialization) doesn't refer to IPC format.
# IPC serialization format: 'length + value'
# IPCMessage serialization: 'value'
# 'Value' is:
# - simply the message payload for UntypedMessage
# - type (u8) + payload for TypedMessage
module IPCMessage
class UntypedMessage
property payload : Bytes
def initialize(string : String)
@payload = Bytes.new string.to_unsafe, string.size
end
def initialize(@payload)
end
def self.deserialize(payload : Bytes) : UntypedMessage
IPCMessage::UntypedMessage.new payload
end
def serialize
@payload
end
end
# WARNING: you can only have up to 256 types.
class TypedMessage < UntypedMessage
property type : UInt8? = nil
def initialize(@type, string : String)
super string
end
def initialize(@type, payload)
super payload
end
def initialize(payload)
super payload
end
def self.deserialize(bytes : Bytes) : TypedMessage?
if bytes.size == 0
nil
else
type = bytes[0]
IPCMessage::TypedMessage.new type, bytes[1..]
end
end
def serialize
bytes = Bytes.new (1 + @payload.size)
type = @type
bytes[0] = type.nil? ? 0.to_u8 : type
bytes[1..].copy_from @payload
bytes
end
end
end
# Send both typed and untyped messages.
class IPC
def schedule(fd : Int32, m : (IPCMessage::TypedMessage | IPCMessage::UntypedMessage))
payload = m.serialize
schedule fd, payload
end
def write(fd : Int32, m : (IPCMessage::TypedMessage | IPCMessage::UntypedMessage))
payload = m.serialize
write fd, payload
end
end

View File

@ -0,0 +1,38 @@
require "../src/main.cr"
# In 5 messages: quit
count = 5
ipc = IPC.new
fd = ipc.service_init("pong")
ipc.loop do |event|
case event.type
when LibIPC::EventType::MessageRx
m = event.message
if m.nil?
puts "No message"
else
received = String.new(m.to_unsafe, m.size)
pp! received
ipc.schedule event.fd, m, m.size
end
when LibIPC::EventType::MessageTx
puts "A message has been sent"
count -= 1
if count == 0
exit
end
when LibIPC::EventType::Connection
puts "A client just connected #JOY"
when LibIPC::EventType::Disconnection
puts "A client just disconnected #SAD"
else
puts "Unexpected: #{event.type}"
exit 1
end
end

View File

@ -0,0 +1,46 @@
require "../main.cr"
def test_high_level
ipc = IPC.new
fd = ipc.connect("pong")
ipc.write(fd, "hello this is some value")
event = ipc.wait()
m = event.message
if m.nil?
puts "No message"
else
pp! String.new(m.to_unsafe, m.size)
end
end
def test_loop
ipc = IPC.new
fd = ipc.connect("pong")
ipc.schedule(fd, "hello this is some value")
ipc.loop do |event|
case event.type
when LibIPC::EventType::MessageRx
m = event.message
if m.nil?
puts "No message"
else
pp! String.new(m.to_unsafe, m.size)
end
exit 0
when LibIPC::EventType::MessageTx
puts "A message has been sent"
else
puts "Unexpected: #{event.type}"
exit 1
end
end
end
# TODO: Write documentation for `Some::Crystal::App`
module Some::Crystal::App
VERSION = "0.1.0"
test_high_level
test_loop
end

View File

@ -0,0 +1,229 @@
{
<insert_a_suppression_name_here>
Memcheck:Addr1
obj:/usr/lib/libgc.so.1.5.0
fun:GC_init_linux_data_start
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_push_all_eager
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_push_all_eager
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:memalign
fun:posix_memalign
obj:/tmp/libipc/zig-impl/build/libipc.so.0.1.0
fun:*Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:*Signal::setup_segfault_handler:(Int32 | Nil)
fun:*Exception::CallStack::setup_crash_handler:(Int32 | Nil)
fun:__crystal_main
fun:*Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_push_all_eager
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Addr1
obj:/usr/lib/libgc.so.1.5.0
fun:GC_init_linux_data_start
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_push_all_eager
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_mark_from
obj:/usr/lib/libgc.so.1.5.0
fun:GC_mark_some
obj:/usr/lib/libgc.so.1.5.0
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}

38
zig-impl/drop/Makefile Normal file
View File

@ -0,0 +1,38 @@
ZIGC=zig
CC=gcc
CFLAGS=-Wall -Wextra
LDFLAGS=-I build/ -L build/ -lipc
all: zigcompilation compilation
ifeq ($(SRC),)
test-src:
@echo SRC must be set via command line.
@exit 1
else
test-src:
endif
list-obj-files: test-src
@# List all .o included in a .a archive.
ar t $(SRC)
list-symbols: test-src
@# List all symbols in a .so.
nm -D $(SRC)
list-symbols-alt: test-src
@# Alternative: grep .text section in an objdump output.
objdump -T $(SRC) | grep text
zigcompilation: build.zig src/*.zig
$(ZIGC) build
compilation: src/main.c
@echo the following compilation will produce errors despite actually working
$(CC) -o main build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
run:
LD_LIBRARY_PATH=build ./main
valgrind:
LD_LIBRARY_PATH=build valgrind --suppressions=./suppress-stuff.suppr --gen-suppressions=all -v --leak-check=full ./main

25
zig-impl/drop/build.zig Normal file
View File

@ -0,0 +1,25 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const lib = b.addStaticLibrary("ipc", "src/main.zig");
lib.setOutputDir("build");
lib.linkLibC();
lib.setBuildMode(mode);
lib.install();
const solib = b.addSharedLibrary("ipc", "src/main.zig", b.version(0, 0, 1));
solib.setOutputDir("build");
solib.linkLibC();
solib.setBuildMode(mode);
solib.install();
const main_tests = b.addTest("src/main.zig");
main_tests.setBuildMode(mode);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
}

24
zig-impl/drop/src/main.c Normal file
View File

@ -0,0 +1,24 @@
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int ret = 0;
printf ("Init context.\n");
void *ctx = NULL;
ret = ipc_context_init (&ctx);
if (ret != 0) {
printf ("Cannot init context.\n");
return 1;
}
// TODO: do stuff
printf ("Deinit context\n");
ipc_context_deinit (ctx);
printf ("Context deinit.\n");
free(ctx);
printf ("Context completely freed.\n");
return 0;
}

View File

@ -0,0 +1,33 @@
const std = @import("std");
const fmt = std.fmt;
const print = std.debug.print;
pub const Context = struct {
rundir: [] u8,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) !Self {
var rundir = try allocator.dupeZ(u8, "/tmp/libipc-run/");
return Self { .rundir = rundir, .allocator = allocator };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.rundir);
}
};
export fn ipc_context_init (ptr: **Context) callconv(.C) i32 {
ptr.* = std.heap.c_allocator.create(Context) catch return 1;
ptr.*.* = Context.init(std.heap.c_allocator) catch |err| {
print ("libipc: error while init context: {}\n", .{err});
return 1;
};
return 0;
}
export fn ipc_context_deinit (ctx: *Context) callconv(.C) void {
ctx.deinit();
}

48
zig-impl/libipc.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef LIBIPC
#define LIBIPC
#include <stdint.h>
enum event_types {
ERROR = 0 // A problem occured.
, CONNECTION = 1 // New user.
, DISCONNECTION = 2 // User disconnected.
, MESSAGE_RX = 3 // New message.
, MESSAGE_TX = 4 // Message sent.
, TIMER = 5 // Timeout in the poll(2) function.
, EXTERNAL = 6 // Message received from a non IPC socket.
, SWITCH_RX = 7 // Message received from a switched FD.
, SWITCH_TX = 8 // Message sent to a switched fd.
};
// Return type of callback functions when switching.
enum cb_event_types {
CB_NO_ERROR = 0 // No error. A message was generated.
, CB_ERROR = 1 // Generic error.
, CB_FD_CLOSING = 2 // The fd is closing.
, CB_IGNORE = 3 // The message should be ignored (protocol specific).
};
int ipc_context_init (void** ptr);
int ipc_service_init (void* ctx, int* servicefd, const char* service_name, uint16_t service_name_len);
int ipc_connect_service (void* ctx, int* servicefd, const char* service_name, uint16_t service_name_len);
void ipc_context_deinit (void** ctx);
int ipc_write (void* ctx, int servicefd, char* mcontent, uint32_t mlen);
int ipc_schedule (void* ctx, int servicefd, const char* mcontent, uint32_t mlen);
int ipc_read_fd (void* ctx, int fd, char* buffer, size_t* buflen);
int ipc_read (void* ctx, size_t index, char* buffer, size_t* buflen);
int ipc_wait_event(void* ctx, char* t, size_t* index, int* originfd, char* buffer, size_t* buflen);
void ipc_context_timer (void* ctx, int timer);
int ipc_close_fd (void* ctx, int fd);
int ipc_close (void* ctx, size_t index);
int ipc_close_all (void* ctx);
// Switch functions (for "protocol" services, such as TCPd).
int ipc_add_external (void* ctx, int newfd);
int ipc_add_switch (void* ctx, int fd1, int fd2);
int ipc_set_switch_callbacks (void* ctx, int fd
, enum cb_event_types (*in (int orig, const char *payload, uint32_t *mlen))
, enum cb_event_types (*out(int dest, char *payload, uint32_t mlen)));
#endif

47
zig-impl/makefile Normal file
View File

@ -0,0 +1,47 @@
all:
ZIGMAKEDOC = -femit-docs -fno-emit-bin
ZIGOPTIM ?= Debug
# Linking against libc is almost mandatory, C allocator is used
# for switching (default reception and emission functions).
ZIGBUSEOPTS ?= -O$(ZIGOPTIM) -freference-trace -lc
ZIGUSROPTS ?=
ZIGC ?= zig
ZIGOPTS ?= $(ZIGBUSEOPTS) $(ZIGUSROPTS)
# Debug with valgrind.
ifdef VG_SUPPRESS_WARNINGS
VALGRIND_SUPPRESS_WARNINGS ?= --suppressions=./valgrind.suppr
endif
ifdef VG_GENERATE_SUPPRESSION
VALGRIND_GEN_SUPPRESSION ?= --gen-suppressions=all
endif
VALGRIND_OPTS=-v --leak-check=full --track-origins=yes
ifdef USE_VALGRIND
VALGRIND ?= valgrind $(VALGRIND_SUPPRESS_WARNINGS) \
$(VALGRIND_GEN_SUPPRESSION) \
$(VALGRIND_OPTS)
endif
# Optional parameters (copied here to help with autocompletion).
VG_SUPPRESS_WARNINGS ?=
VG_GENERATE_SUPPRESSION ?=
USE_VALGRIND ?=
TO_CLEAN != ls misc/*.zig | sed 's/.zig$\//' | sed 's_misc/__'
TO_CLEAN += bin/ipcd bin/tcpd bin/pong bin/pongd
TO_CLEAN += bin/*.o
clean:
@-rm $(TO_CLEAN) 2>/dev/null
mrproper: clean
@-rm -r docs build zig-cache zig-out 2>/dev/null
doc: src/ipcd.zig
$(ZIGC) build-exe $(ZIGOPTS) $(ZIGMAKEDOC) $^
ACCESS_LOGS ?= ./access.log
serve-doc:
darkhttpd docs/ --addr 127.0.0.1 --port 35000 --log $(ACCESS_LOGS)
# You can add your specific instructions there.
-include makefile.user

92
zig-impl/makefile.user Normal file
View File

@ -0,0 +1,92 @@
CC=gcc
CFLAGS=-Wall -Wextra #-Wno-implicit-function-declaration
LDFLAGS=-I build/ -L build/ -lipc
bin: build.zig apps/*.zig src/*.zig
$(ZIGC) build
lib: build.zig src/*.zig
$(ZIGC) build
stop-ipcd:
-pkill -1 ipcd
stop-tcpd:
-pkill -1 tcpd
run-ipcd:
-rm /tmp/libipc-run/ipc 2>/dev/null || true
$(VALGRIND) ./bin/ipcd
run-pongd:
-rm /tmp/libipc-run/pong 2>/dev/null || true
$(VALGRIND) ./bin/pongd
run-tcpd:
@-rm /tmp/libipc-run/tcp 2>/dev/null || true
$(VALGRIND) ./bin/tcpd
TCP_SERVICE_ALT ?= 127.0.0.1:9898
run-tcpd-alternative:
-rm /tmp/libipc-run/tcpdup 2>/dev/null || true
IPC_SERVICE_NAME=tcpdup ADDRESS=$(TCP_SERVICE_ALT) $(VALGRIND) ./tcpd
SERVICE_NAME ?= p
IPC_NETWORK ?= p unix://pong
run-pong:
@#Force pong to contact IPCd.
@#SERVICE is the service to contact and IPC_NETWORK is the IPCd
@#configuration to translate "p" into "pong" (still using UNIX
@#sockets on the same computer).
SERVICE="$(SERVICE_NAME)" IPC_NETWORK="$(IPC_NETWORK)" $(VALGRIND) ./bin/pong
run-pong-test-tcpd:
SERVICE="pong" IPC_NETWORK="pong tcp://$(TCP_SERVICE_ALT)/pong" $(VALGRIND) ./pong
ifeq ($(SRC),)
test-src:
@echo SRC must be set via command line.
@exit 1
else
test-src:
endif
comp: bin test-bindings-pong test-bindings-pongd
list-obj-files: test-src
@# List all .o included in a .a archive.
ar t $(SRC)
list-symbols: test-src
@# List all symbols in a .so.
nm -D $(SRC)
list-symbols-alt: test-src
@# Alternative: grep .text section in an objdump output.
objdump -T $(SRC) | grep text
bindings-compile-pong: test-bindings/pong.c
@-mkdir bin-bindings 2>/dev/null || true
$(CC) -o bin-bindings/pong build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
bindings-compile-pongd: test-bindings/pongd.c
@-mkdir bin-bindings 2>/dev/null || true
$(CC) -o bin-bindings/pongd build/libipc.so $(CFLAGS) $^ $(LDFLAGS)
bindings-test-pong:
LD_LIBRARY_PATH=build/ $(VALGRIND) ./bin-bindings/pong
bindings-test-pongd:
-rm /tmp/libipc-run/pong 2>/dev/null || true
LD_LIBRARY_PATH=build/ $(VALGRIND) ./bin-bindings/pongd
WS_SERVICE ?= 127.0.0.1:8080
TCP_SERVICE ?= 127.0.0.1:9000
init-websocket-tcpd:
@# '-b' binary, '-E' quit on end-of-file, 'ws-l' websocket URI to listen
@# each connection is redirected to last parameter
websocat -b -E ws-l:$(WS_SERVICE) tcp:$(TCP_SERVICE)
init-websocket-client:
@# websocat -b -E tcp-l:127.0.0.1:9000 ws://127.0.0.1:9999
websocat -b -E ws://$(WS_SERVICE)
.PHONY: bin lib

63
zig-impl/misc/cmsghdr.zig Normal file
View File

@ -0,0 +1,63 @@
const std = @import("std");
/// TODO: move this to std
/// This definition enables the use of Zig types with a cmsghdr structure.
/// The oddity of this layout is that the data must be aligned to @sizeOf(usize)
/// rather than its natural alignment.
pub fn Cmsghdr(comptime T: type) type {
const Header = extern struct {
len: usize,
level: c_int,
@"type": c_int,
};
const data_align = @sizeOf(usize);
const data_offset = std.mem.alignForward(@sizeOf(Header), data_align);
return extern struct {
const Self = @This();
bytes: [data_offset + @sizeOf(T)]u8 align(@alignOf(Header)),
pub fn init(args: struct {
level: c_int,
@"type": c_int,
data: T,
}) Self {
var self: Self = undefined;
self.headerPtr().* = .{
.len = data_offset + @sizeOf(T),
.level = args.level,
.@"type" = args.@"type",
};
self.dataPtr().* = args.data;
return self;
}
// TODO: include this version if we submit a PR to add this to std
pub fn initNoData(args: struct {
level: c_int,
@"type": c_int,
}) Self {
var self: Self = undefined;
self.headerPtr().* = .{
.len = data_offset + @sizeOf(T),
.level = args.level,
.@"type" = args.@"type",
};
return self;
}
pub fn headerPtr(self: *Self) *Header {
return @ptrCast(*Header, self);
}
pub fn dataPtr(self: *Self) *align(data_align) T {
return @ptrCast(*T, self.bytes[data_offset..]);
}
};
}
test {
std.testing.refAllDecls(Cmsghdr([3]std.os.fd_t));
}

View File

@ -0,0 +1,51 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const mem = std.mem;
const print = std.debug.print;
fn print_current_dir() !void {
const buffer_size = 10000;
var buffer: [buffer_size]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
var allocator = fba.allocator();
print("Print current directory\n", .{});
var current_dir = try std.fs.cwd().openIterableDir(".", .{});
var walker = try current_dir.walk(allocator);
while (try walker.next()) |entry| {
print("content: {s}\n", .{entry.basename});
}
walker.deinit();
print("DONE\n", .{});
}
fn add_line_in_file() !void {
var cwd = std.fs.cwd();
var f = try cwd.createFile("some-file.log", .{.read = true});
// defer f.close(); // closed in add_line_from_fd
var writer = f.writer();
try writer.print("hello\n", .{});
try add_line_from_fd(f.handle);
}
fn add_line_from_fd(fd: i32) !void {
// var f = std.fs.File {.handle = fd};
// defer f.close();
_ = try std.os.write(fd, "hello this is another line\n");
std.os.close(fd);
}
pub fn main() !u8 {
// var path = "/tmp/.TEST_USOCK";
// print("Opening the file '{s}'.\n", .{path});
//try print_current_dir();
try add_line_in_file();
return 0;
}

View File

@ -0,0 +1,44 @@
const std = @import("std");
const hexdump = @import("./hexdump.zig");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const Timer = std.time.Timer;
const print = std.debug.print;
const P = std.ArrayList(std.os.pollfd);
fn arraylist_test() !void {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var p = P.init(allocator);
defer p.deinit();
try p.append(.{.fd = 8, .events = 0, .revents = 0});
for(p.items) |i| { print("fd: {}\n", .{i.fd}); }
}
fn timer_test() !void {
var timer = try Timer.start();
var count: u64 = 0;
while (count < 100000) {
count += 1;
print("\rcount = {}", .{count});
}
print("\n", .{});
var duration = timer.read();
print("took {} us\n", .{duration / 1000});
}
pub fn main() !u8 {
// try arraylist_test();
try timer_test();
return 0;
}

View File

@ -0,0 +1,104 @@
const std = @import("std");
const hexdump = @import("./hexdump.zig");
const testing = std.testing;
const net = std.net;
const os = std.os;
const fmt = std.fmt;
const print = std.debug.print;
// const config = .{.safety = true};
// var gpa = std.heap.GeneralPurposeAllocator(config){};
// defer _ = gpa.deinit();
// const allocator = gpa.allocator();
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
fn server_init() net.StreamServer {
// no reuse_address and default kernel_backlog
return net.StreamServer.init(.{});
}
fn remove_unix_socket(path: []const u8) void {
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
else => { print("error: {}\n", .{err}); }
};
}
fn waiting_for_connection(stream: *net.StreamServer
, path: []const u8) !net.StreamServer.Connection {
var address = try net.Address.initUnix(path);
try stream.listen(address);
// const linux = os.linux;
// var tfd = try os.timerfd_create(linux.CLOCK.MONOTONIC, linux.TFD.CLOEXEC);
// defer os.close(tfd);
// // Fire event 10_000_000ns = 10s after the os.timerfd_settime call.
// var sit: linux.itimerspec = .{ .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 }
// , .it_value = .{ .tv_sec = 0, .tv_nsec = 10 * (1000 * 1000 * 1000) } };
// print("before os.timerfd_settime\n", .{});
// try os.timerfd_settime(tfd, 0, &sit, null);
// var fds: [2]os.pollfd =
// .{ .{ .fd = tfd, .events = os.linux.POLL.IN, .revents = 0 }
// , .{ .fd = lfd, .events = os.linux.POLL.IN, .revents = 0 }};
// var count = try os.poll(&fds, -1); // -1 => infinite waiting
var waiting_duration: i32 = 10 * 1000; // in ms
print("waiting for 10 seconds, tops\n", .{});
var ssockfd = stream.sockfd; // actual listener (server)
var lfd: os.socket_t = undefined;
if (ssockfd) |sfd| { lfd = sfd; }
else { return error.Undefined; }
print("Let's wait for an event (either stdin or unix socket)\n", .{});
var count: usize = undefined;
while(true) {
var fds: [2]os.pollfd =
.{.{ .fd = 0, .events = os.linux.POLL.IN, .revents = 0 }
, .{ .fd = lfd, .events = os.linux.POLL.IN, .revents = 0 }};
print("fds: {any}\n", .{fds});
count = try os.poll(&fds, waiting_duration);
if (count == 0) { print("no client, still waiting\n", .{}); continue; }
print("fds NOW: {any}\n", .{fds});
break;
}
return stream.accept();
}
fn receive_msg(stream: net.Stream) !void {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var reader = fbs.reader();
_ = try stream.read(buffer[0..]);
const msg_type = try reader.readByte();
const msg_len = try reader.readIntBig(u32);
const msg_payload = buffer[5..5+msg_len];
print ("type: {}, len {}, content: {s}\n"
, .{msg_type, msg_len, msg_payload});
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Init UNIX server to {s}...\n", .{path});
var stream = server_init();
defer stream.deinit();
defer remove_unix_socket(path);
// TODO
print("Waiting for a connection...\n", .{});
var connection = try waiting_for_connection(&stream, path);
print("Someone is connected! Receiving a message...\n", .{});
try receive_msg(connection.stream);
print("Disconnection...\n", .{});
disconnect(&stream);
print("Disconnected!\n", .{});
return 0;
}

208
zig-impl/misc/rcv-fd.zig Normal file
View File

@ -0,0 +1,208 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const mem = std.mem;
const print = std.debug.print;
const Cmsghdr = @import("./cmsghdr.zig").Cmsghdr;
const system = std.os.system;
const socket_t = std.os.socket_t;
const msghdr = system.msghdr;
const builtin = @import("builtin");
const windows = std.os.windows;
const errno = std.os.errno;
const unexpectedErrno = std.os.unexpectedErrno;
const SendMsgError = std.os.SendMsgError;
pub fn recvmsg(
/// The file descriptor of the sending socket.
sockfd: socket_t,
/// Message header and iovecs
msg: msghdr,
flags: u32,
) SendMsgError!usize {
while (true) {
var m = msg;
const rc = system.recvmsg(sockfd, @ptrCast(*std.x.os.Socket.Message, &m), @intCast(c_int, flags));
if (builtin.os.tag == .windows) {
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSAEACCES => return error.AccessDenied,
.WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
.WSAECONNRESET => return error.ConnectionResetByPeer,
.WSAEMSGSIZE => return error.MessageTooBig,
.WSAENOBUFS => return error.SystemResources,
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
.WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
.WSAEDESTADDRREQ => unreachable, // A destination address is required.
.WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
.WSAEHOSTUNREACH => return error.NetworkUnreachable,
// TODO: WSAEINPROGRESS, WSAEINTR
.WSAEINVAL => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAENETRESET => return error.ConnectionResetByPeer,
.WSAENETUNREACH => return error.NetworkUnreachable,
.WSAENOTCONN => return error.SocketNotConnected,
.WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
.WSAEWOULDBLOCK => return error.WouldBlock,
.WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
else => |err| return windows.unexpectedWSAError(err),
}
} else {
return @intCast(usize, rc);
}
} else {
switch (errno(rc)) {
.SUCCESS => return @intCast(usize, rc),
.ACCES => return error.AccessDenied,
.AGAIN => return error.WouldBlock,
.ALREADY => return error.FastOpenAlreadyInProgress,
.BADF => unreachable, // always a race condition
.CONNRESET => return error.ConnectionResetByPeer,
.DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
.FAULT => unreachable, // An invalid user space address was specified for an argument.
.INTR => continue,
.INVAL => unreachable, // Invalid argument passed.
.ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
.MSGSIZE => return error.MessageTooBig,
.NOBUFS => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
.OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
.PIPE => return error.BrokenPipe,
.AFNOSUPPORT => return error.AddressFamilyNotSupported,
.LOOP => return error.SymLinkLoop,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.NotDir,
.HOSTUNREACH => return error.NetworkUnreachable,
.NETUNREACH => return error.NetworkUnreachable,
.NOTCONN => return error.SocketNotConnected,
.NETDOWN => return error.NetworkSubsystemFailed,
else => |err| return unexpectedErrno(err),
}
}
}
}
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
fn server_init() net.StreamServer {
// no reuse_address and default kernel_backlog
return net.StreamServer.init(.{});
}
fn waiting_for_connection(stream: *net.StreamServer
, path: []const u8) !net.StreamServer.Connection {
var address = try net.Address.initUnix(path);
try stream.listen(address);
return stream.accept();
}
fn remove_unix_socket(path: []const u8) void {
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
else => { print("error: {}\n", .{err}); }
};
}
const SCM_RIGHTS: c_int = 1;
fn send_msg(sock: os.socket_t, msg: []const u8, fd: os.fd_t) void {
var iov = [_]os.iovec_const{
.{
.iov_base = msg.ptr,
.iov_len = msg.len,
},
};
var cmsg = Cmsghdr(os.fd_t).init(.{
.level = os.SOL.SOCKET,
.@"type" = SCM_RIGHTS,
.data = fd,
});
const len = os.sendmsg(sock, .{
.name = undefined,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = &cmsg,
.controllen = @sizeOf(@TypeOf(cmsg)),
.flags = 0,
}, 0) catch |err| {
print("error sendmsg failed with {s}", .{@errorName(err)});
return;
};
if (len != msg.len) {
// we don't have much choice but to exit here
// log.err(@src(), "expected sendmsg to return {} but got {}", .{msg.len, len});
print("expected sendmsg to return {} but got {}", .{msg.len, len});
os.exit(0xff);
}
}
fn receive_msg(sock: os.socket_t) !os.fd_t {
var buffer: [100]u8 = undefined;
var iov = [1]os.iovec{
.{
.iov_base = buffer[0..],
.iov_len = buffer.len,
},
};
var cmsg = Cmsghdr(os.fd_t).init(.{
.level = os.SOL.SOCKET,
.@"type" = SCM_RIGHTS,
.data = undefined,
});
var msg: std.os.msghdr = .{
.name = undefined,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = &cmsg,
.controllen = @sizeOf(@TypeOf(cmsg)),
.flags = 0,
};
const len = recvmsg(sock, msg, 0) catch |err| {
print("error sendmsg failed with {s}", .{@errorName(err)});
return 0;
};
print("received {} bytes, fd is {}\n", .{len, @as(i32, cmsg.dataPtr().*)});
print("iov base {s}\n", .{iov[0].iov_base[0..iov[0].iov_len - 1]});
return @as(i32, cmsg.dataPtr().*);
}
fn add_line_from_fd(fd: i32) !void {
// var f = std.fs.File {.handle = fd};
// defer f.close();
_ = try std.os.write(fd, "hello this is another line\n");
std.os.close(fd);
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Init UNIX server to {s}...\n", .{path});
var stream = server_init();
defer stream.deinit();
print("Waiting for a connection...\n", .{});
var connection = try waiting_for_connection(&stream, path);
defer remove_unix_socket(path);
print("Someone is connected! Receiving a file descriptor...\n", .{});
var fd = try receive_msg(connection.stream.handle);
print("FD received, writing a line into the file...\n", .{});
try add_line_from_fd(fd);
print("Disconnection...\n", .{});
disconnect(&stream);
print("Disconnected!\n", .{});
return 0;
}

View File

@ -0,0 +1,56 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const mem = std.mem;
const print = std.debug.print;
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
fn server_init() net.StreamServer {
// no reuse_address and default kernel_backlog
return net.StreamServer.init(.{});
}
fn waiting_for_connection(stream: *net.StreamServer
, path: []const u8) !net.StreamServer.Connection {
var address = try net.Address.initUnix(path);
try stream.listen(address);
return stream.accept();
}
fn remove_unix_socket(path: []const u8) void {
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
else => { print("error: {}\n", .{err}); }
};
}
fn receive_msg(stream: net.Stream) !void {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var reader = fbs.reader();
_ = try stream.read(buffer[0..]);
const msg_type = try reader.readByte();
const msg_len = try reader.readIntBig(u32);
const msg_payload = buffer[5..5+msg_len];
print ("type: {}, len {}, content: {s}\n"
, .{msg_type, msg_len, msg_payload});
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Init UNIX server to {s}...\n", .{path});
var stream = server_init();
defer stream.deinit();
print("Waiting for a connection...\n", .{});
var connection = try waiting_for_connection(&stream, path);
defer remove_unix_socket(path);
print("Someone is connected! Receiving a message...\n", .{});
try receive_msg(connection.stream);
print("Disconnection...\n", .{});
disconnect(&stream);
print("Disconnected!\n", .{});
return 0;
}

View File

@ -0,0 +1,38 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const mem = std.mem;
const print = std.debug.print;
fn disconnect(stream: net.Stream) void { stream.close(); }
fn connect(path: []const u8) !net.Stream {
return try net.connectUnixSocket(path);
}
fn send_msg(stream: net.Stream) !usize {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
try writer.writeByte(2); // DATA
const message = "hello everyone";
try writer.writeIntBig(u32, message.len);
_ = try writer.write(message);
return stream.write (fbs.getWritten());
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Connection to {s}...\n", .{path});
var stream = try connect(path);
print("Connected! Sending a message...\n", .{});
const bytecount = try send_msg(stream);
print("Sent {} bytes! Disconnection...\n", .{bytecount});
disconnect(stream);
print("Disconnected!\n", .{});
return 0;
}

86
zig-impl/misc/snd-fd.zig Normal file
View File

@ -0,0 +1,86 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const mem = std.mem;
const os = std.os;
const print = std.debug.print;
const Cmsghdr = @import("./cmsghdr.zig").Cmsghdr;
fn disconnect(stream: net.Stream) void { stream.close(); }
fn connect(path: []const u8) !net.Stream {
return try net.connectUnixSocket(path);
}
const SCM_RIGHTS: c_int = 1;
fn send_msg(sock: os.socket_t, msg: []const u8, fd: os.fd_t) void {
var iov = [_]os.iovec_const{
.{
.iov_base = msg.ptr,
.iov_len = msg.len,
},
};
var cmsg = Cmsghdr(os.fd_t).init(.{
.level = os.SOL.SOCKET,
.@"type" = SCM_RIGHTS,
.data = fd,
});
const len = os.sendmsg(sock, .{
.name = undefined,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = &cmsg,
.controllen = @sizeOf(@TypeOf(cmsg)),
.flags = 0,
}, 0) catch |err| {
print("error sendmsg failed with {s}", .{@errorName(err)});
return;
};
if (len != msg.len) {
// we don't have much choice but to exit here
// log.err(@src(), "expected sendmsg to return {} but got {}", .{msg.len, len});
print("expected sendmsg to return {} but got {}", .{msg.len, len});
os.exit(0xff);
}
}
// const buffer_size = 10000;
// var buffer: [buffer_size]u8 = undefined;
// var fba = std.heap.fixedBufferAllocator(&buffer);
fn add_line_in_file() !void {
var cwd = std.fs.cwd();
var f = try cwd.createFile("some-file.log", .{.read = true});
defer f.close();
var writer = f.writer();
try writer.print("hello\n", .{});
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Connection to {s}...\n", .{path});
var stream = try connect(path);
print("Connected! Opening a file...\n", .{});
var file = try std.fs.cwd().createFile("some-file.log", .{.read = true});
defer file.close();
print("File opened! Writing some data into it...\n", .{});
var writer = file.writer();
try writer.print("hello this is the first process\n", .{});
print("Data written! Sending its fd...\n", .{});
send_msg(stream.handle, "hello", file.handle);
print("Sent fd! Disconnection...\n", .{});
disconnect(stream);
print("Disconnected!\n", .{});
return 0;
}

54
zig-impl/misc/snd-rcv.zig Normal file
View File

@ -0,0 +1,54 @@
const std = @import("std");
const testing = std.testing;
const net = std.net;
const fmt = std.fmt;
const mem = std.mem;
const print = std.debug.print;
fn disconnect(stream: net.Stream) void { stream.close(); }
fn connect(path: []const u8) !net.Stream {
return try net.connectUnixSocket(path);
}
fn receive_msg(stream: net.Stream) !void {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var reader = fbs.reader();
_ = try stream.read(buffer[0..]);
const msg_type = try reader.readByte();
const msg_len = try reader.readIntBig(u32);
const msg_payload = buffer[5..5+msg_len];
print ("type: {}, len {}, content: {s}\n"
, .{msg_type, msg_len, msg_payload});
}
fn send_msg(stream: net.Stream) !usize {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
try writer.writeByte(2); // DATA
const message = "hello everyone";
try writer.writeIntBig(u32, message.len);
_ = try writer.write(message);
return stream.write (fbs.getWritten());
}
pub fn main() !u8 {
var path = "/tmp/.TEST_USOCK";
print("Connection to {s}...\n", .{path});
var stream = try connect(path);
print("Connected! Sending a message...\n", .{});
const bytecount = try send_msg(stream);
print("Sent {} bytes! Waiting a message...\n", .{bytecount});
try receive_msg(stream);
print("Received a message! Disconnection...\n", .{});
disconnect(stream);
print("Disconnected!\n", .{});
return 0;
}

86
zig-impl/next-video.md Normal file
View File

@ -0,0 +1,86 @@
# functions
var received_fd = @as(i32, cmsg.dataPtr().*);
std.mem.copy(u8, buffer, &msg_buffer);
@ptrCast(*std.x.os.Socket.Message, &m)
os.exit(0xff);
var network_envvar = std.process.getEnvVarOwned(fba, "IPC_NETWORK") catch |err| switch(err) {
// error{ OutOfMemory, EnvironmentVariableNotFound, InvalidUtf8 } (ErrorSet)
.EnvironmentVariableNotFound => { return; }, // no need to contact IPCd
else => { return err; },
};
const log = std.log.scoped(.libipc_context);
log.err("context.deinit(): IndexOutOfBounds", .{});
log.debug("stuff(): IndexOutOfBounds", .{});
# Functions done
receive_fd
send_fd
# switch
An example of `catch |err| switch(err)`.
# Test stuff
zig test src/main.zig
# Documentation
zig build-exe -femit-docs -fno-emit-bin src/main.zig
ACCESS_LOGS ?= ./access.log
servedoc:
darkhttpd docs/ --addr 127.0.0.1 --port 35000 --log $(ACCESS_LOGS)
### Frustration
Searching for a type, this type depends on a sub-type, which depends on the OS, which ultimately... cannot be documented automatically.
Example:
std.fs.File.Mode => const Mode: "mode_t" = os.mode_t;
os.mode_t => const mode_t: "mode_t" = system.mode_t;
### anytype
// create a server path for the UNIX socket based on the service name
pub fn server_path(self: *Self, service_name: []const u8, writer: anytype) !void {
try writer.print("{s}/{s}", .{ self.rundir, service_name });
}
From
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
try ctx.server_path("simple-context-test", writer);
var path = fbs.getWritten();
To
var buffer: [1000]u8 = undefined;
var path = try std.fmt.bufPrint(&buffer, "{s}/{s}", .{ ctx.rundir, "simple-context-test" });
# Errors
Double returning type => no need for specific return structures.
# Timer
const Timer = std.time.Timer;
var timer = try Timer.start();
var duration = timer.read() / 1000000; // ns -> ms
var value = db.get(key) orelse return error.notHere;
# There is still room for improvement.
When the result actually is a single value (anonymous hash).
src/switch.zig:125:37: error: binary operator `|` has whitespace on one side, but not the other.
self.db.fetchSwapRemove(fd) |k,v|{

View File

@ -0,0 +1,11 @@
const std = @import("std");
const print = std.debug.print;
pub fn main() !u8 {
print("First close!\n", .{});
std.os.close(1);
print("SECOND close!\n", .{});
std.os.close(1);
print("Will it be printed?\n", .{});
return 0;
}

View File

@ -0,0 +1,8 @@
const msg_type = @intToEnum(Message.Type, try reader.readByte());
try writer.writeByte(@enumToInt(self.t));
var hexbuf: [4000]u8 = undefined;
var hexfbs = std.io.fixedBufferStream(&hexbuf);
var hexwriter = hexfbs.writer();
try hexdump.hexdump(hexwriter, "Message.read input buffer", buffer);
print("{s}\n", .{hexfbs.getWritten()});

57
zig-impl/other/rcv-fd.zig Normal file
View File

@ -0,0 +1,57 @@
const std = @import("std");
const net = std.net;
const print = std.debug.print;
const receive_fd = @import("./exchange-fd.zig").receive_fd;
fn disconnect(stream: *net.StreamServer) void { stream.close(); }
fn server_init() net.StreamServer {
// no reuse_address and default kernel_backlog
return net.StreamServer.init(.{});
}
fn waiting_for_connection(stream: *net.StreamServer
, path: []const u8) !net.StreamServer.Connection {
var address = try net.Address.initUnix(path);
try stream.listen(address);
return stream.accept();
}
fn remove_unix_socket(path: []const u8) void {
std.fs.deleteFileAbsolute(path) catch |err| switch(err) {
else => { print("error: {}\n", .{err}); }
};
}
fn add_line_from_fd(fd: i32) !void {
_ = try std.os.write(fd, "SECOND LINE\n");
std.os.close(fd);
}
pub fn main() !u8 {
var path = "/tmp/TEST_EXCHANGE_FD";
print("Init UNIX server to {s}...\n", .{path});
var stream = server_init();
defer stream.deinit();
print("Waiting for a connection...\n", .{});
var connection = try waiting_for_connection(&stream, path);
defer remove_unix_socket(path);
print("Someone is connected! Receiving a file descriptor...\n", .{});
var msgbuffer: [1500]u8 = undefined;
var msgsize: usize = 0;
var fd = try receive_fd(connection.stream.handle, msgbuffer[0..], &msgsize);
print("received fd: {}, payload: {s}\n", .{fd, msgbuffer[0..msgsize - 1]});
print("FD received, writing a line into the file...\n", .{});
try add_line_from_fd(fd);
print("Disconnection...\n", .{});
disconnect(&stream);
print("Disconnected!\n", .{});
return 0;
}

43
zig-impl/other/snd-fd.zig Normal file
View File

@ -0,0 +1,43 @@
const std = @import("std");
const net = std.net;
const send_fd = @import("./exchange-fd.zig").send_fd;
const print = std.debug.print;
fn disconnect(stream: net.Stream) void { stream.close(); }
fn connect(path: []const u8) !net.Stream {
return try net.connectUnixSocket(path);
}
fn add_line_in_file() !void {
var cwd = std.fs.cwd();
var f = try cwd.createFile("some-file.log", .{.read = true});
defer f.close();
var writer = f.writer();
try writer.print("hello\n", .{});
}
pub fn main() !u8 {
var path = "/tmp/TEST_EXCHANGE_FD";
print("Connection to {s}...\n", .{path});
var stream = try connect(path);
print("Connected! Opening a file...\n", .{});
var file = try std.fs.cwd().createFile("some-file.log", .{.read = true});
defer file.close();
print("File opened! Writing some data into it...\n", .{});
var writer = file.writer();
try writer.print("hello this is the first process\n", .{});
print("Data written! Sending its fd...\n", .{});
send_fd(stream.handle, "hello this is the payload", file.handle);
print("Sent fd! Disconnection...\n", .{});
disconnect(stream);
print("Disconnected!\n", .{});
return 0;
}

View File

@ -0,0 +1,25 @@
const std = @import("std");
const hexdump = @import("./hexdump.zig");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const print = std.debug.print;
fn say_hello(some_number: i32) void {
print ("hello number {}\n", .{some_number});
}
fn say_coucou(some_number: i32) void {
print ("coucou number {}\n", .{some_number});
}
pub fn main() !u8 {
var fp: *const fn (i32) void = say_hello;
// print ("currently fp {any}\n", .{fp});
fp(10);
fp = say_coucou;
// print ("currently fp {p}\n", .{fp.?});
fp(20);
return 0;
}

View File

@ -0,0 +1,49 @@
const std = @import("std");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const print = std.debug.print;
const testing = std.testing;
const print_eq = @import("./util.zig").print_eq;
const URI = @import("./util.zig").URI;
fn connect_tcp(allocator: std.mem.Allocator) !net.Stream {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
error.EnvironmentVariableNotFound => blk: {
print("no ADDRESS envvar: connecting on 127.0.0.1:9000\n", .{});
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
},
else => { return err; },
};
defer allocator.free(address);
try writer.print("{s}", .{address});
var tcp_address = fbs.getWritten();
var iterator = std.mem.split(u8, tcp_address, ":");
var real_tcp_address = iterator.first();
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
var stream = try net.tcpConnectToAddress(socket_addr);
return stream;
}
pub fn main() !u8 {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var stream = try connect_tcp(allocator);
_ = try stream.write("coucou");
stream.close();
return 0;
}

View File

@ -0,0 +1,57 @@
const std = @import("std");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const print = std.debug.print;
const testing = std.testing;
const print_eq = @import("./util.zig").print_eq;
const URI = @import("./util.zig").URI;
fn init_tcp_server(allocator: std.mem.Allocator) !net.StreamServer {
var buffer: [1000]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
var address = std.process.getEnvVarOwned(allocator, "ADDRESS") catch |err| switch(err) {
error.EnvironmentVariableNotFound => blk: {
print("no ADDRESS envvar: TCPd will listen on 127.0.0.1:9000\n", .{});
break :blk try allocator.dupe(u8, "127.0.0.1:9000");
},
else => { return err; },
};
defer allocator.free(address);
try writer.print("{s}", .{address});
var tcp_address = fbs.getWritten();
var iterator = std.mem.split(u8, tcp_address, ":");
var real_tcp_address = iterator.first();
var real_tcp_port = try std.fmt.parseUnsigned(u16, iterator.rest(), 10);
print ("TCP address [{s}] port [{}]\n", .{real_tcp_address, real_tcp_port});
var server = net.StreamServer.init(.{.reuse_address = true});
var socket_addr = try net.Address.parseIp(real_tcp_address, real_tcp_port);
try server.listen(socket_addr);
return server;
}
fn accept_read(server: *net.StreamServer) !void {
var client = try server.accept(); // net.StreamServer.Connection
var buffer: [1000]u8 = undefined;
var size = try client.stream.read(&buffer);
print ("received: {s}\n", .{buffer[0..size]});
client.stream.close();
}
pub fn main() !u8 {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var streamserver = try init_tcp_server(allocator);
try accept_read(&streamserver);
return 0;
}

22
zig-impl/other/testme.zig Normal file
View File

@ -0,0 +1,22 @@
const std = @import("std");
const hexdump = @import("./hexdump.zig");
const net = std.net;
const fmt = std.fmt;
const os = std.os;
const print = std.debug.print;
const ipc = @import("./main.zig");
const Message = ipc.Message;
pub fn main() !u8 {
const config = .{.safety = true};
var gpa = std.heap.GeneralPurposeAllocator(config){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try ipc.Context.init(allocator);
ctx.deinit(); // There. Can't leak. Isn't Zig wonderful?
print("Goodbye\n", .{});
return 0;
}

View File

@ -0,0 +1,31 @@
const std = @import("std");
const ipc = @import("./ipc.zig");
const hexdump = @import("./hexdump.zig");
const print = std.debug.print;
pub fn main() !void {
var allocator = std.heap.c_allocator;
var buffer = [_]u8{0} ** 10000;
var fbs = std.io.fixedBufferStream(&buffer);
var writer = fbs.writer();
var m = try ipc.Message.init (9, allocator, "hello this is me!");
defer m.deinit();
_ = try m.write(writer);
var msg_bytes = fbs.getWritten();
// var hexbuf: [4000]u8 = undefined;
// var hexfbs = std.io.fixedBufferStream(&hexbuf);
// var hexwriter = hexfbs.writer();
// try hexdump.hexdump(hexwriter, "What should be written in output", msg_bytes);
// print("{s}\n", .{hexfbs.getWritten()});
var out = std.io.getStdOut();
_ = try out.write("pong");
std.time.sleep(1_000_000_000); // wait for 1 seconds
_ = try out.write(msg_bytes);
}

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