/* datamorph.c - enumerated and native integer value support */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2016-2026 The OpenLDAP Foundation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* ACKNOWLEDGEMENTS:
 * This work was developed in 2016 by Ondřej Kuzník for Symas Corp.
 */

#include "portable.h"

#ifdef SLAPD_OVER_DATAMORPH

#include <inttypes.h>
#include <ac/stdlib.h>

#if defined(__linux__)
#include <endian.h>

#elif defined(sun)

#define be16toh(x) BE_16(x)
#define le16toh(x) LE_16(x)
#define htobe16(x) BE_16(x)
#define htole16(x) LE_16(x)

#define be32toh(x) BE_32(x)
#define le32toh(x) LE_32(x)
#define htobe32(x) BE_32(x)
#define htole32(x) LE_32(x)

#define be64toh(x) BE_64(x)
#define le64toh(x) LE_64(x)
#define htobe64(x) BE_64(x)
#define htole64(x) LE_64(x)

#elif defined(__NetBSD__) || defined(__FreeBSD__)
#include <sys/endian.h>

#elif defined(__OpenBSD__)
#include <sys/endian.h>

#define be16toh(x) betoh16(x)
#define le16toh(x) letoh16(x)

#define be32toh(x) betoh32(x)
#define le32toh(x) letoh32(x)

#define be64toh(x) betoh64(x)
#define le64toh(x) letoh64(x)

#elif defined(__BYTE_ORDER__) && \
		( defined(__GNUC__) || defined(__clang__) )

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define be16toh(x) __builtin_bswap16(x)
#define le16toh(x) (x)
#define htobe16(x) __builtin_bswap16(x)
#define htole16(x) (x)

#define be32toh(x) __builtin_bswap32(x)
#define le32toh(x) (x)
#define htobe32(x) __builtin_bswap32(x)
#define htole32(x) (x)

#define be64toh(x) __builtin_bswap64(x)
#define le64toh(x) (x)
#define htobe64(x) __builtin_bswap64(x)
#define htole64(x) (x)

#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define be16toh(x) (x)
#define le16toh(x) __builtin_bswap16(x)
#define htobe16(x) (x)
#define htole16(x) __builtin_bswap16(x)

#define be32toh(x) (x)
#define le32toh(x) __builtin_bswap32(x)
#define htobe32(x) (x)
#define htole32(x) __builtin_bswap32(x)

#define be64toh(x) (x)
#define le64toh(x) __builtin_bswap64(x)
#define htobe64(x) (x)
#define htole64(x) __builtin_bswap64(x)

#else
#error "Only support pure big and little endian at the moment"
#endif

#else
#error "I lack the way to check my endianness and convert to/from big-endian"
#endif

#include "slap.h"
#include "slap-config.h"
#include "lutil.h"
#include "ldap_queue.h"

typedef enum datamorph_type_t {
	DATAMORPH_UNSET,
	DATAMORPH_ENUM,
	DATAMORPH_INT,
} datamorph_type;

typedef enum datamorph_flags_t {
	DATAMORPH_FLAG_SIGNED = 1 << 0,
	DATAMORPH_FLAG_LOWER = 1 << 1,
	DATAMORPH_FLAG_UPPER = 1 << 2,
} datamorph_flags;

typedef union datamorph_interval_bound_t {
	int64_t i;
	uint64_t u;
} datamorph_interval_bound;

typedef struct transformation_info_t {
	AttributeDescription *attr;
	datamorph_type type;
	union {
		struct {
			Avlnode *to_db;
			struct berval from_db[256];
		} maps;
#define ti_enum info.maps
		struct {
			datamorph_flags flags;
			unsigned int size;
			datamorph_interval_bound lower, upper;
		} interval;
#define ti_int info.interval
	} info;
} transformation_info;

typedef struct datamorph_enum_mapping_t {
	struct berval wire_value;
	uint8_t db_value;
	transformation_info *transformation;
} datamorph_enum_mapping;

typedef struct datamorph_info_t {
	Avlnode *transformations;
	transformation_info *wip_transformation;
} datamorph_info;

static int
transformation_mapping_cmp( const void *l, const void *r )
{
	const datamorph_enum_mapping *left = l, *right = r;

	return ber_bvcmp( &left->wire_value, &right->wire_value );
}

static int
transformation_info_cmp( const void *l, const void *r )
{
	const transformation_info *left = l, *right = r;

	return ( left->attr == right->attr ) ? 0 :
			( left->attr < right->attr ) ? -1 :
											1;
}

static int
transform_to_db_format_one(
		Operation *op,
		transformation_info *definition,
		struct berval *value,
		struct berval *outval )
{
	switch ( definition->type ) {
		case DATAMORPH_ENUM: {
			datamorph_enum_mapping *mapping, needle = { .wire_value = *value };
			struct berval db_value = { .bv_len = 1 };

			mapping = ldap_avl_find( definition->ti_enum.to_db, &needle,
					transformation_mapping_cmp );
			if ( !mapping ) {
				Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
						"value '%s' not mapped\n",
						value->bv_val );
				return LDAP_CONSTRAINT_VIOLATION;
			}

			db_value.bv_val = (char *)&mapping->db_value;
			ber_dupbv( outval, &db_value );
			assert( outval->bv_val );
			break;
		}

		case DATAMORPH_INT: {
			union {
				char s[8];
				uint8_t be8;
				uint16_t be16;
				uint32_t be32;
				uint64_t be64;
			} buf;
			struct berval db_value = { .bv_val = buf.s };
			char *ptr = value->bv_val + value->bv_len;
			uint64_t unsigned_value;
			int64_t signed_value;

			assert( definition->ti_int.size == 1 ||
					definition->ti_int.size == 2 ||
					definition->ti_int.size == 4 ||
					definition->ti_int.size == 8 );

			/* Read number */
			if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				signed_value = strtoll( value->bv_val, &ptr, 10 );
			} else {
				unsigned_value = strtoull( value->bv_val, &ptr, 10 );
			}
			if ( *value->bv_val == '\0' || *ptr != '\0' ) {
				Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
						"value '%s' not an integer\n",
						value->bv_val );
				return LDAP_CONSTRAINT_VIOLATION;
			}
			/* Check it's within configured bounds */
			if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				if ( signed_value < definition->ti_int.lower.i ||
						signed_value > definition->ti_int.upper.i ) {
					Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
							"value '%s' doesn't fit configured constraints\n",
							value->bv_val );
					return LDAP_CONSTRAINT_VIOLATION;
				}
			} else {
				if ( unsigned_value < definition->ti_int.lower.u ||
						unsigned_value > definition->ti_int.upper.u ) {
					Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
							"value '%s' doesn't fit configured constraints\n",
							value->bv_val );
					return LDAP_CONSTRAINT_VIOLATION;
				}
			}

			db_value.bv_len = definition->ti_int.size;
			switch ( definition->ti_int.size ) {
				case 1: {
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						buf.be8 = (unsigned char)((char)signed_value);
					} else {
						buf.be8 = unsigned_value;
					}
					break;
				}
				case 2: {
					uint16_t h16;
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						h16 = signed_value;
					} else {
						h16 = unsigned_value;
					}
					buf.be16 = htobe16( h16 );
					break;
				}
				case 4: {
					uint32_t h32;
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						h32 = signed_value;
					} else {
						h32 = unsigned_value;
					}
					buf.be32 = htobe32( h32 );
					break;
				}
				case 8: {
					uint64_t h64;
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						h64 = signed_value;
					} else {
						h64 = unsigned_value;
					}
					buf.be64 = htobe64( h64 );
					break;
				}
			}
			ber_dupbv( outval, &db_value );
			assert( outval->bv_val );
			break;
		}

		default:
			assert(0);
	}

	return LDAP_SUCCESS;
}

