Index: uspace/app/bithenge/Makefile
===================================================================
--- uspace/app/bithenge/Makefile	(revision 8b36bf28adc74d3d3510444d9f6fbf2037ba01cd)
+++ uspace/app/bithenge/Makefile	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -38,4 +38,5 @@
 	print.c \
 	test.c \
+	transform.c \
 	tree.c
 
Index: uspace/app/bithenge/blob.c
===================================================================
--- uspace/app/bithenge/blob.c	(revision 8b36bf28adc74d3d3510444d9f6fbf2037ba01cd)
+++ uspace/app/bithenge/blob.c	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -297,4 +297,32 @@
 }
 
+/** Check whether the contents of two blobs are equal.
+ * @memberof bithenge_blob_t
+ * @param a, b Blobs to compare.
+ * @return Whether the blobs are equal. If an error occurs, returns false.
+ */
+bool bithenge_blob_equal(bithenge_blob_t *a, bithenge_blob_t *b)
+{
+	assert(a);
+	assert(a->base.blob_ops);
+	assert(b);
+	assert(b->base.blob_ops);
+	int rc;
+	char buffer_a[4096], buffer_b[4096];
+	aoff64_t offset = 0, size_a = sizeof(buffer_a), size_b = sizeof(buffer_b);
+	do {
+		rc = bithenge_blob_read(a, offset, buffer_a, &size_a);
+		if (rc != EOK)
+			return false;
+		rc = bithenge_blob_read(b, offset, buffer_b, &size_b);
+		if (rc != EOK)
+			return false;
+		if (size_a != size_b || bcmp(buffer_a, buffer_b, size_a))
+			return false;
+		offset += size_a;
+	} while (size_a == sizeof(buffer_a));
+	return true;
+}
+
 /** @}
  */
Index: uspace/app/bithenge/blob.h
===================================================================
--- uspace/app/bithenge/blob.h	(revision 8b36bf28adc74d3d3510444d9f6fbf2037ba01cd)
+++ uspace/app/bithenge/blob.h	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -182,4 +182,6 @@
     size_t len, bool needs_free);
 
+bool bithenge_blob_equal(bithenge_blob_t *a, bithenge_blob_t *b);
+
 #endif
 
