static void* clone_errcode_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_errcode_attr *asrc = (const pj_stun_errcode_attr*)src;
    pj_stun_errcode_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_errcode_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_errcode_attr));
    pj_strdup(pool, &dst->reason, &asrc->reason);

    return (void*)dst;
}


PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
				       const pj_uint8_t *pdu,
				       pj_size_t pdu_len,
				       unsigned options,
				       pj_stun_msg **p_msg,
				       pj_size_t *p_parsed_len,
				       pj_stun_msg **p_response)
{

    pj_stun_msg *msg;
    const pj_uint8_t *start_pdu = pdu;
    pj_bool_t has_msg_int = PJ_FALSE;
    pj_bool_t has_fingerprint = PJ_FALSE;
    pj_status_t status;

    PJ_UNUSED_ARG(options);

    PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL);
    PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG);

    if (p_parsed_len)
	*p_parsed_len = 0;
    if (p_response)
	*p_response = NULL;

    /* Check if this is a STUN message, if necessary */
    if (options & PJ_STUN_CHECK_PACKET) {
	status = pj_stun_msg_check(pdu, pdu_len, options);
	if (status != PJ_SUCCESS)
	    return status;
    }

    /* Create the message, copy the header, and convert to host byte order */
    msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr));
    msg->hdr.type = pj_ntohs(msg->hdr.type);
    msg->hdr.length = pj_ntohs(msg->hdr.length);
    msg->hdr.magic = pj_ntohl(msg->hdr.magic);

    pdu += sizeof(pj_stun_msg_hdr);
    /* pdu_len -= sizeof(pj_stun_msg_hdr); */
    pdu_len = msg->hdr.length;

    /* No need to create response if this is not a request */
    if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
	p_response = NULL;

    /* Parse attributes */
    while (pdu_len >= 4) {
	unsigned attr_type, attr_val_len;
	const struct attr_desc *adesc;

	/* Get attribute type and length. If length is not aligned
	 * to 4 bytes boundary, add padding.
	 */
	attr_type = GETVAL16H(pdu, 0);
	attr_val_len = GETVAL16H(pdu, 2);
	attr_val_len = (attr_val_len + 3) & (~3);

	/* Check length */
	if (pdu_len < attr_val_len) {
	    pj_str_t err_msg;
	    char err_msg_buf[80];

	    err_msg.ptr = err_msg_buf;
	    err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf),
					    "Attribute %s has invalid length",
					    pj_stun_get_attr_name(attr_type));

	    PJ_LOG(4,(THIS_FILE, "Error decoding message: %.*s",
		      (int)err_msg.slen, err_msg.ptr));

	    if (p_response) {
		pj_stun_msg_create_response(pool, msg,
					    PJ_STUN_SC_BAD_REQUEST,
					    &err_msg, p_response);
	    }
	    return PJNATH_ESTUNINATTRLEN;
	}

	/* Get the attribute descriptor */
	adesc = find_attr_desc(attr_type);

	if (adesc == NULL) {
	    /* Unrecognized attribute */
	    pj_stun_binary_attr *attr = NULL;

	    PJ_LOG(5,(THIS_FILE, "Unrecognized attribute type 0x%x",
		      attr_type));

	    /* Is this a fatal condition? */
	    if (attr_type <= 0x7FFF) {
		/* This is a mandatory attribute, we must return error
		 * if we don't understand the attribute.
		 */
		if (p_response) {
		    unsigned err_code = PJ_STUN_SC_UNKNOWN_ATTRIBUTE;

		    status = pj_stun_msg_create_response(pool, msg,
							 err_code, NULL,
							 p_response);
		    if (status==PJ_SUCCESS) {
			pj_uint16_t d = (pj_uint16_t)attr_type;
			pj_stun_msg_add_unknown_attr(pool, *p_response, 1, &d);
		    }
		}

		return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_ATTRIBUTE);
	    }

	    /* Make sure we have rooms for the new attribute */
	    if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}
		return PJNATH_ESTUNTOOMANYATTR;
	    }

	    /* Create binary attribute to represent this */
	    status = pj_stun_binary_attr_create(pool, attr_type, pdu+4,
						GETVAL16H(pdu, 2), &attr);
	    if (status != PJ_SUCCESS) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}

		PJ_LOG(4,(THIS_FILE,
			  "Error parsing unknown STUN attribute type %d",
			  attr_type));

		return status;
	    }

	    /* Add the attribute */
	    msg->attr[msg->attr_count++] = &attr->hdr;

	} else {
	    void *attr;
	    char err_msg1[PJ_ERR_MSG_SIZE],
		 err_msg2[PJ_ERR_MSG_SIZE];

	    /* Parse the attribute */
	    status = (adesc->decode_attr)(pool, pdu, &msg->hdr, &attr);

	    if (status != PJ_SUCCESS) {
		pj_strerror(status, err_msg1, sizeof(err_msg1));

		if (p_response) {
		    pj_str_t e;

		    e.ptr = err_msg2;
		    e.slen= pj_ansi_snprintf(err_msg2, sizeof(err_msg2),
					     "%s in %s",
					     err_msg1,
					     pj_stun_get_attr_name(attr_type));
		    if (e.slen < 1 || e.slen >= (int)sizeof(err_msg2))
			e.slen = sizeof(err_msg2) - 1;
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_BAD_REQUEST,
						&e, p_response);
		}

		PJ_LOG(4,(THIS_FILE,
			  "Error parsing STUN attribute %s: %s",
			  pj_stun_get_attr_name(attr_type),
			  err_msg1));

		return status;
	    }

	    if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY &&
		!has_fingerprint)
	    {
		if (has_msg_int) {
		    /* Already has MESSAGE-INTEGRITY */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNDUPATTR;
		}
		has_msg_int = PJ_TRUE;

	    } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) {
		if (has_fingerprint) {
		    /* Already has FINGERPRINT */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNDUPATTR;
		}
		has_fingerprint = PJ_TRUE;
	    } else {
		if (has_fingerprint) {
		    /* Another attribute is found which is not FINGERPRINT
		     * after FINGERPRINT. Note that non-FINGERPRINT is
		     * allowed to appear after M-I
		     */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNFINGERPOS;
		}
	    }

	    /* Make sure we have rooms for the new attribute */
	    if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}
		return PJNATH_ESTUNTOOMANYATTR;
	    }

	    /* Add the attribute */
	    msg->attr[msg->attr_count++] = (pj_stun_attr_hdr*)attr;
	}

	/* Next attribute */
	if (attr_val_len + 4 >= pdu_len) {
	    pdu += pdu_len;
	    pdu_len = 0;
	} else {
	    pdu += (attr_val_len + 4);
	    pdu_len -= (attr_val_len + 4);
	}
    }

    if (pdu_len > 0) {
	/* Stray trailing bytes */
	PJ_LOG(4,(THIS_FILE,
		  "Error decoding STUN message: unparsed trailing %d bytes",
		  pdu_len));
	return PJNATH_EINSTUNMSGLEN;
    }

    *p_msg = msg;

    if (p_parsed_len)
	*p_parsed_len = (pdu - start_pdu);

    return PJ_SUCCESS;
}

Merge pull request from GHSA-2qpg-f6wf-w984


PJ_DEF(pj_status_t) pj_stun_errcode_attr_create(pj_pool_t *pool,
						int err_code,
						const pj_str_t *err_reason,
						pj_stun_errcode_attr **p_attr)
{
    pj_stun_errcode_attr *attr;
    char err_buf[80];
    pj_str_t str;

    PJ_ASSERT_RETURN(pool && err_code && p_attr, PJ_EINVAL);

    if (err_reason == NULL) {
	str = pj_stun_get_err_reason(err_code);
	if (str.slen == 0) {
	    str.slen = pj_ansi_snprintf(err_buf, sizeof(err_buf),
				        "Unknown error %d", err_code);
	    str.ptr = err_buf;
	}
	err_reason = &str;
    }

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_ERROR_CODE, 4+err_reason->slen);
    attr->err_code = err_code;
    pj_strdup(pool, &attr->reason, err_reason);

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
				       pj_uint8_t *buf, pj_size_t buf_size,
				       unsigned options,
				       const pj_str_t *key,
				       pj_size_t *p_msg_len)
{
    pj_uint8_t *start = buf;
    pj_stun_msgint_attr *amsgint = NULL;
    pj_stun_fingerprint_attr *afingerprint = NULL;
    unsigned printed = 0, body_len;
    pj_status_t status;
    unsigned i;

    PJ_ASSERT_RETURN(msg && buf && buf_size, PJ_EINVAL);

    PJ_UNUSED_ARG(options);
    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);

    /* Copy the message header part and convert the header fields to
     * network byte order
     */
    if (buf_size < sizeof(pj_stun_msg_hdr))
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, msg->hdr.type);
    PUTVAL16H(buf, 2, 0);   /* length will be calculated later */
    PUTVAL32H(buf, 4, msg->hdr.magic);
    pj_memcpy(buf+8, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id));

    buf += sizeof(pj_stun_msg_hdr);
    buf_size -= sizeof(pj_stun_msg_hdr);

    /* Encode each attribute to the message */
    for (i=0; i<msg->attr_count; ++i) {
	const struct attr_desc *adesc;
	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];

	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
	    pj_assert(amsgint == NULL);
	    amsgint = (pj_stun_msgint_attr*) attr_hdr;

	    /* Stop when encountering MESSAGE-INTEGRITY */
	    break;

	} else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
	    afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
	    break;
	}

	adesc = find_attr_desc(attr_hdr->type);
	if (adesc) {
	    status = adesc->encode_attr(attr_hdr, buf, (unsigned)buf_size,
					&msg->hdr, &printed);
	} else {
	    /* This may be a generic attribute */
	    const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr*)
						   attr_hdr;
	    PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, PJ_EBUG);
	    status = encode_binary_attr(bin_attr, buf, (unsigned)buf_size,
					&msg->hdr, &printed);
	}

	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* We may have stopped printing attribute because we found
     * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the
     * attributes.
     */
    for ( ++i; i<msg->attr_count; ++i) {
	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];

	/* There mustn't any attribute after FINGERPRINT */
	PJ_ASSERT_RETURN(afingerprint == NULL, PJNATH_ESTUNFINGERPOS);

	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
	    /* There mustn't be MESSAGE-INTEGRITY before */
	    PJ_ASSERT_RETURN(amsgint == NULL,
			     PJNATH_ESTUNMSGINTPOS);
	    amsgint = (pj_stun_msgint_attr*) attr_hdr;

	} else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
	    afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
	}
    }

#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
    /*
     * This is the old style MESSAGE-INTEGRITY and FINGERPRINT
     * calculation, used in rfc3489bis-06 and older.
     */
    /* We MUST update the message length in the header NOW before
     * calculating MESSAGE-INTEGRITY and FINGERPRINT.
     * Note that length is not including the 20 bytes header.
      */
    if (amsgint && afingerprint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
    } else if (amsgint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24);
    } else if (afingerprint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 8);
    } else {
	body_len = (pj_uint16_t)((buf - start) - 20);
    }
#else
    /* If MESSAGE-INTEGRITY is present, include the M-I attribute
     * in message length before calculating M-I
     */
    if (amsgint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24);
    } else {
	body_len = (pj_uint16_t)((buf - start) - 20);
    }
