Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

telephone_switch.c

/* $Id: telephone_switch.c,v 1.39 2009-01-28 12:59:22 potyra Exp $ 
 *
 * Copyright (C) 2007-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include "config.h"

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "glue-log.h"
#include "glue-main.h"
#include "glue-shm.h"

#include "cim_telephone.h"
#include "sig_boolean.h"

#include "telephone_switch.h"

#define COMP      "telephone_switch"

/* ********************* DEFINITIONS ************************** */

/* timeout until a number must be dialed */
#define TIMEOUT_DIAL          30 * TIME_HZ
/* timeout for connection attempts (in seconds) */
#define TIMEOUT_DIAL_ANSWER   60 * TIME_HZ
/* timeout for established connections (in seconds) */
#define TIMEOUT_CONN          360 * TIME_HZ
/* timeout after a ring message should be sent again */
#define TIMEOUT_RING          4 * TIME_HZ


/* ******************* MAKRO DEFINITIONS ********************** */

/* print message msg with prog_name and number of p as argument */
#define DPRINT(msg, p) \
      faum_log(FAUM_LOG_DEBUG, "telephone-switch", "", msg, (p)->number);

/* print message msg with prog_name, number of p1 and p2 as argument */
#define DPRINT2(msg, p1, p2) \
      faum_log(FAUM_LOG_DEBUG, "telephone-switch", "", msg, \
                  (p1)->number, \
                  (p2)->number);

/* ****************** GLOBAL VARIABLES ************************ */

/** state of one connection */
enum t_state {
      UNCONNECTED,      /**< not connected */
      CONNECTED,  /**< connected with peer */
      DIALT,            /**< has recvd. dialtone, but not dialed yet */
      CONN_REQ,   /**< has dialed a number but not yet connected */
};

struct conn_state_s {
      enum t_state state;     /**< state of termination */
      struct timeval last_t;  /**< last time, an incoming packet was seen */
      /* FIXME should go away */
      struct sig_telephone *stp; /**< telephone signal */
      unsigned int peer_idx; /**< peer index, -1 for unset */
      uint32_t number;        /**< associated number */
      /* FIXME should go away */
      struct sig_boolean *carrier; /**< carrier signal */
      /* FIXME should go away */
      struct sig_integer *connected; /**< connection established */
};


struct cpssp {
      /* Config */

      /* Ports */
      struct sig_boolean *port_power;

      struct sig_telephone *port_phone0;
      struct sig_telephone *port_phone1;
      struct sig_telephone *port_phone2;

      struct sig_boolean *port_carrier0;
      struct sig_boolean *port_carrier1;
      struct sig_boolean *port_carrier2;

      struct sig_integer *port_connected0;
      struct sig_integer *port_connected1;
      struct sig_integer *port_connected2;

      /* Signals */

      /* State */
      struct conn_state_s state[3];
      /** telephone-switch powered on? */
      bool power_state;

      /* Processes */
};

/** generic callback */
typedef void (*telephone_callback)(void *);

/* ****************** FORWARD DECLARATIONS ******************** */

static void
add_timer_ring(struct cpssp *cpssp, int sender_idx);


/* ******************** IMPLEMENTAION ************************* */

static void
send_ctrl(
      struct cpssp *cpssp, 
      enum sig_telephone_protocol ctrl, 
      int recv_idx
)
{
      struct sig_telephone *dest_port;
      switch (recv_idx) {
      case 0:     
            dest_port = cpssp->port_phone0;
            break;

      case 1:
            dest_port = cpssp->port_phone1;
            break;

      case 2:
            dest_port = cpssp->port_phone2;
            break;
      
      default:
            assert(0);
      }

      sig_telephone_send_ctrl(dest_port, cpssp, ctrl);
}

static int
telephone_get_idx(const struct cpssp *cpssp, const struct conn_state_s *cs)
{
      int i;
      for (i = 0; i < 3; i++) {
            if (&cpssp->state[i] == cs) {
                  return i;
            }
      }

      /* not found: return -1 */
      return -1;
}

