Add fd input (such as socket) event to shill_event

It's understood that the main event loop is changing, but
here's a first cut at describing the sort of things we will
need in that respect.  I'm using this facility for my RTNL
prototype.

BUG=n0ne (this will likely be overwritten)
TEST=Unit tests added to unittest.cc and pass

Change-Id: I4ee89fc4119da0871e9f852707cce9341a5018c0
Reviewed-on: http://gerrit.chromium.org/gerrit/605
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/shill_event.cc b/shill_event.cc
index 6b1356a..b78680c 100644
--- a/shill_event.cc
+++ b/shill_event.cc
@@ -50,4 +50,60 @@
   }
 }
 
+static gboolean DispatchIOHandler(GIOChannel *chan, GIOCondition cond,
+                                  gpointer data);
+
+class GlibIOInputHandler : public IOInputHandler {
+public:
+  GIOChannel *channel_;
+  Callback<InputData *> *callback_;
+  guint source_id_;
+  EventDispatcher *dispatcher_;
+
+  GlibIOInputHandler(EventDispatcher *dispatcher, int fd,
+                     Callback<InputData *> *callback) :
+    dispatcher_(dispatcher), callback_(callback) {
+    channel_ = g_io_channel_unix_new(fd);
+    g_io_channel_set_close_on_unref(channel_, TRUE);
+    source_id_ = g_io_add_watch(channel_,
+                                (GIOCondition)(G_IO_IN | G_IO_NVAL |
+                                               G_IO_HUP | G_IO_ERR),
+                                DispatchIOHandler, this);
+  }
+
+  ~GlibIOInputHandler() {
+    g_source_remove(source_id_);
+    g_io_channel_shutdown(channel_, TRUE, NULL);
+    g_io_channel_unref(channel_);
+  }
+};
+
+static gboolean DispatchIOHandler(GIOChannel *chan, GIOCondition cond,
+                                  gpointer data) {
+  GlibIOInputHandler *handler = static_cast<GlibIOInputHandler *>(data);
+  unsigned char buf[4096];
+  gsize len;
+  GIOError err;
+
+  if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
+    return FALSE;
+
+  err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len);
+  if (err) {
+    if (err == G_IO_ERROR_AGAIN)
+      return TRUE;
+    return FALSE;
+  }
+
+  InputData input_data = { buf, len };
+  handler->callback_->Run(&input_data);
+
+  return TRUE;
+}
+
+IOInputHandler *EventDispatcher::CreateInputHandler(int fd,
+        Callback<InputData *> *callback) {
+  return new GlibIOInputHandler(this, fd, callback);
+}
+
 }  // namespace shill
diff --git a/shill_event.h b/shill_event.h
index 09a85e4..cda8872 100644
--- a/shill_event.h
+++ b/shill_event.h
@@ -97,6 +97,17 @@
   std::vector<Arg> event_queue_;
 };
 
+struct InputData {
+  unsigned char *buf;
+  size_t len;
+};
+
+class IOInputHandler {
+ public:
+  IOInputHandler() {}
+  virtual ~IOInputHandler() {}
+};
+
 // This is the main event dispatcher.  It contains a central instance, and
 // is the entity responsible for dispatching events out of all queues to
 // their listeners during the idle loop.
@@ -106,6 +117,7 @@
   void ExecuteOnIdle();
   void RegisterCallbackQueue(EventQueueItem *queue);
   void UnregisterCallbackQueue(EventQueueItem *queue);
+  IOInputHandler *CreateInputHandler(int fd, Callback<InputData *> *callback);
  private:
   std::vector<EventQueueItem*> queue_list_;
 };
diff --git a/shill_unittest.cc b/shill_unittest.cc
index 5fdf7f2..214a5f7 100644
--- a/shill_unittest.cc
+++ b/shill_unittest.cc
@@ -25,10 +25,14 @@
 class MockEventDispatchTester {
  public:
   explicit MockEventDispatchTester(EventDispatcher *dispatcher)
-    : triggered_(false),
+    : dispatcher_(dispatcher),
+      triggered_(false),
       int_callback_(new ClassCallback<MockEventDispatchTester, int>(this,
-		    &MockEventDispatchTester::HandleInt)),
-      int_callback_queue_(dispatcher) {
+              &MockEventDispatchTester::HandleInt)),
+      int_callback_queue_(dispatcher),
+      got_data_(false),
+      data_callback_(new ClassCallback<MockEventDispatchTester, InputData *>
+              (this, &MockEventDispatchTester::HandleData)) {
     int_callback_queue_.AddCallback(int_callback_);
     timer_source_ = g_timeout_add_seconds(1,
       &MockEventDispatchTester::CallbackFunc, this);
@@ -60,11 +64,34 @@
   void ResetTrigger() { triggered_ = false; }
   void RemoveCallback() { int_callback_queue_.RemoveCallback(int_callback_); }
 
+
+  void HandleData(InputData *inputData) {
+    printf("MockEventDispatchTester handling data len %d %.*s\n",
+           inputData->len, inputData->len, inputData->buf);
+    got_data_ = true;
+    IOComplete(inputData->len);
+  }
+  bool GetData() { return got_data_; }
+
+  void ListenIO(int fd) {
+    input_handler_ = dispatcher_->CreateInputHandler(fd, data_callback_);
+  }
+
+  void StopListenIO() {
+    got_data_ = false;
+    delete(input_handler_);
+  }
+
   MOCK_METHOD1(CallbackComplete, void(int));
+  MOCK_METHOD1(IOComplete, void(int));
  private:
+  EventDispatcher *dispatcher_;
   bool triggered_;
   Callback<int> *int_callback_;
   EventQueue<int>int_callback_queue_;
+  bool got_data_;
+  Callback<InputData *> *data_callback_;
+  IOInputHandler *input_handler_;
   int timer_source_;
   static gboolean CallbackFunc(gpointer data) {
     static int i = 0;
@@ -111,6 +138,21 @@
   for (int main_loop_count = 0;
        main_loop_count < 6 && !dispatcher_test_.GetTrigger(); ++main_loop_count)
     g_main_context_iteration(NULL, TRUE);
+
+
+  EXPECT_CALL(dispatcher_test_, IOComplete(16));
+  int pipefd[2];
+  EXPECT_EQ(pipe(pipefd), 0);
+
+  dispatcher_test_.ListenIO(pipefd[0]);
+  EXPECT_EQ(write(pipefd[1], "This is a test?!", 16), 16);
+
+  // Crank the glib main loop a few times
+  for (int main_loop_count = 0;
+       main_loop_count < 6 && !dispatcher_test_.GetData(); ++main_loop_count)
+    g_main_context_iteration(NULL, TRUE);
+
+  dispatcher_test_.StopListenIO();
 }
 
 }