#endif	/* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */

    /* hdr->length = pj_htons(length); */
    PUTVAL16H(start, 2, (pj_uint16_t)body_len);

    /* Calculate message integrity, if present */
    if (amsgint != NULL) {
	pj_hmac_sha1_context ctx;

	/* Key MUST be specified */
	PJ_ASSERT_RETURN(key, PJ_EINVALIDOP);

	/* MESSAGE-INTEGRITY must be the last attribute in the message, or
	 * the last attribute before FINGERPRINT.
	 */
	if (msg->attr_count>1 && i < msg->attr_count-2) {
	    /* Should not happen for message generated by us */
	    pj_assert(PJ_FALSE);
	    return PJNATH_ESTUNMSGINTPOS;

	} else if (i == msg->attr_count-2)  {
	    if (msg->attr[i+1]->type != PJ_STUN_ATTR_FINGERPRINT) {
		/* Should not happen for message generated by us */
		pj_assert(PJ_FALSE);
		return PJNATH_ESTUNMSGINTPOS;
	    } else {
		afingerprint = (pj_stun_fingerprint_attr*) msg->attr[i+1];
	    }
	}

	/* Calculate HMAC-SHA1 digest, add zero padding to input
	 * if necessary to make the input 64 bytes aligned.
	 */
	pj_hmac_sha1_init(&ctx, (const pj_uint8_t*)key->ptr,
			  (unsigned)key->slen);
	pj_hmac_sha1_update(&ctx, (const pj_uint8_t*)start,
			    (unsigned)(buf-start));
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
	// These are obsoleted in rfc3489bis-08
	if ((buf-start) & 0x3F) {
	    pj_uint8_t zeroes[64];
	    pj_bzero(zeroes, sizeof(zeroes));
	    pj_hmac_sha1_update(&ctx, zeroes, 64-((buf-start) & 0x3F));
	}
#endif	/* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
	pj_hmac_sha1_final(&ctx, amsgint->hmac);

	/* Put this attribute in the message */
	status = encode_msgint_attr(amsgint, buf, (unsigned)buf_size,
			            &msg->hdr, &printed);
	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* Calculate FINGERPRINT if present */
    if (afingerprint != NULL) {

#if !PJ_STUN_OLD_STYLE_MI_FINGERPRINT
	/* Update message length */
	PUTVAL16H(start, 2,
		 (pj_uint16_t)(GETVAL16H(start, 2)+8));
#endif

	afingerprint->value = pj_crc32_calc(start, buf-start);
	afingerprint->value ^= STUN_XOR_FINGERPRINT;

	/* Put this attribute in the message */
	status = encode_uint_attr(afingerprint, buf, (unsigned)buf_size,
				  &msg->hdr, &printed);
	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* Update message length. */
    msg->hdr.length = (pj_uint16_t) ((buf - start) - 20);

    /* Return the length */
    if (p_msg_len)
	*p_msg_len = (buf - start);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_create( pj_pool_t *pool,
					unsigned msg_type,
					pj_uint32_t magic,
					const pj_uint8_t tsx_id[12],
					pj_stun_msg **p_msg)
{
    pj_stun_msg *msg;

    PJ_ASSERT_RETURN(pool && msg_type && p_msg, PJ_EINVAL);

    msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    *p_msg = msg;
    return pj_stun_msg_init(msg, msg_type, magic, tsx_id);
}


static void* clone_unknown_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_unknown_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_unknown_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_unknown_attr));

    return (void*)dst;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool,
						pj_stun_msg *msg)
{
    pj_stun_msgint_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_msgint_attr_create(pool, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(pj_status_t) pj_stun_msg_add_errcode_attr(pj_pool_t *pool,
						 pj_stun_msg *msg,
						 int err_code,
						 const pj_str_t *err_reason)
{
    pj_stun_errcode_attr *err_attr = NULL;
    pj_status_t status;

    status = pj_stun_errcode_attr_create(pool, err_code, err_reason,
					 &err_attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &err_attr->hdr);
}


static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool,
					      const pj_uint8_t *buf,
					      const pj_stun_msg_hdr *msghdr,
					      void **p_attr)
{
    pj_stun_sockaddr_attr *attr;
    pj_status_t status;

    status = decode_sockaddr_attr(pool, buf, msghdr, p_attr);
    if (status != PJ_SUCCESS)
	return status;

    attr = *(pj_stun_sockaddr_attr**)p_attr;

    attr->xor_ed = PJ_TRUE;

    if (attr->sockaddr.addr.sa_family == pj_AF_INET()) {
	attr->sockaddr.ipv4.sin_port ^= pj_htons(PJ_STUN_MAGIC >> 16);
	attr->sockaddr.ipv4.sin_addr.s_addr ^= pj_htonl(PJ_STUN_MAGIC);
    } else if (attr->sockaddr.addr.sa_family == pj_AF_INET6()) {
	unsigned i;
	pj_uint8_t *dst = (pj_uint8_t*) &attr->sockaddr.ipv6.sin6_addr;
	pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC);

	attr->sockaddr.ipv6.sin6_port ^= pj_htons(PJ_STUN_MAGIC >> 16);

	/* If the IP address family is IPv6, X-Address is computed by
	 * taking the mapped IP address in host byte order, XOR'ing it
	 * with the concatenation of the magic cookie and the 96-bit
	 * transaction ID, and converting the result to network byte
	 * order.
	 */
	for (i=0; i<4; ++i) {
	    dst[i] ^= ((const pj_uint8_t*)&magic)[i];
	}
	pj_assert(sizeof(msghdr->tsx_id[0]) == 1);
	for (i=0; i<12; ++i) {
	    dst[i+4] ^= msghdr->tsx_id[i];
	}

    } else {
	return PJNATH_EINVAF;
    }

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static void PUTVAL64H(pj_uint8_t *buf, unsigned pos, const pj_timestamp *ts)
{
    PUTVAL32H(buf, pos, ts->u32.hi);
    PUTVAL32H(buf, pos+4, ts->u32.lo);
}


static void PUTVAL32H(pj_uint8_t *buf, unsigned pos, pj_uint32_t hval)
{
    buf[pos+0] = (pj_uint8_t) ((hval & 0xFF000000UL) >> 24);
    buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF0000UL) >> 16);
    buf[pos+2] = (pj_uint8_t) ((hval & 0x0000FF00UL) >>  8);
    buf[pos+3] = (pj_uint8_t) ((hval & 0x000000FFUL) >>  0);
}


PJ_DEF(pj_status_t) pj_stun_sockaddr_attr_create(pj_pool_t *pool,
						 int attr_type,
						 pj_bool_t xor_ed,
						 const pj_sockaddr_t *addr,
						 unsigned addr_len,
						 pj_stun_sockaddr_attr **p_attr)
{
    pj_stun_sockaddr_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
    *p_attr = attr;
    return pj_stun_sockaddr_attr_init(attr, attr_type, xor_ed,
				      addr, addr_len);
}


static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf,
				      unsigned len,
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_uint64_attr *ca = (const pj_stun_uint64_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    if (len < 12)
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)8);
    PUTVAL64H(buf, 4, &ca->value);

    /* Done */
    *printed = 12;

    return PJ_SUCCESS;
}


static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf,
				    unsigned len,
				    const pj_stun_msg_hdr *msghdr,
				    unsigned *printed)
{
    const pj_stun_uint_attr *ca = (const pj_stun_uint_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    if (len < 8)
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)4);
    PUTVAL32H(buf, 4, ca->value);

    /* Done */
    *printed = 8;

    return PJ_SUCCESS;
}


static void* clone_msgint_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_msgint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_msgint_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_msgint_attr));

    return (void*) dst;
}


static void* clone_binary_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_binary_attr *asrc = (const pj_stun_binary_attr*)src;
    pj_stun_binary_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_binary_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_binary_attr));

    if (asrc->length) {
	dst->data = (pj_uint8_t*) pj_pool_alloc(pool, asrc->length);
	pj_memcpy(dst->data, asrc->data, asrc->length);
    }

    return (void*)dst;
}


static void GETATTRHDR(const pj_uint8_t *buf, pj_stun_attr_hdr *hdr)
{
    hdr->type = GETVAL16H(buf, 0);
    hdr->length = GETVAL16H(buf, 2);
}


PJ_DEF(pj_status_t) pj_stun_uint64_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_timestamp *value,
					       pj_stun_uint64_attr **p_attr)
{
    pj_stun_uint64_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr);
    INIT_ATTR(attr, attr_type, 8);

    if (value) {
	attr->value.u32.hi = value->u32.hi;
	attr->value.u32.lo = value->u32.lo;
    }

    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf,
				      unsigned len,
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_string_attr *ca =
	(const pj_stun_string_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    /* Calculated total attr_len (add padding if necessary) */
    *printed = ((unsigned)ca->value.slen + ATTR_HDR_LEN + 3) & (~3);
    if (len < *printed) {
	*printed = 0;
	return PJ_ETOOSMALL;
    }

    PUTVAL16H(buf, 0, ca->hdr.type);

    /* Special treatment for SOFTWARE attribute:
     * This attribute had caused interop problem when talking to
     * legacy RFC 3489 STUN servers, due to different "length"
     * rules with RFC 5389.
     */
    if (msghdr->magic != PJ_STUN_MAGIC ||
	ca->hdr.type == PJ_STUN_ATTR_SOFTWARE)
    {
	/* Set the length to be 4-bytes aligned so that we can
	 * communicate with RFC 3489 endpoints
	 */
	PUTVAL16H(buf, 2, (pj_uint16_t)((ca->value.slen + 3) & (~3)));
    } else {
	/* Use RFC 5389 rule */
	PUTVAL16H(buf, 2, (pj_uint16_t)ca->value.slen);
    }

    /* Copy the string */
    pj_memcpy(buf+ATTR_HDR_LEN, ca->value.ptr, ca->value.slen);

    /* Add padding character, if string is not 4-bytes aligned. */
    if (ca->value.slen & 0x03) {
	pj_uint8_t pad[3];
	pj_memset(pad, padding_char, sizeof(pad));
	pj_memcpy(buf+ATTR_HDR_LEN+ca->value.slen, pad,
		  4-(ca->value.slen & 0x03));
    }

    /* Done */
    return PJ_SUCCESS;
}


static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos)
{
    return (pj_uint16_t) ((buf[pos + 0] << 8) | \
			  (buf[pos + 1] << 0));
}


static void* clone_uint_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_uint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_uint_attr));

    return (void*)dst;
}


static pj_status_t decode_empty_attr(pj_pool_t *pool,
				     const pj_uint8_t *buf,
				     const pj_stun_msg_hdr *msghdr,
				     void **p_attr)
{
    pj_stun_empty_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Check that the struct address is valid */
    pj_assert(sizeof(pj_stun_empty_attr) == ATTR_HDR_LEN);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != 0)
	return PJNATH_ESTUNINATTRLEN;

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static void PUTVAL16H(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
{
    buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
    buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
}


PJ_DEF(pj_status_t) pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool,
						  pj_stun_msg *msg,
						  int attr_type,
						  pj_bool_t xor_ed,
						  const pj_sockaddr_t *addr,
						  unsigned addr_len)
{
    pj_stun_sockaddr_attr *attr;
    pj_status_t status;

    status = pj_stun_sockaddr_attr_create(pool, attr_type, xor_ed,
					         addr, addr_len, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(pj_status_t) pj_stun_msg_init( pj_stun_msg *msg,
				      unsigned msg_type,
				      pj_uint32_t magic,
				      const pj_uint8_t tsx_id[12])
{
    PJ_ASSERT_RETURN(msg && msg_type, PJ_EINVAL);

    msg->hdr.type = (pj_uint16_t) msg_type;
    msg->hdr.length = 0;
    msg->hdr.magic = magic;
    msg->attr_count = 0;

    if (tsx_id) {
	pj_memcpy(&msg->hdr.tsx_id, tsx_id, sizeof(msg->hdr.tsx_id));
    } else {
	struct transaction_id
	{
	    pj_uint32_t	    proc_id;
	    pj_uint32_t	    random;
	    pj_uint32_t	    counter;
	} id;
	static pj_uint32_t pj_stun_tsx_id_counter;

	if (!pj_stun_tsx_id_counter)
	    pj_stun_tsx_id_counter = pj_rand();

	id.proc_id = pj_getpid();
	id.random = pj_rand();
	id.counter = pj_stun_tsx_id_counter++;

	pj_memcpy(&msg->hdr.tsx_id, &id, sizeof(msg->hdr.tsx_id));
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len,
				      unsigned options)
{
    pj_uint32_t msg_len;

    PJ_ASSERT_RETURN(pdu, PJ_EINVAL);

    if (pdu_len < sizeof(pj_stun_msg_hdr))
	return PJNATH_EINSTUNMSGLEN;

    /* First byte of STUN message is always 0x00 or 0x01. */
    if (*pdu != 0x00 && *pdu != 0x01)
	return PJNATH_EINSTUNMSGTYPE;

    /* Check the PDU length */
    msg_len = GETVAL16H(pdu, 2);
    if ((msg_len + 20 > pdu_len) ||
	((options & PJ_STUN_IS_DATAGRAM) && msg_len + 20 != pdu_len))
    {
	return PJNATH_EINSTUNMSGLEN;
    }

    /* STUN message is always padded to the nearest 4 bytes, thus
     * the last two bits of the length field are always zero.
     */
    if ((msg_len & 0x03) != 0) {
	return PJNATH_EINSTUNMSGLEN;
    }

    /* If magic is set, then there is great possibility that this is
     * a STUN message.
     */
    if (GETVAL32H(pdu, 4) == PJ_STUN_MAGIC) {

	/* Check if FINGERPRINT attribute is present */
	if ((options & PJ_STUN_NO_FINGERPRINT_CHECK )==0 &&
	    GETVAL16H(pdu, msg_len + 20 - 8) == PJ_STUN_ATTR_FINGERPRINT)
	{
	    pj_uint16_t attr_len = GETVAL16H(pdu, msg_len + 20 - 8 + 2);
	    pj_uint32_t fingerprint = GETVAL32H(pdu, msg_len + 20 - 8 + 4);
	    pj_uint32_t crc;

	    if (attr_len != 4)
		return PJNATH_ESTUNINATTRLEN;

	    crc = pj_crc32_calc(pdu, msg_len + 20 - 8);
	    crc ^= STUN_XOR_FINGERPRINT;

	    if (crc != fingerprint)
		return PJNATH_ESTUNFINGERPRINT;
	}
    }

    /* Could be a STUN message */
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool,
					       pj_stun_msgint_attr **p_attr)
{
    pj_stun_msgint_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 20);

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_unknown_attr(pj_pool_t *pool,
						 pj_stun_msg *msg,
						 unsigned attr_cnt,
						 const pj_uint16_t attr_type[])
{
    pj_stun_unknown_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_unknown_attr_create(pool, attr_cnt, attr_type, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_uint8_t *data,
					       unsigned length,
					       pj_stun_binary_attr **p_attr)
{
    pj_stun_binary_attr *attr;

    PJ_ASSERT_RETURN(pool && attr_type && p_attr, PJ_EINVAL);
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
    *p_attr = attr;
    return pj_stun_binary_attr_init(attr, pool, attr_type, data, length);
}


static void* clone_sockaddr_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_sockaddr_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_sockaddr_attr);
    pj_memcpy(dst, src, sizeof(pj_stun_sockaddr_attr));
    return (void*)dst;
}


PJ_DEF(pj_status_t) pj_stun_binary_attr_init( pj_stun_binary_attr *attr,
					      pj_pool_t *pool,
					      int attr_type,
					      const pj_uint8_t *data,
					      unsigned length)
{
    PJ_ASSERT_RETURN(attr_type, PJ_EINVAL);

    INIT_ATTR(attr, attr_type, length);

    attr->magic = PJ_STUN_MAGIC;

    if (data && length) {
	attr->length = length;
	attr->data = (pj_uint8_t*) pj_pool_alloc(pool, length);
	pj_memcpy(attr->data, data, length);
    } else {
	attr->data = NULL;
	attr->length = 0;
    }

    return PJ_SUCCESS;
}


static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf,
				      unsigned len,
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_binary_attr *ca = (const pj_stun_binary_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    /* Calculated total attr_len (add padding if necessary) */
    *printed = (ca->length + ATTR_HDR_LEN + 3) & (~3);
    if (len < *printed)
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t) ca->length);

    /* Copy the data */
    pj_memcpy(buf+ATTR_HDR_LEN, ca->data, ca->length);

    /* Done */
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool,
						pj_stun_msg *msg,
						int attr_type,
						const pj_uint8_t *data,
						unsigned length)
{
    pj_stun_binary_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_binary_attr_create(pool, attr_type,
					data, length, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool,
						const pj_stun_msg *req_msg,
						unsigned err_code,
						const pj_str_t *err_msg,
						pj_stun_msg **p_response)
{
    unsigned msg_type = req_msg->hdr.type;
    pj_stun_msg *response = NULL;
    pj_status_t status;

    PJ_ASSERT_RETURN(pool && p_response, PJ_EINVAL);

    PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(msg_type),
		     PJNATH_EINSTUNMSGTYPE);

    /* Create response or error response */
    if (err_code)
	msg_type |= PJ_STUN_ERROR_RESPONSE_BIT;
    else
	msg_type |= PJ_STUN_SUCCESS_RESPONSE_BIT;

    status = pj_stun_msg_create(pool, msg_type, req_msg->hdr.magic,
				req_msg->hdr.tsx_id, &response);
    if (status != PJ_SUCCESS) {
	return status;
    }

    /* Add error code attribute */
    if (err_code) {
	status = pj_stun_msg_add_errcode_attr(pool, response,
					      err_code, err_msg);
	if (status != PJ_SUCCESS) {
	    return status;
	}
    }

    *p_response = response;
    return PJ_SUCCESS;
}


