/************************************************************************ ** * ** COPYRIGHT (c) DIGITAL EQUIPMENT CORPORATION, 1993 * ** ALL RIGHTS RESERVED. UNPUBLISHED - RIGHTS RESERVED * ** UNDER THE COPYRIGHT LAWS OF THE UNITED STATES. * ** * ** RESTRICTED RIGHTS LEGEND: USE, DUPLICATION, OR DISCLOSURE * ** BY THE U.S. GOVERNMENT IS SUBJECT TO RESTRICTIONS AS SET * ** FORTH IN SUBPARAGRAPH (C)(1)(II) OF DFARS 252.227-7013, * ** OR IN FAR 52.227-19, OR IN FAR 52.227-14 ALT. III, AS * ** APPLICABLE. * ** * ** THIS SOFTWARE IS PROPRIETARY TO AND EMBODIES CONFIDENTIAL * ** TECHNOLOGY OF DIGITAL. POSSESSION, USE, OR COPYING OF THE * ** SOFTWARE AND MEDIA IS AUTHORIZED ONLY PURSUANT TO A VALID * ** WRITTEN LICENSE FROM DIGITAL. * ** * ************************************************************************* **++ ** FACILITY: ** ** X.25 Accounting Example Program ** ** ABSTRACT: ** ** This program is an example of a charging program to ** analyse X.25 accounting records ** ** ** FUNCTIONAL DESCRIPTION ** ** This program opens the input and output files and prints the ** headers. It then loops through each record printing the fields ** and calculating the cost. At the end, the total line is printed. ** ** ** Digital is furnishing this example software "as is" without ** warranty of any kind, express or implied, including the implied ** warranties of merchantability and fitness for a particular purpose. ** Digital disclaims any and all liability for the performance or ** non-performance of this software. **-- */ /* ** ** INCLUDE FILES ** */ #include #include #include #include #include #include #include #include #include #include /* ** ** MACRO DEFINITIONS ** */ #define TRUE 1 #define FALSE 0 #define acc_rec_len 512 #define out_rec_len 132 #define ten_million 10000000 #define check_status(x) if (!((x) & 1)) sys$exit(x) /* Error Handler */ /* ** Note: To maintain simplicity within this example program, double ** precision floating point numbers have been used to represent 64 bit ** numbers. The user should be aware that the use of this type guarantees ** values are precise to only 15 decimal digits. Should the event occur ** that this program is dealing with numbers of a magnitude likely to cause ** rounding errors, a warning will be given . */ #define rounding_error_check(x) if ( x >= 1.0e16) printf("overflow warning") /* ** The current accounting version constant will be used once it is known */ #ifndef ACR$K_VERSION4 #define ACR$K_VERSION4 (ACR$K_VERSION3+1) #endif void process_acc_rec( char *ptr_to_acc_rec); void print_totals( void); double quad_word_to_double( int *ptr_to_quad_word); void double_to_quad_word( int *ptr_to_quad_word, double *ptr_to_double); static int chargeable; static FILE * out_file; static FILE * acc_file; /* ** These locations define the chargeable cost (in arbitrary units) of use of ** an X.25 network. They are pairs of floating numbers. The first number in ** the pair relates to successful calls, the second to unsuccessful calls. ** ** Note that there could be different costs for different networks and there ** could be different rates depending on other parameters (for example, ** window size). However, this program would have to be modified to deal ** with these. */ struct costing_info { float successful; float unsuccessful; }; typedef struct costing_info COST_INFO ; static COST_INFO byte_cost = { 0, 0 }; static COST_INFO segment_cost = { 0, 0 }; static COST_INFO packet_cost = { 0, 0 }; static COST_INFO message_cost = { 0, 0 }; static COST_INFO second_cost = { 0, 0 }; static COST_INFO byte_minimum = { 0, 0 }; static COST_INFO segment_minimum = { 0, 0 }; static COST_INFO packet_minimum = { 0, 0 }; static COST_INFO message_minimum = { 0, 0 }; /* ** Totals area ** ** In this area a running total is kept of the call statistics so that a ** total line can be written to the output file when the end of the input ** file is reached */ typedef struct stats { double not_chargeable; double chargeable; double total; }; typedef struct stats STATS; static STATS tot_bytes = { 0, 0, 0 }; static STATS tot_segments = { 0, 0, 0 }; static STATS tot_packets = { 0, 0, 0 }; static STATS tot_messages = { 0, 0, 0 }; static STATS tot_calls = { 0, 0, 0 }; static double tot_time = 0; static double tot_cost = 0; main() { int status; char *byte_ptr; char buf[acc_rec_len]; /* ** Initialise and open input and output files ** The file is opened by using the optional argument ** to the open system call that allows us to specify ** RMS attributes. The attribute used here is the ** RMS record read-ahead */ acc_file = fopen("psiaccounting.dat", "r"); if (acc_file == NULL) { perror("error opening psiaccounting.dat"); exit(EXIT_FAILURE); } out_file = fopen("psiaccounting.lis", "w"); if (out_file == NULL) { perror("error creating psiaccounting.lis"); exit(EXIT_FAILURE); } /* ** Print heading */ fprintf(out_file, " ELAPSED < - - - CHARGEABLE - - - - > "); fprintf(out_file, "< - - NON-CHARGEABLE - - > < - - - - - TOTAL - - - - ->\n"); fprintf(out_file, "USERNAME ACCOUNT TIME BYTES SEGS. PACKS. MESS. "); fprintf(out_file, " BYTES SEGS. PACKS. MESS. BYTES SEGS. PACKS. MESS. "); fprintf(out_file, "COST\n"); /* ** Print separator line */ fprintf( out_file, "---------------------------------"); fprintf( out_file, "---------------------------------"); fprintf( out_file, "--------------------------------"); fprintf( out_file, "--------------------------------\n"); /* ** Read a record from input file */ while (TRUE) { byte_ptr = &buf[0]; if(fgets(byte_ptr,acc_rec_len, acc_file) != NULL) { /* ** While not end of file process accounting record */ process_acc_rec(byte_ptr); } else { /* ** Print total line and close input and output files */ print_totals(); fclose(acc_file); fclose(out_file); exit(EXIT_SUCCESS); }; }; /* end while TRUE */ } /* end main */ void process_acc_rec(char *ptr_to_acc_rec) /* **++ ** Description ** The accounting record pointed to is processed, so that if the record ** is a valid X.25 accounting record the relevant statistics of the call ** are extracted from the record and the cost of the call is calculated ** if the call is chargeable. Finally, information about the call is ** written to the output file. **-- */ { /* ** Work area ** ** This area contains the statistics about a particular call */ static STATS bytes = { 0, 0, 0 }; static STATS segments = { 0, 0, 0 }; static STATS packets = { 0, 0, 0 }; static STATS messages = { 0, 0, 0 }; static double cost = 0; static unsigned int quad_time[2] = { 0, 0 }; /* Quad word representation of elapsed time */ static double time = 0; /* 64 bit representation of elapsed time */ static unsigned int seconds = 0; /* elapsed time in seconds */ static double start_time = 0; static double stop_time = 0; static unsigned int time_string_length; static char time_string[9]; static unsigned int time_string_desc[2] = {8,(unsigned int)&time_string[0] }; static char username[12]; static char account[8]; static int no_of_char; /* ** Set up pointers for the various packet formats used within the ** accounting record. An accounting record consists of a number ** of packets (not X.25 packets). */ /* ** This packet is found at the start of a record. */ static union record_union { struct acrdef hdr; struct acrdef1 rec_hdr; } *record_ptr; /* ** This packet is found somewhere in the accounting record and contains ** the actual accounting data about the virtual circuit termination. */ static union packet_union { struct acrdef hdr; struct psi$_acr_vct packet; } *packet_ptr; /* ** This packet is usually found straight after the record header packet. ** It describes the process that caused the virtual circuit termination ** to occur. */ static union id_packet_union { struct acrdef hdr; struct acrdef2 id_packet; } *id_packet_ptr; static char *byte_ptr, *string_ptr; static int status; static unsigned int length; /* ** Point to start of record */ record_ptr = (union record_union*)ptr_to_acc_rec; /* ** Check it is a valid X.25 accounting record and the version is correct */ if ( record_ptr->hdr.acr$v_type != acr$k_psi) return; if ( ( record_ptr->hdr.acr$v_version < ACR$K_VERSION3) || ( record_ptr->hdr.acr$v_version > ACR$K_VERSION4) ) return; /* ** Check this is a virtual circuit termination record */ if ( record_ptr->hdr.acr$v_subtype != acr$k_psi_vct) return; /* ** Keep moving to start of next packet in the record until end of record ** is reached or the virtual circuit termination packet is reached. When ** the id packet is reached its address is saved so that further ** information about the user can be obtained. */ /* ** Move to the start of the first packet */ packet_ptr = (union packet_union *)(ptr_to_acc_rec + ACR$K_HDRLEN); byte_ptr = (char*)packet_ptr; while ((packet_ptr != (union packet_union*)(record_ptr + record_ptr->hdr.acr$w_length)) && ((packet_ptr->hdr.acr$v_type) != acr$k_psi)) { if (packet_ptr->hdr.acr$v_type == ACR$K_ID ) id_packet_ptr = (union id_packet_union*)byte_ptr; /* Save address of ID packet */ byte_ptr = byte_ptr + (packet_ptr->hdr.acr$w_length); packet_ptr = (union packet_union*)byte_ptr; }; /* ** Zero the working area */ bytes.chargeable = bytes.not_chargeable = bytes.total = 0; segments.chargeable = segments.not_chargeable = segments.total = 0; packets.chargeable = packets.not_chargeable = packets.total = 0; messages.chargeable = messages.not_chargeable = messages.total = 0; time = 0; seconds = 0; cost = 0; /* ** Decide whether call is chargeable ** ** The algorithm used here is that a call is chargeable if it is an ** outgoing SVC without reverse charging or an incoming SVC with reverse ** charging. PVCs are not treated as chargeable (in this example, there ** is a fixed charge for a PVC and all data is free). */ /* ** If circuit type is PVC call is not chargeable */ if ( packet_ptr->packet.acr$w_psi_vctype.acr$v_psi_pvc ) chargeable = FALSE; /* ** Otherwise virtual circuit type is SVC so determine whether call is ** incoming or outgoing and whether reverse-charging facility is used */ else if ((packet_ptr->packet.acr$w_psi_vctype.acr$v_psi_out && !packet_ptr->packet.acr$w_psi_fac_req.acr$v_psi_revchg) || (!packet_ptr->packet.acr$w_psi_vctype.acr$v_psi_out && packet_ptr->packet.acr$w_psi_fac_req.acr$v_psi_revchg)) chargeable = TRUE; else chargeable = FALSE; /* ** Work out cost ** ** The algorithm used here assumes that there is a duration element and a ** data volume element which has a minimum which depends on whether the ** call is successful. To simplify matters, this example assumes the rate ** does not vary with the time of day. ** ** If necessary, that information could be obtained from the ** ACR$Q_PSI_START_TIME field. ** */ /* ** Duration Element */ /* ** If the call failed the start time will be zero */ start_time = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_start_time)); if (start_time) { stop_time = quad_word_to_double((int*)&(record_ptr->rec_hdr.acr$q_systime)); time = stop_time - start_time; seconds = time/ten_million; if (time - (seconds * ten_million)) /* Round up time in seconds */ seconds++; }; /* ** Volume Element */ if (chargeable) { bytes.chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_bytes_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_bytes_rx)); segments.chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_segments_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_segments_rx)); messages.chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_messages_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_messages_rx)); packets.chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_packets_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_packets_rx)); } else { bytes.not_chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_bytes_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_bytes_rx)); segments.not_chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_segments_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_segments_rx)); messages.not_chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_messages_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_messages_rx)); packets.not_chargeable = quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_packets_tx)) + quad_word_to_double( (int*)&(packet_ptr->packet.acr$q_psi_packets_rx)); }; /* end if chargeable */ bytes.total = bytes.chargeable + bytes.not_chargeable; segments.total = segments.not_chargeable + segments.chargeable; messages.total = messages.not_chargeable + messages.chargeable; packets.total = packets.not_chargeable + packets.chargeable; /* ** Check for rounding errors */ rounding_error_check( bytes.total); rounding_error_check( segments.total); rounding_error_check( messages.total); rounding_error_check( packets.total); /* ** If the call was chargeable test to see if the call was successful ** and cost bytes, segments, packets and messages accordingly */ if (chargeable) { if ( packet_ptr->packet.acr$w_psi_vctype.acr$v_psi_fail ) { if (bytes.chargeable <= byte_minimum.unsuccessful) cost = byte_cost.unsuccessful * byte_minimum.unsuccessful; else cost = byte_cost.unsuccessful * bytes.chargeable; if (segments.chargeable <= segment_minimum.unsuccessful) cost += segment_cost.unsuccessful * segment_minimum.unsuccessful; else cost += segment_cost.unsuccessful * segments.chargeable; if (messages.chargeable <= message_minimum.unsuccessful) cost += message_cost.unsuccessful * message_minimum.unsuccessful; else cost += message_cost.unsuccessful * messages.chargeable; if (packets.chargeable <= packet_minimum.unsuccessful) cost += packet_cost.unsuccessful * packet_minimum.unsuccessful; else cost += packet_cost.unsuccessful * packets.chargeable; } else { if (bytes.chargeable <= byte_minimum.successful) cost = byte_cost.successful * byte_minimum.successful; else cost = byte_cost.successful * bytes.chargeable; if (segments.chargeable <= segment_minimum.successful) cost += segment_cost.successful * segment_minimum.successful; else cost += segment_cost.successful * segments.chargeable; if (messages.chargeable <= message_minimum.successful) cost += message_cost.successful * message_minimum.successful; else cost += message_cost.successful * messages.chargeable; if (packets.chargeable <= packet_minimum.successful) cost += packet_cost.successful * packet_minimum.successful; else cost += packet_cost.successful * packets.chargeable; }; /* end if call failed */ }; /* end if chargeable */ /* ** Round up cost */ cost = (ceil(cost * 100))/100; /* ** Convert the account and username counted strings to nul-terminated ** strings */ byte_ptr = (char *)id_packet_ptr; byte_ptr += id_packet_ptr->id_packet.acr$w_account; no_of_char = *byte_ptr; byte_ptr++; string_ptr = (char*)&account; for ( ; no_of_char >= 0; no_of_char--, byte_ptr++, string_ptr++) strncpy( string_ptr, byte_ptr, 1); *string_ptr = 0; byte_ptr = (char*)id_packet_ptr; byte_ptr += id_packet_ptr->id_packet.acr$w_username; no_of_char = *byte_ptr; byte_ptr++; string_ptr = (char*)&username; for ( ; no_of_char >= 0; no_of_char--, byte_ptr++, string_ptr++) strncpy( string_ptr, byte_ptr, 1); *string_ptr = 0; /* ** Convert binary time to an ascii string */ byte_ptr = (char*)&time_string; /* point to start of ascii time string */ double_to_quad_word( (int*)&quad_time, (double *)&time); status = sys$asctim( &time_string_length, &time_string_desc, &quad_time, 1); check_status( status); byte_ptr += time_string_length; /* point to end of time string */ *byte_ptr = 0; /* zero terminate time string */ /* ** Output the record */ fprintf( out_file, "%-12s %-8s %-8s", &username, &account, &time_string ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", bytes.chargeable, segments.chargeable, packets.chargeable, messages.chargeable ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", bytes.not_chargeable, segments.not_chargeable, packets.not_chargeable, messages.not_chargeable ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", bytes.total, segments.total, packets.total, messages.total ); fprintf( out_file, " %7.2f\n", cost); /* ** Increment the totals */ tot_calls.total++; if (chargeable) tot_calls.chargeable++; else tot_calls.not_chargeable++; tot_bytes.chargeable += bytes.chargeable; tot_bytes.not_chargeable += bytes.not_chargeable; tot_segments.chargeable += segments.chargeable; tot_segments.not_chargeable += segments.not_chargeable; tot_packets.chargeable += packets.chargeable; tot_packets.not_chargeable += packets.not_chargeable; tot_messages.chargeable += messages.chargeable; tot_messages.not_chargeable += messages.not_chargeable; tot_time += time; tot_cost += cost; } /* end process_acc_rec */ void print_totals( void) /* **++ ** Description ** The total line is formatted and printed **-- */ { static char *byte_ptr; static int tot_quad_time[2]; static char tot_time_string[9]; static int tot_time_string_desc[2] = { 8, (int)&tot_time_string}; static int tot_time_string_length; static int status; /* ** Print separator line */ fprintf( out_file, "---------------------------------"); fprintf( out_file, "---------------------------------"); fprintf( out_file, "--------------------------------"); fprintf( out_file, "--------------------------------\n"); /* ** Calculate overall totals */ tot_bytes.total = tot_bytes.chargeable + tot_bytes.not_chargeable; tot_segments.total = tot_segments.chargeable + tot_segments.not_chargeable; tot_packets.total = tot_packets.chargeable + tot_packets.not_chargeable; tot_messages.total = tot_messages.chargeable + tot_messages.not_chargeable; /* ** Check for rounding errors */ rounding_error_check( tot_bytes.total); rounding_error_check( tot_segments.total); rounding_error_check( tot_messages.total); rounding_error_check( tot_packets.total); /* ** Convert binary time to an ascii string */ byte_ptr = (char*)&tot_time_string; double_to_quad_word( (int*)&tot_quad_time, &tot_time); status = sys$asctim( &tot_time_string_length, &tot_time_string_desc, &tot_time, 1); check_status( status); byte_ptr += tot_time_string_length; *byte_ptr = 0; /* ** Print the totals */ fprintf( out_file, "TOTAL %4.0f (%4.0f) %-8s", tot_calls.total, tot_calls.chargeable ,&tot_time_string ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", tot_bytes.chargeable, tot_segments.chargeable, tot_packets.chargeable, tot_messages.chargeable ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", tot_bytes.not_chargeable, tot_segments.not_chargeable, tot_packets.not_chargeable, tot_messages.not_chargeable ); fprintf( out_file, " %8.0f %6.0f %6.0f %6.0f", tot_bytes.total, tot_segments.total, tot_packets.total, tot_messages.total ); fprintf( out_file, " %7.2f\n", tot_cost); } /* end print_totals */ double quad_word_to_double( int *ptr_to_quad_word ) /* **++ ** Description ** Converts a quad word value to a double to simplify arithmetic **-- */ { static double double_result; double_result = *ptr_to_quad_word ; ptr_to_quad_word++; double_result += ( pow( 2, 32) *( *ptr_to_quad_word)); return double_result; } void double_to_quad_word( int *ptr_to_quad_word, double *ptr_to_double) /* **++ ** Description ** Converts a double to a quad word **-- */ { static int quad_word[2]; quad_word[1] = (*ptr_to_double) / (pow( 2, 32)); quad_word[0] = (*ptr_to_double) - (quad_word[1] * pow ( 2, 32)); *ptr_to_quad_word = quad_word[0]; ptr_to_quad_word++; *ptr_to_quad_word = quad_word[1]; }