answer_from_cache(struct worker* worker, struct query_info* qinfo,
	struct respip_client_info* cinfo, int* need_drop, int* is_expired_answer,
	int* is_secure_answer, struct ub_packed_rrset_key** alias_rrset,
	struct reply_info** partial_repp,
	struct reply_info* rep, uint16_t id, uint16_t flags,
	struct comm_reply* repinfo, struct edns_data* edns)
{
	time_t timenow = *worker->env.now;
	uint16_t udpsize = edns->udp_size;
	struct reply_info* encode_rep = rep;
	struct reply_info* partial_rep = *partial_repp;
	int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
		&& worker->env.need_to_validate;
	*partial_repp = NULL;  /* avoid accidental further pass */

	/* Check TTL */
	if(rep->ttl < timenow) {
		/* Check if we need to serve expired now */
		if(worker->env.cfg->serve_expired &&
			!worker->env.cfg->serve_expired_client_timeout) {
				if(worker->env.cfg->serve_expired_ttl &&
					rep->serve_expired_ttl < timenow)
					return 0;
				if(!rrset_array_lock(rep->ref, rep->rrset_count, 0))
					return 0;
				*is_expired_answer = 1;
		} else {
			/* the rrsets may have been updated in the meantime.
			 * we will refetch the message format from the
			 * authoritative server
			 */
			return 0;
		}
	} else {
		if(!rrset_array_lock(rep->ref, rep->rrset_count, timenow))
			return 0;
	}
	/* locked and ids and ttls are OK. */

	/* check CNAME chain (if any) */
	if(rep->an_numrrsets > 0 && (rep->rrsets[0]->rk.type ==
		htons(LDNS_RR_TYPE_CNAME) || rep->rrsets[0]->rk.type ==
		htons(LDNS_RR_TYPE_DNAME))) {
		if(!reply_check_cname_chain(qinfo, rep)) {
			/* cname chain invalid, redo iterator steps */
			verbose(VERB_ALGO, "Cache reply: cname chain broken");
			goto bail_out;
		}
	}
	/* check security status of the cached answer */
	if(must_validate && (rep->security == sec_status_bogus ||
		rep->security == sec_status_secure_sentinel_fail)) {
		/* BAD cached */
		edns->edns_version = EDNS_ADVERTISED_VERSION;
		edns->udp_size = EDNS_ADVERTISED_SIZE;
		edns->ext_rcode = 0;
		edns->bits &= EDNS_DO;
		if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, rep,
			LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
			worker->env.now_tv))
			goto bail_out;
		/* TODO store the reason for the bogus reply in cache
		 * and implement in here instead of the hardcoded EDE */
		if (worker->env.cfg->ede) {
			EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
				worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
		}
		error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
			qinfo, id, flags, edns);
		rrset_array_unlock_touch(worker->env.rrset_cache,
			worker->scratchpad, rep->ref, rep->rrset_count);
		if(worker->stats.extended) {
			worker->stats.ans_bogus ++;
			worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL] ++;
		}
		return 1;
	} else if(rep->security == sec_status_unchecked && must_validate) {
		verbose(VERB_ALGO, "Cache reply: unchecked entry needs "
			"validation");
		goto bail_out; /* need to validate cache entry first */
	} else if(rep->security == sec_status_secure) {
		if(reply_all_rrsets_secure(rep)) {
			*is_secure_answer = 1;
		} else {
			if(must_validate) {
				verbose(VERB_ALGO, "Cache reply: secure entry"
					" changed status");
				goto bail_out; /* rrset changed, re-verify */
			}
			*is_secure_answer = 0;
		}
	} else *is_secure_answer = 0;

	edns->edns_version = EDNS_ADVERTISED_VERSION;
	edns->udp_size = EDNS_ADVERTISED_SIZE;
	edns->ext_rcode = 0;
	edns->bits &= EDNS_DO;
	if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, rep,
		(int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
		worker->env.now_tv))
		goto bail_out;
	*alias_rrset = NULL; /* avoid confusion if caller set it to non-NULL */
	if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
		!partial_rep && !apply_respip_action(worker, qinfo, cinfo, rep,
		repinfo, alias_rrset,
		&encode_rep, worker->env.auth_zones)) {
		goto bail_out;
	} else if(partial_rep &&
		!respip_merge_cname(partial_rep, qinfo, rep, cinfo,
		must_validate, &encode_rep, worker->scratchpad,
		worker->env.auth_zones)) {
		goto bail_out;
	}
	if(encode_rep != rep) {
		/* if rewritten, it can't be considered "secure" */
		*is_secure_answer = 0;
	}
	if(!encode_rep || *alias_rrset) {
		if(!encode_rep)
			*need_drop = 1;
		else {
			/* If a partial CNAME chain is found, we first need to
			 * make a copy of the reply in the scratchpad so we
			 * can release the locks and lookup the cache again. */
			*partial_repp = reply_info_copy(encode_rep, NULL,
				worker->scratchpad);
			if(!*partial_repp)
				goto bail_out;
		}
	} else {
		/* We don't check the global ede as this is a warning, not
		 * an error */
		if (*is_expired_answer == 1 &&
			worker->env.cfg->ede_serve_expired && worker->env.cfg->ede) {
			EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
				worker->scratchpad, LDNS_EDE_STALE_ANSWER, "");
		}
		if(!reply_info_answer_encode(qinfo, encode_rep, id, flags,
			repinfo->c->buffer, timenow, 1, worker->scratchpad,
			udpsize, edns, (int)(edns->bits & EDNS_DO),
			*is_secure_answer)) {
			if(!inplace_cb_reply_servfail_call(&worker->env, qinfo,
				NULL, NULL, LDNS_RCODE_SERVFAIL, edns, repinfo,
				worker->scratchpad, worker->env.now_tv))
					edns->opt_list_inplace_cb_out = NULL;
			error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
				qinfo, id, flags, edns);
		}
	}
	/* cannot send the reply right now, because blocking network syscall
	 * is bad while holding locks. */
	rrset_array_unlock_touch(worker->env.rrset_cache, worker->scratchpad,
		rep->ref, rep->rrset_count);
	/* go and return this buffer to the client */
	return 1;

bail_out:
	rrset_array_unlock_touch(worker->env.rrset_cache, 
		worker->scratchpad, rep->ref, rep->rrset_count);
	return 0;
}