static pj_status_t decode_msgint_attr(pj_pool_t *pool,
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr,
				      void **p_attr)
{
    pj_stun_msgint_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != 20)
	return PJNATH_ESTUNINATTRLEN;

    /* Copy hmac */
    pj_memcpy(attr->hmac, buf+4, 20);

    /* Done */
    *p_attr = attr;
    return PJ_SUCCESS;
}


static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf,
				       unsigned len,
				       const pj_stun_msg_hdr *msghdr,
				       unsigned *printed)
{
    const pj_stun_unknown_attr *ca = (const pj_stun_unknown_attr*) a;
    pj_uint16_t *dst_unk_attr;
    unsigned i;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    /* Check that buffer is enough */
    if (len < ATTR_HDR_LEN + (ca->attr_count << 1))
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)(ca->attr_count << 1));

    /* Copy individual attribute */
    dst_unk_attr = (pj_uint16_t*)(buf + ATTR_HDR_LEN);
    for (i=0; i < ca->attr_count; ++i, ++dst_unk_attr) {
	*dst_unk_attr = pj_htons(ca->attrs[i]);
    }

    /* Done */
    *printed = (ATTR_HDR_LEN + (ca->attr_count << 1) + 3) & (~3);

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
					 pj_stun_attr_hdr *attr)
{
    PJ_ASSERT_RETURN(msg && attr, PJ_EINVAL);
    PJ_ASSERT_RETURN(msg->attr_count < PJ_STUN_MAX_ATTR, PJ_ETOOMANY);

    msg->attr[msg->attr_count++] = attr;
    return PJ_SUCCESS;
}


static void* clone_empty_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_empty_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_empty_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_empty_attr));

    return (void*) dst;
}


static void* clone_uint64_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_uint64_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint64_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_uint64_attr));

    return (void*)dst;
}


PJ_DEF(const char*) pj_stun_get_attr_name(unsigned attr_type)
{
    const struct attr_desc *attr_desc;

    attr_desc = find_attr_desc(attr_type);
    if (!attr_desc || attr_desc->name==NULL)
	return "???";

    return attr_desc->name;
}


static pj_status_t decode_unknown_attr(pj_pool_t *pool,
				       const pj_uint8_t *buf,
				       const pj_stun_msg_hdr *msghdr,
				       void **p_attr)
{
    pj_stun_unknown_attr *attr;
    const pj_uint16_t *punk_attr;
    unsigned i;

    PJ_UNUSED_ARG(msghdr);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
    GETATTRHDR(buf, &attr->hdr);

    attr->attr_count = (attr->hdr.length >> 1);
    if (attr->attr_count > PJ_STUN_MAX_ATTR)
	return PJ_ETOOMANY;

    punk_attr = (const pj_uint16_t*)(buf + ATTR_HDR_LEN);
    for (i=0; i<attr->attr_count; ++i) {
	attr->attrs[i] = pj_ntohs(punk_attr[i]);
    }

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static const struct attr_desc *find_attr_desc(unsigned attr_type)
{
    struct attr_desc *desc;

    /* Check that attr_desc array is valid */
    pj_assert(PJ_ARRAY_SIZE(mandatory_attr_desc)==
	      PJ_STUN_ATTR_END_MANDATORY_ATTR+1);
    pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_END_MANDATORY_ATTR].decode_attr
	      == NULL);
    pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_USE_CANDIDATE].decode_attr
	      == &decode_empty_attr);
    pj_assert(PJ_ARRAY_SIZE(extended_attr_desc) ==
	      PJ_STUN_ATTR_END_EXTENDED_ATTR-PJ_STUN_ATTR_START_EXTENDED_ATTR);

    if (attr_type < PJ_STUN_ATTR_END_MANDATORY_ATTR)
	desc = &mandatory_attr_desc[attr_type];
    else if (attr_type >= PJ_STUN_ATTR_START_EXTENDED_ATTR &&
	     attr_type < PJ_STUN_ATTR_END_EXTENDED_ATTR)
	desc = &extended_attr_desc[attr_type-PJ_STUN_ATTR_START_EXTENDED_ATTR];
    else
	return NULL;

    return desc->decode_attr == NULL ? NULL : desc;
}


PJ_DEF(pj_status_t) pj_stun_string_attr_init( pj_stun_string_attr *attr,
					      pj_pool_t *pool,
					      int attr_type,
					      const pj_str_t *value)
{
    if (value && value->slen) {
	INIT_ATTR(attr, attr_type, value->slen);
	attr->value.slen = value->slen;
	pj_strdup(pool, &attr->value, value);
    } else {
	INIT_ATTR(attr, attr_type, 0);
    }
    return PJ_SUCCESS;
}


PJ_DEF(pj_stun_msg*) pj_stun_msg_clone( pj_pool_t *pool,
					const pj_stun_msg *src)
{
    pj_stun_msg *dst;
    unsigned i;

    PJ_ASSERT_RETURN(pool && src, NULL);

    dst = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    pj_memcpy(dst, src, sizeof(pj_stun_msg));

    /* Duplicate the attributes */
    for (i=0, dst->attr_count=0; i<src->attr_count; ++i) {
	dst->attr[dst->attr_count] = pj_stun_attr_clone(pool, src->attr[i]);
	if (dst->attr[dst->attr_count])
	    ++dst->attr_count;
    }

    return dst;
}


PJ_DEF(pj_stun_attr_hdr*) pj_stun_attr_clone( pj_pool_t *pool,
					      const pj_stun_attr_hdr *attr)
{
    const struct attr_desc *adesc;

    /* Get the attribute descriptor */
    adesc = find_attr_desc(attr->type);
    if (adesc) {
	return (pj_stun_attr_hdr*) (*adesc->clone_attr)(pool, attr);
    } else {
	/* Clone generic attribute */
	const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr*)
					       attr;
	PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, NULL);
	if (bin_attr->magic == PJ_STUN_MAGIC) {
	    return (pj_stun_attr_hdr*) clone_binary_attr(pool, attr);
	} else {
	    return NULL;
	}
    }
}


PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code)
{
#if 0
    /* Find error using linear search */
    unsigned i;

    for (i=0; i<PJ_ARRAY_SIZE(stun_err_msg_map); ++i) {
	if (stun_err_msg_map[i].err_code == err_code)
	    return pj_str((char*)stun_err_msg_map[i].err_msg);
    }
    return pj_str(NULL);
#else
    /* Find error message using binary search */
    int first = 0;
    int n = PJ_ARRAY_SIZE(stun_err_msg_map);

    while (n > 0) {
	int half = n/2;
	int mid = first + half;

	if (stun_err_msg_map[mid].err_code < err_code) {
	    first = mid+1;
	    n -= (half+1);
	} else if (stun_err_msg_map[mid].err_code > err_code) {
	    n = half;
	} else {
	    first = mid;
	    break;
	}
    }

    if (stun_err_msg_map[first].err_code == err_code) {
	return pj_str((char*)stun_err_msg_map[first].err_msg);
    } else {
	return pj_str(NULL);
    }
#endif
}


PJ_DEF(const char*) pj_stun_get_class_name(unsigned msg_type)
{
    if (PJ_STUN_IS_REQUEST(msg_type))
	return "request";
    else if (PJ_STUN_IS_SUCCESS_RESPONSE(msg_type))
	return "success response";
    else if (PJ_STUN_IS_ERROR_RESPONSE(msg_type))
	return "error response";
    else if (PJ_STUN_IS_INDICATION(msg_type))
	return "indication";
    else
	return "???";
}


PJ_DEF(pj_status_t) pj_stun_string_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_str_t *value,
					       pj_stun_string_attr **p_attr)
{
    pj_stun_string_attr *attr;

    PJ_ASSERT_RETURN(pool && value && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
    *p_attr = attr;

    return pj_stun_string_attr_init(attr, pool, attr_type, value);
}


PJ_DEF(int) pj_stun_set_padding_char(int chr)
{
    int old_pad = padding_char;
    padding_char = chr;
    return old_pad;
}


static void GETVAL64H(const pj_uint8_t *buf, unsigned pos, pj_timestamp *ts)
{
    ts->u32.hi = GETVAL32H(buf, pos);
    ts->u32.lo = GETVAL32H(buf, pos+4);
}


PJ_DEF(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool,
						unsigned attr_cnt,
						const pj_uint16_t attr_array[],
						pj_stun_unknown_attr **p_attr)
{
    pj_stun_unknown_attr *attr;
    unsigned i;

    PJ_ASSERT_RETURN(pool && attr_cnt < PJ_STUN_MAX_ATTR && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, attr_cnt * 2);

    attr->attr_count = attr_cnt;
    for (i=0; i<attr_cnt; ++i) {
	attr->attrs[i] = attr_array[i];
    }

    /* If the number of unknown attributes is an odd number, one of the
     * attributes MUST be repeated in the list.
     */
    /* No longer necessary
    if ((attr_cnt & 0x01)) {
	attr->attrs[attr_cnt] = attr_array[attr_cnt-1];
    }
    */

    *p_attr = attr;

    return PJ_SUCCESS;
}


static void* clone_string_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_string_attr *asrc = (const pj_stun_string_attr*)src;
    pj_stun_string_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_string_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_attr_hdr));
    pj_strdup(pool, &dst->value, &asrc->value);

    return (void*)dst;
}


PJ_DEF(pj_status_t) pj_stun_sockaddr_attr_init( pj_stun_sockaddr_attr *attr,
						int attr_type,
						pj_bool_t xor_ed,
						const pj_sockaddr_t *addr,
						unsigned addr_len)
{
    unsigned attr_len;

    PJ_ASSERT_RETURN(attr && addr_len && addr, PJ_EINVAL);
    PJ_ASSERT_RETURN(addr_len == sizeof(pj_sockaddr_in) ||
		     addr_len == sizeof(pj_sockaddr_in6), PJ_EINVAL);

    attr_len = pj_sockaddr_get_addr_len(addr) + 4;
    INIT_ATTR(attr, attr_type, attr_len);

    pj_memcpy(&attr->sockaddr, addr, addr_len);
    attr->xor_ed = xor_ed;

    return PJ_SUCCESS;
}


