Example #1
0
/**
 * Sign name and records
 *
 * @param key the private key
 * @param expire block expiration
 * @param name the name
 * @param rd record data
 * @param rd_count number of records
 *
 * @return the signature
 */
struct GNUNET_CRYPTO_RsaSignature *
GNUNET_NAMESTORE_create_signature (const struct GNUNET_CRYPTO_RsaPrivateKey *key,
				   struct GNUNET_TIME_Absolute expire,
				   const char *name,
				   const struct GNUNET_NAMESTORE_RecordData *rd,
				   unsigned int rd_count)
{
  struct GNUNET_CRYPTO_RsaSignature *sig;
  struct GNUNET_CRYPTO_RsaSignaturePurpose *sig_purpose;
  struct GNUNET_TIME_AbsoluteNBO expire_nbo;
  size_t rd_ser_len;
  size_t name_len;
  struct GNUNET_TIME_AbsoluteNBO *expire_tmp;
  char * name_tmp;
  char * rd_tmp;
  int res;
  uint32_t sig_len;

  if (NULL == name)
  {
    GNUNET_break (0);
    return NULL;
  }
  sig = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_RsaSignature));
  name_len = strlen (name) + 1;
  expire_nbo = GNUNET_TIME_absolute_hton (expire);
  rd_ser_len = GNUNET_NAMESTORE_records_get_size (rd_count, rd);
  {
    char rd_ser[rd_ser_len];

    GNUNET_assert (rd_ser_len ==
		   GNUNET_NAMESTORE_records_serialize (rd_count, rd, rd_ser_len, rd_ser));
    sig_len = sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose) + sizeof (struct GNUNET_TIME_AbsoluteNBO) + rd_ser_len + name_len;
    sig_purpose = GNUNET_malloc (sig_len);
    sig_purpose->size = htonl (sig_len);
    sig_purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN);
    expire_tmp = (struct GNUNET_TIME_AbsoluteNBO *) &sig_purpose[1];
    memcpy (expire_tmp, &expire_nbo, sizeof (struct GNUNET_TIME_AbsoluteNBO));
    name_tmp = (char *) &expire_tmp[1];
    memcpy (name_tmp, name, name_len);
    rd_tmp = &name_tmp[name_len];
    memcpy (rd_tmp, rd_ser, rd_ser_len);
    res = GNUNET_CRYPTO_rsa_sign (key, sig_purpose, sig);
    GNUNET_free (sig_purpose);
  }
  if (GNUNET_OK != res)
  {
    GNUNET_break (0);
    GNUNET_free (sig);
    return NULL;
  }
  return sig;
}
Example #2
0
/**
 * Check if a signature is valid.  This API is used by the GNS Block
 * to validate signatures received from the network.
 *
 * @param public_key public key of the zone
 * @param expire block expiration
 * @param name name that is being mapped (at most 255 characters long)
 * @param rd_count number of entries in 'rd' array
 * @param rd array of records with data to store
 * @param signature signature for all the records in the zone under the given name
 * @return GNUNET_OK if the signature is valid
 */
