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

drive_wapbox.c

/* ==================================================================== 
 * The Kannel Software License, Version 1.0 
 * 
 * Copyright (c) 2001-2004 Kannel Group  
 * Copyright (c) 1998-2001 WapIT Ltd.   
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in 
 *    the documentation and/or other materials provided with the 
 *    distribution. 
 * 
 * 3. The end-user documentation included with the redistribution, 
 *    if any, must include the following acknowledgment: 
 *       "This product includes software developed by the 
 *        Kannel Group (http://www.kannel.org/)." 
 *    Alternately, this acknowledgment may appear in the software itself, 
 *    if and wherever such third-party acknowledgments normally appear. 
 * 
 * 4. The names "Kannel" and "Kannel Group" must not be used to 
 *    endorse or promote products derived from this software without 
 *    prior written permission. For written permission, please  
 *    contact org@kannel.org. 
 * 
 * 5. Products derived from this software may not be called "Kannel", 
 *    nor may "Kannel" appear in their name, without prior written 
 *    permission of the Kannel Group. 
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * ==================================================================== 
 * 
 * This software consists of voluntary contributions made by many 
 * individuals on behalf of the Kannel Group.  For more information on  
 * the Kannel Group, please see <http://www.kannel.org/>. 
 * 
 * Portions of this software are based upon software originally written at  
 * WapIT Ltd., Helsinki, Finland for the Kannel project.  
 */ 

/* drive_wapbox.c - test wapbox through its bearerbox and http interfaces
 *
 * This program starts a wapbox and pretends to be both the bearer box
 * and the http server, so that it can test and benchmark the wapbox in
 * isolation.
 *
 * Richard Braakman
 */

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "gwlib/gwlib.h"
#include "gw/msg.h"

/* These should really be in a header file */
enum wsp_types {
        Bad_PDU = -1,
        Connect_PDU = 0x01,
        ConnectReply_PDU = 0x02,
        Redirect_PDU = 0x03,
        Reply_PDU = 0x04,
        Disconnect_PDU = 0x05,
        Push_PDU = 0x06,
        ConfirmedPush_PDU = 0x07,
        Suspend_PDU = 0x08,
        Resume_PDU = 0x09,
        Get_PDU = 0x40,
        Options_PDU = 0x41,
        Head_PDU = 0x42,
        Delete_PDU = 0x43,
        Trace_PDU = 0x44,
        Post_PDU = 0x60,
        Put_PDU = 0x61
};

enum wtp_types {
      INVOKE = 1,
      RESULT = 2,
      ACK = 3
};

#define WSP_VERSION 0x10

#define TIMEOUT 10.0  /* seconds */

static long max_requests = 1;
static long max_clients = 1;
static long req_per_session = 1;
static unsigned short http_port;
static int wapbox_port = 30188;
static Octstr *http_url = NULL;

static int verbose_debug = 0;
static int user_ack = 0;

static long requests_complete = 0;
static volatile sig_atomic_t dying = 0;

enum WTP_type {
      TR_Invoke = 1,
      TR_Result = 2,
      TR_Ack = 3,
      TR_Abort = 4
};

struct client_status {
      /* True if we expect a WTP reply */
      int wtp_invoked;

      /* Transaction number for WTP level */
      int wtp_tid; /* current tid if wtp_invoked, else next tid to use */

      /* True if we're connected at the WSP level */
      /* Equal to 2 if we're trying to disconnect */
      int wsp_connected;

      /* -1 if we're not connected */
      long wsp_session_id;

      /* Number of successful page fetches this session */
      int pages_fetched;

      /* Source port to use for this client; should be unique. */
      unsigned short port;
};
typedef struct client_status Client;
      
static Client *clients;
List *ready_clients;

static unsigned long get_varint(Octstr *pdu, int pos) {
      int c;
      long result = 0;

      do {
            c = octstr_get_char(pdu, pos++);
            result = (result << 7) | (c & 0x7f);
      } while (c & 0x80);

      return c;
}