static pj_status_t decode_string_attr(pj_pool_t *pool,
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr,
				      void **p_attr)
{
    pj_stun_string_attr *attr;
    pj_str_t value;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Get pointer to the string in the message */
    value.ptr = ((char*)buf + ATTR_HDR_LEN);
    value.slen = attr->hdr.length;

    /* Copy the string to the attribute */
    pj_strdup(pool, &attr->value, &value);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;

}


PJ_DEF(pj_status_t)  pj_stun_msg_add_uint64_attr(pj_pool_t *pool,
					         pj_stun_msg *msg,
					         int attr_type,
					         const pj_timestamp *value)
{
    pj_stun_uint64_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_uint64_attr_create(pool, attr_type, value, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf,
				      unsigned len,
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_msgint_attr *ca = (const pj_stun_msgint_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);

    if (len < 24)
	return PJ_ETOOSMALL;

    /* Copy and convert attribute to network byte order */
    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, ca->hdr.length);

    pj_memcpy(buf+4, ca->hmac, 20);

    /* Done */
    *printed = 24;

    return PJ_SUCCESS;
}


PJ_DEF(pj_stun_attr_hdr*) pj_stun_msg_find_attr( const pj_stun_msg *msg,
						 int attr_type,
						 unsigned index)
{
    PJ_ASSERT_RETURN(msg, NULL);

    for (; index < msg->attr_count; ++index) {
	if (msg->attr[index]->type == attr_type)
	    return (pj_stun_attr_hdr*) msg->attr[index];
    }

    return NULL;
}


