?? LEFT := 1, RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE:  Network Command Processer' ??
?? NEWTITLE := 'Global Declarations' ??
MODULE nam$send_command;
?? PUSH (LISTEXT := ON) ??
*copyc clt$argument_descriptor_table
*copyc nat$command_interface
*copyc nac$reserved_saps
*copyc nae$application_interfaces
*copyc nae$directory_me_conditions
*copyc nae$manage_network_applications
*copyc nae$namve_conditions
*copyc nae$network_operator_utility
*copyc nat$bcd_time
*copyc nat$directory_interfaces
*copyc nat$gt_event
*copyc nat$network_address
*copyc nat$network_message_priority
*copyc nat$network_selector
*copyc nat$system_identifier
*copyc nat$system_title
*copyc nat$ta_alternate_protocol_class
*copyc nat$ta_preferred_protocol_class
*copyc nat$title
*copyc nat$wait_time
*copyc ost$i_wait
?? POP ??
*copyc avp$get_capability
*copyc i#move
*copyc nap$gt_close_sap
*copyc nap$gt_disconnect
*copyc nap$gt_open_sap
*copyc nap$gt_receive_connect_event
*copyc nap$gt_receive_connection_event
*copyc nap$gt_request_connection
*copyc nap$gt_send_data
*copyc nlp$end_title_translation
*copyc nlp$get_title_translation
*copyc nlp$translate_title
*copyc osp$append_status_integer
*copyc osp$append_status_parameter
*copyc osp$disestablish_cond_handler
*copyc osp$establish_condition_handler
*copyc osp$i_await_activity_completion
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc pmp$continue_to_cause
*copyc pmp$get_microsecond_clock
*copyc pmp$log
*copyc pmp$wait

