From 08423faea9c6d146cb583c588d20fd5163e1d92c Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Tue, 20 Nov 2018 19:06:15 +0900 Subject: [PATCH] Initial commit. --- shard.yml | 13 +++ src/ipc.cr | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 shard.yml create mode 100644 src/ipc.cr diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..cd3a775 --- /dev/null +++ b/shard.yml @@ -0,0 +1,13 @@ +name: ipc +version: 0.1.0 + +authors: + - Luka Vandervelden + +description: | + High-level Crystal bindings to libipc. + +libraries: + libipc: >= 0.1 + +license: MIT diff --git a/src/ipc.cr b/src/ipc.cr new file mode 100644 index 0000000..1c1f5e0 --- /dev/null +++ b/src/ipc.cr @@ -0,0 +1,249 @@ + +@[Link("ipc")] +lib LibIPC + struct Service + version : LibC::UInt + index : LibC::UInt + spath : LibC::Char[4096] # [PATH_MAX] + fd : LibC::Int + end + + struct Client + version : LibC::UInt + index : LibC::UInt + fd : LibC::Int + end + + enum MessageType + ServerClose + Error + Data + end + + struct Message + type : UInt8 + length : LibC::UInt + payload : LibC::Char* + end + + struct Clients + clients : Client** + size : LibC::Int + end + + enum EventType + NotSet + Error + Stdin + Connection + Disconnection + Message + end + + struct Event + type : EventType + origin : Client* + m : Message* + end + + # FIXME: IPC.initialize: + # - throw exceptions on error. + # - Make most arguments optional. + fun ipc_server_init(env : LibC::Char**, service : Service*, sname : LibC::Char*) : LibC::Int + # FIXME: IPC.(destroy?) + fun ipc_server_close(Service*) : LibC::Int + fun ipc_server_close_client(Client*) : LibC::Int + + fun ipc_server_accept(Service*, Client*) : LibC::Int + fun ipc_server_read(Client*, Message*) : LibC::Int + fun ipc_server_write(Client*, Message*) : LibC::Int + + fun ipc_server_select(Clients*, Service*, Clients*, LibC::Int*) : LibC::Int + + fun ipc_service_poll_event(Clients*, Service*, Event*) : LibC::Int + + fun ipc_application_connection(LibC::Char**, Service*, LibC::Char*) : LibC::Int + fun ipc_application_close(Service*) : LibC::Int + fun ipc_application_read(Service*, Message*) : LibC::Int + fun ipc_application_write(Service*, Message*) : LibC::Int + + fun ipc_client_add(Clients*, Client*) : LibC::Int + fun ipc_client_del(Clients*, Client*) : LibC::Int + + fun ipc_server_client_copy(Client*) : Client* + fun ipc_server_client_eq(Client*, Client*) : LibC::Int + + fun ipc_server_client_gen(Client*, LibC::UInt, LibC::UInt) + + fun ipc_clients_free(Clients*) +end + +class IPC::Exception < ::Exception + +end + +class IPC::Service + @closed = false + @clients = LibIPC::Clients.new + # FIXME: getter only as long as proper bindings are unfinished + getter service = LibIPC::Service.new + + def initialize(name : String) + if LibIPC.ipc_server_init(LibC.environ, pointerof(@service), name) < 0 + raise Exception.new "ipc_server_init < 0" # FIXME: Add proper descriptions here. + end + + # Very important as there are filesystem side-effects. + at_exit { close } + end + + def initialize(name : String, &block : Proc(IPC::Event::Connection | IPC::Event::Disconnection | IPC::Event::Message, Nil)) + initialize name + loop &block + close + end + + def close + return if @closed + + # FIXME: Probably check it’s not been closed already. + if LibIPC.ipc_server_close(pointerof(@service)) < 0 + raise Exception.new "ipc_server_close < 0" + end + + @closed = true + end + def finalize + close + end + + def accept + ::IPC::Server::Client.new pointerof(@service) + end + + def loop(&block) + ::loop do + event = LibIPC::Event.new + + r = LibIPC.ipc_service_poll_event pointerof(@clients), pointerof(@service), pointerof(event) + + if r < 0 + raise Exception.new "ipc_service_poll_event < 0" + end + + client = IPC::RemoteClient.new event.origin.unsafe_as(Pointer(LibIPC::Client)).value + + pp! event + message = event.m.unsafe_as(Pointer(LibIPC::Message)) + unless message.null? + pp! message.value + end + + case event.type + when LibIPC::EventType::Connection + yield IPC::Event::Connection.new client + when LibIPC::EventType::Message + message = event.m.unsafe_as(Pointer(LibIPC::Message)).value + + yield IPC::Event::Message.new IPC::Message.new(message.type, message.length, message.payload), client + when LibIPC::EventType::Disconnection + yield IPC::Event::Disconnection.new client + end + end + end +end + +class IPC::Message + enum Type + CLOSE + CONNECTION + SYN + ACK + DATA + end + + getter type : UInt8 + getter payload : String + + def initialize(type, length, payload) + @type = type.to_u8 + @payload = String.new payload, length + end +end + +class IPC::RemoteClient + getter client : LibIPC::Client + + def initialize(@client) + end + + def send(type : UInt8, payload : String) + message = LibIPC::Message.new type: type, length: payload.bytesize, payload: payload.to_unsafe + + if LibIPC.ipc_server_write(pointerof(@client), pointerof(message)) < 0 + raise Exception.new "ipc_server_write < 0" + end + end +end + +class IPC::Event + class Connection + getter client : IPC::RemoteClient + def initialize(@client) + end + end + + class Disconnection + getter client : IPC::RemoteClient + def initialize(@client) + end + end + + class Message + getter message : ::IPC::Message + getter client : IPC::RemoteClient + def initialize(@message, @client) + end + end +end + +class IPC::Client + @service = LibIPC::Service.new + + def initialize(@service_name : String) + if LibIPC.ipc_application_connection(LibC.environ, pointerof(@service), @service_name) < 0 + raise Exception.new "ipc_application_connection < 0" + end + end + def initialize(name, &block) + initialize(name) + + yield self + + close + end + + def send(type, payload : String) + message = LibIPC::Message.new type: type, length: payload.bytesize, payload: payload.to_unsafe + + if LibIPC.ipc_application_write(pointerof(@service), pointerof(message)) < 0 + raise Exception.new "ipc_application_write < 0" + end + end + + def read + message = LibIPC::Message.new + if LibIPC.ipc_application_read(pointerof(@service), pointerof(message)) < 0 + raise Exception.new "ipc_application_read < 0" + end + + IPC::Message.new message.type, message.length, message.payload + end + + def close + if LibIPC.ipc_application_close(pointerof(@service)) < 0 + raise Exception.new "ipc_application_close < 0" + end + end +end +