diff options
-rw-r--r-- | .gitignore | 41 | ||||
-rw-r--r-- | README.txt | 19 | ||||
-rwxr-xr-x | c/build | 82 | ||||
-rw-r--r-- | c/sources/client.c | 96 | ||||
-rw-r--r-- | c/sources/common.h | 27 | ||||
-rw-r--r-- | c/sources/ezipc.h | 543 | ||||
-rw-r--r-- | c/sources/server.c | 66 | ||||
-rw-r--r-- | java/build.xml | 56 | ||||
-rw-r--r-- | java/sources/digital/cabin/ezipc/EzIPC.java | 106 | ||||
-rw-r--r-- | java/sources/digital/cabin/ezipc/EzIPCClient.java | 22 | ||||
-rw-r--r-- | java/sources/digital/cabin/ezipc/EzIPCServer.java | 33 | ||||
-rw-r--r-- | java/sources/server.java | 82 |
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. @@ -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..."); + } +} + |