static void http_thread(void *arg) {
      HTTPClient *client;
      Octstr *ip;
      Octstr *url;
      List *headers;
      Octstr *body;
      List *cgivars;
      Octstr *reply_body = octstr_create(
            "<?xml version=\"1.0\"?>\n"
            "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\"\n"
            " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n"
            "<wml>\n"
            "<card id=\"main\" title=\"Hello, world\" newcontext=\"true\">\n"
            "        <p>Hello, world.</p>\n"
            "</card></wml>\n");
      List *reply_headers = list_create();
      int port;

      port = *(int *) arg;
      gw_free(arg);

      list_append(reply_headers,
            octstr_create("Content-Type: text/vnd.wap.wml"));

      for (;!dying;) {
            client = http_accept_request(port, &ip, &url, &headers, 
                                   &body, &cgivars);
            if (client == NULL)
                  break;
            http_send_reply(client, HTTP_OK, reply_headers, reply_body);
            http_destroy_headers(headers);
            octstr_destroy(ip);
            octstr_destroy(url);
            octstr_destroy(body);
            http_destroy_cgiargs(cgivars);
      }

      octstr_destroy(reply_body);
      http_destroy_headers(reply_headers);
}
      

static long http_thread_id;

static int start_http_thread(void) {
      unsigned short port;
      int *port_copy;
    int ssl = 0;   /* indicate if SSL-enabled server should be used */

      for (port = 40000; port < 41000; port += 13) {
            if (http_open_port(port, ssl) != -1)
                break;
      }
      if (port == 41000)
            panic(0, "No ports available for http server");

      port_copy = gw_malloc(sizeof(*port_copy));
      *port_copy = port;
      http_thread_id = gwthread_create(http_thread, port_copy);
      if (http_thread_id == -1) 
            panic(0, "Cannot start http thread");
      return port;
}

static Connection *start_wapbox(void) {
      int wap_socket;
      int wapbox;

      wap_socket = make_server_socket(wapbox_port, NULL);
      if (wap_socket < 0)
            panic(0, "Couldn't make wapbox port\n");

      wapbox = accept(wap_socket, NULL, NULL);
      if (wapbox < 0)
            panic(errno, "Wapbox could not connect\n");

      close(wap_socket);

      return conn_wrap_fd(wapbox, 0);
}

static void initialize_clients(void) {
      long i;

      ready_clients = list_create();

      clients = gw_malloc(max_clients * sizeof(*clients));
      for (i = 0; i < max_clients; i++) {
            clients[i].wtp_invoked = 0;
            clients[i].wtp_tid = 0;
            clients[i].wsp_connected = 0;
            clients[i].wsp_session_id = -1;
            clients[i].pages_fetched = 0;
            clients[i].port = i;
            list_append(ready_clients, &clients[i]);
      }
}

static void destroy_clients(void) {
      gw_free(clients);
      list_destroy(ready_clients, NULL);
}

static Client *find_client(unsigned short port) {
      /* It's easy and fast since we assign ports in linear order */
      if (port >= max_clients)
            return NULL;

      return clients + port;
}

static void client_done(Client *client) {
      requests_complete++;
      list_append(ready_clients, client);
}

static void increment_tid(Client *client) {
      if (client->wtp_tid == 0x7fff) 
            client->wtp_tid = 0;
      else
            client->wtp_tid++;
}

/* Set the U/P flag on an Invoke PDU */
static void set_user_ack(Octstr *pdu) {
      octstr_set_bits(pdu, 3 * 8 + 3, 1, 1);
}

static Octstr *wtp_invoke_create(int class) {
      Octstr *pdu;
      /* data describes a TR-Invoke PDU, with GTR=1 and TTR=1 (segmentation
       * not supported), and Transaction class 0 (which we replace below) */
      static unsigned char data[] = { 0x0e, 0x00, 0x00, 0x00 };
      gw_assert(class >= 0);
      gw_assert(class <= 2);
      pdu = octstr_create_from_data(data, sizeof(data));
      octstr_set_char(pdu, 3, class);

      if (user_ack)
            set_user_ack(pdu);

      return pdu;
}

static Octstr *wtp_ack_create(void) {
      static unsigned char data[] = { 0x18, 0x00, 0x00 };
      return octstr_create_from_data(data, sizeof(data));
}

