static void delete_config(config_t* config) {
	TSDebug(PLUGIN_TAG, "Freeing config");
	TSfree(config->allowIps);
	TSfree(config->allowIps6);
	TSfree(config->stats_path);
	TSfree(config);
}
static void
free_request_info(RequestInfo *req_info)
{
  TSfree(req_info->effective_url);
  TSHandleMLocRelease(req_info->buf, TS_NULL_MLOC, req_info->http_hdr_loc);
  TSMBufferDestroy(req_info->buf);
  TSfree(req_info);
}
void
TSRemapDeleteInstance(void *ih)
{
  secure_link_info *sli = (secure_link_info *)ih;

  TSfree(sli->secret);
  TSfree(sli);
}
/* handle_hook
 * Fires on TS_EVENT_HTTP_READ_REQUEST_HDR events, gets the effectiveUrl
 * finds the host, gets the generation ID, gen_id, for the host
 * and runs TSCacheUrlSet to change the cache key for the read
 */
static int
handle_hook(TSCont *contp, TSEvent event, void *edata)
{
  TSHttpTxn txnp = (TSHttpTxn)edata;
  char *url = NULL, *host = NULL;
  int url_length;
  int gen_id;
  int ok = 1;

  switch (event) {
  case TS_EVENT_HTTP_READ_REQUEST_HDR:
    TSDebug(PLUGIN_NAME, "handling TS_EVENT_HTTP_READ_REQUEST_HDR");

    if (ok) {
      url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length);
      if (!url) {
        TSError("[%s] could not retrieve request url", PLUGIN_NAME);
        ok = 0;
      }
    }

    if (ok) {
      get_genid_host(&host, url);
      if (!host) {
        TSError("[%s] could not retrieve request host", PLUGIN_NAME);
        ok = 0;
      }
    }

    if (ok) {
      TSDebug(PLUGIN_NAME, "From url (%s) discovered host (%s)", url, host);
      if ((gen_id = get_genid(host)) != 0) {
        if (TSHttpTxnConfigIntSet(txnp, TS_CONFIG_HTTP_CACHE_GENERATION, gen_id) != TS_SUCCESS) {
          TSDebug(PLUGIN_NAME, "Error, unable to modify cache url");
          TSError("[%s] Unable to set cache generation for %s to %d", PLUGIN_NAME, url, gen_id);
          ok = 0;
        }
      }
    }

    /* Clean up */
    if (url) {
      TSfree(url);
    }
    if (host) {
      TSfree(host);
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    break;

  default:
    TSAssert(!"Unexpected event");
    ok = 0;
    break;
  }

  return ok;
}
static void
free_plugin_state_t(plugin_state_t *pstate)
{
  if (pstate->invalidate_list)
    free_invalidate_t_list(pstate->invalidate_list);
  if (pstate->config_file)
    TSfree(pstate->config_file);
  if (pstate->log)
    TSTextLogObjectDestroy(pstate->log);
  TSfree(pstate);
}
Exemple #6
0
static void
delete_purge_instance(PurgeInstance *purge)
{
  if (purge) {
    TSfree(purge->id);
    TSfree(purge->state_file);
    TSfree(purge->secret);
    TSfree(purge->header);
    TSMutexDestroy(purge->lock);
    TSfree(purge);
  }
}
Exemple #7
0
/**
 * Handle transaction context destroy.
 *
 * Handles TS_EVENT_HTTP_TXN_CLOSE (transaction close) close event from the
 * ATS.
 *
 * @param[in,out] ctx Transaction context
 */
