2021-05-24 20:44:55 +00:00
|
|
|
/*
|
|
|
|
Organization: Technical University of Kosice (TUKE),
|
|
|
|
Department: Department of Electronics and Multimedia Telecommunications (DEMT/KEMT),
|
|
|
|
Faculties: Faculty of Electrical Engineering and Informatics (FEI),
|
|
|
|
Feld of study: Informatics,
|
|
|
|
Study program: Computer Networks,
|
|
|
|
School year: 3., Bachelor study, 2020/2021,
|
|
|
|
Author: Marek Rohac -- MR,
|
|
|
|
Compiler: Winlibs GCC -- MinGW-W64 x86_64-posix-seh, built by Brecht Sanders, v. 10.2.0,
|
|
|
|
-- also works with GCC 11.1.0
|
|
|
|
compile: gcc winAPIprng.c -o rng -Wall -Wextra -Werror -g -std=c11 -lbcrypt,
|
|
|
|
version: 1.2 , 20.05.2021.
|
|
|
|
Description:
|
|
|
|
This program is only for educational purposes.
|
|
|
|
Program use 3 different windows APIs to generate random data
|
|
|
|
cryptGenRandom, BCryptGenRandom and RtlGenRandom
|
2021-05-27 22:53:37 +00:00
|
|
|
PLUS rand_s API for PRNG
|
2021-05-24 20:44:55 +00:00
|
|
|
Usage: In macro RNG_PER_CYCLE_BITS define how much random bits you want by one cycle.
|
|
|
|
Then define number of cycles in macro RNG_CYCLES
|
|
|
|
Program then generate output according APIs and save values to .bin files with name by current API
|
|
|
|
*/
|
|
|
|
#define __USE_MINGW_ANSI_STDIO
|
|
|
|
#include <stdio.h>
|
2021-05-27 22:53:37 +00:00
|
|
|
#define _CRT_RAND_S
|
|
|
|
#include <stdlib.h>
|
2021-05-24 20:44:55 +00:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <ntsecapi.h>
|
|
|
|
#include <ntstatus.h>
|
|
|
|
|
|
|
|
/*
|
2021-05-27 22:53:37 +00:00
|
|
|
ULONG_MAX = 4096 MB --> 4. meranie (not possible with rand_s)
|
2021-05-24 20:44:55 +00:00
|
|
|
32MB = 268435456b --> 3. meranie
|
|
|
|
16MB = 134217728b --> 2. meranie
|
|
|
|
16KB = 131072b --> 1. meranie
|
|
|
|
YOU CAN SET NUMBER OF BYTES IN RNG_BYTE_OUTPUT_SIZE
|
|
|
|
TO AVOID CALCULATING TO BITS
|
|
|
|
*/
|
|
|
|
|
|
|
|
// bit size per API call ..
|
2021-05-27 22:53:37 +00:00
|
|
|
#define RNG_PER_CYKLE_BITS 16*1024*1024*8
|
|
|
|
//number of calls --> 16gb now
|
2021-05-24 20:44:55 +00:00
|
|
|
#define RNG_CYCLES 1024
|
|
|
|
// byte size of output
|
2021-05-27 22:53:37 +00:00
|
|
|
#define RNG_BYTE_OUTPUT_SIZE RNG_PER_CYKLE_BITS/8
|
2021-05-24 20:44:55 +00:00
|
|
|
//uint size of output
|
2021-05-27 22:53:37 +00:00
|
|
|
#define RNG_UINT_OUTPUT_SIZE RNG_PER_CYKLE_BITS/32
|
2021-05-24 20:44:55 +00:00
|
|
|
//we work with big endian values, there's check macro -- NOT USE, BUT USEFULL
|
|
|
|
#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})
|
|
|
|
|
|
|
|
//32 GB output --> 268435456*1024/8/1024/1024/1024
|
|
|
|
//8b = 1 BYTE
|
|
|
|
//32b = 1 UINT
|
|
|
|
|
|
|
|
/*############################################################
|
|
|
|
FUNCTIONS FOR MEASURMENTS
|
|
|
|
##############################################################*/
|
|
|
|
//MACROS FOR EASIER TIMER MEASURMENT, VIA WINAPI QueryPerformanceCounter
|
|
|
|
#define TIMER_INIT \
|
|
|
|
LARGE_INTEGER frequency; \
|
|
|
|
LARGE_INTEGER t1,t2; \
|
|
|
|
double elapsedTime; \
|
|
|
|
QueryPerformanceFrequency(&frequency);
|
|
|
|
|
|
|
|
|
|
|
|
/** Use to start the performance timer */
|
|
|
|
#define TIMER_START QueryPerformanceCounter(&t1);
|
|
|
|
|
|
|
|
/** Use to stop the performance timer and output the result to the standard stream. Less verbose than \c TIMER_STOP_VERBOSE */
|
|
|
|
#define TIMER_STOP \
|
|
|
|
QueryPerformanceCounter(&t2); \
|
|
|
|
elapsedTime=(double)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart;
|
|
|
|
/*
|
|
|
|
divide frequency.QuadPart by 1000.0 if you want time in ms*/
|
|
|
|
/*
|
|
|
|
function to measure speed of generating process (output is number of number of cpu cycles)
|
|
|
|
CYCLES * AverageCycles* 1/ghzOfProcessor * 10^-9 = cca execution CPUTIME
|
|
|
|
Inspiration by --> https://github.com/newhopecrypto/newhope/tree/master/ref
|
|
|
|
- MR: asm changed to __asm__ for newer c standard support (before we need -std=gnu99 to compile)
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
int64_t cpucycles(void)
|
|
|
|
{
|
|
|
|
uint64_t result;
|
|
|
|
//function use rdts instruction and shift higher values from reg where rdtsc store values
|
|
|
|
//(rax, rdx)
|
|
|
|
__asm__ volatile(".byte 15;.byte 49;shlq $32,%%rdx;orq %%rdx,%%rax"
|
|
|
|
: "=a" (result) :: "%rdx");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
=================================================================================
|
|
|
|
REPLACED BY CPUID/RDTSC/RDTSCP INSTRUCTIONS -->below-->cpucyclesS(),cpucyclesE()
|
|
|
|
***FOR BETTER PRECISIOUS***
|
|
|
|
=================================================================================
|
|
|
|
https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf
|
|
|
|
CYCLES * AverageCycles* 1/ghzOfProcessor * 10^-9 = cca execution CPUTIME
|
|
|
|
*/
|
|
|
|
//cpucyclesS -- start measurment
|
|
|
|
static __inline__ uint64_t cpucyclesS(){
|
|
|
|
unsigned cycles_low, cycles_high;
|
|
|
|
__asm__ volatile ("CPUID\n\t"
|
|
|
|
"RDTSC\n\t"
|
|
|
|
"mov %%edx, %0\n\t"
|
|
|
|
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low)::
|
|
|
|
"%rax", "%rbx", "%rcx", "%rdx");
|
|
|
|
return (((uint64_t)cycles_high << 32) | cycles_low );
|
|
|
|
}
|
|
|
|
//cpucyclesE -- end measurment
|
|
|
|
static __inline__ uint64_t cpucyclesE(){
|
|
|
|
unsigned cycles_low, cycles_high;
|
|
|
|
__asm__ volatile ("RDTSCP\n\t"
|
|
|
|
"mov %%edx, %0\n\t"
|
|
|
|
"mov %%eax, %1\n\t"
|
|
|
|
"CPUID\n\t": "=r" (cycles_high), "=r" (cycles_low)::
|
|
|
|
"%rax", "%rbx", "%rcx", "%rdx");
|
|
|
|
return (((uint64_t)cycles_high << 32) | cycles_low );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
compare for qsort
|
|
|
|
*/
|
|
|
|
static INT cmp_llu(const void *a, const void*b)
|
|
|
|
{
|
|
|
|
if(*(uint64_t *)a < *(uint64_t *)b) return -1;
|
|
|
|
if(*(uint64_t *)a > *(uint64_t *)b) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
calculating of median value from measurment
|
|
|
|
*/
|
|
|
|
static uint64_t median(uint64_t *l, size_t llen)
|
|
|
|
{
|
|
|
|
if (llen<=1) {printf("Not enought values for median\n");
|
|
|
|
return l[llen-1];
|
|
|
|
}
|
|
|
|
qsort(l,llen,sizeof(uint64_t),cmp_llu);
|
|
|
|
if(llen%2) return l[llen/2];
|
|
|
|
else return (l[llen/2-1]+l[llen/2])/2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
calculating of average value from measurment
|
|
|
|
*/
|
|
|
|
static uint64_t average(uint64_t *t, size_t tlen)
|
|
|
|
{
|
|
|
|
uint64_t acc=0;
|
|
|
|
size_t i;
|
|
|
|
for(i=0;i<tlen;i++)acc += t[i];
|
|
|
|
if(acc!=0)return acc/(tlen);
|
|
|
|
return t[tlen];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*############################################################
|
|
|
|
END OF FUNCTIONS FOR MEASURMENTS
|
|
|
|
##############################################################*/
|
|
|
|
|
|
|
|
|
|
|
|
/*############################################################
|
|
|
|
AUXILIARY FUNCTIONS
|
|
|
|
##############################################################*/
|
|
|
|
|
|
|
|
void printBYTERandomArray(const char * name, const void * buffer){
|
|
|
|
BYTE storage[RNG_BYTE_OUTPUT_SIZE];
|
|
|
|
memcpy(storage, buffer,RNG_BYTE_OUTPUT_SIZE);
|
|
|
|
printf("%s sequence generated:\n ",name);
|
2021-05-27 22:53:37 +00:00
|
|
|
for( int i = 0; i < RNG_BYTE_OUTPUT_SIZE;i++ )
|
|
|
|
printf("%02X",storage[i]);
|
|
|
|
printf("\n");
|
2021-05-24 20:44:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void printUINTRandomArray(const char * name, const void * buffer){
|
|
|
|
uint32_t storage[RNG_UINT_OUTPUT_SIZE];
|
|
|
|
memcpy(storage,buffer,RNG_UINT_OUTPUT_SIZE);
|
|
|
|
printf("%s sequence generated:\n ",name);
|
2021-05-27 22:53:37 +00:00
|
|
|
for( int i = 0; i < RNG_UINT_OUTPUT_SIZE;i++ )
|
|
|
|
printf("%02X",storage[i]);
|
|
|
|
printf("\n");
|
2021-05-24 20:44:55 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
function for casting uint to correct BYTE representation independently on machines endian
|
|
|
|
AMD use little endian, we need work with big endian
|
|
|
|
USE CASE --> rand_s function on AMD processors
|
|
|
|
*/
|
|
|
|
void castUintToByte(uint32_t *uintArray,BYTE* output){
|
|
|
|
int i=0;
|
|
|
|
int n=4; //because uint have 32b and BYTE 8b -- 32/8 = 4
|
2021-05-27 22:53:37 +00:00
|
|
|
for (uint32_t j = 0; j < RNG_UINT_OUTPUT_SIZE; j++){
|
|
|
|
for(; n-->0; i++)
|
|
|
|
output[i]=(uintArray[j]>>(n*8))& 0xFF;
|
|
|
|
n=4;
|
2021-05-24 20:44:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//storing generated random data
|
|
|
|
void store_Data(FILE * fp, void * pbBuffer){
|
|
|
|
fwrite(pbBuffer,sizeof(BYTE),RNG_BYTE_OUTPUT_SIZE,fp);
|
|
|
|
if( feof(fp) ) {printf("Problem with %s file\n", (char*)fp);}
|
|
|
|
//else printf("File successfully create!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//storing time datas of executions
|
|
|
|
void store_Time_Data(const char * name, uint64_t *t, size_t tlen,FILE * fp, double programRun){
|
|
|
|
uint64_t sum;
|
|
|
|
fprintf(fp, "%s\n",name);
|
|
|
|
for(size_t i=0;i<tlen;i++){
|
2021-05-27 22:53:37 +00:00
|
|
|
sum+=t[i];
|
|
|
|
//fprintf(fp, "%4.lld%s %llu %s\n", i+1, ".Time: ", t[i]," cpucycles");
|
|
|
|
if( feof(fp) ) {printf("Problem with %s file\n", name);}
|
|
|
|
//else {printf("Time successfully write!\n");}
|
2021-05-24 20:44:55 +00:00
|
|
|
}
|
|
|
|
fprintf(fp,"TOTAL: %llu cycles\nMEDIAN: %llu cpucycles\nAVERAGE: %llu cpucycles\nEXECUTED in %f s\n",sum, median(t, tlen),average(t, tlen),programRun);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*############################################################
|
|
|
|
END OF AUXILIARY FUNCTIONS
|
|
|
|
##############################################################*/
|
|
|
|
|
|
|
|
/*############################################################
|
|
|
|
FUNCTIONS FOR GENERATING RANDOM VALUES VIA APIs
|
|
|
|
##############################################################*/
|
2021-05-27 22:53:37 +00:00
|
|
|
/*
|
|
|
|
and insert 32bit random value into
|
|
|
|
return errno_t value, on success equal 0
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
rand_s
|
|
|
|
this function for generating random bits needs unsigned int variable for store output
|
|
|
|
and insert 32bit random value into
|
|
|
|
return errno_t value, on success equal 0
|
|
|
|
*/
|
|
|
|
int randS(BYTE * output,FILE * timeFile){
|
|
|
|
FILE * storeFile=fopen("Rand_s.bin","ab+");
|
|
|
|
uint32_t* number=NULL; // uint32_t --> 32bits
|
|
|
|
number= (uint32_t*) malloc(sizeof(uint32_t) * RNG_UINT_OUTPUT_SIZE);
|
|
|
|
//memset(number,0,RNG_UINT_OUTPUT_SIZE*sizeof(uint32_t));
|
|
|
|
uint64_t time [RNG_CYCLES],tick;
|
|
|
|
TIMER_INIT
|
|
|
|
{TIMER_START
|
|
|
|
for(int i=0; i < RNG_CYCLES; i++ ){
|
|
|
|
for( uint32_t j = 0; j < RNG_UINT_OUTPUT_SIZE;j++ ){
|
|
|
|
tick = cpucyclesS();
|
|
|
|
int err = rand_s(&number[j]);
|
|
|
|
time[i] += cpucyclesE()-tick; //writing 32bits, so 4xBYTE, into variable number
|
|
|
|
if(err != 0){
|
|
|
|
printf("The rand_s function failed!\n");
|
|
|
|
fclose(storeFile);
|
|
|
|
free(number);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!IS_BIG_ENDIAN)castUintToByte(number,output); //correct casting from UINT to BYTE array if architecture use little endian
|
|
|
|
//printBYTERandomArray("randS",output); //optional and not recomended for bigger outputs or measurments
|
|
|
|
store_Data(storeFile,output);
|
|
|
|
}
|
|
|
|
TIMER_STOP}
|
|
|
|
store_Time_Data("RAND_S:",time,RNG_CYCLES,timeFile, elapsedTime);
|
|
|
|
fclose(storeFile);
|
|
|
|
free(number);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-05-24 20:44:55 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Function demonstrate usage of deprecated API for RNG on windows --CRYPTGENRANDOM
|
|
|
|
for more information visit:
|
|
|
|
https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
|
|
|
|
*/
|
|
|
|
int cryptgenrandom(BYTE * pbData, FILE * timeFile){
|
|
|
|
HCRYPTPROV hCryptProv;
|
|
|
|
int check=0;
|
|
|
|
FILE * storeFile=fopen("CryptGenRandom.bin","ab+");
|
|
|
|
uint64_t time[RNG_CYCLES],tick;
|
|
|
|
TIMER_INIT
|
2021-05-27 22:53:37 +00:00
|
|
|
{TIMER_START
|
2021-05-24 20:44:55 +00:00
|
|
|
check=CryptAcquireContext(&hCryptProv,NULL,"Microsoft Base Cryptographic Provider v1.0",PROV_RSA_FULL,CRYPT_VERIFYCONTEXT);
|
|
|
|
if (check){
|
|
|
|
check=0;
|
|
|
|
for(int i=0; i < RNG_CYCLES; i++ ){
|
|
|
|
tick = cpucyclesS();
|
|
|
|
check += CryptGenRandom(hCryptProv,RNG_BYTE_OUTPUT_SIZE,pbData);
|
|
|
|
time[i] = cpucyclesE() - tick;
|
|
|
|
//printBYTERandomArray("CGRand",pbData); //optional and not recomended for bigger outputs or measurments
|
|
|
|
store_Data(storeFile,pbData);
|
|
|
|
}
|
2021-05-27 22:53:37 +00:00
|
|
|
TIMER_STOP}
|
2021-05-24 20:44:55 +00:00
|
|
|
store_Time_Data("CRYPTGENRANDOM:",time,RNG_CYCLES,timeFile, elapsedTime);
|
|
|
|
fclose(storeFile);
|
|
|
|
if (check==RNG_CYCLES){
|
|
|
|
check=CryptReleaseContext(hCryptProv,0);
|
|
|
|
if (check){
|
|
|
|
return 0;
|
2021-05-27 22:53:37 +00:00
|
|
|
}
|
2021-05-24 20:44:55 +00:00
|
|
|
else{
|
|
|
|
printf("Error during CryptReleaseContext.\n");
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
printf("Error during CryptGenRandom.\n");
|
|
|
|
CryptReleaseContext(hCryptProv,0);
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
printf("Error during CryptAcquireContext!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
BCryptGenRandom function
|
|
|
|
1. paramater is algorithm handler hAlgorithm
|
|
|
|
The simplest way to initialize/use is insert NULL, then it will set handler on default provider
|
|
|
|
Microsoft Base...
|
|
|
|
Otherwise there are differnt option but first you will need to initialize that handler with function
|
|
|
|
BCryptOpenAlgorithmProvider, which create handler for rng API.
|
|
|
|
For more information read https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider
|
|
|
|
2. param is variable for store output
|
|
|
|
3. size of output in BYTES
|
|
|
|
4. param is flag. There are 2 options:
|
|
|
|
BCRYPT_RNG_USE_ENTROPY_IN_BUFFER (0x00000001)
|
|
|
|
function after flag above will use the number in the pbBuffer buffer as additional entropy for the random number.
|
|
|
|
If this flag is not specified, this function will use a random number for the entropy.
|
|
|
|
This flag is ignored in Windows 8 and later because of architecture changes in CNG working.
|
|
|
|
BCRYPT_USE_SYSTEM_PREFERRED_RNG (0x00000002)
|
|
|
|
Use the system-preferred random number generator algorithm. The hAlgorithm parameter must be NULL.
|
|
|
|
On Windows Vista this flag is not supported.
|
|
|
|
*/
|
|
|
|
void bcrypt(BYTE * pbBuffer, FILE * timeFile){
|
|
|
|
FILE * storeFile=fopen("BCryptGenRandom.bin","ab+");
|
|
|
|
uint64_t time[RNG_CYCLES],tick; //for better precisious
|
|
|
|
TIMER_INIT
|
|
|
|
{TIMER_START
|
|
|
|
|
|
|
|
for(int i=0; i < RNG_CYCLES; i++ ){
|
|
|
|
tick = cpucyclesS();
|
|
|
|
if (STATUS_SUCCESS!=BCryptGenRandom(NULL,pbBuffer,RNG_BYTE_OUTPUT_SIZE, BCRYPT_USE_SYSTEM_PREFERRED_RNG)){
|
|
|
|
printf("BCRYPTGENRANDOM ERROR in %d. cycle\n", i );
|
|
|
|
}
|
|
|
|
time[i] = cpucyclesE() - tick;
|
|
|
|
//printBYTERandomArray("bcrypt",pbBuffer); //optional and not recomended for bigger outputs or during measurment.
|
|
|
|
store_Data(storeFile,pbBuffer);
|
|
|
|
}
|
|
|
|
TIMER_STOP}
|
|
|
|
store_Time_Data("BCRYPTGENRANDOM:",time,RNG_CYCLES,timeFile, elapsedTime);
|
|
|
|
fclose(storeFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rtlgenrandom(BYTE * pbBuffer, FILE * timeFile){
|
|
|
|
FILE * storeFile=fopen("RtlGenRandom.bin","ab+");
|
|
|
|
uint64_t time[RNG_CYCLES],tick;
|
|
|
|
TIMER_INIT
|
|
|
|
{TIMER_START
|
|
|
|
for(int i=0; i < RNG_CYCLES; i++ ){
|
2021-05-27 22:53:37 +00:00
|
|
|
tick = cpucyclesS();
|
|
|
|
if(RtlGenRandom(pbBuffer, RNG_BYTE_OUTPUT_SIZE)==0) printf("rtl err -1");
|
|
|
|
time[i] = cpucyclesE()- tick;
|
|
|
|
//printBYTERandomArray("RtlGenRandom",pbBuffer); //optional and not recomended for bigger outputs or measurments
|
|
|
|
store_Data(storeFile, pbBuffer);
|
|
|
|
}
|
|
|
|
TIMER_STOP}
|
2021-05-24 20:44:55 +00:00
|
|
|
store_Time_Data("RTLGENRANDOM:",time,RNG_CYCLES,timeFile, elapsedTime);
|
|
|
|
fclose(storeFile);
|
|
|
|
}
|
|
|
|
/*############################################################
|
|
|
|
END OF FUNCTIONS FOR GENERATING RANDOM VALUES VIA APIs
|
|
|
|
##############################################################*/
|
|
|
|
|
|
|
|
int main(){
|
|
|
|
/*############# INTIAL SETUP ########################*/
|
|
|
|
BYTE * pbData= NULL;
|
|
|
|
pbData=(BYTE*) malloc(sizeof(BYTE) * RNG_BYTE_OUTPUT_SIZE);
|
|
|
|
//memset(pbData,0,sizeof(BYTE) *1024* RNG_BYTE_OUTPUT_SIZE); //prepare memory
|
2021-05-27 22:53:37 +00:00
|
|
|
FILE * timeFile=fopen("winapiResult.txt","a+"); // file for storing measurments
|
2021-05-24 20:44:55 +00:00
|
|
|
TIMER_INIT
|
|
|
|
{TIMER_START
|
|
|
|
/*######## CALLING FUNCTIONS With APIs ##############*/
|
2021-05-27 22:53:37 +00:00
|
|
|
randS(pbData,timeFile);
|
|
|
|
cryptgenrandom(pbData,timeFile);
|
|
|
|
bcrypt(pbData,timeFile);
|
|
|
|
rtlgenrandom(pbData,timeFile);
|
2021-05-24 20:44:55 +00:00
|
|
|
|
|
|
|
/*###################### END ########################*/
|
|
|
|
TIMER_STOP}
|
|
|
|
printf("Whole program executed in: %f sec\n",elapsedTime);
|
|
|
|
fclose(timeFile);
|
|
|
|
if (pbData) free(pbData);
|
|
|
|
printf("COMPLETE!\n");
|
|
|
|
return 0;
|
|
|
|
}
|