/* mem_track.h
 *
 * Copyright (C) 2006-2022 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */


/* The memory tracker overrides the wolfSSL memory callback system and uses a
 * static to track the total, peak and currently allocated bytes.
 *
 * If you are already using the memory callbacks then enabling this will
 * override the memory callbacks and prevent your memory callbacks from
 * working. This assumes malloc() and free() are available. Feel free to
 * customize this for your needs.

 * The enable this feature define the following:
 * #define USE_WOLFSSL_MEMORY
 * #define WOLFSSL_TRACK_MEMORY
 *
 * On startup call:
 * InitMemoryTracker();
 *
 * When ready to dump the memory report call:
 * ShowMemoryTracker();
 *
 * Report example:
 * total   Allocs =       228
 * total   Bytes  =     93442
 * peak    Bytes  =      8840
 * current Bytes  =         0
 *
 *
 * You can also:
 * #define WOLFSSL_DEBUG_MEMORY
 *
 * To print every alloc/free along with the function and line number.
 * Example output:
 * Alloc: 0x7fa14a500010 -> 120 at wc_InitRng:496
 * Free: 0x7fa14a500010 -> 120 at wc_FreeRng:606
 */


#ifndef WOLFSSL_MEM_TRACK_H
#define WOLFSSL_MEM_TRACK_H

#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY)

    #include "wolfssl/wolfcrypt/logging.h"

    #if defined(WOLFSSL_TRACK_MEMORY)
        #define DO_MEM_STATS
        #if defined(__linux__) || defined(__MACH__)
            #define DO_MEM_LIST
        #endif
    #endif


    typedef struct memoryStats {
        long totalAllocs;     /* number of allocations */
        long totalDeallocs;   /* number of deallocations */
        long totalBytes;      /* total number of bytes allocated */
        long peakBytes;       /* concurrent max bytes */
        long currentBytes;    /* total current bytes in use */
#ifdef WOLFSSL_TRACK_MEMORY_VERBOSE
        long peakAllocsTripOdometer; /* peak number of concurrent allocations,
                                      * subject to reset by
                                      * wolfCrypt_heap_peak_checkpoint()
                                      */
        long peakBytesTripOdometer; /* peak concurrent bytes, subject to reset
                                     * by wolfCrypt_heap_peak_checkpoint()
                                     */
#endif
    } memoryStats;

    typedef struct memHint {
        size_t thisSize;      /* size of this memory */

    #ifdef DO_MEM_LIST
        struct memHint* next;
        struct memHint* prev;
        #ifdef WOLFSSL_DEBUG_MEMORY
            const char* func;
            unsigned int line;
        #endif
    #endif
        void*  thisMemory;    /* actual memory for user */
    } memHint;

    typedef struct memoryTrack {
        union {
            memHint hint;
            byte    alignit[sizeof(memHint) + ((16-1) & ~(16-1))]; /* make sure we have strong alignment */
        } u;
    } memoryTrack;

#ifdef DO_MEM_LIST
    /* track allocations and report at end */
    typedef struct memoryList {
        memHint* head;
        memHint* tail;
        word32   count;
    } memoryList;
#endif

#if defined(WOLFSSL_TRACK_MEMORY)
    static memoryStats ourMemStats;

    #ifdef DO_MEM_LIST
        #include <pthread.h>
        static memoryList ourMemList;
        static pthread_mutex_t memLock = PTHREAD_MUTEX_INITIALIZER;
    #endif
#endif


    /* if defined to not using inline then declare function prototypes */
    #ifdef NO_INLINE
        #define WC_STATIC
        #ifdef WOLFSSL_DEBUG_MEMORY
             WOLFSSL_LOCAL void* TrackMalloc(size_t sz, const char* func, unsigned int line);
             WOLFSSL_LOCAL void TrackFree(void* ptr, const char* func, unsigned int line);
             WOLFSSL_LOCAL void* TrackRealloc(void* ptr, size_t sz, const char* func, unsigned int line);
        #else
             WOLFSSL_LOCAL void* TrackMalloc(size_t sz);
             WOLFSSL_LOCAL void TrackFree(void* ptr);
             WOLFSSL_LOCAL void* TrackRealloc(void* ptr, size_t sz);
        #endif
        WOLFSSL_LOCAL int InitMemoryTracker(void);
        WOLFSSL_LOCAL void ShowMemoryTracker(void);
    #else
        #define WC_STATIC static
    #endif

#ifdef WOLFSSL_DEBUG_MEMORY
    WC_STATIC WC_INLINE void* TrackMalloc(size_t sz, const char* func, unsigned int line)
