mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-05 12:16:00 +00:00
IAX2 updates, dial fix
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@873 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
@@ -241,7 +241,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, struct localu
|
||||
case AST_CONTROL_RINGING:
|
||||
if (option_verbose > 2)
|
||||
ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
|
||||
if (!sentringing) {
|
||||
if (!sentringing && !moh) {
|
||||
ast_indicate(in, AST_CONTROL_RINGING);
|
||||
sentringing++;
|
||||
ringind++;
|
||||
|
@@ -379,8 +379,8 @@ struct chan_iax2_pvt {
|
||||
};
|
||||
|
||||
static struct ast_iax2_queue {
|
||||
struct ast_iax2_frame *head;
|
||||
struct ast_iax2_frame *tail;
|
||||
struct iax_frame *head;
|
||||
struct iax_frame *tail;
|
||||
int count;
|
||||
pthread_mutex_t lock;
|
||||
} iaxq;
|
||||
@@ -569,65 +569,13 @@ static int get_samples(struct ast_frame *f)
|
||||
return samples;
|
||||
}
|
||||
|
||||
static int frames = 0;
|
||||
static int iframes = 0;
|
||||
static int oframes = 0;
|
||||
|
||||
static void ast_iax2_frame_wrap(struct ast_iax2_frame *fr, struct ast_frame *f)
|
||||
{
|
||||
fr->af.frametype = f->frametype;
|
||||
fr->af.subclass = f->subclass;
|
||||
fr->af.mallocd = 0; /* Our frame is static relative to the container */
|
||||
fr->af.datalen = f->datalen;
|
||||
fr->af.samples = f->samples;
|
||||
fr->af.offset = AST_FRIENDLY_OFFSET;
|
||||
fr->af.src = f->src;
|
||||
fr->af.data = fr->afdata;
|
||||
if (fr->af.datalen)
|
||||
memcpy(fr->af.data, f->data, fr->af.datalen);
|
||||
}
|
||||
|
||||
static struct ast_iax2_frame *ast_iax2_frame_new(int direction, int datalen)
|
||||
{
|
||||
struct ast_iax2_frame *fr;
|
||||
fr = malloc(sizeof(struct ast_iax2_frame) + datalen);
|
||||
if (fr) {
|
||||
fr->direction = direction;
|
||||
fr->retrans = -1;
|
||||
frames++;
|
||||
if (fr->direction == DIRECTION_INGRESS)
|
||||
iframes++;
|
||||
else
|
||||
oframes++;
|
||||
}
|
||||
return fr;
|
||||
}
|
||||
|
||||
static void ast_iax2_frame_free(struct ast_iax2_frame *fr)
|
||||
{
|
||||
if (fr->retrans > -1)
|
||||
ast_sched_del(sched, fr->retrans);
|
||||
if (fr->direction == DIRECTION_INGRESS)
|
||||
iframes--;
|
||||
else if (fr->direction == DIRECTION_OUTGRESS)
|
||||
oframes--;
|
||||
else {
|
||||
ast_log(LOG_WARNING, "Attempt to double free frame detected\n");
|
||||
CRASH;
|
||||
return;
|
||||
}
|
||||
fr->direction = 0;
|
||||
free(fr);
|
||||
frames--;
|
||||
}
|
||||
|
||||
static struct ast_iax2_frame *iaxfrdup2(struct ast_iax2_frame *fr)
|
||||
static struct iax_frame *iaxfrdup2(struct iax_frame *fr)
|
||||
{
|
||||
/* Malloc() a copy of a frame */
|
||||
struct ast_iax2_frame *new = ast_iax2_frame_new(DIRECTION_INGRESS, fr->af.datalen);
|
||||
struct iax_frame *new = iax_frame_new(DIRECTION_INGRESS, fr->af.datalen);
|
||||
if (new) {
|
||||
memcpy(new, fr, sizeof(struct ast_iax2_frame));
|
||||
ast_iax2_frame_wrap(new, &fr->af);
|
||||
memcpy(new, fr, sizeof(struct iax_frame));
|
||||
iax_frame_wrap(new, &fr->af);
|
||||
new->data = NULL;
|
||||
new->datalen = 0;
|
||||
new->direction = DIRECTION_INGRESS;
|
||||
@@ -802,6 +750,13 @@ static int find_callno(unsigned short callno, unsigned short dcallno, struct soc
|
||||
return res;
|
||||
}
|
||||
|
||||
static void iax2_frame_free(struct iax_frame *fr)
|
||||
{
|
||||
if (fr->retrans > -1)
|
||||
ast_sched_del(sched, fr->retrans);
|
||||
iax_frame_free(fr);
|
||||
}
|
||||
|
||||
static int iax2_queue_frame(int callno, struct ast_frame *f)
|
||||
{
|
||||
int pass =0;
|
||||
@@ -835,7 +790,7 @@ static int __do_deliver(void *data)
|
||||
{
|
||||
/* Just deliver the packet by using queueing. This is called by
|
||||
the IAX thread with the iaxsl lock held. */
|
||||
struct ast_iax2_frame *fr = data;
|
||||
struct iax_frame *fr = data;
|
||||
unsigned int ts;
|
||||
fr->retrans = -1;
|
||||
if (iaxs[fr->callno] && !iaxs[fr->callno]->alreadygone) {
|
||||
@@ -856,7 +811,7 @@ static int __do_deliver(void *data)
|
||||
}
|
||||
}
|
||||
/* Free our iax frame */
|
||||
ast_iax2_frame_free(fr);
|
||||
iax2_frame_free(fr);
|
||||
/* And don't run again */
|
||||
return 0;
|
||||
}
|
||||
@@ -864,7 +819,7 @@ static int __do_deliver(void *data)
|
||||
static int do_deliver(void *data)
|
||||
{
|
||||
/* Locking version of __do_deliver */
|
||||
struct ast_iax2_frame *fr = data;
|
||||
struct iax_frame *fr = data;
|
||||
int callno = fr->callno;
|
||||
int res;
|
||||
ast_pthread_mutex_lock(&iaxsl[callno]);
|
||||
@@ -907,7 +862,7 @@ static int handle_error(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_packet(struct ast_iax2_frame *f)
|
||||
static int send_packet(struct iax_frame *f)
|
||||
{
|
||||
int res;
|
||||
/* Called with iaxsl held */
|
||||
@@ -998,7 +953,7 @@ static int iax2_predestroy_nolock(int callno)
|
||||
static void iax2_destroy(int callno)
|
||||
{
|
||||
struct chan_iax2_pvt *pvt;
|
||||
struct ast_iax2_frame *cur;
|
||||
struct iax_frame *cur;
|
||||
struct ast_channel *owner;
|
||||
|
||||
retry:
|
||||
@@ -1070,7 +1025,7 @@ static void iax2_destroy_nolock(int callno)
|
||||
ast_pthread_mutex_lock(&iaxsl[callno]);
|
||||
}
|
||||
|
||||
static int update_packet(struct ast_iax2_frame *f)
|
||||
static int update_packet(struct iax_frame *f)
|
||||
{
|
||||
/* Called with iaxsl lock held, and iaxs[callno] non-NULL */
|
||||
struct ast_iax2_full_hdr *fh = f->data;
|
||||
@@ -1086,7 +1041,7 @@ static int attempt_transmit(void *data)
|
||||
{
|
||||
/* Attempt to transmit the frame to the remote peer...
|
||||
Called without iaxsl held. */
|
||||
struct ast_iax2_frame *f = data;
|
||||
struct iax_frame *f = data;
|
||||
int freeme=0;
|
||||
int callno = f->callno;
|
||||
/* Make sure this call is still active */
|
||||
@@ -1163,7 +1118,7 @@ static int attempt_transmit(void *data)
|
||||
ast_pthread_mutex_unlock(&iaxq.lock);
|
||||
f->retrans = -1;
|
||||
/* Free the IAX frame */
|
||||
ast_iax2_frame_free(f);
|
||||
iax2_frame_free(f);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1201,7 +1156,7 @@ static char jitter_usage[] =
|
||||
|
||||
static int iax2_show_stats(int fd, int argc, char *argv[])
|
||||
{
|
||||
struct ast_iax2_frame *cur;
|
||||
struct iax_frame *cur;
|
||||
int cnt = 0, dead=0, final=0;
|
||||
if (argc != 3)
|
||||
return RESULT_SHOWUSAGE;
|
||||
@@ -1214,7 +1169,7 @@ static int iax2_show_stats(int fd, int argc, char *argv[])
|
||||
}
|
||||
ast_cli(fd, " IAX Statistics\n");
|
||||
ast_cli(fd, "---------------------\n");
|
||||
ast_cli(fd, "Outstanding frames: %d (%d ingress, %d outgress)\n", frames, iframes, oframes);
|
||||
ast_cli(fd, "Outstanding frames: %d (%d ingress, %d outgress)\n", iax_get_frames(), iax_get_iframes(), iax_get_oframes());
|
||||
ast_cli(fd, "Packets in transmit queue: %d dead, %d final, %d total\n", dead, final, cnt);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -1296,7 +1251,7 @@ static unsigned int calc_rxstamp(struct chan_iax2_pvt *p);
|
||||
#ifdef BRIDGE_OPTIMIZATION
|
||||
static unsigned int calc_fakestamp(struct chan_iax2_pvt *from, struct chan_iax2_pvt *to, unsigned int ts);
|
||||
|
||||
static int forward_delivery(struct ast_iax2_frame *fr)
|
||||
static int forward_delivery(struct iax_frame *fr)
|
||||
{
|
||||
struct chan_iax2_pvt *p1, *p2;
|
||||
p1 = iaxs[fr->callno];
|
||||
@@ -1313,7 +1268,7 @@ static int forward_delivery(struct ast_iax2_frame *fr)
|
||||
}
|
||||
#endif
|
||||
|
||||
static int schedule_delivery(struct ast_iax2_frame *fr, int reallydeliver, int updatehistory)
|
||||
static int schedule_delivery(struct iax_frame *fr, int reallydeliver, int updatehistory)
|
||||
{
|
||||
int ms,x;
|
||||
int drops[MEMORY_SIZE];
|
||||
@@ -1434,7 +1389,7 @@ static int schedule_delivery(struct ast_iax2_frame *fr, int reallydeliver, int u
|
||||
if (option_debug)
|
||||
ast_log(LOG_DEBUG, "Dropping voice packet since %d ms is, too old\n", ms);
|
||||
/* Free our iax frame */
|
||||
ast_iax2_frame_free(fr);
|
||||
iax2_frame_free(fr);
|
||||
}
|
||||
} else {
|
||||
if (option_debug)
|
||||
@@ -1444,7 +1399,7 @@ static int schedule_delivery(struct ast_iax2_frame *fr, int reallydeliver, int u
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iax2_transmit(struct ast_iax2_frame *fr)
|
||||
static int iax2_transmit(struct iax_frame *fr)
|
||||
{
|
||||
/* Lock the queue and place this packet at the end */
|
||||
fr->next = NULL;
|
||||
@@ -2136,8 +2091,8 @@ static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned in
|
||||
struct ast_iax2_full_hdr *fh;
|
||||
struct ast_iax2_mini_hdr *mh;
|
||||
unsigned char buffer[4096]; /* Buffer -- must preceed fr2 */
|
||||
struct ast_iax2_frame fr2;
|
||||
struct ast_iax2_frame *fr;
|
||||
struct iax_frame fr2;
|
||||
struct iax_frame *fr;
|
||||
int res;
|
||||
int sendmini=0;
|
||||
unsigned int lastsent;
|
||||
@@ -2166,23 +2121,23 @@ static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned in
|
||||
/* Mark that mini-style frame is appropriate */
|
||||
sendmini = 1;
|
||||
}
|
||||
/* Allocate an ast_iax2_frame */
|
||||
/* Allocate an iax_frame */
|
||||
if (now) {
|
||||
fr = &fr2;
|
||||
} else
|
||||
fr = ast_iax2_frame_new(DIRECTION_OUTGRESS, f->datalen);
|
||||
fr = iax_frame_new(DIRECTION_OUTGRESS, f->datalen);
|
||||
if (!fr) {
|
||||
ast_log(LOG_WARNING, "Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
/* Copy our prospective frame into our immediate or retransmitted wrapper */
|
||||
ast_iax2_frame_wrap(fr, f);
|
||||
iax_frame_wrap(fr, f);
|
||||
|
||||
fr->ts = fts;
|
||||
if (!fr->ts) {
|
||||
ast_log(LOG_WARNING, "timestamp is 0?\n");
|
||||
if (!now)
|
||||
ast_iax2_frame_free(fr);
|
||||
iax2_frame_free(fr);
|
||||
return -1;
|
||||
}
|
||||
fr->callno = pvt->callno;
|
||||
@@ -2704,7 +2659,6 @@ static int authenticate_verify(struct chan_iax2_pvt *p, struct iax_ies *ies)
|
||||
strncpy(md5secret, ies->md5_result, sizeof(md5secret)-1);
|
||||
if (ies->rsa_result)
|
||||
strncpy(rsasecret, ies->rsa_result, sizeof(rsasecret)-1);
|
||||
printf("Auth methods: %d, rsasecret: %s, inkeys: %s\n", p->authmethods, rsasecret, p->inkeys);
|
||||
if ((p->authmethods & IAX_AUTH_RSA) && strlen(rsasecret) && strlen(p->inkeys)) {
|
||||
struct ast_key *key;
|
||||
char *keyn;
|
||||
@@ -3046,7 +3000,7 @@ static int complete_transfer(int callno, struct iax_ies *ies)
|
||||
{
|
||||
int peercallno = 0;
|
||||
struct chan_iax2_pvt *pvt = iaxs[callno];
|
||||
struct ast_iax2_frame *cur;
|
||||
struct iax_frame *cur;
|
||||
|
||||
if (ies->callno)
|
||||
peercallno = ies->callno;
|
||||
@@ -3378,7 +3332,7 @@ static int iax2_vnak(int callno)
|
||||
|
||||
static void vnak_retransmit(int callno, int last)
|
||||
{
|
||||
struct ast_iax2_frame *f;
|
||||
struct iax_frame *f;
|
||||
ast_pthread_mutex_lock(&iaxq.lock);
|
||||
f = iaxq.head;
|
||||
while(f) {
|
||||
@@ -3406,15 +3360,15 @@ static int send_trunk(struct iax2_peer *peer)
|
||||
int calls = 0;
|
||||
int res = 0;
|
||||
int firstcall = 0;
|
||||
unsigned char buf[65536 + sizeof(struct ast_iax2_frame)], *ptr;
|
||||
unsigned char buf[65536 + sizeof(struct iax_frame)], *ptr;
|
||||
int len = 65536;
|
||||
struct ast_iax2_frame *fr;
|
||||
struct iax_frame *fr;
|
||||
struct ast_iax2_meta_hdr *meta;
|
||||
struct ast_iax2_meta_trunk_hdr *mth;
|
||||
struct ast_iax2_meta_trunk_entry *met;
|
||||
|
||||
/* Point to frame */
|
||||
fr = (struct ast_iax2_frame *)buf;
|
||||
fr = (struct iax_frame *)buf;
|
||||
/* Point to meta data */
|
||||
meta = (struct ast_iax2_meta_hdr *)fr->afdata;
|
||||
mth = (struct ast_iax2_meta_trunk_hdr *)meta->data;
|
||||
@@ -3540,8 +3494,8 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
|
||||
struct ast_iax2_meta_trunk_hdr *mth;
|
||||
struct ast_iax2_meta_trunk_entry *mte;
|
||||
char dblbuf[4096]; /* Declaration of dblbuf must immediately *preceed* fr on the stack */
|
||||
struct ast_iax2_frame fr;
|
||||
struct ast_iax2_frame *cur;
|
||||
struct iax_frame fr;
|
||||
struct iax_frame *cur;
|
||||
struct ast_frame f;
|
||||
struct ast_channel *c;
|
||||
struct iax2_dpcache *dp;
|
||||
@@ -3628,7 +3582,7 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
|
||||
else
|
||||
f.samples = 0;
|
||||
fr.outoforder = 0;
|
||||
ast_iax2_frame_wrap(&fr, &f);
|
||||
iax_frame_wrap(&fr, &f);
|
||||
#ifdef BRIDGE_OPTIMIZATION
|
||||
if (iaxs[fr.callno]->bridgecallno) {
|
||||
forward_delivery(&fr);
|
||||
@@ -4113,7 +4067,7 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
|
||||
f.mallocd = 0;
|
||||
f.offset = 0;
|
||||
f.samples = 0;
|
||||
ast_iax2_frame_wrap(&fr, &f);
|
||||
iax_frame_wrap(&fr, &f);
|
||||
schedule_delivery(iaxfrdup2(&fr), 1, updatehistory);
|
||||
#ifdef BRIDGE_OPTIMIZATION
|
||||
}
|
||||
@@ -4383,7 +4337,7 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
|
||||
f.samples = get_samples(&f);
|
||||
else
|
||||
f.samples = 0;
|
||||
ast_iax2_frame_wrap(&fr, &f);
|
||||
iax_frame_wrap(&fr, &f);
|
||||
|
||||
/* If this is our most recent packet, use it as our basis for timestamping */
|
||||
if (iaxs[fr.callno]->last < fr.ts) {
|
||||
@@ -4560,7 +4514,7 @@ static void *network_thread(void *ignore)
|
||||
/* Our job is simple: Send queued messages, retrying if necessary. Read frames
|
||||
from the network, and queue them for delivery to the channels */
|
||||
int res;
|
||||
struct ast_iax2_frame *f, *freeme;
|
||||
struct iax_frame *f, *freeme;
|
||||
/* Establish I/O callback for socket read */
|
||||
ast_io_add(io, netsocket, socket_read, AST_IO_IN, NULL);
|
||||
if (timingfd > -1)
|
||||
@@ -4599,7 +4553,7 @@ static void *network_thread(void *ignore)
|
||||
}
|
||||
f = f->next;
|
||||
if (freeme)
|
||||
ast_iax2_frame_free(freeme);
|
||||
iax_frame_free(freeme);
|
||||
}
|
||||
ast_pthread_mutex_unlock(&iaxq.lock);
|
||||
res = ast_sched_wait(sched);
|
||||
|
@@ -23,6 +23,10 @@
|
||||
#include "iax2-parser.h"
|
||||
|
||||
|
||||
static int frames = 0;
|
||||
static int iframes = 0;
|
||||
static int oframes = 0;
|
||||
|
||||
static void internaloutput(const char *str)
|
||||
{
|
||||
printf(str);
|
||||
@@ -165,7 +169,7 @@ static void dump_ies(unsigned char *iedata, int len)
|
||||
outputf("\n");
|
||||
}
|
||||
|
||||
void iax_showframe(struct ast_iax2_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
|
||||
void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
|
||||
{
|
||||
char *frames[] = {
|
||||
"(0?)",
|
||||
@@ -504,3 +508,52 @@ int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f)
|
||||
{
|
||||
fr->af.frametype = f->frametype;
|
||||
fr->af.subclass = f->subclass;
|
||||
fr->af.mallocd = 0; /* Our frame is static relative to the container */
|
||||
fr->af.datalen = f->datalen;
|
||||
fr->af.samples = f->samples;
|
||||
fr->af.offset = AST_FRIENDLY_OFFSET;
|
||||
fr->af.src = f->src;
|
||||
fr->af.data = fr->afdata;
|
||||
if (fr->af.datalen)
|
||||
memcpy(fr->af.data, f->data, fr->af.datalen);
|
||||
}
|
||||
|
||||
struct iax_frame *iax_frame_new(int direction, int datalen)
|
||||
{
|
||||
struct iax_frame *fr;
|
||||
fr = malloc(sizeof(struct iax_frame) + datalen);
|
||||
if (fr) {
|
||||
fr->direction = direction;
|
||||
fr->retrans = -1;
|
||||
frames++;
|
||||
if (fr->direction == DIRECTION_INGRESS)
|
||||
iframes++;
|
||||
else
|
||||
oframes++;
|
||||
}
|
||||
return fr;
|
||||
}
|
||||
|
||||
void iax_frame_free(struct iax_frame *fr)
|
||||
{
|
||||
/* Note: does not remove from scheduler! */
|
||||
if (fr->direction == DIRECTION_INGRESS)
|
||||
iframes--;
|
||||
else if (fr->direction == DIRECTION_OUTGRESS)
|
||||
oframes--;
|
||||
else {
|
||||
errorf("Attempt to double free frame detected\n");
|
||||
return;
|
||||
}
|
||||
fr->direction = 0;
|
||||
free(fr);
|
||||
frames--;
|
||||
}
|
||||
|
||||
int iax_get_frames(void) { return frames; }
|
||||
int iax_get_iframes(void) { return iframes; }
|
||||
int iax_get_oframes(void) { return oframes; }
|
||||
|
@@ -47,7 +47,12 @@ struct iax_ies {
|
||||
#define DIRECTION_INGRESS 1
|
||||
#define DIRECTION_OUTGRESS 2
|
||||
|
||||
struct ast_iax2_frame {
|
||||
struct iax_frame {
|
||||
#ifdef LIBIAX
|
||||
struct iax_session *session;
|
||||
struct iax_event *event;
|
||||
#endif
|
||||
|
||||
/* /Our/ call number */
|
||||
unsigned short callno;
|
||||
/* /Their/ call number */
|
||||
@@ -79,8 +84,8 @@ struct ast_iax2_frame {
|
||||
/* Retransmission ID */
|
||||
int retrans;
|
||||
/* Easy linking */
|
||||
struct ast_iax2_frame *next;
|
||||
struct ast_iax2_frame *prev;
|
||||
struct iax_frame *next;
|
||||
struct iax_frame *prev;
|
||||
/* Actual, isolated frame header */
|
||||
struct ast_frame af;
|
||||
unsigned char unused[AST_FRIENDLY_OFFSET];
|
||||
@@ -96,7 +101,7 @@ struct iax_ie_data {
|
||||
extern void iax_set_output(void (*output)(const char *data));
|
||||
/* Choose a different function for errors */
|
||||
extern void iax_set_error(void (*output)(const char *data));
|
||||
extern void iax_showframe(struct ast_iax2_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen);
|
||||
extern void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen);
|
||||
|
||||
extern const char *iax_ie2str(int ie);
|
||||
|
||||
@@ -109,4 +114,11 @@ extern int iax_ie_append_byte(struct iax_ie_data *ied, unsigned char ie, unsigne
|
||||
extern int iax_ie_append(struct iax_ie_data *ied, unsigned char ie);
|
||||
extern int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen);
|
||||
|
||||
extern int iax_get_frames(void);
|
||||
extern int iax_get_iframes(void);
|
||||
extern int iax_get_oframes(void);
|
||||
|
||||
extern void iax_frame_wrap(struct iax_frame *fr, struct ast_frame *f);
|
||||
extern struct iax_frame *iax_frame_new(int direction, int datalen);
|
||||
extern void iax_frame_free(struct iax_frame *fr);
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user