Commit 220c4713 authored by Ghislain MARY's avatar Ghislain MARY
Browse files

Use xml parser to get presence status.

parent 79c72fc8
......@@ -16,7 +16,7 @@ CLEANFILES=$(GITVERSION_FILE)
## Process this file with automake to produce Makefile.in
linphone_includedir=$(includedir)/linphone
linphone_include_HEADERS=linphonecore.h linphonefriend.h linphonecore_utils.h lpconfig.h sipsetup.h event.h
linphone_include_HEADERS=linphonecore.h linphonefriend.h linphonepresence.h linphonecore_utils.h lpconfig.h sipsetup.h event.h
if BUILD_TUNNEL
linphone_include_HEADERS+=linphone_tunnel.h
......
......@@ -486,6 +486,8 @@ void sal_set_callbacks(Sal *ctx, const SalCallbacks *cbs){
ctx->callbacks.subscribe_received=(SalOnSubscribeReceived)unimplemented_stub;
if (ctx->callbacks.subscribe_closed==NULL)
ctx->callbacks.subscribe_closed=(SalOnSubscribeClosed)unimplemented_stub;
if (ctx->callbacks.parse_presence_requested==NULL)
ctx->callbacks.parse_presence_requested=(SalOnParsePresenceRequested)unimplemented_stub;
if (ctx->callbacks.notify_presence==NULL)
ctx->callbacks.notify_presence=(SalOnNotifyPresence)unimplemented_stub;
if (ctx->callbacks.subscribe_presence_received==NULL)
......
......@@ -497,6 +497,26 @@ static void presence_process_transaction_terminated(void *user_ctx, const belle_
ms_message("presence_process_transaction_terminated not implemented yet");
}
static SalPresenceModel * process_presence_notification(SalOp *op, belle_sip_request_t *req) {
belle_sip_header_content_type_t *content_type = belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req), belle_sip_header_content_type_t);
belle_sip_header_content_length_t *content_length = belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req), belle_sip_header_content_length_t);
const char *body = belle_sip_message_get_body(BELLE_SIP_MESSAGE(req));
SalPresenceModel *result = NULL;
if ((content_type == NULL) || (content_length == NULL))
return NULL;
if (belle_sip_header_content_length_get_content_length(content_length) == 0)
return NULL;
op->base.root->callbacks.parse_presence_requested(op,
belle_sip_header_content_type_get_type(content_type),
belle_sip_header_content_type_get_subtype(content_type),
body,
&result);
return result;
}
static void presence_process_request_event(void *op_base, const belle_sip_request_event_t *event) {
SalOp* op = (SalOp*)op_base;
belle_sip_server_transaction_t* server_transaction = belle_sip_provider_create_server_transaction(op->base.root->prov,belle_sip_request_event_get_request(event));
......@@ -505,7 +525,7 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques
belle_sip_header_subscription_state_t* subscription_state_header=belle_sip_message_get_header_by_type(req,belle_sip_header_subscription_state_t);
belle_sip_header_expires_t* expires = belle_sip_message_get_header_by_type(req,belle_sip_header_expires_t);
const char* body = belle_sip_message_get_body(BELLE_SIP_MESSAGE(req));
SalPresenceStatus estatus=SalPresenceOffline;
SalPresenceModel *presence_model = NULL;
SalSubscribeStatus sub_state;
belle_sip_response_t* resp;
belle_sip_object_ref(server_transaction);
......@@ -536,41 +556,22 @@ static void presence_process_request_event(void *op_base, const belle_sip_reques
ms_error("No body in NOTIFY received from [%s]",sal_op_get_from(op));
return;
}
if (strstr(body,"pending")!=NULL){
estatus=SalPresenceOffline;
}else if (strstr(body,"busy")!=NULL){
estatus=SalPresenceBusy;
}else if (strstr(body,"berightback")!=NULL
|| strstr(body,"in-transit")!=NULL ){
estatus=SalPresenceBerightback;
}else if (strstr(body,"away")!=NULL
|| strstr(body,"idle")){
estatus=SalPresenceAway;
}else if (strstr(body,"onthephone")!=NULL
|| strstr(body,"on-the-phone")!=NULL){
estatus=SalPresenceOnthephone;
}else if (strstr(body,"outtolunch")!=NULL
|| strstr(body,"lunch")!=NULL
|| strstr(body,"meal")!=NULL){
estatus=SalPresenceOuttolunch;
}else if (strstr(body,"closed")!=NULL){
estatus=SalPresenceOffline;
}else if ((strstr(body,"online")!=NULL) || (strstr(body,"open")!=NULL)) {
estatus=SalPresenceOnline;
}else if((strstr(body,"vacation")!=NULL)) {
estatus = SalPresenceOnVacation;
}else{
estatus=SalPresenceOffline;
presence_model = process_presence_notification(op, req);
if (presence_model != NULL) {
/* Presence notification body parsed successfully. */
if (!subscription_state_header || strcasecmp(BELLE_SIP_SUBSCRIPTION_STATE_TERMINATED,belle_sip_header_subscription_state_get_state(subscription_state_header)) ==0) {
sub_state=SalSubscribeTerminated;
ms_message("Outgoing subscription terminated by remote [%s]",sal_op_get_to(op));
} else {
sub_state=SalSubscribeActive;
}
op->base.root->callbacks.notify_presence(op, sub_state, presence_model, NULL);
resp = sal_op_create_response_from_request(op, req, 200);
} else {
/* Formatting error in presence notification body. */
ms_error("Wrongly formatted presence notification received");
resp = sal_op_create_response_from_request(op, req, 400);
}
ms_message("We are notified that [%s] has online status [%s]",sal_op_get_to(op),sal_presence_status_to_string(estatus));
if (!subscription_state_header || strcasecmp(BELLE_SIP_SUBSCRIPTION_STATE_TERMINATED,belle_sip_header_subscription_state_get_state(subscription_state_header)) ==0) {
sub_state=SalSubscribeTerminated;
ms_message("And outgoing subscription terminated by remote [%s]",sal_op_get_to(op));
} else
sub_state=SalSubscribeActive;
op->base.root->callbacks.notify_presence(op,sub_state, estatus,NULL);
resp=sal_op_create_response_from_request(op,req,200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else if (strcmp("SUBSCRIBE",belle_sip_request_get_method(req))==0) {
/*either a refresh of an unsubscribe*/
......
......@@ -877,9 +877,13 @@ static void text_received(SalOp *op, const SalMessage *msg){
}
}
static void notify_presence(SalOp *op, SalSubscribeStatus ss, SalPresenceStatus status, const char *msg){
static void parse_presence_requested(SalOp *op, const char *content_type, const char *content_subtype, const char *body, SalPresenceModel **result) {
linphone_notify_parse_presence(op, content_type, content_subtype, body, result);
}
static void notify_presence(SalOp *op, SalSubscribeStatus ss, SalPresenceModel *model, const char *msg){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
linphone_notify_recv(lc,op,ss,status);
linphone_notify_recv(lc,op,ss,model);
}
static void subscribe_presence_received(SalOp *op, const char *from){
......@@ -1085,6 +1089,7 @@ SalCallbacks linphone_sal_callbacks={
notify,
subscribe_presence_received,
subscribe_presence_closed,
parse_presence_requested,
notify_presence,
ping_reply,
auth_requested,
......
......@@ -119,7 +119,7 @@ void __linphone_friend_do_subscribe(LinphoneFriend *fr){
if (fr->outsub==NULL){
/* people for which we don't have yet an answer should appear as offline */
fr->status=LinphoneStatusOffline;
fr->presence=NULL;
/*
if (fr->lc->vtable.notify_recv)
fr->lc->vtable.notify_recv(fr->lc,(LinphoneFriend*)fr);
......@@ -138,7 +138,7 @@ void __linphone_friend_do_subscribe(LinphoneFriend *fr){
LinphoneFriend * linphone_friend_new(){
LinphoneFriend *obj=ms_new0(LinphoneFriend,1);
obj->pol=LinphoneSPAccept;
obj->status=LinphoneStatusOffline;
obj->presence=NULL;
obj->subscribe=TRUE;
return obj;
}
......@@ -304,6 +304,7 @@ void linphone_friend_destroy(LinphoneFriend *lf){
sal_op_release(lf->outsub);
lf->outsub=NULL;
}
if (lf->presence != NULL) linphone_presence_model_delete(lf->presence);
if (lf->uri!=NULL) linphone_address_destroy(lf->uri);
if (lf->info!=NULL) buddy_info_free(lf->info);
ms_free(lf);
......@@ -322,7 +323,90 @@ LinphoneSubscribePolicy linphone_friend_get_inc_subscribe_policy(const LinphoneF
}
LinphoneOnlineStatus linphone_friend_get_status(const LinphoneFriend *lf){
return lf->status;
LinphoneOnlineStatus online_status = LinphoneStatusOffline;
LinphonePresenceBasicStatus basic_status = LinphonePresenceBasicStatusClosed;
LinphonePresenceActivity activity = LinphonePresenceActivityUnknown;
char *activity_description = NULL;
unsigned int nb_activities = 0;
int err = 0;
if (lf->presence != NULL) {
basic_status = linphone_presence_model_get_basic_status(lf->presence);
nb_activities = linphone_presence_model_nb_activities(lf->presence);
online_status = (basic_status == LinphonePresenceBasicStatusOpen) ? LinphoneStatusOnline : LinphoneStatusOffline;
if (nb_activities > 1) {
char *tmp = NULL;
const LinphoneAddress *addr = linphone_friend_get_address(lf);
if (addr) tmp = linphone_address_as_string(addr);
ms_warning("Friend %s has several activities, get status from the first one", tmp ? tmp : "unknown");
if (tmp) ms_free(tmp);
nb_activities = 1;
}
if (nb_activities == 1) {
err = linphone_presence_model_get_activity(lf->presence, 0, &activity, &activity_description);
if (err == 0) {
switch (activity) {
case LinphonePresenceActivityBreakfast:
case LinphonePresenceActivityDinner:
case LinphonePresenceActivityLunch:
case LinphonePresenceActivityMeal:
online_status = LinphoneStatusOutToLunch;
break;
case LinphonePresenceActivityAppointment:
case LinphonePresenceActivityMeeting:
case LinphonePresenceActivityPerformance:
case LinphonePresenceActivityPresentation:
case LinphonePresenceActivitySpectator:
case LinphonePresenceActivityWorking:
case LinphonePresenceActivityWorship:
online_status = LinphoneStatusDoNotDisturb;
break;
case LinphonePresenceActivityAway:
case LinphonePresenceActivitySleeping:
online_status = LinphoneStatusAway;
break;
case LinphonePresenceActivityHoliday:
case LinphonePresenceActivityTravel:
case LinphonePresenceActivityVacation:
online_status = LinphoneStatusVacation;
break;
case LinphonePresenceActivityBusy:
case LinphonePresenceActivityLookingForWork:
case LinphonePresenceActivityPlaying:
case LinphonePresenceActivityShopping:
case LinphonePresenceActivityTV:
online_status = LinphoneStatusBusy;
break;
case LinphonePresenceActivityInTransit:
case LinphonePresenceActivitySteering:
online_status = LinphoneStatusBeRightBack;
break;
case LinphonePresenceActivityOnThePhone:
online_status = LinphoneStatusOnThePhone;
break;
case LinphonePresenceActivityOther:
case LinphonePresenceActivityPermanentAbsence:
online_status = LinphoneStatusMoved;
break;
case LinphonePresenceActivityUnknown:
break;
}
}
}
}
return online_status;
}
LinphonePresenceModel * linphone_friend_get_presence(LinphoneFriend *lf) {
return lf->presence;
}
void linphone_friend_set_presence(LinphoneFriend *lf, LinphonePresenceModel *model) {
if (lf->presence != NULL) {
linphone_presence_model_delete(lf->presence);
}
lf->presence = model;
}
BuddyInfo * linphone_friend_get_info(const LinphoneFriend *lf){
......
......@@ -19,9 +19,13 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef LINPHONEFRIEND_H_
#define LINPHONEFRIEND_H_
#include "linphonepresence.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @addtogroup buddy_list
* @{
......@@ -206,6 +210,10 @@ LINPHONE_PUBLIC void linphone_friend_done(LinphoneFriend *fr);
* @return #LinphoneOnlineStatus
*/
LinphoneOnlineStatus linphone_friend_get_status(const LinphoneFriend *lf);
LinphonePresenceModel * linphone_friend_get_presence(LinphoneFriend *lf);
void linphone_friend_set_presence(LinphoneFriend *lf, LinphonePresenceModel *presence);
BuddyInfo * linphone_friend_get_info(const LinphoneFriend *lf);
void linphone_friend_set_ref_key(LinphoneFriend *lf, const char *key);
const char *linphone_friend_get_ref_key(const LinphoneFriend *lf);
......
/*
linphonepresence.h
Copyright (C) 2010-2013 Belledonne Communications SARL
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef LINPHONEPRESENCE_H_
#define LINPHONEPRESENCE_H_
#ifdef __cplusplus
extern "C" {
#endif
/** Basic status as defined in section 4.1.4 of RFC 3863 */
typedef enum LinphonePresenceBasicStatus {
LinphonePresenceBasicStatusOpen,
LinphonePresenceBasicStatusClosed
} LinphonePresenceBasicStatus;
/** Activities as defined in section 3.2 of RFC 4480 */
typedef enum LinphonePresenceActivity {
LinphonePresenceActivityAppointment,
LinphonePresenceActivityAway,
LinphonePresenceActivityBreakfast,
LinphonePresenceActivityBusy,
LinphonePresenceActivityDinner,
LinphonePresenceActivityHoliday,
LinphonePresenceActivityInTransit,
LinphonePresenceActivityLookingForWork,
LinphonePresenceActivityLunch,
LinphonePresenceActivityMeal,
LinphonePresenceActivityMeeting,
LinphonePresenceActivityOnThePhone,
LinphonePresenceActivityOther,
LinphonePresenceActivityPerformance,
LinphonePresenceActivityPermanentAbsence,
LinphonePresenceActivityPlaying,
LinphonePresenceActivityPresentation,
LinphonePresenceActivityShopping,
LinphonePresenceActivitySleeping,
LinphonePresenceActivitySpectator,
LinphonePresenceActivitySteering,
LinphonePresenceActivityTravel,
LinphonePresenceActivityTV,
LinphonePresenceActivityUnknown,
LinphonePresenceActivityVacation,
LinphonePresenceActivityWorking,
LinphonePresenceActivityWorship
} LinphonePresenceActivity;
struct _LinphonePresenceModel;
typedef struct _LinphonePresenceModel LinphonePresenceModel;
LINPHONE_PUBLIC LinphonePresenceModel * linphone_presence_model_new(void);
LINPHONE_PUBLIC void linphone_presence_model_delete(LinphonePresenceModel *model);
LINPHONE_PUBLIC LinphonePresenceBasicStatus linphone_presence_model_get_basic_status(const LinphonePresenceModel *model);
LINPHONE_PUBLIC unsigned int linphone_presence_model_nb_activities(const LinphonePresenceModel *model);
LINPHONE_PUBLIC int linphone_presence_model_get_activity(const LinphonePresenceModel *model, unsigned int idx, LinphonePresenceActivity *activity, char **description);
#ifdef __cplusplus
}
#endif
#endif /* LINPHONEPRESENCE_H_ */
......@@ -19,11 +19,744 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "linphonecore.h"
#include "private.h"
#include <libxml/xmlreader.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#define XMLPARSING_BUFFER_LEN 2048
#define MAX_XPATH_LENGTH 256
extern const char *__policy_enum_to_str(LinphoneSubscribePolicy pol);
struct _LinphonePresenceNote {
char *lang;
char *content;
};
struct _LinphonePresenceService {
char *id;
LinphonePresenceBasicStatus status;
};
struct _LinphonePresenceActivity {
LinphonePresenceActivity activity;
char *description;
};
struct _LinphonePresencePerson {
char *id;
MSList *activities; /**< A list of _LinphonePresenceActivity structures. */
MSList *activities_notes; /**< A list of _LinphonePresenceNote structures. */
MSList *notes; /**< A list of _LinphonePresenceNote structures. */
time_t timestamp;
};
/**
* Represents the presence model as defined in RFC 4479 and RFC 4480.
* This model is not complete. For example, it does not handle devices.
*/
struct _LinphonePresenceModel {
MSList *services; /**< A list of _LinphonePresenceService structures. Also named tuples in the RFC. */
MSList *persons; /**< A list of _LinphonePresencePerson structures. */
MSList *notes; /**< A list of _LinphonePresenceNote structures. */
};
typedef struct _xmlparsing_context {
xmlDoc *doc;
xmlXPathContextPtr xpath_ctx;
char errorBuffer[XMLPARSING_BUFFER_LEN];
char warningBuffer[XMLPARSING_BUFFER_LEN];
} xmlparsing_context_t;
static xmlparsing_context_t * xmlparsing_context_new() {
xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)malloc(sizeof(xmlparsing_context_t));
if (xmlCtx != NULL) {
xmlCtx->doc = NULL;
xmlCtx->xpath_ctx = NULL;
xmlCtx->errorBuffer[0] = '\0';
xmlCtx->warningBuffer[0] = '\0';
}
return xmlCtx;
}
static void xmlparsing_context_destroy(xmlparsing_context_t *ctx) {
if (ctx->doc != NULL) {
xmlFreeDoc(ctx->doc);
ctx->doc = NULL;
}
if (ctx->xpath_ctx != NULL) {
xmlXPathFreeContext(ctx->xpath_ctx);
ctx->xpath_ctx = NULL;
}
free(ctx);
}
static void xmlparsing_genericxml_error(void *ctx, const char *fmt, ...) {
xmlparsing_context_t *xmlCtx = (xmlparsing_context_t *)ctx;
int sl = strlen(xmlCtx->errorBuffer);
va_list args;
va_start(args, fmt);
vsnprintf(xmlCtx->errorBuffer + sl, XMLPARSING_BUFFER_LEN - sl, fmt, args);
va_end(args);
}
static struct _LinphonePresenceNote * presence_note_new(const char *content, const char *lang) {
struct _LinphonePresenceNote * note = ms_new0(struct _LinphonePresenceNote, 1);
note->content = ms_strdup(content);
if (lang != NULL) {
note->lang = ms_strdup(lang);
}
return note;
}
static void presence_note_delete(struct _LinphonePresenceNote *note) {
ms_free(note->content);
if (note->lang != NULL) {
ms_free(note->lang);
}
ms_free(note);
}
static struct _LinphonePresenceService * presence_service_new(const char *id, LinphonePresenceBasicStatus status) {
struct _LinphonePresenceService *service = ms_new0(struct _LinphonePresenceService, 1);
if (id != NULL) {
service->id = ms_strdup(id);
}
service->status = status;
return service;
}
static void presence_service_delete(struct _LinphonePresenceService *service) {
if (service->id != NULL) {
ms_free(service->id);
}
ms_free(service);
};
static struct _LinphonePresenceActivity * presence_activity_new(LinphonePresenceActivity activity, const char *description) {
struct _LinphonePresenceActivity *act = ms_new0(struct _LinphonePresenceActivity, 1);
act->activity = activity;
if (description != NULL) {
act->description = ms_strdup(description);
}
return act;
}
static void presence_activity_delete(struct _LinphonePresenceActivity *activity) {
if (activity->description != NULL) {
ms_free(activity->description);
}
ms_free(activity);
}
static time_t parse_timestamp(const char *timestamp) {
struct tm ret;
time_t seconds;
memset(&ret, 0, sizeof(ret));
sscanf(timestamp, "%d-%d-%dT%d:%d:%d",
&ret.tm_year, &ret.tm_mon, &ret.tm_mday, &ret.tm_hour, &ret.tm_min, &ret.tm_sec);
ret.tm_mon--;
ret.tm_year -= 1900;
ret.tm_isdst = 0;
seconds = mktime(&ret);
if (seconds == (time_t)-1) {
ms_error("mktime() failed: %s", strerror(errno));
return (time_t)-1;
}
return seconds - timezone;
}
static struct _LinphonePresencePerson * presence_person_new(const char *id, const char *timestamp) {
struct _LinphonePresencePerson *person = ms_new0(struct _LinphonePresencePerson, 1);
if (id != NULL) {
person->id = ms_strdup(id);
}
if (timestamp != NULL) {
person->timestamp = parse_timestamp(timestamp);
if (person->timestamp == ((time_t)-1))
person->timestamp = time(NULL);
} else {
person->timestamp = time(NULL);
}
return person;
}
static void presence_person_delete(struct _LinphonePresencePerson *person) {
if (person->id != NULL) {
ms_free(person->id);
}
ms_list_for_each(person->activities, (MSIterateFunc)presence_activity_delete);
ms_list_free(person->activities);
ms_list_for_each(person->activities_notes, (MSIterateFunc)presence_note_delete);
ms_list_free(person->activities_notes);
ms_list_for_each(person->notes, (MSIterateFunc)presence_note_delete);
ms_list_free(person->notes);
ms_free(person);
}
static void presence_person_add_activity(struct _LinphonePresencePerson *person, struct _LinphonePresenceActivity *activity) {
person->activities = ms_list_append(person->activities, activity);
}
static void presence_person_add_activities_note(struct _LinphonePresencePerson *person, struct _LinphonePresenceNote *note) {
person->activities_notes = ms_list_append(person->activities_notes, note);
}
static void presence_person_add_note(struct _LinphonePresencePerson *person, struct _LinphonePresenceNote *note) {
person->notes = ms_list_append(person->notes, note);
}
static void presence_model_add_service(LinphonePresenceModel *model, struct _LinphonePresenceService *service) {
model->services = ms_list_append(model->services, service);
}
static void presence_model_add_person(LinphonePresenceModel *model, struct _LinphonePresencePerson *person) {
model->persons = ms_list_append(model->persons, person);
}
static void presence_model_add_note(LinphonePresenceModel *model, struct _LinphonePresenceNote *note) {
model->notes = ms_list_append(model->notes, note);
}
static void presence_model_find_open_basic_status(struct _LinphonePresenceService *service, LinphonePresenceBasicStatus *status) {
if (service->status == LinphonePresenceBasicStatusOpen) {
*status = LinphonePresenceBasicStatusOpen;
}
}
LinphonePresenceModel * linphone_presence_model_new(void) {
return ms_new0(LinphonePresenceModel, 1);
}
void linphone_presence_model_delete(LinphonePresenceModel *model) {
ms_list_for_each(model->services, (MSIterateFunc)presence_service_delete);
ms_list_free(model->services);
ms_list_for_each(model->persons, (MSIterateFunc)presence_person_delete);
ms_list_free(model->persons);
ms_list_for_each(model->notes, (MSIterateFunc)presence_note_delete);
ms_list_free(model->notes);
ms_free(model);
}
/* Suppose that if at least one service is open, then the model is open. */
LinphonePresenceBasicStatus linphone_presence_model_get_basic_status(const LinphonePresenceModel *model) {
LinphonePresenceBasicStatus status = LinphonePresenceBasicStatusClosed;
ms_list_for_each2(model->services, (MSIterate2Func)presence_model_find_open_basic_status, &status);
return status;
}
static void presence_model_count_activities(const struct _LinphonePresencePerson *person, unsigned int *nb) {
*nb += ms_list_size(person->activities);
}
unsigned int linphone_presence_model_nb_activities(const LinphonePresenceModel *model) {
unsigned int nb = 0;
ms_list_for_each2(model->persons, (MSIterate2Func)presence_model_count_activities, &nb);
return nb;
}
struct _get_activity_st {
unsigned int requested_idx;
unsigned int current_idx;