?? LEFT := 1, RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE: Network Alarm Processor' ??
?? NEWTITLE := 'Global Declarations' ??
MODULE nam$network_alarm_processor;
?? PUSH (LISTEXT := ON) ??
*copyc nae$manage_network_applications
*copyc nae$namve_conditions
*copyc nat$bcd_time
*copyc nat$command_interface
*copyc nat$community_title
*copyc nat$data_fragments
*copyc nat$directory_interfaces
*copyc nat$gt_event
*copyc nat$network_address
*copyc nat$network_message_priority
*copyc nat$title
*copyc nat$system_title
*copyc nat$wait_time
*copyc nlt$protocol
*copyc ost$i_wait
*copyc ost$status
?? POP ??
*copyc avp$get_capability
*copyc i#move
*copyc nap$gt_accept_connection
*copyc nap$gt_close_sap
*copyc nap$gt_disconnect
*copyc nap$gt_open_sap
*copyc nap$gt_receive_connect_event
*copyc nap$gt_receive_connection_event
*copyc nlp$delete_registered_title
*copyc nlp$register_title
*copyc osp$i_await_activity_completion
*copyc osp$set_status_abnormal
*copyc pmp$get_microsecond_clock
*copyc pmp$log

{ Alarm data unit consists of a fixed header, followed by a variable length list of community
{ titles and a variable number of text fields.

  TYPE
    nat$alarm_header = record
      header_length: 0 .. 0ff(16),
      version: 0 .. 0ff(16),
      time_stamp: nat$bcd_time,
      system_address: nat$system_address,
      message_number: 0 .. 0ffff(16),
      system: nat$system_title,
      community_count: 0 .. 0ff(16),
      {communities: array [1 .. community_count] of nat$alarm_community_entry,
    recend;

  TYPE
    nat$alarm_community_entry = record
      code: 0 .. 0ff(16),
      length: 0 .. 0ff(16),
      community: nat$community_title,
    recend;

  CONST
    timer_index = 1, {index for timer entry in wait list}
    sap_index = 2, {index for connect request in wait list}
    index_bias = 2, {bias between indices for wait list and connection list}
    nac$alarm_title_prefix = '$I_ALARM_ME_',
    nac$alarm_title_prefix_size = 12,
    nac$alarm_version_number = 1,
    nac$max_alarm_systems = 200;

  TYPE
    alarm_connection = record
      activity_status: ost$activity_status,
      connection_id: nat$gt_connection_id,
      event: nat$gt_event,
      alarm: SEQ (REP 512 OF cell), {scratch space for communities and small messages}
      state: (connected, disconnected),
    recend;

  VAR
    alarm_communities: ^array [1 .. *] of string (nac$alarm_title_prefix_size + nac$community_title_length),
    alarm_protocol: ^0 .. 255,
    alarms_active: boolean := FALSE,
    connect_data: array [1 .. 1] of nat$data_fragment := [[^connect_info, #SIZE (connect_info)]],
    connect_event: nat$gt_connect_event,
    connect_info: SEQ (REP 32 OF cell),
    connect_status: ost$activity_status,
    connection_list: array [1 .. nac$max_alarm_systems] of ^alarm_connection := [REP nac$max_alarm_systems OF
          NIL],
    directory_identifiers: ^array [1 .. *] of nat$directory_entry_identifier := NIL,
    max_connection_index: 0 .. nac$max_alarm_systems,
    nil_data: [STATIC] array [1 .. 1] of nat$data_fragment := [[NIL, 0]],
    password: nat$directory_password := 0,
    transport_sap_id: nat$gt_sap_identifier,
    wait_list: ^ost$i_wait_list,
    wait_pointer: ^SEQ (REP nac$max_alarm_systems + index_bias of ost$i_activity) := ^wait_sequence,
    wait_sequence: SEQ (REP nac$max_alarm_systems + index_bias of ost$i_activity);

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$activate_network_alarms', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$activate_network_alarms (communities: array [1 .. *] of nat$community_title;
    VAR status: ost$status);

{ PURPOSE: This procedure establishes an Independent Alarm Management Entity.
{ DESIGN:  A Generic Transport SAP is opened and the requested alarm group titles are
{          registered. Alarm messages may then be obtained by calling the nap$receive_network_alarm
{          interface. Alarm processing is terminated by a call to the nap$deactivate_network_alarm
{          interface.

    VAR
      address: nat$internet_address,
      ignore_status: ost$status,
      index: integer,
      network_operation: boolean,
      osi_address: nat$osi_registration_address,
      title_domain: [STATIC] nat$title_domain := [nac$catenet_domain],
      user_identifier: ost$name;

    status.normal := TRUE;
    IF NOT alarms_active THEN
      avp$get_capability (avc$network_operation, avc$user, network_operation, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;

      IF NOT network_operation THEN
        osp$set_status_abnormal (nac$status_id, nae$invalid_user, 'NETWORK OPERATOR UTILITY', status);
        RETURN;
      IFEND;
      nap$gt_open_sap (nac$max_alarm_systems, nac$system_message_priority, {reserved_sap=} FALSE,
            transport_sap_id, address, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;
      nap$gt_receive_connect_event (transport_sap_id, connect_data, osc$nowait, connect_event,
            connect_status, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;
      RESET wait_pointer;
      NEXT wait_list: [1 .. index_bias] IN wait_pointer;
      wait_list^ [sap_index].activity := nac$i_await_activity_status;
      wait_list^ [sap_index].activity_status := ^connect_status;
      wait_list^ [timer_index].activity := osc$i_await_time;
      max_connection_index := 0;

      alarms_active := TRUE;
      osi_address.kind := nac$osi_transport_address;
      osi_address.transport_selector := transport_sap_id.osi_sap_identifier;

      ALLOCATE directory_identifiers: [1 .. UPPERBOUND (communities)];
      ALLOCATE alarm_communities: [1 .. UPPERBOUND (communities)];
      user_identifier := nac$alarm_title_prefix;
      FOR index := LOWERBOUND (communities) TO UPPERBOUND (communities) DO
        alarm_communities^ [index] := nac$alarm_title_prefix;
        alarm_communities^ [index] (nac$alarm_title_prefix_size + 1, *) := communities [index];
        nlp$register_title (alarm_communities^ [index], osi_address, nac$cdna_transport,
              {user data} NIL, 0, nac$max_directory_priority, title_domain, {distribute} TRUE,
              nac$cdna_internal, password, user_identifier, directory_identifiers^ [index], status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
      FOREND;
    IFEND;

  PROCEND nap$activate_network_alarms;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$deactivate_network_alarms', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$deactivate_network_alarms (VAR status: ost$status);

    VAR
      ignore_status: ost$status,
      index: integer;

{ PURPOSE: Terminate Alarm Management.
{ DESIGN:  The Alarm Management titles are deleted and the associated Transport SAP
{          is closed.

    status.normal := TRUE;
    IF alarms_active THEN
      FOR index := LOWERBOUND (connection_list) TO UPPERBOUND (connection_list) DO
        IF (connection_list [index] <> NIL) AND (connection_list [index]^.state = connected) THEN
          nap$gt_disconnect (connection_list [index]^.connection_id, nil_data, ignore_status);
          FREE connection_list [index];
        IFEND;
      FOREND;
      nap$gt_close_sap (transport_sap_id, status);
      IF directory_identifiers <> NIL THEN
        FOR index := LOWERBOUND (directory_identifiers^) TO UPPERBOUND (directory_identifiers^) DO
          nlp$delete_registered_title (alarm_communities^ [index], password, directory_identifiers^ [index],
                ignore_status);
        FOREND;
        FREE directory_identifiers;
        FREE alarm_communities;
      IFEND;
      alarms_active := FALSE;
    IFEND;

  PROCEND nap$deactivate_network_alarms;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$receive_network_alarm', EJECT ??

  PROCEDURE [XDCL, #GATE] nap$receive_network_alarm (wait_time: nat$wait_time;
        alarm_message: nat$data_fragment;
    VAR message_length: nat$data_length;
    VAR system: nat$system_title;
    VAR alarm_code: nat$command_response_code;
    VAR time_stamp: nat$bcd_time;
    VAR status: ost$status);

{ PURPOSE: This procedure delivers a single network alarm message.
{ DESIGN:  The alarm protocol header information is interpreted and returned as parameter
{          values.

    VAR
      alarm_data: ^SEQ ( * ),
      alarm_header: ^nat$alarm_header,
      alarm_received: boolean,
      alarm_text: ^SEQ ( * ),
      community_list: ^array [1 .. * ] of nat$alarm_community_entry,
      connection: ^alarm_connection,
      current_time: integer,
      end_time: integer,
      ignore_status: ost$status,
      index: integer,
      remaining_length: nat$data_length,
      remaining_time: integer,
      response_buffer: array [1 .. 1] of nat$data_fragment;

    status.normal := TRUE;

    pmp$get_microsecond_clock (current_time, ignore_status);
    end_time := (current_time DIV 1000) + wait_time;
    alarm_received := FALSE;

    REPEAT
      pmp$get_microsecond_clock (current_time, ignore_status);
      remaining_time := end_time - (current_time DIV 1000);
      IF remaining_time > 0 THEN
        wait_list^ [timer_index].milliseconds := remaining_time;
      ELSE
        wait_list^ [timer_index].milliseconds := 1;
      IFEND;
      osp$i_await_activity_completion (wait_list^, index, status);
      IF status.normal THEN
        IF index = sap_index THEN
          connect_system (connect_event.connection, connection);
          nap$gt_receive_connect_event (transport_sap_id, connect_data, osc$nowait, connect_event,
                connect_status, status);
          IF NOT status.normal THEN
            RETURN;
          IFEND;
          nap$gt_accept_connection (connection^.connection_id, nil_data, NIL, status);
          IF NOT status.normal THEN
            RETURN;
          IFEND;
          response_buffer [1].address := ^connection^.alarm;
          response_buffer [1].length := #SIZE (connection^.alarm);
          nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$nowait,
                connection^.event, connection^.activity_status, status);
          IF NOT status.normal THEN
            RETURN;
          IFEND;
        ELSEIF index = timer_index THEN
          osp$set_status_abnormal (nac$status_id, nae$no_event, '', status);
        ELSEIF NOT connection_list [index - index_bias]^.activity_status.status.normal THEN
          status := connection_list [index - index_bias]^.activity_status.status;
        ELSE
          connection := connection_list [index - index_bias];
          CASE connection^.event.kind OF
          = nac$gt_data_event =
            alarm_data := ^connection^.alarm;
            NEXT alarm_header IN alarm_data;
            alarm_received := TRUE;
            system := alarm_header^.system;
            alarm_code := alarm_header^.message_number;
            time_stamp := alarm_header^.time_stamp;
            message_length := connection^.event.data.data_length - #SIZE(nat$alarm_header) - (alarm_header^.
                  community_count * #SIZE (nat$alarm_community_entry));
            IF message_length > 0 THEN {move first part of message to user's buffer}
              NEXT community_list: [1 .. alarm_header^.community_count] IN alarm_data;
              NEXT alarm_text: [[REP message_length OF cell]] IN alarm_data;
              i#move (alarm_text, alarm_message.address, message_length);
            ELSE
              message_length := 0;
            IFEND;
            IF NOT connection^.event.data.end_of_message THEN {get rest of message}
              response_buffer [1].address := #address (#ring (alarm_message.address), #segment
                    (alarm_message.address), #offset (alarm_message.address) + message_length);
              response_buffer [1].length := alarm_message.length - message_length;
              nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$wait,
                    connection^.event, connection^.activity_status, status);
              IF status.normal AND (connection^.event.kind = nac$gt_data_event) THEN
                message_length := message_length + connection^.event.data.data_length;
              IFEND;
            IFEND;
            IF status.normal THEN
              response_buffer [1].address := ^connection^.alarm;
              response_buffer [1].length := #SIZE (connection^.alarm);
              nap$gt_receive_connection_event (connection^.connection_id, response_buffer, osc$nowait,
                    connection^.event, connection^.activity_status, status);
            IFEND;
            IF NOT status.normal THEN
              connection^.state := disconnected;
              wait_list^ [index].activity := osc$i_null_activity;
            IFEND;

          = nac$gt_disconnect_event =
            connection^.state := disconnected;
            wait_list^ [index].activity := osc$i_null_activity;

          ELSE
            pmp$log ('unexpected transport event type', ignore_status);
          CASEND;
        IFEND;
      IFEND;

    UNTIL alarm_received OR NOT status.normal;
  PROCEND nap$receive_network_alarm;

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

  PROCEDURE connect_system (connection_id: nat$gt_connection_id;
    VAR connection: ^alarm_connection);

    VAR
      ignore_status: ost$status,
      connection_index: integer,
      wait_index: integer;

  /search_for_entry/
    BEGIN
      FOR connection_index := 1 TO max_connection_index DO
        IF connection_list [connection_index]^.state = disconnected THEN
          connection_list [connection_index]^.state := connected;
          connection_list [connection_index]^.connection_id := connection_id;
          connection := connection_list [connection_index];
          wait_index := connection_index + index_bias;
          wait_list^ [wait_index].activity := nac$i_await_activity_status;
          wait_list^ [wait_index].activity_status := ^connection_list [connection_index]^.activity_status;
          EXIT /search_for_entry/;
        IFEND;
      FOREND;

      IF max_connection_index < UPPERVALUE (max_connection_index) THEN
        max_connection_index := max_connection_index + 1;
        ALLOCATE connection_list [max_connection_index];
        connection_list [max_connection_index]^.state := connected;
        connection_list [max_connection_index]^.connection_id := connection_id;
        connection := connection_list [max_connection_index];
        wait_index := max_connection_index + index_bias;
        RESET wait_pointer;
        NEXT wait_list: [1 .. wait_index] IN wait_pointer;
        wait_list^ [wait_index].activity := nac$i_await_activity_status;
        wait_list^ [wait_index].activity_status := ^connection_list [max_connection_index]^.activity_status;
        EXIT /search_for_entry/;
      IFEND;
      nap$gt_disconnect (connection_id, nil_data, ignore_status);
      RETURN;
    END /search_for_entry/;

  PROCEND connect_system;
?? OLDTITLE ??
MODEND nam$network_alarm_processor;