static void
telephone_reset(struct cpssp *cpssp, bool shutdown_conns)
{
      int i;

      for (i = 0; i < 3; i++) {
            if (shutdown_conns) {
                  switch (cpssp->state[i].state) {
                  case UNCONNECTED: 
                        break;
                  case DIALT:
                        send_ctrl(cpssp, SIG_TELE_BUSY, i);
                        break;
                  case CONNECTED:
                  case CONN_REQ:
                        send_ctrl(cpssp, SIG_TELE_HANGUP, i);
                        break;
                  }
            }
            cpssp->state[i].state = UNCONNECTED;
            cpssp->state[i].peer_idx = -1;
      
            sig_boolean_set(cpssp->state[i].carrier, cpssp, 0);
            sig_integer_set(cpssp->state[i].connected, cpssp, -1);
      }
}

/* timeout callback after lifting the receiver and number getting called.
 */
static void
timer_dial(struct cpssp *cpssp, int sender_idx)
{
      struct conn_state_s *sender = &cpssp->state[sender_idx];

      assert(sender);

      if (sender->state != DIALT) {
            return;
      }

      send_ctrl(cpssp, SIG_TELE_BUSY, sender_idx);
      sender->state = UNCONNECTED;

      sig_boolean_set(sender->carrier, cpssp, 0);

      DPRINT("%d dialt timeout\n", sender);
}

#define DEF_CB_TIMER_DIAL(NUMBER) \
static void \
timer_dial ##NUMBER(void *_cpssp) \
{\
      struct cpssp *cpssp = (struct cpssp *)_cpssp;\
      timer_dial(cpssp, NUMBER);\
}

DEF_CB_TIMER_DIAL(0)
DEF_CB_TIMER_DIAL(1)
DEF_CB_TIMER_DIAL(2)

#undef DEF_CB_TIMER_DIAL

/* timeout until peer may answer the phone. */
static void
timer_dial_answer(struct cpssp *cpssp, int sender_idx)
{
      struct conn_state_s *sender = &cpssp->state[sender_idx];

      assert(sender);

      if (sender->state != CONN_REQ) {
            return;
      }

      assert(sender->peer_idx != -1);
      assert(cpssp->state[sender->peer_idx].peer_idx == sender_idx);

      send_ctrl(cpssp, SIG_TELE_BUSY, sender_idx);
      sender->state = UNCONNECTED;
      cpssp->state[sender->peer_idx].peer_idx = -1;
      sender->peer_idx = -1;
      sig_boolean_set(sender->carrier, cpssp, 0);

      DPRINT("%d dial timeout\n", sender);
}

#define DEF_CB_TIMER_DIAL_ANSWER(NUMBER)\
static void \
timer_dial_answer ##NUMBER(void *_cpssp) \
{ \
      struct cpssp *cpssp = (struct cpssp *)_cpssp; \
      timer_dial_answer(cpssp, NUMBER); \
}

DEF_CB_TIMER_DIAL_ANSWER(0)
DEF_CB_TIMER_DIAL_ANSWER(1)
DEF_CB_TIMER_DIAL_ANSWER(2)

#undef DEF_CB_TIMER_DIAL_ANSWER

/* timeout, in case an established connection didn't have traffic. */
static void
timer_conn_established(struct cpssp *cpssp, int sender_idx)
{
      struct conn_state_s *sender = &cpssp->state[sender_idx];
      struct conn_state_s *peer;

      assert(sender);
      
      if (sender->state != CONNECTED) {
            return;
      }

      assert(sender->peer_idx != -1);
      peer = &cpssp->state[sender->peer_idx];
      assert(peer->peer_idx == sender_idx);

      send_ctrl(cpssp, SIG_TELE_BUSY, sender_idx);
      send_ctrl(cpssp, SIG_TELE_BUSY, sender->peer_idx);
      peer->state = UNCONNECTED;
      peer->peer_idx = -1;
      sender->state = UNCONNECTED;
      sender->peer_idx = -1;

      /* FIXME use cpssp */
      sig_boolean_set(sender->carrier, cpssp, 0);
      sig_integer_set(sender->connected, cpssp, -1);
      sig_boolean_set(peer->carrier, cpssp, 0);
      sig_integer_set(peer->connected, cpssp, -1);

      DPRINT2("conn between %d and %d timeout\n", sender, peer);
}

