Blame t/binary-sasl.t

Packit 4e8bc4
#!/usr/bin/perl
Packit 4e8bc4
Packit 4e8bc4
use strict;
Packit 4e8bc4
use warnings;
Packit 4e8bc4
use Cwd;
Packit 4e8bc4
use FindBin qw($Bin);
Packit 4e8bc4
use lib "$Bin/lib";
Packit 4e8bc4
use MemcachedTest;
Packit 4e8bc4
Packit 4e8bc4
my $supports_sasl = supports_sasl();
Packit 4e8bc4
Packit 4e8bc4
use Test::More;
Packit 4e8bc4
Packit 4e8bc4
if (supports_sasl()) {
Packit 4e8bc4
    if ($ENV{'RUN_SASL_TESTS'}) {
Packit 4e8bc4
        plan tests => 34;
Packit 4e8bc4
    } else {
Packit 4e8bc4
        plan skip_all => 'Skipping SASL tests';
Packit 4e8bc4
        exit 0;
Packit 4e8bc4
    }
Packit 4e8bc4
} else {
Packit 4e8bc4
    plan tests => 1;
Packit 4e8bc4
    eval {
Packit 4e8bc4
        my $server = new_memcached("-S");
Packit 4e8bc4
    };
Packit 4e8bc4
    ok($@, "Died with illegal -S args when SASL is not supported.");
Packit 4e8bc4
    exit 0;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
eval {
Packit 4e8bc4
    my $server = new_memcached("-S -B auto");
Packit 4e8bc4
};
Packit 4e8bc4
ok($@, "SASL shouldn't be used with protocol auto negotiate");
Packit 4e8bc4
Packit 4e8bc4
eval {
Packit 4e8bc4
    my $server = new_memcached("-S -B ascii");
Packit 4e8bc4
};
Packit 4e8bc4
ok($@, "SASL isn't implemented in the ascii protocol");
Packit 4e8bc4
Packit 4e8bc4
eval {
Packit 4e8bc4
    my $server = new_memcached("-S -B binary -B ascii");
Packit 4e8bc4
};
Packit 4e8bc4
ok($@, "SASL isn't implemented in the ascii protocol");
Packit 4e8bc4
Packit 4e8bc4
# Based almost 100% off testClient.py which is:
Packit 4e8bc4
# Copyright (c) 2007  Dustin Sallings <dustin@spy.net>
Packit 4e8bc4
Packit 4e8bc4
# Command constants
Packit 4e8bc4
use constant CMD_GET        => 0x00;
Packit 4e8bc4
use constant CMD_SET        => 0x01;
Packit 4e8bc4
use constant CMD_ADD        => 0x02;
Packit 4e8bc4
use constant CMD_REPLACE    => 0x03;
Packit 4e8bc4
use constant CMD_DELETE     => 0x04;
Packit 4e8bc4
use constant CMD_INCR       => 0x05;
Packit 4e8bc4
use constant CMD_DECR       => 0x06;
Packit 4e8bc4
use constant CMD_QUIT       => 0x07;
Packit 4e8bc4
use constant CMD_FLUSH      => 0x08;
Packit 4e8bc4
use constant CMD_GETQ       => 0x09;
Packit 4e8bc4
use constant CMD_NOOP       => 0x0A;
Packit 4e8bc4
use constant CMD_VERSION    => 0x0B;
Packit 4e8bc4
use constant CMD_GETK       => 0x0C;
Packit 4e8bc4
use constant CMD_GETKQ      => 0x0D;
Packit 4e8bc4
use constant CMD_APPEND     => 0x0E;
Packit 4e8bc4
use constant CMD_PREPEND    => 0x0F;
Packit 4e8bc4
use constant CMD_STAT       => 0x10;
Packit 4e8bc4
use constant CMD_SETQ       => 0x11;
Packit 4e8bc4
use constant CMD_ADDQ       => 0x12;
Packit 4e8bc4
use constant CMD_REPLACEQ   => 0x13;
Packit 4e8bc4
use constant CMD_DELETEQ    => 0x14;
Packit 4e8bc4
use constant CMD_INCREMENTQ => 0x15;
Packit 4e8bc4
use constant CMD_DECREMENTQ => 0x16;
Packit 4e8bc4
use constant CMD_QUITQ      => 0x17;
Packit 4e8bc4
use constant CMD_FLUSHQ     => 0x18;
Packit 4e8bc4
use constant CMD_APPENDQ    => 0x19;
Packit 4e8bc4
use constant CMD_PREPENDQ   => 0x1A;
Packit 4e8bc4
Packit 4e8bc4
use constant CMD_SASL_LIST_MECHS    => 0x20;
Packit 4e8bc4
use constant CMD_SASL_AUTH          => 0x21;
Packit 4e8bc4
use constant CMD_SASL_STEP          => 0x22;
Packit 4e8bc4
use constant ERR_AUTH_ERROR   => 0x20;
Packit 4e8bc4
Packit 4e8bc4
Packit 4e8bc4
# REQ and RES formats are divided even though they currently share
Packit 4e8bc4
# the same format, since they _could_ differ in the future.
Packit 4e8bc4
use constant REQ_PKT_FMT      => "CCnCCnNNNN";
Packit 4e8bc4
use constant RES_PKT_FMT      => "CCnCCnNNNN";
Packit 4e8bc4
use constant INCRDECR_PKT_FMT => "NNNNN";
Packit 4e8bc4
use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
Packit 4e8bc4
use constant REQ_MAGIC        => 0x80;
Packit 4e8bc4
use constant RES_MAGIC        => 0x81;
Packit 4e8bc4
Packit 4e8bc4
my $pwd=getcwd;
Packit 4e8bc4
$ENV{'SASL_CONF_PATH'} = "$pwd/t/sasl";
Packit 4e8bc4
Packit 4e8bc4
my $server = new_memcached('-B binary -U 0 -S -l 127.0.0.1 ');
Packit 4e8bc4
Packit 4e8bc4
my $mc = MC::Client->new;
Packit 4e8bc4
Packit 4e8bc4
my $check = sub {
Packit 4e8bc4
    my ($key, $orig_val) = @_;
Packit 4e8bc4
    my ($status, $val, $cas) = $mc->get($key);
Packit 4e8bc4
Packit 4e8bc4
    if ($val =~ /^\d+$/) {
Packit 4e8bc4
        cmp_ok($val,'==', $orig_val, "$val = $orig_val");
Packit 4e8bc4
    }
Packit 4e8bc4
    else {
Packit 4e8bc4
        cmp_ok($val, 'eq', $orig_val, "$val = $orig_val");
Packit 4e8bc4
    }
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
my $set = sub {
Packit 4e8bc4
    my ($key, $orig_value, $exp) = @_;
Packit 4e8bc4
    $exp = defined $exp ? $exp : 0;
Packit 4e8bc4
    my ($status, $rv)= $mc->set($key, $orig_value, $exp);
Packit 4e8bc4
    $check->($key, $orig_value);
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
my $empty = sub {
Packit 4e8bc4
    my $key = shift;
Packit 4e8bc4
    my ($status,$rv) =()= eval { $mc->get($key) };
Packit 4e8bc4
    #if ($status == ERR_AUTH_ERROR) {
Packit 4e8bc4
    #    ok($@->auth_error, "Not authorized to connect");
Packit 4e8bc4
    #}
Packit 4e8bc4
    #else {
Packit 4e8bc4
    #    ok($@->not_found, "We got a not found error when we expected one");
Packit 4e8bc4
    #}
Packit 4e8bc4
    if ($status) {
Packit 4e8bc4
        ok($@->not_found, "We got a not found error when we expected one");
Packit 4e8bc4
    }
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
my $delete = sub {
Packit 4e8bc4
    my ($key, $when) = @_;
Packit 4e8bc4
    $mc->delete($key, $when);
Packit 4e8bc4
    $empty->($key);
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
# BEGIN THE TEST
Packit 4e8bc4
ok($server, "started the server");
Packit 4e8bc4
Packit 4e8bc4
my $v = $mc->version;
Packit 4e8bc4
ok(defined $v && length($v), "Proper version: $v");
Packit 4e8bc4
Packit 4e8bc4
# list mechs
Packit 4e8bc4
my $mechs= $mc->list_mechs();
Packit 4e8bc4
Test::More::cmp_ok($mechs, 'eq', 'CRAM-MD5 PLAIN', "list_mechs $mechs");
Packit 4e8bc4
Packit 4e8bc4
# this should fail, not authenticated
Packit 4e8bc4
{
Packit 4e8bc4
    my ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
    my ($status, $val) = $mc->delete('x');
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
    my ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
    my ($status, $val)=  $mc->flush('x');
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
Packit 4e8bc4
# Build the auth DB for testing.
Packit 4e8bc4
my $sasldb = '/tmp/test-memcached.sasldb';
Packit 4e8bc4
unlink $sasldb;
Packit 4e8bc4
Packit 4e8bc4
my $saslpasswd_path;
Packit 4e8bc4
for my $dir (split(/:/, $ENV{PATH}),
Packit 4e8bc4
             "/usr/bin",
Packit 4e8bc4
             "/usr/sbin",
Packit 4e8bc4
             "/usr/local/bin",
Packit 4e8bc4
             "/usr/local/sbin",
Packit 4e8bc4
    ) {
Packit 4e8bc4
    my $exe = $dir . '/saslpasswd2';
Packit 4e8bc4
    if (-x $exe) {
Packit 4e8bc4
        $saslpasswd_path = $exe;
Packit 4e8bc4
        last;
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
my $sasl_realm = 'memcached.realm';
Packit 4e8bc4
Packit 4e8bc4
system("echo testpass | $saslpasswd_path -a memcached -u $sasl_realm -c -p testuser");
Packit 4e8bc4
Packit 4e8bc4
$mc = MC::Client->new;
Packit 4e8bc4
Packit 4e8bc4
# Attempt a bad auth mech.
Packit 4e8bc4
is ($mc->authenticate('testuser', 'testpass', "X" x 40), 0x4, "bad mech");
Packit 4e8bc4
Packit 4e8bc4
# Attempt bad authentication.
Packit 4e8bc4
is ($mc->authenticate('testuser', 'wrongpassword'), 0x20, "bad auth");
Packit 4e8bc4
Packit 4e8bc4
# Now try good authentication and make the tests work.
Packit 4e8bc4
is ($mc->authenticate('testuser', 'testpass'), 0, "authenticated");
Packit 4e8bc4
# these should work
Packit 4e8bc4
{
Packit 4e8bc4
    my ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok(! $status);
Packit 4e8bc4
}
Packit 4e8bc4
$check->('x','somevalue');
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my ($status, $val)= $mc->delete('x');
Packit 4e8bc4
    ok(! $status);
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok(! $status);
Packit 4e8bc4
}
Packit 4e8bc4
$check->('x','somevalue');
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my ($status, $val)=  $mc->flush('x');
Packit 4e8bc4
    ok(! $status);
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x');
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
Packit 4e8bc4
    # Attempt bad authentication.
Packit 4e8bc4
    is ($mc->authenticate('testuser', 'wrongpassword'), 0x20, "bad auth");
Packit 4e8bc4
Packit 4e8bc4
    # This should fail because $mc is not authenticated
Packit 4e8bc4
    my ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
$empty->('x', 'somevalue');
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
Packit 4e8bc4
    # Attempt bad authentication.
Packit 4e8bc4
    is ($mc->authenticate('testuser', 'wrongpassword'), 0x20, "bad auth");
Packit 4e8bc4
Packit 4e8bc4
    # Mix an authenticated connection and an unauthenticated connection to
Packit 4e8bc4
    # confirm c->authenticated is not shared among connections
Packit 4e8bc4
    my $mc2 = MC::Client->new;
Packit 4e8bc4
    is ($mc2->authenticate('testuser', 'testpass'), 0, "authenticated");
Packit 4e8bc4
    my ($status, $val)= $mc2->set('x', "somevalue");
Packit 4e8bc4
    ok(! $status);
Packit 4e8bc4
Packit 4e8bc4
    # This should fail because $mc is not authenticated
Packit 4e8bc4
    ($status, $val)= $mc->set('x', "somevalue");
Packit 4e8bc4
    ok($status, "this fails to authenticate");
Packit 4e8bc4
    cmp_ok($status,'==',ERR_AUTH_ERROR, "error code matches");
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my $mc = MC::Client->new;
Packit 4e8bc4
    is ($mc->sasl_step('testuser', 'testpass'), 0x20, "sasl_step_fails_no_segfault");
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# check the SASL stats, make sure they track things correctly
Packit 4e8bc4
# note: the enabled or not is presence checked in stats.t
Packit 4e8bc4
Packit 4e8bc4
# while authenticated, get current counter
Packit 4e8bc4
#
Packit 4e8bc4
# My initial approach was going to be to get current counts, reauthenticate
Packit 4e8bc4
# and fail, followed by a reauth successfully so I'd know what happened.
Packit 4e8bc4
# Reauthentication is currently unsupported, so it doesn't work that way at the
Packit 4e8bc4
# moment.  Adding tests may break this.
Packit 4e8bc4
Packit 4e8bc4
{
Packit 4e8bc4
    my %stats = $mc->stats('');
Packit 4e8bc4
    is ($stats{'auth_cmds'}, 6, "auth commands counted");
Packit 4e8bc4
    is ($stats{'auth_errors'}, 4, "auth errors correct");
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
Packit 4e8bc4
# Along with the assertion added to the code to verify we're staying
Packit 4e8bc4
# within bounds when we do a stats detail dump (detail turned on at
Packit 4e8bc4
# the top).
Packit 4e8bc4
# my %stats = $mc->stats('detail dump');
Packit 4e8bc4
Packit 4e8bc4
# ######################################################################
Packit 4e8bc4
# Test ends around here.
Packit 4e8bc4
# ######################################################################
Packit 4e8bc4
Packit 4e8bc4
package MC::Client;
Packit 4e8bc4
Packit 4e8bc4
use strict;
Packit 4e8bc4
use warnings;
Packit 4e8bc4
use fields qw(socket);
Packit 4e8bc4
use IO::Socket::INET;
Packit 4e8bc4
Packit 4e8bc4
use constant ERR_AUTH_ERROR   => 0x20;
Packit 4e8bc4
Packit 4e8bc4
sub new {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($s) = @_;
Packit 4e8bc4
    $s = $server unless defined $s;
Packit 4e8bc4
    my $sock = $s->sock;
Packit 4e8bc4
    $self = fields::new($self);
Packit 4e8bc4
    $self->{socket} = $sock;
Packit 4e8bc4
    return $self;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub authenticate {
Packit 4e8bc4
    my ($self, $user, $pass, $mech)= @_;
Packit 4e8bc4
    $mech ||= 'PLAIN';
Packit 4e8bc4
    my $buf = sprintf("%c%s@%s%c%s", 0, $user, $sasl_realm, 0, $pass);
Packit 4e8bc4
    my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_AUTH, $mech, $buf, '');
Packit 4e8bc4
    return $status;
Packit 4e8bc4
}
Packit 4e8bc4
sub sasl_step {
Packit 4e8bc4
    my ($self, $user, $pass, $mech)= @_;
Packit 4e8bc4
    $mech ||= 'PLAIN';
Packit 4e8bc4
    my $buf = sprintf("%c%s@%s%c%s", 0, $user, $sasl_realm, 0, $pass);
Packit 4e8bc4
    my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_STEP, $mech, $buf, '');
Packit 4e8bc4
    return $status;
Packit 4e8bc4
}
Packit 4e8bc4
sub list_mechs {
Packit 4e8bc4
    my ($self)= @_;
Packit 4e8bc4
    my ($status, $rv, undef) = $self->_do_command(::CMD_SASL_LIST_MECHS, '', '', '');
Packit 4e8bc4
    return join(" ", sort(split(/\s+/, $rv)));
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub build_command {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    die "Not enough args to send_command" unless @_ >= 4;
Packit 4e8bc4
    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
Packit 4e8bc4
Packit 4e8bc4
    $extra_header = '' unless defined $extra_header;
Packit 4e8bc4
    my $keylen    = length($key);
Packit 4e8bc4
    my $vallen    = length($val);
Packit 4e8bc4
    my $extralen  = length($extra_header);
Packit 4e8bc4
    my $datatype  = 0;  # field for future use
Packit 4e8bc4
    my $reserved  = 0;  # field for future use
Packit 4e8bc4
    my $totallen  = $keylen + $vallen + $extralen;
Packit 4e8bc4
    my $ident_hi  = 0;
Packit 4e8bc4
    my $ident_lo  = 0;
Packit 4e8bc4
Packit 4e8bc4
    if ($cas) {
Packit 4e8bc4
        $ident_hi = int($cas / 2 ** 32);
Packit 4e8bc4
        $ident_lo = int($cas % 2 ** 32);
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
Packit 4e8bc4
                   $datatype, $reserved, $totallen, $opaque, $ident_hi,
Packit 4e8bc4
                   $ident_lo);
Packit 4e8bc4
    my $full_msg = $msg . $extra_header . $key . $val;
Packit 4e8bc4
    return $full_msg;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub send_command {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    die "Not enough args to send_command" unless @_ >= 4;
Packit 4e8bc4
    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
Packit 4e8bc4
Packit 4e8bc4
    my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
Packit 4e8bc4
Packit 4e8bc4
    my $sent = $self->{socket}->send($full_msg);
Packit 4e8bc4
    die("Send failed:  $!") unless $sent;
Packit 4e8bc4
    if($sent != length($full_msg)) {
Packit 4e8bc4
        die("only sent $sent of " . length($full_msg) . " bytes");
Packit 4e8bc4
    }
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub flush_socket {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    $self->{socket}->flush;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
# Send a silent command and ensure it doesn't respond.
Packit 4e8bc4
sub send_silent {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    die "Not enough args to send_silent" unless @_ >= 4;
Packit 4e8bc4
    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
Packit 4e8bc4
Packit 4e8bc4
    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
Packit 4e8bc4
    $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
Packit 4e8bc4
Packit 4e8bc4
    my ($ropaque, $status, $data) = $self->_handle_single_response;
Packit 4e8bc4
    Test::More::is($ropaque, $opaque + 1);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub silent_mutation {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($cmd, $key, $value) = @_;
Packit 4e8bc4
Packit 4e8bc4
    $empty->($key);
Packit 4e8bc4
    my $extra = pack "NN", 82, 0;
Packit 4e8bc4
    $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
Packit 4e8bc4
    $check->($key, $value);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub _handle_single_response {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my $myopaque = shift;
Packit 4e8bc4
Packit 4e8bc4
    $self->{socket}->recv(my $response, ::MIN_RECV_BYTES);
Packit 4e8bc4
Packit 4e8bc4
    my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
Packit 4e8bc4
        $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $response);
Packit 4e8bc4
Packit 4e8bc4
    return ($opaque, '', '', '', 0) if not defined $remaining;
Packit 4e8bc4
    return ($opaque, '', '', '', 0) if ($remaining == 0);
Packit 4e8bc4
Packit 4e8bc4
    # fetch the value
Packit 4e8bc4
    my $rv="";
Packit 4e8bc4
    while($remaining - length($rv) > 0) {
Packit 4e8bc4
        $self->{socket}->recv(my $buf, $remaining - length($rv));
Packit 4e8bc4
        $rv .= $buf;
Packit 4e8bc4
    }
Packit 4e8bc4
    if(length($rv) != $remaining) {
Packit 4e8bc4
        my $found = length($rv);
Packit 4e8bc4
        die("Expected $remaining bytes, got $found");
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
Packit 4e8bc4
Packit 4e8bc4
    #if ($status) {
Packit 4e8bc4
        #die MC::Error->new($status, $rv);
Packit 4e8bc4
    #}
Packit 4e8bc4
Packit 4e8bc4
    return ($opaque, $status, $rv, $cas, $keylen);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub _do_command {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    die unless @_ >= 3;
Packit 4e8bc4
    my ($cmd, $key, $val, $extra_header, $cas) = @_;
Packit 4e8bc4
Packit 4e8bc4
    $extra_header = '' unless defined $extra_header;
Packit 4e8bc4
    my $opaque = int(rand(2**32));
Packit 4e8bc4
    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
Packit 4e8bc4
    my (undef, $status, $rv, $rcas) = $self->_handle_single_response($opaque);
Packit 4e8bc4
    return ($status, $rv, $rcas);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub _incrdecr_header {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($amt, $init, $exp) = @_;
Packit 4e8bc4
Packit 4e8bc4
    my $amt_hi = int($amt / 2 ** 32);
Packit 4e8bc4
    my $amt_lo = int($amt % 2 ** 32);
Packit 4e8bc4
Packit 4e8bc4
    my $init_hi = int($init / 2 ** 32);
Packit 4e8bc4
    my $init_lo = int($init % 2 ** 32);
Packit 4e8bc4
Packit 4e8bc4
    my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
Packit 4e8bc4
                            $init_lo, $exp);
Packit 4e8bc4
Packit 4e8bc4
    return $extra_header;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub _incrdecr {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($cmd, $key, $amt, $init, $exp) = @_;
Packit 4e8bc4
Packit 4e8bc4
    my ($status, $data, undef) = $self->_do_command($cmd, $key, '',
Packit 4e8bc4
                                           $self->_incrdecr_header($amt, $init, $exp));
Packit 4e8bc4
Packit 4e8bc4
    my $header = substr $data, 0, 8, '';
Packit 4e8bc4
    my ($resp_hi, $resp_lo) = unpack "NN", $header;
Packit 4e8bc4
    my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
Packit 4e8bc4
Packit 4e8bc4
    return $resp;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub silent_incrdecr {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($cmd, $key, $amt, $init, $exp) = @_;
Packit 4e8bc4
    my $opaque = 8275753;
Packit 4e8bc4
Packit 4e8bc4
    $mc->send_silent($cmd, $key, '', $opaque,
Packit 4e8bc4
                     $mc->_incrdecr_header($amt, $init, $exp));
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub stats {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my $key  = shift;
Packit 4e8bc4
    my $cas = 0;
Packit 4e8bc4
    my $opaque = int(rand(2**32));
Packit 4e8bc4
    $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
Packit 4e8bc4
Packit 4e8bc4
    my %rv = ();
Packit 4e8bc4
    my $found_key = '';
Packit 4e8bc4
    my $found_val = '';
Packit 4e8bc4
    my $status= 0;
Packit 4e8bc4
    do {
Packit 4e8bc4
        my ($op, $status, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
Packit 4e8bc4
        if ($keylen > 0) {
Packit 4e8bc4
            $found_key = substr($data, 0, $keylen);
Packit 4e8bc4
            $found_val = substr($data, $keylen);
Packit 4e8bc4
            $rv{$found_key} = $found_val;
Packit 4e8bc4
        } else {
Packit 4e8bc4
            $found_key = '';
Packit 4e8bc4
        }
Packit 4e8bc4
    } while($found_key ne '');
Packit 4e8bc4
    return %rv;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub get {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my $key  = shift;
Packit 4e8bc4
    my ($status, $rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
Packit 4e8bc4
Packit 4e8bc4
    my $header = substr $rv, 0, 4, '';
Packit 4e8bc4
    my $flags  = unpack("N", $header);
Packit 4e8bc4
Packit 4e8bc4
    return ($status, $rv);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub get_multi {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my @keys = @_;
Packit 4e8bc4
Packit 4e8bc4
    for (my $i = 0; $i < @keys; $i++) {
Packit 4e8bc4
        $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    my $terminal = @keys + 10;
Packit 4e8bc4
    $self->send_command(::CMD_NOOP, '', '', $terminal);
Packit 4e8bc4
Packit 4e8bc4
    my %return;
Packit 4e8bc4
    my $status = 0;
Packit 4e8bc4
    while (1) {
Packit 4e8bc4
        my ($opaque, $status, $data) = $self->_handle_single_response;
Packit 4e8bc4
        last if $opaque == $terminal;
Packit 4e8bc4
Packit 4e8bc4
        my $header = substr $data, 0, 4, '';
Packit 4e8bc4
        my $flags  = unpack("N", $header);
Packit 4e8bc4
Packit 4e8bc4
        $return{$keys[$opaque]} = [$flags, $data];
Packit 4e8bc4
    }
Packit 4e8bc4
Packit 4e8bc4
    return %return if wantarray;
Packit 4e8bc4
    return \%return;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub version {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->_do_command(::CMD_VERSION, '', '');
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub flush {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->_do_command(::CMD_FLUSH, '', '');
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub add {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($key, $val, $flags, $expire) = @_;
Packit 4e8bc4
    my $extra_header = pack "NN", $flags, $expire;
Packit 4e8bc4
    my $cas = 0;
Packit 4e8bc4
    return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub set {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my $flags = 0;
Packit 4e8bc4
    my $cas = 0;
Packit 4e8bc4
    my ($key, $val, $expire) = @_;
Packit 4e8bc4
    $expire = defined $expire ? $expire : 0;
Packit 4e8bc4
    my $extra_header = pack "NN", $flags, $expire;
Packit 4e8bc4
    return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub _append_prepend {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($cmd, $key, $val, $cas) = @_;
Packit 4e8bc4
    return $self->_do_command($cmd, $key, $val, '', $cas);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub replace {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($key, $val, $flags, $expire) = @_;
Packit 4e8bc4
    my $extra_header = pack "NN", $flags, $expire;
Packit 4e8bc4
    my $cas = 0;
Packit 4e8bc4
    return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub delete {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($key) = @_;
Packit 4e8bc4
    return $self->_do_command(::CMD_DELETE, $key, '');
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub incr {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($key, $amt, $init, $exp) = @_;
Packit 4e8bc4
    $amt = 1 unless defined $amt;
Packit 4e8bc4
    $init = 0 unless defined $init;
Packit 4e8bc4
    $exp = 0 unless defined $exp;
Packit 4e8bc4
Packit 4e8bc4
    return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub decr {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    my ($key, $amt, $init, $exp) = @_;
Packit 4e8bc4
    $amt = 1 unless defined $amt;
Packit 4e8bc4
    $init = 0 unless defined $init;
Packit 4e8bc4
    $exp = 0 unless defined $exp;
Packit 4e8bc4
Packit 4e8bc4
    return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub noop {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->_do_command(::CMD_NOOP, '', '');
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
package MC::Error;
Packit 4e8bc4
Packit 4e8bc4
use strict;
Packit 4e8bc4
use warnings;
Packit 4e8bc4
Packit 4e8bc4
use constant ERR_UNKNOWN_CMD  => 0x81;
Packit 4e8bc4
use constant ERR_NOT_FOUND    => 0x1;
Packit 4e8bc4
use constant ERR_EXISTS       => 0x2;
Packit 4e8bc4
use constant ERR_TOO_BIG      => 0x3;
Packit 4e8bc4
use constant ERR_EINVAL       => 0x4;
Packit 4e8bc4
use constant ERR_NOT_STORED   => 0x5;
Packit 4e8bc4
use constant ERR_DELTA_BADVAL => 0x6;
Packit 4e8bc4
use constant ERR_AUTH_ERROR   => 0x20;
Packit 4e8bc4
Packit 4e8bc4
use overload '""' => sub {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return "Memcache Error ($self->[0]): $self->[1]";
Packit 4e8bc4
};
Packit 4e8bc4
Packit 4e8bc4
sub new {
Packit 4e8bc4
    my $class = shift;
Packit 4e8bc4
    my $error = [@_];
Packit 4e8bc4
    my $self = bless $error, (ref $class || $class);
Packit 4e8bc4
Packit 4e8bc4
    return $self;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub not_found {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->[0] == ERR_NOT_FOUND;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub exists {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->[0] == ERR_EXISTS;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub too_big {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->[0] == ERR_TOO_BIG;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub delta_badval {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->[0] == ERR_DELTA_BADVAL;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
sub auth_error {
Packit 4e8bc4
    my $self = shift;
Packit 4e8bc4
    return $self->[0] == ERR_AUTH_ERROR;
Packit 4e8bc4
}
Packit 4e8bc4
Packit 4e8bc4
unlink $sasldb;
Packit 4e8bc4
Packit 4e8bc4
# vim: filetype=perl