static void tsib_txn_ctx_destroy(tsib_txn_ctx *txndata)
{
    if (txndata == NULL) {
        return;
    }

    ib_tx_t *tx = txndata->tx;
    tsib_ssn_ctx *ssndata = txndata->ssn;

    assert(tx != NULL);
    assert(ssndata != NULL);

    txndata->tx = NULL;
    ib_log_debug_tx(tx,
                    "TX DESTROY: conn=>%p tx_count=%zd tx=%p id=%s txn_count=%d",
                    tx->conn, tx->conn->tx_count, tx, tx->id, ssndata->txn_count);
    tx_finish(tx);

    ib_lock_lock(ssndata->mutex);
    ib_tx_destroy(tx);

    txndata->ssn = NULL;

    /* Decrement the txn count on the ssn, and destroy ssn if it's closing.
     * We trust TS not to create more TXNs after signalling SSN close!
     */
    if (ssndata->closing && ssndata->txn_count <= 1) {
        if (ssndata->iconn) {
            tx_list_destroy(ssndata->iconn);
            ib_conn_t *conn = ssndata->iconn;
            ib_engine_t *ib = conn->ib;

            ssndata->iconn = NULL;
            TSDebug("ironbee",
                    "tsib_txn_ctx_destroy: calling ib_state_notify_conn_closed()");
            ib_state_notify_conn_closed(ib, conn);
            TSDebug("ironbee",
                    "CONN DESTROY: conn=%p", conn);
            ib_conn_destroy(conn);
        }
        TSContDataSet(ssndata->contp, NULL);
        TSContDestroy(ssndata->contp);
        ib_lock_unlock(ssndata->mutex);
        ib_lock_destroy_malloc(ssndata->mutex);
        TSfree(ssndata);
    }
    else {
        --(ssndata->txn_count);
        ib_lock_unlock(ssndata->mutex);
    }
    TSfree(txndata);
}
static int rewrite_cacheurl(pr_list *prl, TSHttpTxn txnp) {
    int ok = 1;
    char *newurl = 0;
    int retval;

    char *url;
    int url_length;
    int i;
    if (ok) {
        url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length);
        if (!url) {
            TSError("[%s] couldn't retrieve request url\n",
                    PLUGIN_NAME);
            ok = 0;
        }
    }

    if (ok) {
        i=0;
        while (i < prl->patterncount && prl->pr[i]) {
            retval = regex_substitute(&newurl, url, prl->pr[i]);
            if (retval) {
                /* Successful match/substitution */
                break;
            }
            i++;
        }
        if (newurl) {
            if (log) {
                TSTextLogObjectWrite(log,
                        "Rewriting cache URL for %s to %s", url,
                        newurl);
            }
            TSDebug(PLUGIN_NAME, "Rewriting cache URL for %s to %s\n",
                    url, newurl);
            if (TSCacheUrlSet(txnp, newurl, strlen(newurl))
                    != TS_SUCCESS) {
                TSError("[%s] Unable to modify cache url from "
                        "%s to %s\n", PLUGIN_NAME, url, newurl);
                ok = 0;
            }
        }
    }
    /* Clean up */
    if (url) TSfree(url);
    if (newurl) TSfree(newurl);
    return ok;
}
Exemple #9
0
void
ts_lua_destroy_http_ctx(ts_lua_http_ctx* http_ctx)
{
    ts_lua_main_ctx   *main_ctx;

    main_ctx = http_ctx->mctx;

    if (http_ctx->server_request_bufp) {
        TSHandleMLocRelease(http_ctx->server_request_bufp, TS_NULL_MLOC, http_ctx->server_request_hdrp);
    }

    if (http_ctx->server_response_bufp) {
        TSHandleMLocRelease(http_ctx->server_response_bufp, TS_NULL_MLOC, http_ctx->server_response_hdrp);
    }

    if (http_ctx->client_response_bufp) {
        TSHandleMLocRelease(http_ctx->client_response_bufp, TS_NULL_MLOC, http_ctx->client_response_hdrp);
    }

    if (http_ctx->cached_response_bufp) {
        TSHandleMLocRelease(http_ctx->cached_response_bufp, TS_NULL_MLOC, http_ctx->cached_response_hdrp);
    }

    luaL_unref(main_ctx->lua, LUA_REGISTRYINDEX, http_ctx->ref);
    TSfree(http_ctx);
}
Exemple #10
0
TSReturnCode
TSRemapInit(TSRemapInterface * api_info, char *errbuf, int errbuf_size)
{
  int ret;

  if (!api_info || api_info->size < sizeof(TSRemapInterface)) {
    strncpy(errbuf, "[TSRemapInit] - Incorrect size of TSRemapInterface structure", errbuf_size - 1);
    return TS_ERROR;
  }

  if (ts_lua_main_ctx_array != NULL)
    return TS_SUCCESS;

  ts_lua_main_ctx_array = TSmalloc(sizeof(ts_lua_main_ctx) * TS_LUA_MAX_STATE_COUNT);
  memset(ts_lua_main_ctx_array, 0, sizeof(ts_lua_main_ctx) * TS_LUA_MAX_STATE_COUNT);

  ret = ts_lua_create_vm(ts_lua_main_ctx_array, TS_LUA_MAX_STATE_COUNT);

  if (ret) {
    ts_lua_destroy_vm(ts_lua_main_ctx_array, TS_LUA_MAX_STATE_COUNT);
    TSfree(ts_lua_main_ctx_array);
    return TS_ERROR;
  }

  return TS_SUCCESS;
}
static int
ts_lua_client_request_get_pristine_url(lua_State *L)
{
    char        *url;
    int         url_len;

    TSMBuffer   bufp;
    TSMLoc      url_loc;

    ts_lua_http_ctx  *http_ctx;

    http_ctx = ts_lua_get_http_ctx(L);

    if (TSHttpTxnPristineUrlGet(http_ctx->txnp, &bufp, &url_loc) != TS_SUCCESS)
        return 0;

    url = TSUrlStringGet(bufp, url_loc, &url_len);

    if (url) {
        lua_pushlstring(L, url, url_len);
        TSfree(url);

    } else {
        lua_pushnil(L);
    }

    TSHandleMLocRelease(bufp, NULL, url_loc);

    return 1;
}
Exemple #12
0
static int
ts_lua_http_get_remap_to_url(lua_State *L)
{
  TSMLoc url = TS_NULL_MLOC;
  char *str  = NULL;
  int len;
  ts_lua_http_ctx *http_ctx;

  GET_HTTP_CONTEXT(http_ctx, L);

  if (TSRemapToUrlGet(http_ctx->txnp, &url) != TS_SUCCESS) {
    lua_pushnil(L);
    goto done;
  }

  str = TSUrlStringGet(NULL, url, &len);

  lua_pushlstring(L, str, len >= TS_LUA_MAX_URL_LENGTH ? TS_LUA_MAX_URL_LENGTH - 1 : len);

done:
  if (str != NULL) {
    TSfree(str);
  }

  return 1;
}
static void
jcrusher_data_destroy(JCrusherData * data)
{
  TSDebug("jcrusher", "Start of jcrusher_data_destroy()");
  if (data) {
    if (data->downstream_buffer) {
      TSDebug("jcrusher", "jcrusher_data_destroy - destroying downstream buffer");
      TSIOBufferDestroy(data->downstream_buffer);
    }
    if (data->json_obj) {
      TSDebug("jcrusher", "jcrusher_data_destroy - destroying json object");
      json_object_put(data->json_obj);
      data->json_obj = NULL;
      TSDebug("jcrusher", "jcrusher_data_destroy - destroying json object -> done");
    }
    if (data->json_tok) {
      TSDebug("jcrusher", "jcrusher_data_destroy - destroying json tokener");
      json_tokener_free(data->json_tok);
      data->json_tok = NULL;
      TSDebug("jcrusher", "jcrusher_data_destroy - destroying json tokener -> done");
    }
    TSDebug("jcrusher", "jcrusher_data_destroy - Freeing data");
    TSfree(data);
    TSDebug("jcrusher", "jcrusher_data_destroy - Freeing data -> done");
  }
  TSDebug("jcrusher", "End of jcrusher_data_destroy()");
}
Exemple #14
0
void *
remove_from_queue(Queue *q)
{
  void *data = NULL;
  Cell *remove_cell;

  TSMutexLock(q->mutex);
  if (q->nb_elem > 0) {
    remove_cell = q->head;
    TSAssert(remove_cell->magic == MAGIC_ALIVE);

    data = remove_cell->ptr_data;
    q->head = remove_cell->ptr_prev;
    if (q->head == NULL) {
      TSAssert(q->nb_elem == 1);
      q->tail = NULL;
    } else {
      TSAssert(q->head->magic == MAGIC_ALIVE);
      q->head->ptr_next = NULL;
    }

    remove_cell->magic = MAGIC_DEAD;
    TSfree(remove_cell);
    q->nb_elem--;
  }
  TSMutexUnlock(q->mutex);
  return data;
}
static int
ts_lua_remap_get_to_url(lua_State *L)
{
  char output[TS_LUA_MAX_URL_LENGTH];
  char *url;
  int url_len;
  int output_len;

  ts_lua_http_ctx *http_ctx;

  GET_HTTP_CONTEXT(http_ctx, L);

  if (http_ctx->rri != NULL) {
    url = TSUrlStringGet(http_ctx->client_request_bufp, http_ctx->rri->mapToUrl, &url_len);

    output_len = snprintf(output, TS_LUA_MAX_URL_LENGTH, "%.*s", url_len, url);

    if (output_len >= TS_LUA_MAX_URL_LENGTH) {
      lua_pushlstring(L, output, TS_LUA_MAX_URL_LENGTH - 1);
    } else {
      lua_pushlstring(L, output, output_len);
    }

    TSfree(url);
  } else {
    lua_pushnil(L);
  }

  return 1;
}
static RequestInfo *
create_request_info(TSHttpTxn txn)
{
  RequestInfo *req_info;
  char *url;
  int url_len;
  TSMBuffer buf;
  TSMLoc loc;

  req_info = (RequestInfo *)TSmalloc(sizeof(RequestInfo));

  url = TSHttpTxnEffectiveUrlStringGet(txn, &url_len);
  req_info->effective_url = TSstrndup(url, url_len);
  TSfree(url);

  TSHttpTxnClientReqGet(txn, &buf, &loc);
  req_info->buf = TSMBufferCreate();
  TSHttpHdrClone(req_info->buf, buf, loc, &(req_info->http_hdr_loc));
  TSHandleMLocRelease(buf, TS_NULL_MLOC, loc);

  req_info->client_addr = TSmalloc(sizeof(struct sockaddr));
  memmove((void *)req_info->client_addr, (void *)TSHttpTxnClientAddrGet(txn), sizeof(struct sockaddr));

  return req_info;
}
Exemple #17
0
/**
 * Handle session context destroy.
 *
 * Handles TS_EVENT_HTTP_SSN_CLOSE (session close) close event from the
 * ATS.
 *
 * @param[in,out] ctx session context
 */
