/*++
/* NAME
/*	scache_single 3
/* SUMMARY
/*	single-item session cache
/* SYNOPSIS
/*	#include <scache.h>
/* DESCRIPTION
/*	SCACHE *scache_single_create()
/* DESCRIPTION
/*	This module implements an in-memory, single-session cache.
/*
/*	scache_single_create() creates a session cache instance
/*	that stores a single session.
/* DIAGNOSTICS
/*	Fatal error: memory allocation problem;
/*	panic: internal consistency failure.
/* SEE ALSO
/*	scache(3), generic session cache API
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <unistd.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>
#include <events.h>

/*#define msg_verbose 1*/

/* Global library. */

#include <scache.h>

/* Application-specific. */

 /*
  * Data structure for one saved connection. It is left up to the application
  * to serialize attributes upon passivation, and to de-serialize them upon
  * re-activation.
  */
typedef struct {
    VSTRING *endp_label;		/* physical endpoint name */
    VSTRING *endp_prop;			/* endpoint properties, serialized */
    int     fd;				/* the session */
} SCACHE_SINGLE_ENDP;

 /*
  * Data structure for a logical name to physical endpoint binding. It is
  * left up to the application to serialize attributes upon passivation, and
  * to de-serialize then upon re-activation.
  */
typedef struct {
    VSTRING *dest_label;		/* logical destination name */
    VSTRING *dest_prop;			/* binding properties, serialized */
    VSTRING *endp_label;		/* physical endpoint name */
} SCACHE_SINGLE_DEST;

 /*
  * SCACHE_SINGLE is a derived type from the SCACHE super-class.
  */
typedef struct {
    SCACHE  scache[1];			/* super-class */
    SCACHE_SINGLE_ENDP endp;		/* one cached session */
    SCACHE_SINGLE_DEST dest;		/* one cached binding */
} SCACHE_SINGLE;

static void scache_single_expire_endp(int, void *);
static void scache_single_expire_dest(int, void *);

#define SCACHE_SINGLE_ENDP_BUSY(sp)	(VSTRING_LEN(sp->endp.endp_label) > 0)
#define SCACHE_SINGLE_DEST_BUSY(sp)	(VSTRING_LEN(sp->dest.dest_label) > 0)

#define STR(x) vstring_str(x)

/* scache_single_free_endp - discard endpoint */

static void scache_single_free_endp(SCACHE_SINGLE *sp)
{
    const char *myname = "scache_single_free_endp";

    if (msg_verbose)
	msg_info("%s: %s", myname, STR(sp->endp.endp_label));

    event_cancel_timer(scache_single_expire_endp, (void *) sp);
    if (sp->endp.fd >= 0 && close(sp->endp.fd) < 0)
	msg_warn("close session endpoint %s: %m", STR(sp->endp.endp_label));
    VSTRING_RESET(sp->endp.endp_label);
    VSTRING_TERMINATE(sp->endp.endp_label);
    VSTRING_RESET(sp->endp.endp_prop);
    VSTRING_TERMINATE(sp->endp.endp_prop);
    sp->endp.fd = -1;
}

/* scache_single_expire_endp - discard expired session */

static void scache_single_expire_endp(int unused_event, void *context)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) context;

    scache_single_free_endp(sp);
}

/* scache_single_save_endp - save endpoint */

static void scache_single_save_endp(SCACHE *scache, int endp_ttl,
				            const char *endp_label,
				            const char *endp_prop, int fd)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
    const char *myname = "scache_single_save_endp";

    if (endp_ttl <= 0)
	msg_panic("%s: bad endp_ttl: %d", myname, endp_ttl);

    if (SCACHE_SINGLE_ENDP_BUSY(sp))
	scache_single_free_endp(sp);		/* dump the cached fd */

    vstring_strcpy(sp->endp.endp_label, endp_label);
    vstring_strcpy(sp->endp.endp_prop, endp_prop);
    sp->endp.fd = fd;
    event_request_timer(scache_single_expire_endp, (void *) sp, endp_ttl);

    if (msg_verbose)
	msg_info("%s: %s fd=%d", myname, endp_label, fd);
}

/* scache_single_find_endp - look up cached session */

static int scache_single_find_endp(SCACHE *scache, const char *endp_label,
				           VSTRING *endp_prop)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
    const char *myname = "scache_single_find_endp";
    int     fd;

    if (!SCACHE_SINGLE_ENDP_BUSY(sp)) {
	if (msg_verbose)
	    msg_info("%s: no endpoint cache: %s", myname, endp_label);
	return (-1);
    }
    if (strcmp(STR(sp->endp.endp_label), endp_label) == 0) {
	vstring_strcpy(endp_prop, STR(sp->endp.endp_prop));
	fd = sp->endp.fd;
	sp->endp.fd = -1;
	scache_single_free_endp(sp);
	if (msg_verbose)
	    msg_info("%s: found: %s fd=%d", myname, endp_label, fd);
	return (fd);
    }
    if (msg_verbose)
	msg_info("%s: not found: %s", myname, endp_label);
    return (-1);
}

/* scache_single_free_dest - discard destination/endpoint association */