int
GNUNET_NAMESTORE_verify_signature (const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key,
                                   const struct GNUNET_TIME_Absolute expire,
				   const char *name,
				   unsigned int rd_count,
				   const struct GNUNET_NAMESTORE_RecordData *rd,
				   const struct GNUNET_CRYPTO_RsaSignature *signature)
{
  int res = GNUNET_SYSERR;
  size_t rd_ser_len = 0;
  size_t name_len = 0;
  char * name_tmp;
  char * rd_tmp;
  struct GNUNET_CRYPTO_RsaSignaturePurpose *sig_purpose;
  struct GNUNET_TIME_AbsoluteNBO *expire_tmp;
  struct GNUNET_TIME_AbsoluteNBO expire_nbo = GNUNET_TIME_absolute_hton(expire);

  GNUNET_assert (public_key != NULL);
  GNUNET_assert (name != NULL);
  GNUNET_assert (rd != NULL);
  GNUNET_assert (signature != NULL);


  rd_ser_len = GNUNET_NAMESTORE_records_get_size(rd_count, rd);
  char rd_ser[rd_ser_len];
  GNUNET_NAMESTORE_records_serialize(rd_count, rd, rd_ser_len, rd_ser);

  name_len = strlen (name) + 1;
  if (name_len > 256)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  sig_purpose = GNUNET_malloc(sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose) + sizeof (struct GNUNET_TIME_AbsoluteNBO) + rd_ser_len + name_len);
  sig_purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose)+ rd_ser_len + name_len);
  sig_purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN);
  expire_tmp = (struct GNUNET_TIME_AbsoluteNBO *) &sig_purpose[1];
  name_tmp = (char *) &expire_tmp[1];
  rd_tmp = &name_tmp[name_len];
  memcpy (expire_tmp, &expire_nbo, sizeof (struct GNUNET_TIME_AbsoluteNBO));
  memcpy (name_tmp, name, name_len);
  memcpy (rd_tmp, rd_ser, rd_ser_len);

  res = GNUNET_CRYPTO_rsa_verify(GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, sig_purpose, signature, public_key);

  GNUNET_free (sig_purpose);

  return res;
}
static void
run (void *cls, char *const *args, const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  delete_existing_db(cfg);
  endbadly_task = GNUNET_SCHEDULER_add_delayed(TIMEOUT,endbadly, NULL);

  size_t rd_ser_len;

  /* load privat key from file not included in zonekey dir */
  privkey = GNUNET_CRYPTO_rsa_key_create_from_file("test_hostkey");
  GNUNET_assert (privkey != NULL);
  /* get public key */
  GNUNET_CRYPTO_rsa_key_get_public(privkey, &pubkey);

  /* create record */
  s_name = "dummy.dummy.gnunet";
  s_rd = create_record (RECORDS);

  rd_ser_len = GNUNET_NAMESTORE_records_get_size(RECORDS, s_rd);
  char rd_ser[rd_ser_len];
  GNUNET_NAMESTORE_records_serialize(RECORDS, s_rd, rd_ser_len, rd_ser);

  /* sign */
  s_signature = GNUNET_NAMESTORE_create_signature(privkey, s_rd[0].expiration, s_name, s_rd, RECORDS);

  /* create random zone hash */
  GNUNET_CRYPTO_short_hash (&pubkey, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &s_zone);
  start_arm (cfgfile);
  GNUNET_assert (arm != NULL);

  nsh = GNUNET_NAMESTORE_connect (cfg);
  GNUNET_break (NULL != nsh);

  GNUNET_break (s_rd != NULL);
  GNUNET_break (s_name != NULL);

  GNUNET_NAMESTORE_record_put (nsh, &pubkey, s_name,
			       GNUNET_TIME_UNIT_FOREVER_ABS,
			       RECORDS, s_rd, s_signature, put_cont, s_name);
}
/**
 * Reply to client with the result from our lookup.
 *
 * @param cls the closure (our client lookup handle)
 * @param rd_count the number of records
 * @param rd the record data
 */