static pj_status_t decode_errcode_attr(pj_pool_t *pool,
				       const pj_uint8_t *buf,
				       const pj_stun_msg_hdr *msghdr,
				       void **p_attr)
{
    pj_stun_errcode_attr *attr;
    pj_str_t value;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
    GETATTRHDR(buf, &attr->hdr);

    attr->err_code = buf[6] * 100 + buf[7];

    /* Get pointer to the string in the message */
    value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
    value.slen = attr->hdr.length - 4;
    /* Make sure the length is never negative */
    if (value.slen < 0)
    	value.slen = 0;

    /* Copy the string to the attribute */
    pj_strdup(pool, &attr->reason, &value);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_string_attr(pj_pool_t *pool,
						pj_stun_msg *msg,
						int attr_type,
						const pj_str_t *value)
{
    pj_stun_string_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_string_attr_create(pool, attr_type, value,
						&attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool,
					      int attr_type,
					      pj_stun_empty_attr **p_attr)
{
    pj_stun_empty_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
    INIT_ATTR(attr, attr_type, 0);

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_uint_attr_create(pj_pool_t *pool,
					     int attr_type,
					     pj_uint32_t value,
					     pj_stun_uint_attr **p_attr)
{
    pj_stun_uint_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
    INIT_ATTR(attr, attr_type, 4);
    attr->value = value;

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool,
					      pj_stun_msg *msg,
					      int attr_type,
					      pj_uint32_t value)
{
    pj_stun_uint_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_uint_attr_create(pool, attr_type, value, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


PJ_DEF(const char*) pj_stun_get_method_name(unsigned msg_type)
{
    unsigned method = PJ_STUN_GET_METHOD(msg_type);

    if (method >= PJ_ARRAY_SIZE(stun_method_names))
	return "???";

    return stun_method_names[method];
}


PJ_INLINE(pj_uint32_t) GETVAL32H(const pj_uint8_t *buf, unsigned pos)
{
    return (pj_uint32_t) ((buf[pos + 0] << 24UL) | \
	                  (buf[pos + 1] << 16UL) | \
	                  (buf[pos + 2] <<  8UL) | \
			  (buf[pos + 3] <<  0UL));
}


static pj_status_t decode_binary_attr(pj_pool_t *pool,
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr,
				      void **p_attr)
{
    pj_stun_binary_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Copy the data to the attribute */
    attr->length = attr->hdr.length;
    attr->data = (pj_uint8_t*) pj_pool_alloc(pool, attr->length);
    pj_memcpy(attr->data, buf+ATTR_HDR_LEN, attr->length);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;

}


status_symbol_sets(stream * s, pcl_state_t * pcs, pcl_data_storage_t storage)
{
    gs_const_string key;
    void *value;
    pl_dict_enum_t denum;
    ushort *idlist;
    int nid;

    if (storage == 0)
        return 0;               /* no "currently selected" symbol set */

    /* Note carefully the meaning of this status inquiry.  First,
     * we return only symbol sets applicable to unbound fonts.  Second,
     * the "storage" value refers to the location of fonts. */

    /* total up built-in symbol sets, downloaded ones */
    nid = pl_dict_length(&pcs->soft_symbol_sets, false) +
        pl_dict_length(&pcs->built_in_symbol_sets, false);
    idlist = (ushort *) gs_alloc_bytes(pcs->memory, nid * sizeof(ushort),
                                       "status_symbol_sets(idlist)");
    if (idlist == NULL)
        return e_Memory;
    nid = 0;

    /* For each symbol set,
     *   for each font in appropriate storage,
     *     if the font supports that symbol set, list the symbol set
     *     and break (because we only need to find one such font). */

    /* NOTE: Temporarily chain soft, built-in symbol sets.  DON'T
     * exit this section without unchaining them. */
    pl_dict_set_parent(&pcs->soft_symbol_sets, &pcs->built_in_symbol_sets);
    pl_dict_enum_begin(&pcs->soft_symbol_sets, &denum);
    while (pl_dict_enum_next(&denum, &key, &value)) {
        pcl_symbol_set_t *ssp = (pcl_symbol_set_t *) value;
        pl_glyph_vocabulary_t gx;

        for (gx = plgv_MSL; gx < plgv_next; gx++)
            if (ssp->maps[gx] != NULL &&
                status_check_symbol_set(pcs, ssp->maps[gx], storage)) {
                nid = status_add_symbol_id(idlist, nid,
                                           (ssp->maps[gx]->id[0] << 8) +
                                           ssp->maps[gx]->id[1]);
                break;          /* one will suffice */
            }
    }
    pl_dict_set_parent(&pcs->soft_symbol_sets, NULL);
    /* Symbol sets are back to normal. */

    status_print_idlist(s, idlist, nid, "IDLIST");
    gs_free_object(pcs->memory, (void *)idlist, "status_symbol_sets(idlist)");
    return 0;
}

Bug 705156(1): Avoid double free of PCL error buffer


status_end_id_list(stream * s)
{                               /* HACK: we know that there's at least one character in the buffer. */
    if (*s->cursor.w.ptr != '\n')
        stputs(s, "\"\r\n");
}


pcl_flush_all_pages(pcl_args_t * pargs, pcl_state_t * pcs)
{
    switch (uint_arg(pargs)) {
        case 0:
            {                   /* Flush all complete pages. */
                /* This is a driver function.... */
                return 0;
            }
        case 1:
            {                   /* Flush all pages, including an incomplete one. */
                int code = pcl_end_page_if_marked(pcs);

                if (code >= 0)
                    code = pcl_home_cursor(pcs);
                return code;
            }
        default:
            return e_Range;
    }
}


pcl_inquire_readback_entity(pcl_args_t * pargs, pcl_state_t * pcs)
{
    uint i = uint_arg(pargs);
    int unit = pcs->location_unit;
    stream st;

    static const char *entity_types[] = {
        "FONTS", "MACROS", "PATTERNS", "SYMBOLSETS", "FONTS EXTENDED"
    };
    pcl_data_storage_t storage;
    int code = 0;
    long pos;

    if (i > 4)
        return e_Range;
    status_begin(&st, pcs);
    stprintf(&st, "INFO %s\r\n", entity_types[i]);
    switch (pcs->location_type) {
        case 0:                /* invalid location */
            code = -1;
            break;
        case 1:                /* currently selected */
            storage = (pcl_data_storage_t) 0;   /* indicates currently selected */
            break;
        case 2:                /* all locations */
            storage = (pcl_data_storage_t) ~ 0;
            break;
        case 3:                /* internal */
            if (unit != 0) {
                code = -1;
                break;
            }
            storage = pcds_internal;
            break;
        case 4:                /* downloaded */
            if (unit > 2)
                code = -1;
            else {
                static const pcl_data_storage_t dl_masks[] =
                    { pcds_downloaded, pcds_temporary, pcds_permanent
                };
                storage = dl_masks[unit];
            }
            break;
        case 5:                /* cartridges */
            if (unit == 0)
                storage = (pcl_data_storage_t) pcds_all_cartridges;
            else if (unit <= pcds_cartridge_max)
                storage = (pcl_data_storage_t)
                    (1 << (pcds_cartridge_shift + unit - 1));
            else
                code = -1;
            break;
        case 6:                /* SIMMs */
            if (unit == 0)
                storage = (pcl_data_storage_t) pcds_all_simms;
            else if (unit <= pcds_simm_max)
                storage =
                    (pcl_data_storage_t) (1 << (pcds_simm_shift + unit - 1));
            else
                code = -1;
            break;
        default:
            code = -1;
            stputs(&st, "ERROR=INVALID ENTITY\r\n");
            break;
    }
    if (code >= 0) {
        pos = stell(&st);
        code = (*status_write[i]) (&st, pcs, storage);
        if (code >= 0) {
            if (stell(&st) == pos)
                stputs(&st, "ERROR=NONE\r\n");
            else if (storage == 0)      /* currently selected */
                stprintf(&st, "LOCTYPE=%d\r\nLOCUNIT=%d\r\n",
                         pcs->location_type, unit);
        }
    }
    if (code < 0) {
        if (code == e_Memory)
            stputs(&st, "ERROR=INTERNAL ERROR\r\n");
        else
            stputs(&st, "ERROR=INVALID LOCATION\r\n");
    }
    status_end(&st, pcs);
    return 0;
}


status_put_font(stream * s, pcl_state_t * pcs,
                uint font_id, uint internal_id,
                pl_font_t * plfont, int font_set, bool extended)
{
    char paren = (font_set > 0 ? ')' : '(');
    bool proportional = plfont->params.proportional_spacing;

    /* first escape sequence: symbol-set selection */
    stputs(s, "SELECT=\"");
    if (pl_font_is_bound(plfont) || font_set > 0) {
        /* Bound or current font, put out the symbol set. */
        uint symbol_set = font_set > 0 ?
            pcs->font_selection[font_set].params.symbol_set :
            plfont->params.symbol_set;
        stprintf(s, "<Esc>%c%u%c", paren, symbol_set >> 5,
                 (symbol_set & 31) + 'A' - 1);
    }

    /* second escape sequence: font selection */
    stprintf(s, "<Esc>%cs%dp", paren, proportional);
    if (plfont->scaling_technology == plfst_bitmap) {   /* Bitmap font */
        status_put_floating(s, pl_fp_pitch_per_inch(&plfont->params));
        stputs(s, "h");
        status_put_floating(s, plfont->params.height_4ths / 4.0);
        stputs(s, "v");
    } else {
        /* Scalable font: output depends on whether selected */
        if (font_set > 0) {
            /* If selected, we have to cheat and reach up for info;
             * plfont is below where the scaled values exist. */
            if (proportional) {
                status_put_floating(s,
                                    pcs->font_selection[font_set].params.
                                    height_4ths / 4.0);
                stputs(s, "h");
            } else {
                status_put_floating(s,
                                    pl_fp_pitch_per_inch(&pcs->
                                                         font_selection
                                                         [font_set].params));
                stputs(s, "v");
            }
        } else {
            stputs(s, proportional ? "__v" : "__h");
        }
    }
    stprintf(s, "%ds%db%uT", plfont->params.style,
             plfont->params.stroke_weight, plfont->params.typeface_family);
    if (plfont->storage & pcds_downloaded)
        stprintf(s, "<Esc>%c%uX", paren, font_id);
    stputs(s, "\"\r\n");
    if (!pl_font_is_bound(plfont) && font_set < 0) {
        int nid;
        ushort *idlist;
        pl_dict_enum_t denum;
        gs_const_string key;
        void *value;

        idlist = (ushort *) gs_alloc_bytes(pcs->memory,
                                           pl_dict_length(&pcs->
                                                          soft_symbol_sets,
                                                          false) +
                                           pl_dict_length(&pcs->
                                                          built_in_symbol_sets,
                                                          false),
                                           "status_fonts(idlist)");
        if (idlist == NULL)
            return e_Memory;
        nid = 0;
        /* Current fonts show the symbol set bound to them, above. */

        /* NOTE: Temporarily chain soft, built-in symbol sets.  DON'T
         * exit this section without unchaining them. */
        pl_dict_set_parent(&pcs->soft_symbol_sets,
                           &pcs->built_in_symbol_sets);
        pl_dict_enum_begin(&pcs->soft_symbol_sets, &denum);
        while (pl_dict_enum_next(&denum, &key, &value)) {
            pcl_symbol_set_t *ssp = (pcl_symbol_set_t *) value;
            pl_glyph_vocabulary_t gx;

            for (gx = plgv_MSL; gx < plgv_next; gx++)
                if (ssp->maps[gx] != NULL &&
                    pcl_check_symbol_support(ssp->maps[gx]->
                                             character_requirements,
                                             plfont->character_complement)) {
                    nid =
                        status_add_symbol_id(idlist, nid,
                                             (ssp->maps[gx]->id[0] << 8) +
                                             ssp->maps[gx]->id[1]);
                    break;      /* one will suffice */
                }
        }
        pl_dict_set_parent(&pcs->soft_symbol_sets, NULL);
        /* Symbol sets are back to normal. */

        gs_free_object(pcs->memory, (void *)idlist, "status_fonts(idlist)");
    }
    if (extended) {             /* Put out the "internal ID number". */
        if (plfont->storage & pcds_temporary)
            stputs(s, "DEFID=NONE\r\n");
        else {
            stputs(s, "DEFID=\"");
            if (plfont->storage & pcds_all_cartridges) {
                int c;
                int n = (plfont->storage & pcds_all_cartridges) >>
                    pcds_cartridge_shift;

                /* pick out the bit index of the cartridge */
                for (c = 0; (n & 1) == 0; c++)
                    n >>= 1;
                stprintf(s, "C%d ", c);
            } else if (plfont->storage & pcds_all_simms) {
                int m;
                int n = (plfont->storage & pcds_all_simms) >> pcds_simm_shift;

                /* pick out the bit index of the SIMM */
                for (m = 0; (n & 1) == 0; m++)
                    n >>= 1;
                stprintf(s, "M%d ", m);
            } else
                /* internal _vs_ permanent soft */
                stputs(s, (plfont->storage & pcds_internal) ? "I " : "S ");
            stprintf(s, "%d\"\r\n", internal_id);
        }

        /* XXX Put out the font name - we need a way to get the name
         * for fonts that weren't downloaded, hence lack the known
         * header field. */
        if ((plfont->storage & pcds_downloaded) && plfont->header != NULL) {
            /* Wire in the size of the FontName field (16)--it can't
             * change anyway, and this saves work. */
            pcl_font_header_t *hdr = (pcl_font_header_t *) (plfont->header);

            stprintf(s, "NAME=\"%.16s\"\r\n", hdr->FontName);
        }
    }
    return 0;
}


status_check_symbol_set(pcl_state_t * pcs, pl_symbol_map_t * mapp,
                        pcl_data_storage_t storage)
{
    gs_const_string key;
    void *value;
    pl_dict_enum_t fenum;

    pl_dict_enum_begin(&pcs->soft_fonts, &fenum);
    while (pl_dict_enum_next(&fenum, &key, &value)) {
        pl_font_t *fp = (pl_font_t *) value;

        if (fp->storage != storage)
            continue;
        if (pcl_check_symbol_support(mapp->character_requirements,
                                     fp->character_complement))
            return true;
    }
    return false;
}


stputs(stream * s, const char *str)
{
    uint ignore_count;

    sputs(s, (const byte *)str, strlen(str), &ignore_count);
}


status_fonts_extended(stream * s, pcl_state_t * pcs,
                      pcl_data_storage_t storage)
{
    return status_do_fonts(s, pcs, storage, true);
}


stprintf(stream * s, const char *fmt, ...)
{
    int count;
    va_list args;
    char buf[1024];

    va_start(args, fmt);
    count = gs_vsprintf(buf, fmt, args);
    if (count >= 0) {
        unsigned count_u = count;
        sputs(s, (const byte *)buf, count_u, &count_u);
    }
    va_end(args);
}


status_do_fonts(stream * s, pcl_state_t * pcs,
                pcl_data_storage_t storage, bool extended)
{
    gs_const_string key;
    void *value;
    pl_dict_enum_t denum;
    int res;

    pl_dict_enum_begin(&pcs->soft_fonts, &denum);
    while (pl_dict_enum_next(&denum, &key, &value)) {
        uint id = (key.data[0] << 8) + key.data[1];

        if ((((pl_font_t *) value)->storage & storage) != 0 ||
            (storage == 0 && pcs->font == (pl_font_t *) value)
            ) {
            res = status_put_font(s, pcs, id, id, (pl_font_t *) value,
                                  (storage != 0 ? -1 : pcs->font_selected),
                                  extended);
            if (res != 0)
                return res;
        }
    }
    return 0;
}


status_begin(stream * s, pcl_state_t * pcs)
{
    byte *buffer = pcs->status.buffer;

    if (pcs->status.read_pos > 0) {
        memmove(buffer, buffer + pcs->status.read_pos,
                pcs->status.write_pos - pcs->status.read_pos);
        pcs->status.write_pos -= pcs->status.read_pos;
        pcs->status.read_pos = 0;
    }
    if (buffer == 0) {
        buffer = gs_alloc_bytes(pcs->memory, STATUS_BUFFER_SIZE,
                                "status buffer");
        pcs->status.buffer = buffer;
    }
    if (buffer == 0)
        swrite_string(s, pcs->status.internal_buffer,
                      sizeof(pcs->status.internal_buffer));
    else
        swrite_string(s, buffer, gs_object_size(pcs->memory, buffer));
    sseek(s, pcs->status.write_pos);
    stputs(s, "PCL\r\n");
}


status_fonts(stream * s, pcl_state_t * pcs, pcl_data_storage_t storage)
{
    return status_do_fonts(s, pcs, storage, false);
}


pcl_set_readback_loc_type(pcl_args_t * pargs, pcl_state_t * pcs)
{
    pcs->location_type = uint_arg(pargs);
    return 0;
}


status_put_floating(stream * s, double v)
{                               /* Figure the format--easier than printing and chipping out the
                                 * chars we need. */
    int vf = (int)(v * 100 + ((v < 0) ? -0.5 : 0.5));

    if (vf / 100 * 100 == vf)
        stprintf(s, "%d", vf / 100);
    else if (vf / 10 * 10 == vf)
        stprintf(s, "%.1f", v);
    else
        stprintf(s, "%.2f", v);
}


pcl_free_space(pcl_args_t * pargs, pcl_state_t * pcs)
{
    stream st;

    status_begin(&st, pcs);
    stprintf(&st, "INFO MEMORY\r\n");
    if (int_arg(pargs) != 1)
        stprintf(&st, "ERROR=INVALID UNIT\r\n");
    else {
        gs_memory_status_t mstat;

        gs_memory_status(pcs->memory, &mstat);
        if (pcs->memory != pcs->memory->non_gc_memory) {
            gs_memory_status_t dstat;

            gs_memory_status(pcs->memory->non_gc_memory, &dstat);
            mstat.allocated += dstat.allocated;
            mstat.used += dstat.used;
        }
        stprintf(&st, "TOTAL=%ld\r\n", mstat.allocated - mstat.used);
        /* We don't currently have an API for determining */
        /* the largest contiguous block. */
            /**** RETURN SOMETHING RANDOM ****/
        stprintf(&st, "LARGEST=%ld\r\n", (mstat.allocated - mstat.used) >> 2);
    }
    status_end(&st, pcs);
    return 0;
}


pcl_status_read(byte * data, uint max_data, pcl_state_t * pcs)
{
    uint count = min(max_data,
                     pcs->status.write_pos - pcs->status.read_pos);

    if (count)
        memcpy(data, pcs->status.buffer + pcs->status.read_pos, count);
    pcs->status.read_pos += count;
    if (pcs->status.read_pos == pcs->status.write_pos) {
        gs_free_object(pcs->memory, pcs->status.buffer, "status buffer");
        pcs->status.buffer = NULL;
        pcs->status.write_pos = pcs->status.read_pos = 0;
    }
    return count;
}


pcl_echo(pcl_args_t * pargs, pcl_state_t * pcs)
{
    stream st;

    status_begin(&st, pcs);
    stprintf(&st, "ECHO %d\r\n", int_arg(pargs));
    status_end(&st, pcs);
    return 0;
}


pcstatus_do_registration(pcl_parser_state_t * pcl_parser_state,
                         gs_memory_t * mem)
{                               /* Register commands */
    DEFINE_CLASS('*') {
    's', 'T',
            PCL_COMMAND("Set Readback Location Type",
                            pcl_set_readback_loc_type,
                            pca_neg_error | pca_big_error)}, {
    's', 'U',
            PCL_COMMAND("Set Readback Location Unit",
                            pcl_set_readback_loc_unit,
                            pca_neg_error | pca_big_error)}, {
    's', 'I',
            PCL_COMMAND("Inquire Readback Entity",
                            pcl_inquire_readback_entity,
                            pca_neg_error | pca_big_error)}, {
    's', 'M',
            PCL_COMMAND("Free Space", pcl_free_space,
                            pca_neg_ok | pca_big_ok)},
        END_CLASS
        DEFINE_CLASS_COMMAND_ARGS('&', 'r', 'F', "Flush All Pages",
                                  pcl_flush_all_pages,
                                  pca_neg_error | pca_big_error)
        DEFINE_CLASS_COMMAND_ARGS('*', 's', 'X', "Echo",
                                  pcl_echo, pca_neg_ok | pca_big_error)
        return 0;
}


status_macros(stream * s, pcl_state_t * pcs, pcl_data_storage_t storage)
{
    gs_const_string key;
    void *value;
    pl_dict_enum_t denum;

    if (storage == 0)
        return 0;               /* no "currently selected" macro */
    pl_dict_enum_begin(&pcs->macros, &denum);
    while (pl_dict_enum_next(&denum, &key, &value))
        if (((pcl_macro_t *) value)->storage & storage) {
            char id_string[6];

            gs_snprintf(id_string, sizeof(id_string), "%u", (key.data[0] << 8) + key.data[1]);
            status_put_id(s, "IDLIST", id_string);
        }
    status_end_id_list(s);
    return 0;
}


status_print_idlist(stream * s, const ushort * idlist, int nid,
                    const char *title)
{
    int i;

    for (i = 0; i < nid; i++) {
        char idstr[6];          /* ddddL and a null */
        int n, l;

        n = idlist[i] >> 6;
        l = (idlist[i] & 077) + 'A' - 1;
        gs_snprintf(idstr, sizeof(idstr), "%d%c", n, l);
        status_put_id(s, title, idstr);
    }
    status_end_id_list(s);
}


status_put_id(stream * s, const char *title, const char *id)
{                               /* HACK: we know that there's at least one character in the buffer. */
    if (*s->cursor.w.ptr == '\n') {     /* We haven't started the list yet. */
        stprintf(s, "%s=\"%s", title, id);
    } else {
        stprintf(s, ",%s", id);
    }
}


status_patterns(stream * s, pcl_state_t * pcs, pcl_data_storage_t storage)
{
    if (storage == 0) {
        int id = pcs->current_pattern_id;
        pcl_pattern_t *pptrn = pcl_pattern_get_pcl_uptrn(pcs, id);

        if ((pptrn != 0) && (pcs->pattern_type == pcl_pattern_user_defined)) {
            char id_string[6];

            gs_snprintf(id_string, sizeof(id_string), "%u", id);
            status_put_id(s, "IDLIST", id_string);
        }
    } else {
        int id;

        for (id = 0; id < (1L << 15) - 1; id++) {
            pcl_pattern_t *pptrn = pcl_pattern_get_pcl_uptrn(pcs, id);

            if (pptrn != 0) {
                char id_string[6];

                gs_snprintf(id_string, sizeof(id_string), "%u", id);
                status_put_id(s, "IDLIST", id_string);
            }
        }
    }
    status_end_id_list(s);
    return 0;
}


status_add_symbol_id(ushort * idlist, int nid, ushort new_id)
{
    int i;
    ushort *idp;
    ushort t1, t2;

    for (i = 0, idp = idlist; i < nid; i++)
        if (new_id <= *idp)
            break;
    if (new_id == *idp)         /* duplicate item */
        return nid;
    /* insert new_id in front of *idp */
    for (t1 = new_id; i < nid; i++) {
        t2 = *idp;
        *idp++ = t1;
        t1 = t2;
    }
    *idp = t1;
    return nid + 1;
}


pcstatus_do_reset(pcl_state_t * pcs, pcl_reset_type_t type)
{
    if (type & (pcl_reset_initial | pcl_reset_printer)) {
        if (type & pcl_reset_initial) {
            pcs->status.buffer = 0;
            pcs->status.write_pos = 0;
            pcs->status.read_pos = 0;
        }
        pcs->location_type = 0;
        pcs->location_unit = 0;
    }

    return 0;
}


pcl_set_readback_loc_unit(pcl_args_t * pargs, pcl_state_t * pcs)
{
    pcs->location_unit = uint_arg(pargs);
    return 0;
}


status_end(stream * s, pcl_state_t * pcs)
{
    if (sendwp(s)) {            /* Overrun.  Scan back to the last EOL that leaves us */
        /* enough room for the error line. */
        static const char *error_line = "ERROR=INTERNAL ERROR\r\n";
        int error_size = strlen(error_line) + 1;
        uint limit = gs_object_size(pcs->memory, pcs->status.buffer);
        uint wpos = stell(s);

        while (limit - wpos < error_size ||
               pcs->status.buffer[wpos - 1] != '\n')
            --wpos;
        s->end_status = 0;      /**** SHOULDN'T BE NECESSARY ****/
        sseek(s, wpos);
        stputs(s, error_line);
    }
    sputc(s, FF);
    pcs->status.write_pos = stell(s);
}


 */
static void php_wddx_pop_element(void *user_data, const XML_Char *name)
{
	st_entry 			*ent1, *ent2;
	wddx_stack 			*stack = (wddx_stack *)user_data;
	HashTable 			*target_hash;
	zend_class_entry 	**pce;
	zval				*obj;
	zval				*tmp;
	TSRMLS_FETCH();

/* OBJECTS_FIXME */
	if (stack->top == 0) {
		return;
	}

	if (!strcmp(name, EL_STRING) || !strcmp(name, EL_NUMBER) ||
		!strcmp(name, EL_BOOLEAN) || !strcmp(name, EL_NULL) ||
	  	!strcmp(name, EL_ARRAY) || !strcmp(name, EL_STRUCT) ||
		!strcmp(name, EL_RECORDSET) || !strcmp(name, EL_BINARY) ||
		!strcmp(name, EL_DATETIME)) {
		wddx_stack_top(stack, (void**)&ent1);

		if (!ent1->data) {
			if (stack->top > 1) {
				stack->top--;
			} else {
				stack->done = 1;
			}
			efree(ent1);
			return;
		}

		if (!strcmp(name, EL_BINARY)) {
			int new_len=0;
			unsigned char *new_str;

			new_str = php_base64_decode(Z_STRVAL_P(ent1->data), Z_STRLEN_P(ent1->data), &new_len);
			STR_FREE(Z_STRVAL_P(ent1->data));
			if (new_str) {
				Z_STRVAL_P(ent1->data) = new_str;
				Z_STRLEN_P(ent1->data) = new_len;
			} else {
				ZVAL_EMPTY_STRING(ent1->data);
			}
		}

		/* Call __wakeup() method on the object. */
		if (Z_TYPE_P(ent1->data) == IS_OBJECT) {
			zval *fname, *retval = NULL;

			MAKE_STD_ZVAL(fname);
			ZVAL_STRING(fname, "__wakeup", 1);

			call_user_function_ex(NULL, &ent1->data, fname, &retval, 0, 0, 0, NULL TSRMLS_CC);

			zval_dtor(fname);
			FREE_ZVAL(fname);
			if (retval) {
				zval_ptr_dtor(&retval);
			}
		}

		if (stack->top > 1) {
			stack->top--;
			wddx_stack_top(stack, (void**)&ent2);

			/* if non-existent field */
			if (ent2->type == ST_FIELD && ent2->data == NULL) {
				zval_ptr_dtor(&ent1->data);
				efree(ent1);
				return;
			}

			if (Z_TYPE_P(ent2->data) == IS_ARRAY || Z_TYPE_P(ent2->data) == IS_OBJECT) {
				target_hash = HASH_OF(ent2->data);

				if (ent1->varname) {
					if (!strcmp(ent1->varname, PHP_CLASS_NAME_VAR) &&
						Z_TYPE_P(ent1->data) == IS_STRING && Z_STRLEN_P(ent1->data) &&
						ent2->type == ST_STRUCT && Z_TYPE_P(ent2->data) == IS_ARRAY) {
						zend_bool incomplete_class = 0;

						zend_str_tolower(Z_STRVAL_P(ent1->data), Z_STRLEN_P(ent1->data));
						if (zend_hash_find(EG(class_table), Z_STRVAL_P(ent1->data),
										   Z_STRLEN_P(ent1->data)+1, (void **) &pce)==FAILURE) {
							incomplete_class = 1;
							pce = &PHP_IC_ENTRY;
						}

						/* Initialize target object */
						MAKE_STD_ZVAL(obj);
						object_init_ex(obj, *pce);

						/* Merge current hashtable with object's default properties */
						zend_hash_merge(Z_OBJPROP_P(obj),
										Z_ARRVAL_P(ent2->data),
										(void (*)(void *)) zval_add_ref,
										(void *) &tmp, sizeof(zval *), 0);

						if (incomplete_class) {
							php_store_class_name(obj, Z_STRVAL_P(ent1->data), Z_STRLEN_P(ent1->data));
						}

						/* Clean up old array entry */
						zval_ptr_dtor(&ent2->data);

						/* Set stack entry to point to the newly created object */
						ent2->data = obj;

						/* Clean up class name var entry */
						zval_ptr_dtor(&ent1->data);
					} else if (Z_TYPE_P(ent2->data) == IS_OBJECT) {
						zend_class_entry *old_scope = EG(scope);

						EG(scope) = Z_OBJCE_P(ent2->data);
						Z_DELREF_P(ent1->data);
						add_property_zval(ent2->data, ent1->varname, ent1->data);
						EG(scope) = old_scope;
					} else {
						zend_symtable_update(target_hash, ent1->varname, strlen(ent1->varname)+1, &ent1->data, sizeof(zval *), NULL);
					}
					efree(ent1->varname);
				} else	{
					zend_hash_next_index_insert(target_hash, &ent1->data, sizeof(zval *), NULL);
				}
			}
			efree(ent1);
		} else {
			stack->done = 1;
		}
	} else if (!strcmp(name, EL_VAR) && stack->varname) {
		efree(stack->varname);
		stack->varname = NULL;
	} else if (!strcmp(name, EL_FIELD)) {
		st_entry *ent;
		wddx_stack_top(stack, (void **)&ent);
		efree(ent);
		stack->top--;
	}

Fix bug #72750: wddx_deserialize null dereference


removeEPollSock(int sock, epolld_t *epd)
{
	DEFiRet;

	DBGPRINTF("imptcp: removing socket %d from epoll[%d] set\n", sock, epollfd);

	if(epoll_ctl(epollfd, EPOLL_CTL_DEL, sock, &(epd->ev)) != 0) {
		char errStr[1024];
		int eno = errno;
		errmsg.LogError(0, RS_RET_EPOLL_CTL_FAILED, "os error (%d) during epoll DEL: %s",
			        eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
		ABORT_FINALIZE(RS_RET_EPOLL_CTL_FAILED);
	}

finalize_it:
	RETiRet;
}

imptcp: fix Segmentation Fault when octet count is to high


sessActivity(ptcpsess_t *pSess, int *continue_polling)
{
	int lenRcv;
	int lenBuf;
	uchar *peerName;
	int lenPeer;
	int remsock = 0; /* init just to keep compiler happy... :-( */
	sbool bEmitOnClose = 0;
	char rcvBuf[128*1024];
	DEFiRet;

	DBGPRINTF("imptcp: new activity on session socket %d\n", pSess->sock);

	while(1) {
		lenBuf = sizeof(rcvBuf);
		lenRcv = recv(pSess->sock, rcvBuf, lenBuf, 0);

		if(lenRcv > 0) {
			/* have data, process it */
			DBGPRINTF("imptcp: data(%d) on socket %d: %s\n", lenBuf, pSess->sock, rcvBuf);
			CHKiRet(DataRcvd(pSess, rcvBuf, lenRcv));
		} else if (lenRcv == 0) {
			/* session was closed, do clean-up */
			if(pSess->pLstn->pSrv->bEmitMsgOnClose) {
				prop.GetString(pSess->peerName, &peerName, &lenPeer),
				remsock = pSess->sock;
				bEmitOnClose = 1;
			}
			*continue_polling = 0;
			CHKiRet(closeSess(pSess)); /* close may emit more messages in strmzip mode! */
			if(bEmitOnClose) {
				errmsg.LogError(0, RS_RET_PEER_CLOSED_CONN, "imptcp session %d closed by "
					  	"remote peer %s.", remsock, peerName);
			}
			break;
		} else {
			if(errno == EAGAIN || errno == EWOULDBLOCK)
				break;
			DBGPRINTF("imptcp: error on session socket %d - closed.\n", pSess->sock);
			*continue_polling = 0;
			closeSess(pSess); /* try clean-up by dropping session */
			break;
		}
	}

finalize_it:
	RETiRet;
}


DataRcvdCompressed(ptcpsess_t *pThis, char *buf, size_t len)
{
	struct syslogTime stTime;
	time_t ttGenTime;
	int zRet;	/* zlib return state */
	unsigned outavail;
	uchar zipBuf[64*1024]; // TODO: alloc on heap, and much larger (512KiB? batch size!)
	DEFiRet;
	// TODO: can we do stats counters? Even if they are not 100% correct under all cases,
	// by simply updating the input and output sizes?
	uint64_t outtotal;

	datetime.getCurrTime(&stTime, &ttGenTime, TIME_IN_LOCALTIME);
	outtotal = 0;

	if(!pThis->bzInitDone) {
		/* allocate deflate state */
		pThis->zstrm.zalloc = Z_NULL;
		pThis->zstrm.zfree = Z_NULL;
		pThis->zstrm.opaque = Z_NULL;
		zRet = inflateInit(&pThis->zstrm);
		if(zRet != Z_OK) {
			DBGPRINTF("imptcp: error %d returned from zlib/inflateInit()\n", zRet);
			ABORT_FINALIZE(RS_RET_ZLIB_ERR);
		}
		pThis->bzInitDone = RSTRUE;
	}

	pThis->zstrm.next_in = (Bytef*) buf;
	pThis->zstrm.avail_in = len;
	/* run inflate() on buffer until everything has been uncompressed */
	do {
		DBGPRINTF("imptcp: in inflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in);
		pThis->zstrm.avail_out = sizeof(zipBuf);
		pThis->zstrm.next_out = zipBuf;
		zRet = inflate(&pThis->zstrm, Z_SYNC_FLUSH);    /* no bad return value */
		//zRet = inflate(&pThis->zstrm, Z_NO_FLUSH);    /* no bad return value */
		DBGPRINTF("after inflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out);
		outavail = sizeof(zipBuf) - pThis->zstrm.avail_out;
		if(outavail != 0) {
			outtotal += outavail;
			pThis->pLstn->rcvdDecompressed += outavail;
			CHKiRet(DataRcvdUncompressed(pThis, (char*)zipBuf, outavail, &stTime, ttGenTime));
		}
	} while (pThis->zstrm.avail_out == 0);

	dbgprintf("end of DataRcvCompress, sizes: in %lld, out %llu\n", (long long) len, (long long unsigned) outtotal);
finalize_it:
	RETiRet;
}


static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal)
{
	instanceConf_t *inst;
	DEFiRet;

	CHKiRet(createInstance(&inst));
	if(pNewVal == NULL || *pNewVal == '\0') {
		errmsg.LogError(0, NO_ERRCODE, "imptcp: port number must be specified, listener ignored");
	}
	if((pNewVal == NULL) || (pNewVal == '\0')) {
		inst->pszBindPort = NULL;
	} else {
		CHKmalloc(inst->pszBindPort = ustrdup(pNewVal));
	}
	if((cs.lstnIP == NULL) || (cs.lstnIP[0] == '\0')) {
		inst->pszBindAddr = NULL;
	} else {
		CHKmalloc(inst->pszBindAddr = ustrdup(cs.lstnIP));
	}
	if((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) {
		inst->pszBindRuleset = NULL;
	} else {
		CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset));
	}
	if((cs.pszInputName == NULL) || (cs.pszInputName[0] == '\0')) {
		inst->pszInputName = NULL;
	} else {
		CHKmalloc(inst->pszInputName = ustrdup(cs.pszInputName));
	}
	inst->pBindRuleset = NULL;
	inst->bSuppOctetFram = cs.bSuppOctetFram;
	inst->bKeepAlive = cs.bKeepAlive;
	inst->iKeepAliveIntvl = cs.iKeepAliveTime;
	inst->iKeepAliveProbes = cs.iKeepAliveProbes;
	inst->iKeepAliveTime = cs.iKeepAliveTime;
	inst->bEmitMsgOnClose = cs.bEmitMsgOnClose;
	inst->iAddtlFrameDelim = cs.iAddtlFrameDelim;

finalize_it:
	free(pNewVal);
	RETiRet;
}


	for(i = 0 ; i < modpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(modpblk.descr[i].name, "threads")) {
			loadModConf->wrkrMax = (int) pvals[i].val.d.n;
		} else if(!strcmp(modpblk.descr[i].name, "processOnPoller")) {
			loadModConf->bProcessOnPoller = (int) pvals[i].val.d.n;
		} else {
			dbgprintf("imptcp: program error, non-handled "
			  "param '%s' in beginCnfLoad\n", modpblk.descr[i].name);
		}
	}


addLstn(ptcpsrv_t *pSrv, int sock, int isIPv6)
{
	DEFiRet;
	ptcplstn_t *pLstn = NULL;
	uchar statname[64];

	CHKmalloc(pLstn = calloc(1, sizeof(ptcplstn_t)));
	pLstn->pSrv = pSrv;
	pLstn->bSuppOctetFram = pSrv->bSuppOctetFram;
	pLstn->bSPFramingFix = pSrv->bSPFramingFix;
	pLstn->sock = sock;
	/* support statistics gathering */
	uchar *inputname;
	if(pSrv->pszInputName == NULL) {
		inputname = (uchar*)"imptcp";
	} else {
		inputname = pSrv->pszInputName;
	}
	CHKiRet(statsobj.Construct(&(pLstn->stats)));
	snprintf((char*)statname, sizeof(statname), "%s(%s/%s/%s)", inputname,
		(pSrv->lstnIP == NULL) ? "*" : (char*)pSrv->lstnIP, pSrv->port,
		isIPv6 ? "IPv6" : "IPv4");
	statname[sizeof(statname)-1] = '\0'; /* just to be on the save side... */
	CHKiRet(statsobj.SetName(pLstn->stats, statname));
	CHKiRet(statsobj.SetOrigin(pLstn->stats, (uchar*)"imptcp"));
	STATSCOUNTER_INIT(pLstn->ctrSubmit, pLstn->mutCtrSubmit);
	CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("submitted"),
		ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(pLstn->ctrSubmit)));
	/* the following counters are not protected by mutexes; we accept
	 * that they may not be 100% correct */
	pLstn->rcvdBytes = 0,
	pLstn->rcvdDecompressed = 0;
	CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("bytes.received"),
		ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(pLstn->rcvdBytes)));
	CHKiRet(statsobj.AddCounter(pLstn->stats, UCHAR_CONSTANT("bytes.decompressed"),
		ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(pLstn->rcvdDecompressed)));
	CHKiRet(statsobj.ConstructFinalize(pLstn->stats));

	CHKiRet(addEPollSock(epolld_lstn, pLstn, sock, &pLstn->epd));

	/* add to start of server's listener list */
	pLstn->prev = NULL;
	pLstn->next = pSrv->pLstn;
	if(pSrv->pLstn != NULL)
		pSrv->pLstn->prev = pLstn;
	pSrv->pLstn = pLstn;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pLstn != NULL) {
			if(pLstn->stats != NULL)
				statsobj.Destruct(&(pLstn->stats));
			free(pLstn);
		}
	}

	RETiRet;
}