static int
transform_to_db_format(
		Operation *op,
		transformation_info *definition,
		BerVarray values,
		int numvals,
		BerVarray *out )
{
	struct berval *value;
	int i, rc = LDAP_SUCCESS;

	if ( numvals == 0 ) {
		for ( value = values; value; value++, numvals++ )
			; /* Count them */
	}

	assert( out );
	*out = ch_calloc( numvals + 1, sizeof(struct berval) );

	for ( i = 0; i < numvals; i++ ) {
		rc = transform_to_db_format_one(
				op, definition, &values[i], &(*out)[i] );
		if ( rc ) {
			break;
		}
	}

	if ( rc ) {
		for ( ; i >= 0; i-- ) {
			ch_free((*out)[i].bv_val);
		}
		ch_free(*out);
	}

	return rc;
}

static int
transform_from_db_format_one(
		Operation *op,
		transformation_info *definition,
		struct berval *value,
		struct berval *outval )
{
	switch ( definition->type ) {
		case DATAMORPH_ENUM: {
			uint8_t index = value->bv_val[0];
			struct berval *val = &definition->info.maps.from_db[index];

			if ( !BER_BVISNULL( val ) ) {
				ber_dupbv( outval, val );
				assert( outval->bv_val );
			} else {
				Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one: "
						"DB value %d has no mapping!\n",
						index );
				/* FIXME: probably still need to return an error */
				BER_BVZERO( outval );
			}
			break;
		}

		case DATAMORPH_INT: {
			char buf[24];
			struct berval wire_value = { .bv_val = buf };
			union lens_t {
				uint8_t be8;
				uint16_t be16;
				uint32_t be32;
				uint64_t be64;
			} *lens = (union lens_t *)value->bv_val;
			uint64_t unsigned_value;
			int64_t signed_value;

			if ( value->bv_len != definition->ti_int.size ) {
				Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one(%s): "
						"unexpected DB value of length %lu when configured "
						"for %u!\n",
						definition->attr->ad_cname.bv_val, value->bv_len,
						definition->ti_int.size );
				/* FIXME: probably still need to return an error */
				BER_BVZERO( outval );
				break;
			}

			assert( definition->ti_int.size == 1 ||
					definition->ti_int.size == 2 ||
					definition->ti_int.size == 4 ||
					definition->ti_int.size == 8 );

			switch ( definition->ti_int.size ) {
				case 1: {
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						signed_value = (int8_t)lens->be8;
					} else {
						unsigned_value = (uint8_t)lens->be8;
					}
					break;
				}
				case 2: {
					uint16_t h16 = be16toh( lens->be16 );
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						signed_value = (int16_t)h16;
					} else {
						unsigned_value = (uint16_t)h16;
					}
					break;
				}
				case 4: {
					uint32_t h32 = be32toh( lens->be32 );
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						signed_value = (int32_t)h32;
					} else {
						unsigned_value = (uint32_t)h32;
					}
					break;
				}
				case 8: {
					uint64_t h64 = be64toh( lens->be64 );
					if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
						signed_value = (int64_t)h64;
					} else {
						unsigned_value = (uint64_t)h64;
					}
					break;
				}
			}
			if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				wire_value.bv_len = sprintf( buf, "%" PRId64, signed_value );
			} else {
				wire_value.bv_len = sprintf( buf, "%" PRIu64, unsigned_value );
			}
			ber_dupbv( outval, &wire_value );
			assert( outval->bv_val );
			break;
		}

		default:
			assert(0);
	}

	return LDAP_SUCCESS;
}

static int
transform_from_db_format(
		Operation *op,
		transformation_info *definition,
		BerVarray values,
		int numvals,
		BerVarray *out )
{
	struct berval *value;
	int i, rc = LDAP_SUCCESS;

	if ( numvals == 0 ) {
		for ( value = values; value; value++, numvals++ )
			; /* Count them */
	}

	assert( out );
	*out = ch_calloc( numvals + 1, sizeof(struct berval) );

	for ( i = 0; i < numvals; i++ ) {
		struct berval bv;
		rc = transform_from_db_format_one( op, definition, &values[i], &bv );
		if ( !BER_BVISNULL( &bv ) ) {
			ber_bvarray_add( out, &bv );
		}
		if ( rc ) {
			break;
		}
	}

	if ( rc ) {
		for ( ; i >= 0; i-- ) {
			ch_free( (*out)[i].bv_val );
		}
		ch_free( *out );
	}

	return rc;
}

static int
datamorph_filter( Operation *op, datamorph_info *ov, Filter *f )
{
	switch ( f->f_choice ) {
		case LDAP_FILTER_PRESENT:
		/* The matching rules are not in place,
		 * so the filter will be ignored */
		case LDAP_FILTER_APPROX:
		case LDAP_FILTER_SUBSTRINGS:
		default:
			break;
			return LDAP_SUCCESS;

		case LDAP_FILTER_AND:
		case LDAP_FILTER_OR: {
			for ( f = f->f_and; f; f = f->f_next ) {
				int rc = datamorph_filter( op, ov, f );
				if ( rc != LDAP_SUCCESS ) {
					return rc;
				}
			}
		} break;

		case LDAP_FILTER_NOT:
			return datamorph_filter( op, ov, f->f_not );

		case LDAP_FILTER_EQUALITY:
		case LDAP_FILTER_GE:
		case LDAP_FILTER_LE: {
			transformation_info *t, needle = { .attr = f->f_ava->aa_desc };

			t = ldap_avl_find(
					ov->transformations, &needle, transformation_info_cmp );
			if ( t ) {
				struct berval new_val;
				int rc = transform_to_db_format_one(
						op, t, &f->f_ava->aa_value, &new_val );
				ch_free( f->f_ava->aa_value.bv_val );

				if ( rc != LDAP_SUCCESS ) {
					f->f_choice = SLAPD_FILTER_COMPUTED;
					f->f_result = SLAPD_COMPARE_UNDEFINED;
				} else {
					f->f_ava->aa_value = new_val;
				}
			}
		} break;
	}
	return LDAP_SUCCESS;
}

static int
datamorph_op_add( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	Entry *e = op->ora_e;
	Attribute *a, *next;
	AttributeDescription *stop = NULL;
	int rc = LDAP_SUCCESS;

	if ( !BER_BVISNULL( &e->e_nname ) && !BER_BVISEMPTY( &e->e_nname ) ) {
		LDAPRDN rDN;
		const char *p;
		int i;

		rc = ldap_bv2rdn_x( &e->e_nname, &rDN, (char **)&p, LDAP_DN_FORMAT_LDAP,
				op->o_tmpmemctx );
		if ( rc != LDAP_SUCCESS ) {
			Debug( LDAP_DEBUG_ANY, "datamorph_op_add: "
					"can't parse rdn: dn=%s\n",
					op->o_req_ndn.bv_val );
			return SLAP_CB_CONTINUE;
		}

		for ( i = 0; rDN[i]; i++ ) {
			transformation_info needle = {};

			/* If we can't resolve the attribute, ignore it */
			if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) {
				continue;
			}

			if ( ldap_avl_find( ov->transformations, &needle,
						 transformation_info_cmp ) ) {
				rc = LDAP_CONSTRAINT_VIOLATION;
				Debug( LDAP_DEBUG_TRACE, "datamorph_op_add: "
						"attempted to add transformed attribute in RDN\n" );
				break;
			}
		}

		ldap_rdnfree_x( rDN, op->o_tmpmemctx );
		if ( rc != LDAP_SUCCESS ) {
			send_ldap_error( op, rs, rc,
					"datamorph: trying to add transformed attribute in RDN" );
			return rc;
		}
	}

	for ( a = e->e_attrs; a && a->a_desc != stop; a = next ) {
		transformation_info *t, needle = { .attr = a->a_desc };
		BerVarray new_vals;

		next = a->a_next;

		t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
		if ( !t ) continue;

		rc = transform_to_db_format(
				op, t, a->a_vals, a->a_numvals, &new_vals );
		if ( rc != LDAP_SUCCESS ) {
			goto fail;
		}

		(void)attr_delete( &e->e_attrs, needle.attr );

		rc = attr_merge( e, needle.attr, new_vals, NULL );
		ber_bvarray_free( new_vals );
		if ( rc != LDAP_SUCCESS ) {
			goto fail;
		}
		if ( !stop ) {
			stop = needle.attr;
		}
	}

	return SLAP_CB_CONTINUE;

