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

{ PURPOSE:
{   This module contains procedures neccesary to communicate with the System Management Access
{   Provider (SMAP) in the OSI communications device. These procedures provide the OSI System
{   Management Access Agent (SMAA) in the host.
{ DESIGN:
{   The SMAA and the SMAP communicate over a channel connection. After a device is loaded,
{   the SMAP initiates the channel connection connect request. The SMAA and the SMAP exchange
{   initialization information over the channel connection. After the initialization of the
{   SMAA/SMAP is complete, the SMAA will allow the users in the host to obtain routing
{   information and will also process DEFINE OSI SUBNET PDUs from the SMAP. Any protocol
{   error encountered will result in disconnecting the channel connection.
{   The XDCL'd procedures have been grouped in alphabetical order followed by the internal
{   procedures. The internal procedures are also in alphabetical order. The following Finite
{   State Machine describes the states and the associated events. Please refer to the System
{   Management Access Agent Protocol Specification (A7998) and the System Management Access
{   Provider Protocol Specification (A7999) for more information.
{   This module contains code that executes in ring 3. It resides on OSF$JOB_TEMPLATE_23D.
{ NOTES:
{   The CONFIGURED NETWORK DEVICE list is referenced by both the SMAA and the Channel Connection
{   Entity. The SMAA code has to ensure that this list is unlocked before calling the Channel
{   Connection Entity interfaces and vice versa. The following abreviations have been used in
{   this module.
{        PDU = Protocol Data Unit
{        ID  = Identifier

?? NEWTITLE := 'Finite State Machine', EJECT ??

{ ------------+------------+------------+------------+------------+------------+------------+
{ STATE --->  |            |   AWAIT    | AWAIT INT  | AWAIT DEV  |            |   AWAIT    |
{             |   CLOSED   |  VERSION   | ATTRIBUTE  | SPEC HOST  |   OPEN     |  SUBNET    |
{ CC EVENT |  |            |  CONFIRM   |  CONFIRM   |  ADDRESS   |            | DEFINITION |
{          V  |    (1)     |    (2)     |    (3)     |    (4)     |    (5)     |    (6)     |
{ ------------+------------+------------+------------+------------+------------+------------+
{   CONNECT   |            |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |
{  INDICATION | (1)--->(2) |  ERROR     |  ERROR     |  ERROR     |  ERROR     |  ERROR     |
{(DEF VERSION)|            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |            |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |
{  (VERSION   |   ERROR    | (2)--->(3) |  ERROR     |  ERROR     |  ERROR     |  ERROR     |
{   CONFIRM)  |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |            |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |
{ (INT ATTRIB |   ERROR    |  ERROR     | (3)--->(4) |  ERROR     |  ERROR     |  ERROR     |
{   CONFIRM)  |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |  PROTOCOL  |            |  PROTOCOL  |  PROTOCOL  |
{  (DEFINE DEV|   ERROR    |  ERROR     |  ERROR     | (4)--->(5) |  ERROR     |  ERROR     |
{   SPEC ADDR |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |            |            |
{  (DEFINE    |   ERROR    |  ERROR     |  ERROR     |  ERROR     | (5)--->(6) | (6)--->(6) |
{   SUBNETS)  |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |            |
{  (END SUBNET|   ERROR    |  ERROR     |  ERROR     |  ERROR     |  ERROR     | (6)--->(5) |
{   DEFN)     |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |            |            |
{  (DESTN ACC |   ERROR    |  ERROR     |  ERROR     |  ERROR     | (5)--->(5) | (6)--->(6) |
{   RESPONSE) |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DATA EVENT|   SYSTEM   |  PROTOCOL  |  PROTOCOL  |  PROTOCOL  |            |            |
{  (DEFINE DEV|   ERROR    |  ERROR     |  ERROR     |  ERROR     | (5)--->(5) | (6)--->(6) |
{   SERV ATTR)|            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+
{   DISCONNECT|   SYSTEM   |            |            |            |            |            |
{   EVENT     |   ERROR    | (2)--->(1) | (3)--->(1) | (4)--->(1) | (5)--->(1) | (6)--->(1) |
{             |            |            |            |            |            |            |
{-------------+------------+------------+------------+------------+------------+------------+

?? OLDTITLE ??
?? NEWTITLE := 'User Interface', EJECT ??

{ ------------+------------+------------+------------+------------+------------+------------+
{ STATE --->  |            |   AWAIT    | AWAIT INT  | AWAIT DEV  |            |   AWAIT    |
{             |   CLOSED   |  VERSION   | ATTRIBUTE  | SPEC HOST  |   OPEN     |  SUBNET    |
{ USER REQ |  |            |  CONFIRM   |  CONFIRM   |  ADDRESS   |            | DEFINITION |
{          V  |    (1)     |    (2)     |    (3)     |    (4)     |    (5)     |   (6)      |
{ ------------+------------+------------+------------+------------+------------+------------+
{  DESTN ACC  |            |            |            |            |            |            |
{  REQUEST    |   REJECT   |   REJECT   |   REJECT   |   REJECT   | (5)--->(5) | (6)--->(6) |
{             |            |            |            |            |            |            |
{ ------------+------------+------------+------------+------------+------------+------------+

?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc nac$null_connection_id
*copyc nae$application_interfaces
*copyc nae$system_mgmt_access_agent
*copyc nat$connection_id
*copyc nat$network_procedure
*copyc nat$osi_network_address
*copyc nat$ta_alternate_protocol_class
*copyc nat$ta_preferred_protocol_class
*copyc nlc$smaa_versions
*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$network_device
*copyc nlt$network_device_list
*copyc nlt$sm_await_routing_query
*copyc nlt$sm_device_attributes_list
*copyc nlt$sm_device_information
*copyc nlt$sm_device_service_attribute
*copyc nlt$sm_device_service_values
*copyc nlt$sm_device_version_list
*copyc nlt$sm_layer_connection
*copyc nlt$sm_protocol_data_unit
*copyc nlt$sm_route_status
*copyc nlt$subnet_attributes
*copyc ost$signature_lock_status
*copyc ost$status
?? POP ??
*copyc nap$condition_handler_trace
*copyc nap$display_message
*copyc nap$namve_system_error
*copyc nap$system_id
*copyc nlp$bm_create_message
*copyc nlp$bm_flush_message
*copyc nlp$bm_get_message_length
*copyc nlp$bm_release_message
*copyc nlp$cc_accept_connection
*copyc nlp$cc_disconnect
*copyc nlp$cc_initialize_template
*copyc nlp$cc_send_data
*copyc nlp$cl_activate_layer
*copyc nlp$cl_activate_receiver
*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$la_open_saps
*copyc nlp$na_disconnect_connections
*copyc nlp$na_open_saps
*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$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc osp$increment_locked_variable
*copyc osp$set_job_signature_lock
*copyc osp$set_status_condition
*copyc osp$set_status_abnormal
*copyc pmp$get_compact_date_time
*copyc pmp$get_executing_task_gtid
{*copyc pmp$log
*copyc pmp$ready_task
*copyc pmp$wait
*copyc syp$cycle
*copyc nav$global_osi_statistics
*copyc nav$host_subnet_id
*copyc nav$network_paged_heap
*copyc nlv$configured_network_devices
*copyc nlv$sm_await_routing_queries
*copyc nlv$sm_devices
*copyc nlv$transport_network_selector
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by This Module', EJECT ??

  TYPE
    sm_protocol_class_set = SET OF nlt$sm_device_service_values,
    sm_device_selection_status = (sm_route_known, sm_route_unknown, sm_subnet_unknown);

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

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

    VAR
      connection: ^nlt$sm_layer_connection,
      data: nlt$bm_message_id,
      data_fragments: array [1 .. 2] of nat$data_fragment,
      error_message_length: integer,
      error_message: ^string (132),
      ignore_layer_active: boolean,
      ignore_status: ost$status,
      length: integer,
      sm_pdu_header: nlt$sm_pdu_header,
      smap_version: nlt$sm_version,
      system_management: ^nlt$system_management,
      version_response: nlt$sm_version_response;

    inventory_report := 0;
    nlp$cl_activate_layer (nlc$osi_sys_mgmt_access_agent, cl_connection);
    nlp$cl_get_layer_connection (nlc$osi_sys_mgmt_access_agent, cl_connection, ignore_layer_active,
          connection);

{ Validate incoming event and PDU. Note that the connect event is received in the connection
{ establishment task.

    IF event.kind = nlc$cc_connect_event THEN
      nlp$get_exclusive_access (nlv$sm_devices.access_control);
      system_management := ^nlv$sm_devices.list^ [event.connect.device_id];
      data := event.connect.data;
      IF system_management^.state = nlc$sm_uninitialized THEN
        nlp$bm_get_message_length (event.connect.data, length);
        IF length = (#SIZE (nlt$sm_pdu_header) + #SIZE (nlt$sm_version)) THEN
          data_fragments [1].address := ^sm_pdu_header;
          data_fragments [1].length := #SIZE (sm_pdu_header);
          data_fragments [2].address := ^smap_version;
          data_fragments [2].length := #SIZE (smap_version);
          nlp$bm_flush_message (data_fragments, data, length, ignore_status);
          IF length = sm_pdu_header.length THEN
            IF sm_pdu_header.kind = nlc$sm_define_version THEN

{ Save connection attributes.

              system_management^.connection_id := cl_connection^.identifier;
              system_management^.state := nlc$sm_initialization_phase1;

{ Send a connect response PDU with the common version.

              version_response.header.length := #SIZE (version_response);
              version_response.header.kind := nlc$sm_version_response;
              IF (smap_version <= nlc$sm_version_1) THEN
                version_response.common_version := nlc$sm_version_1;
              ELSE
                version_response.common_version := nlc$sm_version_2;
              IFEND;
              system_management^.device_version := version_response.common_version;
              system_management^.supported_protocol_class := nlc$sm_tp4_clns;
              nlp$release_exclusive_access (nlv$sm_devices.access_control);
              data_fragments [1].address := ^version_response;
              data_fragments [1].length := version_response.header.length;
              data_fragments [2].address := NIL;
              data_fragments [2].length := 0;
              nlp$bm_create_message (data_fragments, data, ignore_status);
              nlp$cc_accept_connection (cl_connection, event.connect.class, data, ignore_status);
              connection^.state := nlc$sm_await_version_confirm;
              connection^.device_id := event.connect.device_id;

{ Activate receiver so that all system mgmt events are received in the current task.

              nlp$cl_activate_receiver (cl_connection);
            ELSE { System Management event other than DEFINE_VERSION
              nlp$release_exclusive_access (nlv$sm_devices.access_control);
              disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
            IFEND;
          ELSE { Length in the header does not match the pdu length
            nlp$release_exclusive_access (nlv$sm_devices.access_control);
            disconnect_sm_connection (cl_connection, nlc$sm_dr_length_mismatch);
          IFEND;
        ELSE { Unexpected PDU length
          nlp$release_exclusive_access (nlv$sm_devices.access_control);
          disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
          nlp$bm_release_message (data);
        IFEND;
      ELSE { System Management connection already exists
        nlp$release_exclusive_access (nlv$sm_devices.access_control);
        disconnect_sm_connection (cl_connection, nlc$sm_dr_dup_connect_event);
        nlp$bm_release_message (data);
      IFEND;
    ELSE { Unexpected CC event
      PUSH error_message;
      STRINGREP (error_message^, error_message_length,
          'Invalid CC event received by SME. Expecting a CC connect event but received event ', event.kind);
      nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
      disconnect_sm_connection (cl_connection, nlc$sm_dr_namve_error);
    IFEND;

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

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

    VAR
      actual: integer,
      address_ok: boolean,
      await_routing_query: ^nlt$sm_await_routing_query,
      confirm_dev_spec_host_address: nlt$sm_confrm_dev_spec_host_add,
      connection: ^nlt$sm_layer_connection,
      data: nlt$bm_message_id,
      data_fragments: array [1 .. 2] of nat$data_fragment,
      define_interface_attributes: nlt$sm_define_interface_attrib,
      dest_acc_fixed_response: ^nlt$sm_dest_acc_fixed_response,
      device_attribute_code: ^ 0 .. 0FF(16),
      device_attribute_length: ^ 0 .. 0FF(16),
      device_attribute_value: ^nlt$sm_device_service_attribute,
      disconnect_reason: nlt$sm_disconnect_reason,
      disconnect_reason_code: nlt$sm_disconnect_reason,
      duplicate_subnet: boolean,
      element: cmt$element_name,
      error_message_length: integer,
      error_message: ^string (132),
      generic_host_address: ^nat$osi_network_address,
      i: integer,
      ignore_status: ost$status,
      layer_active: boolean,
      length: integer,
      network_address_length: ^nat$osi_network_address_length,
      network_address_prefix: ^nat$osi_network_address_prefix,
      network_selector: ^nat$network_selector,
      old_subnet_list: ^nlt$subnet_attributes,
      pdu_ok: boolean,
      sm_data: ^SEQ ( * ),
      sm_pdu_header: nlt$sm_pdu_header,
      status: ost$status,
      subnet_attributes_list: ^nlt$subnet_attributes,
      subnet_count: nat$subnet_identifier,
      subnet_id: ^nat$subnet_identifier,
      subnet_list: ^array [1 .. * ] of nat$subnet_identifier,
      system_id: ^nat$system_identifier,
      system_management: ^nlt$system_management,
      system_management_list: ^nlt$sm_device_list,
      unused_byte_count: integer,
      unused_space: ^array [1 .. *] of 0 .. 0ff(16);

    inventory_report := 0;
    nlp$cl_get_layer_connection (nlc$osi_sys_mgmt_access_agent, cl_connection, layer_active, connection);
    IF layer_active THEN
      data_fragments [1].address := ^sm_pdu_header;
      data_fragments [1].length := #SIZE (sm_pdu_header);
      CASE event.kind OF
      = nlc$cc_data_event =
        data := event.data.data;
        nlp$bm_get_message_length (data, length);
        IF length >= #SIZE (nlt$sm_pdu_header) THEN
          IF length > #SIZE (nlt$sm_pdu_header) THEN
            PUSH sm_data: [[REP (length - #SIZE (nlt$sm_pdu_header)) OF cell]];
            RESET sm_data;
            data_fragments [2].address := sm_data;
            data_fragments [2].length := #SIZE (sm_data^);
          ELSE
            data_fragments [2].address := NIL;
            data_fragments [2].length := 0;
          IFEND;
          nlp$bm_flush_message (data_fragments, data, length, {ignore} status);
          IF sm_pdu_header.length = length THEN

{ Process data events.

            CASE sm_pdu_header.kind OF
?? NEWTITLE := 'nlc$cc_data_event' ??
?? NEWTITLE := 'nlc$sm_version_confirm', EJECT ??
            = nlc$sm_version_confirm =
              IF connection^.state = nlc$sm_await_version_confirm THEN
                IF length = #SIZE (nlt$sm_pdu_header) THEN

{ Send the Define_interface_attributes PDU to the SMAP.

                  define_interface_attributes.header.kind := nlc$sm_define_interface_attrib;
                  define_interface_attributes.header.length := #SIZE (define_interface_attributes);
                  define_interface_attributes.transport_network_selector := nlv$transport_network_selector;
                  define_interface_attributes.subnet_id := nav$host_subnet_id;
                  define_interface_attributes.system_id := nap$system_id ();
                  pmp$get_compact_date_time (define_interface_attributes.date_and_time, {ignore} status);
                  IF define_interface_attributes.date_and_time.year > 99 THEN
                     define_interface_attributes.date_and_time.year :=
                        define_interface_attributes.date_and_time.year - 100;
                  IFEND;
                  data_fragments [1].address := ^define_interface_attributes;
                  data_fragments [1].length := define_interface_attributes.header.length;
                  data_fragments [2].address := NIL;
                  data_fragments [2].length := 0;
                  nlp$bm_create_message (data_fragments, data, {ignore} status);
                  nlp$cc_send_data (cl_connection, data, {ignore} status);
                  connection^.state := nlc$sm_await_int_attrib_confirm;
                ELSE { Unexpected length
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_await_routing_query (connection^.device_id);
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_confirm_interface_attrib', EJECT ??
            = nlc$sm_confirm_interface_attrib =
              IF connection^.state = nlc$sm_await_int_attrib_confirm THEN
                IF (length >= (#SIZE (nlt$sm_confirm_int_attrib_fixed) + nac$osi_minimum_prefix_length)) AND
                      (length <= (#SIZE (nlt$sm_confirm_int_attrib_fixed) + nac$osi_maximum_prefix_length))
                      THEN
                  NEXT network_address_length IN sm_data;
                  IF (network_address_length^ > 0) AND (network_address_length^ <=
                        nac$osi_max_network_address_len) THEN
                    NEXT network_address_prefix: [(length - #SIZE (nlt$sm_confirm_int_attrib_fixed))] IN
                          sm_data;
                    connection^.state := nlc$sm_await_dev_spec_host_addr;
                    nlp$get_exclusive_access (nlv$sm_devices.access_control);
                    system_management := ^nlv$sm_devices.list^ [connection^.device_id];
                    system_management^.state := nlc$sm_initialization_phase2;
                    system_management^.network_address_length := network_address_length^;
                    generic_host_address := ^system_management^.generic_host_address;
                    RESET generic_host_address;
                    NEXT system_management^.network_address_prefix: [#SIZE (network_address_prefix^)] IN
                          generic_host_address;
                    system_management^.network_address_prefix^ := network_address_prefix^;
                    unused_byte_count := network_address_length^ - #SIZE (network_address_prefix^) -
                          #SIZE (nat$subnet_identifier) - #SIZE (nat$system_identifier) -
                          #SIZE (nat$network_selector);
                    IF unused_byte_count > 0 THEN
                      NEXT unused_space: [1 .. unused_byte_count] IN generic_host_address;
                      FOR i := 1 TO unused_byte_count DO
                        unused_space^ [i] := 0;
                      FOREND;
                    IFEND;
                    NEXT subnet_id IN generic_host_address;
                    subnet_id^ := nav$host_subnet_id;
                    NEXT system_id IN generic_host_address;
                    system_id^ := nav$system_id;
                    NEXT network_selector IN generic_host_address;
                    network_selector^ := nlv$transport_network_selector;
                    nlp$release_exclusive_access (nlv$sm_devices.access_control);

{ Open network layer saps in the device.

                    nlp$na_open_saps (connection^.device_id);
                  ELSE { Unexpected network address length
                    delete_sm_connection (connection^.device_id);
                    disconnect_sm_connection (cl_connection, nlc$sm_dr_incorrect_addr_length);
                  IFEND;
                ELSE { Unexpected length
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_await_routing_query (connection^.device_id);
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_define_dev_spec_host_add', EJECT ??
            = nlc$sm_define_dev_spec_host_add =
              IF connection^.state = nlc$sm_await_dev_spec_host_addr THEN
                IF length > #SIZE (nlt$sm_pdu_header) THEN
                  define_device_spec_host_address (sm_data^, connection^.device_id, address_ok);
                  IF address_ok THEN

{ Send a Confirm_device_specific_host_address PDU to the SMAP.

                    confirm_dev_spec_host_address.header.kind := nlc$sm_confrm_dev_spec_host_add;
                    confirm_dev_spec_host_address.header.length := #SIZE (confirm_dev_spec_host_address);
                    data_fragments [1].address := ^confirm_dev_spec_host_address;
                    data_fragments [1].length := confirm_dev_spec_host_address.header.length;
                    data_fragments [2].address := NIL;
                    data_fragments [2].length := 0;
                    nlp$bm_create_message (data_fragments, data, {ignore} status);
                    nlp$cc_send_data (cl_connection, data, {ignore} status);
                    connection^.state := nlc$sm_open;
                    nlp$get_exclusive_access (nlv$sm_devices.access_control);
                    nlv$sm_devices.list^ [connection^.device_id].state := nlc$sm_initialized;
                    nlp$release_exclusive_access (nlv$sm_devices.access_control);
                  ELSE { Incorrect device specific host address
                    osp$set_status_abnormal (nac$status_id, nae$sm_dshnet_error,
                          nlv$configured_network_devices.network_device_list^ [connection^.device_id].element,
                          status);
                    nap$display_message (status);
                    delete_sm_connection (connection^.device_id);
                    disconnect_sm_connection (cl_connection, nlc$sm_dr_incorrect_dev_address);
                  IFEND;
                ELSE { Unexpected length
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_await_routing_query (connection^.device_id);
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_define_subnets', EJECT ??
            = nlc$sm_define_subnets =
              IF (connection^.state = nlc$sm_open) OR (connection^.state = nlc$sm_await_subnet_definition)
                    THEN
                IF length > #SIZE (nlt$sm_pdu_header) THEN
                  extract_subnet_attributes (sm_data, subnet_attributes_list, pdu_ok);
                  IF pdu_ok THEN
                    add_subnet_attributes (connection^.device_id, subnet_attributes_list, duplicate_subnet);
                    IF NOT duplicate_subnet THEN
                      connection^.state := nlc$sm_await_subnet_definition;
                    ELSE

{ Send disconnect with reason code of duplicate subnet definition.

                      delete_await_routing_query (connection^.device_id);
                      delete_sm_connection (connection^.device_id);
                      disconnect_sm_connection (cl_connection, nlc$sm_dr_dup_subnet);
                    IFEND;
                  ELSE { Ill formed PDU
                    delete_await_routing_query (connection^.device_id);
                    delete_sm_connection (connection^.device_id);
                    disconnect_sm_connection (cl_connection, nlc$sm_dr_ill_formed_pdu);
                  IFEND;
                ELSE { Unexpected length
                  delete_await_routing_query (connection^.device_id);
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_await_routing_query (connection^.device_id);
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_end_subnet_definition', EJECT ??
            = nlc$sm_end_subnet_definition =
              IF connection^.state = nlc$sm_await_subnet_definition THEN
                IF length = #SIZE (nlt$sm_pdu_header) THEN

{ Switch the subnet lists.

                  nlp$get_exclusive_access (nlv$sm_devices.access_control);
                  system_management_list := nlv$sm_devices.list;
                  old_subnet_list := system_management_list^ [connection^.device_id].subnet_list;
                  system_management_list^ [connection^.device_id].subnet_list :=
                        system_management_list^ [connection^.device_id].new_subnet_list;
                  system_management_list^ [connection^.device_id].new_subnet_list := NIL;

{ If the device is an ICA-II notify Link Access (LA) of the directly connected subnet connected
{ to this device.
{     NOTE: This code assumes that only one directly connected subnet will be defined.

                  subnet_count := 0;
                  IF nlv$configured_network_devices.network_device_list^ [connection^.device_id].
                         kind = nac$ica_2 THEN
                    subnet_attributes_list := system_management_list^ [connection^.device_id].
                          subnet_list;
                    PUSH subnet_list: [1 .. 1];
                    /loop/
                    WHILE (subnet_attributes_list <> NIL) AND (subnet_count = 0) DO
                      IF subnet_attributes_list^.directly_connected THEN
                        subnet_count := 1;
                        subnet_list^ [1] := subnet_attributes_list^.subnet_id;
                        EXIT /loop/;
                      IFEND;
                      subnet_attributes_list := subnet_attributes_list^.next_entry;
                    WHILEND /loop/;
                  IFEND;
                  nlp$release_exclusive_access (nlv$sm_devices.access_control);
                  IF subnet_count > 0 THEN
                    nlp$la_open_saps (connection^.device_id, subnet_count, subnet_list);
                  IFEND;
                  free_subnet_list (old_subnet_list);
                  connection^.state := nlc$sm_open;

{! statistics begin
                  osp$increment_locked_variable (nav$global_osi_statistics.system_management_entity.
                        subnet_attribute_updates_rcvd, 0, actual);
{! statistics end

                ELSE { Unexpected length
                  delete_await_routing_query (connection^.device_id);
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_await_routing_query (connection^.device_id);
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_dest_accessible_response', EJECT ??
            = nlc$sm_dest_accessible_response =
              IF (connection^.state = nlc$sm_open) OR
                    (connection^.state = nlc$sm_await_subnet_definition) THEN
                IF length >= (#SIZE (nlt$sm_pdu_header) + #SIZE (nlt$sm_dest_acc_fixed_response)) THEN
                  NEXT dest_acc_fixed_response IN sm_data;
                  osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);

{ Find the await_routing_query for the given dest_accessible_response.request_id.
{ If the matching await_routing_query entry is not found, ignore the PDU as the
{ task awaiting the response could have timed out.

                  await_routing_query := nlv$sm_await_routing_queries.await_routing_query;
                  WHILE (await_routing_query <> NIL) AND (await_routing_query^.request_id <>
                        dest_acc_fixed_response^.request_id) DO
                    await_routing_query := await_routing_query^.next_entry;
                  WHILEND;

                  IF await_routing_query <> NIL THEN

                  /search/
                    FOR i := 1 TO UPPERBOUND (await_routing_query^.device_information_list) DO
                      IF await_routing_query^.device_information_list [i].device_id =
                            connection^.device_id THEN
                        IF NOT await_routing_query^.device_information_list [i].response_received THEN
                          await_routing_query^.response_count := await_routing_query^.response_count
                                + 1;
                          await_routing_query^.device_information_list [i].response_received := TRUE;
                          await_routing_query^.device_information_list [i].route_status :=
                                dest_acc_fixed_response^.route_status;

{ This code will have to be changed whenever the QOS attributes associated with a
{ route is defined. It will have to save the QOS attribute with each route and the
{ nlp$sm_select_device will have to analyze the QOS attribute.

                        ELSE
                          PUSH error_message;
                          STRINGREP (error_message^, error_message_length,
                                'Duplicate destination accessible response received from network device id ',
                                connection^.device_id);
                          nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
                        IFEND;
                        EXIT /search/;
                      IFEND;
                    FOREND /search/;

{ Ready the task if all responses have been received.

                    IF (await_routing_query^.response_count = await_routing_query^.query_count) OR
                      ((await_routing_query^.ready_on_route_known_response) AND
                       (dest_acc_fixed_response^.route_status = nlc$sm_route_known)) THEN
                      pmp$ready_task (await_routing_query^.task_id, {ignore} status);
                    IFEND;
                  IFEND;
                  osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
                ELSE { Unexpected length
                  delete_await_routing_query (connection^.device_id);
                  delete_sm_connection (connection^.device_id);
                  disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                IFEND;
              ELSE { Unexpected event
                delete_sm_connection (connection^.device_id);
                disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_event);
              IFEND;

?? OLDTITLE ??
?? NEWTITLE := 'nlc$sm_device_service_attribute', EJECT ??
            = nlc$sm_device_service_attribute =
              IF (connection^.state=nlc$sm_open) OR (connection^.state=nlc$sm_await_subnet_definition) THEN
                IF length > #SIZE (nlt$sm_pdu_header) THEN
                  nlp$get_exclusive_access (nlv$sm_devices.access_control);
                  system_management := ^nlv$sm_devices.list^ [connection^.device_id];
                  IF system_management^.device_version >= nlc$sm_version_2 THEN
                    NEXT device_attribute_code in sm_data;
                    IF device_attribute_code <> NIL THEN
                      CASE device_attribute_code^ OF
                        = nlc$sm_transport_service_attrib =
                          NEXT device_attribute_length in sm_data;
                          IF device_attribute_length <> NIL THEN
                            IF device_attribute_length^ = #SIZE (nlt$sm_device_service_attribute) THEN
                              NEXT device_attribute_value in sm_data;
                              system_management^.supported_protocol_class := device_attribute_value^;
                              nlp$release_exclusive_access (nlv$sm_devices.access_control);
                            ELSE { Invalid length sent by SMAP
                              nlp$release_exclusive_access (nlv$sm_devices.access_control);
                              delete_sm_connection (connection^.device_id);
                              disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_length);
                            IFEND;
                          ELSE { Nil pointer
                            nlp$release_exclusive_access (nlv$sm_devices.access_control);
                            delete_sm_connection (connection^.device_id);
                            disconnect_sm_connection (cl_connection, nlc$sm_dr_unknown_pdu_kind);
                          IFEND;
                        ELSE { Unknown define device attributes code
                          nlp$release_exclusive_access (nlv$sm_devices.access_control);
                          delete_sm_connection (connection^.device_id);
                          disconnect_sm_connection (cl_connection, nlc$sm_dr_invalid_service_attr);
                      CASEND;
                    ELSE { Nil pointer
                      nlp$release_exclusive_access (nlv$sm_devices.access_control);
                      delete_sm_connection (connection^.device_id);
                      disconnect_sm_connection (cl_connection, nlc$sm_dr_unknown_pdu_kind);
                    IFEND;
                  ELSE  { Invalid PDU kind sent by SMAP
                    nlp$release_exclusive_access (nlv$sm_devices.access_control);
                    delete_sm_connection (connection^.device_id);
                    disconnect_sm_connection (cl_connection, nlc$sm_dr_unknown_pdu_kind);
                  IFEND;
                IFEND;
              IFEND;

            ELSE { Unknown system management PDU kind
              delete_await_routing_query (connection^.device_id);
              delete_sm_connection (connection^.device_id);
              disconnect_sm_connection (cl_connection, nlc$sm_dr_unknown_pdu_kind);
            CASEND;
          ELSE { Length in the header does not match PDU length
            delete_await_routing_query (connection^.device_id);
            delete_sm_connection (connection^.device_id);
            disconnect_sm_connection (cl_connection, nlc$sm_dr_length_mismatch);
          IFEND;
        ELSE { PDU is too small, even the header is not present
          nlp$bm_release_message (data);
          delete_await_routing_query (connection^.device_id);
          delete_sm_connection (connection^.device_id);
          disconnect_sm_connection (cl_connection, nlc$sm_dr_pdu_too_small);
        IFEND;

?? OLDTITLE ??
?? OLDTITLE ??
?? NEWTITLE := 'nlc$cc_disconnect_event', EJECT ??
      = nlc$cc_disconnect_event =
        nlp$cl_deactivate_layer (nlc$osi_sys_mgmt_access_agent, cl_connection);
        delete_await_routing_query (connection^.device_id);
        delete_sm_connection (connection^.device_id);
        IF event.disconnect.reason = nlc$cc_dr_normal_disconnect THEN
          nlp$bm_get_message_length (event.disconnect.data, length);
          IF length = (#SIZE (nlt$sm_pdu_header) + #SIZE (nlt$sm_disconnect_reason)) THEN
            data_fragments [2].address := ^disconnect_reason;
            data_fragments [2].length := #SIZE (disconnect_reason);
            data := event.disconnect.data;
            nlp$bm_flush_message (data_fragments, data, length, {ignore} status);
            IF sm_pdu_header.kind = nlc$sm_disconnect_indication THEN
              IF sm_pdu_header.length = length THEN

{ Log the disconnect reason code.

                osp$set_status_condition ( nae$sm_peer_disconnect,  status);
                osp$append_status_integer (osc$status_parameter_delimiter, disconnect_reason, 16, FALSE,
                      status);
                nap$display_message (status);

{ If disconnect is due to duplicate host network address, then display the message in the job log.

                IF disconnect_reason = nlc$sm_dr_dup_host_address THEN
                  element := nlv$configured_network_devices.network_device_list^ [connection^.
                    device_id].element;

                  osp$set_status_abnormal (nac$status_id, nae$sm_dup_host_address_reject, element,
                    status);
                  nap$display_message (status);
                IFEND;
              ELSE { Length in the header does not match the PDU length
                PUSH error_message;
                STRINGREP (error_message^, error_message_length,
                      'The length of SME disconnect PDU is ', length, ' but length in the PDU header is ',
                      sm_pdu_header.length);
                nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
              IFEND;
            ELSE { Unexpected PDU kind
              PUSH error_message;
              STRINGREP (error_message^, error_message_length, 'Unexpected PDU kind of ',
                    sm_pdu_header.kind, ' on a SME disconnect indication.');
              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 ',
                  length, ' on a SME disconnect event.');
            nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
            data := event.disconnect.data;
            nlp$bm_release_message (data);
          IFEND;
        ELSE { CC disconnect
{         pmp$log ('SME - CC disconnect', status);
        IFEND;
      ELSE { Unexpected CC event
        PUSH error_message;
        STRINGREP (error_message^, error_message_length, 'Unexpected CC event kind ',
             event.kind, ' received by system management event processor.');
        nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
        delete_await_routing_query (connection^.device_id);
        delete_sm_connection (connection^.device_id);
        disconnect_sm_connection (cl_connection, nlc$sm_dr_unexpected_cc_event);
      CASEND;
    ELSE { Layer inactive

{ Send disconnect with NAM/VE error.

      PUSH error_message;
      STRINGREP (error_message^, error_message_length,
           'Detected an inactive SME layer connection on receipt of a CC event kind of ', event.kind);
      nap$namve_system_error (TRUE, error_message^ (1, error_message_length), NIL);
      delete_await_routing_query (connection^.device_id);
      delete_sm_connection (connection^.device_id);
      disconnect_sm_connection (cl_connection, nlc$sm_dr_namve_error);
    IFEND;

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

  PROCEDURE [XDCL] nlp$sm_initialize;

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

    null_connect_event_processor.layer := nlc$osi_sys_mgmt_access_agent;
    null_sap_event_processor.layer := nlc$osi_sys_mgmt_access_agent;

    nlp$cl_initialize_template (nlc$osi_sys_mgmt_access_agent, nlc$osi_sys_mgmt_access_agent,
          #SIZE (nlt$sm_layer_connection), 0, null_sap_event_processor, nac$nil, null_connect_event_processor,
          nac$nil);
    nlp$cc_initialize_template (nlc$osi_sys_mgmt_access_agent);

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

  PROCEDURE [XDCL] nlp$sm_select_device
    (    destination_address: nat$osi_network_address;
         cdna_address: boolean;
         preferred_protocol_class: nat$ta_preferred_protocol_class;
     VAR device_list: nlt$device_list;
     VAR version_list: nlt$sm_device_version_list;
     VAR count: nlt$device_count;
     VAR status: ost$status);

    VAR
      actual: integer,
      connection_list: ^array [1 .. *] of nlt$cl_connection_id,
      device_selection_status: sm_device_selection_status,
      ignore_status: ost$status,
      min_required_version: nlt$sm_version,
      network_address: ^nat$osi_network_address,
      required_protocol_class: sm_protocol_class_set,
      selected_version: nlt$sm_version,
      statistic: ^integer,
      subnet_id: ^nat$subnet_identifier,
      system_id: ^nat$system_identifier,
      till_subnet_id: ^SEQ ( * );

    status.normal := TRUE;
    count := 0;

    IF preferred_protocol_class = nac$ta_preferred_class_0 THEN
      min_required_version := nlc$sm_version_2;
      required_protocol_class := $sm_protocol_class_set[nlc$sm_tp4_clns_tp0, nlc$sm_tp4_clns_tp0_tp2,
            nlc$sm_tp4_clns_cons_tp0_tp2];
    ELSEIF  preferred_protocol_class = nac$ta_preferred_class_4_clns THEN
      min_required_version := nlc$sm_version_1;
      required_protocol_class := -$sm_protocol_class_set[];
    ELSE  {  A protocol class that SMAA doesn't support was requested.  Return count=0.
      RETURN;
    IFEND;

    PUSH connection_list: [1 .. UPPERBOUND (device_list)];

    IF cdna_address THEN

{ Extract the subnet id from the destination address.

      network_address := ^destination_address;
      RESET network_address;
      NEXT till_subnet_id: [[REP (#SIZE (destination_address) - #SIZE (nat$subnet_identifier) -
            #SIZE (nat$system_identifier) - #SIZE (nat$network_selector)) OF cell]] IN network_address;
      NEXT subnet_id IN network_address;
      NEXT system_id IN network_address;
      nlp$get_nonexclusive_access (nlv$sm_devices.access_control);

{ Find the list of devices that support the prefix in the destination address and that support the
{ preferred_protocol_class.  If only one device is found, it will be selected without further checks.

      match_prefix (destination_address, required_protocol_class, min_required_version, device_list,
            version_list, connection_list^, count);
      IF count > 1 THEN

{ Search the local cache of OSI subnet attributes.

        select_device (subnet_id^, system_id^, device_list, version_list, count, device_selection_status);
        nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
        CASE device_selection_status OF
        = sm_route_known =
          status.normal := TRUE;

        = sm_route_unknown =
          osp$set_status_condition ( nae$sm_route_unknown,  status);

        = sm_subnet_unknown =
          poll_devices_for_routing_info (destination_address, connection_list^, device_list, version_list,
                count, status);

        ELSE
        CASEND;

{! statistics begin
        IF status.normal THEN
          statistic := ^nav$global_osi_statistics.system_management_entity.cdna_address_select_device_reqs;
        ELSEIF status.condition = nae$sm_route_unknown THEN
          statistic := ^nav$global_osi_statistics.system_management_entity.cdna_address_route_unknown;
        IFEND;
        osp$increment_locked_variable (statistic^, 0, actual);
{! statistics end

      ELSE { count = 0 or count = 1
        nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
        IF count = 0 THEN
          osp$set_status_condition ( nae$sm_route_unknown,  status);

{! statistics begin
          osp$increment_locked_variable (nav$global_osi_statistics.system_management_entity.
                cdna_address_route_unknown, 0, actual);
{! statistics end
        IFEND;


      IFEND;
    ELSE { Non CDNA address
      get_sm_connections_and_devices (required_protocol_class, min_required_version, connection_list^,
            device_list, version_list, count);

{ NOTE: A count of 1 implies that there is only one device configured (or available)
{       and so there is no need to poll. The only configured device is returned as
{       the selected device.

      IF count > 1 THEN
        poll_devices_for_routing_info (destination_address, connection_list^, device_list, version_list,
              count, status);
        IF status.normal THEN
          statistic := ^nav$global_osi_statistics.system_management_entity.noncdna_addr_select_device_reqs;
        ELSE
          statistic := ^nav$global_osi_statistics.system_management_entity.noncdna_address_route_unknown;
        IFEND;
        osp$increment_locked_variable (statistic^, 0, actual);
      ELSEIF count = 0 THEN
        osp$set_status_condition ( nae$sm_no_device_configured,  status);
      ELSEIF count = 1 THEN

{! statistics begin
        osp$increment_locked_variable (nav$global_osi_statistics.system_management_entity.
              noncdna_addr_select_device_reqs, 0, actual);
{! statistics end

      IFEND;
    IFEND;

  PROCEND nlp$sm_select_device;
?? OLDTITLE ??
?? NEWTITLE := 'add_subnet_attributes', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to add the given subnet attributes
{   to the network device entry for the specified device. It also verifies
{   that the subnet attributes being added are for unique subnets. In case
{   of an error, the subnet attributes list is freed. The new list being built
{   is not used for device selection. The current list in use is replaced
{   by this new list after the end subnet PDU is received.

  PROCEDURE add_subnet_attributes
    (    device_id: nlt$device_identifier;
     VAR subnet_attributes_list {input, output} : ^nlt$subnet_attributes;
     VAR duplicate_subnet: boolean);

    VAR
      new_subnet_attributes: ^nlt$subnet_attributes,
      previous_subnet_attributes: ^^nlt$subnet_attributes,
      remaining_subnet_attributes: ^nlt$subnet_attributes,
      system_management: ^nlt$system_management;

    duplicate_subnet := FALSE;
  /verify_subnet/
    BEGIN

{ Verify that the new subnet list contains unique subnets.

    new_subnet_attributes := subnet_attributes_list;
    WHILE new_subnet_attributes^.next_entry <> NIL DO
      remaining_subnet_attributes := new_subnet_attributes^.next_entry;
      REPEAT
        IF remaining_subnet_attributes^.subnet_id = new_subnet_attributes^.subnet_id THEN
          duplicate_subnet := TRUE;
          EXIT /verify_subnet/;
        IFEND;
        remaining_subnet_attributes := remaining_subnet_attributes^.next_entry;
      UNTIL remaining_subnet_attributes = NIL;
      new_subnet_attributes := new_subnet_attributes^.next_entry;
    WHILEND;

    nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
    system_management := ^nlv$sm_devices.list^ [device_id];
    previous_subnet_attributes := ^system_management^.new_subnet_list;

    WHILE previous_subnet_attributes^ <> NIL DO

{ Verify that the new subnets being added are unique with respect to all new subnets
{ added on previous define subnet requests for the current update.

      new_subnet_attributes := subnet_attributes_list;
      WHILE new_subnet_attributes <> NIL DO
        IF new_subnet_attributes^.subnet_id = previous_subnet_attributes^^.subnet_id THEN
          duplicate_subnet := TRUE;
          nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
          EXIT /verify_subnet/;
        IFEND;
        new_subnet_attributes := new_subnet_attributes^.next_entry;
      WHILEND;
      previous_subnet_attributes := ^previous_subnet_attributes^^.next_entry;
    WHILEND;

{ Add the subnet attributes.

    previous_subnet_attributes^ := subnet_attributes_list;
    nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
    END /verify_subnet/;

    IF duplicate_subnet THEN
      free_subnet_list (subnet_attributes_list);
    IFEND;

  PROCEND add_subnet_attributes;
?? OLDTITLE ??
?? NEWTITLE := 'await_query_responses', EJECT ??
?? NEWTITLE := '    release_routing_query', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to wait for the response to the routing
{   queries sent to the devices. This procedure will wait for two intervals.
{   During the first wait interval it waits for all the devices to respond.
{   Out of all the 'route known' responses received it selects the device
{   with the minimum active connection count. However, if no 'route known'
{   response is received in the first wait interval, then this procedure will
{   wait for a longer second interval. In the second wait interval it waits
{   for the first 'route known' response.

  PROCEDURE await_query_responses
    (    executing_taskid: ost$global_task_id;
     VAR device_list: nlt$device_list;
     VAR version_list: nlt$sm_device_version_list;
     VAR count: nlt$device_count;
     VAR status: ost$status);

    VAR
      await_routing_query: ^nlt$sm_await_routing_query,
      current_time: integer,
      expiration_time: integer,
      i: integer,
      j: integer,
      previous_await_routing_query: ^^nlt$sm_await_routing_query,
      remaining_time: integer,
      route_known: boolean,
      route_known_count: integer;

?? NEWTITLE := '    release_routing_query', EJECT ??

    PROCEDURE release_routing_query (
           ignore_condition: pmt$condition;
           ignore_condition_descriptor: ^pmt$condition_information;
           ignore_sa: ^ost$stack_frame_save_area;
       VAR condition_status: ost$status);

     VAR
       routing_query: ^nlt$sm_await_routing_query,
       previous_routing_query: ^^nlt$sm_await_routing_query;

      nap$condition_handler_trace (ignore_condition, ignore_sa);
      condition_status.normal := TRUE;
      osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
      previous_routing_query := ^nlv$sm_await_routing_queries.await_routing_query;
      WHILE (previous_routing_query^ <> NIL) AND (previous_routing_query^^.task_id <>
           executing_taskid) DO
        previous_routing_query := ^previous_routing_query^^.next_entry;
      WHILEND;
      routing_query := previous_routing_query^;
      IF routing_query <> NIL THEN
        previous_routing_query^ := routing_query^.next_entry;
        osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
        FREE routing_query IN nav$network_paged_heap^;
      IFEND;

   PROCEND release_routing_query;
?? OLDTITLE, EJECT ??
    status.normal := TRUE;
    current_time := #free_running_clock (0) DIV 1000;
    remaining_time := 500*count;
    expiration_time := current_time + remaining_time;
    osp$establish_block_exit_hndlr (^release_routing_query);

   /wait_loop/
    FOR i := 1 TO 2 DO
    WHILE remaining_time > 0 DO
      pmp$wait (remaining_time, 0);

{ Find the await routing query entry.

      count := 0;
      osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
      previous_await_routing_query := ^nlv$sm_await_routing_queries.await_routing_query;
      WHILE (previous_await_routing_query^ <> NIL) AND (previous_await_routing_query^^.task_id <>
           executing_taskid) DO
        previous_await_routing_query := ^previous_await_routing_query^^.next_entry;
      WHILEND;
      await_routing_query := previous_await_routing_query^;

{ Check the responses to the routing queries.

      IF await_routing_query^.query_count > 0 THEN
        route_known := FALSE;
        route_known_count := 0;

        FOR j := 1 TO UPPERBOUND (await_routing_query^.device_information_list) DO
          IF await_routing_query^.device_information_list [j].route_status = nlc$sm_route_known THEN
            route_known := TRUE;
            route_known_count := route_known_count + 1;
            device_list [route_known_count] := await_routing_query^.device_information_list [j].device_id;
            version_list [route_known_count] := await_routing_query^.device_information_list [j]
                  .device_version;

{ It is the second wait interval. Select the first device that knowns the route.

            IF i = 2 THEN
              previous_await_routing_query^ := await_routing_query^.next_entry;
              osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
              FREE await_routing_query IN nav$network_paged_heap^;
              count := 1;
              EXIT /wait_loop/;
            IFEND;
          ELSEIF (NOT route_known) AND (await_routing_query^.device_information_list [j].route_status =
              nlc$sm_route_indeterminate) THEN
            count := count + 1;
          device_list [count] := await_routing_query^.device_information_list [j].device_id;
          version_list [count] := await_routing_query^.device_information_list [j]
                .device_version;
        IFEND;
      FOREND;

      IF await_routing_query^.response_count = await_routing_query^.query_count THEN

{ All devices have responded.

        previous_await_routing_query^ := await_routing_query^.next_entry;
        osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
        FREE await_routing_query IN nav$network_paged_heap^;
        IF route_known THEN
          select_device_with_min_load (route_known_count, device_list, version_list);
          count := 1;
        ELSEIF count = 0 THEN
          osp$set_status_condition ( nae$sm_route_unknown,  status);
        IFEND;
        EXIT /wait_loop/;
      ELSE { Await remaining responses
        current_time := #free_running_clock (0) DIV 1000;
        IF current_time >= expiration_time THEN
          IF i = 1 THEN

{ If the first wait interval has expired, select the device from the subset that
{ have responded with 'route known'.

            IF route_known THEN
              previous_await_routing_query^ := await_routing_query^.next_entry;
              osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
              FREE await_routing_query IN nav$network_paged_heap^;
              select_device_with_min_load (route_known_count, device_list, version_list);
              count := 1;
              EXIT /wait_loop/;
            IFEND;

{ If no 'route known' response has been received then wait for a longer interval for
{ the first 'route known' response.

            await_routing_query^.ready_on_route_known_response := TRUE;
            osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
            current_time := #free_running_clock (0) DIV 1000;

            remaining_time := 2*500*(await_routing_query^.query_count -
              await_routing_query^.response_count);
            expiration_time := current_time + remaining_time;
            CYCLE /wait_loop/;
          ELSE { Second wait interval has expired
            previous_await_routing_query^ := await_routing_query^.next_entry;
            osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
            FREE await_routing_query IN nav$network_paged_heap^;
            IF count = 0 THEN
              osp$set_status_condition ( nae$sm_route_unknown,  status);
            IFEND;
            EXIT /wait_loop/;
          IFEND;
        ELSE { Wait for the remaining time
          osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
          remaining_time := expiration_time - current_time;
        IFEND;
      IFEND;

      ELSE { query count = 0; devices went down
        previous_await_routing_query^ := await_routing_query^.next_entry;
        osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
        FREE await_routing_query IN nav$network_paged_heap^;
        osp$set_status_condition ( nae$sm_route_unknown,  status);
        EXIT /wait_loop/;
      IFEND;
    WHILEND;
    FOREND /wait_loop/;
    osp$disestablish_cond_handler;

  PROCEND await_query_responses;
?? OLDTITLE ??
?? NEWTITLE := 'define_device_spec_host_address', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to verify that the given device
{   specific host address is valid and is unique across all configured
{   network devices. If the address is valid and unique, it is saved
{   in the network device entry for the given device.

  PROCEDURE define_device_spec_host_address
    (    device_specific_host_address: nat$osi_network_address;
         device_id: nlt$device_identifier;
     VAR address_ok: boolean);

    VAR
      configured_device_id: nlt$device_identifier,
      current_device_address: ^nat$osi_network_address,
      current_subnet_id: ^nat$subnet_identifier,
      current_system_id: ^nat$system_identifier,
      device_address: ^nat$osi_network_address,
      prefix: ^nat$osi_network_address_prefix,
      save_device_spec_host_address: ^nat$osi_network_address,
      subnet_id: ^nat$subnet_identifier,
      system_id: ^nat$system_identifier,
      system_management_list: ^nlt$sm_device_list,
      till_subnet_id: ^string ( * ),
      unused_byte_count: 0 .. 0ff(16),
      unused_bytes: ^string ( * );

    nlp$get_exclusive_access (nlv$sm_devices.access_control);
    system_management_list := nlv$sm_devices.list;

    address_ok := #SIZE (device_specific_host_address) = system_management_list^ [device_id].
          network_address_length;
    IF address_ok THEN
      device_address := ^device_specific_host_address;
      RESET device_address;
      NEXT prefix: [#SIZE (system_management_list^ [device_id].network_address_prefix^)] IN
            device_address;
      address_ok := prefix^ = system_management_list^ [device_id].network_address_prefix^;
      IF address_ok THEN
        unused_byte_count := #SIZE (device_address^) - #SIZE (prefix^) - #SIZE (nat$subnet_identifier) -
              #SIZE (nat$system_identifier) - #SIZE (nat$network_selector);
        IF unused_byte_count > 0 THEN
          NEXT unused_bytes: [unused_byte_count] IN device_address;
        IFEND;
        NEXT subnet_id IN device_address;
        NEXT system_id IN device_address;

{ If more than one device is configured, verify that the device specific host address is not the
{ same as the generic host address.

        address_ok := NOT((UPPERBOUND (system_management_list^) > 1) AND
          (subnet_id^ = nav$host_subnet_id) AND
          (system_id^ = nap$system_id ()));
        IF address_ok THEN
      /verify_and_define/
        BEGIN
          FOR configured_device_id := LOWERBOUND (system_management_list^)
                TO UPPERBOUND (system_management_list^) DO
            IF configured_device_id <> device_id THEN
              IF (system_management_list^ [configured_device_id].state =
                    nlc$sm_initialized) AND (system_management_list^ [configured_device_id].
                    network_address_length = #SIZE (device_specific_host_address)) THEN
                current_device_address := ^system_management_list^ [configured_device_id].
                      device_specific_host_address;
                RESET current_device_address;
                NEXT till_subnet_id: [system_management_list^ [configured_device_id].network_address_length -
                      #SIZE (nat$subnet_identifier) - #SIZE (nat$system_identifier) -
                      #SIZE (nat$network_selector)] IN current_device_address;
                NEXT current_subnet_id IN current_device_address;
                NEXT current_system_id IN current_device_address;
                IF (subnet_id^ = current_subnet_id^) AND (system_id^ = current_system_id^) THEN
                  address_ok := FALSE;
                  EXIT /verify_and_define/;
                IFEND;
              IFEND;
            IFEND;
          FOREND;

          device_address := ^system_management_list^ [device_id].device_specific_host_address;
          RESET device_address;
          NEXT save_device_spec_host_address: [[REP #SIZE (device_specific_host_address) OF cell]] IN
                device_address;
          save_device_spec_host_address^ := device_specific_host_address;
        END /verify_and_define/;
      IFEND;
      IFEND;
    IFEND;

    nlp$release_exclusive_access (nlv$sm_devices.access_control);

  PROCEND define_device_spec_host_address;
?? OLDTITLE ??
?? NEWTITLE := 'delete_await_routing_query', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to delete the await routing query for
{   the specified device from the await routing queries list.
{ DESIGN:
{   The await routing queries list is locked and searched for entries for
{   the specified device. If a response has been received from the device,
{   the response is reset to route not known. If the device has not responded
{   yet, the device identifier is cleared from the entry and the query count
{   is decremented.

  PROCEDURE delete_await_routing_query
    (    device_id: iot$logical_unit);

    VAR
      await_routing_query: ^nlt$sm_await_routing_query,
      device_information_list: ^nlt$sm_device_information_list,
      i: integer,
      ignore_status: ost$status;

    osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
    await_routing_query := nlv$sm_await_routing_queries.await_routing_query;
    WHILE await_routing_query <> NIL DO
      device_information_list := ^await_routing_query^.device_information_list;

    /search/
      FOR i := LOWERBOUND (device_information_list^) TO UPPERBOUND (device_information_list^) DO
        IF device_information_list^ [i].device_id = device_id THEN
          device_information_list^ [i].device_id := nlc$null_device_identifier;
          IF device_information_list^ [i].response_received THEN
            device_information_list^ [i].route_status := nlc$sm_route_unknown;
          ELSE { Response has not been received
            await_routing_query^.query_count := await_routing_query^.query_count - 1;
            IF await_routing_query^.query_count = await_routing_query^.response_count THEN
              pmp$ready_task (await_routing_query^.task_id, ignore_status);
            IFEND;
          IFEND;
          EXIT /search/;
        IFEND;
      FOREND /search/;
      await_routing_query := await_routing_query^.next_entry;
    WHILEND;

    osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);

  PROCEND delete_await_routing_query;
?? OLDTITLE ??
?? NEWTITLE := 'delete_sm_connection', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to delete the system management connection id
{   from the network device attributes entry for the given device. This procedure
{   also frees the allocated structures for the given device. All the network access
{   connections are also disconnected.

  PROCEDURE delete_sm_connection
    (    device_id: nlt$device_identifier);

    VAR
      system_management: ^nlt$system_management;

    nlp$get_exclusive_access (nlv$sm_devices.access_control);
    system_management := ^nlv$sm_devices.list^ [device_id];
    system_management^.state := nlc$sm_uninitialized;
    system_management^.connection_id := nac$null_connection_id;
    system_management^.network_address_length := 0;
    system_management^.network_address_prefix := NIL;

    free_subnet_list (system_management^.subnet_list);

    IF system_management^.new_subnet_list <> NIL THEN
      free_subnet_list (system_management^.new_subnet_list);
    IFEND;
    nlp$release_exclusive_access (nlv$sm_devices.access_control);
    nlp$na_disconnect_connections (device_id);

  PROCEND delete_sm_connection;
?? OLDTITLE ??
?? NEWTITLE := 'disconnect_sm_connection', EJECT ??

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

  PROCEDURE disconnect_sm_connection
    (    cl_connection { input, output } : ^nlt$cl_connection;
         disconnect_reason: nlt$sm_disconnect_reason);

    VAR
      data_fragments: array [1 .. 1] of nat$data_fragment,
      disconnect_data: nlt$bm_message_id,
      disconnect_pdu: nlt$sm_disconnect_request,
      ignore_status: ost$status;

    disconnect_pdu.header.kind := nlc$sm_disconnect_request;
    disconnect_pdu.header.length := #SIZE (nlt$sm_disconnect_request);
    disconnect_pdu.reason := disconnect_reason;
    data_fragments [1].address := ^disconnect_pdu;
    data_fragments [1].length := disconnect_pdu.header.length;
    nlp$bm_create_message (data_fragments, disconnect_data, ignore_status);
    nlp$cc_disconnect (cl_connection, disconnect_data, ignore_status);
    nlp$cl_deactivate_layer (nlc$osi_sys_mgmt_access_agent, cl_connection);

  PROCEND disconnect_sm_connection;
?? OLDTITLE ??
?? NEWTITLE := 'extract_subnet_attributes', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to extract the OSI subnet attributes
{   from the raw data and return a list of OSI subnet attributes to the
{   caller.

  PROCEDURE extract_subnet_attributes
    (VAR raw_data {input} : ^SEQ ( * );
     VAR subnet_attributes_list: ^nlt$subnet_attributes;
     VAR pdu_ok: boolean);

    VAR
      calculated_size: integer,
      fixed_subnet_attributes: ^nlt$sm_fixed_subnet_attributes,
      previous_subnet_attributes: ^^nlt$subnet_attributes,
      quality_of_service: ^nlt$quality_of_service,
      raw_multicast_address: ^SEQ ( * ),
      subnet_attributes: ^nlt$subnet_attributes;

    pdu_ok := TRUE;
    calculated_size := 0;
    subnet_attributes_list := NIL;
    previous_subnet_attributes := ^subnet_attributes_list;

  /extract/
    BEGIN

      REPEAT
        NEXT fixed_subnet_attributes IN raw_data;
        IF fixed_subnet_attributes <> NIL THEN
          REPEAT
            ALLOCATE subnet_attributes IN nav$network_paged_heap^;
            IF subnet_attributes = NIL THEN
              syp$cycle;
            IFEND;
          UNTIL subnet_attributes <> NIL;
          subnet_attributes^.next_entry := NIL;
          subnet_attributes^.subnet_id := fixed_subnet_attributes^.subnet_id;
          subnet_attributes^.multicast_address := NIL;
          subnet_attributes^.quality_of_service := NIL;
          subnet_attributes^.directly_connected := fixed_subnet_attributes^.directly_connected;
          IF subnet_attributes^.directly_connected THEN
            subnet_attributes^.subnet_status := fixed_subnet_attributes^.subnet_status;
            subnet_attributes^.subnet_type := fixed_subnet_attributes^.subnet_type;
            subnet_attributes^.max_link_sdu_size := fixed_subnet_attributes^.max_link_sdu_size;
          IFEND;

          calculated_size := calculated_size + #SIZE (fixed_subnet_attributes^);
          previous_subnet_attributes^ := subnet_attributes;
          previous_subnet_attributes := ^subnet_attributes^.next_entry;

{ Extract the multicast address if present.

          IF fixed_subnet_attributes^.multicast_address_length > 0 THEN
            NEXT raw_multicast_address: [[REP fixed_subnet_attributes^.multicast_address_length OF cell]] IN
                  raw_data;
            IF raw_multicast_address = NIL THEN
              pdu_ok := FALSE;
              EXIT /extract/;
            IFEND;
            REPEAT
              ALLOCATE subnet_attributes^.multicast_address: [[REP fixed_subnet_attributes^.
                    multicast_address_length OF cell]] IN nav$network_paged_heap^;
              IF subnet_attributes^.multicast_address = NIL THEN
                syp$cycle;
              IFEND;
            UNTIL subnet_attributes^.multicast_address <> NIL;
            subnet_attributes^.multicast_address^ := raw_multicast_address^;
            calculated_size := calculated_size + fixed_subnet_attributes^.multicast_address_length;
          IFEND;

{ Extract and save the quality of service record if present.

          IF fixed_subnet_attributes^.quality_of_service_size > 0 THEN
            NEXT quality_of_service: [[REP fixed_subnet_attributes^.quality_of_service_size OF cell]] IN
                  raw_data;
            IF quality_of_service = NIL THEN
              pdu_ok := FALSE;
              EXIT /extract/;
            IFEND;
            REPEAT
              ALLOCATE subnet_attributes^.quality_of_service: [[REP fixed_subnet_attributes^.
                    quality_of_service_size OF cell]] IN nav$network_paged_heap^;
              IF subnet_attributes^.quality_of_service = NIL THEN
                syp$cycle;
              IFEND;
            UNTIL subnet_attributes^.quality_of_service <> NIL;
            subnet_attributes^.quality_of_service^ := quality_of_service^;
            calculated_size := calculated_size + fixed_subnet_attributes^.quality_of_service_size;
          IFEND;
        IFEND;
      UNTIL fixed_subnet_attributes = NIL;

      pdu_ok := (calculated_size = #SIZE (raw_data^)) AND (subnet_attributes_list <> NIL);
    END /extract/;

    IF NOT pdu_ok THEN
      free_subnet_list (subnet_attributes_list);
    IFEND;

  PROCEND extract_subnet_attributes;
?? OLDTITLE ??
?? NEWTITLE := 'free_subnet_list', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to free the given subnet
{   attributes list.
{ NOTES:
{   If the subnet attributes list is linked off of the network
{   device list, it is assumed that the network device list has
{   been locked by the caller.

  PROCEDURE free_subnet_list
    (VAR subnet_attributes_list: ^nlt$subnet_attributes);

    VAR
      subnet_attributes: ^nlt$subnet_attributes;

    WHILE subnet_attributes_list <> NIL DO
      IF subnet_attributes_list^.multicast_address <> NIL THEN
        FREE subnet_attributes_list^.multicast_address IN nav$network_paged_heap^;
      IFEND;
      IF subnet_attributes_list^.quality_of_service <> NIL THEN
        FREE subnet_attributes_list^.quality_of_service IN nav$network_paged_heap^;
      IFEND;
      subnet_attributes := subnet_attributes_list;
      subnet_attributes_list := subnet_attributes_list^.next_entry;
      FREE subnet_attributes IN nav$network_paged_heap^;
    WHILEND;

  PROCEND free_subnet_list;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] get_sm_connections_and_devices', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the System Management connection
{   and device identifiers of all the configured network devices.
{ DESIGN:
{   This procedure searches the configured network device list with a non
{   exclusive lock.

  PROCEDURE [INLINE] get_sm_connections_and_devices
    (    required_protocol_class: sm_protocol_class_set,
         min_required_version: nlt$sm_version;
     VAR sm_connection_ids: array [1 .. * ] of nlt$cl_connection_id;
     VAR device_list: nlt$device_list;
     VAR version_list: nlt$sm_device_version_list;
     VAR count: nlt$device_count);

    VAR
      device: nlt$device_identifier,
      system_management_list: ^nlt$sm_device_list;

    nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
    system_management_list := nlv$sm_devices.list;
    count := 0;

    FOR device := LOWERBOUND (system_management_list^) TO UPPERBOUND (system_management_list^) DO
      IF (system_management_list^ [device].state = nlc$sm_initialized) AND
         (system_management_list^ [device].device_version >= min_required_version) AND
         (system_management_list^ [device].supported_protocol_class IN required_protocol_class) THEN
        count := count + 1;
        sm_connection_ids [count] := system_management_list^ [device].connection_id;
        device_list [count] := device;
        version_list [count] := system_management_list^ [device].device_version;
      IFEND;
    FOREND;

    nlp$release_nonexclusive_access (nlv$sm_devices.access_control);

  PROCEND get_sm_connections_and_devices;
?? OLDTITLE ??
?? NEWTITLE := 'match_prefix', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the list of devices that support
{   the prefix and the preferred protocol class in the given OSI network address.
{ DESIGN:
{   The prefix in the give OSI network address is extracted and matched with
{   prefix configured in each device. A list of the identifiers of the devices
{   with matching prefixes and which support the preferred protocol class is
{   returned to the caller. However, if a match is not found, the prefix is
{   compared with the default prefix. If it matches the default prefix, then the
{   identifiers of all the devices is returned.  If no match is found, the
{   destination is not reachable.
{ NOTES:
{   The network device list must be locked by the caller for non exclusive
{   access.

  PROCEDURE match_prefix
    (    network_address: nat$osi_network_address;
         required_protocol_class: sm_protocol_class_set;
         min_required_version: nlt$sm_version;
     VAR device_list: nlt$device_list;
     VAR version_list: nlt$sm_device_version_list;
     VAR connection_list: array [1 .. *] of nlt$cl_connection_id;
     VAR count: nlt$device_count);

    VAR
      device_id: nlt$device_identifier,
      ignore_status: ost$status,
      network_address_seq: ^nat$osi_network_address,
      next_device: nlt$device_count,
      prefix: ^nat$osi_network_address_prefix,
      system_management_list: ^nlt$sm_device_list;

    next_device := LOWERBOUND (device_list);
    count := 0;
    network_address_seq := ^network_address;
    system_management_list := nlv$sm_devices.list;

    FOR device_id := LOWERBOUND (system_management_list^) TO UPPERBOUND (system_management_list^) DO
      IF (system_management_list^ [device_id].state = nlc$sm_initialized) AND
            (system_management_list^ [device_id].network_address_length =
            #SIZE (network_address)) THEN
        RESET network_address_seq;
        NEXT prefix: [#SIZE (system_management_list^ [device_id].network_address_prefix^)] IN
              network_address_seq;
        IF (prefix^ = system_management_list^ [device_id].network_address_prefix^) AND
           (system_management_list^ [device_id].device_version >= min_required_version) AND
           (system_management_list^ [device_id].supported_protocol_class IN required_protocol_class) THEN
          device_list [next_device] := device_id;
          version_list [next_device] := system_management_list^ [device_id].device_version;
          connection_list [next_device] := system_management_list^ [device_id].connection_id;
          count := count + 1;
          next_device := next_device + 1;
        IFEND;
      IFEND;
    FOREND;

    IF count = 0 THEN

{ Compare the prefix against the default prefix. If it matches the default prefix
{ return the device identifiers of all the available devices.

    IFEND;

  PROCEND match_prefix;
?? OLDTITLE ??
?? OLDTITLE ??
?? NEWTITLE := 'poll_devices_for_routing_info', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to send the routing queries to all the
{   configured devices and to await the responses from the devices.

  PROCEDURE poll_devices_for_routing_info
    (    destination_address: nat$osi_network_address;
         connection_list: array [1 .. * ] of nlt$cl_connection_id;
     VAR device_list { input, output } : nlt$device_list;
     VAR version_list { input, output } : nlt$sm_device_version_list;
     VAR count { input, output } : nlt$device_count;
     VAR status: ost$status);

    VAR
      actual: integer,
      await_routing_query: ^nlt$sm_await_routing_query,
      cl_connection: ^nlt$cl_connection,
      connection: ^nlt$sm_layer_connection,
      connection_exists: boolean,
      data: nlt$bm_message_id,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      dest_accessible_request_pdu: ^nlt$sm_dest_accessible_request,
      destination_address_contents: ^nat$osi_network_address,
      destination_address_seq: ^SEQ ( * ),
      executing_taskid: ost$global_task_id,
      i: integer,
      layer_active: boolean,
      previous_await_routing_query: ^^nlt$sm_await_routing_query,
      query_count: nlt$device_count,
      query_sent: boolean;

    status.normal := TRUE;

    pmp$get_executing_task_gtid (executing_taskid);
    PUSH dest_accessible_request_pdu: [[REP #SIZE (destination_address) OF
          cell]];
    dest_accessible_request_pdu^.header.kind := nlc$sm_dest_accessible_request;
    dest_accessible_request_pdu^.header.length :=
          #SIZE (dest_accessible_request_pdu^);
    dest_accessible_request_pdu^.destination_address := destination_address;
    dest_accessible_request_pdu^.request_id := executing_taskid.index;
    data_fragment [1].address := dest_accessible_request_pdu;
    data_fragment [1].length := dest_accessible_request_pdu^.header.length;

{ Poll the devices for routing information.

    ALLOCATE await_routing_query: [1 .. count] IN nav$network_paged_heap^;
    IF await_routing_query <> NIL THEN

{ Initialize await routing query.

      await_routing_query^.request_id := executing_taskid.index;
      await_routing_query^.task_id := executing_taskid;
      await_routing_query^.next_entry := NIL;
      await_routing_query^.ready_on_route_known_response := FALSE;
      await_routing_query^.response_count := 0;

{ **** DEBUG BEGIN.
{ The following lines of code must be deleted after JIT.
{ The destination_address does not have to be saved in the await_routing_query.

      destination_address_seq := ^await_routing_query^.destination_address;
      RESET destination_address_seq;
      NEXT destination_address_contents: [[REP #SIZE (destination_address) OF
            cell]] IN destination_address_seq;
      destination_address_contents^ := destination_address;

{ **** DEBUG END.

      await_routing_query^.query_count := count;
      FOR i := 1 TO count DO
        await_routing_query^.device_information_list [i].device_id := device_list [i];
        await_routing_query^.device_information_list [i].device_version := version_list [i];
        await_routing_query^.device_information_list [i].route_status := nlc$sm_route_unknown;
        await_routing_query^.device_information_list [i].response_received := FALSE;
      FOREND;

{ Add the entry to the end of the await routing queries list.

      osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
      previous_await_routing_query := ^nlv$sm_await_routing_queries.
            await_routing_query;
      WHILE previous_await_routing_query^ <> NIL DO
        previous_await_routing_query := ^previous_await_routing_query^^.
              next_entry;
      WHILEND;
      previous_await_routing_query^ := await_routing_query;
      osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);

{ Send the routing queries.

      query_count := 0;
      FOR i := 1 TO count DO
        query_sent := FALSE;
        nlp$cl_get_exclusive_via_cid (connection_list [i], connection_exists,
              cl_connection);
        IF connection_exists THEN
          nlp$cl_get_layer_connection (nlc$osi_sys_mgmt_access_agent,
                cl_connection, layer_active, connection);
          IF (layer_active) AND (connection^.state >= nlc$sm_open) THEN
            query_sent := TRUE;
            query_count := query_count + 1;
            nlp$bm_create_message (data_fragment, data, {ignore} status);
            nlp$cc_send_data (cl_connection, data, {ignore} status);
          IFEND;
          nlp$cl_release_exclusive_access (cl_connection);
        IFEND;

        IF NOT query_sent THEN
          osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
          await_routing_query := nlv$sm_await_routing_queries.
                await_routing_query;
          WHILE await_routing_query^.task_id <> executing_taskid DO
            await_routing_query := await_routing_query^.next_entry;
          WHILEND;

{ Update the device id in device information list. Note that the device could
{ have gone down in the mean time.

          IF await_routing_query^.device_information_list [i].device_id <>
                0 THEN
            await_routing_query^.device_information_list [i].device_id :=
                  nlc$null_device_identifier;
            await_routing_query^.query_count :=
                  await_routing_query^.query_count - 1;
          IFEND;
          osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
        IFEND;
      FOREND;

      IF query_count > 0 THEN

{! statistics begin
        osp$add_to_locked_variable (nav$global_osi_statistics.system_management_entity.
              device_routing_queries, 0, query_count, actual);
{! statistics end

        await_query_responses (executing_taskid, device_list,
          version_list, count, status);
      ELSE { Devices are inaccessible, remove the routing query from the list
        osp$set_job_signature_lock (nlv$sm_await_routing_queries.lock);
        previous_await_routing_query := ^nlv$sm_await_routing_queries.
              await_routing_query;
        WHILE (previous_await_routing_query^ <> NIL) AND
              (previous_await_routing_query^^.task_id <> executing_taskid) DO
          previous_await_routing_query := ^previous_await_routing_query^^.
                next_entry;
        WHILEND;
        await_routing_query := previous_await_routing_query^;
        previous_await_routing_query^ := await_routing_query^.next_entry;
        osp$clear_job_signature_lock (nlv$sm_await_routing_queries.lock);
        FREE await_routing_query IN nav$network_paged_heap^;
        osp$set_status_condition ( nae$sm_devices_inaccessible,
               status);
      IFEND;
    ELSE { Heap full
      osp$set_status_condition ( nae$insufficient_resources,
            status);
    IFEND;

  PROCEND poll_devices_for_routing_info;
?? OLDTITLE ??
?? NEWTITLE := 'select_device', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the identifier of the device
{   with the best path to the given subnet.
{ DESIGN:
{   This procedure searches the subnet attributes associated with the given
{   network devices for the given subnet id. If more than one device has
{   a path to the given subnet, this procedure attempts to load level the
{   traffic across the directly connected devices by selecting the device
{   with minimum active connections.
{ NOTES:
{   Whenever the quality of service attribute is defined, this code must be
{   changed to examine this attribute to determine the best path to the given
{   subnet. The network device list must be locked by the caller for non
{   exclusive access.

  PROCEDURE select_device
    (    subnet_id: nat$subnet_identifier;
         system_id: nat$system_identifier;
     VAR device_list { input, output } : nlt$device_list;
     VAR version_list { input, output } : nlt$sm_device_version_list;
     VAR count {input,output} : nlt$device_count;
     VAR status: sm_device_selection_status);

    VAR
      actual_count: integer,
      compare_swap_status: osc$cs_successful .. osc$cs_variable_locked,
      device_id: nlt$device_identifier,
      device_selected: boolean,
      i: integer,
      new_count: integer,
      old_count: integer,
      selected_device: nlt$device_identifier,
      selected_version: nlt$sm_version,
      selection_count: integer,
      subnet_attributes: ^nlt$subnet_attributes,
      subnet_known: boolean,
      system_management_list: ^nlt$sm_device_list;

    subnet_known := FALSE;
    system_management_list := nlv$sm_devices.list;
    device_selected := FALSE;

{   Check to see whether the device is directly connected.  If so, select this device.

    nlp$get_nonexclusive_access (nlv$configured_network_devices.access_control);

    /match_system_id/
    FOR i:=1 to count DO
      device_id := device_list [i];
      IF (nlv$configured_network_devices.network_device_list^ [device_id].path_status = nlc$path_available)
         AND (nlv$configured_network_devices.network_device_list^ [device_id].system_id = system_id) THEN
        device_selected := TRUE;
        selected_device := device_id;
        selected_version := system_management_list^ [device_id].device_version;
        EXIT /match_system_id/
      IFEND;

    FOREND /match_system_id/;

    nlp$release_nonexclusive_access (nlv$configured_network_devices.access_control);

    IF NOT device_selected THEN
    /main_loop/
      FOR i := 1 TO count DO
        device_id := device_list [i];

{ Search for a matching subnet attributes entry.

        subnet_attributes := system_management_list^ [device_id].subnet_list;
        WHILE (subnet_attributes <> NIL) AND (subnet_attributes^.subnet_id <> subnet_id) DO
          subnet_attributes := subnet_attributes^.next_entry;
        WHILEND;

{ Note: This code needs to be changed to evaluate the QOS attribute associated with
{       the subnet attributes entry to determine the best path to the given destination
{       address. At present, this code selects the device with the least number of
{       active connections.

        IF (subnet_attributes <> NIL) THEN
          subnet_known := TRUE;
          IF ((subnet_attributes^.directly_connected) AND (subnet_attributes^.subnet_status = nlc$subnet_up))
                OR (NOT subnet_attributes^.directly_connected) THEN

{ Retrieve the selection count.

            old_count := 0;
            new_count := old_count;
            REPEAT
              #COMPARE_SWAP (system_management_list^ [device_id].active_connection_count,
                    old_count, new_count, actual_count, compare_swap_status);
            UNTIL compare_swap_status <> osc$cs_variable_locked;

            IF (NOT device_selected) OR ((device_selected) AND (actual_count < selection_count)) THEN
              selected_device := device_list [i];
              selected_version := version_list [i];
              selection_count := actual_count;
            IFEND;
            device_selected := TRUE;
          IFEND;
        IFEND;
      FOREND /main_loop/;
    IFEND;

    IF device_selected THEN
      count := 1;
      device_list [1] := selected_device;
      version_list [1] := selected_version;
      status := sm_route_known;
    ELSE
      IF subnet_known THEN
        status := sm_route_unknown;
      ELSE
        status := sm_subnet_unknown;
      IFEND;
    IFEND;

  PROCEND select_device;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] select_device_with_min_load', EJECT ??
{ PURPOSE:
{   The purpose of this procedure is to select from the given list of devices, the
{   device with the least no of active connections.

  PROCEDURE [INLINE] select_device_with_min_load (count: nlt$device_count;
    VAR device_list { input, output } : nlt$device_list;
    VAR version_list { input, output } : nlt$sm_device_version_list);

    VAR
      active_connection_count: integer,
      actual_count: integer,
      compare_swap_status: osc$cs_successful .. osc$cs_variable_locked,
      j: integer,
      new_count: integer,
      old_count: integer,
      selected_device: nlt$device_identifier,
      selected_version: nlt$sm_version,
      system_management_list: ^nlt$sm_device_list;

    IF count > 1 THEN

{ Select the device with minimum active connections.

      nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
      system_management_list := nlv$sm_devices.list;
      FOR j := 1 TO count DO
        old_count := 0;
        REPEAT
          new_count := old_count;
          #COMPARE_SWAP (system_management_list^ [device_list[j]].active_connection_count,
                old_count, new_count, actual_count, compare_swap_status);
        UNTIL compare_swap_status <> osc$cs_variable_locked;

        IF (j = 1) OR (actual_count < active_connection_count) THEN
          selected_device := device_list [j];
          selected_version := version_list [j];
          active_connection_count := actual_count;
        IFEND;
      FOREND;

      nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
      device_list [1] := selected_device;
      version_list [1] := selected_version;
    IFEND;

  PROCEND select_device_with_min_load;
?? OLDTITLE ??
MODEND nlm$system_mgmt_access_agent;
