?? NEWTITLE := 'NOS/VE Network Access: Network Access Agent' ??
MODULE nlm$network_access_agent;
?? RIGHT := 110 ??

{ PURPOSE:
{   This module contains procedures necessary to communicate with the Network Access
{   Provider (NAP) in the OSI communications device. These procedures provide the OSI
{   Network Access Agent (NAA) in the host.
{ DESIGN:
{   The NAA and the NAP together enable a user in the host to obtain the services of
{   the OSI Connection Less Mode Network Layer in the device. The network layer user
{   in the host can open a network sap via the NAA/NAP. The NAA sends the open sap
{   request to all the devices directly connected to the host. Each open sap request
{   establishes a channel connection between the NAA and the NAP. The subsequent data
{   requests and indications sent and received over a network sap are sent and received
{   over the channel connection.
{   The NAA provides interfaces to open/close network layer saps and to send and receive
{   data over these saps. A single open sap request results in opening the sap over all the
{   directy connected devices that have been initialized via System Management Access Agent.
{   The NAA also provides a broadcast capability.
{   When a directly connected network device becomes unavailable, all network saps opened
{   across the device are closed. When the device becomes available again, the System
{   Management Access Agent will send open sap requests to the NAP for all network layer
{   saps that have been opened in the host.
{   This module contains code that executes in ring 3. It resides on OSF$JOB_TEMPLATE_23D.
{   The XDCL'd procedures have been grouped in alphabetical order followed by the internal
{   procedures. The internal procedures are also in alphabetical order.

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc nac$null_connection_id
*copyc nac$xi_maximum_data_length
*copyc nae$application_interfaces
*copyc nae$namve_conditions
*copyc nae$network_access_agent
*copyc nat$connection_id
*copyc nat$network_message_priority
*copyc nat$network_procedures
*copyc nat$osi_network_address
*copyc nat$osi_network_address_prefix
*copyc nlt$cc_address
*copyc nlt$cc_connection_class
*copyc nlt$cc_device_and_data_list
*copyc nlt$cc_disconnect_reason
*copyc nlt$cc_interface
*copyc nlt$cl_connection
*copyc nlt$cl_connection_layer_templat
*copyc nlt$cl_layer_name
*copyc nlt$device_count
*copyc nlt$device_identifier
*copyc nlt$device_list
*copyc nlt$na_disconnect_reason
*copyc nlt$na_layer_connection
*copyc nlt$na_protocol_data_unit
*copyc nlt$na_sap_attributes
*copyc nlt$network_device
*copyc nlt$sm_devices
*copyc ost$signature_lock_status
*copyc ost$status
?? POP ??
*copyc nap$display_message
*copyc nap$namve_system_error
*copyc nlp$al_get_data_length
*copyc nlp$bm_create_message
*copyc nlp$bm_extract_message_prefix
*copyc nlp$bm_flush_message
*copyc nlp$bm_get_message_length
*copyc nlp$bm_release_message
*copyc nlp$cc_disconnect
*copyc nlp$cc_initialize_template
*copyc nlp$cc_request_connection
*copyc nlp$cc_send_data
*copyc nlp$cl_activate_layer
*copyc nlp$cl_create_connection
*copyc nlp$cl_deactivate_layer
*copyc nlp$cl_get_exclusive_via_cid
*copyc nlp$cl_get_layer_connection
*copyc nlp$cl_initialize_template
*copyc nlp$cl_release_exclusive_access
*copyc nlp$get_exclusive_access
*copyc nlp$get_nonexclusive_access
*copyc nlp$release_exclusive_access
*copyc nlp$release_nonexclusive_access
*copyc osp$add_to_locked_variable
*copyc osp$append_status_integer
*copyc osp$clear_job_signature_lock
*copyc osp$decrement_locked_variable
*copyc osp$increment_locked_variable
*copyc osp$set_job_signature_lock
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc pmp$log
*copyc nav$cdna_multicast_address
*copyc nav$global_osi_statistics
*copyc nav$network_paged_heap
*copyc nav$network_procedures
*copyc nlv$configured_network_devices
*copyc nlv$na_sap_list
*copyc nlv$sm_devices
*copyc nlv$transport_network_selector
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] nlp$na_broadcast_data', EJECT ??
*copy nlh$na_broadcast_data

  PROCEDURE [XDCL] nlp$na_broadcast_data
    (    device_id: nlt$device_identifier;
         source_sap_id: nat$network_selector;
         destination_sap_id: nat$network_selector;
         data: nat$data_fragments;
     VAR status: ost$status);

    VAR
      actual: integer,
      address_prefix: ^nat$osi_network_address_prefix,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      connection_exists: boolean,
      connection_id: nat$connection_id,
      data_fragments: ^nat$data_fragments,
      data_length: nat$data_length,
      data_request_fixed: nlt$na_data_request_fixed,
      destination_address: ^SEQ ( * ),
      destination_address_seq: ^SEQ ( * ),
      i: integer,
      ignore_status: ost$status,
      layer_active: boolean,
      log_message: ^string (132),
      log_message_length: integer,
      system_management_list: ^nlt$sm_device_list,
      sap_attributes: ^nlt$na_sap_attributes,
      subnet_attributes: ^nlt$subnet_attributes,
      subnet_id: ^nat$subnet_identifier,
      system_id: ^nat$system_identifier,
      total_bytes_sent: integer,
      unused_byte_count: 0 .. 0ff(16),
      unused_portion: ^array [ * ] of 0 .. 0ff(16),
      user_data: nlt$bm_message_id;

    status.normal := TRUE;
    nlp$al_get_data_length (data, data_length);
    IF data_length <= nac$xi_maximum_data_length THEN
      data_request_fixed.header.kind := nlc$na_data_request;

{ Setup the data fragments with the first three entries for the fixed sized
{ NA header and the variable sized destination address.

      PUSH data_fragments: [1 .. (UPPERBOUND (data) + 3)];
      data_fragments^ [1].address := ^data_request_fixed;
      data_fragments^ [1].length := #SIZE (data_request_fixed);

{ Reserve the second entry for the variable sized destination address (without the
{ network selector) and the third entry for the network selector.
{ Move the remaining entries for the user data.

      data_fragments^ [3].address := ^destination_sap_id;
      data_fragments^ [3].length := #SIZE (destination_sap_id);
      FOR i := 1 TO UPPERBOUND (data) DO
        data_fragments^ [i + 3] := data [i];
      FOREND;


{ Verify that the source sap is open.

      osp$set_job_signature_lock (nlv$na_sap_list.lock);
      find_sap_attributes (source_sap_id, sap_attributes);
      IF (sap_attributes <> NIL) AND (sap_attributes^.sap_device_list [device_id].status =
            nlc$na_sap_open) THEN
        connection_id := sap_attributes^.sap_device_list [device_id].connection_id;
        data_request_fixed.priority := sap_attributes^.priority;
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);

        PUSH destination_address_seq: [[REP nac$osi_max_network_address_len OF cell]];
        RESET destination_address_seq;

{ Broadcast the data on each subnet accessible via the given device.

        data_length := data_length + #SIZE (data_request_fixed) + #SIZE (destination_sap_id);
        total_bytes_sent := 0;
        nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
        IF connection_exists THEN
          nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, layer_active, connection);
          IF layer_active THEN
            nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
            system_management_list := nlv$sm_devices.list;

{ Verify that the device has been initialized via system mgmt protocol.

            IF system_management_list^ [device_id].state = nlc$sm_initialized THEN

{ Initialize the default destination address. This address will be used to broadcast
{ over subnets that do not have an associated multicast address.
{ Note that the network selector is being setup as a separate fragment.

              NEXT destination_address: [[REP (system_management_list^ [device_id].
                    network_address_length - #SIZE (destination_sap_id)) OF cell]] IN destination_address_seq;
              RESET destination_address;
              NEXT address_prefix: [#SIZE (system_management_list^ [device_id].network_address_prefix^)] IN
                    destination_address;
              address_prefix^ := system_management_list^ [device_id].network_address_prefix^;
              unused_byte_count := (#SIZE (destination_address^) - #SIZE (address_prefix^) -
                    #SIZE (nat$subnet_identifier) - #SIZE (nat$system_identifier));
              IF unused_byte_count > 0 THEN
                NEXT unused_portion: [1 .. unused_byte_count] IN destination_address;
                FOR i := 1 TO UPPERBOUND (unused_portion^) DO
                  unused_portion^ [i] := 0;
                FOREND;
              IFEND;

{ The following pointers should not be changed after they have been initialized.

              NEXT subnet_id IN destination_address;
              NEXT system_id IN destination_address;

{ Broadcast over each subnet accessible via this device.

              subnet_attributes := system_management_list^ [device_id].subnet_list;

            /broadcast_loop/
              WHILE subnet_attributes <> NIL DO
                IF subnet_attributes^.multicast_address <> NIL THEN
                  data_fragments^ [2].address := subnet_attributes^.multicast_address;
                  data_fragments^ [2].length := #SIZE (subnet_attributes^.multicast_address^) -
                        #SIZE (destination_sap_id);
                ELSE

{ Setup the multicast address using the CDNA multicast address.

                  subnet_id^ := subnet_attributes^.subnet_id;
                  system_id^ := nav$cdna_multicast_address;
                  data_fragments^ [2].address := destination_address;
                  data_fragments^ [2].length := #SIZE (destination_address^);
                IFEND;

                data_request_fixed.header.length := data_length + data_fragments^ [2].length;
                data_request_fixed.destination_address_length := data_fragments^ [2].
                      length + #SIZE (destination_sap_id);
                nlp$bm_create_message (data_fragments^, user_data, ignore_status);
                nlp$cc_send_data (cl_connection, user_data, ignore_status);
                total_bytes_sent := total_bytes_sent + data_request_fixed.header.length;
                subnet_attributes := subnet_attributes^.next_entry;
              WHILEND /broadcast_loop/;
            IFEND;
            nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
          IFEND;
          nlp$cl_release_exclusive_access (cl_connection);

{! statistics begin
          osp$increment_locked_variable (nav$global_osi_statistics.network_access_agent.broadcasts_sent, 0,
                actual);
          IF total_bytes_sent > 0 THEN
            osp$add_to_locked_variable (nav$global_osi_statistics.network_access_agent.total_bytes_sent, 0,
                  total_bytes_sent, actual);
          IFEND;
{! statistics end
        IFEND;

      ELSE { Sap not open
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);
        osp$set_status_condition (nae$na_sap_not_open, status);
        osp$append_status_integer (osc$status_parameter_delimiter, source_sap_id, 10, TRUE, status);
      IFEND;
    ELSE { Max data length exceeded
      osp$set_status_condition (nae$max_data_length_exceeded, status);
      osp$append_status_integer (osc$status_parameter_delimiter, data_length, 10, TRUE, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$na_close_sap
    (    sap_id: nat$network_selector;
     VAR status: ost$status);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      i: integer,
      previous_sap_attributes: ^^nlt$na_sap_attributes,
      sap_attributes: ^nlt$na_sap_attributes,
      sap_device_list: ^nlt$na_sap_device_list;

    status.normal := TRUE;
    osp$set_job_signature_lock (nlv$na_sap_list.lock);
    IF nlv$na_sap_list.open_saps [sap_id] THEN
      nlv$na_sap_list.open_saps [sap_id] := FALSE;

{ Find the sap attributes entry for the given sap_id.

      previous_sap_attributes := ^nlv$na_sap_list.sap_attributes;
      WHILE (previous_sap_attributes^ <> NIL) AND (previous_sap_attributes^^.sap_id <> sap_id) DO
        previous_sap_attributes := ^previous_sap_attributes^^.next_entry;
      WHILEND;
      IF previous_sap_attributes^ <> NIL THEN

{ Delink the sap attributes entry from the sap list.

        sap_attributes := previous_sap_attributes^;
        previous_sap_attributes^ := sap_attributes^.next_entry;
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);

{ Close the sap in the devices.

        sap_device_list := ^sap_attributes^.sap_device_list;
        FOR i := LOWERBOUND (sap_device_list^) TO UPPERBOUND (sap_device_list^) DO
          IF (sap_device_list^ [i].status = nlc$na_sap_open) OR
                (sap_device_list^ [i].status = nlc$na_await_open_sap_confirm) THEN
            nlp$cl_get_exclusive_via_cid (sap_device_list^ [i].connection_id, connection_exists,
                  cl_connection);
            IF connection_exists THEN
              disconnect_na_connection (cl_connection, nlc$na_dr_user_request);
              nlp$cl_release_exclusive_access (cl_connection);
            IFEND;
          IFEND;
        FOREND;
        FREE sap_attributes IN nav$network_paged_heap^;
      ELSE {Lost the sap attributes entry
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);
        osp$set_status_condition (nae$na_sap_not_open, status);
        osp$append_status_integer (osc$status_parameter_delimiter, sap_id, 10, TRUE, status);
        nap$namve_system_error (FALSE, 'NAA - Lost an open sap entry.', ^status);
      IFEND;
    ELSE { Sap not open
      osp$clear_job_signature_lock (nlv$na_sap_list.lock);
      osp$set_status_condition (nae$na_sap_not_open, status);
      osp$append_status_integer (osc$status_parameter_delimiter, sap_id, 10, TRUE, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$na_connect_event_processor
    (    cl_connection: ^nlt$cl_connection;
         event: nlt$cc_event;
     VAR inventory_report: integer);

    VAR
      data: nlt$bm_message_id,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      disconnect_request: nlt$na_close_sap_request,
      ignore_status: ost$status;

    inventory_report := 0;
    disconnect_request.header.kind := nlc$na_close_sap_request;
    disconnect_request.header.length := #SIZE (disconnect_request);
    disconnect_request.reason := nlc$na_dr_unexpected_event;
    data_fragment [1].address := ^disconnect_request;
    data_fragment [1].length := disconnect_request.header.length;
    nlp$bm_create_message (data_fragment, data, ignore_status);
    nlp$cc_disconnect (cl_connection, data, ignore_status);

{ Release connect data.

    data := event.connect.data;
    nlp$bm_release_message (data);

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

  PROCEDURE [XDCL] nlp$na_disconnect_connections
    (    device_id: nlt$device_identifier);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      connection_count: integer,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      i: integer,
      layer_active: boolean,
      local_status: ost$status,
      sap_attributes: ^nlt$na_sap_attributes,
      sap_count: integer;

    connection_count := 0;
    sap_count := 0;

{ Get the list of all active network access connections.

    osp$set_job_signature_lock (nlv$na_sap_list.lock);
    sap_attributes := nlv$na_sap_list.sap_attributes;
    WHILE sap_attributes <> NIL DO
      sap_count := sap_count + 1;
      sap_attributes := sap_attributes^.next_entry;
    WHILEND;

    IF sap_count > 0 THEN
      PUSH connection_list: [1 .. sap_count];
      sap_attributes := nlv$na_sap_list.sap_attributes;
      WHILE sap_attributes <> NIL DO
        IF (sap_attributes^.sap_device_list [device_id].status = nlc$na_sap_open) OR
              (sap_attributes^.sap_device_list [device_id].status = nlc$na_await_open_sap_confirm) THEN
          connection_count := connection_count + 1;
          connection_list^ [connection_count] := sap_attributes^.sap_device_list [device_id].connection_id;
        IFEND;
        sap_attributes := sap_attributes^.next_entry;
      WHILEND;
    IFEND;
    osp$clear_job_signature_lock (nlv$na_sap_list.lock);

{ Disconnect all network access connections.

    IF connection_count > 0 THEN
      FOR i := 1 TO connection_count DO
        nlp$cl_get_exclusive_via_cid (connection_list^ [i], connection_exists, cl_connection);
        IF connection_exists THEN
          nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, layer_active, connection);
          IF layer_active THEN
            disconnect_na_connection (cl_connection, nlc$na_dr_sme_disconnect);
            ;
          IFEND;
          nlp$cl_release_exclusive_access (cl_connection);
        IFEND;
      FOREND;

{ Clear the connection ids and update sap status in the sap list.
{ Note that in the meantime while the sap list was unlocked,
{ no additional saps could have been opened across the device
{ on account of the system mgmt initialization status in the
{ network device list entry.

      osp$set_job_signature_lock (nlv$na_sap_list.lock);
      sap_attributes := nlv$na_sap_list.sap_attributes;
      WHILE sap_attributes <> NIL DO
        sap_attributes^.sap_device_list [device_id].status := nlc$na_sap_closed;
        sap_attributes^.sap_device_list [device_id].connection_id := nac$null_connection_id;
        sap_attributes := sap_attributes^.next_entry;
      WHILEND;
      osp$clear_job_signature_lock (nlv$na_sap_list.lock);
    IFEND;

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

  PROCEDURE [XDCL] nlp$na_event_processor
    (    cl_connection { input, output } : ^nlt$cl_connection;
         event: nlt$cc_event;
     VAR inventory_report: integer);


    VAR
      accept_indication: nlt$na_pdu_header,
      actual: integer,
      connection: ^nlt$na_layer_connection,
      data: nlt$bm_message_id,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      data_indication_fixed: nlt$na_data_indication_fixed,
      data_length: integer,
      disconnect_indication: nlt$na_disconnect_indication,
      error_message: ^string (132),
      error_message_length: integer,
      ignore_bytes_moved: nat$data_length,
      layer_active: boolean,
      local_status: ost$status,
      sap_attributes: ^nlt$na_sap_attributes,
      source_address: ^nat$osi_network_address;

    inventory_report := 0;
    nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, layer_active, connection);
    IF layer_active THEN
      CASE event.kind OF
?? NEWTITLE := 'nlc$cc_data_event', EJECT ??
      = nlc$cc_data_event =
        data := event.data.data;
        nlp$bm_get_message_length (data, data_length);
        IF data_length > #SIZE (data_indication_fixed) THEN
          nlp$bm_extract_message_prefix (^data_indication_fixed, #SIZE (data_indication_fixed), data,
                ignore_bytes_moved);
          IF data_indication_fixed.header.kind = nlc$na_data_indication THEN
            IF (data_indication_fixed.header.length = data_length) AND
                  (data_indication_fixed.source_address_length > 0) AND
                  (data_length > (#SIZE (data_indication_fixed) +
                  data_indication_fixed.source_address_length)) THEN
              PUSH source_address: [[REP data_indication_fixed.source_address_length OF cell]];
              nlp$bm_extract_message_prefix (source_address, #SIZE (source_address^), data,
                    ignore_bytes_moved);

{ Call the event handler to deliver the user data.

              nav$network_procedures [connection^.event_processor].
                    network_event_processor^ (connection^.sap_id, source_address^, connection^.device_id,
                    data);

{! statistics begin
              osp$increment_locked_variable (nav$global_osi_statistics.network_access_agent.pdus_received, 0,
                    actual);
              osp$add_to_locked_variable (nav$global_osi_statistics.network_access_agent.total_bytes_received,
                    0, data_length, actual);
{! statistics end

            ELSE { Unexpected length
              nlp$bm_release_message (data);
              disconnect_na_connection (cl_connection, nlc$na_dr_unexpected_length);
              update_sap_status (connection^.sap_id, connection^.device_id);
            IFEND;
          ELSE { Invalid NA event kind
            nlp$bm_release_message (data);
            disconnect_na_connection (cl_connection, nlc$na_dr_unexpected_event);
            update_sap_status (connection^.sap_id, connection^.device_id);
          IFEND;
        ELSE { Insufficient data
          nlp$bm_release_message (data);
          disconnect_na_connection (cl_connection, nlc$na_dr_insufficient_data);
          update_sap_status (connection^.sap_id, connection^.device_id);
        IFEND;
?? OLDTITLE ??
?? NEWTITLE := 'nlc$cc_accept_event', EJECT ??
      = nlc$cc_accept_event =
        data := event.accept.data;
        nlp$bm_get_message_length (data, data_length);
        IF data_length = #SIZE (accept_indication) THEN
          data_fragment [1].address := ^accept_indication;
          data_fragment [1].length := #SIZE (accept_indication);
          nlp$bm_flush_message (data_fragment, data, data_length, {ignore} local_status);
          IF data_length = accept_indication.length THEN
            IF accept_indication.kind = nlc$na_open_sap_confirm THEN
              osp$set_job_signature_lock (nlv$na_sap_list.lock);
              find_sap_attributes (connection^.sap_id, sap_attributes);
              IF sap_attributes <> NIL THEN
                IF sap_attributes^.sap_device_list [connection^.device_id].status =
                      nlc$na_await_open_sap_confirm THEN
                  sap_attributes^.sap_device_list [connection^.device_id].status := nlc$na_sap_open;
                  osp$clear_job_signature_lock (nlv$na_sap_list.lock);
                  connection^.state := nlc$na_connection_open;
                ELSE { Unexpected NA event
                  osp$clear_job_signature_lock (nlv$na_sap_list.lock);
                  disconnect_na_connection (cl_connection, nlc$na_dr_unexpected_event);
                  update_sap_status (connection^.sap_id, connection^.device_id);
                IFEND;
              ELSE

{ Sap must have been closed in the meantime.

                osp$clear_job_signature_lock (nlv$na_sap_list.lock);
              IFEND;
            ELSE { Unexpected NA event
              disconnect_na_connection (cl_connection, nlc$na_dr_unexpected_event);
              update_sap_status (connection^.sap_id, connection^.device_id);
            IFEND;
          ELSE { Length mismatch
            disconnect_na_connection (cl_connection, nlc$na_dr_length_mismatch);
            update_sap_status (connection^.sap_id, connection^.device_id);
          IFEND;
        ELSE { Unexpected length
          nlp$bm_release_message (data);
          disconnect_na_connection (cl_connection, nlc$na_dr_unexpected_length);
          update_sap_status (connection^.sap_id, connection^.device_id);
        IFEND;
?? OLDTITLE ??
?? NEWTITLE := 'nlc$cc_disconnect_event', EJECT ??
      = nlc$cc_disconnect_event =
        osp$set_job_signature_lock (nlv$na_sap_list.lock);

{ Ignore if sap entry not found. The sap may have been closed in the meantime.

        find_sap_attributes (connection^.sap_id, sap_attributes);
        IF sap_attributes <> NIL THEN
          sap_attributes^.sap_device_list [connection^.device_id].status := nlc$na_sap_closed;
        IFEND;
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);

        nlp$cl_deactivate_layer (nlc$osi_network_access_agent, cl_connection);
        IF event.disconnect.reason = nlc$cc_dr_normal_disconnect THEN
          data := event.disconnect.data;
          nlp$bm_get_message_length (data, data_length);
          IF data_length = #SIZE (nlt$na_disconnect_indication) THEN
            data_fragment [1].address := ^disconnect_indication;
            data_fragment [1].length := #SIZE (disconnect_indication);
            nlp$bm_flush_message (data_fragment, data, data_length, {ignore} local_status);
            IF disconnect_indication.header.length = data_length THEN

{ Log the disconnect reason code.

              IF (disconnect_indication.header.kind = nlc$na_close_sap_indication) THEN
                osp$set_status_abnormal (nac$status_id, nae$na_peer_disconnect, 'CLOSE SAP INDICATION',
                      local_status);
                osp$append_status_integer (osc$status_parameter_delimiter, disconnect_indication.reason, 10,
                      FALSE, local_status);
                nap$display_message (local_status);
              ELSEIF (disconnect_indication.header.kind = nlc$na_open_sap_reject) THEN
                osp$set_status_abnormal (nac$status_id, nae$na_peer_disconnect, 'OPEN SAP REJECT',
                      local_status);
                osp$append_status_integer (osc$status_parameter_delimiter, disconnect_indication.reason, 10,
                      FALSE, local_status);
                nap$display_message (local_status);
              ELSE { Unexpected PDU kind
                PUSH error_message;
                STRINGREP (error_message^, error_message_length, 'Unexpected PDU kind of ',
                      disconnect_indication.header.kind, ' on a NAA disconnect indication.');
                nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
              IFEND;
            ELSE { Length in the header does not match the PDU length
              PUSH error_message;
              STRINGREP (error_message^, error_message_length, 'The length of NAA disconnect ', 'PDU is ',
                    data_length, ' but length in the PDU header is ', disconnect_indication.header.length,
                    ' .');
              nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
            IFEND;
          ELSE { Unexpected PDU length
            PUSH error_message;
            STRINGREP (error_message^, error_message_length, 'Unexpected PDU length of ',
                  disconnect_indication.header.length, ' on a NAA disconnect indication.');
            nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
            nlp$bm_release_message (data);
          IFEND;
        ELSE { CC disconnect
          pmp$log ('NAA - CC disconnect', {ignore} local_status);
        IFEND;
      ELSE { Unexpected CC event
        PUSH error_message;
        STRINGREP (error_message^, error_message_length, 'Unexpected CC event kind ', event.kind,
              ' received by network access event processor.');
        nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
        disconnect_na_connection (cl_connection, nlc$na_dr_namve_error);
        update_sap_status (connection^.sap_id, connection^.device_id);
      CASEND;
    ELSE { Layer inactive

{ Send disconnect with NAM/VE error.

      PUSH error_message;
      STRINGREP (error_message^, error_message_length, 'Detected an inactive NAA layer ',
            'connection on receipt of a CC event kind of ', event.kind, '.');
      nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
      disconnect_na_connection (cl_connection, nlc$na_dr_namve_error);
    IFEND;

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

  PROCEDURE [XDCL] nlp$na_initialize;

    VAR
      null_connect_event_processor: nlt$cl_event_processor,
      sap_processor: nlt$cl_event_processor;

    null_connect_event_processor.layer := nlc$osi_network_access_agent;
    sap_processor.layer := nlc$osi_network_access_agent;

    nlp$cl_initialize_template (nlc$osi_network_access_agent, nlc$osi_network_access_agent,
          #SIZE (nlt$na_layer_connection), 0, sap_processor, nlc$na_retry_constrained_saps,
          null_connect_event_processor, nac$nil);
    nlp$cc_initialize_template (nlc$osi_network_access_agent);

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

  PROCEDURE [XDCL] nlp$na_open_sap
    (    priority: nlt$na_priority;
         event_processor: nat$network_procedure;
         sap_id: nat$network_selector;
     VAR status: ost$status);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      device_and_data_list: array [1 .. 1] of nlt$cc_device_and_data_record,
      i: integer,
      ignore_layer_active: boolean,
      system_management_list: ^nlt$sm_device_list,
      open_sap_request: nlt$na_open_sap_request,
      previous_sap_attributes: ^^nlt$na_sap_attributes,
      sap_attributes: ^nlt$na_sap_attributes;

    status.normal := TRUE;

{ Setup the open sap request.

    data_fragment [1].address := ^open_sap_request;
    data_fragment [1].length := #SIZE (open_sap_request);
    open_sap_request.header.kind := nlc$na_open_sap_request;
    open_sap_request.header.length := data_fragment [1].length;
    open_sap_request.network_selector := sap_id;

    osp$set_job_signature_lock (nlv$na_sap_list.lock);

{ Verify that the specified sap is not already open. Note that the network selector used
{ by OSI transport layer is reserved.

    IF (NOT nlv$na_sap_list.open_saps [sap_id]) AND (sap_id <> nlv$transport_network_selector) THEN
      nlv$na_sap_list.open_saps [sap_id] := TRUE;

{ Setup sap_attributes entry.

      ALLOCATE sap_attributes: [1 .. UPPERBOUND (nlv$sm_devices.list^)] IN nav$network_paged_heap^;
      IF sap_attributes <> NIL THEN
        sap_attributes^.next_entry := NIL;
        sap_attributes^.sap_id := sap_id;
        sap_attributes^.priority := priority;
        sap_attributes^.event_processor := event_processor;
        sap_attributes^.connection_class := nlc$cc_normal_class;

{ Send open sap request to each configured network device.

        nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
        system_management_list := nlv$sm_devices.list;
        FOR i := 1 TO UPPERBOUND (system_management_list^) DO
          sap_attributes^.sap_device_list [i].device_id := system_management_list^ [i].device_id;
          sap_attributes^.sap_device_list [i].status := nlc$na_sap_closed;
          sap_attributes^.sap_device_list [i].connection_id := nac$null_connection_id;
          IF system_management_list^ [i].state >= nlc$sm_initialization_phase2 THEN
            nlp$cl_create_connection (nlc$osi_network_access_agent, cl_connection);
            IF cl_connection <> NIL THEN
              nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, ignore_layer_active,
                    connection);
              device_and_data_list [1].device_id := sap_attributes^.sap_device_list [i].device_id;
              nlp$bm_create_message (data_fragment, device_and_data_list [1].data, {ignore} status);
              nlp$cc_request_connection (cl_connection, device_and_data_list, nlc$network_access_address,
                    sap_attributes^.connection_class, status);
              IF status.normal THEN
                nlp$cl_activate_layer (nlc$osi_network_access_agent, cl_connection);
                connection^.state := nlc$na_await_connection_confirm;
                connection^.device_id := device_and_data_list [1].device_id;
                connection^.sap_id := sap_id;
                connection^.event_processor := event_processor;
                sap_attributes^.sap_device_list [i].connection_id := cl_connection^.identifier;
                sap_attributes^.sap_device_list [i].status := nlc$na_await_open_sap_confirm;
              ELSE
                IF status.condition = nae$resources_unavailable THEN
                  sap_attributes^.sap_device_list [i].status := nlc$na_resource_constraint;
                IFEND;
              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            ELSE

{ Mark the sap device entry as being in resource constraint, the timer task will periodically
{ try to open the sap across this device.

              sap_attributes^.sap_device_list [i].status := nlc$na_resource_constraint;
            IFEND;
          IFEND;
        FOREND;
        nlp$release_nonexclusive_access (nlv$sm_devices.access_control);

{ Link sap_attributes at the end of the sap list.

        previous_sap_attributes := ^nlv$na_sap_list.sap_attributes;
        WHILE previous_sap_attributes^ <> NIL DO
          previous_sap_attributes := ^previous_sap_attributes^^.next_entry;
        WHILEND;
        previous_sap_attributes^ := sap_attributes;
      ELSE { Insufficient resources
        osp$set_status_abnormal (nac$status_id, nae$allocation_failed, 'Open OSI Network Sap', status);
      IFEND;
    ELSE { Sap already open
      osp$set_status_condition (nae$na_sap_already_open, status);
      osp$append_status_integer (osc$status_parameter_delimiter, sap_id, 10, TRUE, status);
    IFEND;

    osp$clear_job_signature_lock (nlv$na_sap_list.lock);

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

  PROCEDURE [XDCL] nlp$na_open_saps
    (    device_id: nlt$device_identifier);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      device_and_data_list: array [1 .. 1] of nlt$cc_device_and_data_record,
      error_message: ^string (132),
      error_message_length: integer,
      ignore_layer_active: boolean,
      local_status: ost$status,
      open_sap_request: nlt$na_open_sap_request,
      sap_attributes: ^nlt$na_sap_attributes;

    data_fragment [1].address := ^open_sap_request;
    data_fragment [1].length := #SIZE (open_sap_request);
    open_sap_request.header.kind := nlc$na_open_sap_request;
    open_sap_request.header.length := data_fragment [1].length;
    device_and_data_list [1].device_id := device_id;
    osp$set_job_signature_lock (nlv$na_sap_list.lock);

    sap_attributes := nlv$na_sap_list.sap_attributes;
    WHILE sap_attributes <> NIL DO

{ The order of the sap device list is the same as the configured network devices
{ list. Hence, the device id can be used to index the sap device list directly.

      IF (sap_attributes^.sap_device_list [device_id].status = nlc$na_sap_closed) OR
            (sap_attributes^.sap_device_list [device_id].status = nlc$na_resource_constraint) THEN
        nlp$cl_create_connection (nlc$osi_network_access_agent, cl_connection);
        IF cl_connection <> NIL THEN
          nlp$cl_activate_layer (nlc$osi_network_access_agent, cl_connection);
          nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, ignore_layer_active,
                connection);
          open_sap_request.network_selector := sap_attributes^.sap_id;
          nlp$bm_create_message (data_fragment, device_and_data_list [1].data, {ignore} local_status);
          nlp$cc_request_connection (cl_connection, device_and_data_list, nlc$network_access_address,
                sap_attributes^.connection_class, local_status);
          IF local_status.normal THEN
            connection^.state := nlc$na_await_connection_confirm;
            connection^.device_id := device_id;
            connection^.sap_id := sap_attributes^.sap_id;
            connection^.event_processor := sap_attributes^.event_processor;
            sap_attributes^.sap_device_list [device_id].connection_id := cl_connection^.identifier;
            sap_attributes^.sap_device_list [device_id].status := nlc$na_await_open_sap_confirm;
          ELSE
            nlp$cl_deactivate_layer (nlc$osi_network_access_agent, cl_connection);
            IF local_status.condition = nae$resources_unavailable THEN
              sap_attributes^.sap_device_list [device_id].status := nlc$na_resource_constraint;
            IFEND;
          IFEND;
          nlp$cl_release_exclusive_access (cl_connection);
        ELSE { Max connections limit reached
          sap_attributes^.sap_device_list [device_id].status := nlc$na_resource_constraint;
        IFEND;
      ELSE { Sap is already open in the device
        PUSH error_message;
        STRINGREP (error_message^, error_message_length, ' NAM/VE: The network layer sap ',
              sap_attributes^.sap_id, ' is already open in the device ', device_id, ' .');
        pmp$log (error_message^ (1, error_message_length), local_status);
      IFEND;

      sap_attributes := sap_attributes^.next_entry;
    WHILEND;

    osp$clear_job_signature_lock (nlv$na_sap_list.lock);

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

  PROCEDURE [XDCL] nlp$na_retry_constrained_saps
    (    current_time: integer);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      device_and_data_list: array [1 .. 1] of nlt$cc_device_and_data_record,
      i: integer,
      ignore_layer_active: boolean,
      local_status: ost$status,
      open_sap_request: nlt$na_open_sap_request,
      sap_attributes: ^nlt$na_sap_attributes;

    data_fragment [1].address := ^open_sap_request;
    data_fragment [1].length := #SIZE (open_sap_request);
    open_sap_request.header.kind := nlc$na_open_sap_request;
    open_sap_request.header.length := data_fragment [1].length;
    osp$set_job_signature_lock (nlv$na_sap_list.lock);
    sap_attributes := nlv$na_sap_list.sap_attributes;
    WHILE sap_attributes <> NIL DO
      FOR i := 1 TO UPPERBOUND (sap_attributes^.sap_device_list) DO
        IF sap_attributes^.sap_device_list [i].status = nlc$na_resource_constraint THEN
          nlp$cl_create_connection (nlc$osi_network_access_agent, cl_connection);
          IF cl_connection <> NIL THEN
            nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, ignore_layer_active,
                  connection);
            open_sap_request.network_selector := sap_attributes^.sap_id;
            device_and_data_list [1].device_id := sap_attributes^.sap_device_list [i].device_id;
            nlp$bm_create_message (data_fragment, device_and_data_list [1].data, {ignore} local_status);
            nlp$cc_request_connection (cl_connection, device_and_data_list, nlc$network_access_address,
                  sap_attributes^.connection_class, local_status);
            IF local_status.normal THEN
              nlp$cl_activate_layer (nlc$osi_network_access_agent, cl_connection);
              connection^.state := nlc$na_await_connection_confirm;
              connection^.device_id := device_and_data_list [1].device_id;
              connection^.sap_id := sap_attributes^.sap_id;
              connection^.event_processor := sap_attributes^.event_processor;
              sap_attributes^.sap_device_list [i].connection_id := cl_connection^.identifier;
              sap_attributes^.sap_device_list [i].status := nlc$na_await_open_sap_confirm;
            ELSE
              IF local_status.condition <> nae$resources_unavailable THEN
                sap_attributes^.sap_device_list [i].status := nlc$na_sap_closed;
              IFEND;
            IFEND;
            nlp$cl_release_exclusive_access (cl_connection);
          IFEND;
        IFEND;
      FOREND;
      sap_attributes := sap_attributes^.next_entry;
    WHILEND;
    osp$clear_job_signature_lock (nlv$na_sap_list.lock);

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

  PROCEDURE [XDCL] nlp$na_send_data
    (    sap_id: nat$network_selector;
         device_id: nlt$device_identifier;
         destination: nat$osi_network_address;
         data: nat$data_fragments;
     VAR status: ost$status);

    VAR
      actual: integer,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$na_layer_connection,
      connection_exists: boolean,
      connection_id: nat$connection_id,
      data_fragments: ^nat$data_fragments,
      data_length: nat$data_length,
      data_request_fixed: nlt$na_data_request_fixed,
      i: integer,
      layer_active: boolean,
      sap_attributes: ^nlt$na_sap_attributes,
      user_data: nlt$bm_message_id;

    status.normal := TRUE;

{ Setup the data fragments. The first entry is for the fixed sized NAA header and
{ the second entry is for the variable sized destination address.

    PUSH data_fragments: [1 .. (UPPERBOUND (data) + 2)];
    data_fragments^ [1].address := ^data_request_fixed;
    data_fragments^ [1].length := #SIZE (data_request_fixed);
    data_fragments^ [2].address := ^destination;
    data_fragments^ [2].length := #SIZE (destination);

{ Move the remaining user data fragments.

    FOR i := 1 TO UPPERBOUND (data) DO
      data_fragments^ [i + 2] := data [i];
    FOREND;

    nlp$al_get_data_length (data_fragments^, data_length);
    data_request_fixed.header.kind := nlc$na_data_request;
    data_request_fixed.header.length := data_length;
    data_request_fixed.destination_address_length := #SIZE (destination);

{ Verify that the sap over which the data is being sent is open.

    osp$set_job_signature_lock (nlv$na_sap_list.lock);
    find_sap_attributes (sap_id, sap_attributes);
    IF sap_attributes <> NIL THEN

{ Find the channel connection to the device through which the destination
{ is accessible.

      IF sap_attributes^.sap_device_list [device_id].status = nlc$na_sap_open THEN
        connection_id := sap_attributes^.sap_device_list [device_id].connection_id;
        data_request_fixed.priority := sap_attributes^.priority;
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);
        nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
        IF connection_exists THEN
          nlp$cl_get_layer_connection (nlc$osi_network_access_agent, cl_connection, layer_active, connection);
          IF layer_active THEN
            nlp$bm_create_message (data_fragments^, user_data, {ignore} status);
            nlp$cc_send_data (cl_connection, user_data, status);

{! statistics begin
            osp$increment_locked_variable (nav$global_osi_statistics.network_access_agent.pdus_sent, 0,
                  actual);
            osp$add_to_locked_variable (nav$global_osi_statistics.network_access_agent.total_bytes_sent, 0,
                  data_length, actual);
{! statistics end

          IFEND;

          nlp$cl_release_exclusive_access (cl_connection);
        IFEND;
      ELSE
        osp$clear_job_signature_lock (nlv$na_sap_list.lock);
        osp$set_status_condition (nae$na_device_sap_not_open, status);
        osp$append_status_integer (osc$status_parameter_delimiter, sap_id, 10, TRUE, status);
      IFEND;
    ELSE
      osp$clear_job_signature_lock (nlv$na_sap_list.lock);
      osp$set_status_condition (nae$na_sap_not_open, status);
      osp$append_status_integer (osc$status_parameter_delimiter, sap_id, 10, TRUE, status);
    IFEND;

  PROCEND nlp$na_send_data;
?? OLDTITLE ??
?? NEWTITLE := 'disconnect_na_connection', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to format and send a disconnect
{   request to the peer entity. It also deactivates the layer connection.

  PROCEDURE [INLINE] disconnect_na_connection
    (    cl_connection { input, output } : ^nlt$cl_connection;
         disconnect_reason: nlt$na_disconnect_reason);

    VAR
      close_sap_request: nlt$na_close_sap_request,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      disconnect_data: nlt$bm_message_id,
      ignore_status: ost$status;

    close_sap_request.header.kind := nlc$na_close_sap_request;
    close_sap_request.header.length := #SIZE (close_sap_request);
    close_sap_request.reason := disconnect_reason;
    data_fragment [1].address := ^close_sap_request;
    data_fragment [1].length := #SIZE (close_sap_request);
    nlp$bm_create_message (data_fragment, disconnect_data, ignore_status);
    nlp$cc_disconnect (cl_connection, disconnect_data, ignore_status);
    nlp$cl_deactivate_layer (nlc$osi_network_access_agent, cl_connection);

  PROCEND disconnect_na_connection;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] find_sap_attributes', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to find the sap attributes
{   entry corresponding to the specified sap identifier.
{ NOTES:
{   The sap list must be locked by the caller.

  PROCEDURE [INLINE] find_sap_attributes
    (    sap_id: nat$network_selector;
     VAR sap_attributes: ^nlt$na_sap_attributes);

    sap_attributes := nlv$na_sap_list.sap_attributes;
    WHILE (sap_attributes <> NIL) AND (sap_attributes^.sap_id <> sap_id) DO
      sap_attributes := sap_attributes^.next_entry;
    WHILEND;

  PROCEND find_sap_attributes;
?? OLDTITLE ??
?? NEWTITLE := 'update_sap_status', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to set the status of the given sap for
{   the specified device to closed.

  PROCEDURE [INLINE] update_sap_status
    (    sap_id: nat$network_selector;
         device_id: nlt$device_identifier);

    VAR
      sap_attributes: ^nlt$na_sap_attributes;

    osp$set_job_signature_lock (nlv$na_sap_list.lock);
    find_sap_attributes (sap_id, sap_attributes);
    IF sap_attributes <> NIL THEN
      sap_attributes^.sap_device_list [device_id].status := nlc$na_sap_closed;
    IFEND;
    osp$clear_job_signature_lock (nlv$na_sap_list.lock);

  PROCEND update_sap_status;
?? OLDTITLE ??
MODEND nlm$network_access_agent;
