?? NEWTITLE := 'NOS/VE Network Access : Channel Connection Event Manager' ??
MODULE nlm$cc_network_event_manager;
?? RIGHT := 110 ??

{ PURPOSE:
{   The purpose of this module is to process all incoming Channel Connection
{   PDU's. The PDU is associated with the connection for which it is destined
{   and the CC protocol is applied. Any protocol violations result in discarding
{   the PDU and the reset of the network device which delivered the PDU.
{   Valid PDU's are processed as per the protocol, with the state machine being
{   updated and events delivered to the CC users.
{
{ DESIGN:
{   This module was designed to reside in the OSF$JOB_TEMPLATE_23D library and
{   execute in any task.
{
{ NOTES:
{   Procedures in this module are grouped by function, and order within each group
{   is based on the flow of control.

?? NEWTITLE := 'Global Declarations Referenced by this Module' ??
?? PUSH (LISTEXT := ON) ??
*copyc nlt$cc_seq#_or_connect_time
*copyc oss$job_paged_literal
*copyc ost$system_flag
?? POP ??
*copyc nap$condition_handler_trace
*copyc nap$namve_system_error
*copyc nlp$bm_add_message_prefix
*copyc nlp$bm_create_message
*copyc nlp$bm_extract_message_prefix
*copyc nlp$bm_get_message_header
*copyc nlp$bm_get_message_length
*copyc nlp$bm_get_message_prefix
*copyc nlp$bm_release_message
*copyc nlp$cc_abort_connection
*copyc nlp$cc_decr_connection_count
*copyc nlp$cc_find_duplicate_connect
*copyc nlp$cc_get_device_specific_attr
*copyc nlp$cc_get_event_processor
*copyc nlp$cc_get_exclusive_via_cid
*copyc nlp$cc_get_exclus_to_unaccepted
*copyc nlp$cc_get_received_messages
*copyc nlp$cc_grant_credits
*copyc nlp$cc_incr_connection_count
*copyc nlp$cc_obtain_credits
*copyc nlp$cc_requeue_msgs_on_conn
*copyc nlp$cc_reset_device
*copyc nlp$cc_shut_down_connection
*copyc nlp$cc_send_buffer_empty
*copyc nlp$cc_send_pdu
*copyc nlp$cc_user_data_pad_size
*copyc nlp$cl_activate_layer
*copyc nlp$cl_add_device_to_connection
*copyc nlp$cl_clear_exclusive_access
*copyc nlp$cl_create_connection
*copyc nlp$cl_deactivate_layer
*copyc nlp$cl_decr_priority_connection
*copyc nlp$cl_get_layer_connection
*copyc nlp$cl_incr_priority_connection
*copyc nlp$cl_release_exclusive_access
*copyc nlp$connection_queued
*copyc nlp$get_nonexclusive_access
*copyc nlp$process_receiving_conection
*copyc nlp$release_nonexclusive_access
*copyc osp$establish_block_exit_hndlr
*copyc osp$disestablish_cond_handler
*copyc osp$decrement_locked_variable
*copyc osp$increment_locked_variable
*copyc osp$sub_from_locked_variable
*copyc osp$set_status_condition
*copyc pmp$get_executing_task_gtid
*copyc syp$cycle

*copyc nav$global_osi_statistics
*copyc nav$network_paged_heap
*copyc nav$network_procedures
*copyc nav$statistics_enabled
*copyc nav$system_id
*copyc nav$system_input_taskid
*copyc nlv$bm_large_buffer_size
*copyc nlv$bm_null_message_id
*copyc nlv$cc_grant_credit_trigger
*copyc nlv$cc_initialize_connection
*copyc nlv$cc_work_list
*copyc nlv$configured_network_devices
*copyc osv$task_private_heap
?? OLDTITLE ??
?? NEWTITLE := 'Global Debug Declarations', EJECT ??

*copyc nav$debug_mode

  VAR
    nav$multiple_namve_hndler_calls: [XREF] integer,
    nav$namve_tsk_hndl_active_count: [XREF] integer;

?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by this Module', EJECT ??

  VAR
    clear_to_send: [oss$job_paged_literal, READ] nlt$cc_event := [nlc$cc_clear_to_send_event];

?? OLDTITLE ??
?? NEWTITLE := '[XDCL] nlp$cc_receive_event', EJECT ??
*copy nlh$cc_receive_event

  PROCEDURE [XDCL] nlp$cc_receive_event
    (VAR cc_pdu { input, output } : nlt$bm_message_id);

    VAR
      actual: integer,
      cc_header: nlt$cc_protocol_header,
      connection_id: nlt$cl_connection_id,
      device_received_on: nlt$device_identifier,
      duplicate_connect_request: boolean,
      local_status: ost$status,
      sequence#_or_connect_timestamp: nlt$cc_seq#_or_connect_time;

{ The following received message attributes MUST be retreived from the message
{ descriptor BEFORE any extracts are attempted. This is because the received
{ message attributes are in the first message descriptor which may be released
{ as part of an extract.

    connection_id := cc_pdu.descriptor^.received_message.connection_id;
    device_received_on := cc_pdu.descriptor^.received_message.device_id;
    sequence#_or_connect_timestamp := cc_pdu.descriptor^.received_message.sequence#_or_connect_timestamp;

    extract_cc_header (cc_pdu, cc_header, local_status);
    IF local_status.normal THEN
      CASE cc_header.kind OF
      = nlc$cc_connect_request =
        nlp$cc_find_duplicate_connect (device_received_on, cc_header.connect_request.source_reference,
              duplicate_connect_request);
        IF NOT duplicate_connect_request THEN
          establish_new_connection (device_received_on, sequence#_or_connect_timestamp.
                time_connect_request_received, cc_header, cc_pdu);
        ELSE
{ 28. }

{! statistics begin}

          IF nav$statistics_enabled THEN
            osp$increment_locked_variable (nav$global_osi_statistics.
                  channel_connection_device^ [device_received_on].duplicate_connect_indications, 0, actual);
          IFEND;

{! statistics end}

          nlp$cc_reset_device (device_received_on);
          nlp$bm_release_message (cc_pdu);
        IFEND;

      = nlc$cc_connect_confirm .. nlc$cc_expedited_data =
        process_cc_event (device_received_on, sequence#_or_connect_timestamp.sequence_number, connection_id,
              cc_header, cc_pdu);

      = nlc$cc_global_window =

{  Ignore global window PDU. Change in global flow control will
{  be communicated as part of channel protocol.

        nlp$bm_release_message (cc_pdu);
      ELSE { Protocol error - Invalid PDU kind.
        nlp$cc_reset_device (device_received_on);
        nlp$bm_release_message (cc_pdu);
      CASEND;
    ELSE
      nlp$cc_reset_device (device_received_on);
      nlp$bm_release_message (cc_pdu);
    IFEND;

  PROCEND nlp$cc_receive_event;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$cc_receive_data' ??
?? NEWTITLE := 'terminate_input_processing -- Job Recovery / Task Termination', EJECT ??
*copy nlh$cc_receive_data

  PROCEDURE [XDCL] nlp$cc_receive_data
    (    cl_connection: ^nlt$cl_connection);

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

      VAR
        i: integer;

      nap$condition_handler_trace (condition, sa);
      IF ((pmc$program_termination IN condition.reason) OR (pmc$program_abort IN condition.reason)) THEN
        IF next_message <> NIL THEN

{ Reverse the order of the remaining messages to LIFO.

          previous_message := NIL;
          current_message := next_message;
          REPEAT
            next_message := current_message^.received_message.next_received_message;
            current_message^.received_message.next_received_message := previous_message;
            previous_message := current_message;
            current_message := next_message;
          UNTIL (current_message = NIL);

          received_messages := previous_message;
          nlp$cc_requeue_msgs_on_conn (cl_connection, received_messages);
        IFEND;
      IFEND;
      condition_status.normal := TRUE;
    PROCEND terminate_input_processing;
?? OLDTITLE, EJECT ??

    VAR
      current_message,
      previous_message,
      next_message: ^nlt$bm_message_descriptor,
      ignore_status: ost$status,
      message_id: nlt$bm_message_id,
      received_messages: ^nlt$bm_message_descriptor;

{ If the connection is queued, the input must be processed by the system input task.
{ Problems occur if the received messages close the connection while the queued flag is set.

    IF nlp$connection_queued (cl_connection) THEN
      RETURN; {----->
    IFEND;

    nlp$cc_get_received_messages (cl_connection, received_messages);
    IF received_messages <> NIL THEN
      osp$establish_block_exit_hndlr (^terminate_input_processing);

{ Relink the received message list in order to process the messages in FIFO order.
      previous_message := NIL;
      current_message := received_messages;
      REPEAT
        next_message := current_message^.received_message.next_received_message;
        current_message^.received_message.next_received_message := previous_message;
        previous_message := current_message;
        current_message := next_message;
      UNTIL (current_message = NIL);

      received_messages := previous_message;
      REPEAT
        next_message := received_messages^.received_message.next_received_message;
        message_id.descriptor := received_messages;
        message_id.sequence_number := received_messages^.sequence_number;
        process_received_message (cl_connection, message_id);
        received_messages := next_message;
      UNTIL (received_messages = NIL);
      osp$disestablish_cond_handler;
    IFEND;

  PROCEND nlp$cc_receive_data;
?? OLDTITLE ??
?? NEWTITLE := 'establish_new_connection', EJECT ??
{
{ PURPOSE:
{   The purpose of this procedure is to process incoming connect requests.
{   This involves creating and initializing the connection structures and
{   delivering the connect event to the appropriate user. This procedure will
{   execute in the connection establishment task.

  PROCEDURE establish_new_connection
    (    device_received_on: nlt$device_identifier;
         time_connect_request_received: integer;
         cc_header: nlt$cc_protocol_header;
     VAR data { input, output } : nlt$bm_message_id);

    VAR
      application_layer: nlt$cl_application_layer,
      application_layer_found: boolean,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$cc_connection,
      event: nlt$cc_event,
      event_processor: nat$network_procedure,
      ignore_accumulated_buffers: integer,
      ignore_layer_active: boolean,
      network_device_list: ^nlt$network_device_list,
      time_of_last_reset: integer;

?? NEWTITLE := 'GET_APPLICATION_LAYER', EJECT ??

    PROCEDURE [INLINE] get_application_layer
      (    address: nlt$cc_address;
           message_id: nlt$bm_message_id;
       VAR application_layer_found: boolean;
       VAR application_layer: nlt$cl_application_layer);

      VAR
        length: integer,
        local_status: ost$status,
        user_data: record
          fill: 0 .. 0ffffffff(16),
          sap_identifier: nlt$ta_sap_selector,
        recend;

      application_layer_found := TRUE;
      CASE address OF
      = nlc$network_access_address =
        application_layer := nlc$osi_network_access_agent;

      = nlc$transport_access_address =
        nlp$bm_get_message_length (message_id, length);
        IF length >= #SIZE (user_data) THEN
          nlp$bm_get_message_prefix (^user_data, #SIZE (user_data), message_id, local_status);
          IF ((user_data.sap_identifier >= nlc$ta_min_rsvd_transport_sap) AND
                (user_data.sap_identifier <= nlc$ta_max_rsvd_transport_sap)) OR
                ((user_data.sap_identifier >= nlc$ta_min_transport_sap) AND
                (user_data.sap_identifier <= nlc$ta_max_transport_sap)) OR
                ((user_data.sap_identifier >= nlc$ta_low_min_osi_sap) AND
                (user_data.sap_identifier <= nlc$ta_low_max_osi_sap)) OR
                ((user_data.sap_identifier >= nlc$ta_high_min_osi_sap) AND
                (user_data.sap_identifier <= nlc$ta_high_max_osi_sap)) THEN
            application_layer := nlc$osi_generic_xport_interface;
          ELSEIF ((user_data.sap_identifier >= nlc$ta_min_rsvd_se_session_sap) AND
                (user_data.sap_identifier <= nlc$ta_max_rsvd_se_session_sap)) OR
                ((user_data.sap_identifier >= nlc$ta_min_se_session_sap) AND
                (user_data.sap_identifier <= nlc$ta_max_se_session_sap)) THEN
            application_layer := nlc$osi_session_interface;
          ELSE
            application_layer_found := FALSE;
          IFEND;
        ELSE
          application_layer_found := FALSE;
        IFEND;

{  Currently, the only application layers for the transport access agent
{  are nlc$osi_generic_xport_interface and nlc$osi_session_interface.  Once the
{  address format has stabilized the address in the message will have to
{  be searched to determine the application layer.

      = nlc$tcp_access_address =
        application_layer := nlc$tcp_interface;
      = nlc$udp_access_address =
        application_layer := nlc$udp_interface;
      = nlc$tcpip_management_address =
        application_layer := nlc$tcpip_mgmt_access_agent;
      = nlc$link_access_address =
        application_layer := nlc$osi_link_access_agent;
      = nlc$system_management_address =
        application_layer := nlc$osi_sys_mgmt_access_agent;
      ELSE { invalid address
        application_layer_found := FALSE;
      CASEND;

    PROCEND get_application_layer;
?? OLDTITLE, EJECT ??
?? NEWTITLE := '9.  <nlc$cc_closed>  --->  <nlc$cc_connect_response_wait>' ??
?? NEWTITLE := '10.  <nlc$cc_closed>  --->  <nlc$cc_closed>' ??
    get_application_layer (cc_header.connect_request.destination_address, data, application_layer_found,
          application_layer);
    IF application_layer_found THEN
      nlp$cl_create_connection (application_layer, cl_connection);
      IF cl_connection <> NIL THEN
{ 9. }
        nlp$cl_get_layer_connection (nlc$channel_connection_layer, cl_connection, ignore_layer_active,
              connection);
        connection^ := nlv$cc_initialize_connection;
        nlp$get_nonexclusive_access (nlv$configured_network_devices.access_control);
        network_device_list := nlv$configured_network_devices.network_device_list;
        time_of_last_reset := network_device_list^ [device_received_on].reset_timestamp;
        connection^.device_specific_attributes.maximum_data_length :=
              network_device_list^ [device_received_on].maximum_pdu_size - #SIZE (nlt$cc_protocol_header);
        connection^.buffers_per_credit := connection^.device_specific_attributes.maximum_data_length DIV
              nlv$bm_large_buffer_size;
        IF connection^.buffers_per_credit = 0 THEN
          connection^.buffers_per_credit := 1;
        IFEND;
        IF time_connect_request_received > time_of_last_reset THEN
          nlp$cc_incr_connection_count (device_received_on);
        IFEND;
        nlp$release_nonexclusive_access (nlv$configured_network_devices.access_control);
        IF time_connect_request_received > time_of_last_reset THEN
          nlp$cc_get_event_processor (cc_header.connect_request.destination_address,
                connection^.event_processor);
          connection^.device_specific_attributes.state := nlc$cc_connect_response_wait;
          connection^.peer_reference_number := cc_header.connect_request.source_reference;
          connection^.connection_id := cl_connection^.identifier;
          connection^.send_credits := cc_header.connect_request.initial_credit_allocation;
          connection^.device_specific_attributes.device_id := device_received_on;
          nlp$cl_add_device_to_connection (device_received_on, cl_connection);
          connection^.class := cc_header.connect_request.class;
          IF connection^.class = nlc$cc_priority_class THEN
            nlp$cl_incr_priority_connection;
          IFEND;
          nlp$cl_activate_layer (nlc$channel_connection_layer, cl_connection);

          event.kind := nlc$cc_connect_event;
          event.connect.device_id := device_received_on;
          event.connect.destination_address := cc_header.connect_request.destination_address;
          event.connect.class := cc_header.connect_request.class;
          event.connect.data := data;

          CASE cc_header.connect_request.destination_address OF
          = nlc$network_access_address =
            event_processor := nlc$na_connect_event_processor;

          = nlc$transport_access_address =
            event_processor := nlc$ta_connect_event_processor;

          = nlc$link_access_address =
            event_processor := nlc$la_connect_event_processor;

          = nlc$system_management_address =
            event_processor := nlc$sm_connect_event_processor;

          = nlc$tcp_access_address =
            event_processor := nlc$tcp_connect_event_processor;

          = nlc$udp_access_address =
            event_processor := nlc$udp_connect_event_processor;

          = nlc$tcpip_management_address =
            event_processor := nlc$tm_connect_event_processor;

          ELSE { Unknown destination address

{ This should never happen as get_application_layer was previously called and verified
{ that the destination address was valid.

            nap$namve_system_error (FALSE, 'Unknown CC connect request destination', NIL);
          CASEND;
          nav$network_procedures [event_processor].cc_event_processor^
                (cl_connection, event, ignore_accumulated_buffers);
        ELSE

{  This connect request was received before the device last reset, therefore it should not
{  be processed and the just created connection will be terminated. The connection structure
{  is created before the timestamp check in order to ensure that it will be found and
{  terminated if the device resets immediately after the check. The connection structure will
{  be released by the timer task since no layers are active.

          nlp$bm_release_message (data);
        IFEND;
        nlp$cl_release_exclusive_access (cl_connection);
      ELSE { cl_connection = NIL
{ 10. }
        nlp$bm_release_message (data);
        send_disconnect_to_peer (nlc$cc_disconnect_request, nlc$cc_dr_system_unaccomodating,
              device_received_on, cc_header.connect_request.source_reference, 0,
              cc_header.connect_request.class);
      IFEND;
    ELSE
      nlp$bm_release_message (data);
      send_disconnect_to_peer (nlc$cc_disconnect_request, nlc$cc_dr_unknown_address, device_received_on,
            cc_header.connect_request.source_reference, 0, cc_header.connect_request.class);
    IFEND;

  PROCEND establish_new_connection;
?? OLDTITLE, OLDTITLE ??
?? OLDTITLE ??
?? NEWTITLE := 'process_cc_event', EJECT ??
{ PURPOSE:
{   The purpose of this procedure is to associate a particular CC event with
{   the corresponding connection structures. This procedure obtains and releases
{   exclusive access to the cl_connection structure. If the connection exists and
{   access is obtained the event is passed on for protocol application.

  PROCEDURE process_cc_event
    (    device_received_on: nlt$device_identifier;
         sequence_number: nlt$cc_sequence_number;
         connection_id: nlt$cl_connection_id;
         cc_header: nlt$cc_protocol_header;
     VAR data {INPUT, OUTPUT} : nlt$bm_message_id);

    VAR
      access_gained: boolean,
      actual: integer,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$cc_connection,
      connection_exists: boolean,
      device_specific_attributes: ^nlt$cc_device_specific_attr,
      executing_task_id: ost$global_task_id,
      main_connection: boolean,
      queued_cc_header: nlt$cc_protocol_header,
      queued_data: nlt$bm_message_id,
      received_cc_pdu: ^nlt$cc_received_pdu,
      system_input_task: boolean;

?? NEWTITLE := 'merge_event_in_receive_buffer', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to merge a Channel Connection event
{   into the connection receive buffer. The event is merged such that the
{   buffer contains events in order of increasing sequence numbers, i.e.
{   the first event has the lowest sequence number and the last the highest.
{   If system resources are not available this procedure will wait until
{   resources are available.


    PROCEDURE [INLINE] merge_event_in_receive_buffer
      (    device_id: nlt$device_identifier;
           sequence_number: nlt$cc_sequence_number;
           cc_header: nlt$cc_protocol_header;
           data: nlt$bm_message_id;
       VAR receive_buffer { input, output } : ^nlt$cc_received_pdu);

      VAR
        new_cc_pdu: ^nlt$cc_received_pdu,
        received_cc_pdu: ^^nlt$cc_received_pdu;

      REPEAT
        ALLOCATE new_cc_pdu IN nav$network_paged_heap^;
        IF new_cc_pdu = NIL THEN
          syp$cycle;
        IFEND;
      UNTIL new_cc_pdu <> NIL;
      new_cc_pdu^.sequence_number := sequence_number;
      new_cc_pdu^.device_id := device_id;
      new_cc_pdu^.cc_header := cc_header;
      new_cc_pdu^.data := data;
      received_cc_pdu := ^receive_buffer;
      WHILE (received_cc_pdu^ <> NIL) AND (received_cc_pdu^^.sequence_number < sequence_number) DO
        received_cc_pdu := ^received_cc_pdu^^.next_cc_pdu;
      WHILEND;
      new_cc_pdu^.next_cc_pdu := received_cc_pdu^;
      received_cc_pdu^ := new_cc_pdu;

    PROCEND merge_event_in_receive_buffer;
?? OLDTITLE ??
?? NEWTITLE := 'release_connection_access', EJECT ??

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

      nap$condition_handler_trace (condition, save_area);
      IF ((pmc$program_termination IN condition.reason) OR (pmc$program_abort IN condition.reason)) THEN
        nlp$cl_clear_exclusive_access (cl_connection);
      IFEND;
      condition_status.normal := TRUE;

    PROCEND release_connection_access;
?? OLDTITLE ??
?? EJECT ??
    pmp$get_executing_task_gtid (executing_task_id);
    system_input_task := (executing_task_id = nav$system_input_taskid);
    REPEAT

{  The 'destination_reference' field is always in the same location for all CCPDU kinds,
{  the template for a data PDU is used to reference the field.

      IF cc_header.data.destination_reference <> 0 THEN
        nlp$cc_get_exclusive_via_cid (connection_id, system_input_task, connection_exists, access_gained,
              connection, cl_connection);
      ELSEIF cc_header.kind = nlc$cc_disconnect_request THEN
        nlp$cc_get_exclus_to_unaccepted (cc_header.disconnect_request.source_reference, device_received_on,
              system_input_task, connection_exists, access_gained, connection, cl_connection);
      ELSE { Protocol error - Only disconnect PDU's may have destination reference number of zero.
        nlp$cc_reset_device (device_received_on);
        nlp$bm_release_message (data);
        RETURN; {----->
      IFEND;

      IF connection_exists AND access_gained THEN
        osp$establish_block_exit_hndlr (^release_connection_access);

{ Disconnect request and confirm PDU's are processed immediately. (i.e., the CC sequence
{ numbers assigned to these PDU's are ignored)

        IF (sequence_number = connection^.next_deliverable_sequence#) OR
              (cc_header.kind = nlc$cc_disconnect_request) OR (cc_header.kind = nlc$cc_disconnect_confirm)
              THEN
          IF (cc_header.kind <> nlc$cc_disconnect_request) AND
                (cc_header.kind <> nlc$cc_disconnect_confirm) THEN
            connection^.next_deliverable_sequence# := connection^.next_deliverable_sequence# + 1;
          IFEND;
          nlp$cc_get_device_specific_attr (device_received_on, connection, main_connection,
                device_specific_attributes);
          apply_cc_protocol (cl_connection, cc_header, main_connection, device_specific_attributes,
                connection, data);
          WHILE ((connection^.receive_buffer <> NIL) AND (connection^.receive_buffer^.sequence_number =
                connection^.next_deliverable_sequence#)) DO
            received_cc_pdu := connection^.receive_buffer;
            queued_cc_header := received_cc_pdu^.cc_header;
            IF (queued_cc_header.kind <> nlc$cc_disconnect_request) AND
                  (queued_cc_header.kind <> nlc$cc_disconnect_confirm) THEN
              connection^.next_deliverable_sequence# := connection^.next_deliverable_sequence# + 1;
            IFEND;
            queued_data := received_cc_pdu^.data;
            IF received_cc_pdu^.device_id <> device_specific_attributes^.device_id THEN
              nlp$cc_get_device_specific_attr (received_cc_pdu^.device_id, connection, main_connection,
                    device_specific_attributes);
            IFEND;
            connection^.receive_buffer := received_cc_pdu^.next_cc_pdu;
            FREE received_cc_pdu IN nav$network_paged_heap^;
            apply_cc_protocol (cl_connection, queued_cc_header, main_connection, device_specific_attributes,
                  connection, queued_data);
          WHILEND;
        ELSE { CCPDU received out of sequence
          merge_event_in_receive_buffer (device_received_on, sequence_number, cc_header, data,
                connection^.receive_buffer);

{! statistics begin}

          IF nav$statistics_enabled THEN
            osp$increment_locked_variable (nav$global_osi_statistics.
                  channel_connection_device^ [device_received_on].pdus_processed_out_of_order, 0, actual);
          IFEND;

{! statistics end}

        IFEND;
        osp$disestablish_cond_handler;
        nlp$cl_release_exclusive_access (cl_connection);
      ELSEIF connection_exists THEN
        IF system_input_task THEN
          add_event_to_cc_work_list (device_received_on, sequence_number, connection_id, cc_header, data);
        ELSE
          syp$cycle;
        IFEND;
      ELSE { Connection does not exist.
        IF (cc_header.kind <> nlc$cc_disconnect_request) OR
              (cc_header.disconnect_request.destination_reference <> 0) THEN
          nlp$cc_reset_device (device_received_on);
        IFEND;
        nlp$bm_release_message (data);
      IFEND;
    UNTIL ((connection_exists AND (access_gained OR system_input_task)) OR NOT connection_exists);

  PROCEND process_cc_event;
?? OLDTITLE ??
?? NEWTITLE := 'apply_cc_protocol', EJECT ??

  PROCEDURE apply_cc_protocol
    (    cl_connection: ^nlt$cl_connection;
         cc_header: nlt$cc_protocol_header;
         main_connection: boolean;
         device_specific_attributes: ^nlt$cc_device_specific_attr;
         connection: ^nlt$cc_connection;
     VAR data { input, output } : nlt$bm_message_id);

?? NEWTITLE := '19, 20, 21. <nlc$cc_open>  --->  <nlc$cc_open>' ??
?? NEWTITLE := '23, 24, 26. <nlc$cc_open>  --->  <nlc$cc_closed>' ??

    VAR
      actual: integer,
      data_length: integer,
      event: nlt$cc_event,
      i: integer,
      ignore_accumulated_buffers: integer,
      ignore_error: boolean,
      statistic1: ^integer,
      statistic2: ^integer;

    CASE device_specific_attributes^.state OF
    = nlc$cc_open =
      CASE cc_header.kind OF
      = nlc$cc_data =

{! statistics begin}

        IF nav$statistics_enabled THEN
          nlp$bm_get_message_length (data, data_length);
          IF data_length > 0 THEN
            IF connection^.class = nlc$cc_normal_class THEN
              increment_receive_statistic (data_length, nav$global_osi_statistics.
                    channel_connection_device^ [device_specific_attributes^.device_id].receive);
            ELSE { IF connection^.class = nlc$cc_priority_class THEN
              increment_receive_statistic (data_length, nav$global_osi_statistics.
                    channel_connection_device^ [device_specific_attributes^.device_id].priority_receive);
            IFEND;
          IFEND;
        IFEND;

{! statistics end}

{ 19. }
        IF connection^.receive_credits > 0 THEN
          connection^.receive_credits := connection^.receive_credits - 1;
          connection^.send_credits := connection^.send_credits + cc_header.data.credits_granted;
          IF (connection^.send_credits > 0) AND (NOT nlp$cc_send_buffer_empty (connection)) THEN
            send_queued_data (connection);
            IF cl_connection^.message_sender.active THEN
              nav$network_procedures [connection^.event_processor].
                    cc_event_processor^ (cl_connection, clear_to_send,
                    connection^.accumulated_message_buffers);
            IFEND;
          IFEND;
          event.kind := nlc$cc_data_event;
          event.data.data := data;
          nav$network_procedures [connection^.event_processor].
                cc_event_processor^ (cl_connection, event, connection^.accumulated_message_buffers);
          IF ((device_specific_attributes^.state = nlc$cc_open) AND
                (connection^.receive_credits <= nlv$cc_grant_credit_trigger)) THEN
            nlp$cc_grant_credits (connection);
          IFEND;
        ELSE
{ 23. }

{! statistics begin}

          IF nav$statistics_enabled THEN
            IF connection^.class = nlc$cc_normal_class THEN
              statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                    [device_specific_attributes^.device_id].receive_pdus_discarded;
            ELSE { IF connection^.class = nlc$cc_priority_class THEN
              statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                    [device_specific_attributes^.device_id].priority_receive_pdus_discarded;
            IFEND;
            osp$increment_locked_variable (statistic1^, 0, actual);
          IFEND;

{! statistics end}

          nlp$cc_abort_connection (nlc$cc_dr_flow_cntrl_violation, connection, cl_connection);
          nlp$cc_reset_device (device_specific_attributes^.device_id);
          nlp$bm_release_message (data);
        IFEND;



      = nlc$cc_expedited_data =
{ 20. }
        event.kind := nlc$cc_expedited_data_event;
        event.expedited_data.data := data;
        nav$network_procedures [connection^.event_processor].
              cc_event_processor^ (cl_connection, event, connection^.accumulated_message_buffers);

{! statistics begin}

        IF nav$statistics_enabled THEN
          IF connection^.class = nlc$cc_normal_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].received_expedited_pdus;
          ELSE { IF connection^.class = nlc$cc_priority_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].priority_receive_expedited_pdus;
          IFEND;
          osp$increment_locked_variable (statistic1^, 0, actual);
        IFEND;

{! statistics end}

      = nlc$cc_credit_allocation =
{ 21. }
        connection^.send_credits := connection^.send_credits + cc_header.credit_allocation.credits_granted;
        IF (connection^.send_credits > 0) AND (NOT nlp$cc_send_buffer_empty (connection)) THEN
          send_queued_data (connection);
          IF cl_connection^.message_sender.active THEN
            nav$network_procedures [connection^.event_processor].
                  cc_event_processor^ (cl_connection, clear_to_send, connection^.accumulated_message_buffers);
          IFEND;
        IFEND;

{! statistics begin}

        IF nav$statistics_enabled THEN
          osp$increment_locked_variable (nav$global_osi_statistics.
                channel_connection_device^ [device_specific_attributes^.device_id].credit_pdus_received, 0,
                actual);
        IFEND;

{! statistics end}

      = nlc$cc_disconnect_request =
{ 26. }
        send_disconnect_to_peer (nlc$cc_disconnect_confirm, 0, device_specific_attributes^.device_id,
              connection^.peer_reference_number, connection^.connection_id.reference_number,
              connection^.class);
        device_specific_attributes^.state := nlc$cc_closed;
        nlp$cc_shut_down_connection (connection, cl_connection, device_specific_attributes^.device_id);
        event.kind := nlc$cc_disconnect_event;
        event.disconnect.reason := cc_header.disconnect_request.reason;
        event.disconnect.data := data;
        nav$network_procedures [connection^.event_processor].
              cc_event_processor^ (cl_connection, event, ignore_accumulated_buffers);

{! statistics begin}

        IF nav$statistics_enabled THEN
          IF connection^.class = nlc$cc_normal_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].current_normal_connections;
            statistic2 := ^nav$global_osi_statistics.channel_connection.normal_connections;
          ELSE { IF connection^.class = nlc$cc_priority_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].current_priority_connections;
            statistic2 := ^nav$global_osi_statistics.channel_connection.priority_connections;
          IFEND;
          osp$decrement_locked_variable (statistic1^, 0, actual, ignore_error);
          osp$decrement_locked_variable (statistic2^, 0, actual, ignore_error);
        IFEND;

{! statistics end}

      ELSE
{ 24. }
        nlp$cc_abort_connection (nlc$cc_dr_invalid_state, connection, cl_connection);
        nlp$cc_reset_device (device_specific_attributes^.device_id);
        nlp$bm_release_message (data);
      CASEND;

?? OLDTITLE, OLDTITLE, EJECT ??
?? NEWTITLE := '14, 16. <nlc$cc_connect_response_wait>  --->  <nlc$cc_closed>' ??
?? NEWTITLE := '27. <nlc$cc_closing>  --->  <nlc$cc_closing>' ??
?? NEWTITLE := '29. <nlc$cc_closing>  --->  <nlc$cc_closed>' ??
    = nlc$cc_closing =
      IF (cc_header.kind = nlc$cc_disconnect_confirm) OR (cc_header.kind = nlc$cc_disconnect_request) THEN
{ 29. }
        IF cc_header.kind = nlc$cc_disconnect_request THEN
          nlp$bm_release_message (data);
        IFEND;
        device_specific_attributes^.state := nlc$cc_closed;

{  Determine if the "main" connection or a "subconnection" was closed. If a
{  "subconnection" was closed, decrement the subconnection count.

        IF NOT main_connection THEN
          connection^.sub_connection_count := connection^.sub_connection_count - 1;
        IFEND;
        nlp$cc_shut_down_connection (connection, cl_connection, device_specific_attributes^.device_id);
      ELSE { Discard all other PDU kinds
{ 27. }
        nlp$bm_release_message (data);

{! statistics begin}

        IF nav$statistics_enabled THEN
          IF cc_header.kind = nlc$cc_data THEN
            IF connection^.class = nlc$cc_normal_class THEN
              statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                    [device_specific_attributes^.device_id].receive_pdus_discarded;
            ELSE { IF connection^.class = nlc$cc_priority_class THEN
              statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                    [device_specific_attributes^.device_id].priority_receive_pdus_discarded;
            IFEND;
            osp$increment_locked_variable (statistic1^, 0, actual);
          IFEND;
        IFEND;

{! statistics end}

      IFEND;

    = nlc$cc_connect_response_wait =
      IF cc_header.kind = nlc$cc_disconnect_request THEN
{ 14. }
        send_disconnect_to_peer (nlc$cc_disconnect_confirm, 0, device_specific_attributes^.device_id,
              connection^.peer_reference_number, connection^.connection_id.reference_number,
              connection^.class);
        device_specific_attributes^.state := nlc$cc_closed;
        nlp$cc_shut_down_connection (connection, cl_connection, device_specific_attributes^.device_id);
        event.kind := nlc$cc_disconnect_event;
        event.disconnect.reason := cc_header.disconnect_request.reason;
        event.disconnect.data := data;
        nav$network_procedures [connection^.event_processor].
              cc_event_processor^ (cl_connection, event, ignore_accumulated_buffers);
      ELSE
{ 16. }
        nlp$cc_abort_connection (nlc$cc_dr_invalid_state, connection, cl_connection);
        nlp$cc_reset_device (device_specific_attributes^.device_id);
        nlp$bm_release_message (data);
      IFEND;

?? OLDTITLE, OLDTITLE, OLDTITLE, EJECT ??
?? NEWTITLE := '2. <nlc$cc_connect_confirm_wait>  --->  <nlc$cc_open>' ??
?? NEWTITLE := '4, 7. <nlc$cc_connect_confirm_wait>  --->  <nlc$cc_closed>' ??
    = nlc$cc_connect_confirm_wait =
      IF cc_header.kind = nlc$cc_connect_confirm THEN
{ 2. }
        connection^.peer_reference_number := cc_header.connect_confirm.source_reference;
        connection^.send_credits := cc_header.connect_confirm.initial_credit_allocation;
        IF cc_header.connect_confirm.class <> connection^.class THEN
          IF cc_header.connect_confirm.class = nlc$cc_normal_class THEN
            nlp$cl_decr_priority_connection;
          ELSE
            nlp$cl_incr_priority_connection;
          IFEND;
          connection^.class := cc_header.connect_confirm.class;
        IFEND;
        connection^.device_specific_attributes.state := nlc$cc_open;
        IF NOT main_connection THEN
          connection^.device_specific_attributes.maximum_data_length :=
                device_specific_attributes^.maximum_data_length;
          connection^.buffers_per_credit := connection^.device_specific_attributes.maximum_data_length DIV
                nlv$bm_large_buffer_size;
          IF connection^.buffers_per_credit = 0 THEN
            connection^.buffers_per_credit := 1;
          IFEND;
          device_specific_attributes^.state := nlc$cc_closed;
          connection^.device_specific_attributes.device_id := device_specific_attributes^.device_id;
          connection^.sub_connection_count := connection^.sub_connection_count - 1;
          IF connection^.sub_connection_count > 0 THEN
            FOR i := 1 TO UPPERBOUND (connection^.sub_connections^) DO
              IF connection^.sub_connections^ [i].state = nlc$cc_connect_confirm_wait THEN
                send_disconnect_to_peer (nlc$cc_disconnect_request, nlc$cc_dr_reason_not_specified,
                      connection^.sub_connections^ [i].device_id, 0,
                      connection^.connection_id.reference_number, connection^.class);
                connection^.sub_connections^ [i].state := nlc$cc_closing;
              IFEND;
            FOREND;
          IFEND;
        IFEND;
        event.kind := nlc$cc_accept_event;
        event.accept.class := cc_header.connect_confirm.class;
        event.accept.data := data;
        nav$network_procedures [connection^.event_processor].
              cc_event_processor^ (cl_connection, event, connection^.accumulated_message_buffers);

{! statistics begin}

        IF nav$statistics_enabled THEN
          IF connection^.class = nlc$cc_normal_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].current_normal_connections;
            statistic2 := ^nav$global_osi_statistics.channel_connection.normal_connections;
          ELSE { IF connection^.class = nlc$cc_priority_class THEN
            statistic1 := ^nav$global_osi_statistics.channel_connection_device^
                  [device_specific_attributes^.device_id].current_priority_connections;
            statistic2 := ^nav$global_osi_statistics.channel_connection.priority_connections;
          IFEND;
          osp$increment_locked_variable (statistic1^, 0, actual);
          osp$increment_locked_variable (statistic2^, 0, actual);
        IFEND;

{! statistics end}

      ELSEIF cc_header.kind = nlc$cc_disconnect_request THEN
{ 4. }
        device_specific_attributes^.state := nlc$cc_closed;
        IF NOT main_connection THEN
          connection^.sub_connection_count := connection^.sub_connection_count - 1;
        IFEND;
        IF connection^.sub_connection_count = 0 THEN
          connection^.device_specific_attributes.state := nlc$cc_closed;
          nlp$cc_shut_down_connection (connection, cl_connection, device_specific_attributes^.device_id);
          event.kind := nlc$cc_disconnect_event;
          event.disconnect.reason := cc_header.disconnect_request.reason;
          event.disconnect.data := data;
          nav$network_procedures [connection^.event_processor].
                cc_event_processor^ (cl_connection, event, ignore_accumulated_buffers);
        ELSE
          nlp$bm_release_message (data);
          nlp$cc_decr_connection_count (device_specific_attributes^.device_id);
        IFEND;
      ELSE
{ 7. }
        device_specific_attributes^.state := nlc$cc_closed;
        IF NOT main_connection THEN
          connection^.sub_connection_count := connection^.sub_connection_count - 1;
        IFEND;
        IF connection^.sub_connection_count = 0 THEN
          nlp$cc_abort_connection (nlc$cc_dr_invalid_state, connection, cl_connection);
        ELSE
          nlp$cc_decr_connection_count (device_specific_attributes^.device_id);
        IFEND;
        nlp$cc_reset_device (device_specific_attributes^.device_id);
        nlp$bm_release_message (data);
      IFEND;

    ELSE { nlc$cc_closed
      nlp$bm_release_message (data);
      IF (connection^.device_specific_attributes.state = nlc$cc_closed) AND
            (connection^.sub_connection_count = 0) AND (((cl_connection^.queue_on_connection) AND
            (NOT nlp$connection_queued (cl_connection))) OR ((NOT cl_connection^.queue_on_connection) AND
            (connection^.next_deliverable_sequence# = cl_connection^.next_assignable_cc_sequence#))) THEN

{ This connection is completely closed down (i.e., the main connection and all sub connections are closed)
{ and all PDU's associated with this connection have been processed; the CC layer can now be
{ deactivated and the connection identifier reused.

        nlp$cl_deactivate_layer (nlc$channel_connection_layer, cl_connection);
      IFEND;
    CASEND;

  PROCEND apply_cc_protocol;
?? OLDTITLE, OLDTITLE ??
?? OLDTITLE ??
?? NEWTITLE := 'extract_cc_header', EJECT ??

{
{  PURPOSE:
{     The purpose of this procedure is to extract the entire Channel
{   Connection header including any user data pad bytes. The CC header
{   is returned to the caller with the pad bytes being discarded.
{

  PROCEDURE extract_cc_header
    (VAR cc_pdu { input, output } : nlt$bm_message_id;
     VAR cc_header: nlt$cc_protocol_header;
     VAR status: ost$status);

    VAR
      bytes_moved: nat$data_length,
      user_data_pad: 0 .. nlc$cc_max_user_data_pad,
      user_data_pad_size: 0 .. nlc$cc_max_user_data_pad_size;

    status.normal := TRUE;
    nlp$bm_extract_message_prefix (^cc_header, #SIZE (nlt$cc_protocol_header), cc_pdu, bytes_moved);
    IF bytes_moved = #SIZE (nlt$cc_protocol_header) THEN
      CASE cc_header.kind OF
      = nlc$cc_connect_request =
        user_data_pad_size := cc_header.connect_request.user_data_pad_size;

      = nlc$cc_connect_confirm =
        user_data_pad_size := cc_header.connect_confirm.user_data_pad_size;

      = nlc$cc_disconnect_request =
        user_data_pad_size := cc_header.disconnect_request.user_data_pad_size;

      = nlc$cc_data =
        user_data_pad_size := cc_header.data.user_data_pad_size;

      = nlc$cc_expedited_data =
        user_data_pad_size := cc_header.expedited_data.user_data_pad_size;
      ELSE
        user_data_pad_size := 0;
      CASEND;
      IF user_data_pad_size > 0 THEN
        nlp$bm_extract_message_prefix (^user_data_pad, user_data_pad_size, cc_pdu, bytes_moved);
        IF bytes_moved <> user_data_pad_size THEN
          osp$set_status_condition (nae$insufficient_data, status);
        IFEND;
      IFEND;
    ELSE
      osp$set_status_condition (nae$insufficient_data, status);
    IFEND;

  PROCEND extract_cc_header;
?? OLDTITLE ??
?? NEWTITLE := 'process_received_message', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to process the message received
{ on the given channel connection. It is assumed that the channel
{ connection has been locked by the caller. This process will execute
{ in the user task that has initiated the receipt of network input.
{ All CC pdus besides a connect request, disconnect request, disconnect
{ confirm and a flow control PDU will be processed by this procedure.

  PROCEDURE process_received_message
    (    cl_connection: ^nlt$cl_connection;
     VAR cc_pdu { input, output } : nlt$bm_message_id);

    VAR
      cc_header: nlt$cc_protocol_header,
      connection: ^nlt$cc_connection,
      device_received_on: nlt$device_identifier,
      device_specific_attributes: ^nlt$cc_device_specific_attr,
      error_message: ^string (64),
      error_message_length: integer,
      layer_active: boolean,
      local_status: ost$status,
      main_connection: boolean;

{ The following received message attributes MUST be retreived from the message
{ descriptor BEFORE any extracts are attempted. This is because the received
{ message attributes are in the first message descriptor which may be released
{ as part of an extract.

    device_received_on := cc_pdu.descriptor^.received_message.device_id;

    extract_cc_header (cc_pdu, cc_header, local_status);
    IF local_status.normal THEN
      CASE cc_header.kind OF
      = nlc$cc_connect_confirm .. nlc$cc_expedited_data =
        nlp$cl_get_layer_connection (nlc$channel_connection_layer, cl_connection, layer_active, connection);
        IF layer_active THEN
          nlp$cc_get_device_specific_attr (device_received_on, connection, main_connection,
                device_specific_attributes);
          apply_cc_protocol (cl_connection, cc_header, main_connection, device_specific_attributes,
                connection, cc_pdu);
        ELSE { Layer inactive
          PUSH error_message;
          STRINGREP (error_message^, error_message_length, 'Received a CC pdu on device ', device_received_on,
                ' when the cc layer is inactive.');
          nap$namve_system_error ({Recoverable_error=} TRUE, error_message^ (1, error_message_length), NIL);
          nlp$bm_release_message (cc_pdu);
        IFEND;
      ELSE { NAM/VE Internal error - invalid PDU for this process
        nlp$cc_reset_device (device_received_on);
        nlp$bm_release_message (cc_pdu);
      CASEND;
    ELSE
      nlp$cc_reset_device (device_received_on);
      nlp$bm_release_message (cc_pdu);
    IFEND;

  PROCEND process_received_message;
?? OLDTITLE ??
?? NEWTITLE := 'send_disconnect_to_peer', EJECT ??
{
{      The purpose of this request is to send a disconnect request or confirm
{   to the Channel Connection peer. This request is only used by the Channel
{   Connection Entity, it is not to be used by users of the Channel Connection
{   service.
{
{         SEND_DISCONNECT_TO_PEER (DISCONNECT_KIND, DISCONNECT_REASON,
{               DEVICE_ID, DESTINATION_REFERENCE, SOURCE_REFERENCE, CLASS)
{
{  DISCONNECT_KIND: (input) This paramter specifies whether a disconnect request
{      or a disconnect confirm is to be sent to the peer.
{
{  DISCONNECT_REASON: (input) This parameter specifies the disconnect reason that
{      is to be part of the disconnect request. This parameter is not valid if
{      DISCONNECT_KIND specifies a disconnect confirm.
{
{  DEVICE_ID: (input) This parameter specifies the network device to which the
{      disconnect is to be sent.
{
{  DESTINATION_REFERENCE: (input) This parameter specifies the destination
{      reference number.
{
{  SOURCE_REFERENCE: (input) This parameter specifies the source reference
{      number.
{
{  CLASS: (input) This parameter specifies the connection class.
{

  PROCEDURE send_disconnect_to_peer
    (    disconnect_kind: nlt$cc_pdu_kind;
         disconnect_reason: nlt$cc_disconnect_reason;
         device_id: nlt$device_identifier;
         destination_reference: nlt$cl_reference_number;
         source_reference: nlt$cl_reference_number;
         class: nlt$cc_connection_class);

    VAR
      cc_header: nlt$cc_protocol_header,
      cc_pdu: nlt$bm_message_id,
      data_fragments: array [1 .. 1] of nat$data_fragment,
      ignore_status: ost$status;

    cc_header.length := #SIZE (nlt$cc_protocol_header);
    cc_header.kind := disconnect_kind;
    IF cc_header.kind = nlc$cc_disconnect_request THEN
      cc_header.disconnect_request.destination_reference := destination_reference;
      cc_header.disconnect_request.source_reference := source_reference;
      cc_header.disconnect_request.reason := disconnect_reason;
      cc_header.disconnect_request.user_data_pad_size := 0;
      cc_header.disconnect_request.class := class;
    ELSE { disconnect confirm
      cc_header.disconnect_confirm.destination_reference := destination_reference;
      cc_header.disconnect_confirm.source_reference := source_reference;
      cc_header.disconnect_confirm.class := class;
    IFEND;
    data_fragments [1].address := ^cc_header;
    data_fragments [1].length := cc_header.length;
    nlp$bm_create_message (data_fragments, cc_pdu, ignore_status);
    nlp$cc_send_pdu (device_id, class, cc_pdu);

  PROCEND send_disconnect_to_peer;
?? OLDTITLE ??
?? NEWTITLE := 'send_queued_data', EJECT ??
{
{ PURPOSE:
{   The purpose of this procedure is to send previously queued
{   Channel Connection PDU's. These PDU's were placed in the send queue
{   because there were no outstanding peer credits available at the time.
{   This procedure will send as many queued PDU's as possible, i.e., it will
{   send PDU's until the queue is empty or all outstanding credits have been
{   consumed.
{
{ NOTES: This procedure assumes that upon entry there are credits available
{        and PDU's to send.

  PROCEDURE send_queued_data
    (    connection {INPUT, OUTPUT} : ^nlt$cc_connection);

    VAR
      actual: integer,
      buffer_count: integer,
      cc_header: nlt$cc_protocol_header_with_pad,
      cc_header_and_pad_length: nlt$cc_pdu_size,
      data: nlt$bm_message_id,
      data_length: integer,
      extension: ^nlt$cc_send_buffer_extension,
      ignore_error: boolean,
      statistic: ^integer;

    buffer_count := 0;
    cc_header.kind := nlc$cc_data;
    cc_header.data.destination_reference := connection^.peer_reference_number;
    cc_header.data.credits_granted := nlp$cc_obtain_credits (connection);
    connection^.receive_credits := connection^.receive_credits + cc_header.data.credits_granted;

    REPEAT
      data := connection^.send_buffer.cc_pdu [connection^.send_buffer.out].data;
      nlp$bm_get_message_length (data, data_length);
      cc_header.data.user_data_pad_size := nlp$cc_user_data_pad_size (data_length);
      cc_header_and_pad_length := #SIZE (nlt$cc_protocol_header) + cc_header.data.user_data_pad_size;
      cc_header.length := data_length + cc_header_and_pad_length;
      nlp$bm_add_message_prefix (^cc_header, cc_header_and_pad_length, data);
      nlp$cc_send_pdu (connection^.device_specific_attributes.device_id, connection^.class, data);
      connection^.send_credits := connection^.send_credits - 1;
      cc_header.data.credits_granted := 0;
      connection^.send_buffer.out := (connection^.send_buffer.out + 1) MOD nlc$cc_send_buffer_limit;
      IF connection^.send_buffer.extension <> NIL THEN
        extension := connection^.send_buffer.extension;
        connection^.send_buffer.extension := extension^.nextt;
        connection^.send_buffer.cc_pdu [connection^.send_buffer.inn] := extension^.cc_pdu;
        connection^.send_buffer.inn := (connection^.send_buffer.inn + 1) MOD nlc$cc_send_buffer_limit;
        FREE extension IN nav$network_paged_heap^;
      IFEND;
      buffer_count := buffer_count + 1;
    UNTIL ((connection^.send_buffer.out = connection^.send_buffer.inn) OR (connection^.send_credits = 0));

{! statistics begin}

    IF nav$statistics_enabled THEN
      IF connection^.class = nlc$cc_normal_class THEN
        statistic := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].normal_send_pdus_queued;
      ELSE { IF connection^.class = nlc$cc_priority_class THEN
        statistic := ^nav$global_osi_statistics.channel_connection_device^
              [connection^.device_specific_attributes.device_id].priority_send_pdus_queued;
      IFEND;
      osp$sub_from_locked_variable (statistic^, buffer_count, buffer_count, actual, ignore_error);
    IFEND;

{! statistics end}

  PROCEND send_queued_data;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] nlp$cc_work_list_processor', EJECT ??
*copy nlh$cc_work_list_processor

  PROCEDURE [XDCL] nlp$cc_work_list_processor
    (    flag_id: ost$system_flag);


    VAR
      cc_header: nlt$cc_protocol_header,
      connection_id: nlt$cl_connection_id,
      current_last_work_unit: ^nlt$cc_work_unit,
      data: nlt$bm_message_id,
      device_received_on: nlt$device_identifier,
      last_work_unit: ^cell,
      pass_not_complete: boolean,
      sequence#: nlt$cc_sequence_number,
      work_unit: ^nlt$cc_work_unit;

{Debug Code

    VAR
      actual_value: integer,
      ignore_error: boolean;

    osp$increment_locked_variable (nav$namve_tsk_hndl_active_count, 0, actual_value);
    IF actual_value > 1 THEN
      osp$increment_locked_variable (nav$multiple_namve_hndler_calls, 0, actual_value);
      IF nav$debug_mode > nac$no_debug THEN
        nap$namve_system_error (TRUE {=recoverable} ,
              'NLP$CC_WORK_LIST_PROCESSOR invoked while another handler active.', NIL);
      IFEND;
    IFEND;
{End Debug Code

{ Variable last_work_unit is used solely for type conversion.
{ This algorithm depends on the fact that next_work_unit is
{ the first field in the record.

    last_work_unit := nlv$cc_work_list.append;
    current_last_work_unit := last_work_unit;
    pass_not_complete := (nlv$cc_work_list.first <> NIL);
    WHILE pass_not_complete DO
      pass_not_complete := (nlv$cc_work_list.first <> current_last_work_unit);
      work_unit := nlv$cc_work_list.first;
      nlv$cc_work_list.first := nlv$cc_work_list.first^.next_work_unit;
      IF nlv$cc_work_list.first = NIL THEN
        nlv$cc_work_list.append := ^nlv$cc_work_list.first;
      IFEND;
      connection_id := work_unit^.connection_id;
      IF work_unit^.kind = nlc$cc_event_work_unit THEN
        device_received_on := work_unit^.device_received_on;
        sequence# := work_unit^.sequence#;
        cc_header := work_unit^.cc_header;
        data := work_unit^.data;
        FREE work_unit IN nav$network_paged_heap^;
        process_cc_event (device_received_on, sequence#, connection_id, cc_header, data);
      ELSE { nlc$cc_connection_work_unit
        FREE work_unit IN nav$network_paged_heap^;
        nlp$process_receiving_conection (connection_id);
      IFEND;
    WHILEND;

{Debug Code
    osp$decrement_locked_variable (nav$namve_tsk_hndl_active_count, 1, actual_value, ignore_error);
{End Debug Code

  PROCEND nlp$cc_work_list_processor;
?? OLDTITLE ??
?? NEWTITLE := 'add_event_to_cc_work_list', EJECT ??
{
{  PURPOSE:
{      The purpose of this procedure is to add an incoming Channel Connection
{   event into the Channel Connection work list for processing at a later time.
{   This procedure is only executed in the system input task.
{
{   NOTE: This procedure will not return until the event has been placed in the
{         work list. This may require a wait for system resources to free up.
{
{
{        ADD_EVENT_TO_CC_WORK_LIST (DEVICE_RECEIVED_ON, SEQUENCE#, CC_HEADER,
{              DATA);
{
{  DEVICE_RECEIVED_ON: (input) This parameter specifies the network device the
{       event was received on.
{
{  SEQUENCE#: (input) This parameter specifies the sequence number of the event.
{
{  CONNECTION_ID: (input) This parameter specifies the local connection identifier.
{       This identifier will be used to obtain access to the connection structure.
{
{  CC_HEADER: (input) This parameter specifies the Channel Connection header.
{
{  DATA: (input) This parameter specifies the user data of the event.
{

  PROCEDURE [INLINE] add_event_to_cc_work_list
    (    device_received_on: nlt$device_identifier;
         sequence#: nlt$cc_sequence_number;
         connection_id: nlt$cl_connection_id;
         cc_header: nlt$cc_protocol_header;
         data: nlt$bm_message_id);

    VAR
      cc_event: ^nlt$cc_work_unit;

    REPEAT
      ALLOCATE cc_event IN nav$network_paged_heap^;
      IF cc_event = NIL THEN
        syp$cycle;
      IFEND;
    UNTIL cc_event <> NIL;
    cc_event^.next_work_unit := NIL;
    cc_event^.kind := nlc$cc_event_work_unit;
    cc_event^.device_received_on := device_received_on;
    cc_event^.sequence# := sequence#;
    cc_event^.connection_id := connection_id;
    cc_event^.cc_header := cc_header;
    cc_event^.data := data;
    nlv$cc_work_list.append^ := cc_event;
    nlv$cc_work_list.append := ^cc_event^.next_work_unit;

  PROCEND add_event_to_cc_work_list;
?? OLDTITLE ??
?? NEWTITLE := ' increment_receive_statistic', EJECT ??

  PROCEDURE increment_receive_statistic
    (    pdu_size: integer;
     VAR statistic: osi_receive_pdu);

    VAR
      actual: osi_receive_pdu,
      compare_swap_status: osc$cs_successful .. osc$cs_variable_locked,
      expected: osi_receive_pdu,
      i: integer,
      new: osi_receive_pdu;

{ The expected_value is initialized to zero instead of the value of the
{ statistic.  If another cpu has contol of the word i.e. the left half of the
{ word would be all ones.

    expected.value := 0;
    new.pdu_total := 1;
    new.pdu_average := pdu_size;
    #SPOIL (new);

  /write_statistics/
    REPEAT
      #COMPARE_SWAP (statistic.value, expected.value, new.value, actual.value, compare_swap_status);
      IF compare_swap_status = osc$cs_successful THEN
        EXIT /write_statistics/; {----->
      ELSEIF compare_swap_status = osc$cs_failed THEN

{ expected_value is different than the actual}

        #SPOIL (actual);
        expected.value := actual.value;
        new.pdu_total := actual.pdu_total + 1;
        new.pdu_average := ((actual.pdu_average * actual.pdu_total) + pdu_size) DIV new.pdu_total;
        #SPOIL (new);
      ELSE {another cpu has the word}
        syp$cycle;
      IFEND;
    UNTIL compare_swap_status = osc$cs_successful;
  PROCEND increment_receive_statistic;
?? OLDTITLE ??
MODEND nlm$cc_network_event_manager;