#define DEF_CB_TIMER_CONN_ESTABLISHED(NUMBER) \
static void \
timer_conn_established ##NUMBER(void *_cpssp) \
{\
      struct cpssp *cpssp = (struct cpssp*)_cpssp;\
      timer_conn_established(cpssp, NUMBER); \
}

DEF_CB_TIMER_CONN_ESTABLISHED(0)
DEF_CB_TIMER_CONN_ESTABLISHED(1)
DEF_CB_TIMER_CONN_ESTABLISHED(2)

#undef DEF_CB_TIMER_CONN_ESTABLISHED

/* timout, when the next RING should be sent. */
static void
timer_ring(struct cpssp *cpssp, int sender_idx) 
{
      struct conn_state_s *sender = &cpssp->state[sender_idx];
      struct conn_state_s *peer; 

      assert(sender);

      if (sender->state != UNCONNECTED) {
            return;
      }
      
      if (sender->peer_idx == -1) {
            return;
      }

      peer = &cpssp->state[sender->peer_idx];
      assert(peer->peer_idx == sender_idx);

      if (peer->state != CONN_REQ) {
            return;
      }

      send_ctrl(cpssp, SIG_TELE_RING, sender_idx);
      add_timer_ring(cpssp, sender_idx);
}

#define DEF_CB_TIMER_RING(NUMBER)\
static void \
timer_ring ##NUMBER(void *_cpssp)\
{ \
      struct cpssp *cpssp = (struct cpssp*)_cpssp;\
      timer_ring(cpssp, NUMBER);\
}

DEF_CB_TIMER_RING(0)
DEF_CB_TIMER_RING(1)
DEF_CB_TIMER_RING(2)

#undef DEF_CB_TIMER_RING

static void
update_timer_dial(struct cpssp *cpssp, int sender_idx)
{
      static telephone_callback tdcb[3] = {
            timer_dial0, timer_dial1, timer_dial2
      };

      time_call_delete(tdcb[sender_idx], cpssp);
      time_call_after(TIMEOUT_DIAL, tdcb[sender_idx], cpssp);
}

static void
update_timer_dial_answer(struct cpssp *cpssp, int sender_idx)
{
      static telephone_callback tdacb[3] = {
            timer_dial_answer0, timer_dial_answer1, timer_dial_answer2
      };

      time_call_delete(tdacb[sender_idx], cpssp);
      time_call_after(TIMEOUT_DIAL_ANSWER, tdacb[sender_idx], cpssp);
}

static void
update_timer_conn_established(struct cpssp *cpssp, int sender_idx)
{
      static telephone_callback tcecb[3] = {
            timer_conn_established0, 
            timer_conn_established1, 
            timer_conn_established2
      };

      time_call_delete(tcecb[sender_idx], cpssp);
      time_call_after(TIMEOUT_CONN, tcecb[sender_idx], cpssp);
}

static void
add_timer_ring(struct cpssp *cpssp, int sender_idx)
{
      static telephone_callback rcb[3] = { 
            timer_ring0, timer_ring1, timer_ring2 
      };

      time_call_after(TIMEOUT_RING, rcb[sender_idx], cpssp);
}

/* returns connection structure for given peer of NULl if no such peer */
static struct conn_state_s *
lookup_number(struct cpssp *cpssp, uint32_t number)
{
      unsigned int i;

      for (i = 0; i < 3; i++) {
            if (cpssp->state[i].number == number) {
                  return &cpssp->state[i];
            }
      }

      return NULL;
}

static void
telephone_power_set(void *_cpssp, unsigned int val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      telephone_reset(cpssp, ! val);

        cpssp->power_state = val;
}

