Commit 1adf4429 authored by Geoff Simmons's avatar Geoff Simmons

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

parent 60cc5f9b
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");
}
}
}
......@@ -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
......
......@@ -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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment