Commit 355dafd0 authored by Kai Vehmanen's avatar Kai Vehmanen

Major update to the nice/agent interface: Added full-mode API and initial...

Major update to the nice/agent interface: Added full-mode API and initial implementation using the new nice/stun interface. Added unit test test-fullmode.c to cover the added functionality. Some public APIs of nice/agent/agent.h have been modified, making this change API/ABI incompatible.

darcs-hash:20070521153033-77cd4-c76ab583d06839e601f46b6734355dd8b66f7494.gz
parent 91d4e0a1
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2006, 2007 Collabora Ltd.
* Contact: Dafydd Harries
* (C) 2006, 2007 Nokia Corporation. All rights reserved.
* Contact: Kai Vehmanen
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Dafydd Harries, Collabora Ltd.
* Kai Vehmanen, Nokia
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
#ifndef _NICE_AGENT_PRIV_H
#define _NICE_AGENT_PRIV_H
/* note: this is a private header part of agent.h */
#include <glib.h>
#include "agent.h"
#include "component.h"
#include "candidate.h"
#include "stream.h"
#include "conncheck.h"
#define NICE_AGENT_TIMER_TA_DEFAULT 20 /* timer Ta, msecs */
#define NICE_AGENT_TIMER_TR_DEFAULT 15000 /* timer Tr, msecs */
/** An upper limit to size of STUN packets handled (based on Ethernet
* MTU and estimated typical sizes of ICE STUN packet */
#define MAX_STUN_DATAGRAM_PAYLOAD 1300
struct _NiceAgent
{
GObject parent; /**< gobject pointer */
gboolean full_mode; /**< property: full-mode */
NiceUDPSocketFactory *socket_factory; /**< property: socket factory */
GTimeVal next_check_tv; /**< property: next conncheck timestamp */
gchar *stun_server_ip; /**< property: STUN server IP */
guint stun_server_port; /**< property: STUN server port */
gchar *turn_server_ip; /**< property: TURN server IP */
guint turn_server_port; /**< property: TURN server port */
gboolean controlling_mode; /**< property: controlling-mode */
GSList *local_addresses; /**< list of NiceAddresses for local
interfaces */
GSList *streams; /**< list of Stream objects */
gboolean main_context_set; /**< is the main context set */
GMainContext *main_context; /**< main context pointer */
NiceAgentRecvFunc read_func; /**< callback for media deliver */
gpointer read_func_data; /**< media delivery callback context */
guint next_candidate_id; /**< id of next created candidate */
guint next_stream_id; /**< id of next created candidate */
NiceRNG *rng; /**< random number generator */
GSList *discovery_list; /**< list of CandidateDiscovery items */
guint discovery_unsched_items; /**< number of discovery items unscheduled */
guint discovery_timer_id; /**< id of discovery timer */
GSList *conncheck_list; /**< list of CandidatePair items */
guint conncheck_timer_id; /**< id of discovery timer */
NiceCheckListState conncheck_state; /**< checklist state */
/* XXX: add pointer to internal data struct for ABI-safe extensions */
};
gboolean
agent_find_component (
NiceAgent *agent,
guint stream_id,
guint component_id,
Stream **stream,
Component **component);
Stream *agent_find_stream (NiceAgent *agent, guint stream_id);
void agent_signal_gathering_done (NiceAgent *agent);
void agent_signal_new_selected_pair (
NiceAgent *agent,
guint stream_id,
guint component_id,
const gchar *local_foundation,
const gchar *remote_foundation);
void agent_signal_component_state_change (
NiceAgent *agent,
guint stream_id,
guint component_id,
NiceComponentState state);
void agent_signal_new_candidate (
NiceAgent *agent,
NiceCandidate *candidate);
void agent_signal_initial_binding_request_received (NiceAgent *agent, Stream *stream);
void agent_free_discovery_candidate_udp (gpointer data, gpointer user_data);
#endif /*_NICE_AGENT_PRIV_H */
......@@ -2,3 +2,10 @@
VOID:UINT,UINT,UINT
# candidate-gathering-done
VOID:VOID
# new-selected-pair
VOID:UINT,UINT,STRING,STRING
# new-candidate
VOID:UINT,UINT,STRING
# initial-binding-request-received
VOID:UINT
......@@ -23,6 +23,7 @@
*
* Contributors:
* Dafydd Harries, Collabora Ltd.
* Kai Vehmanen, Nokia
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
......@@ -50,74 +51,31 @@
#include <glib.h>
#include "stun/bind.h"
#include "stun.h"
#include "udp.h"
#include "candidate.h"
#include "component.h"
#include "conncheck.h"
#include "discovery.h"
#include "agent.h"
#include "agent-priv.h"
#include "agent-signals-marshal.h"
typedef enum
{
NICE_CHECK_WAITING = 1,
NICE_CHECK_IN_PROGRESS,
NICE_CHECK_SUCCEEDED,
NICE_CHECK_FAILED,
NICE_CHECK_FROZEN
} NiceCheckState;
typedef struct _CandidateDiscoveryUDP CandidateDiscoveryUDP;
struct _CandidateDiscoveryUDP
{
NiceAgent *agent; /* back pointer to owner */
NiceCandidateType type; /* candidate type STUN or TURN */
int socket; /* existing socket to use */
gchar *server_addr; /* STUN/TURN server address */
NiceAddress *interface; /* Address of local interface */
stun_bind_t *ctx;
GTimeVal next_tick; /* next tick timestamp */
gboolean pending; /* is discovery in progress? */
gboolean done; /* is discovery complete? */
guint stream_id;
guint component_id;
};
typedef struct _CandidatePair CandidatePair;
struct _CandidatePair
{
NiceAgent *agent; /* back pointer to owner */
guint stream_id;
guint component_id;
NiceCandidate *local;
NiceCandidate *remote;
gchar *foundation;
NiceCheckState state;
};
#include "stream.h"
typedef enum
{
CHECK_LIST_STATE_RUNNING,
CHECK_LIST_STATE_COMPLETED,
} CheckListState;
#define NICE_AGENT_TIMER_TA_DEFAULT 20; /* timer Ta, msecs */
#define NICE_AGENT_TIMER_TR_DEFAULT 15000; /* timer Tr, msecs */
G_DEFINE_TYPE (NiceAgent, nice_agent, G_TYPE_OBJECT);
enum
{
PROP_SOCKET_FACTORY = 1,
PROP_STUN_SERVER,
PROP_STUN_SERVER_PORT,
PROP_TURN_SERVER,
PROP_TURN_SERVER_PORT
PROP_TURN_SERVER_PORT,
PROP_CONTROLLING_MODE,
PROP_FULL_MODE
};
......@@ -125,15 +83,19 @@ enum
{
SIGNAL_COMPONENT_STATE_CHANGED,
SIGNAL_CANDIDATE_GATHERING_DONE,
SIGNAL_NEW_SELECTED_PAIR,
SIGNAL_NEW_CANDIDATE,
SIGNAL_INITIAL_BINDING_REQUEST_RECEIVED,
N_SIGNALS,
};
static guint signals[N_SIGNALS];
static gboolean priv_attach_new_stream (NiceAgent *agent, Stream *stream);
static void priv_deattach_stream (Stream *stream);
static Stream *
find_stream (NiceAgent *agent, guint stream_id)
Stream *agent_find_stream (NiceAgent *agent, guint stream_id)
{
GSList *i;
......@@ -149,8 +111,8 @@ find_stream (NiceAgent *agent, guint stream_id)
}
static gboolean
find_component (
gboolean
agent_find_component (
NiceAgent *agent,
guint stream_id,
guint component_id,
......@@ -162,7 +124,7 @@ find_component (
if (component_id != 1)
return FALSE;
s = find_stream (agent, stream_id);
s = agent_find_stream (agent, stream_id);
if (s == NULL)
return FALSE;
......@@ -205,6 +167,11 @@ nice_agent_class_init (NiceAgentClass *klass)
gobject_class->dispose = nice_agent_dispose;
/* install properties */
/* XXX: add properties:
* - Ta-timer (construct-property, msec)
* - make the others construct-time only as well...?
*/
g_object_class_install_property (gobject_class, PROP_SOCKET_FACTORY,
g_param_spec_pointer (
......@@ -227,7 +194,7 @@ nice_agent_class_init (NiceAgentClass *klass)
"STUN server port",
"The STUN server used to obtain server-reflexive candidates",
1, 65536,
3478, /* default port */
IPPORT_STUN, /* default port */
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_STUN_SERVER,
......@@ -247,8 +214,25 @@ nice_agent_class_init (NiceAgentClass *klass)
3478, /* no default port for TURN, use the STUN default*/
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CONTROLLING_MODE,
g_param_spec_boolean (
"controlling-mode",
"ICE controlling mode",
"Whether the agent is in controlling mode",
FALSE, /* not a construct property, ignored */
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FULL_MODE,
g_param_spec_boolean (
"full-mode",
"ICE full mode",
"Whether agent runs in ICE full mode",
TRUE, /* use full mode by default */
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* install signals */
/* signature: void cb(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer self) */
signals[SIGNAL_COMPONENT_STATE_CHANGED] =
g_signal_new (
"component-state-changed",
......@@ -263,6 +247,7 @@ nice_agent_class_init (NiceAgentClass *klass)
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT,
G_TYPE_INVALID);
/* signature: void cb(NiceAgent *agent, gpointer self) */
signals[SIGNAL_CANDIDATE_GATHERING_DONE] =
g_signal_new (
"candidate-gathering-done",
......@@ -275,6 +260,53 @@ nice_agent_class_init (NiceAgentClass *klass)
G_TYPE_NONE,
0,
G_TYPE_INVALID);
/* signature: void cb(NiceAgent *agent, guint stream_id, guint component_id,
gchar *lfoundation, gchar* rfoundation, gpointer self) */
signals[SIGNAL_NEW_SELECTED_PAIR] =
g_signal_new (
"new-selected-pair",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL,
NULL,
agent_marshal_VOID__UINT_UINT_STRING_STRING,
G_TYPE_NONE,
4,
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INVALID);
/* signature: void cb(NiceAgent *agent, guint stream_id, guint component_id, gchar *foundation) */
signals[SIGNAL_NEW_CANDIDATE] =
g_signal_new (
"new-candidate",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL,
NULL,
agent_marshal_VOID__UINT_UINT_STRING,
G_TYPE_NONE,
3,
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING,
G_TYPE_INVALID);
/* signature: void cb(NiceAgent *agent, guint stream_id, gpointer self) */
signals[SIGNAL_INITIAL_BINDING_REQUEST_RECEIVED] =
g_signal_new (
"initial-binding-request-received",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL,
NULL,
agent_marshal_VOID__UINT,
G_TYPE_NONE,
1,
G_TYPE_UINT,
G_TYPE_INVALID);
}
......@@ -284,11 +316,17 @@ nice_agent_init (NiceAgent *agent)
agent->next_candidate_id = 1;
agent->next_stream_id = 1;
agent->discovery_unsched_items = 0;
/* set defaults; not construct params, so set here */
g_assert (agent->stun_server_port != IPPORT_STUN);
g_assert (agent->turn_server_port != IPPORT_STUN);
agent->stun_server_port = IPPORT_STUN;
agent->turn_server_port = IPPORT_STUN;
agent->controlling_mode = TRUE;
/* XXX: make configurable */
agent->full_mode = TRUE;
agent->discovery_list = NULL;
agent->discovery_unsched_items = 0;
agent->discovery_timer_id = 0;
agent->conncheck_timer_id = 0;
agent->rng = nice_rng_new ();
}
......@@ -328,15 +366,27 @@ nice_agent_get_property (
case PROP_STUN_SERVER:
g_value_set_string (value, agent->stun_server_ip);
break;
case PROP_STUN_SERVER_PORT:
g_value_set_uint (value, agent->stun_server_port);
break;
case PROP_TURN_SERVER:
g_value_set_string (value, agent->turn_server_ip);
break;
case PROP_TURN_SERVER_PORT:
g_value_set_uint (value, agent->turn_server_port);
break;
case PROP_CONTROLLING_MODE:
g_value_set_boolean (value, agent->controlling_mode);
break;
case PROP_FULL_MODE:
g_value_set_boolean (value, agent->full_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
......@@ -371,138 +421,63 @@ nice_agent_set_property (
agent->turn_server_ip = g_value_dup_string (value);
break;
case PROP_CONTROLLING_MODE:
agent->controlling_mode = g_value_get_boolean (value);
break;
case PROP_FULL_MODE:
agent->full_mode = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static int
nice_agent_get_local_host_candidate_sockfd (
NiceAgent *agent,
guint stream_id,
guint component_id,
NiceAddress *address)
void agent_signal_gathering_done (NiceAgent *agent)
{
Component *component;
GSList *i;
if (!find_component (agent, stream_id, component_id, NULL, &component)) {
return -1;
}
for (i = component->local_candidates; i; i = i->next) {
NiceCandidate *candidate = i->data;
/* note nice_address_equal() also compares the ports and
* we don't want that here */
if (address->type == NICE_ADDRESS_TYPE_IPV4 &&
address->addr_ipv4 == candidate->base_addr.addr_ipv4)
return candidate->sock.fileno;
}
return -1;
g_signal_emit (agent, signals[SIGNAL_CANDIDATE_GATHERING_DONE], 0);
}
static void
nice_agent_add_local_host_candidate (
NiceAgent *agent,
guint stream_id,
guint component_id,
NiceAddress *address)
{
NiceCandidate *candidate;
Component *component;
if (!find_component (agent, stream_id, component_id, NULL, &component))
return;
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_HOST);
candidate->id = agent->next_candidate_id++;
candidate->stream_id = stream_id;
candidate->component_id = component_id;
candidate->addr = *address;
candidate->base_addr = *address;
component->local_candidates = g_slist_append (component->local_candidates,
candidate);
candidate->priority =
0x1000000 * 126 + 0x100 * 0 + 256 - component_id; /* sect:4.1.2.1(-14) */
/* generate username/password */
nice_rng_generate_bytes_print (agent->rng, 8, candidate->username);
nice_rng_generate_bytes_print (agent->rng, 8, candidate->password);
/* allocate socket */
/* XXX: handle error */
if (!nice_udp_socket_factory_make (agent->socket_factory,
&(candidate->sock), address))
g_assert_not_reached ();
candidate->addr = candidate->sock.addr;
candidate->base_addr = candidate->sock.addr;
}
/* compiles but is not called yet */
static void
nice_agent_generate_username_and_password(NiceCandidate *candidate)
void agent_signal_initial_binding_request_received (NiceAgent *agent, Stream *stream)
{
NiceRNG *rng;
/* generate username/password */
rng = nice_rng_new ();
nice_rng_generate_bytes_print (rng, 8, candidate->username);
nice_rng_generate_bytes_print (rng, 8, candidate->password);
nice_rng_free (rng);
if (stream->initial_binding_request_received != TRUE) {
stream->initial_binding_request_received = TRUE;
g_signal_emit (agent, signals[SIGNAL_INITIAL_BINDING_REQUEST_RECEIVED], 0, stream->id);
}
}
static void
nice_agent_add_server_reflexive_candidate (
NiceAgent *agent,
guint stream_id,
guint component_id,
NiceAddress *address)
void agent_signal_new_selected_pair (NiceAgent *agent, guint stream_id, guint component_id, const gchar *local_foundation, const gchar *remote_foundation)
{
NiceCandidate *candidate;
Component *component;
if (!find_component (agent, stream_id, component_id, NULL, &component))
if (!agent_find_component (agent, stream_id, component_id, NULL, &component))
return;
candidate = nice_candidate_new (NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE);
candidate->id = agent->next_candidate_id++;
candidate->stream_id = stream_id;
candidate->component_id = component_id;
candidate->addr = *address;
candidate->base_addr = *address;
component->local_candidates = g_slist_append (component->local_candidates,
candidate);
candidate->priority =
0x1000000 * 125 + 0x100 * 0 + 256 - component_id; /* sect:4.1.2.1(-14) */
/* generate username/password */
nice_agent_generate_username_and_password (candidate);
/* XXX: how to link to the socket of a local candidate? */
#if 0
candidate->base_addr = candidate->sock.addr;
#endif
g_signal_emit (agent, signals[SIGNAL_NEW_SELECTED_PAIR], 0,
stream_id, component_id,
local_foundation, remote_foundation);
}
static void nice_agent_free_discovery_candidate_udp (gpointer data, gpointer user_data)
void agent_signal_new_candidate (NiceAgent *agent, NiceCandidate *candidate)
{
CandidateDiscoveryUDP *cand_udp = data;
g_free (cand_udp->server_addr);
g_slice_free (CandidateDiscoveryUDP, cand_udp);
g_signal_emit (agent, signals[SIGNAL_NEW_CANDIDATE], 0,
candidate->stream_id,
candidate->component_id,
candidate->foundation);
}
static void priv_signal_component_state_gathering (NiceAgent *agent, guint stream_id, guint component_id)
void agent_signal_component_state_change (NiceAgent *agent, guint stream_id, guint component_id, NiceComponentState state)
{
Component *component;
if (!find_component (agent, stream_id, component_id, NULL, &component))
if (!agent_find_component (agent, stream_id, component_id, NULL, &component))
return;
if (component->state != NICE_COMPONENT_STATE_GATHERING) {
component->state = NICE_COMPONENT_STATE_GATHERING;
if (component->state != state && state < NICE_COMPONENT_STATE_LAST) {
g_debug ("stream %u component %u state change %u -> %u.",
stream_id, component_id, component->state, state);
component->state = state;
g_signal_emit (agent, signals[SIGNAL_COMPONENT_STATE_CHANGED], 0,
stream_id, component_id, component->state);
......@@ -514,7 +489,7 @@ static void priv_signal_component_state_connecting (NiceAgent *agent, guint stre
{
Component *component;
if (!find_component (agent, stream_id, component_id, NULL, &component))
if (!agent_find_component (agent, stream_id, component_id, NULL, &component))
return;
if (component->state != NICE_COMPONENT_STATE_CONNECTING) {
......@@ -526,197 +501,14 @@ static void priv_signal_component_state_connecting (NiceAgent *agent, guint stre
}
#endif
/**
* Timer callback that handles scheduling new candidate discovery
* processes (paced by the Ta timer), and handles running of the
* existing discovery processes.
*
* This function is designed for the g_timeout_add() interface.
*
* @return will return FALSE when no more pending timers.
*/
static gboolean nice_agent_discovery_tick (gpointer pointer)
{
CandidateDiscoveryUDP *cand_udp;
NiceAgent *agent = pointer;
GSList *i;
int not_done = 0;
g_debug ("check tick with list %p (1)", agent->discovery_list);
for (i = agent->discovery_list; i ; i = i->next) {
cand_udp = i->data;
if (cand_udp->pending != TRUE) {
cand_udp->pending = TRUE;
if (agent->discovery_unsched_items)
--agent->discovery_unsched_items;
g_debug ("scheduling cand type %u addr %s and socket %d.\n", cand_udp->type, cand_udp->server_addr, cand_udp->socket);
if (cand_udp->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) {
struct sockaddr_in stun_server;
memset (&stun_server, 0, sizeof(stun_server));
/* XXX: using hardcoded server address for now */
stun_server.sin_addr.s_addr = inet_addr("127.0.0.1");
stun_server.sin_port = htons(3478);
int res;
res = stun_bind_start (&cand_udp->ctx, cand_udp->socket,
(struct sockaddr*)&stun_server, sizeof(stun_server));
if (res == 0) {
/* case: success, start waiting for the result */
g_get_current_time (&cand_udp->next_tick);
priv_signal_component_state_gathering (agent,
cand_udp->stream_id,
cand_udp->component_id);
}
else {
/* case: error in starting discovery, start the next discovery */
cand_udp->done = TRUE;
continue;
}
}
else
/* allocate relayed candidates */
g_assert_not_reached ();
++not_done;
}
if (cand_udp->done != TRUE) {
struct sockaddr_in mapped_addr;
socklen_t socklen = sizeof(mapped_addr);
GTimeVal now;
g_get_current_time (&now);
/* note: macro from sys/time.h but compatible with GTimeVal */
if (timercmp(&cand_udp->next_tick, &now, <)) {
int res = stun_bind_resume (cand_udp->ctx, (struct sockaddr*)&mapped_addr, &socklen);
if (res == 0) {
/* case: discovery process succesfully completed */
NiceAddress *niceaddr;
niceaddr = nice_address_new();
niceaddr->type = NICE_ADDRESS_TYPE_IPV4;
niceaddr->addr_ipv4 = ntohl(mapped_addr.sin_addr.s_addr);
niceaddr->port = ntohs(mapped_addr.sin_port);
{
gchar ip[NICE_ADDRESS_STRING_LEN];
nice_address_to_string (niceaddr, ip);
g_debug("%s: our public contact address is %s\n",
__func__, ip);
}
/* XXX: add
* g_signal_emit (agent, signals[SIGNAL_NEW_CANDIDATE], 0); */
nice_agent_add_server_reflexive_candidate (
cand_udp->agent,
cand_udp->stream_id,
cand_udp->component_id,
niceaddr);
nice_address_free (niceaddr);
cand_udp->done = TRUE;
}
else if (res == EAGAIN) {
/* case: not ready complete, so schedule next timeout */
unsigned int timeout = stun_bind_timeout (cand_udp->ctx);
g_get_current_time (&cand_udp->next_tick);
g_time_val_add (&cand_udp->next_tick, timeout * 10);
/* note: macro from sys/time.h but compatible with GTimeVal */
if (timercmp(&cand_udp->next_tick, &agent->next_check_tv, <)) {
agent->next_check_tv = cand_udp->next_tick;
}
++not_done;
}
else {
/* case: error, abort processing */
cand_udp->done = TRUE;
}
}
}
}
if (not_done == 0) {
g_debug ("Candidate gathering FINISHED, stopping Ta timer.");
g_slist_foreach (agent->discovery_list, nice_agent_free_discovery_candidate_udp, NULL);
g_slist_free (agent->discovery_list),
agent->discovery_list = NULL;
g_signal_emit (agent, signals[SIGNAL_CANDIDATE_GATHERING_DONE], 0);
/* note: no pending timers, return FALSE to stop timer */
return FALSE;