?? NEWTITLE := 'NOS/VE Network Access : Monitor Mode' ??
MODULE nam$process_network_response;
?? RIGHT := 110 ??

{ PURPOSE:
{   This module contains all of the NAM/VE monitor code which processes network PP responses.
{
{ DESIGN:
{   This module contains code to process all solicited and unsolicited
{   responses from any network device driver. The processing which occurs is
{   specific to the particular response.
{
{   NOTE: the alogorithm employed to distribute a received message to a specific task is dependent on
{         the interlocking performed by monitor mode software outside the realm of procedures contained
{         in this module.  If this interlocking scheme changes (i.e., the duration of the locks or the
{         the protection of locks changes), the distribution procedure may be required to ensure that
{         job swapping or task termination does not cause an execution control block to disappear during
{         the execution of the distribution function.
{

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc iot$io_request
*copyc ioc$unsolicited_response_codes
*copyc nat$network_driver_response
*copyc nat$preallocated_rb_control
*copyc nat$preallocated_request_blocks
*copyc nat$received_message_list
*copyc nat$request_block_list
*copyc nat$system_identifier
*copyc nlc$nam_configuration_constants
*copyc nlt$cc_protocol_data_unit
*copyc nlt$cl_connections
*copyc nlt$cl_reference_number
*copyc nlt$pdu_type
*copyc nlt$connections_per_system
*copyc nlt$device_usage_data_list
*copyc nlt$pp_pool_status_and_message
*copyc nlt$receiving_connections
*copyc nlt$signal_device_error
*copyc oss$network_wired
*copyc oss$mainframe_wired
*copyc oss$mainframe_wired_cb
*copyc oss$mainframe_wired_literal
*copyc ost$execution_control_block
*copyc ost$global_task_id
*copyc ost$heap
*copyc pmt$signal
*copyc syt$monitor_status
*copyc tmc$signal_identifiers
?? POP ??
*copyc jmf$ijle_p
*copyc jmp$unlock_ajl
*copyc mtp$error_stop
*copyc tmp$check_taskid
*copyc tmp$find_xcb
*copyc tmp$monitor_ready_system_task
*copyc tmp$send_signal
*copyc tmp$set_system_flag
*copyc tmp$set_namve_task_ready
*copyc jmv$ijl_p
*copyc tmv$ptl_p
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by This Module', EJECT ??

  TYPE
    t$udp_active_receiver_converter = record
      case boolean of
      = TRUE =
        value: nlt$udp_active_receiver,
      = FALSE =
        i: integer,
      casend,
    recend;

  VAR
    nav$intranet_mgmt_work_list: [XDCL, #GATE, oss$network_wired] nat$network_driver_responses :=
          [[0, NIL], [0, NIL]],
    nav$network_wired_heap: [XDCL, #GATE, oss$network_wired] ^ost$heap,


    nav$system_id: [XDCL, #GATE, oss$mainframe_wired] nat$system_identifier,

    nav$system_input_taskid: [XDCL, #GATE, oss$mainframe_wired] ost$global_task_id,

    nav$si_received_message_list: [XDCL, #GATE, oss$mainframe_wired_cb] nat$received_message_list := [NIL, 0],

    nav$completed_output_taskid: [XDCL, #GATE, oss$mainframe_wired] ost$global_task_id,

    nav$connection_establish_taskid: [XDCL, #GATE, oss$mainframe_wired] ost$global_task_id,

    nav$directory_me_taskid: [XDCL, #GATE, oss$mainframe_wired] ost$global_task_id,

    nav$completed_output_requests: [XDCL, #GATE, oss$mainframe_wired] nat$request_block_list := [NIL, 0],

    nav$network_response_processor: [XDCL, #GATE, oss$mainframe_wired] iot$response_processor :=
          ^nap$network_response_processor;

  VAR
    nav$preallocated_rb_control: [XDCL, #GATE, oss$mainframe_wired] nat$preallocated_rb_control := [0, 1],
    nav$preallocated_request_block: [XDCL, #GATE, oss$mainframe_wired] nat$preallocated_request_blocks := NIL;

{ It's very unclear what changing of the following variable would do, as tmp$find_xcb
{ is returning a bad status for any swap_status >= jmc$iss_swapped_io_cannot_init.
{ So currently, it does not make much sense to check this variable when it's always FALSE.
{
{ VAR
{   nav$access_swapped_tasks: [XDCL, #GATE, oss$mainframe_wired] boolean := FALSE;

  VAR

{ The following must be locked before modifying the pointer to connections (nlv$cl_connections).

    nlv$cl_connections_control: [XDCL, #GATE] nlt$cl_connections_control := [nlc$cl_connections_unlocked],

    nlv$maximum_system_connections: [XDCL, #GATE] nlt$connections_per_system :=
          nlc$default_maximum_connections,

    nlv$cl_connections: [XDCL, #GATE] nlt$cl_connections := [0, 0, NIL],

{   The total number of active connections (including priority connections).

    nlv$cl_active_connections: [XDCL, #GATE] nlt$cl_reference_number := 0;

  VAR
    nlv$device_usage_data: [XDCL, #GATE, oss$network_wired] ^nlt$device_usage_data_list := NIL;

  VAR
    nlv$receiving_connections: [XDCL, #GATE, oss$mainframe_wired] nlt$receiving_connections := [0, NIL];

  VAR
    nlv$replenish_pp_buffer_pools: [XDCL, #GATE, oss$network_wired] boolean := TRUE;

  VAR
    illegal_pp_response: [STATIC, READ, oss$mainframe_wired_literal] string (29) :=
          'NA - ILLEGAL RESPONSE FROM PP';

?? OLDTITLE ??
?? NEWTITLE := '[inline] DISTRIBUTE_RECEIVED_MESSAGE', EJECT ??

  PROCEDURE [INLINE] distribute_received_message
    (    message_descriptor: ^nlt$bm_message_descriptor;
         xcb: ^ost$execution_control_block;
         ijle_p: ^jmt$initiated_job_list_entry;
     VAR task_to_activate: ost$global_task_id);

{
{   NOTE: the algorithm employed to distribute a received message to a specific task is dependent on
{         the interlocking performed by monitor mode software outside the realm of procedures contained
{         in this module.  If this interlocking scheme changes (i.e., the duration of the locks or the
{         the protection of locks changes), the distribution procedure may be required to ensure that
{         job swapping or task termination does not cause an execution control block to disappear during
{         the execution of the distribution function.
{

    VAR
      result: osc$cs_successful .. osc$cs_variable_locked,
      initial,
      new,
      actual: nat$received_message_list,
      message_distributed: boolean,
      replenish_pp_buffer_pools: boolean,
      status: syt$monitor_status;

    message_distributed := FALSE;
    new.next_received_message := message_descriptor;
    new.fill := 0;
    initial.fill := 0;
    replenish_pp_buffer_pools := message_descriptor^.received_message.pp_pools_need_replenishing;
    IF replenish_pp_buffer_pools THEN
      nlv$replenish_pp_buffer_pools := replenish_pp_buffer_pools;
    IFEND;

    REPEAT
      message_descriptor^.received_message.next_received_message := NIL;
      initial.next_received_message := NIL;

    /push_message/
      BEGIN
        IF (task_to_activate = nav$system_input_taskid) THEN
          REPEAT
            #COMPARE_SWAP (nav$si_received_message_list, initial, new, initial, result);
            CASE result OF
            = osc$cs_successful =
              message_distributed := TRUE;
            = osc$cs_failed =
              message_descriptor^.received_message.next_received_message := initial.next_received_message;
            = osc$cs_variable_locked =
              ;
            CASEND;
          UNTIL message_distributed;
          IF (initial.next_received_message = NIL) THEN
            tmp$monitor_ready_system_task (tmc$stid_namve_system_input, status);
          IFEND;
        ELSE { Queue the message on the user task. }
          REPEAT
            #COMPARE_SWAP (xcb^.received_message_list, initial, new, initial, result);
            CASE result OF
            = osc$cs_successful =
              IF (initial.next_received_message = NIL) THEN
                tmp$set_system_flag (task_to_activate, nac$network_input_received, status);
                IF status.normal THEN
                  message_distributed := TRUE;
                ELSE { Reset the user task queue to nil. }
                  REPEAT
                    #COMPARE_SWAP (xcb^.received_message_list, new, initial, actual, result);
                  UNTIL (result = osc$cs_successful);
                  task_to_activate := nav$system_input_taskid;
                IFEND;
              ELSE
                message_distributed := TRUE;
              IFEND;
            = osc$cs_failed =
              message_descriptor^.received_message.next_received_message := initial.next_received_message;
            = osc$cs_variable_locked =
              ;
            CASEND;
          UNTIL (message_distributed OR (task_to_activate = nav$system_input_taskid));
          IF message_distributed AND replenish_pp_buffer_pools THEN
            tmp$monitor_ready_system_task (tmc$stid_namve_system_input, status);
          IFEND;
        IFEND;
      END /push_message/;
    UNTIL message_distributed;

    IF xcb <> NIL THEN
      jmp$unlock_ajl (ijle_p);
    IFEND;

  PROCEND distribute_received_message;
?? OLDTITLE ??
?? NEWTITLE := 'get_message_contents', EJECT ??

{ PURPOSE:
{    The purpose of this request is to retrieve two pointers.  The first pointer (pdu_kind) will
{    point to the 'kind' field in the CC header (NLT$CC_PROTOCOL_DATA_UNIT), this field will be
{    the first byte of the received_message.  The second pointer (destination) will point to the
{    'destination_reference' field in the CC header, this field will be located at the ninth and
{    tenth bytes of the received_message.  It is assummed that the message length will be at least
{    ten bytes.

  PROCEDURE [INLINE] get_message_contents
    (    received_message: ^nlt$bm_message_descriptor;
     VAR pdu_kind: ^cell;
     VAR destination: ^cell);

    CONST
      eight_bytes = 8;

    pdu_kind := ^received_message^.container^ (1 + received_message^.data_start);

{ The ninth byte of the first descriptor contains the start of the CC destination field.

    IF (received_message^.container_length - received_message^.data_start) > eight_bytes THEN
      destination := ^received_message^.container^ (9 + received_message^.data_start);

{ The first container held eight bytes so the destination will start at the first byte of the
{ second container.

    ELSE
      destination := ^received_message^.link^.container^ (1);
    IFEND;
  PROCEND get_message_contents;

?? OLDTITLE ??
?? NEWTITLE := '[INLINE] get_xcb_p', EJECT ??

  PROCEDURE [INLINE] get_xcb_p
    (    task_id: ost$global_task_id;
     VAR xcb_p: ^ost$execution_control_block;
     VAR ijle_p: ^jmt$initiated_job_list_entry);

*if false
{Original Code with a check for nav$access_swapped_tasks. But as it's never changed ....

    VAR
      local_status: syt$monitor_status;

    local_status.normal := TRUE;
    jmp$get_ijle_p (tmv$ptl_p^ [task_id.index].ijl_ordinal, ijle_p);
    IF (NOT nav$access_swapped_tasks) AND (ijle_p^.swap_status <> jmc$iss_executing) THEN
      xcb_p := NIL;
    ELSE
      tmp$find_xcb (task_id, xcb_p, ijle_p, local_status);
      IF NOT local_status.normal THEN
        xcb_p := NIL;
      ELSEIF xcb_p^.task_is_terminating THEN
        xcb_p := NIL;
        jmp$unlock_ajl (ijle_p);
      IFEND;
    IFEND;
*else

    VAR
      local_status: syt$monitor_status;

    local_status.normal := TRUE;
    xcb_p := NIL;
    ijle_p := jmf$ijle_p (tmv$ptl_p^ [task_id.index].ijl_ordinal);
    IF ijle_p^.swap_status = jmc$iss_executing THEN
      tmp$find_xcb (task_id, xcb_p, ijle_p, local_status);
      IF local_status.normal AND xcb_p^.task_is_terminating THEN {XCB_P is not touched, if is not normal
        xcb_p := NIL;
        jmp$unlock_ajl (ijle_p);
      IFEND;
    IFEND;
*ifend

  PROCEND get_xcb_p;
?? OLDTITLE ??
?? NEWTITLE := 'process_invalid_cc_pdu', EJECT ??

{
{  PURPOSE:
{    The purpose of this procedure is to report that an invalid CC PDU
{    has been received and request that the message be released and the
{    offending device be reset.

  PROCEDURE process_invalid_cc_pdu
    (    message_descriptor: ^nlt$bm_message_descriptor;
     VAR status: syt$monitor_status);

    VAR
      signal: pmt$signal,
      signal_contents: ^nlt$signal_device_error;


    signal.identifier := nac$network_device_error;
    signal_contents := #LOC (signal.contents);
    signal_contents^.reset_device := TRUE;
    signal_contents^.device_id := message_descriptor^.received_message.device_id;
    signal_contents^.pp_pools_need_replenishing := message_descriptor^.received_message.
          pp_pools_need_replenishing;
    signal_contents^.peer_global_flow_control := message_descriptor^.received_message.
          peer_global_flow_control;
    signal_contents^.message := message_descriptor;
    tmp$send_signal (nav$system_input_taskid, signal, status);

  PROCEND process_invalid_cc_pdu;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] process_received_message', EJECT ??

{
{ PURPOSE:
{   The purpose of this procedure is to process incoming network
{   messages that were received without error by the PP. This procedure
{   processes both Channelnet and Channel Connection messages. If the
{   message cannot be delivered to job mode, an abnormal status is returned;
{   this will result in the message being left in the PP response buffer
{   for processing at a later time. Returning abnormal status does NOT indicate
{   that an error occurred.
{

  PROCEDURE [INLINE] process_received_message
    (    message_descriptor { input, output } : ^nlt$bm_message_descriptor;
     VAR status: syt$monitor_status);

    VAR
      cc_pdu_kind: ^nlt$cc_pdu_kind,
      consume_sequence_number: boolean,
      destination_reference: ^nlt$cl_reference_number,
      ijle_p: ^jmt$initiated_job_list_entry,
      message_queued: boolean,
      reference_number_valid: boolean,
      task: ost$global_task_id,
      validation_complete: boolean,
      xcb: ^ost$execution_control_block;

    status.normal := TRUE;
    ijle_p := NIL;
    xcb := NIL;
    task := nav$system_input_taskid;

{ Fall through the following code for CHANNELNET PDU.

    IF message_descriptor^.received_message.pdu_type = nlc$channel_connection_pdu THEN
      get_message_contents (message_descriptor, cc_pdu_kind, destination_reference);

      CASE cc_pdu_kind^ OF
      = nlc$cc_connect_confirm, nlc$cc_disconnect_request, nlc$cc_disconnect_confirm,
            nlc$cc_credit_allocation, nlc$cc_data, nlc$cc_expedited_data =
        IF (cc_pdu_kind^ <> nlc$cc_disconnect_request) OR (destination_reference^ <> 0) THEN
          consume_sequence_number := ((cc_pdu_kind^ <> nlc$cc_disconnect_request) AND
                (cc_pdu_kind^ <> nlc$cc_disconnect_confirm));

{ The CC sequence number assigned to a disconnect request or confirm is not unique and is not
{ used in sequencing the disconnect for processing. A disconnect request or confirm will always
{ be processed immediately. The sequence number assigned serves only as a possible aid in debugging.

          validate_and_optional_q (message_descriptor, cc_pdu_kind^, destination_reference^,
                consume_sequence_number, validation_complete, reference_number_valid, message_queued, task);
          IF (validation_complete) AND (reference_number_valid) THEN
            IF message_queued THEN
              RETURN; {----->
            IFEND;
            IF (cc_pdu_kind^ = nlc$cc_disconnect_confirm) OR (cc_pdu_kind^ = nlc$cc_disconnect_request) THEN
              task := nav$connection_establish_taskid;
            IFEND;
          ELSEIF validation_complete THEN


{  The destination reference number was found to be invalid (i.e., no connection
{  structure was found).

            process_invalid_cc_pdu (message_descriptor, status);

            RETURN; {----->
          ELSE

{  Access to the connection structure was not obtained and therefore a sequence
{  number could not be assigned.  An abnormal status will be returned; this will
{  result in the message being left in the PP response buffer for processing at
{  a later time.

            status.normal := FALSE;
            RETURN; {----->
          IFEND;
        ELSE { disconnect request with destination reference = 0
          message_descriptor^.received_message.sequence#_or_connect_timestamp.sequence_number := 1;
          task := nav$connection_establish_taskid;
        IFEND;

      = nlc$cc_connect_request =
        message_descriptor^.received_message.sequence#_or_connect_timestamp.
              time_connect_request_received := #FREE_RUNNING_CLOCK (0);
        task := nav$connection_establish_taskid;

      = nlc$cc_global_window =
        message_descriptor^.received_message.sequence#_or_connect_timestamp.sequence_number := 1;
        task := nav$connection_establish_taskid;

      ELSE {Invalid CC PDU kind.
        process_invalid_cc_pdu (message_descriptor, status);
        RETURN; {----->
      CASEND;

      IF task <> nav$system_input_taskid THEN
        get_xcb_p (task, xcb, ijle_p);
        IF xcb = NIL THEN
          task := nav$system_input_taskid;
        IFEND;
      IFEND;
    IFEND;

    distribute_received_message (message_descriptor, xcb, ijle_p, task);

  PROCEND process_received_message;
?? OLDTITLE ??
?? NEWTITLE := 'queue_response_for_mgmt_task' ??
?? NEWTITLE := '[INLINE] move_bytes', EJECT ??

{ The purpose of this procedure is to get an available response from the
{ available response queue, fill in the details and queue it on the
{ outstanding response queue in the intranet layer mgmt work list.
{ The intranet layer mgmt task is readied to process the response.


  PROCEDURE queue_response_for_mgmt_task
    (    pp_response: ^iot$pp_response;
         received_detailed_status: ^iot$detailed_status;
     VAR status: syt$monitor_status);


    PROCEDURE [INLINE] move_bytes
      (    source: ^cell;
           dest: ^cell;
           length: 0 .. ioc$detailed_status_length_b);

      VAR
        str1: ^string (ioc$detailed_status_length_b),
        str2: ^string (ioc$detailed_status_length_b);

      IF length <> 0 THEN
        str1 := source;
        str2 := dest;
        str2^ (1, length) := str1^ (1, length);
        #SPOIL (str2^);
      IFEND;
    PROCEND move_bytes;
?? OLDTITLE, EJECT ??

    VAR
      available_response: ^nat$network_driver_response,
      cs_status: osc$cs_successful .. osc$cs_variable_locked,
      detailed_status: ^iot$detailed_status,
      ignore_status: syt$monitor_status,
      network_request: ^nat$network_request,
      new_response_list: nat$response_queue_access,
      old_response_list: nat$response_queue_access,
      temp_sequence: 0 .. 0ffffff(16);

    status.normal := TRUE;
    new_response_list.sequence := 0;
    new_response_list.next_entry := NIL;
    old_response_list := new_response_list;

    REPEAT
      #COMPARE_SWAP (nav$intranet_mgmt_work_list.free_responses, old_response_list, new_response_list,
            old_response_list, cs_status);
      IF cs_status = osc$cs_failed THEN
        IF old_response_list.next_entry <> NIL THEN
          temp_sequence := old_response_list.sequence + 1;
          temp_sequence := temp_sequence MOD 10000(16);
          new_response_list.sequence := temp_sequence;
          new_response_list.next_entry := old_response_list.next_entry^.next_entry;
        ELSE

{ If there are no free responses, an abnormal status is returned to the caller.
{ This will cause the response to be requeued in the pp response buffer. The response
{ is reissued later.

          status.normal := FALSE;
          RETURN; {----->
        IFEND;
      IFEND;
    UNTIL cs_status = osc$cs_successful;

    available_response := old_response_list.next_entry;
    IF available_response = NIL THEN
      status.normal := FALSE;
      RETURN; {----->
    IFEND;
    available_response^.next_entry := NIL;
    available_response^.pp_response := pp_response^;

{ Break the link between the solicited response and the request block.

    IF pp_response^.response_code.primary_response <> ioc$unsolicited_response THEN
      network_request := pp_response^.request^.device_request_p;
      available_response^.command := network_request^.peripheral_request.command;
      available_response^.pp_response.request := NIL;
    IFEND;

    IF received_detailed_status <> NIL THEN
      detailed_status := ^available_response^.detailed_status;
      RESET detailed_status;
      move_bytes (received_detailed_status, detailed_status,
            (pp_response^.response_length - #SIZE (iot$pp_response)));
      available_response^.detailed_status_pointer := detailed_status;
    ELSE
      available_response^.detailed_status_pointer := NIL;
    IFEND;

{ Queue the response in the outstanding responses queue.

    new_response_list.sequence := 0;
    new_response_list.next_entry := available_response;
    old_response_list := new_response_list;
    REPEAT
      #COMPARE_SWAP (nav$intranet_mgmt_work_list.outstanding_responses, old_response_list, new_response_list,
            old_response_list, cs_status);
      IF cs_status = osc$cs_failed THEN
        temp_sequence := old_response_list.sequence + 1;
        temp_sequence := temp_sequence MOD 10000(16);
        new_response_list.sequence := temp_sequence;
        available_response^.next_entry := old_response_list.next_entry;
      IFEND;
    UNTIL cs_status = osc$cs_successful;

    tmp$monitor_ready_system_task (tmc$stid_intranet_layer_mgmt, ignore_status);

  PROCEND queue_response_for_mgmt_task;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] queue_completed_request', EJECT ??

  PROCEDURE [INLINE] queue_completed_request
    (    completed_request: ^nat$request_block;
     VAR request_block_link: ^nat$request_block);

    CONST
      maximum_queued_requests = 8;

    VAR
      current,
      new: nat$request_block_list,
      cs_status: osc$cs_successful .. osc$cs_variable_locked,
      ignore_status: syt$monitor_status;

{ Queue the completed request block on nav$completed_output_requests.

    request_block_link := NIL;
    current.request_block_link := NIL;
    current.requests_queued := 0;
    new.request_block_link := completed_request;
    new.requests_queued := 1;
    REPEAT
      #COMPARE_SWAP (nav$completed_output_requests, current, new, current, cs_status);
      IF (cs_status = osc$cs_failed) THEN
        new.requests_queued := current.requests_queued + 1;
        request_block_link := current.request_block_link;
      IFEND;
    UNTIL (cs_status = osc$cs_successful);

    IF new.requests_queued = maximum_queued_requests THEN
      tmp$monitor_ready_system_task (tmc$stid_completed_output, ignore_status);
    IFEND;

  PROCEND queue_completed_request;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] validate_and_optional_q', EJECT ??
{
{    The purpose of this procedure is to access the channel connection for
{ which the given message is destined.  The channel connection is accessed via
{ a non exclusive to the root.  The given reference number is used to find the
{ channel connection and the message is validated as belonging to the
{ referenced channel connection.  If the channel connection has the 'queue on
{ connection' attribute set and the message is other than a connect or a
{ disconnect PDU, it is queued on the channel connection and the cooresponding
{ receiver task is readied.  If the 'queue on connection' attribute is not set,
{ the requested information is returned from the channel connection.
{
{ MESSAGE_DESCRIPTOR: (input, output)  This parameter contains the pointer to
{       the message.
{
{ CC_PDU_KIND: (input) This parameter specifies the kind of the received
{       channel connection event.
{
{ RERERENCE_NUMBER: (input)  This parameter contains the reference number of
{       the channel connection for which the message is destined.
{
{ CONSUME_SEQUENCE_NUMBER: (input)  This parameter specifies whether or not a
{        Channel Connection sequence number should be consumed (i.e., whether
{        or not a unique sequence number should be assigned).  Note that only
{        data PDUs consume sequence numbers.  A disconnect or disconnect PDU
{        will not consume sequence numbers.
{
{ VALIDATION_COMPLETE: (output)  This parameter specifies whether validation
{        of the reference number was possible.  If the procedure was unable to
{        obtain access to the connection list, validation is not possible and
{        this parameter will be set to FALSE.
{
{ REFERENCE_NUMBER_VALID: (output)  This parameter specifies whether the
{        reference number is valid (i.e., whether the connection exists).  This
{        parameter is valid only if the value of the VALIDATION_COMPLETE
{        parameter is TRUE.
{
{ MESSAGE_QUEUED: (output)  This parameter is set to TRUE if the given message
{       was queued on the channel connection.  Note that if the message is not
{       going to consume sequence number it is not queued on the channel
{       connection.
{
{ TASK: (output)  This parameter specifies the last task recorded as a
{        receiver or sender if one is active, otherwise it is set to the
{        system input task.
{

  PROCEDURE [INLINE] validate_and_optional_q
    (    message_descriptor { input, output } : ^nlt$bm_message_descriptor;
         cc_pdu_kind: nlt$cc_pdu_kind;
         reference_number: nlt$cl_reference_number;
         consume_sequence_number: boolean;
     VAR validation_complete: boolean;
     VAR reference_number_valid: boolean;
     VAR message_queued: boolean;
     VAR task: ost$global_task_id);

    TYPE
      t$cl_connection_root_access = record
        case boolean of
        = TRUE =
          i: integer,
        = FALSE =
          value: nlt$cl_connection_root_access,
        casend,
      recend;

    VAR
      actual_active_receiver: nlt$udp_active_receiver,
      actual_connection_queue: nlt$connection_queue,
      actual_receiving_connections: nlt$receiving_connections,
      cl_connection: ^nlt$cl_connection,
      cs_result: osc$cs_successful .. osc$cs_variable_locked,
      initial_active_receiver: t$udp_active_receiver_converter,
      initial_connection_queue: nlt$connection_queue,
      initial_input_queue: nat$received_message_list,
      initial_receiving_connections: nlt$receiving_connections,
      initial_root: t$cl_connection_root_access,
      local_status: syt$monitor_status,
      message_offset: ost$segment_offset,
      new_active_receiver: nlt$udp_active_receiver,
      new_connection_queue: nlt$connection_queue,
      new_input_queue: nat$received_message_list,
      new_receiving_connections: nlt$receiving_connections,
      new_root: nlt$cl_connection_root_access,
      receiver_active: boolean,
      replenish_pp_buffer_pools: boolean,
      result: osc$cs_successful .. osc$cs_variable_locked,
      root: nlt$cl_reference_number,
      sender_active: boolean;

    validation_complete := FALSE;
    reference_number_valid := FALSE;
    message_queued := FALSE;
    IF nlv$cl_connections.list <> NIL THEN
      initial_root.i := 0;
      new_root := initial_root.value;
      new_root.nonexclusive_accessors := 1;
      root := (reference_number MOD (UPPERBOUND (nlv$cl_connections.list^) + 1));

    /lock_root/
      REPEAT
        #COMPARE_SWAP (nlv$cl_connections.list^ [root].access_control, initial_root, new_root, initial_root,
              result);
        IF (result = osc$cs_successful) THEN
          validation_complete := TRUE;
          cl_connection := nlv$cl_connections.list^ [root].first;

        /find_connection/
          WHILE cl_connection <> NIL DO
            IF (reference_number = cl_connection^.identifier.reference_number) AND
                  (message_descriptor^.received_message.device_id IN cl_connection^.device_ids) AND
                  (nlc$channel_connection_layer IN cl_connection^.layers_active) THEN
              IF cl_connection^.queue_on_connection THEN
                IF consume_sequence_number THEN

{ Queue the message on the connnection.

                  replenish_pp_buffer_pools := message_descriptor^.received_message.
                        pp_pools_need_replenishing;
                  IF replenish_pp_buffer_pools THEN
                    nlv$replenish_pp_buffer_pools := replenish_pp_buffer_pools;
                  IFEND;
                  message_descriptor^.received_message.next_received_message := NIL;
                  initial_input_queue.next_received_message := NIL;
                  initial_input_queue.fill := 0;
                  new_input_queue := initial_input_queue;
                  new_input_queue.next_received_message := message_descriptor;
                  REPEAT
                    #COMPARE_SWAP (cl_connection^.input_queue, initial_input_queue, new_input_queue,
                          initial_input_queue, result);
                    CASE result OF
                    = osc$cs_successful =
                      IF initial_input_queue.next_received_message = NIL THEN

{ Activate the receiver or sender task. If neither is active, activate the system input task.

                        receiver_active := FALSE;
                        sender_active := FALSE;
                        actual_active_receiver.task_id.index := 0;
                        IF cl_connection^.active_receiver <> NIL THEN

{ This is a connection for a UDP socket.

                          initial_active_receiver.i := 0;
                          new_active_receiver := initial_active_receiver.value;
                          REPEAT
                            #COMPARE_SWAP (cl_connection^.active_receiver^, initial_active_receiver,
                                  new_active_receiver, actual_active_receiver, cs_result);
                          UNTIL cs_result <> osc$cs_variable_locked;
                          IF actual_active_receiver.task_id.index <> 0 THEN
                            tmp$set_namve_task_ready (actual_active_receiver.task_id, receiver_active);
                          IFEND;
                        ELSEIF (cl_connection^.message_receiver.active) AND
                              (cl_connection^.message_receiver.task.index <> 0) THEN
                          tmp$set_namve_task_ready (cl_connection^.message_receiver.task, receiver_active);
                        IFEND;
                        IF NOT receiver_active THEN
                          IF (cl_connection^.message_sender.active) AND
                                (cl_connection^.message_sender.task.index <> 0) AND
                                (cl_connection^.message_sender.task <> actual_active_receiver.task_id) THEN
                            tmp$set_namve_task_ready (cl_connection^.message_sender.task, sender_active);
                          IFEND;
                        IFEND;
                        IF (NOT receiver_active) AND (NOT sender_active) THEN

{ Queue the connection in the receiving connections queue.

                          new_receiving_connections.fill := 0;
                          new_receiving_connections.next_connection := NIL;
                          initial_receiving_connections := new_receiving_connections;
                          REPEAT
                            #COMPARE_SWAP (nlv$receiving_connections, initial_receiving_connections,
                                  new_receiving_connections, actual_receiving_connections, cs_result);
                          UNTIL cs_result <> osc$cs_variable_locked;

                          initial_connection_queue.in_queue := FALSE;
                          initial_connection_queue.fill := 0;
                          initial_connection_queue.next_connection := NIL;

                        /queue_connection/
                          REPEAT
                            new_connection_queue.in_queue := TRUE;
                            new_connection_queue.fill := 0;
                            new_connection_queue.next_connection := actual_receiving_connections.
                                  next_connection;
                            REPEAT
                              #COMPARE_SWAP (cl_connection^.connection_queue, initial_connection_queue,
                                    new_connection_queue, actual_connection_queue, cs_result);
                              IF cs_result = osc$cs_failed THEN
                                IF actual_connection_queue.in_queue THEN

{ Connection is already queued.

                                  EXIT /queue_connection/; {----->
                                ELSE
{ There is a bug in the system.
                                IFEND;
                              IFEND;
                            UNTIL cs_result = osc$cs_successful;
                            initial_connection_queue := new_connection_queue;
                            initial_receiving_connections := actual_receiving_connections;
                            new_receiving_connections.fill := 0;
                            new_receiving_connections.next_connection := cl_connection;
                            REPEAT
                              #COMPARE_SWAP (nlv$receiving_connections, initial_receiving_connections,
                                    new_receiving_connections, actual_receiving_connections, cs_result);
                              IF cs_result = osc$cs_failed THEN
                                CYCLE /queue_connection/; {----->
                              IFEND;
                            UNTIL cs_result = osc$cs_successful;
                          UNTIL cs_result = osc$cs_successful;

{ Ready the system input task.

                          IF actual_receiving_connections.next_connection = NIL THEN
                            tmp$monitor_ready_system_task (tmc$stid_namve_system_input, local_status);
                          IFEND;
                        ELSE { the receiver or sender task had been readied
                          IF replenish_pp_buffer_pools THEN
                            tmp$monitor_ready_system_task (tmc$stid_namve_system_input, local_status);
                          IFEND;
                        IFEND;
                      ELSE { messages already queued on the connection
                        IF replenish_pp_buffer_pools THEN
                          tmp$monitor_ready_system_task (tmc$stid_namve_system_input, local_status);
                        IFEND;
                      IFEND;
                      message_queued := TRUE;

                    = osc$cs_failed =
                      message_descriptor^.received_message.next_received_message :=
                            initial_input_queue.next_received_message;

                    = osc$cs_variable_locked =
                      ;
                    CASEND;
                  UNTIL message_queued;
                ELSE { NOT consume_sequence_number
                  message_descriptor^.received_message.connection_id := cl_connection^.identifier;
                  message_descriptor^.received_message.sequence#_or_connect_timestamp.sequence_number := 1;
                IFEND;
              ELSE { do not queue on the connection
                IF cl_connection^.message_receiver.active THEN
                  task := cl_connection^.message_receiver.task;
                ELSEIF cl_connection^.message_sender.active THEN
                  task := cl_connection^.message_sender.task;
                ELSE
                  task := nav$system_input_taskid;
                IFEND;
                message_descriptor^.received_message.connection_id := cl_connection^.identifier;
                message_descriptor^.received_message.sequence#_or_connect_timestamp.sequence_number :=
                      cl_connection^.next_assignable_cc_sequence#;
                IF consume_sequence_number THEN
                  cl_connection^.next_assignable_cc_sequence# :=
                        cl_connection^.next_assignable_cc_sequence# + 1;
                IFEND;
              IFEND;
              reference_number_valid := TRUE;
              EXIT /find_connection/; {----->
            ELSE
              cl_connection := cl_connection^.nextt;
            IFEND;
          WHILEND /find_connection/;

          initial_root.value := new_root;
          new_root.nonexclusive_accessors := new_root.nonexclusive_accessors - 1;

        /release_root/
          REPEAT
            #COMPARE_SWAP (nlv$cl_connections.list^ [root].access_control, initial_root, new_root,
                  initial_root, result);
            IF (result = osc$cs_failed) THEN
              new_root.nonexclusive_accessors := initial_root.value.nonexclusive_accessors - 1;
            IFEND;
          UNTIL (result = osc$cs_successful);

        ELSEIF (result = osc$cs_failed) THEN
          IF NOT initial_root.value.exclusive THEN
            new_root.nonexclusive_accessors := initial_root.value.nonexclusive_accessors + 1;
          ELSE
            EXIT /lock_root/; {----->
          IFEND;
        IFEND;
      UNTIL (result = osc$cs_successful);
    IFEND;
  PROCEND validate_and_optional_q;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] nap$network_response_processor', EJECT ??

  PROCEDURE [XDCL] nap$network_response_processor
    (    pp_response: ^iot$pp_response;
         received_detailed_status: ^iot$detailed_status;
         pp: 1 .. ioc$pp_count;
     VAR status: syt$monitor_status);

{ The purpose of this procedure is to process the responses from the network
{ PPs. This procedure executes in monitor mode. The network responses are
{ communicated to the appropriate process in job mode. The return of an abnormal
{ status from this procedure does NOT indicate an error, rather it indicates
{ that a response could not be processed and should be left in the PP response
{ buffer for processing at a later time (abnormal status does NOT indicate an
{ error).

    VAR
      buffer_status_and_message: ^nlt$pp_pool_status_and_message,
      completed_network_request: ^nat$network_request,
      detailed_status: ^iot$detailed_status,
      device_error_contents: ^nlt$signal_device_error,
      i: integer,
      io_request: ^iot$io_request,
      message_descriptor: ^nlt$bm_message_descriptor,
      next_descriptor: ^nlt$bm_message_descriptor,
      normal_response: iot$io_error,
      pp_status_report: ^nlt$pp_status_report,
      signal: pmt$signal;


    status.normal := TRUE;
    detailed_status := received_detailed_status;
    IF (detailed_status <> NIL) THEN
      RESET detailed_status;
    IFEND;

    CASE pp_response^.response_code.primary_response OF
    = ioc$unsolicited_response =

      CASE pp_response^.unsolicited_response_code OF
      = ioc$unit_ready_to_not_ready, ioc$unit_not_ready_to_ready, ioc$device_operational, ioc$log_pp_message =
        queue_response_for_mgmt_task (pp_response, detailed_status, status);

      = ioc$internal_error =

      = ioc$channelnet_input, ioc$channel_connection_input =
        next_descriptor := NIL;
        NEXT buffer_status_and_message: [1 .. ((pp_response^.response_length - #SIZE (iot$pp_response) -
              #SIZE (nlt$pp_status_report)) DIV #SIZE (nlt$pp_message_delivery))] IN detailed_status;
        FOR i := UPPERBOUND (buffer_status_and_message^.received_message) DOWNTO 1 DO
          message_descriptor := buffer_status_and_message^.received_message [i].message_descriptor;
          message_descriptor^.data_start := message_descriptor^.container_length -
                buffer_status_and_message^.received_message [i].data_length;
          message_descriptor^.link := next_descriptor;
          next_descriptor := message_descriptor;
        FOREND;


{ The 'priority' field of the PP response is defined to contain the network device identifier.

        message_descriptor^.received_message.device_id := pp_response^.priority;
        message_descriptor^.received_message.peer_global_flow_control :=
              buffer_status_and_message^.pp_status_report.peer_global_flow_control;

        message_descriptor^.received_message.pp_pools_need_replenishing :=
              (buffer_status_and_message^.pp_status_report.pp_buffer_pool_status [nlc$bm_large_buffer_index].
              buffer_pool_status <> nlc$buffer_pool_ok) OR (buffer_status_and_message^.pp_status_report.
              pp_buffer_pool_status [nlc$bm_small_buffer_index].buffer_pool_status <> nlc$buffer_pool_ok);

        IF pp_response^.unsolicited_response_code = ioc$channel_connection_input THEN
          message_descriptor^.received_message.pdu_type := nlc$channel_connection_pdu;
        ELSE
          message_descriptor^.received_message.pdu_type := nlc$channelnet_pdu;
        IFEND;
        process_received_message (message_descriptor, status);
        IF status.normal THEN
          nlv$device_usage_data^ [pp_response^.priority].bytes_received :=
                nlv$device_usage_data^ [pp_response^.priority].bytes_received + pp_response^.transfer_count;
        IFEND;

      = ioc$device_error, ioc$flow_control_status_change =

        IF (pp_response^.response_length - #SIZE (iot$pp_response)) = #SIZE (nlt$pp_status_report) THEN
          NEXT pp_status_report IN detailed_status;
          message_descriptor := NIL;
        ELSEIF pp_response^.unsolicited_response_code = ioc$device_error THEN
          next_descriptor := NIL;
          NEXT buffer_status_and_message: [1 .. ((pp_response^.response_length - #SIZE (iot$pp_response) -
                #SIZE (nlt$pp_status_report)) DIV #SIZE (nlt$pp_message_delivery))] IN detailed_status;
          pp_status_report := ^buffer_status_and_message^.pp_status_report;
          FOR i := UPPERBOUND (buffer_status_and_message^.received_message) DOWNTO 1 DO
            message_descriptor := buffer_status_and_message^.received_message [i].message_descriptor;
            message_descriptor^.data_start := message_descriptor^.container_length -
                  buffer_status_and_message^.received_message [i].data_length;
            message_descriptor^.link := next_descriptor;
            next_descriptor := message_descriptor;
          FOREND;
        ELSE {flow_control_status_change response too long
          mtp$error_stop (illegal_pp_response);
        IFEND;

        signal.identifier := nac$network_device_error;
        device_error_contents := #LOC (signal.contents);
        device_error_contents^.reset_device := FALSE;

{  The 'priority' field of the PP response is defined to contain the network device identifier.

        device_error_contents^.device_id := pp_response^.priority;
        device_error_contents^.pp_pools_need_replenishing :=
              (pp_status_report^.pp_buffer_pool_status [nlc$bm_large_buffer_index].buffer_pool_status <>
              nlc$buffer_pool_ok) OR (pp_status_report^.pp_buffer_pool_status [nlc$bm_small_buffer_index].
              buffer_pool_status <> nlc$buffer_pool_ok);
        device_error_contents^.peer_global_flow_control := pp_status_report^.peer_global_flow_control;
        device_error_contents^.message := message_descriptor;
        tmp$send_signal (nav$system_input_taskid, signal, status);

      ELSE
        mtp$error_stop (illegal_pp_response);
      CASEND;

    = ioc$normal_response, ioc$abnormal_response =
      io_request := pp_response^.request;
      completed_network_request := io_request^.device_request_p;
      CASE completed_network_request^.peripheral_request.command.command_code OF
      = ioc$cc_resume, ioc$cc_synchronize_pp, ioc$cc_debug_mode, ioc$cc_normal_flow_control,
            ioc$cc_reset_device, ioc$cc_define_ethernet_address =
        ;
      = ioc$cc_idle =
        queue_response_for_mgmt_task (pp_response, detailed_status, status);
        IF NOT status.normal THEN
          RETURN; {----->
        IFEND;

      = ioc$cc_network_output =

{  The 'priority' field of the PP response is defined to contain the network device
{  identifier.

        nlv$device_usage_data^ [pp_response^.priority].bytes_transmitted :=
              nlv$device_usage_data^ [pp_response^.priority].bytes_transmitted + pp_response^.transfer_count;

      ELSE
        mtp$error_stop (illegal_pp_response);
      CASEND;
      queue_completed_request (#LOC (io_request^), completed_network_request^.request_block_link);
    ELSE
      mtp$error_stop (illegal_pp_response);
    CASEND;

  PROCEND nap$network_response_processor;
?? OLDTITLE ??
MODEND nam$process_network_response;

