?? RIGHT := 110 ??
?? NEWTITLE := 'NAM/VE: Accounting Data Parser' ??
MODULE nam$parse_accounting_data;

{ PURPOSE:
{   This module contains the interface used to parse the peer_accounting_information
{   and peer_connect_data network file attributes for communication accounting statistics.
{
{ DESIGN:
{   Procedures in this module may run in rings 2 through 13 with a call bracket of
{   ring 13.  Communication equipment identification is passed in as an input parameter
{   which may be from the network connection or the job_input_device job attribute and
{   requested fields are returned.  Since the format of the accounting data is dependent
{   on CDCNET, the design is flexible so that changes may be made to support upcoming
{   CDCNET changes without becoming dependent on a certain CDCNET build level.
{

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc clc$max_string_size
*copyc fst$file_reference
*copyc jmt$service_data
*copyc nae$application_interfaces
*copyc nat$accounting_data_fields
*copyc nat$data_length
*copyc oss$job_paged_literal
*copyc ost$name
?? POP ??
*copyc osp$append_status_integer
*copyc osp$set_status_abnormal
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by This Module', EJECT ??

{ The following accounting and user data record formats were obtained from a CDCNET
{ document titled  Session Call Data Formats, DCS# ARH7243.

  CONST
    connect_data_length_position = 115,
    current_accounting_version = 1,
    ci_cdcnet_interactive_tip = 1,
    ci_cdcnet_paired_connection = 129,
    ci_cdcnet_batch_tip = 2,
    ci_cdcnet_aa_gateway = 64,
    ai_non_x25_device = 1,
    ai_x25_device = 2,
    ai_telnet_device = 3,
    ai_non_x25_aa = 64,
    ai_x25_aa = 65;

  CONST
    batch_user_data_version = 1;

  TYPE
    accounting_record_version = 0 .. 0ffff(16),
    accounting_caller_id = 0 .. 0ff(16),
    accounting_identifier = 0 .. 0ff(16),
    user_data_record_version = 0 .. 0ff(16);

  TYPE
    element_field = record
      index: 1 .. clc$max_string_size,
      size: 1 .. clc$max_string_size,
      last_character: 1 .. clc$max_string_size,
    recend;

  TYPE
    accounting_record_header = record
      version: element_field,
      caller_id: element_field,
      accounting_id: element_field,
    recend;

  TYPE
    non_x25_batch_record = record
      device_name: element_field,
      line_speed: element_field,
      line_name: element_field,
      line_subtype: element_field,
      di_system_name: element_field,
    recend;

  TYPE
    non_x25_batch_user_data_record = record
      version: element_field,
      i_o_station_name: element_field,
    recend;

  TYPE
    non_x25_interactive_record = record
      device_name: element_field,
      line_speed: element_field,
      line_name: element_field,
      line_subtype: element_field,
      di_system_name: element_field,
    recend;

  TYPE
    telnet_interactive_record = record
      device_name: element_field,
      di_system_name: element_field,
    recend;

  TYPE
    x25_aa_gateway_record = record
      trunk_name: element_field,
      pdn_name: element_field,
    recend;

  TYPE
    x25_interactive_record = record
      device_name: element_field,
      line_speed: element_field,
      trunk_name: element_field,
      trunk_subtype: element_field,
      pdn_name: element_field,
      di_system_name: element_field,
    recend;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$get_connect_data', EJECT ??
  PROCEDURE [XDCL, #GATE] nap$get_connect_data
    (peer_connect_data: ^SEQ (*);
     VAR connect_data: jmt$service_data;
     VAR connect_data_length: jmt$service_data_length;
     VAR status: ost$status);

    VAR
      connect_string: ^string (*),
      p_connect_data_seq: ^SEQ (*),
      peer_connect_info_length: nat$data_length;

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

    IF peer_connect_data <> NIL THEN
      peer_connect_info_length := #SIZE (peer_connect_data^);
      p_connect_data_seq := peer_connect_data;
      RESET p_connect_data_seq;
      NEXT connect_string: [peer_connect_info_length] IN p_connect_data_seq;
    ELSE
      RETURN;
    IFEND;

    IF peer_connect_info_length > 1 THEN
      IF ORD (connect_string^ (1)) = 1 THEN
        connect_data_length := ORD (connect_string^ (connect_data_length_position));
        IF connect_data_length > 0 THEN
          connect_data := connect_string^ (connect_data_length_position + 1, connect_data_length);
        IFEND;
      ELSE
        {Wrong version of connect data!
        osp$set_status_abnormal (nac$status_id, nae$acct_version_mismatch, 'User Data Record', status);
      IFEND;
    IFEND;

  PROCEND nap$get_connect_data;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nap$parse_accounting_data', EJECT ??
*copyc nah$parse_accounting_data

  PROCEDURE [XDCL, #GATE] nap$parse_accounting_data
    (    peer_accounting_information: ^string ( * );
         peer_connect_data: ^string ( * );
         accounting_data_fields {input, output} : ^nat$accounting_data_fields;
     VAR status: ost$status);


?? EJECT ??
?? FMT (FORMAT := OFF) ??

    VAR
      accounting_header: [STATIC, READ, oss$job_paged_literal] accounting_record_header := [
{                                      index |  size | last_character
{ version                         }  [    1,      2,      2],
{ caller_id                       }  [    3,      1,      3],
{ accounting_id                   }  [    4,      1,      4]],

      non_x25_batch: [STATIC, READ, oss$job_paged_literal] non_x25_batch_record := [
{                                      index |  size | last_character
{ device_name                     }  [   79,     31,    109],
{ line_speed                      }  [  113,      2,    114],
{ line_name                       }  [  115,     31,    145],
{ line_subtype                    }  [  146,     31,    176],
{ di_system_name                  }  [  182,     31,    212]],

      non_x25_batch_user_data: [STATIC, READ, oss$job_paged_literal] non_x25_batch_user_data_record := [
{ version                         }  [    1,      1,      1],
{ i_o_station_name                }  [    2,     31,     32]],

      non_x25_interactive: [STATIC, READ, oss$job_paged_literal] non_x25_interactive_record := [
{                                      index |  size | last_character
{ device_name                     }  [   79,     31,    109],
{ line_speed                      }  [  113,      2,    114],
{ line_name                       }  [  115,     31,    145],
{ line_subtype                    }  [  146,     31,    176],
{ di_system_name                  }  [  182,     31,    212]],

      telnet_interactive: [STATIC, READ, oss$job_paged_literal] telnet_interactive_record := [
{                                      index |  size | last_character
{ device_name                     }  [   79,     31,    109],
{ di_system_name                  }  [  125,     31,    155]],

      x25_aa_gateway: [STATIC, READ, oss$job_paged_literal] x25_aa_gateway_record := [
{ trunk_name                      }  [   70,     31,    100],
{ pdn_name                        }  [  101,     31,    131]],

      x25_interactive: [STATIC, READ, oss$job_paged_literal] x25_interactive_record := [
{                                      index |  size | last_character
{ device_name                     }  [   79,     31,    109],
{ line_speed                      }  [  113,      2,    114],
{ trunk_name                      }  [  115,     31,    145],
{ trunk_subtype                   }  [  146,     31,    176],
{ pdn_name                        }  [  146,     31,    176],
{ di_system_name                  }  [  211,     31,    241]];

?? FMT (FORMAT := ON) ??
?? EJECT ??

    VAR
      accounting_id: accounting_identifier,
      caller_id: accounting_caller_id,
      index: nat$accounting_data_kind,
      int: integer,
      local_status: ost$status,
      peer_accounting_info_length: nat$data_length,
      user_data_version: user_data_record_version,
      version: accounting_record_version;

    status.normal := TRUE;
    local_status.normal := TRUE;

    IF peer_accounting_information <> NIL THEN
      peer_accounting_info_length := STRLENGTH (peer_accounting_information^);
    ELSE
      peer_accounting_info_length := 0;
    IFEND;

{ Get the accounting version, caller id and accounting id from the accounting header.

    IF accounting_header.accounting_id.last_character <= peer_accounting_info_length THEN
      #UNCHECKED_CONVERSION (peer_accounting_information^ (accounting_header.version.index,
            accounting_header.version.size), version);

      #UNCHECKED_CONVERSION (peer_accounting_information^ (accounting_header.caller_id.index,
            accounting_header.caller_id.size), caller_id);

      #UNCHECKED_CONVERSION (peer_accounting_information^ (accounting_header.accounting_id.index,
            accounting_header.accounting_id.size), accounting_id);

    ELSE
      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        accounting_data_fields^ [index].kind := nac$ca_unavailable_information;
      FOREND;
      RETURN;

    IFEND;

{ IF the accounting version is not the current version, return.

    IF version <> current_accounting_version THEN
      osp$set_status_abnormal (nac$status_id, nae$acct_version_mismatch, 'Accounting Record', status);
      osp$append_status_integer (osc$status_parameter_delimiter, version, 10, FALSE, status);
      osp$append_status_integer (osc$status_parameter_delimiter, current_accounting_version, 10, FALSE,
            status);
      RETURN;
    IFEND;

    IF ((caller_id = ci_cdcnet_interactive_tip) OR (caller_id = ci_cdcnet_paired_connection)) AND
          (accounting_id = ai_non_x25_device) THEN

{ Set the value of each element in accounting_data_fields for a non x.25 interactive TIP.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        CASE accounting_data_fields^ [index].kind OF
        = nac$ca_device_name =
          set_string_data_field (peer_accounting_information^, non_x25_interactive.device_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].device_name);

        = nac$ca_line_speed =
          set_integer_data_field (peer_accounting_information^, non_x25_interactive.line_speed,
                accounting_data_fields^ [index].kind, int);
          accounting_data_fields^ [index].line_speed := int;

        = nac$ca_line_name =
          set_string_data_field (peer_accounting_information^, non_x25_interactive.line_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].line_name);

        = nac$ca_line_subtype =
          set_string_data_field (peer_accounting_information^, non_x25_interactive.line_subtype,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].line_subtype);

        = nac$ca_di_system_name =
          set_string_data_field (peer_accounting_information^, non_x25_interactive.di_system_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].di_system_name);

        = nac$ca_null_information =
          ;

        ELSE
          accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

        CASEND;
      FOREND;

    ELSEIF ((caller_id = ci_cdcnet_interactive_tip) OR (caller_id = ci_cdcnet_paired_connection)) AND
          (accounting_id = ai_x25_device) THEN

{ Set the value of each element in accounting_data_fields for an x.25 interactive TIP.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        CASE accounting_data_fields^ [index].kind OF
        = nac$ca_device_name =
          set_string_data_field (peer_accounting_information^, x25_interactive.device_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].device_name);

        = nac$ca_line_speed =
          set_integer_data_field (peer_accounting_information^, x25_interactive.line_speed,
                accounting_data_fields^ [index].kind, int);
          accounting_data_fields^ [index].line_speed := int;

        = nac$ca_trunk_name =
          set_string_data_field (peer_accounting_information^, x25_interactive.trunk_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].trunk_name);

        = nac$ca_trunk_subtype =
          set_string_data_field (peer_accounting_information^, x25_interactive.trunk_subtype,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].trunk_subtype);

        = nac$ca_pdn_name =
          set_string_data_field (peer_accounting_information^, x25_interactive.pdn_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].pdn_name);

        = nac$ca_di_system_name =
          set_x25_interact_di_system_name (peer_accounting_information^, x25_interactive.di_system_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].di_system_name);

        = nac$ca_null_information =
          ;

        ELSE
          accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

        CASEND;
      FOREND;

    ELSEIF ((caller_id = ci_cdcnet_interactive_tip) OR (caller_id = ci_cdcnet_paired_connection)) AND
          (accounting_id = ai_telnet_device) THEN

{ Set the value of each element in accounting_data_fields for a TELNET interactive TIP.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        CASE accounting_data_fields^ [index].kind OF
        = nac$ca_device_name =
          set_string_data_field (peer_accounting_information^, telnet_interactive.device_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].device_name);

        = nac$ca_di_system_name =
          set_string_data_field (peer_accounting_information^, telnet_interactive.di_system_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].di_system_name);

        = nac$ca_null_information =
          ;

        ELSE
          accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

        CASEND;
      FOREND;

    ELSEIF (caller_id = ci_cdcnet_batch_tip) AND (accounting_id = ai_non_x25_device) THEN

{ Set the value of each element in accounting_data_fields for a non x.25 batch TIP.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        CASE accounting_data_fields^ [index].kind OF
        = nac$ca_device_name =
          set_string_data_field (peer_accounting_information^, non_x25_batch.device_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].device_name);

        = nac$ca_line_speed =
          set_integer_data_field (peer_accounting_information^, non_x25_batch.line_speed,
                accounting_data_fields^ [index].kind, int);
          accounting_data_fields^ [index].line_speed := int;

        = nac$ca_line_name =
          set_string_data_field (peer_accounting_information^, non_x25_batch.line_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].line_name);

        = nac$ca_line_subtype =
          set_string_data_field (peer_accounting_information^, non_x25_batch.line_subtype,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].line_subtype);

        = nac$ca_di_system_name =
          set_string_data_field (peer_accounting_information^, non_x25_batch.di_system_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].di_system_name);

        = nac$ca_i_o_station_name =
          IF peer_connect_data <> NIL THEN
            #UNCHECKED_CONVERSION (peer_connect_data^ (non_x25_batch_user_data.version.index,
                  non_x25_batch_user_data.version.size), user_data_version);
            IF user_data_version = batch_user_data_version THEN
              set_string_data_field (peer_connect_data^, non_x25_batch_user_data.i_o_station_name,
                    accounting_data_fields^ [index].kind, accounting_data_fields^ [index].i_o_station_name);

            ELSE
              osp$set_status_abnormal (nac$status_id, nae$acct_version_mismatch, 'User Data Record', status);
              osp$append_status_integer (osc$status_parameter_delimiter, user_data_version, 10, FALSE,
                    status);
              osp$append_status_integer (osc$status_parameter_delimiter, batch_user_data_version, 10, FALSE,
                    status);
              RETURN;

            IFEND;

          ELSE
            accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

          IFEND;

        = nac$ca_null_information =
          ;

        ELSE
          accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

        CASEND;
      FOREND;

    ELSEIF (caller_id = ci_cdcnet_aa_gateway) AND (accounting_id = ai_x25_aa) THEN

{ Set values for each element of accounting_data_fields for an x.25 A to A gateway.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        CASE accounting_data_fields^ [index].kind OF
        = nac$ca_pdn_name =
          set_string_data_field (peer_accounting_information^, x25_aa_gateway.pdn_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].pdn_name);

        = nac$ca_trunk_name =
          set_string_data_field (peer_accounting_information^, x25_aa_gateway.trunk_name,
                accounting_data_fields^ [index].kind, accounting_data_fields^ [index].trunk_name);

        = nac$ca_null_information =
          ;

        ELSE
          accounting_data_fields^ [index].kind := nac$ca_unavailable_information;

        CASEND;
      FOREND;

    ELSE

{ Set value for each element of accounting_data_fields to nac$ca_unavailable_information.

      FOR index := 1 TO UPPERBOUND (accounting_data_fields^) DO
        accounting_data_fields^ [index].kind := nac$ca_unavailable_information;
      FOREND;
    IFEND;

  PROCEND nap$parse_accounting_data;
?? OLDTITLE ??
?? NEWTITLE := 'set_integer_data_field', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to retrieve a field in a substring.
{
{ DESIGN:
{   If the string passed in contains the field being requested, convert the
{   string field to an integer.  Otherwise set the kind parameter to
{   nac$ca_unavailable_information.

  PROCEDURE set_integer_data_field
    (    data_string: string ( * );
         map: element_field;
     VAR kind: nat$accounting_data_kind;
     VAR data_field: integer);

    VAR
      one_byte: 0 .. 0ff(16),
      two_bytes: 0 .. 0ffff(16),
      three_bytes: 0 .. 0ffffff(16),
      four_bytes: 0 .. 0ffffffff(16),
      five_bytes: 0 .. 0ffffffffff(16),
      six_bytes: 0 .. 0ffffffffffff(16),
      seven_bytes: 0 .. 0ffffffffffffff(16),
      eight_bytes: integer;

    IF map.last_character <= #SIZE (data_string) THEN
      CASE map.size OF
      = 1 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), one_byte);
        data_field := one_byte;
      = 2 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), two_bytes);
        data_field := two_bytes;
      = 3 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), three_bytes);
        data_field := three_bytes;
      = 4 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), four_bytes);
        data_field := four_bytes;
      = 5 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), five_bytes);
        data_field := five_bytes;
      = 6 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), six_bytes);
        data_field := six_bytes;
      = 7 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), seven_bytes);
        data_field := seven_bytes;
      = 8 =
        #UNCHECKED_CONVERSION (data_string (map.index, map.size), eight_bytes);
        data_field := eight_bytes;
      ELSE
        ;
      CASEND;

    ELSE
      kind := nac$ca_unavailable_information;
    IFEND;
  PROCEND set_integer_data_field;