std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst)
{
	errmsg.LogError(0, NO_ERRCODE, "imptcp: ruleset '%s' for port %s not found - "
			"using default ruleset instead", inst->pszBindRuleset,
			inst->pszBindPort);
}


processDataRcvd(ptcpsess_t *const __restrict__ pThis,
	char **buff,
	const int buffLen,
	struct syslogTime *stTime,
	const time_t ttGenTime,
	multi_submit_t *pMultiSub,
	unsigned *const __restrict__ pnMsgs)
{
	DEFiRet;
	char c = **buff;
	int octatesToCopy, octatesToDiscard;

	if(pThis->inputState == eAtStrtFram) {
		if(pThis->bSuppOctetFram && isdigit((int) c)) {
			pThis->inputState = eInOctetCnt;
			pThis->iOctetsRemain = 0;
			pThis->eFraming = TCP_FRAMING_OCTET_COUNTING;
		} else if(pThis->bSPFramingFix && c == ' ') {
			/* Cisco very occasionally sends a SP after a LF, which
			 * thrashes framing if not taken special care of. Here,
			 * we permit space *in front of the next frame* and
			 * ignore it.
			 */
			 FINALIZE;
		} else {
			pThis->inputState = eInMsg;
			pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
		}
	}

	if(pThis->inputState == eInOctetCnt) {
		if(isdigit(c)) {
			if(pThis->iOctetsRemain <= 200000000) {
				pThis->iOctetsRemain = pThis->iOctetsRemain * 10 + c - '0';
			} else {
				errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: "
						"frame too large (at least %d%c), change to octet stuffing",
						pThis->iOctetsRemain, c);
				pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
				pThis->inputState = eInMsg;
			}
			*(pThis->pMsg + pThis->iMsg++) = c;
		} else { /* done with the octet count, so this must be the SP terminator */
			DBGPRINTF("TCP Message with octet-counter, size %d.\n", pThis->iOctetsRemain);
			if(c != ' ') {
				errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: "
					    "delimiter is not SP but has ASCII value %d.", c);
			}
			if(pThis->iOctetsRemain < 1) {
				/* TODO: handle the case where the octet count is 0! */
				errmsg.LogError(0, NO_ERRCODE, "Framing Error in received TCP message: "
					    "invalid octet count %d.", pThis->iOctetsRemain);
				pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
			} else if(pThis->iOctetsRemain > iMaxLine) {
				/* while we can not do anything against it, we can at least log an indication
				 * that something went wrong) -- rgerhards, 2008-03-14
				 */
				DBGPRINTF("truncating message with %d octets - max msg size is %d\n",
					  pThis->iOctetsRemain, iMaxLine);
				errmsg.LogError(0, NO_ERRCODE, "received oversize message: size is %d bytes, "
					        "max msg size is %d, truncating...", pThis->iOctetsRemain, iMaxLine);
			}
			pThis->inputState = eInMsg;
			pThis->iMsg = 0;
		}
	} else {
		assert(pThis->inputState == eInMsg);

		if (pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) {
			if(pThis->iMsg >= iMaxLine) {
				/* emergency, we now need to flush, no matter if we are at end of message or not... */
				int i = 1;
				char currBuffChar;
				while(i < buffLen && ((currBuffChar = (*buff)[i]) != '\n'
					&& (pThis->pLstn->pSrv->iAddtlFrameDelim == TCPSRV_NO_ADDTL_DELIMITER
						|| currBuffChar != pThis->pLstn->pSrv->iAddtlFrameDelim))) {
					i++;
				}
				LogError(0, NO_ERRCODE, "error: message received is at least %d byte larger than max msg"
					" size; message will be split starting at: \"%.*s\"\n", i, (i < 32) ? i : 32, *buff);
				doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub);
				++(*pnMsgs);
				/* we might think if it is better to ignore the rest of the
				 * message than to treat it as a new one. Maybe this is a good
				 * candidate for a configuration parameter...
				 * rgerhards, 2006-12-04
				 */
			}

			if ((c == '\n')
				   || ((pThis->pLstn->pSrv->iAddtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER)
					   && (c == pThis->pLstn->pSrv->iAddtlFrameDelim))
				   ) { /* record delimiter? */
				doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub);
				++(*pnMsgs);
				pThis->inputState = eAtStrtFram;
			} else {
				/* IMPORTANT: here we copy the actual frame content to the message - for BOTH framing modes!
				 * If we have a message that is larger than the max msg size, we truncate it. This is the best
				 * we can do in light of what the engine supports. -- rgerhards, 2008-03-14
				 */
				if(pThis->iMsg < iMaxLine) {
					*(pThis->pMsg + pThis->iMsg++) = c;
				}
			}
		} else {
			assert(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING);
			octatesToCopy = pThis->iOctetsRemain;
			octatesToDiscard = 0;
			if (buffLen < octatesToCopy) {
				octatesToCopy = buffLen;
			}
			if (octatesToCopy + pThis->iMsg > iMaxLine) {
				octatesToDiscard = octatesToCopy - (iMaxLine - pThis->iMsg);
				octatesToCopy = iMaxLine - pThis->iMsg;
			}

			memcpy(pThis->pMsg + pThis->iMsg, *buff, octatesToCopy);
			pThis->iMsg += octatesToCopy;
			pThis->iOctetsRemain -= (octatesToCopy + octatesToDiscard);
			*buff += (octatesToCopy + octatesToDiscard - 1);
			if (pThis->iOctetsRemain == 0) {
				/* we have end of frame! */
				doSubmitMsg(pThis, stTime, ttGenTime, pMultiSub);
				++(*pnMsgs);
				pThis->inputState = eAtStrtFram;
			}
		}

	}

