/* $Id: assoc.c,v 1.2 1999/07/25 21:55:47 he Exp $ */ /* Copyright 1997 by Henner Eisen This code is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "tdu.h" tdu_handler tdu_released = tdu_aborted; /* * Check if association is deactivated ( released or aborted ) */ static int tdu_not_associated( struct tdu_fsm * fsm ) { return ( * (fsm -> regime_handler) == fsm->idle.handler ); } /* * Check if in idle state of association regime as slave */ static int in_idle_assoc_regime_slave( struct tdu_fsm * fsm ) { return ( ( (fsm -> regime_handler) == &( fsm->assoc.handler ) ) && ( fsm->assoc.handler == fsm->assoc.slave_handler ) ); } /* * Check if in idle state of association regime as master */ int in_idle_assoc_regime_master( struct tdu_fsm * fsm ) { return ( ( (fsm -> regime_handler) == &( fsm->assoc.handler ) ) && ( fsm->assoc.handler == fsm->assoc.master_handler ) ); } /* * process events until the association regime ceases to be established */ int tdu_wait_for_release(struct tdu_fsm * fsm, struct timeval * timeout) { tdu_printf(TDU_LOG_TRC, "tdu_wait_for_release()\n"); return tdu_wait( fsm, timeout, tdu_not_associated ); } /* * Fill in parameters in a t_associate request packet */ int tdu_add_assoc_params( struct tdu_buf * tb, struct tdu_assoc_param * par) { int ret=0; unsigned char byte; if( par->udata && (par->udata_len >= 0) ) ret += tdu_add_string_par(tb, TDU_PI_USER_DATA, par->udata , LIMIT(par->udata_len,TDU_PLEN_UDATA) ); if( par->req_ident >= 0){ ret += tdu_add_byte_par(tb,TDU_PI_REQUEST_IDENT, par->req_ident? TDU_BIT_REQ_ID : 0); } if( par->ident && (par->ident_len >= 0) ) ret += tdu_add_string_par(tb, TDU_PI_IDENTIFICATION, par->ident , LIMIT(par->ident_len,TDU_PLEN_IDENT) ); if( par->expl_conf >= 0 ){ ret += tdu_add_byte_par(tb,TDU_PI_EX_CONF_1ST_LAST_BLNO, par->expl_conf? TDU_BIT_EXPL_CONF : 0); } byte = 0; if( par->symm_service ) byte|= TDU_BIT_SYMM_SERVICE; if( par->basic_kernel ) byte|= TDU_BIT_BASIC_KERNEL; ret += tdu_add_byte_par(tb,TDU_PI_SERVICE_CLASS, byte); ret += tdu_add_byte_par(tb,TDU_PI_APPL_RESPONSE_TIMEOUT, 12); if( par->appl_name && (par->appl_len >= 0) ) ret += tdu_add_string_par(tb, TDU_PI_APPLICATION_NAME, par->appl_name , LIMIT(par->appl_len,TDU_PLEN_APPLNAME) ); if( par->calling_addr && (par->calling_len >= 0) ) ret += tdu_add_string_par(tb, TDU_PI_CALLING_ADDRESS , par->calling_addr , LIMIT(par->calling_len,TDU_PLEN_ADDR) ); if( par->called_addr && (par->called_len >= 0) ) ret += tdu_add_string_par(tb, TDU_PI_CALLED_ADDRESS , par->called_addr , LIMIT(par->called_len,TDU_PLEN_ADDR) ); return ret; } /* * Parse parameters of an association_request or positive response message. * returns: * 0 on success * an integer reason code from the TDU_RE_* set when an error is detected. */ int tdu_parse_assoc( struct tdu_assoc_param *par, struct tdu_buf *tb) { int pi, pv; tdu_printf(TDU_LOG_TRC, "tdu_parse_assoc()\n"); do { tdu_printf(TDU_LOG_DBG, "parsing parameter %d=%s\n",*tb->data, tdu_param_descr(*tb->data) ); pi = tdu_get_next_pi(tb); if( pi < 0 ) return -pi; switch( pi ){ case TDU_PI_RESULT_REASON: tdu_printf(TDU_LOG_DBG, "result/reason\n"); if( *tb->ci == TDU_CI_T_RESPONSE_POSITIVE ){ if( (pv=tdu_parse_byte(tb)) < 0 ) return TDU_RE_SYNTAX_ERROR; if( pv != TDU_CI_T_ASSOCIATE ) return TDU_RE_PROTOCOL_CONFLICT; }; tb->data = tb->pn; break; case TDU_PI_CALLED_ADDRESS: tdu_printf(TDU_LOG_DBG, "called addr\n"); par->called_len = tdu_parse_string(tb, par->called_addr, TDU_PLEN_ADDR); break; case TDU_PI_CALLING_ADDRESS: tdu_printf(TDU_LOG_DBG, "calling addr\n"); par->calling_len = tdu_parse_string(tb, par->calling_addr, TDU_PLEN_ADDR); break; case TDU_PI_APPLICATION_NAME: tdu_printf(TDU_LOG_DBG, "appl name\n"); par->appl_len = tdu_parse_string(tb, par->appl_name, TDU_PLEN_APPLNAME); break; case TDU_PI_APPL_RESPONSE_TIMEOUT: tdu_printf(TDU_LOG_DBG, "resp timout\n"); par->resp_timeout = tdu_parse_li(tb); break; case TDU_PI_SERVICE_CLASS: tdu_printf(TDU_LOG_DBG, "svc class\n"); if( (pv=tdu_parse_byte(tb)) < 0 ) return TDU_RE_SYNTAX_ERROR; par->symm_service = pv & TDU_BIT_SYMM_SERVICE; par->basic_kernel = pv & TDU_BIT_BASIC_KERNEL; break; case TDU_PI_EX_CONF_1ST_LAST_BLNO: tdu_printf(TDU_LOG_DBG, "expl conf\n"); if( (pv=tdu_parse_byte(tb)) < 0 ) return TDU_RE_SYNTAX_ERROR; par->expl_conf = pv & TDU_BIT_EXPL_CONF; break; case TDU_PI_IDENTIFICATION: tdu_printf(TDU_LOG_DBG, "ident\n"); par->ident_len = tdu_parse_string(tb, par->ident, TDU_PLEN_IDENT); break; case TDU_PI_REQUEST_IDENT: tdu_printf(TDU_LOG_DBG, "req ident\n"); if( (pv=tdu_parse_byte(tb)) < 0 ) return TDU_RE_SYNTAX_ERROR; par->req_ident = pv & TDU_BIT_REQ_ID; break; case TDU_PI_USER_DATA: tdu_printf(TDU_LOG_DBG, "user data\n"); par->udata_len = tdu_parse_string(tb, par->udata, TDU_PLEN_UDATA); break; default: tdu_printf(TDU_LOG_LOG, "tdu_parse_assoc(): unexpected" " parameter '%s' ignored\n", tdu_param_descr(pi) ); tb->data = tb->pn; }; } while( tb->pn < tb->tail ); return 0; } void tdu_assoc_set_master(struct tdu_fsm *); void tdu_assoc_set_slave(struct tdu_fsm *); /* * Set application response timer to the value received from peer * during association establishment. */ static void tdu_set_timeout( struct tdu_fsm * fsm) { fsm->assoc.resp_timeout = fsm->assoc.remote.resp_timeout; if( fsm->assoc.resp_timeout <= 0 ) fsm->assoc.resp_timeout = 600; if( fsm->assoc.resp_timeout > 254 ) fsm->assoc.resp_timeout = 254; } static int tdu_send_assoc( struct tdu_fsm * fsm, int resp ) { struct tdu_buf tb[1]; tdu_init_tb(tb); tdu_printf(TDU_LOG_TRC, "tdu_send_assoc()\n"); tdu_add_assoc_params( tb, & fsm->assoc.local ); if( resp ) { tdu_add_reason(tb, TDU_CI_T_ASSOCIATE, 0, NULL); tdu_add_ci_header( tb, TDU_CI_T_RESPONSE_POSITIVE ); } else { tdu_add_ci_header( tb, TDU_CI_T_ASSOCIATE ); } return tdu_send_packet( tb, fsm ); } /* * process an association establishment indication */ static int assoc_req_received(struct tdu_fsm *fsm, struct tdu_buf * tb) { int err; struct tdu_param par; tdu_printf(TDU_LOG_TRC, "assoc_req_received()\n"); par.reason = TDU_RE_ROLE_REFUSED_U; par.other_reason[0] = 0; if( ! fsm->assoc.slave_handler ) goto refuse; if( ! fsm->user->t_associate) goto refuse; tdu_set_default_assoc_param( &fsm->assoc.remote ); tdu_set_default_assoc_param( &fsm->assoc.local ); par.reason = tdu_parse_assoc(&fsm->assoc.remote,tb); if ( par.reason ) goto refuse; par.par.assoc = &fsm->assoc.remote; par.res.assoc = &fsm->assoc.local; err = fsm->user->t_associate(fsm->user,&par); if( err>0 ) goto refuse; /* remove parameters not allowed in response */ par.res.assoc->calling_len = -1; par.res.assoc->appl_len = -1; par.res.assoc->expl_conf = -1; par.res.assoc->req_ident = -1; if( tdu_send_assoc(fsm,1) > 0 ){ tdu_assoc_set_slave(fsm); tdu_assoc_set_idle(fsm); tdu_set_timeout(fsm); return 0; } else { tdu_abort(fsm); return -3; } refuse: tdu_send_response_neg(fsm,TDU_CI_T_ASSOCIATE, par.reason,par.other_reason); return -3; } /* * refuse accepting association request. */ int dont_accept(struct tdu_fsm * fsm, struct tdu_buf * dummy) { tdu_printf(TDU_LOG_TRC, "dont_acept()\n"); tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); return 0; } /* * Set master mode of association regime */ void tdu_assoc_set_master(struct tdu_fsm * fsm) { fsm -> assoc.handler = fsm->assoc.master_handler; fsm -> assoc.idle_assoc_req_processor = dont_accept; } /* * Set slave mode of association regime */ void tdu_assoc_set_slave(struct tdu_fsm * fsm) { tdu_printf(TDU_LOG_TRC, "tdu_assoc_set_slave()\n"); fsm -> assoc.handler = fsm->assoc.slave_handler; fsm -> assoc.idle_assoc_req_processor = dont_accept; } /* * Pepare fsm for usage as listener (server) before any regime is * established */ void tdu_assoc_allow_listen(struct tdu_fsm * fsm) { fsm -> assoc.idle_assoc_req_processor = assoc_req_received; } /* * Set idle state of association regime */ void tdu_assoc_set_idle(struct tdu_fsm * fsm) { tdu_printf(TDU_LOG_TRC, "tdu_assoc_set_idle()\n"); fsm -> regime_handler = & fsm->assoc.handler; tdu_del_timer(fsm); fsm->wait=0; } static void tdu_release_received(struct tdu_fsm * fsm) { tdu_printf(TDU_LOG_TRC, "tdu_release_received()\n"); #if 0 tdu_printf(TDU_LOG_AP3, "tdu_release_received()\n"); #endif if( fsm->regime_handler == & fsm->assoc.handler ){ tdu_send_response_pos(fsm, TDU_CI_T_RELEASE); fsm->idle.handler = tdu_released; fsm->regime_handler = & fsm->idle.handler; tdu_del_timer(fsm); } else { tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); } } /* * association regime handler for master */ int tdu_assoc_master ( struct tdu_fsm *fsm, int event, struct tdu_buf *tb) { tdu_printf(TDU_LOG_TRC, "tdu_assoc_master(), event = %s\n", tdu_cmd_descr(event) ); switch (event) { case TDU_CI_T_RELEASE: tdu_release_received(fsm); break; case TDU_CI_T_ABORT: /* fall through */ case TDU_CI_SOCK_READ_ERROR: case TDU_CI_TC_DISCONNECT: tdu_abort(fsm); break; default: tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); }; return 0; } /* * Association regime handler for slave. * If "basic kernel" service class gets * supported once, this needs to be changed. */ int tdu_assoc_slave ( struct tdu_fsm *fsm, int event, struct tdu_buf *tb) { tdu_printf(TDU_LOG_TRC, "tdu_assoc_slave(), event = %s\n", tdu_cmd_descr(event) ); switch (event) { case TDU_CI_T_ACCESS: tdu_access_received(fsm,tb); break; case TDU_CI_T_RELEASE: tdu_release_received(fsm); break; case TDU_CI_T_ABORT: /* fall through */ case TDU_CI_SOCK_READ_ERROR: case TDU_CI_TC_DISCONNECT: tdu_abort(fsm); break; default: tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); }; return 0; } /* * association regime handler, waiting for confirmation of t_release */ int tdu_await_release( struct tdu_fsm *fsm, int event, struct tdu_buf *tb) { int err, pi; tdu_printf(TDU_LOG_TRC, "tdu_await_release(), event = %s\n", tdu_cmd_descr(event) ); switch (event) { case TDU_CI_T_RESPONSE_POSITIVE: pi = tdu_get_next_pi(tb); err = tdu_check_response(tb,pi,fsm->wait); if( err ) goto error; /* fall through */ case TDU_CI_T_ABORT: /* fall through */ case TDU_CI_SOCK_READ_ERROR: case TDU_CI_TC_DISCONNECT: tdu_abort(fsm); break; case TDU_CI_RESPONSE_TIMEOUT: default: error: tdu_printf(TDU_LOG_ERR, "unexpected event %s while waiting for t_release" " confirmation\n", tdu_cmd_descr(event) ); tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); }; fsm->wait = 0; return 0; } /* * Request termination of association regime. * Specifying user data not yet supported. * * FIXME: this is ugly and return logic not consistent with tdu_end_access_req */ int tdu_release_req(struct tdu_fsm * fsm) { int ret; struct tdu_buf tb[1]; tdu_printf(TDU_LOG_TRC, "tdu_release_req()\n"); ret = in_idle_assoc_regime_master( fsm ); if( ret <= 0 ) { tdu_printf( TDU_LOG_ERR, "tdu_release_req: not in idle master" " state of association regime\n"); return ret; } tdu_init_tb(tb); tdu_add_ci_header(tb,TDU_CI_T_RELEASE); ret = tdu_send_packet(tb,fsm); if ( ret < 0 ) { perror("tdu_release_req:send_packet"); tdu_abort(fsm); return ret; } fsm -> assoc.handler = tdu_await_release; fsm->wait = TDU_CI_T_RELEASE; tdu_start_timer(fsm); return ret; } /* * idle regime handler, waiting for t_associate confirmation */ int tdu_await_assoc( struct tdu_fsm *fsm, int event, struct tdu_buf *tb) { int err; tdu_printf(TDU_LOG_TRC, "tdu_await_assoc(), event = %s\n", tdu_cmd_descr(event) ); switch (event) { case TDU_CI_T_RESPONSE_POSITIVE: tdu_del_timer(fsm); tdu_set_default_assoc_param( &fsm->assoc.remote ); err = tdu_parse_assoc( & fsm->assoc.remote, tb ); if( err ) { tdu_abort_req( fsm, err, NULL ); } else { /* initial role is master */ tdu_assoc_set_master(fsm); tdu_assoc_set_idle(fsm); tdu_set_timeout(fsm); tdu_printf(TDU_LOG_DBG,"Timeout set to %d\n",fsm->assoc.resp_timeout); } break; case TDU_CI_T_ASSOCIATE: /* association establish request conflict. The initator of the physical connection wins */ tdu_printf(TDU_LOG_LOG,"resolving association req conflict\n"); if( fsm->idle.initiator ) { /* ignore, continue waiting for t_response */ } else { fsm->assoc.idle_assoc_req_processor(fsm,tb); } break; case TDU_CI_T_RESPONSE_NEGATIVE: tdu_printf(TDU_LOG_ERR,"association req rejected by peer\n"); tdu_del_timer(fsm); fsm->idle.handler = tdu_aborted; break; case TDU_CI_T_ABORT: /* fall through */ case TDU_CI_SOCK_READ_ERROR: case TDU_CI_TC_DISCONNECT: tdu_abort(fsm); break; default: tdu_printf(TDU_LOG_ERR,"unexpected event %s while waiting for t_associate" " confirmation\n", tdu_cmd_descr(event) ); tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL); }; fsm->wait = 0; return 0; } /* * Check if not awaiting association establishment */ static int not_associating( struct tdu_fsm * fsm ) { return ! ( ( fsm->regime_handler == &fsm->idle.handler ) &&(fsm->idle.handler == tdu_await_assoc ) ); } /* process events until no longer awaiting association regime establishment returns: >0 if idle state of association regime master is reached <0 if other state != awaiting association is reached */ int tdu_wait_for_not_associating(struct tdu_fsm * fsm, struct timeval * timeout) { int ret; tdu_printf(TDU_LOG_TRC, "wait_for_not_associating()\n"); ret = tdu_wait( fsm, timeout, not_associating ); if ( in_idle_assoc_regime_master(fsm) ) return 1; if ( in_idle_assoc_regime_slave(fsm) ) return 1; return -1; } /* * establish association regime */ int tdu_assoc_req( struct tdu_fsm * fsm, struct tdu_assoc_param * par ) { /* when the association regime is established, we need to become master. Reject, if this is not possible*/ tdu_printf(TDU_LOG_TRC, "tdu_assoc_req()\n"); if ( (! tdu_before_regime(fsm) ) || (! fsm->assoc.master_handler) ){ tdu_printf(TDU_LOG_ERR,"tdu_assoc_req(): no association request possible\n"); return -1; } fsm->assoc.local = *par; /* FIXME: contents of *par needs checking */ if( tdu_send_assoc( fsm, 0 ) > 0 ){ fsm->idle.handler = tdu_await_assoc; fsm->wait = TDU_CI_T_ASSOCIATE; tdu_start_timer( fsm ); return 1; } return 0; } void tdu_set_default_assoc_param( struct tdu_assoc_param * par ) { tdu_printf(TDU_LOG_TRC, "tdu_set_default_assoc_param()\n"); par->called_len = -1; par->calling_len = -1; par->appl_len = -1; par->resp_timeout = 0; par->symm_service=1; par->basic_kernel=0; par->expl_conf= TDU_BIT_EXPL_CONF; par->ident_len = -1; par->req_ident = 0; par->udata_len = -1; }