static void tsib_ssn_ctx_destroy(tsib_ssn_ctx * ssndata)
{
    if (ssndata == NULL) {
        return;
    }

    /* To avoid the risk of sequencing issues with this coming before TXN_CLOSE,
     * we just mark the session as closing, but leave actually closing it
     * for the TXN_CLOSE if there's a TXN
     */
    if (ssndata->txn_count == 0) { /* No outstanding TXN_CLOSE to come. */
        if (ssndata->iconn != NULL) {
            ib_conn_t *conn = ssndata->iconn;
            ssndata->iconn = NULL;

            tx_list_destroy(conn);
            TSDebug("ironbee",
                    "tsib_ssn_ctx_destroy: calling ib_state_notify_conn_closed()");
            ib_state_notify_conn_closed(conn->ib, conn);
            TSDebug("ironbee", "CONN DESTROY: conn=%p", conn);
            ib_conn_destroy(conn);
        }

        /* Store off the continuation pointer */
        TSCont contp = ssndata->contp;
        TSContDataSet(contp, NULL);
        ssndata->contp = NULL;

        TSContDestroy(contp);
        TSfree(ssndata);
    }
    else {
        ssndata->closing = 1;
    }
}
static void
free_request_state(StateInfo *state)
{
#if defined(DEBUG)
  int verify = 1;
#else
  int verify = TSIsDebugTagSet(PLUGIN_NAME);
#endif

  // Verify that the effective URL of this state object has been removed before we delete the state.
  if (verify) {
    void *ptr;

    TSMutexLock(state->plugin_config->troot_mutex);
    ptr = tfind(state->req_info->effective_url, &(state->plugin_config->troot), xstrcmp);
    TSMutexUnlock(state->plugin_config->troot_mutex);

    if (ptr) {
      TSReleaseAssert(ptr != state->req_info->effective_url);
    }
  }

  if (state->resp_info) {
    free_response_info(state->resp_info);
  }

  free_request_info(state->req_info);
  TSfree(state);
}
Exemple #19
0
int
ts_lua_transform_entry(TSCont contp, TSEvent event, void *edata)
{
    TSVIO       input_vio;

    ts_lua_transform_ctx *transform_ctx = (ts_lua_transform_ctx*)TSContDataGet(contp);

    if (TSVConnClosedGet(contp)) {
        TSContDestroy(contp);
        TSfree(transform_ctx);
        return 0;
    }

    switch (event) {

        case TS_EVENT_ERROR:
            input_vio = TSVConnWriteVIOGet(contp);
            TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
            break;

        case TS_EVENT_VCONN_WRITE_COMPLETE:
            TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
            break;

        case TS_EVENT_VCONN_WRITE_READY:
        default:
            ts_lua_transform_handler(contp, transform_ctx);
            break;
    }

    return 0;
}
static void
transform_destroy(TSCont contp)
{
  TransformData *data;

  data = TSContDataGet(contp);
  if (data != NULL) {
    if (data->input_buf) {
      TSIOBufferDestroy(data->input_buf);
    }

    if (data->output_buf) {
      TSIOBufferDestroy(data->output_buf);
    }

    if (data->pending_action) {
      TSActionCancel(data->pending_action);
    }

    if (data->server_vc) {
      TSVConnAbort(data->server_vc, 1);
    }

    TSfree(data);
  } else {
    TSError("[%s] Unable to get Continuation's Data. TSContDataGet returns NULL", PLUGIN_NAME);
  }

  TSContDestroy(contp);
}
Exemple #21
0
static void
ts_lua_release_cache_info(ts_lua_cache_info *info, int destroy)
{
    if (info == NULL)
        return;

    if (info->cache_action) {
        TSActionCancel(info->cache_action);
        info->cache_action = NULL;
    }

    if (info->cache_key) {
        TSCacheKeyDestroy(info->cache_key);
        info->cache_key = NULL;
    }

    if (info->cache_vc) {
        TSVConnClose(info->cache_vc);
        info->cache_vc = NULL;
    }

    TS_LUA_RELEASE_IO_HANDLE((&info->ioh));
    TS_LUA_RELEASE_IO_HANDLE((&info->reserved));

    if (destroy) {
        TSfree(info);
    }
}
Exemple #22
0
/*-------------------------------------------------------------------------
  cont_data_destroy
  Deallocate ContData structure associated to a transaction

  Input:
    data   structure to deallocate
  Output:
  Return Value:
    none
  -------------------------------------------------------------------------*/
