static rstatus_t memcache_append_key(struct msg *r, uint8_t *key, uint32_t keylen) { struct mbuf *mbuf; struct keypos *kpos; mbuf = msg_ensure_mbuf(r, keylen + 2); if (mbuf == NULL) { return NC_ENOMEM; } kpos = array_push(r->keys); if (kpos == NULL) { return NC_ENOMEM; } kpos->start = mbuf->last; kpos->end = mbuf->last + keylen; mbuf_copy(mbuf, key, keylen); r->mlen += keylen; mbuf_copy(mbuf, (uint8_t *)" ", 1); r->mlen += 1; return NC_OK; }
rstatus_t dyn_aes_decrypt(unsigned char *enc_msg, size_t enc_msg_len, struct mbuf *mbuf, unsigned char *aes_key) { if (ENCRYPTION) { size_t dec_len = 0; size_t block_len = 0; ASSERT(mbuf != NULL && mbuf->last == mbuf->pos); //if(!EVP_DecryptInit_ex(aes_decrypt_ctx, aes_cipher, NULL, aes_key, aes_iv)) { if(!EVP_DecryptInit_ex(aes_decrypt_ctx, aes_cipher, NULL, aes_key, aes_key)) { return DN_ERROR; } if(!EVP_DecryptUpdate(aes_decrypt_ctx, mbuf->pos, (int*) &block_len, enc_msg, (int)enc_msg_len)) { return DN_ERROR; } dec_len += block_len; if(!EVP_DecryptFinal_ex(aes_decrypt_ctx, mbuf->pos + dec_len, (int*) &block_len)) { return DN_ERROR; } dec_len += block_len; mbuf->last = mbuf->pos + dec_len; EVP_CIPHER_CTX_cleanup(aes_decrypt_ctx); return (int) dec_len; } mbuf_copy(mbuf, enc_msg, enc_msg_len); return (int) enc_msg_len; }
/* * Put partial data to the next mbuf. */ struct mbuf * mbuf_split(struct context *ctx, struct mbuf *mbuf, uint8_t *pos, mbuf_copy_t cb, void *cbarg) { struct mbuf *nbuf; size_t size; nbuf = mbuf_get(ctx); if (nbuf == NULL) { return NULL; } if (cb != NULL) { /* precopy nbuf */ cb(nbuf, cbarg); } /* copy data from mbuf to nbuf */ size = (size_t)(mbuf->last - pos); mbuf_copy(nbuf, pos, size); /* adjust mbuf */ mbuf->last = pos; return nbuf; }
/* * Split mbuf h into h and t by copying data from h to t. Before * the copy, we invoke a precopy handler cb that will copy a predefined * string to the head of t. * * Return new mbuf t, if the split was successful. */ struct mbuf * mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg) { struct mbuf *mbuf, *nbuf; size_t size; ASSERT(!STAILQ_EMPTY(h)); mbuf = STAILQ_LAST(h, mbuf, next); ASSERT(pos >= mbuf->pos && pos <= mbuf->last); nbuf = mbuf_get(); if (nbuf == NULL) { return NULL; } if (cb != NULL) { /* precopy nbuf */ cb(nbuf, cbarg); } /* copy data from mbuf to nbuf */ size = (size_t)(mbuf->last - pos); mbuf_copy(nbuf, pos, size); /* adjust mbuf */ mbuf->last = pos; log_debug(LOG_VVERB, "split into mbuf %p len %"PRIu32" and nbuf %p len " "%"PRIu32" copied %zu bytes", mbuf, mbuf_length(mbuf), nbuf, mbuf_length(nbuf), size); return nbuf; }
/* * fill the mbuf in the msg with the content */ int msg_append_full(struct msg *msg, const uint8_t *pos, uint32_t n) { struct mbuf *mbuf; uint32_t left, len; mbuf_base *mb = msg->mb; const uint8_t *start; start = pos; left = n; while (left > 0) { mbuf = listLastValue(msg->data); if (mbuf == NULL || mbuf_size(mbuf) == 0) { mbuf = mbuf_get(mb); if (mbuf == NULL) { log_error("ERROR: Mbuf get failed: out of memory"); return RMT_ENOMEM; } listAddNodeTail(msg->data, mbuf); } len = MIN(left, mbuf_size(mbuf)); mbuf_copy(mbuf, start, len); left -= len; start += len; msg->mlen += len; } return RMT_OK; }
rstatus_t dyn_aes_encrypt(const unsigned char *msg, size_t msg_len, struct mbuf *mbuf, unsigned char *aes_key) { if (ENCRYPTION) { size_t block_len = 0; size_t enc_msg_len = 0; ASSERT(mbuf != NULL && mbuf->last == mbuf->pos); //if(!EVP_EncryptInit_ex(aes_encrypt_ctx, aes_cipher, NULL, aes_key, aes_iv)) { if(!EVP_EncryptInit_ex(aes_encrypt_ctx, aes_cipher, NULL, aes_key, aes_key)) { return DN_ERROR; } if(!EVP_EncryptUpdate(aes_encrypt_ctx, mbuf->start, (int*)&block_len, (unsigned char*) msg, msg_len)) { return DN_ERROR; } enc_msg_len += block_len; if(!EVP_EncryptFinal_ex(aes_encrypt_ctx, mbuf->start + enc_msg_len, (int*) &block_len)) { return DN_ERROR; } EVP_CIPHER_CTX_cleanup(aes_encrypt_ctx); mbuf->last = mbuf->pos + enc_msg_len + block_len; return enc_msg_len + block_len; } else { mbuf_copy(mbuf, msg, msg_len); return (int) msg_len; } }
void dyn_parse_rsp(struct msg *r) { if (log_loggable(LOG_VVERB)) { log_debug(LOG_VVERB, ":::::::::::::::::::::: In dyn_parse_rsp, start to process response :::::::::::::::::::::::: "); msg_dump(r); } if (dyn_parse_core(r)) { struct dmsg *dmsg = r->dmsg; struct mbuf *b = STAILQ_LAST(&r->mhdr, mbuf, next); if (dmsg->type != DMSG_UNKNOWN && dmsg->type != DMSG_RES) { log_debug(LOG_DEBUG, "Resp parser: I got a dnode msg of type %d", dmsg->type); r->state = 0; r->result = MSG_PARSE_OK; r->dyn_state = DYN_DONE; return; } //check whether we need to decrypt the payload if (dmsg->bit_field == 1) { //dmsg->owner->owner->dnode_secured = 1; struct mbuf *decrypted_buf = mbuf_get(); if (decrypted_buf == NULL) { log_debug(LOG_INFO, "Unable to obtain an mbuf for dnode msg's header!"); r->result = MSG_OOM_ERROR; return; } //Dont need to decrypt AES key - pull it out from the conn dyn_aes_decrypt(dmsg->payload, dmsg->plen, decrypted_buf, r->owner->aes_key); b->pos = b->pos + dmsg->plen; r->pos = decrypted_buf->start; mbuf_copy(decrypted_buf, b->pos, mbuf_length(b)); mbuf_insert(&r->mhdr, decrypted_buf); mbuf_remove(&r->mhdr, b); mbuf_put(b); r->mlen = mbuf_length(decrypted_buf); } if (r->redis) { return redis_parse_rsp(r); } return memcache_parse_rsp(r); } //bad case if (log_loggable(LOG_DEBUG)) { log_debug(LOG_DEBUG, "Resp: bad message - cannot parse"); //fix me to do something msg_dump(r); } r->result = MSG_PARSE_AGAIN; }
rstatus_t dnode_peer_forward_state(void *rmsg) { rstatus_t status; struct ring_msg *msg = rmsg; struct server_pool *sp = msg->sp; log_debug(LOG_VVERB, "dnode_peer_forward_state: forwarding"); //we assume one mbuf is enough for now - will enhance with multiple mbufs later struct mbuf *mbuf = mbuf_get(); if (mbuf == NULL) { log_debug(LOG_VVERB, "Too bad, not enough memory!"); return DN_ENOMEM; } mbuf_copy(mbuf, msg->data, msg->len); struct array *peers = &sp->peers; uint32_t i,nelem; nelem = array_n(peers); //pick a random peer int ran_index = rand() % nelem; if (ran_index == 0) ran_index += 1; struct server *peer = (struct server *) array_get(peers, ran_index); //log_debug(LOG_VVERB, "Gossiping to node '%.*s'", peer->name.len, peer->name.data); struct conn * conn = dnode_peer_conn(peer); if (conn == NULL) { //running out of connection due to memory exhaust log_debug(LOG_ERR, "Unable to obtain a connection object"); return DN_ERROR; } status = dnode_peer_connect(sp->ctx, peer, conn); if (status != DN_OK ) { dnode_peer_close(sp->ctx, conn); log_debug(LOG_ERR, "Error happened in connecting on conn %d", conn->sd); return DN_ERROR; } dnode_peer_gossip_forward(sp->ctx, conn, sp->redis, mbuf); //free this as nobody else will do //mbuf_put(mbuf); return status; }
/* * Post-split copy handler invoked when the request is a multi vector - * 'get' or 'gets' request and has already been split into two requests */ rstatus_t memcache_post_splitcopy(struct msg *r) { struct mbuf *mbuf; struct string crlf = string(CRLF); ASSERT(r->request); ASSERT(!STAILQ_EMPTY(&r->mhdr)); mbuf = STAILQ_LAST(&r->mhdr, mbuf, next); mbuf_copy(mbuf, crlf.data, crlf.len); return FC_OK; }
/* * Pre-split copy handler invoked when the request is a multi vector - * 'get' or 'gets' request and is about to be split into two requests */ void memcache_pre_splitcopy(struct mbuf *mbuf, void *arg) { struct msg *r = arg; /* request vector */ struct string get = string("get "); /* 'get ' string */ struct string gets = string("gets "); /* 'gets ' string */ ASSERT(r->request); ASSERT(mbuf_empty(mbuf)); switch (r->type) { case MSG_REQ_GET: mbuf_copy(mbuf, get.data, get.len); break; case MSG_REQ_GETS: mbuf_copy(mbuf, gets.data, gets.len); break; default: NOT_REACHED(); } }
/* * append small(small than a mbuf) content into msg */ int msg_append(struct msg *msg, uint8_t *pos, size_t n) { struct mbuf *mbuf; ASSERT(n <= mbuf_data_size(msg->mb)); mbuf = msg_ensure_mbuf(msg, n); if (mbuf == NULL) { return RMT_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; return RMT_OK; }
/* * prepend small(small than a mbuf) content into msg */ int msg_prepend(struct msg *msg, uint8_t *pos, size_t n) { mbuf_base *mb = msg->mb; struct mbuf *mbuf; mbuf = mbuf_get(mb); if (mbuf == NULL) { return RMT_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; listAddNodeHead(msg->data, mbuf); return RMT_OK; }
/* * note: we should not call conn_add_out here. * because we may call append many times. */ rstatus_t conn_sendq_append(struct conn *conn, char *pos, size_t n) { struct mbuf *mbuf; size_t bytes = 0; size_t len; while (bytes < n) { mbuf = STAILQ_LAST(&conn->send_queue, mbuf, next); if ((mbuf == NULL) || mbuf_full(mbuf)) { mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_insert(&conn->send_queue, mbuf); } len = MIN(mbuf_size(mbuf), n - bytes); mbuf_copy(mbuf, (uint8_t *)pos + bytes, len); bytes += len; } return NC_OK; }
void mbuf_write_bytes(struct mbuf *mbuf, char *data, int len) { mbuf_copy(mbuf, data, len); }
void mbuf_write_mbuf(struct mbuf *mbuf, struct mbuf *data) { mbuf_copy(mbuf, data->pos, data->last - data->pos); }
void mbuf_write_string(struct mbuf *mbuf, struct string *s) { ASSERT(s->len < mbuf_size(mbuf)); mbuf_copy(mbuf, s->data, s->len); }
void dyn_parse_rsp(struct msg *r) { if (log_loggable(LOG_VVERB)) { log_debug(LOG_VVERB, ":::::::::::::::::::::: In dyn_parse_rsp, start to process response :::::::::::::::::::::::: "); msg_dump(r); } bool done_parsing = false; struct mbuf *b = STAILQ_LAST(&r->mhdr, mbuf, next); if (dyn_parse_core(r)) { struct dmsg *dmsg = r->dmsg; struct conn *conn = r->owner; conn->same_dc = dmsg->same_dc; if (dmsg->type != DMSG_UNKNOWN && dmsg->type != DMSG_RES) { log_debug(LOG_DEBUG, "Resp parser: I got a dnode msg of type %d", dmsg->type); r->state = 0; r->result = MSG_PARSE_OK; r->dyn_state = DYN_DONE; return; } if (r->dyn_state == DYN_DONE && dmsg->bit_field == 1) { dmsg->owner->owner->dnode_secured = 1; r->owner->dnode_crypto_state = 1; r->dyn_state = DYN_POST_DONE; r->result = MSG_PARSE_REPAIR; if (dmsg->mlen > 1) { //Decrypt AES key dyn_rsa_decrypt(dmsg->data, aes_decrypted_buf); strncpy(r->owner->aes_key, aes_decrypted_buf, strlen(aes_decrypted_buf)); } if (dmsg->plen + b->pos <= b->last) { struct mbuf *decrypted_buf = mbuf_get(); if (decrypted_buf == NULL) { loga("Unable to obtain an mbuf for dnode msg's header!"); r->result = MSG_OOM_ERROR; return; } dyn_aes_decrypt(b->pos, dmsg->plen, decrypted_buf, r->owner->aes_key); b->pos = b->pos + dmsg->plen; r->pos = decrypted_buf->start; mbuf_copy(decrypted_buf, b->pos, mbuf_length(b)); mbuf_insert(&r->mhdr, decrypted_buf); mbuf_remove(&r->mhdr, b); mbuf_put(b); r->mlen = mbuf_length(decrypted_buf); return data_store_parse_rsp(r); } //Subtract already received bytes dmsg->plen -= b->last - b->pos; return; } else if (r->dyn_state == DYN_POST_DONE) { struct mbuf *last_buf = STAILQ_LAST(&r->mhdr, mbuf, next); if (last_buf->read_flip == 1) { data_store_parse_rsp(r); } else { r->result = MSG_PARSE_AGAIN; } return; } if (done_parsing) return; return data_store_parse_rsp(r); } //bad case if (log_loggable(LOG_DEBUG)) { log_debug(LOG_DEBUG, "Resp: bad message - cannot parse"); //fix me to do something msg_dump(r); } r->result = MSG_PARSE_AGAIN; }
void dyn_parse_req(struct msg *r) { if (log_loggable(LOG_VVERB)) { log_debug(LOG_VVERB, ":::::::::::::::::::::: In dyn_parse_req, start to process request :::::::::::::::::::::: "); msg_dump(r); } bool done_parsing = false; struct mbuf *b = STAILQ_LAST(&r->mhdr, mbuf, next); if (dyn_parse_core(r)) { struct dmsg *dmsg = r->dmsg; struct conn *conn = r->owner; conn->same_dc = dmsg->same_dc; if (dmsg->type != DMSG_UNKNOWN && dmsg->type != DMSG_REQ && dmsg->type != DMSG_REQ_FORWARD && dmsg->type != GOSSIP_SYN) { r->state = 0; r->result = MSG_PARSE_OK; r->dyn_state = DYN_DONE; return; } if (r->dyn_state == DYN_DONE && dmsg->bit_field == 1) { dmsg->owner->owner->dnode_secured = 1; r->owner->dnode_crypto_state = 1; r->dyn_state = DYN_POST_DONE; r->result = MSG_PARSE_REPAIR; if (dmsg->mlen > 1) { //Decrypt AES key dyn_rsa_decrypt(dmsg->data, aes_decrypted_buf); strncpy(r->owner->aes_key, aes_decrypted_buf, strlen(aes_decrypted_buf)); } if (dmsg->plen + b->pos <= b->last) { struct mbuf *decrypted_buf = mbuf_get(); if (decrypted_buf == NULL) { loga("Unable to obtain an mbuf for dnode msg's header!"); r->result = MSG_OOM_ERROR; return; } dyn_aes_decrypt(b->pos, dmsg->plen, decrypted_buf, r->owner->aes_key); b->pos = b->pos + dmsg->plen; r->pos = decrypted_buf->start; mbuf_copy(decrypted_buf, b->pos, mbuf_length(b)); mbuf_insert(&r->mhdr, decrypted_buf); mbuf_remove(&r->mhdr, b); mbuf_put(b); r->mlen = mbuf_length(decrypted_buf); data_store_parse_req(r); } //substract alraedy received bytes dmsg->plen -= b->last - b->pos; return; } else if (r->dyn_state == DYN_POST_DONE) { struct mbuf *last_buf = STAILQ_LAST(&r->mhdr, mbuf, next); if (last_buf->read_flip == 1) { data_store_parse_req(r); } else { r->result = MSG_PARSE_AGAIN; } return; } if (dmsg->type == GOSSIP_SYN) { //TODOs: need to address multi-buffer msg later dmsg->payload = b->pos; b->pos = b->pos + dmsg->plen; r->pos = b->pos; done_parsing = true; } if (done_parsing) return; return data_store_parse_req(r); } //bad case if (log_loggable(LOG_VVERB)) { log_debug(LOG_VVERB, "Bad or splitted message"); //fix me to do something msg_dump(r); } r->result = MSG_PARSE_AGAIN; }
/* * copy one response from src to dst * return bytes copied * */ static rstatus_t memcache_copy_bulk(struct msg *dst, struct msg *src) { struct mbuf *mbuf, *nbuf; uint8_t *p; uint32_t len = 0; uint32_t bytes = 0; uint32_t i = 0; for (mbuf = STAILQ_FIRST(&src->mhdr); mbuf && mbuf_empty(mbuf); mbuf = STAILQ_FIRST(&src->mhdr)) { mbuf_remove(&src->mhdr, mbuf); mbuf_put(mbuf); } mbuf = STAILQ_FIRST(&src->mhdr); if (mbuf == NULL) { return NC_OK; /* key not exists */ } p = mbuf->pos; /* get : VALUE key 0 len\r\nval\r\n */ /* gets: VALUE key 0 len cas\r\nval\r\n */ ASSERT(*p == 'V'); for (i = 0; i < 3; i++) { /* eat 'VALUE key 0 ' */ for (; *p != ' ';) { p++; } p++; } len = 0; for (; p < mbuf->last && isdigit(*p); p++) { len = len * 10 + (uint32_t)(*p - '0'); } for (; p < mbuf->last && ('\r' != *p); p++) { /* eat cas for gets */ ; } len += CRLF_LEN * 2; len += (p - mbuf->pos); bytes = len; /* copy len bytes to dst */ for (; mbuf;) { if (mbuf_length(mbuf) <= len) { /* steal this mbuf from src to dst */ nbuf = STAILQ_NEXT(mbuf, next); mbuf_remove(&src->mhdr, mbuf); mbuf_insert(&dst->mhdr, mbuf); len -= mbuf_length(mbuf); mbuf = nbuf; } else { /* split it */ nbuf = mbuf_get(); if (nbuf == NULL) { return NC_ENOMEM; } mbuf_copy(nbuf, mbuf->pos, len); mbuf_insert(&dst->mhdr, nbuf); mbuf->pos += len; break; } } dst->mlen += bytes; src->mlen -= bytes; log_debug(LOG_VVERB, "memcache_copy_bulk copy bytes: %d", bytes); return NC_OK; }
/* dnode sends a response back to a peer */ struct msg * dnode_rsp_send_next(struct context *ctx, struct conn *conn) { if (TRACING_LEVEL == LOG_VVERB) { log_debug(LOG_VVERB, "dnode_rsp_send_next entering"); } ASSERT(conn->dnode_client && !conn->dnode_server); struct msg *msg = rsp_send_next(ctx, conn); if (msg != NULL && conn->dyn_mode) { struct msg *pmsg = TAILQ_FIRST(&conn->omsg_q); //peer request's msg //need to deal with multi-block later uint64_t msg_id = pmsg->dmsg->id; struct mbuf *header_buf = mbuf_get(); if (header_buf == NULL) { loga("Unable to obtain an mbuf for header!"); return NULL; //need to address error here properly } //TODOs: need to set the outcoming conn to be secured too if the incoming conn is secured if (pmsg->owner->dnode_secured || conn->dnode_secured) { if (TRACING_LEVEL == LOG_VVERB) { log_debug(LOG_VVERB, "Encrypting response ..."); loga("AES encryption key: %s\n", base64_encode(conn->aes_key, AES_KEYLEN)); } struct mbuf *data_buf = STAILQ_LAST(&msg->mhdr, mbuf, next); //if (ENCRYPTION) { struct mbuf *encrypted_buf = mbuf_get(); if (encrypted_buf == NULL) { loga("Unable to obtain an mbuf for encryption!"); return NULL; //TODOs: need to clean up } rstatus_t status = dyn_aes_encrypt(data_buf->pos, mbuf_length(data_buf), encrypted_buf, conn->aes_key); if (TRACING_LEVEL == LOG_VVERB) { log_debug(LOG_VERB, "#encrypted bytes : %d", status); } dmsg_write(header_buf, msg_id, DMSG_RES, conn, mbuf_length(encrypted_buf)); if (TRACING_LEVEL == LOG_VVERB) { log_hexdump(LOG_VVERB, data_buf->pos, mbuf_length(data_buf), "resp dyn message - original payload: "); log_hexdump(LOG_VVERB, encrypted_buf->pos, mbuf_length(encrypted_buf), "dyn message encrypted payload: "); } mbuf_copy(header_buf, encrypted_buf->start, mbuf_length(encrypted_buf)); mbuf_insert(&msg->mhdr, header_buf); //remove the original dbuf out of the queue and insert encrypted mbuf to replace mbuf_remove(&msg->mhdr, data_buf); //mbuf_insert(&msg->mhdr, encrypted_buf); mbuf_put(data_buf); mbuf_put(encrypted_buf); //} else { // log_debug(LOG_VERB, "no encryption on the response's payload"); // dmsg_write(header_buf, msg_id, DMSG_RES, conn, mbuf_length(data_buf)); //} } else { dmsg_write(header_buf, msg_id, DMSG_RES, conn, 0);//Dont care about 0 or the real length as we don't use that value in unencryption mode mbuf_insert_head(&msg->mhdr, header_buf); } if (TRACING_LEVEL == LOG_VVERB) { log_hexdump(LOG_VVERB, header_buf->pos, mbuf_length(header_buf), "resp dyn message - header: "); msg_dump(msg); } } return msg; }
uint8_t dns_get_seeds(struct context * ctx, struct mbuf *seeds_buf) { static int _env_checked = 0; if (!_env_checked) { _env_checked = 1; txtName = getenv("DYNOMITE_DNS_TXT_NAME"); if (txtName == NULL) txtName = DNS_TXT_NAME; } log_debug(LOG_VVERB, "checking for %s", txtName); if (!seeds_check()) { return DN_NOOPS; } unsigned char buf[BUFSIZ]; int r = res_query(txtName, C_IN, T_TXT, buf, sizeof(buf)); if (r == -1) { log_debug(LOG_DEBUG, "DNS response for %s: %s", txtName, hstrerror(h_errno)); return DN_NOOPS; } if (r >= sizeof(buf)) { log_debug(LOG_DEBUG, "DNS reply is too large for %s: %d, bufsize: %d", txtName, r, sizeof(buf)); return DN_NOOPS; } HEADER *hdr = (HEADER*)buf; if (hdr->rcode != NOERROR) { log_debug(LOG_DEBUG, "DNS reply code for %s: %d", txtName, hdr->rcode); return DN_NOOPS; } int na = ntohs(hdr->ancount); ns_msg m; int k = ns_initparse(buf, r, &m); if (k == -1) { log_debug(LOG_DEBUG, "ns_initparse error for %s: %s", txtName, strerror(errno)); return DN_NOOPS; } int i; ns_rr rr; for (i = 0; i < na; ++i) { int k = ns_parserr(&m, ns_s_an, i, &rr); if (k == -1) { log_debug(LOG_DEBUG, "ns_parserr for %s: %s", txtName, strerror (errno)); return DN_NOOPS; } mbuf_rewind(seeds_buf); unsigned char *r = ns_rr_rdata(rr); if (r[0] >= ns_rr_rdlen(rr)) { log_debug(LOG_DEBUG, "invalid TXT length for %s: %d < %d", txtName, r[0], ns_rr_rdlen(rr)); return DN_NOOPS; } log_debug(LOG_VERB, "seeds for %s: %.*s", txtName, r[0], r +1); mbuf_copy(seeds_buf, r + 1, r[0]); } uint32_t seeds_hash = hash_seeds(seeds_buf->pos, mbuf_length(seeds_buf)); if (last_seeds_hash != seeds_hash) { last_seeds_hash = seeds_hash; } else { return DN_NOOPS; } return DN_OK; }