finalize_it:
	RETiRet;
}


AcceptConnReq(ptcplstn_t *pLstn, int *newSock, prop_t **peerName, prop_t **peerIP)
{
	int sockflags;
	struct sockaddr_storage addr;
	socklen_t addrlen = sizeof(addr);
	int iNewSock = -1;

	DEFiRet;

	iNewSock = accept(pLstn->sock, (struct sockaddr*) &addr, &addrlen);
	if(iNewSock < 0) {
		if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EMFILE)
			ABORT_FINALIZE(RS_RET_NO_MORE_DATA);
		ABORT_FINALIZE(RS_RET_ACCEPT_ERR);
	}

	if(pLstn->pSrv->bKeepAlive)
		EnableKeepAlive(pLstn, iNewSock);/* we ignore errors, best to do! */

	CHKiRet(getPeerNames(peerName, peerIP, (struct sockaddr *) &addr, pLstn->pSrv->bUnixSocket));

	/* set the new socket to non-blocking IO */
	if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) {
		sockflags |= O_NONBLOCK;
		/* SETFL could fail too, so get it caught by the subsequent
		 * error check.
		 */
		sockflags = fcntl(iNewSock, F_SETFL, sockflags);
	}
	if(sockflags == -1) {
		DBGPRINTF("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock);
		prop.Destruct(peerName);
		prop.Destruct(peerIP);
		ABORT_FINALIZE(RS_RET_IO_ERROR);
	}

	*newSock = iNewSock;

finalize_it:
	if(iRet != RS_RET_OK) {
		/* the close may be redundant, but that doesn't hurt... */
		if(iNewSock != -1)
			close(iNewSock);
	}

	RETiRet;
}


createInstance(instanceConf_t **pinst)
{
	instanceConf_t *inst;
	DEFiRet;
	CHKmalloc(inst = MALLOC(sizeof(instanceConf_t)));
	inst->next = NULL;

	inst->pszBindPort = NULL;
	inst->pszBindAddr = NULL;
	inst->pszBindPath = NULL;
	inst->fileUID = -1;
	inst->fileGID = -1;
	inst->fCreateMode = 0644;
	inst->bFailOnPerms = 1;
	inst->bUnlink = 0;
	inst->pszBindRuleset = NULL;
	inst->pszInputName = NULL;
	inst->bSuppOctetFram = 1;
	inst->bSPFramingFix = 0;
	inst->bKeepAlive = 0;
	inst->iKeepAliveIntvl = 0;
	inst->iKeepAliveProbes = 0;
	inst->iKeepAliveTime = 0;
	inst->bEmitMsgOnClose = 0;
	inst->dfltTZ = NULL;
	inst->iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER;
	inst->pBindRuleset = NULL;
	inst->ratelimitBurst = 10000; /* arbitrary high limit */
	inst->ratelimitInterval = 0; /* off */
	inst->compressionMode = COMPRESS_SINGLE_MSG;

	/* node created, let's add to config */
	if(loadModConf->tail == NULL) {
		loadModConf->tail = loadModConf->root = inst;
	} else {
		loadModConf->tail->next = inst;
		loadModConf->tail = inst;
	}

	*pinst = inst;
finalize_it:
	RETiRet;
}


stopWorkerPool(void)
{
	int i;
	DBGPRINTF("imptcp: stoping worker pool\n");
	pthread_mutex_lock(&io_q.mut);
	pthread_cond_broadcast(&io_q.wakeup_worker); /* awake wrkr if not running */
	pthread_mutex_unlock(&io_q.mut);
	for(i = 0 ; i < runModConf->wrkrMax ; ++i) {
		pthread_join(wrkrInfo[i].tid, NULL);
		DBGPRINTF("imptcp: info: worker %d was called %llu times\n", i, wrkrInfo[i].numCalled);
	}
    free(wrkrInfo);
}


wrkr(void *myself)
{
	struct wrkrInfo_s *me = (struct wrkrInfo_s*) myself;
	pthread_mutex_lock(&io_q.mut);
	++wrkrRunning;
	pthread_mutex_unlock(&io_q.mut);

	io_req_t *n;
	while(1) {
		n = NULL;
		pthread_mutex_lock(&io_q.mut);
		if (io_q.sz == 0) {
			--wrkrRunning;
			if (glbl.GetGlobalInputTermState() != 0) {
				pthread_mutex_unlock(&io_q.mut);
				break;
			} else {
				pthread_cond_wait(&io_q.wakeup_worker, &io_q.mut);
			}
			++wrkrRunning;
		}
		if (io_q.sz > 0) {
			n = STAILQ_FIRST(&io_q.q);
			STAILQ_REMOVE_HEAD(&io_q.q, link);
			io_q.sz--;
		}
		pthread_mutex_unlock(&io_q.mut);

		if (n != NULL) {
			++me->numCalled;
			processWorkItem(n->epd);
			free(n);
		}
	}
	return NULL;
}


	for(inst = pModConf->root ; inst != NULL ; ) {
		free(inst->pszBindPort);
		free(inst->pszBindPath);
		free(inst->pszBindAddr);
		free(inst->pszBindRuleset);
		free(inst->pszInputName);
		free(inst->dfltTZ);
		del = inst;
		inst = inst->next;
		free(del);
	}


	if(pvals == NULL) {
		errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imptcp: error processing module "
				"config parameters [module(...)]");
		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
	}


destructSrv(ptcpsrv_t *pSrv)
{
	if(pSrv->ratelimiter != NULL)
		ratelimitDestruct(pSrv->ratelimiter);
	if(pSrv->pInputName != NULL)
		prop.Destruct(&pSrv->pInputName);
	pthread_mutex_destroy(&pSrv->mutSessLst);
	if(pSrv->pszInputName != NULL)
		free(pSrv->pszInputName);
	if(pSrv->port != NULL)
		free(pSrv->port);
	if(pSrv->path != NULL)
		free(pSrv->path);
	if(pSrv->lstnIP != NULL)
		free(pSrv->lstnIP);
	free(pSrv);
}