static void scache_single_free_dest(SCACHE_SINGLE *sp)
{
    const char *myname = "scache_single_free_dest";

    if (msg_verbose)
	msg_info("%s: %s -> %s", myname, STR(sp->dest.dest_label),
		 STR(sp->dest.endp_label));

    event_cancel_timer(scache_single_expire_dest, (void *) sp);
    VSTRING_RESET(sp->dest.dest_label);
    VSTRING_TERMINATE(sp->dest.dest_label);
    VSTRING_RESET(sp->dest.dest_prop);
    VSTRING_TERMINATE(sp->dest.dest_prop);
    VSTRING_RESET(sp->dest.endp_label);
    VSTRING_TERMINATE(sp->dest.endp_label);
}

/* scache_single_expire_dest - discard expired destination/endpoint binding */

static void scache_single_expire_dest(int unused_event, void *context)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) context;

    scache_single_free_dest(sp);
}

/* scache_single_save_dest - create destination/endpoint association */

static void scache_single_save_dest(SCACHE *scache, int dest_ttl,
				            const char *dest_label,
				            const char *dest_prop,
				            const char *endp_label)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
    const char *myname = "scache_single_save_dest";
    int     refresh;

    if (dest_ttl <= 0)
	msg_panic("%s: bad dest_ttl: %d", myname, dest_ttl);

    /*
     * Optimize: reset timer only, if nothing has changed.
     */
    refresh =
	(SCACHE_SINGLE_DEST_BUSY(sp)
	 && strcmp(STR(sp->dest.dest_label), dest_label) == 0
	 && strcmp(STR(sp->dest.dest_prop), dest_prop) == 0
	 && strcmp(STR(sp->dest.endp_label), endp_label) == 0);

    if (refresh == 0) {
	vstring_strcpy(sp->dest.dest_label, dest_label);
	vstring_strcpy(sp->dest.dest_prop, dest_prop);
	vstring_strcpy(sp->dest.endp_label, endp_label);
    }
    event_request_timer(scache_single_expire_dest, (void *) sp, dest_ttl);

    if (msg_verbose)
	msg_info("%s: %s -> %s%s", myname, dest_label, endp_label,
		 refresh ? " (refreshed)" : "");
}

/* scache_single_find_dest - look up cached session */

static int scache_single_find_dest(SCACHE *scache, const char *dest_label,
			             VSTRING *dest_prop, VSTRING *endp_prop)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;
    const char *myname = "scache_single_find_dest";
    int     fd;

    if (!SCACHE_SINGLE_DEST_BUSY(sp)) {
	if (msg_verbose)
	    msg_info("%s: no destination cache: %s", myname, dest_label);
	return (-1);
    }
    if (strcmp(STR(sp->dest.dest_label), dest_label) == 0) {
	if (msg_verbose)
	    msg_info("%s: found: %s", myname, dest_label);
	if ((fd = scache_single_find_endp(scache, STR(sp->dest.endp_label), endp_prop)) >= 0) {
	    vstring_strcpy(dest_prop, STR(sp->dest.dest_prop));
	    return (fd);
	}
    }
    if (msg_verbose)
	msg_info("%s: not found: %s", myname, dest_label);
    return (-1);
}

/* scache_single_size - size of single-element cache :-) */

static void scache_single_size(SCACHE *scache, SCACHE_SIZE *size)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;

    size->dest_count = (!SCACHE_SINGLE_DEST_BUSY(sp) ? 0 : 1);
    size->endp_count = (!SCACHE_SINGLE_ENDP_BUSY(sp) ? 0 : 1);
    size->sess_count = (sp->endp.fd < 0 ? 0 : 1);
}

/* scache_single_free - destroy single-element cache object */

static void scache_single_free(SCACHE *scache)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) scache;

    if (SCACHE_SINGLE_ENDP_BUSY(sp))
        scache_single_free_endp(sp);
    if (SCACHE_SINGLE_DEST_BUSY(sp))
        scache_single_free_dest(sp);

    vstring_free(sp->endp.endp_label);
    vstring_free(sp->endp.endp_prop);
    if (sp->endp.fd >= 0)
	close(sp->endp.fd);

    vstring_free(sp->dest.dest_label);
    vstring_free(sp->dest.dest_prop);
    vstring_free(sp->dest.endp_label);

    myfree((void *) sp);
}

/* scache_single_create - initialize */

SCACHE *scache_single_create(void)
{
    SCACHE_SINGLE *sp = (SCACHE_SINGLE *) mymalloc(sizeof(*sp));

    sp->scache->save_endp = scache_single_save_endp;
    sp->scache->find_endp = scache_single_find_endp;
    sp->scache->save_dest = scache_single_save_dest;
    sp->scache->find_dest = scache_single_find_dest;
    sp->scache->size = scache_single_size;
    sp->scache->free = scache_single_free;

    sp->endp.endp_label = vstring_alloc(10);
    sp->endp.endp_prop = vstring_alloc(10);
    sp->endp.fd = -1;

    sp->dest.dest_label = vstring_alloc(10);
    sp->dest.dest_prop = vstring_alloc(10);
    sp->dest.endp_label = vstring_alloc(10);

    return (sp->scache);
}
