/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 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 . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * wsp_push_client.c: Client WSP Push implementation, for testing purposes * * Aarno Syvänen for Wapit Ltd */ #include "wsp_push_client.h" #include "wsp.h" #include "wtp.h" #include "wsp_pdu.h" #include "wsp_headers.h" #include "wap.h" /************************************************************************** * * Internal data structures: * * List of client WSP push machines. */ static List *push_client_machines = NULL; /* * Counter for client push machine id numbers, to make sure that they are * unique. */ static Counter *push_client_machine_id_counter = NULL; /* * Give the status of push client: * * limbo * not running at all * running * operating normally * terminating * waiting for operations to terminate, returning to limbo */ static enum {limbo, running, terminating } push_client_run_status = limbo; /* * Queue of events to be handled by the push client. */ static List *push_client_queue = NULL; wap_dispatch_func_t *dispatch_to_self; wap_dispatch_func_t *dispatch_to_wtp_resp; /***************************************************************************** * * Prototypes of internal functions: * * Create and destroy an uniniatilised push client state machine. */ static WSPPushClientMachine *push_client_machine_create(long cpid); static void push_client_machine_destroy(void *a); /* * Checks whether the client push machines list includes a specific machine. * Creates it, if the event is TR-Invoke.ind */ static WSPPushClientMachine *push_client_machine_find_or_create(WAPEvent *e); /* * Feed an event to the client push state machine. Do not report errors to * caller. */ static void push_client_event_handle(WSPPushClientMachine *cpm, WAPEvent *e); /* * Print WSP client push machine state as a string. */ static unsigned char *name_push_client_state(int name); static void main_thread(void *); static WAPEvent *indicate_confirmedpush(WSPPushClientMachine *cpm, Octstr *push_body); static WAPEvent *indicate_pushabort(WSPPushClientMachine *cpm, long abort_reason); static WAPEvent *response_confirmedpush(WSPPushClientMachine *cpm); static WAPEvent *send_abort_to_responder(WSPPushClientMachine *cpm, long reason); static WAPEvent *response_responder_invoke(WSPPushClientMachine *cpm); /************************************************************************** * * EXTERNAL FUNCTIONS: * */ void wsp_push_client_init(wap_dispatch_func_t *dispatch_self, wap_dispatch_func_t *dispatch_wtp_resp) { push_client_machines = gwlist_create(); push_client_machine_id_counter = counter_create(); push_client_queue = gwlist_create(); gwlist_add_producer(push_client_queue); dispatch_to_self = dispatch_self; dispatch_to_wtp_resp = dispatch_wtp_resp; gw_assert(push_client_run_status == limbo); push_client_run_status = running; gwthread_create(main_thread, NULL); } void wsp_push_client_shutdown(void) { gw_assert(push_client_run_status == running); push_client_run_status = terminating; gwlist_remove_producer(push_client_queue); gwthread_join_every(main_thread); debug("wap.wsp", 0, "wsp_push_client_shutdown: %ld push client machines" "left", gwlist_len(push_client_machines)); gwlist_destroy(push_client_machines, push_client_machine_destroy); gwlist_destroy(push_client_queue, wap_event_destroy_item); counter_destroy(push_client_machine_id_counter); } void wsp_push_client_dispatch_event(WAPEvent *e) { gwlist_produce(push_client_queue, e); } /*************************************************************************** * * INTERNAL FUNCTIONS: * */ static void main_thread(void *arg) { WSPPushClientMachine *cpm; WAPEvent *e; while (push_client_run_status == running && (e = gwlist_consume(push_client_queue)) != NULL) { cpm = push_client_machine_find_or_create(e); if (cpm == NULL) wap_event_destroy(e); else push_client_event_handle(cpm, e); } } /* * Give the name of a push client machine state in a readable form */ static unsigned char *name_push_client_state(int n) { switch (n) { #define PUSH_CLIENT_STATE_NAME(state) case state : return #state; #define ROW(state, event, condition, action, new_state) #include "wsp_push_client_states.def" default: return "unknown state"; } } /* * Feed an event to a WSP push client state machine. Do not report errors to * the caller. */ static void push_client_event_handle(WSPPushClientMachine *cpm, WAPEvent *e) { WAPEvent *wtp_event; WSP_PDU *pdu = NULL; wap_event_assert(e); gw_assert(cpm); if (e->type == TR_Invoke_Ind) { pdu = wsp_pdu_unpack(e->u.TR_Invoke_Ind.user_data); /* * Class 1 tests here * Case 4, no session matching address quadruplet, handled by the session mach- * ine. * Tests from table WSP, page 45. Case 5, a PDU state tables cannot handle. */ if (pdu == NULL || pdu->type != ConfirmedPush) { wap_event_destroy(e); wtp_event = send_abort_to_responder(cpm, PROTOERR); wtp_resp_dispatch_event(wtp_event); return; } } debug("wap.wsp", 0, "WSP_PUSH: WSPPushClientMachine %ld, state %s," " event %s", cpm->client_push_id, name_push_client_state(cpm->state), wap_event_name(e->type)); #define PUSH_CLIENT_STATE_NAME(state) #define ROW(push_state, event_type, condition, action, next_state) \ if (cpm->state == push_state && \ e->type == event_type && \ (condition)) { \ action \ cpm->state = next_state; \ debug("wap.wsp", 0, "WSP_PUSH %ld: new state %s", \ cpm->client_push_id, #next_state); \ } else #include "wsp_push_client_states.def" { error(0, "WSP_PUSH: handle_event: unhandled event!"); debug("wap.wsp", 0, "Unhandled event was:"); wap_event_dump(e); wap_event_destroy(e); return; } wsp_pdu_destroy(pdu); wap_event_destroy(e); if (cpm->state == PUSH_CLIENT_NULL_STATE) push_client_machine_destroy(cpm); } static int push_client_machine_has_transid(void *a, void *b) { long transid; WSPPushClientMachine *m; m = a; transid = *(long *)b; return m->transaction_id == transid; } static WSPPushClientMachine *push_client_machine_find_using_transid( long transid) { WSPPushClientMachine *m; m = gwlist_search(push_client_machines, &transid, push_client_machine_has_transid); return m; } /* * Checks client push machines list for a specific machine. Creates it, if the * event is TR-Invoke.ind. * Client push machine is identified (when searching) by transcation identifi- * er. * Note that only WTP responder send its class 1 messages to client push state * machine. So, it is no need to specify WTP machine type. */ static WSPPushClientMachine *push_client_machine_find_or_create(WAPEvent *e) { WSPPushClientMachine *cpm; long transid; cpm = NULL; transid = -1; switch (e->type) { case TR_Invoke_Ind: transid = e->u.TR_Invoke_Ind.handle; break; case S_ConfirmedPush_Res: transid = e->u.S_ConfirmedPush_Res.client_push_id; break; case S_PushAbort_Req: transid = e->u.S_PushAbort_Req.push_id; break; case Abort_Event: break; case TR_Abort_Ind: transid = e->u.TR_Abort_Ind.handle; break; default: debug("wap.wsp", 0, "WSP PUSH: push_client_find_or_create: unhandled" " event"); wap_event_dump(e); wap_event_destroy(e); return NULL; } gw_assert(transid != -1); cpm = push_client_machine_find_using_transid(transid); if (cpm == NULL) { switch (e->type) { case TR_Invoke_Ind: cpm = push_client_machine_create(transid); break; case S_ConfirmedPush_Res: case S_PushAbort_Req: error(0, "WSP_PUSH_CLIENT: POT primitive to a nonexisting" " push client machine"); break; case Abort_Event: error(0, "WSP_PUSH_CLIENT: internal abort to a nonexisting" " push client machine"); break; case TR_Abort_Ind: error(0, "WSP_PUSH_CLIENT: WTP abort to a nonexisting push client" " machine"); break; default: error(0, "WSP_PUSH_CLIENT: Cannot handle event type %s", wap_event_name(e->type)); break; } } return cpm; } static WSPPushClientMachine *push_client_machine_create(long transid) { WSPPushClientMachine *m; m = gw_malloc(sizeof(WSPPushClientMachine)); debug("wap.wsp", 0, "WSP_PUSH_CLIENT: Created WSPPushClientMachine %p", (void *) m); #define INTEGER(name) m->name = 0; #define HTTPHEADERS(name) m->name = NULL; #define MACHINE(fields) fields #include "wsp_push_client_machine.def" m->state = PUSH_CLIENT_NULL_STATE; m->transaction_id = transid; m->client_push_id = counter_increase(push_client_machine_id_counter); gwlist_append(push_client_machines, m); return m; } static void push_client_machine_destroy(void *a) { WSPPushClientMachine *m; m = a; debug("wap.wsp", 0, "Destroying WSPPushClientMachine %p", (void *) m); gwlist_delete_equal(push_client_machines, m); #define INTEGER(name) m->name = 0; #define HTTPHEADERS(name) http_destroy_headers(m->name); #define MACHINE(fields) fields; #include "wsp_push_client_machine.def" gw_free(m); } static WAPEvent *indicate_confirmedpush(WSPPushClientMachine *cpm, Octstr *push_body) { WAPEvent *e; e = wap_event_create(S_ConfirmedPush_Ind); e->u.S_ConfirmedPush_Ind.client_push_id = cpm->client_push_id; e->u.S_ConfirmedPush_Ind.push_headers = http_header_duplicate(cpm->push_headers); e->u.S_ConfirmedPush_Ind.push_body = octstr_duplicate(push_body); return e; } static WAPEvent *indicate_pushabort(WSPPushClientMachine *cpm, long abort_reason) { WAPEvent *e; e = wap_event_create(S_PushAbort_Ind); e->u.S_PushAbort_Ind.push_id = cpm->client_push_id; e->u.S_PushAbort_Ind.reason = abort_reason; return e; } /* * For debugging: create S-ConfirmedPush.res by ourselves. */ static WAPEvent *response_confirmedpush(WSPPushClientMachine *cpm) { WAPEvent *e; e = wap_event_create(S_ConfirmedPush_Res); e->u.S_ConfirmedPush_Res.client_push_id = cpm->client_push_id; return e; } static WAPEvent *send_abort_to_responder(WSPPushClientMachine *cpm, long reason) { WAPEvent *e; e = wap_event_create(TR_Abort_Req); e->u.TR_Abort_Req.abort_type = USER; e->u.TR_Abort_Req.abort_reason = reason; e->u.TR_Abort_Req.handle = cpm->client_push_id; return e; } static WAPEvent *response_responder_invoke(WSPPushClientMachine *cpm) { WAPEvent *e; e = wap_event_create(TR_Invoke_Res); e->u.TR_Invoke_Res.handle = cpm->transaction_id; return e; }