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

{ PURPOSE:
{   This module contains procedures neccesary to support the UDP Access Agent.
{ DESIGN:
{   These procedures are called by the socket layer external interface code and in turn
{   access the channel connections. These procedures support the user requests issued
{   over user datagram sockets as well as the indications received over the channel
{   connections from the UDP Access Provider in the device.
{   There is a unique global socket for each datagram socket
{   created by the user. These procedures translate the user requests issued over a datagram
{   sockets to the actual channel connection requests.
{   The XDCL'd procedures have been grouped in alphabetical order
{   followed by the internal procedures. The internal procedures are also in alphabetical
{   order.
{   This module contains code that executes in ring 3. It resides on OSF$JOB_TEMPLATE_23D.
{
{ NOTES:
{   The following abbreviations have been used in this module:
{          UDP - User Datagram Protocol

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc nac$null_connection_id
*copyc nac$sk_all_ip_addresses
*copyc nae$sk_socket_layer
*copyc nat$connection_id
*copyc nat$data_fragments
*copyc nat$data_length
*copyc nat$sk_interface_mode
*copyc nat$sk_socket_identifier
*copyc nat$sk_traffic_pattern
*copyc nat$wait_time
*copyc nlc$udp_max_pool_size
*copyc nlt$bm_message_id
*copyc nlt$cc_address
*copyc nlt$cc_device_and_data_record
*copyc nlt$cc_interface
*copyc nlt$cl_connection
*copyc nlt$cl_layer_name
*copyc nlt$device_count
*copyc nlt$device_identifier
*copyc nlt$tcpip_address
*copyc nlt$tm_device_address_list
*copyc nlt$udpaa_protocol_data_unit
*copyc nlt$udp_global_socket
*copyc nlt$udp_local_routing_cache
*copyc nlt$udp_receive_data_signal
*copyc nlt$udp_socket_inventory
*copyc nlt$udp_socket_layer
*copyc ost$free_running_clock
*copyc ost$activity_status
*copyc ost$status
?? POP ??
*copyc nap$condition_handler_trace
*copyc nap$namve_system_error
*copyc nlp$al_get_data_length
*copyc nlp$bm_concatenate_messages
*copyc nlp$bm_create_message
*copyc nlp$bm_deliver_message
*copyc nlp$bm_extract_message_prefix
*copyc nlp$bm_flush_message
*copyc nlp$bm_get_message_length
*copyc nlp$bm_get_message_resources
*copyc nlp$bm_release_message
*copyc nlp$cc_disconnect
*copyc nlp$cc_initialize_template
*copyc nlp$cc_receive_data
*copyc nlp$cc_report_undelivered_data
*copyc nlp$cc_request_connection
*copyc nlp$cc_send_data_fragments
*copyc nlp$cl_activate_layer
*copyc nlp$cl_activate_receiver
*copyc nlp$cl_activate_sender
*copyc nlp$cl_create_connection
*copyc nlp$cl_deactivate_layer
*copyc nlp$cl_deactivate_receiver
*copyc nlp$cl_deactivate_sender
*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_nonexclusive_access
*copyc nlp$osi_get_outbound_capacity
*copyc nlp$release_nonexclusive_access
*copyc nlp$sk_fragment_data
*copyc nlp$tm_get_local_udp_devices
*copyc nlp$tm_select_by_local_udp_addr
*copyc nlp$tm_udp_select_device
*copyc nlp$udp_activate_receiver
*copyc nlp$udp_deactivate_receiver
*copyc nlp$udp_delete_global_socket
*copyc nlp$udp_free_exclusive_access
*copyc nlp$udp_free_nonexclu_to_root
*copyc nlp$udp_get_exclusive_access
*copyc nlp$udp_get_exclusive_via_gsid
*copyc nlp$udp_get_nonexclu_to_root
*copyc nlp$udp_store_receiver
*copyc osp$append_status_integer
*copyc osp$clear_job_signature_lock
*copyc osp$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc osp$establish_condition_handler
*copyc osp$set_status_from_condition
*copyc osp$set_job_signature_lock
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc pmp$continue_to_cause
*copyc pmp$get_executing_task_gtid
*copyc pmp$log_ascii
*copyc pmp$long_term_wait
*copyc pmp$ready_task
*copyc pmp$wait
*copyc syp$cycle
*copyc nav$network_paged_heap
*copyc nlv$configured_network_devices
*copyc nlv$udp_global_sockets
*copyc oss$job_paged_literal
*copyc oss$task_private
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by This Module', EJECT ??

  CONST
    udpaa_cc_disconnect = 'UDPAA - CC disconnect',
    udpaa_open_reject = 'UDPAA - open reject',
    udpaa_release = 'UDPAA - release indication';

  VAR
    null_socket_address: [READ, oss$job_paged_literal] nat$sk_socket_address := [0, 0],
    nlv$udp_local_routing_cache: [XDCL, oss$task_private] nlt$udp_local_routing_cache :=
          [[0, 0, 0], [0, 0, 0]];

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

  PROCEDURE [XDCL] nlp$udp_await_clear_to_send
    (    global_socket_id: nlt$udp_global_socket_id;
         wait: boolean;
     VAR activity_complete: boolean);

    VAR
      active_connection_count: nlt$device_count,
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      current_task_id: ost$global_task_id,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      ignore_status: ost$status,
      layer_active: boolean,
      outbound_capacity: nat$data_length,
      previous_sender_task: ^^nlt$udp_sender_task,
      queue_count: nlt$device_count,
      root: nlt$udp_reference_number,
      sender_task: ^nlt$udp_sender_task,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    activity_complete := FALSE;
    queue_count := 0;
    pmp$get_executing_task_gtid (current_task_id);
    IF NOT wait THEN
      root := global_socket_id.reference_number MOD (UPPERBOUND (nlv$udp_global_sockets.list^) + 1);
      nlp$udp_get_nonexclu_to_root (root);
      global_socket := nlv$udp_global_sockets.list^ [root].first;
      WHILE (global_socket <> NIL) AND (global_socket^.identifier <> global_socket_id) DO
        global_socket := global_socket^.next_entry;
      WHILEND;
    ELSE { wait = TRUE
      nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IFEND;

    IF (global_socket <> NIL) THEN
      IF global_socket^.status = nlc$udp_global_socket_open THEN

{ Build a list of connection ids.

        socket_device_list := ^global_socket^.device_list;
        PUSH connection_list: [1 .. UPPERBOUND (socket_device_list^)];
        FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
          IF socket_device_list^ [device_id].status <> nlc$udp_device_closed THEN
            connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
          ELSE
            connection_list^ [device_id] := nac$null_connection_id;
          IFEND;
        FOREND;
        IF NOT wait THEN
          nlp$udp_free_nonexclu_to_root (root);
        ELSE
          nlp$udp_free_exclusive_access (global_socket);
        IFEND;

{ Check outbound capacity of each connection.

      /check_clear_to_send/
        FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
          IF connection_list^ [device_id] <> nac$null_connection_id THEN
            nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
            IF cl_connection <> NIL THEN
              nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
              IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) THEN
                IF udp_connection^.send_queue = NIL THEN
                  nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                  IF outbound_capacity <= 0 THEN
                    nlp$cc_receive_data (cl_connection);
                    nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                  IFEND;
                  IF outbound_capacity > 0 THEN
                    activity_complete := TRUE;
                    nlp$cl_release_exclusive_access (cl_connection);
                    EXIT /check_clear_to_send/;
                  ELSEIF wait THEN

{ Queue the task in the send queue.

                    get_sender_task_entry (udp_connection, sender_task);
                    sender_task^.next_entry := NIL;
                    sender_task^.task_id := current_task_id;
                    sender_task^.send_type := nlc$udp_await_clear_to_send;
                    udp_connection^.send_queue := sender_task;
                  IFEND;
                ELSE { udp_connection^.send_queue <> NIL
                  IF udp_connection^.send_queue^.task_id = current_task_id THEN

{ Current task is already queued and is at the head of the queue.

                    nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                    IF outbound_capacity <= 0 THEN
                      nlp$cc_receive_data (cl_connection);
                      nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                    IFEND;
                    IF outbound_capacity > 0 THEN
                      activity_complete := TRUE;
                      sender_task := udp_connection^.send_queue;
                      udp_connection^.send_queue := sender_task^.next_entry;
                      return_sender_task_entry (udp_connection, sender_task);
                      IF udp_connection^.send_queue <> NIL THEN
                        pmp$ready_task (udp_connection^.send_queue^.task_id, ignore_status);
                      IFEND;
                      nlp$cl_release_exclusive_access (cl_connection);
                      EXIT /check_clear_to_send/;
                    ELSEIF NOT wait THEN

{ Dequeue the task.

                      sender_task := udp_connection^.send_queue;
                      udp_connection^.send_queue := sender_task^.next_entry;
                      return_sender_task_entry (udp_connection, sender_task);
                      nlp$cl_release_exclusive_access (cl_connection);
                      CYCLE /check_clear_to_send/;
                    IFEND;
                  ELSE { task not at the head of the send queue

{ Queue the task if not already queued.

                    previous_sender_task := ^udp_connection^.send_queue;
                    WHILE (previous_sender_task^ <> NIL) AND (previous_sender_task^^.task_id <>
                          current_task_id) DO
                      previous_sender_task := ^previous_sender_task^^.next_entry;
                    WHILEND;
                    IF wait THEN
                      IF previous_sender_task^ = NIL THEN

{ The task is not already queued.

                        get_sender_task_entry (udp_connection, sender_task);
                        sender_task^.next_entry := NIL;
                        sender_task^.task_id := current_task_id;
                        sender_task^.send_type := nlc$udp_await_clear_to_send;
                        previous_sender_task^ := sender_task;
                      IFEND;
                    ELSE { NOT wait
                      IF previous_sender_task^ <> NIL THEN
                        sender_task := previous_sender_task^;
                        previous_sender_task^ := sender_task^.next_entry;
                        return_sender_task_entry (udp_connection, sender_task);
                        nlp$cl_release_exclusive_access (cl_connection);
                      IFEND;
                      CYCLE /check_clear_to_send/;
                    IFEND;
                  IFEND;
                IFEND;
                queue_count := queue_count + 1;
              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            IFEND;
          IFEND;
        FOREND /check_clear_to_send/;

        IF activity_complete THEN

{ Dequeue the task from the send queue on all the connections.

          FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
            IF connection_list^ [device_id] <> nac$null_connection_id THEN
              nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
              IF cl_connection <> NIL THEN
                nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
                IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) THEN
                  IF udp_connection^.send_queue <> NIL THEN
                    sender_task := udp_connection^.send_queue;
                    previous_sender_task := ^udp_connection^.send_queue;

                  /dequeue_sender/
                    REPEAT
                      IF sender_task^.task_id = current_task_id THEN
                        previous_sender_task^ := sender_task^.next_entry;
                        return_sender_task_entry (udp_connection, sender_task);
                      ELSE
                        previous_sender_task := ^sender_task^.next_entry;
                        sender_task := sender_task^.next_entry;
                      IFEND;
                    UNTIL (sender_task = NIL);
                  IFEND;
                IFEND;
                nlp$cl_release_exclusive_access (cl_connection);
              IFEND;
            IFEND;
          FOREND;
        IFEND;
        IF (queue_count = 0) AND (NOT activity_complete) THEN

{ No device available. Is it ok to return with activity complete = TRUE ?????

          activity_complete := TRUE;
        IFEND;
      ELSE { global_socket^.status <> nlc$udp_global_socket_open

{ Socket terminated via application mgmt.

        activity_complete := TRUE;
        IF NOT wait THEN
          nlp$udp_free_nonexclu_to_root (root);
        ELSE
          nlp$udp_free_exclusive_access (global_socket);
        IFEND;
      IFEND;
    ELSE { global_socket = NIL

{ Socket is assumed to be terminated via application management.

      activity_complete := TRUE;
      IF NOT wait THEN
        nlp$udp_free_nonexclu_to_root (root);
      IFEND;
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_await_data_available
    (    global_socket_id: nlt$udp_global_socket_id;
         wait: boolean;
     VAR activity_complete: boolean);

    VAR
      another_receiver_active: boolean,
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      current_task_id: ost$global_task_id,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      ignore_status: ost$status,
      layer_active: boolean,
      previous_receiver_task: ^^nlt$udp_receiver_task,
      receiver_task: ^nlt$udp_receiver_task,
      scan_connections: boolean,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    activity_complete := FALSE;
    another_receiver_active := FALSE;
    scan_connections := FALSE;
    pmp$get_executing_task_gtid (current_task_id);
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);

    IF global_socket <> NIL THEN
      IF global_socket^.status = nlc$udp_global_socket_open THEN
        IF global_socket^.receive_wait_queue = NIL THEN
          nlp$udp_activate_receiver (global_socket^.active_receiver, another_receiver_active);
        IFEND;

        IF (global_socket^.receive_wait_queue = NIL) AND (NOT another_receiver_active) THEN
          PUSH connection_list: [1 .. UPPERBOUND (global_socket^.device_list)];

{ Scan the devices for queued messages.

          socket_device_list := ^global_socket^.device_list;

        /scan_devices_1/
          FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
            IF socket_device_list^ [device_id].received_messages <> NIL THEN
              activity_complete := TRUE;
              nlp$udp_deactivate_receiver (global_socket^.active_receiver);
              EXIT /scan_devices_1/;
            IFEND;
            IF (socket_device_list^ [device_id].status = nlc$udp_device_open) OR
                  (socket_device_list^ [device_id].status = nlc$udp_device_await_confirm) THEN
              connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
            ELSE
              connection_list^ [device_id] := nac$null_connection_id;
            IFEND;
          FOREND /scan_devices_1/;
          IF NOT activity_complete THEN

{ Queue the task.

            scan_connections := TRUE;
            get_receiver_task_entry (global_socket, receiver_task);
            receiver_task^.next_entry := NIL;
            receiver_task^.task_id := current_task_id;
            receiver_task^.receive_type := nlc$udp_await_data_available;
            receiver_task^.activity_complete := ^activity_complete;
            receiver_task^.receiver_active := TRUE;
            global_socket^.receive_wait_queue := receiver_task;
          IFEND;
        ELSE

{ Find the task in the receive queue. If task is already queued and wait is not selected,
{ dequeue the task. If wait is selected, queue the task at the end of the receive queue
{ if not already queued.

          previous_receiver_task := ^global_socket^.receive_wait_queue;
          WHILE (previous_receiver_task^ <> NIL) AND (previous_receiver_task^^.task_id <> current_task_id) DO
            previous_receiver_task := ^previous_receiver_task^^.next_entry;
          WHILEND;
          IF previous_receiver_task^ = NIL THEN
            IF wait THEN

{ Queue the curent task as it is not in the receive queue.

              get_receiver_task_entry (global_socket, receiver_task);
              receiver_task^.next_entry := NIL;
              receiver_task^.task_id := current_task_id;
              receiver_task^.receive_type := nlc$udp_await_data_available;
              receiver_task^.activity_complete := ^activity_complete;
              receiver_task^.receiver_active := FALSE;
              previous_receiver_task^ := receiver_task;
            IFEND;
          ELSE { task already queued
            IF global_socket^.receive_wait_queue^.task_id = current_task_id THEN

{ Task is at the head of the receive wait queue.

              receiver_task := global_socket^.receive_wait_queue;
              IF NOT receiver_task^.receiver_active THEN
                nlp$udp_activate_receiver (global_socket^.active_receiver, another_receiver_active);
                receiver_task^.receiver_active := NOT another_receiver_active;
              IFEND;

              IF receiver_task^.receiver_active THEN
                PUSH connection_list: [1 .. UPPERBOUND (global_socket^.device_list)];

{ Scan the devices for queued messages.

                socket_device_list := ^global_socket^.device_list;

              /scan_devices_2/
                FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
                  IF socket_device_list^ [device_id].received_messages <> NIL THEN
                    activity_complete := TRUE;
                    global_socket^.receive_wait_queue := receiver_task^.next_entry;
                    return_receiver_task_entry (global_socket, receiver_task);
                    nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                    IF global_socket^.receive_wait_queue <> NIL THEN
                      pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                    IFEND;
                    EXIT /scan_devices_2/;
                  IFEND;
                  IF (socket_device_list^ [device_id].status = nlc$udp_device_open) OR
                        (socket_device_list^ [device_id].status = nlc$udp_device_await_confirm) THEN
                    connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
                  ELSE
                    connection_list^ [device_id] := nac$null_connection_id;
                  IFEND;
                FOREND /scan_devices_2/;
                scan_connections := NOT activity_complete;
              IFEND;
            ELSE { task is not at the head of the queue
              IF NOT wait THEN

{ Dequeue the task.

                receiver_task := previous_receiver_task^;
                previous_receiver_task^ := receiver_task^.next_entry;
                return_receiver_task_entry (global_socket, receiver_task);
              IFEND;
            IFEND;
          IFEND;
        IFEND;
      ELSE { global_socket^.status <> nlc$udp_global_socket_open

{ The socket has been terminated via application management.

        activity_complete := TRUE;
      IFEND;
      nlp$udp_free_exclusive_access (global_socket);
    ELSE { global_socket = NIL

{ The socket is assumed to have been terminated via application management.

      activity_complete := TRUE;
    IFEND;

    IF NOT activity_complete AND scan_connections THEN

{ Scan the connections for data.

    /scan_connections_for_data/
      FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
        IF connection_list^ [device_id] <> nac$null_connection_id THEN
          nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
          IF cl_connection <> NIL THEN
            nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
            IF (layer_active) AND ((udp_connection^.state = nlc$udp_conn_open) OR
                  (udp_connection^.state = nlc$udp_conn_await_confirm)) THEN
              nlp$cc_receive_data (cl_connection);
              #SPOIL (activity_complete);
              IF activity_complete THEN
                nlp$cl_release_exclusive_access (cl_connection);
                EXIT /scan_connections_for_data/;
              IFEND;
            IFEND;
            nlp$cl_release_exclusive_access (cl_connection);
          IFEND;
        IFEND;
      FOREND /scan_connections_for_data/;

      IF (NOT activity_complete) AND (NOT wait) THEN
        nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
        IF (global_socket <> NIL) AND (global_socket^.status = nlc$udp_global_socket_open) THEN
          receiver_task := global_socket^.receive_wait_queue;
          global_socket^.receive_wait_queue := receiver_task^.next_entry;
          return_receiver_task_entry (global_socket, receiver_task);
          nlp$udp_deactivate_receiver (global_socket^.active_receiver);
          IF global_socket^.receive_wait_queue <> NIL THEN
            pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
          IFEND;
          nlp$udp_free_exclusive_access (global_socket);
        IFEND;
      IFEND;
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_bind_socket
    (    global_socket_id: nlt$udp_global_socket_id;
         port: nat$sk_port_number;
         traffic_pattern: nat$sk_traffic_pattern;
         ip_address: nat$sk_ip_address;
     VAR status: ost$status);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_id: nat$connection_id,
      count: nlt$device_count,
      current_time: integer,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      device_and_data_list: array [1 .. 1] of nlt$cc_device_and_data_record,
      device_id: nlt$device_identifier,
      end_time: integer,
      global_socket: ^nlt$udp_global_socket,
      i: integer,
      ignore_layer_active: boolean,
      j: integer,
      next_sender_pool_entry: ^nlt$udp_sender_task,
      open_socket_request: nlt$udpaa_open_request,
      open_socket_pdus: ^array [1 .. * ] of nlt$bm_message_id,
      previous_pool_entry: ^^nlt$udp_sender_task,
      remaining_time: integer,
      request_count: nlt$device_count,
      response_count: nlt$device_count,
      sender_pool_entry: ^nlt$udp_sender_task,
      udp_connection: ^nlt$udp_socket_layer,
      udp_devices: ^nlt$tm_device_address_list;

    count := 0;
    PUSH udp_devices: [1 .. UPPERBOUND (nlv$configured_network_devices.network_device_list^)];
    IF ip_address <> nac$sk_all_ip_addresses THEN
      nlp$tm_select_by_local_udp_addr (ip_address, udp_devices^ [1].device_id, status);
      IF status.normal THEN
        udp_devices^ [1].address := ip_address;
        count := 1;
      IFEND;
    ELSE

{ This procedure will return the list of UDP devices and the associated
{ IP addresses.

      nlp$tm_get_local_udp_devices (udp_devices^, count);
      IF count = 0 THEN
        osp$set_status_abnormal (nac$status_id, nae$sk_no_device_configured, 'UDP', status);
      IFEND;
    IFEND;

{ Send open socket requests to all the devices configured with UDP.

    IF status.normal THEN
      PUSH open_socket_pdus: [1 .. count];
      open_socket_request.header.kind := nlc$udpaa_open_req;
      open_socket_request.header.length := #SIZE (open_socket_request);
      open_socket_request.port := port;
      open_socket_request.traffic_pattern := traffic_pattern;
      data_fragment [1].address := ^open_socket_request;
      data_fragment [1].length := open_socket_request.header.length;

      request_count := 0;
      nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
      IF global_socket <> NIL THEN
        IF global_socket^.status = nlc$udp_global_socket_unbound THEN
          FOR i := 1 TO count DO
            nlp$bm_create_message (data_fragment, open_socket_pdus^ [i], {ignore} status);
          FOREND;

          global_socket^.port := port;
          global_socket^.status := nlc$udp_global_socket_open;

{ Initialize all entries in the socket device list.

          FOR i := 1 TO count DO
            device_id := udp_devices^ [i].device_id;
            device_and_data_list [1].device_id := device_id;
            device_and_data_list [1].data := open_socket_pdus^ [i];
            global_socket^.device_list [device_id].ip_address := udp_devices^ [i].address;
            nlp$cl_create_connection (nlc$udp_interface, cl_connection);
            IF cl_connection <> NIL THEN
              nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, ignore_layer_active,
                    udp_connection);
              nlp$cc_request_connection (cl_connection, device_and_data_list, nlc$udp_access_address,
                    nlc$cc_normal_class {?} , status);
              IF status.normal THEN
                nlp$udp_store_receiver (global_socket^.active_receiver, cl_connection);
                nlp$cl_activate_layer (nlc$udp_interface, cl_connection);
                global_socket^.device_list [device_id].connection_id := cl_connection^.identifier;
                global_socket^.device_list [device_id].status := nlc$udp_device_await_confirm;
                global_socket^.active_device_count := global_socket^.active_device_count + 1;

{ Setup the remaining fields in the UDP socket layer connection.

                udp_connection^.state := nlc$udp_conn_await_confirm;
                udp_connection^.device_id := device_id;
                udp_connection^.local_ip_address := udp_devices^ [i].address;
                udp_connection^.global_socket_id := global_socket_id;
                udp_connection^.inventory_report := 0;
                udp_connection^.send_queue := NIL;
                udp_connection^.available_sender_pool := NIL;
                request_count := request_count + 1;
              ELSE { Unable to request CC connection
                global_socket^.device_list [device_id].status := nlc$udp_device_res_constraint;
                status.normal := TRUE;

{ Note all other abnormal status codes are being treated as resource constraint.

              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            ELSE

{ Mark the device as being in resource contraint and the timer task will periodically
{ try to open the connection.

              global_socket^.device_list [device_id].status := nlc$udp_device_res_constraint;
              nlp$bm_release_message (open_socket_pdus^ [i]);
            IFEND;
          FOREND;
          IF request_count > 0 THEN
            pmp$get_executing_task_gtid (global_socket^.waiting_task_id);
          IFEND;
        ELSE { all other states unaccepted
          nap$namve_system_error ({Recoverable_error=} TRUE, 'Encountered unexpected global socket status ',
                NIL);
          osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'BIND SOCKET', status);
        IFEND;
        nlp$udp_free_exclusive_access (global_socket);
      ELSE { global_socket = NIL

{ The socket is assumed to be terminated via application management.

        osp$set_status_condition (nae$sk_socket_terminated, status);
      IFEND;

{ Wait for (1/2 to 1 sec) responses from the devices.

      IF request_count > 0 THEN
        IF request_count = 1 THEN
          remaining_time := 500000;
        ELSE
          remaining_time := 1000000;
        IFEND;
        end_time := #FREE_RUNNING_CLOCK (0) + remaining_time;

      /wait_loop/
        REPEAT
          pmp$wait (remaining_time, 0);
          current_time := #FREE_RUNNING_CLOCK (0);
          IF current_time < end_time THEN
            remaining_time := (end_time - current_time);
          ELSE
            remaining_time := 0;
          IFEND;

          nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
          IF global_socket <> NIL THEN
            IF global_socket^.status = nlc$udp_global_socket_open THEN

{ Check the socket status with respect to each device.

              response_count := 0;

            /await_response/
              FOR i := 1 TO UPPERBOUND (global_socket^.device_list) DO
                IF global_socket^.device_list [i].status = nlc$udp_device_await_confirm THEN
                  connection_id := global_socket^.device_list [i].connection_id;
                  nlp$udp_free_exclusive_access (global_socket);
                  nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
                  IF connection_exists THEN
                    nlp$cc_receive_data (cl_connection);
                    nlp$cl_release_exclusive_access (cl_connection);
                  IFEND;
                  nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
                  IF global_socket = NIL THEN
                    nap$namve_system_error ({Recoverable_error=} TRUE,
                          'Encountered unexpected global socket status ', NIL);
                    osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'BIND SOCKET', status);
                    EXIT /wait_loop/;
                  ELSEIF global_socket^.status <> nlc$udp_global_socket_open THEN
                    osp$set_status_condition (nae$sk_socket_terminated, status);
                    EXIT /wait_loop/;
                  IFEND;
                IFEND;
                IF global_socket^.device_list [i].status = nlc$udp_device_open THEN
                  response_count := response_count + 1;
                ELSEIF (global_socket^.device_list [i].status = nlc$udp_device_closed) AND
                      (global_socket^.device_list [i].ip_address <> nac$sk_all_ip_addresses) THEN
                  response_count := response_count + 1;
                IFEND;
              FOREND /await_response/;
            ELSE { all other states are unaccepted
              nap$namve_system_error ({Recoverable_error=} TRUE,
                    'Encountered unexpected global socket status ', NIL);
              osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'BIND SOCKET', status);
            IFEND;
            IF (response_count = request_count) OR (remaining_time = 0) OR (NOT status.normal) THEN
              global_socket^.waiting_task_id.index := 0;
            IFEND;
            nlp$udp_free_exclusive_access (global_socket);
          ELSE { global_socket = NIL

{ The socket is assumed to be terminated via application management.

            osp$set_status_condition (nae$sk_socket_terminated, status);
          IFEND;
        UNTIL (response_count = request_count) OR (remaining_time = 0) OR (NOT status.normal);
      IFEND;
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_cancel_socket_offer
    (    global_socket_id: nlt$udp_global_socket_id;
     VAR status: ost$status);

    VAR
      global_socket: ^nlt$udp_global_socket;

    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      IF global_socket^.status = nlc$udp_global_socket_offered THEN
        global_socket^.status := nlc$udp_global_socket_open;
      ELSE { unexpected status
        nap$namve_system_error ({Recoverable_error=} TRUE,
              'Encountered unexpected global socket status ', NIL);
        osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'CANCEL SOCKET OFFER', status);
      IFEND;
      nlp$udp_free_exclusive_access (global_socket);
    ELSE { global_socket = NIL

{ The socket is assumed to be terminated via application management.

      osp$set_status_condition (nae$sk_socket_terminated, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_close_socket
    (    global_socket_id: nlt$udp_global_socket_id;
         close_via_application_mgmt: boolean);

    VAR
      active_connection_count: integer,
      cl_connection: ^nlt$cl_connection,
      closed_connection_count: integer,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      delete_global_socket: boolean,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      ignore_status: ost$status,
      layer_active: boolean,
      new_status: nlt$udp_global_socket_status,
      next_received_message: ^nlt$udp_received_message,
      previous_receiver_task: ^^nlt$udp_receiver_task,
      previous_sender_task: ^^nlt$udp_sender_task,
      received_message: ^nlt$udp_received_message,
      receiver_task: ^nlt$udp_receiver_task,
      sender_task: ^nlt$udp_sender_task,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    closed_connection_count := 0;
    IF close_via_application_mgmt THEN
      new_status := nlc$udp_global_socket_term;
    ELSE
      new_status := nlc$udp_global_socket_closed;
    IFEND;

    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      IF (global_socket^.status = nlc$udp_global_socket_open) OR
            (global_socket^.status = nlc$udp_global_socket_offered) THEN
        global_socket^.status := new_status;
        socket_device_list := ^global_socket^.device_list;
        PUSH connection_list: [1 .. UPPERBOUND (socket_device_list^)];

{ Update the status of each device to closed. Ready all active
{ receivers and discard all queued messages.

        FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
          IF socket_device_list^ [device_id].status <> nlc$udp_device_closed THEN
            connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
            IF socket_device_list^ [device_id].received_messages <> NIL THEN
              received_message := socket_device_list^ [device_id].received_messages;
              socket_device_list^ [device_id].received_messages := NIL;
              WHILE received_message <> NIL DO
                IF received_message^.data <> nlv$bm_null_message_id THEN
                  nlp$bm_release_message (received_message^.data);
                IFEND;
                next_received_message := received_message^.next_entry;
                FREE received_message IN nav$network_paged_heap^;
                received_message := next_received_message;
              WHILEND;
            IFEND;
            IF socket_device_list^ [device_id].receiver_task <> NIL THEN

{ Requeue and ready the receiver task.

              receiver_task := socket_device_list^ [device_id].receiver_task;
              socket_device_list^ [device_id].receiver_task := NIL;
              receiver_task^.next_entry := global_socket^.receive_wait_queue;
              global_socket^.receive_wait_queue := receiver_task;
              pmp$ready_task (receiver_task^.task_id, ignore_status);
            IFEND;
          ELSE
            connection_list^ [device_id] := nac$null_connection_id;
          IFEND;
        FOREND;

{ Ready all tasks in the receive queue.

        IF global_socket^.receive_wait_queue <> NIL THEN
          previous_receiver_task := ^global_socket^.receive_wait_queue;
          receiver_task := global_socket^.receive_wait_queue;
          WHILE receiver_task <> NIL DO
            pmp$ready_task (receiver_task^.task_id, ignore_status);
            IF receiver_task^.receive_type = nlc$udp_await_data_available THEN
              previous_receiver_task^ := receiver_task^.next_entry;
              FREE receiver_task IN nav$network_paged_heap^;
              receiver_task := previous_receiver_task^;
            ELSE
              previous_receiver_task := ^receiver_task^.next_entry;
              receiver_task := receiver_task^.next_entry;
            IFEND;
          WHILEND;
        IFEND;

        nlp$udp_free_exclusive_access (global_socket);

{ Terminate all active connections.

        FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
          IF connection_list^ [device_id] <> nac$null_connection_id THEN
            nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
            IF connection_exists THEN
              nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
              IF layer_active THEN
                IF udp_connection^.state <> nlc$udp_conn_closed THEN
                  udp_connection^.state := nlc$udp_conn_closed;
                  issue_disconnect (cl_connection, nlc$udpaa_rr_user_request);
                  IF udp_connection^.send_queue <> NIL THEN
                    previous_sender_task := ^udp_connection^.send_queue;
                    sender_task := udp_connection^.send_queue;

{ Ready each task awaiting clear to send and dequeue its entry.
{ Send a signal to each task waiting to send data.

                    WHILE sender_task <> NIL DO
                      pmp$ready_task (sender_task^.task_id, ignore_status);
                      IF sender_task^.send_type = nlc$udp_await_clear_to_send THEN
                        previous_sender_task^ := sender_task^.next_entry;
                        FREE sender_task IN nav$network_paged_heap^;
                        sender_task := previous_sender_task^;
                      ELSE
                        previous_sender_task := ^sender_task^.next_entry;
                        sender_task := sender_task^.next_entry;
                      IFEND;
                    WHILEND;
                  IFEND;
                  IF udp_connection^.send_queue = NIL THEN
                    deactivate_udp_layer (cl_connection, udp_connection);
                    closed_connection_count := closed_connection_count + 1;
                  IFEND;
                IFEND;
              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            IFEND;
          IFEND;
        FOREND;

{ Determine if the global socket can be deleted.

        nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
        IF global_socket <> NIL THEN
          global_socket^.active_device_count := global_socket^.active_device_count - closed_connection_count;
          delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                (global_socket^.active_device_count = 0);
          nlp$udp_free_exclusive_access (global_socket);
          IF delete_global_socket THEN
            nlp$udp_delete_global_socket (global_socket_id);
          IFEND;
        IFEND;
      ELSE { global_socket^.status = unbound, terminated
        global_socket^.status := new_status;
        nlp$udp_free_exclusive_access (global_socket);
        nlp$udp_delete_global_socket (global_socket_id);
      IFEND;
    IFEND;

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

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


{ A connect event is treated as a protocol error and results in a
{ disconnect being sent to the peer.

    issue_disconnect (cl_connection, nlc$udpaa_rr_protocol_error);

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

  PROCEDURE [XDCL] nlp$udp_device_available
    (    device_id: nlt$device_identifier;
         local_ip_address: nat$sk_ip_address);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      device_and_data_list: array [1 .. 1] of nlt$cc_device_and_data_record,
      global_socket: ^nlt$udp_global_socket,
      ignore_layer_active: boolean,
      local_status: ost$status,
      next_global_socket: ^nlt$udp_global_socket,
      open_socket_request: nlt$udpaa_open_request,
      root: nlt$udp_reference_number,
      udp_connection: ^nlt$udp_socket_layer;

    IF nlv$udp_global_sockets.list <> NIL THEN
      open_socket_request.header.kind := nlc$udpaa_open_req;
      open_socket_request.header.length := #SIZE (open_socket_request);
      data_fragment [1].address := ^open_socket_request;
      data_fragment [1].length := open_socket_request.header.length;

    /traverse_roots/
      FOR root := LOWERBOUND (nlv$udp_global_sockets.list^) TO UPPERBOUND (nlv$udp_global_sockets.list^) DO
        nlp$udp_get_nonexclu_to_root (root);
        global_socket := nlv$udp_global_sockets.list^ [root].first;

      /traverse_stem/
        WHILE global_socket <> NIL DO
          nlp$udp_get_exclusive_access (global_socket^.lock);
          IF (global_socket^.status = nlc$udp_global_socket_open) AND
                ((global_socket^.bound_address = nac$sk_all_ip_addresses) OR
                (global_socket^.bound_address = local_ip_address)) THEN
            IF global_socket^.device_list [device_id].status = nlc$udp_device_closed THEN

{ Setup the open socket pdu.

              open_socket_request.port := global_socket^.port;
              open_socket_request.traffic_pattern := global_socket^.traffic_pattern;
              nlp$bm_create_message (data_fragment, device_and_data_list [1].data, local_status);
              global_socket^.device_list [device_id].ip_address := local_ip_address;
              device_and_data_list [1].device_id := device_id;
              nlp$cl_create_connection (nlc$udp_interface, cl_connection);
              IF cl_connection <> NIL THEN
                nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, ignore_layer_active,
                      udp_connection);
                nlp$cc_request_connection (cl_connection, device_and_data_list, nlc$udp_access_address,
                      nlc$cc_normal_class {?} , local_status);
                IF local_status.normal THEN
                  nlp$udp_store_receiver (global_socket^.active_receiver, cl_connection);
                  nlp$cl_activate_layer (nlc$udp_interface, cl_connection);
                  global_socket^.device_list [device_id].connection_id := cl_connection^.identifier;
                  global_socket^.device_list [device_id].status := nlc$udp_device_await_confirm;
                  global_socket^.active_device_count := global_socket^.active_device_count + 1;

{ Setup the remaining fields in the UDP socket layer connection.

                  udp_connection^.state := nlc$udp_conn_await_confirm;
                  udp_connection^.device_id := device_id;
                  udp_connection^.local_ip_address := local_ip_address;
                  udp_connection^.global_socket_id := global_socket^.identifier;
                  udp_connection^.inventory_report := 0;
                  udp_connection^.send_queue := NIL;
                  udp_connection^.available_sender_pool := NIL;
                  udp_connection^.device_id := device_id;
                ELSE { Unable to request CC connection
                  global_socket^.device_list [device_id].status := nlc$udp_device_res_constraint;
                  local_status.normal := TRUE;

{ Note all other abnormal status codes are being treated as resource constraint.

                IFEND;
                nlp$cl_release_exclusive_access (cl_connection);
              ELSE

{ Mark the connection as being in resource contraint and the timer task will periodically
{ try to open the connection.

                global_socket^.device_list [device_id].status := nlc$udp_device_res_constraint;
                nlp$bm_release_message (device_and_data_list [1].data);
              IFEND;
            IFEND;
          IFEND;
          next_global_socket := global_socket^.next_entry;
          nlp$udp_free_exclusive_access (global_socket);
          global_socket := next_global_socket;
        WHILEND /traverse_stem/;
        nlp$udp_free_nonexclu_to_root (root);
      FOREND /traverse_roots/;
    IFEND;

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

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

    VAR
      broadcast_enabled: boolean,
      buffers_freed: nat$data_length,
      bytes_moved: nat$data_length,
      connection_exists: boolean,
      current_task_id: ost$global_task_id,
      data: nlt$bm_message_id,
      data_fragment: array [1 .. 1] of nat$data_fragment,
      data_length: integer,
      global_socket: ^nlt$udp_global_socket,
      ignore_delete_global_socket: boolean,
      layer_active: boolean,
      local_status: ost$status,
      new_received_message: ^nlt$udp_received_message,
      number_of_buffers_received: integer,
      only_one_message_queued: boolean,
      partial_messages: array [1 .. 2] of nlt$bm_message_id,
      previous_received_message: ^^nlt$udp_received_message,
      previous_sender_task: ^^nlt$udp_sender_task,
      received_message: ^nlt$udp_received_message,
      remaining_length: integer,
      sender_task: ^nlt$udp_sender_task,
      socket_inventory: ^nlt$udp_socket_inventory,
      status: ost$status,
      traffic_pattern: nat$sk_traffic_pattern,
      udp_connection: ^nlt$udp_socket_layer,
      udpaa_clear_send_ind: nlt$udpaa_clear_send_ind,
      udpaa_data_ind: nlt$udpaa_data_ind,
      udpaa_disconnect_ind: nlt$udpaa_release_ind,
      udpaa_open_confirm_ind: nlt$udpaa_open_confirm_ind,
      udpaa_set_options_request: nlt$udpaa_set_options_request;

    inventory_report := 0;
    pmp$get_executing_task_gtid (current_task_id);
    nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
    IF layer_active THEN
      CASE event.kind OF
      = nlc$cc_data_event =
        data := event.data.data;
        IF udp_connection^.state = nlc$udp_conn_open THEN
          nlp$bm_get_message_length (data, data_length);
          IF data_length >= #SIZE (udpaa_data_ind) THEN
            nlp$bm_extract_message_prefix (^udpaa_data_ind, #SIZE (udpaa_data_ind), data,
                  {ignore} bytes_moved);
            nlp$bm_get_message_resources (data, remaining_length, number_of_buffers_received);
            IF (udpaa_data_ind.header.kind = nlc$udpaa_data_ind) AND
                  (udpaa_data_ind.header.length = data_length) THEN
              nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
              IF global_socket <> NIL THEN
                IF (global_socket^.status <> nlc$udp_global_socket_closed) AND
                      (global_socket^.status <> nlc$udp_global_socket_term) THEN

{ The incoming data will be queued for all socket states execpt closed.

                  IF NOT global_socket^.device_list [udp_connection^.device_id].discard_data THEN
                    only_one_message_queued := FALSE;

{ Queue the data on the socket device entry.
{ Merge the data with any previous fragment or queue it as a separate fragment.

                    received_message := global_socket^.device_list [udp_connection^.device_id].
                          received_messages;
                    previous_received_message := ^global_socket^.device_list [udp_connection^.device_id].
                          received_messages;


                    IF received_message <> NIL THEN
                      WHILE received_message^.next_entry <> NIL DO
                        previous_received_message := ^received_message^.next_entry;
                        received_message := received_message^.next_entry;
                      WHILEND;
                    IFEND;
                    IF (received_message = NIL) OR (received_message^.end_of_message) OR
                          (received_message^.abort_receive) THEN

{ Add the new message.

                      IF global_socket^.available_message_pool <> NIL THEN
                        new_received_message := global_socket^.available_message_pool;
                        global_socket^.available_message_pool := new_received_message^.next_entry;
                        global_socket^.available_message_pool_size :=
                              global_socket^.available_message_pool_size - 1;
                      ELSE { pool empty
                        REPEAT
                          ALLOCATE new_received_message IN nav$network_paged_heap^;
                          IF new_received_message = NIL THEN
                            syp$cycle;
                          IFEND;
                        UNTIL new_received_message <> NIL;
                      IFEND;
                      new_received_message^.next_entry := NIL;
                      new_received_message^.abort_receive := FALSE;
                      new_received_message^.source_socket.port := udpaa_data_ind.source_port;
                      new_received_message^.source_socket.ip_address := udpaa_data_ind.source_ip_address;
                      new_received_message^.destination_ip_address := udpaa_data_ind.destination_ip_address;
                      new_received_message^.end_of_message := udpaa_data_ind.end_of_message;
                      new_received_message^.data := data;
                      new_received_message^.data_length := data_length - #SIZE (udpaa_data_ind);
                      new_received_message^.buffer_count := number_of_buffers_received;
                      IF received_message = NIL THEN
                        previous_received_message^ := new_received_message;
                        only_one_message_queued := TRUE;
                      ELSE { link at the end of the queue
                        received_message^.next_entry := new_received_message;
                      IFEND;
                    ELSE { partial message

{ Verify that the incoming fragment matches the queued data fragment.

                      IF (received_message^.source_socket.port = udpaa_data_ind.source_port) AND
                            (received_message^.source_socket.ip_address =
                            udpaa_data_ind.source_ip_address) AND (received_message^.destination_ip_address =
                            udpaa_data_ind.destination_ip_address) THEN
                        partial_messages [1] := received_message^.data;
                        partial_messages [2] := data;
                        nlp$bm_concatenate_messages (partial_messages, received_message^.data);
                        received_message^.end_of_message := udpaa_data_ind.end_of_message;
                        received_message^.data_length := received_message^.data_length + data_length -
                              #SIZE (udpaa_data_ind);
                        received_message^.buffer_count := received_message^.buffer_count +
                              number_of_buffers_received;
                      ELSE { protocol error
                        nlp$bm_release_message (data);
                        nlp$bm_release_message (received_message^.data);
                        previous_received_message^ := received_message^.next_entry;
                        return_received_message_entry (global_socket, received_message);
                        issue_protocol_error (cl_connection, global_socket, udp_connection);
                      IFEND;
                    IFEND;
                    buffers_freed := 0;
                    IF global_socket^.device_list [udp_connection^.device_id].receiver_task <> NIL THEN
                      IF global_socket^.device_list [udp_connection^.device_id].receiver_task^.task_id =
                            current_task_id THEN
                        receive_data (global_socket, udp_connection^.device_id, buffers_freed);
                      ELSE { current task not the receiver
                        pmp$ready_task (global_socket^.device_list [udp_connection^.device_id].receiver_task^.
                              task_id, {ignore} local_status);
                      IFEND;
                    ELSEIF global_socket^.receive_wait_queue <> NIL THEN
                      IF global_socket^.receive_wait_queue^.task_id = current_task_id THEN
                        activate_next_receiver_task (global_socket, udp_connection^.device_id, buffers_freed);
                      ELSEIF only_one_message_queued THEN
                        pmp$ready_task (global_socket^.receive_wait_queue^.task_id, {ignore} local_status);
                      IFEND;
                    IFEND;
                    udp_connection^.inventory_report := udp_connection^.inventory_report +
                          number_of_buffers_received - buffers_freed;
                  ELSE { discard data
                    IF udpaa_data_ind.end_of_message THEN

{ Clear the discard data flag.

                      global_socket^.device_list [udp_connection^.device_id].discard_data := FALSE;
                    IFEND;
                    nlp$bm_release_message (data);
                  IFEND;
                ELSE { global socket is closed or terminated

{ Discard the data.

                  nlp$bm_release_message (data);
                IFEND;
                inventory_report := udp_connection^.inventory_report;
                nlp$udp_free_exclusive_access (global_socket);
              ELSE {global_socket = NIL

{ Global socket cannot be deleted until all connections have been disconnected.

                nlp$bm_release_message (data);
                inventory_report := udp_connection^.inventory_report;
                nap$namve_system_error ({Recoverable_error=} TRUE,
                      'Encountered a NIL global socket while processing a UDPAA data event.', NIL);
              IFEND;
            ELSE { Invalid event
              nlp$bm_release_message (data);
              inventory_report := udp_connection^.inventory_report;
              process_protocol_error (cl_connection, udp_connection);
            IFEND;
          ELSEIF (data_length = #SIZE (udpaa_clear_send_ind)) THEN
            data_fragment [1].address := ^udpaa_clear_send_ind;
            data_fragment [1].length := #SIZE (udpaa_clear_send_ind);
            nlp$bm_flush_message (data_fragment, data, data_length, {ignore} local_status);
            IF (udpaa_clear_send_ind.header.length = data_length) AND
                  (udpaa_clear_send_ind.header.kind = nlc$udpaa_clear_send_ind) THEN
              process_clear_send_indication (udp_connection, buffers_freed);
              udp_connection^.inventory_report := udp_connection^.inventory_report - buffers_freed;
              inventory_report := udp_connection^.inventory_report;
            ELSE { invalid pdu
              process_protocol_error (cl_connection, udp_connection);
            IFEND;
          ELSE { invalid pdu
            nlp$bm_release_message (data);
            inventory_report := udp_connection^.inventory_report;
            process_protocol_error (cl_connection, udp_connection);
          IFEND;
        ELSE { udp_connection^.state <> nlc$udp_conn_open
          IF udp_connection^.state <> nlc$udp_conn_closed THEN
            process_protocol_error (cl_connection, udp_connection);
          ELSE
            nap$namve_system_error ({Recoverable_error=} TRUE,
                  'Received a data event when the UDP socket layer is closed.', NIL);
          IFEND;
        IFEND;

      = nlc$cc_accept_event =
        IF udp_connection^.state = nlc$udp_conn_await_confirm THEN
          data := event.accept.data;
          nlp$bm_get_message_length (data, data_length);
          IF data_length = #SIZE (udpaa_open_confirm_ind) THEN
            data_fragment [1].address := ^udpaa_open_confirm_ind;
            data_fragment [1].length := data_length;
            nlp$bm_flush_message (data_fragment, data, data_length, {ignore} local_status);
            IF (udpaa_open_confirm_ind.header.kind = nlc$udpaa_open_confirm_ind) AND
                  (udpaa_open_confirm_ind.header.length = data_length) THEN

              udp_connection^.state := nlc$udp_conn_open;
              nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
              IF global_socket <> NIL THEN

{ Update the status of the socket with respect to the device.

                traffic_pattern := 0;
                broadcast_enabled := FALSE;
                IF global_socket^.device_list [udp_connection^.device_id].status =
                      nlc$udp_device_await_confirm THEN
                  global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_open;
                  traffic_pattern := global_socket^.traffic_pattern;
                  broadcast_enabled := global_socket^.broadcast_enabled;
                  IF global_socket^.waiting_task_id.index > 0 THEN
                    pmp$ready_task (global_socket^.waiting_task_id, {ignore} local_status);
                  IFEND;
                IFEND;
                nlp$udp_free_exclusive_access (global_socket);
                IF (traffic_pattern > 0) OR (broadcast_enabled) THEN

{ Send the set socket options request to UDPAP.

                  udpaa_set_options_request.header.kind := nlc$udpaa_set_options_req;
                  udpaa_set_options_request.header.length := #SIZE (udpaa_set_options_request);
                  udpaa_set_options_request.traffic_pattern := traffic_pattern;
                  udpaa_set_options_request.broadcast_enabled := broadcast_enabled;
                  data_fragment [1].address := ^udpaa_set_options_request;
                  data_fragment [1].length := udpaa_set_options_request.header.length;

                  nlp$cc_send_data_fragments (cl_connection, data_fragment, {ignore} local_status);
                  local_status.normal := TRUE;
                IFEND;
              IFEND;
            ELSE
              process_protocol_error (cl_connection, udp_connection);
            IFEND;
          ELSE
            process_protocol_error (cl_connection, udp_connection);
          IFEND;
        ELSE
          nap$namve_system_error ({Recoverable_error=} TRUE, 'Received an unexpected CC accept event', NIL);
        IFEND;

      = nlc$cc_clear_to_send_event =

        IF (udp_connection^.send_queue <> NIL) AND (udp_connection^.send_queue^.task_id <> current_task_id)
              THEN
          activate_next_sender_task (udp_connection);
        IFEND;

      = nlc$cc_disconnect_event =
        IF event.disconnect.reason = nlc$cc_dr_normal_disconnect THEN
          data := event.disconnect.data;
          nlp$bm_get_message_length (event.disconnect.data, data_length);
          IF data_length = #SIZE (udpaa_disconnect_ind) THEN
            data_fragment [1].address := ^udpaa_disconnect_ind;
            data_fragment [1].length := #SIZE (udpaa_disconnect_ind);
            nlp$bm_flush_message (data_fragment, data, data_length, {ignore} local_status);
            IF udp_connection^.state = nlc$udp_conn_open THEN
              IF udpaa_disconnect_ind.header.kind = nlc$udpaa_release_ind THEN
                log_disconnect (udpaa_release, udp_connection^.global_socket_id, udp_connection^.device_id);
                process_disconnect_indication (cl_connection, udp_connection);
              ELSE { Unexpected UDPAA event kind
                nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected PDU kind in disconnect event.',
                      NIL);
              IFEND;
            ELSEIF udp_connection^.state = nlc$udp_conn_await_confirm THEN
              IF (udpaa_disconnect_ind.header.kind = nlc$udpaa_open_reject_ind) OR
                    (udpaa_disconnect_ind.header.kind = nlc$udpaa_release_ind) THEN
                deactivate_udp_layer (cl_connection, udp_connection);
                log_disconnect (udpaa_open_reject, udp_connection^.global_socket_id,
                      udp_connection^.device_id);

{ Update the socket device status in the global socket entry and
{ decrement the active device count if the state is closed.

                nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
                IF global_socket <> NIL THEN
                  global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
                  global_socket^.active_device_count := global_socket^.active_device_count - 1;
                  IF global_socket^.waiting_task_id.index <> 0 THEN
                    pmp$ready_task (global_socket^.waiting_task_id, {ignore} local_status);
                  IFEND;
                  nlp$udp_free_exclusive_access (global_socket);
                IFEND;
              ELSE { Unexpected UDPAA event kind
                nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected PDU kind in disconnect event.',
                      NIL);
              IFEND;
            ELSE { Unexpected state
              nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected UDP layer connection state.',
                    NIL);
            IFEND;
          ELSE { unexpected length
            nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected UDPAA disconnect PDU length', NIL);
          IFEND;
        ELSE { CC disconnect
          IF udp_connection^.state = nlc$udp_conn_open THEN
            log_disconnect (udpaa_cc_disconnect, udp_connection^.global_socket_id, udp_connection^.device_id);
            process_disconnect_indication (cl_connection, udp_connection);
          ELSEIF udp_connection^.state = nlc$udp_conn_await_confirm THEN
            deactivate_udp_layer (cl_connection, udp_connection);
            log_disconnect (udpaa_open_reject, udp_connection^.global_socket_id, udp_connection^.device_id);

{ Update the socket device status in the global socket entry and decrement the active device count
{ if the state is closed.

            nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
            IF global_socket <> NIL THEN
              global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
              global_socket^.active_device_count := global_socket^.active_device_count - 1;
              IF global_socket^.waiting_task_id.index <> 0 THEN
                pmp$ready_task (global_socket^.waiting_task_id, {ignore} local_status);
              IFEND;
              nlp$udp_free_exclusive_access (global_socket);
            IFEND;
          ELSE { Unexpected state
            nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected UDP layer connection state', NIL);
          IFEND;
        IFEND;
      ELSE { Invalid event
        nap$namve_system_error ({Recoverable_error=} TRUE, 'Unexpected CC event', NIL);
      CASEND;
    ELSE { Layer inactive
      nap$namve_system_error ({Recoverable_error=} TRUE,
            'A CC event was delivered when UDP socket layer is inactive.', NIL);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_get_bound_addresses
    (    global_socket_id: nlt$udp_global_socket_id;
     VAR local_addresses: array [1 .. * ] of nat$sk_ip_address;
     VAR count: nlt$device_count;
     VAR status: ost$status);

    VAR
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket;

    count := 0;
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      IF global_socket^.status = nlc$udp_global_socket_open THEN

      /get_addresses/
        FOR device_id := 1 TO UPPERBOUND (global_socket^.device_list) DO
          IF global_socket^.device_list [device_id].status <> nlc$udp_device_closed THEN
            count := count + 1;
            local_addresses [count] := global_socket^.device_list [device_id].ip_address;
            IF count = UPPERBOUND (local_addresses) THEN
              EXIT /get_addresses/;
            IFEND;
          IFEND;
        FOREND /get_addresses/;
      ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN

{ The socket has been terminated via application management.

        osp$set_status_condition (nae$sk_socket_terminated, status);
      ELSE { unexpected status
        nap$namve_system_error ({Recoverable_error=} TRUE, 'Encountered unexpected global socket status',
              NIL);
        osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'GET BOUND ADDRESSES', status);
      IFEND;
      nlp$udp_free_exclusive_access (global_socket);
    ELSE { global_socket = NIL

{ The socket is assumed to be terminated via application management.

      osp$set_status_condition (nae$sk_socket_terminated, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_get_socket_status
    (    global_socket_id: nlt$udp_global_socket_id;
     VAR clear_to_send: boolean;
     VAR data_pending_receive: integer;
     VAR status: ost$status);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      connection_exists: boolean,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      layer_active: boolean,
      outbound_capacity: nat$data_length,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    clear_to_send := FALSE;
    data_pending_receive := 0;
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      IF global_socket^.status = nlc$udp_global_socket_open THEN
        socket_device_list := ^global_socket^.device_list;
        PUSH connection_list: [1 .. UPPERBOUND (socket_device_list^)];
        IF global_socket^.receive_wait_queue = NIL THEN

{ Scan the devices for queued messages.

          FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
            IF socket_device_list^ [device_id].status = nlc$udp_device_open THEN
              IF (data_pending_receive = 0) AND (socket_device_list^ [device_id].received_messages <> NIL) AND
                    (socket_device_list^ [device_id].receiver_task = NIL) AND
                    (socket_device_list^ [device_id].received_messages^.end_of_message) THEN
                data_pending_receive := socket_device_list^ [device_id].received_messages^.data_length;

                IF data_pending_receive = 0 THEN { These three lines are intended to be temporary and be }
                  data_pending_receive := 1; { replaced when this interface is modified to indicate a that }
                IFEND; {datagram is available even if it is zero length. }

              IFEND;
              connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
            ELSE
              connection_list^ [device_id] := nac$null_connection_id;
            IFEND;
          FOREND;
        ELSE { receive_queue <> NIL

{ Build a list of connection ids.

          FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
            IF socket_device_list^ [device_id].status = nlc$udp_device_open THEN
              connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
            ELSE
              connection_list^ [device_id] := nac$null_connection_id;
            IFEND;
          FOREND;
        IFEND;

        nlp$udp_free_exclusive_access (global_socket);

{ Check the outbound capacity on each active connection.

      /check_connection_capacity/
        FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
          IF connection_list^ [device_id] <> nac$null_connection_id THEN
            nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
            IF cl_connection <> NIL THEN
              nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
              IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) AND
                    (udp_connection^.send_queue = NIL) THEN
                nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                IF outbound_capacity <= 0 THEN
                  nlp$cc_receive_data (cl_connection);
                  nlp$osi_get_outbound_capacity (cl_connection, outbound_capacity);
                IFEND;
                IF outbound_capacity > 0 THEN
                  clear_to_send := TRUE;
                  nlp$cl_release_exclusive_access (cl_connection);
                  EXIT /check_connection_capacity/;
                IFEND;
              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            IFEND;
          IFEND;
        FOREND /check_connection_capacity/;
      ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN

{ The socket has been terminated via application management.

        osp$set_status_condition (nae$sk_socket_terminated, status);
        nlp$udp_free_exclusive_access (global_socket);
      ELSE { unexpected status
        nap$namve_system_error ({Recoverable_error=} TRUE, 'Encountered unexpected global socket status',
              NIL);
        osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'GET SOCKET STATUS', status);
        nlp$udp_free_exclusive_access (global_socket);
      IFEND;
    ELSE { global_socket = NIL

{ The socket has been terminated via application management.

      osp$set_status_condition (nae$sk_socket_terminated, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_initialize;

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

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

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

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

  PROCEDURE [XDCL] nlp$udp_offer_socket
    (    global_socket_id: nlt$udp_global_socket_id;
     VAR status: ost$status);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      layer_active: boolean,
      local_socket_id: nat$sk_socket_identifier,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    status.normal := TRUE;
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      IF global_socket^.status = nlc$udp_global_socket_open THEN
        local_socket_id := global_socket^.local_socket_id;
        IF global_socket^.receive_wait_queue <> NIL THEN
          osp$set_status_condition (nae$sk_io_pending, status);
          osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
        ELSE
          socket_device_list := ^global_socket^.device_list;
          PUSH connection_list: [1 .. UPPERBOUND (socket_device_list^)];

        /check_for_receivers/
          FOR device_id := 1 TO UPPERBOUND (socket_device_list^) DO
            IF socket_device_list^ [device_id].status = nlc$udp_device_open THEN
              connection_list^ [device_id] := socket_device_list^ [device_id].connection_id;
              IF socket_device_list^ [device_id].receiver_task <> NIL THEN
                osp$set_status_condition (nae$sk_io_pending, status);
                osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
                EXIT /check_for_receivers/;
              IFEND;
            ELSE
              connection_list^ [device_id] := nac$null_connection_id;
            IFEND;
          FOREND /check_for_receivers/;
        IFEND;

        nlp$udp_free_exclusive_access (global_socket);

        IF status.normal THEN

        /check_for_senders/
          FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
            IF connection_list^ [device_id] <> nac$null_connection_id THEN
              nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
              IF cl_connection <> NIL THEN
                nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
                IF (layer_active) AND (udp_connection^.send_queue <> NIL) THEN
                  osp$set_status_condition (nae$sk_io_pending, status);
                  osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                        status);
                  nlp$cl_release_exclusive_access (cl_connection);
                  EXIT /check_for_senders/;
                IFEND;
                nlp$cl_release_exclusive_access (cl_connection);
              IFEND;
            IFEND;
          FOREND /check_for_senders/;
        IFEND;
        IF status.normal THEN

{ No io active , update the status of the global socket.

          nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
          IF global_socket <> NIL THEN
            IF global_socket^.status = nlc$udp_global_socket_open THEN
              global_socket^.status := nlc$udp_global_socket_offered;
            ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN

{ The socket has been terminated via application management.

              osp$set_status_condition (nae$sk_socket_terminated, status);
            ELSE { unexpected status
              nap$namve_system_error ({Recoverable_error=} TRUE,
                    'Encountered unexpected global socket status', NIL);
              osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'OFFER SOCKET', status);
            IFEND;
            nlp$udp_free_exclusive_access (global_socket);
          ELSE { global_socket = NIL

{ The socket has been terminated via application management.

            osp$set_status_condition (nae$sk_socket_terminated, status);
          IFEND;
        IFEND;
      ELSE { global socket terminated

{ The socket is assumed to be closed by application mgmt request.

        osp$set_status_condition (nae$sk_socket_terminated, status);
        nlp$udp_free_exclusive_access (global_socket);
      IFEND;
    ELSE { global_socket = NIL
      osp$set_status_condition (nae$sk_socket_terminated, status);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_receive_data
    (    global_socket_id: nlt$udp_global_socket_id;
         time_stamp: ost$free_running_clock;
         selection_criteria: nat$sk_socket_address;
         user_cache_enabled: boolean;
         interface_mode: nat$sk_interface_mode;
         interface_timeout: nat$wait_time;
         data: nat$data_fragments;
     VAR foreign_socket: nat$sk_socket_address;
     VAR local_ip_address: nat$sk_ip_address;
     VAR data_length: integer;
     VAR status: ost$status);

?? NEWTITLE := 'terminate_receive', EJECT ??

    PROCEDURE terminate_receive
      (    condition: pmt$condition;
           condition_descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      VAR
        delete_global_socket: boolean;

      nap$condition_handler_trace (condition, save_area);
      delete_global_socket := FALSE;
      IF received_data_length > 0 THEN

{ IF receive has been initiated, then try and complete the receive
{ by data queued on the global socket. However if there is no data
{ queued, set the discard data flag on the socket device entry and
{ terminate receive.

        nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
        IF global_socket <> NIL THEN
          IF global_socket^.status = nlc$udp_global_socket_open THEN
            IF global_socket^.device_list [selected_device].status = nlc$udp_device_open THEN
              IF global_socket^.device_list [selected_device].received_messages <> NIL THEN
                received_message := global_socket^.device_list [selected_device].received_messages;
                IF NOT received_message^.abort_receive THEN
                  IF received_message^.end_of_message THEN
                    receive_data (global_socket, selected_device, buffers_freed);
                    IF activity_status.status.normal THEN
                      foreign_socket := foreign_address;
                      local_ip_address := local_address;
                      data_length := received_data_length;
                    ELSE
                      status := activity_status.status;
                    IFEND;
                  ELSE { only partial fragment queued
                    nlp$bm_release_message (received_message^.data);
                    global_socket^.device_list [selected_device].received_messages := NIL;
                    return_received_message_entry (global_socket, received_message);
                    global_socket^.device_list [selected_device].discard_data := TRUE;
                    osp$set_status_condition (nae$sk_no_data_available, status);
                    osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                          status);
                  IFEND;
                ELSE { abort receive
                  global_socket^.device_list [selected_device].receiver_task := NIL;
                  global_socket^.device_list [selected_device].received_messages := NIL;
                  return_received_message_entry (global_socket, received_message);
                  return_receiver_task_entry (global_socket, receiver_task);
                  osp$set_status_condition (nae$sk_no_data_available, status);
                  osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                        status);
                IFEND;
              ELSE { received_message = NIL

{ Abort receive.

                global_socket^.device_list [selected_device].discard_data := TRUE;
                osp$set_status_condition (nae$sk_no_data_available, status);
                osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
              IFEND;
            ELSE { device closed
              receiver_task := global_socket^.receive_wait_queue;
              global_socket^.receive_wait_queue := receiver_task^.next_entry;
              return_receiver_task_entry (global_socket, receiver_task);
              osp$set_status_condition (nae$sk_no_data_available, status);
              osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
            IFEND;
            IF global_socket^.receive_wait_queue <> NIL THEN
              pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
            IFEND;
          ELSE { global_socket^.status <> nlc$udp_global_socket_open
            receiver_task := global_socket^.receive_wait_queue;
            global_socket^.receive_wait_queue := receiver_task^.next_entry;
            FREE receiver_task IN nav$network_paged_heap^;
            IF (global_socket^.status = nlc$udp_global_socket_closed) OR
                  (global_socket^.status = nlc$udp_global_socket_term) THEN
              delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                    (global_socket^.active_device_count = 0);
            IFEND;
            osp$set_status_condition (nae$sk_unknown_socket, status);
            osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
          IFEND;
          nlp$udp_deactivate_receiver (global_socket^.active_receiver);
          nlp$udp_free_exclusive_access (global_socket);
          IF delete_global_socket THEN
            nlp$udp_delete_global_socket (global_socket_id);
          IFEND;
        IFEND;
      ELSE { received_data_length = 0
        nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
        IF global_socket <> NIL THEN
          previous_receiver_task := ^global_socket^.receive_wait_queue;
          WHILE (previous_receiver_task^ <> NIL) AND (previous_receiver_task^^.task_id <> current_task_id) DO
            previous_receiver_task := ^previous_receiver_task^^.next_entry;
          WHILEND;
          receiver_task := previous_receiver_task^;
          previous_receiver_task^ := receiver_task^.next_entry;
          return_receiver_task_entry (global_socket, receiver_task);
          IF (global_socket^.status = nlc$udp_global_socket_closed) OR
                (global_socket^.status = nlc$udp_global_socket_term) THEN
            delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                  (global_socket^.active_device_count = 0);
          IFEND;
          osp$set_status_condition (nae$sk_interface_timeout, status);
          osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
          IF current_receiver_active THEN
            nlp$udp_deactivate_receiver (global_socket^.active_receiver);
          IFEND;
          nlp$udp_free_exclusive_access (global_socket);
          IF delete_global_socket THEN
            nlp$udp_delete_global_socket (global_socket_id);
          IFEND;
        IFEND;
      IFEND;
    PROCEND terminate_receive;
?? OLDTITLE, EJECT ??

    VAR
      activity_status: ost$activity_status,
      another_receiver_active: boolean,
      buffer_length: nat$data_length,
      buffers_freed: nat$data_length,
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      current_receiver_active: boolean,
      current_task_id: ost$global_task_id,
      current_time: ost$free_running_clock,
      delete_global_socket: boolean,
      device_id: nlt$device_identifier,
      end_time: integer,
      foreign_address: nat$sk_socket_address,
      global_socket: ^nlt$udp_global_socket,
      i: integer,
      ignore_status: ost$status,
      layer_active: boolean,
      local_address: nat$sk_ip_address,
      local_socket_id: nat$sk_socket_identifier,
      previous_receiver_task: ^^nlt$udp_receiver_task,
      receive_buffer: ^nat$data_fragments,
      received_message: ^nlt$udp_received_message,
      received_data_length: integer,
      receiver_task: ^nlt$udp_receiver_task,
      remaining_buffer_length: integer,
      remaining_time: integer,
      restart_receive: boolean,
      selected_device: nlt$device_identifier,
      socket_inventory: ^nlt$udp_socket_inventory,
      socket_device_list: ^nlt$udp_socket_device_list,
      udp_connection: ^nlt$udp_socket_layer;

    status.normal := TRUE;
    activity_status.complete := FALSE;
    activity_status.status.normal := TRUE;
    received_data_length := 0;

    pmp$get_executing_task_gtid (current_task_id);
    nlp$al_get_data_length (data, buffer_length);
    PUSH socket_inventory: [1 .. UPPERBOUND (nlv$configured_network_devices.network_device_list^)];
    PUSH receive_buffer: [1 .. UPPERBOUND (data)];
    current_receiver_active := FALSE;
    another_receiver_active := FALSE;
    delete_global_socket := FALSE;

    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF global_socket <> NIL THEN
      local_socket_id := global_socket^.local_socket_id;
      #SPOIL (local_socket_id);
      socket_device_list := ^global_socket^.device_list;
      IF global_socket^.status = nlc$udp_global_socket_open THEN

{ Save initial buffer attributes.

        receive_buffer^ := data;
        IF global_socket^.time_stamp = time_stamp THEN
          nlp$udp_activate_receiver (global_socket^.active_receiver, another_receiver_active);
          current_receiver_active := NOT another_receiver_active;

          IF current_receiver_active THEN

{ Receive data queued on the global socket.

            remaining_buffer_length := buffer_length;
            scan_global_socket_for_data (global_socket, receive_buffer^, remaining_buffer_length,
                  received_data_length, selection_criteria, foreign_address, local_address, socket_inventory^,
                  selected_device, activity_status);
            IF activity_status.complete THEN
              IF activity_status.status.normal THEN
                data_length := received_data_length;
                foreign_socket := foreign_address;
                local_ip_address := local_address;
              ELSE
                status := activity_status.status;
              IFEND;
            ELSE { Not activity complete
              IF received_data_length > 0 THEN

{ Queue the current task as the receiver on the socket device.

                initialize_receiver_task_entry (global_socket, current_task_id, nlc$udp_receive_data, ^data,
                      buffer_length, receive_buffer, remaining_buffer_length, ^received_data_length,
                      selection_criteria, ^foreign_address, ^local_address, ^selected_device,
                      socket_device_list^ [selected_device].connection_id, interface_mode, ^activity_status,
                      receiver_task);
                socket_device_list^ [selected_device].receiver_task := receiver_task;
              ELSE {  received_data_length = 0
                IF interface_mode = nac$sk_blocking_mode THEN

{ Queue the current task on the receive wait queue (with the receiving connection =
{ nac$null_connection_id)

                  initialize_receiver_task_entry (global_socket, current_task_id, nlc$udp_receive_data, ^data,
                        buffer_length, receive_buffer, buffer_length, ^received_data_length,
                        selection_criteria, ^foreign_address, ^local_address, ^selected_device,
                        nac$null_connection_id, interface_mode, ^activity_status, receiver_task);
                  receiver_task^.next_entry := global_socket^.receive_wait_queue;
                  global_socket^.receive_wait_queue := receiver_task;
                ELSE { Non-blocking mode
                  activity_status.complete := TRUE;
                  osp$set_status_condition (nae$sk_no_data_available, status);
                  osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                        status);
                IFEND;
              IFEND;
            IFEND;
            IF activity_status.complete THEN
              nlp$udp_deactivate_receiver (global_socket^.active_receiver);
            IFEND;
            nlp$udp_free_exclusive_access (global_socket);
            IF activity_status.complete THEN
              update_inventory (socket_inventory);
            ELSE

{ Access the channel connections to update the inventory report. Scan the channel
{ connection over which receive was initiated to cause the data to be pulled
{ up to the global socket. If data is available, the receive will complete.

              scan_connections_for_data (socket_inventory, receiver_task);
              IF activity_status.complete THEN
                IF activity_status.status.normal THEN
                  data_length := received_data_length;
                  foreign_socket := foreign_address;
                  local_ip_address := local_address;
                ELSE
                  status := activity_status.status;
                IFEND;
              IFEND;
            IFEND;
          ELSE { another_receiver_active

{ Other receivers are active.

            IF interface_mode = nac$sk_blocking_mode THEN
              initialize_receiver_task_entry (global_socket, current_task_id, nlc$udp_receive_data, ^data,
                    buffer_length, receive_buffer, buffer_length, ^received_data_length, selection_criteria,
                    ^foreign_address, ^local_address, ^selected_device, nac$null_connection_id,
                    interface_mode, ^activity_status, receiver_task);

{ Queue the task at the end of the receive queue.

              previous_receiver_task := ^global_socket^.receive_wait_queue;
              WHILE previous_receiver_task^ <> NIL DO
                previous_receiver_task := ^previous_receiver_task^^.next_entry;
              WHILEND;
              previous_receiver_task^ := receiver_task;
            ELSE { non-blocking mode
              activity_status.complete := TRUE;
              osp$set_status_condition (nae$sk_receive_in_progress, status);
              osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
            IFEND;
            nlp$udp_free_exclusive_access (global_socket);
          IFEND;

          IF NOT activity_status.complete THEN
            #SPOIL (receiver_task);
            #SPOIL (current_task_id);

            osp$establish_block_exit_hndlr (^terminate_receive);
            IF interface_mode = nac$sk_non_blocking_mode THEN
              remaining_time := 500;
            ELSE { interface_mode = nac$sk_blocking_mode
              remaining_time := interface_timeout;
            IFEND;
            end_time := #FREE_RUNNING_CLOCK (0) + remaining_time * 1000;
            restart_receive := FALSE;

            REPEAT
              #SPOIL (received_data_length);
              #SPOIL (current_receiver_active);
              IF NOT restart_receive THEN
                pmp$wait (remaining_time, 0);
              ELSE
                restart_receive := FALSE;
              IFEND;
              current_time := #FREE_RUNNING_CLOCK (0);
              IF current_time < end_time THEN
                remaining_time := (end_time - current_time) DIV 1000;
              ELSE
                remaining_time := 0;
              IFEND;
              #SPOIL (remaining_time);

              IF received_data_length > 0 THEN

{ Receive had been initiated. Check for data on the global socket.

                buffers_freed := 0;
                nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
                IF global_socket <> NIL THEN
                  IF global_socket^.status = nlc$udp_global_socket_open THEN
                    IF global_socket^.device_list [selected_device].status = nlc$udp_device_open THEN
                      IF global_socket^.device_list [selected_device].received_messages <> NIL THEN
                        receive_data (global_socket, selected_device, buffers_freed);
                        IF activity_status.complete THEN
                          IF activity_status.status.normal THEN
                            foreign_socket := foreign_address;
                            local_ip_address := local_address;
                            data_length := received_data_length;
                          ELSE
                            status := activity_status.status;
                          IFEND;
                        ELSE { NOT activity_status.complete
                          IF received_data_length > 0 THEN

{ Extend wait time as remaining data should arrive soon.

                            IF remaining_time = 0 THEN
                              remaining_time := 500;
                            IFEND;
                          ELSE { received_data_length = 0

{ Receive has been aborted via a clear send request. This will happen
{ only for a blocked receive. A non blocked receiver is terminated
{ and marked as complete (with an abnormal status).

                            IF remaining_time > 0 THEN
                              restart_receive := TRUE;
                            ELSE { remaining_time = 0 THEN

{ Terminate receive.

                              global_socket^.receive_wait_queue := receiver_task^.next_entry;
                              return_receiver_task_entry (global_socket, receiver_task);
                              nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                              IF global_socket^.receive_wait_queue <> NIL THEN
                                pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                              IFEND;
                              osp$set_status_condition (nae$sk_interface_timeout, status);
                              osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                    TRUE, status);
                              activity_status.complete := TRUE;
                            IFEND;
                          IFEND;
                        IFEND;
                      IFEND;
                    ELSE { device status <> open

{ Terminate receive.

                      global_socket^.receive_wait_queue := receiver_task^.next_entry;
                      return_receiver_task_entry (global_socket, receiver_task);
                      nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                      IF global_socket^.receive_wait_queue <> NIL THEN
                        pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                      IFEND;
                      osp$set_status_condition (nae$sk_interface_timeout, status);
                      osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                            status);
                      activity_status.complete := TRUE;
                    IFEND;
                    nlp$udp_free_exclusive_access (global_socket);
                  ELSE { global_socket^.status <> nlc$udp_global_socket_open
                    receiver_task := global_socket^.receive_wait_queue;
                    global_socket^.receive_wait_queue := receiver_task^.next_entry;
                    return_receiver_task_entry (global_socket, receiver_task);
                    nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                    delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                          (global_socket^.active_device_count = 0);
                    nlp$udp_free_exclusive_access (global_socket);
                    IF delete_global_socket THEN
                      nlp$udp_delete_global_socket (global_socket_id);
                    IFEND;
                    activity_status.complete := TRUE;
                  IFEND;
                ELSE {global_socket = NIL (this should not happen)
                  osp$set_status_condition (nae$sk_unknown_socket, status);
                IFEND;
                IF (buffers_freed > 0) AND (activity_status.complete) THEN
                  nlp$cl_get_exclusive_via_cid (receiver_task^.connection_id, connection_exists,
                        cl_connection);
                  IF cl_connection <> NIL THEN
                    nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active,
                          udp_connection);
                    IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) THEN
                      udp_connection^.inventory_report := udp_connection^.inventory_report - buffers_freed;
                      nlp$cc_report_undelivered_data (cl_connection, udp_connection^.inventory_report);
                    IFEND;
                    nlp$cl_release_exclusive_access (cl_connection);
                  IFEND;
                ELSEIF (NOT restart_receive) AND (NOT activity_status.complete) THEN
                  nlp$cl_get_exclusive_via_cid (receiver_task^.connection_id, connection_exists,
                        cl_connection);
                  IF cl_connection <> NIL THEN
                    nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active,
                          udp_connection);
                  IFEND;
                  IF (cl_connection <> NIL) AND (layer_active) AND
                        (udp_connection^.state = nlc$udp_conn_open) THEN
                    IF buffers_freed > 0 THEN
                      udp_connection^.inventory_report := udp_connection^.inventory_report - buffers_freed;
                      nlp$cc_report_undelivered_data (cl_connection, udp_connection^.inventory_report);
                    IFEND;
                    nlp$cc_receive_data (cl_connection);
                    IF activity_status.complete THEN
                      IF activity_status.status.normal THEN
                        foreign_socket := foreign_address;
                        local_ip_address := local_address;
                        data_length := received_data_length;
                      ELSE
                        status := activity_status.status;
                      IFEND;
                    ELSE { NOT activity_status.complete
                      IF received_data_length > 0 THEN

{ Extend wait time as remaining data should arrive soon.

                        IF remaining_time = 0 THEN
                          remaining_time := 500;
                        IFEND;
                      ELSE { received_data_length = 0

{ Receive has been aborted via a clear send request. This will happen
{ only for a blocked receive. A non blocked receiver is terminated
{ and marked as complete (with an abnormal status).

                        IF remaining_time = 0 THEN
                          nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
                          IF global_socket <> NIL THEN

{ Find the receiver task. It must be at the head of the receive wait queue.

                            receiver_task := global_socket^.receive_wait_queue;
                            global_socket^.receive_wait_queue := receiver_task^.next_entry;
                            return_receiver_task_entry (global_socket, receiver_task);
                            nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                            IF global_socket^.receive_wait_queue <> NIL THEN
                              pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                            IFEND;
                            nlp$udp_free_exclusive_access (global_socket);
                          IFEND;
                          osp$set_status_condition (nae$sk_interface_timeout, status);
                          osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                TRUE, status);
                          activity_status.complete := TRUE;
                        ELSE { remaining_time > 0
                          restart_receive := TRUE;
                        IFEND;
                      IFEND;
                    IFEND;
                    nlp$cl_release_exclusive_access (cl_connection);
                  ELSE { layer inactive or udp_connection not open or cl_connection = nil
                    IF cl_connection <> NIL THEN
                      nlp$cl_release_exclusive_access (cl_connection);
                    IFEND;

{ Terminate receive if remaining_time = 0 or if the receiver is non blocking.
{ Otherwise, restart receive.

                    IF (interface_mode = nac$sk_non_blocking_mode) OR (remaining_time = 0) THEN
                      nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
                      IF global_socket <> NIL THEN

{ Find the receiver task. It must be at the head of the queue.

                        receiver_task := global_socket^.receive_wait_queue;
                        global_socket^.receive_wait_queue := receiver_task^.next_entry;
                        return_receiver_task_entry (global_socket, receiver_task);
                        IF (global_socket^.status = nlc$udp_global_socket_closed) OR
                              (global_socket^.status = nlc$udp_global_socket_term) THEN
                          delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                                (global_socket^.active_device_count = 0);
                        IFEND;
                        nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                        nlp$udp_free_exclusive_access (global_socket);
                        IF delete_global_socket THEN
                          nlp$udp_delete_global_socket (global_socket_id);
                        IFEND;
                      IFEND;
                      IF remaining_time = 0 THEN
                        osp$set_status_condition (nae$sk_interface_timeout, status);
                      ELSE
                        osp$set_status_condition (nae$sk_no_data_available, status);
                      IFEND;
                      osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                            status);
                      activity_status.complete := TRUE;
                    ELSE { blocking mode and remaining_time > 0
                      restart_receive := TRUE;
                      received_data_length := 0;
                    IFEND;
                  IFEND;
                IFEND;
              ELSE { received_data_length = 0

{ Receive has not been initiated.

                nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
                IF global_socket <> NIL THEN
                  IF global_socket^.status = nlc$udp_global_socket_open THEN
                    IF global_socket^.receive_wait_queue^.task_id = current_task_id THEN

{ Current task is at the head of the receive wait queue and is the next
{ receiving task.

                      IF remaining_time > 0 THEN
                        IF NOT current_receiver_active THEN
                          nlp$udp_activate_receiver (global_socket^.active_receiver, another_receiver_active);
                          current_receiver_active := NOT another_receiver_active;
                        IFEND;
                        IF current_receiver_active THEN
                          scan_global_socket_for_data (global_socket, receiver_task^.receive_buffer^,
                                receiver_task^.buffer_length, receiver_task^.received_data_length^,
                                receiver_task^.selection_criteria, receiver_task^.foreign_socket^,
                                receiver_task^.local_ip_address^, socket_inventory^, selected_device,
                                activity_status);
                          IF activity_status.complete THEN
                            IF activity_status.status.normal THEN
                              foreign_socket := foreign_address;
                              local_ip_address := local_address;
                              data_length := received_data_length;
                            ELSE
                              status := activity_status.status;
                            IFEND;
                            global_socket^.receive_wait_queue := receiver_task^.next_entry;
                            return_receiver_task_entry (global_socket, receiver_task);
                            nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                            IF global_socket^.receive_wait_queue <> NIL THEN
                              pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                            IFEND;
                            nlp$udp_free_exclusive_access (global_socket);
                            update_inventory (socket_inventory);
                          ELSE { scan connections for data
                            IF received_data_length > 0 THEN

{ Receive has been initiated. Queue the receiver on the socket device.

                              receiver_task^.connection_id := global_socket^.device_list [selected_device].
                                    connection_id;
                              global_socket^.receive_wait_queue := receiver_task^.next_entry;
                              receiver_task^.next_entry := NIL;
                              global_socket^.device_list [selected_device].receiver_task := receiver_task;
                            IFEND;
                            nlp$udp_free_exclusive_access (global_socket);
                            scan_connections_for_data (socket_inventory, receiver_task);
                            IF activity_status.complete THEN
                              IF activity_status.status.normal THEN
                                foreign_socket := foreign_address;
                                local_ip_address := local_address;
                                data_length := received_data_length;
                              ELSE
                                status := activity_status.status;
                              IFEND;
                            IFEND;
                          IFEND;
                        ELSE { another receiver is active
                          nlp$udp_free_exclusive_access (global_socket);
                        IFEND;
                      ELSE { remaining_time = 0

{ Terminate receive.

                        global_socket^.receive_wait_queue := receiver_task^.next_entry;
                        return_receiver_task_entry (global_socket, receiver_task);
                        IF current_receiver_active THEN
                          nlp$udp_deactivate_receiver (global_socket^.active_receiver);
                        IFEND;
                        IF global_socket^.receive_wait_queue <> NIL THEN
                          pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
                        IFEND;
                        osp$set_status_condition (nae$sk_interface_timeout, status);
                        osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                              status);
                        activity_status.complete := TRUE;
                        nlp$udp_free_exclusive_access (global_socket);
                      IFEND;
                    ELSE { current task is not at the head of the wait queue
                      IF remaining_time = 0 THEN

{ Find the receiver task entry for the current task and terminate receive.

                        previous_receiver_task := ^global_socket^.receive_wait_queue;
                        WHILE (previous_receiver_task^ <> NIL) AND (previous_receiver_task^^.task_id <>
                              current_task_id) DO
                          previous_receiver_task := ^previous_receiver_task^^.next_entry;
                        WHILEND;
                        receiver_task := previous_receiver_task^;
                        IF receiver_task <> NIL THEN

{ Dequeue the receiver task and return the entry.

                          previous_receiver_task^ := receiver_task^.next_entry;
                          return_receiver_task_entry (global_socket, receiver_task);
                          osp$set_status_condition (nae$sk_interface_timeout, status);
                          osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                TRUE, status);
                          activity_status.complete := TRUE;
                        IFEND;
                      IFEND;
                      nlp$udp_free_exclusive_access (global_socket);
                    IFEND;
                  ELSE { global_socket not open
                    previous_receiver_task := ^global_socket^.receive_wait_queue;
                    WHILE (previous_receiver_task^ <> NIL) AND (previous_receiver_task^^.task_id <>
                          current_task_id) DO
                      previous_receiver_task := ^previous_receiver_task^^.next_entry;
                    WHILEND;
                    receiver_task := previous_receiver_task^;
                    IF receiver_task <> NIL THEN

{ Dequeue the receiver task and return the entry.

                      previous_receiver_task^ := receiver_task^.next_entry;
                      FREE receiver_task IN nav$network_paged_heap^;
                    IFEND;
                    IF global_socket^.status = nlc$udp_global_socket_closed THEN
                      osp$set_status_condition (nae$sk_unknown_socket, status);
                    ELSE
                      osp$set_status_condition (nae$sk_socket_terminated, status);
                    IFEND;
                    osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                          status);
                    activity_status.complete := TRUE;
                    delete_global_socket := (global_socket^.receive_wait_queue = NIL) AND
                          (global_socket^.active_device_count = 0);
                    nlp$udp_free_exclusive_access (global_socket);
                    IF delete_global_socket THEN
                      nlp$udp_delete_global_socket (global_socket_id);
                    IFEND;
                  IFEND;
                ELSE { global_socket = NIL
                  osp$set_status_condition (nae$sk_unknown_socket, status);
                  osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                        status);
                  activity_status.complete := TRUE;
                IFEND;
              IFEND;
            UNTIL (activity_status.complete) OR (NOT status.normal);
            osp$disestablish_cond_handler;
          IFEND;

          IF status.normal AND user_cache_enabled THEN
            nlv$udp_local_routing_cache.last_receive.source_ip_address := foreign_socket.ip_address;
            nlv$udp_local_routing_cache.last_receive.destination_ip_address := local_ip_address;
            nlv$udp_local_routing_cache.last_receive.device_id := selected_device;
          IFEND;
        ELSE { time stamps do not match
          osp$set_status_condition (nae$sk_unknown_socket, status);
          osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
                status);
          nlp$udp_free_exclusive_access (global_socket);
        IFEND;
      ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN
        osp$set_status_condition (nae$sk_socket_terminated, status);
        osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
              status);
        nlp$udp_free_exclusive_access (global_socket);
      ELSE { socket closed
        osp$set_status_condition (nae$sk_unknown_socket, status);
        osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
              status);
        nlp$udp_free_exclusive_access (global_socket);
      IFEND;
    ELSE { global_socket = NIL
      osp$set_status_condition (nae$sk_unknown_socket, status);
    IFEND;

  PROCEND nlp$udp_receive_data;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$udp_remove_clear_to_send', EJECT ??
*copy nlh$udp_remove_clear_to_send

  PROCEDURE [XDCL] nlp$udp_remove_clear_to_send
    (    global_socket_id: nlt$udp_global_socket_id);

    VAR
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_list: ^array [1 .. * ] of nat$connection_id,
      current_task_id: ost$global_task_id,
      device_id: nlt$device_identifier,
      global_socket: ^nlt$udp_global_socket,
      layer_active: boolean,
      previous_sender_task: ^^nlt$udp_sender_task,
      sender_task: ^nlt$udp_sender_task,
      udp_connection: ^nlt$udp_socket_layer;

    pmp$get_executing_task_gtid (current_task_id);
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF (global_socket <> NIL) THEN
      IF (global_socket^.status = nlc$udp_global_socket_open) THEN

{ Build a list of all active connection ids.

        PUSH connection_list: [1 .. UPPERBOUND (global_socket^.device_list)];
        FOR device_id := 1 TO UPPERBOUND (global_socket^.device_list) DO
          IF global_socket^.device_list [device_id].status = nlc$udp_device_open THEN
            connection_list^ [device_id] := global_socket^.device_list [device_id].connection_id;
          ELSE
            connection_list^ [device_id] := nac$null_connection_id;
          IFEND;
        FOREND;

        nlp$udp_free_exclusive_access (global_socket);

{ Dequeue the current task from the send queue in each active connection.

        FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
          IF connection_list^ [device_id] <> nac$null_connection_id THEN
            nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
            IF cl_connection <> NIL THEN
              nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
              IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) AND
                    (udp_connection^.send_queue <> NIL) THEN
                previous_sender_task := ^udp_connection^.send_queue;
                sender_task := udp_connection^.send_queue;
                WHILE (sender_task <> NIL) AND (sender_task^.task_id <> current_task_id) DO
                  previous_sender_task := ^sender_task^.next_entry;
                  sender_task := sender_task^.next_entry;
                WHILEND;
                IF sender_task <> NIL THEN
                  previous_sender_task^ := sender_task^.next_entry;
                  return_sender_task_entry (udp_connection, sender_task);
                IFEND;
              IFEND;
              nlp$cl_release_exclusive_access (cl_connection);
            IFEND;
          IFEND;
        FOREND;
      ELSE
        nlp$udp_free_exclusive_access (global_socket);
      IFEND;
    IFEND;

  PROCEND nlp$udp_remove_clear_to_send;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$udp_remove_data_available', EJECT ??
*copy nlh$udp_remove_data_available

  PROCEDURE [XDCL] nlp$udp_remove_data_available
    (    global_socket_id: nlt$udp_global_socket_id);

    VAR
      current_task_id: ost$global_task_id,
      global_socket: ^nlt$udp_global_socket,
      ignore_status: ost$status,
      previous_receiver_task: ^^nlt$udp_receiver_task,
      receiver_task: ^nlt$udp_receiver_task;

    pmp$get_executing_task_gtid (current_task_id);
    nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
    IF (global_socket <> NIL) THEN
      IF (global_socket^.status = nlc$udp_global_socket_open) AND
            (global_socket^.receive_wait_queue <> NIL) THEN
        IF global_socket^.receive_wait_queue^.task_id = current_task_id THEN

{ Task is at the head of the queue.

          receiver_task := global_socket^.receive_wait_queue;
          global_socket^.receive_wait_queue := receiver_task^.next_entry;
          IF receiver_task^.receiver_active THEN
            nlp$udp_deactivate_receiver (global_socket^.active_receiver);
          IFEND;
          IF global_socket^.receive_wait_queue <> NIL THEN
            pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
          IFEND;
          return_receiver_task_entry (global_socket, receiver_task);
        ELSE { task not at the head of the queue
          previous_receiver_task := ^global_socket^.receive_wait_queue;
          receiver_task := global_socket^.receive_wait_queue;
          WHILE (receiver_task <> NIL) AND (receiver_task^.task_id <> current_task_id) DO
            previous_receiver_task := ^receiver_task^.next_entry;
            receiver_task := receiver_task^.next_entry;
          WHILEND;
          IF receiver_task <> NIL THEN
            previous_receiver_task^ := receiver_task^.next_entry;
            return_receiver_task_entry (global_socket, receiver_task);
          IFEND;
        IFEND;
      IFEND;
      nlp$udp_free_exclusive_access (global_socket);
    IFEND;

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

  PROCEDURE [XDCL] nlp$udp_send_data
    (    global_socket_id: nlt$udp_global_socket_id;
         time_stamp: ost$free_running_clock;
         local_ip_address: nat$sk_ip_address;
         destination_socket: nat$sk_socket_address;
         data: nat$data_fragments;
         data_length: integer;
         checksum: boolean;
         interface_mode: nat$sk_interface_mode;
         interface_timeout: nat$wait_time;
         user_cache_enabled: boolean;
     VAR status: ost$status);

?? NEWTITLE := 'broadcast_address', EJECT ??

    FUNCTION broadcast_address
      (    destination_address: nlt$tcpip_address): boolean;

      CONST
        local_broadcast_address = 0ffffffff(16);

      broadcast_address := (destination_address.full = local_broadcast_address) OR

{Class B network broadcast

      ((destination_address.class = 2) AND (destination_address.host_id_class_b = 0ffff(16))) OR

{Class A network broadcast

      (((destination_address.class = 0) OR (destination_address.class = 1)) AND
            (destination_address.host_id_class_a = 0ffffff(16))) OR

{Class C network broadcast

      ((destination_address.class = 3) AND (destination_address.host_id_class_c = 0ff(16)));

    FUNCEND broadcast_address;

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

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

      VAR
        ignore_status: ost$status;

      nap$condition_handler_trace (condition, sa);
      CASE condition.selector OF
      = pmc$system_conditions, mmc$segment_access_condition =
        activity_complete := TRUE;
        osp$set_status_from_condition (nac$status_id, condition, sa, status, condition_status);
        condition_status.normal := TRUE;

      = pmc$user_defined_condition =
        IF condition.user_condition_name = osc$job_recovery_condition_name THEN
          pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
        IFEND;
        condition_status.normal := TRUE;

      = pmc$block_exit_processing =

{ Task termination during pmp$wait.

        IF NOT activity_complete THEN
          nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
          IF connection_exists THEN
            nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
            IF layer_active THEN
              IF udp_connection^.state = nlc$udp_conn_open THEN

{ Find the current task on the send queue.

                sender_task := udp_connection^.send_queue;
                IF sender_task^.task_id = current_task_id THEN
                  udp_connection^.send_queue := sender_task^.next_entry;
                  nlp$cl_deactivate_sender (cl_connection);
                  IF remaining_data_length_to_send < data_length THEN
                    abort_send (cl_connection);
                  IFEND;
                  return_sender_task_entry (udp_connection, sender_task);
                  IF udp_connection^.send_queue <> NIL THEN
                    pmp$ready_task (udp_connection^.send_queue^.task_id, ignore_status);
                  IFEND;
                ELSE { find the sender task
                  previous_sender_task := ^udp_connection^.send_queue;
                  WHILE (sender_task <> NIL) AND (sender_task^.task_id <> current_task_id) DO
                    previous_sender_task := ^sender_task^.next_entry;
                    sender_task := sender_task^.next_entry;
                  WHILEND;
                  IF sender_task <> NIL THEN
                    previous_sender_task^ := sender_task^.next_entry;
                    return_sender_task_entry (udp_connection, sender_task);
                  IFEND;
                IFEND;
              ELSEIF udp_connection^.state = nlc$udp_conn_closed THEN
                previous_sender_task := ^udp_connection^.send_queue;
                WHILE (sender_task <> NIL) AND (sender_task^.task_id <> current_task_id) DO
                  previous_sender_task := ^sender_task^.next_entry;
                  sender_task := sender_task^.next_entry;
                WHILEND;
                IF sender_task <> NIL THEN
                  previous_sender_task^ := sender_task^.next_entry;
                IFEND;
                FREE sender_task IN nav$network_paged_heap^;
              IFEND;
            IFEND;
            nlp$cl_release_exclusive_access (cl_connection);
          IFEND;
        IFEND;
      ELSE

{ Note: Interactive condition is being ignored.

        condition_status.normal := TRUE;
      CASEND;

    PROCEND terminate_send;
?? OLDTITLE, EJECT ??

    VAR
      activity_complete: boolean,
      broadcast: boolean,
      capacity: nat$data_length,
      cl_connection: ^nlt$cl_connection,
      connection_exists: boolean,
      connection_count: integer,
      connection_id: nat$connection_id,
      connections: ^array [1 .. * ] of nat$connection_id,
      count: integer,
      current_lowerbound: nat$data_fragment_count,
      current_task_id: ost$global_task_id,
      current_time: ost$free_running_clock,
      data_fragments: ^nat$data_fragments,
      delete_global_socket: boolean,
      destination_address: nlt$tcpip_address,
      device_id: nlt$device_identifier,
      end_time: integer,
      global_socket: ^nlt$udp_global_socket,
      i: integer,
      initial_send: boolean,
      j: integer,
      layer_active: boolean,
      local_socket_id: nat$sk_socket_identifier,
      new_lowerbound: nat$data_fragment_count,
      previous_sender_task: ^^nlt$udp_sender_task,
      remaining_data_length: integer,
      remaining_data_length_to_send: integer,
      remaining_time: integer,
      send_queue: ^nlt$udp_sender_task,
      sender_active: boolean,
      sender_task: ^nlt$udp_sender_task,
      udp_connection: ^nlt$udp_socket_layer,
      udpaa_data_header: nlt$udpaa_data_request;

{ Select the appropriate device.

    status.normal := TRUE;

    destination_address.full := destination_socket.ip_address;
    broadcast := broadcast_address (destination_address);

    IF local_ip_address <> nac$sk_all_ip_addresses THEN
      nlp$tm_select_by_local_udp_addr (local_ip_address, device_id, status);
    ELSEIF user_cache_enabled AND NOT broadcast THEN

{ Search local cache (in task private segment) for a match on the
{ ip address portion of the destination socket address and use the
{ corresponding device id.

      IF nlv$udp_local_routing_cache.last_send.destination_ip_address = destination_socket.ip_address THEN
        device_id := nlv$udp_local_routing_cache.last_send.device_id;
      ELSEIF nlv$udp_local_routing_cache.last_receive.source_ip_address = destination_socket.ip_address THEN
        device_id := nlv$udp_local_routing_cache.last_receive.device_id;
      ELSE

{ Invoke TCP/IP Management provided device selection.

        nlp$tm_udp_select_device (destination_socket.ip_address, device_id, status);
      IFEND;
    ELSEIF NOT broadcast THEN
      nlp$tm_udp_select_device (destination_socket.ip_address, device_id, status);
    ELSE
      device_id := 0;
    IFEND;

{ Access the global socket entry.

    IF status.normal THEN
      sender_active := FALSE;
      activity_complete := FALSE;
      #SPOIL (activity_complete);
      osp$establish_condition_handler (^terminate_send, TRUE);
      nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
      IF global_socket <> NIL THEN
        IF global_socket^.status = nlc$udp_global_socket_open THEN
          IF global_socket^.time_stamp = time_stamp THEN
            local_socket_id := global_socket^.local_socket_id;
            IF (NOT broadcast) OR (global_socket^.broadcast_enabled) THEN
              connections := NIL;
              IF device_id = 0 THEN { broadcast message via all devices
                PUSH connections: [1 .. UPPERBOUND (global_socket^.device_list)];
                connection_count := 0;
                FOR i := 1 TO UPPERBOUND (global_socket^.device_list) DO
                  IF global_socket^.device_list [i].status = nlc$udp_device_open THEN
                    connection_count := connection_count + 1;
                    connections^ [connection_count] := global_socket^.device_list [i].connection_id;
                  IFEND;
                FOREND;
              ELSEIF global_socket^.device_list [device_id].status = nlc$udp_device_open THEN
                PUSH connections: [1 .. 1];
                connection_count := 1;
                connections^ [1] := global_socket^.device_list [device_id].connection_id;
              IFEND;
              nlp$udp_free_exclusive_access (global_socket);
              IF connections <> NIL THEN
                data_fragments := NIL;
                FOR j := 1 TO connection_count DO
                  status.normal := TRUE;
                  connection_id := connections^ [j];
                  #SPOIL (connection_id);
                  nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
                  IF connection_exists THEN
                    nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active,
                          udp_connection);
                    IF layer_active THEN
                      IF udp_connection^.state = nlc$udp_conn_open THEN
                        IF data_fragments = NIL THEN

{ Set up the data fragments with the first entry pointing to the PDU header.

                          udpaa_data_header.header.kind := nlc$udpaa_data_req;

{ Length will be set up as each fragment is sent.

                          udpaa_data_header.checksum := checksum;
                          udpaa_data_header.destination_port := destination_socket.port;
                          udpaa_data_header.destination_ip_address := destination_socket.ip_address;
                          udpaa_data_header.end_of_message := FALSE;
                          PUSH data_fragments: [1 .. (1 + UPPERBOUND (data))];
                          data_fragments^ [1].address := ^udpaa_data_header;
                          data_fragments^ [1].length := #SIZE (udpaa_data_header);

{ Move the remaining fragments.

                          FOR i := 1 TO UPPERBOUND (data) DO
                            data_fragments^ [i + 1] := data [i];
                          FOREND;
                          pmp$get_executing_task_gtid (current_task_id);
                          #SPOIL (current_task_id);
                        IFEND;
                        udpaa_data_header.source_ip_address := udp_connection^.local_ip_address;
                        IF udp_connection^.send_queue = NIL THEN

{ No sender queued. Send data.

                          nlp$osi_get_outbound_capacity (cl_connection, capacity);
                          IF capacity <= 0 THEN
                            nlp$cc_receive_data (cl_connection);
                            nlp$osi_get_outbound_capacity (cl_connection, capacity);
                          IFEND;

                          IF capacity > 0 THEN
                            send_data (cl_connection, capacity, {initial_send} TRUE, data_fragments^,
                                  data_length, interface_mode, 1, new_lowerbound, remaining_data_length);
                            activity_complete := remaining_data_length = 0;
                            #SPOIL (activity_complete);
                            IF NOT activity_complete THEN
                              IF interface_mode = nac$sk_blocking_mode THEN

{ Queue the sender task.

                                get_sender_task_entry (udp_connection, sender_task);

{ Queue the current task on the send queue.

                                sender_task^.next_entry := NIL;
                                sender_task^.task_id := current_task_id;
                                sender_task^.send_type := nlc$udp_send_data;
                                remaining_data_length_to_send := remaining_data_length;
                                current_lowerbound := new_lowerbound;
                                udp_connection^.send_queue := sender_task;
                                nlp$cl_activate_sender (cl_connection);
                                sender_active := TRUE;
                              ELSE { non blocking mode

{ Should never end up here.

                                nap$namve_system_error ({Recoverable_error=} TRUE,
                                      'Unable to send non blocked data.', NIL);
                                osp$set_status_abnormal (nac$status_id, nae$sk_internal_error,
                                      'SEND TO SOCKET', status);
                              IFEND;
                            IFEND;
                          ELSE { No outbound capacity
                            IF interface_mode = nac$sk_blocking_mode THEN
                              get_sender_task_entry (udp_connection, sender_task);

{ Queue the current task on the send queue.

                              sender_task^.next_entry := NIL;
                              sender_task^.task_id := current_task_id;
                              sender_task^.send_type := nlc$udp_send_data;
                              remaining_data_length_to_send := data_length;
                              current_lowerbound := 1;
                              udp_connection^.send_queue := sender_task;
                              nlp$cl_activate_sender (cl_connection);
                              sender_active := TRUE;
                            ELSE { Non blocking interface mode
                              osp$set_status_abnormal (nac$status_id, nae$sk_insufficient_resources,
                                    'UDP Send', status);
                            IFEND;
                          IFEND;
                        ELSE { udp_connection^.send_queue <> NIL

                          IF interface_mode = nac$sk_blocking_mode THEN
                            get_sender_task_entry (udp_connection, sender_task);

{ Queue the current task at the end of the send queue.

                            sender_task^.next_entry := NIL;
                            sender_task^.task_id := current_task_id;
                            sender_task^.send_type := nlc$udp_send_data;
                            remaining_data_length_to_send := data_length;
                            current_lowerbound := 1;
                            send_queue := udp_connection^.send_queue;

                            WHILE (send_queue^.next_entry <> NIL) DO
                              send_queue := send_queue^.next_entry;
                            WHILEND;
                            send_queue^.next_entry := sender_task;
                          ELSE { non blocking mode
                            osp$set_status_condition (nae$sk_send_in_progress, status);
                            osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                  TRUE, status);
                          IFEND;
                        IFEND;
                      ELSE { UDP layer closed
                        osp$set_status_condition (nae$sk_socket_disconnected, status);
                        osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                              status);
                      IFEND;
                    ELSE { UDP layer inactive
                      osp$set_status_condition (nae$sk_socket_disconnected, status);
                      osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                            status);
                    IFEND;
                    nlp$cl_release_exclusive_access (cl_connection);
                  ELSE { Connection does not exist
                    osp$set_status_condition (nae$sk_socket_disconnected, status);
                    osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                          status);
                  IFEND;

                  IF status.normal AND NOT activity_complete THEN
                    #SPOIL (activity_complete);
                    end_time := #FREE_RUNNING_CLOCK (0) + interface_timeout * 1000;
                    remaining_time := interface_timeout;
                    delete_global_socket := FALSE;
                    REPEAT
                      #SPOIL (remaining_data_length_to_send);
                      pmp$wait (remaining_time, 0);
                      current_time := #FREE_RUNNING_CLOCK (0);
                      IF current_time < end_time THEN
                        remaining_time := (end_time - current_time) DIV 1000;
                      ELSE
                        remaining_time := 0;
                      IFEND;

                      nlp$cl_get_exclusive_via_cid (connection_id, connection_exists, cl_connection);
                      IF cl_connection <> NIL THEN
                        nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active,
                              udp_connection);
                        IF layer_active THEN
                          IF udp_connection^.state = nlc$udp_conn_open THEN

{ Find the current task on the send queue.

                            sender_task := udp_connection^.send_queue;
                            IF sender_task^.task_id = current_task_id THEN

{ Current task is at the head of the queue.

                              IF NOT sender_active THEN
                                nlp$cl_activate_sender (cl_connection);
                                sender_active := TRUE;
                              IFEND;
                              nlp$cc_receive_data (cl_connection);
                              nlp$osi_get_outbound_capacity (cl_connection, capacity);
                              IF capacity > 0 THEN
                                initial_send := (data_length = remaining_data_length_to_send);
                                send_data (cl_connection, capacity, initial_send, data_fragments^,
                                      remaining_data_length_to_send, nac$sk_blocking_mode, current_lowerbound,
                                      new_lowerbound, remaining_data_length);
                                IF remaining_data_length = 0 THEN
                                  activity_complete := TRUE;
                                  nlp$cl_deactivate_sender (cl_connection);

{ The following needs to be done to process the clear to send indication that may have
{ been queued on the connection.

                                  nlp$cc_receive_data (cl_connection);

{ Dequeue the sender task from the send queue.
{ Return the sender task entry.

                                  udp_connection^.send_queue := sender_task^.next_entry;
                                  return_sender_task_entry (udp_connection, sender_task);
                                  IF udp_connection^.send_queue <> NIL THEN
                                    pmp$ready_task (udp_connection^.send_queue^.task_id, {ignore} status);
                                 status.normal := TRUE;
                               IFEND;
                             ELSE { remaining_data_length > 0
                               remaining_data_length_to_send := remaining_data_length;
                               current_lowerbound := new_lowerbound;
                             IFEND;
                           IFEND;
                           IF NOT activity_complete THEN
                             IF remaining_time = 0 THEN
                               udp_connection^.send_queue := sender_task^.next_entry;
                               nlp$cl_deactivate_sender (cl_connection);

{ The following needs to be done to process the clear to send indication that may have
{ been queued on the connection.

                               nlp$cc_receive_data (cl_connection);
                               IF remaining_data_length_to_send < data_length THEN

{ Partial data has been sent.
{ Send a Clear Send Request to the UDPAP.

                                 abort_send (cl_connection);
                               IFEND;

{ Queue the sender task in the available pool.

                               return_sender_task_entry (udp_connection, sender_task);
                               IF udp_connection^.send_queue <> NIL THEN
                                 pmp$ready_task (udp_connection^.send_queue^.task_id, {ignore} status);
                                 status.normal := TRUE;
                               IFEND;
                               osp$set_status_condition (nae$sk_interface_timeout, status);
                               osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                     TRUE, status);
                             IFEND;
                           IFEND;
                         ELSE { current task not at the head of the queue
                           IF remaining_time = 0 THEN
                             previous_sender_task := ^udp_connection^.send_queue;
                             WHILE (sender_task <> NIL) AND (sender_task^.task_id <> current_task_id) DO
                               previous_sender_task := ^sender_task^.next_entry;
                               sender_task := sender_task^.next_entry;
                             WHILEND;
                             IF sender_task <> NIL THEN

{ If the task is not at the head of the queue, it couldn't have sent
{ data. Dequeue the sender task from the send queue.

                               previous_sender_task^ := sender_task^.next_entry;
                               return_sender_task_entry (udp_connection, sender_task);
                             IFEND;
                             osp$set_status_condition (nae$sk_interface_timeout, status);
                             osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10,
                                   TRUE, status);
                           IFEND;
                         IFEND;
                       ELSEIF udp_connection^.state = nlc$udp_conn_closed THEN

{ Find the sender task.

                         previous_sender_task := ^udp_connection^.send_queue;
                         WHILE (sender_task <> NIL) AND (sender_task^.task_id <> current_task_id) DO
                           previous_sender_task := ^sender_task^.next_entry;
                           sender_task := sender_task^.next_entry;
                         WHILEND;
                         IF sender_task <> NIL THEN

{ Dequeue the sender task from the send queue.

                           previous_sender_task^ := sender_task^.next_entry;
                           FREE sender_task IN nav$network_paged_heap^;
                         IFEND;
                         osp$set_status_condition (nae$sk_socket_disconnected, status);
                         osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE,
                               status);
                         IF udp_connection^.send_queue = NIL THEN
                           close_udp_socket_device (global_socket_id, udp_connection^.device_id,
                                 delete_global_socket);
                           deactivate_udp_layer (cl_connection, udp_connection);
                         IFEND;
                       ELSE { Unexpected state
                         nap$namve_system_error ({Recoverable_error=} TRUE,
                               'Encountered an unexpected UDP layer state', NIL);
                         osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'SEND TO SOCKET',
                               status);
                       IFEND;
                     ELSE { Layer inactive
                       nap$namve_system_error ({Recoverable_error=} TRUE,
                             'Encountered  an inactive UDP layer.', NIL);
                       osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'SEND TO SOCKET',
                             status);
                     IFEND;
                     nlp$cl_release_exclusive_access (cl_connection);
                     IF delete_global_socket THEN
                       nlp$udp_delete_global_socket (global_socket_id);
                     IFEND;
                   ELSE { Connection terminated
                     nap$namve_system_error ({Recoverable_error=} TRUE,
                           'UDP connection teminated while IO was active.', NIL);
                     osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'SEND TO SOCKET', status);
                   IFEND;
                 UNTIL (activity_complete) OR (NOT status.normal);
               IFEND;
             FOREND;
           ELSE { No channel connection to the device
             osp$set_status_condition (nae$sk_socket_disconnected, status);
             osp$append_status_integer (osc$status_parameter_delimiter, local_socket_id, 10, TRUE, status);
           IFEND;

{ Update local routing cache.

           IF user_cache_enabled AND NOT broadcast THEN
             IF status.normal THEN
               nlv$udp_local_routing_cache.last_send.destination_ip_address := destination_socket.ip_address;
               nlv$udp_local_routing_cache.last_send.source_ip_address := udpaa_data_header.source_ip_address;
               nlv$udp_local_routing_cache.last_send.device_id := device_id;
             ELSE { Remove the entry
               IF nlv$udp_local_routing_cache.last_send.destination_ip_address =
                     destination_socket.ip_address THEN
                 nlv$udp_local_routing_cache.last_send.destination_ip_address := 0;
                 nlv$udp_local_routing_cache.last_send.source_ip_address := 0;
                 nlv$udp_local_routing_cache.last_send.device_id := 0;
               IFEND;
               IF nlv$udp_local_routing_cache.last_receive.source_ip_address =
                     destination_socket.ip_address THEN
                 nlv$udp_local_routing_cache.last_receive.source_ip_address := 0;
                 nlv$udp_local_routing_cache.last_receive.destination_ip_address := 0;
                 nlv$udp_local_routing_cache.last_receive.device_id := 0;
               IFEND;
             IFEND;
           IFEND;
         ELSE { invalid broadcast attempt
           osp$set_status_condition (nae$sk_broadcast_not_enabled, status);
           nlp$udp_free_exclusive_access (global_socket);
         IFEND;
       ELSE { time stamps do not match
         osp$set_status_condition (nae$sk_unknown_socket, status);
         nlp$udp_free_exclusive_access (global_socket);
       IFEND;
     ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN
       osp$set_status_condition (nae$sk_socket_terminated, status);
       osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
             status);
       nlp$udp_free_exclusive_access (global_socket);
     ELSE { socket closed
       osp$set_status_condition (nae$sk_unknown_socket, status);
       nlp$udp_free_exclusive_access (global_socket);
     IFEND;
   ELSE { global_socket = NIL
     osp$set_status_condition (nae$sk_unknown_socket, status);
   IFEND;
   osp$disestablish_cond_handler;
 IFEND;

 PROCEND nlp$udp_send_data;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] nlp$udp_set_socket_options', EJECT ??

 PROCEDURE [XDCL] nlp$udp_set_socket_options
   (    global_socket_id: nlt$udp_global_socket_id;
        traffic_pattern: nat$sk_traffic_pattern;
        broadcast_enabled: boolean;
    VAR status: ost$status);

   VAR
     cl_connection: ^nlt$cl_connection,
     connection_exists: boolean,
     connection_list: ^array [1 .. * ] of nat$connection_id,
     data_fragment: array [1 .. 1] of nat$data_fragment,
     device_id: nlt$device_identifier,
     global_socket: ^nlt$udp_global_socket,
     ignore_status: ost$status,
     layer_active: boolean,
     udp_connection: ^nlt$udp_socket_layer,
     udpaa_set_options_request: nlt$udpaa_set_options_request;

   nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
   IF global_socket <> NIL THEN
     IF global_socket^.status = nlc$udp_global_socket_open THEN
       global_socket^.traffic_pattern := traffic_pattern;
       global_socket^.broadcast_enabled := broadcast_enabled;
       PUSH connection_list: [1 .. UPPERBOUND (global_socket^.device_list)];
       FOR device_id := 1 TO UPPERBOUND (global_socket^.device_list) DO
         IF global_socket^.device_list [device_id].status = nlc$udp_device_open THEN
           connection_list^ [device_id] := global_socket^.device_list [device_id].connection_id;
         ELSE
           connection_list^ [device_id] := nac$null_connection_id;
         IFEND;
       FOREND;

       nlp$udp_free_exclusive_access (global_socket);
       udpaa_set_options_request.header.kind := nlc$udpaa_set_options_req;
       udpaa_set_options_request.header.length := #SIZE (udpaa_set_options_request);
       udpaa_set_options_request.traffic_pattern := traffic_pattern;
       udpaa_set_options_request.broadcast_enabled := broadcast_enabled;
       data_fragment [1].address := ^udpaa_set_options_request;
       data_fragment [1].length := udpaa_set_options_request.header.length;

       FOR device_id := 1 TO UPPERBOUND (connection_list^) DO
         IF connection_list^ [device_id] <> nac$null_connection_id THEN
           nlp$cl_get_exclusive_via_cid (connection_list^ [device_id], connection_exists, cl_connection);
           IF cl_connection <> NIL THEN
             nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
             IF layer_active THEN
               nlp$cc_send_data_fragments (cl_connection, data_fragment, ignore_status);
             IFEND;
             nlp$cl_release_exclusive_access (cl_connection);
           IFEND;
         IFEND;
       FOREND;
     ELSEIF global_socket^.status = nlc$udp_global_socket_term THEN

{ The socket has been terminated via application management.

       osp$set_status_condition (nae$sk_socket_terminated, status);
       nlp$udp_free_exclusive_access (global_socket);
     ELSEIF global_socket^.status = nlc$udp_global_socket_unbound THEN
       global_socket^.traffic_pattern := traffic_pattern;
       global_socket^.broadcast_enabled := broadcast_enabled;
       nlp$udp_free_exclusive_access (global_socket);
     ELSE { unexpected status
       nap$namve_system_error ({Recoverable_error=} TRUE,
             'Encountered unexpected global socket status', NIL);
       osp$set_status_abnormal (nac$status_id, nae$sk_internal_error, 'SET SOCKET OPTIONS', status);
       nlp$udp_free_exclusive_access (global_socket);
     IFEND;
   ELSE { global_socket = NIL

{ The socket has been terminated via application management.

     osp$set_status_condition (nae$sk_socket_terminated, status);
   IFEND;

 PROCEND nlp$udp_set_socket_options;
?? OLDTITLE ??
?? NEWTITLE := 'abort_send', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to send a clear send request
{ to the UDP Access Provider. This request is sent whenever  partial
{ data has been sent to the UDPAP and the remaining data is not going
{ to be sent.

 PROCEDURE abort_send
   (    cl_connection: ^nlt$cl_connection);

   VAR
     data_fragment: array [1 .. 1] of nat$data_fragment,
     ignore_status: ost$status,
     udpaa_clear_send_request: nlt$udpaa_clear_send_request;

   udpaa_clear_send_request.header.kind := nlc$udpaa_clear_send_req;
   udpaa_clear_send_request.header.length := #SIZE (udpaa_clear_send_request);
   data_fragment [1].address := ^udpaa_clear_send_request;
   data_fragment [1].length := udpaa_clear_send_request.header.length;
   nlp$cc_send_data_fragments (cl_connection, data_fragment, ignore_status);

 PROCEND abort_send;
?? OLDTITLE ??
?? NEWTITLE := 'activate_next_receiver_task', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to activate the next receiver in the
{ receive wait queue. The task at the head of the queue is assumed
{ to be the current task. If the next receiver task is awaiting the data
{ available indication, the activity is marked as complete and the receiver
{ is dequeued. If the task is awaiting data, the attributes of the queued
{ data are matched against the selection criteria specified by the receiver.
{ If the attributes match, data is delivered to the receiver. Otherwise the
{ data is discarded.

 PROCEDURE activate_next_receiver_task
   (VAR global_socket: ^nlt$udp_global_socket;
        device_id: nlt$device_identifier;
    VAR buffers_freed: nat$data_length);

   VAR
     ignore_status: ost$status,
     received_message: ^nlt$udp_received_message,
     receiver_task: ^nlt$udp_receiver_task;

   receiver_task := global_socket^.receive_wait_queue;
   IF receiver_task^.receive_type = nlc$udp_receive_data THEN
     received_message := global_socket^.device_list [device_id].received_messages;
     IF ((receiver_task^.selection_criteria.ip_address = 0) OR
           (receiver_task^.selection_criteria.ip_address = received_message^.source_socket.ip_address)) AND
           ((receiver_task^.selection_criteria.port = 0) OR (receiver_task^.selection_criteria.port =
           received_message^.source_socket.port)) THEN
       global_socket^.receive_wait_queue := receiver_task^.next_entry;
       receiver_task^.next_entry := NIL;
       global_socket^.device_list [device_id].receiver_task := receiver_task;
       receiver_task^.connection_id := global_socket^.device_list [device_id].connection_id;
       receiver_task^.device_id^ := device_id;
       receive_data (global_socket, device_id, buffers_freed);
     ELSE { discard received message
       nlp$bm_release_message (received_message^.data);
       buffers_freed := received_message^.buffer_count;
       global_socket^.device_list [device_id].received_messages := received_message^.next_entry;
       return_received_message_entry (global_socket, received_message);
     IFEND;
   ELSE { await data available
     receiver_task^.activity_complete^ := TRUE;
     global_socket^.receive_wait_queue := receiver_task^.next_entry;
     IF receiver_task^.receiver_active THEN
       nlp$udp_deactivate_receiver (global_socket^.active_receiver);
     IFEND;
     return_receiver_task_entry (global_socket, receiver_task);
     IF global_socket^.receive_wait_queue <> NIL THEN
       pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
     IFEND;
   IFEND;

 PROCEND activate_next_receiver_task;
?? OLDTITLE ??
?? NEWTITLE := 'activate_next_sender_task', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to ready the next task
{ waiting to send data. All the tasks awaiting a clear to
{ send indication and queued ahead of the task waiting to send
{ data are readied and dequeued.

 PROCEDURE activate_next_sender_task
   (VAR udp_connection: ^nlt$udp_socket_layer);

   VAR
     ignore_status: ost$status,
     previous_sender_task: ^^nlt$udp_sender_task,
     sender_task: ^nlt$udp_sender_task;

{ Find the next task waiting to send data.

   sender_task := udp_connection^.send_queue;
   previous_sender_task := ^udp_connection^.send_queue;

 /activate_sender/
   WHILE sender_task <> NIL DO
     IF sender_task^.send_type = nlc$udp_await_clear_to_send THEN
       previous_sender_task^ := sender_task^.next_entry;
       pmp$ready_task (sender_task^.task_id, ignore_status);
       return_sender_task_entry (udp_connection, sender_task);
       sender_task := previous_sender_task^;
     ELSE
       pmp$ready_task (sender_task^.task_id, ignore_status);
       EXIT /activate_sender/;
     IFEND;
   WHILEND /activate_sender/;

 PROCEND activate_next_sender_task;
?? OLDTITLE ??
?? NEWTITLE := 'close_udp_socket_device', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to update the status of
{ the given device to "closed" in the socket device list. It also
{ decrements the active device count. An active receiver on the
{ device is also readied.  This procedure is meant to be called
{ when the last sender on a UDP channel connection is dequeued.
{ If the global socket is closed or terminated (via appl mgmt),
{ an appropriate flag is returned to the caller so it can delete
{ the global socket.

 PROCEDURE close_udp_socket_device
   (    global_socket_id: nlt$udp_global_socket_id;
        device_id: nlt$device_identifier;
    VAR delete_global_socket: boolean);

   VAR
     global_socket: ^nlt$udp_global_socket,
     ignore_status: ost$status,
     receiver_task: ^nlt$udp_receiver_task;

   delete_global_socket := FALSE;
   nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
   IF global_socket <> NIL THEN
     global_socket^.device_list [device_id].status := nlc$udp_device_closed;
     global_socket^.device_list [device_id].connection_id := nac$null_connection_id;
     global_socket^.active_device_count := global_socket^.active_device_count - 1;
     discard_received_messages (device_id, global_socket);
     IF global_socket^.device_list [device_id].receiver_task <> NIL THEN
       receiver_task := global_socket^.device_list [device_id].receiver_task;
       global_socket^.device_list [device_id].receiver_task := NIL;
       pmp$ready_task (receiver_task^.task_id, ignore_status);
       receiver_task^.next_entry := global_socket^.receive_wait_queue;
       global_socket^.receive_wait_queue := receiver_task;
     IFEND;
     delete_global_socket := (global_socket^.status = nlc$udp_global_socket_closed) AND
           (global_socket^.receive_wait_queue = NIL) AND (global_socket^.active_device_count = 0);
     nlp$udp_free_exclusive_access (global_socket);
   IFEND;

 PROCEND close_udp_socket_device;
?? OLDTITLE ??
?? NEWTITLE := 'deactivate_udp_layer', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to free all allocated structures
{   associated with the layer connection and to deactivate the layer.

 PROCEDURE [INLINE] deactivate_udp_layer
   (    cl_connection: ^nlt$cl_connection;
    VAR udp_connection: ^nlt$udp_socket_layer);

   VAR
     next_pool_entry: ^nlt$udp_sender_task,
     pool_entry: ^nlt$udp_sender_task;

   pool_entry := udp_connection^.available_sender_pool;
   WHILE pool_entry <> NIL DO
     next_pool_entry := pool_entry^.next_entry;
     FREE pool_entry IN nav$network_paged_heap^;
     pool_entry := next_pool_entry;
   WHILEND;
   nlp$cl_deactivate_layer (nlc$udp_interface, cl_connection);

 PROCEND deactivate_udp_layer;
?? OLDTITLE ??
?? NEWTITLE := 'discard_received_messages', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to discard the received messages
{ queued on the given device. This procedure is meant to be called when
{ the channel connection to a given device breaks.
{
{ The global socket must be locked by the caller.

 PROCEDURE discard_received_messages
   (    device_id: nlt$device_identifier;
    VAR global_socket: ^nlt$udp_global_socket);

   VAR
     next_received_message: ^nlt$udp_received_message,
     received_message: ^nlt$udp_received_message;

   IF global_socket^.device_list [device_id].received_messages <> NIL THEN
     received_message := global_socket^.device_list [device_id].received_messages;
     global_socket^.device_list [device_id].received_messages := NIL;

     WHILE (received_message <> NIL) DO
       IF received_message^.data <> nlv$bm_null_message_id THEN
         nlp$bm_release_message (received_message^.data);
       IFEND;
       next_received_message := received_message^.next_entry;
       return_received_message_entry (global_socket, received_message);
       received_message := next_received_message;
     WHILEND;
   IFEND;

 PROCEND discard_received_messages;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] get_receiver_task_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to get a receiver task entry
{ from the pool of available entries. If the pool is empty, the
{ entry is allocated. The pool of available receiver task entries is
{ maintained in the global socket.

 PROCEDURE [INLINE] get_receiver_task_entry
   (VAR global_socket: ^nlt$udp_global_socket;
    VAR receiver_task: ^nlt$udp_receiver_task);

   IF global_socket^.available_receiver_pool <> NIL THEN
     receiver_task := global_socket^.available_receiver_pool;
     global_socket^.available_receiver_pool := receiver_task^.next_entry;
   ELSE
     REPEAT
       ALLOCATE receiver_task IN nav$network_paged_heap^;
       IF receiver_task = NIL THEN
         syp$cycle;
       IFEND;
     UNTIL receiver_task <> NIL;
   IFEND;

 PROCEND get_receiver_task_entry;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE] get_sender_task_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to find a sender task entry
{ in the pool of available entries. If the pool is empty, the
{ entry is allocated.

 PROCEDURE [INLINE] get_sender_task_entry
   (VAR udp_connection: ^nlt$udp_socket_layer;
    VAR sender_task: ^nlt$udp_sender_task);

   IF udp_connection^.available_sender_pool <> NIL THEN
     sender_task := udp_connection^.available_sender_pool;
     udp_connection^.available_sender_pool := sender_task^.next_entry;
   ELSE { Allocate sender task entry
     REPEAT
       ALLOCATE sender_task IN nav$network_paged_heap^;
       IF sender_task = NIL THEN
         syp$cycle;
       IFEND;
     UNTIL sender_task <> NIL;
   IFEND;

 PROCEND get_sender_task_entry;
?? OLDTITLE ??
?? NEWTITLE := 'initialize_receiver_task_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to get a receiver task
{ entry and initialize all the fields to the specified values.

 PROCEDURE [INLINE] initialize_receiver_task_entry
   (VAR global_socket: ^nlt$udp_global_socket;
        current_task_id: ost$global_task_id;
        receive_type: nlt$udp_receive_type;
        original_receive_buffer: ^nat$data_fragments;
        original_buffer_length: integer;
        receive_buffer: ^nat$data_fragments;
        buffer_length: integer;
        received_data_length: ^integer;
        selection_criteria: nat$sk_socket_address;
        foreign_socket: ^nat$sk_socket_address;
        local_ip_address: ^nat$sk_ip_address;
        device_id: ^nlt$device_identifier;
        connection_id: nat$connection_id;
        interface_mode: nat$sk_interface_mode;
        activity_status: ^ost$activity_status;
    VAR receiver_task: ^nlt$udp_receiver_task);


   get_receiver_task_entry (global_socket, receiver_task);
   receiver_task^.next_entry := NIL;
   receiver_task^.task_id := current_task_id;
   receiver_task^.end_of_message := FALSE;
   receiver_task^.receive_type := receive_type;
   receiver_task^.original_receive_buffer := original_receive_buffer;
   receiver_task^.original_buffer_length := original_buffer_length;
   receiver_task^.receive_buffer := receive_buffer;
   receiver_task^.buffer_length := buffer_length;
   receiver_task^.received_data_length := received_data_length;
   receiver_task^.selection_criteria := selection_criteria;
   receiver_task^.foreign_socket := foreign_socket;
   receiver_task^.local_ip_address := local_ip_address;
   receiver_task^.device_id := device_id;
   receiver_task^.connection_id := connection_id;
   receiver_task^.interface_mode := interface_mode;
   receiver_task^.activity_status := activity_status;

 PROCEND initialize_receiver_task_entry;
?? OLDTITLE ??
?? NEWTITLE := 'issue_disconnect', EJECT ??

{ PUROSE:
{   The purpose of this procedure is to format the release request
{ PDU and send it to the UDP Access Provider via a channel connection
{ disconnect request.

 PROCEDURE issue_disconnect
   (    cl_connection: ^nlt$cl_connection;
        disconnect_reason: nlt$udpaa_release_req_reason);

   VAR
     data_fragment: array [1 .. 1] of nat$data_fragment,
     ignore_status: ost$status,
     release_request: nlt$udpaa_release_request,
     udpaa_pdu: nlt$bm_message_id;

   release_request.header.kind := nlc$udpaa_release_req;
   release_request.header.length := #SIZE (release_request);
   release_request.reason := disconnect_reason;
   data_fragment [1].address := ^release_request;
   data_fragment [1].length := release_request.header.length;
   nlp$bm_create_message (data_fragment, udpaa_pdu, ignore_status);
   nlp$cc_disconnect (cl_connection, udpaa_pdu, ignore_status);

 PROCEND issue_disconnect;
?? OLDTITLE ??
?? NEWTITLE := 'issue_protocol_error', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to issue a protocol
{ error to the UDP Access Provider. It is assumed that the
{ global socket is locked by the caller. All active senders
{ on the connection will be readied and if no IO is active
{ on the connection the socket device list will be updated.
{ The only difference between this routine and the 'PROCESS
{ PROTOCOL ERROR' routine is that the latter will explicitly
{ lock the global socket.

 PROCEDURE issue_protocol_error
   (    cl_connection: ^nlt$cl_connection;
    VAR global_socket: ^nlt$udp_global_socket;
    VAR udp_connection: ^nlt$udp_socket_layer);

   VAR
     current_task_id: ost$global_task_id,
     ignore_status: ost$status,
     previous_sender_task: ^^nlt$udp_sender_task,
     receiver_task: ^nlt$udp_receiver_task,
     sender_task: ^nlt$udp_sender_task;

   IF udp_connection^.send_queue <> NIL THEN
     sender_task := udp_connection^.send_queue;
     previous_sender_task := ^udp_connection^.send_queue;

     WHILE sender_task <> NIL DO
       pmp$ready_task (sender_task^.task_id, ignore_status);
       IF sender_task^.send_type = nlc$udp_await_clear_to_send THEN
         previous_sender_task^ := sender_task^.next_entry;
         FREE sender_task IN nav$network_paged_heap^;
         sender_task := previous_sender_task^;
       ELSE
         previous_sender_task := ^sender_task^.next_entry;
         sender_task := sender_task^.next_entry;
       IFEND;
     WHILEND;
   IFEND;

   IF global_socket^.device_list [udp_connection^.device_id].receiver_task <> NIL THEN
     pmp$get_executing_task_gtid (current_task_id);
     receiver_task := global_socket^.device_list [udp_connection^.device_id].receiver_task;
     IF receiver_task^.task_id = current_task_id THEN
       receiver_task^.received_data_length^ := 0;
       IF receiver_task^.interface_mode = nac$sk_blocking_mode THEN

{ Queue the receiver back in the receive queue.

         receiver_task^.next_entry := global_socket^.receive_wait_queue;
         global_socket^.receive_wait_queue := receiver_task;
       ELSE { non blocking mode
         receiver_task^.activity_status^.complete := TRUE;
         osp$set_status_condition (nae$sk_no_data_available, receiver_task^.activity_status^.status);
         osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
               receiver_task^.activity_status^.status);
         return_receiver_task_entry (global_socket, receiver_task);
         nlp$udp_deactivate_receiver (global_socket^.active_receiver);
         IF global_socket^.receive_wait_queue <> NIL THEN
           pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
         IFEND;
       IFEND;
     IFEND;
   IFEND;

   discard_received_messages (udp_connection^.device_id, global_socket);
   issue_disconnect (cl_connection, nlc$udpaa_rr_protocol_error);

{ Update the device status in the global socket.

   IF udp_connection^.send_queue = NIL THEN
     global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
     global_socket^.active_device_count := global_socket^.active_device_count - 1;
     deactivate_udp_layer (cl_connection, udp_connection);
   ELSE
     global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
   IFEND;

 PROCEND issue_protocol_error;
?? OLDTITLE ??
?? NEWTITLE := 'log_disconnect', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to record the occurance of an UDP
{ release or Channel Connection disconnect indication on the system and
{ job logs.

 PROCEDURE [INLINE] log_disconnect
   (    indication: string ( * );
        global_socket_id: nlt$udp_global_socket_id;
        device_id: nlt$device_identifier);

{   VAR
{     element: cmt$element_name,
{     global_socket: ^nlt$udp_global_socket,
{     ignore_status: ost$status,
{     local_socket: integer,
{     message: string (132),
{     message_length: integer,
{     port: nat$sk_port_number;
{
{   nlp$udp_get_exclusive_via_gsid (global_socket_id, global_socket);
{   IF global_socket <> NIL THEN
{     local_socket := global_socket^.local_socket_id;
{     port := global_socket^.port;
{     nlp$udp_free_exclusive_access (global_socket);
{   ELSE
{     port := 0;
{     local_socket := 0;
{   IFEND;
{   nlp$get_nonexclusive_access (nlv$configured_network_devices.access_control);
{   element := nlv$configured_network_devices.network_device_list^ [device_id].element;
{   nlp$release_nonexclusive_access (nlv$configured_network_devices.access_control);
{   STRINGREP (message, message_length, indication, ' from device = ', element, ', for local socket = ',
{         local_socket, ', port = ', port);
{   pmp$log_ascii (message (1, message_length), $pmt$ascii_logset [pmc$system_log, pmc$job_log],
{         pmc$msg_origin_system, ignore_status);
{

 PROCEND log_disconnect;
?? OLDTITLE ??
?? NEWTITLE := 'process_clear_send_indication', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to process the clear send
{ indication received from the UDP Access Provider. If there is a
{ task receiving data over the specified device, the receive is
{ terminated.
{ If there is more than one message queued, the partial fragment
{ (which should be at the end of the list) is discarded.
{ Note that if a receiver is active on the device, there will be
{ no data queued.

 PROCEDURE process_clear_send_indication
   (    udp_connection: ^nlt$udp_socket_layer;
    VAR buffers_freed: nat$data_length);


   VAR
     current_task_id: ost$global_task_id,
     global_socket: ^nlt$udp_global_socket,
     ignore_status: ost$status,
     previous_received_message: ^^nlt$udp_received_message,
     received_message: ^nlt$udp_received_message,
     receiver_task: ^nlt$udp_receiver_task,
     socket_device_list: ^nlt$udp_socket_device_list;

   buffers_freed := 0;
   pmp$get_executing_task_gtid (current_task_id);
   nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
   IF global_socket <> NIL THEN
     IF (global_socket^.status <> nlc$udp_global_socket_closed) AND
           (global_socket^.status <> nlc$udp_global_socket_term) THEN
       socket_device_list := ^global_socket^.device_list;
       IF NOT socket_device_list^ [udp_connection^.device_id].discard_data THEN
         IF socket_device_list^ [udp_connection^.device_id].receiver_task <> NIL THEN
           receiver_task := socket_device_list^ [udp_connection^.device_id].receiver_task;
           IF receiver_task^.task_id = current_task_id THEN

{ Abort the receive.
{ Initialize all buffer attributes.

             receiver_task^.receive_buffer^ := receiver_task^.original_receive_buffer^;
             receiver_task^.buffer_length := receiver_task^.original_buffer_length;
             receiver_task^.received_data_length^ := 0;
             receiver_task^.device_id^ := 0;

{ Dequeue the receiver task. Terminate the receive if the receiver is
{ non blocking. For a blocking receive the nlp$udp_receive_data process
{ will decide if receive is to be terminated or restarted.

             socket_device_list^ [udp_connection^.device_id].receiver_task := NIL;
             IF receiver_task^.interface_mode = nac$sk_non_blocking_mode THEN
               receiver_task^.activity_status^.complete := TRUE;
               osp$set_status_condition (nae$sk_no_data_available, receiver_task^.activity_status^.status);
               osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10,
                     TRUE, receiver_task^.activity_status^.status);
               return_receiver_task_entry (global_socket, receiver_task);
             IFEND;
           ELSE { not the right task.

{ Queue the abort receive indication on the connection.

             IF global_socket^.device_list [udp_connection^.device_id].received_messages <> NIL THEN
               received_message := global_socket^.device_list [udp_connection^.device_id].received_messages;
               nlp$bm_release_message (received_message^.data);
               buffers_freed := received_message^.buffer_count;
               received_message^.abort_receive := TRUE;
               received_message^.buffer_count := 0;
             ELSE
               IF global_socket^.available_message_pool <> NIL THEN
                 received_message := global_socket^.available_message_pool;
                 global_socket^.available_message_pool := received_message^.next_entry;
                 global_socket^.available_message_pool_size := global_socket^.available_message_pool_size - 1;
               ELSE { pool empty
                 REPEAT
                   ALLOCATE received_message IN nav$network_paged_heap^;
                   IF received_message = NIL THEN
                     syp$cycle;
                   IFEND;
                 UNTIL received_message <> NIL;
               IFEND;
               received_message^.abort_receive := TRUE;
               received_message^.buffer_count := 0;
               received_message^.data := nlv$bm_null_message_id;
             IFEND;
             pmp$ready_task (receiver_task^.task_id, ignore_status);
           IFEND;
         ELSE { no receiver task

{ Dequeue the received message and return it to the pool.
{ Find the partial fragment.

           received_message := global_socket^.device_list [udp_connection^.device_id].received_messages;
           previous_received_message := ^global_socket^.device_list [udp_connection^.device_id].
                 received_messages;
           WHILE (received_message <> NIL) AND (received_message^.end_of_message) DO
             previous_received_message := ^received_message^.next_entry;
             received_message := received_message^.next_entry;
           WHILEND;
           IF received_message <> NIL THEN

{ Dequeue the partial fragment. The partial fragment should be the last fragment in
{ the queue.

             previous_received_message^ := NIL;
             buffers_freed := received_message^.buffer_count;
             nlp$bm_release_message (received_message^.data);
             return_received_message_entry (global_socket, received_message);
           ELSE { no partial fragment
             nap$namve_system_error ({Recoverable_error=} TRUE,
                   'Clear send indication received and no partial fragment found.', NIL);
           IFEND;
         IFEND;
       ELSE { discard data
         socket_device_list^ [udp_connection^.device_id].discard_data := FALSE;
       IFEND;
     IFEND;
     nlp$udp_free_exclusive_access (global_socket);
   IFEND;

 PROCEND process_clear_send_indication;
?? OLDTITLE ??
?? NEWTITLE := 'process_protocol_error', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to disconnect the channel
{ connection to the device. All senders and receivers are signalled
{ or readied. If no IO is active on the connection, the layer is
{ deactivated. The device status in the global socket is updated
{ to closed.

 PROCEDURE process_protocol_error
   (    cl_connection: ^nlt$cl_connection;
    VAR udp_connection: ^nlt$udp_socket_layer);

   VAR
     global_socket: ^nlt$udp_global_socket,
     ignore_status: ost$status,
     previous_sender_task: ^^nlt$udp_sender_task,
     receiver_task: ^nlt$udp_receiver_task,
     sender_task: ^nlt$udp_sender_task;

   IF udp_connection^.send_queue <> NIL THEN
     sender_task := udp_connection^.send_queue;
     previous_sender_task := ^udp_connection^.send_queue;

     WHILE sender_task <> NIL DO
       pmp$ready_task (sender_task^.task_id, ignore_status);
       IF sender_task^.send_type = nlc$udp_await_clear_to_send THEN
         previous_sender_task^ := sender_task^.next_entry;
         FREE sender_task IN nav$network_paged_heap^;
         sender_task := previous_sender_task^;
       ELSE
         previous_sender_task := ^sender_task^.next_entry;
         sender_task := sender_task^.next_entry;
       IFEND;
     WHILEND;
   IFEND;

   issue_disconnect (cl_connection, nlc$udpaa_rr_protocol_error);

{ Update the device status in the global socket.

   nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
   IF global_socket <> NIL THEN
     global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
     IF udp_connection^.send_queue = NIL THEN
       global_socket^.active_device_count := global_socket^.active_device_count - 1;
       deactivate_udp_layer (cl_connection, udp_connection);
     IFEND;
     IF global_socket^.device_list [udp_connection^.device_id].receiver_task <> NIL THEN
       receiver_task := global_socket^.device_list [udp_connection^.device_id].receiver_task;
       global_socket^.device_list [udp_connection^.device_id].receiver_task := NIL;

{ The receiver_task must be for the current task.

       receiver_task^.received_data_length^ := 0;
       IF receiver_task^.interface_mode = nac$sk_non_blocking_mode THEN
         receiver_task^.activity_status^.complete := TRUE;
         osp$set_status_condition (nae$sk_no_data_available, receiver_task^.activity_status^.status);
         osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
               receiver_task^.activity_status^.status);
         return_receiver_task_entry (global_socket, receiver_task);
       ELSE

{ Requeue the task in the receive wait queue.

         receiver_task^.next_entry := global_socket^.receive_wait_queue;
         global_socket^.receive_wait_queue := receiver_task;
       IFEND;
     IFEND;
     discard_received_messages (udp_connection^.device_id, global_socket);
     nlp$udp_free_exclusive_access (global_socket);
   ELSE
     nap$namve_system_error ({Recoverable_error=} TRUE,
           'Encountered a NIL global socket while processing a UDPAA protocol error.', NIL);
   IFEND;

 PROCEND process_protocol_error;
?? OLDTITLE ??
?? NEWTITLE := 'process_disconnect_indication', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to process the disconnect indication received when
{ the channel connection to the device is in the open state. All senders are idled
{ and partial received data is discarded. If no senders are active on the connection,
{ the layer connection is deactivated. Any receiver active on the device is dequeued
{ and readied.

 PROCEDURE process_disconnect_indication
   (    cl_connection: ^nlt$cl_connection;
    VAR udp_connection: ^nlt$udp_socket_layer);

   VAR
     global_socket: ^nlt$udp_global_socket,
     ignore_delete_global_socket: boolean,
     ignore_status: ost$status,
     previous_sender_task: ^^nlt$udp_sender_task,
     receiver_task: ^nlt$udp_receiver_task,
     sender_task: ^nlt$udp_sender_task;

   IF udp_connection^.send_queue <> NIL THEN
     sender_task := udp_connection^.send_queue;
     previous_sender_task := ^udp_connection^.send_queue;
     WHILE sender_task <> NIL DO
       pmp$ready_task (sender_task^.task_id, ignore_status);
       IF sender_task^.send_type = nlc$udp_await_clear_to_send THEN

{ Dequeue tasks waiting for a clear to send indication.

         previous_sender_task^ := sender_task^.next_entry;
         FREE sender_task IN nav$network_paged_heap^;
         sender_task := previous_sender_task^;
       ELSEIF sender_task^.send_type = nlc$udp_send_data THEN
         previous_sender_task := ^sender_task^.next_entry;
         sender_task := sender_task^.next_entry;
       IFEND;
     WHILEND;
   IFEND;

{ If no sender active then deactivate the layer.

   IF udp_connection^.send_queue = NIL THEN
     deactivate_udp_layer (cl_connection, udp_connection);

{ Update the socket device status in the global socket entry and
{ decrement the active device count.

     close_udp_socket_device (udp_connection^.global_socket_id, udp_connection^.device_id,
           ignore_delete_global_socket);
   ELSE
     nlp$udp_get_exclusive_via_gsid (udp_connection^.global_socket_id, global_socket);
     IF global_socket <> NIL THEN
       global_socket^.device_list [udp_connection^.device_id].status := nlc$udp_device_closed;
       IF global_socket^.device_list [udp_connection^.device_id].receiver_task <> NIL THEN

{ Requeue the receiver in the receive wait queue.

         receiver_task := global_socket^.device_list [udp_connection^.device_id].receiver_task;
         global_socket^.device_list [udp_connection^.device_id].receiver_task := NIL;
         pmp$ready_task (receiver_task^.task_id, ignore_status);
         receiver_task^.next_entry := global_socket^.receive_wait_queue;
         global_socket^.receive_wait_queue := receiver_task;
         discard_received_messages (udp_connection^.device_id, global_socket);
       IFEND;
       nlp$udp_free_exclusive_access (global_socket);
     IFEND;
   IFEND;
   udp_connection^.state := nlc$udp_conn_closed;

 PROCEND process_disconnect_indication;
?? OLDTITLE ??
?? NEWTITLE := 'receive_data', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to deliver the next queued
{ messages to the receiver task queued on the specified device.
{ If the receive is to be terminated on account of an abort
{ request, the receiver task is queued back in the receive
{ queue on the global socket. It is assumed that this procedure
{ is called when there is a message queued on the device and a
{ receiver active on the device.

 PROCEDURE receive_data
   (VAR global_socket: ^nlt$udp_global_socket;
        device_id: nlt$device_identifier;
    VAR buffers_freed: nat$data_length);

?? NEWTITLE := 'terminate_receive', EJECT ??

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

     nap$condition_handler_trace (condition, sa);
     CASE condition.selector OF
     = pmc$system_conditions, mmc$segment_access_condition =
       IF receiver_task <> NIL THEN

{ Mark receive complete.

         receiver_task^.activity_status^.complete := TRUE;
         nlp$udp_deactivate_receiver (global_socket^.active_receiver);
         osp$set_status_from_condition (nac$status_id, condition, sa, receiver_task^.activity_status^.status,
               condition_status);
         condition_status.normal := TRUE;

{ Dequeue the receiver task.

         socket_device_list^ [device_id].receiver_task := NIL;
         IF NOT received_message^.end_of_message THEN
           buffers_freed := received_message^.buffer_count;
           socket_device_list^ [device_id].discard_data := TRUE;
         ELSEIF global_socket^.receive_wait_queue <> NIL THEN
           pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
         IFEND;

         return_received_message_entry (global_socket, received_message);
         return_receiver_task_entry (global_socket, receiver_task);
       IFEND;

       EXIT receive_data;
     = pmc$user_defined_condition =
       IF condition.user_condition_name = osc$job_recovery_condition_name THEN
         pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
       IFEND;
       condition_status.normal := TRUE;
     ELSE
       condition_status.normal := TRUE;
     CASEND;

   PROCEND terminate_receive;
?? OLDTITLE ??
?? EJECT ??

   VAR
     delivered_data_length: integer,
     ignore_status: ost$status,
     previous_received_message: ^nlt$udp_received_message,
     received_message: ^nlt$udp_received_message,
     receiver_task: ^nlt$udp_receiver_task,
     socket_device_list: ^nlt$udp_socket_device_list;

   buffers_freed := 0;
   socket_device_list := ^global_socket^.device_list;
   #SPOIL (socket_device_list);

{ Dequeue received message.

   receiver_task := socket_device_list^ [device_id].receiver_task;
   #SPOIL (receiver_task);
   received_message := socket_device_list^ [device_id].received_messages;
   #SPOIL (received_message);
   socket_device_list^ [device_id].received_messages := received_message^.next_entry;
   osp$establish_condition_handler (^terminate_receive, FALSE);

   IF NOT received_message^.abort_receive THEN

{ Here it is assumed that the data meets the receiver's selection criteria.
{ The receiver task is queued on the socket device entry only after the selection
{ criteria is matched.

     IF ((received_message^.data_length < receiver_task^.buffer_length) AND
           (NOT received_message^.end_of_message)) OR ((received_message^.data_length <=
           receiver_task^.buffer_length) AND (received_message^.end_of_message)) THEN
       nlp$bm_deliver_message (receiver_task^.receive_buffer^, received_message^.data, delivered_data_length,
             buffers_freed);
       receiver_task^.end_of_message := received_message^.end_of_message;
       receiver_task^.received_data_length^ := receiver_task^.received_data_length^ +delivered_data_length;
       receiver_task^.buffer_length := receiver_task^.buffer_length - delivered_data_length;
       receiver_task^.foreign_socket^ := received_message^.source_socket;
       receiver_task^.local_ip_address^ := received_message^.destination_ip_address;
       IF receiver_task^.end_of_message THEN
         receiver_task^.activity_status^.complete := TRUE;

{ Dequeue and return the receiver task.

         socket_device_list^ [device_id].receiver_task := NIL;
         return_receiver_task_entry (global_socket, receiver_task);
         nlp$udp_deactivate_receiver (global_socket^.active_receiver);

{ Activate the next receiver.

         IF global_socket^.receive_wait_queue <> NIL THEN
           pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
         IFEND;
       IFEND;
     ELSE { data area too small

{ Discard the data and terminate receive.

       nlp$bm_release_message (received_message^.data);
       buffers_freed := received_message^.buffer_count;
       IF NOT received_message^.end_of_message THEN
         socket_device_list^ [device_id].discard_data := TRUE;
       IFEND;
       receiver_task^.activity_status^.complete := TRUE;

{ Dequeue the receiver task.

       socket_device_list^ [device_id].receiver_task := NIL;
       osp$set_status_condition (nae$sk_data_area_too_small, receiver_task^.activity_status^.status);
       osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
             receiver_task^.activity_status^.status);
       return_receiver_task_entry (global_socket, receiver_task);
       IF global_socket^.receive_wait_queue <> NIL THEN
         pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
       IFEND;
     IFEND;
   ELSE { abort receive

{ Abort the current receive.
{ Reset the buffer area.

     receiver_task^.receive_buffer^ := receiver_task^.original_receive_buffer^;
     receiver_task^.buffer_length := receiver_task^.original_buffer_length;
     receiver_task^.received_data_length^ := 0;
     receiver_task^.device_id^ := 0;
     receiver_task^.connection_id := nac$null_connection_id;

{ Dequeue the receiver task.

     socket_device_list^ [device_id].receiver_task := NIL;

{ The global socket is assumed to be open.

     IF receiver_task^.interface_mode = nac$sk_blocking_mode THEN

{ Queue the receiver back in the receive queue.

       receiver_task^.next_entry := global_socket^.receive_wait_queue;
       global_socket^.receive_wait_queue := receiver_task;
     ELSE { non blocking mode
       receiver_task^.activity_status^.complete := TRUE;
       osp$set_status_condition (nae$sk_no_data_available, receiver_task^.activity_status^.status);
       osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10, TRUE,
             receiver_task^.activity_status^.status);
       return_receiver_task_entry (global_socket, receiver_task);
       nlp$udp_deactivate_receiver (global_socket^.active_receiver);
       IF global_socket^.receive_wait_queue <> NIL THEN
         pmp$ready_task (global_socket^.receive_wait_queue^.task_id, ignore_status);
       IFEND;
     IFEND;
   IFEND;
   return_received_message_entry (global_socket, received_message);

 PROCEND receive_data;
?? OLDTITLE ??
?? NEWTITLE := 'return_received_message_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the given received
{ message entry. It is either added to the pool of received message
{ entries or is freed.

 PROCEDURE [INLINE] return_received_message_entry
   (VAR global_socket: ^nlt$udp_global_socket;
    VAR received_message: ^nlt$udp_received_message);

   IF (global_socket^.status <> nlc$udp_global_socket_closed) AND
         (global_socket^.status <> nlc$udp_global_socket_term) THEN
     IF global_socket^.available_message_pool_size < (nlc$udp_max_pool_size) *
           UPPERBOUND (global_socket^.device_list) THEN
       received_message^.next_entry := global_socket^.available_message_pool;
       global_socket^.available_message_pool := received_message;
       global_socket^.available_message_pool_size := global_socket^.available_message_pool_size + 1;
     ELSE { max pool size
       FREE received_message IN nav$network_paged_heap^;
     IFEND;
   ELSE { global_socket closed or terminated
     FREE received_message IN nav$network_paged_heap^;
   IFEND;
   received_message := NIL;

 PROCEND return_received_message_entry;
?? OLDTITLE ??
?? NEWTITLE := 'return_receiver_task_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the given receiver
{ task entry. It is either added to the pool of receiver task
{ entries or is freed via the heap manager FREE call.

 PROCEDURE [INLINE] return_receiver_task_entry
   (VAR global_socket: ^nlt$udp_global_socket;
    VAR receiver_task: ^nlt$udp_receiver_task);

   VAR
     pool_entry: ^nlt$udp_receiver_task,
     pool_size: integer;

   IF (global_socket^.status <> nlc$udp_global_socket_closed) AND
         (global_socket^.status <> nlc$udp_global_socket_term) THEN
     receiver_task^.next_entry := NIL;
     IF global_socket^.available_receiver_pool = NIL THEN
       global_socket^.available_receiver_pool := receiver_task;
     ELSE { calculate the queue size
       pool_size := 1;
       pool_entry := global_socket^.available_receiver_pool;
       WHILE pool_entry^.next_entry <> NIL DO
         pool_entry := pool_entry^.next_entry;
         pool_size := pool_size + 1;
       WHILEND;
       IF pool_size < nlc$udp_max_pool_size THEN
         pool_entry^.next_entry := receiver_task;
       ELSE
         FREE receiver_task IN nav$network_paged_heap^;
       IFEND;
     IFEND;
     receiver_task := NIL;
   ELSE { global socket closed or terminated
     FREE receiver_task IN nav$network_paged_heap^;
   IFEND;

 PROCEND return_receiver_task_entry;
?? OLDTITLE ??
?? NEWTITLE := 'return_sender_task_entry', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to return the given sender
{ task entry. It is either added to the pool of sender task
{ entries or is freed via the heap manager FREE call.

 PROCEDURE [INLINE] return_sender_task_entry
   (VAR udp_connection: ^nlt$udp_socket_layer;
    VAR sender_task: ^nlt$udp_sender_task);

   VAR
     pool_entry: ^nlt$udp_sender_task,
     pool_size: integer;

   IF udp_connection^.state <> nlc$udp_conn_closed THEN
     sender_task^.next_entry := NIL;
     IF udp_connection^.available_sender_pool = NIL THEN
       udp_connection^.available_sender_pool := sender_task;
     ELSE { calculate the queue size
       pool_size := 1;
       pool_entry := udp_connection^.available_sender_pool;
       WHILE pool_entry^.next_entry <> NIL DO
         pool_entry := pool_entry^.next_entry;
         pool_size := pool_size + 1;
       WHILEND;
       IF pool_size < nlc$udp_max_pool_size THEN
         pool_entry^.next_entry := sender_task;
       ELSE
         FREE sender_task IN nav$network_paged_heap^;
       IFEND;
     IFEND;
     sender_task := NIL;
   ELSE { udp_connection^.state = nlc$udp_conn_closed
     FREE sender_task IN nav$network_paged_heap^;
   IFEND;

 PROCEND return_sender_task_entry;
?? OLDTITLE ??
?? NEWTITLE := 'scan_connections_for_data', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to access the specified connections,
{ update the inventory and to pull up any data that may have been queued
{ on it.

 PROCEDURE scan_connections_for_data
   (    socket_inventory: ^nlt$udp_socket_inventory;
        receiver_task: ^nlt$udp_receiver_task);


   VAR
     cl_connection: ^nlt$cl_connection,
     connection_exists: boolean,
     device_id: nlt$device_identifier,
     i: integer,
     layer_active: boolean,
     udp_connection: ^nlt$udp_socket_layer;

   IF receiver_task^.received_data_length^ > 0 THEN

{ Receive has been initiated as a result of scanning the global socket.

     FOR device_id := 1 TO UPPERBOUND (socket_inventory^) DO
       IF socket_inventory^ [device_id].buffers_freed > 0 THEN

{ It is assumed that buffers must have been freed in order to initiate receive.

         nlp$cl_get_exclusive_via_cid (socket_inventory^ [device_id].connection_id, connection_exists,
               cl_connection);
         IF cl_connection <> NIL THEN
           nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
           IF (layer_active) AND (udp_connection^.state = nlc$udp_conn_open) THEN
             udp_connection^.inventory_report := udp_connection^.inventory_report -
                   socket_inventory^ [device_id].buffers_freed;
             nlp$cc_report_undelivered_data (cl_connection, udp_connection^.inventory_report);
             IF socket_inventory^ [device_id].connection_id = receiver_task^.connection_id THEN

{ This is the receiving connection.

               nlp$cc_receive_data (cl_connection);
             IFEND;
           IFEND;
           nlp$cl_release_exclusive_access (cl_connection);
         IFEND;
       IFEND;
     FOREND;
   ELSE { received_data_length = 0

{ Receive has not been initiated on scanning the global socket.

     FOR device_id := 1 TO UPPERBOUND (socket_inventory^) DO
       IF socket_inventory^ [device_id].connection_id <> nac$null_connection_id THEN
         nlp$cl_get_exclusive_via_cid (socket_inventory^ [device_id].connection_id, connection_exists,
               cl_connection);
         IF cl_connection <> NIL THEN
           nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
           IF (layer_active) AND ((udp_connection^.state = nlc$udp_conn_open) OR
                 (udp_connection^.state = nlc$udp_conn_await_confirm)) THEN
             IF socket_inventory^ [device_id].buffers_freed > 0 THEN
               udp_connection^.inventory_report := udp_connection^.inventory_report -
                     socket_inventory^ [device_id].buffers_freed;
               nlp$cc_report_undelivered_data (cl_connection, udp_connection^.inventory_report);
             IFEND;

{ The following code must be executed only if receive has not been initiated on any
{ connection.

             IF (NOT receiver_task^.activity_status^.complete) AND
                   (receiver_task^.received_data_length^ = 0) THEN
               nlp$cc_receive_data (cl_connection);
             IFEND;
           IFEND;
           nlp$cl_release_exclusive_access (cl_connection);
         IFEND;
       IFEND;
     FOREND;
   IFEND;

 PROCEND scan_connections_for_data;
?? OLDTITLE ??
?? NEWTITLE := 'scan_global_socket_for_data', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to scan the UDP devices
{ in the global socket for complete or partial messages. If a
{ complete message is found and the selection criteria is met,
{ the messages is moved to the user's buffer and the receive
{ is marked as complete. If the selection criteria is not met
{ the message is discarded. If a partial message is dicarded,
{ a discard data flag is set on the device entry so that the
{ remaining fragments for the message can be discarded. The flag
{ is cleared when the last fragment is received for the message.
{ If a partial message is found it is also moved to the user's
{ buffer and the receive is marked as incomplete. It is the
{ caller's responsibility to queue the receiver task on the
{ device entry.
{   If the user's buffer is not large enough to hold the message
{ (complete or partial) the message is discarded and an abnormal
{ status is returned to the caller. The discard data flag is set
{ if the partial message is discarded

 PROCEDURE scan_global_socket_for_data
   (VAR global_socket: ^nlt$udp_global_socket;
    VAR receive_buffer: nat$data_fragments;
    VAR remaining_buffer_length { input, output } : integer;
    VAR received_data_length: integer;
        selection_criteria: nat$sk_socket_address;
    VAR foreign_socket: nat$sk_socket_address;
    VAR local_ip_address: nat$sk_ip_address;
    VAR socket_inventory: nlt$udp_socket_inventory;
    VAR receiving_device_id: nlt$device_identifier;
    VAR activity_status: ost$activity_status);

?? NEWTITLE := 'terminate_scan', EJECT ??

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

     nap$condition_handler_trace (condition, sa);
     CASE condition.selector OF
     = pmc$system_conditions, mmc$segment_access_condition =
       activity_status.complete := TRUE;
       IF received_message <> NIL THEN
         IF NOT received_message^.end_of_message THEN
           socket_device_list^ [device_id].discard_data := TRUE;
         IFEND;
         socket_device_list^ [device_id].received_messages := received_message^.next_entry;
         osp$set_status_from_condition (nac$status_id, condition, sa, activity_status.status,
               condition_status);
         condition_status.normal := TRUE;
         return_received_message_entry (global_socket, received_message);
       IFEND;

       EXIT scan_global_socket_for_data;
     = pmc$user_defined_condition =
       IF condition.user_condition_name = osc$job_recovery_condition_name THEN
         pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
       IFEND;
       condition_status.normal := TRUE;
     ELSE
       condition_status.normal := TRUE;
     CASEND;

   PROCEND terminate_scan;
?? OLDTITLE ??
?? EJECT ??

   VAR
     buffers_freed: nat$data_length,
     device_id: nlt$device_identifier,
     received_message: ^nlt$udp_received_message,
     socket_device_list: ^nlt$udp_socket_device_list;

   activity_status.complete := FALSE;
   activity_status.status.normal := TRUE;
   received_data_length := 0;
   received_message := NIL;
   #SPOIL (received_message);

{ Search for a complete message. Start from next to the last device data was
{ received over.

   socket_device_list := ^global_socket^.device_list;
   #SPOIL (socket_device_list);
   FOR device_id := 1 TO UPPERBOUND (socket_inventory) DO
     socket_inventory [device_id].buffers_freed := 0;
     socket_inventory [device_id].connection_id := nac$null_connection_id;
   FOREND;
   device_id := global_socket^.last_receiving_device;
   osp$establish_condition_handler (^terminate_scan, FALSE);

 /examine_complete_messages/
   REPEAT
     IF device_id < UPPERBOUND (socket_device_list^) THEN
       device_id := device_id + 1;
     ELSE
       device_id := 1;
     IFEND;
     #SPOIL (device_id);
     IF (socket_device_list^ [device_id].status = nlc$udp_device_open) OR
           (socket_device_list^ [device_id].status = nlc$udp_device_await_confirm) THEN
       socket_inventory [device_id].connection_id := socket_device_list^ [device_id].connection_id;
     IFEND;

     IF (socket_device_list^ [device_id].receiver_task = NIL) AND
           (socket_device_list^ [device_id].received_messages <> NIL) THEN
       received_message := socket_device_list^ [device_id].received_messages;
       #SPOIL (received_message);
       WHILE (received_message <> NIL) AND (received_message^.end_of_message) DO

{ Verify selection criteria.

         IF ((selection_criteria.ip_address = 0) OR (selection_criteria.ip_address =
               received_message^.source_socket.ip_address)) AND
               ((selection_criteria.port = 0) OR (selection_criteria.port =
               received_message^.source_socket.port)) THEN
           IF received_message^.data_length <= remaining_buffer_length THEN
             nlp$bm_deliver_message (receive_buffer, received_message^.data, received_data_length,
                   buffers_freed);
             remaining_buffer_length := remaining_buffer_length - received_data_length;
             socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                   buffers_freed;
             global_socket^.last_receiving_device := device_id;
             foreign_socket := received_message^.source_socket;
             local_ip_address := received_message^.destination_ip_address;
             receiving_device_id := device_id;
           ELSE { buffer length too small

{ Discard the complete message.

             nlp$bm_release_message (received_message^.data);
             socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                   received_message^.buffer_count;
             osp$set_status_condition (nae$sk_data_area_too_small, activity_status.status);
             osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10,
                   TRUE, activity_status.status);
           IFEND;

{ Dequeue the received message.

           activity_status.complete := TRUE;
           socket_device_list^ [device_id].received_messages := received_message^.next_entry;
           return_received_message_entry (global_socket, received_message);
           EXIT /examine_complete_messages/;
         ELSE { selection criteria not met

{ Discard received message.

           nlp$bm_release_message (received_message^.data);
           socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                 received_message^.buffer_count;
           socket_device_list^ [device_id].received_messages := received_message^.next_entry;
           return_received_message_entry (global_socket, received_message);
           received_message := socket_device_list^ [device_id].received_messages;
           #SPOIL (received_message);
         IFEND;
       WHILEND
     IFEND;
   UNTIL device_id = global_socket^.last_receiving_device;

   IF NOT activity_status.complete THEN

{ Make a second pass over the devices to see if there is a partial message available.

     device_id := global_socket^.last_receiving_device;

   /examine_partial_messages/
     REPEAT
       IF device_id < UPPERBOUND (socket_device_list^) THEN
         device_id := device_id + 1;
       ELSE
         device_id := 1;
       IFEND;
       #SPOIL (device_id);

{ There shouldn't be any partial messages queued on a device that is down.

       IF socket_device_list^ [device_id].status = nlc$udp_device_open THEN
         IF (socket_device_list^ [device_id].receiver_task = NIL) AND
               (socket_device_list^ [device_id].received_messages <> NIL) THEN
           received_message := socket_device_list^ [device_id].received_messages;
           #SPOIL (received_message);
           IF ((selection_criteria.ip_address = 0) OR (selection_criteria.ip_address =
                 received_message^.source_socket.ip_address)) AND
                 ((selection_criteria.port = 0) OR (selection_criteria.port =
                 received_message^.source_socket.port)) THEN
             IF received_message^.data_length < remaining_buffer_length THEN
               nlp$bm_deliver_message (receive_buffer, received_message^.data, received_data_length,
                     buffers_freed);
               remaining_buffer_length := remaining_buffer_length - received_data_length;
               socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                     buffers_freed;
               receiving_device_id := device_id;
               global_socket^.last_receiving_device := device_id;
               foreign_socket := received_message^.source_socket;
               local_ip_address := received_message^.destination_ip_address;
             ELSE { buffer area too small

{ Discard the partial message.

               nlp$bm_release_message (received_message^.data);
               socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                     received_message^.buffer_count;
               osp$set_status_condition (nae$sk_data_area_too_small, activity_status.status);
               osp$append_status_integer (osc$status_parameter_delimiter, global_socket^.local_socket_id, 10,
                     TRUE, activity_status.status);
               socket_device_list^ [device_id].discard_data := TRUE;
               activity_status.complete := TRUE;
             IFEND;

{ Dequeue the received message.

             socket_device_list^ [device_id].received_messages := received_message^.next_entry;
             return_received_message_entry (global_socket, received_message);
             EXIT /examine_partial_messages/;
           ELSE { selection criteria not met

{ Discard the partial message.

             nlp$bm_release_message (received_message^.data);
             socket_inventory [device_id].buffers_freed := socket_inventory [device_id].buffers_freed +
                   received_message^.buffer_count;

{ Dequeue partial received message. Note that only one partial message can be queued
{ on the socket device.

             socket_device_list^ [device_id].received_messages := received_message^.next_entry;
             return_received_message_entry (global_socket, received_message);
             socket_device_list^ [device_id].discard_data := TRUE;
           IFEND;
         IFEND;
       IFEND;
     UNTIL device_id = global_socket^.last_receiving_device;
   IFEND;

 PROCEND scan_global_socket_for_data;
?? OLDTITLE ??
?? NEWTITLE := 'send_data', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to fragment and send data
{ over the given channel connection. IF the interface mode is
{ non blocking, all data is pushed to the channel connection
{ interface event if there is no outbound capacity. It is
{ assumed that this procedure will be called only if there is
{ outbound capacity to begin with.
{   It is assumed that on entry, the first data fragment points
{ to the protocol header and the current lowerbound is the index
{ of the next user data fragment to be sent. If all data is not
{ sent, on output, the new lowerbound will point to the first
{ fragment in the remaining data.

 PROCEDURE send_data
   (    cl_connection: ^nlt$cl_connection;
        initial_capacity: nat$data_length;
        initial_send: boolean;
    VAR data_fragments: nat$data_fragments;
        data_length: integer;
        interface_mode: nat$sk_interface_mode;
        starting_fragment: nat$data_fragment_count;
    VAR remaining_fragment: nat$data_fragment_count;
    VAR remaining_data_length: integer);

?? NEWTITLE := 'terminate_internal_send', EJECT ??

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

     nap$condition_handler_trace (condition, sa);
     CASE condition.selector OF
     = pmc$system_conditions, mmc$segment_access_condition =
       IF (NOT initial_send) OR (remaining_data_length < data_length) THEN
         abort_send (cl_connection);
       IFEND;
       pmp$continue_to_cause (pmc$inhibit_standard_procedure, condition_status);
       remaining_data_length := 0;
       condition_status.normal := TRUE;
       EXIT send_data;

     = pmc$user_defined_condition =
       IF condition.user_condition_name = osc$job_recovery_condition_name THEN
         pmp$continue_to_cause (pmc$execute_standard_procedure, condition_status);
       IFEND;
       condition_status.normal := TRUE;

     ELSE

{ Note: Interactive condition is being ignored.

       condition_status.normal := TRUE;
     CASEND;

   PROCEND terminate_internal_send;
?? OLDTITLE, EJECT ??

   VAR
     actual_fragment_size: integer,
     capacity: nat$data_length,
     current_lowerbound: nat$data_fragment_count,
     fragment: ^nat$data_fragments,
     fragment_size: integer,
     ignore_status: ost$status,
     new_lowerbound: nat$data_fragment_count,
     udpaa_data_header: ^nlt$udpaa_data_request;


   capacity := initial_capacity;
   udpaa_data_header := data_fragments [1].address;
   osp$establish_condition_handler (^terminate_internal_send, FALSE);
   IF capacity >= data_length THEN
     udpaa_data_header^.header.length := data_length + #SIZE (udpaa_data_header^);
     udpaa_data_header^.end_of_message := TRUE;
     nlp$cc_send_data_fragments (cl_connection, data_fragments, ignore_status);
     remaining_data_length := 0;
   ELSE { Insufficient outbound capacity
     current_lowerbound := starting_fragment;
     remaining_data_length := data_length;
     #SPOIL (remaining_data_length);
     new_lowerbound := current_lowerbound;
     PUSH fragment: [1 .. UPPERBOUND (data_fragments)];
     REPEAT

{ Move the protocol header such that it is the first fragment in the
{ next data block to be sent.

       IF new_lowerbound > 2 THEN
         current_lowerbound := new_lowerbound - 1;

{ Initialize the previous entry to point to the TCPAA pdu header.

         data_fragments [current_lowerbound] := data_fragments [1];
       IFEND;
       IF remaining_data_length > capacity THEN
         fragment_size := capacity;
       ELSE
         fragment_size := remaining_data_length;
         udpaa_data_header^.end_of_message := TRUE;
       IFEND;

{ The actual fragment must include the protocol header.

       actual_fragment_size := fragment_size + #SIZE (udpaa_data_header^);
       nlp$sk_fragment_data (actual_fragment_size, current_lowerbound, data_fragments, new_lowerbound,
             fragment^);

{ Reset the length of the PDU header fragment.

       data_fragments [1].length := #SIZE (udpaa_data_header^);
       udpaa_data_header^.header.length := actual_fragment_size;
       nlp$cc_send_data_fragments (cl_connection, fragment^, ignore_status);
       remaining_data_length := remaining_data_length - fragment_size;
       #SPOIL (remaining_data_length);
       IF remaining_data_length > 0 THEN
         nlp$osi_get_outbound_capacity (cl_connection, capacity);

{ If the interface mode is non blocking push all the data to the channel connection
{ interface.

         IF (capacity = 0) AND (interface_mode = nac$sk_non_blocking_mode) THEN
           capacity := initial_capacity;
         IFEND;
       IFEND;
     UNTIL (remaining_data_length = 0) OR (capacity = 0);
   IFEND;
   remaining_fragment := new_lowerbound;

 PROCEND send_data;
?? OLDTITLE ??
?? NEWTITLE := 'update_inventory', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to update the inventory
{ report on the active connections for a socket.

 PROCEDURE update_inventory
   (    socket_inventory: ^nlt$udp_socket_inventory);

   VAR
     cl_connection: ^nlt$cl_connection,
     connection_exists: boolean,
     device_id: nlt$device_identifier,
     layer_active: boolean,
     udp_connection: ^nlt$udp_socket_layer;

   FOR device_id := 1 TO UPPERBOUND (socket_inventory^) DO
     IF (socket_inventory^ [device_id].connection_id <> nac$null_connection_id) AND
           (socket_inventory^ [device_id].buffers_freed > 0) THEN
       nlp$cl_get_exclusive_via_cid (socket_inventory^ [device_id].connection_id, connection_exists,
             cl_connection);
       IF cl_connection <> NIL THEN
         nlp$cl_get_layer_connection (nlc$udp_interface, cl_connection, layer_active, udp_connection);
         IF layer_active THEN
           udp_connection^.inventory_report := udp_connection^.inventory_report -
                 socket_inventory^ [device_id].buffers_freed;
           nlp$cc_report_undelivered_data (cl_connection, udp_connection^.inventory_report);
         IFEND;
         nlp$cl_release_exclusive_access (cl_connection);
       IFEND;
     IFEND;
   FOREND;

 PROCEND update_inventory;
?? OLDTITLE ??
 MODEND nlm$udp_access_agent;