fail:
	send_ldap_error(
			op, rs, rc, "datamorph: trying to add values outside definitions" );
	return rc;
}

static int
datamorph_op_compare( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	transformation_info *t, needle = { .attr = op->orc_ava->aa_desc };
	int rc = SLAP_CB_CONTINUE;

	t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
	if ( t ) {
		struct berval new_val;

		rc = transform_to_db_format_one(
				op, t, &op->orc_ava->aa_value, &new_val );
		if ( rc != LDAP_SUCCESS ) {
			Debug( LDAP_DEBUG_TRACE, "datamorph_op_compare: "
					"transformation failed for '%s', rc=%d\n",
					op->orc_ava->aa_value.bv_val, rc );
			rs->sr_err = rc = LDAP_COMPARE_FALSE;
			send_ldap_result( op, rs );
			return rc;
		}
		ch_free( op->orc_ava->aa_value.bv_val );
		op->orc_ava->aa_value = new_val;
	}

	return SLAP_CB_CONTINUE;
}

static int
datamorph_op_mod( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	Modifications *mod;
	int rc = SLAP_CB_CONTINUE;

	for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
		transformation_info *t, needle = { .attr = mod->sml_desc };
		BerVarray new_vals = NULL;

		if ( mod->sml_numvals == 0 ) continue; /* Nothing to transform */

		t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
		if ( !t ) continue;

		assert( !mod->sml_nvalues );
		rc = transform_to_db_format(
				op, t, mod->sml_values, mod->sml_numvals, &new_vals );
		if ( rc != LDAP_SUCCESS ) {
			goto fail;
		}
		ber_bvarray_free( mod->sml_values );
		mod->sml_values = new_vals;
	}

	return SLAP_CB_CONTINUE;

fail:
	Debug( LDAP_DEBUG_TRACE, "datamorph_op_mod: "
			"dn=%s failed rc=%d\n",
			op->o_req_ndn.bv_val, rc );
	send_ldap_error( op, rs, rc,
			"datamorph: trying to operate on values outside definitions" );
	return rc;
}

static int
datamorph_op_modrdn( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	LDAPRDN rDN;
	const char *p;
	int i, rc;

	rc = ldap_bv2rdn_x( &op->orr_nnewrdn, &rDN, (char **)&p,
			LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
	if ( rc != LDAP_SUCCESS ) {
		Debug( LDAP_DEBUG_ANY, "datamorph_op_modrdn: "
				"can't parse rdn for dn=%s\n",
				op->o_req_ndn.bv_val );
		return SLAP_CB_CONTINUE;
	}

	for ( i = 0; rDN[i]; i++ ) {
		transformation_info needle = {};

		/* If we can't resolve the attribute, ignore it */
		if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) {
			continue;
		}

		if ( ldap_avl_find(
					 ov->transformations, &needle, transformation_info_cmp ) ) {
			rc = LDAP_CONSTRAINT_VIOLATION;
			Debug( LDAP_DEBUG_TRACE, "datamorph_op_modrdn: "
					"attempted to add transformed values in RDN\n" );
			break;
		}
	}

	ldap_rdnfree_x( rDN, op->o_tmpmemctx );
	if ( rc != LDAP_SUCCESS ) {
		send_ldap_error( op, rs, rc,
				"datamorph: trying to put transformed values in RDN" );
		return rc;
	}

	return SLAP_CB_CONTINUE;
}

static int
datamorph_response( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	Entry *e = NULL, *e_orig = rs->sr_entry;
	AttributeDescription *stop = NULL;
	Attribute *a, *next = NULL;
	int rc = SLAP_CB_CONTINUE;

	if ( rs->sr_type != REP_SEARCH ) {
		return rc;
	}

	for ( a = e_orig->e_attrs; a && a->a_desc != stop; a = next ) {
		transformation_info *t, needle = { .attr = a->a_desc };
		BerVarray new_vals;

		next = a->a_next;

		t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
		if ( !t ) continue;

		rc = transform_from_db_format(
				op, t, a->a_vals, a->a_numvals, &new_vals );
		if ( rc != LDAP_SUCCESS ) {
			break;
		}
		if ( !e ) {
			if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) {
				e = e_orig;
			} else {
				e = entry_dup( e_orig );
			}
		}

		(void)attr_delete( &e->e_attrs, needle.attr );

		rc = attr_merge( e, needle.attr, new_vals, NULL );
		ber_bvarray_free( new_vals );
		if ( rc != LDAP_SUCCESS ) {
			break;
		}
		if ( !stop ) {
			stop = needle.attr;
		}
	}

	if ( rc == LDAP_SUCCESS ) {
		rc = SLAP_CB_CONTINUE;
		if ( e && e != e_orig ) {
			rs_replace_entry( op, rs, on, e );
			rs->sr_flags &= ~REP_ENTRY_MASK;
			rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
		}
	} else if ( e && e != e_orig ) {
		entry_free( e );
	}

	return rc;
}

static int
datamorph_op_search( Operation *op, SlapReply *rs )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	int rc = SLAP_CB_CONTINUE;

	/*
	 * 1. check all requested attributes -> register callback if one matches
	 * 2. check filter: parse filter, traverse, for configured attributes:
	 *    - presence -> do not touch
	 *    - ava -> replace assertion value with db value if possible, assertion with undefined otherwise 
	 *    - inequality -> ???
	 *    - anything else -> undefined
	 *    - might just check for equality and leave the rest to syntax?
	 * 3. unparse filter
	 */
	if ( datamorph_filter( op, ov, op->ors_filter ) ) {
		send_ldap_error(
				op, rs, LDAP_OTHER, "datamorph: failed to process filter" );
		return LDAP_OTHER;
	}

	return rc;
}

static int
datamorph_entry_release_rw( Operation *op, Entry *e, int rw )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	int rc = LDAP_SUCCESS;

	if ( on->on_next ) {
		rc = overlay_entry_release_ov( op, e, rw, on->on_next );
	} else if ( on->on_info->oi_orig->bi_entry_release_rw ) {
		/* FIXME: there should be a better way */
		rc = on->on_info->oi_orig->bi_entry_release_rw( op, e, rw );
	} else {
		entry_free( e );
	}

	return rc;
}

