summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordweller <dweller@cabin.digital>2024-03-09 00:55:36 +0200
committerdweller <dweller@cabin.digital>2024-03-09 00:55:36 +0200
commit86d3f93ee338b28ab7d40aa83c129cf6b97ef4b7 (patch)
tree507a8d66932e6dea9b121dfcbf980f7925575c9f
Initial commit, 2 years later
Diffstat (limited to '')
-rw-r--r--.gitignore41
-rw-r--r--README.txt19
-rwxr-xr-xc/build82
-rw-r--r--c/sources/client.c96
-rw-r--r--c/sources/common.h27
-rw-r--r--c/sources/ezipc.h543
-rw-r--r--c/sources/server.c66
-rw-r--r--java/build.xml56
-rw-r--r--java/sources/digital/cabin/ezipc/EzIPC.java106
-rw-r--r--java/sources/digital/cabin/ezipc/EzIPCClient.java22
-rw-r--r--java/sources/digital/cabin/ezipc/EzIPCServer.java33
-rw-r--r--java/sources/server.java82
12 files changed, 1173 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7fcc06e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+c/binaries/
+java/build/
+java/libraries/
+
+.
+├── c
+│   ├── binaries
+│   ├── sources
+│   │   ├── client.c
+│   │   ├── common.h
+│   │   ├── ezipc.h
+│   │   └── server.c
+│   └── build
+├── java
+│   ├── build
+│   │   ├── classes
+│   │   │   ├── digital
+│   │   │   │   └── cabin
+│   │   │   │   └── ezipc
+│   │   │   │   ├── EzIPC$LibraryNamedFIFO.class
+│   │   │   │   ├── EzIPC.class
+│   │   │   │   ├── EzIPCClient.class
+│   │   │   │   └── EzIPCServer.class
+│   │   │   └── EzIPCServerTest.class
+│   │   ├── MANIFEST.MF
+│   │   └── server-0.1.jar
+│   ├── libraries
+│   │   ├── jna-jpms-5.12.1.jar
+│   │   └── jna-platform-5.12.1.jar
+│   ├── sources
+│   │   ├── digital
+│   │   │   └── cabin
+│   │   │   └── ezipc
+│   │   │   ├── EzIPC.java
+│   │   │   ├── EzIPCClient.java
+│   │   │   └── EzIPCServer.java
+│   │   └── server.java
+│   └── build.xml
+└── README.txt
+
+15 directories, 20 files
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..c688384
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,19 @@
+EZIPC - Easy Inter-Process Communication single-file library
+
+See c/sources/ezipc.h for more explenation.
+common.h, server.c, and client.c are just examples.
+
+Java version is not really tested, I am not a java dev. C version was tested in the basic case.
+Unlike C version, Java version also depends on 2 libraries:
+ - jna-jpms-5.12.1.jar
+ - jna-platform-5.12.1.jar
+
+You will need to source them and put them into java/libraries directory. I don't wanna deal with
+distribution of 3rd party libs.
+
+All of this was done at a request for help that was never actually utilized or even tested by the
+one who asked for this, so use at your own risk. 🤷
+
+
+Copyright (C) 2022 - dweller@cabin.digital
+Licensed under BSD 3-Clause license. See ezipc.h for more details.
diff --git a/c/build b/c/build
new file mode 100755
index 0000000..f452e5a
--- /dev/null
+++ b/c/build
@@ -0,0 +1,82 @@
+#!/usr/bin/env sh
+set -e
+#set -x #trace
+
+CC=gcc
+CFLAGS=" \
+ -pipe \
+ -Wall -Wextra -Wshadow -Wpedantic -Wformat=2 \
+ -std=c99"
+
+
+CFASTF="-O2 -flto"
+CDBGF="-g3 -Og -D _DEBUG"
+
+
+# avoid "bin" just in case something goes wrong and we wipe /bin
+# #paranoia
+LIBDIR="libraries"
+SRCDIR="sources"
+BUILDDIR="binaries"
+
+main()
+{
+ case "$1" in
+ "") build_debug ;;
+ "debug") build_debug ;;
+ "release") build_release ;;
+ "clean") clean ;;
+ "help") help ;;
+ *)
+ echo "Error: Unknown argument '$1'"
+ help
+ ;;
+ esac
+}
+
+help()
+{
+ echo "Build script for Linux OSes"
+ echo "Usage: $0 [debug|release|clean|help]"
+ echo " debug - build debug mode (default)"
+ echo " release - build in release mode, aka optimized"
+ echo " clean - delete the build artifacts"
+ echo " help - print this message"
+ exit
+}
+
+build_unity()
+{
+ "$CC" -D _UNITY_BUILD $CFLAGS -fwhole-program -I $(realpath "$SRCDIR") \
+ -I $(realpath "$LIBDIR") $1 "$SRCDIR"/server.c \
+ -o "$BUILDDIR"/server
+
+ "$CC" -D _UNITY_BUILD $CFLAGS -fwhole-program -I $(realpath "$SRCDIR") \
+ -I $(realpath "$LIBDIR") $1 "$SRCDIR"/client.c \
+ -o "$BUILDDIR"/client
+}
+
+build()
+{
+ mkdir -p "$BUILDDIR"
+
+ build_unity "$1"
+}
+
+build_debug()
+{
+ build "$CDBGF"
+}
+
+build_release()
+{
+ build "$CFASTF"
+}
+
+clean()
+{
+ rm -rf $(realpath ${BUILDDIR:?})
+}
+
+
+main "$@"
diff --git a/c/sources/client.c b/c/sources/client.c
new file mode 100644
index 0000000..672e3e8
--- /dev/null
+++ b/c/sources/client.c
@@ -0,0 +1,96 @@
+#define EZIPC_IMPL
+#include "ezipc.h"
+
+#include "common.h"
+
+
+int main(void)
+{
+ printf("EZIPC C Client\n");
+
+
+ printf("Connecting...");
+ fflush(stdout);
+
+ ezi_conn* conn = ezi_connect(EZIPC_TEST_PATH);
+ assert(conn);
+
+ printf(" DONE!\n");
+
+ msg msg_txt = { .type = MSG_TEXT };
+ msg msg_exit = { .type = MSG_EXIT };
+
+ bool running = true;
+ while(running)
+ {
+ char in[1024] = {0};
+ printf("send> ");
+ scanf("%[^\n]%*c", in);
+
+ if(strcmp(in, "exit") != 0)
+ {
+ strcpy((char*)msg_txt.data, in);
+ if(!ezi_send(conn, &msg_txt, sizeof(msg_txt)))
+ {
+ printf("Send error, resetting...\n");
+
+ ezi_disconnect(conn);
+ conn = ezi_connect(EZIPC_TEST_PATH);
+ assert(conn);
+
+ continue;
+ }
+ }
+ else
+ {
+ if(!ezi_send(conn, &msg_exit, sizeof(msg_exit)))
+ {
+ ezi_disconnect(conn);
+ exit(1);
+ }
+
+ break;
+ }
+
+ msg rmsg = {0};
+ size_t rsz = sizeof(rmsg);
+ if(!ezi_recv(conn, &rmsg, &rsz))
+ {
+ printf("Recv error, resetting...\n");
+
+ ezi_disconnect(conn);
+ conn = ezi_connect(EZIPC_TEST_PATH);
+ assert(conn);
+
+ continue;
+ }
+
+ switch(rmsg.type)
+ {
+ case MSG_OK:
+ {
+ printf("acknowledged!\n");
+
+ } break;
+
+ case MSG_EXIT:
+ {
+ running = false;
+ printf("told to exit...\n");
+
+ } break;
+
+ case MSG_TEXT:
+ {
+ printf("received: '%s'\n", (char*)rmsg.data);
+
+ } break;
+
+ default: exit(1);
+ }
+ }
+
+ ezi_disconnect(conn);
+ return 0;
+}
+
diff --git a/c/sources/common.h b/c/sources/common.h
new file mode 100644
index 0000000..bc3ae30
--- /dev/null
+++ b/c/sources/common.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+
+#define EZIPC_TEST_PATH "/tmp/ezi_conn_test"
+
+
+typedef enum msg_type_e
+{
+ MSG_TEXT,
+ MSG_OK,
+ MSG_EXIT,
+
+} msg_type;
+
+
+typedef struct msg_s
+{
+ msg_type type;
+ uint8_t data[128];
+
+} msg;
diff --git a/c/sources/ezipc.h b/c/sources/ezipc.h
new file mode 100644
index 0000000..ff69fe1
--- /dev/null
+++ b/c/sources/ezipc.h
@@ -0,0 +1,543 @@
+/*
+ * BSD 3-Clause License (BSD-3-Clause)
+ *
+ * Copyright (C) 2022 - dweller <dweller@cabin.digital>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * EZIPC
+ * EZ(easy) Inter-Process Communication single-file library
+ *
+ *
+ * At the moment, this is just a wrapper around POSIX named pipes (aka FIFOs)
+ * that creates duplex communication channel between 2 processes.
+ *
+ *
+ * Brief Description
+ *
+ * Just create the connections with ezi_create() on a "server" and ezi_connect()
+ * on a "client" process. Then use ezi_{send,recv}() functions to push data
+ * around. There is no functional difference between "server" and "client"
+ * except that "server" is ultimately responsible for OS resources (the created
+ * named pipes) clean up.
+ * When you're done, ezi_destroy() the connection on the "server" and
+ * ezi_disconnect() it on the "client" side.
+ *
+ * The library provides a reliable way to send sized frames between 2 processes.
+ * The data size is enforced, but other than that there are no assumptions about
+ * possible higher-level usage of this code. If the reader cannot receive full
+ * frame at once, it is truncated and the reset is discarded. Send and receive
+ * failures are considered fatal and restart (disconnected, reconnect) of the
+ * connection is required.
+ *
+ * All functions are blocking on their specific action. Create/Connect wait for
+ * the other end of the connection, send waits for there to be room in the OS
+ * buffers, recv waits for something to read from OS buffers.
+ *
+ * While more than 2 process are able to read from same connection
+ * (read: named pipes), THIS IS *HIGHLY* INADVISABLE, as there are no guarantees
+ * about data interleaving in such case.
+ *
+ *
+ * Single-file Usage
+ *
+ * Define "EZIPC_IMPL" before this header inclusion into any *.c file to get
+ * the implementation code injected into said .c file. Compile and run.
+ *
+ *
+ * Examples
+ *
+ * See files server.c, client.c, and common.h for a simple usage example.
+ *
+ *
+ * Symbol List
+ *
+ * EZIPC_VERSION
+ *
+ * typedef struct ezi_conn_s ezi_conn
+ *
+ * ezi_conn* ezi_create(const char* conn_path)
+ * void ezi_destroy(ezi_conn* conn)
+ *
+ * ezi_conn* ezi_connect(const char* conn_path)
+ * void ezi_disconnect(ezi_conn* conn)
+ *
+ * bool ezi_send(ezi_conn* conn, const void* data, size_t size)
+ * bool ezi_recv(ezi_conn* conn, void* data, size_t* size);
+ *
+ * See bellow for specific function descriptions.
+ *
+ *
+ * Compliance
+ *
+ * This code adheres to C99 standard, and was tested with following command:
+ * > $ gcc -std=c99 -Wall -Wextra -pedantic
+ *
+ *
+ * Known Issues
+ *
+ * 1. When "server" abruptly disconnects "client" gets SIGPIPE "broken pipe"
+ * signal, which is annoying because it would be way better to just return
+ * false from last called function and perror() for information.
+ *
+ * Workaround: "client" can insert a signal handler and have a callback, or
+ * accept quick death as a positive.
+ *
+ *
+ * Changelog
+ *
+ * v1.2 - ezi_recv() now fails on receiving a size of 0 bytes
+ * v1.1 - C++ support (extern and void* casts)
+ * v1 - release
+ */
+
+
+#ifndef EZIPC_H
+#define EZIPC_H
+
+
+#define EZIPC_VERSION "v1.2"
+
+
+#include <stddef.h>
+#include <stdbool.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+ * Opaque pointer to the structure representing the connection.
+ */
+typedef struct ezi_conn_s ezi_conn;
+
+
+/*
+ * Creates the "server" (controlling/initial) end of the connection. This end
+ * of the connection may create underlying OS resources if they do not exist,
+ * AND IS responsible for cleaning them.
+ * resources.
+ *
+ * BLOCKS: waits for other end to connect.
+ *
+ * Arguments:
+ * IN `conn_path` - path to store the implementation-related resources,
+ * doubles as a connection identifier.
+ *
+ * Returns the connection object as an opaque pointer.
+ */
+extern ezi_conn* ezi_create(const char* conn_path);
+
+/*
+ * Destroys the connection and frees the underlying OS resources if connection
+ * was established with `ezi_create()` function.
+ *
+ * Arguments:
+ * IN `conn` - connection object that represents the connection.
+ *
+ * Returns: nothing.
+ */
+extern void ezi_destroy(ezi_conn* conn);
+
+/*
+ * Creates the "client" end of the connection. This end of the connection may
+ * create underlying OS resources if they do not exist, but IS NOT responsible
+ * for them.
+ *
+ * BLOCKS: waits for other end to connect.
+ *
+ * Arguments:
+ * IN `conn_path` - path to store the implementation-related resources,
+ * doubles as a connection identifier.
+ *
+ * Returns the connection object as an opaque pointer.
+ */
+
+extern ezi_conn* ezi_connect(const char* conn_path);
+
+/*
+ * Destroys the connection and frees the underlying OS resources if connection
+ * was established with `ezi_create()` function.
+ * Is an alias of `ezi_destroy`. Exists for symmetry in the API.
+ *
+ * Arguments:
+ * IN `conn` - connection object that represents the connection.
+ *
+ * Returns: nothing.
+ */
+#define ezi_disconnect(conn) ezi_destroy(conn)
+
+
+/*
+ * Sends `size` bytes to the other endpoint.
+ *
+ * BLOCKS: waits for room in the underlying buffer.
+ *
+ * Arguments:
+ * IN `conn` - connection object that represents the connection.
+ * IN `data` - buffer of `size` bytes to be sent.
+ * IN `size` - size of the data to be sent, in bytes.
+ *
+ * Returns: true on success, false on failure. If failed, the connection is
+ * thought to be in undefined state and has to be closed and reopened
+ * for communication to continue.
+ */
+extern bool ezi_send(ezi_conn* conn, const void* data, size_t size);
+
+/*
+ * Receives up to `size` bytes from the other endpoint. If there is more data
+ * available that `size`, it is *discarded*!
+ *
+ * BLOCKS: waits for data in the underlying buffer.
+ *
+ * Arguments:
+ * IN `conn` - connection object that represents the connection.
+ * OUT `data` - buffer of `size` bytes to be filled with incoming data;
+ * on success holds the received data, which may be truncated
+ * to the input `size`;
+ * on failure data is undefined.
+ * INOUT `size` - as an input: specifies the size of `data` buffer in bytes;
+ * as an output:
+ * on success holds the size of the received data, which may
+ * be smaller than the `data` buffer size passed as an
+ * input;
+ * on failure is unchanged from input.
+ *
+ * Returns: true on success, false on failure. If failed, the connection is
+ * thought to be in undefined state and has to be closed and reopened
+ * for communication to continue.
+ */
+extern bool ezi_recv(ezi_conn* conn, void* data, size_t* size);
+
+#ifdef __cplusplus
+}
+#endif // EXTERN C
+
+
+#endif // EZIPC_H
+
+
+//
+// =========================================================================
+//
+// What follows are implementation details
+//
+//
+
+#ifdef EZIPC_IMPL
+#ifndef EZIPC_IMPLEMENTED
+#define EZIPC_IMPLEMENTED // redefinition guard
+#ifdef __linux__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+struct ezi_conn_s
+{
+ char* path_c2s;
+ char* path_s2c;
+ int c2s, s2c;
+
+ bool server;
+};
+
+
+static ezi_conn* _ezi_internal_create(const char* conn_path,
+ const mode_t modes[2])
+{
+ if(!conn_path) return NULL;
+
+ ezi_conn* conn = (ezi_conn*)calloc(1, sizeof(*conn));
+ if(!conn) return NULL;
+
+ conn->c2s = -1;
+ conn->s2c = -1;
+
+ // Populate path strings
+ {
+ const size_t path_len = strlen(conn_path);
+
+ conn->path_c2s = (char*)malloc(path_len + 5);
+ if(!conn->path_c2s) goto cleanup;
+
+ strcat(conn->path_c2s, conn_path);
+ strcat(conn->path_c2s, "_c2s");
+
+ conn->path_s2c = (char*)malloc(path_len + 5);
+ if(!conn->path_s2c) goto cleanup;
+
+ strcat(conn->path_s2c, conn_path);
+ strcat(conn->path_s2c, "_s2c");
+ }
+
+
+ // Create POSIX FIFOs
+ {
+ if(mkfifo(conn->path_c2s, 0660) != 0)
+ {
+ if(errno != EEXIST)
+ {
+ perror("[EZIPC] Could not create client->server pipe");
+ goto cleanup;
+ }
+ }
+
+ if(mkfifo(conn->path_s2c, 0660) != 0)
+ {
+ if(errno != EEXIST)
+ {
+ perror("[EZIPC] Could not create server->client pipe");
+ goto cleanup;
+ }
+ }
+ }
+
+ // Open the FIFOs
+ {
+ conn->c2s = open(conn->path_c2s, modes[0]);
+ if(conn->c2s < 0)
+ {
+ perror("[EZIPC] Could not open client->server pipe");
+ goto cleanup;
+ }
+
+ conn->s2c = open(conn->path_s2c, modes[1]);
+ if(conn->s2c < 0)
+ {
+ perror("[EZIPC] Could not open server->client pipe");
+ goto cleanup;
+ }
+ }
+
+ return conn;
+
+cleanup:
+
+ if(conn)
+ {
+ ezi_destroy(conn);
+
+ if(conn->path_c2s) free(conn->path_c2s);
+ if(conn->path_s2c) free(conn->path_s2c);
+
+ free(conn);
+ }
+
+ return NULL;
+}
+
+
+ezi_conn* ezi_create(const char* conn_path)
+{
+ const mode_t modes[2] = { O_RDONLY, O_WRONLY};
+
+ ezi_conn* conn = _ezi_internal_create(conn_path, modes);
+ if(conn) conn->server = true;
+
+ return conn;
+}
+
+ezi_conn* ezi_connect(const char* conn_path)
+{
+ const mode_t modes[2] = { O_WRONLY, O_RDONLY};
+
+ ezi_conn* conn = _ezi_internal_create(conn_path, modes);
+ if(conn) conn->server = false;
+
+ return conn;
+}
+
+void ezi_destroy(ezi_conn* conn)
+{
+ if(!conn) return;
+
+ // NOTE: even if close() errors out, DO NOT retry (even if EINTR) as per
+ // man 2 close. Just report the error and clobber the FDs.
+
+ if(conn->c2s >= 0)
+ {
+ int rc = close(conn->c2s);
+ if(rc < 0) perror("[EZIPC] Could not close client->server pipe");
+
+ conn->c2s = -1;
+ }
+
+ if(conn->s2c >= 0)
+ {
+ int rc = close(conn->s2c);
+ if(rc < 0) perror("[EZIPC] Could not close server->client pipe");
+
+ conn->s2c = -1;
+ }
+
+ // I could check and handle remove error here, but I'd rather tell the user
+ // and not accidentally delete something else
+
+ if(conn->server)
+ {
+ if(conn->path_c2s && (remove(conn->path_c2s) != 0))
+ perror("[EZIPC] Could not delete client->server pipe");
+
+ if(conn->path_s2c && (remove(conn->path_s2c) != 0))
+ perror("[EZIPC] Could not delete server->client pipe");
+ }
+}
+
+// wrapper over write() to handle interrupted syscall
+static bool _ezi_write(int ep, const void* buffer, size_t size)
+{
+ const uint8_t* bytes = (uint8_t*)buffer;
+
+ size_t written = 0;
+ while(written < size)
+ {
+ size_t got = write(ep, bytes + written, size - written);
+ if(got <= 0)// FIXME: technically < 0 but I don't want an infinite loop
+ {
+ perror("[EZIPC] Could not write to pipe");
+ return false;
+ }
+
+ written += size;
+ }
+
+ return true;
+}
+
+
+bool ezi_send(ezi_conn* conn, const void* data, size_t size)
+{
+ if(!conn) return false;
+ if(!data) return false;
+ if(size == 0) return true;
+
+ const int ep = conn->server ? conn->s2c : conn->c2s;
+ if(ep >= 0)
+ {
+ const uint64_t out_size = (uint64_t)size;
+ if(!_ezi_write(ep, &out_size, sizeof(out_size))) return false;
+ if(!_ezi_write(ep, data, size)) return false;
+
+ return true;
+ }
+ else return false;
+
+}
+
+// wrapper over read() to handle interrupted syscall
+static bool _ezi_read(int ep, void* buffer, size_t size)
+{
+ uint8_t* bytes = (uint8_t*)buffer;
+
+ size_t readback = 0;
+ while(readback < size)
+ {
+ ssize_t got = read(ep, bytes + readback, size - readback);
+ if(got == 0)
+ {
+ // NOTE: I think reading 0 bytes in non-blocking mode is fine,
+ // but since I don't provide non-blocking mode, this is
+ // still an error.
+ return false;
+ }
+ else if(got < 0)
+ {
+ perror("[EZIPC] Could not read from pipe");
+ return false;
+ }
+
+ readback += got;
+ }
+
+ return true;
+}
+
+
+bool ezi_recv(ezi_conn* conn, void* data, size_t* size)
+{
+ if(!conn) return false;
+ if(!data) return false;
+ if(!size) return false;
+ if(*size == 0) return true;
+
+ const int ep = conn->server ? conn->c2s : conn->s2c;
+ if(ep >= 0)
+ {
+ // NOTE: since this is IPC, we're on the same machine so no need to
+ // ensure endianness
+ uint64_t in_size = 0;
+ if(!_ezi_read(ep, &in_size, sizeof(in_size))) return false;
+
+ if(in_size == 0) return false;
+
+ const uint64_t read_size = in_size > *size ? *size : in_size;
+ if(!_ezi_read(ep, data, read_size)) return false;
+
+ // If we have left over, get rid of it to guarantee correct read next
+ // time
+ for(uint64_t i = 0; i < (in_size - read_size); i++)
+ {
+ uint8_t temp; // I know I could read in bigger chunks, but honestly
+ // I don't care, and assume this is an edge case
+ if(!_ezi_read(ep, &temp, 1)) return false;
+ }
+
+ *size = read_size;
+ return true;
+
+ } else return false;
+
+}
+
+#ifdef __cplusplus
+}
+#endif // EXTERN C
+
+#else // __linux__
+ #error "EZ IPC library is not supported on your platform. Want to port?"
+#endif // platforms
+#endif // EZIPC_IMPLEMENTED
+#endif // EZIPC_IMPL
diff --git a/c/sources/server.c b/c/sources/server.c
new file mode 100644
index 0000000..57c2492
--- /dev/null
+++ b/c/sources/server.c
@@ -0,0 +1,66 @@
+#include "ezipc.h"
+#include "common.h"
+
+
+int main(void)
+{
+ ezi_conn* conn = ezi_create(EZIPC_TEST_PATH);
+ assert(conn);
+
+ msg ok;
+ ok.type = MSG_OK;
+ strcpy((char*)ok.data, "ok");
+
+ msg quit;
+ quit.type = MSG_EXIT;
+ strcpy((char*)quit.data, "exit");
+
+ int counter = 0;
+ bool running = true;
+ while(running)
+ {
+ msg rmsg;
+ size_t rsz = sizeof(rmsg) - 100; // truncate test
+ if(ezi_recv(conn, &rmsg, &rsz))
+ {
+ switch(rmsg.type)
+ {
+ case MSG_TEXT:
+ {
+ printf("got: '%s'\n", (char*)rmsg.data);
+
+ counter++;
+ if(counter >= 10)
+ {
+ if(!ezi_send(conn, &quit, sizeof(quit)))
+ printf("Could not send msg to client\n");
+
+ counter = 0;
+ }
+ else
+ {
+ if(!ezi_send(conn, &ok, sizeof(ok)))
+ printf("Could not send msg to client\n");
+ }
+
+ } break;
+
+ default: break;
+ }
+ }
+ else
+ {
+ printf("Recv error, resetting...\n");
+
+ ezi_destroy(conn);
+ conn = ezi_create(EZIPC_TEST_PATH);
+ assert(conn);
+ }
+ }
+
+ ezi_destroy(conn);
+ return 0;
+}
+
+#define EZIPC_IMPL
+#include "ezipc.h"
diff --git a/java/build.xml b/java/build.xml
new file mode 100644
index 0000000..3d0953f
--- /dev/null
+++ b/java/build.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<project name="EZIPC Server Test" basedir="." default="jar">
+ <property name="version">0.1</property>
+ <property name="dir.src">sources</property>
+ <property name="dir.lib">libraries</property>
+ <property name="dir.build">build</property>
+ <property name="dir.build.classes">${dir.build}/classes</property>
+ <property name="dir.build.javadoc">${dir.build}/javadoc</property>
+ <property name="file.jar">${dir.build}/server-${version}.jar</property>
+
+ <path id="external.classpath">
+ <fileset dir="${dir.lib}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+
+ <target name="clean">
+ <delete dir="${dir.build}"/>
+ </target>
+
+ <target name="init">
+ <mkdir dir="${dir.build}"/>
+ <mkdir dir="${dir.build.classes}"/>
+ </target>
+
+ <target name="compile" depends="init">
+ <echo>Compiling Java source</echo>
+
+ <javac classpathref="external.classpath"
+ srcdir="${dir.src}"
+ destdir="${dir.build.classes}"/>
+ </target>
+
+ <target name="jar" depends="compile">
+ <echo>Making JAR file</echo>
+
+ <manifest file="build/MANIFEST.MF">
+ <attribute name="Main-Class" value="EzIPCServerTest"/>
+ </manifest>
+
+ <jar manifest="build/MANIFEST.MF"
+ basedir="${dir.build.classes}"
+ file="${file.jar}">
+ <zipgroupfileset dir="${dir.lib}">
+ <include name="**/*.jar"/>
+ </zipgroupfileset>
+ </jar>
+ </target>
+
+ <target name="javadoc">
+ <echo>Making JavaDoc from source</echo>
+
+ <javadoc sourcepath="${dir.src}" destdir="${dir.build.javadoc}">
+ </javadoc>
+ </target>
+</project>
diff --git a/java/sources/digital/cabin/ezipc/EzIPC.java b/java/sources/digital/cabin/ezipc/EzIPC.java
new file mode 100644
index 0000000..7c9943d
--- /dev/null
+++ b/java/sources/digital/cabin/ezipc/EzIPC.java
@@ -0,0 +1,106 @@
+package digital.cabin.ezipc;
+
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.platform.linux.ErrNo;
+
+
+public abstract class EzIPC
+{
+ protected interface LibraryNamedFIFO extends Library
+ {
+ static LibraryNamedFIFO INSTANCE =
+ Native.load("c", LibraryNamedFIFO.class);
+
+ public int mkfifo(String pathname, int mode);
+ }
+
+
+ protected LibraryNamedFIFO lib;
+
+ protected String connPath;
+
+ protected FileInputStream in_pipe;
+ protected FileOutputStream out_pipe;
+
+
+ public EzIPC(String connPath) throws IOException
+ {
+ this.lib = LibraryNamedFIFO.INSTANCE;
+ this.connPath = connPath;
+
+ if(lib.mkfifo(connPath+"_c2s", 0660) != 0)
+ if(Native.getLastError() != ErrNo.EEXIST)
+ throw new IOException("Could not create client->server pipe");
+
+ if(lib.mkfifo(connPath+"_s2c", 0660) != 0)
+ if(Native.getLastError() != ErrNo.EEXIST)
+ throw new IOException("Could not create server->client pipe");
+
+ this.in_pipe = null;
+ this.out_pipe = null;
+ }
+
+ public abstract void open() throws IOException;
+
+ public void close() throws IOException
+ {
+ if(in_pipe != null) in_pipe.close();
+ if(out_pipe != null) out_pipe.close();
+ }
+
+
+ // FIXME: check write() return val
+ public boolean send(byte[] data) throws IOException
+ {
+ if(out_pipe == null) return false;
+ if(data.length == 0) return true;
+
+ // FIXME: JAVA DOES NOT HAVE FUCKING UNSIGNED BRO
+ ByteBuffer bb = ByteBuffer.allocate(8);
+ bb.order(ByteOrder.nativeOrder()); // Imagine not being native by def.
+ bb.putLong(data.length);
+
+ out_pipe.write(bb.array());
+ out_pipe.write(data);
+
+ return true;
+ }
+
+ // FIXME: check write() return val
+ public boolean receive(byte[] data) throws IOException
+ {
+ if(in_pipe == null) return false;
+ if(data.length == 0) return true;
+
+ byte[] bytes = new byte[8];
+
+ // FIXME: yikes
+ while(in_pipe.available() < 8);
+ in_pipe.read(bytes);
+
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ bb.order(ByteOrder.nativeOrder()); // Imagine not being native by def.
+
+ // FIXME: JAVA DOES NOT HAVE FUCKING UNSIGNED BRO
+ long in_size = bb.getLong();
+ long read_size = in_size > data.length ? data.length : in_size;
+
+ // FIXME: yikes
+ while(in_pipe.available() < read_size);
+ long read = in_pipe.read(data);
+
+ // truncate
+ for(long i = 0; i < (in_size - read_size); i++) in_pipe.read();
+
+ return true;
+ }
+}
diff --git a/java/sources/digital/cabin/ezipc/EzIPCClient.java b/java/sources/digital/cabin/ezipc/EzIPCClient.java
new file mode 100644
index 0000000..03bc5db
--- /dev/null
+++ b/java/sources/digital/cabin/ezipc/EzIPCClient.java
@@ -0,0 +1,22 @@
+package digital.cabin.ezipc;
+
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+public class EzIPCClient extends EzIPC
+{
+ public EzIPCClient(String connPath) throws IOException
+ {
+ super(connPath);
+ }
+
+ public void open() throws IOException
+ {
+ super.in_pipe = new FileInputStream(super.connPath + "_s2c");
+ super.out_pipe = new FileOutputStream(super.connPath + "_c2s");
+ }
+}
+
diff --git a/java/sources/digital/cabin/ezipc/EzIPCServer.java b/java/sources/digital/cabin/ezipc/EzIPCServer.java
new file mode 100644
index 0000000..f490770
--- /dev/null
+++ b/java/sources/digital/cabin/ezipc/EzIPCServer.java
@@ -0,0 +1,33 @@
+package digital.cabin.ezipc;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+public class EzIPCServer extends EzIPC
+{
+ public EzIPCServer(String connPath) throws IOException
+ {
+ super(connPath);
+ }
+
+ public void open() throws IOException
+ {
+ super.in_pipe = new FileInputStream(super.connPath + "_c2s");
+ super.out_pipe = new FileOutputStream(super.connPath + "_s2c");
+ }
+
+ public void close() throws IOException
+ {
+ super.close();
+
+ File fc2s = new File(super.connPath + "_c2s");
+ fc2s.delete();
+
+ File fs2c = new File(super.connPath + "_s2c");
+ fc2s.delete();
+ }
+}
diff --git a/java/sources/server.java b/java/sources/server.java
new file mode 100644
index 0000000..0ba1127
--- /dev/null
+++ b/java/sources/server.java
@@ -0,0 +1,82 @@
+import digital.cabin.ezipc.EzIPCServer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+
+class EzIPCServerTest
+{
+ private static final String PIPE_PATH = "/tmp/ezi_conn_test";
+
+ public static void main(String[] args)
+ {
+ System.out.println("EZIPC Java Server");
+
+ try
+ {
+ EzIPCServer srv = new EzIPCServer("/tmp/ezi_conn_test");
+
+ System.out.print("Opening connection...");
+ srv.open();
+ System.out.println(" DONE!");
+
+ ByteBuffer msg_ok = ByteBuffer.allocate(132);
+ msg_ok.order(ByteOrder.nativeOrder());
+ msg_ok.putInt(1);
+
+ ByteBuffer msg_exit = ByteBuffer.allocate(132);
+ msg_exit.order(ByteOrder.nativeOrder());
+ msg_exit.putInt(2);
+
+ while(true)
+ {
+ byte[] in = new byte[132];
+ srv.receive(in);
+
+ ByteBuffer msg_in = ByteBuffer.wrap(in);
+ msg_in.order(ByteOrder.nativeOrder());
+
+ int type = msg_in.getInt();
+ if(type == 0)
+ {
+ int end = 4;
+ for(; end < 132; end++) if(in[end] == 0) break;
+
+ String msg = new String(in, 4, end - 4,
+ StandardCharsets.UTF_8);
+
+ System.out.println("received: " + msg);
+
+ srv.send(msg_ok.array());
+ }
+ else if(type == 1)
+ {
+ System.out.println("received spurious OK");
+ srv.send(msg_exit.array());
+ }
+ else if(type == 2)
+ {
+ System.out.println("received EXIT");
+ srv.send(msg_exit.array());
+ break;
+ }
+ else
+ {
+ System.out.println("received unkown msg type:" + type);
+ srv.send(msg_exit.array());
+ }
+ }
+
+ srv.close();
+ }
+ catch (Exception ex)
+ {
+ System.out.println(ex.getMessage());
+ System.exit(1);
+ }
+
+ System.out.println("bye...");
+ }
+}
+