MODULE nlm$directory_management_entity;
?? LEFT := 1, RIGHT := 110 ??

{ PURPOSE:
{       1.   Register  a title and corresponding address over a
{       specified translation domain.
{
{       2.   Translate  a  title.  Return one or more addresses
{       for valid translations of the title over a specified
{       search domain.
{
{ EXTERNAL INTERFACES:
{
{       Directory uses the OSI NETWORK layer.
{
{  DIRECTORY COMPONENTS:
{
{       The  Directory  M-E  is  contained in one module called
{       nlm$directory_management_entity.  There are 3 basic parts
{       to the module.
{
{       1.  Directory management.
{       Receives directory data units from other Directory M-Es.
{       Periodically distributes registered titles and translation
{       requests. Deletes expired cache entries.
{
{       2.  Registration.  The registration section consists of
{       user  called procedures which create, change, or delete
{       a directory  entry.   The  Registration  Data Store, RDS,
{       holds  these  titles.  The create  and  delete
{       primitives only  search  the  RDS  table.
{       The registration procedures are:
{
{         . nlp$register_title
{         . nlp$delete_registered_title
{
{       3.  Translation.  The translation section consists of a
{       user   called   procedure   which   requests   a  title
{       translation.    The   user  can  also  request  that  a
{       translation   request   be   terminated.    Outstanding
{       translation  requests  are  put  into  the  Translation
{       Request Data Store, TRDS.   Translations are obtained by
{       calling another procedure. The translation procedures are:
{
{         . nlp$translate_title
{         . nap$check_title_translation
{         . nlp$get_title_translation
{         . nlp$end_title_translation
{
{       The possible PDUs are:
{
{       A.   Translation  Data Unit.  The Translation Data unit
{       may be broadcast over the  distribution   domain  after
{       registration or change. The algorithm for  broadcasting
{       Translation   Data   Units  is: distribute  n times at
{       time1 intervals, then continue indefinitely at time2
{       intervals. Current  value for time1 is 1 minute and
{       current value for n is 3. time2 is currently set to 15 minutes.
{       A Translation Data Unit is also returned to the requesting
{       system in response to a Translation Request Data Unit.
{
{       B.    Delete   Translation  Data  Unit.   This  PDU  is
{       broadcast  over  the  title  domain  after  the RDS
{       entry is deleted when distribution was requested on
{       registration.
{
{       C.   Translation  Request  Data  Unit.  The Translation
{       Request  Data Unit is periodically broadcast  over  the
{       search  domain.  The time delay for broadcasting  these
{       data  units  is 5 seconds. Since directory data units are
{       sent at system priority, network traffic levels should not
{       delay them significantly.
{
{       D.   Start-up Data Unit.  Directory M-E broadcasts this
{       PDU to  remote  Directory  M-E's  in the catenet during
{       Directory  M-E  initialization.  It causes  the  remote
{       Directory  M-E's to  delete  old  entries  out  of  the
{       Translation Data Store. The start-up data unit is sent
{       out 5 times at one minute intervals to allow for loading
{       MDIs and NDIs to gain access to the entire catenet.
{
{  DATA STORES:
{
{       1.   Registration  Data  Store,  RDS.   This  store
{       contains  all  the  currently registered titles.  These
{       titles were created by  a primitive.  Duplicate entries
{       are rejected.  There  is no maximum set for the
{       number of RDS entries.  The data  store is allocated in
{       the network paged heap and entries are allocated
{       dynamically  until the  heap is full.
{
{       2.   Translation  Data  Store,  TDS.   This  data store
{       contains  a  list  of the most recent translation  data
{       units received from other  systems. The TDS  data  store
{       is also allocated  in  the  network paged heap. The  TDS
{       data structure  is  the  same as the RDS data structure.
{       Entries are retained in the TDS only for a specified time
{       period after receipt (currently 1 minute) or until a
{       delete translation  data unit is received. Only maximum
{       priority translations are saved in the cache.
{
{       3.   Translation  Request Data Store, TRDS.  This store
{       contains all the currently active translation requests.
{       There is no maximum set for the number of TRDS entries.
{       The data store is allocated  in  the network paged heap.
{       A translation request entry will be deleted after a fixed
{       delay after completion of the search for a non-recurrent
{       search (currently 10 minutes) or when terminated by the
{       requestor. A recurrent search will be deleted when the
{       requestor is no longer present or when the status of the
{       request is not checked periodically (at least once every
{       ten minutes).

?? NEWTITLE := 'Directory Tuning Options', EJECT ??

  CONST

{ * * * PERIODIC TIMES IN SECONDS FOR DISTRIBUTION OF THE REGISTERED
{       TITLE IF DISTRIBUTION IS REQUESTED.

    nac$title_distribution_delay = 60000000 {microseconds} ,
    nac$title_redistribute_delay = 900000000 {microseconds} ,
    nac$title_distribution_count = 3,

{       INCREMENTAL DELAY USED FOR SENDING TRANSLATION REQUEST PDUS

    nac$translation_request_delay = 5000000 {microseconds} ,
    nac$max_request_broadcast_count = 3,

{       TRANSLATION CACHE AGING TIME AFTER RECEIPT (nac$cache_timeout)

    nac$cache_timeout = 60000000 {microseconds} ,

{       DIRECTORY MANAGER MAX CYCLE TIME (must be less than 48 bits)

    nac$very_long_time = 03fffffffffff(16),

{       TIMEOUT FOR DELIVERY OF TRANSLATIONS FOR AN ACTIVE SEARCH

    nac$max_request_hold_time = 600000000 {microseconds} ,

{       NUMBER AND INTERVAL FOR STARTUP DATA UNIT TRANSMISSION AT STARTUP

    nac$max_startup_pdu_count = 5,
    nac$startup_pdu_interval = 60000000 {microseconds; one minute} ;

{       PREFIX FOR DIRECTORY ASSIGNED USER IDENTIFIER VALUES

  CONST
    nac$default_user_identifier = 'NAI$';

?? OLDTITLE ??
?? NEWTITLE := 'directory type declarations', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc clt$parameter_list
*copyc nac$reserved_saps
*copyc nae$application_interfaces
*copyc nae$directory_me_conditions
*copyc nae$namve_conditions
*copyc nat$community_title
*copyc nat$directory_data
*copyc nat$directory_entry_identifier
*copyc nat$directory_search_identifier
*copyc nat$directory_interfaces
*copyc nat$directory_priority
*copyc nat$network_address_kind
*copyc nat$network_selector
*copyc nat$osi_address_length
*copyc nat$osi_network_address
*copyc nat$osi_presentation_address
*copyc nat$osi_session_address
*copyc nat$osi_transport_address
*copyc nat$protocol
*copyc nat$subnet_identifier
*copyc nat$system_identifier
*copyc nat$title
*copyc nat$title_pattern
*copyc nlt$device_identifier
*copyc nlt$network_device
*copyc nlt$system_management
*copyc nlt$ta_sap_selector
*copyc ost$name
*copyc ost$string
*copyc pmt$condition
*copyc pmt$established_handler
?? POP ??

{ * * * MAXIMUM PROTOCOL DATA UNIT SIZE AND CURRENT VERSION NUMBER.

  CONST
    nac$max_directory_pdu_size = 1400,
    nac$osi_directory_version = 2,
    nac$directory_version_3 = 3;

{ * * * DIRECTORY ME PROTOCOL DATA UNIT ID VALUES.

  CONST
    nac$translation_req_data_unit = 1,
    nac$translation_data_unit = 2,
    nac$del_translation_data_unit = 3,
    nac$startup_data_unit = 4,
    nac$v3_translation_rq_data_unit = 5,
    nac$v3_translation_data_unit = 6,
    nac$v3_dl_translation_data_unit = 7,
    nac$v3_startup_data_unit = 8;

{ * * * MINIMUM RING VALIDATION FOR USING THE NETWORK_OPERATOR_UTILITY

  CONST
    minimum_ring_allowed = 6;

{ * * * PROTOCOL DATA UNIT DEFINITION

  CONST
    nac$initial_pdu_lifetime = 64;

  TYPE
    pdu_header = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
    recend;

  TYPE
    translation_request_pdu = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      title_size: nat$title_length,
      protocol: nat$protocol,
      info: packed record
        wild_card: boolean,
        class: nat$title_class,
      recend,
      community_count: 0 .. 0ff(16),
      {communities: array [ community_count ] of nat$community_title,
      {title: string (title_size),
    recend;

  TYPE
    translation_request_pdu_v3 = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      sequence: 0 .. 0ffff(16),
      lifetime: 0 .. 0ffff(16),
      title_size: nat$title_length,
      source_nsap_length: 0 .. 0ff(16),
      protocol: nat$protocol,
      info: packed record
        wild_card: boolean,
        class: nat$title_class,
      recend,
      {title: string (title_size),
      {source_nsap_address: string (source_nsap_length),
    recend;

  TYPE
    translation_pdu = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      identifier: nat$directory_entry_identifier,
      change_count: 0 .. 0ffff(16),
      title_size: nat$title_length,
      address: record
        kind: nat$network_address_kind,
        filler: SEQ (REP 14 of cell) {formerly used for XNS addresses} ,
      recend,
      protocol: nat$protocol,
      priority: 1 .. 0ff(16),
      info: packed record
        class: nat$title_class,
        response: boolean,
        userinfo_size: 0 .. 03f(16),
      recend,
      community_count: 0 .. 0ff(16),
      {communities: array [ community_count ] of nat$community_title,
      {title: string (title_size),
      {user_info: string (userinfo_size),
      {osi_address: nat$translation_address,
    recend;

  TYPE
    translation_pdu_v3 = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      sequence: 0 .. 0ffff(16),
      lifetime: 0 .. 0ffff(16),
      identifier: nat$directory_entry_identifier,
      change_count: 0 .. 0ffff(16),
      title_size: nat$title_length,
      address_kind: nat$network_address_kind,
      protocol: nat$protocol,
      priority: 1 .. 0ff(16),
      info: packed record
        class: nat$title_class,
        response: boolean,
        userinfo_size: 0 .. 03f(16),
      recend,
      reserved: 0 .. 0ff(16),
      {title: string (title_size),
      {user_info: string (userinfo_size),
      {osi_address: nat$translation_address,
    recend;

  TYPE
    delete_translation_pdu = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      identifier: nat$directory_entry_identifier,
      title_size: nat$title_length,
      {title: string (title_size),
    recend;

  TYPE
    delete_translation_pdu_v3 = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      sequence: 0 .. 0ffff(16),
      lifetime: 0 .. 0ffff(16),
      identifier: nat$directory_entry_identifier,
      title_size: nat$title_length,
      {title: string (title_size),
    recend;

  TYPE
    startup_pdu = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      identifier: nat$directory_entry_identifier,
    recend;

  TYPE
    startup_pdu_v3 = record
      id: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      sequence: 0 .. 0ffff(16),
      lifetime: 0 .. 0ffff(16),
      identifier: nat$directory_entry_identifier,
    recend;

  TYPE
    generate_reason = (distribution, response);

  VAR
    nlv$log_broadcast_requests: [XREF] boolean,
    nlv$log_broadcast_translations: [XREF] boolean;

*copyc nat$network_layer_address
*copyc nat$translation_request
*copyc nat$translation
*copyc nav$cdna_multicast_address
*copyc nav$global_statistics
*copyc nav$host_subnet_id
*copyc nav$network_paged_heap
*copyc nav$namve_active
*copyc nav$registered_titles
*copyc nav$system_id
*copyc nav$translation_cache
*copyc nav$translation_requests
*copyc nav$unique_directory_identifier
*copyc nlv$configured_network_devices
*copyc nlv$directory_lock
*copyc nlv$directory_id_seq_number
*copyc nlv$directory_pdu_seq_number
*copyc nlv$directory_version
*copyc nlv$sm_devices
*copyc nlv$transport_network_selector
*copyc ost$caller_identifier
?? OLDTITLE ??
?? NEWTITLE := 'XREF procedures', EJECT ??
*copyc avp$get_capability
*copyc clp$convert_integer_to_string
*copyc i#move
*copyc jmv$executing_within_system_job
*copyc nap$condition_handler_trace
*copyc nap$receive_network_data
*copyc nap$record_directory_me
*copyc nap$send_network_data
*copyc nap$system_id
*copyc nap$user_network_id
*copyc nlp$get_nonexclusive_access
*copyc nlp$na_broadcast_data
*copyc nlp$release_nonexclusive_access
*copyc osp$begin_subsystem_activity
*copyc osp$end_subsystem_activity
*copyc osp$clear_job_signature_lock
*copyc osp$increment_locked_variable
*copyc osp$pop_inhibit_job_recovery
*copyc osp$push_inhibit_job_recovery
*copyc osp$set_job_signature_lock
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc osp$test_signature_lock
*copyc osp$disestablish_cond_handler
*copyc osp$establish_block_exit_hndlr
*copyc pmp$get_compact_date_time
*copyc pmp$get_executing_task_gtid
*copyc pmp$log
*copyc pmp$ready_task
?? OLDTITLE ??
?? NEWTITLE := 'nlp$directory_manager', EJECT ??

  PROCEDURE [XDCL, #GATE] nlp$directory_manager
    (    parameter_list: clt$parameter_list;
     VAR status: ost$status);

{     PURPOSE:
{       This  routine  opens the directory SAP and processes all
{       received directory data units.
{
{    DESIGN:
{       Data units are validated to be in CDNA Directory protocol from
{       another system. Valid packets are then processed.

    VAR
      current_time: integer,
      data_buffer: ^SEQ (REP nac$max_directory_pdu_size of cell),
      data_length: integer,
      data_unit: ^SEQ ( * ),
      delete_time: integer,
      device_id: nlt$device_identifier,
      directory_data_unit: array [1 .. 1] of nat$data_fragment,
      input_buffer: SEQ (REP nac$max_directory_pdu_size of cell),
      local_system: nat$system_identifier,
      network_address: ^SEQ ( * ),
      network_device_list: ^nlt$network_device_list,
      next_maintenance_time: integer,
      next_request: ^nat$translation_request,
      next_translation: ^nat$translation,
      prefix: ^SEQ ( * ),
      sequence_number: integer,
      source: nat$network_layer_address,
      startup_count: 0 .. nac$max_startup_pdu_count,
      startup_data_unit: array [1 .. 1] of nat$data_fragment,
      startup_data: startup_pdu,
      startup_data_v3: startup_pdu_v3,
      startup_timer: integer,
      system_identifier: ^nat$system_identifier,
      translation: ^nat$translation,
      translation_request: ^nat$translation_request,
      wait_time: 0 .. 0ffffffff(16);

?? NEWTITLE := '  exit_condition_handler', EJECT ??

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

      VAR
        lock_status: ost$signature_lock_status;

      nap$condition_handler_trace (condition, save_area);
      handler_status.normal := TRUE;
      osp$test_signature_lock (nlv$directory_lock, lock_status);
      IF lock_status = osc$sls_locked_by_current_task THEN
        osp$clear_job_signature_lock (nlv$directory_lock);
      IFEND;

    PROCEND exit_condition_handler;
?? OLDTITLE, EJECT ??
    IF NOT jmv$executing_within_system_job THEN
      osp$set_status_abnormal ('NA', nae$insufficient_privilege, 'nlp$directory_manager', status);
      RETURN;
    IFEND;

    status.normal := TRUE;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    nap$record_directory_me {task identifier} ;
    local_system := nap$system_id ();

    startup_data.id := nac$startup_data_unit;
    startup_data.version := nac$osi_directory_version;
    assign_directory_identifier (startup_data.identifier);
    startup_data_v3.id := nac$v3_startup_data_unit;
    startup_data_v3.version := nac$directory_version_3;
    startup_data_v3.lifetime := nac$initial_pdu_lifetime;
    startup_data_v3.identifier := startup_data.identifier;
    startup_timer := 0;
    startup_count := 0;

    network_device_list := nlv$configured_network_devices.network_device_list;

    data_buffer := ^input_buffer;
    directory_data_unit [1].address := data_buffer;
    directory_data_unit [1].length := #SIZE (data_buffer^);
    wait_time := 0;

  /main_loop/
    WHILE TRUE DO

{ Process a directory data unit if one is available.

      nap$receive_network_data (nac$xi_cdna_directory_sap, directory_data_unit, wait_time, source,
            data_length, status);

      IF status.normal THEN
        network_address := ^source.network_address;
        RESET network_address;
        NEXT prefix: [[REP (source.network_address_length - #SIZE (nat$network_selector) -
              #SIZE (nat$system_identifier)) OF cell]] IN network_address;
        NEXT system_identifier IN network_address;
        IF system_identifier^ <> local_system THEN
          RESET data_buffer;
          NEXT data_unit: [[REP data_length OF cell]] IN data_buffer;
          process_data_unit (source, system_identifier^, data_unit);
        IFEND;
      IFEND;

      current_time := #FREE_RUNNING_CLOCK (0);

{ Broadcast startup pdu if necessary.

      IF current_time >= startup_timer THEN
        IF startup_count < nac$max_startup_pdu_count THEN
          IF nlv$directory_version = nac$osi_directory_version THEN
            startup_data_unit [1].address := ^startup_data;
            startup_data_unit [1].length := #SIZE (startup_data);
            FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
              nlp$na_broadcast_data (device_id, nac$xi_cdna_directory_sap, nac$xi_cdna_directory_sap,
                    startup_data_unit, status);
            FOREND;
          ELSE {nac$directory_version_3
            startup_data_unit [1].address := ^startup_data_v3;
            startup_data_unit [1].length := #SIZE (startup_data_v3);
            get_pdu_sequence_number (sequence_number);
            startup_data_v3.sequence := sequence_number;
            nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
            FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
              send_distributed_pdu (device_id, startup_data_unit);
            FOREND;
            nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
          IFEND;
          startup_count := startup_count + 1;
          startup_timer := current_time + nac$startup_pdu_interval;
        ELSE
          startup_timer := current_time + nac$very_long_time;
        IFEND;
      IFEND;

      next_maintenance_time := startup_timer;
      osp$set_job_signature_lock (nlv$directory_lock);

{ Rebroadcast any translation requests that have not been satisfied.
{ Delete any translation requests that have expired.

      IF current_time >= nav$translation_requests.next_broadcast_time THEN
        nav$translation_requests.next_broadcast_time := current_time + nac$very_long_time;
        translation_request := nav$translation_requests.first;
        WHILE (translation_request <> NIL) DO
          next_request := translation_request^.link;
          IF (translation_request^.time_stamp <= current_time) THEN
            IF translation_request^.broadcast_counter >= nac$max_request_broadcast_count THEN
              delete_time := translation_request^.time_stamp + nac$max_request_hold_time;
              pmp$ready_task (translation_request^.requestor, {ignore} status);
              IF delete_time <= current_time THEN
                delete_request (translation_request);
              ELSEIF delete_time < nav$translation_requests.next_broadcast_time THEN
                nav$translation_requests.next_broadcast_time := delete_time;
              IFEND;
            ELSE {time to redistribute the translation request}
              distribute_translation_request (translation_request);
              IF translation_request^.recurrent_search AND (translation_request^.broadcast_counter >=
                    nac$max_request_broadcast_count) THEN {no more searches...go into recurrent search mode}
                translation_request^.time_stamp := current_time + nac$max_request_hold_time;
              IFEND;
              IF translation_request^.time_stamp < nav$translation_requests.next_broadcast_time THEN
                nav$translation_requests.next_broadcast_time := translation_request^.time_stamp;
              IFEND;
            IFEND;
          ELSEIF translation_request^.time_stamp < nav$translation_requests.next_broadcast_time THEN
            nav$translation_requests.next_broadcast_time := translation_request^.time_stamp;
          IFEND;
          translation_request := next_request;
        WHILEND;
      IFEND;
      IF nav$translation_requests.next_broadcast_time < next_maintenance_time THEN
        next_maintenance_time := nav$translation_requests.next_broadcast_time;
      IFEND;

{ Periodically distribute registered titles as requested.

      IF current_time >= nav$registered_titles.next_timer THEN
        nav$registered_titles.next_timer := current_time + nac$very_long_time;
        translation := nav$registered_titles.first;
        WHILE (translation <> NIL) DO
          IF (translation^.time_stamp <= current_time) THEN
            distribute_translation (translation);
          IFEND;
          IF translation^.time_stamp < nav$registered_titles.next_timer THEN
            nav$registered_titles.next_timer := translation^.time_stamp;
          IFEND;
          translation := translation^.link;
        WHILEND;
      IFEND;
      IF nav$registered_titles.next_timer < next_maintenance_time THEN
        next_maintenance_time := nav$registered_titles.next_timer;
      IFEND;

{ Delete any expired cache entries.

      IF current_time >= nav$translation_cache.next_timer THEN
        nav$translation_cache.next_timer := current_time + nac$very_long_time;
        translation := nav$translation_cache.first;
        WHILE (translation <> NIL) DO
          next_translation := translation^.link;
          IF translation^.time_stamp <= current_time THEN
            delete_translation (translation, nav$translation_cache.first);
            nav$global_statistics.directory.current_cache_entries :=
                  nav$global_statistics.directory.current_cache_entries - 1;
          ELSEIF translation^.time_stamp < nav$translation_cache.next_timer THEN
            nav$translation_cache.next_timer := translation^.time_stamp;
          IFEND;
          translation := next_translation;
        WHILEND;
      IFEND;
      IF nav$translation_cache.next_timer < next_maintenance_time THEN
        next_maintenance_time := nav$translation_cache.next_timer;
      IFEND;

      osp$clear_job_signature_lock (nlv$directory_lock);
      current_time := #FREE_RUNNING_CLOCK (0);
      IF next_maintenance_time > current_time THEN
        wait_time := (next_maintenance_time - current_time) DIV 1000;
      ELSE
        wait_time := 0;
      IFEND;
    WHILEND;

  PROCEND nlp$directory_manager;
?? OLDTITLE ??
?? NEWTITLE := 'Directory Primitives' ??
?? NEWTITLE := 'nlp$register_title', EJECT ??

  PROCEDURE [#GATE, XDCL] nlp$register_title
    (    title: string ( * <= nac$max_title_length);
         osi_address: nat$osi_registration_address;
         protocol: nat$protocol;
         user_information: ^cell;
         user_information_length: 0 .. nac$max_directory_data_length;
         priority: nat$directory_priority;
         domain: nat$title_domain;
         distribute: boolean;
         class: nat$title_class;
         password: nat$directory_password;
     VAR user_identifier: ost$name;
     VAR identifier: nat$directory_entry_identifier;
     VAR status: ost$status);

*copy nlh$register_title

    VAR
      caller_id: ost$caller_identifier,
      detail: ^SEQ ( * ),
      detail_size: integer,
      network_application_management: boolean,
      osi_address_length: nat$osi_address_length,
      str: ost$string,
      title_length: nat$title_length,
      translation: ^nat$translation;

    status.normal := TRUE;

    #CALLER_ID (caller_id);
    IF (caller_id.ring > minimum_ring_allowed) THEN
      avp$get_capability (avc$network_applic_management, avc$user, network_application_management, status);
      IF  status.normal THEN
        IF (NOT network_application_management) THEN
          osp$set_status_abnormal (nac$status_id, nae$insufficient_privilege, 'nlp$register_titles', status);
          RETURN;
        IFEND;
      ELSE
        RETURN;
      IFEND;
    IFEND;

    IF NOT nav$namve_active THEN
      osp$set_status_condition (nae$network_inactive, status);
      RETURN;
    IFEND;

    title_length := STRLENGTH (title);
    WHILE (title_length > 1) AND (title (title_length) = ' ') DO
      title_length := title_length - 1;
    WHILEND;
    IF title_length = 0 THEN
      osp$set_status_abnormal (nac$status_id, nae$title_too_short, 'NLP$REGISTER_TITLE', status);
      RETURN;
    IFEND;

    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);

{ Validate registration parameters.

    find_title_address (title (1, title_length), osi_address, nav$registered_titles.first, translation);
    IF translation <> NIL THEN {currently registered}
      osp$set_status_condition (nae$duplicate_registration, status);
    IFEND;

{ Create translation entry for registered title.

    IF status.normal THEN
      CASE osi_address.kind OF
      = nac$osi_non_cdna_session_addr, nac$osi_non_cdna_present_addr =
        osi_address_length := #SIZE (osi_address.osi_address^);
      ELSE
        osi_address_length := 0;
      CASEND;

      detail_size := title_length + user_information_length + osi_address_length;
      ALLOCATE translation: [[REP detail_size OF char]] IN nav$network_paged_heap^;
      IF translation <> NIL THEN
        detail := ^translation^.detail;
        RESET detail;
        NEXT translation^.title: [title_length] IN detail;
        translation^.title^ := title;
        translation^.protocol := protocol;
        IF user_information_length > 0 THEN
          NEXT translation^.user_information: [user_information_length] IN detail;
          i#move (user_information, translation^.user_information, user_information_length);
        ELSE
          translation^.user_information := NIL;
        IFEND;
        IF osi_address_length = 0 THEN
          translation^.osi_address_kind := registration_address;
          translation^.registered_address := osi_address;
        ELSE {save the non_cdna address as though it came from the non_cdna system}
          translation^.osi_address_kind := translation_address;
          NEXT translation^.osi_address: [[REP osi_address_length OF cell]] IN detail;
          translation^.osi_address^ := osi_address.osi_address^;
          translation^.registered_address.osi_address := translation^.osi_address;
        IFEND;
        translation^.priority := priority;
        translation^.domain.kind := domain.kind;
        translation^.distribute := distribute;
        translation^.class := class;
        translation^.password := password;
        assign_directory_identifier (translation^.identifier);
        identifier := translation^.identifier;
        translation^.change_count := 0;
        translation^.broadcast_counter := 1;
        translation^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$very_long_time;
        IF user_identifier = osc$null_name THEN
          translation^.user_identifier := nac$default_user_identifier;
          nav$unique_directory_identifier := nav$unique_directory_identifier + 1;
          clp$convert_integer_to_string (nav$unique_directory_identifier, 10, FALSE, str, status);
          translation^.user_identifier (STRLENGTH (nac$default_user_identifier) + 1, * ) :=
                str.value (1, str.size);
        ELSE
          translation^.user_identifier := user_identifier;
        IFEND;

        add_translation (translation, nav$registered_titles.first);
        nav$global_statistics.directory.current_registered_titles :=
              nav$global_statistics.directory.current_registered_titles + 1;

        IF distribute THEN
          distribute_translation (translation);
          IF translation^.time_stamp < nav$registered_titles.next_timer THEN
            nav$registered_titles.next_timer := translation^.time_stamp;
          IFEND;
        IFEND;

        satisfy_translation_requests (translation);
      ELSE
        osp$set_status_abnormal (nac$status_id, nae$insufficient_resources, 'title registration', status);
      IFEND;
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nlp$register_title;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$delete_registered_title', EJECT ??

  PROCEDURE [#GATE, XDCL] nlp$delete_registered_title
    (    title: string ( * <= nac$max_title_length);
         password: nat$directory_password;
         identifier: nat$directory_entry_identifier;
     VAR status: ost$status);

*copy nlh$delete_registered_title

    VAR
      caller_id: ost$caller_identifier,
      network_application_management: boolean,
      title_length: nat$title_length,
      translation: ^nat$translation;

    status.normal := TRUE;

    #CALLER_ID (caller_id);
    IF (caller_id.ring > minimum_ring_allowed) THEN
      avp$get_capability (avc$network_applic_management, avc$user, network_application_management, status);
      IF  status.normal THEN
        IF (NOT network_application_management) THEN
          osp$set_status_abnormal (nac$status_id, nae$insufficient_privilege, 'nlp$delete_registered_titles',
                status);
          RETURN;
        IFEND;
      ELSE
        RETURN;
      IFEND;
    IFEND;

    title_length := STRLENGTH (title);
    WHILE (title_length > 1) AND (title (title_length) = ' ') DO
      title_length := title_length - 1;
    WHILEND;

    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);

    find_title (title (1, title_length), identifier, nav$registered_titles.first, translation);
    IF translation <> NIL THEN
      IF password = translation^.password THEN
        IF translation^.distribute THEN
          distribute_delete_translation (translation);
        IFEND;
        delete_translation (translation, nav$registered_titles.first);
        IF nav$global_statistics.directory.current_registered_titles > 0 THEN
          nav$global_statistics.directory.current_registered_titles :=
                nav$global_statistics.directory.current_registered_titles - 1;
        IFEND;
      ELSE
        osp$set_status_condition (nae$incorrect_password, status);
      IFEND;
    ELSE
      osp$set_status_condition (nae$title_id_not_found, status);
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nlp$delete_registered_title;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$translate_title', EJECT ??

  PROCEDURE [#GATE, XDCL] nlp$translate_title
    (    title: string ( * <= nac$max_title_length);
         wild_card: boolean;
         protocol: nat$protocol;
         recurrent_search: boolean;
         search_domain: nat$title_domain;
         class: nat$title_class;
     VAR request_id: nat$directory_search_identifier;
     VAR status: ost$status);

*copy nlh$translate_title

    VAR
      actual: integer,
      duplicate_id: ^nat$translation_request,
      local_translation_found: boolean,
      title_length: nat$title_length,
      translation: ^nat$translation,
      request: ^nat$translation_request;

    osp$increment_locked_variable (nav$global_statistics.directory.directory_searches_initiated, 0, actual);

    IF NOT nav$namve_active THEN
      osp$set_status_condition (nae$network_inactive, status);
      RETURN;
    IFEND;

{ Validate request parameters.

    title_length := STRLENGTH (title);
    WHILE (title_length > 1) AND (title (title_length) = ' ') DO
      title_length := title_length - 1;
    WHILEND;
    IF title_length = 0 THEN
      osp$set_status_abnormal (nac$status_id, nae$title_too_short, 'NLP$TRANSLATE_TITLE', status);
      RETURN;
    IFEND;

{ Create a translation request entry.

    ALLOCATE request: [title_length] IN nav$network_paged_heap^;
    IF request = NIL THEN
      osp$set_status_abnormal (nac$status_id, nae$insufficient_resources, 'translation request', status);
      RETURN;
    IFEND;
    request^.title := title;
    request^.wild_card := wild_card;
    request^.protocol := protocol;
    request^.class := class;
    request^.recurrent_search := recurrent_search;
    request^.domain.kind := search_domain.kind;
    request^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$max_request_hold_time;
    request^.search_required := search_domain.kind > nac$local_system_domain;
    IF search_domain.kind > nac$local_system_domain THEN
      request^.broadcast_counter := 0;
    ELSE
      request^.broadcast_counter := nac$max_request_broadcast_count;
    IFEND;
    pmp$get_executing_task_gtid (request^.requestor);
    request^.first_translation := NIL;

{ Look for local registrations that satisfy this request.

    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);
    REPEAT { Assign a unique identifier
      assign_directory_identifier (request^.identifier); { Must be done with directory lock set
      request_id := request^.identifier;
      find_request (request_id, duplicate_id);
    UNTIL duplicate_id = NIL;
    local_translation_found := FALSE;
    translation := nav$registered_titles.first;

    WHILE translation <> NIL DO
      IF valid_translation (translation, request) THEN
        IF (translation^.osi_address_kind = translation_address) THEN
          save_valid_translation (translation, request);
        ELSE
          local_translation_found := TRUE;
          save_local_translation (translation, request);
        IFEND;
        nav$global_statistics.directory.translations_found_in_local_dir :=
              nav$global_statistics.directory.translations_found_in_local_dir + 1;
        IF translation^.priority = nac$max_directory_priority THEN

{ Search of network is always performed for wild card or recurrent search requests with catenet domain.
{ Search may be delayed if a maximum priority translation is found locally for any other request.

          request^.search_required := (wild_card OR recurrent_search) AND
                (request^.domain.kind > nac$local_system_domain);
        IFEND;
      IFEND;
      translation := translation^.link;
    WHILEND;

{ If a local translation was found but no translation was recorded, no device is active. The list will have
{ to be rescanned later on the chance that a device becomes available.

    request^.repeat_local_search := local_translation_found AND (request^.first_translation = NIL);

{ Look for translation cache entries that satisfy this request.

    IF request^.domain.kind > nac$local_system_domain THEN
      translation := nav$translation_cache.first;

      WHILE translation <> NIL DO
        IF valid_translation (translation, request) THEN
          save_valid_translation (translation, request);
          nav$global_statistics.directory.translations_found_in_cache :=
                nav$global_statistics.directory.translations_found_in_cache + 1;
          IF translation^.priority = nac$max_directory_priority THEN

{ Search of network is always performed for wild card or recurrent search requests.
{ Search may be delayed if a maximum priority translation is found locally for any other request.

            request^.search_required := wild_card OR recurrent_search;
          IFEND;
        IFEND;
        translation := translation^.link;
      WHILEND;
    IFEND;

{ Add this request to the list of outstanding translation requests.

    add_request (request, nav$translation_requests);

{ A translation request will be broadcast immediately if no maximum priority
{ translations are found locally, if a wild card search is requested,
{ or if a recurrent search is requested.
{ The assumption is that a wild card search or recurrent search will want to
{ receive all available translations, while the normal request will only want
{ one translation. If more translations are requested and the translation request
{ has not been broadcast, then it will be broadcast at that time.

    IF request^.search_required THEN
      distribute_translation_request (request);
    IFEND;

    IF request^.time_stamp < nav$translation_requests.next_broadcast_time THEN
      nav$translation_requests.next_broadcast_time := request^.time_stamp;
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nlp$translate_title;
?? OLDTITLE ??
?? NEWTITLE := 'nap$check_title_translation', EJECT ??

  PROCEDURE [XDCL] nap$check_title_translation
    (    translation_request: nat$directory_search_identifier;
     VAR activity_complete: boolean;
     VAR status: ost$status);

    VAR
      current_time: integer,
      local_translation_found: boolean,
      request: ^nat$translation_request,
      translation: ^nat$translation;

    status.normal := TRUE;
    activity_complete := FALSE;
    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);

    find_request (translation_request, request);
    IF request <> NIL THEN
      current_time := #FREE_RUNNING_CLOCK (0);
      IF request^.recurrent_search AND (request^.broadcast_counter >= nac$max_request_broadcast_count) THEN

{update time of last inquiry

        request^.time_stamp := current_time + nac$max_request_hold_time;
      IFEND;

      IF request^.repeat_local_search THEN
        local_translation_found := FALSE;
        translation := nav$registered_titles.first;

        WHILE translation <> NIL DO
          IF valid_translation (translation, request) THEN
            IF (translation^.osi_address_kind = registration_address) THEN
              local_translation_found := TRUE;
              save_local_translation (translation, request);
            IFEND;
          IFEND;
          translation := translation^.link;
        WHILEND;

{ If a local translation was found but no translation was recorded, no device is active. The list will have
{ to be rescanned later on the chance that a device becomes available.

        request^.repeat_local_search := local_translation_found AND (request^.first_translation = NIL);
      IFEND;

      translation := request^.first_translation;
      activity_complete := (request^.broadcast_counter >= nac$max_request_broadcast_count) AND
            (request^.time_stamp <= current_time);

      WHILE (translation <> NIL) AND (NOT activity_complete) DO
        activity_complete := (NOT translation^.reported) AND
              ((translation^.priority = nac$max_directory_priority) OR
              (request^.broadcast_counter >= nac$max_request_broadcast_count));
        translation := translation^.link;
      WHILEND;
      IF (NOT activity_complete) AND (NOT request^.search_required) AND
            (request^.domain.kind > nac$local_system_domain) THEN {search now required}
        request^.search_required := TRUE;
        distribute_translation_request (request);
        IF request^.time_stamp < nav$translation_requests.next_broadcast_time THEN
          nav$translation_requests.next_broadcast_time := request^.time_stamp;
        IFEND;
      IFEND;
    ELSE
      activity_complete := TRUE;
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nap$check_title_translation;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$get_title_translation', EJECT ??

  PROCEDURE [#GATE, XDCL] nlp$get_title_translation
    (    request_id: nat$directory_search_identifier;
     VAR title: nat$title;
     VAR address: nat$osi_translation_address;
     VAR protocol: nat$protocol;
         user_information: ^cell;
     VAR user_info_length: 0 .. nac$max_directory_data_length;
     VAR priority: nat$directory_priority;
     VAR user_identifier: ost$name;
     VAR identifier: nat$directory_entry_identifier;
     VAR status: ost$status);

*copy nlh$get_title_translation

    VAR
      best_translation: ^nat$translation,
      current_time: integer,
      local_translation_found: boolean,
      network_address: ^SEQ ( * ),
      network_selector: ^nat$network_selector,
      nsap_address: ^SEQ ( * ),
      nsap_address_size: ^nat$osi_network_address_length,
      osi_address: ^SEQ ( * ),
      osi_address_kind: ^nat$translation_address_kind,
      osi_address_length: ^nat$osi_address_length,
      request: ^nat$translation_request,
      presentation_selector: ^string ( * ),
      presentation_selector_length: ^nat$osi_psap_selector_length,
      session_selector: ^string ( * ),
      session_selector_length: ^nat$osi_ssap_selector_length,
      translation: ^nat$translation,
      transport_selector: ^string ( * ),
      transport_selector_length: ^nat$osi_tsap_selector_length;


    status.normal := TRUE;

    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);

    find_request (request_id, request);
    IF request <> NIL THEN
      IF request^.recurrent_search AND (request^.broadcast_counter >= nac$max_request_broadcast_count) THEN

{update time of last inquiry

        request^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$max_request_hold_time;
      IFEND;

      IF request^.repeat_local_search THEN
        local_translation_found := FALSE;
        translation := nav$registered_titles.first;

        WHILE translation <> NIL DO
          IF valid_translation (translation, request) THEN
            IF (translation^.osi_address_kind = registration_address) THEN
              local_translation_found := TRUE;
              save_local_translation (translation, request);
            IFEND;
          IFEND;
          translation := translation^.link;
        WHILEND;

{ If a local translation was found but no translation was recorded, no device is active. The list will have
{ to be rescanned later on the chance that a device becomes available.

        request^.repeat_local_search := local_translation_found AND (request^.first_translation = NIL);
      IFEND;

      translation := request^.first_translation;
      best_translation := NIL;

{     NOTE: The first translation received is the last one in the list ... return them in order received.

    /search_for_best/
      WHILE translation <> NIL DO
        IF NOT translation^.reported THEN
          IF (best_translation = NIL) OR (translation^.priority <= best_translation^.priority) THEN
            best_translation := translation;
          IFEND;
        IFEND;
        translation := translation^.link;
      WHILEND /search_for_best/;

{ A non-maximum priority translation cannot be returned until the network search is completed, since
{ translations are returned in priority order and a higher priority translation may be found later.

      IF (best_translation <> NIL) AND ((best_translation^.priority = nac$max_directory_priority) OR
            (request^.broadcast_counter >= nac$max_request_broadcast_count)) THEN
        title := best_translation^.title^;
        osi_address := best_translation^.osi_address;
        RESET osi_address;
        NEXT osi_address_kind IN osi_address;
        address.kind := osi_address_kind^;
        NEXT osi_address_length IN osi_address;
        CASE osi_address_kind^ OF
        = nac$osi_transport_address =
          NEXT transport_selector_length IN osi_address;
          address.osi_transport_address.transport_sap_selector_length := transport_selector_length^;
          NEXT transport_selector: [transport_selector_length^] IN osi_address;
          address.osi_transport_address.transport_sap_selector := transport_selector^;
          NEXT nsap_address_size IN osi_address;
          address.osi_transport_address.network_address_length := nsap_address_size^;
          NEXT nsap_address: [[REP nsap_address_size^ OF cell]] IN osi_address;
          i#move (nsap_address, ^address.osi_transport_address.network_address, nsap_address_size^);

        = nac$osi_session_address, nac$osi_non_cdna_session_addr =
          NEXT session_selector_length IN osi_address;
          address.osi_session_address.session_selector_length := session_selector_length^;
          NEXT session_selector: [session_selector_length^] IN osi_address;
          address.osi_session_address.session_selector := session_selector^;
          NEXT transport_selector_length IN osi_address;
          address.osi_session_address.transport_selector_length := transport_selector_length^;
          NEXT transport_selector: [transport_selector_length^] IN osi_address;
          address.osi_session_address.transport_selector := transport_selector^;
          NEXT nsap_address_size IN osi_address;
          address.osi_session_address.network_address_length := nsap_address_size^;
          NEXT nsap_address: [[REP nsap_address_size^ OF cell]] IN osi_address;
          i#move (nsap_address, ^address.osi_session_address.network_address, nsap_address_size^);

        = nac$osi_presentation_address, nac$osi_non_cdna_present_addr =
          NEXT presentation_selector_length IN osi_address;
          address.osi_presentation_address.presentation_selector_length := presentation_selector_length^;
          NEXT presentation_selector: [presentation_selector_length^] IN osi_address;
          address.osi_presentation_address.presentation_selector := presentation_selector^;
          NEXT session_selector_length IN osi_address;
          address.osi_presentation_address.session_selector_length := session_selector_length^;
          NEXT session_selector: [session_selector_length^] IN osi_address;
          address.osi_presentation_address.session_selector := session_selector^;
          NEXT transport_selector_length IN osi_address;
          address.osi_presentation_address.transport_selector_length := transport_selector_length^;
          NEXT transport_selector: [transport_selector_length^] IN osi_address;
          address.osi_presentation_address.transport_selector := transport_selector^;
          NEXT nsap_address_size IN osi_address;
          address.osi_presentation_address.network_address_length := nsap_address_size^;
          NEXT nsap_address: [[REP nsap_address_size^ OF cell]] IN osi_address;
          i#move (nsap_address, ^address.osi_presentation_address.network_address, nsap_address_size^);
        ELSE
        CASEND;

        protocol := best_translation^.protocol;
        IF best_translation^.user_information <> NIL THEN
          user_info_length := STRLENGTH (best_translation^.user_information^);
          IF user_information <> NIL THEN
            i#move (best_translation^.user_information, user_information, user_info_length);
          IFEND;
        ELSE
          user_info_length := 0;
        IFEND;
        priority := best_translation^.priority;
        identifier := best_translation^.identifier;
        user_identifier := best_translation^.user_identifier;
        best_translation^.reported := TRUE;
        nav$global_statistics.directory.translations_delivered :=
              nav$global_statistics.directory.translations_delivered + 1;
      ELSE {no translations to deliver}
        osp$set_status_condition (nae$no_translation_available, status);
        current_time := #FREE_RUNNING_CLOCK (0);
        IF (NOT request^.search_required) AND (request^.domain.kind > nac$local_system_domain) THEN
              {search now required}
          request^.search_required := TRUE;
          distribute_translation_request (request);
          IF request^.time_stamp < nav$translation_requests.next_broadcast_time THEN
            nav$translation_requests.next_broadcast_time := request^.time_stamp;
          IFEND;
        ELSEIF (request^.broadcast_counter >= nac$max_request_broadcast_count) AND
              (request^.time_stamp <= current_time) THEN
          IF request^.recurrent_search THEN
            request^.time_stamp := current_time + nac$max_request_hold_time;
            osp$set_status_condition (nae$wait_for_distributed_title, status);
          ELSE
            delete_request (request);
            osp$set_status_condition (nae$directory_search_complete, status);
          IFEND;
        ELSEIF request^.domain.kind = nac$local_system_domain THEN {prepare request for auto deletion}
          request^.broadcast_counter := nac$max_request_broadcast_count;
          request^.time_stamp := current_time;
        IFEND;
      IFEND;
    ELSE
      osp$set_status_condition (nae$translation_req_not_active, status);
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nlp$get_title_translation;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$end_title_translation', EJECT ??

  PROCEDURE [#GATE, XDCL] nlp$end_title_translation
    (    request_id: nat$directory_search_identifier;
     VAR status: ost$status);

*copy nlh$end_title_translation

    VAR
      translation_request: ^nat$translation_request;

    status.normal := TRUE;

    osp$push_inhibit_job_recovery;
    osp$begin_subsystem_activity;
    osp$establish_block_exit_hndlr (^exit_condition_handler);
    osp$set_job_signature_lock (nlv$directory_lock);

    find_request (request_id, translation_request);
    IF translation_request <> NIL THEN
      delete_request (translation_request);
    ELSE
      osp$set_status_condition (nae$translation_req_not_active, status);
    IFEND;

    osp$clear_job_signature_lock (nlv$directory_lock);
    osp$end_subsystem_activity;
    osp$disestablish_cond_handler;
    osp$pop_inhibit_job_recovery;

  PROCEND nlp$end_title_translation;
?? OLDTITLE ??
?? OLDTITLE ??
?? NEWTITLE := 'Miscellaneous utility routines' ??
?? NEWTITLE := 'add_request', EJECT ??

  PROCEDURE [INLINE] add_request
    (    request: ^nat$translation_request;
     VAR translation_request_list: nat$translation_request_list);

{     PURPOSE:
{       This  routine is called by directory routines to add an
{       entry to a list of requests.

    request^.link := translation_request_list.first;
    translation_request_list.first := request;
    nav$global_statistics.directory.directory_searches_active :=
          nav$global_statistics.directory.directory_searches_active + 1;

  PROCEND add_request;
?? OLDTITLE ??
?? NEWTITLE := 'add_translation', EJECT ??

  PROCEDURE [INLINE] add_translation
    (    translation: ^nat$translation;
     VAR first_translation: ^nat$translation);

{     PURPOSE:
{       This  routine is called by directory routines to add an
{       entry to a list of translations.

    translation^.link := first_translation;
    first_translation := translation;

  PROCEND add_translation;
?? OLDTITLE ??
?? NEWTITLE := 'assign_directory_identifier', EJECT ??

  PROCEDURE assign_directory_identifier
    (VAR identifier: nat$directory_entry_identifier);

{ PURPOSE: Assign a unique directory identifier value.
{          NOTE: The directory lock must be set by the caller to guarantee uniqueness.

    VAR
      date_time: ost$date_time,
      ignore_status: ost$status;

    identifier.system.network := nap$user_network_id ();
    identifier.system.system := nap$system_id ();

    pmp$get_compact_date_time (date_time, ignore_status);

    identifier.time_stamp.date.year1 := (date_time.year MOD 100) DIV 10;
    identifier.time_stamp.date.year2 := date_time.year MOD 10;
    identifier.time_stamp.date.month1 := date_time.month DIV 10;
    identifier.time_stamp.date.month2 := date_time.month MOD 10;
    identifier.time_stamp.date.day1 := date_time.day DIV 10;
    identifier.time_stamp.date.day2 := date_time.day MOD 10;
    identifier.time_stamp.time.hours1 := date_time.hour DIV 10;
    identifier.time_stamp.time.hours2 := date_time.hour MOD 10;
    identifier.time_stamp.time.minutes1 := date_time.minute DIV 10;
    identifier.time_stamp.time.minutes2 := date_time.minute MOD 10;
    identifier.time_stamp.time.seconds1 := date_time.second DIV 10;
    identifier.time_stamp.time.seconds2 := date_time.second MOD 10;
    identifier.time_stamp.time.milliseconds1 := date_time.millisecond DIV 100;
    identifier.time_stamp.time.milliseconds2 := (date_time.millisecond MOD 100) DIV 10;
    identifier.time_stamp.time.milliseconds3 := date_time.millisecond MOD 10;
    nlv$directory_id_seq_number := (nlv$directory_id_seq_number + 1) MOD 10;
    identifier.time_stamp.time.fill := nlv$directory_id_seq_number;

  PROCEND assign_directory_identifier;
?? OLDTITLE ??
?? NEWTITLE := 'delete_request', EJECT ??

  PROCEDURE delete_request
    (VAR request: ^nat$translation_request);

{     PURPOSE:
{       This  routine  is  called  by the directory routines to
{       locate an existing translation request and remove it from
{       the table.

    VAR
      link: ^^nat$translation_request,
      next_translation: ^nat$translation,
      translation: ^nat$translation;

    translation := request^.first_translation;
    WHILE translation <> NIL DO
      next_translation := translation^.link;
      FREE translation IN nav$network_paged_heap^;
      translation := next_translation;
    WHILEND;

    link := ^nav$translation_requests.first;
    WHILE link^ <> NIL DO
      IF link^ = request THEN
        link^ := request^.link;
        FREE request IN nav$network_paged_heap^;
        nav$global_statistics.directory.directory_searches_active :=
              nav$global_statistics.directory.directory_searches_active - 1;
        RETURN;
      IFEND;
      link := ^link^^.link;
    WHILEND;

  PROCEND delete_request;
?? OLDTITLE ??
?? NEWTITLE := 'delete_translation', EJECT ??

  PROCEDURE [INLINE]delete_translation
    (VAR translation: ^nat$translation;
     VAR first_translation: ^nat$translation);

{     PURPOSE:
{       This  routine  is  called  by the directory routines to
{       locate an existing translation and remove it from
{       the table.

    VAR
      link: ^^nat$translation;

    link := ^first_translation;
    WHILE link^ <> NIL DO
      IF link^ = translation THEN
        link^ := translation^.link;
        FREE translation IN nav$network_paged_heap^;
        RETURN;
      IFEND;
      link := ^link^^.link;
    WHILEND;

  PROCEND delete_translation;
?? OLDTITLE ??
?? NEWTITLE := 'distribute_delete_translation', EJECT ??

  PROCEDURE distribute_delete_translation
    (    translation: ^nat$translation);

{     PURPOSE:
{       This  routine  is  called  by the directory routines to send out a delete translation data unit.

    VAR
      delete_translation: delete_translation_pdu,
      delete_translation_v3: delete_translation_pdu_v3,
      device_id: nlt$device_identifier,
      network_device_list: ^nlt$network_device_list,
      pdu: array [1 .. 2] of nat$data_fragment,
      sequence_number: integer,
      status: ost$status;

    IF nlv$directory_version = nac$osi_directory_version THEN
      delete_translation.id := nac$del_translation_data_unit;
      delete_translation.version := nac$osi_directory_version;
      delete_translation.identifier := translation^.identifier;
      delete_translation.title_size := STRLENGTH (translation^.title^);

      pdu [1].address := ^delete_translation;
      pdu [1].length := #SIZE (delete_translation);
      pdu [2].address := translation^.title;
      pdu [2].length := delete_translation.title_size;

      network_device_list := nlv$configured_network_devices.network_device_list;
      FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
        nlp$na_broadcast_data (device_id, nac$xi_cdna_directory_sap, nac$xi_cdna_directory_sap, pdu, status);
      FOREND;
    ELSE {nac$directory_version_3
      delete_translation_v3.id := nac$v3_dl_translation_data_unit;
      delete_translation_v3.version := nac$directory_version_3;
      delete_translation_v3.lifetime := nac$initial_pdu_lifetime;
      delete_translation_v3.identifier := translation^.identifier;
      delete_translation_v3.title_size := STRLENGTH (translation^.title^);
      get_pdu_sequence_number (sequence_number);
      delete_translation_v3.sequence := sequence_number;

      pdu [1].address := ^delete_translation_v3;
      pdu [1].length := #SIZE (delete_translation_v3);
      pdu [2].address := translation^.title;
      pdu [2].length := delete_translation_v3.title_size;

      network_device_list := nlv$configured_network_devices.network_device_list;
      nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
      FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
        send_distributed_pdu (device_id, pdu);
      FOREND;
      nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
    IFEND;
  PROCEND distribute_delete_translation;
?? OLDTITLE ??
?? NEWTITLE := 'distribute_translation', EJECT ??

  PROCEDURE distribute_translation
    (    translation: ^nat$translation);

{     PURPOSE:
{       This  routine  is  called  by the directory routines to send out a translation data unit.

    VAR
      device_id: nlt$device_identifier,
      ignore_status: ost$status,
      network_device_list: ^nlt$network_device_list,
      osi_address: SEQ (REP nac$max_osi_address_length of cell),
      pdu: array [1 .. 4] of nat$data_fragment,
      translation_pdu_header: translation_pdu,
      translation_pdu_header_v3: translation_pdu_v3,
      work_space: ^SEQ ( * );

{ A data unit is generated for every active network device, then broadcast over that device.

    IF nlv$directory_version = nac$osi_directory_version THEN
      network_device_list := nlv$configured_network_devices.network_device_list;
      FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
        work_space := ^osi_address;
        generate_translation_data_unit (translation, device_id, distribution, work_space,
              translation_pdu_header, pdu);
        IF work_space <> NIL THEN
          nlp$na_broadcast_data (device_id, nac$xi_cdna_directory_sap, nac$xi_cdna_directory_sap, pdu,
                ignore_status);
        IFEND;
      FOREND;
    ELSE {nac$directory_version_3
      network_device_list := nlv$configured_network_devices.network_device_list;
      FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
        work_space := ^osi_address;
        generate_v3_trans_data_unit (translation, device_id, distribution, work_space,
              translation_pdu_header_v3, pdu);
        IF work_space <> NIL THEN
          nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
          send_distributed_pdu (device_id, pdu);
          nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
        IFEND;
      FOREND;
    IFEND;

    IF translation^.broadcast_counter < nac$title_distribution_count THEN
      translation^.broadcast_counter := translation^.broadcast_counter + 1;
      translation^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$title_distribution_delay;
    ELSE
      translation^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$title_redistribute_delay;
    IFEND;
    nav$global_statistics.directory.translations_broadcast :=
          nav$global_statistics.directory.translations_broadcast + 1;

  PROCEND distribute_translation;
?? OLDTITLE ??
?? NEWTITLE := 'distribute_translation_request', EJECT ??

  PROCEDURE distribute_translation_request
    (    request: ^nat$translation_request);

{     PURPOSE:
{       This  routine  is  called  by the directory routines to send out a translation request data unit.

    VAR
      device_id: nlt$device_identifier,
      ignore_status: ost$status,
      network_device_list: ^nlt$network_device_list,
      network_selector: nat$network_selector,
      pdu: array [1 .. 4] of nat$data_fragment,
      request_data_unit: translation_request_pdu,
      request_data_unit_v3: translation_request_pdu_v3,
      sequence_number: integer;

    IF nlv$directory_version = nac$osi_directory_version THEN
      request_data_unit.id := nac$translation_req_data_unit;
      request_data_unit.version := nac$osi_directory_version;
      request_data_unit.title_size := STRLENGTH (request^.title);
      request_data_unit.protocol := request^.protocol;
      request_data_unit.info.wild_card := request^.wild_card;
      request_data_unit.info.class := request^.class;

      pdu [1].address := ^request_data_unit;
      pdu [1].length := #SIZE (request_data_unit);
      request_data_unit.community_count := 0;
      pdu [2].address := ^request^.title;
      pdu [2].length := request_data_unit.title_size;
      pdu [3].address := NIL;
      pdu [3].length := 0;
      pdu [4].address := NIL;
      pdu [4].length := 0;

      network_device_list := nlv$configured_network_devices.network_device_list;
      FOR device_id := LOWERBOUND (network_device_list^) TO UPPERBOUND (network_device_list^) DO
        nlp$na_broadcast_data (device_id, nac$xi_cdna_directory_sap, nac$xi_cdna_directory_sap, pdu,
              ignore_status);
      FOREND;
    ELSE {nac$directory_version_3
      request_data_unit_v3.id := nac$v3_translation_rq_data_unit;
      request_data_unit_v3.version := nac$directory_version_3;
      request_data_unit_v3.lifetime := nac$initial_pdu_lifetime;
      request_data_unit_v3.title_size := STRLENGTH (request^.title);
      request_data_unit_v3.protocol := request^.protocol;
      request_data_unit_v3.info.wild_card := request^.wild_card;
      request_data_unit_v3.info.class := request^.class;
      get_pdu_sequence_number (sequence_number);
      request_data_unit_v3.sequence := sequence_number;

      pdu [1].address := ^request_data_unit_v3;
      pdu [1].length := #SIZE (request_data_unit_v3);
      pdu [2].address := ^request^.title;
      pdu [2].length := request_data_unit_v3.title_size;
      pdu [4].address := ^network_selector;
      pdu [4].length := #SIZE (nat$network_selector);
      network_selector := nac$xi_cdna_directory_sap;

      nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
      FOR device_id := LOWERBOUND (nlv$sm_devices.list^) TO UPPERBOUND (nlv$sm_devices.list^) DO
        pdu [3].address := ^nlv$sm_devices.list^ [device_id].generic_host_address;
        pdu [3].length := nlv$sm_devices.list^ [device_id].network_address_length
              - #SIZE (nat$network_selector);
        request_data_unit_v3.source_nsap_length := nlv$sm_devices.list^ [device_id].network_address_length;
        send_distributed_pdu (device_id, pdu);
      FOREND;
      nlp$release_nonexclusive_access (nlv$sm_devices.access_control);
    IFEND;

    request^.broadcast_counter := request^.broadcast_counter + 1;
    request^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$translation_request_delay;

    nav$global_statistics.directory.translation_requests_broadcast :=
          nav$global_statistics.directory.translation_requests_broadcast + 1;

  PROCEND distribute_translation_request;
?? OLDTITLE ??
?? NEWTITLE := '  exit_condition_handler', EJECT ??

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

      VAR
        lock_status: ost$signature_lock_status;

      nap$condition_handler_trace (condition, save_area);
      handler_status.normal := TRUE;
      osp$test_signature_lock (nlv$directory_lock, lock_status);
      IF lock_status = osc$sls_locked_by_current_task THEN
        osp$clear_job_signature_lock (nlv$directory_lock);
      IFEND;
      osp$end_subsystem_activity;
      osp$pop_inhibit_job_recovery;

    PROCEND exit_condition_handler;
?? NEWTITLE := 'find_request', EJECT ??

  PROCEDURE [INLINE] find_request
    (    request_id: nat$directory_search_identifier;
     VAR request: ^nat$translation_request);

{     PURPOSE:
{       This procedure locates a translation request in a list with a given
{       user identifier.

    request := nav$translation_requests.first;
    WHILE (request <> NIL) AND (request^.identifier <> request_id) DO
      request := request^.link;
    WHILEND;

  PROCEND find_request;
?? OLDTITLE ??
?? NEWTITLE := 'find_title', EJECT ??

  PROCEDURE [INLINE] find_title
    (    title: string ( * <= nac$max_title_length);
         identifier: nat$directory_entry_identifier;
         first_translation: ^nat$translation;
     VAR translation: ^nat$translation);

{     PURPOSE:
{       This procedure locates a translation in a list with a given
{       title and identifier.

    translation := first_translation;
    WHILE (translation <> NIL) AND ((translation^.title^ <> title) OR (translation^.identifier <> identifier))
          DO
      translation := translation^.link;
    WHILEND;

  PROCEND find_title;
?? OLDTITLE ??
?? NEWTITLE := 'find_title_address', EJECT ??

  PROCEDURE [INLINE] find_title_address
    (    title: string ( * <= nac$max_title_length);
         osi_address: nat$osi_registration_address;
         first_translation: ^nat$translation;
     VAR translation: ^nat$translation);

{     PURPOSE:
{       This procedure locates a translation in a list with a given title and address.

    translation := first_translation;
    WHILE (translation <> NIL) AND ((translation^.title^ <> title) OR NOT same_address
          (translation^.registered_address, osi_address)) DO
      translation := translation^.link;
    WHILEND;

  PROCEND find_title_address;
?? OLDTITLE ??
?? NEWTITLE := 'generate_osi_address', EJECT ??

  PROCEDURE generate_osi_address
    (    translation: ^nat$translation;
         osi_device: nlt$device_identifier;
     VAR osi_address: ^SEQ ( * );
     VAR osi_address_length: nat$osi_address_length);

{     PURPOSE:
{       This procedure generates an OSI address given a translation entry and an OSI device.

    TYPE
      osi_address_header = record
        kind: nat$network_address_kind,
        length: nat$osi_address_length,
      recend,

      osi_presentation_selector = record
        length: nat$osi_psap_selector_length,
        value: nat$osi_presentation_selector,
      recend,

      osi_session_selector = record
        length: nat$osi_ssap_selector_length,
        value: nat$osi_session_selector,
      recend,

      osi_transport_address = record
        tsap_length: nat$osi_tsap_selector_length,
        tsap: nlt$ta_sap_selector,
        network_address_length: nat$osi_network_address_length,
        network_address: nat$osi_network_address,
      recend;

    VAR
      address_header: ^osi_address_header,
      device_attributes: ^nlt$system_management,
      i: integer,
      network_address: ^SEQ ( * ),
      network_address_prefix: ^nat$osi_network_address_prefix,
      network_selector: ^nat$network_selector,
      psap_selector: ^osi_presentation_selector,
      psap_selector_length: nat$osi_psap_selector_length,
      ssap_selector: ^osi_session_selector,
      ssap_selector_length: nat$osi_ssap_selector_length,
      subnet_identifier: ^nat$subnet_identifier,
      system_identifier: ^nat$system_identifier,
      transport_address: ^osi_transport_address,
      unused_byte_count: integer,
      unused_space: ^array [1 .. * ] of 0 .. 0ff(16);

    RESET osi_address;
    nlp$get_nonexclusive_access (nlv$sm_devices.access_control);
    device_attributes := ^nlv$sm_devices.list^ [osi_device];
    IF (device_attributes^.network_address_length > 0) AND
          (device_attributes^.network_address_prefix <> NIL) THEN
      NEXT address_header IN osi_address;
      address_header^.kind := translation^.registered_address.kind;
      address_header^.length := 0;
      IF address_header^.kind = nac$osi_presentation_address THEN
        psap_selector_length := translation^.registered_address.presentation_selector_length;
        NEXT psap_selector: [psap_selector_length] IN osi_address;
        address_header^.length := address_header^.length + #SIZE (psap_selector^);
        psap_selector^.length := psap_selector_length;
        psap_selector^.value := translation^.registered_address.presentation_selector;
      IFEND;
      IF (address_header^.kind = nac$osi_presentation_address) OR
            (address_header^.kind = nac$osi_session_address) THEN
        ssap_selector_length := translation^.registered_address.session_selector_length;
        NEXT ssap_selector: [ssap_selector_length] IN osi_address;
        address_header^.length := address_header^.length + #SIZE (ssap_selector^);
        ssap_selector^.length := ssap_selector_length;
        ssap_selector^.value := translation^.registered_address.session_selector;
      IFEND;

      NEXT transport_address: [[REP device_attributes^.network_address_length OF cell]] IN osi_address;
      address_header^.length := address_header^.length + #SIZE (transport_address^);
      transport_address^.tsap_length := #SIZE (nlt$ta_sap_selector);
      transport_address^.tsap := translation^.registered_address.transport_selector;
      transport_address^.network_address_length := device_attributes^.network_address_length;
      i#move (^device_attributes^.generic_host_address, ^transport_address^.network_address,
            device_attributes^.network_address_length);
      osi_address_length := address_header^.length + #SIZE (address_header^);
    ELSE { OSI stack not configured in the device
      osi_address := NIL;
    IFEND;
    nlp$release_nonexclusive_access (nlv$sm_devices.access_control);

  PROCEND generate_osi_address;
?? OLDTITLE ??
?? NEWTITLE := 'generate_translation_data_unit', EJECT ??

  PROCEDURE generate_translation_data_unit
    (    translation: ^nat$translation;
         osi_device: nlt$device_identifier;
         reason: generate_reason;
     VAR osi_address: ^SEQ ( * );
     VAR data_unit_header: translation_pdu;
     VAR translation_data_unit: array [1 .. 4] of nat$data_fragment);

{     PURPOSE:
{       This procedure generates a translation data unit given a translation entry.

    VAR
      osi_address_length: nat$osi_address_length;

    data_unit_header.id := nac$translation_data_unit;
    data_unit_header.version := nac$osi_directory_version;
    data_unit_header.identifier := translation^.identifier;
    data_unit_header.change_count := translation^.change_count;
    data_unit_header.address.kind := translation^.registered_address.kind;
    data_unit_header.title_size := STRLENGTH (translation^.title^);
    data_unit_header.protocol := translation^.protocol;
    data_unit_header.priority := translation^.priority;
    data_unit_header.info.class := translation^.class;
    data_unit_header.info.response := reason = response;
    data_unit_header.community_count := 0;

    translation_data_unit [1].address := ^data_unit_header;
    translation_data_unit [1].length := #SIZE (data_unit_header);
    translation_data_unit [2].address := translation^.title;
    translation_data_unit [2].length := data_unit_header.title_size;
    translation_data_unit [3].address := translation^.user_information;
    IF translation^.user_information <> NIL THEN
      translation_data_unit [3].length := STRLENGTH (translation^.user_information^);
      data_unit_header.info.userinfo_size := STRLENGTH (translation^.user_information^);
    ELSE
      translation_data_unit [3].length := 0;
      data_unit_header.info.userinfo_size := 0;
    IFEND;

    generate_osi_address (translation, osi_device, osi_address, osi_address_length);
    translation_data_unit [4].address := osi_address;
    translation_data_unit [4].length := osi_address_length;

  PROCEND generate_translation_data_unit;
?? OLDTITLE ??
?? NEWTITLE := 'generate_v3_trans_data_unit', EJECT ??

  PROCEDURE generate_v3_trans_data_unit
    (    translation: ^nat$translation;
         osi_device: nlt$device_identifier;
         reason: generate_reason;
     VAR osi_address: ^SEQ ( * );
     VAR data_unit_header: translation_pdu_v3;
     VAR translation_data_unit: array [1 .. 4] of nat$data_fragment);

{     PURPOSE:
{       This procedure generates a translation data unit given a translation entry.

    VAR
      sequence_number: integer,
      osi_address_length: nat$osi_address_length;

    data_unit_header.id := nac$v3_translation_data_unit;
    data_unit_header.version := nac$directory_version_3;
    data_unit_header.identifier := translation^.identifier;
    data_unit_header.lifetime := nac$initial_pdu_lifetime;
    get_pdu_sequence_number (sequence_number);
    data_unit_header.sequence := sequence_number;
    data_unit_header.change_count := translation^.change_count;
    data_unit_header.address_kind := translation^.registered_address.kind;
    data_unit_header.title_size := STRLENGTH (translation^.title^);
    data_unit_header.protocol := translation^.protocol;
    data_unit_header.priority := translation^.priority;
    data_unit_header.info.class := translation^.class;
    data_unit_header.info.response := reason = response;

    translation_data_unit [1].address := ^data_unit_header;
    translation_data_unit [1].length := #SIZE (data_unit_header);
    translation_data_unit [2].address := translation^.title;
    translation_data_unit [2].length := data_unit_header.title_size;
    translation_data_unit [3].address := translation^.user_information;
    IF translation^.user_information <> NIL THEN
      translation_data_unit [3].length := STRLENGTH (translation^.user_information^);
      data_unit_header.info.userinfo_size := STRLENGTH (translation^.user_information^);
    ELSE
      translation_data_unit [3].length := 0;
      data_unit_header.info.userinfo_size := 0;
    IFEND;

    generate_osi_address (translation, osi_device, osi_address, osi_address_length);
    translation_data_unit [4].address := osi_address;
    translation_data_unit [4].length := osi_address_length;

  PROCEND generate_v3_trans_data_unit;
?? TITLE := 'get_pdu_sequence_number', EJECT  ??

  PROCEDURE get_pdu_sequence_number
    (VAR sequence_number: integer);

{     PURPOSE:
{       This procedure manages the directory protocol sequence number.

    VAR
      cs_status: osc$cs_successful .. osc$cs_variable_locked,
      new_sequence_number: integer,
      pdu_sequence_number: [STATIC] integer := 0; { This variable is used to optimize
{                           managing the directory protocol sequence number.

    sequence_number := 0;
    new_sequence_number := pdu_sequence_number + 1;
    REPEAT
      #compare_swap (nlv$directory_pdu_seq_number, pdu_sequence_number,
            new_sequence_number, pdu_sequence_number, cs_status);
      CASE cs_status OF
      = osc$cs_successful =
        IF new_sequence_number <= 0ffff(16) THEN
          pdu_sequence_number := new_sequence_number;
          sequence_number := new_sequence_number;
        ELSE
          new_sequence_number := 1;
        IFEND;
      = osc$cs_failed =
        new_sequence_number := pdu_sequence_number + 1;
        IF new_sequence_number > 0ffff(16) THEN
          new_sequence_number := 1;
        IFEND;
      = osc$cs_variable_locked =
        ;
      CASEND;
    UNTIL (cs_status = osc$cs_successful) AND (sequence_number > 0);

  PROCEND get_pdu_sequence_number;
?? OLDTITLE ??
?? NEWTITLE := 'nlp$name_match - wild card comparison function', EJECT ??

  FUNCTION [XDCL, #GATE] nlp$name_match
    (    name: nat$title_pattern;
         model: nat$title): boolean;

{ PURPOSE:
{   Compare the two strings entered by checking for model conformity.
{
{ CALL FORMAT:
{   result := nlp$name_match(name,model);
{
{ DESCRIPTION:
{   This function compares the two strings entered, name and model.
{   The name string may contain wild card attributes, the model string
{   is used to compare against the name string.  If the two strings
{   conform (match) the function returns a TRUE value; oherwise, it
{   returns FALSE.
{
{   Inputs:
{     name   string(*)   the name to be compared
{     model  string(*)   the model to compare against
{
{   The following characters have special meaning.  These characters
{   may be used in the name string as wild card entries.
{
{     [ ... ]  any single character among those in brackets
{
{      a-z     within a bracketed group, a range of characters
{              is represented with a dash, i.e.:
{              "a-z", where "a" and "z" are any two characters for
{              which the expression a <= z or a >= z is accepted
{
{      *       any character string including the NULL string
{
{      ?       any single character
{
{      '       If the model contains any special characters,
{              those special characters (*, [, ?) must be surrounded
{              with single quotes.  If the model contains a single
{              quote, 2 single quotes must be in  the name.
{              example: the name string A'*'B  matches  the model
{              string A*B and the name string A''B matches the model
{              string A'B.
{
{         Special characters are not recognized within a bracketed group.

    VAR
      name_index, { indexes to name string
      max_name,

      model_index, { indexes to model string
      max_model,
      special_index,
      asterisk_index: integer, { asterisk index in name string
      closing_bracket_processed, { closing bracket found ']'
      char_in_range, { bracket character in range flag
      char_in_bracket, { character found in bracket
      special_restart,
      asterisk_found: boolean; { asterisk detected in name string

    max_name := STRLENGTH (name);
    max_model := STRLENGTH (model);

    WHILE (max_name > 1) AND (name (max_name) = ' ') DO
      max_name := max_name - 1;
    WHILEND;

    WHILE (max_model > 1) AND (model (max_model) = ' ') DO
      max_model := max_model - 1;
    WHILEND;

    nlp$name_match := FALSE;
    asterisk_found := FALSE;
    special_restart := FALSE;
    name_index := 1;
    model_index := 1;

  /next_special/
    WHILE (model_index <= max_model) AND (name_index <= max_name) DO

    /special_case/
      BEGIN
        CASE name (name_index) OF
        = '*' =
          WHILE (name_index < max_name) AND (name (name_index) = '*') DO
            name_index := name_index + 1;
          WHILEND;
          asterisk_index := name_index - 1;

          IF name (name_index) = '*' THEN { last character *
            nlp$name_match := TRUE;
            RETURN;
          IFEND;

          special_index := model_index;
          asterisk_found := TRUE;

        = '?' =
          name_index := name_index + 1;
          model_index := model_index + 1;

        = '[' =
          closing_bracket_processed := FALSE;
          char_in_range := FALSE;
          char_in_bracket := FALSE;
          name_index := name_index + 1;

          IF name (name_index + 1) = '-' THEN
            name_index := name_index + 1;
          IFEND;

          WHILE (name_index <= max_name) AND (NOT closing_bracket_processed) DO
            CASE name (name_index) OF
            = ']' =
              IF NOT char_in_bracket THEN
                IF asterisk_found THEN
                  special_restart := TRUE;
                  EXIT /special_case/;
                ELSE
                  RETURN;
                IFEND;
              IFEND;

              closing_bracket_processed := TRUE;
              name_index := name_index + 1;
              model_index := model_index + 1;

            = '-' =
              IF name_index + 1 <= max_name THEN
                char_in_bracket := ((model (model_index) >= name (name_index - 1)) AND
                      (model (model_index) <= name (name_index + 1))) OR
                      ((model (model_index) <= name (name_index - 1)) AND
                      (model (model_index) >= name (name_index + 1)));
                name_index := name_index + 2;
              ELSE
                RETURN;
              IFEND;
            ELSE
              char_in_bracket := name (name_index) = model (model_index);
              name_index := name_index + 1;
            CASEND;
            WHILE char_in_bracket AND NOT closing_bracket_processed AND
                  (name_index <= max_name) AND (name (name_index) <> ']') DO
              name_index := name_index + 1;
            WHILEND;
          WHILEND;

        = '''' =
          IF (name_index < max_name) AND (name (name_index + 1) = '''') AND (model (model_index) = '''') THEN
            name_index := name_index + 2;
            model_index := model_index + 1;
          ELSE
            name_index := name_index + 1;
            WHILE (name_index <= max_name) AND (name (name_index) <> '''') AND (model_index <= max_model) DO
              IF name (name_index) <> model (model_index) THEN
                IF asterisk_found THEN
                  special_restart := TRUE;
                  EXIT /special_case/;
                ELSE
                  RETURN;
                IFEND;
              IFEND;
              name_index := name_index + 1;
              model_index := model_index + 1;
            WHILEND;
            IF (name_index > max_name) OR (name (name_index) <> '''') THEN
              RETURN; { missing end quote
            IFEND;
            name_index := name_index + 1;
          IFEND;

        ELSE { compare characters

          IF name (name_index) <> model (model_index) THEN
            IF asterisk_found THEN
              special_restart := TRUE;
              EXIT /special_case/;
            ELSE
              RETURN;
            IFEND;
          ELSE
            name_index := name_index + 1;
            model_index := model_index + 1;
          IFEND;
        CASEND;
      END /special_case/;

      IF (special_restart) OR ((asterisk_found) AND (name_index > max_name) AND (model_index <= max_model))
            THEN
        special_restart := FALSE;
        special_index := special_index + 1;
        model_index := special_index;
        name_index := asterisk_index + 1;
      IFEND;
    WHILEND /next_special/;

    nlp$name_match := (model_index > max_model) AND ((name_index > max_name) OR
          ((name_index = max_name) AND (name (max_name) = '*')));

{ check for * after name that exactly matches model

  FUNCEND nlp$name_match;
?? OLDTITLE ??
?? NEWTITLE := 'process_data_unit', EJECT ??

  PROCEDURE process_data_unit
    (    source_address: nat$network_layer_address;
         system_identifier: nat$system_identifier;
     VAR data_unit: ^SEQ ( * ));

{     PURPOSE:
{       This routine processes a directory data unit received over the network.
{
{     DESCRIPTION:
{       Data units processed are the Translation Data Unit, the
{       Delete Translation Data Unit, the Translation Request Data Unit,
{       and the Startup Data Unit.

    VAR
      actual: integer,
      community_count: 0 .. 0ff(16),
      delete_translation_data_unit: ^delete_translation_pdu,
      delete_translation_data_unit_v3: ^delete_translation_pdu_v3,
      detail: ^SEQ ( * ),
      detail_size: integer,
      expired_translation: ^nat$translation,
      header: ^pdu_header,
      identifier: nat$directory_entry_identifier,
      log_message: string (150),
      log_message_length: integer,
      old_translation: ^nat$translation,
      osi_address: ^SEQ ( * ),
      osi_address_buffer: SEQ (REP nac$max_osi_address_length of cell),
      osi_address_kind: ^nat$translation_address_kind,
      osi_address_length: ^nat$osi_address_length,
      osi_address_present: boolean,
      pdu: array [1 .. 4] of nat$data_fragment,
      registration_communities: ^array [1 .. * ] of nat$community_title,
      request: ^nat$translation_request,
      requestor: nat$network_layer_address,
      requestor_id: ^nat$system_identifier,
      requestor_nsap: ^nat$osi_network_address,
      startup_data_unit: ^startup_pdu,
      startup_data_unit_v3: ^startup_pdu_v3,
      statistic: ^integer,
      status: ost$status,
      till_system_id: ^string ( * ),
      title: ^string ( * ),
      title_length: nat$title_length,
      translation: ^nat$translation,
      translation_data_unit: ^translation_pdu,
      translation_data_unit_v3: ^translation_pdu_v3,
      translation_data_unit_header: translation_pdu,
      trans_data_unit_header_v3: translation_pdu_v3,
      translation_request_data_unit: ^translation_request_pdu,
      translation_req_data_unit_v3: ^translation_request_pdu_v3,
      user_info: ^string ( * ),
      userinfo_length: 0 .. 07f(16);

    status.normal := TRUE;

    RESET data_unit;
    NEXT header IN data_unit;
    RESET data_unit;

    IF header^.version = nac$osi_directory_version THEN
      CASE header^.id OF
      = nac$translation_req_data_unit =
        NEXT translation_request_data_unit IN data_unit;
        IF (translation_request_data_unit <> NIL) AND (translation_request_data_unit^.title_size > 0) THEN
          PUSH request: [translation_request_data_unit^.title_size];
          request^.wild_card := translation_request_data_unit^.info.wild_card;
          request^.protocol := translation_request_data_unit^.protocol;
          request^.class := translation_request_data_unit^.info.class;
          IF translation_request_data_unit^.community_count > 0 THEN
            NEXT registration_communities: [1 .. translation_request_data_unit^.community_count] IN
                  data_unit;
          IFEND;
          NEXT title: [translation_request_data_unit^.title_size] IN data_unit;
          request^.title := title^;
        ELSE
          RETURN;
        IFEND;
        IF nlv$log_broadcast_requests THEN
          STRINGREP (log_message, log_message_length, title^, ' requested by ', system_identifier: #(16));
          pmp$log (log_message (1, log_message_length), status);
        IFEND;

        osp$set_job_signature_lock (nlv$directory_lock);
        nav$global_statistics.directory.translation_requests_received :=
              nav$global_statistics.directory.translation_requests_received + 1;
        translation := nav$registered_titles.first;

        WHILE translation <> NIL DO
          IF (translation^.domain.kind = nac$catenet_domain) THEN
            IF valid_translation (translation, request) THEN
              osi_address := ^osi_address_buffer;
              generate_translation_data_unit (translation, source_address.device_id, response, osi_address,
                    translation_data_unit_header, pdu);
              IF (osi_address <> NIL) THEN
                nap$send_network_data (nac$xi_cdna_directory_sap, source_address, pdu, status);
                nav$global_statistics.directory.translations_sent :=
                      nav$global_statistics.directory.translations_sent + 1;
              IFEND;
            IFEND;
          IFEND;
          translation := translation^.link;
        WHILEND;
        osp$clear_job_signature_lock (nlv$directory_lock);

      = nac$translation_data_unit =
        NEXT translation_data_unit IN data_unit;
        IF (translation_data_unit <> NIL) AND (translation_data_unit^.title_size > 0) THEN
          IF translation_data_unit^.info.response THEN
            statistic := ^nav$global_statistics.directory.translations_received;
          ELSE
            statistic := ^nav$global_statistics.directory.broadcast_translations_received;
          IFEND;
          osp$increment_locked_variable (statistic^, 0, actual);
          title_length := translation_data_unit^.title_size;
          userinfo_length := translation_data_unit^.info.userinfo_size;
          community_count := translation_data_unit^.community_count;
          detail_size := title_length + userinfo_length;
          IF community_count > 0 THEN
            NEXT registration_communities: [1 .. community_count] IN data_unit;
          IFEND;
          NEXT title: [title_length] IN data_unit;
          IF nlv$log_broadcast_translations AND NOT translation_data_unit^.info.response THEN
            STRINGREP (log_message, log_message_length, title^, ' broadcast by ',
                  translation_data_unit^.identifier.system.system: #(16));
            pmp$log (log_message (1, log_message_length), status);
          IFEND;
          IF userinfo_length > 0 THEN
            NEXT user_info: [userinfo_length] IN data_unit;
          IFEND;
          osi_address_present := (translation_data_unit^.address.kind >= nac$osi_network_address) AND
                (translation_data_unit^.address.kind <= nac$osi_non_cdna_present_addr);
          IF osi_address_present THEN
            NEXT osi_address_kind IN data_unit;
            NEXT osi_address_length IN data_unit;
            IF osi_address_length <> NIL THEN
              detail_size := detail_size + osi_address_length^ + #SIZE (osi_address_kind^) +
                    #SIZE (osi_address_length^);
              RESET data_unit TO osi_address_kind;
              NEXT osi_address: [[REP osi_address_length^ + #SIZE (osi_address_kind^) +
                    #SIZE (osi_address_length^) OF cell]] IN data_unit;
              IF osi_address = NIL THEN
                RETURN;
              IFEND;
            ELSE
              RETURN;
            IFEND;
          IFEND;
          ALLOCATE translation: [[REP detail_size OF char]] IN nav$network_paged_heap^;
          IF translation <> NIL THEN
            translation^.identifier := translation_data_unit^.identifier;
            translation^.user_identifier := osc$null_name;
            translation^.change_count := translation_data_unit^.change_count;
            translation^.protocol := translation_data_unit^.protocol;
            translation^.priority := translation_data_unit^.priority;
            translation^.class := translation_data_unit^.info.class;
            detail := ^translation^.detail;
            RESET detail;
            translation^.domain.kind := nac$catenet_domain;
            NEXT translation^.title: [title_length] IN detail;
            translation^.title^ := title^;
            IF userinfo_length > 0 THEN
              NEXT translation^.user_information: [userinfo_length] IN detail;
              translation^.user_information^ := user_info^;
            ELSE
              translation^.user_information := NIL;
            IFEND;
            IF osi_address_present THEN
              translation^.osi_address_kind := translation_address;
              NEXT translation^.osi_address: [[REP #SIZE (osi_address^) OF cell]] IN detail;
              translation^.osi_address^ := osi_address^;
            ELSE
              translation^.osi_address_kind := undefined_address;
              translation^.osi_address := NIL;
            IFEND;
            translation^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$cache_timeout;

            osp$set_job_signature_lock (nlv$directory_lock);
            find_title (title^, translation^.identifier, nav$translation_cache.first, old_translation);
            IF (old_translation = NIL) THEN
              satisfy_translation_requests (translation);
              IF (translation^.priority = nac$max_directory_priority) THEN
                add_translation (translation, nav$translation_cache.first);
                nav$global_statistics.directory.current_cache_entries :=
                      nav$global_statistics.directory.current_cache_entries + 1;
                IF translation^.time_stamp < nav$translation_cache.next_timer THEN
                  nav$translation_cache.next_timer := translation^.time_stamp;
                IFEND;
              ELSE
                FREE translation IN nav$network_paged_heap^;
              IFEND;
            ELSEIF old_translation^.change_count = translation^.change_count THEN
              old_translation^.time_stamp := translation^.time_stamp;
              FREE translation IN nav$network_paged_heap^;
            ELSEIF old_translation^.change_count < translation^.change_count THEN
              satisfy_translation_requests (translation);
              delete_translation (old_translation, nav$translation_cache.first);
              IF (translation^.priority = nac$max_directory_priority) THEN
                add_translation (translation, nav$translation_cache.first);
                IF translation^.time_stamp < nav$translation_cache.next_timer THEN
                  nav$translation_cache.next_timer := translation^.time_stamp;
                IFEND;
              ELSE
                IF nav$global_statistics.directory.current_cache_entries > 0 THEN
                  nav$global_statistics.directory.current_cache_entries :=
                        nav$global_statistics.directory.current_cache_entries - 1;
                IFEND;
                FREE translation IN nav$network_paged_heap^;
              IFEND;
            ELSE {old_translation^.change_count > translation^.change_count}
              FREE translation IN nav$network_paged_heap^;
            IFEND;
            osp$clear_job_signature_lock (nlv$directory_lock);
          IFEND;
        IFEND;

      = nac$del_translation_data_unit =
        NEXT delete_translation_data_unit IN data_unit;
        IF (delete_translation_data_unit <> NIL) AND (delete_translation_data_unit^.title_size > 0) THEN
          identifier := delete_translation_data_unit^.identifier;
          title_length := delete_translation_data_unit^.title_size;
          NEXT title: [title_length] IN data_unit;

          osp$set_job_signature_lock (nlv$directory_lock);
          find_title (title^, identifier, nav$translation_cache.first, translation);
          IF translation <> NIL THEN
            delete_translation (translation, nav$translation_cache.first);
            IF nav$global_statistics.directory.current_cache_entries > 0 THEN
              nav$global_statistics.directory.current_cache_entries :=
                    nav$global_statistics.directory.current_cache_entries - 1;
            IFEND;
          IFEND;

{ A delete indication would be returned to appropriate requestors now.

          osp$clear_job_signature_lock (nlv$directory_lock);
        IFEND;

      = nac$startup_data_unit =
        NEXT startup_data_unit IN data_unit;
        IF startup_data_unit <> NIL THEN
          osp$set_job_signature_lock (nlv$directory_lock);
          translation := nav$translation_cache.first;
          WHILE translation <> NIL DO
            IF (startup_data_unit^.identifier.system = translation^.identifier.system) AND
                  time1_less_than_time2 (translation^.identifier.time_stamp,
                  startup_data_unit^.identifier.time_stamp) THEN
              expired_translation := translation;
              translation := translation^.link;
              delete_translation (expired_translation, nav$translation_cache.first);
              IF nav$global_statistics.directory.current_cache_entries > 0 THEN
                nav$global_statistics.directory.current_cache_entries :=
                      nav$global_statistics.directory.current_cache_entries - 1;
              IFEND;
            ELSE
              translation := translation^.link;
            IFEND;
          WHILEND;
          osp$clear_job_signature_lock (nlv$directory_lock);
        IFEND;

      ELSE
        ;
      CASEND;

    ELSEIF header^.version = nac$directory_version_3 THEN

      CASE header^.id OF
      = nac$v3_translation_rq_data_unit =
        NEXT translation_req_data_unit_v3 IN data_unit;
        IF (translation_req_data_unit_v3 <> NIL) AND (translation_req_data_unit_v3^.title_size > 0) THEN
          PUSH request: [translation_req_data_unit_v3^.title_size];
          request^.wild_card := translation_req_data_unit_v3^.info.wild_card;
          request^.protocol := translation_req_data_unit_v3^.protocol;
          request^.class := translation_req_data_unit_v3^.info.class;
          NEXT title: [translation_req_data_unit_v3^.title_size] IN data_unit;
          request^.title := title^;
        ELSE
          RETURN;
        IFEND;
        requestor.kind := nac$osi_network_address;
        requestor.device_id := source_address.device_id;
        requestor.network_address_length := translation_req_data_unit_v3^.source_nsap_length;
        NEXT requestor_nsap: [[ REP requestor.network_address_length OF cell]] IN data_unit;
        IF requestor_nsap <> NIL THEN
          i#move (requestor_nsap, ^requestor.network_address, requestor.network_address_length);
        ELSE
          RETURN;
        IFEND;
        RESET requestor_nsap;
        NEXT till_system_id: [requestor.network_address_length - #SIZE (nat$system_identifier) -
              #SIZE (nat$network_selector)] IN requestor_nsap;
        NEXT requestor_id IN requestor_nsap;
        IF requestor_id^ =  nav$system_id THEN {This is our own request ... ignore it.
          RETURN;
        IFEND;
        IF nlv$log_broadcast_requests THEN
          STRINGREP (log_message, log_message_length, title^, ' requested by ', requestor_id^: #(16));
          pmp$log (log_message (1, log_message_length), status);
        IFEND;

        osp$set_job_signature_lock (nlv$directory_lock);
        nav$global_statistics.directory.translation_requests_received :=
              nav$global_statistics.directory.translation_requests_received + 1;
        translation := nav$registered_titles.first;

        WHILE translation <> NIL DO
          IF (translation^.domain.kind = nac$catenet_domain) THEN
            IF valid_translation (translation, request) THEN
              osi_address := ^osi_address_buffer;
              generate_v3_trans_data_unit (translation, source_address.device_id, response, osi_address,
                    trans_data_unit_header_v3, pdu);
              IF (osi_address <> NIL) THEN
                nap$send_network_data (nac$xi_cdna_directory_sap, requestor, pdu, status);
                nav$global_statistics.directory.translations_sent :=
                      nav$global_statistics.directory.translations_sent + 1;
              IFEND;
            IFEND;
          IFEND;
          translation := translation^.link;
        WHILEND;
        osp$clear_job_signature_lock (nlv$directory_lock);

      = nac$v3_translation_data_unit =
        NEXT translation_data_unit_v3 IN data_unit;
        IF (translation_data_unit_v3 <> NIL) AND (translation_data_unit_v3^.title_size > 0) THEN
          IF translation_data_unit_v3^.info.response THEN
            statistic := ^nav$global_statistics.directory.translations_received;
          ELSE
            statistic := ^nav$global_statistics.directory.broadcast_translations_received;
          IFEND;
          osp$increment_locked_variable (statistic^, 0, actual);
          title_length := translation_data_unit_v3^.title_size;
          userinfo_length := translation_data_unit_v3^.info.userinfo_size;
          detail_size := title_length + userinfo_length;
          NEXT title: [title_length] IN data_unit;
          IF nlv$log_broadcast_translations AND NOT translation_data_unit_v3^.info.response THEN
            STRINGREP (log_message, log_message_length, title^, ' broadcast by ',
                  translation_data_unit_v3^.identifier.system.system: #(16));
            pmp$log (log_message (1, log_message_length), status);
          IFEND;
          IF userinfo_length > 0 THEN
            NEXT user_info: [userinfo_length] IN data_unit;
          IFEND;
          osi_address_present := (translation_data_unit_v3^.address_kind >= nac$osi_network_address) AND
                (translation_data_unit_v3^.address_kind <= nac$osi_non_cdna_present_addr);
          IF osi_address_present THEN
            NEXT osi_address_kind IN data_unit;
            NEXT osi_address_length IN data_unit;
            IF osi_address_length <> NIL THEN
              detail_size := detail_size + osi_address_length^ + #SIZE (osi_address_kind^) +
                    #SIZE (osi_address_length^);
              RESET data_unit TO osi_address_kind;
              NEXT osi_address: [[REP osi_address_length^ + #SIZE (osi_address_kind^) +
                    #SIZE (osi_address_length^) OF cell]] IN data_unit;
              IF osi_address = NIL THEN
                RETURN;
              IFEND;
            ELSE
              RETURN;
            IFEND;
          IFEND;
          ALLOCATE translation: [[REP detail_size OF char]] IN nav$network_paged_heap^;
          IF translation <> NIL THEN
            translation^.identifier := translation_data_unit_v3^.identifier;
            translation^.user_identifier := osc$null_name;
            translation^.change_count := translation_data_unit_v3^.change_count;
            translation^.protocol := translation_data_unit_v3^.protocol;
            translation^.priority := translation_data_unit_v3^.priority;
            translation^.class := translation_data_unit_v3^.info.class;
            detail := ^translation^.detail;
            RESET detail;
            translation^.domain.kind := nac$catenet_domain;
            NEXT translation^.title: [title_length] IN detail;
            translation^.title^ := title^;
            IF userinfo_length > 0 THEN
              NEXT translation^.user_information: [userinfo_length] IN detail;
              translation^.user_information^ := user_info^;
            ELSE
              translation^.user_information := NIL;
            IFEND;
            IF osi_address_present THEN
              translation^.osi_address_kind := translation_address;
              NEXT translation^.osi_address: [[REP #SIZE (osi_address^) OF cell]] IN detail;
              translation^.osi_address^ := osi_address^;
            ELSE
              translation^.osi_address_kind := undefined_address;
              translation^.osi_address := NIL;
            IFEND;
            translation^.time_stamp := #FREE_RUNNING_CLOCK (0) + nac$cache_timeout;

            osp$set_job_signature_lock (nlv$directory_lock);
            find_title (title^, translation^.identifier, nav$translation_cache.first, old_translation);
            IF (old_translation = NIL) THEN
              satisfy_translation_requests (translation);
              IF (translation^.priority = nac$max_directory_priority) THEN
                add_translation (translation, nav$translation_cache.first);
                nav$global_statistics.directory.current_cache_entries :=
                      nav$global_statistics.directory.current_cache_entries + 1;
                IF translation^.time_stamp < nav$translation_cache.next_timer THEN
                  nav$translation_cache.next_timer := translation^.time_stamp;
                IFEND;
              ELSE
                FREE translation IN nav$network_paged_heap^;
              IFEND;
            ELSEIF old_translation^.change_count = translation^.change_count THEN
              old_translation^.time_stamp := translation^.time_stamp;
              FREE translation IN nav$network_paged_heap^;
            ELSEIF old_translation^.change_count < translation^.change_count THEN
              satisfy_translation_requests (translation);
              delete_translation (old_translation, nav$translation_cache.first);
              IF (translation^.priority = nac$max_directory_priority) THEN
                add_translation (translation, nav$translation_cache.first);
                IF translation^.time_stamp < nav$translation_cache.next_timer THEN
                  nav$translation_cache.next_timer := translation^.time_stamp;
                IFEND;
              ELSE
                IF nav$global_statistics.directory.current_cache_entries > 0 THEN
                  nav$global_statistics.directory.current_cache_entries :=
                        nav$global_statistics.directory.current_cache_entries - 1;
                IFEND;
                FREE translation IN nav$network_paged_heap^;
              IFEND;
            ELSE {old_translation^.change_count > translation^.change_count}
              FREE translation IN nav$network_paged_heap^;
            IFEND;
            osp$clear_job_signature_lock (nlv$directory_lock);
          IFEND;
        IFEND;

      = nac$v3_dl_translation_data_unit =
        NEXT delete_translation_data_unit_v3 IN data_unit;
        IF (delete_translation_data_unit_v3 <> NIL) AND (delete_translation_data_unit_v3^.title_size > 0) THEN
          identifier := delete_translation_data_unit_v3^.identifier;
          title_length := delete_translation_data_unit_v3^.title_size;
          NEXT title: [title_length] IN data_unit;
          IF title <> NIL THEN
            osp$set_job_signature_lock (nlv$directory_lock);
            find_title (title^, identifier, nav$translation_cache.first, translation);
            IF translation <> NIL THEN
              delete_translation (translation, nav$translation_cache.first);
              IF nav$global_statistics.directory.current_cache_entries > 0 THEN
                nav$global_statistics.directory.current_cache_entries :=
                      nav$global_statistics.directory.current_cache_entries - 1;
              IFEND;
            IFEND;

{ A delete indication would be returned to appropriate requestors now.

            osp$clear_job_signature_lock (nlv$directory_lock);
          IFEND;
        IFEND;

      = nac$v3_startup_data_unit =
        NEXT startup_data_unit_v3 IN data_unit;
        IF startup_data_unit_v3 <> NIL THEN
          osp$set_job_signature_lock (nlv$directory_lock);
          translation := nav$translation_cache.first;
          WHILE translation <> NIL DO
            IF (startup_data_unit_v3^.identifier.system = translation^.identifier.system) AND
                  time1_less_than_time2 (translation^.identifier.time_stamp,
                  startup_data_unit_v3^.identifier.time_stamp) THEN
              expired_translation := translation;
              translation := translation^.link;
              delete_translation (expired_translation, nav$translation_cache.first);
              IF nav$global_statistics.directory.current_cache_entries > 0 THEN
                nav$global_statistics.directory.current_cache_entries :=
                      nav$global_statistics.directory.current_cache_entries - 1;
              IFEND;
            ELSE
              translation := translation^.link;
            IFEND;
          WHILEND;
          osp$clear_job_signature_lock (nlv$directory_lock);
        IFEND;
      ELSE
        ;
      CASEND;
    IFEND;

  PROCEND process_data_unit;
?? OLDTITLE ??
?? NEWTITLE := 'same_address', EJECT ??

  FUNCTION same_address
    (    address_3: nat$osi_registration_address;
         address_4: nat$osi_registration_address): boolean;

{     PURPOSE:
{       This function compares two registered addresses and determines
{       whether they are equivalent or not.

    VAR
      sequence: ^SEQ ( * ),
      string_1: ^string ( * ),
      string_2: ^string ( * );

    IF address_3.kind = address_4.kind THEN
      CASE address_3.kind OF
      = nac$osi_transport_address =
        same_address := (address_3.transport_selector = address_4.transport_selector);

      = nac$osi_session_address =
        same_address := (address_3.transport_selector = address_4.transport_selector) AND
              (address_3.session_selector = address_4.session_selector);

      = nac$osi_presentation_address =
        same_address := (address_3.transport_selector = address_4.transport_selector) AND
              (address_3.session_selector = address_4.session_selector) AND
              (address_3.presentation_selector = address_4.presentation_selector);

      = nac$osi_non_cdna_session_addr, nac$osi_non_cdna_present_addr =
        sequence := address_3.osi_address;
        RESET sequence;
        NEXT string_1: [#SIZE (sequence^)] IN sequence;
        sequence := address_4.osi_address;
        RESET sequence;
        NEXT string_2: [#SIZE (sequence^)] IN sequence;
        same_address := string_1 = string_2;

      ELSE
        same_address := FALSE;
      CASEND;
    ELSE
      same_address := FALSE;
    IFEND;

  FUNCEND same_address;
?? OLDTITLE ??
?? NEWTITLE := 'satisfy_translation_requests', EJECT ??

  PROCEDURE satisfy_translation_requests
    (    translation: ^nat$translation);

{     PURPOSE:
{       This  routine  is called by directory routines to check
{       if the given translation matches  any  outstanding TRDS
{       request.
{
{     DESCRIPTION:
{       This  routine  scans  the  outstanding TRDS entries to
{       check if the given translation satisfies  any translation
{       request. The requesting task is notified when one is found.

    VAR
      ready_status: ost$status,
      next_request: ^nat$translation_request,
      request: ^nat$translation_request;

    request := nav$translation_requests.first;

    WHILE request <> NIL DO
      next_request := request^.link;
      IF valid_translation (translation, request) THEN
        IF (translation^.osi_address_kind = translation_address) THEN
          save_valid_translation (translation, request);
        ELSE
          save_local_translation (translation, request);
        IFEND;
        ready_status.normal := TRUE;
        pmp$ready_task (request^.requestor, ready_status);
        IF NOT ready_status.normal THEN {requesting task is gone..delete request}
          delete_request (request);
        IFEND;
      IFEND;
      request := next_request;
    WHILEND;

  PROCEND satisfy_translation_requests;
?? OLDTITLE ??
?? NEWTITLE := 'save_local_translation', EJECT ??

  PROCEDURE save_local_translation
    (    translation: ^nat$translation;
         request: ^nat$translation_request);

{     PURPOSE:
{       This routine is called by directory routines to save a
{      local translation for a translation request primitive.
{
{     DESCRIPTION:
{       The translation is copied to the chain of valid translations
{       for the request, once for each active OSI device. The OSI address
{       for the device is placed in the corresponding translation.
{       The translation will be returned to the requestor by nlp$get_title_translation.

    VAR
      detail: ^SEQ ( * ),
      detail_length: integer,
      new_translation: ^nat$translation,
      osi_address: ^SEQ ( * ),
      osi_address_buffer: SEQ (REP nac$max_osi_address_length of cell),
      osi_address_length: nat$osi_address_length,
      osi_device: nlt$device_identifier;

    FOR osi_device := LOWERBOUND (nlv$configured_network_devices.network_device_list^)
          TO UPPERBOUND (nlv$configured_network_devices.network_device_list^) DO
      osi_address := ^osi_address_buffer;
      generate_osi_address (translation, osi_device, osi_address, osi_address_length);
      IF osi_address <> NIL THEN
        detail_length := STRLENGTH (translation^.title^) + osi_address_length;
        IF translation^.user_information <> NIL THEN
          detail_length := detail_length + STRLENGTH (translation^.user_information^);
        IFEND;
        ALLOCATE new_translation: [[REP detail_length OF char]] IN nav$network_paged_heap^;
        IF new_translation <> NIL THEN
          detail := ^new_translation^.detail;
          RESET detail;
          NEXT new_translation^.title: [STRLENGTH (translation^.title^)] IN detail;
          new_translation^.title^ := translation^.title^;
          new_translation^.identifier := translation^.identifier;
          new_translation^.user_identifier := translation^.user_identifier;
          new_translation^.change_count := translation^.change_count;
          new_translation^.protocol := translation^.protocol;
          IF translation^.user_information <> NIL THEN
            NEXT new_translation^.user_information: [STRLENGTH (translation^.user_information^)] IN detail;
            new_translation^.user_information^ := translation^.user_information^;
          ELSE
            new_translation^.user_information := NIL;
          IFEND;
          new_translation^.osi_address_kind := translation_address;
          NEXT new_translation^.osi_address: [[REP osi_address_length OF cell]] IN detail;
          i#move (osi_address, new_translation^.osi_address, osi_address_length);
          new_translation^.priority := translation^.priority;
          new_translation^.reported := FALSE;
          add_translation (new_translation, request^.first_translation);
        IFEND;
      IFEND;
    FOREND;

  PROCEND save_local_translation;
?? OLDTITLE ??
?? NEWTITLE := 'save_valid_translation', EJECT ??

  PROCEDURE save_valid_translation
    (    translation: ^nat$translation;
         request: ^nat$translation_request);

{     PURPOSE:
{       This routine is called by directory routines to save a
{      valid translation for a translation request primitive.
{
{     DESCRIPTION:
{       The translation is copied to the chain of valid translations
{       for the request. The translation will be returned to the
{       requestor by nlp$get_title_translation.

    VAR
      detail: ^SEQ ( * ),
      new_translation: ^nat$translation,
      old_translation: ^nat$translation,
      osi_address_length: 0 .. 0ff(16);

    find_title (translation^.title^, translation^.identifier, request^.first_translation, old_translation);

{ See if this translation is a new translation or an old one that has changed.

    IF (old_translation = NIL) OR (old_translation^.change_count < translation^.change_count) THEN
      ALLOCATE new_translation: [[REP #SIZE (translation^.detail) OF char]] IN nav$network_paged_heap^;
      IF new_translation <> NIL THEN
        detail := ^new_translation^.detail;
        RESET detail;
        NEXT new_translation^.title: [STRLENGTH (translation^.title^)] IN detail;
        new_translation^.title^ := translation^.title^;
        new_translation^.identifier := translation^.identifier;
        new_translation^.user_identifier := translation^.user_identifier;
        new_translation^.change_count := translation^.change_count;
        new_translation^.protocol := translation^.protocol;
        new_translation^.osi_address_kind := translation^.osi_address_kind;
        new_translation^.registered_address := translation^.registered_address;
        IF translation^.user_information <> NIL THEN
          NEXT new_translation^.user_information: [STRLENGTH (translation^.user_information^)] IN detail;
          new_translation^.user_information^ := translation^.user_information^;
        ELSE
          new_translation^.user_information := NIL;
        IFEND;
        IF translation^.osi_address_kind = translation_address THEN
          NEXT new_translation^.osi_address: [[REP #SIZE (translation^.osi_address^) OF cell]] IN detail;
          new_translation^.osi_address^ := translation^.osi_address^;
        ELSEIF (translation^.osi_address_kind = registration_address) AND
              ((translation^.registered_address.kind = nac$osi_non_cdna_session_addr) OR
              (translation^.registered_address.kind = nac$osi_non_cdna_present_addr)) THEN
          NEXT new_translation^.registered_address.osi_address:
                [[REP #SIZE (translation^.registered_address.osi_address^) OF cell]] IN detail;
          new_translation^.registered_address.osi_address^ := translation^.registered_address.osi_address^;
        IFEND;
        new_translation^.priority := translation^.priority;
        new_translation^.reported := FALSE;
        add_translation (new_translation, request^.first_translation);
        IF old_translation <> NIL THEN
          delete_translation (old_translation, request^.first_translation);
        IFEND;
      IFEND;
    IFEND;

  PROCEND save_valid_translation;
?? OLDTITLE ??
?? NEWTITLE := 'send_distributed_pdu', EJECT ??

  PROCEDURE send_distributed_pdu
    (    device_id: nlt$device_identifier;
         pdu: array [1 .. * ] of nat$data_fragment);

    VAR
      broadcast_address: nat$network_layer_address,
      device_attributes: ^nlt$system_management,
      ignore_status: ost$status,
      network_address: ^SEQ ( * ),
      network_selector: ^nat$network_selector,
      system_id: ^nat$system_identifier,
      till_system_id: ^string ( * );

    device_attributes := ^nlv$sm_devices.list^ [device_id];
    IF (device_attributes^.network_address_length > 0) AND (device_attributes^.network_address_prefix <> NIL)
          THEN
      broadcast_address.kind := nac$osi_network_address;
      broadcast_address.device_id := device_id;
      broadcast_address.network_address_length := device_attributes^.network_address_length;
      i#move (^device_attributes^.generic_host_address, ^broadcast_address.network_address,
            broadcast_address.network_address_length);
      network_address := ^broadcast_address.network_address;
      RESET network_address;
      NEXT till_system_id: [broadcast_address.network_address_length - #SIZE (nat$system_identifier) -
            #SIZE (nat$network_selector)] IN network_address;
      NEXT system_id IN network_address;
      system_id^ := nav$cdna_multicast_address;
      NEXT network_selector IN network_address;
      network_selector^ := nac$xi_cdna_directory_sap;
      nap$send_network_data (nac$xi_cdna_directory_sap, broadcast_address, pdu, ignore_status);
    IFEND;

  PROCEND send_distributed_pdu;
?? OLDTITLE ??
?? NEWTITLE := 'time1_less_than_time2', EJECT ??

  FUNCTION time1_less_than_time2
    (    time1: nat$bcd_time;
         time2: nat$bcd_time): boolean;

{     PURPOSE:
{       This routine is called by directory routines to compare
{       two bcd date times.
{
{     DESCRIPTION:
{       This  routine  is  called  with  two  bcd  dates.  This
{       routine determines if the first time is less  than  the
{       second time.  The boolean result is returned.

    VAR
      date_1: 0 .. 0ffffff(16),
      date_2: 0 .. 0ffffff(16),
      time_1: 0 .. 0ffffffffff(16),
      time_2: 0 .. 0ffffffffff(16);

    #UNCHECKED_CONVERSION (time1.date, date_1);
    #UNCHECKED_CONVERSION (time2.date, date_2);
    #UNCHECKED_CONVERSION (time1.time, time_1);
    #UNCHECKED_CONVERSION (time2.time, time_2);

    time1_less_than_time2 := (date_1 < date_2) OR ((date_1 = date_2) AND (time_1 < time_2));

  FUNCEND time1_less_than_time2;
?? OLDTITLE ??
?? NEWTITLE := 'valid_translation', EJECT ??

  FUNCTION [INLINE] valid_translation
    (    translation: ^nat$translation;
         request: ^nat$translation_request): boolean;

{     PURPOSE:
{       This  routine  is  called  by the directory routines to
{       verify  a title translation meets the requested criteria.
{
{     DESCRIPTION:
{       Given  a  RDS/TDS entry and a Translation request, this
{       routine  verifies   the  title  matches  the  requested
{       criteria.
{       1.  Title  match  (may  be  wild  card)
{       2.  Directly Accessible  Service  layers match
{       3.  User Classification match

    valid_translation :=

    ((request^.protocol = nac$unknown_protocol) OR (request^.protocol = translation^.protocol))

    AND

    (request^.class = translation^.class)

    AND

    ((request^.wild_card AND nlp$name_match (request^.title, translation^.title^))

    OR

    (NOT (request^.wild_card) AND (translation^.title^ = request^.title)));

  FUNCEND valid_translation;
?? OLDTITLE ??
MODEND nlm$directory_management_entity;