#else
    WC_STATIC WC_INLINE void* TrackMalloc(size_t sz)
#endif
    {
        memoryTrack* mt;
        memHint* header;

        if (sz == 0)
            return NULL;

    #ifdef FREERTOS
        mt = (memoryTrack*)pvPortMalloc(sizeof(memoryTrack) + sz);
    #else
        mt = (memoryTrack*)malloc(sizeof(memoryTrack) + sz);
    #endif
        if (mt == NULL)
            return NULL;

        header = &mt->u.hint;
        header->thisSize   = sz;
        header->thisMemory = (byte*)mt + sizeof(memoryTrack);

    #ifdef WOLFSSL_DEBUG_MEMORY
    #ifdef WOLFSSL_DEBUG_MEMORY_PRINT
        fprintf(stderr, "Alloc: %p -> %u at %s:%d\n", header->thisMemory, (word32)sz, func, line);
    #else
        (void)func;
        (void)line;
    #endif
    #endif

    #ifdef DO_MEM_STATS
        ourMemStats.totalAllocs++;
        ourMemStats.totalBytes   += sz;
        ourMemStats.currentBytes += sz;
        #ifdef WOLFSSL_TRACK_MEMORY_VERBOSE
        if (ourMemStats.peakAllocsTripOdometer < ourMemStats.totalAllocs - ourMemStats.totalDeallocs)
            ourMemStats.peakAllocsTripOdometer = ourMemStats.totalAllocs - ourMemStats.totalDeallocs;
        if (ourMemStats.peakBytesTripOdometer < ourMemStats.currentBytes) {
            ourMemStats.peakBytesTripOdometer = ourMemStats.currentBytes;
        #endif
            if (ourMemStats.currentBytes > ourMemStats.peakBytes)
                ourMemStats.peakBytes = ourMemStats.currentBytes;
        #ifdef WOLFSSL_TRACK_MEMORY_VERBOSE
        }
        #endif
    #endif
    #ifdef DO_MEM_LIST
        if (pthread_mutex_lock(&memLock) == 0) {
        #ifdef WOLFSSL_DEBUG_MEMORY
            header->func = func;
            header->line = line;
        #endif

            /* Setup event */
            header->next = NULL;
            if (ourMemList.tail == NULL)  {
                ourMemList.head = header;
                header->prev = NULL;
            }
            else {
                ourMemList.tail->next = header;
                header->prev = ourMemList.tail;
            }
            ourMemList.tail = header;      /* add to the end either way */
            ourMemList.count++;

            pthread_mutex_unlock(&memLock);
        }
    #endif

        return header->thisMemory;
    }


#ifdef WOLFSSL_DEBUG_MEMORY
    WC_STATIC WC_INLINE void TrackFree(void* ptr, const char* func, unsigned int line)
#else
    WC_STATIC WC_INLINE void TrackFree(void* ptr)
#endif
    {
        memoryTrack* mt;
        memHint* header;
        size_t sz;

        if (ptr == NULL) {
            return;
        }

        mt = (memoryTrack*)((byte*)ptr - sizeof(memoryTrack));
        header = &mt->u.hint;
        sz = header->thisSize;

    #ifdef DO_MEM_LIST
        if (pthread_mutex_lock(&memLock) == 0)
        {
    #endif

    #ifdef DO_MEM_STATS
            ourMemStats.currentBytes -= header->thisSize;
            ourMemStats.totalDeallocs++;
    #endif

    #ifdef DO_MEM_LIST
            if (header == ourMemList.head && header == ourMemList.tail) {
                ourMemList.head = NULL;
                ourMemList.tail = NULL;
            }
            else if (header == ourMemList.head) {
                ourMemList.head = header->next;
                ourMemList.head->prev = NULL;
            }
            else if (header == ourMemList.tail) {
                ourMemList.tail = header->prev;
                ourMemList.tail->next = NULL;
            }
            else {
                memHint* next = header->next;
                memHint* prev = header->prev;
                if (next)
                    next->prev = prev;
                if (prev)
                    prev->next = next;
            }
            ourMemList.count--;

            pthread_mutex_unlock(&memLock);
        }
    #endif

#ifdef WOLFSSL_DEBUG_MEMORY
#ifdef WOLFSSL_DEBUG_MEMORY_PRINT
        fprintf(stderr, "Free: %p -> %u at %s:%d\n", ptr, (word32)sz, func, line);
#else
        (void)func;
        (void)line;
#endif
#endif
        (void)sz;

    #ifdef FREERTOS
        vPortFree(mt);
    #else
        free(mt);
    #endif
    }