initConfigSettings(void)
{
	cs.bEmitMsgOnClose = 0;
	cs.wrkrMax = DFLT_wrkrMax;
	cs.bSuppOctetFram = 1;
	cs.iAddtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER;
	cs.pszInputName = NULL;
	cs.pszBindRuleset = NULL;
	cs.pszInputName = NULL;
	cs.lstnIP = NULL;
}


	if(pSrvRoot == NULL) {
		errmsg.LogError(0, RS_RET_NO_LSTN_DEFINED, "imptcp: no ptcp server defined, module can not run.");
		ABORT_FINALIZE(RS_RET_NO_RUN);
	}


closeSess(ptcpsess_t *pSess)
{
	int sock;
	DEFiRet;

	if(pSess->compressionMode >= COMPRESS_STREAM_ALWAYS)
		doZipFinish(pSess);

	sock = pSess->sock;
	CHKiRet(removeEPollSock(sock, pSess->epd));
	close(sock);

	pthread_mutex_lock(&pSess->pLstn->pSrv->mutSessLst);
	/* finally unlink session from structures */
	if(pSess->next != NULL)
		pSess->next->prev = pSess->prev;
	if(pSess->prev == NULL) {
		/* need to update root! */
		pSess->pLstn->pSrv->pSess = pSess->next;
	} else {
		pSess->prev->next = pSess->next;
	}
	pthread_mutex_unlock(&pSess->pLstn->pSrv->mutSessLst);

	/* unlinked, now remove structure */
	destructSess(pSess);

finalize_it:
	DBGPRINTF("imptcp: session on socket %d closed with iRet %d.\n", sock, iRet);
	RETiRet;
}


addListner(modConfData_t __attribute__((unused)) *modConf, instanceConf_t *inst)
{
	DEFiRet;
	ptcpsrv_t *pSrv = NULL;

	CHKmalloc(pSrv = calloc(1, sizeof(ptcpsrv_t)));
	pthread_mutex_init(&pSrv->mutSessLst, NULL);
	pSrv->pSess = NULL;
	pSrv->pLstn = NULL;
	pSrv->bSuppOctetFram = inst->bSuppOctetFram;
	pSrv->bSPFramingFix = inst->bSPFramingFix;
	pSrv->bKeepAlive = inst->bKeepAlive;
	pSrv->iKeepAliveIntvl = inst->iKeepAliveTime;
	pSrv->iKeepAliveProbes = inst->iKeepAliveProbes;
	pSrv->iKeepAliveTime = inst->iKeepAliveTime;
	pSrv->bEmitMsgOnClose = inst->bEmitMsgOnClose;
	pSrv->compressionMode = inst->compressionMode;
	pSrv->dfltTZ = inst->dfltTZ;
	if (inst->pszBindPort != NULL) {
		CHKmalloc(pSrv->port = ustrdup(inst->pszBindPort));
	}
	pSrv->iAddtlFrameDelim = inst->iAddtlFrameDelim;
	if (inst->pszBindAddr == NULL) {
		pSrv->lstnIP = NULL;
	} else {
		CHKmalloc(pSrv->lstnIP = ustrdup(inst->pszBindAddr));
	}
	if (inst->pszBindPath == NULL) {
		pSrv->path = NULL;
	} else {
		CHKmalloc(pSrv->path = ustrdup(inst->pszBindPath));
		CHKmalloc(pSrv->port = ustrdup(inst->pszBindPath));
		pSrv->bUnixSocket = 1;
		pSrv->fCreateMode = inst->fCreateMode;
		pSrv->fileUID = inst->fileUID;
		pSrv->fileGID = inst->fileGID;
		pSrv->bFailOnPerms = inst->bFailOnPerms;
	}

	pSrv->bUnlink = inst->bUnlink;
	pSrv->pRuleset = inst->pBindRuleset;
	pSrv->pszInputName = ustrdup((inst->pszInputName == NULL) ?  UCHAR_CONSTANT("imptcp") : inst->pszInputName);
	CHKiRet(prop.Construct(&pSrv->pInputName));
	CHKiRet(prop.SetString(pSrv->pInputName, pSrv->pszInputName, ustrlen(pSrv->pszInputName)));
	CHKiRet(prop.ConstructFinalize(pSrv->pInputName));

	CHKiRet(ratelimitNew(&pSrv->ratelimiter, "imptcp", (char*) pSrv->port));
	ratelimitSetLinuxLike(pSrv->ratelimiter, inst->ratelimitInterval, inst->ratelimitBurst);
	ratelimitSetThreadSafe(pSrv->ratelimiter);
	/* add to linked list */
	pSrv->pNext = pSrvRoot;
	pSrvRoot = pSrv;

	/* all config vars are auto-reset -- this also is very useful with the
	 * new config format effort (v6).
	 */
	resetConfigVariables(NULL, NULL);

finalize_it:
	if(iRet != RS_RET_OK) {
		errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet);
		if(pSrv != NULL) {
			destructSrv(pSrv);
		}
	}
	RETiRet;
}


addEPollSock(epolld_type_t typ, void *ptr, int sock, epolld_t **pEpd)
{
	DEFiRet;
	epolld_t *epd = NULL;

	CHKmalloc(epd = calloc(1, sizeof(epolld_t)));
	epd->typ = typ;
	epd->ptr = ptr;
	epd->sock = sock;
	*pEpd = epd;
	epd->ev.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
	epd->ev.data.ptr = (void*) epd;

	if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &(epd->ev)) != 0) {
		char errStr[1024];
		int eno = errno;
		errmsg.LogError(0, RS_RET_EPOLL_CTL_FAILED, "os error (%d) during epoll ADD: %s",
			        eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
		ABORT_FINALIZE(RS_RET_EPOLL_CTL_FAILED);
	}

	DBGPRINTF("imptcp: added socket %d to epoll[%d] set\n", sock, epollfd);

finalize_it:
	if(iRet != RS_RET_OK) {
		if (epd != NULL) {
			errmsg.LogError(0, RS_RET_INTERNAL_ERROR, "error: could not initialize mutex for ptcp "
			"connection for socket: %d", sock);
		}
		free(epd);
	}
	RETiRet;
}


	for(i = 0 ; i < inppblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(inppblk.descr[i].name, "port")) {
			inst->pszBindPort = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "address")) {
			inst->pszBindAddr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "path")) {
			inst->pszBindPath = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "unlink")) {
			inst->bUnlink = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "fileowner")) {
			inst->fileUID = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "fileownernum")) {
			inst->fileUID = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "filegroup")) {
			inst->fileGID = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "filegroupnum")) {
			inst->fileGID = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "filecreatemode")) {
			inst->fCreateMode = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "failonpermsfailure")) {
			inst->bFailOnPerms = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "name")) {
			inst->pszInputName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "ruleset")) {
			inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "supportoctetcountedframing")) {
			inst->bSuppOctetFram = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "framingfix.cisco.asa")) {
			inst->bSPFramingFix = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "compression.mode")) {
			cstr = es_str2cstr(pvals[i].val.d.estr, NULL);
			if(!strcasecmp(cstr, "stream:always")) {
				inst->compressionMode = COMPRESS_STREAM_ALWAYS;
			} else if(!strcasecmp(cstr, "none")) {
				inst->compressionMode = COMPRESS_NEVER;
			} else {
				errmsg.LogError(0, RS_RET_PARAM_ERROR, "omfwd: invalid value for 'compression.mode' "
					 "parameter (given is '%s')", cstr);
				free(cstr);
				ABORT_FINALIZE(RS_RET_PARAM_ERROR);
			}
			free(cstr);
		} else if(!strcmp(inppblk.descr[i].name, "keepalive")) {
			inst->bKeepAlive = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "keepalive.probes")) {
			inst->iKeepAliveProbes = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "keepalive.time")) {
			inst->iKeepAliveTime = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "keepalive.interval")) {
			inst->iKeepAliveIntvl = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "addtlframedelimiter")) {
			inst->iAddtlFrameDelim = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "notifyonconnectionclose")) {
			inst->bEmitMsgOnClose = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "defaulttz")) {
			inst->dfltTZ = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "ratelimit.burst")) {
			inst->ratelimitBurst = (int) pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "ratelimit.interval")) {
			inst->ratelimitInterval = (int) pvals[i].val.d.n;
		} else {
			dbgprintf("imptcp: program error, non-handled "
			  "param '%s'\n", inppblk.descr[i].name);
		}

		char *bindPort = (char *) inst->pszBindPort;
		char *bindPath = (char *) inst->pszBindPath;
		if ((bindPort == NULL || strlen(bindPort) < 1) && (bindPath == NULL || strlen (bindPath) < 1)) {
			errmsg.LogError(0, RS_RET_PARAM_ERROR, "imptcp: Must have either port or path defined");
			ABORT_FINALIZE(RS_RET_PARAM_ERROR);
		}
	}


startupSrv(ptcpsrv_t *pSrv)
{
	DEFiRet;
	int error, maxs, on = 1;
	int sock = -1;
	int numSocks;
	int sockflags;
	struct addrinfo hints, *res = NULL, *r;
	uchar *lstnIP;
	int isIPv6 = 0;

	if (pSrv->bUnixSocket) {
		return startupUXSrv(pSrv);
	}

	lstnIP = pSrv->lstnIP == NULL ? UCHAR_CONSTANT("") : pSrv->lstnIP;

	DBGPRINTF("imptcp: creating listen socket on server '%s', port %s\n", lstnIP, pSrv->port);

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = glbl.GetDefPFFamily();
	hints.ai_socktype = SOCK_STREAM;

	error = getaddrinfo((char*)pSrv->lstnIP, (char*) pSrv->port, &hints, &res);
	if(error) {
		DBGPRINTF("error %d querying server '%s', port '%s'\n", error, pSrv->lstnIP, pSrv->port);
		ABORT_FINALIZE(RS_RET_INVALID_PORT);
	}

	/* Count max number of sockets we may open */
	for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) {
		/* EMPTY */;
	}

	numSocks = 0;   /* num of sockets counter at start of array */
	for(r = res; r != NULL ; r = r->ai_next) {
		sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
		if(sock < 0) {
			if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) {
				DBGPRINTF("error %d creating tcp listen socket", errno);
				/* it is debatable if PF_INET with EAFNOSUPPORT should
				 * also be ignored...
				 */
				continue;
			}
		}

		if(r->ai_family == AF_INET6) {
			isIPv6 = 1;
#ifdef IPV6_V6ONLY
			int iOn = 1;
			if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&iOn, sizeof (iOn)) < 0) {
				close(sock);
				sock = -1;
				continue;
			}
#endif
		} else {
			isIPv6 = 0;
		}

		if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
			DBGPRINTF("error %d setting tcp socket option\n", errno);
			close(sock);
			sock = -1;
			continue;
		}

		/* We use non-blocking IO! */
		if((sockflags = fcntl(sock, F_GETFL)) != -1) {
			sockflags |= O_NONBLOCK;
			/* SETFL could fail too, so get it caught by the subsequent
			 * error check.
			 */
			sockflags = fcntl(sock, F_SETFL, sockflags);
		}
		if(sockflags == -1) {
			DBGPRINTF("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno);
            close(sock);
			sock = -1;
			continue;
		}

		/* We need to enable BSD compatibility. Otherwise an attacker
		 * could flood our log files by sending us tons of ICMP errors.
		 */
#if !defined (_AIX)
#ifndef BSD
		if(net.should_use_so_bsdcompat()) {
			if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT,
					(char *) &on, sizeof(on)) < 0) {
				errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)");
                                close(sock);
				sock = -1;
				continue;
			}
		}
#endif
#endif
		if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0)
#ifndef IPV6_V6ONLY
		     && (errno != EADDRINUSE)
#endif
	    ) {
			/* TODO: check if *we* bound the socket - else we *have* an error! */
			char errStr[1024];
			rs_strerror_r(errno, errStr, sizeof(errStr));
			dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr);
			close(sock);
			sock = -1;
			continue;
		}

		if(listen(sock, 511) < 0) {
			DBGPRINTF("tcp listen error %d, suspending\n", errno);
			close(sock);
			sock = -1;
			continue;
		}

		/* if we reach this point, we were able to obtain a valid socket, so we can
		 * create our listener object. -- rgerhards, 2010-08-10
		 */
		CHKiRet(addLstn(pSrv, sock, isIPv6));
		++numSocks;
	}

	if(numSocks != maxs) {
		DBGPRINTF("We could initialize %d TCP listen sockets out of %d we received "
		 	  "- this may or may not be an error indication.\n", numSocks, maxs);
	}

	if(numSocks == 0) {
		DBGPRINTF("No TCP listen sockets could successfully be initialized");
		ABORT_FINALIZE(RS_RET_COULD_NOT_BIND);
	}

finalize_it:
	if(res != NULL) {
		freeaddrinfo(res);
	}

	if(iRet != RS_RET_OK) {
		if(sock != -1) {
			close(sock);
		}
	}

	RETiRet;
}