/* ==================================================================== * 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_session.c - Implement WSP session oriented service * * Lars Wirzenius * Stipe Tolj */ #include #include #include "gwlib/gwlib.h" #include "wsp.h" #include "wsp_pdu.h" #include "wsp_headers.h" #include "wsp_caps.h" #include "wsp_strings.h" #include "cookies.h" #include "wap.h" #include "wtp.h" typedef enum { #define STATE_NAME(name) name, #define ROW(state, event, condition, action, next_state) #include "wsp_server_session_states.def" #define STATE_NAME(name) name, #define ROW(state, event, condition, action, next_state) #include "wsp_server_method_states.def" #define STATE_NAME(name) name, #define ROW(state, event, condition, action, next_state) #include "wsp_server_push_states.def" WSPState_count } WSPState; /* * Give the status the module: * * limbo * not running at all * running * operating normally * terminating * waiting for operations to terminate, returning to limbo */ static enum { limbo, running, terminating } run_status = limbo; static wap_dispatch_func_t *dispatch_to_wtp_resp; static wap_dispatch_func_t *dispatch_to_wtp_init; static wap_dispatch_func_t *dispatch_to_appl; static wap_dispatch_func_t *dispatch_to_ota; /* * True iff "Session resume facility" is enabled. This means we are * willing to let sessions go to SUSPENDED state, and later resume them. * Currently we always support it, but this may become configurable * at some point. */ static int resume_enabled = 1; static List *queue = NULL; static List *session_machines = NULL; static Counter *session_id_counter = NULL; static WSPMachine *find_session_machine(WAPEvent *event, WSP_PDU *pdu); static void handle_session_event(WSPMachine *machine, WAPEvent *event, WSP_PDU *pdu); static WSPMachine *machine_create(void); static void machine_destroy(void *p); static void handle_method_event(WSPMachine *session, WSPMethodMachine *machine, WAPEvent *event, WSP_PDU *pdu); static void cant_handle_event(WSPMachine *sm, WAPEvent *event); static WSPMethodMachine *method_machine_create(WSPMachine *, long); static void method_machine_destroy(void *msm); static void handle_push_event(WSPMachine *session, WSPPushMachine *machine, WAPEvent *e); static WSPPushMachine *push_machine_create(WSPMachine *session, long id); static void push_machine_destroy(void *p); static char *state_name(WSPState state); static unsigned long next_wsp_session_id(void); static List *make_capabilities_reply(WSPMachine *m); static List *make_reply_headers(WSPMachine *m); static Octstr *make_connectreply_pdu(WSPMachine *m); static Octstr *make_resume_reply_pdu(WSPMachine *m, List *headers); static WSP_PDU *make_confirmedpush_pdu(WAPEvent *e); static WSP_PDU *make_push_pdu(WAPEvent *e); static int transaction_belongs_to_session(void *session, void *tuple); static int find_by_session_id(void *session, void *idp); static int same_client(void *sm1, void *sm2); static WSPMethodMachine *find_method_machine(WSPMachine *, long id); static WSPPushMachine *find_push_machine(WSPMachine *m, long id); static List *unpack_new_headers(WSPMachine *sm, Octstr *hdrs); static void disconnect_other_sessions(WSPMachine *sm); static void send_abort(long reason, long handle); static void indicate_disconnect(WSPMachine *sm, long reason); static void indicate_suspend(WSPMachine *sm, long reason); static void indicate_resume(WSPMachine *sm, WAPAddrTuple *tuple, List *client_headers); static void release_holding_methods(WSPMachine *sm); static void abort_methods(WSPMachine *sm, long reason); static void abort_pushes(WSPMachine *sm, long reason); static void method_abort(WSPMethodMachine *msm, long reason); static void indicate_method_abort(WSPMethodMachine *msm, long reason); static WAPEvent *make_abort(long reason, long handle); static void send_invoke(WSPMachine *session, WSP_PDU *pdu, WAPEvent *e, long class); static void send_abort_to_initiator(long reason, long handle); static void indicate_pushabort(WSPPushMachine *machine, long reason); static void confirm_push(WSPPushMachine *machine); static void main_thread(void *); static int id_belongs_to_session (void *, void *); static int wsp_encoding_string_to_version(Octstr *enc); static Octstr *wsp_encoding_version_to_string(int version); /*********************************************************************** * Public functions. */ void wsp_session_init(wap_dispatch_func_t *responder_dispatch, wap_dispatch_func_t *initiator_dispatch, wap_dispatch_func_t *application_dispatch, wap_dispatch_func_t *push_ota_dispatch) { queue = gwlist_create(); gwlist_add_producer(queue); session_machines = gwlist_create(); session_id_counter = counter_create(); dispatch_to_wtp_resp = responder_dispatch; dispatch_to_wtp_init = initiator_dispatch; dispatch_to_appl = application_dispatch; dispatch_to_ota = push_ota_dispatch; wsp_strings_init(); run_status = running; gwthread_create(main_thread, NULL); } void wsp_session_shutdown(void) { gw_assert(run_status == running); run_status = terminating; gwlist_remove_producer(queue); gwthread_join_every(main_thread); gwlist_destroy(queue, wap_event_destroy_item); debug("wap.wsp", 0, "WSP: %ld session machines left.", gwlist_len(session_machines)); gwlist_destroy(session_machines, machine_destroy); counter_destroy(session_id_counter); wsp_strings_shutdown(); } void wsp_session_dispatch_event(WAPEvent *event) { wap_event_assert(event); gwlist_produce(queue, event); } /*********************************************************************** * Local functions */ static void main_thread(void *arg) { WAPEvent *e; WSPMachine *sm; WSP_PDU *pdu; while (run_status == running && (e = gwlist_consume(queue)) != NULL) { wap_event_assert(e); switch (e->type) { case TR_Invoke_Ind: pdu = wsp_pdu_unpack(e->u.TR_Invoke_Ind.user_data); if (pdu == NULL) { warning(0, "WSP: Broken PDU ignored."); wap_event_destroy(e); continue; } break; default: pdu = NULL; break; } sm = find_session_machine(e, pdu); if (sm == NULL) { wap_event_destroy(e); } else { handle_session_event(sm, e, pdu); } wsp_pdu_destroy(pdu); } } static WSPMachine *find_session_machine(WAPEvent *event, WSP_PDU *pdu) { WSPMachine *sm; long session_id; WAPAddrTuple *tuple; tuple = NULL; session_id = -1; switch (event->type) { case TR_Invoke_Ind: tuple = wap_addr_tuple_duplicate( event->u.TR_Invoke_Ind.addr_tuple); break; case TR_Invoke_Cnf: tuple = wap_addr_tuple_duplicate( event->u.TR_Invoke_Cnf.addr_tuple); break; case TR_Result_Cnf: tuple = wap_addr_tuple_duplicate( event->u.TR_Result_Cnf.addr_tuple); break; case TR_Abort_Ind: tuple = wap_addr_tuple_duplicate( event->u.TR_Abort_Ind.addr_tuple); break; case S_Connect_Res: session_id = event->u.S_Connect_Res.session_id; break; case S_Resume_Res: session_id = event->u.S_Resume_Res.session_id; break; case Disconnect_Event: session_id = event->u.Disconnect_Event.session_handle; break; case Suspend_Event: session_id = event->u.Suspend_Event.session_handle; break; case S_MethodInvoke_Res: session_id = event->u.S_MethodInvoke_Res.session_id; break; case S_MethodResult_Req: session_id = event->u.S_MethodResult_Req.session_id; break; case S_ConfirmedPush_Req: session_id = event->u.S_ConfirmedPush_Req.session_id; break; case S_Push_Req: session_id = event->u.S_Push_Req.session_id; break; default: error(0, "WSP: Cannot find machine for %s event", wap_event_name(event->type)); } gw_assert(tuple != NULL || session_id != -1); /* Pre-state-machine tests, according to 7.1.5. After the tests, * caller will pass the event to sm if sm is not NULL. */ sm = NULL; /* First test is for MRUEXCEEDED, and we don't have a MRU */ /* Second test is for class 2 TR-Invoke.ind with Connect PDU */ if (event->type == TR_Invoke_Ind && event->u.TR_Invoke_Ind.tcl == 2 && pdu->type == Connect) { /* Create a new session, even if there is already * a session open for this address. The new session * will take care of killing the old ones. */ sm = machine_create(); gw_assert(tuple != NULL); sm->addr_tuple = wap_addr_tuple_duplicate(tuple); sm->connect_handle = event->u.TR_Invoke_Ind.handle; /* Third test is for class 2 TR-Invoke.ind with Resume PDU */ } else if (event->type == TR_Invoke_Ind && event->u.TR_Invoke_Ind.tcl == 2 && pdu->type == Resume) { /* Pass to session identified by session id, not * the address tuple. */ session_id = pdu->u.Resume.sessionid; sm = gwlist_search(session_machines, &session_id, find_by_session_id); if (sm == NULL) { /* No session; TR-Abort.req(DISCONNECT) */ send_abort(WSP_ABORT_DISCONNECT, event->u.TR_Invoke_Ind.handle); } /* Fourth test is for a class 1 or 2 TR-Invoke.Ind with no * session for that address tuple. We also handle class 0 * TR-Invoke.ind here by ignoring them; this seems to be * an omission in the spec table. */ } else if (event->type == TR_Invoke_Ind) { sm = gwlist_search(session_machines, tuple, transaction_belongs_to_session); if (sm == NULL && (event->u.TR_Invoke_Ind.tcl == 1 || event->u.TR_Invoke_Ind.tcl == 2)) { send_abort(WSP_ABORT_DISCONNECT, event->u.TR_Invoke_Ind.handle); } /* Other tests are for events not handled by the state tables; * do those later, after we've tried to handle them. */ } else { if (session_id != -1) { sm = gwlist_search(session_machines, &session_id, find_by_session_id); } else { sm = gwlist_search(session_machines, tuple, transaction_belongs_to_session); } /* The table doesn't really say what we should do with * non-Invoke events for which there is no session. But * such a situation means there is an error _somewhere_ * in the gateway. */ if (sm == NULL) { error(0, "WSP: Cannot find session machine for event."); wap_event_dump(event); } } wap_addr_tuple_destroy(tuple); return sm; } static void handle_session_event(WSPMachine *sm, WAPEvent *current_event, WSP_PDU *pdu) { debug("wap.wsp", 0, "WSP: machine %p, state %s, event %s", (void *) sm, state_name(sm->state), wap_event_name(current_event->type)); #define STATE_NAME(name) #define ROW(state_name, event, condition, action, next_state) \ { \ struct event *e; \ e = ¤t_event->u.event; \ if (sm->state == state_name && \ current_event->type == event && \ (condition)) { \ action \ sm->state = next_state; \ debug("wap.wsp", 0, "WSP %ld: New state %s", \ sm->session_id, #next_state); \ goto end; \ } \ } #include "wsp_server_session_states.def" cant_handle_event(sm, current_event); end: wap_event_destroy(current_event); if (sm->state == NULL_SESSION) machine_destroy(sm); } static void cant_handle_event(WSPMachine *sm, WAPEvent *event) { /* We do the rest of the pre-state-machine tests here. The first * four were done in find_session_machine(). The fifth is a * class 1 or 2 TR-Invoke.ind not handled by the state tables. */ if (event->type == TR_Invoke_Ind && (event->u.TR_Invoke_Ind.tcl == 1 || event->u.TR_Invoke_Ind.tcl == 2)) { warning(0, "WSP: Can't handle TR-Invoke.ind, aborting transaction."); debug("wap.wsp", 0, "WSP: The unhandled event:"); wap_event_dump(event); send_abort(WSP_ABORT_PROTOERR, event->u.TR_Invoke_Ind.handle); /* The sixth is a class 0 TR-Invoke.ind not handled by state tables. */ } else if (event->type == TR_Invoke_Ind) { warning(0, "WSP: Can't handle TR-Invoke.ind, ignoring."); debug("wap.wsp", 0, "WSP: The ignored event:"); wap_event_dump(event); /* The seventh is any other event not handled by state tables. */ } else { error(0, "WSP: Can't handle event. Aborting session."); debug("wap.wsp", 0, "WSP: The unhandled event:"); wap_event_dump(event); /* TR-Abort.req(PROTOERR) if it is some other transaction * event than abort. */ /* Currently that means TR-Result.cnf, because we already * tested for Invoke. */ /* FIXME We need a better way to get at event values than * by hardcoding the types. */ if (event->type == TR_Result_Cnf) { send_abort(WSP_ABORT_PROTOERR, event->u.TR_Result_Cnf.handle); } /* Abort(PROTOERR) all method and push transactions */ abort_methods(sm, WSP_ABORT_PROTOERR); abort_pushes(sm, WSP_ABORT_PROTOERR); /* S-Disconnect.ind(PROTOERR) */ indicate_disconnect(sm, WSP_ABORT_PROTOERR); } } static WSPMachine *machine_create(void) { WSPMachine *p; p = gw_malloc(sizeof(WSPMachine)); debug("wap.wsp", 0, "WSP: Created WSPMachine %p", (void *) p); #define INTEGER(name) p->name = 0; #define OCTSTR(name) p->name = NULL; #define HTTPHEADERS(name) p->name = NULL; #define ADDRTUPLE(name) p->name = NULL; #define MACHINESLIST(name) p->name = gwlist_create(); #define CAPABILITIES(name) p->name = NULL; #define COOKIES(name) p->name = gwlist_create(); #define REFERER(name) p->name = NULL; #define MACHINE(fields) fields #include "wsp_server_session_machine.def" p->state = NULL_SESSION; /* set capabilities to default values (defined in 1.1) */ p->client_SDU_size = 1400; p->MOR_push = 1; /* Insert new machine at the _front_, because 1) it's more likely * to get events than old machines are, so this speeds up the linear * search, and 2) we want the newest machine to get any method * invokes that come through before the Connect is established. */ gwlist_insert(session_machines, 0, p); return p; } static void destroy_methodmachines(List *machines) { if (gwlist_len(machines) > 0) { warning(0, "Destroying WSP session with %ld active methods\n", gwlist_len(machines)); } gwlist_destroy(machines, method_machine_destroy); } static void destroy_pushmachines(List *machines) { if (gwlist_len(machines) > 0) { warning(0, "Destroying WSP session with %ld active pushes\n", gwlist_len(machines)); } gwlist_destroy(machines, push_machine_destroy); } static void machine_destroy(void *pp) { WSPMachine *p; p = pp; debug("wap.wsp", 0, "Destroying WSPMachine %p", pp); gwlist_delete_equal(session_machines, p); #define INTEGER(name) p->name = 0; #define OCTSTR(name) octstr_destroy(p->name); #define HTTPHEADERS(name) http_destroy_headers(p->name); #define ADDRTUPLE(name) wap_addr_tuple_destroy(p->name); #define MACHINESLIST(name) destroy_##name(p->name); #define CAPABILITIES(name) wsp_cap_destroy_list(p->name); #define COOKIES(name) cookies_destroy(p->name); #define REFERER(name) octstr_destroy(p->name); #define MACHINE(fields) fields #include "wsp_server_session_machine.def" gw_free(p); } struct msm_pattern { WAPAddrTuple *addr_tuple; long msmid, tid; }; /* This function does NOT consume its event; it leaves that task up * to the parent session */ static void handle_method_event(WSPMachine *sm, WSPMethodMachine *msm, WAPEvent *current_event, WSP_PDU *pdu) { if (msm == NULL) { warning(0, "No method machine for event."); wap_event_dump(current_event); return; } debug("wap.wsp", 0, "WSP: method %ld, state %s, event %s", msm->transaction_id, state_name(msm->state), wap_event_name(current_event->type)); gw_assert(sm->session_id == msm->session_id); #define STATE_NAME(name) #define ROW(state_name, event, condition, action, next_state) \ { \ struct event *e; \ e = ¤t_event->u.event; \ if (msm->state == state_name && \ current_event->type == event && \ (condition)) { \ action \ msm->state = next_state; \ debug("wap.wsp", 0, "WSP %ld/%ld: New method state %s", \ msm->session_id, msm->transaction_id, #next_state); \ goto end; \ } \ } #include "wsp_server_method_states.def" cant_handle_event(sm, current_event); end: if (msm->state == NULL_METHOD) { method_machine_destroy(msm); gwlist_delete_equal(sm->methodmachines, msm); } } static WSPMethodMachine *method_machine_create(WSPMachine *sm, long wtp_handle) { WSPMethodMachine *msm; msm = gw_malloc(sizeof(*msm)); #define INTEGER(name) msm->name = 0; #define ADDRTUPLE(name) msm->name = NULL; #define EVENT(name) msm->name = NULL; #define MACHINE(fields) fields #include "wsp_server_method_machine.def" msm->transaction_id = wtp_handle; msm->state = NULL_METHOD; msm->addr_tuple = wap_addr_tuple_duplicate(sm->addr_tuple); msm->session_id = sm->session_id; gwlist_append(sm->methodmachines, msm); return msm; } static void method_machine_destroy(void *p) { WSPMethodMachine *msm; if (p == NULL) return; msm = p; debug("wap.wsp", 0, "Destroying WSPMethodMachine %ld", msm->transaction_id); #define INTEGER(name) #define ADDRTUPLE(name) wap_addr_tuple_destroy(msm->name); #define EVENT(name) wap_event_destroy(msm->name); #define MACHINE(fields) fields #include "wsp_server_method_machine.def" gw_free(msm); } static void handle_push_event(WSPMachine *sm, WSPPushMachine *pm, WAPEvent *current_event) { if (pm == NULL) { warning(0, "No push machine for event."); wap_event_dump(current_event); return; } debug("wap.wsp", 0, "WSP(tid/pid): push %ld/%ld, state %s, event %s", pm->transaction_id, pm->server_push_id, state_name(pm->state), wap_event_name(current_event->type)); gw_assert(sm->session_id == pm->session_id); #define STATE_NAME(name) #define ROW(state_name, event, condition, action, next_state) \ { \ if (pm->state == state_name && \ current_event->type == event && \ (condition)) { \ action \ pm->state = next_state; \ debug("wap.wsp", 0, "WSP %ld/%ld: New push state %s", \ pm->session_id, pm->transaction_id, #next_state); \ goto end; \ } \ } #include "wsp_server_push_states.def" cant_handle_event(sm, current_event); end: if (pm->state == SERVER_PUSH_NULL_STATE) { push_machine_destroy(pm); gwlist_delete_equal(sm->pushmachines, pm); } } static WSPPushMachine *push_machine_create(WSPMachine *sm, long pid) { WSPPushMachine *m; m = gw_malloc(sizeof(WSPPushMachine)); #define INTEGER(name) m->name = 0; #define ADDRTUPLE(name) m->name = NULL; #define HTTPHEADER(name) m->name = http_create_empty_headers(); #define MACHINE(fields) fields #include "wsp_server_push_machine.def" m->server_push_id = pid; m->transaction_id = pid; m->state = SERVER_PUSH_NULL_STATE; m->addr_tuple = wap_addr_tuple_duplicate(sm->addr_tuple); m->session_id = sm->session_id; gwlist_append(sm->pushmachines, m); return m; } static void push_machine_destroy(void *p) { WSPPushMachine *m = NULL; if (p == NULL) return; m = p; debug("wap.wsp", 0, "Destroying WSPPushMachine %ld", m->transaction_id); #define INTEGER(name) #define ADDRTUPLE(name) wap_addr_tuple_destroy(m->name); #define HTTPHEADER(name) http_destroy_headers(m->name); #define MACHINE(fields) fields #include "wsp_server_push_machine.def" gw_free(m); } static char *state_name(WSPState state) { switch (state) { #define STATE_NAME(name) case name: return #name; #define ROW(state, event, cond, stmt, next_state) #include "wsp_server_session_states.def" #define STATE_NAME(name) case name: return #name; #define ROW(state, event, cond, stmt, next_state) #include "wsp_server_method_states.def" #define STATE_NAME(name) case name: return #name; #define ROW(state, event, cond, stmt, next_state) #include "wsp_server_push_states.def" default: return "unknown wsp state"; } } static unsigned long next_wsp_session_id(void) { return counter_increase(session_id_counter); } static void sanitize_capabilities(List *caps, WSPMachine *m) { long i; Capability *cap; unsigned long ui; for (i = 0; i < gwlist_len(caps); i++) { cap = gwlist_get(caps, i); /* We only know numbered capabilities. Let the application * layer negotiate whatever it wants for unknown ones. */ if (cap->name != NULL) continue; switch (cap->id) { case WSP_CAPS_CLIENT_SDU_SIZE: /* Check if it's a valid uintvar. The value is the * max SDU size we will send, and there's no * internal limit to that, so accept any value. */ if (cap->data != NULL && octstr_extract_uintvar(cap->data, &ui, 0) < 0) goto bad_cap; else m->client_SDU_size = ui; break; case WSP_CAPS_SERVER_SDU_SIZE: /* Check if it's a valid uintvar */ if (cap->data != NULL && (octstr_extract_uintvar(cap->data, &ui, 0) < 0)) goto bad_cap; /* XXX Our MRU is not quite unlimited, since we * use signed longs in the library functions -- * should we make sure we limit the reply value * to LONG_MAX? (That's already a 2GB packet) */ break; case WSP_CAPS_PROTOCOL_OPTIONS: /* Currently we don't support any Push, nor * session resume, nor acknowledgement headers, * so make sure those bits are not set. */ if (cap->data != NULL && octstr_len(cap->data) > 0 && (octstr_get_char(cap->data, 0) & 0xf0) != 0) { warning(0, "WSP: Application layer tried to " "negotiate protocol options."); octstr_set_bits(cap->data, 0, 4, 0); } break; case WSP_CAPS_EXTENDED_METHODS: /* XXX Check format here */ break; case WSP_CAPS_HEADER_CODE_PAGES: /* We don't support any yet, so don't let this * be negotiated. */ if (cap->data) goto bad_cap; break; } continue; bad_cap: error(0, "WSP: Found illegal value in capabilities reply."); wsp_cap_dump(cap); gwlist_delete(caps, i, 1); i--; wsp_cap_destroy(cap); continue; } } static void reply_known_capabilities(List *caps, List *req, WSPMachine *m) { unsigned long ui; Capability *cap; Octstr *data; if (wsp_cap_count(caps, WSP_CAPS_CLIENT_SDU_SIZE, NULL) == 0) { if (wsp_cap_get_client_sdu(req, &ui) > 0) { /* Accept value if it is not silly. */ if ((ui >= 256 && ui < LONG_MAX) || ui == 0) { m->client_SDU_size = ui; } } /* Reply with the client SDU we decided on */ data = octstr_create(""); octstr_append_uintvar(data, m->client_SDU_size); cap = wsp_cap_create(WSP_CAPS_CLIENT_SDU_SIZE, NULL, data); gwlist_append(caps, cap); } if (wsp_cap_count(caps, WSP_CAPS_SERVER_SDU_SIZE, NULL) == 0) { /* Accept whatever size the client is willing * to send. If the client did not specify anything, * then use the default. */ if (wsp_cap_get_server_sdu(req, &ui) <= 0) { ui = 1400; } data = octstr_create(""); octstr_append_uintvar(data, ui); cap = wsp_cap_create(WSP_CAPS_SERVER_SDU_SIZE, NULL, data); gwlist_append(caps, cap); } /* Currently we cannot handle any protocol options */ if (wsp_cap_count(caps, WSP_CAPS_PROTOCOL_OPTIONS, NULL) == 0) { data = octstr_create(""); octstr_append_char(data, 0); cap = wsp_cap_create(WSP_CAPS_PROTOCOL_OPTIONS, NULL, data); gwlist_append(caps, cap); } /* Accept any Method-MOR the client sent; if it sent none, * use the default. */ if (wsp_cap_count(caps, WSP_CAPS_METHOD_MOR, NULL) == 0) { if (wsp_cap_get_method_mor(req, &ui) <= 0) { ui = 1; } data = octstr_create(""); octstr_append_char(data, ui); cap = wsp_cap_create(WSP_CAPS_METHOD_MOR, NULL, data); gwlist_append(caps, cap); } /* We will never send any Push requests because we don't support * that yet. But we already specified that in protocol options; * so, pretend we do, and handle the value that way. */ if (wsp_cap_count(caps, WSP_CAPS_PUSH_MOR, NULL) == 0) { if (wsp_cap_get_push_mor(req, &ui) > 0) { m->MOR_push = ui; } data = octstr_create(""); octstr_append_char(data, m->MOR_push); cap = wsp_cap_create(WSP_CAPS_PUSH_MOR, NULL, data); gwlist_append(caps, cap); } /* Supporting extended methods is up to the application layer, * not up to us. If the application layer didn't specify any, * then we refuse whatever the client requested. The default * is to support none, so we don't really have to add anything here. */ /* We do not support any header code pages. sanitize_capabilities * must have already deleted any reply that indicates otherwise. * Again, not adding anything here is the same as refusing support. */ /* Listing aliases is something the application layer can do if * it wants to. We don't care. */ } /* Generate a refusal for all requested capabilities that are not * replied to. */ static void refuse_unreplied_capabilities(List *caps, List *req) { long i, len; Capability *cap; len = gwlist_len(req); for (i = 0; i < len; i++) { cap = gwlist_get(req, i); if (wsp_cap_count(caps, cap->id, cap->name) == 0) { cap = wsp_cap_create(cap->id, cap->name, NULL); gwlist_append(caps, cap); } } } static int is_default_cap(Capability *cap) { unsigned long ui; /* All unknown values are empty by default */ if (cap->name != NULL || cap->id < 0 || cap->id >= WSP_NUM_CAPS) return cap->data == NULL || octstr_len(cap->data) == 0; switch (cap->id) { case WSP_CAPS_CLIENT_SDU_SIZE: case WSP_CAPS_SERVER_SDU_SIZE: return (cap->data != NULL && octstr_extract_uintvar(cap->data, &ui, 0) >= 0 && ui == 1400); case WSP_CAPS_PROTOCOL_OPTIONS: return cap->data != NULL && octstr_get_char(cap->data, 0) == 0; case WSP_CAPS_METHOD_MOR: case WSP_CAPS_PUSH_MOR: return cap->data != NULL && octstr_get_char(cap->data, 0) == 1; case WSP_CAPS_EXTENDED_METHODS: case WSP_CAPS_HEADER_CODE_PAGES: case WSP_CAPS_ALIASES: return cap->data == NULL || octstr_len(cap->data) == 0; default: return 0; } } /* Remove any replies that have no corresponding request and that * are equal to the default. */ static void strip_default_capabilities(List *caps, List *req) { long i; Capability *cap; int count; /* Hmm, this is an O(N*N) operation, which may be bad. */ i = 0; while (i < gwlist_len(caps)) { cap = gwlist_get(caps, i); count = wsp_cap_count(req, cap->id, cap->name); if (count == 0 && is_default_cap(cap)) { gwlist_delete(caps, i, 1); wsp_cap_destroy(cap); } else { i++; } } } static List *make_capabilities_reply(WSPMachine *m) { List *caps; /* In principle, copy the application layer's capabilities * response, add refusals for all unknown requested capabilities, * and add responses for all known capabilities that are * not already responded to. Then eliminate any replies that * would have no effect because they are equal to the default. */ caps = wsp_cap_duplicate_list(m->reply_caps); /* Don't let the application layer negotiate anything we * cannot handle. Also parse the values it set if we're * interested. */ sanitize_capabilities(caps, m); /* Add capability records for all capabilities we know about * that are not already in the reply list. */ reply_known_capabilities(caps, m->request_caps, m); /* All remaining capabilities in the request list that are * not in the reply list at this point must be unknown ones * that we want to refuse. */ refuse_unreplied_capabilities(caps, m->request_caps); /* Now eliminate replies that would be equal to the requested * value, or (if there was none) to the default value. */ strip_default_capabilities(caps, m->request_caps); return caps; } static List *make_reply_headers(WSPMachine *m) { List *headers; Octstr *encoding_version; /* Add all server wsp level hop-by-hop headers. Currently only * Encoding-Version, as defined by wsp, chapter 8.4.2.70. * What headers belong to which version is defined in appendix A, * table 39.. encoding_version = request_version = NULL; * Essentially, if the client sends us an Encoding-Version * higher than ours (1.3) we send our version number to it, * if it is lower, we left version number intact. */ /* First the case that we have no Encoding-Version header at all. * This case we must assume that the client supports version 1.2 * or lower. */ headers = http_create_empty_headers(); encoding_version = wsp_encoding_version_to_string(m->encoding_version); http_header_add(headers, "Encoding-Version", octstr_get_cstr(encoding_version)); octstr_destroy(encoding_version); return headers; } static Octstr *make_connectreply_pdu(WSPMachine *m) { WSP_PDU *pdu; Octstr *os; List *caps; List *reply_headers; pdu = wsp_pdu_create(ConnectReply); pdu->u.ConnectReply.sessionid = m->session_id; caps = make_capabilities_reply(m); pdu->u.ConnectReply.capabilities = wsp_cap_pack_list(caps); wsp_cap_destroy_list(caps); reply_headers = make_reply_headers(m); pdu->u.ConnectReply.headers = wsp_headers_pack(reply_headers, 0, m->encoding_version); http_destroy_headers(reply_headers); os = wsp_pdu_pack(pdu); wsp_pdu_destroy(pdu); return os; } static Octstr *make_resume_reply_pdu(WSPMachine *m, List *headers) { WSP_PDU *pdu; Octstr *os; pdu = wsp_pdu_create(Reply); /* Not specified for Resume replies */ pdu->u.Reply.status = wsp_convert_http_status_to_wsp_status(HTTP_OK); if (headers == NULL) { headers = http_create_empty_headers(); pdu->u.Reply.headers = wsp_headers_pack(headers, 1, m->encoding_version); http_destroy_headers(headers); } else { pdu->u.Reply.headers = wsp_headers_pack(headers, 1, m->encoding_version); } pdu->u.Reply.data = octstr_create(""); os = wsp_pdu_pack(pdu); wsp_pdu_destroy(pdu); return os; } static WSP_PDU *make_confirmedpush_pdu(WAPEvent *e) { WSP_PDU *pdu; List *headers; pdu = wsp_pdu_create(ConfirmedPush); /* * Both push headers and push body are optional. */ if (e->u.S_ConfirmedPush_Req.push_headers == NULL) { headers = http_create_empty_headers(); pdu->u.ConfirmedPush.headers = wsp_headers_pack(headers, 1, WSP_1_2); http_destroy_headers(headers); } else pdu->u.ConfirmedPush.headers = wsp_headers_pack(e->u.S_ConfirmedPush_Req.push_headers, 1, WSP_1_2); if (e->u.S_ConfirmedPush_Req.push_body == NULL) pdu->u.ConfirmedPush.data = octstr_create(""); else pdu->u.ConfirmedPush.data = octstr_duplicate(e->u.S_ConfirmedPush_Req.push_body); return pdu; } static WSP_PDU *make_push_pdu(WAPEvent *e) { WSP_PDU *pdu; List *headers; pdu = wsp_pdu_create(Push); /* * Both push headers and push body are optional */ if (e->u.S_Push_Req.push_headers == NULL) { headers = http_create_empty_headers(); pdu->u.Push.headers = wsp_headers_pack(headers, 1, WSP_1_2); http_destroy_headers(headers); } else pdu->u.Push.headers = wsp_headers_pack(e->u.S_Push_Req.push_headers, 1, WSP_1_2); if (e->u.S_Push_Req.push_body == NULL) pdu->u.Push.data = octstr_create(""); else pdu->u.Push.data = octstr_duplicate(e->u.S_Push_Req.push_body); return pdu; } static int transaction_belongs_to_session(void *wsp_ptr, void *tuple_ptr) { WSPMachine *wsp; WAPAddrTuple *tuple; wsp = wsp_ptr; tuple = tuple_ptr; return wap_addr_tuple_same(wsp->addr_tuple, tuple); } static int find_by_session_id(void *wsp_ptr, void *id_ptr) { WSPMachine *wsp = wsp_ptr; long *idp = id_ptr; return wsp->session_id == *idp; } static int find_by_method_id(void *wspm_ptr, void *id_ptr) { WSPMethodMachine *msm = wspm_ptr; long *idp = id_ptr; return msm->transaction_id == *idp; } static int find_by_push_id(void *m_ptr, void *id_ptr) { WSPPushMachine *m = m_ptr; long *idp = id_ptr; return m->transaction_id == *idp; } static WSPMethodMachine *find_method_machine(WSPMachine *sm, long id) { return gwlist_search(sm->methodmachines, &id, find_by_method_id); } static WSPPushMachine *find_push_machine(WSPMachine *m, long id) { return gwlist_search(m->pushmachines, &id, find_by_push_id); } static int same_client(void *a, void *b) { WSPMachine *sm1, *sm2; sm1 = a; sm2 = b; return wap_addr_tuple_same(sm1->addr_tuple, sm2->addr_tuple); } static void disconnect_other_sessions(WSPMachine *sm) { List *old_sessions; WAPEvent *disconnect; WSPMachine *sm2; long i; old_sessions = gwlist_search_all(session_machines, sm, same_client); if (old_sessions == NULL) return; for (i = 0; i < gwlist_len(old_sessions); i++) { sm2 = gwlist_get(old_sessions, i); if (sm2 != sm) { disconnect = wap_event_create(Disconnect_Event); handle_session_event(sm2, disconnect, NULL); } } gwlist_destroy(old_sessions, NULL); } static List *unpack_new_headers(WSPMachine *sm, Octstr *hdrs) { List *new_headers; if (hdrs && octstr_len(hdrs) > 0) { new_headers = wsp_headers_unpack(hdrs, 0); if (sm->http_headers == NULL) sm->http_headers = http_create_empty_headers(); http_header_combine(sm->http_headers, new_headers); return new_headers; } return NULL; } static WAPEvent *make_abort(long reason, long handle) { WAPEvent *wtp_event; wtp_event = wap_event_create(TR_Abort_Req); wtp_event->u.TR_Abort_Req.abort_type = 0x01; wtp_event->u.TR_Abort_Req.abort_reason = reason; wtp_event->u.TR_Abort_Req.handle = handle; return wtp_event; } static void send_abort(long reason, long handle) { WAPEvent *wtp_event; wtp_event = make_abort(reason, handle); dispatch_to_wtp_resp(wtp_event); } static void send_abort_to_initiator(long reason, long handle) { WAPEvent *wtp_event; wtp_event = make_abort(reason, handle); dispatch_to_wtp_init(wtp_event); } /* * The server sends invoke (to be exact, makes TR-Invoke.req) only when it is * pushing. (Only the client disconnects sessions.) */ static void send_invoke(WSPMachine *m, WSP_PDU *pdu, WAPEvent *e, long class) { WAPEvent *wtp_event; wtp_event = wap_event_create(TR_Invoke_Req); wtp_event->u.TR_Invoke_Req.addr_tuple = wap_addr_tuple_duplicate(m->addr_tuple); /* * There is no mention of acknowledgement type in the specs. But because * confirmed push is confirmed after response from OTA, provider acknowledge- * ments seem redundant. */ wtp_event->u.TR_Invoke_Req.up_flag = USER_ACKNOWLEDGEMENT; wtp_event->u.TR_Invoke_Req.tcl = class; if (e->type == S_ConfirmedPush_Req) wtp_event->u.TR_Invoke_Req.handle = e->u.S_ConfirmedPush_Req.server_push_id; wtp_event->u.TR_Invoke_Req.user_data = wsp_pdu_pack(pdu); wsp_pdu_destroy(pdu); dispatch_to_wtp_init(wtp_event); } static void indicate_disconnect(WSPMachine *sm, long reason) { WAPEvent *new_event; new_event = wap_event_create(S_Disconnect_Ind); new_event->u.S_Disconnect_Ind.reason_code = reason; new_event->u.S_Disconnect_Ind.redirect_security = 0; new_event->u.S_Disconnect_Ind.redirect_addresses = 0; new_event->u.S_Disconnect_Ind.error_headers = NULL; new_event->u.S_Disconnect_Ind.error_body = NULL; new_event->u.S_Disconnect_Ind.session_handle = sm->session_id; dispatch_to_appl(new_event); } static void indicate_suspend(WSPMachine *sm, long reason) { WAPEvent *new_event; new_event = wap_event_create(S_Suspend_Ind); new_event->u.S_Suspend_Ind.reason = reason; new_event->u.S_Suspend_Ind.session_id = sm->session_id; dispatch_to_appl(new_event); } static void indicate_resume(WSPMachine *sm, WAPAddrTuple *tuple, List *headers) { WAPEvent *new_event; new_event = wap_event_create(S_Resume_Ind); new_event->u.S_Resume_Ind.addr_tuple = wap_addr_tuple_duplicate(tuple); new_event->u.S_Resume_Ind.client_headers = http_header_duplicate(headers); new_event->u.S_Resume_Ind.session_id = sm->session_id; dispatch_to_appl(new_event); } static void indicate_pushabort(WSPPushMachine *spm, long reason) { WAPEvent *ota_event; ota_event = wap_event_create(S_PushAbort_Ind); ota_event->u.S_PushAbort_Ind.push_id = spm->server_push_id; ota_event->u.S_PushAbort_Ind.reason = reason; ota_event->u.S_PushAbort_Ind.session_id = spm->session_id; dispatch_to_appl(ota_event); } static void confirm_push(WSPPushMachine *m) { WAPEvent *ota_event; ota_event = wap_event_create(S_ConfirmedPush_Cnf); ota_event->u.S_ConfirmedPush_Cnf.server_push_id = m->server_push_id; ota_event->u.S_ConfirmedPush_Cnf.session_id = m->session_id; dispatch_to_appl(ota_event); } static void method_abort(WSPMethodMachine *msm, long reason) { WAPEvent *wtp_event; /* Send TR-Abort.req(reason) */ wtp_event = wap_event_create(TR_Abort_Req); /* FIXME: Specs are unclear about this; we may indeed have to * guess abort whether this is a WSP or WTP level abort code */ if (reason < WSP_ABORT_PROTOERR) { wtp_event->u.TR_Abort_Req.abort_type = 0x00; } else { wtp_event->u.TR_Abort_Req.abort_type = 0x01; } wtp_event->u.TR_Abort_Req.abort_reason = reason; wtp_event->u.TR_Abort_Req.handle = msm->transaction_id; dispatch_to_wtp_resp(wtp_event); } static void indicate_method_abort(WSPMethodMachine *msm, long reason) { WAPEvent *new_event; /* Send S-MethodAbort.ind(reason) */ new_event = wap_event_create(S_MethodAbort_Ind); new_event->u.S_MethodAbort_Ind.transaction_id = msm->transaction_id; new_event->u.S_MethodAbort_Ind.reason = reason; new_event->u.S_MethodAbort_Ind.session_handle = msm->session_id; dispatch_to_appl(new_event); } static int method_is_holding(void *item, void *pattern) { WSPMethodMachine *msm = item; return msm->state == HOLDING; } static void release_holding_methods(WSPMachine *sm) { WAPEvent *release; WSPMethodMachine *msm; List *holding; long i, len; holding = gwlist_search_all(sm->methodmachines, NULL, method_is_holding); if (holding == NULL) return; /* We can re-use this because wsp_handle_method_event does not * destroy its event */ release = wap_event_create(Release_Event); len = gwlist_len(holding); for (i = 0; i < len; i++) { msm = gwlist_get(holding, i); handle_method_event(sm, msm, release, NULL); } gwlist_destroy(holding, NULL); wap_event_destroy(release); } static void abort_methods(WSPMachine *sm, long reason) { WAPEvent *ab; WSPMethodMachine *msm; long i, len; ab = wap_event_create(Abort_Event); ab->u.Abort_Event.reason = reason; /* This loop goes backward because it has to deal with the * possibility of method machines disappearing after their event. */ len = gwlist_len(sm->methodmachines); for (i = len - 1; i >= 0; i--) { msm = gwlist_get(sm->methodmachines, i); handle_method_event(sm, msm, ab, NULL); } wap_event_destroy(ab); } static void abort_pushes(WSPMachine *sm, long reason) { WAPEvent *ab; WSPPushMachine *psm; long i, len; ab = wap_event_create(Abort_Event); ab->u.Abort_Event.reason = reason; len = gwlist_len(sm->pushmachines); for (i = len - 1; i >= 0; i--) { psm = gwlist_get(sm->pushmachines, i); handle_push_event(sm, psm, ab); } wap_event_destroy(ab); } WSPMachine *find_session_machine_by_id (int id) { return gwlist_search(session_machines, &id, id_belongs_to_session); } static int id_belongs_to_session (void *wsp_ptr, void *pid) { WSPMachine *wsp; int *id; wsp = wsp_ptr; id = (int *) pid; if (*id == wsp->session_id) return 1; return 0; } static int wsp_encoding_string_to_version(Octstr *enc) { int v; /* default will be WSP 1.2, as defined by WAPWSP */ v = WSP_1_2; if (octstr_compare(enc, octstr_imm("1.1")) == 0) { v = WSP_1_1; } else if (octstr_compare(enc, octstr_imm("1.2")) == 0) { v = WSP_1_2; } else if (octstr_compare(enc, octstr_imm("1.3")) == 0) { v = WSP_1_3; } else if (octstr_compare(enc, octstr_imm("1.4")) == 0) { v = WSP_1_4; } else if (octstr_compare(enc, octstr_imm("1.5")) == 0) { v = WSP_1_5; } return v; } static Octstr *wsp_encoding_version_to_string(int version) { Octstr *os; switch (version) { case WSP_1_1: os = octstr_create("1.1"); break; case WSP_1_2: os = octstr_create("1.2"); break; case WSP_1_3: os = octstr_create("1.3"); break; case WSP_1_4: os = octstr_create("1.4"); break; case WSP_1_5: os = octstr_create("1.5"); break; default: os = octstr_create("1.2"); break; } return os; }