static int
datamorph_entry_get_rw(
		Operation *op,
		struct berval *ndn,
		ObjectClass *oc,
		AttributeDescription *at,
		int rw,
		Entry **ep )
{
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;
	Entry *e_orig, *e = NULL;
	int rc;

	if ( on->on_next ) {
		rc = overlay_entry_get_ov( op, ndn, oc, at, rw, ep, on->on_next );
	} else {
		/* FIXME: there should be a better way */
		rc = on->on_info->oi_orig->bi_entry_get_rw( op, ndn, oc, at, rw, ep );
	}
	e_orig = *ep;

	if ( rc == LDAP_SUCCESS && e_orig ) {
		AttributeDescription *stop = NULL;
		Attribute *a;

		for ( a = e_orig->e_attrs; a; a = a->a_next ) {
			transformation_info *t, needle = { .attr = a->a_desc };
			BerVarray new_vals;

			t = ldap_avl_find(
					ov->transformations, &needle, transformation_info_cmp );
			if ( !t ) continue;

			rc = transform_from_db_format(
					op, t, a->a_vals, a->a_numvals, &new_vals );
			if ( rc != LDAP_SUCCESS ) {
				goto fail;
			}
			if ( !e ) {
				e = entry_dup( e_orig );
			}

			(void)attr_delete( &e->e_attrs, needle.attr );

			rc = attr_merge( e, needle.attr, new_vals, NULL );
			ber_bvarray_free( new_vals );
			if ( rc != LDAP_SUCCESS ) {
				goto fail;
			}
			if ( !stop ) {
				stop = needle.attr;
			}
		}
	}
	if ( e ) {
		datamorph_entry_release_rw( op, e_orig, rw );
		*ep = e;
	}

	return rc;

fail:
	if ( e ) {
		entry_free( e );
	}
	(void)datamorph_entry_release_rw( op, *ep, rw );
	return rc;
}

/* Schema */

static int
datamorphBlobValidate( Syntax *syntax, struct berval *in )
{
	/* any value allowed */
	return LDAP_SUCCESS;
}

int
datamorphBinarySignedOrderingMatch( int *matchp,
		slap_mask_t flags,
		Syntax *syntax,
		MatchingRule *mr,
		struct berval *value,
		void *assertedValue )
{
	struct berval *asserted = assertedValue;
	ber_len_t v_len = value->bv_len;
	ber_len_t av_len = asserted->bv_len;

	/* Ordering:
	 * 1. Negative always before non-negative
	 * 2. Shorter before longer
	 * 3. Rest ordered by memory contents (they are big-endian numbers)
	 */
	int match = ( *value->bv_val >= 0 ) - ( *asserted->bv_val >= 0 );

	if ( match == 0 ) match = (int)v_len - (int)av_len;

	if ( match == 0 ) match = memcmp( value->bv_val, asserted->bv_val, v_len );

	/* If used in extensible match filter, match if value < asserted */
	if ( flags & SLAP_MR_EXT ) match = ( match >= 0 );

	*matchp = match;
	return LDAP_SUCCESS;
}

/* Index generation function: Ordered index */
int
datamorphUnsignedIndexer( slap_mask_t use,
		slap_mask_t flags,
		Syntax *syntax,
		MatchingRule *mr,
		struct berval *prefix,
		BerVarray values,
		BerVarray *keysp,
		void *ctx )
{
	int i;
	BerVarray keys;

	for ( i = 0; values[i].bv_val != NULL; i++ ) {
		/* just count them */
	}

	/* we should have at least one value at this point */
	assert( i > 0 );

	keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx );

	for ( i = 0; values[i].bv_val != NULL; i++ ) {
		ber_dupbv_x( &keys[i], &values[i], ctx );
	}

	BER_BVZERO( &keys[i] );

	*keysp = keys;

	return LDAP_SUCCESS;
}

/* Index generation function: Ordered index */
int
datamorphUnsignedFilter(
		slap_mask_t use,
		slap_mask_t flags,
		Syntax *syntax,
		MatchingRule *mr,
		struct berval *prefix,
		void *assertedValue,
		BerVarray *keysp,
		void *ctx )
{
	BerVarray keys;
	BerValue *value = assertedValue;

	keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx );
	ber_dupbv_x( &keys[0], value, ctx );

	BER_BVZERO( &keys[1] );

	*keysp = keys;

	return LDAP_SUCCESS;
}

/* Index generation function: Ordered index */
int
datamorphSignedIndexer(
		slap_mask_t use,
		slap_mask_t flags,
		Syntax *syntax,
		MatchingRule *mr,
		struct berval *prefix,
		BerVarray values,
		BerVarray *keysp,
		void *ctx )
{
	int i;
	BerVarray keys;

	for ( i = 0; values[i].bv_val != NULL; i++ ) {
		/* just count them */
	}

	/* we should have at least one value at this point */
	assert( i > 0 );

	keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx );

	for ( i = 0; values[i].bv_val != NULL; i++ ) {
		keys[i].bv_len = values[i].bv_len + 1;
		keys[i].bv_val = slap_sl_malloc( keys[i].bv_len, ctx );

		/* if positive (highest bit is not set), note that in the first byte */
		*keys[i].bv_val = ~( *values[i].bv_val & 0x80 );
		AC_MEMCPY( keys[i].bv_val + 1, values[i].bv_val, values[i].bv_len );
	}

	BER_BVZERO( &keys[i] );

	*keysp = keys;

	return LDAP_SUCCESS;
}

/* Index generation function: Ordered index */
int
datamorphSignedFilter(
		slap_mask_t use,
		slap_mask_t flags,
		Syntax *syntax,
		MatchingRule *mr,
		struct berval *prefix,
		void *assertedValue,
		BerVarray *keysp,
		void *ctx )
{
	BerVarray keys;
	BerValue *value = assertedValue;

	keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx );

	keys[0].bv_len = value->bv_len + 1;
	keys[0].bv_val = slap_sl_malloc( keys[0].bv_len, ctx );

	/* if positive (highest bit is not set), note that in the first byte */
	*keys[0].bv_val = ~( *value->bv_val & 0x80 );
	AC_MEMCPY( keys[0].bv_val + 1, value->bv_val, value->bv_len );

	BER_BVZERO( &keys[1] );

	*keysp = keys;

	return LDAP_SUCCESS;
}

#define DATAMORPH_ARC "1.3.6.1.4.1.4203.666.11.12"

#define DATAMORPH_SYNTAXES DATAMORPH_ARC ".1"
#define DATAMORPH_SYNTAX_BASE DATAMORPH_SYNTAXES ".1"
#define DATAMORPH_SYNTAX_ENUM DATAMORPH_SYNTAXES ".2"
#define DATAMORPH_SYNTAX_INT DATAMORPH_SYNTAXES ".3"
#define DATAMORPH_SYNTAX_SIGNED_INT DATAMORPH_SYNTAXES ".4"

#define DATAMORPH_MATCHES DATAMORPH_ARC ".2"
#define DATAMORPH_MATCH_EQUALITY DATAMORPH_MATCHES ".1"
#define DATAMORPH_MATCH_SIGNED_EQUALITY DATAMORPH_MATCHES ".2"
#define DATAMORPH_MATCH_ORDERING DATAMORPH_MATCHES ".3"
#define DATAMORPH_MATCH_SIGNED_ORDERING DATAMORPH_MATCHES ".4"

static char *datamorph_sups[] = {
	DATAMORPH_SYNTAX_BASE,
	NULL
};

static char *datamorphSyntaxes[] = {
	DATAMORPH_SYNTAX_SIGNED_INT,
	DATAMORPH_SYNTAX_ENUM,
	DATAMORPH_SYNTAX_INT,

	NULL
};

