2006-12-21 06:30:28 +00:00
|
|
|
/*
|
|
|
|
* This file is part of the Sofia-SIP package
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Nokia Corporation.
|
|
|
|
*
|
|
|
|
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2.1 of
|
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
|
|
* 02110-1301 USA
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**@defgroup su_uniqueid GloballyUniqueIDs
|
|
|
|
*
|
|
|
|
* Globally unique IDs and random integers.
|
|
|
|
*
|
|
|
|
* GloballyUniqueID or #su_guid_t is a 128-bit identifier based on current
|
|
|
|
* time and MAC address of the node generating the ID. A new ID is generated
|
|
|
|
* each time su_guid_generate() is called. Please note that such IDs are @b
|
|
|
|
* not unique if multiple processes are run on the same node.
|
|
|
|
*
|
|
|
|
* Use su_guid_sprintf() to convert #su_guid_t to printable format.
|
2008-12-16 18:05:22 +00:00
|
|
|
*
|
2006-12-21 06:30:28 +00:00
|
|
|
* The random integers can be generated with functions
|
|
|
|
* - su_randint(),
|
|
|
|
* - su_randmem(), or
|
|
|
|
* - su_random().
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**@ingroup su_uniqueid
|
|
|
|
*
|
|
|
|
* @CFILE su_uniqueid.c Construct a GloballyUniqueID as per H.225.0 v2.
|
|
|
|
*
|
|
|
|
* @author Pekka Pessi <pessi@research.nokia.com>
|
2008-12-16 18:05:22 +00:00
|
|
|
*
|
2006-12-21 06:30:28 +00:00
|
|
|
* @date Created: Tue Apr 15 06:31:41 1997 pessi
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#if defined(_WIN32)
|
|
|
|
int _getpid(void);
|
|
|
|
#define getpid _getpid
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
#if HAVE_SYS_TIME_H
|
|
|
|
#include <sys/time.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if HAVE_UNISTD_H
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "sofia-sip/su.h"
|
|
|
|
#include "sofia-sip/su_time.h"
|
|
|
|
#include "sofia-sip/su_uniqueid.h"
|
|
|
|
|
|
|
|
/* For random number generator */
|
2009-03-24 15:54:18 +00:00
|
|
|
static FILE *urandom;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
union state {
|
|
|
|
uint64_t u64;
|
|
|
|
};
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#if SU_HAVE_PTHREADS
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#include <pthread.h>
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-05-19 17:23:16 +00:00
|
|
|
#if __sun
|
|
|
|
#undef PTHREAD_ONCE_INIT
|
|
|
|
#define PTHREAD_ONCE_INIT {{ 0, 0, 0, PTHREAD_ONCE_NOTDONE }}
|
2009-05-15 21:21:06 +00:00
|
|
|
#endif
|
2009-05-19 17:23:16 +00:00
|
|
|
|
|
|
|
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
2009-03-24 15:54:18 +00:00
|
|
|
static int done_once = 1;
|
|
|
|
static pthread_key_t state_key;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
static void
|
|
|
|
init_once(void)
|
2006-12-21 06:30:28 +00:00
|
|
|
{
|
2009-03-24 15:54:18 +00:00
|
|
|
pthread_key_create(&state_key, free);
|
|
|
|
#if HAVE_DEV_URANDOM
|
|
|
|
urandom = fopen("/dev/urandom", "rb");
|
|
|
|
#endif /* HAVE_DEV_URANDOM */
|
|
|
|
done_once = 1;
|
|
|
|
}
|
2008-12-16 18:05:22 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#else
|
|
|
|
static int initialized;
|
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
static union state *
|
|
|
|
get_state(void)
|
|
|
|
{
|
2014-01-27 01:57:05 +00:00
|
|
|
static union state state0[1];
|
|
|
|
union state *retval;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#if SU_HAVE_PTHREADS
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
pthread_once(&once, init_once);
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (urandom)
|
|
|
|
return NULL;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
retval = pthread_getspecific(state_key);
|
|
|
|
if (retval) {
|
|
|
|
return retval;
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
retval = calloc(1, sizeof *retval);
|
|
|
|
if (retval != NULL)
|
|
|
|
pthread_setspecific(state_key, retval);
|
|
|
|
else
|
|
|
|
retval = state0;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#else /* !SU_HAVE_PTHREADS */
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (urandom == NULL) {
|
|
|
|
#if HAVE_DEV_URANDOM
|
|
|
|
urandom = fopen("/dev/urandom", "rb");
|
|
|
|
#endif /* HAVE_DEV_URANDOM */
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (urandom)
|
|
|
|
return NULL;
|
2009-02-25 19:34:54 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
retval = state0;
|
|
|
|
|
|
|
|
if (initialized)
|
|
|
|
return retval;
|
2009-02-12 21:21:30 +00:00
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
{
|
|
|
|
uint32_t seed[32];
|
|
|
|
int i;
|
|
|
|
union {
|
|
|
|
uint32_t u32;
|
|
|
|
pthread_t tid;
|
|
|
|
} tid32 = { 0 };
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
tid32.tid = pthread_self();
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
memset(seed, 0, sizeof seed); /* Make valgrind happy */
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i += 2) {
|
2006-12-21 06:30:28 +00:00
|
|
|
#if HAVE_CLOCK_GETTIME
|
|
|
|
struct timespec ts;
|
|
|
|
(void)clock_gettime(CLOCK_REALTIME, &ts);
|
2009-02-25 19:34:54 +00:00
|
|
|
seed[i] ^= ts.tv_sec; seed[i + 1] ^= ts.tv_nsec;
|
2009-03-24 15:54:18 +00:00
|
|
|
#else
|
|
|
|
su_time_t now;
|
2006-12-21 06:30:28 +00:00
|
|
|
su_time(&now);
|
2009-02-25 19:34:54 +00:00
|
|
|
seed[i] ^= now.tv_sec; seed[i + 1] ^= now.tv_sec;
|
2009-03-24 15:54:18 +00:00
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
2009-02-25 19:34:54 +00:00
|
|
|
seed[0] ^= getuid();
|
|
|
|
seed[1] ^= getpid();
|
2009-03-24 15:54:18 +00:00
|
|
|
seed[2] ^= tid32.u32;
|
|
|
|
seed[3] ^= (uint32_t)(intptr_t)retval;
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i+= 4) {
|
|
|
|
retval->u64 += ((uint64_t)seed[i] << 32) | seed[i + 1];
|
|
|
|
retval->u64 *= ((uint64_t)seed[i + 3] << 32) | seed[i + 2];
|
|
|
|
}
|
|
|
|
|
|
|
|
retval->u64 += (uint64_t)su_nanotime(NULL);
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
return retval;
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#if !defined(WIN32) && !defined(WIN64)
|
|
|
|
void sofia_su_uniqueid_destructor(void)
|
|
|
|
__attribute__((destructor));
|
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
void
|
|
|
|
sofia_su_uniqueid_destructor(void)
|
|
|
|
{
|
|
|
|
#if HAVE_DEV_URANDOM
|
2013-01-14 17:02:28 +00:00
|
|
|
if (urandom) {
|
|
|
|
fclose(urandom);
|
|
|
|
urandom=NULL;
|
|
|
|
}
|
2009-03-24 15:54:18 +00:00
|
|
|
#endif /* HAVE_DEV_URANDOM */
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#if SU_HAVE_PTHREADS
|
|
|
|
if (done_once) {
|
|
|
|
pthread_key_delete(state_key);
|
|
|
|
done_once = 0;
|
|
|
|
}
|
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if HAVE_GETIFADDRS
|
|
|
|
#include <ifaddrs.h>
|
|
|
|
#if HAVE_NETPACKET_PACKET_H
|
|
|
|
#define HAVE_SOCKADDR_LL 1
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <net/if_arp.h>
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
2009-04-03 23:04:42 +00:00
|
|
|
#define SIZEOF_NODE 6
|
2006-12-21 06:30:28 +00:00
|
|
|
static
|
2009-04-03 23:04:42 +00:00
|
|
|
void init_node(uint8_t node[SIZEOF_NODE])
|
2006-12-21 06:30:28 +00:00
|
|
|
{
|
|
|
|
#if HAVE_GETIFADDRS && HAVE_SOCKADDR_LL
|
|
|
|
struct ifaddrs *ifa, *results;
|
|
|
|
|
|
|
|
if (getifaddrs(&results) == 0) {
|
|
|
|
for (ifa = results; ifa; ifa = ifa->ifa_next) {
|
|
|
|
#if HAVE_SOCKADDR_LL
|
|
|
|
struct sockaddr_ll const *sll = (void *)ifa->ifa_addr;
|
|
|
|
|
|
|
|
if (sll == NULL || sll->sll_family != AF_PACKET)
|
|
|
|
continue;
|
|
|
|
switch (sll->sll_hatype) {
|
|
|
|
case ARPHRD_ETHER:
|
|
|
|
case ARPHRD_EETHER:
|
|
|
|
case ARPHRD_IEEE802:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-04-03 23:04:42 +00:00
|
|
|
memcpy(node, sll->sll_addr, SIZEOF_NODE);
|
2006-12-21 06:30:28 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
freeifaddrs(results);
|
|
|
|
|
|
|
|
if (ifa)
|
|
|
|
return; /* Success */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-04-03 23:04:42 +00:00
|
|
|
su_randmem(node, SIZEOF_NODE);
|
2006-12-21 06:30:28 +00:00
|
|
|
node[0] |= 1; /* "multicast" address */
|
|
|
|
}
|
|
|
|
|
2009-04-03 23:04:42 +00:00
|
|
|
static unsigned char node[SIZEOF_NODE];
|
2009-03-24 15:54:18 +00:00
|
|
|
|
2006-12-21 06:30:28 +00:00
|
|
|
size_t su_node_identifier(void *address, size_t addrlen)
|
|
|
|
{
|
2009-04-03 23:04:42 +00:00
|
|
|
if (addrlen > SIZEOF_NODE)
|
|
|
|
addrlen = SIZEOF_NODE;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
su_guid_generate(NULL);
|
2006-12-21 06:30:28 +00:00
|
|
|
memcpy(address, node, addrlen);
|
|
|
|
|
|
|
|
return addrlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
void su_guid_generate(su_guid_t *v)
|
|
|
|
{
|
2009-03-24 15:54:18 +00:00
|
|
|
/* Constants */
|
|
|
|
static const unsigned version = 1; /* Current version */
|
|
|
|
static const unsigned reserved = 128; /* DCE variant */
|
|
|
|
#define granularity (10000000UL)
|
|
|
|
static const uint64_t mask60 = SU_U64_C(0xfffFFFFffffFFFF);
|
|
|
|
#define MAGIC (16384)
|
|
|
|
|
|
|
|
/* 100-nanosecond intervals between 15 October 1582 and 1 January 1900 */
|
|
|
|
static const uint64_t ntp_epoch =
|
|
|
|
(uint64_t)(141427) * (24 * 60 * 60L) * granularity;
|
|
|
|
|
|
|
|
static uint64_t timestamp0 = 0;
|
|
|
|
static unsigned clock_sequence = MAGIC;
|
|
|
|
|
|
|
|
#if SU_HAVE_PTHREADS
|
|
|
|
static pthread_mutex_t update = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint64_t tl = su_ntp_now();
|
|
|
|
uint64_t hi = su_ntp_hi(tl), lo = su_ntp_lo(tl);
|
|
|
|
|
|
|
|
lo *= granularity;
|
|
|
|
hi *= granularity;
|
|
|
|
|
|
|
|
tl = hi + (lo >> 32) + ntp_epoch;
|
|
|
|
|
|
|
|
#ifdef TESTING
|
|
|
|
printf("timestamp %08x-%08x\n", (unsigned)(tl >>32), (unsigned)tl);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
tl &= mask60;
|
|
|
|
if (tl == 0) tl++;
|
|
|
|
|
|
|
|
#if SU_HAVE_PTHREADS
|
|
|
|
pthread_mutex_lock(&update);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (timestamp0 == 0) {
|
|
|
|
clock_sequence = su_randint(0, MAGIC - 1);
|
|
|
|
init_node(node);
|
|
|
|
}
|
|
|
|
else if (tl <= timestamp0) {
|
|
|
|
clock_sequence = (clock_sequence + 1) & (MAGIC - 1);
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
timestamp0 = tl;
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
#if SU_HAVE_PTHREADS
|
|
|
|
pthread_mutex_unlock(&update);
|
|
|
|
#endif
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (v) {
|
|
|
|
v->s.time_high_and_version =
|
|
|
|
htons((unsigned short)(((tl >> 48) & 0x0fff) | (version << 12)));
|
|
|
|
v->s.time_mid = htons((unsigned short)((tl >> 32) & 0xffff));
|
|
|
|
v->s.time_low = htonl((unsigned long)(tl & 0xffffffffUL));
|
|
|
|
v->s.clock_seq_low = clock_sequence & 0xff;
|
|
|
|
v->s.clock_seq_hi_and_reserved = (clock_sequence >> 8) | reserved;
|
|
|
|
memcpy(v->s.node, node, sizeof(v->s.node));
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Human-readable form of GloballyUniqueID
|
|
|
|
*/
|
|
|
|
isize_t su_guid_sprintf(char* buf, size_t len, su_guid_t const *v)
|
|
|
|
{
|
|
|
|
char mybuf[su_guid_strlen + 1];
|
|
|
|
sprintf(mybuf, "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
|
|
(unsigned long)ntohl(v->s.time_low),
|
|
|
|
ntohs(v->s.time_mid),
|
|
|
|
ntohs(v->s.time_high_and_version),
|
|
|
|
v->s.clock_seq_low,
|
|
|
|
v->s.clock_seq_hi_and_reserved,
|
2008-12-16 18:05:22 +00:00
|
|
|
v->s.node[0], v->s.node[1], v->s.node[2],
|
2006-12-21 06:30:28 +00:00
|
|
|
v->s.node[3], v->s.node[4], v->s.node[5]);
|
|
|
|
memcpy(buf, mybuf, len > sizeof(mybuf) ? sizeof(mybuf) : len);
|
|
|
|
return su_guid_strlen;
|
|
|
|
}
|
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
uint64_t su_random64(void)
|
2006-12-21 06:30:28 +00:00
|
|
|
{
|
2009-03-24 15:54:18 +00:00
|
|
|
union state *state = get_state();
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (state) {
|
|
|
|
/* Simple rand64 from AoCP */
|
|
|
|
return state->u64 = state->u64 * 0X5851F42D4C957F2DULL + 1ULL;
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
2009-03-24 15:54:18 +00:00
|
|
|
else {
|
|
|
|
uint64_t retval;
|
|
|
|
size_t len = fread(&retval, 1, sizeof retval, urandom); (void)len;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
void *su_randmem(void *mem, size_t siz)
|
|
|
|
{
|
|
|
|
union state *state = get_state();
|
|
|
|
|
|
|
|
if (state) {
|
|
|
|
size_t i;
|
|
|
|
uint64_t r64;
|
|
|
|
uint32_t r32;
|
|
|
|
|
|
|
|
for (i = 0; i < siz; i += 4) {
|
|
|
|
/* Simple rand64 from AoCP */
|
|
|
|
state->u64 = r64 = state->u64 * 0X5851F42D4C957F2DULL + 1ULL;
|
|
|
|
r32 = (uint32_t) (r64 >> 32) ^ (uint32_t)r64;
|
|
|
|
if (siz - i >= 4)
|
|
|
|
memcpy((char *)mem + i, &r32, 4);
|
|
|
|
else
|
|
|
|
memcpy((char *)mem + i, &r32, siz - i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
size_t len = fread(mem, 1, siz, urandom); (void)len;
|
|
|
|
}
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
return mem;
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
/**
|
|
|
|
* Generate random integer in range [lb, ub] (inclusive)
|
|
|
|
*/
|
|
|
|
int su_randint(int lb, int ub)
|
2006-12-21 06:30:28 +00:00
|
|
|
{
|
2009-03-24 15:54:18 +00:00
|
|
|
uint64_t rnd;
|
|
|
|
unsigned modulo = (unsigned)(ub - lb + 1);
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
if (modulo != 0) {
|
|
|
|
do {
|
|
|
|
rnd = su_random64();
|
|
|
|
} while (rnd / modulo == 0xffffFFFFffffFFFFULL / modulo);
|
2006-12-21 06:30:28 +00:00
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
rnd %= modulo;
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
2009-03-24 15:54:18 +00:00
|
|
|
else {
|
|
|
|
rnd = su_random64();
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
return (int)rnd + lb;
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|
|
|
|
|
2009-03-24 15:54:18 +00:00
|
|
|
/** Get random 32-bit unsigned number.
|
2006-12-21 06:30:28 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
uint32_t su_random(void)
|
|
|
|
{
|
2009-03-24 15:54:18 +00:00
|
|
|
return (uint32_t)(su_random64() >> 16);
|
2006-12-21 06:30:28 +00:00
|
|
|
}
|