#ifdef WOLFSSL_DEBUG_MEMORY
    WC_STATIC WC_INLINE void* TrackRealloc(void* ptr, size_t sz, const char* func, unsigned int line)
#else
    WC_STATIC WC_INLINE void* TrackRealloc(void* ptr, size_t sz)
#endif
    {
    #ifdef WOLFSSL_DEBUG_MEMORY
        void* ret = TrackMalloc(sz, func, line);
    #else
        void* ret = TrackMalloc(sz);
    #endif

        if (ptr) {
            /* if realloc is bigger, don't overread old ptr */
            memoryTrack* mt;
            memHint* header;

            mt = (memoryTrack*)((byte*)ptr - sizeof(memoryTrack));
            header = &mt->u.hint;

            if (header->thisSize < sz)
                sz = header->thisSize;
        }

        if (ret && ptr)
            XMEMCPY(ret, ptr, sz);

        if (ret) {
        #ifdef WOLFSSL_DEBUG_MEMORY
            TrackFree(ptr, func, line);
        #else
            TrackFree(ptr);
        #endif
        }

        return ret;
    }

#ifdef WOLFSSL_TRACK_MEMORY
    static wolfSSL_Malloc_cb mfDefault = NULL;
    static wolfSSL_Free_cb ffDefault = NULL;
    static wolfSSL_Realloc_cb rfDefault = NULL;

    WC_STATIC WC_INLINE int InitMemoryTracker(void)
    {
        int ret;

        ret = wolfSSL_GetAllocators(&mfDefault, &ffDefault, &rfDefault);
        if (ret < 0) {
            fprintf(stderr, "wolfSSL GetAllocators failed to get the defaults\n");
        }
        ret = wolfSSL_SetAllocators(TrackMalloc, TrackFree, TrackRealloc);
        if (ret < 0) {
            fprintf(stderr, "wolfSSL SetAllocators failed for track memory\n");
            return ret;
        }

    #ifdef DO_MEM_LIST
        if (pthread_mutex_lock(&memLock) == 0)
        {
    #endif

    #ifdef DO_MEM_STATS
        ourMemStats.totalAllocs  = 0;
        ourMemStats.totalDeallocs = 0;
        ourMemStats.totalBytes   = 0;
        ourMemStats.peakBytes    = 0;
        ourMemStats.currentBytes = 0;
#ifdef WOLFSSL_TRACK_MEMORY_VERBOSE
        ourMemStats.peakAllocsTripOdometer = 0;
        ourMemStats.peakBytesTripOdometer    = 0;
#endif
    #endif

    #ifdef DO_MEM_LIST
        XMEMSET(&ourMemList, 0, sizeof(ourMemList));

        pthread_mutex_unlock(&memLock);
        }
    #endif

        return ret;
    }

    WC_STATIC WC_INLINE void ShowMemoryTracker(void)
    {
    #ifdef DO_MEM_LIST
        if (pthread_mutex_lock(&memLock) == 0)
        {
    #endif

    #ifdef DO_MEM_STATS
        fprintf(stderr, "total   Allocs   = %9ld\n", ourMemStats.totalAllocs);
        fprintf(stderr, "total   Deallocs = %9ld\n", ourMemStats.totalDeallocs);
        fprintf(stderr, "total   Bytes    = %9ld\n", ourMemStats.totalBytes);
        fprintf(stderr, "peak    Bytes    = %9ld\n", ourMemStats.peakBytes);
        fprintf(stderr, "current Bytes    = %9ld\n", ourMemStats.currentBytes);
    #endif

    #ifdef DO_MEM_LIST
        if (ourMemList.count > 0) {
            /* print list of allocations */
            memHint* header;
            for (header = ourMemList.head; header != NULL; header = header->next) {
                fprintf(stderr, "Leak: Ptr %p, Size %u"
                #ifdef WOLFSSL_DEBUG_MEMORY
                    ", Func %s, Line %d"
                #endif
                    "\n",
                    (byte*)header + sizeof(memHint), (unsigned int)header->thisSize
                #ifdef WOLFSSL_DEBUG_MEMORY
                    , header->func, header->line
                #endif
                );
            }
        }

        pthread_mutex_unlock(&memLock);
        }
    #endif
    }

    WC_STATIC WC_INLINE int CleanupMemoryTracker(void)
    {
        /* restore default allocators */
        return wolfSSL_SetAllocators(mfDefault, ffDefault, rfDefault);
    }
#endif

#endif /* USE_WOLFSSL_MEMORY */

#endif /* WOLFSSL_MEM_TRACK_H */