static void add_wsp_connect(Octstr *pdu) {
      static unsigned char data[] = { Connect_PDU, WSP_VERSION, 68, 0x00,
      0x03, 0x80, 0x90, 0x00, 0x03, 0x81, 0x90, 0x00,
      0x02, 0x82, 0x30, 0x02, 0x83, 0x01, 0x02, 0x84,
      0x01, 0x28, 0x85, 0x50, 0x58, 0x2d, 0x55, 0x50,
      0x2d, 0x41, 0x47, 0x45, 0x54, 0x00, 0x51, 0x58,
      0x2d, 0x55, 0x50, 0x2d, 0x47, 0x45, 0x54, 0x4e,
      0x4f, 0x54, 0x49, 0x46, 0x59, 0x00, 0x70, 0x58,
      0x2d, 0x55, 0x50, 0x2d, 0x41, 0x50, 0x4f, 0x53,
      0x54, 0x00, 0x09, 0x86, 0x02, 0x78, 0x2d, 0x75,
      0x70, 0x2d, 0x31, 0x00 };
      octstr_append_data(pdu, data, sizeof(data));
}

static void add_wsp_get(Octstr *pdu) {
      Octstr *urlbuf;

      octstr_append_char(pdu, Get_PDU);
      if (http_url) {
            octstr_append_uintvar(pdu, octstr_len(http_url));
            octstr_append(pdu, http_url);
      } else {
            urlbuf = octstr_format("http://localhost:%ld/hello.wml",
                               (long) http_port);
            octstr_append_uintvar(pdu, octstr_len(urlbuf));
            octstr_append(pdu, urlbuf);
            octstr_destroy(urlbuf);
      }
}

static void add_wsp_disconnect(Octstr *pdu, long session_id) {
      octstr_append_char(pdu, Disconnect_PDU);
      octstr_append_uintvar(pdu, session_id);
}

static void set_tid(Octstr *pdu, int tid) {
      int c;

      /* Tid wraps at 15 bits. */
      tid &= 0x7fff;

      c = octstr_get_char(pdu, 1);
      c = (tid >> 8) | (c & 0x80);
      octstr_set_char(pdu, 1, c);
      octstr_set_char(pdu, 2, tid & 0xff);
}

static int get_tid(Octstr *pdu) {
      return octstr_get_bits(pdu, 8, 16);
}

static int wtp_type(Octstr *pdu) {
      return octstr_get_bits(pdu, 1, 4);
}

static Msg *wdp_create(Octstr *data, Client *client) {
      Msg *msg;

      msg = msg_create(wdp_datagram);
      msg->wdp_datagram.source_address = octstr_create("127.0.0.1");
      msg->wdp_datagram.source_port = client->port;
      msg->wdp_datagram.destination_address = octstr_create("127.0.0.1");
      msg->wdp_datagram.destination_port = 9201;
      msg->wdp_datagram.user_data = octstr_duplicate(data);

      return msg;
}

static void send_pdu(Octstr *pdu, Connection *boxc, Client *client) {
      Msg *msg;
      Octstr *data;

      if (verbose_debug) {
            debug("test", 0, "Sending:");
            octstr_dump(pdu, 0);
      }

      msg = wdp_create(pdu, client);
      data = msg_pack(msg);
      conn_write_withlen(boxc, data);

      octstr_destroy(data);
      msg_destroy(msg);
}

static void send_invoke_connect(Connection *boxc, Client *client) {
      Octstr *pdu;
      
      gw_assert(client != NULL);
      gw_assert(client->wtp_invoked == 0);
      gw_assert(client->wsp_connected == 0);

      pdu = wtp_invoke_create(2);
      set_tid(pdu, client->wtp_tid);
      add_wsp_connect(pdu);

      send_pdu(pdu, boxc, client);
      octstr_destroy(pdu);

      client->wtp_invoked = 1;
}

static void send_invoke_get(Connection *boxc, Client *client) {
      Octstr *pdu;

      gw_assert(client != NULL);
      gw_assert(client->wtp_invoked == 0);
      gw_assert(client->wsp_connected == 1);

      pdu = wtp_invoke_create(2);
      set_tid(pdu, client->wtp_tid);
      add_wsp_get(pdu);

      send_pdu(pdu, boxc, client);
      octstr_destroy(pdu);

      client->wtp_invoked = 1;
}