static void
send_lookup_response(void* cls,
                     uint32_t rd_count,
                     const struct GNUNET_NAMESTORE_RecordData *rd)
{
  struct ClientLookupHandle* clh = (struct ClientLookupHandle*)cls;
  struct GNUNET_GNS_ClientLookupResultMessage *rmsg;
  size_t len;
  
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending `%s' message with %d results\n",
              "LOOKUP_RESULT", rd_count);
  
  len = GNUNET_NAMESTORE_records_get_size (rd_count, rd);
  rmsg = GNUNET_malloc(len+sizeof(struct GNUNET_GNS_ClientLookupResultMessage));
  
  rmsg->id = clh->unique_id;
  rmsg->rd_count = htonl(rd_count);
  rmsg->header.type = htons(GNUNET_MESSAGE_TYPE_GNS_LOOKUP_RESULT);
  rmsg->header.size = 
    htons(len+sizeof(struct GNUNET_GNS_ClientLookupResultMessage));

  GNUNET_NAMESTORE_records_serialize (rd_count, rd, len, (char*)&rmsg[1]);
  
  GNUNET_SERVER_notification_context_unicast (nc, clh->client,
                                (const struct GNUNET_MessageHeader *) rmsg,
                                GNUNET_NO);
  GNUNET_SERVER_receive_done (clh->client, GNUNET_OK);
  
  GNUNET_free(rmsg);
  GNUNET_free(clh->name);
  
  if (NULL != clh->zone_key)
    GNUNET_free(clh->zone_key);

  GNUNET_free(clh);

}
/**
 * Function used to put all records successively into the DHT.
 *
 * @param cls the closure (NULL)
 * @param key the public key of the authority (ours)
 * @param expiration lifetime of the namestore entry
 * @param name the name of the records
 * @param rd_count the number of records in data
 * @param rd the record data
 * @param signature the signature for the record data
 */
static void
put_gns_record(void *cls,
                const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *key,
                struct GNUNET_TIME_Absolute expiration,
                const char *name,
                unsigned int rd_count,
                const struct GNUNET_NAMESTORE_RecordData *rd,
                const struct GNUNET_CRYPTO_RsaSignature *signature)
{
  
  struct GNSNameRecordBlock *nrb;
  struct GNUNET_CRYPTO_ShortHashCode name_hash;
  struct GNUNET_CRYPTO_ShortHashCode zhash;
  GNUNET_HashCode xor_hash;
  GNUNET_HashCode name_hash_double;
  GNUNET_HashCode zone_hash_double;
  uint32_t rd_payload_length;
  char* nrb_data = NULL;
  size_t namelen;

  /* we're done */
  if (NULL == name)
  {
    GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
               "Zone iteration finished. Rescheduling put in %ds\n",
               dht_max_update_interval);
    zone_update_taskid = GNUNET_SCHEDULER_add_delayed (
                                        GNUNET_TIME_relative_multiply(
                                            GNUNET_TIME_UNIT_SECONDS,
                                            dht_max_update_interval
                                            ),
                                            &update_zone_dht_start,
                                            NULL);
    return;
  }
  
  namelen = strlen(name) + 1;
  
  if (signature == NULL)
  {
    GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
               "No signature for %s record data provided! Skipping...\n",
               name);
    zone_update_taskid = GNUNET_SCHEDULER_add_now (&update_zone_dht_next,
                                                   NULL);
    return;

  }
  
  GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
             "Putting records for %s into the DHT\n", name);
  
  rd_payload_length = GNUNET_NAMESTORE_records_get_size (rd_count, rd);
  
  nrb = GNUNET_malloc(rd_payload_length + namelen
                      + sizeof(struct GNSNameRecordBlock));
  
  nrb->signature = *signature;
  
  nrb->public_key = *key;

  nrb->rd_count = htonl(rd_count);
  
  memcpy(&nrb[1], name, namelen);

  nrb_data = (char*)&nrb[1];
  nrb_data += namelen;

  rd_payload_length += sizeof(struct GNSNameRecordBlock) + namelen;

  if (-1 == GNUNET_NAMESTORE_records_serialize (rd_count,
                                                rd,
                                                rd_payload_length,
                                                nrb_data))
  {
    GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
               "Record serialization failed! Skipping...\n");
    GNUNET_free(nrb);
    zone_update_taskid = GNUNET_SCHEDULER_add_now (&update_zone_dht_next,
                                                   NULL);
    return;
  }


  /*
   * calculate DHT key: H(name) xor H(pubkey)
   */
  GNUNET_CRYPTO_short_hash(key,
                     sizeof(struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
                     &zhash);
  GNUNET_CRYPTO_short_hash(name, strlen(name), &name_hash);
  GNUNET_CRYPTO_short_hash_double (&name_hash, &name_hash_double);
  GNUNET_CRYPTO_short_hash_double (&zhash, &zone_hash_double);
  GNUNET_CRYPTO_hash_xor(&zone_hash_double, &name_hash_double, &xor_hash);

  GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
             "zone identity: %s\n", GNUNET_h2s (&zone_hash_double));

  GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
             "putting records for %s under key: %s with size %d\n",
             name, GNUNET_h2s (&xor_hash), rd_payload_length);
  
  GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
             "DHT req to %d\n", DHT_OPERATION_TIMEOUT.rel_value);
  /* FIXME: keep return value to possibly cancel? */
  GNUNET_DHT_put (dht_handle, &xor_hash,
                  DHT_GNS_REPLICATION_LEVEL,
                  GNUNET_DHT_RO_NONE,
                  GNUNET_BLOCK_TYPE_GNS_NAMERECORD,
                  rd_payload_length,
                  (char*)nrb,
                  expiration,
                  DHT_OPERATION_TIMEOUT,
                  &record_dht_put,
                  NULL); //cls for cont
  
  num_public_records++;

  /**
   * Reschedule periodic put
   */
  zone_update_taskid = GNUNET_SCHEDULER_add_delayed (record_put_interval,
                                &update_zone_dht_next,
                                NULL);

  GNUNET_free(nrb);

}
Example #6
0
/**
 * Store an item in the namestore.  If the item is already present,
 * the expiration time is updated to the max of the existing time and
 * the new time.  This API is used when we cache signatures from other
 * authorities.
 *
 * @param h handle to the namestore
 * @param zone_key public key of the zone
 * @param name name that is being mapped (at most 255 characters long)
 * @param freshness when does the corresponding block in the DHT expire (until
 *               when should we never do a DHT lookup for the same name again)?
 * @param rd_count number of entries in 'rd' array
 * @param rd array of records with data to store
 * @param signature signature for all the records in the zone under the given name
 * @param cont continuation to call when done
 * @param cont_cls closure for cont
 * @return handle to abort the request
 */
struct GNUNET_NAMESTORE_QueueEntry *
GNUNET_NAMESTORE_record_put (struct GNUNET_NAMESTORE_Handle *h,
			     const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
			     const char *name,
			     struct GNUNET_TIME_Absolute freshness,
			     unsigned int rd_count,
			     const struct GNUNET_NAMESTORE_RecordData *rd,
			     const struct GNUNET_CRYPTO_RsaSignature *signature,
			     GNUNET_NAMESTORE_ContinuationWithStatus cont,
			     void *cont_cls)
{
  struct GNUNET_NAMESTORE_QueueEntry *qe;
  struct PendingMessage *pe;

  /* pointer to elements */
  char * rd_tmp;
  char * name_tmp;

  size_t msg_size = 0;
  size_t name_len = 0;
  size_t rd_ser_len = 0;
  uint32_t rid = 0;

  GNUNET_assert (NULL != h);
  GNUNET_assert (NULL != zone_key);
  GNUNET_assert (NULL != name);
  GNUNET_assert (NULL != rd);
  GNUNET_assert (NULL != signature);

  name_len = strlen(name) + 1;
  if (name_len > 256)
  {
    GNUNET_break (0);
    return NULL;
  }

  rid = get_op_id(h);
  qe = GNUNET_malloc(sizeof (struct GNUNET_NAMESTORE_QueueEntry));
  qe->nsh = h;
  qe->cont = cont;
  qe->cont_cls = cont_cls;
  qe->op_id = rid;
  GNUNET_CONTAINER_DLL_insert_tail(h->op_head, h->op_tail, qe);

  /* set msg_size*/
  rd_ser_len = GNUNET_NAMESTORE_records_get_size(rd_count, rd);
  char rd_ser[rd_ser_len];
  GNUNET_NAMESTORE_records_serialize(rd_count, rd, rd_ser_len, rd_ser);

  struct RecordPutMessage * msg;
  msg_size = sizeof (struct RecordPutMessage) + name_len  + rd_ser_len;

  /* create msg here */
  pe = GNUNET_malloc(sizeof (struct PendingMessage) + msg_size);
  pe->size = msg_size;
  pe->is_init = GNUNET_NO;
  msg = (struct RecordPutMessage *) &pe[1];
  name_tmp = (char *) &msg[1];
  rd_tmp = &name_tmp[name_len];

  msg->gns_header.header.type = htons (GNUNET_MESSAGE_TYPE_NAMESTORE_RECORD_PUT);
  msg->gns_header.header.size = htons (msg_size);
  msg->gns_header.r_id = htonl (rid);
  msg->signature = *signature;
  msg->name_len = htons (name_len);
  msg->expire = GNUNET_TIME_absolute_hton (freshness);
  msg->rd_len = htons (rd_ser_len);
  msg->rd_count = htons (rd_count);

  msg->public_key = *zone_key;
  memcpy (name_tmp, name, name_len);
  memcpy (rd_tmp, rd_ser, rd_ser_len);

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending `%s' message for name `%s' with size %u\n", "NAMESTORE_RECORD_PUT", name, msg_size);

  GNUNET_CONTAINER_DLL_insert_tail (h->pending_head, h->pending_tail, pe);
  do_transmit(h);

  return qe;
}
Example #7
0
/**
 * Explicitly remove some content from the database.  The
 * "cont"inuation will be called with status "GNUNET_OK" if content
 * was removed, "GNUNET_NO" if no matching entry was found and
 * "GNUNET_SYSERR" on all other types of errors.
 * This API is used by the authority of a zone.
 *
 * @param h handle to the namestore
 * @param pkey private key of the zone
 * @param name name that is being mapped (at most 255 characters long)
 * @param rd record data, remove specific record,  NULL to remove the name and all records
 * @param cont continuation to call when done
 * @param cont_cls closure for cont
 * @return handle to abort the request
 */