static void
telephone_recv_data(
      struct cpssp *cpssp, 
      int sender_idx,
      uint8_t data
)
{
      struct conn_state_s *peer;
      struct conn_state_s *sender = &cpssp->state[sender_idx];

      if (! cpssp->power_state) {
            return;
      }
      
      if (sender->state == CONNECTED) {
            assert(sender->peer_idx != -1);
            peer = &cpssp->state[sender->peer_idx];
            assert(peer->state == CONNECTED);
            sig_telephone_send_data(peer->stp, cpssp, data);
            update_timer_conn_established(cpssp, sender_idx);
            /* FIXME? maybe peers timer should only be updated,
             *        if peer sends some data, not if sender does.
             */
            update_timer_conn_established(cpssp, sender->peer_idx);

      } else {
            DPRINT("received data from unconnected termination %d\n", 
                  sender);
      }
}

#define DEF_CB_RECV_DATA(NUMBER) \
static void \
telephone_recv_data ##NUMBER(void *_cpssp, uint8_t data)\
{\
      struct cpssp *cpssp = (struct cpssp *)_cpssp;\
\
      telephone_recv_data(cpssp, NUMBER, data);\
}

DEF_CB_RECV_DATA(0)
DEF_CB_RECV_DATA(1)
DEF_CB_RECV_DATA(2)
#undef DEF_CB_RECV_DATA

static void
telephone_recv_ctrl(
      struct cpssp *cpssp, 
      int sender_idx, 
      enum sig_telephone_protocol event
)
{
      struct conn_state_s *sender = &cpssp->state[sender_idx];
      struct conn_state_s *peer;
      int peer_idx;

      if (! cpssp->power_state) {
            return;
      }

      peer_idx = sender->peer_idx;
      if (peer_idx != -1) {
            peer = &cpssp->state[peer_idx];
      } else {
            peer = NULL;
      }

      switch (sender->state) {
      case UNCONNECTED:
            switch (event) {
            case SIG_TELE_LIFT:
                  if (peer_idx != -1) {
                        if (peer->state == CONN_REQ) {
                              /* idx got a call from peer */
                              sender->state = CONNECTED;
                              peer->state = CONNECTED;
                              update_timer_conn_established(
                                    cpssp,
                                    sender_idx);

                              update_timer_conn_established(
                                    cpssp,
                                    peer_idx);

                              sig_integer_set(
                                    sender->connected, 
                                    cpssp, 
                                    peer->number);

                              sig_boolean_set(
                                    sender->carrier, 
                                    cpssp, 
                                    1);

                              sig_integer_set(
                                    peer->connected, 
                                    cpssp,
                                    sender->number);

                              sig_boolean_set(
                                    peer->carrier, 
                                    cpssp, 
                                    1);

                              send_ctrl(cpssp, 
                                    SIG_TELE_CONNECT,
                                    peer_idx);

                              return;
                        } 
                        /* otherwise fall thru to invalid protocol */
                  } else {
                        /* someone lifts the receiver and wants to dial */
                        sender->state = DIALT;
                        update_timer_dial(cpssp, sender_idx);

                        sig_boolean_set(sender->carrier, cpssp, 1);
                        send_ctrl(cpssp, SIG_TELE_DIALTONE, 
                              sender_idx);

                        DPRINT("%d lifted the receiver\n", sender);
                        return;
                  }
            default:
                  /* invalid protocol used */
                  DPRINT("termination %d sent wrong protocol\n", 
                            sender);
                  break;
            }
            break;

      case CONNECTED:
            assert(peer_idx != -1);

            switch (event) {
            case SIG_TELE_HANGUP:
                  DPRINT2("%d sent hangup (connected with %d)\n",
                        sender, peer);
                  /* send occ to peer (notification of hangup) */
                  /* reset conn states */
                  sender->state = UNCONNECTED;
                  peer->state = UNCONNECTED;

                  sender->peer_idx = -1;
                  peer->peer_idx = -1;

                  sig_integer_set(sender->connected, cpssp, -1);
                  sig_boolean_set(sender->carrier, cpssp, 0);
                  sig_integer_set(peer->connected, cpssp, -1);
                  sig_boolean_set(peer->carrier, cpssp, 0);
                  send_ctrl(cpssp, SIG_TELE_BUSY, peer_idx);
                  break;

            default:
                  /* invalid protocol used */
                  DPRINT("termination %d sent wrong protocol\n", sender);
                  break;
            }
            break;

      case CONN_REQ:
            switch (event) {
            case SIG_TELE_HANGUP:
                  assert(peer_idx != -1);
                  sender->state = UNCONNECTED;
                  peer->state = UNCONNECTED;
                  sender->peer_idx = -1;
                  peer->peer_idx = -1;
                  
                  sig_boolean_set(sender->carrier, cpssp, 0);
                  sig_boolean_set(peer->carrier, cpssp, 0);
                  
                  DPRINT("hangup after dialing from %d\n", sender);
                  break;
            default:
                  /* invalid protocol used */
                  DPRINT("termination %d sent wrong protocol\n", sender);
                  break;
            }
            break;

      case DIALT:
            switch (event) {
            case SIG_TELE_HANGUP:
                  sender->state = UNCONNECTED;
                  sig_boolean_set(sender->carrier, cpssp, 0);

                  DPRINT("%d hung up before dialing\n", sender);
                  break;
            default:
                  break;
            }
            break;

      default:
            assert(0);
      }
}


