/*= -*- c-basic-offset: 4; indent-tabs-mode: nil; -*-
 *
 * n4: A fast userspace NFSv4 server.
 * $Id: rpcspank.c,v 1.20 2001/06/21 14:07:40 mbp Exp $
 * 
 * Copyright (C) 2001 by Martin Pool <mbp@samba.org>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <includes.h>


struct {
    char const *name;
    n4err (*fn)(n4outbuf *);
    n4err (*infn)(n4inbuf *);
    char const *comment;
} tests[] = {
    { "badrpcversion",
      n4test_spit_badrpcversion,
      n4test_swallow_generic,
      "packet with the wrong RPC version" },
    
    { "reply",
      n4spank_spit_reply,
      n4test_swallow_generic,
      "an unexpected reply" },
    
    { "short0",
      n4test_spit_short0,
      n4test_swallow_generic,
      "packet with nothing but a XID" },
    
    { "shortauth",
      n4test_spit_shortauth,
      n4test_swallow_generic,
      "packet truncated before authentication data" },

    { "noargs",
      n4test_spit_noargs,
      n4test_swallow_generic,
      "CALL COMPOUND with no RPC arguments" },
    
    { "noops",
      n4spank_spit_noops,
      n4test_swallow_compound,
      "CALL COMPOUND with no COMPOUND operations"},
    
    { "minorvers",
      n4spank_spit_minorvers,
      n4test_swallow_compound,
      "COMPOUND with NFS4 minor version 69" },
    
    { NULL, NULL, NULL, NULL }
};


static void usage(void);


int main(int argc, char **argv)
{
    n4err     err;
    n4ccon  *conn;
    int         i;
    TALLOC_CTX  *pool;
    
    CHECK_ERR(n4net_ignore_sigpipe());

    if (argc != 3)
        usage();

    n4trace_level = N4LOG_DEBUG;

    CHECK_ERR(n4cli_init_context(&conn));
    CHECK_ERR(n4cli_open_tcp(conn, argv[1], N4_NFS_PORT));

    pool = talloc_init();

    for (i = 0; tests[i].name; i++) {
        if (!strcmp(tests[i].name, argv[2])) {
            err = n4test_synch_call(pool, conn, tests[i].fn, tests[i].infn);
            goto done;
        }
    }
    n4log_error("no such test as %s", argv[2]);
    
 done:
    talloc_destroy(pool);

    return 0;
}


static void usage(void) {
    int i;
    
    fprintf(stderr, "usage: rpctest SERVER TESTCASE\n"
            "available test cases:\n");
    for (i = 0; tests[i].name; i++)
        fprintf(stderr, "%15s: %s\n", tests[i].name, tests[i].comment);
    exit(1);
}


n4err n4test_spit_short0(n4outbuf *b)
{
    n4outbuf_expand(b, 1);

    PACK(b, 0x12345678);

    return N4OK;
}


n4err n4test_spit_badrpcversion(n4outbuf *b)
{
    n4outbuf_expand(b, 10);
    
    PACK(b, g_htonl(N4_SEXY_XID));
    PACK(b, g_htonl(RPC_CALL));
    PACK(b, g_htonl(3));        /* this is wrong */
    PACK(b, g_htonl(NFS_PROGRAM));
    PACK(b, g_htonl(NFS4_VERSION));
    PACK(b, g_htonl(NFSPROC4_NULL));
    PACK(b, g_htonl(RPC_AUTH_NONE));
    PACK(b, g_htonl(0));                   /* null opaque */
    PACK(b, g_htonl(RPC_AUTH_NONE));
    PACK(b, g_htonl(0));

    return N4OK;
}


n4err n4spank_spit_reply(n4outbuf *b)
{
    n4outbuf_expand(b, 6);
    
    PACK(b, g_htonl(N4_SEXY_XID));
    PACK(b, g_htonl(RPC_REPLY));
    PACK(b, g_htonl(RPC_MSG_ACCEPTED));        
    PACK(b, g_htonl(RPC_AUTH_NONE));
    PACK(b, g_htonl(0));        /* auth length */
    PACK(b, g_htonl(RPC_SUCCESS));

    return N4OK;
}


n4err n4test_spit_shortauth(n4outbuf *b)
{
    n4outbuf_expand(b, 6);
    
    PACK(b, g_htonl(N4_SEXY_XID));
    PACK(b, g_htonl(RPC_CALL));
    PACK(b, g_htonl(RPC_VERSION2));
    PACK(b, g_htonl(NFS_PROGRAM));
    PACK(b, g_htonl(NFS4_VERSION));
    PACK(b, g_htonl(NFSPROC4_NULL));

    return N4OK;
}



n4err n4test_spit_noargs(n4outbuf *b)
{
    return n4pack_rpc_call_hdr(b, N4_SEXY_XID, NFS_PROGRAM, NFS4_VERSION,
                               NFSPROC4_COMPOUND);
}


n4err n4spank_spit_noops(n4outbuf *b)
{
    char const *tag = "test truncated COMPOUND call";
    
    CHECK_ERR(n4pack_rpc_call_hdr(b, N4_SEXY_XID, NFS_PROGRAM, NFS4_VERSION,
                                  NFSPROC4_COMPOUND));
    CHECK_ERR(n4pack_compound_call_hdr(b, tag, 42));

    return N4OK;    
}


n4err n4spank_spit_minorvers(n4outbuf *b)
{
    char const *tag = "NFS4 with minor version 69";
    size_t tag_len = strlen(tag);
    
    CHECK_ERR(n4pack_rpc_call_hdr(b, N4_SEXY_XID,
                                  NFS_PROGRAM, NFS4_VERSION, NFSPROC4_COMPOUND));
    CHECK_ERR(n4outbuf_expand(b, 1 + 1 + PADDED(tag_len) + 1));
    CHECK_ERR(n4xdr_pack_var(b, tag_len, tag));
    PACK(b, g_htonl(69));       /* minor version */
    PACK(b, 0);                 /* n_ops */

    return N4OK;    
}


n4err n4test_swallow_generic(n4inbuf *b)
{
    guint32 xid;

    CHECK_ERR(n4unpack_rpc_reply_hdr(b, &xid));
        
    return N4OK;
}


n4err n4test_swallow_compound(n4inbuf *b)
{
    guint32 xid;
    n4err   overall_err;
    int     n_ops;
    char const *tag;
    size_t      tag_len;

    CHECK_ERR(n4unpack_rpc_reply_hdr(b, &xid));
    CHECK_ERR(n4unpack_compound_reply_hdr(b, &tag, &tag_len, &overall_err, &n_ops));
        
    return N4OK;
}


n4err n4test_synch_call(TALLOC_CTX *pool, n4ccon *conn,
                        n4err (*spit_fn)(n4outbuf *),
                        n4err (*swallow_fn)(n4inbuf *))
{
    n4outbuf       outb;
    n4inbuf        inb;

    CHECK_ERR(n4buf_init_outbuf(&outb, pool));
    CHECK_ERR(spit_fn(&outb));
    CHECK_ERR(n4buf_check_outbuf(&outb));
    CHECK_ERR(n4xdr_send_outbuf(conn->fd, &outb));
    CHECK_ERR(n4xdr_recv_inbuf(pool, conn->fd, &inb));
    CHECK_ERR(swallow_fn(&inb));

    return N4OK;
}

