Blob Blame History Raw
/*
 * Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact information: Carbonite Inc., 756 N Pastoria Ave
 * Sunnyvale, CA 94085, or: http://www.zmanda.com
 */

%module "Amanda::IPC::Binary"
%include "amglue/amglue.swg"
%include "exception.i"

%include "Amanda/IPC/Binary.pod"

%{
#include <glib.h>
#include "ipc-binary.h"
%}

/*
 * types
 */
typedef struct ipc_binary_proto_t ipc_binary_proto_t;
typedef struct ipc_binary_cmd_t ipc_binary_cmd_t;
typedef struct ipc_binary_channel_t ipc_binary_channel_t;
typedef struct ipc_binary_message_t ipc_binary_message_t;

/*
 * typemaps
 */

%typemap(out) ipc_binary_message_t * {
    static HV *amanda_xfer_msg_stash = NULL;
    HV *hash;
    SV *rv;
    AV *args;
    int i, nargs;

    if ($1) {
	hash = newHV();
	rv = newRV_noinc((SV *)hash);

	/* bless the rv as an Amanda::Xfer::Msg object */
	if (!amanda_xfer_msg_stash) {
	    amanda_xfer_msg_stash = gv_stashpv("Amanda::IPC::Binary::Message", GV_ADD);
	}
	sv_bless(rv, amanda_xfer_msg_stash);

	args = newAV();
	hv_store(hash, "cmd_id", 6, newSViv($1->cmd_id), 0);
	hv_store(hash, "args", 4, newRV_noinc((SV *)args), 0);

	/* loop over all messages, using av_store to insert the args which are present;
	* this will fill in undef's where necessary */
	for (i = 0; i < $1->n_args; i++) {
	    if ($1->args[i].data == NULL)
		continue;

	    g_assert(NULL !=
		av_store(args, i, newSVpvn($1->args[i].data, $1->args[i].len)));
	}

	/* we don't need the C data any more */
	ipc_binary_free_message($1);

	$result = rv;
	argvi++;
    }
}

%typemap(in) ipc_binary_message_t * {
    HV *hv;
    AV *av;
    SV **svp;
    int cmd_id;
    ipc_binary_channel_t *chan = NULL;
    ipc_binary_message_t *msg;
    int i, len;

    if (!SvROK($input) || SvTYPE(SvRV($input)) != SVt_PVHV
	    || !sv_isa($input, "Amanda::IPC::Binary::Message"))
	SWIG_exception(SWIG_TypeError, "Expected an Amanda::IPC::Binary::Message");

    hv = (HV *)SvRV($input);

    /* get cmd_id */
    svp = hv_fetch(hv, "cmd_id", 6, FALSE);
    if (!svp || !SvIOK(*svp))
	SWIG_exception(SWIG_TypeError, "'cmd_id' key missing or not numeric");
    cmd_id = SvIV(*svp);

    /* get channel */
    svp = hv_fetch(hv, "chan", 4, FALSE);
    if (!svp || SWIG_ConvertPtr(*svp, (void **)&chan,
				$descriptor(ipc_binary_channel_t *), 0) == -1
	     || !chan)
	SWIG_exception(SWIG_TypeError, "'chan' key missing or incorrect");

    /* get args */
    svp = hv_fetch(hv, "args", 4, FALSE);
    if (!svp || !SvROK(*svp) || SvTYPE(SvRV(*svp)) != SVt_PVAV)
	SWIG_exception(SWIG_TypeError, "'args' key missing or not an arrayref");
    av = (AV *)SvRV(*svp);

    msg = ipc_binary_new_message(chan, cmd_id);

    len = av_len(av);
    for (i = 0; i <= len; i++) {
	SV **elt = av_fetch(av, i, 0);
	STRLEN datasize;
	gpointer data;

	if (elt && SvPOK(*elt)) {
	    data = (gpointer)SvPV(*elt, datasize);
	    ipc_binary_add_arg(msg, i, datasize, data, 0);
	}
    }

    $1 = msg;
}

/*
 * functions
 */

ipc_binary_proto_t *ipc_binary_proto_new(
    guint16 magic);