#define DEF_CB_RECV_CTRL(NUMBER)\
static void \
telephone_recv_ctrl ##NUMBER(void *_cpssp, enum sig_telephone_protocol event)\
{\
      struct cpssp *cpssp = (struct cpssp *)_cpssp;\
\
      telephone_recv_ctrl(cpssp, NUMBER, event);\
}

DEF_CB_RECV_CTRL(0)
DEF_CB_RECV_CTRL(1)
DEF_CB_RECV_CTRL(2)

#undef DEF_CB_RECV_CTRL

static void
telephone_recv_dial(
      struct cpssp *cpssp, 
      int sender_idx,
      uint32_t number
)
{
      struct conn_state_s *peer;
      struct conn_state_s *sender = &cpssp->state[sender_idx];

      if (! cpssp->power_state) {
            return;
      }

      if (sender->state != DIALT) {
            DPRINT("received dial event in wrong state from %d\n",
                  sender);
            return;
      }

      peer = lookup_number(cpssp, number);
      if (peer == NULL) {
            /* no such number */
            sender->state = UNCONNECTED;
            sig_boolean_set(sender->carrier, cpssp, 0);
            
            DPRINT("%d dialed invalid number\n", sender);
            faum_log(FAUM_LOG_DEBUG, "telephone-switch", "",
                        "which was %d\n", number);
            send_ctrl(cpssp, SIG_TELE_INVALID_NUMBER, sender_idx);

      } else if (peer == sender) {
            /* dialing own number -> occupied */
            sender->state = UNCONNECTED;
            sig_boolean_set(sender->carrier, cpssp, 0);

            DPRINT("%d dialed own number\n", sender);
            send_ctrl(cpssp, SIG_TELE_BUSY, sender_idx);

      } else {
            if (peer->state != UNCONNECTED) {
                  sender->state = UNCONNECTED;
                  DPRINT2("%d dialed %d, but busy\n",
                        sender, peer);

                  sig_boolean_set(sender->carrier, cpssp, 0);
                  send_ctrl(cpssp, SIG_TELE_BUSY, sender_idx);
            } else {
                  /* peer must be valid */
                  
                  sender->state = CONN_REQ;
                  /* peer remains unconnected! */
                  
                  sender->peer_idx = telephone_get_idx(cpssp, peer);
                  peer->peer_idx = sender_idx;
                  update_timer_dial_answer(cpssp, sender_idx);
                  add_timer_ring(cpssp, telephone_get_idx(cpssp, peer));
                  send_ctrl(cpssp, SIG_TELE_RING, 
                        telephone_get_idx(cpssp, peer));
                  
                  DPRINT2("%d dialed %d (success)\n",
                        sender, peer);
            }
      }
}

#define DEF_CB_RECV_DIAL(NUMBER)\
static void \
telephone_recv_dial ##NUMBER(void *_cpssp, uint32_t number)\
{\
      struct cpssp *cpssp = (struct cpssp *)_cpssp;\
\
      telephone_recv_dial(cpssp, NUMBER, number);\
}

