[master] 1adf442 Add VMOD blob same(), equal(), length() and subblob().

Geoff Simmons geoff at uplex.de
Mon Sep 4 14:59:11 CEST 2017


commit 1adf4429a7b934829632d529fa36c68b74e2c584
Author: Geoff Simmons <geoff at uplex.de>
Date:   Fri Sep 1 13:30:02 2017 +0200

    Add VMOD blob same(), equal(), length() and subblob().

diff --git a/bin/varnishtest/tests/m00045.vtc b/bin/varnishtest/tests/m00045.vtc
new file mode 100644
index 0000000..654b16a
--- /dev/null
+++ b/bin/varnishtest/tests/m00045.vtc
@@ -0,0 +1,323 @@
+varnishtest "VMOD blob same(), equal(), length() and subblob()"
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_init {
+		new foo1 = blob.blob(IDENTITY, "foobarbazquux");
+		new foo2 = blob.blob(IDENTITY, "foobarbazquux");
+
+		new hobbes1 = blob.blob(IDENTITY,
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."});
+		new hobbes2 = blob.blob(IDENTITY,
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."});
+
+		new empty1 = blob.blob(IDENTITY, "");
+		new empty2 = blob.blob(IDENTITY, "");
+	}
+
+	sub vcl_recv {
+		return(synth(200));
+	}
+
+	sub vcl_synth {
+		if (blob.same(foo1.get(), foo1.get())) {
+			set resp.http.foo1same = "true";
+		}
+		if (blob.same(foo2.get(), foo2.get())) {
+			set resp.http.foo2same = "true";
+		}
+		if (blob.same(foo1.get(), foo2.get())) {
+			set resp.http.foo12same = "true";
+		}
+		if (blob.same(foo2.get(), foo1.get())) {
+			set resp.http.foo21same = "true";
+		}
+		if (blob.same(foo1.get(), hobbes1.get())) {
+			set resp.http.foohobbessame = "true";
+		}
+		if (blob.same(foo1.get(), empty1.get())) {
+			set resp.http.fooemptysame = "true";
+		}
+
+		if (blob.equal(foo1.get(), foo1.get())) {
+			set resp.http.foo1equal = "true";
+		}
+		if (blob.equal(foo1.get(), foo2.get())) {
+			set resp.http.foo12equal = "true";
+		}
+		if (blob.equal(foo2.get(), foo1.get())) {
+			set resp.http.foo21equal = "true";
+		}
+		if (blob.equal(foo1.get(), hobbes1.get())) {
+			set resp.http.foohobbesequal = "true";
+		}
+		if (blob.equal(foo1.get(), empty1.get())) {
+			set resp.http.fooemptyequal = "true";
+		}
+
+		set resp.http.foo1len = blob.length(foo1.get());
+		set resp.http.foo2len = blob.length(foo2.get());
+
+		if (blob.same(hobbes1.get(), hobbes1.get())) {
+			set resp.http.hobbes1same = "true";
+		}
+		if (blob.same(hobbes2.get(), hobbes2.get())) {
+			set resp.http.hobbes2same = "true";
+		}
+		if (blob.same(hobbes1.get(), hobbes2.get())) {
+			set resp.http.hobbes12same = "true";
+		}
+		if (blob.same(hobbes2.get(), hobbes1.get())) {
+			set resp.http.hobbes21same = "true";
+		}
+		if (blob.same(hobbes1.get(), empty1.get())) {
+			set resp.http.hobbesemptysame = "true";
+		}
+
+		if (blob.equal(hobbes1.get(), hobbes1.get())) {
+			set resp.http.hobbes1equal = "true";
+		}
+		if (blob.equal(hobbes1.get(), hobbes2.get())) {
+			set resp.http.hobbes12equal = "true";
+		}
+		if (blob.equal(hobbes2.get(), hobbes1.get())) {
+			set resp.http.hobbes21equal = "true";
+		}
+		if (blob.equal(hobbes1.get(), empty1.get())) {
+			set resp.http.hobbesemptyequal = "true";
+		}
+
+		set resp.http.hobbes1len = blob.length(hobbes1.get());
+		set resp.http.hobbes2len = blob.length(hobbes2.get());
+
+		if (blob.same(empty1.get(), empty1.get())) {
+			set resp.http.empty1same = "true";
+		}
+		if (blob.same(empty2.get(), empty2.get())) {
+			set resp.http.empty2same = "true";
+		}
+		if (blob.same(empty1.get(), empty2.get())) {
+			set resp.http.empty12same = "true";
+		}
+		if (blob.same(empty2.get(), empty1.get())) {
+			set resp.http.empty21same = "true";
+		}
+
+		if (blob.equal(empty1.get(), empty1.get())) {
+			set resp.http.empty1equal = "true";
+		}
+		if (blob.equal(empty1.get(), empty2.get())) {
+			set resp.http.empty12equal = "true";
+		}
+		if (blob.equal(empty2.get(), empty1.get())) {
+			set resp.http.empty21equal = "true";
+		}
+
+		set resp.http.empty1len = blob.length(empty1.get());
+		set resp.http.empty2len = blob.length(empty2.get());
+		return(deliver);
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.status == 200
+	expect resp.http.foo1same == "true"
+	expect resp.http.foo2same == "true"
+	expect resp.http.foo12same == <undef>
+	expect resp.http.foo21same == <undef>
+	expect resp.http.foohobbessame == <undef>
+	expect resp.http.fooemptysame == <undef>
+	expect resp.http.foo1equal == "true"
+	expect resp.http.foo12equal == "true"
+	expect resp.http.foo21equal == "true"
+	expect resp.http.foohobbesequal == <undef>
+	expect resp.http.fooemptyequal == <undef>
+	expect resp.http.foo1len == "13"
+	expect resp.http.foo2len == "13"
+	expect resp.http.hobbes1same == "true"
+	expect resp.http.hobbes2same == "true"
+	expect resp.http.hobbes12same == <undef>
+	expect resp.http.hobbes21same == <undef>
+	expect resp.http.hobbesemptysame == <undef>
+	expect resp.http.hobbes1equal == "true"
+	expect resp.http.hobbes12equal == "true"
+	expect resp.http.hobbes21equal == "true"
+	expect resp.http.hobbesemptyequal == <undef>
+	expect resp.http.hobbes1len == "269"
+	expect resp.http.hobbes2len == "269"
+	expect resp.http.empty1same == "true"
+	expect resp.http.empty2same == "true"
+
+	# VMOD blob uses a statically allocated empty blob for empty
+	# decodings. So the empty blobs from different objects
+	# evaluate as true for same().
+	expect resp.http.empty12same == "true"
+	expect resp.http.empty21same == "true"
+
+	expect resp.http.empty1equal == "true"
+	expect resp.http.empty12equal == "true"
+	expect resp.http.empty21equal == "true"
+	expect resp.http.empty1len == "0"
+	expect resp.http.empty2len == "0"
+} -run
+
+# subblob()
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_init {
+		# Byte values 0 up to 7
+		new up07 = blob.blob(BASE64, "AAECAwQFBgc=");
+		# Byte values 7 down to 0
+		new down07 = blob.blob(BASE64, "BwYFBAMCAQA=");
+	}
+
+	sub vcl_recv {
+		return(synth(200));
+	}
+
+	sub vcl_synth {
+		set resp.http.up03
+		    = blob.encode(BASE64, blob.subblob(up07.get(), 4B));
+		set resp.http.down07060504
+		    = blob.encode(BASE64, blob.subblob(down07.get(), 4B));
+		set resp.http.up04050607
+		    = blob.encode(BASE64, blob.subblob(up07.get(), 4B, 4B));
+		set resp.http.down03
+		    = blob.encode(BASE64, blob.subblob(down07.get(), 4B, 4B));
+		set resp.http.up07
+		    = blob.encode(BASE64, blob.subblob(up07.get(), 8B));
+		set resp.http.down07
+		    = blob.encode(BASE64, blob.subblob(down07.get(), 8B));
+		set resp.http.zerobytes
+		    = blob.encode(BASE64, blob.subblob(down07.get(), 0B));
+		set resp.http.zerolen
+		    = blob.length(blob.subblob(down07.get(), 0B));
+	}
+}
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.status == 200
+	expect resp.http.up03 == "AAECAw=="
+	expect resp.http.down07060504 == "BwYFBA=="
+	expect resp.http.up04050607 == "BAUGBw=="
+	expect resp.http.down03 == "AwIBAA=="
+	expect resp.http.up07 == "AAECAwQFBgc="
+	expect resp.http.down07 == "BwYFBAMCAQA="
+	expect resp.http.zerobytes == ""
+	expect resp.http.zerolen == "0"
+} -run
+
+# subblob() failures
+server s1 -repeat 3 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_init {
+		new up07 = blob.blob(BASE64, "AAECAwQFBgc=");
+		new empty = blob.blob(IDENTITY, "");
+	}
+
+	sub vcl_deliver {
+		if (req.url == "/empty") {
+			set resp.http.empty = blob.encode(BASE64,
+			    blob.subblob(empty.get(), 1B));
+		}
+		elsif (req.url == "/toolong") {
+			set resp.http.toolong
+			    = blob.encode(BASE64, blob.subblob(up07.get(), 9B));
+		}
+		elsif (req.url == "/badoffset") {
+			set resp.http.badoffset = blob.encode(BASE64,
+			    blob.subblob(up07.get(), 4B, 5B));
+		}
+	}
+}
+
+logexpect l1 -v v1 -d 0 -g vxid -q "VCL_Error" {
+	expect 0 * Begin req
+	expect * = VCL_Error "^vmod blob error: blob is empty in blob.subblob..$"
+	expect * = End
+} -start
+
+client c1 {
+	txreq -url /empty
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.empty == <undef>
+} -run
+
+logexpect l1 -wait
+
+client c1 {
+	txreq -url /toolong
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.toolong == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error "^vmod blob error: size 9 from offset 0 requires more bytes than blob length 8 in blob.subblob..$"
+} -run
+
+client c1 {
+	txreq -url /badoffset
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badoffset == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error "^vmod blob error: size 4 from offset 5 requires more bytes than blob length 8 in blob.subblob..$"
+} -run
+
+# VCL load failures from subblob()
+varnish v1 -errvcl {vmod blob error: blob is empty in blob.subblob()} {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_init {
+		new empty = blob.blob(IDENTITY, "");
+		if (blob.same(empty.get(), blob.subblob(empty.get(), 0B))) {
+			new B = blob.blob(IDENTITY, "b");
+		}
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: size 9 from offset 0 requires more bytes than blob length 8 in blob.subblob()} {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_init {
+		new up07 = blob.blob(BASE64, "AAECAwQFBgc=");
+		if (blob.same(up07.get(), blob.subblob(up07.get(), 9B))) {
+			new B = blob.blob(IDENTITY, "b");
+		}
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: size 4 from offset 5 requires more bytes than blob length 8 in blob.subblob()} {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_init {
+		new up07 = blob.blob(BASE64, "AAECAwQFBgc=");
+		if (blob.same(up07.get(), blob.subblob(up07.get(), 4B, 5B))) {
+			new B = blob.blob(IDENTITY, "b");
+		}
+	}
+}
diff --git a/lib/libvmod_blob/vmod.vcc b/lib/libvmod_blob/vmod.vcc
index 826a6c9..e1319a3 100644
--- a/lib/libvmod_blob/vmod.vcc
+++ b/lib/libvmod_blob/vmod.vcc
@@ -10,6 +10,7 @@ $Module blob 3 utilities for the VCL blob type
 
 ::
 
+  # binary-to-text encodings
   STRING blob.encode(ENUM encoding, BLOB blob)
   BLOB blob.decode(ENUM decoding, STRING_LIST encoded)
   BLOB blob.decode_n(INT n, ENUM decoding, STRING_LIST encoded)
@@ -18,6 +19,13 @@ $Module blob 3 utilities for the VCL blob type
   STRING blob.transcode_n(INT n, ENUM decoding, ENUM encoding,
                           STRING_LIST encoded)
 
+  # other utilities
+  BOOL blob.same(BLOB, BLOB)
+  BOOL blob.equal(BLOB, BLOB)
+  INT blob.length(BLOB)
+  BLOB blob.subblob(BLOB, BYTES length [, BYTES offset])
+
+  # blob object
   new OBJ = blob.blob(ENUM decoding, STRING_LIST encoded)
   BLOB <obj>.get()
   STRING <obj>.encode(ENUM encoding)
@@ -264,6 +272,38 @@ $Function STRING transcode_n(INT n,
 Same as ``transcode()``, but only from the first ``n`` characters of
 the encoded string.
 
+$Function BOOL same(BLOB, BLOB)
+
+Returns true if and only if the two BLOB arguments are the same
+object, i.e. they specify exactly the same region of memory.
+
+If the BLOBs are both empty (length is 0 and/or the internal pointer
+is NULL), then ``same()`` returns ``true``. If any non-empty BLOB
+is compared to an empty BLOB, then ``same()`` returns ``false``.
+
+$Function BOOL equal(BLOB, BLOB)
+
+Returns true if and only if the two BLOB arguments have equal contents
+(possibly in different memory regions).
+
+As with ``same()``: If the BLOBs are both empty, then ``equal()``
+returns ``true``. If any non-empty BLOB is compared to an empty BLOB,
+then ``equal()`` returns ``false``.
+
+$Function INT length(BLOB)
+
+Returns the length of the BLOB.
+
+$Function BLOB subblob(BLOB, BYTES length, BYTES offset = 0)
+
+Returns a new BLOB formed from ``length`` bytes of the BLOB argument
+starting at ``offset`` bytes from the start of its memory region. The
+default value of ``offset`` is 0B.
+
+``subblob()`` fails and returns NULL if the BLOB argument is empty, or
+if ``offset + length`` requires more bytes than are available in the
+BLOB.
+
 $Object blob(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD, HEX,
                    URL} decoding="IDENTITY",
              STRING_LIST encoded)
@@ -330,9 +370,10 @@ not known until runtime.
 ERRORS
 ======
 
-The encoders and decoders may fail if there is insufficient space to
-create the new blob or string. Decoders may also fail if the encoded
-string is an illegal format for the decoding scheme.
+The encoders, decoders and ``subblob()`` may fail if there is
+insufficient space to create the new blob or string. Decoders may also
+fail if the encoded string is an illegal format for the decoding
+scheme.
 
 If any of the VMOD's methods, functions or constructor fail, then VCL
 failure is invoked, just as if ``return(fail)`` had been called in the
@@ -360,10 +401,11 @@ strings. The ``blob`` object and its methods allocate memory from the
 heap, and hence they are only limited by available virtual memory.
 
 The ``encode()``, ``decode()`` and ``transcode()`` functions allocate
-Varnish workspace.  If these functions are failing, as indicated by
-"out of space" messages in the Varnish log (with the ``VCL_Error``
-tag), then you will need to increase the varnishd parameters
-``workspace_client`` and/or ``workspace_backend``.
+Varnish workspace, as does ``subblob()`` for the newly created BLOB.
+If these functions are failing, as indicated by "out of space"
+messages in the Varnish log (with the ``VCL_Error`` tag), then you
+will need to increase the varnishd parameters ``workspace_client``
+and/or ``workspace_backend``.
 
 The ``transcode()`` function also allocates space on the stack for a
 temporary BLOB. If this function causes stack overflow, you may need
diff --git a/lib/libvmod_blob/vmod_blob.c b/lib/libvmod_blob/vmod_blob.c
index 7d0ad92..da105d9 100644
--- a/lib/libvmod_blob/vmod_blob.c
+++ b/lib/libvmod_blob/vmod_blob.c
@@ -244,7 +244,7 @@ vmod_blob_get(VRT_CTX, struct vmod_blob_blob *b)
 	return &b->blob;
 }
 
-VCL_STRING
+VCL_STRING __match_proto__(td_blob_blob_encode)
 vmod_blob_encode(VRT_CTX, struct vmod_blob_blob *b, VCL_ENUM encs)
 {
 	enum encoding enc = parse_encoding(encs);
@@ -607,3 +607,82 @@ vmod_transcode_n(VRT_CTX, VCL_INT n, VCL_ENUM decs, VCL_ENUM encs,
 
 	return (r);
 }
+
+VCL_BOOL __match_proto__(td_blob_same)
+vmod_same(VRT_CTX, VCL_BLOB b1, VCL_BLOB b2)
+{
+	(void) ctx;
+
+	if (b1 == NULL && b2 == NULL)
+		return 1;
+	if (b1 == NULL || b2 == NULL)
+		return 0;
+	return (b1->len == b2->len && b1->priv == b2->priv);
+}
+
+VCL_BOOL __match_proto__(td_blob_equal)
+vmod_equal(VRT_CTX, VCL_BLOB b1, VCL_BLOB b2)
+{
+	(void) ctx;
+
+	if (b1 == NULL && b2 == NULL)
+		return 1;
+	if (b1 == NULL || b2 == NULL)
+		return 0;
+	if (b1->len != b2->len)
+		return 0;
+	if (b1->priv == b2->priv)
+		return 1;
+	if (b1->priv == NULL || b2->priv == NULL)
+		return 0;
+	return (memcmp(b1->priv, b2->priv, b1->len) == 0);
+}
+
+VCL_INT __match_proto__(td_blob_length)
+vmod_length(VRT_CTX, VCL_BLOB b)
+{
+	(void) ctx;
+
+	if (b == NULL)
+		return 0;
+	return b->len;
+}
+
+VCL_BLOB __match_proto__(td_blob_subblob)
+vmod_subblob(VRT_CTX, VCL_BLOB b, VCL_BYTES n, VCL_BYTES off)
+{
+	uintptr_t snap;
+	struct vmod_priv *sub;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	assert(n >= 0);
+	assert(off >= 0);
+
+	if (b == NULL || b->len == 0 || b->priv == NULL) {
+		ERR(ctx, "blob is empty in blob.subblob()");
+		return NULL;
+	}
+	assert(b->len >= 0);
+	if (off + n > b->len) {
+		VERR(ctx, "size %lld from offset %lld requires more bytes than "
+		     "blob length %d in blob.subblob()", n, off, b->len);
+		return NULL;
+	}
+
+	if (n == 0)
+		return null_blob;
+
+	snap = WS_Snapshot(ctx->ws);
+	if ((sub = WS_Alloc(ctx->ws, sizeof(*sub))) == NULL) {
+		ERRNOMEM(ctx, "Allocating BLOB result in blob.subblob()");
+		return NULL;
+	}
+	if ((sub->priv = WS_Alloc(ctx->ws, n)) == NULL) {
+		VERRNOMEM(ctx, "Allocating %lld bytes in blob.subblob()", n);
+		WS_Reset(ctx->ws, snap);
+		return NULL;
+	}
+	memcpy(sub->priv, (char *)b->priv + off, n);
+	sub->len = n;
+	return sub;
+}



More information about the varnish-commit mailing list