*copyc nat$command_data_units

  TYPE
    command_connection = record
      active_command_count: integer,
      activity_status: ost$activity_status,
      address: nat$osi_translation_address,
      aliases: ^system_alias,
      connection_id: nat$gt_connection_id,
      event: nat$gt_event,
      last_activity_time: integer,
      queued_commands: ^queued_command,
      request_id: nat$directory_search_identifier,
      response: command_response,
      retain: boolean,
      state: (translation_required, translation_requested, connecting, connected, disconnected),
      system: nat$system_title,
      system_id: nat$system_identifier,
    recend,
    command_response = record
      header: nat$command_response_data_unit,
      message_data: SEQ (REP 128 OF cell), {scratch space for receipt of small messages}
    recend,
    system_alias = record
      next_alias: ^system_alias,
      system: nat$system_title,
    recend,
    queued_command = record
      link: ^queued_command,
      command: SEQ ( * ),
    recend;

  CONST
    max_connection_idle_time = 120000000 {microseconds...2 minutes},
    nac$command_pdu_header_length = 7,
    nac$command_protocol_version = 1,
    nac$command_syntax_code = 0;

  CONST
    index_bias = 2,
    sap_index = 2,
    timer_index = 1;

  TYPE
    wait_list_index = 1 .. nac$max_command_connections + index_bias;

  VAR
    connections: array [1 .. nac$max_command_connections] of ^command_connection,
    connect_data: array [1 .. 1] of nat$data_fragment := [[^connect_info, #SIZE (connect_info)]],
    connect_event: nat$gt_connect_event,
    connect_info: SEQ (REP 32 OF cell),
    delete_inactive_connections: boolean := FALSE,
    max_connection_index: 0 .. nac$max_command_connections,
    nil_data: array [1..1] of nat$data_fragment := [[NIL, 0]],
    sap_status: ost$activity_status,
    search_domain: nat$title_domain := [nac$catenet_domain],
    transport_sap_open: boolean := FALSE,
    transport_sap_id: nat$gt_sap_identifier,
    wait_list: ^ost$i_wait_list,
    wait_pointer: ^SEQ (REP nac$max_command_connections + index_bias of ost$i_activity) := ^wait_sequence,
    wait_sequence: SEQ (REP nac$max_command_connections + index_bias of ost$i_activity){systems+timer+SAP};

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$send_command', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$send_command (command: ^string ( * );
        system: string ( * <= nac$system_title_size );
        command_id: nat$command_identifier;
        retain_connection: boolean;
    VAR many_systems_specified: boolean;
    VAR status: ost$status);

{ PURPOSE: This procedure sends a network command to a single destination.
{ DESIGN:  Upon the first call to this procedure, a Generic Transport SAP
{          is opened (validation of Network Operator privilege will also
{          occur at this point). A translation request for the system title
{          will be initiated if a translation is not already known.
{          If a translation is available but the system is not connected,
{          a connection request is issued. If a connection exists, the command
{          is sent. Processing of the command is suspended after the
{          translation request, the connection request, or the command
{          transmission. The remaining processing is resumed when possible
{          by the next call to nap$receive_command_response.

    VAR
      address: nat$internet_address,
      command_pdu: [STATIC] nat$command_data_unit := [ nac$command_pdu_header_length,
            nac$command_protocol_version, *, nac$command_syntax_code, *],
      connection: ^command_connection,
      ignore_status: ost$status,
      index: integer,
      message: [STATIC] array [1 .. 2] of nat$data_fragment := [[^command_pdu, #SIZE (command_pdu)], [ * , * ]
        ],
      network_operation: boolean,
      wait_index: wait_list_index;

    status.normal := TRUE;
    IF NOT transport_sap_open THEN
      avp$get_capability (avc$network_operation, avc$user, network_operation, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;
      IF NOT network_operation THEN
        osp$set_status_abnormal (nac$status_id, nae$invalid_user, 'NETWORK OPERATOR UTILITY', status);
        RETURN;
      IFEND;
      command_pdu.privilege := nac$max_privilege;
      nap$gt_open_sap (nac$max_command_connections, nac$system_message_priority, {reserved_sap=} FALSE,
            transport_sap_id, address, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;
      transport_sap_open := TRUE;
      nap$gt_receive_connect_event (transport_sap_id, connect_data, osc$nowait, connect_event,
            sap_status, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;

    /initialize_connection_entries/
      FOR index := 1 TO UPPERBOUND (connections) DO
        connections [index] := NIL;
      FOREND /initialize_connection_entries/;
      max_connection_index := 0;
      RESET wait_pointer;
      NEXT wait_list: [1 .. index_bias] IN wait_pointer;
      wait_list^ [timer_index].activity := osc$i_await_time;
      wait_list^ [sap_index].activity := nac$i_await_activity_status;
      wait_list^ [sap_index].activity_status := ^sap_status;
    IFEND;

    find_system (system, connection, wait_index);
    IF connection = NIL THEN
      IF many_systems_specified THEN
        cache_system_titles (status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
        many_systems_specified := FALSE;
        find_system (system, connection, wait_index);
      IFEND;
      IF connection = NIL THEN
        create_system_entry (system, connection, wait_index, status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
      IFEND;
    IFEND;

    connection^.retain := retain_connection;
    command_pdu.identifier := command_id;
    message [2].address := command;
    message [2].length := #SIZE (command^);

    CASE connection^.state OF
    = translation_required =
      request_translation (connection^, wait_index, status);
      IF status.normal THEN
        queue_command (message, connection^, status);
      IFEND;

    = connected =
      send_command (message, connection^, wait_index, status);
      IF (NOT status.normal) AND (status.condition = nae$system_disconnected) THEN {try to reconnect}
        status.normal := TRUE;
        connect_system (connection^, wait_index, status);
        IF status.normal THEN
          queue_command (message, connection^, status);
        IFEND;
      IFEND;

    = disconnected =
      connect_system (connection^, wait_index, status);
      IF status.normal THEN
        queue_command (message, connection^, status);
      IFEND;

    ELSE
      queue_command (message, connection^, status);
    CASEND;

    delete_inactive_connections := TRUE;

  PROCEND nap$send_command;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$receive_command_response', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$receive_command_response (wait_time: nat$wait_time;
        response: nat$data_fragment;
    VAR response_length: nat$data_length;
    VAR system: nat$system_title;
    VAR command_id: nat$command_identifier;
    VAR response_code: nat$command_response_code;
    VAR normal_response: boolean;
    VAR truncated: boolean;
    VAR status: ost$status);

{ PURPOSE: This procedure delivers a single response for a command sent to a system
{          in the network.
{ DESIGN:  All translation requests and connection requests initiated by the procedure
{          nap$send_command are processed when responses are available. When a data event
{          is received, it is returned as a command response to the caller.
{          Inactive connections are deleted on the first call to this routine after a call
{          to nap$send_command.

      PROCEDURE condition_handler (condition: pmt$condition;
            ignore_condition_descriptor: ^pmt$condition_information;
            sa: ^ost$stack_frame_save_area;
        VAR condition_status: ost$status);

        CASE condition.selector OF
        = pmc$system_conditions =
          pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
        = ifc$interactive_condition =
          osp$set_status_condition (nae$no_event, status);
          pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
        = pmc$user_defined_condition =
          IF condition.user_condition_name = 'OSC$JOB_RECOVERY' THEN
            osp$set_status_abnormal (nac$status_id, nae$job_recovery, 'NETWORK OPERATOR', status);
            pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
          IFEND;
          condition_status.normal := TRUE;
        ELSE
          condition_status.normal := TRUE;
        CASEND;

      PROCEND condition_handler;

    VAR
      address: nat$osi_translation_address,
      alias_index: wait_list_index,
      alias_connection: ^command_connection,
      connection: ^command_connection,
      current_time: integer,
      end_time: integer,
      ignore_status: ost$status,
      index: integer,
      remaining_time: integer,
      response_buffer: array [1 .. 1] of nat$data_fragment,
      response_received: boolean,
      system_id: nat$system_identifier,
      system_identifier: ^nat$system_identifier,
      system_name: nat$system_title;

    status.normal := TRUE;

    pmp$get_microsecond_clock (current_time, ignore_status);
    end_time := (current_time DIV 1000) + wait_time;
    response_received := FALSE;

    IF delete_inactive_connections THEN
      FOR index := 1 to max_connection_index DO
        connection := connections [index];
        IF (connection^.state = connected) AND (connection^.queued_commands = NIL) AND (connection^.
              active_command_count = 0) AND (current_time > (connection^.last_activity_time +
              max_connection_idle_time)) THEN
          disconnect_system (connection^, index + index_bias, ignore_status);
        IFEND;
      FOREND;
      delete_inactive_connections := FALSE;
    IFEND;

    osp$establish_condition_handler (^condition_handler, {block exit=} FALSE);

    REPEAT
      pmp$get_microsecond_clock (current_time, ignore_status);
      remaining_time := end_time - (current_time DIV 1000);
      IF remaining_time > 0 THEN
        wait_list^ [timer_index].milliseconds := remaining_time;
      ELSE
        wait_list^ [timer_index].milliseconds := 1;
      IFEND;
      osp$i_await_activity_completion (wait_list^, index, status);
      IF status.normal THEN
        IF index = timer_index THEN
          osp$set_status_condition (nae$no_event, status);
        ELSEIF index = sap_index THEN
          nap$gt_disconnect (connect_event.connection, nil_data, ignore_status);
          nap$gt_receive_connect_event (transport_sap_id, connect_data, osc$nowait, connect_event,
                sap_status, status);
          IF NOT status.normal THEN
            RETURN;
          IFEND;
        ELSE
          connection := connections [index - index_bias];
          IF connection^.state = translation_requested THEN
            get_title_translation (connection^.request_id, system_name, system_id, address, status);
            IF status.normal THEN
              nlp$end_title_translation (connection^.request_id, ignore_status);
              find_alias (system_id, connection^.system, alias_connection, alias_index);
              IF alias_connection = NIL THEN
                connection^.address := address;
                connection^.system_id := system_id;
                connect_system (connection^, index, status);
              ELSE {send command on existing connection to alias entry}
                append_command_queue (connection^.queued_commands, alias_connection^);
                IF alias_connection^.state = disconnected THEN
                  connect_system (alias_connection^, alias_index, status);
                ELSEIF alias_connection^.state = connected THEN
                  send_queued_commands (alias_connection^, alias_index, status);
                ELSE
                  {no action required at this time...wait for pending event.
                IFEND;
                delete_connection (connection^, index, ignore_status);
              IFEND;
            ELSEIF status.condition = nae$no_translation_available THEN
              status.normal := TRUE;
            ELSEIF (status.condition = nae$translation_req_not_active) OR (status.condition =
                    nae$directory_search_complete) THEN
              osp$set_status_abnormal (nac$status_id, nae$unknown_system, connection^.system, status);
              delete_connection (connection^, index, ignore_status);
            ELSE {unexpected abnormal status}
            IFEND;

          ELSEIF NOT connection^.activity_status.status.normal THEN
            status := connection^.activity_status.status;

          ELSE
            CASE connection^.event.kind OF
            = nac$gt_data_event =
              response_received := TRUE;
              IF connection^.event.data.data_length - #SIZE (nat$command_response_data_unit) > 0 THEN
                response_length := connection^.event.data.data_length - #SIZE(nat$command_response_data_unit);
                i#move (^connection^.response.message_data, response.address, response_length);
              ELSE
                response_length := 0;
              IFEND;
              system := connection^.response.header.system_title;
              normal_response := connection^.response.header.flags.normal_response;
              truncated := connection^.response.header.flags.truncated;
              response_code := connection^.response.header.condition_code;
              command_id := connection^.response.header.command_id;
              connection^.active_command_count := connection^.active_command_count - 1;
              IF NOT connection^.event.data.end_of_message THEN {get rest of message}
                response_buffer [1].address := #address (#ring (response.address), #segment
                      (response.address), #offset (response.address) + response_length);
                response_buffer [1].length := response.length - response_length;
                nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$wait,
                      connection^.event, connection^.activity_status, status);
                IF status.normal AND (connection^.event.kind = nac$gt_data_event) THEN
                  response_length := response_length + connection^.event.data.data_length;
                  truncated := truncated OR (NOT connection^.event.data.end_of_message);
                IFEND;
              IFEND;
              IF (connection^.active_command_count = 0) AND (NOT connection^.retain) THEN
                disconnect_system (connection^, index, ignore_status);
              ELSE
                response_buffer [1].address := ^connection^.response.header;
                response_buffer [1].length := #SIZE (command_response);
                nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$nowait,
                      connection^.event, connection^.activity_status, status);
                pmp$get_microsecond_clock (connection^.last_activity_time, ignore_status);
              IFEND;

            = nac$gt_accept_event =
              connection^.state := connected;
              send_queued_commands (connection^, index, status);
              response_buffer [1].address := ^connection^.response.header;
              response_buffer [1].length := #SIZE (command_response);
              nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$nowait,
                    connection^.event, connection^.activity_status, status);

            = nac$gt_disconnect_event, nac$gt_reject_event =
              IF connection^.state = connecting THEN
                osp$set_status_abnormal (nac$status_id, nae$command_connection_rejected, connection^.system,
                      status);
              ELSEIF connection^.active_command_count > 0 THEN
                osp$set_status_abnormal (nac$status_id, nae$access_lost, connection^.system, status);
              IFEND;
              delete_connection (connection^, index, ignore_status);

            ELSE
              pmp$log ('unexpected transport event type', ignore_status);
            CASEND;
          IFEND;
        IFEND;
      IFEND;

    UNTIL response_received OR NOT status.normal;

    osp$disestablish_cond_handler;

  PROCEND nap$receive_command_response;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$terminate_command', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$terminate_command (system: string ( * <= nac$system_title_size );
        retain_connection: boolean;
    VAR status: ost$status);

{ PURPOSE: This procedure terminates whatever activity is being performed for a specific
{          network system and returns an appropriate status response based on the current
{          state of that system. If a command has already been transmitted to a system,
{          execution of the command cannot be stopped by this procedure.

    VAR
      connection: ^command_connection,
      ignore_status: ost$status,
      wait_index: wait_list_index;

    status.normal := TRUE;
    find_system (system, connection, wait_index);

    IF connection <> NIL THEN
      connection^.retain := retain_connection;
      CASE connection^.state OF
      = translation_requested =
        nlp$end_title_translation (connection^.request_id, ignore_status);
        osp$set_status_abnormal (nac$status_id, nae$unknown_system, connection^.system, status);
        delete_connection (connection^, wait_index, ignore_status);

      = connecting =
        osp$set_status_abnormal (nac$status_id, nae$command_connection_ignored, connection^.system, status);
        delete_connection (connection^, wait_index, ignore_status);

      = connected =
        IF connection^.active_command_count > 0 THEN
          osp$set_status_abnormal (nac$status_id, nae$response_not_received, connection^.system, status);
          connection^.active_command_count := connection^.active_command_count - 1;
        IFEND;
        IF (connection^.active_command_count = 0) AND (NOT connection^.retain) THEN
          disconnect_system (connection^, wait_index, ignore_status);
        IFEND;

      ELSE
      CASEND;
    IFEND;

  PROCEND nap$terminate_command;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$end_command_processing', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$end_command_processing (VAR status: ost$status);

{ PURPOSE: This procedure terminates network command processing.
{ DESIGN:  All outstanding command connections are closed and the Generic
{          Transport SAP used for command connections is closed.


    status.normal := TRUE;

    IF transport_sap_open THEN

    /close_all_connections/
      WHILE max_connection_index > 0 DO
        delete_connection (connections [1]^, 1 + index_bias, {ignore} status);
      WHILEND /close_all_connections/;
      nap$gt_close_sap (transport_sap_id, status);
      transport_sap_open := FALSE;
    IFEND;
  PROCEND nap$end_command_processing;
?? OLDTITLE ??
?? NEWTITLE := 'append_command_queue', EJECT ??

  PROCEDURE append_command_queue (VAR command_queue: ^queued_command;
    VAR connection: command_connection);

    VAR
      index: integer,
      last_link: ^^queued_command;

    IF command_queue <> NIL THEN
      last_link := ^connection.queued_commands;
      WHILE last_link^ <> NIL DO
        last_link := ^last_link^^.link;
      WHILEND;
      last_link^ := command_queue;
      command_queue := NIL;
    IFEND;

  PROCEND append_command_queue;
?? OLDTITLE ??
?? NEWTITLE := 'cache_system_titles', EJECT ??

  PROCEDURE cache_system_titles (
    VAR status: ost$status);

{ If many translation requests are going to be required, a wild card translation request for all
{ system titles will first be issued so that the translations will be cached by Directory and we
{ will not flood the network with requests for individual titles. Note that Directory sends one
{ PDU to each known subnet for each active device in the host configuration.

    VAR
      address: nat$osi_translation_address,
      alias_connection: ^command_connection,
      alias_index: wait_list_index,
      ignore_status: ost$status,
      new_connection: ^command_connection,
      request_id: nat$directory_search_identifier,
      system_id: nat$system_identifier,
      system_name: nat$system_title,
      system_title: [STATIC] string (nac$system_title_size + nac$system_title_prefix_size) :=
            nac$system_title_prefix CAT '*',
      wait_index: wait_list_index;

    nlp$translate_title (system_title, {wild_card} TRUE, nac$unknown_protocol, {recurrent_search} FALSE,
          search_domain, nac$cdna_internal, request_id, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;
    pmp$wait (4000, 4000); {Wait no more than four seconds for first translation}
    REPEAT
      get_title_translation (request_id, system_name, system_id, address, status);
      IF (NOT status.normal) AND (status.condition = nae$no_translation_available) THEN
        pmp$wait (4000, 4000); {Wait no more than four seconds after the last translation arrives}
        get_title_translation (request_id, system_name, system_id, address, status);
      IFEND;
      IF status.normal THEN
        find_alias (system_id, system_name, alias_connection, alias_index);
        IF alias_connection = NIL THEN
          create_system_entry (system_name, new_connection, wait_index, status);
          IF status.normal THEN
            new_connection^.address := address;
            new_connection^.system_id := system_id;
            new_connection^.state := disconnected;
          IFEND;
        IFEND;
      IFEND;
    UNTIL NOT status.normal;
    IF status.condition <> nae$directory_search_complete THEN
      nlp$end_title_translation (request_id, ignore_status);
      IF status.condition = nae$no_translation_available THEN
        status.normal := TRUE;
      IFEND;
    ELSE
      status.normal := TRUE;
    IFEND;

  PROCEND cache_system_titles;
?? OLDTITLE ??
?? NEWTITLE := 'clear_connection', EJECT ??

  PROCEDURE clear_connection (VAR connection: command_connection;
        index: wait_list_index;
    VAR status: ost$status);

    VAR
      next_alias: ^system_alias,
      current_alias: ^system_alias;

    free_queued_commands (connection);
    IF (connection.state = connecting) OR (connection.state = connected) THEN
      disconnect_system (connection, index, status);
    IFEND;

    current_alias := connection.aliases;
    WHILE current_alias <> NIL DO
      next_alias := current_alias^.next_alias;
      FREE current_alias;
      current_alias := next_alias;
    WHILEND;

  PROCEND clear_connection;
?? OLDTITLE ??
?? NEWTITLE := 'connect_system', EJECT ??

  PROCEDURE connect_system (VAR connection: command_connection;
        wait_index: wait_list_index;
    VAR status: ost$status);

    VAR
      alternate_protocol_class: nat$ta_alternate_protocol_class,
      connection_data: array [1 .. 1] of nat$data_fragment,
      ignore_status: ost$status,
      preferred_protocol_class: nat$ta_preferred_protocol_class,
      protocol_version: [STATIC, READ] 0 .. 255 := nac$command_protocol_version,
      transport_address: nat$network_address;

    CASE connection.address.kind OF
    = nac$osi_transport_address =
      transport_address.kind := nac$osi_transport_address;
      transport_address.osi_transport_address := connection.address.osi_transport_address;
      preferred_protocol_class := nac$ta_preferred_class_4_clns;
      alternate_protocol_class := nac$ta_no_alternate_protocol;
    ELSE
      osp$set_status_abnormal (nac$status_id, nae$unsupported_address_kind, connection.system, status);
      osp$append_status_integer (osc$status_parameter_delimiter, connection.address.kind, 10, FALSE, status);
      RETURN;
    CASEND;

    connection_data [1].address := ^protocol_version;
    connection_data [1].length := #SIZE (protocol_version);
    nap$gt_request_connection (transport_sap_id, transport_address, connection_data, NIL,
          preferred_protocol_class, alternate_protocol_class, connection.connection_id, status);
    IF status.normal THEN
      connection.state := connecting;
      connection_data [1].address := ^connection.response;
      connection_data [1].length := #SIZE (connection.response);
      nap$gt_receive_connection_event (connection.connection_id, connection_data, osc$nowait,
            connection.event, connection.activity_status, status);
      wait_list^ [wait_index].activity := nac$i_await_activity_status;
      wait_list^ [wait_index].activity_status := ^connection.activity_status;
    ELSE
      delete_connection (connection, wait_index, ignore_status);
    IFEND;

  PROCEND connect_system;
?? OLDTITLE ??
?? NEWTITLE := 'create_system_entry', EJECT ??

  PROCEDURE create_system_entry (system: string ( * <= nac$system_title_size );
    VAR connection: ^command_connection;
    VAR wait_index: wait_list_index;
    VAR status: ost$status);

    PROCEDURE [INLINE] initialize_connection_entry (system: string ( * <= nac$system_title_size);
      VAR connection: command_connection);

      connection.system := system;
      connection.system_id := 0;
      connection.state := translation_required;
      connection.active_command_count := 0;
      connection.queued_commands := NIL;
      connection.aliases := NIL;

    PROCEND initialize_connection_entry;

    VAR
      connection_index: integer;

    IF max_connection_index < UPPERVALUE (max_connection_index) THEN
      max_connection_index := max_connection_index + 1;
      ALLOCATE connections [max_connection_index];
      connection := connections [max_connection_index];
      IF connection = NIL THEN
        max_connection_index := max_connection_index - 1;
        osp$set_status_abnormal (nac$status_id, nae$allocation_failed, 'command queue', status);
        RETURN;
      IFEND;
      initialize_connection_entry (system, connection^);
      wait_index := max_connection_index + index_bias;
      RESET wait_pointer;
      NEXT wait_list: [1 .. wait_index] IN wait_pointer;
      wait_list^ [wait_index].activity := osc$i_null_activity;
      RETURN;
    IFEND;

  /search_for_unused/
    FOR connection_index := 1 TO max_connection_index DO
      IF (connections [connection_index]^.state = disconnected) OR (connections [connection_index]^.state =
            translation_required) THEN
        connection := connections [connection_index];
        wait_index := connection_index + index_bias;
        clear_connection (connection^, wait_index, status);
        initialize_connection_entry (system, connection^);
        RETURN;
      IFEND;
    FOREND /search_for_unused/;

  /disconnect_inactive_entry/
    FOR connection_index := 1 TO max_connection_index DO
      IF (connections [connection_index]^.state = connected) AND (connections [connection_index]^.
            queued_commands = NIL) AND (connections [connection_index]^.active_command_count = 0) THEN
        connection := connections [connection_index];
        wait_index := connection_index + index_bias;
        clear_connection (connection^, wait_index, status);
        initialize_connection_entry (system, connection^);
        RETURN;
      IFEND;
    FOREND /disconnect_inactive_entry/;

    connection := NIL; {connection table full}
    osp$set_status_abnormal (nac$status_id, nae$allocation_failed, 'connection table', status);

  PROCEND create_system_entry;
?? OLDTITLE ??
?? NEWTITLE := 'delete_connection', EJECT ??

  PROCEDURE delete_connection (VAR connection: command_connection;
        index: wait_list_index;
    VAR status: ost$status);

    VAR
      next_alias: ^system_alias,
      current_alias: ^system_alias;

    status.normal := TRUE;
    clear_connection (connection, index, status);
    FREE connections [index - index_bias];
    IF max_connection_index > 1 THEN {compress connection list and wait list}
      connections [index - index_bias] := connections [max_connection_index];
      wait_list^ [index] := wait_list^ [UPPERBOUND (wait_list^)];
    IFEND;
    max_connection_index := max_connection_index - 1;
    RESET wait_pointer;
    NEXT wait_list: [1 .. max_connection_index + index_bias] IN wait_pointer;

  PROCEND delete_connection;
?? OLDTITLE ??
?? NEWTITLE := 'disconnect_system', EJECT ??

  PROCEDURE disconnect_system (VAR connection: command_connection;
        wait_index: wait_list_index;
    VAR status: ost$status);

    free_queued_commands (connection);
    nap$gt_disconnect (connection.connection_id, nil_data, status);
    connection.state := disconnected;
    wait_list^ [wait_index].activity := osc$i_null_activity;

  PROCEND disconnect_system;
?? OLDTITLE ??
?? NEWTITLE := 'find_alias', EJECT ??

  PROCEDURE find_alias (system_id: nat$system_identifier;
        system: nat$system_title;
    VAR connection: ^command_connection;
    VAR wait_index: wait_list_index);

    VAR
      connection_index: integer,
      current_alias: ^system_alias;

  /search_for_id/
    FOR connection_index := 1 TO max_connection_index DO
      IF connections [connection_index]^.system_id = system_id THEN
        connection := connections [connection_index];
        wait_index := connection_index + index_bias;
        ALLOCATE current_alias;
        current_alias^.system := system;
        current_alias^.next_alias := connection^.aliases;
        connection^.aliases := current_alias;
        RETURN;
      IFEND;
    FOREND /search_for_id/;

    connection := NIL;

  PROCEND find_alias;
?? OLDTITLE ??
?? NEWTITLE := 'find_system', EJECT ??

  PROCEDURE find_system (system: string ( * <= nac$system_title_size );
    VAR connection: ^command_connection;
    VAR wait_index: wait_list_index);

    VAR
      current_alias: ^system_alias,
      connection_index: integer;

  /search_for_title/
    FOR connection_index := 1 TO max_connection_index DO
      IF connections [connection_index]^.system = system THEN
        connection := connections [connection_index];
        wait_index := connection_index + index_bias;
        RETURN;
      ELSE
        current_alias := connections [connection_index]^.aliases;
        WHILE current_alias <> NIL DO
          IF current_alias^.system = system THEN
            connection := connections [connection_index];
            wait_index := connection_index + index_bias;
            RETURN;
          IFEND;
          current_alias := current_alias^.next_alias;
        WHILEND;
      IFEND;
    FOREND /search_for_title/;

    connection := NIL;

  PROCEND find_system;
?? OLDTITLE ??
?? NEWTITLE := 'free_queued_commands', EJECT ??

  PROCEDURE free_queued_commands (VAR connection: command_connection);

    VAR
      command_to_free: ^queued_command,
      next_command: ^queued_command;

    command_to_free := connection.queued_commands;
    connection.queued_commands := NIL;
    WHILE command_to_free <> NIL DO
      next_command := command_to_free^.link;
      FREE command_to_free;
      command_to_free := next_command;
    WHILEND;

  PROCEND free_queued_commands;
?? OLDTITLE ??
?? NEWTITLE := 'get_title_translation', EJECT ??

  PROCEDURE get_title_translation (request_id: nat$directory_search_identifier;
    VAR system_name: nat$system_title;
    VAR system_id: nat$system_identifier;
    VAR address: nat$osi_translation_address;
    VAR status: ost$status);

    VAR
      identifier: nat$directory_entry_identifier,
      network_address: ^SEQ ( * ),
      prefix: ^ SEQ ( * ),
      priority: nat$directory_priority,
      service: nat$protocol,
      system_identifier: ^nat$system_identifier,
      title: string (nac$max_title_length),
      user_identifier: ost$name,
      user_info_length: 0 .. nac$max_directory_data_length;


    nlp$get_title_translation (request_id, title, address, service, NIL, user_info_length, priority,
          user_identifier, identifier, status);
    IF status.normal THEN
      system_name := title (nac$system_title_prefix_size + 1, *);
      CASE address.kind OF
      = nac$osi_transport_address =
        network_address := ^address.osi_transport_address.network_address;
        RESET network_address;
        NEXT prefix: [[REP (address.osi_transport_address.network_address_length -
              #SIZE (nat$network_selector) - #SIZE (nat$system_identifier)) OF cell]] IN network_address;
        NEXT system_identifier IN network_address;
        system_id := system_identifier^;
      ELSE
        system_id := 0;
      CASEND;
    IFEND;

  PROCEND get_title_translation;

?? OLDTITLE ??
?? NEWTITLE := 'queue_command', EJECT ??

  PROCEDURE queue_command (command: nat$data_fragments;
    VAR connection: command_connection;
    VAR status: ost$status);

    VAR
      command_length: nat$data_length,
      index: integer,
      last_link: ^^queued_command,
      next_link: ^queued_command,
      save_address: ^SEQ ( * ),
      save_area: ^SEQ ( * );

    command_length := 0;
    FOR index := LOWERBOUND (command) TO UPPERBOUND (command) DO
      command_length := command_length + command [index].length;
    FOREND;

    last_link := ^connection.queued_commands;
    WHILE last_link^ <> NIL DO
      last_link := ^last_link^^.link;
    WHILEND;

    ALLOCATE next_link: [[REP command_length OF cell]];
    IF next_link <> NIL THEN
      last_link^ := next_link;
      last_link^^.link := NIL;
      save_area := ^next_link^.command;
      RESET save_area;

      FOR index := LOWERBOUND (command) TO UPPERBOUND (command) DO
        NEXT save_address: [[REP command [index].length OF cell]] IN save_area;
        i#move (command [index].address, save_address, command [index].length);
      FOREND;
    ELSE
      osp$set_status_abnormal (nac$status_id, nae$allocation_failed, 'command queue', status);
    IFEND;

  PROCEND queue_command;
?? OLDTITLE ??
?? NEWTITLE := 'request_translation', EJECT ??

  PROCEDURE request_translation (VAR connection: command_connection;
        wait_index: wait_list_index;
    VAR status: ost$status);

    VAR
      system_title: [STATIC] string (nac$system_title_size + nac$system_title_prefix_size) :=
            nac$system_title_prefix;

    system_title (nac$system_title_prefix_size + 1, * ) := connection.system;

    nlp$translate_title (system_title, {wild_card} FALSE, nac$unknown_protocol, {recurrent_search} FALSE,
          search_domain, nac$cdna_internal, connection.request_id, status);
    IF status.normal THEN
      connection.state := translation_requested;
      wait_list^ [wait_index].activity := nac$i_await_title_translation;
      wait_list^ [wait_index].translation_request := connection.request_id;
    IFEND;
  PROCEND request_translation;
?? OLDTITLE ??
?? NEWTITLE := 'send_command', EJECT ??

  PROCEDURE send_command (command: nat$data_fragments;
    VAR connection: command_connection;
        wait_index: wait_list_index;
    VAR status: ost$status);

    VAR
      ignore_status: ost$status,
      send_status: ost$activity_status;

    IF connection.queued_commands <> NIL THEN
      send_queued_commands (connection, wait_index, status);
      IF NOT status.normal THEN
        free_queued_commands (connection);
        RETURN;
      IFEND;
    IFEND;

    nap$gt_send_data (connection.connection_id, command, {end_of_message=} TRUE, osc$wait, send_status,
          status);
    IF status.normal AND NOT send_status.status.normal THEN
      status := send_status.status;
    IFEND;
    IF status.normal THEN
      connection.active_command_count := connection.active_command_count + 1;
      pmp$get_microsecond_clock (connection.last_activity_time, ignore_status);
    ELSEIF status.condition = nae$connection_not_open THEN
      osp$set_status_abnormal (nac$status_id, nae$system_disconnected, connection.system, status);
      connection.state := disconnected;
      connection.active_command_count := 0;
      wait_list^ [wait_index].activity := osc$i_null_activity;
    IFEND;

  PROCEND send_command;
?? OLDTITLE ??
?? NEWTITLE := 'send_queued_commands', EJECT ??

  PROCEDURE send_queued_commands (VAR connection: command_connection;
        wait_index: wait_list_index;
    VAR status: ost$status);

    VAR
      command_to_free: ^queued_command,
      message: array [1 .. 1] of nat$data_fragment,
      queue: ^queued_command;

    queue := connection.queued_commands;
    connection.queued_commands := NIL;

    WHILE queue <> NIL DO
      message [1].address := ^queue^.command;
      message [1].length := #SIZE (queue^.command);
      send_command (message, connection, wait_index, status);
      IF NOT status.normal THEN
        connection.queued_commands := queue;
        RETURN;
      IFEND;
      command_to_free := queue;
      queue := queue^.link;
      FREE command_to_free;
    WHILEND;

  PROCEND send_queued_commands;

MODEND nam$send_command;