DEF_CB_RECV_DIAL(0)
DEF_CB_RECV_DIAL(1)
DEF_CB_RECV_DIAL(2)

#undef DEF_CB_RECV_DIAL

void
telephone_switch_init(
      unsigned int nr,
      struct sig_boolean *port_switch,
      struct sig_telephone *port_phone0,
      struct sig_telephone *port_phone1,
      struct sig_telephone *port_phone2,
      struct sig_boolean *port_carrier0,
      struct sig_boolean *port_carrier1,
      struct sig_boolean *port_carrier2,
      struct sig_integer *port_connected0,
      struct sig_integer *port_connected1,
      struct sig_integer *port_connected2
)
{
      static struct sig_boolean_funcs tpf = {
            .set = telephone_power_set
      };

      static struct sig_telephone_funcs tf0 = {
            .recv_data = telephone_recv_data0,
            .recv_ctrl = telephone_recv_ctrl0,
            .recv_dial = telephone_recv_dial0
      };

      static struct sig_telephone_funcs tf1 = {
            .recv_data = telephone_recv_data1,
            .recv_ctrl = telephone_recv_ctrl1,
            .recv_dial = telephone_recv_dial1
      };

      static struct sig_telephone_funcs tf2 = {
            .recv_data = telephone_recv_data2,
            .recv_ctrl = telephone_recv_ctrl2,
            .recv_dial = telephone_recv_dial2
      };

      struct cpssp *cpssp;
      unsigned int i;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      for (i = 0; i < 3; i++) {
            cpssp->state[i].state = UNCONNECTED;
            cpssp->state[i].peer_idx = -1;
      }

      cpssp->port_power = port_switch;

      cpssp->port_phone0 = port_phone0;
      cpssp->port_phone1 = port_phone1;
      cpssp->port_phone2 = port_phone2;

      cpssp->port_carrier0 = port_carrier0;
      cpssp->port_carrier1 = port_carrier1;
      cpssp->port_carrier2 = port_carrier2;

      cpssp->port_connected0 = port_connected0;
      cpssp->port_connected1 = port_connected1;
      cpssp->port_connected2 = port_connected2;

      /* FIXME should all be handled via cpssp only! */
      cpssp->state[0].stp = port_phone0;
      cpssp->state[1].stp = port_phone1;
      cpssp->state[2].stp = port_phone2;
      cpssp->state[0].carrier = port_carrier0;
      cpssp->state[1].carrier = port_carrier1;
      cpssp->state[2].carrier = port_carrier2;
      cpssp->state[0].connected = port_connected0;
      cpssp->state[1].connected = port_connected1;
      cpssp->state[2].connected = port_connected2;

      /* Out */
      sig_integer_connect_out(port_connected0, cpssp, 0);
      sig_integer_connect_out(port_connected1, cpssp, 0);
      sig_integer_connect_out(port_connected2, cpssp, 0);

      /* In */
      sig_boolean_connect_in(port_switch, cpssp, &tpf);

      sig_telephone_connect(port_phone0, cpssp, &tf0);
      sig_telephone_connect(port_phone1, cpssp, &tf1);
      sig_telephone_connect(port_phone2, cpssp, &tf2);
}

void
telephone_switch_create(
      unsigned int nr,
      const char *name,
      const char *num1,
      const char *num2,
      const char *num3
)
{
      struct cpssp *cpssp;

      shm_create(COMP, nr, sizeof(*cpssp));
      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      if (num1 == 0) num1 = "1";
      if (num2 == 0) num2 = "2";
      if (num3 == 0) num3 = "3";

      /* Get Config */
      cpssp->state[0].number = strtoul(num1, NULL, 0);
      cpssp->state[1].number = strtoul(num2, NULL, 0);
      cpssp->state[2].number = strtoul(num3, NULL, 0);

      cpssp->power_state = false;

      shm_unmap(cpssp, sizeof(*cpssp));
}

void
telephone_switch_destroy(unsigned int nr)
{
      struct cpssp *cpssp;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      shm_unmap(cpssp, sizeof(*cpssp));
      shm_destroy(COMP, nr);
}

Generated by  Doxygen 1.6.0   Back to index