static void record_disconnect(Client *client) {
      client->wsp_connected = 0;
      client->wsp_session_id = -1;
      client->pages_fetched = 0;
      increment_tid(client);
}

static void send_invoke_disconnect(Connection *boxc, Client *client) {
      Octstr *pdu;

      gw_assert(client != NULL);
      gw_assert(client->wtp_invoked == 0);
      gw_assert(client->wsp_connected == 1);

      /* Kannel can't handle it as class 1 yet, so send class 0 */
      pdu = wtp_invoke_create(0);
      set_tid(pdu, client->wtp_tid);
      add_wsp_disconnect(pdu, client->wsp_session_id);

      send_pdu(pdu, boxc, client);
      octstr_destroy(pdu);

      record_disconnect(client);
      client_done(client);
}

static void handle_connect_reply(Connection *boxc, Client *client, Octstr *pdu) {
      Octstr *ack;

      gw_assert(client);
      gw_assert(client->wtp_invoked);
      gw_assert(!client->wsp_connected);

      if (octstr_get_char(pdu, 3) != ConnectReply_PDU) {
            error(0, "Unexpected CONNECT reply");
            octstr_dump(pdu, 0);
            return;
      }

      ack = wtp_ack_create();
      set_tid(ack, client->wtp_tid);
      send_pdu(ack, boxc, client);
      octstr_destroy(ack);

      client->wtp_invoked = 0;
      increment_tid(client);
      client->wsp_connected = 1;
      client->wsp_session_id = get_varint(pdu, 4);

      send_invoke_get(boxc, client);
}

static void handle_get_reply(Connection *boxc, Client *client, Octstr *pdu) {
      Octstr *ack;

      gw_assert(client);
      gw_assert(client->wtp_invoked);
      gw_assert(client->wsp_connected);

      if (octstr_get_char(pdu, 3) != Reply_PDU) {
            error(0, "Unexpected GET reply");
            octstr_dump(pdu, 0);
            return;
      }

      ack = wtp_ack_create();
      set_tid(ack, client->wtp_tid);
      send_pdu(ack, boxc, client);
      octstr_destroy(ack);

      client->wtp_invoked = 0;
      increment_tid(client);
      client->pages_fetched++;

      if (client->pages_fetched == req_per_session) {
            send_invoke_disconnect(boxc, client);
      } else {
            client_done(client);
      }
}

static void handle_reply(Connection *boxc, Msg *reply) {
      Client *client;
      Octstr *wtp;
      int type;
      int dumped = 0;

      gw_assert(reply != NULL);
      gw_assert(reply->type == wdp_datagram);

      client = find_client(reply->wdp_datagram.destination_port);
      if (client == NULL)
            panic(0, "got packet for nonexisting client %ld",
                  (long) reply->wdp_datagram.destination_port);

      wtp = reply->wdp_datagram.user_data;
      type = wtp_type(wtp);

      if (verbose_debug) {
            debug("test", 0, "Received:");
            octstr_dump(wtp, 0);
            dumped = 1;
      }

      if (client->wtp_invoked == 0) {
            error(0, "Got packet for client that wasn't waiting");
            if (!dumped) {
                  octstr_dump(wtp, 0);
                  dumped = 1;
            }
            return;
      }

      /* Server should invert the MSB of the tid in its replies */
      if (get_tid(wtp) != (client->wtp_tid ^ 0x8000)) {
            error(0, "Got packet with wrong tid %d, expected %d.",
                  get_tid(wtp), client->wtp_tid ^ 0x8000);
            if (!dumped) {
                  octstr_dump(wtp, 0);
                  dumped = 1;
            }
            return;
      }

      /* We're going to be stupid here, and assume that replies that
       * look vaguely like what we expect are actually what we wanted. */
      if (client->wsp_connected == 0 && type == RESULT) {
            handle_connect_reply(boxc, client, wtp);
      } else if (client->wsp_connected == 1 && type == RESULT) {
            handle_get_reply(boxc, client, wtp);
      } else if (client->wsp_connected == 2 && type == ACK) {
            record_disconnect(client);
            client_done(client);
      } else {
            error(0, "Got unexpected packet");
            if (!dumped) {
                  octstr_dump(wtp, 0);
                  dumped = 1;
            }
      }
}

