summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-ua/sip_reg.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_reg.c')
-rw-r--r--pjsip/src/pjsip-ua/sip_reg.c434
1 files changed, 315 insertions, 119 deletions
diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c
index b015170d..d6f85720 100644
--- a/pjsip/src/pjsip-ua/sip_reg.c
+++ b/pjsip/src/pjsip-ua/sip_reg.c
@@ -35,7 +35,7 @@
#define REFRESH_TIMER 1
-#define DELAY_BEFORE_REFRESH 5
+#define DELAY_BEFORE_REFRESH PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH
#define THIS_FILE "sip_reg.c"
/* Outgoing transaction timeout when server sends 100 but never replies
@@ -43,6 +43,18 @@
*/
#define REGC_TSX_TIMEOUT 33000
+enum { NOEXP = 0x1FFFFFFF };
+
+static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 };
+
+
+/* Current/pending operation */
+enum regc_op
+{
+ REGC_IDLE,
+ REGC_REGISTERING,
+ REGC_UNREGISTERING
+};
/**
* SIP client registration structure.
@@ -54,6 +66,9 @@ struct pjsip_regc
pj_bool_t _delete_flag;
pj_bool_t has_tsx;
pj_int32_t busy;
+ enum regc_op current_op;
+
+ pj_bool_t add_xuid_param;
void *token;
pjsip_regc_cb *cb;
@@ -65,10 +80,9 @@ struct pjsip_regc
pj_str_t from_uri;
pjsip_from_hdr *from_hdr;
pjsip_to_hdr *to_hdr;
- pjsip_hdr contact_hdr_list;
+ pjsip_contact_hdr contact_hdr_list;
+ pjsip_contact_hdr removed_contact_hdr_list;
pjsip_expires_hdr *expires_hdr;
- pjsip_contact_hdr *unreg_contact_hdr;
- pjsip_expires_hdr *unreg_expires_hdr;
pj_uint32_t expires;
pjsip_route_hdr route_set;
pjsip_hdr hdr_list;
@@ -92,7 +106,6 @@ struct pjsip_regc
};
-
PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
pjsip_regc_cb *cb,
pjsip_regc **p_regc)
@@ -114,6 +127,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
regc->token = token;
regc->cb = cb;
regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
+ regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param;
status = pjsip_auth_clt_init(&regc->auth_sess, endpt, regc->pool, 0);
if (status != PJ_SUCCESS)
@@ -122,6 +136,7 @@ PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
pj_list_init(&regc->route_set);
pj_list_init(&regc->hdr_list);
pj_list_init(&regc->contact_hdr_list);
+ pj_list_init(&regc->removed_contact_hdr_list);
/* Done */
*p_regc = regc;
@@ -142,6 +157,10 @@ PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc)
pjsip_transport_dec_ref(regc->last_transport);
regc->last_transport = NULL;
}
+ if (regc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+ }
pjsip_endpt_release_pool(regc->endpt, regc->pool);
}
@@ -199,17 +218,31 @@ static pj_status_t set_contact( pjsip_regc *regc,
const pj_str_t contact[] )
{
const pj_str_t CONTACT = { "Contact", 7 };
+ pjsip_contact_hdr *h;
int i;
- /* Clear existing contacts */
- pj_list_init(&regc->contact_hdr_list);
+ /* Save existing contact list to removed_contact_hdr_list and
+ * clear contact_hdr_list.
+ */
+ pj_list_merge_last(&regc->removed_contact_hdr_list,
+ &regc->contact_hdr_list);
+
+ /* Set the expiration of Contacts in to removed_contact_hdr_list
+ * zero.
+ */
+ h = regc->removed_contact_hdr_list.next;
+ while (h != &regc->removed_contact_hdr_list) {
+ h->expires = 0;
+ h = h->next;
+ }
+ /* Process new contacts */
for (i=0; i<contact_cnt; ++i) {
- pjsip_hdr *hdr;
+ pjsip_contact_hdr *hdr;
pj_str_t tmp;
pj_strdup_with_null(regc->pool, &tmp, &contact[i]);
- hdr = (pjsip_hdr*)
+ hdr = (pjsip_contact_hdr*)
pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL);
if (hdr == NULL) {
PJ_LOG(4,(THIS_FILE, "Invalid Contact URI: \"%.*s\"",
@@ -217,6 +250,42 @@ static pj_status_t set_contact( pjsip_regc *regc,
return PJSIP_EINVALIDURI;
}
+ /* Find the new contact in old contact list. If found, remove
+ * the old header from the old header list.
+ */
+ h = regc->removed_contact_hdr_list.next;
+ while (h != &regc->removed_contact_hdr_list) {
+ int rc;
+
+ rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
+ h->uri, hdr->uri);
+ if (rc == 0) {
+ /* Match */
+ pj_list_erase(h);
+ break;
+ }
+
+ h = h->next;
+ }
+
+ /* If add_xuid_param option is enabled and Contact URI is sip/sips,
+ * add xuid parameter to assist matching the Contact URI in the
+ * REGISTER response later.
+ */
+ if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(hdr->uri)))
+ {
+ pjsip_param *xuid_param;
+ pjsip_sip_uri *sip_uri;
+
+ xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param);
+ xuid_param->name = XUID_PARAM_NAME;
+ pj_create_unique_string(regc->pool, &xuid_param->value);
+
+ sip_uri = pjsip_uri_get_uri(hdr->uri);
+ pj_list_push_back(&sip_uri->other_param, xuid_param);
+ }
+
pj_list_push_back(&regc->contact_hdr_list, hdr);
}
@@ -288,13 +357,6 @@ PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc,
regc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
pjsip_method_set( &regc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
- /* Create "Contact" header used in unregistration. */
- regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool);
- regc->unreg_contact_hdr->star = 1;
-
- /* Create "Expires" header used in unregistration. */
- regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool, 0);
-
/* Done. */
return PJ_SUCCESS;
}
@@ -436,7 +498,7 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
pjsip_tx_data **p_tdata)
{
pjsip_msg *msg;
- pjsip_hdr *hdr;
+ pjsip_contact_hdr *hdr;
pj_status_t status;
pjsip_tx_data *tdata;
@@ -456,6 +518,15 @@ PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
hdr = hdr->next;
}
+ /* Also add bindings which are to be removed */
+ while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+ hdr = regc->removed_contact_hdr_list.next;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ pj_list_erase(hdr);
+ }
+
+
if (regc->expires_hdr)
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool,
@@ -496,14 +567,24 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc,
msg = tdata->msg;
/* Add Contact headers. */
- hdr = regc->contact_hdr_list.next;
- while (hdr != &regc->contact_hdr_list) {
+ hdr = (pjsip_hdr*)regc->contact_hdr_list.next;
+ while (hdr != (pjsip_hdr*)&regc->contact_hdr_list) {
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, hdr));
hdr = hdr->next;
}
- pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr);
+ /* Also add bindings which are to be removed */
+ while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+ hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ pj_list_erase(hdr);
+ }
+
+ /* Add Expires:0 header */
+ hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr(msg, hdr);
*p_tdata = tdata;
return PJ_SUCCESS;
@@ -513,6 +594,8 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
+ pjsip_contact_hdr *hcontact;
+ pjsip_hdr *hdr;
pjsip_msg *msg;
pj_status_t status;
@@ -528,8 +611,18 @@ PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
return status;
msg = tdata->msg;
- pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr);
- pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr);
+
+ /* Clear removed_contact_hdr_list */
+ pj_list_init(&regc->removed_contact_hdr_list);
+
+ /* Add Contact:* header */
+ hcontact = pjsip_contact_hdr_create(tdata->pool);
+ hcontact->star = 1;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact);
+
+ /* Add Expires:0 header */
+ hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr(msg, hdr);
*p_tdata = tdata;
return PJ_SUCCESS;
@@ -615,6 +708,173 @@ static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
}
}
+static pj_int32_t calculate_response_expiration(const pjsip_regc *regc,
+ const pjsip_rx_data *rdata,
+ unsigned *contact_cnt,
+ unsigned max_contact,
+ pjsip_contact_hdr *contacts[])
+{
+ pj_int32_t expiration = NOEXP;
+ const pjsip_msg *msg = rdata->msg_info.msg;
+ const pjsip_hdr *hdr;
+
+ /* Enumerate all Contact headers in the response */
+ *contact_cnt = 0;
+ for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
+ if (hdr->type == PJSIP_H_CONTACT &&
+ *contact_cnt < max_contact)
+ {
+ contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
+ ++(*contact_cnt);
+ }
+ }
+
+ if (regc->current_op == REGC_REGISTERING) {
+ pj_bool_t has_our_contact = PJ_FALSE;
+ const pjsip_expires_hdr *expires;
+
+ /* Get Expires header */
+ expires = (const pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+ /* Try to find the Contact URIs that we register, in the response
+ * to get the expires value. We'll try both with comparing the URI
+ * and comparing the extension param only.
+ */
+ if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
+ unsigned i;
+ for (i=0; i<*contact_cnt; ++i) {
+ const pjsip_contact_hdr *our_hdr;
+
+ our_hdr = (const pjsip_contact_hdr*)
+ regc->contact_hdr_list.next;
+
+ /* Match with our Contact header(s) */
+ while ((void*)our_hdr != (void*)&regc->contact_hdr_list) {
+
+ const pjsip_uri *uri1, *uri2;
+ pj_bool_t matched = PJ_FALSE;
+
+ /* Exclude the display name when comparing the URI
+ * since server may not return it.
+ */
+ uri1 = (const pjsip_uri*)
+ pjsip_uri_get_uri(contacts[i]->uri);
+ uri2 = (const pjsip_uri*)
+ pjsip_uri_get_uri(our_hdr->uri);
+
+ /* First try with exact matching, according to RFC 3261
+ * Section 19.1.4 URI Comparison
+ */
+ if (pjsip_cfg()->regc.check_contact) {
+ matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
+ uri1, uri2)==0;
+ }
+
+ /* If no match is found, try with matching the extension
+ * parameter only if extension parameter was added.
+ */
+ if (!matched && regc->add_xuid_param &&
+ (PJSIP_URI_SCHEME_IS_SIP(uri1) ||
+ PJSIP_URI_SCHEME_IS_SIPS(uri1)) &&
+ (PJSIP_URI_SCHEME_IS_SIP(uri2) ||
+ PJSIP_URI_SCHEME_IS_SIPS(uri2)))
+ {
+ const pjsip_sip_uri *sip_uri1, *sip_uri2;
+ const pjsip_param *p1, *p2;
+
+ sip_uri1 = (const pjsip_sip_uri*)uri1;
+ sip_uri2 = (const pjsip_sip_uri*)uri2;
+
+ p1 = pjsip_param_cfind(&sip_uri1->other_param,
+ &XUID_PARAM_NAME);
+ p2 = pjsip_param_cfind(&sip_uri2->other_param,
+ &XUID_PARAM_NAME);
+ matched = p1 && p2 &&
+ pj_strcmp(&p1->value, &p2->value)==0;
+
+ }
+
+ if (matched) {
+ has_our_contact = PJ_TRUE;
+
+ if (contacts[i]->expires >= 0 &&
+ contacts[i]->expires < expiration)
+ {
+ /* Get the lowest expiration time. */
+ expiration = contacts[i]->expires;
+ }
+
+ break;
+ }
+
+ our_hdr = our_hdr->next;
+
+ } /* while ((void.. */
+
+ } /* for (i=.. */
+
+ /* If matching Contact header(s) are found but the
+ * header doesn't contain expires parameter, get the
+ * expiration value from the Expires header. And
+ * if Expires header is not present, get the expiration
+ * value from the request.
+ */
+ if (has_our_contact && expiration == NOEXP) {
+ if (expires) {
+ expiration = expires->ivalue;
+ } else if (regc->expires_hdr) {
+ expiration = regc->expires_hdr->ivalue;
+ } else {
+ /* We didn't request explicit expiration value,
+ * and server doesn't specify it either. This
+ * shouldn't happen unless we have a broken
+ * registrar.
+ */
+ expiration = 3600;
+ }
+ }
+
+ }
+
+ /* If we still couldn't get matching Contact header(s), it means
+ * there must be something wrong with the registrar (e.g. it may
+ * have modified the URI's in the response, which is prohibited).
+ */
+ if (expiration==NOEXP) {
+ /* If the number of Contact headers in the response matches
+ * ours, they're all probably ours. Get the expiration
+ * from there if this is the case, or from Expires header
+ * if we don't have exact Contact header count, or
+ * from the request as the last resort.
+ */
+ unsigned our_contact_cnt;
+
+ our_contact_cnt = pj_list_size(&regc->contact_hdr_list);
+
+ if (*contact_cnt == our_contact_cnt && *contact_cnt &&
+ contacts[0]->expires >= 0)
+ {
+ expiration = contacts[0]->expires;
+ } else if (expires)
+ expiration = expires->ivalue;
+ else if (regc->expires_hdr)
+ expiration = regc->expires_hdr->ivalue;
+ else
+ expiration = 3600;
+ }
+
+ } else {
+ /* Just assume that the unregistration has been successful. */
+ expiration = 0;
+ }
+
+ /* Must have expiration value by now */
+ pj_assert(expiration != NOEXP);
+
+ return expiration;
+}
+
static void tsx_callback(void *token, pjsip_event *event)
{
pj_status_t status;
@@ -645,6 +905,9 @@ static void tsx_callback(void *token, pjsip_event *event)
pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
+ /* reset current op */
+ regc->current_op = REGC_IDLE;
+
status = pjsip_auth_clt_reinit_req( &regc->auth_sess,
rdata,
tsx->last_tx,
@@ -682,114 +945,30 @@ static void tsx_callback(void *token, pjsip_event *event)
* This regc will be destroyed later in this function.
*/
- /* Nothing to do */
- ;
+ /* Just reset current op */
+ regc->current_op = REGC_IDLE;
} else {
- int contact_cnt = 0;
- pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
pjsip_rx_data *rdata;
- enum { NOEXP = 0x1FFFFFFF };
pj_int32_t expiration = NOEXP;
+ unsigned contact_cnt = 0;
+ pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
if (tsx->status_code/100 == 2) {
- int i;
- pjsip_contact_hdr *hdr;
- pjsip_msg *msg;
- pj_bool_t has_our_contact = PJ_FALSE;
- pjsip_expires_hdr *expires;
rdata = event->body.tsx_state.src.rdata;
- msg = rdata->msg_info.msg;
-
- /* Record all Contact headers in the response */
- hdr = (pjsip_contact_hdr*)
- pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
- while (hdr) {
- contact[contact_cnt++] = hdr;
- hdr = hdr->next;
- if (hdr == (void*)&msg->hdr)
- break;
- hdr = (pjsip_contact_hdr*)
- pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr);
- }
-
- /* Set default expiration value to the value of Expires hdr */
- expires = (pjsip_expires_hdr*)
- pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
-
- if (expires)
- expiration = expires->ivalue;
-
- /* Enumerate all Contact headers found in the response and
- * find the Contact(s) that we register.
- *
- * Note:
- * by default we require that the exact same URI that we
- * register is returned in the 200/OK response (by exact,
- * meaning all URI components including transport param),
- * otherwise if we don't detect that our URI is there, we
- * treat registration as failed.
- *
- * If your registrar server couldn't do this, you can
- * disable this exact URI checking. See the compile time
- * setting PJSIP_REGISTER_CLIENT_CHECK_CONTACT or the
- * corresponding run-time setting in pjsip_cfg().
- */
- for (i=0; i<contact_cnt && pjsip_cfg()->regc.check_contact; ++i) {
- pjsip_contact_hdr *our_contact;
-
- our_contact = (pjsip_contact_hdr*)
- regc->contact_hdr_list.next;
-
- while ((void*)our_contact != (void*)&regc->contact_hdr_list) {
-
- const pjsip_uri *uri1, *uri2;
- /* Compare URIs.
- * Exclude the display name when comparing the URI since
- * server may not return it.
- */
+ /* Calculate expiration */
+ expiration = calculate_response_expiration(regc, rdata,
+ &contact_cnt,
+ PJSIP_REGC_MAX_CONTACT,
+ contact);
- uri1=(const pjsip_uri*)pjsip_uri_get_uri(contact[i]->uri);
- uri2=(const pjsip_uri*)pjsip_uri_get_uri(our_contact->uri);
- if (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, uri1, uri2)==0)
- {
- has_our_contact = PJ_TRUE;
-
- if (contact[i]->expires >= 0 &&
- contact[i]->expires < expiration)
- {
- /* Get the lowest expiration time. */
- expiration = contact[i]->expires;
- }
- }
-
- our_contact = our_contact->next;
- }
- }
-
- /* If regc.check_contact is disabled, and no Expires header
- * has been found, but the server does return one single
- * Contact header, assumes that the server is broken/unable to
- * return the correct Contact. In this case, get the expiration
- * from the single Contact header in the response.
- */
- if (expiration==NOEXP && !pjsip_cfg()->regc.check_contact &&
- contact_cnt==1)
- {
- if (contact[0]->expires >= 0)
- expiration = contact[0]->expires;
- }
-
- /* When the response doesn't contain our Contact header, that
- * means we have been unregistered.
- */
- if (pjsip_cfg()->regc.check_contact && !has_our_contact)
- expiration = 0;
+ /* Mark operation as complete */
+ regc->current_op = REGC_IDLE;
/* Schedule next registration */
- if (regc->auto_reg && expiration != 0 && expiration != NOEXP) {
+ if (regc->auto_reg && expiration > 0) {
pj_time_val delay = { 0, 0};
delay.sec = expiration - DELAY_BEFORE_REFRESH;
@@ -819,8 +998,11 @@ static void tsx_callback(void *token, pjsip_event *event)
*/
++regc->busy;
+ /* Update registration */
+ if (expiration==NOEXP) expiration=-1;
+ regc->expires = expiration;
+
/* Call callback. */
- if (expiration == NOEXP) expiration = -1;
call_callback(regc, PJ_SUCCESS, tsx->status_code,
(rdata ? &rdata->msg_info.msg->line.status.reason
: pjsip_get_status_text(tsx->status_code)),
@@ -841,6 +1023,7 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
{
pj_status_t status;
pjsip_cseq_hdr *cseq_hdr;
+ pjsip_expires_hdr *expires_hdr;
pj_uint32_t cseq;
/* Make sure we don't have pending transaction. */
@@ -851,6 +1034,8 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
return PJSIP_EBUSY;
}
+ pj_assert(regc->current_op == REGC_IDLE);
+
/* Invalidate message buffer. */
pjsip_tx_data_invalidate_msg(tdata);
@@ -860,6 +1045,10 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
cseq_hdr->cseq = cseq;
+ /* Find Expires header */
+ expires_hdr = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL);
+
/* Bind to transport selector */
pjsip_tx_data_set_transport(tdata, &regc->tp_sel);
@@ -868,6 +1057,13 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
*/
regc->has_tsx = PJ_TRUE;
++regc->busy;
+
+ /* Set current operation based on the value of Expires header */
+ if (expires_hdr && expires_hdr->ivalue==0)
+ regc->current_op = REGC_UNREGISTERING;
+ else
+ regc->current_op = REGC_REGISTERING;
+
status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
regc, &tsx_callback);
if (status!=PJ_SUCCESS) {