Added SILC Local Network Stream API
authorPekka Riikonen <priikone@silcnet.org>
Sun, 22 Jun 2008 19:57:19 +0000 (22:57 +0300)
committerPekka Riikonen <priikone@silcnet.org>
Sun, 22 Jun 2008 19:57:19 +0000 (22:57 +0300)
Allows simple interprocess communication (IPC) style communication
between processes in local machine.  The implementation uses TCP socket
connection in the local machine.

TODO
lib/silcutil/Makefile.ad
lib/silcutil/silclocalnetstream.c [new file with mode: 0644]
lib/silcutil/silclocalnetstream.h [new file with mode: 0644]
lib/silcutil/silcruntime.h.in
lib/silcutil/tests/Makefile.am
lib/silcutil/tests/test_silclocalnetstream.c [new file with mode: 0644]

diff --git a/TODO b/TODO
index 0cb16895e7d934f9829daa23427408b0d5cf720a..e7693efc7bf6f951fa6c0fa8b2c023527a5e1a94 100644 (file)
--- a/TODO
+++ b/TODO
@@ -16,8 +16,6 @@ Runtime library, lib/silcutil/
 
  o Add SILC Zip API, compression.
 
- o Unix socket support to Socket Stream API (local socket stream).
-
  o file removing, chmod, rmmod, etc. chdir, rmdir, stat, etc. to
    lib/silcutil/silcfileutil.h.
 
index 972b83d68c29c8e5812b97011b20c8bc4602d586..2d565d815a253e84f22cd896ecd28565cb381c63 100644 (file)
@@ -69,6 +69,7 @@ libsilcutil_la_SOURCES = \
        silcrand.c      \
        silcglobal.c    \
        silcbufferstream.c \
+       silclocalnetstream.c \
        silcxml.c
 
 include_HEADERS =      \
@@ -126,6 +127,7 @@ include_HEADERS =   \
        silcruntime.h   \
        silcdir.h       \
        silcbufferstream.h \
+       silclocalnetstream.h \
        silcxml.h
 
 SILC_EXTRA_DIST =