static void start_request(Connection *boxc, Client *client) {
      gw_assert(client != NULL);
      gw_assert(client->wsp_connected != 2);
      gw_assert(client->wtp_invoked == 0);

      if (client->wsp_connected == 0) {
            send_invoke_connect(boxc, client); 
      } else {
            send_invoke_get(boxc, client);
      }
}

static long run_requests(Connection *boxc) {
      int requests_sent;
      Octstr *data;
      Msg *msg;
      int ret;

      requests_sent = 0;
      requests_complete = 0;

      while (requests_complete < max_requests) {
            data = conn_read_withlen(boxc);
            if (!data) {
                  Client *client;

                  if (requests_sent < max_requests
                      && (client = list_extract_first(ready_clients))) {
                        start_request(boxc, client);
                        requests_sent++;
                  }
                  ret = conn_wait(boxc, TIMEOUT);
                  if (ret < 0 || conn_eof(boxc))
                        panic(0, "Wapbox dead.");
                  if (ret == 1)
                        break; /* Timed out. */
            } else {
                  msg = msg_unpack(data);
                  if (!msg) {
                        octstr_dump(data, 0);
                        panic(0, "Received bad data from wapbox.");
                  }
                  if (msg->type == wdp_datagram)
                        handle_reply(boxc, msg);
                  msg_destroy(msg);
            }
            octstr_destroy(data);
      }

      if (requests_complete < max_requests)
            info(0, "Timeout.  %ld requests unsatisfied.",
                  max_requests - requests_complete);

      return requests_complete;
}

static void help(void) {
      info(0, "Usage: drive_wapbox [options...]\n");
      info(0, "  -r requests  Stop after this many; default 1.");
      info(0, "  -c clients   # of concurrent clients; default 1.");
      info(0, "  -w wapport   Port wapbox should connect to; default 30188");
      info(0, "  -u url       Use this url instead of internal http server");
      info(0, "  -g requests  Number of requests per WSP session; default 1");
      info(0, "  -U           Set the User ack flag on all WTP transactions");
}

int main(int argc, char **argv) {
      int opt;
      struct timeval start, end;
      Connection *boxc;
      long completed;
      double run_time;

      gwlib_init();

      while ((opt = getopt(argc, argv, "hv:r:c:w:du:Ug:")) != EOF) {
            switch (opt) {
            case 'v':
                  log_set_output_level(atoi(optarg));
                  break;

            case 'r':
                  max_requests = atol(optarg);
                  break;

            case 'c':
                  max_clients = atol(optarg);
                  break;

            case 'w':
                  wapbox_port = atoi(optarg);
                  break;

            case 'u':
                  http_url = octstr_create(optarg);
                  break;

            case 'U':
                  user_ack = 1;
                  break;

            case 'h':
                  help();
                  exit(0);

            case 'd':
                  verbose_debug = 1;
                  break;

            case 'g':
                  req_per_session = atoi(optarg);
                  break;

            case '?':
            default:
                  error(0, "Invalid option %c", opt);
                  help();
                  panic(0, "Stopping.");
            }
      }

      if (!http_url)
            http_port = start_http_thread();
      boxc = start_wapbox();

      initialize_clients();

      if (gettimeofday(&start, NULL) < 0)
            panic(errno, "gettimeofday failed");
      completed = run_requests(boxc);
      if (gettimeofday(&end, NULL) < 0)
            panic(errno, "gettimeofday failed");

      conn_destroy(boxc);

      run_time = end.tv_sec - start.tv_sec;
      run_time += (double) (end.tv_usec - start.tv_usec) / 1000000.0;

      /* We must have timed out.  Don't count the waiting time. */
      if (completed < max_requests)
            run_time -= TIMEOUT;

      info(0, "%ld request%s in %0.1f seconds, %0.1f requests/s.",
            completed, completed != 1 ? "s" : "",
            run_time, max_requests / run_time);

      dying = 1;
      http_close_all_ports();
      if (!http_url)
            gwthread_join(http_thread_id);

      destroy_clients();
      octstr_destroy(http_url);
      
      gwlib_shutdown();

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index