/*=r=*************************************************************************/ static MACHINE *m__start_new_sender_machine (REQUEST put_request) /* WHAT IT DOES: Given a User Request (presumably a Put Request) * it attempts to open a comm-link to the CFDP partner, * allocate a state machine slot, and initialize the state machine. * If successful, returns a pointer to the new state machine; * otherwise, returns NULL. */ { MACHINE *m = NULL; ID partner_id; /*------------------------------------------------------------*/ /* Attempt to open a comm-link to the CFDP partner entity. * (The Put Request tells us who our partner is) */ partner_id = put_request.info.put.dest_id; if (!pdu_output__open (mib__get_my_id(), partner_id)) e_msg__ ("cfdp_engine: discarded incoming Put Request - " "(unable to communicate with node '%s').\n", cfdp_id_as_string (partner_id)); /* If successful, attempt to allocate a slot in the machine-list */ else if ((m = machine_list__allocate_slot ()) == NULL) e_msg__ ("cfdp_engine: discarded incoming Put Request - " "can't exceed %lu transactions\n", MAX_CONCURRENT_TRANSACTIONS); /* If successful, initialize the state machine's MACHINE structure */ else aaa__initialize (m, &put_request, NULL); return (m); }
/*=r=*************************************************************************/ static MACHINE *m__restart_old_sender_machine (HDR hdr) /* WHAT IT DOES: Given a PDU-header (that is presumably a Finished * PDU), it attempts to open a comm-link to the CFDP partner, * allocate a state machine slot, and initialize the state machine. * If successful, returns a pointer to the new state machine; * otherwise, returns NULL. */ { MACHINE *m = NULL; ID partner_id; /*------------------------------------------------------------*/ /* Attempt to open a comm-link to the CFDP partner entity. Since we were * (and are) the sender, our partner is the destination of this transaction. */ partner_id = hdr.dest_id; /* We're the sender, partner is dest-id */ if (!pdu_output__open (mib__get_my_id(), partner_id)) e_msg__ ("cfdp_engine: discarded incoming Fin PDU - " "unable to communicate with node '%s'\n", cfdp_id_as_string (partner_id)); /* If successful, attempt to allocate a slot in the machine-list */ else if ((m = machine_list__allocate_slot ()) == NULL) e_msg__ ("cfdp_engine: discarded incoming Fin PDU - " "can't exceed %lu transactions\n", MAX_CONCURRENT_TRANSACTIONS); /* If successful, initialize the state machine's MACHINE structure */ else aaa__initialize (m, NULL, &hdr); return (m); }
/*=r=*************************************************************************/ static MACHINE *m__start_new_receiver_machine (HDR hdr) /* WHAT IT DOES: Given a PDU-header (presumably Metadata/Filedata/EOF * PDU), it attempts to open a comm-link to the CFDP partner, * allocate a state machine slot, and initialize the state machine. * If successful, returns a pointer to the new state machine; * otherwise, returns NULL. */ { MACHINE *m = NULL; ID partner_id; /*------------------------------------------------------------*/ /* Attempt to open a comm-link to the CFDP partner entity. Since we are * the receiver, our partner is the source of this transaction. */ partner_id = hdr.trans.source_id; if (!pdu_output__open (mib__get_my_id(), partner_id)) e_msg__ ("cfdp_engine: discarded incoming PDU " "(unable to communicate with node '%s').\n", cfdp_id_as_string (partner_id)); /* If successful, attempt to allocate a slot in the machine-list */ else if ((m = machine_list__allocate_slot ()) == NULL) e_msg__ ("cfdp_engine: discarded incoming PDU - " "can't exceed %lu transactions\n", MAX_CONCURRENT_TRANSACTIONS); /* If successful, initialize the state machine's MACHINE structure */ else aaa__initialize (m, NULL, &hdr); return (m); }
/*=r=************************************************************************/ static void m__state_table (MACHINE *m, int event, PDU_AS_STRUCT *pdu_ptr, REQUEST *req_ptr) /* WHAT IT DOES: Calls the appropriate state table routine (based * upon the role that we are to play - e.g. Class 2 Sender). * The given machine, event, PDU, and Request are all passed along * to the state table routine. * NOTE: Also enforces the restriction that the state tables are * not re-entrant for any particular state machine. * NOTE: If the given state machine finishes, it is deleted. */ { /*------------------------------------------------------------*/ /* Avoid re-entering the engine core for any particular transaction */ if (m->is_user_inside_the_engine_core) { w_msg__ ("cfdp_engine: user attempt to re-enter state tables denied.\n"); return; } m->is_user_inside_the_engine_core = YES; /* Run the given state machine in the appropriate state table routine */ if (m->publik.role == S_1) s1__state_table (m, event, pdu_ptr, req_ptr); else if (m->publik.role == S_2) s2__state_table (m, event, pdu_ptr, req_ptr); else if (m->publik.role == R_1) r1__state_table (m, event, pdu_ptr, req_ptr); else if (m->publik.role == R_2) r2__state_table (m, event, pdu_ptr, req_ptr); else e_msg__ ("cfdp_engine: unable to run state table; unknown Role.\n"); m->is_user_inside_the_engine_core = NO; /* If the state machine has finished, delete it */ if (m->has_this_state_machine_finished) { /* Engine-wide bookeeping */ misc__update_summary_statistics (m); misc__set_last_condition_code (m->publik.condition_code); misc__set_last_trans_abandoned (m->publik.abandoned); /* Let the engine user know what has happened */ indication__ (IND_MACHINE_DEALLOCATED, &(m->publik)); /* Delete the state machine (give up the slot associated with it) */ if (!machine_list__deallocate_slot (m)) e_msg__ ("cfdp_engine: " "attempt to delete completed state machine failed.\n"); } }
/*=r=************************************************************************/ static void m__fault (const TRANS_STATUS *info) { /*------------------------------------------------------------*/ e_msg__ (":::Fault trans %s, fault='%s'.\n", cfdp_trans_as_string (info->trans), cfdp_condition_as_string (info->condition_code)); }
/*=r=*************************************************************************/ static boolean m__fault_handler (MACHINE *m, CONDITION_CODE fault) /* WHAT IT DOES: Responds to the given fault in accordance with the * engine user's setting of "response to fault" (in the MIB). * RETURN STATUS: If '1', the response to the given fault was 'ignore', * and the calling state table routine may continue with whatever it * was doing. If '0', the response was suspend, cancel, or abandon * (and the calling state table routine should return immediately). */ { boolean ok_for_caller_to_continue; /*------------------------------------------------------------*/ /* Save the fault-type */ m->publik.condition_code = fault; /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_FAULT, &(m->publik)); /* Unless the user has configured to 'ignore' this fault, the calling * routine should not continue with what it was doing. */ ok_for_caller_to_continue = NO; if (mib__response (fault) == RESPONSE_CANCEL) { m->have_we_initiated_a_cancel = YES; aaa__cancel_locally (m); m__ask_partner_to_cancel (m); m__move_to_state_s2 (m); } else if (mib__response (fault) == RESPONSE_SUSPEND) aaa__notice_of_suspension (m); else if (mib__response (fault) == RESPONSE_IGNORE) ok_for_caller_to_continue = YES; else if (mib__response (fault) == RESPONSE_ABANDON) aaa__abandon_this_transaction (m); else e_msg__ ("cfdp_engine: problem with the Fault mechanism in <R2>.\n"); return (ok_for_caller_to_continue); }
/*=r=*************************************************************************/ static void m__release_a_metadata_pdu (MACHINE *m) /* WHAT IT DOES: Outputs a Metadata PDU, * and performs the actions required after the PDU is released. */ { TRANS_STATUS *mp = &(m->publik); /* useful shorthand */ /*------------------------------------------------------------*/ /*----------------*/ /* Output the PDU */ /*----------------*/ aaa__send_metadata (m->hdr, m->publik.md); m->is_outgoing_md_buffered = NO; /*----------------------------------------------------*/ /* Perform actions required after the PDU is released */ /*----------------------------------------------------*/ /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_METADATA_SENT, &(m->publik)); if (mp->md.file_transfer && !m->is_external_xfer_open) /* This is the typical case -- we have been asked to send a file, * and the engine user is letting us build all the PDUs. */ { if (!aaa__open_source_file (m)) { e_msg__ ("cfdp_engine: unable to open source file '%s'.\n", m->publik.md.source_file_name); if (!m__fault_handler (m, FILESTORE_REJECTION)) return; } if (!aaa__is_file_structure_valid (m)) { if (!m__fault_handler (m, INVALID_FILE_STRUCTURE)) return; } /* Queue all the Filedata for metered released */ nak__set_end_of_scope (&(m->nak), mp->md.file_size); } else if (mp->md.file_transfer && m->is_external_xfer_open) /* Special case: Although a file is to be transferred, we are * not supposed to send Filedata PDUs (unless retransmissions * are necessary). The engine user will send the initial set * of Filedata PDUs. * So, just chill until the engine user finishes sending the file. */ ; else /* No file transfer, so initiate transfer of EOF */ { /* Build and queue an EOF Pdu */ m->eof = aaa__build_eof (mp->condition_code, 0, 0); m->is_outgoing_eof_buffered = YES; } }
/*=r=************************************************************************/ void nak__data_received (NAK *nak_ptr, u_int_4 begin, u_int_4 end) /* ALGORITHM: The given data-range (begin-end) must fit one of the four * cases below. Determine which case applies, and perform the * corresponding action(s). * ASSUMPTION: The scope always starts at zero. */ { /*------------------------------------------------------------*/ /* Validate the given inputs */ if ((begin < 0) || (end < 0) || (begin > end)) { e_msg__ ("cfdp_engine: Nak module ignored receipt of Filedata " "with invalid range (%lu-%lu)\n", begin, end); return; } else { /* If configured to do so, output a debug message */ if (cfdp_is_message_class_enabled (CFDP_MSG_DEBUG_NAK)) d_msg__ ("Valid_filedata_received: %lu-%lu.\n", begin, end); } /*-------------------------------------------------------------- * Case 1 -- the given range is fully within the current scope. * Action: the scope doesn't change; just remove the given range * from the current gap-list. * Example: scope=0-1000, range=100-200. *--------------------------------------------------------------*/ if (end <= nak_ptr->end_of_scope) m__remove_range_from_list (nak_ptr, begin, end); /*------------------------------------------------------------------------ * Case 2 -- the given range is both inside and outside the current scope. * Action: The scope has to be extended, and the portion of the given * range that is within the current scope has to be removed from the * current gap-list. * Example: scope=0-1000, range=900-1100. New scope is 0-1100, * and the range 900-1000 is removed from gap-list. *------------------------------------------------------------------------*/ else if ((begin < nak_ptr->end_of_scope) && (end > nak_ptr->end_of_scope)) { m__remove_range_from_list (nak_ptr, begin, nak_ptr->end_of_scope); nak_ptr->end_of_scope = end; } /*----------------------------------------------------------------- * Case 3 -- the given range begins at the current scope boundary * (i.e. there is no gap between this new data and previously-received * data). * Action: Just extend the current scope. * Example: scope=0-1000, range=1000-1100. New scope is 0-1100. *-----------------------------------------------------------------*/ else if (begin == nak_ptr->end_of_scope) nak_ptr->end_of_scope = end; /*---------------------------------------------------------------------- * Case 4 -- the given range is completely outside the current scope; * i.e. there is a gap between the end of any previously-received * data and the start of this new data. * Action: Add the new gap to the list, and extend the current scope. * Example: scope=0-1000, range=1100-1200. New scope is 0-1200, and * a gap from 1000-1100 is added to the gap-list. *----------------------------------------------------------------------*/ else if (begin > nak_ptr->end_of_scope) { m__add_gap_to_list (nak_ptr, nak_ptr->end_of_scope, begin); nak_ptr->end_of_scope = end; } else /* If we got here, then there is a bug in the above code */ ASSERT__BUG_DETECTED; }
/*=r=************************************************************************/ boolean cfdp_give_request (const char *request_string) { CFDP_FILE *fp; MACHINE *m; REQUEST request; boolean successful = YES; /*------------------------------------------------------------*/ REPORT_VERSION_ONCE; /*---------------------------------*/ /* Validate the given User Request */ /*---------------------------------*/ if (request_string == NULL) { e_msg__ ("cfdp_engine: cfdp_give_request: given a null-pointer.\n"); successful = NO; } else if (!utils__request_from_string (request_string, &request)) /* The string does not represent a valid request */ { e_msg__ ("cfdp_engine: request is invalid (%s).\n", request_string); successful = NO; } else if ((request.type == REQ_PUT) && (cfdp_are_these_ids_equal (request.info.put.dest_id, mib__get_my_id()))) /* This Put Request is invalid */ { e_msg__ ("cfdp_engine: Won't allow you to send a file to yourself!\n"); successful = NO; } /* If the User Request is an "empty" one, just ignore it */ else if (request.type == REQ_NONE) /* No action needed */ d_msg__ ("cfdp_engine: ignoring an empty User Request.\n"); /*--------------------------------------------------------- * If the User Request is a Put Request, then create a new * state machine, and pass the Put Request to that machine *---------------------------------------------------------*/ else if (request.type == REQ_PUT) { /* Optimization: If the source-file doesn't exist, catch it now */ fp = fopen_callback (request.info.put.source_file_name, "rb"); if (fp == NULL) /* The source-file doesn't exist; no point in trying to transfer it */ { e_msg__ ("cfdp_engine: can't transfer non-existent file (%s).\n", request.info.put.source_file_name); successful = NO; } else { /* It does exist; close it and let the state machine re-open it */ fclose_callback (fp); /* Start a new state machine */ if ((m = m__start_new_sender_machine (request)) != NULL) /* Fire the state table event associated with a Put Request */ m__state_table (m, RECEIVED_PUT_REQUEST, NULL, &request); else successful = NO; } } /*------------------------------------------------------------------ * If the User Request applies to *all* transactions, then pass it * to all active state machines *------------------------------------------------------------------*/ else if (request.type == REQ_REPORT) /* Report all active transactions */ m__give_request_to_all_trans (request.type); else if (request.type == REQ_FREEZE) /* Freeze all active transactions, and any future ones too */ { m__give_request_to_all_trans (request.type); misc__freeze_all_partners (); } else if (request.type == REQ_THAW) /* Thaw all active transactions, and any future ones too */ { m__give_request_to_all_trans (request.type); misc__thaw_all_partners (); } else if (request.type == REQ_ABANDON_ALL_TRANSACTIONS) m__give_request_to_all_trans (REQ_ABANDON); else if (request.type == REQ_CANCEL_ALL_TRANSACTIONS) m__give_request_to_all_trans (REQ_CANCEL); else if (request.type == REQ_SUSPEND_ALL_TRANSACTIONS) m__give_request_to_all_trans (REQ_SUSPEND); else if (request.type == REQ_RESUME_ALL_TRANSACTIONS) m__give_request_to_all_trans (REQ_RESUME); /*----------------------------------------------------------------- * If the User Request applies to one transaction, then attempt to * find an active state machine assigned to that transaction, and * pass the Request to that state machine. *-----------------------------------------------------------------*/ else if ((request.type == REQ_ABANDON) || (request.type == REQ_CANCEL) || (request.type == REQ_SUSPEND) || (request.type == REQ_RESUME)) { if ((m = machine_list__get_this_trans (request.info.trans)) != NULL) /* Success (a machine is assigned to this transaction) */ m__give_request_to_one_trans (m, request.type); else /* No state machine assigned */ { e_msg__ ("cfdp_engine: ignoring User-Request that references " "unknown transaction (%s).\n", cfdp_trans_as_string (request.info.trans)); successful = NO; } } else { w_msg__ ("cfdp_engine: ignoring unrecognized User-Request (%s).\n", request_string); successful = NO; } return (successful); }
/*=r=************************************************************************/ static boolean m__is_pdu_sane (PDU_TYPE pdu_type, ROLE my_role) /* WHAT IT DOES: Returns 1 if it makes sense for a PDU of the given * type to be received by a transaction assigned the given role; * otherwise, returns 0. */ { boolean sane; /*------------------------------------------------------------*/ sane = NO; if (pdu_type == _MD_) { if ((my_role == R_1) || (my_role == R_2)) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Metadata can only come from Sender.\n"); } else if (pdu_type == _FD_) { if ((my_role == R_1) || (my_role == R_2)) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Filedata can only come from Sender.\n"); } else if (pdu_type == _EOF_) { if ((my_role == R_1) || (my_role == R_2)) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "EOF can only come from Sender.\n"); } else if (pdu_type == _ACK_EOF_) { if (my_role == S_2) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Ack-EOF can only come from Class 2 Receiver.\n"); } else if (pdu_type == _NAK_) { if (my_role == S_2) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Nak can only come from Class 2 Receiver.\n"); } else if (pdu_type == _FIN_) { if (my_role == S_2) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Fin can only come from Class 2 Receiver.\n"); } else if (pdu_type == _ACK_FIN_) { if (my_role == R_2) sane = YES; else e_msg__ ("cfdp_engine: PDU rejected - " "Ack-Fin can only come from Class 2 Sender.\n"); } else e_msg__ ("cfdp_engine: PDU rejected - unrecognized pdu-type.\n"); return (sane); }
/*=r=************************************************************************/ boolean mib__set_parameter (const char *param_in, const char *value_in) { int i; ID id; unsigned long int long_int; char param [MAX_MIB_PARAMETER_LENGTH+1]; u_int_4 requested_size; char value [MAX_MIB_VALUE_LENGTH+1]; /*------------------------------------------------------------*/ INITIALIZE_ONCE; /*--------------------------*/ /* Validate input arguments */ /*--------------------------*/ if (strlen(param_in) == 0) { e_msg__ ("cfdp_engine: can't set MIB parameter; " "no parameter given.\n"); return (0); } else if (strlen(param_in) > MAX_MIB_PARAMETER_LENGTH) { e_msg__ ("cfdp_engine: can't set MIB parameter; " "parameter-string (%s) is too long.\n", param_in); return (0); } else if (strlen(value_in) == 0) { e_msg__ ("cfdp_engine: can't set MIB parameter; no value given.\n"); return (0); } else if (strlen(value_in) > MAX_MIB_PARAMETER_LENGTH) { e_msg__ ("cfdp_engine: can't set MIB parameter; " "value-string (%s) is too long.\n", value_in); return (0); } COPY (param, m__convert_string_to_uppercase (param_in)); COPY (value, m__convert_string_to_uppercase (value_in)); /*-----------------------------------------*/ /* Compare input with each Local parameter */ /*-----------------------------------------*/ if (!strcmp (param, MIB_MY_ID)) { if (cfdp_id_from_string (value, &id)) { m_mib_local.my_id = id; i_msg__ ("cfdp_engine: entity-id set to '%s'.\n", cfdp_id_as_string (m_mib_local.my_id)); } else { e_msg__ ("cfdp_engine: can't set entity-id to illegal value " "(%s).\n", value); return (0); } } else if (!strcmp (param, MIB_ISSUE_EOF_RECV)) { if (!strcmp (value, "YES")) { m_mib_local.issue_eof_recv = YES; i_msg__ ("cfdp_engine: 'issue_eof_recv' set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_eof_recv = NO; i_msg__ ("cfdp_engine: 'issue_eof_recv' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_eof_recv'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_EOF_SENT)) { if (!strcmp (value, "YES")) { m_mib_local.issue_eof_sent = YES; i_msg__ ("cfdp_engine: 'issue_eof_sent' set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_eof_sent = NO; i_msg__ ("cfdp_engine: 'issue_eof_sent' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_eof_sent'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_FILE_SEGMENT_RECV)) { if (!strcmp (value, "YES")) { m_mib_local.issue_file_segment_recv = YES; i_msg__ ("cfdp_engine: 'issue_file_segment_recv' set to 'yes'\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_file_segment_recv = NO; i_msg__ ("cfdp_engine: 'issue_file_segment_recv' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_file_segment_recv'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_FILE_SEGMENT_SENT)) { if (!strcmp (value, "YES")) { m_mib_local.issue_file_segment_sent = YES; i_msg__ ("cfdp_engine: 'issue_file_segment_sent' set to 'yes'\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_file_segment_sent = NO; i_msg__ ("cfdp_engine: 'issue_file_segment_sent' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_file_segment_sent'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_RESUMED)) { if (!strcmp (value, "YES")) { m_mib_local.issue_resumed = YES; i_msg__ ("cfdp_engine: 'issue_resumed' set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_resumed = NO; i_msg__ ("cfdp_engine: 'issue_resumed' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_resumed'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_SUSPENDED)) { if (!strcmp (value, "YES")) { m_mib_local.issue_suspended = YES; i_msg__ ("cfdp_engine: 'issue_suspended' set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_suspended = NO; i_msg__ ("cfdp_engine: 'issue_suspended' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_suspended'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_ISSUE_TRANSACTION_FINISHED)) { if (!strcmp (value, "YES")) { m_mib_local.issue_transaction_finished = YES; i_msg__ ("cfdp_engine: 'issue_transaction_finished' " "set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_local.issue_transaction_finished = NO; i_msg__ ("cfdp_engine: 'issue_transaction_finished' " "set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'issue_transaction_finished'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else if (!strcmp (param, MIB_RESPONSE_TO_FAULT)) { if ((!strcmp (value, "IGNORE")) || (!strcmp (value, "SUSPEND")) || (!strcmp (value, "CANCEL")) || (!strcmp (value, "ABANDON"))) { for (i=0; i<NUMBER_OF_CONDITION_CODES; i++) /* WARNING: This library uses the same response for all faults. * The CFDP protocol allows the user to choose a different * response to each fault. So, this library is not fully * compliant with the CFDP protocol. However, the library * developer considers this to be a practical shortcut that * simplifies things. */ m_mib_local.response[i] = m__string_as_response (value); i_msg__ ("cfdp_engine: 'response_to_fault' set to '%s'.\n", cfdp_response_as_string (m_mib_local.response[0])); } else { e_msg__ ("cfdp_engine: can't set 'response_to_fault'; " "value (%s) is invalid.\n", value_in); return (0); } } /*------------------------------------------*/ /* Compare input with each Remote parameter */ /*------------------------------------------*/ else if (!strcmp (param, MIB_ACK_LIMIT)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'ack_limit'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); m_mib_remote_default.ack_limit = (u_int_4) long_int; i_msg__ ("cfdp_engine: 'ack_limit' set to '%lu'.\n", m_mib_remote_default.ack_limit); } else if (!strcmp (param, MIB_ACK_TIMEOUT)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'ack_timeout'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); m_mib_remote_default.ack_timeout = (u_int_4) long_int; i_msg__ ("cfdp_engine: 'ack_timeout' set to '%lu'.\n", m_mib_remote_default.ack_timeout); } else if (!strcmp (param, MIB_INACTIVITY_TIMEOUT)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'inactivity_timeout'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); m_mib_remote_default.inactivity_timeout = (u_int_4) long_int; i_msg__ ("cfdp_engine: 'inactivity_timeout' set to '%lu'.\n", m_mib_remote_default.inactivity_timeout); } else if (!strcmp (param, MIB_OUTGOING_FILE_CHUNK_SIZE)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'outgoing_file_chunk_size'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); requested_size = (u_int_4) long_int; if (requested_size > MAX_FILE_CHUNK_SIZE) { e_msg__ ("cfdp_engine: can't set 'outgoing_file_chunk_size'; " "value (%s) exceeds max (%lu).\n", value_in, MAX_FILE_CHUNK_SIZE); return (0); } else { m_mib_remote_default.outgoing_file_chunk_size = requested_size; i_msg__ ("cfdp_engine: 'outgoing_file_chunk_size' " "set to '%lu'.\n", m_mib_remote_default.outgoing_file_chunk_size); } } else if (!strcmp (param, MIB_NAK_LIMIT)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'nak_limit'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); m_mib_remote_default.nak_limit = (u_int_4) long_int; i_msg__ ("cfdp_engine: 'nak_limit' set to '%lu'.\n", m_mib_remote_default.nak_limit); } else if (!strcmp (param, MIB_NAK_TIMEOUT)) { if (!isdigit ((int) value_in[0])) { e_msg__ ("cfdp_engine: can't set 'nak_timeout'; " "value (%s) must be numeric.\n", value_in); return (0); } sscanf (value, "%lu", &long_int); m_mib_remote_default.nak_timeout = (u_int_4) long_int; i_msg__ ("cfdp_engine: 'nak_timeout' set to '%lu'.\n", m_mib_remote_default.nak_timeout); } else if (!strcmp (param, MIB_SAVE_INCOMPLETE_FILES)) { if (!strcmp (value, "YES")) { m_mib_remote_default.save_incomplete_files = YES; i_msg__ ("cfdp_engine: 'save_incomplete_files' set to 'yes'.\n"); } else if (!strcmp (value, "NO")) { m_mib_remote_default.save_incomplete_files = NO; i_msg__ ("cfdp_engine: 'save_incomplete_files' set to 'no'.\n"); } else { e_msg__ ("cfdp_engine: can't set 'save_incomplete_files'; " "value (%s) must be 'yes' or 'no'.\n", value_in); return (0); } } else { e_msg__ ("cfdp_engine: can't set unrecognized MIB parameter " "(%s).\n", param); return (0); } return (1); }
/*=r=************************************************************************/ void r2__state_table (MACHINE *m, int event, PDU_AS_STRUCT *pdu_ptr, REQUEST *req_ptr) /* NOTE: Conceptually, this state table has 2 states: * S1 = Nominal * S2 = Transaction Cancelled * So, the state is always S1 unless the transaction is cancelled. * Transactions can be cancelled in 3 ways: * 1) the local User can issue a Cancel Request * 2) our CFDP partner can send an EOF-Cancel PDU. * 3) A fault may occur, and the response to that fault may be * to cancel. */ { TRANS_STATUS *mp = &(m->publik); /* useful shorthand */ /*------------------------------------------------------------*/ /* Possibly display some debug info */ if (cfdp_is_message_class_enabled (CFDP_MSG_STATE_ALL)) aaa__display_state_and_event ("R2", m->publik.state, event); /*-----------------------*/ /* Respond to each Event */ /*-----------------------*/ if (event == THROTTLE_OUTGOING_FILE_DIR_PDU) /* Release, at most, one outgoing PDU. */ { if (mp->frozen) /* We can't release any PDUs while frozen */ ; else if (mp->suspended) /* The only PDU that can released while suspended is an Ack */ { if (m->is_outgoing_ack_buffered) /* An Ack PDU is ready. Do we have the "green light"? */ if (pdu_output__ready (FILE_DIR_PDU, mp->trans, m->hdr.dest_id)) /* Light is green. Send it */ m__release_an_ack_pdu (m); } else if (m->is_outgoing_ack_buffered) /* We're not frozen or suspended, so we can release any PDU. * An Ack is ready. Do we have the "green light"? */ { if (pdu_output__ready (FILE_DIR_PDU, mp->trans, m->hdr.dest_id)) /* Light is green. Send it */ m__release_an_ack_pdu (m); } else if (m->is_outgoing_nak_buffered) /* We're not frozen or suspended, so we can release any PDU. * A Nak is ready. Do we have the "green light"? */ { if (pdu_output__ready (FILE_DIR_PDU, mp->trans, m->hdr.dest_id)) /* Light is green. Send it */ m__release_a_nak_pdu (m); } else if (m->is_outgoing_fin_buffered) /* We're not frozen or suspended, so we can release any PDU. * A Finished is ready. Do we have the "green light"? */ { if (pdu_output__ready (FILE_DIR_PDU, mp->trans, m->hdr.dest_id)) /* Light is green. Send it */ m__release_a_fin_pdu (m); } } else if (event == RECEIVED_SUSPEND_REQUEST) /* A Suspend Request was received; suspend timers */ aaa__notice_of_suspension (m); else if (event == RECEIVED_RESUME_REQUEST) /* A Resume Request was received; possibly resume timers */ { /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_RESUMED, &(m->publik)); mp->suspended = NO; /* Timers remain paused if either 'suspended' or 'frozen' */ if (!mp->frozen) aaa__resume_timers (m); } else if (event == RECEIVED_CANCEL_REQUEST) /* Received a Cancel Request (from our User, not from our partner) */ { /* If the transaction isn't already being cancelled, begin doing so */ if (mp->state == S1) { mp->condition_code = CANCEL_REQUEST_RECEIVED; m->have_we_initiated_a_cancel = YES; /* Cancel both locally... */ aaa__cancel_locally (m); /* ... and remotely (i.e. have our partner cancel too) */ m__ask_partner_to_cancel (m); m__move_to_state_s2 (m); } } else if (event == RECEIVED_ABANDON_REQUEST) /* Abandon this transaction */ aaa__abandon_this_transaction (m); else if (event == RECEIVED_REPORT_REQUEST) /* A Report Request was received; issue a Report */ indication__ (IND_REPORT, &(m->publik)); else if (event == RECEIVED_FREEZE_REQUEST) /* A "Freeze" was received, so suspend all timers. */ { mp->frozen = YES; aaa__suspend_timers (m); } else if (event == RECEIVED_THAW_REQUEST) /* A "Thaw" was received; possibly resume all timers. */ { mp->frozen = NO; /* Timers remain paused if either 'suspended' or 'frozen' */ if (!mp->suspended) aaa__resume_timers (m); } else if (event == RECEIVED_METADATA_PDU) /* Received a Metadata PDU. This is normally the first PDU we receive * but not always. And its possible that we might receive more than one * Metadata pdu; We only respond to the first Metadata we receive. */ { /* Ignore PDUs received while cancelling */ if (mp->state == S2) return; /* CFDP messages that we send back to our partner will reuse the * header that we receive in the first message from them (except * that the "direction" field will be reversed). * Note: The first message we receive may be Metadata, Filedata, or EOF. */ /* Our outgoing PDU header will be the same as the first incoming * PDU header (but with the 'direction' field reversed). */ if (!m->has_a_pdu_been_received) { aaa__reuse_senders_first_hdr (pdu_ptr->hdr, m); /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_MACHINE_ALLOCATED, &(m->publik)); m->has_a_pdu_been_received = YES; } /* Note: Metadata may arrive more than once (it shouldn't, but it * can). Respond to the first Metadata and ignore all the others. */ if (!mp->has_md_been_received) /* This is the first Metadata PDU for this transaction */ { mp->has_md_been_received = YES; mp->md = pdu_ptr->data_field.md; /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_METADATA_RECV, &(m->publik)); /* If this transaction will transfer a file, prepare */ if (mp->md.file_transfer) { /* Open a temporary file (unless a file was already opened * in response to a Filedata or EOF PDU). * Note: Normally, Metadata is the first PDU received, * but that is not guaranteed. */ if (!m->is_there_an_open_file) { if (aaa__open_temp_file (m)) m->is_there_an_open_file = YES; else { e_msg__ ("cfdp_engine: unable to open a temp file " "(MD).\n"); if (!m__fault_handler (m, FILESTORE_REJECTION)) return; } } } /* Update the Nak-list to indicate that Metadata has been received */ nak__metadata_sent_or_received (&(m->nak)); /* <NOT_SUPPORTED> Metadata TLVs (processed here) */ /* Protocol Optimization: Don't wait for the Nak-timer to expire * before checking to see if retransmission is complete. */ if (m->has_eof_been_received) { if (nak__is_list_empty (&m->nak)) /* All data has been received; begin nominal Finish */ m__all_data_has_been_received (m); } } } else if (event == RECEIVED_FILEDATA_PDU) /* Received a Filedata Pdu. Nominally, we store it in a file, and * update our bookkeeping. But if the transaction is being cancelled, * don't bother. */ { /* Ignore PDUs received while cancelling */ if (mp->state == S2) return; /* Our outgoing PDU header will be the same as the first incoming * PDU header (but with the 'direction' field reversed). */ if (!m->has_a_pdu_been_received) { aaa__reuse_senders_first_hdr (pdu_ptr->hdr, m); /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_MACHINE_ALLOCATED, &(m->publik)); m->has_a_pdu_been_received = YES; } /* If we haven't already opened a file to receive the data, do so now */ if (!m->is_there_an_open_file) { if (aaa__open_temp_file (m)) m->is_there_an_open_file = YES; else { e_msg__ ("cfdp_engine: unable to open a temp file (FD).\n"); if (!m__fault_handler (m, FILESTORE_REJECTION)) return; } } /* Save the File-data in the temporary file */ mp->fd_offset = pdu_ptr->data_field.fd.offset; mp->fd_length = pdu_ptr->data_field.fd.buffer_length; if (mib__issue_file_segment_recv()) /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_FILE_SEGMENT_RECV, &(m->publik)); aaa__store_file_data (m->fp, &pdu_ptr->data_field.fd); /* Update the received file size */ if (mp->fd_offset + mp->fd_length > mp->received_file_size) /* This FD has made the file bigger than before */ mp->received_file_size = mp->fd_offset + mp->fd_length; /* If this Filedata segment exposes a gap, add gap to nak-list. * (the 'nak' module takes care of all this bookkeeping stuff) */ nak__data_received (&m->nak, mp->fd_offset, mp->fd_offset + mp->fd_length); /* If this Filedata PDU arrived *after* the EOF PDU (typically, not * the case), check for a "file size error". */ if (m->has_eof_been_received) /* A "file_size_error" means that the received file-data cannot be * stored without extending the file's length beyond the length * specified in the EOF PDU. */ if (!aaa__is_file_size_valid (m)) if (!m__fault_handler (m, FILE_SIZE_ERROR)) return; /* Protocol Optimization: Don't wait for the Nak-timer to expire * before checking to see if retransmission is complete. */ if (m->has_eof_been_received) { if (nak__is_list_empty (&m->nak)) /* All data has been received; begin nominal Finish */ m__all_data_has_been_received (m); } } else if (event == RECEIVED_EOF_NO_ERROR_PDU) /* Received an EOF-no-error Pdu. This tells us that the Sender has * sent all data once. We always Ack this pdu. Nominally, we * take stock and respond with either a Nak or Finished PDU. However, * if the transaction is being cancelled, don't bother. */ { /*-------------------------------*/ /* Always acknowledge an EOF PDU */ /*-------------------------------*/ /* Save a copy of the EOF */ m->eof = pdu_ptr->data_field.eof; /* Our outgoing PDU header will be the same as the first incoming * PDU header (but with the 'direction' field reversed). */ aaa__reuse_senders_first_hdr (pdu_ptr->hdr, m); /* Build and queue an Ack of the EOF (every EOF must be acked) */ m->ack = aaa__build_ack_eof (m); m->is_outgoing_ack_buffered = YES; /*---------------------------------------*/ /* Ignore PDUs received while cancelling */ /*---------------------------------------*/ if (mp->state == S2) return; /*--------------------------------------------------*/ /* The rest of this logic only executes in state S1 */ /*--------------------------------------------------*/ /* We are now in Phase 2 (Handoff) */ mp->phase = 2; /* Issue the Machine-Allocated Indication once per transaction */ if (!m->has_a_pdu_been_received) { /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_MACHINE_ALLOCATED, &(m->publik)); m->has_a_pdu_been_received = YES; } /* If configured to do so, issue an Indication to the user */ if (mib__issue_eof_recv()) indication__ (IND_EOF_RECV, &(m->publik)); /* If this is the *first* EOF received for this transaction, then * additional actions must be taken; otherwise, it can be ignored. * Why would a second EOF be sent if we received the first one? * Because our Ack of the first EOF was dropped. */ if (!m->has_eof_been_received) { m->has_eof_been_received = YES; /* Update the Nak-list (because this EOF may expose a file gap * beyond the highest Filedata offset received -- for example, * we received Filedata for offset 0 through 900, and the EOF * tells us the file is 1000 bytes long, so we are missing * filedata 900-1000). * (Again, the 'nak' module takes care of this bookkeeping stuff) */ nak__set_end_of_scope (&m->nak, m->eof.file_size); /* Verify that the file-size given in the EOF is valid. For * example, if we received Filedata for offset 0 through 1100, * and the EOF tells us the file is 1000 bytes long, the file size * is invalid. */ if (!aaa__is_file_size_valid (m)) if (!m__fault_handler (m, FILE_SIZE_ERROR)) return; if (nak__is_list_empty (&m->nak)) /* All data has been received; begin nominal Finish */ m__all_data_has_been_received (m); else /* Get the missing data (queue up our first Nak) */ m->is_outgoing_nak_buffered = YES; } } else if (event == RECEIVED_EOF_CANCEL_PDU) /* Received an EOF-Cancel Pdu. We always Ack this pdu. If we have * initiated a Cancel, we have to wait for an Ack of our own cancel; * otherwise, we shut down after sending the Ack. */ { /* Save a copy of the EOF PDU */ m->eof = pdu_ptr->data_field.eof; /* Our outgoing PDU header will be the same as the first incoming * PDU header (but with the 'direction' field reversed). */ if (!m->has_a_pdu_been_received) { aaa__reuse_senders_first_hdr (pdu_ptr->hdr, m); /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_MACHINE_ALLOCATED, &(m->publik)); m->has_a_pdu_been_received = YES; } /* If we haven't already cancelled locally, accept partner's request */ if (mp->state == S1) { aaa__cancel_locally (m); mp->condition_code = m->eof.condition_code; mp->phase = 4; m__move_to_state_s2 (m); } /* Regardless, we must acknowledge receipt of the EOF PDU */ m->ack = aaa__build_ack_eof (m); m->is_outgoing_ack_buffered = YES; } else if (event == RECEIVED_ACK_FIN_NO_ERROR_PDU) /* Received Ack of Finished-no-error Pdu. Nominally, we are done and * can shut down immediately. However, if we have initiated a cancel, * we have to wait for an Ack of our Finished-Cancel PDU. */ { if (mp->state == S1) /* This is the nominal situation */ { if (mp->delivery_code == DATA_COMPLETE) /* We're done; we can shut down now */ aaa__transaction_has_finished (m); else /* Why is our partner sending an Ack-Finished when we haven't * sent a Finished? */ w_msg__ ("cfdp_engine: protocol violation: " "ack of non-existent Finished-no-error.\n"); } else /* Transaction is already being cancelled, so ignore this pdu */ ; } else if (event == RECEIVED_ACK_FIN_CANCEL_PDU) /* Received Ack of Finished-Cancel Pdu. If we have initiated a cancel, * this completes the transaction, and we can shut down immediately. * Otherwise, our partner has violated the protocol. */ { if (mp->state == S1) /* Protocol violation - we didn't send a Finished-Cancel, * but our partner has acknowledged receipt. */ w_msg__ ("cfdp_engine: protocol violation: " "ack of non-existent Finish-Cancel.\n"); else /* Transaction is being cancelled */ { /* This is the nominal path when a Cancel is initiated from our * side. We sent a Finished-Cancel, and partner ack'd it, so * we can shut down this state machine. */ aaa__transaction_has_finished (m); } } else if (event == ACK_TIMER_EXPIRED) /* Ack-timer expired. Resend the Fin Pdu (if permitted). */ { /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_ACK_TIMER_EXPIRED, &(m->publik)); if (mp->state == S1) /* This is the nominal situation */ { /* If we've already sent the Fin PDU as many times as allowed, * then declare a Fault. */ if (mp->attempts >= mib__ack_limit (NULL)) /* Let the Fault mechanism decide what to do */ if (!m__fault_handler (m, POSITIVE_ACK_LIMIT_REACHED)) return; /* Try again -- Re-queue the Fin for metered release */ m->is_outgoing_fin_buffered = YES; } else /* Transaction is being cancelled */ { /* If we've already sent the Fin-Cancel PDU as many times as * allowed, the only choice is to give up (abandon the transaction). */ if (mp->attempts >= mib__ack_limit (NULL)) /* "Positive Ack Limit" has been reached; give up */ aaa__abandon_this_transaction (m); else /* Try again -- Re-queue the Fin for metered release */ m->is_outgoing_fin_buffered = YES; } } else if (event == NAK_TIMER_EXPIRED) /* Nak-timer expired. If all data has been received, we move on to * phase 4 (send a Finished-No-Error PDU). If not, send another * Nak PDU (if permitted). */ { /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_NAK_TIMER_EXPIRED, &(m->publik)); /* We should always be in state S1 when the Nak-timer expires, but * if we are not, then ignore it. */ if (mp->state == S2) return; /* Every time the Nak-timer expires we check to see whether we * have received all the requested retransmissions. */ if (nak__is_list_empty (&m->nak)) /* All data has been received; begin nominal Finish */ m__all_data_has_been_received (m); else /* Still some data missing, so send another Nak (if permitted) */ { /* If we've already sent the Nak PDU as many times as allowed, * then declare a Fault. */ if (mp->attempts >= mib__nak_limit (NULL)) /* Let the Fault mechanism decide what to do */ if (!m__fault_handler (m, NAK_LIMIT_REACHED)) return; /* Queue another outgoing Nak */ m->is_outgoing_nak_buffered = YES; } } else if (event == INACTIVITY_TIMER_EXPIRED) /* Inactivity-timer expired */ { /* 'Indication' callback allows engine user to respond to events */ indication__ (IND_INACTIVITY_TIMER_EXPIRED, &(m->publik)); if (mp->state == S1) { /* Restart the Inactivity-timer */ m->inactivity_timer = timer__start (mib__inactivity_timeout(NULL)); /* Let the Fault mechanism decide what else to do */ if (!m__fault_handler (m, INACTIVITY_DETECTED)) return; } else /* Transaction is being cancelled; give up */ aaa__abandon_this_transaction (m); } else /* Ignore any other events */ w_msg__ ("cfdp_engine: ignored event '%s' in state %s " "for trans '%s' <R2>.\n", event__event_as_string(event), mp->state, cfdp_trans_as_string (mp->trans)); }
/*=r=*************************************************************************/ static void m__all_data_has_been_received (MACHINE *m) /* WHAT IT DOES: Performs the actions required after all data has * been delivered. Typically, validates the delivered file, moves * it to the destination specified in the Metadata PDU, and * builds/queues an outgoing Finished-No-error PDU. */ { TRANS_STATUS *mp = &(m->publik); /* useful shorthand */ /*------------------------------------------------------------*/ /* IMPORTANT: The program will crash if the actions in this routine are * carried out more than once, so prevent that from happening. */ if (mp->delivery_code == DATA_COMPLETE) /* Return immediately! */ { w_msg__ ("cfdp_engine: 'all_data_has_been_received' called 2+ times.\n"); return; } /* We've received everything, so delivery is complete... */ mp->delivery_code = DATA_COMPLETE; /* ... and there is no need to send a Nak back to the sender. */ m->is_outgoing_nak_buffered = NO; timer__cancel (&m->nak_timer); /* If a file has been transferred, do something with it */ if (mp->md.file_transfer) { /* If the temporary file is still open, then close it */ if (m->is_there_an_open_file) { ASSERT__ (m->fp != NULL); if (m->fp == NULL) /* This should never happen, but if it does, avoid crashing */ ; else fclose_callback (m->fp); m->is_there_an_open_file = NO; } /* Validate the received file (via the checksum) */ if (!aaa__is_file_checksum_valid (m)) { if (!m__fault_handler (m, FILE_CHECKSUM_FAILURE)) return; } /* Move the temporary file to the real destination */ if (!rename_callback (mp->temp_file_name, mp->md.dest_file_name)) /* Success */ m->is_there_a_temp_file = NO; else /* Oops. Our attempt to move the file failed. */ { e_msg__ ("cfdp_engine: unable to rename '%s' to '%s'.\n", mp->temp_file_name, mp->md.dest_file_name); e_msg__ ("cfdp_engine: are they on separate filesystems?\n"); if (!m__fault_handler (m, FILESTORE_REJECTION)) return; } } /* <NOT_SUPPORTED> Filestore Requests (executed here) */ /* Build and queue a Finished Pdu */ m->fin = aaa__build_fin (NO_ERROR, mp->delivery_code); m->is_outgoing_fin_buffered = YES; mp->attempts = 0; }