static slap_syntax_defs_rec datamorph_syntax_defs[] = {
	{ "( " DATAMORPH_SYNTAX_BASE " DESC 'Fixed size value' )",
		0, NULL, NULL, NULL
	},
	{ "( " DATAMORPH_SYNTAX_ENUM " DESC 'Enumerated value' )",
		0, datamorph_sups, datamorphBlobValidate, NULL
	},
	{ "( " DATAMORPH_SYNTAX_INT " DESC 'Fixed-size integer' )",
		0, datamorph_sups, datamorphBlobValidate, NULL
	},
	{ "( " DATAMORPH_SYNTAX_SIGNED_INT " DESC 'Fixed-size signed integer' )",
		0, datamorph_sups, datamorphBlobValidate, NULL
	},

	{ NULL, 0, NULL, NULL, NULL }
};

static Syntax *datamorph_base_syntax;

static slap_mrule_defs_rec datamorph_mrule_defs[] = {
	{ "( " DATAMORPH_MATCH_EQUALITY
		" NAME 'fixedSizeIntegerMatch'"
		" SYNTAX " DATAMORPH_SYNTAX_INT " )",
		SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
		datamorphSyntaxes + 1,
		NULL, NULL, octetStringOrderingMatch,
		datamorphUnsignedIndexer, datamorphUnsignedFilter,
		NULL
	},

	{ "( " DATAMORPH_MATCH_SIGNED_EQUALITY
		" NAME 'fixedSizeSignedIntegerMatch'"
		" SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )",
		SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
		NULL,
		NULL, NULL, datamorphBinarySignedOrderingMatch,
		datamorphSignedIndexer, datamorphSignedFilter,
		NULL
	},

	{ "( " DATAMORPH_MATCH_ORDERING
		" NAME 'fixedSizeIntegerOrderingMatch'"
		" SYNTAX " DATAMORPH_SYNTAX_INT " )",
		SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
		datamorphSyntaxes + 1,
		NULL, NULL, octetStringOrderingMatch,
		datamorphUnsignedIndexer, datamorphUnsignedFilter,
		"octetStringMatch" },

	{ "( " DATAMORPH_MATCH_SIGNED_ORDERING
		" NAME 'fixedSizeSignedIntegerOrderingMatch'"
		" SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )",
		SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
		NULL,
		NULL, NULL, datamorphBinarySignedOrderingMatch,
		datamorphSignedIndexer, datamorphSignedFilter,
		"octetStringMatch" },

	{ NULL, SLAP_MR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

/* Configuration */

static ConfigLDAPadd datamorph_ldadd_enum;
static ConfigLDAPadd datamorph_ldadd_interval;
static ConfigLDAPadd datamorph_ldadd_mapping;

static ConfigDriver datamorph_set_attribute;
static ConfigDriver datamorph_set_size;
static ConfigDriver datamorph_set_signed;
static ConfigDriver datamorph_set_bounds;
static ConfigDriver datamorph_set_index;
static ConfigDriver datamorph_set_value;
static ConfigDriver datamorph_add_mapping;
static ConfigDriver datamorph_add_transformation;

static ConfigCfAdd datamorph_cfadd;

enum {
	DATAMORPH_INT_SIZE = 1,
	DATAMORPH_INT_SIGNED,
	DATAMORPH_INT_LOWER,
	DATAMORPH_INT_UPPER,

	DATAMORPH_INT_LAST,
};

static ConfigTable datamorph_cfg[] = {
	{ "datamorph_attribute", "attr", 2, 2, 0,
		ARG_STRING|ARG_QUOTE|ARG_MAGIC,
		datamorph_set_attribute,
		"( OLcfgCtAt:8.1 NAME 'olcDatamorphAttribute' "
			"DESC 'Attribute to transform' "
			"EQUALITY caseIgnoreMatch "
			"SYNTAX OMsDirectoryString "
			"SINGLE-VALUE )",
		NULL, NULL
	},
	{ "datamorph_size", "<1|2|4|8>", 2, 2, 0,
		ARG_INT|ARG_MAGIC|DATAMORPH_INT_SIZE,
		datamorph_set_size,
		"( OLcfgCtAt:8.2 NAME 'olcDatamorphIntegerBytes' "
			"DESC 'Integer size in bytes' "
			"EQUALITY integerMatch "
			"SYNTAX OMsInteger "
			"SINGLE-VALUE )",
		NULL, NULL
	},
	{ "datamorph_signed", "TRUE|FALSE", 2, 2, 0,
		ARG_ON_OFF|ARG_MAGIC|DATAMORPH_INT_SIGNED,
		datamorph_set_signed,
		"( OLcfgCtAt:8.3 NAME 'olcDatamorphIntegerSigned' "
			"DESC 'Whether integers maintain sign' "
			"EQUALITY booleanMatch "
			"SYNTAX OMsBoolean "
				"SINGLE-VALUE )",
			NULL, NULL
	},
	{ "datamorph_lower_bound", "int", 2, 2, 0,
		ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_LOWER,
		datamorph_set_bounds,
		"( OLcfgCtAt:8.4 NAME 'olcDatamorphIntegerLowerBound' "
			"DESC 'Lowest valid value for the attribute' "
			"EQUALITY integerMatch "
			"SYNTAX OMsInteger "
			"SINGLE-VALUE )",
		NULL, NULL
	},
	{ "datamorph_upper_bound", "int", 2, 2, 0,
		ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_UPPER,
		datamorph_set_bounds,
		"( OLcfgCtAt:8.5 NAME 'olcDatamorphIntegerUpperBound' "
			"DESC 'Highest valid value for the attribute' "
			"EQUALITY integerMatch "
			"SYNTAX OMsInteger "
			"SINGLE-VALUE )",
		NULL, NULL
	},

	/* These have no equivalent in slapd.conf */
	{ "", NULL, 2, 2, 0,
		ARG_INT|ARG_MAGIC,
		datamorph_set_index,
		"( OLcfgCtAt:8.6 NAME 'olcDatamorphIndex' "
			"DESC 'Internal DB value' "
			"EQUALITY integerMatch "
			"SYNTAX OMsInteger "
			"SINGLE-VALUE )",
		NULL, NULL
	},
	{ "", NULL, 2, 2, 0,
		ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
		datamorph_set_value,
		"( OLcfgCtAt:8.7 NAME 'olcDatamorphValue' "
			"DESC 'Wire value' "
			"EQUALITY caseExactMatch "
			"SYNTAX OMsDirectoryString "
			"SINGLE-VALUE )",
		NULL, NULL
	},

	/* slapd.conf alternative for the two above */
	{ "datamorph_value", "int> <name", 3, 3, 0,
		ARG_QUOTE|ARG_MAGIC,
		datamorph_add_mapping,
		NULL, NULL, NULL
	},

	/* slapd.conf alternative for objectclasses below */
	{ "datamorph", "enum|int> <attr", 3, 3, 0,
		ARG_QUOTE|ARG_MAGIC,
		datamorph_add_transformation,
		NULL, NULL, NULL
	},

	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};

static ConfigOCs datamorph_ocs[] = {
	{ "( OLcfgCtOc:8.1 "
		"NAME 'olcDatamorphConfig' "
		"DESC 'Datamorph overlay configuration' "
		"SUP olcOverlayConfig )",
		Cft_Overlay, datamorph_cfg, NULL, datamorph_cfadd },
	{ "( OLcfgCtOc:8.2 "
		"NAME 'olcTransformation' "
		"DESC 'Transformation configuration' "
		"MUST ( olcDatamorphAttribute ) "
		"SUP top "
		"ABSTRACT )",
		Cft_Misc, datamorph_cfg, NULL },
	{ "( OLcfgCtOc:8.3 "
		"NAME 'olcDatamorphEnum' "
		"DESC 'Configuration for an enumerated attribute' "
		"SUP olcTransformation "
		"STRUCTURAL )",
		Cft_Misc, datamorph_cfg, datamorph_ldadd_enum },
	{ "( OLcfgCtOc:8.4 "
		"NAME 'olcDatamorphInteger' "
		"DESC 'Configuration for a compact integer attribute' "
		"MUST ( olcDatamorphIntegerBytes ) "
		"MAY ( olcDatamorphIntegerLowerBound $ "
			"olcDatamorphIntegerUpperBound $ "
			"olcDatamorphIntegerSigned "
		") "
		"SUP olcTransformation "
		"STRUCTURAL )",
		Cft_Misc, datamorph_cfg, datamorph_ldadd_interval },
	{ "( OLcfgCtOc:8.5 "
		"NAME 'olcDatamorphEnumValue' "
		"DESC 'Configuration for an enumerated attribute' "
		"MUST ( olcDatamorphIndex $ "
			"olcDatamorphValue "
		") "
		"STRUCTURAL )",
		Cft_Misc, datamorph_cfg, datamorph_ldadd_mapping },

	{ NULL, 0, NULL }
};

static void
datamorph_mapping_free( void *arg )
{
	datamorph_enum_mapping *mapping = arg;

	ch_free( mapping->wire_value.bv_val );
	ch_free( mapping );
}

static void
datamorph_info_free( void *arg )
{
	transformation_info *info = arg;

	if ( info->type == DATAMORPH_ENUM ) {
		ldap_avl_free( info->ti_enum.to_db, datamorph_mapping_free );
	}
	ch_free( info );
}

static int
datamorph_set_attribute( ConfigArgs *ca )
{
	transformation_info needle = {}, *info = ca->ca_private;
	slap_overinst *on = (slap_overinst *)ca->bi;
	datamorph_info *ov = on->on_bi.bi_private;
	char *s = ca->value_string;
	const char *text;
	int rc = LDAP_SUCCESS;

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		ca->value_string = info->attr->ad_cname.bv_val;
		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		info = ldap_avl_delete( &ov->transformations, info,
				transformation_info_cmp );
		assert( info );

		info->attr = NULL;
		return LDAP_SUCCESS;
	}

	if ( *s == '{' ) {
		s = strchr( s, '}' );
		if ( !s ) {
			rc = LDAP_UNDEFINED_TYPE;
			goto done;
		}
		s += 1;
	}

	rc = slap_str2ad( s, &info->attr, &text );
	ch_free( ca->value_string );
	if ( rc ) {
		goto done;
	}

	/* The type has to be set appropriately */
	if ( !info->attr->ad_type->sat_syntax->ssyn_sups ||
			info->attr->ad_type->sat_syntax->ssyn_sups[0] !=
					datamorph_base_syntax ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg),
				"improper syntax for attribute %s",
				info->attr->ad_cname.bv_val );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		rc = LDAP_CONSTRAINT_VIOLATION;
		goto done;
	}

	needle.attr = info->attr;
	if ( ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ) ) {
		rc = LDAP_CONSTRAINT_VIOLATION;
		goto done;
	}