?? OLDTITLE ??
?? NEWTITLE := 'set_string_data_field', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to retrieve a field in a substring.
{
{ DESIGN:
{   If the string passed in contains the field being requested, set the
{   data_field parameter to that value.  Otherwise set the kind parameter
{   to nac$ca_unavailable_information.

  PROCEDURE set_string_data_field
    (    data_string: string ( * );
         map: element_field;
     VAR kind: nat$accounting_data_kind;
     VAR data_field: string ( * ));

    IF map.last_character <= #SIZE (data_string) THEN
      data_field := data_string (map.index, map.size);
    ELSE
      kind := nac$ca_unavailable_information;
    IFEND;
  PROCEND set_string_data_field;
?? OLDTITLE ??
?? NEWTITLE := 'set_x25_interact_di_system_name', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to retrieve a field in a substring.
{
{ DESIGN:
{   If the string passed in contains the field being requested, set the
{   data_field parameter to that value.  Otherwise set the kind parameter
{   to nac$ca_unavailable_information.  The exception is if the string
{   length is 231.  The value 231 was the old value for the di system name
{   which was incorrect.  To remain compatible we need to special case 231.
{   When level 1.5.1 is nolonger supported this procedure can be deleted.

  PROCEDURE set_x25_interact_di_system_name
    (    data_string: string ( * );
         map: element_field;
     VAR kind: nat$accounting_data_kind;
     VAR data_field: string ( * ));

    IF map.last_character <= #SIZE (data_string) THEN
      data_field := data_string (map.index, map.size);
    ELSEIF 231 <= #SIZE (data_string) THEN
      data_field := data_string (map.index, 21);
    ELSE
      kind := nac$ca_unavailable_information;
    IFEND;
  PROCEND set_x25_interact_di_system_name;
?? OLDTITLE ??
MODEND nam$parse_accounting_data