diff --git a/lib/silcutil/silclocalnetstream.c b/lib/silcutil/silclocalnetstream.c
new file mode 100644 (file)
index 0000000..d93e909
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+
+  silclocalnetstream.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2008 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include <silcruntime.h>
+
+/************************** Types and definitions ***************************/
+
+/* Local net listener context */
+typedef struct {
+  SilcNetListener listener;
+  char *filepath;
+  SilcNetCallback callback;
+  void *context;
+} *SilcLocalNetListener;
+
+/************************ Static utility functions **************************/
+
+/* Connection accept callback */
+
+static void silc_local_net_accept(SilcResult result, SilcStream stream,
+                                 void *context)
+{
+  SilcLocalNetListener listener = context;
+  const char *remote_ip;
+
+  if (!silc_socket_stream_get_info(stream, NULL, NULL, &remote_ip, NULL)) {
+    silc_stream_destroy(stream);
+    return;
+  }
+
+  /* Make sure the connection comes from local host */
+  if (strcmp(remote_ip, "127.0.0.1")) {
+    SILC_LOG_DEBUG(("Connection is coming from %s, will not accept",
+                   remote_ip));
+    silc_stream_destroy(stream);
+    return;
+  }
+
+  listener->callback(result, stream, listener->context);
+}
+
+/****************************** Public API **********************************/
+
+/* Create listener */
+
+SilcNetListener silc_local_net_create_listener(const char *filepath,
+                                              SilcSchedule schedule,
+                                              SilcNetCallback callback,
+                                              void *context)
+{
+  SilcLocalNetListener listener;
+  SilcUInt16 *local_port;
+  const char *addr = "127.0.0.1";
+  char port[8];
+
+  SILC_LOG_DEBUG(("Creating local network stream listener %s", filepath));
+
+  if (!filepath || !callback) {
+    silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+    return NULL;
+  }
+
+  /* Make sure file doesn't exist */
+  if (silc_file_stat(filepath, FALSE, NULL)) {
+    silc_set_errno(SILC_ERR_ALREADY_EXISTS);
+    return NULL;
+  }
+
+  listener = silc_calloc(1, sizeof(*listener));
+  if (!listener)
+    return NULL;
+  listener->callback = callback;
+  listener->context = context;
+
+  listener->filepath = silc_strdup(filepath);
+  if (!listener->filepath) {
+    silc_free(listener);
+    return NULL;
+  }
+
+  /* Create local TCP listener */
+  listener->listener =
+    silc_net_tcp_create_listener(&addr, 1, 0, TRUE, FALSE, schedule,
+                                silc_local_net_accept, listener);
+  if (!listener) {
+    silc_free(listener);
+    return NULL;
+  }
+
+  /* Get the bound port */
+  local_port = silc_net_listener_get_port(listener->listener, NULL);
+  if (!local_port) {
+    silc_net_close_listener(listener->listener);
+    silc_free(listener);
+    return NULL;
+  }
+
+  /* Create the file */
+  silc_snprintf(port, sizeof(port), "%d", *local_port);
+  if (silc_file_writefile(filepath, port, strlen(port) + 1)) {
+    silc_free(local_port);
+    silc_net_close_listener(listener->listener);
+    silc_free(listener);
+    return NULL;
+  }
+
+  SILC_LOG_DEBUG(("Created local network stream listener %p", listener));
+
+  silc_free(local_port);
+
+  return (SilcNetListener)listener;
+}
+
+/* Close listener */
+
+void silc_local_net_close_listener(SilcNetListener local_listener)
+{
+  SilcLocalNetListener listener = (SilcLocalNetListener)local_listener;
+
+  SILC_LOG_DEBUG(("Closing local network stream listener %p, %s",
+                 listener, listener->filepath));
+
+  unlink(listener->filepath);
+  silc_net_close_listener(listener->listener);
+  silc_free(listener);
+}
+
+/* Connect to the local network listener */
+
+SilcAsyncOperation silc_local_net_connect(const char *filepath,
+                                         SilcSchedule schedule,
+                                         SilcNetCallback callback,
+                                         void *context)
+{
+  unsigned char *port_data;
+  int port;
+
+  SILC_LOG_DEBUG(("Connecting to local network listner %s", filepath));
+
+  if (!filepath) {
+    silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+    if (callback)
+      callback(silc_errno, NULL, context);
+    return NULL;
+  }
+
+  /* Read the port from file */
+  port_data = silc_file_readfile(filepath, NULL, NULL);
+  if (!port_data) {
+    if (callback)
+      callback(silc_errno, NULL, context);
+    return NULL;
+  }
+  port = atoi(port_data);
+
+  silc_free(port_data);
+
+  /* Connect */
+  return silc_net_tcp_connect("127.0.0.1", "127.0.0.1", port, schedule,
+                             callback, context);
+}
diff --git a/lib/silcutil/silclocalnetstream.h b/lib/silcutil/silclocalnetstream.h
new file mode 100644 (file)
index 0000000..87f62d2
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+
+  silclocalnetstream.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2008 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+/****h* silcutil/Local Network Stream Interface
+ *
+ * DESCRIPTION
+ *
+ * Local network stream interface enables two or more processes to communicate
+ * with each other in the local machine using local network start.  The
+ * interface provides a form of interprocess communication (IPC) using network
+ * sockets.
+ *
+ ***/
+
+#ifndef SILCLOCALNETSTREAM_H
+#define SILCLOCALNETSTREAM_H
+
+/****f* silcutil/silc_local_net_create_listener
+ *
+ * SYNOPSIS
+ *
+ *    SilcNetListener silc_local_net_create_listener(const char *filepath,
+ *                                                   SilcSchedule schedule,
+ *                                                   SilcNetCallback callback,
+ *                                                   void *context);
+ *
+ * DESCRIPTION
+ *
+ *    Creates a local network stream listener and returns a network server.
+ *    The `filepath' is a local filepath that must be used by the clients to
+ *    connect to the server.
+ *
+ *    The `callback' will be called when a client connects to the listener
+ *    with the `context'.  The returned listener must be closed by calling
+ *    silc_local_net_close_listener.
+ *
+ *    Clients can connect to the listener by calling the
+ *    silc_local_net_connect.
+ *
+ *    Returns NULL on error and set silc_errno.  If `schedule' is NULL this
+ *    will call silc_schedule_get_global to try to get global scheduler.
+ *
+ ***/
+SilcNetListener silc_local_net_create_listener(const char *filepath,
+                                              SilcSchedule schedule,
+                                              SilcNetCallback callback,
+                                              void *context);
+
+/****f* silcutil/silc_local_net_close_listener
+ *
+ * SYNOPSIS
+ *
+ *    void silc_local_net_close_listener(SilcNetListener local_listener);
+ *
+ * DESCRIPTION
+ *
+ *    Closes the local network stream listener.
+ *
+ ***/
+void silc_local_net_close_listener(SilcNetListener local_listener);
+
+/****f* silcutil/silc_local_net_connect
+ *
+ * SYNOPSIS
+ *
+ *    SilcAsyncOperation silc_local_net_connect(const char *filepath,
+ *                                              SilcSchedule schedule,
+ *                                              SilcNetCallback callback,
+ *                                              void *context);
+ *
+ * DESCRIPTION
+ *
+ *    Connects to the local network server at the provided `filepath'.  If
+ *    the `filepath' does not exist or is not valid local network listener
+ *    the connection will fail.
+ *
+ *    The `callback' with `context' will be called once the connection has
+ *    been created.
+ *
+ *    If `schedule' is NULL this will call silc_schedule_get_global to try
+ *    to get global scheduler.
+ *
+ ***/
+SilcAsyncOperation silc_local_net_connect(const char *filepath,
+                                         SilcSchedule schedule,
+                                         SilcNetCallback callback,
+                                         void *context);
+
+#endif /* SILCLOCALNETSTREAM_H */
index 2ad645e8d46e484c2e28a05c4d97b8f785d07d6d..257b4dd5853ce8e6fc853252465162594ab1a7d2 100644 (file)
@@ -240,6 +240,7 @@ extern "C" {
 #include <silcmime.h>
 #include <silcrand.h>
 #include <silcbufferstream.h>
+#include <silclocalnetstream.h>
 #include <silcxml.h>
 #include <silchttpserver.h>
 #include <silchttpphp.h>
index bc6937041bbff27ee0f82b1cd8313533f53c19c6..78323eda32d5cc3d581541063495748d427ee83f 100644 (file)
@@ -24,7 +24,8 @@ check_PROGRAMS = \
        test_silcatomic test_silcmutex test_silctime test_silcthread \
        test_silcdll test_silcenv test_silctimer test_silcbitops \
        test_silcregex test_silcbuffmt test_silcdir test_silcthreadqueue \
-       test_silcrand test_silcglobal test_silcbufferstream test_silcxml
+       test_silcrand test_silcglobal test_silcbufferstream test_silcxml \
+       test_silclocalnetstream
 
 TESTS = test_silcstrutil test_silcstringprep test_silchashtable \
        test_silclist test_silcfsm test_silcasync test_silcschedule \
@@ -32,7 +33,8 @@ TESTS = test_silcstrutil test_silcstringprep test_silchashtable \
        test_silcatomic test_silctime test_silcthread \
        test_silcdll test_silcenv test_silctimer test_silcbitops \
        test_silcregex test_silcbuffmt test_silcdir test_silcthreadqueue \
-       test_silcrand test_silcglobal test_silcbufferstream
+       test_silcrand test_silcglobal test_silcbufferstream \
+       test_silclocalnetstream
 
 LIBS = $(SILC_COMMON_LIBS)
 LDADD = -L.. -L../.. -lsrt
diff --git a/lib/silcutil/tests/test_silclocalnetstream.c b/lib/silcutil/tests/test_silclocalnetstream.c
new file mode 100644 (file)
index 0000000..010936e
--- /dev/null
@@ -0,0 +1,205 @@
+/* SILC Local Net Stream API tests */
+
+#include "silcruntime.h"
+
+SilcSchedule schedule;
+
+typedef struct {
+  SilcFSM fsm;
+  SilcFSMEventStruct sema;
+  SilcFSMThreadStruct thread;
+  SilcNetListener server;
+  SilcStream client_stream;
+  SilcResult client_status;
+  SilcStream server_stream;
+  SilcResult server_status;
+  SilcBool success;
+} *Foo;
+
+SILC_FSM_STATE(test_st_start);
+SILC_FSM_STATE(test_st_second);
+SILC_FSM_STATE(test_st_finish);
+
+SILC_FSM_STATE(test_st_connect);
+SILC_FSM_STATE(test_st_connected);
+
+static void test_accept_connection(SilcResult status, SilcStream stream,
+                                  void *context)
+{
+  Foo f = context;
+  SILC_LOG_DEBUG(("Accepted new connection"));
+  f->client_status = status;
+  f->client_stream = stream;
+  SILC_FSM_EVENT_SIGNAL(&f->sema);
+}
+
+static void test_connected(SilcResult status, SilcStream stream,
+                          void *context)
+{
+  Foo f = context;
+  SILC_LOG_DEBUG(("Connected to server"));
+  f->server_status = status;
+  f->server_stream = stream;
+  SILC_FSM_CALL_CONTINUE(&f->thread);
+}
+
+SILC_FSM_STATE(test_st_connect)
+{
+  Foo f = fsm_context;
+
+  SILC_LOG_DEBUG(("test_st_connect"));
+  SILC_LOG_DEBUG(("Connecting to server"));
+
+  silc_fsm_next(fsm, test_st_connected);
+  SILC_FSM_CALL(silc_local_net_connect("local_net",
+                                      silc_fsm_get_schedule(fsm),
+                                      test_connected, f));
+}
+
+SILC_FSM_STATE(test_st_connected)
+{
+  Foo f = fsm_context;
+  const char *host, *ip;
+  SilcUInt16 port;
+
+  SILC_LOG_DEBUG(("test_st_connected"));
+
+  if (f->server_status != SILC_OK) {
+    SILC_LOG_DEBUG(("Creating connection failed"));
+    return SILC_FSM_FINISH;
+  }
+
+  silc_socket_stream_get_info(f->server_stream, NULL, &host, &ip, &port);
+  SILC_LOG_DEBUG(("Connected to server %s, %s:%d", host, ip, port));
+
+  return SILC_FSM_FINISH;
+}
+
+SILC_FSM_STATE(test_st_start)
+{
+  Foo f = fsm_context;
+
+  SILC_LOG_DEBUG(("test_st_start"));
+
+  SILC_LOG_DEBUG(("Creating local network listener"));
+  f->server = silc_local_net_create_listener("local_net",
+                                            silc_fsm_get_schedule(fsm),
+                                            test_accept_connection, f);
+  if (!f->server) {
+    /** Creating network listener failed */
+    SILC_LOG_DEBUG(("Listener creation failed"));
+    silc_fsm_next(fsm, test_st_finish);
+    return SILC_FSM_CONTINUE;
+  }
+
+  /* Create thread to connect to the listener */
+  silc_fsm_thread_init(&f->thread, fsm, f, NULL, NULL, FALSE);
+  silc_fsm_start(&f->thread, test_st_connect);
+
+  /** Start waiting connection */
+  SILC_LOG_DEBUG(("Start waiting for incoming connections"));
+  silc_fsm_event_init(&f->sema, fsm);
+  silc_fsm_next(fsm, test_st_second);
+  return SILC_FSM_CONTINUE;
+}
+
+SILC_FSM_STATE(test_st_second)
+{
+  Foo f = fsm_context;
+  const char *ip, *host;
+  SilcUInt16 port;
+
+  SILC_LOG_DEBUG(("test_st_second"));
+
+  SILC_FSM_EVENT_WAIT(&f->sema);
+
+  if (f->client_status != SILC_OK) {
+    /** Accepting new connection failed */
+    SILC_LOG_DEBUG(("Accepting failed %d", f->client_status));
+    silc_fsm_next(fsm, test_st_finish);
+    return SILC_FSM_CONTINUE;
+  }
+
+  silc_socket_stream_get_info(f->client_stream, NULL, &host, &ip, &port);
+  SILC_LOG_DEBUG(("Accepted new connection %s, %s:%d", host, ip, port));
+
+  /** Wait thread to terminate */
+  f->success = TRUE;
+  silc_fsm_next(fsm, test_st_finish);
+  SILC_FSM_THREAD_WAIT(&f->thread);
+}
+
+SILC_FSM_STATE(test_st_finish)
+{
+  Foo f = fsm_context;
+
+  SILC_LOG_DEBUG(("test_st_finish"));
+
+  if (f->server_stream) {
+    silc_stream_close(f->server_stream);
+    silc_stream_destroy(f->server_stream);
+  }
+  if (f->client_stream) {
+    silc_stream_close(f->client_stream);
+    silc_stream_destroy(f->client_stream);
+  }
+
+  SILC_LOG_DEBUG(("Closing network listener"));
+  if (f->server)
+    silc_local_net_close_listener(f->server);
+
+  SILC_LOG_DEBUG(("Finish machine"));
+  return SILC_FSM_FINISH;
+}
+
+static void destructor(SilcFSM fsm, void *fsm_context,
+                      void *destructor_context)
+{
+  SILC_LOG_DEBUG(("FSM destructor, stopping scheduler"));
+  silc_fsm_free(fsm);
+  silc_schedule_stop(schedule);
+}
+
+int main(int argc, char **argv)
+{
+  SilcBool success = FALSE;
+  SilcFSM fsm;
+  Foo f;
+
+  if (argc > 1 && !strcmp(argv[1], "-d")) {
+    silc_log_debug(TRUE);
+    silc_log_debug_hexdump(TRUE);
+    silc_log_set_debug_string("*net*,*stream*,*errno*");
+  }
+
+  SILC_LOG_DEBUG(("Allocating scheduler"));
+  schedule = silc_schedule_init(0, NULL, NULL, NULL);
+
+  f = silc_calloc(1, sizeof(*f));
+  if (!f)
+    goto err;
+
+  SILC_LOG_DEBUG(("Allocating FSM context"));
+  fsm = silc_fsm_alloc(f, destructor, NULL, schedule);
+  if (!fsm)
+    goto err;
+  silc_fsm_start(fsm, test_st_start);
+  f->fsm = fsm;
+
+  SILC_LOG_DEBUG(("Running scheduler"));
+  silc_schedule(schedule);
+
+  if (!f->success)
+    goto err;
+
+  silc_schedule_uninit(schedule);
+  silc_free(f);
+
+  success = TRUE;
+
+ err:
+  SILC_LOG_DEBUG(("Testing was %s", success ? "SUCCESS" : "FAILURE"));
+  fprintf(stderr, "Testing was %s\n", success ? "SUCCESS" : "FAILURE");
+
+  return !success;
+}