done:
	return rc;
}

static int
datamorph_set_size( ConfigArgs *ca )
{
	transformation_info *info = ca->ca_private;

	if ( !info ) {
		slap_overinst *on = (slap_overinst *)ca->bi;
		datamorph_info *ov = on->on_bi.bi_private;
		info = ov->wip_transformation;
		assert( ca->op == SLAP_CONFIG_ADD );
	}

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		ca->value_int = info->ti_int.size;
		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		info->ti_int.size = 0;
		return LDAP_SUCCESS;
	}

	if ( ca->value_int != 1 &&
			ca->value_int != 2 &&
			ca->value_int != 4 &&
			ca->value_int != 8 ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid size %d",
				ca->value_int );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		return LDAP_CONSTRAINT_VIOLATION;
	}
	info->ti_int.size = ca->value_int;

	return LDAP_SUCCESS;
}

static int
datamorph_set_signed( ConfigArgs *ca )
{
	transformation_info *info = ca->ca_private;

	if ( !info ) {
		slap_overinst *on = (slap_overinst *)ca->bi;
		datamorph_info *ov = on->on_bi.bi_private;
		info = ov->wip_transformation;
		assert( ca->op == SLAP_CONFIG_ADD );
	}

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		ca->value_int = info->ti_int.flags & DATAMORPH_FLAG_SIGNED;
		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED;
		return LDAP_SUCCESS;
	}

	info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED;
	if ( ca->value_int ) {
		info->ti_int.flags |= DATAMORPH_FLAG_SIGNED;
	}

	return LDAP_SUCCESS;
}

static int
datamorph_set_bounds( ConfigArgs *ca )
{
	transformation_info *info = ca->ca_private;
	datamorph_interval_bound *bound;
	uint64_t unsigned_bound;
	int64_t signed_bound;
	char *ptr = ca->value_bv.bv_val + ca->value_bv.bv_len;
	int flag;

	if ( !info ) {
		slap_overinst *on = (slap_overinst *)ca->bi;
		datamorph_info *ov = on->on_bi.bi_private;
		info = ov->wip_transformation;
		assert( ca->op == SLAP_CONFIG_ADD );
	}

	switch ( ca->type ) {
		case DATAMORPH_INT_LOWER:
			bound = &info->ti_int.lower;
			flag = DATAMORPH_FLAG_LOWER;
			break;
		case DATAMORPH_INT_UPPER:
			bound = &info->ti_int.upper;
			flag = DATAMORPH_FLAG_UPPER;
			break;
		default:
			assert(0);
	}

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		char buf[24];
		struct berval bv = { .bv_val = buf };

		if ( !(info->ti_int.flags & flag) ) {
			/* Bound not set, do not emit */
			return LDAP_SUCCESS;
		}
		if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
			bv.bv_len = sprintf( buf, "%" PRId64, bound->i );
		} else {
			bv.bv_len = sprintf( buf, "%" PRIu64, bound->u );
		}
		ber_dupbv_x( &ca->value_bv, &bv, ca->ca_op->o_tmpmemctx );

		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		info->ti_int.flags &= ~flag;
		if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
			bound->i = (flag == DATAMORPH_FLAG_LOWER) ? INT64_MIN : INT64_MAX;
		} else {
			bound->u = (flag == DATAMORPH_FLAG_LOWER) ? 0 : UINT64_MAX;
		}
		return LDAP_SUCCESS;
	}

	/* FIXME: if attributes in the Add operation come in the wrong order
	 * (signed=true after the bound definition), we can't check the interval
	 * sanity. */
	/*
	if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
		signed_bound = strtoll( ca->value_bv.bv_val, &ptr, 10 );
	} else {
		unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 );
	}
	*/
	/* Also, no idea what happens in the case of big-endian, hopefully,
	 * it behaves the same */
	unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 );
	signed_bound = (int64_t)unsigned_bound;

	if ( *ca->value_bv.bv_val == '\0' || *ptr != '\0' ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg),
				"failed to parse '%s' as integer",
				ca->value_bv.bv_val );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		return LDAP_CONSTRAINT_VIOLATION;
	}
	ch_free( ca->value_bv.bv_val );

	info->ti_int.flags |= flag;
	switch ( info->ti_int.size ) {
		case 1:
			if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				/* See FIXME above
				if ( signed_bound < INT8_MIN || signed_bound > INT8_MAX ) {
					goto fail;
				}
				*/
			} else {
				/* See FIXME above
				if ( unsigned_bound > UINT8_MAX ) {
					goto fail;
				}
				*/
			}
			break;
		case 2:
			if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				/* See FIXME above
				if ( signed_bound < INT16_MIN || signed_bound > INT16_MAX ) {
					goto fail;
				}
				*/
			} else {
				/* See FIXME above
				if ( unsigned_bound > UINT16_MAX ) {
					goto fail;
				}
				*/
			}
			break;
		case 4:
			if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
				/* See FIXME above
				if ( signed_bound < INT32_MIN || signed_bound > INT32_MAX ) {
					goto fail;
				}
				*/
			} else {
				/* See FIXME above
				if ( unsigned_bound > UINT32_MAX ) {
					goto fail;
				}
				*/
			}
			break;
		case 8:
			break;
		default:
			/* Should only happen in these two cases:
			 * 1. datamorph_size not yet encountered for this one (when
			 *    processing slapd.conf)
			 * 2. When someone runs a fun modification on the config entry
			 *    messing with more attributes at once
			 *
			 * The error message is expected to be helpful only for the former,
			 * so use the slapd.conf name.
			 */
			snprintf( ca->cr_msg, sizeof(ca->cr_msg),
					"datamorph_size has to be set first!" );
			Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
			return LDAP_CONSTRAINT_VIOLATION;
	}
	if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
		bound->i = signed_bound;
	} else {
		bound->u = unsigned_bound;
	}

	return LDAP_SUCCESS;
}

