ocs init
This commit is contained in:
2347
plat/diameter/libfdproto/dictionary.c
Normal file
2347
plat/diameter/libfdproto/dictionary.c
Normal file
File diff suppressed because it is too large
Load Diff
405
plat/diameter/libfdproto/dictionary_functions.c
Normal file
405
plat/diameter/libfdproto/dictionary_functions.c
Normal file
@@ -0,0 +1,405 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2015, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
#include <time.h>
|
||||
|
||||
/* This file contains helpers functions to be reused as callbacks in the struct dict_type_data structure.
|
||||
There are three callbacks there:
|
||||
|
||||
- type_encode :
|
||||
- type_interpret :
|
||||
Those two callbacks allow to manipulate more natural structures of data in the code, and to
|
||||
map transparently these natural structures with the AVP-encoded format by calling the functions
|
||||
msg_avp_value_encode or msg_avp_value_interpret.
|
||||
- type_dump :
|
||||
This callback if provided gives a more human-readable debug information.
|
||||
|
||||
*/
|
||||
|
||||
/****************************/
|
||||
/* Address AVP type */
|
||||
/****************************/
|
||||
|
||||
/* The interpret and encode functions work with a "struct sockaddr_storage" pointer for mapping
|
||||
the contents of the AVP */
|
||||
|
||||
int fd_dictfct_Address_encode(void * data, union avp_value * avp_value)
|
||||
{
|
||||
sSS * ss = (sSS *) data;
|
||||
uint16_t AddressType = 0;
|
||||
size_t size = 0;
|
||||
unsigned char * buf = NULL;
|
||||
|
||||
TRACE_ENTRY("%p %p", data, avp_value);
|
||||
CHECK_PARAMS( data && avp_value );
|
||||
|
||||
switch (ss->ss_family) {
|
||||
case AF_INET:
|
||||
{
|
||||
/* We are encoding an IP address */
|
||||
sSA4 * sin = (sSA4 *)ss;
|
||||
|
||||
AddressType = 1;/* see http://www.iana.org/assignments/address-family-numbers/ */
|
||||
size = 6; /* 2 for AddressType + 4 for data */
|
||||
|
||||
CHECK_MALLOC( buf = malloc(size) );
|
||||
|
||||
/* may not work because of alignment: *(uint32_t *)(buf+2) = htonl(sin->sin_addr.s_addr); */
|
||||
memcpy(buf + 2, &sin->sin_addr.s_addr, 4);
|
||||
}
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
{
|
||||
/* We are encoding an IPv6 address */
|
||||
sSA6 * sin6 = (sSA6 *)ss;
|
||||
|
||||
AddressType = 2;/* see http://www.iana.org/assignments/address-family-numbers/ */
|
||||
size = 18; /* 2 for AddressType + 16 for data */
|
||||
|
||||
CHECK_MALLOC( buf = malloc(size) );
|
||||
|
||||
/* The order is already good here */
|
||||
memcpy(buf + 2, &sin6->sin6_addr.s6_addr, 16);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CHECK_PARAMS( AddressType = 0 );
|
||||
}
|
||||
|
||||
*(uint16_t *)buf = htons(AddressType);
|
||||
|
||||
avp_value->os.len = size;
|
||||
avp_value->os.data = buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_dictfct_Address_interpret(union avp_value * avp_value, void * interpreted)
|
||||
{
|
||||
uint16_t AddressType = 0;
|
||||
unsigned char * buf;
|
||||
|
||||
TRACE_ENTRY("%p %p", avp_value, interpreted);
|
||||
|
||||
CHECK_PARAMS( avp_value && interpreted && (avp_value->os.len >= 2) );
|
||||
|
||||
AddressType = ntohs(*(uint16_t *)avp_value->os.data);
|
||||
buf = &avp_value->os.data[2];
|
||||
|
||||
switch (AddressType) {
|
||||
case 1 /* IP */:
|
||||
{
|
||||
sSA4 * sin = (sSA4 *)interpreted;
|
||||
|
||||
CHECK_PARAMS( avp_value->os.len == 6 );
|
||||
|
||||
sin->sin_family = AF_INET;
|
||||
/* sin->sin_addr.s_addr = ntohl( * (uint32_t *) buf); -- may not work because of bad alignment */
|
||||
memcpy(&sin->sin_addr.s_addr, buf, 4);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2 /* IP6 */:
|
||||
{
|
||||
sSA6 * sin6 = (sSA6 *)interpreted;
|
||||
|
||||
CHECK_PARAMS( avp_value->os.len == 18 );
|
||||
|
||||
sin6->sin6_family = AF_INET6;
|
||||
memcpy(&sin6->sin6_addr.s6_addr, buf, 16);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CHECK_PARAMS( AddressType = 0 );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Dump the content of an Address AVP */
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_dictfct_Address_dump, union avp_value * avp_value)
|
||||
{
|
||||
union {
|
||||
sSA sa;
|
||||
sSS ss;
|
||||
sSA4 sin;
|
||||
sSA6 sin6;
|
||||
} s;
|
||||
uint16_t fam;
|
||||
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
|
||||
/* The first two octets represent the address family, http://www.iana.org/assignments/address-family-numbers/ */
|
||||
if (avp_value->os.len < 2) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[invalid length: %zd]", avp_value->os.len), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
|
||||
/* Following octets are the address in network byte order already */
|
||||
fam = avp_value->os.data[0] << 8 | avp_value->os.data[1];
|
||||
switch (fam) {
|
||||
case 1:
|
||||
/* IP */
|
||||
s.sa.sa_family = AF_INET;
|
||||
if ((avp_value->os.len != 6) && (avp_value->os.len != 8)) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[invalid IP length: %zd]", avp_value->os.len), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
memcpy(&s.sin.sin_addr.s_addr, avp_value->os.data + 2, 4);
|
||||
if (avp_value->os.len == 8)
|
||||
memcpy(&s.sin.sin_port, avp_value->os.data + 6, 2);
|
||||
break;
|
||||
case 2:
|
||||
/* IP6 */
|
||||
s.sa.sa_family = AF_INET6;
|
||||
if ((avp_value->os.len != 18) && (avp_value->os.len != 20)) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[invalid IP6 length: %zd]", avp_value->os.len), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
memcpy(&s.sin6.sin6_addr.s6_addr, avp_value->os.data + 2, 16);
|
||||
if (avp_value->os.len == 20)
|
||||
memcpy(&s.sin6.sin6_port, avp_value->os.data + 18, 2);
|
||||
break;
|
||||
case 8:
|
||||
/* E.164 */
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "%.*s", (int)(avp_value->os.len-2), avp_value->os.data+2), return NULL);
|
||||
return *buf;
|
||||
default:
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[unsupported family: 0x%hx]", fam), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
|
||||
return fd_sa_dump(FD_DUMP_STD_PARAMS, &s.sa, NI_NUMERICHOST);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************/
|
||||
/* UTF8String AVP type */
|
||||
/*******************************/
|
||||
|
||||
/* Dump the AVP in a natural human-readable format. This dumps the complete length of the AVP, it is up to the caller to truncate if needed */
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_dictfct_UTF8String_dump, union avp_value * avp_value)
|
||||
{
|
||||
size_t l;
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
l = avp_value->os.len;
|
||||
/* Just in case the string ends in invalid UTF-8 chars, we shorten it */
|
||||
while ((l > 0) && (avp_value->os.data[l - 1] & 0x80)) {
|
||||
/* this byte is start or cont. of multibyte sequence, as we do not know the next byte we need to delete it. */
|
||||
l--;
|
||||
if (avp_value->os.data[l] & 0x40)
|
||||
break; /* This was a start byte, we can stop the loop */
|
||||
}
|
||||
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "\"%.*s\"", (int)l, (char *)avp_value->os.data), return NULL);
|
||||
|
||||
return *buf;
|
||||
}
|
||||
|
||||
|
||||
/*******************************/
|
||||
/* Time AVP type */
|
||||
/*******************************/
|
||||
|
||||
/* The interpret and encode functions work with a "time_t" pointer for mapping
|
||||
the contents of the AVP */
|
||||
|
||||
/* Unix Epoch starts 1970-01-01, NTP 0 is at 1900-01-01 */
|
||||
#define DIFF_EPOCH_TO_NTP ((365*(1970-1900) + 17ul) * 24 * 60 * 60)
|
||||
|
||||
static int diameter_string_to_time_t(const char *str, size_t len, time_t *result) {
|
||||
time_t time_stamp;
|
||||
CHECK_PARAMS(len == 4);
|
||||
|
||||
time_stamp = (((unsigned long)(str[0]&0xff))<<24) + ((str[1]&0xff)<<16) + ((str[2]&0xff)<<8) + ((str[3]&0xff));
|
||||
time_stamp -= DIFF_EPOCH_TO_NTP;
|
||||
#ifdef FIX__NEEDED_FOR_YEAR_2036_AND_LATER
|
||||
/* NTP overflows in 2036; after that, values start at zero again */
|
||||
#define NTP_OVERFLOW_CORRECTION (0x100000000ull)
|
||||
/* XXX: debug and find correct conversion */
|
||||
if (str[0] & 0x80 == 0x00) {
|
||||
time_stamp += NTP_OVERFLOW_CORRECTION;
|
||||
}
|
||||
#endif
|
||||
*result = time_stamp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int time_t_to_diameter_string(time_t time_stamp, char **result) {
|
||||
uint64_t out = time_stamp;
|
||||
char *conv;
|
||||
/* XXX: 2036 fix */
|
||||
out += DIFF_EPOCH_TO_NTP;
|
||||
CHECK_PARAMS( (out >> 32) == 0);
|
||||
|
||||
CHECK_MALLOC(conv=(char *)malloc(5));
|
||||
|
||||
conv[0] = (out>>24) & 0xff;
|
||||
conv[1] = (out>>16) & 0xff;
|
||||
conv[2] = (out>> 8) & 0xff;
|
||||
conv[3] = out & 0xff;
|
||||
conv[4] = '\0';
|
||||
*result = conv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int time_t_to_diameter_timestamp(char *strTime)
|
||||
{
|
||||
time_t now;
|
||||
int out;
|
||||
|
||||
time(&now);
|
||||
|
||||
out = now+DIFF_EPOCH_TO_NTP;
|
||||
|
||||
strTime[0] = (out>>24) & 0xff;
|
||||
strTime[1] = (out>>16) & 0xff;
|
||||
strTime[2] = (out>> 8) & 0xff;
|
||||
strTime[3] = out & 0xff;
|
||||
strTime[4] = '\0';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd_dictfct_Time_encode(void * data, union avp_value * avp_value)
|
||||
{
|
||||
char * buf;
|
||||
size_t len;
|
||||
|
||||
TRACE_ENTRY("%p %p", data, avp_value);
|
||||
CHECK_PARAMS( data && avp_value );
|
||||
|
||||
CHECK_FCT( time_t_to_diameter_string( *((time_t *)data), &buf) );
|
||||
/* FIXME: return len from the function above? */ len = 4;
|
||||
|
||||
avp_value->os.len = len;
|
||||
avp_value->os.data = (uint8_t *)buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_dictfct_Time_interpret(union avp_value * avp_value, void * interpreted)
|
||||
{
|
||||
TRACE_ENTRY("%p %p", avp_value, interpreted);
|
||||
|
||||
CHECK_PARAMS( avp_value && interpreted );
|
||||
|
||||
return diameter_string_to_time_t((const char *)avp_value->os.data, avp_value->os.len, interpreted);
|
||||
}
|
||||
|
||||
static void _format_offs (long offset, char *buf) {
|
||||
int offs_hours, offs_minutes, sgn = 1;
|
||||
if (offset < 0) {
|
||||
offset = -offset;
|
||||
sgn = 1;
|
||||
}
|
||||
offs_hours = (int)(offset/3600);
|
||||
offs_minutes = (offset%3600)/60;
|
||||
|
||||
char* s = buf;
|
||||
|
||||
*(s++) = sgn == 1 ? '+' : '-';
|
||||
*(s++) = (char)(offs_hours/10) + '0';
|
||||
*(s++) = offs_hours%10 + '0';
|
||||
|
||||
if (offs_minutes == 0) {
|
||||
*(s++) = '\0';
|
||||
} else {
|
||||
*(s++) = (char)(offs_minutes/10) + '0';
|
||||
*(s++) = offs_minutes%10 + '0';
|
||||
*(s++) = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_dictfct_Time_dump, union avp_value * avp_value)
|
||||
{
|
||||
time_t val;
|
||||
struct tm conv;
|
||||
char tz_buf[7];
|
||||
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
if (avp_value->os.len != 4) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[invalid length: %zd]", avp_value->os.len), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
|
||||
if (diameter_string_to_time_t((char *)avp_value->os.data, avp_value->os.len, &val) != 0) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "[time conversion error]"), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
|
||||
CHECK_MALLOC_DO( localtime_r(&val, &conv), return NULL);
|
||||
_format_offs(conv.tm_gmtoff, tz_buf);
|
||||
CHECK_MALLOC_DO( fd_dump_extend(FD_DUMP_STD_PARAMS, "%d%02d%02dT%02d%02d%02d%s", conv.tm_year+1900, conv.tm_mon+1, conv.tm_mday, conv.tm_hour, conv.tm_min, conv.tm_sec, tz_buf), return NULL);
|
||||
return *buf;
|
||||
}
|
||||
|
||||
/* Check that a given AVP value contains all the characters from data in the same order */
|
||||
static char error_message[80];
|
||||
int fd_dictfct_CharInOS_check(void * data, union avp_value * val, char ** error_msg)
|
||||
{
|
||||
char * inChar = data;
|
||||
char * inData = (char *)val->os.data;
|
||||
int i = 0;
|
||||
CHECK_PARAMS(data);
|
||||
while (*inChar != '\0') {
|
||||
while (i < val->os.len) {
|
||||
if (*inChar == inData[i++]) {
|
||||
inChar++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= val->os.len)
|
||||
break;
|
||||
}
|
||||
if (*inChar == '\0')
|
||||
return 0;
|
||||
|
||||
if (error_msg) {
|
||||
snprintf(error_message, sizeof(error_message), "Could not find '%c' in AVP", *inChar);
|
||||
*error_msg = error_message;
|
||||
}
|
||||
return EBADMSG;
|
||||
}
|
||||
228
plat/diameter/libfdproto/dispatch.c
Normal file
228
plat/diameter/libfdproto/dispatch.c
Normal file
@@ -0,0 +1,228 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* The dispatch module in the library is quite simple: callbacks are saved in a global list
|
||||
* in no particular order. In addition, they are also linked from the dictionary objects they
|
||||
* refer to. */
|
||||
|
||||
/* Protection for the lists managed in this module. */
|
||||
pthread_rwlock_t fd_disp_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
|
||||
/* List of all registered handlers -- useful if we want to cleanup properly at some point... */
|
||||
static struct fd_list all_handlers = FD_LIST_INITIALIZER( all_handlers );
|
||||
|
||||
/* List of handlers registered for DISP_HOW_ANY. Other handlers are stored in the dictionary */
|
||||
static struct fd_list any_handlers = FD_LIST_INITIALIZER( any_handlers );
|
||||
|
||||
/* The structure to store a callback */
|
||||
struct disp_hdl {
|
||||
int eyec; /* Eye catcher, DISP_EYEC */
|
||||
struct fd_list all; /* link in the all_handlers list */
|
||||
struct fd_list parent;/* link in dictionary cb_list or in any_handlers */
|
||||
enum disp_how how; /* Copy of registration parameter */
|
||||
struct disp_when when; /* Copy of registration parameter */
|
||||
int (*cb)( struct msg **, struct avp *, struct session *, void *, enum disp_action *); /* The callback itself */
|
||||
void *opaque; /* opaque data passed back to the callback */
|
||||
};
|
||||
|
||||
#define DISP_EYEC 0xD15241C1
|
||||
#define VALIDATE_HDL( _hdl ) \
|
||||
( ( ( _hdl ) != NULL ) && ( ((struct disp_hdl *)( _hdl ))->eyec == DISP_EYEC ) )
|
||||
|
||||
/**************************************************************************************/
|
||||
|
||||
/* Call CBs from a given list (any_handlers if cb_list is NULL) -- must have locked fd_disp_lock before */
|
||||
int fd_disp_call_cb_int( struct fd_list * cb_list, struct msg ** msg, struct avp *avp, struct session *sess, enum disp_action *action,
|
||||
struct dict_object * obj_app, struct dict_object * obj_cmd, struct dict_object * obj_avp, struct dict_object * obj_enu,
|
||||
char ** drop_reason, struct msg ** drop_msg)
|
||||
{
|
||||
struct fd_list * senti, *li;
|
||||
int r;
|
||||
TRACE_ENTRY("%p %p %p %p %p %p %p %p %p", cb_list, msg, avp, sess, action, obj_app, obj_cmd, obj_avp, obj_enu);
|
||||
CHECK_PARAMS(msg && action);
|
||||
|
||||
senti = cb_list;
|
||||
if (!senti)
|
||||
senti = &any_handlers;
|
||||
|
||||
for (li = senti->next; li != senti; li = li->next) {
|
||||
struct disp_hdl * hdl = (struct disp_hdl *)(li->o);
|
||||
|
||||
TRACE_DEBUG(ANNOYING, "when: %p %p %p %p", hdl->when.app, hdl->when.command, hdl->when.avp, hdl->when.value);
|
||||
|
||||
/* Check this handler matches this message / avp */
|
||||
if (hdl->when.app && (hdl->when.app != obj_app))
|
||||
continue;
|
||||
if (hdl->when.command && (hdl->when.command != obj_cmd))
|
||||
continue;
|
||||
if (hdl->when.avp && (hdl->when.avp != obj_avp))
|
||||
continue;
|
||||
if (hdl->when.value && (hdl->when.value != obj_enu))
|
||||
continue;
|
||||
|
||||
/* We have a match, the cb must be called. */
|
||||
CHECK_FCT_DO( (r = (*hdl->cb)(msg, avp, sess, hdl->opaque, action)),
|
||||
{
|
||||
*drop_reason = "Internal error: a DISPATCH callback returned an error";
|
||||
*drop_msg = *msg;
|
||||
*msg = NULL;
|
||||
}
|
||||
);
|
||||
|
||||
if (*action != DISP_ACT_CONT)
|
||||
break;
|
||||
|
||||
if ( *msg == NULL )
|
||||
break;
|
||||
}
|
||||
|
||||
/* We're done on this list */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************************************************************************************/
|
||||
|
||||
/* Create a new handler and link it */
|
||||
int fd_disp_register ( int (*cb)( struct msg **, struct avp *, struct session *, void *, enum disp_action *),
|
||||
enum disp_how how, struct disp_when * when, void * opaque, struct disp_hdl ** handle )
|
||||
{
|
||||
struct fd_list * cb_list = NULL;
|
||||
struct disp_hdl * new;
|
||||
struct dict_object * type_enum = NULL, * type_avp;
|
||||
struct dictionary * dict = NULL;
|
||||
|
||||
TRACE_ENTRY("%p %d %p %p", cb, how, when, handle);
|
||||
CHECK_PARAMS( cb && ( (how == DISP_HOW_ANY) || when ));
|
||||
|
||||
switch (how) {
|
||||
case DISP_HOW_ANY:
|
||||
cb_list = &any_handlers;
|
||||
break;
|
||||
|
||||
case DISP_HOW_APPID:
|
||||
CHECK_FCT( fd_dict_disp_cb(DICT_APPLICATION, when->app, &cb_list) );
|
||||
break;
|
||||
|
||||
case DISP_HOW_CC:
|
||||
CHECK_FCT( fd_dict_disp_cb(DICT_COMMAND, when->command, &cb_list) );
|
||||
break;
|
||||
|
||||
case DISP_HOW_AVP_ENUMVAL:
|
||||
CHECK_FCT( fd_dict_disp_cb(DICT_ENUMVAL, when->value, &cb_list) ); /* cb_list is then overwritten */
|
||||
CHECK_FCT( fd_dict_getdict(when->value, &dict) );
|
||||
CHECK_FCT( fd_dict_search(dict, DICT_TYPE, TYPE_OF_ENUMVAL, when->value, &type_enum, EINVAL) );
|
||||
case DISP_HOW_AVP:
|
||||
CHECK_FCT( fd_dict_disp_cb(DICT_AVP, when->avp, &cb_list) );
|
||||
if (dict) {
|
||||
CHECK_FCT( fd_dict_search(dict, DICT_TYPE, TYPE_OF_AVP, when->avp, &type_avp, EINVAL) );
|
||||
if (type_enum) {
|
||||
CHECK_PARAMS( type_enum == type_avp );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
CHECK_PARAMS(how = 0);
|
||||
}
|
||||
/* We might further check optional fields, but we trust the caller ^^ */
|
||||
|
||||
/* Create the new handler */
|
||||
CHECK_MALLOC( new = malloc( sizeof(struct disp_hdl) ) );
|
||||
memset(new, 0, sizeof(struct disp_hdl));
|
||||
new->eyec = DISP_EYEC;
|
||||
fd_list_init(&new->all, new);
|
||||
fd_list_init(&new->parent, new);
|
||||
new->how = how;
|
||||
switch (how) {
|
||||
case DISP_HOW_ANY:
|
||||
/* there is no "when" in that case */
|
||||
break;
|
||||
case DISP_HOW_AVP_ENUMVAL:
|
||||
new->when.value = when->value;
|
||||
case DISP_HOW_AVP:
|
||||
new->when.avp = when->avp;
|
||||
case DISP_HOW_CC:
|
||||
new->when.command = when->command;
|
||||
case DISP_HOW_APPID:
|
||||
new->when.app = when->app;
|
||||
}
|
||||
new->cb = cb;
|
||||
new->opaque = opaque;
|
||||
|
||||
/* Now, link this new element in the appropriate lists */
|
||||
CHECK_POSIX( pthread_rwlock_wrlock(&fd_disp_lock) );
|
||||
fd_list_insert_before(&all_handlers, &new->all);
|
||||
fd_list_insert_before(cb_list, &new->parent);
|
||||
CHECK_POSIX( pthread_rwlock_unlock(&fd_disp_lock) );
|
||||
|
||||
/* We're done */
|
||||
if (handle)
|
||||
*handle = new;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Delete a handler */
|
||||
int fd_disp_unregister ( struct disp_hdl ** handle, void ** opaque )
|
||||
{
|
||||
struct disp_hdl * del;
|
||||
TRACE_ENTRY("%p", handle);
|
||||
CHECK_PARAMS( handle && VALIDATE_HDL(*handle) );
|
||||
del = *handle;
|
||||
*handle = NULL;
|
||||
|
||||
CHECK_POSIX( pthread_rwlock_wrlock(&fd_disp_lock) );
|
||||
fd_list_unlink(&del->all);
|
||||
fd_list_unlink(&del->parent);
|
||||
CHECK_POSIX( pthread_rwlock_unlock(&fd_disp_lock) );
|
||||
|
||||
if (opaque)
|
||||
*opaque = del->opaque;
|
||||
|
||||
free(del);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Delete all handlers */
|
||||
void fd_disp_unregister_all ( void )
|
||||
{
|
||||
TRACE_ENTRY("");
|
||||
while (!FD_IS_LIST_EMPTY(&all_handlers)) {
|
||||
CHECK_FCT_DO( fd_disp_unregister((void *)&(all_handlers.next->o), NULL), /* continue */ );
|
||||
}
|
||||
return;
|
||||
}
|
||||
65
plat/diameter/libfdproto/fdproto-internal.h
Normal file
65
plat/diameter/libfdproto/fdproto-internal.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
/* This file contains the definitions for internal use in the freeDiameter protocol library */
|
||||
|
||||
#ifndef _LIBFDPROTO_INTERNAL_H
|
||||
#define _LIBFDPROTO_INTERNAL_H
|
||||
|
||||
#include "freeDiameter-host.h"
|
||||
#include "libfdproto.h"
|
||||
|
||||
/* Internal to the library */
|
||||
extern const char * type_base_name[];
|
||||
void fd_msg_eteid_init(void);
|
||||
int fd_sess_init(void);
|
||||
void fd_sess_fini(void);
|
||||
|
||||
/* Iterator on the rules of a parent object */
|
||||
int fd_dict_iterate_rules ( struct dict_object *parent, void * data, int (*cb)(void *, struct dict_rule_data *) );
|
||||
|
||||
/* Dispatch / messages / dictionary API */
|
||||
int fd_dict_disp_cb(enum dict_object_type type, struct dict_object *obj, struct fd_list ** cb_list);
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_dict_dump_avp_value, union avp_value *avp_value, struct dict_object * model, int indent, int header);
|
||||
int fd_disp_call_cb_int( struct fd_list * cb_list, struct msg ** msg, struct avp *avp, struct session *sess, enum disp_action *action,
|
||||
struct dict_object * obj_app, struct dict_object * obj_cmd, struct dict_object * obj_avp, struct dict_object * obj_enu,
|
||||
char ** drop_reason, struct msg ** drop_msg);
|
||||
extern pthread_rwlock_t fd_disp_lock;
|
||||
|
||||
/* Messages / sessions API */
|
||||
int fd_sess_reclaim_msg ( struct session ** session );
|
||||
|
||||
|
||||
#endif /* _LIBFDPROTO_INTERNAL_H */
|
||||
700
plat/diameter/libfdproto/fifo.c
Normal file
700
plat/diameter/libfdproto/fifo.c
Normal file
@@ -0,0 +1,700 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
/* FIFO queues module.
|
||||
*
|
||||
* The threads that call these functions must be in the cancellation state PTHREAD_CANCEL_ENABLE and type PTHREAD_CANCEL_DEFERRED.
|
||||
* This is the default state and type on thread creation.
|
||||
*
|
||||
* In order to destroy properly a queue, the application must:
|
||||
* -> shutdown any process that can add into the queue first.
|
||||
* -> pthread_cancel any thread that could be waiting on the queue.
|
||||
* -> consume any element that is in the queue, using fd_qu_tryget_int.
|
||||
* -> then destroy the queue using fd_mq_del.
|
||||
*/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* Definition of a FIFO queue object */
|
||||
struct fifo {
|
||||
int eyec; /* An eye catcher, also used to check a queue is valid. FIFO_EYEC */
|
||||
|
||||
pthread_mutex_t mtx; /* Mutex protecting this queue */
|
||||
pthread_cond_t cond_pull; /* condition variable for pulling threads */
|
||||
pthread_cond_t cond_push; /* condition variable for pushing threads */
|
||||
|
||||
struct fd_list list; /* sentinel for the list of elements */
|
||||
int count; /* number of objects in the list */
|
||||
int thrs; /* number of threads waiting for a new element (when count is 0) */
|
||||
|
||||
int max; /* maximum number of items to accept if not 0 */
|
||||
int thrs_push; /* number of threads waitnig to push an item */
|
||||
|
||||
uint16_t high; /* High level threshold (see libfreeDiameter.h for details) */
|
||||
uint16_t low; /* Low level threshhold */
|
||||
void *data; /* Opaque pointer for threshold callbacks */
|
||||
void (*h_cb)(struct fifo *, void **); /* The callbacks */
|
||||
void (*l_cb)(struct fifo *, void **);
|
||||
int highest;/* The highest count value for which h_cb has been called */
|
||||
int highest_ever; /* The max count value this queue has reached (for tweaking) */
|
||||
|
||||
long long total_items; /* Cumulated number of items that went through this fifo (excluding current count), always increasing. */
|
||||
struct timespec total_time; /* Cumulated time all items spent in this queue, including blocking time (always growing, use deltas for monitoring) */
|
||||
struct timespec blocking_time; /* Cumulated time threads trying to post new items were blocked (queue full). */
|
||||
struct timespec last_time; /* For the last element retrieved from the queue, how long it take between posting (including blocking) and poping */
|
||||
|
||||
};
|
||||
|
||||
struct fifo_item {
|
||||
struct fd_list item;
|
||||
struct timespec posted_on;
|
||||
};
|
||||
|
||||
/* The eye catcher value */
|
||||
#define FIFO_EYEC 0xe7ec1130
|
||||
|
||||
/* Macro to check a pointer */
|
||||
#define CHECK_FIFO( _queue ) (( (_queue) != NULL) && ( (_queue)->eyec == FIFO_EYEC) )
|
||||
|
||||
|
||||
/* Create a new queue, with max number of items -- use 0 for no max */
|
||||
int fd_fifo_new ( struct fifo ** queue, int max )
|
||||
{
|
||||
struct fifo * new;
|
||||
|
||||
TRACE_ENTRY( "%p", queue );
|
||||
|
||||
CHECK_PARAMS( queue );
|
||||
|
||||
/* Create a new object */
|
||||
CHECK_MALLOC( new = malloc (sizeof (struct fifo) ) );
|
||||
|
||||
/* Initialize the content */
|
||||
memset(new, 0, sizeof(struct fifo));
|
||||
|
||||
new->eyec = FIFO_EYEC;
|
||||
CHECK_POSIX( pthread_mutex_init(&new->mtx, NULL) );
|
||||
CHECK_POSIX( pthread_cond_init(&new->cond_pull, NULL) );
|
||||
CHECK_POSIX( pthread_cond_init(&new->cond_push, NULL) );
|
||||
new->max = max;
|
||||
|
||||
fd_list_init(&new->list, NULL);
|
||||
|
||||
/* We're done */
|
||||
*queue = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Dump the content of a queue */
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_fifo_dump, char * name, struct fifo * queue, fd_fifo_dump_item_cb dump_item)
|
||||
{
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
if (name) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "'%s'(@%p): ", name, queue), return NULL);
|
||||
} else {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{fifo}(@%p): ", queue), return NULL);
|
||||
}
|
||||
|
||||
if (!CHECK_FIFO( queue )) {
|
||||
return fd_dump_extend(FD_DUMP_STD_PARAMS, "INVALID/NULL");
|
||||
}
|
||||
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &queue->mtx ), /* continue */ );
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "items:%d,%d,%d threads:%d,%d stats:%lld/%ld.%06ld,%ld.%06ld,%ld.%06ld thresholds:%d,%d,%d,%p,%p,%p",
|
||||
queue->count, queue->highest_ever, queue->max,
|
||||
queue->thrs, queue->thrs_push,
|
||||
queue->total_items,(long)queue->total_time.tv_sec,(long)(queue->total_time.tv_nsec/1000),(long)queue->blocking_time.tv_sec,(long)(queue->blocking_time.tv_nsec/1000),(long)queue->last_time.tv_sec,(long)(queue->last_time.tv_nsec/1000),
|
||||
queue->high, queue->low, queue->highest, queue->h_cb, queue->l_cb, queue->data),
|
||||
goto error);
|
||||
|
||||
if (dump_item) {
|
||||
struct fd_list * li;
|
||||
int i = 0;
|
||||
for (li = queue->list.next; li != &queue->list; li = li->next) {
|
||||
struct fifo_item * fi = (struct fifo_item *)li;
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n [#%i](@%p)@%ld.%06ld: ",
|
||||
i++, fi->item.o, (long)fi->posted_on.tv_sec,(long)(fi->posted_on.tv_nsec/1000)),
|
||||
goto error);
|
||||
CHECK_MALLOC_DO( (*dump_item)(FD_DUMP_STD_PARAMS, fi->item.o), goto error);
|
||||
}
|
||||
}
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &queue->mtx ), /* continue */ );
|
||||
|
||||
return *buf;
|
||||
error:
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &queue->mtx ), /* continue */ );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Delete a queue. It must be empty. */
|
||||
int fd_fifo_del ( struct fifo ** queue )
|
||||
{
|
||||
struct fifo * q;
|
||||
int loops = 0;
|
||||
|
||||
TRACE_ENTRY( "%p", queue );
|
||||
|
||||
CHECK_PARAMS( queue && CHECK_FIFO( *queue ) );
|
||||
|
||||
q = *queue;
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock( &q->mtx ) );
|
||||
|
||||
if ((q->count != 0) || (q->data != NULL)) {
|
||||
TRACE_DEBUG(INFO, "The queue cannot be destroyed (%d, %p)", q->count, q->data);
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &q->mtx ), /* no fallback */ );
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* Ok, now invalidate the queue */
|
||||
q->eyec = 0xdead;
|
||||
|
||||
/* Have all waiting threads return an error */
|
||||
while (q->thrs) {
|
||||
CHECK_POSIX( pthread_mutex_unlock( &q->mtx ));
|
||||
CHECK_POSIX( pthread_cond_signal(&q->cond_pull) );
|
||||
usleep(1000);
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock( &q->mtx ) );
|
||||
ASSERT( ++loops < 20 ); /* detect infinite loops */
|
||||
}
|
||||
|
||||
/* sanity check */
|
||||
ASSERT(FD_IS_LIST_EMPTY(&q->list));
|
||||
|
||||
/* And destroy it */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &q->mtx ) );
|
||||
|
||||
CHECK_POSIX_DO( pthread_cond_destroy( &q->cond_pull ), );
|
||||
|
||||
CHECK_POSIX_DO( pthread_cond_destroy( &q->cond_push ), );
|
||||
|
||||
CHECK_POSIX_DO( pthread_mutex_destroy( &q->mtx ), );
|
||||
|
||||
free(q);
|
||||
*queue = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Move the content of old into new, and update loc_update atomically. We leave the old queue empty but valid */
|
||||
int fd_fifo_move ( struct fifo * old, struct fifo * new, struct fifo ** loc_update )
|
||||
{
|
||||
int loops = 0;
|
||||
|
||||
TRACE_ENTRY("%p %p %p", old, new, loc_update);
|
||||
CHECK_PARAMS( CHECK_FIFO( old ) && CHECK_FIFO( new ));
|
||||
|
||||
CHECK_PARAMS( ! old->data );
|
||||
if (new->high) {
|
||||
TODO("Implement support for thresholds in fd_fifo_move...");
|
||||
}
|
||||
|
||||
/* Update loc_update */
|
||||
if (loc_update)
|
||||
*loc_update = new;
|
||||
|
||||
/* Lock the queues */
|
||||
CHECK_POSIX( pthread_mutex_lock( &old->mtx ) );
|
||||
|
||||
CHECK_PARAMS_DO( (! old->thrs_push), {
|
||||
pthread_mutex_unlock( &old->mtx );
|
||||
return EINVAL;
|
||||
} );
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock( &new->mtx ) );
|
||||
|
||||
/* Any waiting thread on the old queue returns an error */
|
||||
old->eyec = 0xdead;
|
||||
while (old->thrs) {
|
||||
CHECK_POSIX( pthread_mutex_unlock( &old->mtx ));
|
||||
CHECK_POSIX( pthread_cond_signal( &old->cond_pull ) );
|
||||
usleep(1000);
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock( &old->mtx ) );
|
||||
ASSERT( loops < 20 ); /* detect infinite loops */
|
||||
}
|
||||
|
||||
/* Move all data from old to new */
|
||||
fd_list_move_end( &new->list, &old->list );
|
||||
if (old->count && (!new->count)) {
|
||||
CHECK_POSIX( pthread_cond_signal(&new->cond_pull) );
|
||||
}
|
||||
new->count += old->count;
|
||||
|
||||
/* Reset old */
|
||||
old->count = 0;
|
||||
old->eyec = FIFO_EYEC;
|
||||
|
||||
/* Merge the stats in the new queue */
|
||||
new->total_items += old->total_items;
|
||||
old->total_items = 0;
|
||||
|
||||
new->total_time.tv_nsec += old->total_time.tv_nsec;
|
||||
new->total_time.tv_sec += old->total_time.tv_sec + (new->total_time.tv_nsec / 1000000000);
|
||||
new->total_time.tv_nsec %= 1000000000;
|
||||
old->total_time.tv_nsec = 0;
|
||||
old->total_time.tv_sec = 0;
|
||||
|
||||
new->blocking_time.tv_nsec += old->blocking_time.tv_nsec;
|
||||
new->blocking_time.tv_sec += old->blocking_time.tv_sec + (new->blocking_time.tv_nsec / 1000000000);
|
||||
new->blocking_time.tv_nsec %= 1000000000;
|
||||
old->blocking_time.tv_nsec = 0;
|
||||
old->blocking_time.tv_sec = 0;
|
||||
|
||||
/* Unlock, we're done */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &new->mtx ) );
|
||||
CHECK_POSIX( pthread_mutex_unlock( &old->mtx ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the information on the queue */
|
||||
int fd_fifo_getstats( struct fifo * queue, int * current_count, int * limit_count, int * highest_count, long long * total_count,
|
||||
struct timespec * total, struct timespec * blocking, struct timespec * last)
|
||||
{
|
||||
TRACE_ENTRY( "%p %p %p %p %p %p %p %p", queue, current_count, limit_count, highest_count, total_count, total, blocking, last);
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) );
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
|
||||
if (current_count)
|
||||
*current_count = queue->count;
|
||||
|
||||
if (limit_count)
|
||||
*limit_count = queue->max;
|
||||
|
||||
if (highest_count)
|
||||
*highest_count = queue->highest_ever;
|
||||
|
||||
if (total_count)
|
||||
*total_count = queue->total_items;
|
||||
|
||||
if (total)
|
||||
memcpy(total, &queue->total_time, sizeof(struct timespec));
|
||||
|
||||
if (blocking)
|
||||
memcpy(blocking, &queue->blocking_time, sizeof(struct timespec));
|
||||
|
||||
if (last)
|
||||
memcpy(last, &queue->last_time, sizeof(struct timespec));
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
|
||||
/* Done */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* alternate version with no error checking */
|
||||
int fd_fifo_length ( struct fifo * queue )
|
||||
{
|
||||
if ( !CHECK_FIFO( queue ) )
|
||||
return 0;
|
||||
|
||||
return queue->count; /* Let's hope it's read atomically, since we are not locking... */
|
||||
}
|
||||
|
||||
/* Set the thresholds of the queue */
|
||||
int fd_fifo_setthrhd ( struct fifo * queue, void * data, uint16_t high, void (*h_cb)(struct fifo *, void **), uint16_t low, void (*l_cb)(struct fifo *, void **) )
|
||||
{
|
||||
TRACE_ENTRY( "%p %p %hu %p %hu %p", queue, data, high, h_cb, low, l_cb );
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) && (high > low) && (queue->data == NULL) );
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
|
||||
/* Save the values */
|
||||
queue->high = high;
|
||||
queue->low = low;
|
||||
queue->data = data;
|
||||
queue->h_cb = h_cb;
|
||||
queue->l_cb = l_cb;
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
|
||||
/* Done */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* This handler is called when a thread is blocked on a queue, and cancelled */
|
||||
static void fifo_cleanup_push(void * queue)
|
||||
{
|
||||
struct fifo * q = (struct fifo *)queue;
|
||||
TRACE_ENTRY( "%p", queue );
|
||||
|
||||
/* The thread has been cancelled, therefore it does not wait on the queue anymore */
|
||||
q->thrs_push--;
|
||||
|
||||
/* Now unlock the queue, and we're done */
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &q->mtx ), /* nothing */ );
|
||||
|
||||
/* End of cleanup handler */
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Post a new item in the queue */
|
||||
int fd_fifo_post_internal ( struct fifo * queue, void ** item, int skip_max )
|
||||
{
|
||||
struct fifo_item * new;
|
||||
int call_cb = 0;
|
||||
struct timespec posted_on, queued_on;
|
||||
|
||||
/* Get the timing of this call */
|
||||
CHECK_SYS( clock_gettime(CLOCK_REALTIME, &posted_on) );
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
|
||||
if ((!skip_max) && (queue->max)) {
|
||||
while (queue->count >= queue->max) {
|
||||
int ret = 0;
|
||||
|
||||
/* We have to wait for an item to be pulled */
|
||||
queue->thrs_push++ ;
|
||||
pthread_cleanup_push( fifo_cleanup_push, queue);
|
||||
ret = pthread_cond_wait( &queue->cond_push, &queue->mtx );
|
||||
pthread_cleanup_pop(0);
|
||||
queue->thrs_push-- ;
|
||||
|
||||
ASSERT( ret == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a new list item */
|
||||
CHECK_MALLOC_DO( new = malloc (sizeof (struct fifo_item)) , {
|
||||
pthread_mutex_unlock( &queue->mtx );
|
||||
return ENOMEM;
|
||||
} );
|
||||
|
||||
fd_list_init(&new->item, *item);
|
||||
*item = NULL;
|
||||
|
||||
/* Add the new item at the end */
|
||||
fd_list_insert_before( &queue->list, &new->item);
|
||||
queue->count++;
|
||||
if (queue->highest_ever < queue->count)
|
||||
queue->highest_ever = queue->count;
|
||||
if (queue->high && ((queue->count % queue->high) == 0)) {
|
||||
call_cb = 1;
|
||||
queue->highest = queue->count;
|
||||
}
|
||||
|
||||
/* store timing */
|
||||
memcpy(&new->posted_on, &posted_on, sizeof(struct timespec));
|
||||
|
||||
/* update queue timing info "blocking time" */
|
||||
{
|
||||
long long blocked_ns;
|
||||
CHECK_SYS( clock_gettime(CLOCK_REALTIME, &queued_on) );
|
||||
blocked_ns = (queued_on.tv_sec - posted_on.tv_sec) * 1000000000;
|
||||
blocked_ns += (queued_on.tv_nsec - posted_on.tv_nsec);
|
||||
blocked_ns += queue->blocking_time.tv_nsec;
|
||||
queue->blocking_time.tv_sec += blocked_ns / 1000000000;
|
||||
queue->blocking_time.tv_nsec = blocked_ns % 1000000000;
|
||||
}
|
||||
|
||||
/* Signal if threads are asleep */
|
||||
if (queue->thrs > 0) {
|
||||
CHECK_POSIX( pthread_cond_signal(&queue->cond_pull) );
|
||||
}
|
||||
if (queue->thrs_push > 0) {
|
||||
/* cascade */
|
||||
CHECK_POSIX( pthread_cond_signal(&queue->cond_push) );
|
||||
}
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
|
||||
/* Call high-watermark cb as needed */
|
||||
if (call_cb && queue->h_cb)
|
||||
(*queue->h_cb)(queue, &queue->data);
|
||||
|
||||
/* Done */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Post a new item in the queue */
|
||||
int fd_fifo_post_int ( struct fifo * queue, void ** item )
|
||||
{
|
||||
TRACE_ENTRY( "%p %p", queue, item );
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) && item && *item );
|
||||
|
||||
return fd_fifo_post_internal ( queue,item, 0 );
|
||||
|
||||
}
|
||||
|
||||
/* Post a new item in the queue, not blocking */
|
||||
int fd_fifo_post_noblock ( struct fifo * queue, void ** item )
|
||||
{
|
||||
TRACE_ENTRY( "%p %p", queue, item );
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) && item && *item );
|
||||
|
||||
return fd_fifo_post_internal ( queue,item, 1 );
|
||||
|
||||
}
|
||||
|
||||
/* Pop the first item from the queue */
|
||||
static void * mq_pop(struct fifo * queue)
|
||||
{
|
||||
void * ret = NULL;
|
||||
struct fifo_item * fi;
|
||||
struct timespec now;
|
||||
|
||||
ASSERT( ! FD_IS_LIST_EMPTY(&queue->list) );
|
||||
|
||||
fi = (struct fifo_item *)(queue->list.next);
|
||||
ret = fi->item.o;
|
||||
fd_list_unlink(&fi->item);
|
||||
queue->count--;
|
||||
queue->total_items++;
|
||||
|
||||
/* Update the timings */
|
||||
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), goto skip_timing );
|
||||
{
|
||||
long long elapsed = (now.tv_sec - fi->posted_on.tv_sec) * 1000000000;
|
||||
elapsed += now.tv_nsec - fi->posted_on.tv_nsec;
|
||||
|
||||
queue->last_time.tv_sec = elapsed / 1000000000;
|
||||
queue->last_time.tv_nsec = elapsed % 1000000000;
|
||||
|
||||
elapsed += queue->total_time.tv_nsec;
|
||||
queue->total_time.tv_sec += elapsed / 1000000000;
|
||||
queue->total_time.tv_nsec = elapsed % 1000000000;
|
||||
}
|
||||
skip_timing:
|
||||
free(fi);
|
||||
|
||||
if (queue->thrs_push) {
|
||||
CHECK_POSIX_DO( pthread_cond_signal( &queue->cond_push ), );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check if the low watermark callback must be called. */
|
||||
static __inline__ int test_l_cb(struct fifo * queue)
|
||||
{
|
||||
if ((queue->high == 0) || (queue->low == 0) || (queue->l_cb == 0))
|
||||
return 0;
|
||||
|
||||
if (((queue->count % queue->high) == queue->low) && (queue->highest > queue->count)) {
|
||||
queue->highest -= queue->high;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try poping an item */
|
||||
int fd_fifo_tryget_int ( struct fifo * queue, void ** item )
|
||||
{
|
||||
int wouldblock = 0;
|
||||
int call_cb = 0;
|
||||
|
||||
TRACE_ENTRY( "%p %p", queue, item );
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) && item );
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
|
||||
/* Check queue status */
|
||||
if (queue->count > 0) {
|
||||
got_item:
|
||||
/* There are elements in the queue, so pick the first one */
|
||||
*item = mq_pop(queue);
|
||||
call_cb = test_l_cb(queue);
|
||||
} else {
|
||||
if (queue->thrs_push > 0) {
|
||||
/* A thread is trying to push something, let's give it a chance */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
CHECK_POSIX( pthread_cond_signal( &queue->cond_push ) );
|
||||
usleep(1000);
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
if (queue->count > 0)
|
||||
goto got_item;
|
||||
}
|
||||
|
||||
wouldblock = 1;
|
||||
*item = NULL;
|
||||
}
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
|
||||
/* Call low watermark callback as needed */
|
||||
if (call_cb)
|
||||
(*queue->l_cb)(queue, &queue->data);
|
||||
|
||||
/* Done */
|
||||
return wouldblock ? EWOULDBLOCK : 0;
|
||||
}
|
||||
|
||||
/* This handler is called when a thread is blocked on a queue, and cancelled */
|
||||
static void fifo_cleanup(void * queue)
|
||||
{
|
||||
struct fifo * q = (struct fifo *)queue;
|
||||
TRACE_ENTRY( "%p", queue );
|
||||
|
||||
/* The thread has been cancelled, therefore it does not wait on the queue anymore */
|
||||
q->thrs--;
|
||||
|
||||
/* Now unlock the queue, and we're done */
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &q->mtx ), /* nothing */ );
|
||||
|
||||
/* End of cleanup handler */
|
||||
return;
|
||||
}
|
||||
|
||||
/* The internal function for fd_fifo_timedget and fd_fifo_get */
|
||||
static int fifo_tget ( struct fifo * queue, void ** item, int istimed, const struct timespec *abstime)
|
||||
{
|
||||
int call_cb = 0;
|
||||
int ret = 0;
|
||||
|
||||
/* Check the parameters */
|
||||
CHECK_PARAMS( CHECK_FIFO( queue ) && item && (abstime || !istimed) );
|
||||
|
||||
/* Initialize the return value */
|
||||
*item = NULL;
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX( pthread_mutex_lock( &queue->mtx ) );
|
||||
|
||||
awaken:
|
||||
/* Check queue status */
|
||||
if (!CHECK_FIFO( queue )) {
|
||||
/* The queue is being destroyed */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
TRACE_DEBUG(FULL, "The queue is being destroyed -> EPIPE");
|
||||
return EPIPE;
|
||||
}
|
||||
|
||||
if (queue->count > 0) {
|
||||
/* There are items in the queue, so pick the first one */
|
||||
*item = mq_pop(queue);
|
||||
call_cb = test_l_cb(queue);
|
||||
} else {
|
||||
/* We have to wait for a new item */
|
||||
queue->thrs++ ;
|
||||
pthread_cleanup_push( fifo_cleanup, queue);
|
||||
if (istimed) {
|
||||
ret = pthread_cond_timedwait( &queue->cond_pull, &queue->mtx, abstime );
|
||||
} else {
|
||||
ret = pthread_cond_wait( &queue->cond_pull, &queue->mtx );
|
||||
}
|
||||
pthread_cleanup_pop(0);
|
||||
queue->thrs-- ;
|
||||
if (ret == 0)
|
||||
goto awaken; /* test for spurious wake-ups */
|
||||
|
||||
/* otherwise (ETIMEDOUT / other error) just continue */
|
||||
}
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX( pthread_mutex_unlock( &queue->mtx ) );
|
||||
|
||||
/* Call low watermark callback as needed */
|
||||
if (call_cb)
|
||||
(*queue->l_cb)(queue, &queue->data);
|
||||
|
||||
/* Done */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the next available item, block until there is one */
|
||||
int fd_fifo_get_int ( struct fifo * queue, void ** item )
|
||||
{
|
||||
TRACE_ENTRY( "%p %p", queue, item );
|
||||
return fifo_tget(queue, item, 0, NULL);
|
||||
}
|
||||
|
||||
/* Get the next available item, block until there is one, or the timeout expires */
|
||||
int fd_fifo_timedget_int ( struct fifo * queue, void ** item, const struct timespec *abstime )
|
||||
{
|
||||
TRACE_ENTRY( "%p %p %p", queue, item, abstime );
|
||||
return fifo_tget(queue, item, 1, abstime);
|
||||
}
|
||||
|
||||
/* Test if data is available in the queue, without pulling it */
|
||||
int fd_fifo_select ( struct fifo * queue, const struct timespec *abstime )
|
||||
{
|
||||
int ret = 0;
|
||||
TRACE_ENTRY( "%p %p", queue, abstime );
|
||||
|
||||
CHECK_PARAMS_DO( CHECK_FIFO( queue ), return -EINVAL );
|
||||
|
||||
/* lock the queue */
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &queue->mtx ), return -__ret__ );
|
||||
|
||||
awaken:
|
||||
ret = (queue->count > 0 ) ? queue->count : 0;
|
||||
if ((ret == 0) && (abstime != NULL)) {
|
||||
/* We have to wait for a new item */
|
||||
queue->thrs++ ;
|
||||
pthread_cleanup_push( fifo_cleanup, queue);
|
||||
ret = pthread_cond_timedwait( &queue->cond_pull, &queue->mtx, abstime );
|
||||
pthread_cleanup_pop(0);
|
||||
queue->thrs-- ;
|
||||
if (ret == 0)
|
||||
goto awaken; /* test for spurious wake-ups */
|
||||
|
||||
if (ret == ETIMEDOUT)
|
||||
ret = 0;
|
||||
else
|
||||
ret = -ret;
|
||||
}
|
||||
|
||||
/* Unlock */
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &queue->mtx ), return -__ret__ );
|
||||
|
||||
return ret;
|
||||
}
|
||||
71
plat/diameter/libfdproto/init.c
Normal file
71
plat/diameter/libfdproto/init.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* function to free the threadnames */
|
||||
static void freelogstr(void * str) {
|
||||
if (TRACE_BOOL(ANNOYING)) {
|
||||
if (str) {
|
||||
fd_log_debug("(Thread '%s' terminating)", (char *)str);
|
||||
}
|
||||
}
|
||||
free(str);
|
||||
}
|
||||
|
||||
/* Initialize library variables and threads */
|
||||
int fd_libproto_init()
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* Create the thread key that contains thread name for debug messages */
|
||||
ret = pthread_key_create(&fd_log_thname, freelogstr);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error initializing the libfreeDiameter library: %s\n", strerror(ret) );
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Initialize the modules that need it */
|
||||
fd_msg_eteid_init();
|
||||
CHECK_FCT( fd_sess_init() );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Stop all threads created in the library */
|
||||
void fd_libproto_fini(void)
|
||||
{
|
||||
fd_sess_fini();
|
||||
}
|
||||
169
plat/diameter/libfdproto/lists.c
Normal file
169
plat/diameter/libfdproto/lists.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* Initialize a list element */
|
||||
void fd_list_init ( struct fd_list * list, void * obj )
|
||||
{
|
||||
memset(list, 0, sizeof(struct fd_list));
|
||||
list->next = list;
|
||||
list->prev = list;
|
||||
list->head = list;
|
||||
list->o = obj;
|
||||
}
|
||||
|
||||
#define CHECK_SINGLE( li ) { \
|
||||
ASSERT( ((struct fd_list *)(li))->next == (li) ); \
|
||||
ASSERT( ((struct fd_list *)(li))->prev == (li) ); \
|
||||
ASSERT( ((struct fd_list *)(li))->head == (li) ); \
|
||||
}
|
||||
|
||||
/* insert after a reference, checks done */
|
||||
static void list_insert_after( struct fd_list * ref, struct fd_list * item )
|
||||
{
|
||||
item->prev = ref;
|
||||
item->next = ref->next;
|
||||
item->head = ref->head;
|
||||
ref->next->prev = item;
|
||||
ref->next = item;
|
||||
}
|
||||
|
||||
/* insert after a reference */
|
||||
void fd_list_insert_after ( struct fd_list * ref, struct fd_list * item )
|
||||
{
|
||||
ASSERT(item != NULL);
|
||||
ASSERT(ref != NULL);
|
||||
CHECK_SINGLE ( item );
|
||||
ASSERT(ref->head != item);
|
||||
list_insert_after(ref, item);
|
||||
}
|
||||
|
||||
/* Move all elements of list senti at the end of list ref */
|
||||
void fd_list_move_end(struct fd_list * ref, struct fd_list * senti)
|
||||
{
|
||||
struct fd_list * li;
|
||||
ASSERT(ref->head == ref);
|
||||
ASSERT(senti->head == senti);
|
||||
|
||||
if (senti->next == senti)
|
||||
return;
|
||||
|
||||
for (li = senti->next; li != senti; li = li->next)
|
||||
li->head = ref;
|
||||
|
||||
senti->next->prev = ref->prev;
|
||||
ref->prev->next = senti->next;
|
||||
senti->prev->next = ref;
|
||||
ref->prev = senti->prev;
|
||||
senti->prev = senti;
|
||||
senti->next = senti;
|
||||
}
|
||||
|
||||
/* insert before a reference, checks done */
|
||||
static void list_insert_before ( struct fd_list * ref, struct fd_list * item )
|
||||
{
|
||||
item->prev = ref->prev;
|
||||
item->next = ref;
|
||||
item->head = ref->head;
|
||||
ref->prev->next = item;
|
||||
ref->prev = item;
|
||||
}
|
||||
|
||||
/* insert before a reference */
|
||||
void fd_list_insert_before ( struct fd_list * ref, struct fd_list * item )
|
||||
{
|
||||
ASSERT(item != NULL);
|
||||
ASSERT(ref != NULL);
|
||||
CHECK_SINGLE ( item );
|
||||
ASSERT(ref->head != item);
|
||||
list_insert_before(ref, item);
|
||||
}
|
||||
|
||||
/* Insert an item in an ordered list -- ordering function provided. If duplicate object found, it is returned in ref_duplicate */
|
||||
int fd_list_insert_ordered( struct fd_list * head, struct fd_list * item, int (*cmp_fct)(void *, void *), void ** ref_duplicate)
|
||||
{
|
||||
struct fd_list * ptr = head;
|
||||
int cmp;
|
||||
|
||||
/* Some debug sanity checks */
|
||||
ASSERT(head != NULL);
|
||||
ASSERT(item != NULL);
|
||||
ASSERT(cmp_fct != NULL);
|
||||
ASSERT(head->head == head);
|
||||
CHECK_SINGLE ( item );
|
||||
|
||||
/* loop in the list */
|
||||
while (ptr->next != head)
|
||||
{
|
||||
/* Compare the object to insert with the next object in list */
|
||||
cmp = cmp_fct( item->o, ptr->next->o );
|
||||
if (!cmp) {
|
||||
/* An element with the same key already exists */
|
||||
if (ref_duplicate != NULL)
|
||||
*ref_duplicate = ptr->next->o;
|
||||
return EEXIST;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
break; /* We must insert the element here */
|
||||
|
||||
ptr = ptr->next;
|
||||
}
|
||||
|
||||
/* Now insert the element between ptr and ptr->next */
|
||||
list_insert_after( ptr, item );
|
||||
|
||||
|
||||
/* Ok */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Unlink an object */
|
||||
void fd_list_unlink ( struct fd_list * item )
|
||||
{
|
||||
ASSERT(item != NULL);
|
||||
if (item->head == item)
|
||||
return;
|
||||
/* unlink */
|
||||
item->next->prev = item->prev;
|
||||
item->prev->next = item->next;
|
||||
/* sanitize */
|
||||
item->next = item;
|
||||
item->prev = item;
|
||||
item->head = item;
|
||||
}
|
||||
|
||||
|
||||
325
plat/diameter/libfdproto/log.c
Normal file
325
plat/diameter/libfdproto/log.c
Normal file
@@ -0,0 +1,325 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
pthread_mutex_t fd_log_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_key_t fd_log_thname;
|
||||
int fd_g_debug_lvl = FD_LOG_NOTICE; // FD_LOG_NOTICE;
|
||||
|
||||
static void fd_internal_logger( int, const char *, va_list );
|
||||
static int use_colors = 0; /* 0: not init, 1: yes, 2: no */
|
||||
|
||||
/* These may be used to pass specific debug requests via the command-line parameters */
|
||||
char * fd_debug_one_function = NULL;
|
||||
char * fd_debug_one_file = NULL;
|
||||
|
||||
/* Useless function, only to ease setting up a breakpoint in gdb (break fd_breakhere) -- use TRACE_HERE */
|
||||
int fd_breaks = 0;
|
||||
int fd_breakhere(void) { return ++fd_breaks; }
|
||||
|
||||
/* Allow passing of the log and debug information from base stack to extensions */
|
||||
void (*fd_logger)( int loglevel, const char * format, va_list args ) = fd_internal_logger;
|
||||
|
||||
/* Register an external call back for tracing and debug */
|
||||
int fd_log_handler_register( void (*logger)(int loglevel, const char * format, va_list args) )
|
||||
{
|
||||
CHECK_PARAMS( logger );
|
||||
|
||||
if ( fd_logger != fd_internal_logger )
|
||||
{
|
||||
return EALREADY; /* only one registration allowed */
|
||||
}
|
||||
else
|
||||
{
|
||||
fd_logger = logger;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Implement a simple reset function here */
|
||||
int fd_log_handler_unregister ( void )
|
||||
{
|
||||
fd_logger = fd_internal_logger;
|
||||
return 0; /* Successfull in all cases. */
|
||||
}
|
||||
|
||||
static void fd_cleanup_mutex_silent( void * mutex )
|
||||
{
|
||||
(void)pthread_mutex_unlock((pthread_mutex_t *)mutex);
|
||||
}
|
||||
|
||||
|
||||
static void fd_internal_logger( int printlevel, const char *format, va_list ap )
|
||||
{
|
||||
char buf[25];
|
||||
|
||||
/* Do we need to trace this ? */
|
||||
if (printlevel < fd_g_debug_lvl)
|
||||
return;
|
||||
|
||||
/* add timestamp */
|
||||
printf("%s ", fd_log_time(NULL, buf, sizeof(buf),
|
||||
#if (defined(DEBUG) && defined(DEBUG_WITH_META))
|
||||
1, 1
|
||||
#else /* (defined(DEBUG) && defined(DEBUG_WITH_META)) */
|
||||
0, 0
|
||||
#endif /* (defined(DEBUG) && defined(DEBUG_WITH_META)) */
|
||||
));
|
||||
/* Use colors on stdout ? */
|
||||
if (!use_colors) {
|
||||
if (isatty(STDOUT_FILENO))
|
||||
use_colors = 1;
|
||||
else
|
||||
use_colors = 2;
|
||||
}
|
||||
|
||||
switch(printlevel) {
|
||||
case FD_LOG_ANNOYING: printf("%s A ", (use_colors == 1) ? "\e[0;37m" : ""); break;
|
||||
case FD_LOG_DEBUG: printf("%s DBG ", (use_colors == 1) ? "\e[0;37m" : ""); break;
|
||||
case FD_LOG_NOTICE: printf("%sNOTI ", (use_colors == 1) ? "\e[1;37m" : ""); break;
|
||||
case FD_LOG_ERROR: printf("%sERROR ", (use_colors == 1) ? "\e[0;31m" : ""); break;
|
||||
case FD_LOG_FATAL: printf("%sFATAL! ", (use_colors == 1) ? "\e[0;31m" : ""); break;
|
||||
default: printf("%s ??? ", (use_colors == 1) ? "\e[0;31m" : "");
|
||||
}
|
||||
vprintf(format, ap);
|
||||
if (use_colors == 1)
|
||||
printf("\e[00m");
|
||||
printf("\n");
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Log a debug message */
|
||||
void fd_log ( int loglevel, const char * format, ... )
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
(void)pthread_mutex_lock(&fd_log_lock);
|
||||
|
||||
pthread_cleanup_push(fd_cleanup_mutex_silent, &fd_log_lock);
|
||||
|
||||
va_start(ap, format);
|
||||
fd_logger(loglevel, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
pthread_cleanup_pop(0);
|
||||
|
||||
(void)pthread_mutex_unlock(&fd_log_lock);
|
||||
}
|
||||
|
||||
/* Log a debug message */
|
||||
void fd_log_va ( int loglevel, const char * format, va_list args )
|
||||
{
|
||||
(void)pthread_mutex_lock(&fd_log_lock);
|
||||
|
||||
pthread_cleanup_push(fd_cleanup_mutex_silent, &fd_log_lock);
|
||||
fd_logger(loglevel, format, args);
|
||||
pthread_cleanup_pop(0);
|
||||
|
||||
(void)pthread_mutex_unlock(&fd_log_lock);
|
||||
}
|
||||
|
||||
/* Function to set the thread's friendly name */
|
||||
void fd_log_threadname ( const char * name )
|
||||
{
|
||||
void * val = NULL;
|
||||
|
||||
TRACE_ENTRY("%p(%s)", name, name?:"/");
|
||||
|
||||
/* First, check if a value is already assigned to the current thread */
|
||||
val = pthread_getspecific(fd_log_thname);
|
||||
if (TRACE_BOOL(ANNOYING)) {
|
||||
if (val) {
|
||||
fd_log_debug("(Thread '%s' renamed to '%s')", (char *)val, name?:"(nil)");
|
||||
} else {
|
||||
fd_log_debug("(Thread %p named '%s')", (void *)pthread_self(), name?:"(nil)");
|
||||
}
|
||||
}
|
||||
if (val != NULL) {
|
||||
free(val);
|
||||
}
|
||||
|
||||
/* Now create the new string */
|
||||
if (name == NULL) {
|
||||
CHECK_POSIX_DO( pthread_setspecific(fd_log_thname, NULL), /* continue */);
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_MALLOC_DO( val = strdup(name), return );
|
||||
|
||||
CHECK_POSIX_DO( pthread_setspecific(fd_log_thname, val), /* continue */);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write time into a buffer */
|
||||
char * fd_log_time ( struct timespec * ts, char * buf, size_t len, int incl_date, int incl_ms )
|
||||
{
|
||||
int ret;
|
||||
size_t offset = 0;
|
||||
struct timespec tp;
|
||||
struct tm tm;
|
||||
|
||||
/* Get current time */
|
||||
if (!ts) {
|
||||
ret = clock_gettime(CLOCK_REALTIME, &tp);
|
||||
if (ret != 0) {
|
||||
snprintf(buf, len, "%s", strerror(ret));
|
||||
return buf;
|
||||
}
|
||||
ts = &tp;
|
||||
}
|
||||
|
||||
offset += strftime(buf + offset, len - offset, incl_date?"%D,%T":"%T", localtime_r( &ts->tv_sec , &tm ));
|
||||
if (incl_ms)
|
||||
offset += snprintf(buf + offset, len - offset, ".%6.6ld", ts->tv_nsec / 1000);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
static size_t sys_mempagesz = 0;
|
||||
|
||||
static size_t get_mempagesz(void) {
|
||||
if (!sys_mempagesz) {
|
||||
sys_mempagesz = sysconf(_SC_PAGESIZE); /* We alloc buffer by memory pages for efficiency. This can be readjusted if too memory consuming */
|
||||
if (sys_mempagesz <= 0)
|
||||
sys_mempagesz = 256; /* default size if above call failed */
|
||||
}
|
||||
return sys_mempagesz;
|
||||
}
|
||||
|
||||
|
||||
/* Helper function for fd_*_dump. Prints the format string from 'offset' into '*buf', extends if needed. The location of buf can be updated by this function. */
|
||||
char * fd_dump_extend(char ** buf, size_t *len, size_t *offset, const char * format, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int to_write;
|
||||
size_t o = 0;
|
||||
size_t mempagesz = get_mempagesz();
|
||||
|
||||
/* we do not TRACE_ENTRY this one on purpose */
|
||||
|
||||
CHECK_PARAMS_DO(buf && len, return NULL);
|
||||
|
||||
if (*buf == NULL) {
|
||||
CHECK_MALLOC_DO(*buf = malloc(mempagesz), return NULL);
|
||||
*len = mempagesz;
|
||||
}
|
||||
|
||||
if (offset)
|
||||
o = *offset;
|
||||
|
||||
va_start(ap, format);
|
||||
to_write = vsnprintf(*buf + o, *len - o, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (to_write + o >= *len) {
|
||||
/* There was no room in the buffer, we extend and redo */
|
||||
size_t new_len = (((to_write + o) / mempagesz) + 1) * mempagesz;
|
||||
CHECK_MALLOC_DO(*buf = realloc(*buf, new_len), return NULL);
|
||||
*len = new_len;
|
||||
|
||||
va_start(ap, format);
|
||||
to_write = vsnprintf(*buf + o, *len - o, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (offset)
|
||||
*offset += to_write;
|
||||
|
||||
return *buf;
|
||||
}
|
||||
|
||||
char * fd_dump_extend_hexdump(char ** buf, size_t *len, size_t *offset, uint8_t *data, size_t datalen, size_t trunc, size_t wrap )
|
||||
{
|
||||
int truncated = 0;
|
||||
size_t towrite = 0;
|
||||
size_t o = 0;
|
||||
int i;
|
||||
char * p;
|
||||
size_t mempagesz = get_mempagesz();
|
||||
#define TRUNK_MARK "[...]"
|
||||
|
||||
CHECK_PARAMS_DO(buf && len && data, return NULL);
|
||||
|
||||
if (trunc && (datalen > trunc)) {
|
||||
datalen = trunc;
|
||||
truncated = 1;
|
||||
}
|
||||
|
||||
towrite = datalen * 2;
|
||||
|
||||
if (wrap)
|
||||
towrite += datalen / wrap; /* add 1 '\n' every wrap byte */
|
||||
|
||||
if (truncated)
|
||||
towrite += CONSTSTRLEN(TRUNK_MARK);
|
||||
|
||||
|
||||
if (offset)
|
||||
o = *offset;
|
||||
|
||||
if (*buf == NULL) {
|
||||
/* Directly allocate the size we need */
|
||||
*len = (((towrite + o) / mempagesz) + 1 ) * mempagesz;
|
||||
CHECK_MALLOC_DO(*buf = malloc(*len), return NULL);
|
||||
} else if ((towrite + o) >= *len) {
|
||||
/* There is no room in the buffer, we extend and redo */
|
||||
size_t new_len = (((towrite + o) / mempagesz) + 1) * mempagesz;
|
||||
CHECK_MALLOC_DO(*buf = realloc(*buf, new_len), return NULL);
|
||||
*len = new_len;
|
||||
}
|
||||
|
||||
p = *buf + o;
|
||||
for (i = 0; i < datalen; i++) {
|
||||
sprintf(p, "%02hhX", data[i]);
|
||||
p+=2;
|
||||
if ((wrap) && ((i+1) % wrap == 0)) {
|
||||
*p++='\n'; *p ='\0'; /* we want to ensure the buffer is always 0-terminated */
|
||||
}
|
||||
}
|
||||
|
||||
if (truncated)
|
||||
memcpy(p, TRUNK_MARK, CONSTSTRLEN(TRUNK_MARK));
|
||||
|
||||
if (offset)
|
||||
*offset += towrite;
|
||||
|
||||
return *buf;
|
||||
}
|
||||
2869
plat/diameter/libfdproto/messages.c
Normal file
2869
plat/diameter/libfdproto/messages.c
Normal file
File diff suppressed because it is too large
Load Diff
563
plat/diameter/libfdproto/ostr.c
Normal file
563
plat/diameter/libfdproto/ostr.c
Normal file
@@ -0,0 +1,563 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
#if (!defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT))
|
||||
/* Process IDNA with stringprep -- See RFC5890 -- and libidn documentation... */
|
||||
#include <idna.h> /* idna_to_ascii_8z() */
|
||||
#endif /* !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT) */
|
||||
|
||||
/* Similar to strdup with (must have been verified) os0_t */
|
||||
os0_t os0dup_int(os0_t s, size_t l) {
|
||||
os0_t r;
|
||||
CHECK_MALLOC_DO( r = malloc(l+1), return NULL );
|
||||
if (l)
|
||||
memcpy(r, s, l); /* this might be faster than a strcpy or strdup because it can work with 32 or 64b blocks */
|
||||
r[l] = '\0';
|
||||
return r;
|
||||
}
|
||||
|
||||
/* case sensitive comparison, fast */
|
||||
int fd_os_cmp_int(uint8_t * os1, size_t os1sz, uint8_t * os2, size_t os2sz)
|
||||
{
|
||||
ASSERT( os1 && os2);
|
||||
if (os1sz < os2sz)
|
||||
return -1;
|
||||
if (os1sz > os2sz)
|
||||
return 1;
|
||||
return os1sz ? memcmp(os1, os2, os1sz) : 0;
|
||||
}
|
||||
|
||||
/* a local version of tolower() that does not depend on LC_CTYPE locale */
|
||||
static uint8_t asciitolower(uint8_t a)
|
||||
{
|
||||
if ((a >= 'A') && (a <= 'Z'))
|
||||
return a + 32 /* == 'a' - 'A' */;
|
||||
return a;
|
||||
}
|
||||
|
||||
/* less sensitive to case, slower. */
|
||||
/* the semantics of "maybefurther" assume you are searching for os1 in a list of elements ordered, each element passed as os2 */
|
||||
int fd_os_almostcasesrch_int(uint8_t * os1, size_t os1sz, uint8_t * os2, size_t os2sz, int *maybefurther)
|
||||
{
|
||||
int i;
|
||||
int res = 0;
|
||||
|
||||
ASSERT( os1 && os2);
|
||||
if (maybefurther)
|
||||
*maybefurther = 0;
|
||||
|
||||
if (os1sz < os2sz)
|
||||
return -1;
|
||||
|
||||
if (maybefurther)
|
||||
*maybefurther = 1;
|
||||
|
||||
if (os1sz > os2sz)
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < os1sz; i++) {
|
||||
if (os1[i] == os2[i])
|
||||
continue;
|
||||
|
||||
if (!res)
|
||||
res = os1[i] < os2[i] ? -1 : 1;
|
||||
|
||||
if (asciitolower(os1[i]) == asciitolower(os2[i]))
|
||||
continue;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if the string contains only ASCII */
|
||||
int fd_os_is_valid_DiameterIdentity(uint8_t * os, size_t ossz)
|
||||
{
|
||||
#ifdef DIAMID_IDNA_IGNORE
|
||||
|
||||
/* Allow anything */
|
||||
|
||||
#else /* DIAMID_IDNA_IGNORE */
|
||||
|
||||
int i;
|
||||
|
||||
/* Allow only letters, digits, hyphen, dot */
|
||||
for (i=0; i < ossz; i++) {
|
||||
if (os[i] > 'z')
|
||||
break;
|
||||
if (os[i] >= 'a')
|
||||
continue;
|
||||
if ((os[i] >= 'A') && (os[i] <= 'Z'))
|
||||
continue;
|
||||
if ((os[i] == '-') || (os[i] == '.'))
|
||||
continue;
|
||||
if ((os[i] == '@') )
|
||||
continue;
|
||||
if ((os[i] >= '0') && (os[i] <= '9'))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (i < ossz) {
|
||||
int nb = 1;
|
||||
/* To get a better display, check if the invalid char is UTF-8 */
|
||||
if ((os[i] & 0xE0) == 0xC0 /* 110xxxxx */) {
|
||||
if ((i < ossz - 1) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */))
|
||||
nb = 2;
|
||||
goto disp;
|
||||
}
|
||||
if ((os[i] & 0xF0) == 0xE0 /* 1110xxxx */) {
|
||||
if ((i < ossz - 2) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */))
|
||||
nb = 3;
|
||||
goto disp;
|
||||
}
|
||||
if ((os[i] & 0xF8) == 0xF0 /* 11110xxx */) {
|
||||
if ((i < ossz - 3) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */))
|
||||
nb = 4;
|
||||
goto disp;
|
||||
}
|
||||
if ((os[i] & 0xFC) == 0xF8 /* 111110xx */) {
|
||||
if ((i < ossz - 4) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 4] & 0xC0) == 0x80 /* 10xxxxxx */))
|
||||
nb = 5;
|
||||
goto disp;
|
||||
}
|
||||
if ((os[i] & 0xFE) == 0xFC /* 1111110x */) {
|
||||
if ((i < ossz - 5) && ((os[i + 1] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 2] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 3] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 4] & 0xC0) == 0x80 /* 10xxxxxx */)
|
||||
&& ((os[i + 5] & 0xC0) == 0x80 /* 10xxxxxx */))
|
||||
nb = 6;
|
||||
goto disp;
|
||||
}
|
||||
/* otherwise, we just display the hex code */
|
||||
TRACE_DEBUG(INFO, "Invalid character (0x%hhX) at offset %d in DiameterIdentity '%.*s'", os[i], i+1, (int)ossz, os);
|
||||
return 0;
|
||||
disp:
|
||||
TRACE_DEBUG(INFO, "Invalid character '%.*s' at offset %d in DiameterIdentity '%.*s'", nb, os + i, i+1, (int)ossz, os);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* DIAMID_IDNA_IGNORE */
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The following function validates a string as a Diameter Identity or applies the IDNA transformation on it
|
||||
if *inoutsz is != 0 on entry, *id may not be \0-terminated.
|
||||
memory has the following meaning: 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static)
|
||||
*/
|
||||
int fd_os_validate_DiameterIdentity(char ** id, size_t * inoutsz, int memory)
|
||||
{
|
||||
#if !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT)
|
||||
int gotsize = 0;
|
||||
#endif /* defined(DIAMID_IDNA_IGNORE) || defined(DIAMID_IDNA_REJECT) */
|
||||
|
||||
TRACE_ENTRY("%p %p", id, inoutsz);
|
||||
CHECK_PARAMS( id && *id && inoutsz );
|
||||
|
||||
if (!*inoutsz)
|
||||
*inoutsz = strlen(*id);
|
||||
#if !defined(DIAMID_IDNA_IGNORE) && !defined(DIAMID_IDNA_REJECT)
|
||||
else
|
||||
gotsize = 1;
|
||||
#endif /* defined(DIAMID_IDNA_IGNORE) || defined(DIAMID_IDNA_REJECT) */
|
||||
|
||||
#ifndef DIAMID_IDNA_IGNORE
|
||||
|
||||
if (!fd_os_is_valid_DiameterIdentity((os0_t)*id, *inoutsz)) {
|
||||
|
||||
#ifdef DIAMID_IDNA_REJECT
|
||||
|
||||
TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity!", *id);
|
||||
TRACE_DEBUG(INFO, "Returning EINVAL since fD is compiled with option DIAMID_IDNA_REJECT.");
|
||||
return EINVAL;
|
||||
|
||||
#else /* DIAMID_IDNA_REJECT */
|
||||
|
||||
char *processed;
|
||||
int ret;
|
||||
|
||||
if (gotsize) { /* make it \0-terminated */
|
||||
if (memory) {
|
||||
CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
|
||||
memory = 0;
|
||||
} else {
|
||||
CHECK_MALLOC( *id = realloc(*id, *inoutsz + 1) );
|
||||
(*id)[*inoutsz] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
ret = idna_to_ascii_8z ( *id, &processed, IDNA_USE_STD3_ASCII_RULES );
|
||||
if (ret == IDNA_SUCCESS) {
|
||||
TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity, it was changed to '%s'", *id, processed);
|
||||
if (memory == 0)
|
||||
free(*id);
|
||||
*id = processed;
|
||||
*inoutsz = strlen(processed);
|
||||
/* Done! */
|
||||
} else {
|
||||
TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity and cannot be sanitanized: %s", *id, idna_strerror (ret));
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
#endif /* DIAMID_IDNA_REJECT */
|
||||
} else
|
||||
#endif /* ! DIAMID_IDNA_IGNORE */
|
||||
{
|
||||
if (memory == 1) {
|
||||
CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Analyze a DiameterURI and return its components.
|
||||
Return EINVAL if the URI is not valid.
|
||||
*diamid is malloc'd on function return and must be freed (it is processed by fd_os_validate_DiameterIdentity).
|
||||
*secure is 0 (no security) or 1 (security enabled) on return.
|
||||
*port is 0 (default) or a value in host byte order on return.
|
||||
*transport is 0 (default) or IPPROTO_* on return.
|
||||
*proto is 0 (default) or 'd' (diameter), 'r' (radius), or 't' (tacacs+) on return.
|
||||
*/
|
||||
int fd_os_parse_DiameterURI(uint8_t * uri, size_t urisz, DiamId_t * diamid, size_t * diamidlen, int * secure, uint16_t * port, int * transport, char *proto)
|
||||
{
|
||||
size_t offset = 0;
|
||||
DiamId_t fqdn = NULL;
|
||||
size_t fqdnlen;
|
||||
TRACE_ENTRY("%p %zd %p %p %p %p %p %p", uri, urisz, diamid, diamidlen, secure, port, transport, proto);
|
||||
CHECK_PARAMS( uri && urisz );
|
||||
|
||||
CHECK_PARAMS( urisz > 7 ); /* "aaa" + "://" + something else at least */
|
||||
|
||||
/* Initialize values */
|
||||
if (secure)
|
||||
*secure = 0;
|
||||
if (port)
|
||||
*port = 0;
|
||||
if (transport)
|
||||
*transport = 0;
|
||||
if (proto)
|
||||
*proto = 0;
|
||||
|
||||
/* Check the beginning */
|
||||
if (memcmp( uri, "aaa", 3)) {
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa'", 3, uri);
|
||||
return EINVAL;
|
||||
}
|
||||
offset += 3;
|
||||
|
||||
/* Secure? */
|
||||
if (uri[offset] == (uint8_t)'s') {
|
||||
if (secure)
|
||||
*secure = 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
/* Remaining of URI marker */
|
||||
if (memcmp( uri + offset, "://", 3)) {
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa://' or 'aaas://'", (int)offset + 3, uri);
|
||||
return EINVAL;
|
||||
}
|
||||
offset += 3;
|
||||
|
||||
/* This is the start of the FQDN */
|
||||
fqdn = (DiamId_t)uri + offset;
|
||||
for ( ; offset < urisz ; offset++ ) {
|
||||
/* Stop only when we find ':' or ';' */
|
||||
if ((uri[offset] == (uint8_t)':') || (uri[offset] == (uint8_t)';'))
|
||||
break;
|
||||
}
|
||||
fqdnlen = offset - (fqdn - (DiamId_t)uri);
|
||||
CHECK_FCT(fd_os_validate_DiameterIdentity(&fqdn, &fqdnlen, 1));
|
||||
if (diamid)
|
||||
*diamid = fqdn;
|
||||
else
|
||||
free(fqdn);
|
||||
if (diamidlen)
|
||||
*diamidlen = fqdnlen;
|
||||
|
||||
if (offset == urisz)
|
||||
return 0; /* Finished */
|
||||
|
||||
/* Is there a port ? */
|
||||
if (uri[offset] == ':') {
|
||||
uint16_t p = 0;
|
||||
do {
|
||||
offset++;
|
||||
|
||||
if (offset == urisz)
|
||||
break;
|
||||
|
||||
uint32_t t = (uint32_t)((char)uri[offset] - '0');
|
||||
if (t > 9)
|
||||
break; /* we did not get a digit */
|
||||
|
||||
t += p * 10; /* the port is specified in decimal base */
|
||||
|
||||
if (t >= (1<<16)) {
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI: port value is too big.");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
p = t;
|
||||
} while (1);
|
||||
|
||||
if (port)
|
||||
*port = p;
|
||||
}
|
||||
|
||||
if (offset == urisz)
|
||||
return 0; /* Finished */
|
||||
|
||||
/* Is there a transport? */
|
||||
if ( (urisz - offset > CONSTSTRLEN(";transport="))
|
||||
&& !strncasecmp((char *)uri + offset, ";transport=", CONSTSTRLEN(";transport=")) ) {
|
||||
|
||||
offset += CONSTSTRLEN(";transport=");
|
||||
|
||||
if (urisz - offset < 3) {
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is too short, ignored.");
|
||||
return 0;
|
||||
}
|
||||
if (!strncasecmp((char *)uri + offset, "tcp", CONSTSTRLEN("tcp"))) {
|
||||
if (transport)
|
||||
*transport = IPPROTO_TCP;
|
||||
offset += CONSTSTRLEN("tcp");
|
||||
goto after_transport;
|
||||
}
|
||||
if (!strncasecmp((char *)uri + offset, "udp", CONSTSTRLEN("udp"))) {
|
||||
if (transport)
|
||||
*transport = IPPROTO_UDP;
|
||||
offset += CONSTSTRLEN("udp");
|
||||
goto after_transport;
|
||||
}
|
||||
if ((urisz - offset > 3) && !strncasecmp((char *)uri + offset, "sctp", CONSTSTRLEN("sctp"))) {
|
||||
if (transport) {
|
||||
#ifndef DISABLE_SCTP
|
||||
*transport = IPPROTO_SCTP;
|
||||
#else /* DISABLE_SCTP */
|
||||
TRACE_DEBUG(INFO, "Received DiameterURI with 'transport=sctp' but DISABLE_SCTP was selected");
|
||||
*transport = 0;
|
||||
#endif /* DISABLE_SCTP */
|
||||
}
|
||||
offset += CONSTSTRLEN("sctp");
|
||||
goto after_transport;
|
||||
}
|
||||
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
|
||||
return EINVAL;
|
||||
}
|
||||
after_transport:
|
||||
if (offset == urisz)
|
||||
return 0; /* Finished */
|
||||
|
||||
/* Is there a protocol? */
|
||||
if ( ((urisz - offset) > CONSTSTRLEN(";protocol="))
|
||||
&& (!strncasecmp((char *)uri + offset, ";protocol=", CONSTSTRLEN(";protocol="))) ) {
|
||||
|
||||
offset += CONSTSTRLEN(";protocol=");
|
||||
|
||||
if ( ((urisz - offset) >= CONSTSTRLEN("diameter"))
|
||||
&& (!strncasecmp((char *)uri + offset, "diameter", CONSTSTRLEN("diameter"))) ) {
|
||||
if (proto)
|
||||
*proto = 'd';
|
||||
offset += CONSTSTRLEN("diameter");
|
||||
goto after_proto;
|
||||
}
|
||||
|
||||
if ( ((urisz - offset) >= CONSTSTRLEN("radius"))
|
||||
&& (!strncasecmp((char *)uri + offset, "radius", CONSTSTRLEN("radius"))) ) {
|
||||
if (proto)
|
||||
*proto = 'r';
|
||||
offset += CONSTSTRLEN("radius");
|
||||
goto after_proto;
|
||||
}
|
||||
|
||||
if ( ((urisz - offset) >= CONSTSTRLEN("tacacs+"))
|
||||
&& (!strncasecmp((char *)uri + offset, "tacacs+", CONSTSTRLEN("tacacs+"))) ) {
|
||||
if (proto)
|
||||
*proto = 't';
|
||||
offset += CONSTSTRLEN("tacacs+");
|
||||
goto after_proto;
|
||||
}
|
||||
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI: protocol string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
|
||||
return EINVAL;
|
||||
|
||||
}
|
||||
after_proto:
|
||||
if (offset == urisz)
|
||||
return 0; /* Finished */
|
||||
|
||||
TRACE_DEBUG(INFO, "Invalid DiameterURI: final part of string is not recognized ('%.*s').", (int)(urisz - offset), uri + offset);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
|
||||
/********************************************************************************************************/
|
||||
/* Hash function -- credits to Austin Appleby, thank you ^^ */
|
||||
/* See http://murmurhash.googlepages.com for more information on this function */
|
||||
|
||||
/* the strings are NOT always aligned properly (ex: received in RADIUS message), so we use the aligned MurmurHash2 function as needed */
|
||||
#define _HASH_MIX(h,k,m) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
|
||||
uint32_t fd_os_hash ( uint8_t * string, size_t len )
|
||||
{
|
||||
uint32_t hash = len;
|
||||
uint8_t * data = string;
|
||||
|
||||
const unsigned int m = 0x5bd1e995;
|
||||
const int r = 24;
|
||||
int align = (long)string & 3;
|
||||
|
||||
if (!align || (len < 4)) {
|
||||
/* In case data is aligned, MurmurHash2 function */
|
||||
while(len >= 4)
|
||||
{
|
||||
/* Mix 4 bytes at a time into the hash */
|
||||
uint32_t k = *(uint32_t *)data; /* We don't care about the byte order */
|
||||
|
||||
_HASH_MIX(hash, k, m);
|
||||
|
||||
data += 4;
|
||||
len -= 4;
|
||||
}
|
||||
|
||||
/* Handle the last few bytes of the input */
|
||||
switch(len) {
|
||||
case 3: hash ^= data[2] << 16;
|
||||
case 2: hash ^= data[1] << 8;
|
||||
case 1: hash ^= data[0];
|
||||
hash *= m;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Unaligned data, use alignment-safe slower version */
|
||||
|
||||
/* Pre-load the temp registers */
|
||||
uint32_t t = 0, d = 0;
|
||||
switch(align)
|
||||
{
|
||||
case 1: t |= data[2] << 16;
|
||||
case 2: t |= data[1] << 8;
|
||||
case 3: t |= data[0];
|
||||
}
|
||||
t <<= (8 * align);
|
||||
|
||||
data += 4-align;
|
||||
len -= 4-align;
|
||||
|
||||
/* From this point, "data" can be read by chunks of 4 bytes */
|
||||
|
||||
int sl = 8 * (4-align);
|
||||
int sr = 8 * align;
|
||||
|
||||
/* Mix */
|
||||
while(len >= 4)
|
||||
{
|
||||
uint32_t k;
|
||||
|
||||
d = *(unsigned int *)data;
|
||||
k = (t >> sr) | (d << sl);
|
||||
|
||||
_HASH_MIX(hash, k, m);
|
||||
|
||||
t = d;
|
||||
|
||||
data += 4;
|
||||
len -= 4;
|
||||
}
|
||||
|
||||
/* Handle leftover data in temp registers */
|
||||
d = 0;
|
||||
if(len >= align)
|
||||
{
|
||||
uint32_t k;
|
||||
|
||||
switch(align)
|
||||
{
|
||||
case 3: d |= data[2] << 16;
|
||||
case 2: d |= data[1] << 8;
|
||||
case 1: d |= data[0];
|
||||
}
|
||||
|
||||
k = (t >> sr) | (d << sl);
|
||||
_HASH_MIX(hash, k, m);
|
||||
|
||||
data += align;
|
||||
len -= align;
|
||||
|
||||
/* Handle tail bytes */
|
||||
|
||||
switch(len)
|
||||
{
|
||||
case 3: hash ^= data[2] << 16;
|
||||
case 2: hash ^= data[1] << 8;
|
||||
case 1: hash ^= data[0];
|
||||
hash *= m;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(len)
|
||||
{
|
||||
case 3: d |= data[2] << 16;
|
||||
case 2: d |= data[1] << 8;
|
||||
case 1: d |= data[0];
|
||||
case 0: hash ^= (t >> sr) | (d << sl);
|
||||
hash *= m;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Do a few final mixes of the hash to ensure the last few
|
||||
bytes are well-incorporated. */
|
||||
hash ^= hash >> 13;
|
||||
hash *= m;
|
||||
hash ^= hash >> 15;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
70
plat/diameter/libfdproto/portability.c
Normal file
70
plat/diameter/libfdproto/portability.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2012, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* Replacement for clock_gettime for the Mac OS */
|
||||
#ifndef HAVE_CLOCK_GETTIME
|
||||
int clock_gettime(int clk_id, struct timespec* ts)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday (&tv, NULL);
|
||||
ts->tv_sec = tv.tv_sec;
|
||||
ts->tv_nsec = tv.tv_usec * 1000;
|
||||
return 0;
|
||||
}
|
||||
#endif /* HAVE_CLOCK_GETTIME */
|
||||
|
||||
/* Replacement for strndup for the Mac OS */
|
||||
#ifndef HAVE_STRNDUP
|
||||
char * strndup (char *str, size_t len)
|
||||
{
|
||||
char * output;
|
||||
size_t outlen;
|
||||
|
||||
output = memchr(str, 0, len);
|
||||
if (output == NULL) {
|
||||
outlen = len;
|
||||
} else {
|
||||
outlen = output - str;
|
||||
}
|
||||
|
||||
CHECK_MALLOC_DO( output = malloc (outlen + 1), return NULL );
|
||||
|
||||
output[outlen] = '\0';
|
||||
memcpy (output, str, outlen);
|
||||
return output;
|
||||
}
|
||||
#endif /* HAVE_STRNDUP */
|
||||
338
plat/diameter/libfdproto/rt_data.c
Normal file
338
plat/diameter/libfdproto/rt_data.c
Normal file
@@ -0,0 +1,338 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
/* Routing module helpers.
|
||||
*
|
||||
* This file provides support for the rt_data structure manipulation.
|
||||
*/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/* Structure that contains the routing data for a message */
|
||||
struct rt_data {
|
||||
int extracted; /* if 0, candidates is ordered by diamid, otherwise the order is unspecified. This also counts the number of times the message was (re-)sent, as a side effect */
|
||||
struct fd_list candidates; /* All the candidates. Items are struct rtd_candidate. */
|
||||
struct fd_list errors; /* All errors received from other peers for this message */
|
||||
};
|
||||
|
||||
/* Items of the errors list */
|
||||
struct rtd_error {
|
||||
struct fd_list chain; /* link in the list, ordered by nexthop (fd_os_cmp) */
|
||||
DiamId_t nexthop;/* the peer the message was sent to */
|
||||
size_t nexthoplen; /* cached string length */
|
||||
DiamId_t erh; /* the origin of the error */
|
||||
size_t erhlen; /* cached string length */
|
||||
uint32_t code; /* the error code */
|
||||
};
|
||||
|
||||
/* Create a new structure to store routing data */
|
||||
int fd_rtd_init(struct rt_data ** rtd)
|
||||
{
|
||||
struct rt_data *new;
|
||||
TRACE_ENTRY("%p", rtd);
|
||||
CHECK_PARAMS(rtd);
|
||||
|
||||
/* Alloc the structure */
|
||||
CHECK_MALLOC( new = malloc(sizeof(struct rt_data)) );
|
||||
memset(new, 0, sizeof(struct rt_data) );
|
||||
fd_list_init(&new->candidates, new);
|
||||
fd_list_init(&new->errors, new);
|
||||
|
||||
*rtd = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Destroy the routing data */
|
||||
void fd_rtd_free(struct rt_data ** rtd)
|
||||
{
|
||||
struct rt_data *old;
|
||||
|
||||
TRACE_ENTRY("%p", rtd);
|
||||
CHECK_PARAMS_DO(rtd, return );
|
||||
|
||||
old = *rtd;
|
||||
*rtd = NULL;
|
||||
|
||||
while (!FD_IS_LIST_EMPTY(&old->candidates)) {
|
||||
struct rtd_candidate * c = (struct rtd_candidate *) old->candidates.next;
|
||||
|
||||
fd_list_unlink(&c->chain);
|
||||
free(c->diamid);
|
||||
free(c->realm);
|
||||
free(c);
|
||||
}
|
||||
|
||||
while (!FD_IS_LIST_EMPTY(&old->errors)) {
|
||||
struct rtd_error * c = (struct rtd_error *) old->errors.next;
|
||||
|
||||
fd_list_unlink(&c->chain);
|
||||
free(c->nexthop);
|
||||
free(c->erh);
|
||||
free(c);
|
||||
}
|
||||
|
||||
free(old);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add a peer to the candidates list. The source is our local peer list, so no need to care for the case here. */
|
||||
int fd_rtd_candidate_add(struct rt_data * rtd, DiamId_t peerid, size_t peeridlen, DiamId_t realm, size_t realmlen)
|
||||
{
|
||||
struct fd_list * prev;
|
||||
struct rtd_candidate * new;
|
||||
|
||||
TRACE_ENTRY("%p %p %zd %p %zd", rtd, peerid, peeridlen, realm, realmlen);
|
||||
CHECK_PARAMS(rtd && peerid && peeridlen);
|
||||
|
||||
/* Since the peers are ordered when they are added (fd_g_activ_peers) we search for the position from the end -- this should be efficient */
|
||||
for (prev = rtd->candidates.prev; prev != &rtd->candidates; prev = prev->prev) {
|
||||
struct rtd_candidate * cp = (struct rtd_candidate *) prev;
|
||||
int cmp = fd_os_cmp(peerid, peeridlen, cp->diamid, cp->diamidlen);
|
||||
if (cmp > 0)
|
||||
break;
|
||||
if (cmp == 0)
|
||||
/* The candidate is already in the list */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create the new entry */
|
||||
CHECK_MALLOC( new = malloc(sizeof(struct rtd_candidate)) );
|
||||
memset(new, 0, sizeof(struct rtd_candidate) );
|
||||
fd_list_init(&new->chain, new);
|
||||
CHECK_MALLOC( new->diamid = os0dup(peerid, peeridlen) )
|
||||
new->diamidlen = peeridlen;
|
||||
if (realm) {
|
||||
CHECK_MALLOC( new->realm = os0dup(realm, realmlen) )
|
||||
new->realmlen = realmlen;
|
||||
}
|
||||
|
||||
/* insert in the list at the correct position */
|
||||
fd_list_insert_after(prev, &new->chain);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove a peer from the candidates (if it is found). Case insensitive search since the names are received from other peers */
|
||||
void fd_rtd_candidate_del(struct rt_data * rtd, uint8_t * id, size_t idsz)
|
||||
{
|
||||
struct fd_list * li;
|
||||
|
||||
TRACE_ENTRY("%p %p %zd", rtd, id, idsz);
|
||||
CHECK_PARAMS_DO( rtd && id && idsz, return );
|
||||
|
||||
if (!fd_os_is_valid_DiameterIdentity(id, idsz))
|
||||
/* it cannot be in the list */
|
||||
return;
|
||||
|
||||
for (li = rtd->candidates.next; li != &rtd->candidates; li = li->next) {
|
||||
struct rtd_candidate * c = (struct rtd_candidate *) li;
|
||||
int cont;
|
||||
int cmp = fd_os_almostcasesrch(id, idsz, c->diamid, c->diamidlen, &cont);
|
||||
|
||||
if (!cmp) {
|
||||
/* Found it! Remove it */
|
||||
fd_list_unlink(&c->chain);
|
||||
free(c->diamid);
|
||||
free(c->realm);
|
||||
free(c);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cont)
|
||||
continue;
|
||||
|
||||
/* The list is guaranteed to be ordered only if not extracted */
|
||||
if (! rtd->extracted)
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* If a peer returned a protocol error for this message, save it so that we don't try to send it there again.
|
||||
Case insensitive search since the names are received from other peers*/
|
||||
int fd_rtd_error_add(struct rt_data * rtd, DiamId_t sentto, size_t senttolen, uint8_t * origin, size_t originsz, uint32_t rcode, struct fd_list ** candidates, int * sendingattemtps)
|
||||
{
|
||||
struct fd_list * li;
|
||||
int match = 0;
|
||||
|
||||
TRACE_ENTRY("%p %p %zd %p %zd %u %p %p", rtd, sentto, senttolen, origin, originsz, rcode, candidates, sendingattemtps);
|
||||
CHECK_PARAMS( rtd && sentto && senttolen ); /* origin may be NULL */
|
||||
|
||||
/* First add the new error entry */
|
||||
for (li = rtd->errors.next; li != &rtd->errors; li = li->next) {
|
||||
struct rtd_error * e = (struct rtd_error *) li;
|
||||
int cmp = fd_os_cmp(sentto, senttolen, e->nexthop, e->nexthoplen);
|
||||
if (cmp > 0)
|
||||
continue;
|
||||
if (!cmp)
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we already had this entry, we should not have sent the message again to this peer... anyway, let's close our eyes. */
|
||||
/* in the normal case, we save the error */
|
||||
if (!match) {
|
||||
/* Add a new entry in the error list */
|
||||
struct rtd_error * new;
|
||||
CHECK_MALLOC( new = malloc(sizeof(struct rtd_error)) );
|
||||
memset(new, 0, sizeof(struct rtd_error));
|
||||
fd_list_init(&new->chain, NULL);
|
||||
|
||||
CHECK_MALLOC(new->nexthop = os0dup(sentto, senttolen));
|
||||
new->nexthoplen = senttolen;
|
||||
|
||||
if (origin) {
|
||||
if (!originsz) {
|
||||
originsz=strlen((char *)origin);
|
||||
} else {
|
||||
if (!fd_os_is_valid_DiameterIdentity(origin, originsz)){
|
||||
TRACE_DEBUG(FULL, "Received error %d from peer with invalid Origin-Host AVP, not saved", rcode);
|
||||
origin = NULL;
|
||||
goto after_origin;
|
||||
}
|
||||
}
|
||||
CHECK_MALLOC( new->erh = (DiamId_t)os0dup(origin, originsz) );
|
||||
new->erhlen = originsz;
|
||||
}
|
||||
after_origin:
|
||||
new->code = rcode;
|
||||
fd_list_insert_before(li, &new->chain);
|
||||
}
|
||||
|
||||
/* Finally, remove this (these) peers from the candidate list */
|
||||
fd_rtd_candidate_del(rtd, (os0_t)sentto, senttolen);
|
||||
if (origin)
|
||||
fd_rtd_candidate_del(rtd, origin, originsz);
|
||||
|
||||
if (candidates)
|
||||
*candidates = &rtd->candidates;
|
||||
|
||||
if (sendingattemtps)
|
||||
*sendingattemtps = rtd->extracted;
|
||||
|
||||
/* Done! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Only retrieve the number of times this message has been processed by the routing-out mechanism (i.e. number of times it was failed over) */
|
||||
int fd_rtd_get_nb_attempts(struct rt_data * rtd, int * sendingattemtps)
|
||||
{
|
||||
TRACE_ENTRY("%p %p", rtd, sendingattemtps);
|
||||
CHECK_PARAMS( rtd && sendingattemtps );
|
||||
|
||||
*sendingattemtps = rtd->extracted;
|
||||
|
||||
/* Done! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Extract the list of valid candidates, and initialize their scores */
|
||||
void fd_rtd_candidate_extract(struct rt_data * rtd, struct fd_list ** candidates, int ini_score)
|
||||
{
|
||||
struct fd_list * li;
|
||||
|
||||
TRACE_ENTRY("%p %p", rtd, candidates);
|
||||
CHECK_PARAMS_DO( candidates, return );
|
||||
CHECK_PARAMS_DO( rtd, { *candidates = NULL; return; } );
|
||||
|
||||
*candidates = &rtd->candidates;
|
||||
|
||||
/* Reset all scores to INITIAL score */
|
||||
for (li = rtd->candidates.next; li != &rtd->candidates; li = li->next) {
|
||||
struct rtd_candidate * c = (struct rtd_candidate *) li;
|
||||
c->score = ini_score;
|
||||
}
|
||||
|
||||
rtd->extracted += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reorder the list of peers. If several peer have the same highest score, they are randomized. */
|
||||
int fd_rtd_candidate_reorder(struct fd_list * candidates)
|
||||
{
|
||||
struct fd_list unordered = FD_LIST_INITIALIZER(unordered), *li;
|
||||
struct fd_list highest = FD_LIST_INITIALIZER(highest);
|
||||
int hs = -1;
|
||||
|
||||
TRACE_ENTRY("%p", candidates);
|
||||
CHECK_PARAMS( candidates );
|
||||
|
||||
/* First, move all items from candidates to the undordered list */
|
||||
fd_list_move_end(&unordered, candidates);
|
||||
|
||||
/* Now extract each element from unordered and add it back to list ordered by score */
|
||||
while (!FD_IS_LIST_EMPTY(&unordered)) {
|
||||
struct rtd_candidate * c = (struct rtd_candidate *) unordered.next;
|
||||
|
||||
fd_list_unlink(&c->chain);
|
||||
|
||||
/* If this candidate has a higher score than the previous ones */
|
||||
if (c->score > hs) {
|
||||
/* Then we move the previous high score items at end of the list */
|
||||
fd_list_move_end(candidates, &highest);
|
||||
|
||||
/* And the new high score is set */
|
||||
hs = c->score;
|
||||
}
|
||||
|
||||
/* If this candidate equals the higher score, add it into highest list at a random place */
|
||||
if (c->score == hs) {
|
||||
if (rand() & 1) {
|
||||
fd_list_insert_after(&highest, &c->chain);
|
||||
} else {
|
||||
fd_list_insert_before(&highest, &c->chain);
|
||||
}
|
||||
/* Otherwise, insert at normal place in the list */
|
||||
} else {
|
||||
/* Find the position in ordered candidates list */
|
||||
for (li = candidates->next; li != candidates; li = li->next) {
|
||||
struct rtd_candidate * cnext = (struct rtd_candidate *) li;
|
||||
if (cnext->score >= c->score)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Add the element there */
|
||||
fd_list_insert_before(li, &c->chain);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now simply move back all the "highest" candidates at the end of the list */
|
||||
fd_list_move_end(candidates, &highest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
940
plat/diameter/libfdproto/sessions.c
Normal file
940
plat/diameter/libfdproto/sessions.c
Normal file
@@ -0,0 +1,940 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
/* Sessions module.
|
||||
*
|
||||
* Basic functionalities to help implementing User sessions state machines from RFC3588.
|
||||
*/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
/*********************** Parameters **********************/
|
||||
|
||||
/* Size of the hash table containing the session objects (pow of 2. ex: 6 => 2^6 = 64). must be between 0 and 31. */
|
||||
#ifndef SESS_HASH_SIZE
|
||||
#define SESS_HASH_SIZE 6
|
||||
#endif /* SESS_HASH_SIZE */
|
||||
|
||||
/* Default lifetime of a session, in seconds. (31 days = 2678400 seconds) */
|
||||
#ifndef SESS_DEFAULT_LIFETIME
|
||||
#define SESS_DEFAULT_LIFETIME 2678400
|
||||
#endif /* SESS_DEFAULT_LIFETIME */
|
||||
|
||||
/********************** /Parameters **********************/
|
||||
|
||||
/* Eyescatchers definitions */
|
||||
#define SH_EYEC 0x53554AD1
|
||||
#define SD_EYEC 0x5355D474
|
||||
#define SI_EYEC 0x53551D
|
||||
|
||||
/* Macro to check an object is valid */
|
||||
#define VALIDATE_SH( _obj ) ( ((_obj) != NULL) && ( ((struct session_handler *)(_obj))->eyec == SH_EYEC) )
|
||||
#define VALIDATE_SI( _obj ) ( ((_obj) != NULL) && ( ((struct session *)(_obj))->eyec == SI_EYEC) )
|
||||
|
||||
|
||||
/* Handlers registered by users of the session module */
|
||||
struct session_handler {
|
||||
int eyec; /* An eye catcher also used to ensure the object is valid, must be SH_EYEC */
|
||||
int id; /* A unique integer to identify this handler */
|
||||
void (*cleanup)(struct sess_state *, os0_t, void *); /* The cleanup function to be called for cleaning a state */
|
||||
session_state_dump state_dump; /* dumper function */
|
||||
void *opaque; /* a value that is passed as is to the cleanup callback */
|
||||
};
|
||||
|
||||
static int hdl_id = 0; /* A global counter to initialize the id field */
|
||||
static pthread_mutex_t hdl_lock = PTHREAD_MUTEX_INITIALIZER; /* lock to protect hdl_id; we could use atomic operations otherwise (less portable) */
|
||||
|
||||
|
||||
/* Data structures linked from the sessions, containing the applications states */
|
||||
struct state {
|
||||
int eyec; /* Must be SD_EYEC */
|
||||
struct sess_state *state; /* The state registered by the application, never NULL (or the whole object is deleted) */
|
||||
struct fd_list chain; /* Chaining in the list of session's states ordered by hdl->id */
|
||||
union {
|
||||
struct session_handler *hdl; /* The handler for which this state was registered */
|
||||
os0_t sid; /* For deleted state, the sid of the session it belong to */
|
||||
};
|
||||
};
|
||||
|
||||
/* Session object, one for each value of Session-Id AVP */
|
||||
struct session {
|
||||
int eyec; /* Eyecatcher, SI_EYEC */
|
||||
|
||||
os0_t sid; /* The \0-terminated Session-Id */
|
||||
size_t sidlen; /* cached length of sid */
|
||||
uint32_t hash; /* computed hash of sid */
|
||||
struct fd_list chain_h;/* chaining in the hash table of sessions. */
|
||||
|
||||
struct timespec timeout;/* Timeout date for the session */
|
||||
struct fd_list expire; /* List of expiring sessions, ordered by timeouts. */
|
||||
|
||||
pthread_mutex_t stlock; /* A lock to protect the list of states associated with this session */
|
||||
struct fd_list states; /* Sentinel for the list of states of this session. */
|
||||
int msg_cnt;/* Reference counter for the messages pointing to this session */
|
||||
int is_destroyed; /* boolean telling if fd_sess_detroy has been called on this */
|
||||
};
|
||||
|
||||
/* Sessions hash table, to allow fast sid to session retrieval */
|
||||
static struct {
|
||||
struct fd_list sentinel; /* sentinel element for this sublist. The sublist is ordered by hash value, then fd_os_cmp(sid). */
|
||||
pthread_mutex_t lock; /* the mutex for this sublist -- we might probably change it to rwlock for a little optimization */
|
||||
} sess_hash [ 1 << SESS_HASH_SIZE ] ;
|
||||
#define H_MASK( __hash ) ((__hash) & (( 1 << SESS_HASH_SIZE ) - 1))
|
||||
#define H_LIST( _hash ) (&(sess_hash[H_MASK(_hash)].sentinel))
|
||||
#define H_LOCK( _hash ) (&(sess_hash[H_MASK(_hash)].lock ))
|
||||
|
||||
static uint32_t sess_cnt = 0; /* counts all active session (that are in the expiry list) */
|
||||
|
||||
/* The following are used to generate sid values that are eternaly unique */
|
||||
static uint32_t sid_h; /* initialized to the current time in fd_sess_init */
|
||||
static uint32_t sid_l; /* incremented each time a session id is created */
|
||||
static pthread_mutex_t sid_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* Expiring sessions management */
|
||||
static struct fd_list exp_sentinel = FD_LIST_INITIALIZER(exp_sentinel); /* list of sessions ordered by their timeout date */
|
||||
static pthread_mutex_t exp_lock = PTHREAD_MUTEX_INITIALIZER; /* lock protecting the list. */
|
||||
static pthread_cond_t exp_cond = PTHREAD_COND_INITIALIZER; /* condvar used by the expiry mecahinsm. */
|
||||
static pthread_t exp_thr = (pthread_t)NULL; /* The expiry thread that handles cleanup of expired sessions */
|
||||
|
||||
/* Hierarchy of the locks, to avoid deadlocks:
|
||||
* hash lock > state lock > expiry lock
|
||||
* i.e. state lock can be taken while holding the hash lock, but not while holding the expiry lock.
|
||||
* As well, the hash lock cannot be taken while holding a state lock.
|
||||
*/
|
||||
|
||||
/********************************************************************************************************/
|
||||
|
||||
/* Initialize a session object. It is not linked now. sid must be already malloc'ed. The hash has already been computed. */
|
||||
static struct session * new_session(os0_t sid, size_t sidlen, uint32_t hash)
|
||||
{
|
||||
struct session * sess;
|
||||
|
||||
TRACE_ENTRY("%p %zd", sid, sidlen);
|
||||
CHECK_PARAMS_DO( sid && sidlen, return NULL );
|
||||
|
||||
CHECK_MALLOC_DO( sess = malloc(sizeof(struct session)), return NULL );
|
||||
memset(sess, 0, sizeof(struct session));
|
||||
|
||||
sess->eyec = SI_EYEC;
|
||||
|
||||
sess->sid = sid;
|
||||
sess->sidlen = sidlen;
|
||||
sess->hash = hash;
|
||||
fd_list_init(&sess->chain_h, sess);
|
||||
|
||||
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &sess->timeout), return NULL );
|
||||
sess->timeout.tv_sec += SESS_DEFAULT_LIFETIME;
|
||||
fd_list_init(&sess->expire, sess);
|
||||
|
||||
CHECK_POSIX_DO( pthread_mutex_init(&sess->stlock, NULL), return NULL );
|
||||
fd_list_init(&sess->states, sess);
|
||||
|
||||
return sess;
|
||||
}
|
||||
|
||||
/* destroy the session object. It should really be already unlinked... */
|
||||
static void del_session(struct session * s)
|
||||
{
|
||||
ASSERT(FD_IS_LIST_EMPTY(&s->states));
|
||||
free(s->sid);
|
||||
fd_list_unlink(&s->chain_h);
|
||||
fd_list_unlink(&s->expire);
|
||||
CHECK_POSIX_DO( pthread_mutex_destroy(&s->stlock), /* continue */ );
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* The expiry thread */
|
||||
static void * exp_fct(void * arg)
|
||||
{
|
||||
fd_log_threadname ( "Session/expire" );
|
||||
TRACE_ENTRY( "" );
|
||||
|
||||
|
||||
do {
|
||||
struct timespec now;
|
||||
struct session * first;
|
||||
|
||||
CHECK_POSIX_DO( pthread_mutex_lock(&exp_lock), break );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
|
||||
again:
|
||||
/* Check if there are expiring sessions available */
|
||||
if (FD_IS_LIST_EMPTY(&exp_sentinel)) {
|
||||
/* Just wait for a change or cancelation */
|
||||
CHECK_POSIX_DO( pthread_cond_wait( &exp_cond, &exp_lock ), break /* this might not pop the cleanup handler, but since we ASSERT(0), it is not the big issue... */ );
|
||||
/* Restart the loop on wakeup */
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Get the pointer to the session that expires first */
|
||||
first = (struct session *)(exp_sentinel.next->o);
|
||||
ASSERT( VALIDATE_SI(first) );
|
||||
|
||||
/* Get the current time */
|
||||
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &now), break );
|
||||
|
||||
/* If first session is not expired, we just wait until it happens */
|
||||
if ( TS_IS_INFERIOR( &now, &first->timeout ) ) {
|
||||
|
||||
CHECK_POSIX_DO2( pthread_cond_timedwait( &exp_cond, &exp_lock, &first->timeout ),
|
||||
ETIMEDOUT, /* ETIMEDOUT is a normal error, continue */,
|
||||
/* on other error, */ break );
|
||||
|
||||
/* on wakeup, loop */
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Now, the first session in the list is expired; destroy it */
|
||||
pthread_cleanup_pop( 0 );
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock(&exp_lock), break );
|
||||
|
||||
CHECK_FCT_DO( fd_sess_destroy( &first ), break );
|
||||
|
||||
} while (1);
|
||||
|
||||
TRACE_DEBUG(INFO, "A system error occurred in session module! Expiry thread is terminating...");
|
||||
ASSERT(0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/********************************************************************************************************/
|
||||
|
||||
/* Initialize the session module */
|
||||
int fd_sess_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
TRACE_ENTRY( "" );
|
||||
|
||||
/* Initialize the global counters */
|
||||
sid_h = (uint32_t) time(NULL);
|
||||
sid_l = 0;
|
||||
|
||||
/* Initialize the hash table */
|
||||
for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
|
||||
fd_list_init( &sess_hash[i].sentinel, NULL );
|
||||
CHECK_POSIX( pthread_mutex_init(&sess_hash[i].lock, NULL) );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Run this when initializations are complete. */
|
||||
int fd_sess_start(void)
|
||||
{
|
||||
/* Start session garbage collector (expiry) */
|
||||
CHECK_POSIX( pthread_create(&exp_thr, NULL, exp_fct, NULL) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Terminate */
|
||||
void fd_sess_fini(void)
|
||||
{
|
||||
TRACE_ENTRY("");
|
||||
CHECK_FCT_DO( fd_thr_term(&exp_thr), /* continue */ );
|
||||
|
||||
/* Destroy all sessions in the hash table, and the hash table itself? -- How to do it without a race condition ? */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new handler */
|
||||
int fd_sess_handler_create ( struct session_handler ** handler, void (*cleanup)(struct sess_state *, os0_t, void *), session_state_dump dumper, void * opaque )
|
||||
{
|
||||
struct session_handler *new;
|
||||
|
||||
TRACE_ENTRY("%p %p", handler, cleanup);
|
||||
|
||||
CHECK_PARAMS( handler && cleanup );
|
||||
|
||||
CHECK_MALLOC( new = malloc(sizeof(struct session_handler)) );
|
||||
memset(new, 0, sizeof(struct session_handler));
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock(&hdl_lock) );
|
||||
new->id = ++hdl_id;
|
||||
CHECK_POSIX( pthread_mutex_unlock(&hdl_lock) );
|
||||
|
||||
new->eyec = SH_EYEC;
|
||||
new->cleanup = cleanup;
|
||||
new->state_dump = dumper;
|
||||
new->opaque = opaque;
|
||||
|
||||
*handler = new;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Destroy a handler, and all states attached to this handler. This operation is very slow but we don't care since it's rarely used.
|
||||
* Note that it's better to call this function after all sessions have been deleted... */
|
||||
int fd_sess_handler_destroy ( struct session_handler ** handler, void ** opaque )
|
||||
{
|
||||
struct session_handler * del;
|
||||
/* place to save the list of states to be cleaned up. We do it after finding them to avoid deadlocks. the "o" field becomes a copy of the sid. */
|
||||
struct fd_list deleted_states = FD_LIST_INITIALIZER( deleted_states );
|
||||
int i;
|
||||
|
||||
TRACE_ENTRY("%p", handler);
|
||||
CHECK_PARAMS( handler && VALIDATE_SH(*handler) );
|
||||
|
||||
del = *handler;
|
||||
*handler = NULL;
|
||||
|
||||
del->eyec = 0xdead; /* The handler is not valid anymore for any other operation */
|
||||
|
||||
/* Now find all sessions with data registered for this handler, and move this data to the deleted_states list. */
|
||||
for (i = 0; i < sizeof(sess_hash) / sizeof(sess_hash[0]); i++) {
|
||||
struct fd_list * li_si;
|
||||
CHECK_POSIX( pthread_mutex_lock(&sess_hash[i].lock) );
|
||||
|
||||
for (li_si = sess_hash[i].sentinel.next; li_si != &sess_hash[i].sentinel; li_si = li_si->next) { /* for each session in the hash line */
|
||||
struct fd_list * li_st;
|
||||
struct session * sess = (struct session *)(li_si->o);
|
||||
CHECK_POSIX( pthread_mutex_lock(&sess->stlock) );
|
||||
for (li_st = sess->states.next; li_st != &sess->states; li_st = li_st->next) { /* for each state in this session */
|
||||
struct state * st = (struct state *)(li_st->o);
|
||||
/* The list is ordered */
|
||||
if (st->hdl->id < del->id)
|
||||
continue;
|
||||
if (st->hdl->id == del->id) {
|
||||
/* This state belongs to the handler we are deleting, move the item to the deleted_states list */
|
||||
fd_list_unlink(&st->chain);
|
||||
st->sid = sess->sid;
|
||||
fd_list_insert_before(&deleted_states, &st->chain);
|
||||
}
|
||||
break;
|
||||
}
|
||||
CHECK_POSIX( pthread_mutex_unlock(&sess->stlock) );
|
||||
}
|
||||
CHECK_POSIX( pthread_mutex_unlock(&sess_hash[i].lock) );
|
||||
}
|
||||
|
||||
/* Now, delete all states after calling their cleanup handler */
|
||||
while (!FD_IS_LIST_EMPTY(&deleted_states)) {
|
||||
struct state * st = (struct state *)(deleted_states.next->o);
|
||||
TRACE_DEBUG(FULL, "Calling cleanup handler for session '%s' and data %p", st->sid, st->state);
|
||||
(*del->cleanup)(st->state, st->sid, del->opaque);
|
||||
fd_list_unlink(&st->chain);
|
||||
free(st);
|
||||
}
|
||||
|
||||
if (opaque)
|
||||
*opaque = del->opaque;
|
||||
|
||||
/* Free the handler */
|
||||
free(del);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Create a new session object with the default timeout value, and link it. The refcount is increased by 1, whether the session existed or not */
|
||||
int fd_sess_new ( struct session ** session, DiamId_t diamid, size_t diamidlen, uint8_t * opt, size_t optlen )
|
||||
{
|
||||
os0_t sid = NULL;
|
||||
size_t sidlen;
|
||||
uint32_t hash;
|
||||
struct session * sess;
|
||||
struct fd_list * li;
|
||||
int found = 0;
|
||||
int ret = 0;
|
||||
|
||||
TRACE_ENTRY("%p %p %zd %p %zd", session, diamid, diamidlen, opt, optlen);
|
||||
CHECK_PARAMS( session && (diamid || opt) );
|
||||
|
||||
if (diamid) {
|
||||
if (!diamidlen) {
|
||||
diamidlen = strlen(diamid);
|
||||
}
|
||||
/* We check if the string is a valid DiameterIdentity */
|
||||
CHECK_PARAMS( fd_os_is_valid_DiameterIdentity((uint8_t *)diamid, diamidlen) );
|
||||
} else {
|
||||
diamidlen = 0;
|
||||
}
|
||||
if (opt) {
|
||||
if (!optlen) {
|
||||
optlen = strlen((char *)opt);
|
||||
} else {
|
||||
CHECK_PARAMS( fd_os_is_valid_os0(opt, optlen) );
|
||||
}
|
||||
} else {
|
||||
optlen = 0;
|
||||
}
|
||||
|
||||
/* Ok, first create the identifier for the string */
|
||||
if (diamid == NULL) {
|
||||
/* opt is the full string */
|
||||
CHECK_MALLOC( sid = os0dup(opt, optlen) );
|
||||
sidlen = optlen;
|
||||
} else {
|
||||
uint32_t sid_h_cpy;
|
||||
uint32_t sid_l_cpy;
|
||||
/* "<diamId>;<high32>;<low32>[;opt]" */
|
||||
sidlen = diamidlen;
|
||||
sidlen += 22; /* max size of ';<high32>;<low32>' */
|
||||
if (opt)
|
||||
sidlen += 1 + optlen; /* ';opt' */
|
||||
sidlen++; /* space for the final \0 also */
|
||||
CHECK_MALLOC( sid = malloc(sidlen) );
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock(&sid_lock) );
|
||||
if ( ++sid_l == 0 ) /* overflow */
|
||||
++sid_h;
|
||||
sid_h_cpy = sid_h;
|
||||
sid_l_cpy = sid_l;
|
||||
CHECK_POSIX( pthread_mutex_unlock(&sid_lock) );
|
||||
|
||||
if (opt) {
|
||||
sidlen = snprintf((char*)sid, sidlen, "%.*s;%u;%u;%.*s", (int)diamidlen, diamid, sid_h_cpy, sid_l_cpy, (int)optlen, opt);
|
||||
} else {
|
||||
sidlen = snprintf((char*)sid, sidlen, "%.*s;%u;%u", (int)diamidlen, diamid, sid_h_cpy, sid_l_cpy);
|
||||
}
|
||||
}
|
||||
|
||||
hash = fd_os_hash(sid, sidlen);
|
||||
|
||||
/* Now find the place to add this object in the hash table. */
|
||||
CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash) ) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
|
||||
|
||||
for (li = H_LIST(hash)->next; li != H_LIST(hash); li = li->next) {
|
||||
int cmp;
|
||||
struct session * s = (struct session *)(li->o);
|
||||
|
||||
/* The list is ordered by hash and sid (in case of collisions) */
|
||||
if (s->hash < hash)
|
||||
continue;
|
||||
if (s->hash > hash)
|
||||
break;
|
||||
|
||||
cmp = fd_os_cmp(s->sid, s->sidlen, sid, sidlen);
|
||||
if (cmp < 0)
|
||||
continue;
|
||||
if (cmp > 0)
|
||||
break;
|
||||
|
||||
/* A session with the same sid was already in the hash table */
|
||||
found = 1;
|
||||
*session = s;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the session did not exist, we can create it & link it in global tables */
|
||||
if (!found) {
|
||||
CHECK_MALLOC_DO(sess = new_session(sid, sidlen, hash),
|
||||
{
|
||||
ret = ENOMEM;
|
||||
free(sid);
|
||||
goto out;
|
||||
} );
|
||||
|
||||
fd_list_insert_before(li, &sess->chain_h); /* hash table */
|
||||
sess->msg_cnt++;
|
||||
} else {
|
||||
free(sid);
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
|
||||
(*session)->msg_cnt++;
|
||||
CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
|
||||
|
||||
/* it was found: was it previously destroyed? */
|
||||
if ((*session)->is_destroyed == 0) {
|
||||
ret = EALREADY;
|
||||
goto out;
|
||||
} else {
|
||||
/* the session was marked destroyed, let's re-activate it. */
|
||||
sess = *session;
|
||||
sess->is_destroyed = 0;
|
||||
|
||||
/* update the expiry time */
|
||||
CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &sess->timeout), { ASSERT(0); } );
|
||||
sess->timeout.tv_sec += SESS_DEFAULT_LIFETIME;
|
||||
}
|
||||
}
|
||||
|
||||
/* We must insert in the expiry list */
|
||||
CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
|
||||
|
||||
/* Find the position in that list. We take it in reverse order */
|
||||
for (li = exp_sentinel.prev; li != &exp_sentinel; li = li->prev) {
|
||||
struct session * s = (struct session *)(li->o);
|
||||
if (TS_IS_INFERIOR( &s->timeout, &sess->timeout ) )
|
||||
break;
|
||||
}
|
||||
fd_list_insert_after( li, &sess->expire );
|
||||
sess_cnt++;
|
||||
|
||||
/* We added a new expiring element, we must signal */
|
||||
if (li == &exp_sentinel) {
|
||||
CHECK_POSIX_DO( pthread_cond_signal(&exp_cond), { ASSERT(0); } ); /* if it fails, we might not pop the cleanup handlers, but this should not happen -- and we'd have a serious problem otherwise */
|
||||
}
|
||||
|
||||
/* We're done with the locked part */
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); } ); /* if it fails, we might not pop the cleanup handler, but this should not happen -- and we'd have a serious problem otherwise */
|
||||
|
||||
out:
|
||||
;
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
|
||||
|
||||
if (ret) /* in case of error */
|
||||
return ret;
|
||||
|
||||
*session = sess;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find or create a session -- the msg refcount is increased */
|
||||
int fd_sess_fromsid_msg ( uint8_t * sid, size_t len, struct session ** session, int * new)
|
||||
{
|
||||
int ret;
|
||||
|
||||
TRACE_ENTRY("%p %zd %p %p", sid, len, session, new);
|
||||
CHECK_PARAMS( sid && session );
|
||||
|
||||
if (!fd_os_is_valid_os0(sid,len)) {
|
||||
TRACE_DEBUG(INFO, "Warning: a Session-Id value contains \\0 chars... (len:%zd, begin:'%.*s') => Debug messages may be truncated.", len, (int)len, sid);
|
||||
}
|
||||
|
||||
/* All the work is done in sess_new */
|
||||
ret = fd_sess_new ( session, NULL, 0, sid, len );
|
||||
switch (ret) {
|
||||
case 0:
|
||||
case EALREADY:
|
||||
break;
|
||||
|
||||
default:
|
||||
CHECK_FCT(ret);
|
||||
}
|
||||
|
||||
if (new)
|
||||
*new = ret ? 0 : 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the sid of a session */
|
||||
int fd_sess_getsid ( struct session * session, os0_t * sid, size_t * sidlen )
|
||||
{
|
||||
TRACE_ENTRY("%p %p", session, sid);
|
||||
|
||||
CHECK_PARAMS( VALIDATE_SI(session) && sid );
|
||||
|
||||
*sid = session->sid;
|
||||
if (sidlen)
|
||||
*sidlen = session->sidlen;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Change the timeout value of a session */
|
||||
int fd_sess_settimeout( struct session * session, const struct timespec * timeout )
|
||||
{
|
||||
struct fd_list * li;
|
||||
|
||||
TRACE_ENTRY("%p %p", session, timeout);
|
||||
CHECK_PARAMS( VALIDATE_SI(session) && timeout );
|
||||
|
||||
/* Lock -- do we need to lock the hash table as well? I don't think so... */
|
||||
CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &exp_lock );
|
||||
|
||||
/* Update the timeout */
|
||||
fd_list_unlink(&session->expire);
|
||||
memcpy(&session->timeout, timeout, sizeof(struct timespec));
|
||||
|
||||
/* Find the new position in expire list. We take it in normal order */
|
||||
for (li = exp_sentinel.next; li != &exp_sentinel; li = li->next) {
|
||||
struct session * s = (struct session *)(li->o);
|
||||
|
||||
if (TS_IS_INFERIOR( &s->timeout, &session->timeout ) )
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
fd_list_insert_before( li, &session->expire );
|
||||
|
||||
/* We added a new expiring element, we must signal if it was in first position */
|
||||
if (session->expire.prev == &exp_sentinel) {
|
||||
CHECK_POSIX_DO( pthread_cond_signal(&exp_cond), { ASSERT(0); /* so that we don't have a pending cancellation handler */ } );
|
||||
}
|
||||
|
||||
/* We're done */
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock( &exp_lock ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Destroy the states associated to a session, and mark it destroyed. */
|
||||
int fd_sess_destroy ( struct session ** session )
|
||||
{
|
||||
struct session * sess;
|
||||
int destroy_now;
|
||||
os0_t sid;
|
||||
int ret = 0;
|
||||
|
||||
/* place to save the list of states to be cleaned up. We do it after finding them to avoid deadlocks. the "o" field becomes a copy of the sid. */
|
||||
struct fd_list deleted_states = FD_LIST_INITIALIZER( deleted_states );
|
||||
|
||||
TRACE_ENTRY("%p", session);
|
||||
CHECK_PARAMS( session && VALIDATE_SI(*session) );
|
||||
|
||||
sess = *session;
|
||||
*session = NULL;
|
||||
|
||||
/* Lock the hash line */
|
||||
CHECK_POSIX( pthread_mutex_lock( H_LOCK(sess->hash) ) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(sess->hash) );
|
||||
|
||||
/* Unlink from the expiry list */
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &exp_lock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
|
||||
if (!FD_IS_LIST_EMPTY(&sess->expire)) {
|
||||
sess_cnt--;
|
||||
fd_list_unlink( &sess->expire ); /* no need to signal the condition here */
|
||||
}
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
|
||||
|
||||
/* Now move all states associated to this session into deleted_states */
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &sess->stlock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
|
||||
while (!FD_IS_LIST_EMPTY(&sess->states)) {
|
||||
struct state * st = (struct state *)(sess->states.next->o);
|
||||
fd_list_unlink(&st->chain);
|
||||
fd_list_insert_before(&deleted_states, &st->chain);
|
||||
}
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &sess->stlock ), { ASSERT(0); /* otherwise cleanup handler is not pop'd */ } );
|
||||
|
||||
/* Mark the session as destroyed */
|
||||
destroy_now = (sess->msg_cnt == 0);
|
||||
if (destroy_now) {
|
||||
fd_list_unlink( &sess->chain_h );
|
||||
sid = sess->sid;
|
||||
} else {
|
||||
sess->is_destroyed = 1;
|
||||
CHECK_MALLOC_DO( sid = os0dup(sess->sid, sess->sidlen), ret = ENOMEM );
|
||||
}
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock( H_LOCK(sess->hash) ) );
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Now, really delete the states */
|
||||
while (!FD_IS_LIST_EMPTY(&deleted_states)) {
|
||||
struct state * st = (struct state *)(deleted_states.next->o);
|
||||
fd_list_unlink(&st->chain);
|
||||
TRACE_DEBUG(FULL, "Calling handler %p cleanup for state %p registered with session '%s'", st->hdl, st, sid);
|
||||
(*st->hdl->cleanup)(st->state, sid, st->hdl->opaque);
|
||||
free(st);
|
||||
}
|
||||
|
||||
/* Finally, destroy the session itself, if it is not referrenced by any message anymore */
|
||||
if (destroy_now) {
|
||||
del_session(sess);
|
||||
} else {
|
||||
free(sid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Destroy a session if it is not used */
|
||||
int fd_sess_reclaim ( struct session ** session )
|
||||
{
|
||||
struct session * sess;
|
||||
uint32_t hash;
|
||||
int destroy_now = 0;
|
||||
|
||||
TRACE_ENTRY("%p", session);
|
||||
CHECK_PARAMS( session && VALIDATE_SI(*session) );
|
||||
|
||||
sess = *session;
|
||||
hash = sess->hash;
|
||||
*session = NULL;
|
||||
|
||||
CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash) ) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &sess->stlock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &sess->stlock );
|
||||
CHECK_POSIX_DO( pthread_mutex_lock( &exp_lock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
|
||||
|
||||
/* We only do something if the states list is empty */
|
||||
if (FD_IS_LIST_EMPTY(&sess->states)) {
|
||||
/* In this case, we do as in destroy */
|
||||
fd_list_unlink( &sess->expire );
|
||||
destroy_now = (sess->msg_cnt == 0);
|
||||
if (destroy_now) {
|
||||
fd_list_unlink(&sess->chain_h);
|
||||
} else {
|
||||
/* just mark it as destroyed, it will be freed when the last message stops referencing it */
|
||||
sess->is_destroyed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &exp_lock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock( &sess->stlock ), { ASSERT(0); /* otherwise, cleanup not poped on FreeBSD */ } );
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
|
||||
|
||||
if (destroy_now)
|
||||
del_session(sess);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Save a state information with a session */
|
||||
int fd_sess_state_store ( struct session_handler * handler, struct session * session, struct sess_state ** state )
|
||||
{
|
||||
struct state *new;
|
||||
struct fd_list * li;
|
||||
int already = 0;
|
||||
int ret = 0;
|
||||
|
||||
TRACE_ENTRY("%p %p %p", handler, session, state);
|
||||
CHECK_PARAMS( handler && VALIDATE_SH(handler) && session && VALIDATE_SI(session) && (!session->is_destroyed) && state );
|
||||
|
||||
/* Lock the session state list */
|
||||
CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
|
||||
|
||||
/* Create the new state object */
|
||||
CHECK_MALLOC_DO(new = malloc(sizeof(struct state)), { ret = ENOMEM; goto out; } );
|
||||
memset(new, 0, sizeof(struct state));
|
||||
|
||||
new->eyec = SD_EYEC;
|
||||
new->state= *state;
|
||||
fd_list_init(&new->chain, new);
|
||||
new->hdl = handler;
|
||||
|
||||
/* find place for this state in the list */
|
||||
for (li = session->states.next; li != &session->states; li = li->next) {
|
||||
struct state * st = (struct state *)(li->o);
|
||||
/* The list is ordered by handler's id */
|
||||
if (st->hdl->id < handler->id)
|
||||
continue;
|
||||
|
||||
if (st->hdl->id == handler->id) {
|
||||
TRACE_DEBUG(INFO, "A state was already stored for session '%s' and handler '%p', at location %p", session->sid, st->hdl, st->state);
|
||||
already = EALREADY;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!already) {
|
||||
fd_list_insert_before(li, &new->chain);
|
||||
*state = NULL;
|
||||
} else {
|
||||
free(new);
|
||||
}
|
||||
out:
|
||||
;
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
|
||||
|
||||
return ret ?: already;
|
||||
}
|
||||
|
||||
/* Get the data back */
|
||||
int fd_sess_state_retrieve ( struct session_handler * handler, struct session * session, struct sess_state ** state )
|
||||
{
|
||||
struct fd_list * li;
|
||||
struct state * st = NULL;
|
||||
|
||||
TRACE_ENTRY("%p %p %p", handler, session, state);
|
||||
CHECK_PARAMS( handler && VALIDATE_SH(handler) && session && VALIDATE_SI(session) && state );
|
||||
|
||||
*state = NULL;
|
||||
|
||||
/* Lock the session state list */
|
||||
CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
|
||||
|
||||
/* find the state in the list */
|
||||
for (li = session->states.next; li != &session->states; li = li->next) {
|
||||
st = (struct state *)(li->o);
|
||||
|
||||
/* The list is ordered by handler's id */
|
||||
if (st->hdl->id > handler->id)
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we found the state */
|
||||
if (st && (st->hdl == handler)) {
|
||||
fd_list_unlink(&st->chain);
|
||||
*state = st->state;
|
||||
free(st);
|
||||
}
|
||||
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For the messages module */
|
||||
int fd_sess_fromsid ( uint8_t * sid, size_t len, struct session ** session, int * new)
|
||||
{
|
||||
TRACE_ENTRY("%p %zd %p %p", sid, len, session, new);
|
||||
CHECK_PARAMS( sid && len && session );
|
||||
|
||||
/* Get the session object */
|
||||
CHECK_FCT( fd_sess_fromsid_msg ( sid, len, session, new) );
|
||||
|
||||
/* Decrease the refcount */
|
||||
CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
|
||||
(*session)->msg_cnt--; /* was increased in fd_sess_new */
|
||||
CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
|
||||
|
||||
/* Done */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_sess_ref_msg ( struct session * session )
|
||||
{
|
||||
TRACE_ENTRY("%p", session);
|
||||
CHECK_PARAMS( VALIDATE_SI(session) );
|
||||
|
||||
/* Update the msg refcount */
|
||||
CHECK_POSIX( pthread_mutex_lock(&session->stlock) );
|
||||
session->msg_cnt++;
|
||||
CHECK_POSIX( pthread_mutex_unlock(&session->stlock) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fd_sess_reclaim_msg ( struct session ** session )
|
||||
{
|
||||
int reclaim;
|
||||
uint32_t hash;
|
||||
|
||||
TRACE_ENTRY("%p", session);
|
||||
CHECK_PARAMS( session && VALIDATE_SI(*session) );
|
||||
|
||||
/* Lock the hash line to avoid possibility that session is freed while we are reclaiming */
|
||||
hash = (*session)->hash;
|
||||
CHECK_POSIX( pthread_mutex_lock( H_LOCK(hash)) );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, H_LOCK(hash) );
|
||||
|
||||
/* Update the msg refcount */
|
||||
CHECK_POSIX( pthread_mutex_lock(&(*session)->stlock) );
|
||||
reclaim = (*session)->msg_cnt;
|
||||
(*session)->msg_cnt = reclaim - 1;
|
||||
CHECK_POSIX( pthread_mutex_unlock(&(*session)->stlock) );
|
||||
|
||||
/* Ok, now unlock the hash line */
|
||||
pthread_cleanup_pop( 0 );
|
||||
CHECK_POSIX( pthread_mutex_unlock( H_LOCK(hash) ) );
|
||||
|
||||
/* and reclaim if no message references the session anymore */
|
||||
if (reclaim == 1) {
|
||||
CHECK_FCT(fd_sess_reclaim ( session ));
|
||||
} else {
|
||||
*session = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Dump functions */
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump, struct session * session, int with_states)
|
||||
{
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{session}(@%p): ", session), return NULL);
|
||||
|
||||
if (!VALIDATE_SI(session)) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
|
||||
} else {
|
||||
char timebuf[30];
|
||||
struct tm tm;
|
||||
|
||||
strftime(timebuf, sizeof(timebuf), "%D,%T", localtime_r( &session->timeout.tv_sec , &tm ));
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "'%s'(%zd) h:%x m:%d d:%d to:%s.%06ld",
|
||||
session->sid, session->sidlen, session->hash, session->msg_cnt, session->is_destroyed,
|
||||
timebuf, session->timeout.tv_nsec/1000),
|
||||
return NULL);
|
||||
|
||||
if (with_states) {
|
||||
struct fd_list * li;
|
||||
CHECK_POSIX_DO( pthread_mutex_lock(&session->stlock), /* ignore */ );
|
||||
pthread_cleanup_push( fd_cleanup_mutex, &session->stlock );
|
||||
|
||||
for (li = session->states.next; li != &session->states; li = li->next) {
|
||||
struct state * st = (struct state *)(li->o);
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "\n {state i:%d}(@%p): ", st->hdl->id, st), return NULL);
|
||||
if (st->hdl->state_dump) {
|
||||
CHECK_MALLOC_DO( (*st->hdl->state_dump)( FD_DUMP_STD_PARAMS, st->state),
|
||||
fd_dump_extend( FD_DUMP_STD_PARAMS, "[dumper error]"));
|
||||
} else {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "<%p>", st->state), return NULL);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cleanup_pop(0);
|
||||
CHECK_POSIX_DO( pthread_mutex_unlock(&session->stlock), /* ignore */ );
|
||||
}
|
||||
}
|
||||
|
||||
return *buf;
|
||||
}
|
||||
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_sess_dump_hdl, struct session_handler * handler)
|
||||
{
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "{sesshdl}(@%p): ", handler), return NULL);
|
||||
|
||||
if (!VALIDATE_SH(handler)) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "INVALID/NULL"), return NULL);
|
||||
} else {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "i:%d cl:%p d:%p o:%p", handler->id, handler->cleanup, handler->state_dump, handler->opaque), return NULL);
|
||||
}
|
||||
return *buf;
|
||||
}
|
||||
|
||||
int fd_sess_getcount(uint32_t *cnt)
|
||||
{
|
||||
CHECK_PARAMS(cnt);
|
||||
CHECK_POSIX( pthread_mutex_lock( &exp_lock ) );
|
||||
*cnt = sess_cnt;
|
||||
CHECK_POSIX( pthread_mutex_unlock( &exp_lock ) );
|
||||
return 0;
|
||||
}
|
||||
84
plat/diameter/libfdproto/utils.c
Normal file
84
plat/diameter/libfdproto/utils.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
|
||||
DECLARE_FD_DUMP_PROTOTYPE(fd_sa_dump, sSA * sa, int flags)
|
||||
{
|
||||
char addrbuf[INET6_ADDRSTRLEN];
|
||||
char servbuf[32];
|
||||
int rc;
|
||||
FD_DUMP_HANDLE_OFFSET();
|
||||
|
||||
servbuf[0] = 0;
|
||||
|
||||
if (sa) {
|
||||
if (sSAport(sa)) {
|
||||
rc = getnameinfo(sa, sSAlen( sa ), addrbuf, sizeof(addrbuf), servbuf, sizeof(servbuf), flags);
|
||||
} else {
|
||||
rc = getnameinfo(sa, sSAlen( sa ), addrbuf, sizeof(addrbuf), NULL, 0, flags);
|
||||
}
|
||||
if (rc) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "%s", gai_strerror(rc)), return NULL);
|
||||
} else {
|
||||
if (servbuf[0]) {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "%s(%s)", &addrbuf[0], &servbuf[0]), return NULL);
|
||||
} else {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "%s", &addrbuf[0]), return NULL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CHECK_MALLOC_DO( fd_dump_extend( FD_DUMP_STD_PARAMS, "(NULL / ANY)"), return NULL);
|
||||
}
|
||||
return *buf;
|
||||
}
|
||||
|
||||
void fd_sa_sdump_numeric(char * buf /* must be at least sSA_DUMP_STRLEN */, sSA * sa)
|
||||
{
|
||||
char addrbuf[INET6_ADDRSTRLEN];
|
||||
char servbuf[32];
|
||||
|
||||
if (sa) {
|
||||
int rc = getnameinfo(sa, sSAlen( sa ), addrbuf, sizeof(addrbuf), servbuf, sizeof(servbuf), NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
if (rc) {
|
||||
snprintf(buf, sSA_DUMP_STRLEN, "%s", gai_strerror(rc));
|
||||
} else {
|
||||
snprintf(buf, sSA_DUMP_STRLEN, "%s(%s)", addrbuf, servbuf);
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, sSA_DUMP_STRLEN, "(NULL / ANY)");
|
||||
}
|
||||
|
||||
}
|
||||
47
plat/diameter/libfdproto/version.c
Normal file
47
plat/diameter/libfdproto/version.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/*********************************************************************************************************
|
||||
* Software License Agreement (BSD License) *
|
||||
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
|
||||
* *
|
||||
* Copyright (c) 2013, WIDE Project and NICT *
|
||||
* All rights reserved. *
|
||||
* *
|
||||
* Redistribution and use of this software in source and binary forms, with or without modification, are *
|
||||
* permitted provided that the following conditions are met: *
|
||||
* *
|
||||
* * Redistributions of source code must retain the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer. *
|
||||
* *
|
||||
* * Redistributions in binary form must reproduce the above *
|
||||
* copyright notice, this list of conditions and the *
|
||||
* following disclaimer in the documentation and/or other *
|
||||
* materials provided with the distribution. *
|
||||
* *
|
||||
* * Neither the name of the WIDE Project or NICT nor the *
|
||||
* names of its contributors may be used to endorse or *
|
||||
* promote products derived from this software without *
|
||||
* specific prior written permission of WIDE Project and *
|
||||
* NICT. *
|
||||
* *
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
|
||||
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
|
||||
*********************************************************************************************************/
|
||||
|
||||
#include "fdproto-internal.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef FD_PROJECT_VERSION_HG
|
||||
# define FD_LIBFDPROTO_VERSION \
|
||||
_stringize(FD_PROJECT_VERSION_MAJOR) "." _stringize(FD_PROJECT_VERSION_MINOR) "." _stringize(FD_PROJECT_VERSION_REV) "-" FD_PROJECT_VERSION_HG_VAL
|
||||
#else
|
||||
# define FD_LIBFDPROTO_VERSION \
|
||||
_stringize(FD_PROJECT_VERSION_MAJOR) "." _stringize(FD_PROJECT_VERSION_MINOR) "." _stringize(FD_PROJECT_VERSION_REV)
|
||||
#endif
|
||||
|
||||
const char fd_libproto_version[] = FD_LIBFDPROTO_VERSION;
|
||||
Reference in New Issue
Block a user