static void
cont_data_destroy(ContData * data)
{
  TSDebug(DBG_TAG, "Destroying continuation data");
  if (data) {
    TSAssert(data->magic == MAGIC_ALIVE);
    if (data->output_reader) {
      TSIOBufferReaderFree(data->output_reader);
      data->output_reader = NULL;
    }
    if (data->output_buffer) {
      TSIOBufferDestroy(data->output_buffer);
      data->output_buffer = NULL;
    }
    if (data->psi_reader) {
      TSIOBufferReaderFree(data->psi_reader);
      data->psi_reader = NULL;
    }
    if (data->psi_buffer) {
      TSIOBufferDestroy(data->psi_buffer);
      data->psi_buffer = NULL;
    }
    data->magic = MAGIC_DEAD;
    TSfree(data);
  }
}
// This serves to consume all the data that arrives. If it's not consumed the tunnel gets stalled
// and the transaction doesn't complete. Other things could be done with the data, accessible via
// the IO buffer @a reader, such as writing it to disk to make an externally accessible copy.
static int
client_reader(TSCont contp, TSEvent event, void *edata)
{
  SinkData *data = TSContDataGet(contp);

  // If we got closed, we're done.
  if (TSVConnClosedGet(contp)) {
    TSfree(data);
    TSContDestroy(contp);
    return 0;
  }

  TSVIO input_vio = TSVConnWriteVIOGet(contp);

  if (!data) {
    data        = TSmalloc(sizeof(SinkData));
    data->total = 0;
    TSContDataSet(contp, data);
  }

  switch (event) {
  case TS_EVENT_ERROR:
    TSDebug(PLUGIN_NAME, "Error event");
    TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
    break;
  case TS_EVENT_VCONN_READ_COMPLETE:
    TSDebug(PLUGIN_NAME, "READ_COMPLETE");
    break;
  case TS_EVENT_VCONN_READ_READY:
  case TS_EVENT_IMMEDIATE:
    TSDebug(PLUGIN_NAME, "Data event - %s", event == TS_EVENT_IMMEDIATE ? "IMMEDIATE" : "READ_READY");
    // Look for data and if we find any, consume.
    if (TSVIOBufferGet(input_vio)) {
      TSIOBufferReader reader = TSVIOReaderGet(input_vio);
      int64_t n               = TSIOBufferReaderAvail(reader);
      if (n > 0) {
        TSIOBufferReaderConsume(reader, n);
        TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + n);
        data->total += n; // internal accounting so we can print the value at the end.
        TSDebug(PLUGIN_NAME, "Consumed %" PRId64 " bytes", n);
      }
      if (TSVIONTodoGet(input_vio) > 0) {
        // signal that we can accept more data.
        TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
      } else {
        TSDebug(PLUGIN_NAME, "send WRITE_COMPLETE");
        TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
      }
    } else { // buffer gone, we're done.
      TSDebug(PLUGIN_NAME, "upstream buffer disappeared - %" PRId64 " bytes", data->total);
    }
    break;
  default:
    TSDebug(PLUGIN_NAME, "unhandled event %d", event);
    break;
  }

  return 0;
}
static void
free_response_info(ResponseInfo *resp_info)
{
  TSHandleMLocRelease(resp_info->buf, TS_NULL_MLOC, resp_info->http_hdr_loc);
  TSMBufferDestroy(resp_info->buf);
  TSHttpParserDestroy(resp_info->parser);
  TSfree(resp_info);
}
Exemple #25
0
void TSRemapDeleteInstance(void* ih) {
    /* Release instance memory allocated in TSRemapNewInstance */

    TSDebug(PLUGIN_NAME, "deleting instance %p", ih);

    if (ih != NULL) {
        hash_remap_struct *hash = (hash_remap_struct*) ih;

        if (hash->isp_name != NULL)
          TSfree(hash->isp_name);

        if (hash->hash != NULL)
          TSfree(hash->hash);

        TSfree(hash);
    }
}
Exemple #26
0
static void ib_ssn_ctx_destroy(ib_ssn_ctx * data)
{
  if (data) {
    if (data->iconn)
      ib_state_notify_conn_closed(ironbee, data->iconn);
    TSfree(data);
  }
}
Exemple #27
0
void
TSRemapDeleteInstance(void *ih)
{
  ts_lua_del_module((ts_lua_instance_conf *) ih, ts_lua_main_ctx_array, TS_LUA_MAX_STATE_COUNT);
  ts_lua_del_instance(ih);
  TSfree(ih);
  return;
}
Exemple #28
0
/*-------------------------------------------------------------------------
  trylock_handler
  Small handler to handle TSMutexLockTry failures

  Input:
    contp      continuation for the current transaction
    event      event received
    data       pointer on optional data
  Output :
  Return Value:
  -------------------------------------------------------------------------*/
static int
trylock_handler(TSCont contp, TSEvent event, void *edata)
{
  TryLockData *data = TSContDataGet(contp);
  transform_handler(data->contp, data->event, NULL);
  TSfree(data);
  TSContDestroy(contp);
  return 0;
}
Exemple #29
0
static void
free_cfg(struct config *cfg)
{
  TSError("[url_sig] Cleaning up...");
  TSfree(cfg->err_url);

  if (cfg->regex_extra)
#ifndef PCRE_STUDY_JIT_COMPILE
    pcre_free(cfg->regex_extra);
#else
    pcre_free_study(cfg->regex_extra);
#endif

  if (cfg->regex)
    pcre_free(cfg->regex);

  TSfree(cfg);
}
static void
my_data_destroy(MyData *data)
{
  if (data) {
    if (data->output_buffer)
      TSIOBufferDestroy(data->output_buffer);
    TSfree(data);
  }
}