ipc_binary_cmd_t *ipc_binary_proto_add_cmd(
    ipc_binary_proto_t *proto,
    guint16 id);

void ipc_binary_cmd_add_arg(
    ipc_binary_cmd_t *cmd,
    guint16 id,
    guint8 flags);

/* flag symbols for use in perl; values don't matter */
enum {
    IPC_BINARY_STRING,
    IPC_BINARY_OPTIONAL,
};
amglue_export(
    $IPC_BINARY_STRING $IPC_BINARY_OPTIONAL);

ipc_binary_channel_t *ipc_binary_new_channel(
    ipc_binary_proto_t *proto);

void ipc_binary_free_channel(
    ipc_binary_channel_t *channel);

ipc_binary_message_t *ipc_binary_read_message(
    ipc_binary_channel_t *chan,
    int fd);

int ipc_binary_write_message(
    ipc_binary_channel_t *chan,
    int fd,
    ipc_binary_message_t *msg);

void ipc_binary_feed_data(
    ipc_binary_channel_t *chan,
    gsize size,
    gpointer data);

void ipc_binary_data_transmitted(
    ipc_binary_channel_t *chan,
    gsize size);

ipc_binary_message_t *ipc_binary_poll_message(
    ipc_binary_channel_t *chan);

void ipc_binary_queue_message(
    ipc_binary_channel_t *chan,
    ipc_binary_message_t *msg);


/*
 * Perl layer
 */

%perlcode %{

use Carp;
push @EXPORT, qw( magic command new message );

# a map from package name to protocol
my %protos_by_pkg;

sub magic {
    my ($magic) = @_;
    my $caller = caller;

    croak "magic already set for this protocol"
	if (exists $protos_by_pkg{$caller});

    $protos_by_pkg{$caller} = ipc_binary_proto_new($magic);
}

sub command {
    my ($cmd_id, @args) = @_;
    my $caller = caller;

    croak "magic not set for this protocol"
	unless (exists $protos_by_pkg{$caller});

    croak "command args must be specified in pairs"
	unless (@args % 2 == 0);

    my $proto = $protos_by_pkg{$caller};
    $cmd = ipc_binary_proto_add_cmd($proto, $cmd_id);

    while (@args) {
	my $arg = shift @args;
	my $flags = shift @args;
	ipc_binary_cmd_add_arg($cmd, $arg, $flags);
    }
}

##
# Class Methods

sub new {
    my $class = shift;

    my $self = bless {
	chan => ipc_binary_new_channel($protos_by_pkg{$class}),
    }, $class;
}

sub message {
    my $self = shift;
    my ($cmd_id, @args) = @_;

    $self = bless {
	cmd_id => $cmd_id,
	chan => $self->{'chan'},
	args => [],
    }, "Amanda::IPC::Binary::Message";


    while (@args) {
	my $arg = shift @args;
	my $val = shift @args;
	$self->{'args'}[$arg] = $val;
    }

    return $self;
}

sub close {
    if ($self->{'chan'}) {
	ipc_binary_free_channel($self->{'chan'});
	$self->{'chan'} = undef;
    }
}

*DESTROY = *close;

##
# Blocking interface

sub read_message {
    my $self = shift;
    my ($fd) = @_;

    return ipc_binary_read_message($self->{'chan'}, $fd);
}

sub write_message {
    my $self = shift;
    my ($fd, $msg) = @_;

    if (ipc_binary_write_message($self->{'chan'}, $fd, $msg) < 0) {
	return 0;
    }
    return 1;
}

##
# Nonblocking interface -- TODO

##
# Message structure

package Amanda::IPC::Binary::Message;

# (constructor is the protocol's C<message> method)

# format:
# { cmd_id => $cmd_id,
#   chan => $channel,
#   args => [ $arg0, $arg1, .. ],
# }

sub get_cmd {
    return $self->{'cmd_id'};
}

sub get_arg {
    my ($self, $arg_id) = @_;

    return $self->{'args'}[$arg_id];
}

sub set_arg {
    my ($self, $arg_id, $value) = @_;
    $self->{'args'}[$arg_id] = $value;
}

package Amanda::IPC::Binary;

%}