static int
datamorph_set_value( ConfigArgs *ca )
{
	datamorph_enum_mapping *mapping = ca->ca_private;
	char *s = ca->value_bv.bv_val;

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		/* We generate the value as part of the RDN, don't add anything */
		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		ch_free( mapping->wire_value.bv_val );
		BER_BVZERO( &mapping->wire_value );
		/* TODO: remove from info->ti_enum.to_db? */
		return LDAP_SUCCESS;
	}

	/* As long as this attribute can be in the RDN,
	 * we have to expect the '{n}' prefix */
	if ( *s == '{' ) {
		ber_len_t len;
		s = memchr( s, '}', ca->value_bv.bv_len );
		if ( !s ) {
			return LDAP_UNDEFINED_TYPE;
		}
		s += 1;

		len = ca->value_bv.bv_len - ( s - ca->value_bv.bv_val );
		ber_str2bv( s, len, 1, &mapping->wire_value );
		ch_free( ca->value_bv.bv_val );
	} else {
		mapping->wire_value = ca->value_bv;
	}

	return LDAP_SUCCESS;
}

static int
datamorph_set_index( ConfigArgs *ca )
{
	datamorph_enum_mapping *mapping = ca->ca_private;
	struct berval *from_db = mapping->transformation->ti_enum.from_db;

	if ( ca->op == SLAP_CONFIG_EMIT ) {
		ca->value_int = mapping->db_value;
		return LDAP_SUCCESS;
	} else if ( ca->op == LDAP_MOD_DELETE ) {
		BER_BVZERO( &from_db[mapping->db_value] );
		return LDAP_SUCCESS;
	}

	if ( ca->value_int < 0 || ca->value_int >= 256 ) {
		return LDAP_CONSTRAINT_VIOLATION;
	} else if ( !BER_BVISNULL( &from_db[ca->value_int] ) ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg), "duplicate index %d",
				ca->value_int );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		return LDAP_CONSTRAINT_VIOLATION;
	}
	mapping->db_value = ca->value_int;
	from_db[ca->value_int] = mapping->wire_value;

	return LDAP_SUCCESS;
}

/* Called when processing slapd.conf only,
 * cn=config uses the objectclass to decide which type we're dealing with.
 */
static int
datamorph_add_transformation( ConfigArgs *ca )
{
	slap_overinst *on = (slap_overinst *)ca->bi;
	datamorph_info *ov = on->on_bi.bi_private;
	transformation_info *info;

	if ( ov->wip_transformation ) {
		/* We checked everything as were processing the lines */
		int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation,
				transformation_info_cmp, ldap_avl_dup_error );
		assert( rc == LDAP_SUCCESS );
	}

	info = ch_calloc( 1, sizeof(transformation_info) );
	ov->wip_transformation = ca->ca_private = info;

	if ( !strcasecmp( ca->argv[1], "enum" ) ) {
		info->type = DATAMORPH_ENUM;
	} else if ( !strcasecmp( ca->argv[1], "int" ) ) {
		info->type = DATAMORPH_INT;
	} else {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg),
				"unknown transformation type '%s'", ca->argv[1] );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		return LDAP_CONSTRAINT_VIOLATION;
	}

	ca->value_string = strdup( ca->argv[2] );

	return datamorph_set_attribute( ca );
}

static int
datamorph_add_mapping( ConfigArgs *ca )
{
	slap_overinst *on = (slap_overinst *)ca->bi;
	datamorph_info *ov = on->on_bi.bi_private;
	transformation_info *info = ov->wip_transformation;
	datamorph_enum_mapping *mapping;
	int rc = LDAP_CONSTRAINT_VIOLATION;

	if ( !info ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg), "no attribute configured" );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		goto done;
	}

	mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) );
	mapping->transformation = info;
	ca->ca_private = mapping;

	ber_str2bv( ca->argv[2], 0, 1, &ca->value_bv );
	rc = datamorph_set_value( ca );
	if ( rc != LDAP_SUCCESS ) {
		goto done;
	}

	rc = lutil_atoix( &ca->value_int, ca->argv[1], 0 );
	if ( rc != LDAP_SUCCESS ) {
		snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid integer %s",
				ca->argv[1] );
		Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
		goto done;
	}

	rc = datamorph_set_index( ca );
	if ( rc != LDAP_SUCCESS ) {
		goto done;
	}

done:
	if ( rc == LDAP_SUCCESS ) {
		rc = ldap_avl_insert( &info->ti_enum.to_db, mapping,
				transformation_mapping_cmp, ldap_avl_dup_error );
	}

	return rc;
}

static int
datamorph_ldadd_info_cleanup( ConfigArgs *ca )
{
	slap_overinst *on = (slap_overinst *)ca->bi;
	datamorph_info *ov = on->on_bi.bi_private;
	transformation_info *info = ca->ca_private;

	if ( ca->reply.err != LDAP_SUCCESS ) {
		ch_free( info );
		return LDAP_SUCCESS;
	}

	if ( ldap_avl_insert( &ov->transformations, info, transformation_info_cmp,
			ldap_avl_dup_error ) ) {
		return LDAP_CONSTRAINT_VIOLATION;
	}
	return LDAP_SUCCESS;
}

static int
datamorph_ldadd_transformation(
		CfEntryInfo *cei,
		Entry *e,
		ConfigArgs *ca,
		datamorph_type type )
{
	transformation_info *info;

	if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
			cei->ce_bi->bi_cf_ocs != datamorph_ocs )
		return LDAP_CONSTRAINT_VIOLATION;

	info = ch_calloc( 1, sizeof(transformation_info) );
	info->type = type;

	ca->bi = cei->ce_bi;
	ca->ca_private = info;
	config_push_cleanup( ca, datamorph_ldadd_info_cleanup );
	/* config_push_cleanup is only run in the case of online config but we use it to
	 * enable the new config when done with the entry */
	ca->lineno = 0;

	return LDAP_SUCCESS;
}

static int
datamorph_ldadd_enum( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
	return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_ENUM );
}

static int
datamorph_ldadd_interval( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
	return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_INT );
}

