void forwarder_deinit(forwarder_t *f)
{
  su_root_unregister(f->f_pr->pr_root, f->f_wait, NULL, f);
  su_wait_destroy(f->f_wait);
  su_root_unregister(f->f_pr->pr_root, f->f_wait + 1, NULL, f);
  su_wait_destroy(f->f_wait + 1);
  if (f->f_socket != INVALID_SOCKET)
    su_close(f->f_socket), f->f_socket = INVALID_SOCKET;
  if (f->f_buf)
    su_free(f->f_pr->pr_home, f->f_buf), f->f_buf = NULL;
}
int forwarder_shutdown(forwarder_t *f)
{
  forwarder_t *f_peer = f->f_peer;
  su_sockaddr_t *su = f->f_dest;
  char buf[SU_ADDRSIZE];

  SU_DEBUG_3(("forwarder_shutdown: shutdown from %s:%u\n",
	      su_inet_ntop(su->su_family, SU_ADDR(su), buf, sizeof(buf)),
	      ntohs(su->su_port)));

  if (su_root_unregister(f->f_pr->pr_root, f->f_wait, forwarder_recv, f) < 0) {
    SU_DEBUG_1(("%s: su_root_unregister failed\n", __func__));
  }

  if (shutdown(f->f_socket, 0) < 0) {
    SU_DEBUG_1(("shutdown(0): %s\n", su_strerror(su_errno())));
  }
  f_peer->f_shutdown = 1;
  if (!f_peer->f_buf) {
    if (shutdown(f_peer->f_socket, 1) < 0) {
      SU_DEBUG_1(("shutdown(1): %s\n", su_strerror(su_errno())));
    }
    if (f->f_shutdown) {
      forwarder_close(f);
    }
  }
  return 0;
}
/** Connection is complete. */
int forwarder_connected(proxy_t *pr, su_wait_t *w, forwarder_t *f)
{
  int events, error;
  forwarder_t *f_peer;

  events = su_wait_events(w, f->f_socket);

  error = su_soerror(f->f_socket);

  if (error) {
    SU_DEBUG_1(("connect: %s\n", su_strerror(error)));
    forwarder_destroy(f);
    return 0;
  }

  su_root_unregister(pr->pr_root, f->f_wait + 1, forwarder_connected, f);

  /* Wait for data, forward it to peer */
  assert(f->f_peer);
  f_peer = f->f_peer;
  su_root_register(pr->pr_root, f->f_wait, forwarder_recv, f, 0);
  su_root_register(pr->pr_root, f_peer->f_wait, forwarder_recv, f_peer, 0);

  return 0;
}
/** Unregister standard input. */
static void sofsip_deinit(cli_t *cli)
{
  if (cli->cli_init) {
    cli->cli_init = 0;
    if (su_root_unregister(cli->cli_root, 
			   &cli->cli_input, 
			   sofsip_handle_input, 
			   NULL) == SOCKET_ERROR) {
      su_perror("su_root_unregister");
    }

    su_wait_destroy(&cli->cli_input);

    ssc_input_remove_handler();

    /* g_main_loop_quit(cli->cli_gmain); */
  }
}
/** Receive data, forward it to peer */
int forwarder_recv(proxy_t *pr, su_wait_t *w, forwarder_t *f)
{
  buffer_t b[1];
  int n, events;

  events = su_wait_events(w, f->f_socket);

  n = recv(f->f_socket, b->b_data, sizeof(b->b_data), 0);

  if (n > 0) {
    b->b_sent = 0; b->b_used = n;
    if (f->f_peer->f_buf) {
      forwarder_append(f, b);
      return 0;
    }
    if (forwarder_send(pr, f->f_peer, b) >= 0) {
      if (b->b_sent < b->b_used) {
	su_root_unregister(pr->pr_root, w, forwarder_recv, f);
	su_root_register(pr->pr_root, f->f_peer->f_wait + 1,
			 forwarder_empty, f->f_peer, 0);
	forwarder_append(f, b);
      }
      return 0;
    }
    else {
      /* Error when sending */
    }
  }
  if (n < 0) {
    int error = su_errno();
    SU_DEBUG_1(("recv: %s\n", su_strerror(error)));

    if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK) {
      return 0;
    }
    /* XXX */
    forwarder_destroy(f);
  }

  /* shutdown */
  forwarder_shutdown(f);

  return 0;
}
/** Empty forwarder buffers */
int forwarder_empty(proxy_t *pr, su_wait_t *w, forwarder_t *f)
{
  buffer_t *b;
  int n, events;

  events = su_wait_events(w, f->f_socket);

  while ((b = f->f_buf)) {
    n = forwarder_send(f->f_pr, f, b);
    if (n == 0) {
      if ((f->f_buf = b->b_next))
	b->b_next->b_prev = &f->f_buf;
      su_free(f->f_pr->pr_home, b);
      continue;
    }
    else if (n < 0) {
      /* XXX */
    }
    break;
  }

  if (!f->f_buf) {
    forwarder_t *f_peer = f->f_peer;

    su_root_unregister(pr->pr_root, w, forwarder_empty, f);

    if (!f->f_shutdown) {
      /* Buffer is empty - start receiving */
      su_root_register(pr->pr_root, f_peer->f_wait, forwarder_recv, f_peer, 0);
    }
    else {
      if (shutdown(f->f_socket, 1) < 0) {
	SU_DEBUG_1(("shutdown(1): %s\n", su_strerror(su_errno())));
      }
      if (f_peer->f_shutdown) {
	forwarder_close(f);
      }
    }
  }

  return 0;
}
static int register_test(root_test_t *rt)
{
  int i;
  int s;
  char msg[3] = "foo";

  BEGIN();

  TEST_1((s = su_socket(rt->rt_family, SOCK_DGRAM, 0)) != -1);

  for (i = 0; i < 5; i++) {
    rt->rt_ep[i]->registered =
      su_root_register(rt->rt_root, rt->rt_ep[i]->wait,
		       wakeups[i], rt->rt_ep[i], 0);
    TEST(rt->rt_ep[i]->registered, i + 1 + SU_HAVE_PTHREADS);
  }

  for (i = 0; i < 5; i++) {
    test_ep_t *ep = rt->rt_ep[i];
    TEST(su_sendto(s, msg, sizeof(msg), 0, ep->addr, ep->addrlen),
	 sizeof(msg));
    test_run(rt);
    TEST(rt->rt_received, i);
    TEST(rt->rt_wakeup, i);
  }

  for (i = 0; i < 5; i++) {
    TEST(su_root_unregister(rt->rt_root, rt->rt_ep[i]->wait,
			    wakeups[i], rt->rt_ep[i]),
	 rt->rt_ep[i]->registered);
  }


  for (i = 0; i < 5; i++) {
    rt->rt_ep[i]->registered =
      su_root_register(rt->rt_root, rt->rt_ep[i]->wait,
		       wakeups[i], rt->rt_ep[i], 1);
    TEST_1(rt->rt_ep[i]->registered > 0);
  }

  for (i = 0; i < 5; i++) {
    test_ep_t *ep = rt->rt_ep[i];
    TEST(su_sendto(s, msg, sizeof(msg), 0, ep->addr, ep->addrlen),
	 sizeof(msg));
    test_run(rt);
    TEST(rt->rt_received, i);
    TEST(rt->rt_wakeup, i);
  }

  for (i = 0; i < 5; i++) {
    TEST(su_root_deregister(rt->rt_root, rt->rt_ep[i]->registered),
	 rt->rt_ep[i]->registered);
  }

  for (i = 0; i < 5; i++) {
    test_ep_t *ep = rt->rt_ep[i];
    TEST_1(su_wait_create(ep->wait, ep->s, SU_WAIT_IN|SU_WAIT_ERR) != -1);
    ep->registered =
      su_root_register(rt->rt_root, ep->wait,
		       wakeups[i], ep, 1);
    TEST_1(ep->registered > 0);
  }

  for (i = 0; i < 5; i++) {
    test_ep_t *ep = rt->rt_ep[i];
    TEST(su_sendto(s, msg, sizeof(msg), 0, ep->addr, ep->addrlen),
	 sizeof(msg));
    test_run(rt);
    TEST(rt->rt_received, i);
    TEST(rt->rt_wakeup, i);
  }

  for (i = 0; i < 5; i++) {
    TEST(su_root_unregister(rt->rt_root, rt->rt_ep[i]->wait,
			    wakeups[i], rt->rt_ep[i]),
	 rt->rt_ep[i]->registered);
  }

  END();
}