First commit.
This commit is contained in:
commit
448b56ee3a
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/docs/
|
||||||
|
/lib/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
||||||
|
old
|
||||||
|
shard.lock
|
||||||
|
*.log
|
8
makefile
Normal file
8
makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
doc:
|
||||||
|
crystal docs
|
||||||
|
|
||||||
|
DOC_HTTPD_ACCESS_LOGS ?= /tmp/access.log
|
||||||
|
DOC_HTTPD_ADDRESS ?= 127.0.0.1
|
||||||
|
DOC_HTTPD_PORT ?= 9000
|
||||||
|
serve-doc:
|
||||||
|
darkhttpd docs/ --addr $(DOC_HTTPD_ADDRESS) --port $(DOC_HTTPD_PORT) --log $(DOC_HTTPD_ACCESS_LOGS)
|
20
shard.yml
Normal file
20
shard.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
name: ipc
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Philippe Pittoli <karchnu@karchnu.fr>
|
||||||
|
|
||||||
|
description: |
|
||||||
|
High-level Crystal bindings to libipc.
|
||||||
|
|
||||||
|
crystal: 1.7.1
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
cbor:
|
||||||
|
git: https://git.baguette.netlib.re/Baguette/crystal-cbor
|
||||||
|
branch: master
|
||||||
|
|
||||||
|
libraries:
|
||||||
|
libipc: ">= 0.1"
|
||||||
|
|
||||||
|
license: ISC
|
37
src/bindings.cr
Normal file
37
src/bindings.cr
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@[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
|
60
src/cbor.cr
Normal file
60
src/cbor.cr
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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
|
134
src/high-level-bindings.cr
Normal file
134
src/high-level-bindings.cr
Normal 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
|
59
src/json.cr
Normal file
59
src/json.cr
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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
|
3
src/main.cr
Normal file
3
src/main.cr
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
require "./bindings.cr"
|
||||||
|
require "./message.cr"
|
||||||
|
require "./high-level-bindings.cr"
|
75
src/message.cr
Normal file
75
src/message.cr
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# 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
|
Loading…
Reference in New Issue
Block a user