static int
datamorph_ldadd_mapping_cleanup( ConfigArgs *ca )
{
	datamorph_enum_mapping *mapping = ca->ca_private;
	transformation_info *info = mapping->transformation;

	if ( ca->reply.err != LDAP_SUCCESS ) {
		if ( mapping ) {
			datamorph_mapping_free( mapping );
		}
		return LDAP_SUCCESS;
	}

	if ( ldap_avl_insert( &info->ti_enum.to_db, mapping, transformation_mapping_cmp,
			ldap_avl_dup_error ) ) {
		return LDAP_CONSTRAINT_VIOLATION;
	}
	info->ti_enum.from_db[mapping->db_value] = mapping->wire_value;

	return LDAP_SUCCESS;
}

static int
datamorph_ldadd_mapping( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
{
	transformation_info *info;
	datamorph_enum_mapping *mapping;
	CfEntryInfo *parent = cei->ce_parent;

	if ( cei->ce_type != Cft_Misc || !parent || !parent->ce_bi ||
			parent->ce_bi->bi_cf_ocs != datamorph_ocs )
		return LDAP_CONSTRAINT_VIOLATION;

	info = cei->ce_private;

	mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) );
	mapping->transformation = info;

	ca->ca_private = mapping;
	config_push_cleanup( ca, datamorph_ldadd_mapping_cleanup );
	/* config_push_cleanup is only run in the case of online config but we use it to
	 * enable the new config when done with the entry */
	ca->lineno = 0;

	return LDAP_SUCCESS;
}

struct datamorph_cfadd_args {
	Operation *op;
	SlapReply *rs;
	Entry *p;
	ConfigArgs *ca;
	int index;
};

static int
datamorph_config_build_enum( void *item, void *arg )
{
	datamorph_enum_mapping *mapping = item;
	struct datamorph_cfadd_args *args = arg;
	struct berval rdn;
	Entry *e;
	char *p;
	ber_len_t index;

	rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg),
			"olcDatamorphValue={%d}", args->index++ );
	rdn.bv_val = args->ca->cr_msg;
	p = rdn.bv_val + rdn.bv_len;

	rdn.bv_len += mapping->wire_value.bv_len;
	for ( index = 0; index < mapping->wire_value.bv_len; index++ ) {
		if ( RDN_NEEDSESCAPE(mapping->wire_value.bv_val[index]) ) {
			rdn.bv_len++;
			*p++ = '\\';
		}
		*p++ = mapping->wire_value.bv_val[index];
	}
	*p = '\0';

	args->ca->ca_private = mapping;
	args->ca->ca_op = args->op;
	e = config_build_entry( args->op, args->rs, args->p->e_private, args->ca,
			&rdn, &datamorph_ocs[4], NULL );
	assert( e );

	return LDAP_SUCCESS;
}

static int
datamorph_config_build_attr( void *item, void *arg )
{
	transformation_info *info = item;
	struct datamorph_cfadd_args *args = arg;
	struct berval rdn;
	ConfigOCs *oc;
	Entry *e;

	rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg),
			"olcDatamorphAttribute={%d}%s", args->index++,
			info->attr->ad_cname.bv_val );
	rdn.bv_val = args->ca->cr_msg;

	switch ( info->type ) {
		case DATAMORPH_ENUM:
			oc = &datamorph_ocs[2];
			break;
		case DATAMORPH_INT:
			oc = &datamorph_ocs[3];
			break;
		default:
			assert(0);
			break;
	}

	args->ca->ca_private = info;
	args->ca->ca_op = args->op;
	e = config_build_entry(
			args->op, args->rs, args->p->e_private, args->ca, &rdn, oc, NULL );
	assert( e );

	if ( info->type == DATAMORPH_ENUM ) {
		struct datamorph_cfadd_args new_args = *args;
		new_args.p = e;
		new_args.index = 0;

		return ldap_avl_apply( info->ti_enum.to_db, datamorph_config_build_enum,
				&new_args, 1, AVL_PREORDER );
	}

	return LDAP_SUCCESS;
}

static int
datamorph_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
{
	slap_overinst *on = (slap_overinst *)ca->bi;
	datamorph_info *ov = on->on_bi.bi_private;
	struct datamorph_cfadd_args args = {
			.op = op,
			.rs = rs,
			.p = p,
			.ca = ca,
			.index = 0,
	};

	if ( ov->wip_transformation ) {
		/* There is one last item that is unfinished */
		int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation,
				transformation_info_cmp, ldap_avl_dup_error );
		assert( rc == LDAP_SUCCESS );
	}

	return ldap_avl_apply( ov->transformations, &datamorph_config_build_attr, &args,
			1, AVL_PREORDER );
}

static slap_overinst datamorph;

static int
datamorph_db_init( BackendDB *be, ConfigReply *cr )
{
	slap_overinst *on = (slap_overinst *)be->bd_info;
	datamorph_info *ov;

	/* TODO: can this be global? */
	if ( SLAP_ISGLOBALOVERLAY(be) ) {
		Debug( LDAP_DEBUG_ANY, "datamorph overlay must be instantiated "
				"within a database.\n" );
		return 1;
	}

	ov = ch_calloc( 1, sizeof(datamorph_info) );
	on->on_bi.bi_private = ov;

	return LDAP_SUCCESS;
}

static int
datamorph_db_destroy( BackendDB *be, ConfigReply *cr )
{
	slap_overinst *on = (slap_overinst *)be->bd_info;
	datamorph_info *ov = on->on_bi.bi_private;

	if ( ov ) {
		ldap_avl_free( ov->transformations, datamorph_info_free );
	}
	ch_free( ov );

	return LDAP_SUCCESS;
}

int
datamorph_initialize()
{
	int rc, i;

	datamorph.on_bi.bi_type = "datamorph";
	datamorph.on_bi.bi_db_init = datamorph_db_init;
	datamorph.on_bi.bi_db_destroy = datamorph_db_destroy;

	datamorph.on_bi.bi_op_add = datamorph_op_add;
	datamorph.on_bi.bi_op_compare = datamorph_op_compare;
	datamorph.on_bi.bi_op_modify = datamorph_op_mod;
	datamorph.on_bi.bi_op_modrdn = datamorph_op_modrdn;
	datamorph.on_bi.bi_op_search = datamorph_op_search;
	datamorph.on_response = datamorph_response;

	datamorph.on_bi.bi_entry_release_rw = datamorph_entry_release_rw;
	datamorph.on_bi.bi_entry_get_rw = datamorph_entry_get_rw;

	datamorph.on_bi.bi_cf_ocs = datamorph_ocs;

	for ( i = 0; datamorph_syntax_defs[i].sd_desc != NULL; i++ ) {
		rc = register_syntax( &datamorph_syntax_defs[i] );

		if ( rc ) {
			Debug( LDAP_DEBUG_ANY, "datamorph_initialize: "
					"error registering syntax %s\n",
					datamorph_syntax_defs[i].sd_desc );
			return rc;
		}
	}

	datamorph_base_syntax = syn_find( DATAMORPH_SYNTAX_BASE );
	assert( datamorph_base_syntax );

	for ( i = 0; datamorph_mrule_defs[i].mrd_desc != NULL; i++ ) {
		rc = register_matching_rule( &datamorph_mrule_defs[i] );

		if ( rc ) {
			Debug( LDAP_DEBUG_ANY, "datamorph_initialize: "
					"error registering matching rule %s\n",
					datamorph_mrule_defs[i].mrd_desc );
			return rc;
		}
	}

	rc = config_register_schema( datamorph_cfg, datamorph_ocs );
	if ( rc ) return rc;

	return overlay_register( &datamorph );
}

#if SLAPD_OVER_DATAMORPH == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
	return datamorph_initialize();
}
#endif

#endif /* SLAPD_OVER_DATAMORPH */
