#include "config.h"
#include "lsasystem.h"
#include "lsadef.h"
#include "lsa/lsa.h"
#include "lwmem.h"
#include "lwstr.h"
#include "lwsecurityidentifier.h"
#include "lsautils.h"

typedef struct __USERINFO
{
    LSA_CACHE_ENTRY entry;
    DWORD uid;
    DWORD gid;
    PSTR pszUpn;
    PSTR pszSid;
} USERINFO, *PUSERINFO;

typedef enum
{
    USERINFO_UID,
    USERINFO_GID,
    USERINFO_UPN,
    USERINFO_SID,
} USERINFO_KEY;

static PUSERINFO
userinfo_create(DWORD uid)
{
    PUSERINFO pUser = calloc(1, sizeof(*pUser));

    pUser->uid = uid;
    pUser->gid = ~uid;

    LwAllocateStringPrintf(&pUser->pszUpn, "user%.4lu@domain.com", (unsigned long) uid);
    LwAllocateStringPrintf(&pUser->pszSid, "S-NOT-A-REAL-SID-%.4lu", (unsigned long) uid);

    return pUser;
}

static DWORD
generic_hash(unsigned char* pKey, DWORD dwSize)
{
    DWORD dwIndex;
    DWORD dwHash = 0;

    for (dwIndex = 0; dwIndex < dwSize; dwIndex++)
    {
        dwHash += pKey[dwIndex];
        dwHash += (dwHash << 10);
        dwHash ^= (dwHash >> 6);
    }

    dwHash += (dwHash << 3);
    dwHash ^= (dwHash >> 11);
    dwHash += (dwHash << 15);

    return dwHash;
}

static PVOID
userinfo_getkey(void* pEntry, DWORD dwIndex, void* data)
{
    PUSERINFO pUser = (PUSERINFO) pEntry;

    switch ((USERINFO_KEY) dwIndex)
    {
    case USERINFO_UID:
        return &pUser->uid;
    case USERINFO_GID:
        return &pUser->gid;
    case USERINFO_SID:
        return pUser->pszSid;
    case USERINFO_UPN:
        return pUser->pszUpn;
    }

    return NULL;
}

static DWORD
userinfo_hash(void* pKey, DWORD dwIndex, void* data)
{
    switch ((USERINFO_KEY) dwIndex)
    {
    case USERINFO_UID:
    case USERINFO_GID:
        return *(DWORD*) pKey;
    case USERINFO_SID:
    case USERINFO_UPN:
        return generic_hash(pKey, strlen((const char*) pKey));
    }

    return 0;
}

static BOOLEAN
userinfo_equal(void* pKey1, void* pKey2, DWORD dwIndex, void* data)
{
    switch ((USERINFO_KEY) dwIndex)
    {
    case USERINFO_UID:
    case USERINFO_GID:
        return *(DWORD*) pKey1 == *(DWORD*) pKey2;
    case USERINFO_SID:
    case USERINFO_UPN:
        return !strcmp((const char *) pKey1, (const char*) pKey2);
    }

    return FALSE;
}

static DWORD
userinfo_miss(void* pKey, DWORD dwIndex, void* data, PVOID* ppEntry)
{
    DWORD uid = 0;

    switch ((USERINFO_KEY) dwIndex)
    {
    case USERINFO_UID:
        uid = *(DWORD*) pKey;
        break;
    case USERINFO_GID:
        uid = ~*(DWORD*) pKey;
        break;
    case USERINFO_SID:
        uid = atoi(((char*) pKey) + strlen("S-NOT-A-REAL-SID-"));
        break;
    case USERINFO_UPN:
        uid = atoi(((char*) pKey) + strlen("user"));
        break;
    }

    *ppEntry = userinfo_create(uid);

    return 0;
}

static DWORD
userinfo_kick(PVOID pEntry, void* data)
{
    free(pEntry);

    return 0;
}

#define BUCKETS 1333
#define USERS 1000

int main(int argc, char** argv)
{
    PLSA_CACHE pCache = NULL;
    DWORD i;
    PUSERINFO pUser1 = NULL;
    PUSERINFO pUser2 = NULL;
    char pszUserName[256];

    LsaCacheNew(
        4,
        BUCKETS,
        userinfo_hash,
        userinfo_equal,
        userinfo_getkey,
        userinfo_miss,
        userinfo_kick,
        NULL,
        &pCache
        );

    for (i = 1; i <= USERS; i++)
    {
        sprintf(pszUserName, "user%.4lu@domain.com", (unsigned long) i);
        LsaCacheLookup(pCache, pszUserName, USERINFO_UPN, (PVOID*) (PVOID) &pUser1);
        LsaCacheLookup(pCache, &i, USERINFO_UID, (PVOID*) (PVOID) &pUser2);

        if (pUser1 != pUser2 || pUser1->uid != i || strcmp(pUser1->pszUpn, pszUserName))
        {
            abort();
        }
    }

    for (i = 1; i <= USERS; i++)
    {
        DWORD gid = ~i;
        LsaCacheLookup(pCache, &gid, USERINFO_GID, (PVOID*) (PVOID) &pUser1);

        if (pUser1->gid != gid)
        {
            abort();
        }
    }

    printf("Cache collisions: %lu (%f per key)\n", (unsigned long) pCache->dwNumCollisions, (double) pCache->dwNumCollisions / 4.0);
    printf("Cache hash misses: %lu\n", (unsigned long) pCache->dwNumHashMisses - USERS);
    printf("Cache full misses: %lu\n", (unsigned long) pCache->dwNumFullMisses - USERS);
    printf("Cache kicks: %lu\n", (unsigned long) pCache->dwNumKicks);
    printf("Cache load: %f\n", (double) pCache->dwNumUsedBuckets / (4.0 * BUCKETS));

    return 0;
}