Index: uspace/app/bithenge/transform.c
===================================================================
--- uspace/app/bithenge/transform.c	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
+++ uspace/app/bithenge/transform.c	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Transforms.
+ */
+
+#include <byteorder.h>
+#include <errno.h>
+#include "blob.h"
+#include "transform.h"
+
+static int transform_indestructible(bithenge_transform_t *xform)
+{
+	return EINVAL;
+}
+
+static int uint32le_apply(bithenge_transform_t *xform, bithenge_node_t *in,
+    bithenge_node_t **out)
+{
+	int rc;
+	if (bithenge_node_type(in) != BITHENGE_NODE_BLOB)
+		return EINVAL;
+	bithenge_blob_t *blob = bithenge_node_as_blob(in);
+
+	// Try to read 5 bytes and fail if the blob is too long.
+	uint32_t val[2];
+	aoff64_t size = sizeof(val[0]) + 1;
+	rc = bithenge_blob_read(blob, 0, (char *)val, &size);
+	if (rc != EOK)
+		return rc;
+	if (size != 4)
+		return EINVAL;
+
+	return bithenge_new_integer_node(out, uint32_t_le2host(val[0]));
+}
+
+static int uint32le_prefix_length(bithenge_transform_t *xform,
+    bithenge_blob_t *blob, aoff64_t *out)
+{
+	*out = 4;
+	return EOK;
+}
+
+static const bithenge_transform_ops_t uint32le_ops = {
+	.apply = uint32le_apply,
+	.prefix_length = uint32le_prefix_length,
+	.destroy = transform_indestructible,
+};
+
+static bithenge_transform_t uint32le_transform = {
+	&uint32le_ops, 1
+};
+
+/** Create a little-endian 32-bit unsigned integer transform.
+ * @param out Holds the transform.
+ * @return EOK on success or an error code from errno.h. */
+int bithenge_uint32le_transform(bithenge_transform_t **out)
+{
+	*out = &uint32le_transform;
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/app/bithenge/transform.h
===================================================================
--- uspace/app/bithenge/transform.h	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
+++ uspace/app/bithenge/transform.h	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2012 Sean Bartell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup bithenge
+ * @{
+ */
+/**
+ * @file
+ * Transforms.
+ */
+
+#ifndef BITHENGE_TRANSFORM_H_
+#define BITHENGE_TRANSFORM_H_
+
+#include "tree.h"
+
+/** A transform that creates a new tree from an old tree. */
+typedef struct {
+	/** @privatesection */
+	const struct bithenge_transform_ops_t *ops;
+	unsigned int refs;
+} bithenge_transform_t;
+
+/** Operations that may be provided by a transform. */
+typedef struct bithenge_transform_ops_t {
+	/** @copydoc bithenge_transform_t::bithenge_transform_apply */
+	int (*apply)(bithenge_transform_t *xform, bithenge_node_t *in, bithenge_node_t **out);
+	/** @copydoc bithenge_transform_t::bithenge_transform_prefix_length */
+	int (*prefix_length)(bithenge_transform_t *xform, bithenge_blob_t *blob, aoff64_t *out);
+	/** Destroy the transform.
+	 * @param blob The transform.
+	 * @return EOK on success or an error code from errno.h. */
+	int (*destroy)(bithenge_transform_t *xform);
+} bithenge_transform_ops_t;
+
+/** Apply a transform.
+ * @memberof bithenge_transform_t
+ * @param xform The transform.
+ * @param in The input tree.
+ * @param[out] out Where the output tree will be stored.
+ * @return EOK on success or an error code from errno.h. */
+static inline int bithenge_transform_apply(bithenge_transform_t *xform,
+    bithenge_node_t *in, bithenge_node_t **out)
+{
+	assert(xform);
+	assert(xform->ops);
+	return xform->ops->apply(xform, in, out);
+}
+
+/** Find the length of the prefix of a blob this transform can use as input. In
+ * other words, figure out how many bytes this transform will use up.  This
+ * method is optional and can return an error, but it is required for struct
+ * subtransforms.
+ * @memberof bithenge_transform_t
+ * @param xform The transform.
+ * @param blob The blob.
+ * @param[out] out Where the prefix length will be stored.
+ * @return EOK on success, ENOTSUP if not supported, or another error code from
+ * errno.h. */
+static inline int bithenge_transform_prefix_length(bithenge_transform_t *xform,
+    bithenge_blob_t *blob, aoff64_t *out)
+{
+	assert(xform);
+	assert(xform->ops);
+	if (!xform->ops->prefix_length)
+		return ENOTSUP;
+	return xform->ops->prefix_length(xform, blob, out);
+}
+
+/** Increment a transform's reference count.
+ * @param xform The transform to reference.
+ * @return EOK on success or an error code from errno.h. */
+static inline int bithenge_transform_inc_ref(bithenge_transform_t *xform)
+{
+	assert(xform);
+	xform->refs++;
+	return EOK;
+}
+
+/** Decrement a transform's reference count.
+ * @param xform The transform to dereference.
+ * @return EOK on success or an error code from errno.h. */
+static inline int bithenge_transform_dec_ref(bithenge_transform_t *xform)
+{
+	assert(xform);
+	assert(xform->ops);
+	if (--xform->refs == 0)
+		return xform->ops->destroy(xform);
+	return EOK;
+}
+
+int bithenge_uint32le_transform(bithenge_transform_t **out);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/bithenge/tree.c
===================================================================
--- uspace/app/bithenge/tree.c	(revision 8b36bf28adc74d3d3510444d9f6fbf2037ba01cd)
+++ uspace/app/bithenge/tree.c	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -37,4 +37,5 @@
 #include <errno.h>
 #include <stdlib.h>
+#include <str.h>
 #include "blob.h"
 #include "tree.h"
@@ -207,4 +208,31 @@
 }
 
+/** Check whether the contents of two nodes are equal. Does not yet work for
+ * internal nodes.
+ * @memberof bithenge_node_t
+ * @param a, b Nodes to compare.
+ * @return Whether the nodes are equal. If an error occurs, returns false.
+ * @todo Add support for internal nodes.
+ */
+bool bithenge_node_equal(bithenge_node_t *a, bithenge_node_t *b)
+{
+	if (a->type != b->type)
+		return false;
+	switch (a->type) {
+	case BITHENGE_NODE_INTERNAL:
+		return false;
+	case BITHENGE_NODE_BOOLEAN:
+		return a->boolean_value == b->boolean_value;
+	case BITHENGE_NODE_INTEGER:
+		return a->integer_value == b->integer_value;
+	case BITHENGE_NODE_STRING:
+		return !str_cmp(a->string_value.ptr, b->string_value.ptr);
+	case BITHENGE_NODE_BLOB:
+		return bithenge_blob_equal(bithenge_node_as_blob(a),
+		    bithenge_node_as_blob(b));
+	}
+	return false;
+}
+
 /** @}
  */
Index: uspace/app/bithenge/tree.h
===================================================================
--- uspace/app/bithenge/tree.h	(revision 8b36bf28adc74d3d3510444d9f6fbf2037ba01cd)
+++ uspace/app/bithenge/tree.h	(revision d5070eff49234bdcf4f61f6456094186dfa0e673)
@@ -153,4 +153,5 @@
 int bithenge_new_string_node(bithenge_node_t **, const char *, bool);
 int bithenge_node_destroy(bithenge_node_t *);
+bool bithenge_node_equal(bithenge_node_t *, bithenge_node_t *);
 
 #endif