struct GNUNET_NAMESTORE_QueueEntry *
GNUNET_NAMESTORE_record_remove (struct GNUNET_NAMESTORE_Handle *h,
				const struct GNUNET_CRYPTO_RsaPrivateKey *pkey,
				const char *name,
				const struct GNUNET_NAMESTORE_RecordData *rd,
				GNUNET_NAMESTORE_ContinuationWithStatus cont,
				void *cont_cls)
{
  struct GNUNET_NAMESTORE_QueueEntry *qe;
  struct PendingMessage *pe;
  char *pkey_tmp;
  char *rd_tmp;
  char *name_tmp;
  size_t rd_ser_len = 0;
  size_t msg_size = 0;
  size_t name_len = 0;
  size_t key_len = 0;
  uint32_t rid = 0;
  uint16_t rd_count = 1;

  GNUNET_assert (NULL != h);

  rid = get_op_id(h);
  qe = GNUNET_malloc(sizeof (struct GNUNET_NAMESTORE_QueueEntry));
  qe->nsh = h;
  qe->cont = cont;
  qe->cont_cls = cont_cls;
  qe->op_id = rid;
  GNUNET_CONTAINER_DLL_insert_tail(h->op_head, h->op_tail, qe);

  /* set msg_size*/
  struct GNUNET_CRYPTO_RsaPrivateKeyBinaryEncoded * pkey_enc = GNUNET_CRYPTO_rsa_encode_key (pkey);
  GNUNET_assert (pkey_enc != NULL);
  key_len = ntohs (pkey_enc->len);

  if (NULL == rd)
    rd_count = 0;
  else
    rd_count = 1;
  rd_ser_len = GNUNET_NAMESTORE_records_get_size (rd_count, rd);
  char rd_ser[rd_ser_len];
  GNUNET_NAMESTORE_records_serialize (rd_count, rd, rd_ser_len, rd_ser);

  name_len = strlen (name) + 1;

  struct RecordRemoveMessage * msg;
  msg_size = sizeof (struct RecordRemoveMessage) + key_len + name_len + rd_ser_len;

  /* create msg here */
  pe = GNUNET_malloc(sizeof (struct PendingMessage) + msg_size);
  pe->size = msg_size;
  pe->is_init = GNUNET_NO;
  msg = (struct RecordRemoveMessage *) &pe[1];

  pkey_tmp = (char *) &msg[1];
  name_tmp = &pkey_tmp[key_len];
  rd_tmp = &name_tmp[name_len];

  msg->gns_header.header.type = htons (GNUNET_MESSAGE_TYPE_NAMESTORE_RECORD_REMOVE);
  msg->gns_header.header.size = htons (msg_size);
  msg->gns_header.r_id = htonl (rid);
  msg->name_len = htons (name_len);
  msg->rd_len = htons (rd_ser_len);
  msg->rd_count = htons (rd_count);
  msg->pkey_len = htons (key_len);
  memcpy (pkey_tmp, pkey_enc, key_len);
  memcpy (name_tmp, name, name_len);
  memcpy (rd_tmp, rd_ser, rd_ser_len);

  GNUNET_free (pkey_enc);

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending `%s' message for name `%s' with size %u\n", "NAMESTORE_RECORD_REMOVE", name, msg_size);

  GNUNET_CONTAINER_DLL_insert_tail (h->pending_head, h->pending_tail, pe);
  do_transmit(h);
  return qe;
}
static void
run (void *cls, char *const *args, const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  size_t len;
  int c;

  int rd_count = 3;
  size_t data_len;
  struct GNUNET_NAMESTORE_RecordData src[rd_count];

  memset(src, '\0', rd_count * sizeof (struct GNUNET_NAMESTORE_RecordData));

  data_len = 0;
  for (c = 0; c < rd_count; c++)
  {
    src[c].record_type = c+1;
    src[c].data_size = data_len;
    src[c].data = GNUNET_malloc (data_len);

    /* Setting data to data_len * record_type */
    memset ((char *) src[c].data, 'a', data_len);
    data_len += 10;
  }
  res = 0;

  len = GNUNET_NAMESTORE_records_get_size(rd_count, src);
  char rd_ser[len];
  GNUNET_assert (len == GNUNET_NAMESTORE_records_serialize(rd_count, src, len, rd_ser));

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Serialized data len: %u\n",len);

  GNUNET_assert (rd_ser != NULL);

  struct GNUNET_NAMESTORE_RecordData dst[rd_count];
  GNUNET_assert (GNUNET_OK == GNUNET_NAMESTORE_records_deserialize (len, rd_ser, rd_count, dst));

  GNUNET_assert (dst != NULL);

  for (c = 0; c < rd_count; c++)
  {
    if (src[c].data_size != dst[c].data_size)
    {
      GNUNET_break (0);
      res = 1;
    }
    if (0 != GNUNET_TIME_absolute_get_difference(src[c].expiration, dst[c].expiration).rel_value)
    {
      GNUNET_break (0);
      res = 1;
    }
    if (src[c].flags != dst[c].flags)
    {
      GNUNET_break (0);
      res = 1;
    }
    if (src[c].record_type != dst[c].record_type)
    {
      GNUNET_break (0);
      res = 1;
    }

    size_t data_size = src[c].data_size;
    char data[data_size];
    memset (data, 'a', data_size);
    if (0 != memcmp (data, dst[c].data, data_size))
    {
      GNUNET_break (0);
      res = 1;
    }
    if (0 != memcmp (data, src[c].data, data_size))
    {
      GNUNET_break (0);
      res = 1;
    }
    if (0 != memcmp (src[c].data, dst[c].data, src[c].data_size))
    {
      GNUNET_break (0);
      res = 1;
    }

    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Element [%i]: EQUAL\n", c);
  }

  for (c = 0; c < rd_count; c++)
  {
    GNUNET_free ((void *)src[c].data);
  }
}