/*
+ * Copyright (c) 2016 Oracle. All rights reserved.
  * Copyright (c) 2005-2006 Network Appliance, Inc. All rights reserved.
  *
  * This software is available to you under a choice of one of two
 
 #define RPCDBG_FACILITY        RPCDBG_SVCXPRT
 
-/*
- * Decodes a read chunk list. The expected format is as follows:
- *    descrim  : xdr_one
- *    position : __be32 offset into XDR stream
- *    handle   : __be32 RKEY
- *    . . .
- *  end-of-list: xdr_zero
- */
-static __be32 *decode_read_list(__be32 *va, __be32 *vaend)
+static __be32 *xdr_check_read_list(__be32 *p, __be32 *end)
 {
-       struct rpcrdma_read_chunk *ch = (struct rpcrdma_read_chunk *)va;
+       __be32 *next;
 
-       while (ch->rc_discrim != xdr_zero) {
-               if (((unsigned long)ch + sizeof(struct rpcrdma_read_chunk)) >
-                   (unsigned long)vaend) {
-                       dprintk("svcrdma: vaend=%p, ch=%p\n", vaend, ch);
+       while (*p++ != xdr_zero) {
+               next = p + rpcrdma_readchunk_maxsz - 1;
+               if (next > end)
                        return NULL;
-               }
-               ch++;
+               p = next;
        }
-       return &ch->rc_position;
+       return p;
 }
 
-/*
- * Decodes a write chunk list. The expected format is as follows:
- *    descrim  : xdr_one
- *    nchunks  : <count>
- *       handle   : __be32 RKEY           ---+
- *       length   : __be32 <len of segment>  |
- *       offset   : remove va                + <count>
- *       . . .                               |
- *                                        ---+
- */
-static __be32 *decode_write_list(__be32 *va, __be32 *vaend)
+static __be32 *xdr_check_write_list(__be32 *p, __be32 *end)
 {
-       unsigned long start, end;
-       int nchunks;
-
-       struct rpcrdma_write_array *ary =
-               (struct rpcrdma_write_array *)va;
-
-       /* Check for not write-array */
-       if (ary->wc_discrim == xdr_zero)
-               return &ary->wc_nchunks;
+       __be32 *next;
 
-       if ((unsigned long)ary + sizeof(struct rpcrdma_write_array) >
-           (unsigned long)vaend) {
-               dprintk("svcrdma: ary=%p, vaend=%p\n", ary, vaend);
-               return NULL;
-       }
-       nchunks = be32_to_cpu(ary->wc_nchunks);
-
-       start = (unsigned long)&ary->wc_array[0];
-       end = (unsigned long)vaend;
-       if (nchunks < 0 ||
-           nchunks > (SIZE_MAX - start) / sizeof(struct rpcrdma_write_chunk) ||
-           (start + (sizeof(struct rpcrdma_write_chunk) * nchunks)) > end) {
-               dprintk("svcrdma: ary=%p, wc_nchunks=%d, vaend=%p\n",
-                       ary, nchunks, vaend);
-               return NULL;
+       while (*p++ != xdr_zero) {
+               next = p + 1 + be32_to_cpup(p) * rpcrdma_segment_maxsz;
+               if (next > end)
+                       return NULL;
+               p = next;
        }
-       /*
-        * rs_length is the 2nd 4B field in wc_target and taking its
-        * address skips the list terminator
-        */
-       return &ary->wc_array[nchunks].wc_target.rs_length;
+       return p;
 }
 
-static __be32 *decode_reply_array(__be32 *va, __be32 *vaend)
+static __be32 *xdr_check_reply_chunk(__be32 *p, __be32 *end)
 {
-       unsigned long start, end;
-       int nchunks;
-       struct rpcrdma_write_array *ary =
-               (struct rpcrdma_write_array *)va;
-
-       /* Check for no reply-array */
-       if (ary->wc_discrim == xdr_zero)
-               return &ary->wc_nchunks;
-
-       if ((unsigned long)ary + sizeof(struct rpcrdma_write_array) >
-           (unsigned long)vaend) {
-               dprintk("svcrdma: ary=%p, vaend=%p\n", ary, vaend);
-               return NULL;
-       }
-       nchunks = be32_to_cpu(ary->wc_nchunks);
-
-       start = (unsigned long)&ary->wc_array[0];
-       end = (unsigned long)vaend;
-       if (nchunks < 0 ||
-           nchunks > (SIZE_MAX - start) / sizeof(struct rpcrdma_write_chunk) ||
-           (start + (sizeof(struct rpcrdma_write_chunk) * nchunks)) > end) {
-               dprintk("svcrdma: ary=%p, wc_nchunks=%d, vaend=%p\n",
-                       ary, nchunks, vaend);
-               return NULL;
+       __be32 *next;
+
+       if (*p++ != xdr_zero) {
+               next = p + 1 + be32_to_cpup(p) * rpcrdma_segment_maxsz;
+               if (next > end)
+                       return NULL;
+               p = next;
        }
-       return (__be32 *)&ary->wc_array[nchunks];
+       return p;
 }
 
 /**
  */
 int svc_rdma_xdr_decode_req(struct xdr_buf *rq_arg)
 {
-       struct rpcrdma_msg *rmsgp;
-       __be32 *va, *vaend;
-       unsigned int len;
-       u32 hdr_len;
+       __be32 *p, *end, *rdma_argp;
+       unsigned int hdr_len;
 
        /* Verify that there's enough bytes for header + something */
-       if (rq_arg->len <= RPCRDMA_HDRLEN_ERR) {
-               dprintk("svcrdma: header too short = %d\n",
-                       rq_arg->len);
-               return -EINVAL;
-       }
+       if (rq_arg->len <= RPCRDMA_HDRLEN_ERR)
+               goto out_short;
 
-       rmsgp = (struct rpcrdma_msg *)rq_arg->head[0].iov_base;
-       if (rmsgp->rm_vers != rpcrdma_version) {
-               dprintk("%s: bad version %u\n", __func__,
-                       be32_to_cpu(rmsgp->rm_vers));
-               return -EPROTONOSUPPORT;
-       }
+       rdma_argp = rq_arg->head[0].iov_base;
+       if (*(rdma_argp + 1) != rpcrdma_version)
+               goto out_version;
 
-       switch (be32_to_cpu(rmsgp->rm_type)) {
-       case RDMA_MSG:
-       case RDMA_NOMSG:
+       switch (*(rdma_argp + 3)) {
+       case rdma_msg:
+       case rdma_nomsg:
                break;
 
-       case RDMA_DONE:
-               /* Just drop it */
-               dprintk("svcrdma: dropping RDMA_DONE message\n");
-               return 0;
-
-       case RDMA_ERROR:
-               /* Possible if this is a backchannel reply.
-                * XXX: We should cancel this XID, though.
-                */
-               dprintk("svcrdma: dropping RDMA_ERROR message\n");
-               return 0;
-
-       case RDMA_MSGP:
-               /* Pull in the extra for the padded case, bump our pointer */
-               rmsgp->rm_body.rm_padded.rm_align =
-                       be32_to_cpu(rmsgp->rm_body.rm_padded.rm_align);
-               rmsgp->rm_body.rm_padded.rm_thresh =
-                       be32_to_cpu(rmsgp->rm_body.rm_padded.rm_thresh);
-
-               va = &rmsgp->rm_body.rm_padded.rm_pempty[4];
-               rq_arg->head[0].iov_base = va;
-               len = (u32)((unsigned long)va - (unsigned long)rmsgp);
-               rq_arg->head[0].iov_len -= len;
-               if (len > rq_arg->len)
-                       return -EINVAL;
-               return len;
-       default:
-               dprintk("svcrdma: bad rdma procedure (%u)\n",
-                       be32_to_cpu(rmsgp->rm_type));
-               return -EINVAL;
-       }
+       case rdma_done:
+               goto out_drop;
 
-       /* The chunk list may contain either a read chunk list or a write
-        * chunk list and a reply chunk list.
-        */
-       va = &rmsgp->rm_body.rm_chunks[0];
-       vaend = (__be32 *)((unsigned long)rmsgp + rq_arg->len);
-       va = decode_read_list(va, vaend);
-       if (!va) {
-               dprintk("svcrdma: failed to decode read list\n");
-               return -EINVAL;
-       }
-       va = decode_write_list(va, vaend);
-       if (!va) {
-               dprintk("svcrdma: failed to decode write list\n");
-               return -EINVAL;
-       }
-       va = decode_reply_array(va, vaend);
-       if (!va) {
-               dprintk("svcrdma: failed to decode reply chunk\n");
-               return -EINVAL;
+       case rdma_error:
+               goto out_drop;
+
+       default:
+               goto out_proc;
        }
 
-       rq_arg->head[0].iov_base = va;
-       hdr_len = (unsigned long)va - (unsigned long)rmsgp;
+       end = (__be32 *)((unsigned long)rdma_argp + rq_arg->len);
+       p = xdr_check_read_list(rdma_argp + 4, end);
+       if (!p)
+               goto out_inval;
+       p = xdr_check_write_list(p, end);
+       if (!p)
+               goto out_inval;
+       p = xdr_check_reply_chunk(p, end);
+       if (!p)
+               goto out_inval;
+       if (p > end)
+               goto out_inval;
+
+       rq_arg->head[0].iov_base = p;
+       hdr_len = (unsigned long)p - (unsigned long)rdma_argp;
        rq_arg->head[0].iov_len -= hdr_len;
        return hdr_len;
+
+out_short:
+       dprintk("svcrdma: header too short = %d\n", rq_arg->len);
+       return -EINVAL;
+
+out_version:
+       dprintk("svcrdma: bad xprt version: %u\n",
+               be32_to_cpup(rdma_argp + 1));
+       return -EPROTONOSUPPORT;
+
+out_drop:
+       dprintk("svcrdma: dropping RDMA_DONE/ERROR message\n");
+       return 0;
+
+out_proc:
+       dprintk("svcrdma: bad rdma procedure (%u)\n",
+               be32_to_cpup(rdma_argp + 3));
+       return -EINVAL;
+
+out_inval:
+       dprintk("svcrdma: failed to parse transport header\n");
+       return -EINVAL;
 }
 
 int svc_rdma_xdr_encode_error(struct svcxprt_rdma *xprt,