root/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2013-2021 Tintri by DDN, Inc. All rights reserved.
 */

/*
 * SMB mbuf marshaling encode/decode.
 */

#include <smbsrv/smb_kproto.h>


#define MALLOC_QUANTUM  80

#define DECODE_NO_ERROR         0
#define DECODE_NO_MORE_DATA     1
#define DECODE_ALLOCATION_ERROR 2
#define DECODE_CONVERSION_ERROR 3

static int mbc_marshal_make_room(mbuf_chain_t *, int32_t);
static void mbc_marshal_store_byte(mbuf_chain_t *, uint8_t);
static int mbc_marshal_put_char(mbuf_chain_t *mbc, uint8_t);
static int mbc_marshal_put_short(mbuf_chain_t *mbc, uint16_t);
static int mbc_marshal_put_long(mbuf_chain_t *mbc, uint32_t);
static int mbc_marshal_put_long_long(mbuf_chain_t *mbc, uint64_t);
static int mbc_marshal_put_oem_string(mbuf_chain_t *, char *, int);
static int mbc_marshal_put_unicode_string(mbuf_chain_t *, char *, int);
static int mbc_marshal_put_uio(mbuf_chain_t *, struct uio *);
static int mbc_marshal_put_mbufs(mbuf_chain_t *mbc, mbuf_t *m);
static int mbc_marshal_put_mbuf_chain(mbuf_chain_t *mbc, mbuf_chain_t *nmbc);
static uint8_t mbc_marshal_fetch_byte(mbuf_chain_t *mbc);
static int mbc_marshal_get_char(mbuf_chain_t *mbc, uint8_t *data);
static int mbc_marshal_get_short(mbuf_chain_t *mbc, uint16_t *data);
static int mbc_marshal_get_long(mbuf_chain_t *mbc, uint32_t *data);
static uint64_t qswap(uint64_t ll);
static int mbc_marshal_get_odd_long_long(mbuf_chain_t *mbc, uint64_t *data);
static int mbc_marshal_get_long_long(mbuf_chain_t *mbc, uint64_t *data);
static int mbc_marshal_get_oem_string(smb_request_t *, mbuf_chain_t *,
    char **, int);
static int mbc_marshal_get_unicode_string(smb_request_t *, mbuf_chain_t *,
    char **, int);
static int mbc_marshal_get_mbufs(mbuf_chain_t *, int32_t, mbuf_t **);
static int mbc_marshal_get_mbuf_chain(mbuf_chain_t *, int32_t, mbuf_chain_t *);
static int mbc_marshal_get_uio(mbuf_chain_t *, struct uio *);
static int mbc_marshal_get_skip(mbuf_chain_t *, uint_t);

/*
 * smb_mbc_vdecodef
 *
 * This function reads the contents of the mbc chain passed in under the list
 * of arguments passed in.
 *
 * The format string provides a description of the parameters passed in as well
 * as an action to be taken by smb_mbc_vdecodef().
 *
 *      %       Pointer to an SMB request structure (smb_request_t *). There
 *              should be only one of these in the string.
 *
 *      C       Pointer to an mbuf chain. Copy to that mbuf chain the number of
 *              bytes specified (number preceding C).
 *
 *      m       Pointer to an mbuf. Copy to that mbuf the number of bytes
 *              specified (number preceding m).
 *
 *      M       Read the 32 bit value at the current location of the mbuf chain
 *              and check if it matches the signature of an SMB1 request (SMBx).
 *
 *      N       Read the 32 bit value at the current location of the mbuf chain
 *              and check if it matches the signature of an SMB2 request (SMBx).
 *
 *      b       Pointer to a buffer. Copy to that buffer the number of bytes
 *              specified (number preceding b).
 *
 *      c       Same as 'b'.
 *
 *      w       Pointer to a word (16bit value). Copy the next 16bit value into
 *              that location.
 *
 *      l       Pointer to a long (32bit value). Copy the next 32bit value into
 *              that location.
 *
 *      q       Pointer to a quad (64bit value). Copy the next 64bit value into
 *              that location.
 *
 *      Q       Same as above with a call to qswap().
 *
 *      B       Pointer to a vardata_block structure. That structure is used to
 *              retrieve data from the mbuf chain (an iovec type structure is
 *              embedded in a vardata_block).
 *
 *      D       Pointer to a vardata_block structure. That structure is used to
 *              retrieve data from the mbuf chain, however, two fields of the
 *              vardata_block structure (tag and len) are first initialized
 *              using the mbuf chain itself.
 *
 *      V       Same as 'D'.
 *
 *      L
 *
 *      A
 *
 *      P       Same as 'A'
 *
 *      S       Same as 'A'
 *
 *      u       Pointer to a string pointer. Allocate memory and retrieve the
 *              string at the current location in the mbuf chain. Store the
 *              address to the buffer allocated at the address specified by
 *              the pointer. In addition if an sr was passed and it indicates
 *              that the string is an unicode string, convert it.
 *
 *      s       Same as 'u' without convertion.
 *
 *      U       Same as 'u'. The string to retrieve is unicode.
 *
 *      y       Pointer to a 32bit value. Read the dos time at the current mbuf
 *              chain location, convert it to unix time and store it at the
 *              location indicated by the pointer.
 *
 *      Y       Same as 'y' bt the dos time coded in the mbuf chain is inverted.
 *
 *      .       Skip the number of bytes indicated by the number preceding '.'.
 *
 *      ,       Same as '.' but take in account it is an unicode string.
 */
int
smb_mbc_vdecodef(mbuf_chain_t *mbc, const char *fmt, va_list ap)
{
        uint8_t         c;
        uint8_t         cval;
        uint8_t         *cvalp;
        char            **charpp;
        uint16_t        wval;
        uint16_t        *wvalp;
        uint32_t        *lvalp;
        uint64_t        *llvalp;
        smb_vdb_t       *vdp;
        smb_request_t   *sr = NULL;
        uint32_t        lval;
        int             unicode = 0;
        int             repc;
        boolean_t       repc_specified;

        while ((c = *fmt++) != 0) {
                repc_specified = B_FALSE;
                repc = 1;

                if ('0' <= c && c <= '9') {
                        repc = 0;
                        do {
                                repc = repc * 10 + c - '0';
                                c = *fmt++;
                        } while ('0' <= c && c <= '9');
                        repc_specified = B_TRUE;
                } else if (c == '#') {
                        repc = va_arg(ap, int);
                        c = *fmt++;
                        repc_specified = B_TRUE;
                }

                switch (c) {
                case '%':
                        sr = va_arg(ap, struct smb_request *);
                        if (sr->session->dialect >= SMB_VERS_2_BASE) {
                                unicode = 1;
                                break;
                        }
                        unicode = sr->smb_flg2 & SMB_FLAGS2_UNICODE;
                        break;

                case 'C':       /* Mbuf_chain */
                        if (mbc_marshal_get_mbuf_chain(mbc, repc,
                            va_arg(ap, mbuf_chain_t *)) != 0)
                                return (-1);
                        break;

                case 'm':       /* struct_mbuf */
                        if (mbc_marshal_get_mbufs(mbc, repc,
                            va_arg(ap, mbuf_t **)) != 0)
                                return (-1);
                        break;

                case 'M':
                        if (mbc_marshal_get_long(mbc, &lval) != 0)
                                return (-1);
                        if (lval != 0x424D53FF) /* 0xFF S M B */
                                return (-1);
                        break;

                case 'N':
                        if (mbc_marshal_get_long(mbc, &lval) != 0)
                                return (-1);
                        if (lval != 0x424D53FE) /* 0xFE S M B */
                                return (-1);
                        break;

                case 'b':
                case 'c':
                        cvalp = va_arg(ap, uint8_t *);
                        if (MBC_ROOM_FOR(mbc, repc) == 0)
                                /* Data will never be available */
                                return (-1);

                        while (repc-- > 0)
                                *cvalp++ = mbc_marshal_fetch_byte(mbc);
                        break;

                case 'w':
                        wvalp = va_arg(ap, uint16_t *);
                        while (repc-- > 0)
                                if (mbc_marshal_get_short(mbc, wvalp++) != 0)
                                        return (-1);
                        break;

                case 'l':
                        lvalp = va_arg(ap, uint32_t *);
                        while (repc-- > 0)
                                if (mbc_marshal_get_long(mbc, lvalp++) != 0)
                                        return (-1);
                        break;

                case 'q':
                        llvalp = va_arg(ap, uint64_t *);
                        while (repc-- > 0)
                                if (mbc_marshal_get_long_long(
                                    mbc, llvalp++) != 0)
                                        return (-1);
                        break;

                case 'Q':
                        llvalp = va_arg(ap, uint64_t *);
                        while (repc-- > 0)
                                if (mbc_marshal_get_odd_long_long(
                                    mbc, llvalp++) != 0)
                                        return (-1);
                        break;

                case 'B':
                        vdp = va_arg(ap, struct vardata_block *);
                        vdp->vdb_tag = 0;
                        vdp->vdb_len = repc;
                        vdp->vdb_uio.uio_iov = &vdp->vdb_iovec[0];
                        vdp->vdb_uio.uio_iovcnt = MAX_IOVEC;
                        vdp->vdb_uio.uio_extflg = UIO_COPY_DEFAULT;
                        vdp->vdb_uio.uio_resid = repc;
                        if (mbc_marshal_get_uio(mbc, &vdp->vdb_uio) != 0)
                                return (-1);
                        break;

                case 'D':
                case 'V':
                        vdp = va_arg(ap, struct vardata_block *);
                        if (mbc_marshal_get_char(mbc, &vdp->vdb_tag) != 0)
                                return (-1);
                        if (mbc_marshal_get_short(mbc, &wval) != 0)
                                return (-1);
                        vdp->vdb_len = (uint32_t)wval;
                        vdp->vdb_uio.uio_iov = &vdp->vdb_iovec[0];
                        vdp->vdb_uio.uio_iovcnt = MAX_IOVEC;
                        vdp->vdb_uio.uio_extflg = UIO_COPY_DEFAULT;
                        vdp->vdb_uio.uio_resid = vdp->vdb_len;
                        if (vdp->vdb_len != 0) {
                                if (mbc_marshal_get_uio(mbc,
                                    &vdp->vdb_uio) != 0)
                                        return (-1);
                        }
                        break;

                case 'L':
                        if (mbc_marshal_get_char(mbc, &cval) != 0)
                                return (-1);
                        if (cval != 2)
                                return (-1);
                        goto oem_conversion;

                case 'A':
                case 'S':
                        if (mbc_marshal_get_char(mbc, &cval) != 0)
                                return (-1);
                        if (((c == 'A' || c == 'S') && cval != 4) ||
                            (c == 'L' && cval != 2))
                                return (-1);
                        /* FALLTHROUGH */

                case 'u': /* Convert from unicode if flags are set */
                        if (unicode)
                                goto unicode_translation;
                        /* FALLTHROUGH */

                case 's':       /* get OEM string */
oem_conversion:
                        ASSERT(sr != NULL);
                        charpp = va_arg(ap, char **);
                        if (!repc_specified)
                                repc = 0;
                        if (mbc_marshal_get_oem_string(sr,
                            mbc, charpp, repc) != 0)
                                return (-1);
                        break;

                case 'U':       /* get UTF-16 string */
unicode_translation:
                        ASSERT(sr != 0);
                        charpp = va_arg(ap, char **);
                        if (!repc_specified)
                                repc = 0;
                        if (mbc_marshal_get_unicode_string(sr,
                            mbc, charpp, repc) != 0)
                                return (-1);
                        break;

                case 'Y': /* dos time to unix time tt/dd */
                        lvalp = va_arg(ap, uint32_t *);
                        while (repc-- > 0) {
                                short   d, t;

                                if (mbc_marshal_get_short(mbc,
                                    (uint16_t *)&t) != 0)
                                        return (-1);
                                if (mbc_marshal_get_short(mbc,
                                    (uint16_t *)&d) != 0)
                                        return (-1);
                                *lvalp++ = smb_time_dos_to_unix(d, t);
                        }
                        break;

                case 'y': /* dos time to unix time dd/tt */
                        lvalp = va_arg(ap, uint32_t *);
                        while (repc-- > 0) {
                                short   d, t;

                                if (mbc_marshal_get_short(mbc,
                                    (uint16_t *)&d) != 0)
                                        return (-1);
                                if (mbc_marshal_get_short(mbc,
                                    (uint16_t *)&t) != 0)
                                        return (-1);
                                *lvalp++ = smb_time_dos_to_unix(d, t);
                        }
                        break;

                case ',':
                        if (unicode)
                                repc *= 2;
                        /* FALLTHROUGH */

                case '.':
                        if (mbc_marshal_get_skip(mbc, repc) != 0)
                                return (-1);
                        break;

                default:
                        ASSERT(0);
                        return (-1);
                }
        }
        return (0);
}

/*
 * smb_mbc_decodef
 *
 * This function reads the contents of the mbc chain passed in under the
 * control of the format fmt.
 *
 * (for a description of the format string see smb_mbc_vencodef()).
 */
int
smb_mbc_decodef(mbuf_chain_t *mbc, const char *fmt, ...)
{
        int     xx;
        va_list ap;

        va_start(ap, fmt);
        xx = smb_mbc_vdecodef(mbc, fmt, ap);
        va_end(ap);
        return (xx);
}

/*
 * smb_mbc_peek
 *
 * This function reads the contents of the mbc passed in at the specified offset
 * under the control of the format fmt. The offset of the chain passed in is not
 * modified.
 *
 * (for a description of the format string see smb_mbc_vdecodef()).
 */
int
smb_mbc_peek(mbuf_chain_t *mbc, int offset, const char *fmt, ...)
{
        mbuf_chain_t    tmp;
        va_list         ap;
        int             xx;

        va_start(ap, fmt);

        (void) MBC_SHADOW_CHAIN(&tmp, mbc, offset, mbc->max_bytes - offset);
        xx = smb_mbc_vdecodef(&tmp, fmt, ap);
        va_end(ap);
        return (xx);
}

/*
 * smb_mbc_vencodef
 *
 * This function builds a stream of bytes in the mbc chain passed in under the
 * control of the list of arguments passed in.
 *
 * The format string provides a description of the parameters passed in as well
 * as an action to be taken by smb_mbc_vencodef().
 *
 *      \b      Restore the mbuf chain offset to its initial value.
 *
 *      %       Pointer to an SMB request structure (smb_request_t *). There
 *              should be only one of these in the string. If an sr in present
 *              it will be used to determine if unicode conversion should be
 *              applied to the strings.
 *
 *      C       Pointer to an mbuf chain. Copy that mbuf chain into the
 *              destination mbuf chain.
 *
 *      D       Pointer to a vardata_block structure. Copy the data described
 *              by that structure into the mbuf chain. The tag field is hard
 *              coded to '1'.
 *
 *      M       Write the SMB1 request signature ('SMBX') into the mbuf chain.
 *
 *      N       Write the SMB2 request signature ('SMBX') into the mbuf chain.
 *
 *      T       Pointer to a timestruc_t. Convert the content of the structure
 *              into NT time and store the result of the conversion in the
 *              mbuf chain.
 *
 *      V       Same as 'D' but the tag field is hard coded to '5'.
 *
 *      b       Byte. Store the byte or the nymber of bytes specified into the
 *              the mbuf chain. A format string like this "2b" would require 2
 *              bytes to be passed in.
 *
 *      m       Pointer to an mbuf. Copy the contents of the mbuf into the mbuf
 *              chain.
 *
 *      c       Pointer to a buffer. Copy the buffer into the mbuf chain. The
 *              size of the buffer is indicated by the number preceding 'c'.
 *
 *      w       Word (16bit value). Store the word or the number of words
 *              specified into the the mbuf chain. A format string like this
 *              "2w" would require 2 words to be passed in.
 *
 *      l       Long (32bit value). Store the long or the number of longs
 *              specified into the the mbuf chain. A format string like this
 *              "2l" would require 2 longs to be passed in.
 *
 *      q       Quad (64bit value). Store the quad or the number of quads
 *              specified into the the mbuf chain. A format string like this
 *              "2q" would require 2 quads to be passed in.
 *
 *      L       Pointer to a string. Store the string passed in into the mbuf
 *              chain preceded with a tag value of '2'.
 *
 *      S       Pointer to a string. Store the string passed in into the mbuf
 *              chain preceded with a tag value of '4'. Applied a unicode
 *              conversion is appropriate.
 *
 *      A       Same as 'S'
 *
 *      P       Pointer to a string. Store the string passed in into the mbuf
 *              chain preceded with a tag value of '5'. Applied a unicode
 *              conversion is appropriate.
 *
 *      u       Pointer to a string. Store the string passed in into the mbuf
 *              chain. Applied a unicode conversion is appropriate.
 *
 *      s       Pointer to a string. Store the string passed in into the mbuf
 *              chain.
 *
 *      Y       Date/Time.  Store the Date/Time or the number of Date/Time(s)
 *              specified into the the mbuf chain. A format string like this
 *              "2Y" would require 2 Date/Time values. The Date/Time is
 *              converted to DOS before storing.
 *
 *      y       Same as 'Y'. The order of Date and Time is reversed.
 *
 *      ,       Character. Store the character or number of character specified
 *              into the mbuf chain.  A format string like this "2c" would
 *              require 2 characters to be passed in. A unicode conversion is
 *              applied if appropriate.
 *
 *      .       Same as '`' without unicode conversion.
 *
 *      U       Align the offset of the mbuf chain on a 16bit boundary.
 */
int
smb_mbc_vencodef(mbuf_chain_t *mbc, const char *fmt, va_list ap)
{
        char            *charp;
        uint8_t         *cvalp;
        timestruc_t     *tvp;
        smb_vdb_t       *vdp;
        smb_request_t   *sr = NULL;
        uint64_t        llval;
        int64_t         nt_time;
        uint32_t        lval;
        uint_t          tag;
        int             unicode = 0;
        int             repc;
        boolean_t       repc_specified;
        uint16_t        wval;
        uint8_t         cval;
        uint8_t         c;

        while ((c = *fmt++) != 0) {
                repc_specified = B_FALSE;
                repc = 1;

                if ('0' <= c && c <= '9') {
                        repc = 0;
                        do {
                                repc = repc * 10 + c - '0';
                                c = *fmt++;
                        } while ('0' <= c && c <= '9');
                        repc_specified = B_TRUE;
                } else if (c == '#') {
                        repc = va_arg(ap, int);
                        c = *fmt++;
                        repc_specified = B_TRUE;
                }

                switch (c) {
                case '%':
                        sr = va_arg(ap, struct smb_request *);
                        if (sr->session->dialect >= SMB_VERS_2_BASE) {
                                unicode = 1;
                                break;
                        }
                        unicode = sr->smb_flg2 & SMB_FLAGS2_UNICODE;
                        break;

                case 'C':       /* Mbuf_chain */
                        if (mbc_marshal_put_mbuf_chain(mbc,
                            va_arg(ap, mbuf_chain_t *)) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'D':
                        vdp = va_arg(ap, struct vardata_block *);

                        if (mbc_marshal_put_char(mbc, 1) != 0)
                                return (DECODE_NO_MORE_DATA);
                        if (mbc_marshal_put_short(mbc, vdp->vdb_len) != 0)
                                return (DECODE_NO_MORE_DATA);
                        if (mbc_marshal_put_uio(mbc, &vdp->vdb_uio) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'M':
                        /* 0xFF S M B */
                        if (mbc_marshal_put_long(mbc, 0x424D53FF))
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'N':
                        /* 0xFE S M B */
                        if (mbc_marshal_put_long(mbc, 0x424D53FE))
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'T':
                        tvp = va_arg(ap, timestruc_t *);
                        nt_time = smb_time_unix_to_nt(tvp);
                        if (mbc_marshal_put_long_long(mbc, nt_time) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'V':
                        vdp = va_arg(ap, struct vardata_block *);

                        if (mbc_marshal_put_char(mbc, 5) != 0)
                                return (DECODE_NO_MORE_DATA);
                        if (mbc_marshal_put_short(mbc, vdp->vdb_len) != 0)
                                return (DECODE_NO_MORE_DATA);
                        if (mbc_marshal_put_uio(mbc, &vdp->vdb_uio) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'b':
                        while (repc-- > 0) {
                                cval = va_arg(ap, int);
                                if (mbc_marshal_put_char(mbc, cval) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case 'm':       /* struct_mbuf */
                        if (mbc_marshal_put_mbufs(mbc,
                            va_arg(ap, mbuf_t *)) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'c':
                        cvalp = va_arg(ap, uint8_t *);
                        while (repc-- > 0) {
                                if (mbc_marshal_put_char(mbc,
                                    *cvalp++) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case 'w':
                        while (repc-- > 0) {
                                wval = va_arg(ap, int);
                                if (mbc_marshal_put_short(mbc, wval) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case 'l':
                        while (repc-- > 0) {
                                lval = va_arg(ap, uint32_t);
                                if (mbc_marshal_put_long(mbc, lval) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case 'q':
                        while (repc-- > 0) {
                                llval = va_arg(ap, uint64_t);
                                if (mbc_marshal_put_long_long(mbc, llval) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;


                case 'L':
                        tag = 2;
                        goto oem_conversion;

                case 'S':
                case 'A':
                        tag = 4;
                        goto tagged_str;

                case 'P':
                        tag = 3;
                        goto tagged_str;

                tagged_str:
                        if (mbc_marshal_put_char(mbc, tag) != 0)
                                return (DECODE_NO_MORE_DATA);
                        /* FALLTHROUGH */

                case 'u':       /* Convert from unicode if flags are set */
                        if (unicode)
                                goto unicode_translation;
                        /* FALLTHROUGH */

                case 's':       /* put OEM string */
oem_conversion:
                        charp = va_arg(ap, char *);
                        if (!repc_specified)
                                repc = 0;
                        if (mbc_marshal_put_oem_string(mbc,
                            charp, repc) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'U':       /* put UTF-16 string */
unicode_translation:
                        charp = va_arg(ap, char *);
                        if (!repc_specified)
                                repc = 0;
                        if (mbc_marshal_put_unicode_string(mbc,
                            charp, repc) != 0)
                                return (DECODE_NO_MORE_DATA);
                        break;

                case 'Y':               /* int32_t, encode dos date/time */
                        while (repc-- > 0) {
                                uint16_t        d, t;

                                lval = va_arg(ap, uint32_t);
                                smb_time_unix_to_dos(lval,
                                    (short *)&d, (short *)&t);
                                if (mbc_marshal_put_short(mbc, t) != 0)
                                        return (DECODE_NO_MORE_DATA);
                                if (mbc_marshal_put_short(mbc, d) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case 'y':               /* int32_t, encode dos date/time */
                        while (repc-- > 0) {
                                uint16_t        d, t;

                                lval = va_arg(ap, uint32_t);
                                smb_time_unix_to_dos(lval,
                                    (short *)&d, (short *)&t);
                                if (mbc_marshal_put_short(mbc, d) != 0)
                                        return (DECODE_NO_MORE_DATA);
                                if (mbc_marshal_put_short(mbc, t) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        }
                        break;

                case ',':
                        if (unicode)
                                repc *= 2;
                        /* FALLTHROUGH */

                case '.':
                        while (repc-- > 0)
                                if (mbc_marshal_put_char(mbc, 0) != 0)
                                        return (DECODE_NO_MORE_DATA);
                        break;

                default:
                        ASSERT(0);
                        return (-1);
                }
        }
        return (0);
}

/*
 * smb_mbc_encodef
 *
 * This function builds a stream of bytes in the mbc chain passed in under the
 * control of the format fmt.
 *
 * (for a description of the format string see smb_mbc_vencodef()).
 */
int
smb_mbc_encodef(mbuf_chain_t *mbc, const char *fmt, ...)
{
        int     rc;
        va_list ap;

        va_start(ap, fmt);
        rc = smb_mbc_vencodef(mbc, fmt, ap);
        va_end(ap);
        return (rc);
}

/*
 * smb_mbc_poke
 *
 * This function writes a stream of bytes in the mbc passed in at the specified
 * offset under the control of the format fmt. The offset of the chain passed in
 * is not modified.
 *
 * (for a description of the format string see smb_mbc_vencodef()).
 */
int
smb_mbc_poke(mbuf_chain_t *mbc, int offset, const char *fmt, ...)
{
        int             len, rc;
        mbuf_chain_t    tmp;
        va_list         ap;

        if ((len = mbc->max_bytes - offset) < 0)
                return (DECODE_NO_MORE_DATA);
        rc = MBC_SHADOW_CHAIN(&tmp, mbc, offset, len);
        if (rc)
                return (DECODE_NO_MORE_DATA);

        va_start(ap, fmt);
        rc = smb_mbc_vencodef(&tmp, fmt, ap);
        va_end(ap);

        return (rc);
}

/*
 * Copy data from the src mbuf chain to the dst mbuf chain,
 * at the given offset in the src and current offset in dst,
 * for copy_len bytes.  Does NOT update src->chain_offset.
 */
int
smb_mbc_copy(mbuf_chain_t *dst_mbc, const mbuf_chain_t *src_mbc,
    int copy_offset, int copy_len)
{
        mbuf_t  *src_m;
        int offset, len;
        int rc;

        if (copy_len <= 0)
                return (0);
        if (copy_offset < 0)
                return (EINVAL);
        if ((copy_offset + copy_len) > src_mbc->max_bytes)
                return (EMSGSIZE);

        /*
         * Advance to the src mbuf where we start copying.
         */
        offset = copy_offset;
        src_m = src_mbc->chain;
        while (src_m && offset >= src_m->m_len) {
                offset -= src_m->m_len;
                src_m = src_m->m_next;
        }
        if (src_m == NULL)
                return (EFAULT);

        /*
         * Copy the first part, which may start somewhere past
         * the beginning of the current mbuf.
         */
        len = src_m->m_len - offset;
        if (len > copy_len)
                len = copy_len;
        rc = smb_mbc_put_mem(dst_mbc, src_m->m_data + offset, len);
        if (rc != 0)
                return (rc);
        copy_len -= len;

        /*
         * Copy remaining mbufs...
         */
        while (copy_len > 0) {
                src_m = src_m->m_next;
                if (src_m == NULL)
                        break;
                len = src_m->m_len;
                if (len > copy_len)
                        len = copy_len;
                rc = smb_mbc_put_mem(dst_mbc, src_m->m_data, len);
                copy_len -= len;
        }

        return (0);
}

/*
 * Copy data from the passed memory buffer into the mbuf chain
 * at the current offset.
 */
int
smb_mbc_put_mem(mbuf_chain_t *mbc, void *vmem, int mem_len)
{
        caddr_t mem = vmem;
        mbuf_t  *m;
        int32_t offset, tlen;
        int rc;

        if (mem_len <= 0)
                return (0);

        if ((rc = mbc_marshal_make_room(mbc, mem_len)) != 0)
                return (rc);

        /*
         * Advance to the dst mbuf where we start copying.
         * Allocations were done by _make_room().
         */
        offset = mbc->chain_offset;
        m = mbc->chain;
        while (offset >= m->m_len) {
                ASSERT(m->m_len > 0);
                offset -= m->m_len;
                m = m->m_next;
        }

        /*
         * Copy the first part, which may start somewhere past
         * the beginning of the current mbuf.
         */
        tlen = m->m_len - offset;
        if (tlen > mem_len)
                tlen = mem_len;
        bcopy(mem, m->m_data + offset, tlen);
        mbc->chain_offset += tlen;
        mem += tlen;
        mem_len -= tlen;

        /*
         * Copy remaining mem into mbufs.  These all start
         * at the beginning of each mbuf, and the last may
         * end somewhere short of m_len.
         */
        while (mem_len > 0) {
                m = m->m_next;
                tlen = m->m_len;
                if (tlen > mem_len)
                        tlen = mem_len;
                bcopy(mem, m->m_data, tlen);
                mbc->chain_offset += tlen;
                mem += tlen;
                mem_len -= tlen;
        }

        return (0);
}

/*
 * Put padding sufficient to align to A, where
 * A is some power of 2 greater than zero.
 */
int
smb_mbc_put_align(mbuf_chain_t *mbc, int align)
{
        int mask = align - 1;
        int padsz;

        ASSERT(align > 0 && (align & mask) == 0);
        if ((mbc->chain_offset & mask) == 0)
                return (0);
        padsz = align - (mbc->chain_offset & mask);
        return (smb_mbc_encodef(mbc, "#.", padsz));
}

/*
 * Put data into mbuf chain allocating as needed.
 * Adds room to end of mbuf chain if needed.
 */
static int
mbc_marshal_make_room(mbuf_chain_t *mbc, int32_t bytes_needed)
{
        mbuf_t  *m;
        mbuf_t  *last;
        int32_t bytes_available;

        bytes_needed += mbc->chain_offset;
        if (bytes_needed > mbc->max_bytes)
                return (EMSGSIZE);

        /*
         * First mbuf in chain should have prepend space.
         * See M_LEADINGSPACE() below.
         */
        if ((m = mbc->chain) == NULL) {
                MGET(m, M_WAIT, MT_DATA);
                m->m_len = 0;
                MCLGET(m, M_WAIT);
                m->m_data += MH_PREPEND_SPACE;
                mbc->chain = m;
                /* xxxx */
                /* ^    */
        }

        /* ---- ----- --xx ---xxx */
        /* ^                      */

        last = NULL;
        while ((m != NULL) && (bytes_needed >= m->m_len)) {
                last = m;
                bytes_needed -= m->m_len;
                m = m->m_next;
        }

        if ((bytes_needed == 0) || (m != NULL)) {
                /* We have enough room already */
                return (0);
        }

        /* ---- ----- --xx ---xxx */
        /*                       ^ */
        /* Back up to start of last mbuf */
        m = last;
        bytes_needed += m->m_len;

        /* ---- ----- --xx ---xxx */
        /*                 ^      */
        bytes_available = M_SIZE(m) - M_LEADINGSPACE(m);

        /* ---- ----- --xx ---xxx */
        /*                 ^      */
        while ((bytes_needed != 0) && (bytes_needed > bytes_available)) {
                m->m_len = bytes_available;
                bytes_needed -= m->m_len;
                /* ---- ----- --xx ------ */
                /*                 ^      */

                MGET(m->m_next, M_WAIT, MT_DATA);
                m = m->m_next;
                m->m_len = 0;
                MCLGET(m, M_WAIT);

                ASSERT(M_LEADINGSPACE(m) == 0);
                bytes_available = M_SIZE(m);

                /* ---- ----- --xx ------ xxxx */
                /*                        ^    */
        }

        /* ---- ----- --xx ------ xxxx */
        /*                        ^    */
        /* Expand last tail as needed */
        if (m->m_len <= bytes_needed) {
                m->m_len = bytes_needed;
                /* ---- ----- --xx ------ --xx */
                /*                         ^   */
        }

        return (0);
}

static void
mbc_marshal_store_byte(mbuf_chain_t *mbc, uint8_t data)
{
        mbuf_t  *m = mbc->chain;
        int32_t cur_offset = mbc->chain_offset;

        /*
         * Scan forward looking for the last data currently in chain.
         */
        while (cur_offset >= m->m_len) {
                cur_offset -= m->m_len;
                m = m->m_next;
        }
        ((char *)m->m_data)[cur_offset] = data;
        mbc->chain_offset++;
}

static int
mbc_marshal_put_char(mbuf_chain_t *mbc, uint8_t data)
{
        if (mbc_marshal_make_room(mbc, sizeof (char)) != 0)
                return (DECODE_NO_MORE_DATA);
        mbc_marshal_store_byte(mbc, data);
        return (0);
}

static int
mbc_marshal_put_short(mbuf_chain_t *mbc, uint16_t data)
{
        if (mbc_marshal_make_room(mbc, sizeof (short)))
                return (DECODE_NO_MORE_DATA);
        mbc_marshal_store_byte(mbc, data);
        mbc_marshal_store_byte(mbc, data >> 8);
        return (0);
}

static int
mbc_marshal_put_long(mbuf_chain_t *mbc, uint32_t data)
{
        if (mbc_marshal_make_room(mbc, sizeof (int32_t)))
                return (DECODE_NO_MORE_DATA);
        mbc_marshal_store_byte(mbc, data);
        mbc_marshal_store_byte(mbc, data >> 8);
        mbc_marshal_store_byte(mbc, data >> 16);
        mbc_marshal_store_byte(mbc, data >> 24);
        return (0);
}

static int
mbc_marshal_put_long_long(mbuf_chain_t *mbc, uint64_t data)
{
        if (mbc_marshal_make_room(mbc, sizeof (int64_t)))
                return (DECODE_NO_MORE_DATA);

        mbc_marshal_store_byte(mbc, data);
        mbc_marshal_store_byte(mbc, data >> 8);
        mbc_marshal_store_byte(mbc, data >> 16);
        mbc_marshal_store_byte(mbc, data >> 24);
        mbc_marshal_store_byte(mbc, data >> 32);
        mbc_marshal_store_byte(mbc, data >> 40);
        mbc_marshal_store_byte(mbc, data >> 48);
        mbc_marshal_store_byte(mbc, data >> 56);
        return (0);
}

/*
 * Marshal a UTF-8 string (str) into mbc, converting to OEM codeset.
 * Also write a null unless the repc count limits the length we put.
 * When (repc > 0) the length we marshal must be exactly repc, and
 * truncate or pad the mbc data as necessary.
 * See also: msgbuf_put_oem_string
 */
static int
mbc_marshal_put_oem_string(mbuf_chain_t *mbc, char *mbs, int repc)
{
        uint8_t         *oembuf = NULL;
        uint8_t         *s;
        int             oemlen;
        int             rlen;
        int             rc;

        /*
         * Compute length of converted OEM string,
         * NOT including null terminator
         */
        if ((oemlen = smb_sbequiv_strlen(mbs)) == -1)
                return (DECODE_NO_MORE_DATA);

        /*
         * If repc not specified, put whole string + NULL,
         * otherwise will truncate or pad as needed.
         */
        if (repc <= 0)
                repc = oemlen + 1;

        /*
         * Convert into a temporary buffer
         * Free oembuf before return.
         */
        oembuf = smb_mem_zalloc(oemlen + 1);
        ASSERT(oembuf != NULL);
        rlen = smb_mbstooem(oembuf, mbs, oemlen);
        if (rlen < 0) {
                rc = DECODE_NO_MORE_DATA;
                goto out;
        }
        if (rlen > oemlen)
                rlen = oemlen;
        oembuf[rlen] = '\0';

        /*
         * Copy the converted string into the message,
         * truncated or paded as required.
         */
        s = oembuf;
        while (repc > 0) {
                if (mbc_marshal_make_room(mbc, 1)) {
                        rc = DECODE_NO_MORE_DATA;
                        goto out;
                }
                mbc_marshal_store_byte(mbc, *s);
                if (*s != '\0')
                        s++;
                repc--;
        }
        rc = 0;

out:
        if (oembuf != NULL)
                smb_mem_free(oembuf);
        return (rc);
}

/*
 * Marshal a UTF-8 string (str) into mbc, converting to UTF-16.
 * Also write a null unless the repc count limits the length.
 * When (repc > 0) the length we marshal must be exactly repc,
 * and truncate or pad the mbc data as necessary.
 * See also: msgbuf_put_unicode_string
 */
static int
mbc_marshal_put_unicode_string(mbuf_chain_t *mbc, char *mbs, int repc)
{
        smb_wchar_t     *wcsbuf = NULL;
        smb_wchar_t     *wp;
        smb_wchar_t     wchar;
        size_t          wcslen, wcsbytes;
        size_t          rlen;
        int             rc;

        /* align to word boundary */
        if (mbc->chain_offset & 1) {
                if (mbc_marshal_make_room(mbc, 1))
                        return (DECODE_NO_MORE_DATA);
                mbc_marshal_store_byte(mbc, 0);
        }

        /*
         * Compute length of converted UTF-16 string,
         * NOT including null terminator (in bytes).
         */
        wcsbytes = smb_wcequiv_strlen(mbs);
        if (wcsbytes == (size_t)-1)
                return (DECODE_NO_MORE_DATA);

        /*
         * If repc not specified, put whole string + NULL,
         * otherwise will truncate or pad as needed.
         */
        if (repc <= 0)
                repc = wcsbytes + 2;

        /*
         * Convert into a temporary buffer
         * Free wcsbuf before return.
         */
        wcslen = wcsbytes / 2;
        wcsbuf = smb_mem_zalloc(wcsbytes + 2);
        ASSERT(wcsbuf != NULL);
        rlen = smb_mbstowcs(wcsbuf, mbs, wcslen);
        if (rlen == (size_t)-1) {
                rc = DECODE_NO_MORE_DATA;
                goto out;
        }
        if (rlen > wcslen)
                rlen = wcslen;
        wcsbuf[rlen] = 0;

        /*
         * Copy the converted string into the message,
         * truncated or paded as required.  Preserve
         * little-endian order while copying.
         */
        wp = wcsbuf;
        while (repc >= sizeof (smb_wchar_t)) {
                if (mbc_marshal_make_room(mbc, sizeof (smb_wchar_t))) {
                        rc = DECODE_NO_MORE_DATA;
                        goto out;
                }
                wchar = LE_IN16(wp);
                mbc_marshal_store_byte(mbc, wchar);
                mbc_marshal_store_byte(mbc, wchar >> 8);
                if (wchar != 0)
                        wp++;
                repc -= sizeof (smb_wchar_t);
        }
        if (repc > 0) {
                if (mbc_marshal_make_room(mbc, 1)) {
                        rc = DECODE_NO_MORE_DATA;
                        goto out;
                }
                mbc_marshal_store_byte(mbc, 0);
        }
        rc = 0;

out:
        if (wcsbuf != NULL)
                smb_mem_free(wcsbuf);
        return (rc);
}

static void /*ARGSUSED*/
uiorefnoop(mbuf_t *m)
{
}

static int
mbc_marshal_put_uio(mbuf_chain_t *mbc, struct uio *uio)
{
        mbuf_t          **t;
        mbuf_t          *m = NULL;
        struct iovec    *iov = uio->uio_iov;
        int32_t         i, iov_cnt = uio->uio_iovcnt;

        iov = uio->uio_iov;
        t = &mbc->chain;
        for (i = 0; i < iov_cnt; i++) {
                MGET(m, M_WAIT, MT_DATA);
                m->m_ext.ext_buf = iov->iov_base;
                m->m_ext.ext_free = uiorefnoop;
                m->m_data = m->m_ext.ext_buf;
                m->m_flags |= M_EXT;
                m->m_len = m->m_ext.ext_size = iov->iov_len;
                mbc->max_bytes += m->m_len;
                m->m_next = 0;
                *t = m;
                t = &m->m_next;
                iov++;
        }
        return (0);
}

int smb_mbuf_put_copy_threshold = 128;
/*
 * Append an mbuf to the chain at chain_offset, with some optimizations:
 * If the chain is empty, just set the head (done).
 * If m is no larger than the copy threshold, copy.
 * Always consumes (or free's) the mbuf passed in.
 */
static int
mbc_marshal_put_mbufs(mbuf_chain_t *mbc, mbuf_t *mbuf)
{
        mbuf_t  *m;
        int     bytes, rc;

        /*
         * Length of mbuf(s) to be appended
         */
        bytes = 0;
        m = mbuf;
        while (m != NULL) {
                bytes += m->m_len;
                m = m->m_next;
        }
        if (bytes == 0) {
                m_freem(mbuf);
                return (0);
        }

        /*
         * Check for space vs max_bytes
         */
        if (!MBC_ROOM_FOR(mbc, bytes)) {
                m_freem(mbuf);
                return (EMSGSIZE);
        }

        /*
         * Empty mbc? (probably rare)
         */
        if (mbc->chain == NULL) {
                mbc->chain = mbuf;
                mbc->chain_offset = bytes;
                return (0);
        }

        /*
         * Copy optimization.  We've already checked that there's room
         * for the _put_mem operations, and that's the only error case
         * for that call, so just assert success and continue.
         */
        if (bytes <= smb_mbuf_put_copy_threshold) {
                m = mbuf;
                while (m != NULL) {
                        rc = smb_mbc_put_mem(mbc, m->m_data, m->m_len);
                        ASSERT3S(rc, ==, 0);
                        m = m_free(m);
                }
                return (0);
        }

        /*
         * Trim existing chain and append
         */
        smb_mbuf_trim(mbc->chain, mbc->chain_offset);
        m = mbc->chain;
        while (m->m_next != NULL)
                m = m->m_next;
        m->m_next = mbuf;
        mbc->chain_offset += bytes;

        return (0);
}

/*
 * Append a new mbc (nmbc) to the existing chain mbc
 * Assumes the new mbc has been "trimmed" to length.
 * (This ignores nmbc.chain_offset)
 *
 * Always consume or dispose of nmbc->chain
 */
static int
mbc_marshal_put_mbuf_chain(mbuf_chain_t *mbc, mbuf_chain_t *nmbc)
{
        int rc = 0;

        if (nmbc->chain != NULL) {
                rc = mbc_marshal_put_mbufs(mbc, nmbc->chain);
                nmbc->chain = NULL;
        }
        return (rc);
}

static uint8_t
mbc_marshal_fetch_byte(mbuf_chain_t *mbc)
{
        uint8_t data;
        mbuf_t  *m = mbc->chain;
        int32_t offset = mbc->chain_offset;

        while (offset >= m->m_len) {
                offset -= m->m_len;
                m = m->m_next;
        }
        data = ((uint8_t *)m->m_data)[offset];
        mbc->chain_offset++;
        return (data);
}

static int
mbc_marshal_get_char(mbuf_chain_t *mbc, uint8_t *data)
{
        if (MBC_ROOM_FOR(mbc, sizeof (char)) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }
        *data = mbc_marshal_fetch_byte(mbc);
        return (0);
}

static int
mbc_marshal_get_short(mbuf_chain_t *mbc, uint16_t *data)
{
        uint16_t        tmp;
        mbuf_t          *m = mbc->chain;
        int32_t         offset = mbc->chain_offset;

        if (MBC_ROOM_FOR(mbc, sizeof (short)) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }

        while (offset >= m->m_len) {
                offset -= m->m_len;
                m = m->m_next;
        }
        if ((m->m_len - offset) >= sizeof (short)) {
                *data = LE_IN16(m->m_data + offset);
                mbc->chain_offset += sizeof (short);
        } else {
                tmp = (uint16_t)mbc_marshal_fetch_byte(mbc);
                tmp |= ((uint16_t)mbc_marshal_fetch_byte(mbc)) << 8;
                *data = tmp;
        }
        return (0);
}

static int
mbc_marshal_get_long(mbuf_chain_t *mbc, uint32_t *data)
{
        uint32_t        tmp;
        mbuf_t          *m = mbc->chain;
        int32_t         offset = mbc->chain_offset;

        if (MBC_ROOM_FOR(mbc, sizeof (int32_t)) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }
        while (offset >= m->m_len) {
                offset -= m->m_len;
                m = m->m_next;
        }
        if ((m->m_len - offset) >= sizeof (int32_t)) {
                *data = LE_IN32(m->m_data + offset);
                mbc->chain_offset += sizeof (int32_t);
        } else {
                tmp = (uint32_t)mbc_marshal_fetch_byte(mbc);
                tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 8;
                tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 16;
                tmp |= ((uint32_t)mbc_marshal_fetch_byte(mbc)) << 24;
                *data = tmp;
        }
        return (0);
}

static uint64_t
qswap(uint64_t ll)
{
        uint64_t v;

        v = ll >> 32;
        v |= ll << 32;

        return (v);
}

static int
mbc_marshal_get_odd_long_long(mbuf_chain_t *mbc, uint64_t *data)
{
        uint64_t        tmp;
        mbuf_t          *m = mbc->chain;
        int32_t         offset = mbc->chain_offset;

        if (MBC_ROOM_FOR(mbc, sizeof (int64_t)) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }
        while (offset >= m->m_len) {
                offset -= m->m_len;
                m = m->m_next;
        }

        if ((m->m_len - offset) >= sizeof (int64_t)) {
                *data = qswap(LE_IN64(m->m_data + offset));
                mbc->chain_offset += sizeof (int64_t);
        } else {
                tmp = (uint64_t)mbc_marshal_fetch_byte(mbc) << 32;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 40;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 48;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 56;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc);
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 8;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 16;
                tmp |= (uint64_t)mbc_marshal_fetch_byte(mbc) << 24;

                *(uint64_t *)data = tmp;
        }
        return (0);
}

static int
mbc_marshal_get_long_long(mbuf_chain_t *mbc, uint64_t *data)
{
        uint64_t        tmp;
        mbuf_t          *m = mbc->chain;
        int32_t         offset = mbc->chain_offset;

        if (MBC_ROOM_FOR(mbc, sizeof (int64_t)) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }
        while (offset >= m->m_len) {
                offset -= m->m_len;
                m = m->m_next;
        }
        if ((m->m_len - offset) >= sizeof (int64_t)) {
                *data = LE_IN64(m->m_data + offset);
                mbc->chain_offset += sizeof (int64_t);
        } else {
                tmp = (uint32_t)mbc_marshal_fetch_byte(mbc);
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 8;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 16;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 24;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 32;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 40;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 48;
                tmp |= ((uint64_t)mbc_marshal_fetch_byte(mbc)) << 56;
                *(uint64_t *)data = tmp;
        }
        return (0);
}

/*
 * mbc_marshal_get_oem_string
 *
 * Decode an OEM string, returning its UTF-8 form in strpp,
 * allocated using smb_srm_zalloc (automatically freed).
 * If max_bytes != 0, consume at most max_bytes of the mbc.
 * See also: msgbuf_get_oem_string
 */
static int
mbc_marshal_get_oem_string(smb_request_t *sr,
    mbuf_chain_t *mbc, char **strpp, int max_bytes)
{
        char            *mbs;
        uint8_t         *oembuf = NULL;
        int             oemlen, oemmax;
        int             mbsmax;
        int             rlen;
        int             rc;

        if (max_bytes == 0)
                max_bytes = 0xffff;

        /*
         * Get the OtW data into a temporary buffer.
         * Free oembuf before return.
         */
        oemlen = 0;
        oemmax = MALLOC_QUANTUM;
        oembuf = smb_mem_alloc(oemmax);
        for (;;) {
                uint8_t ch;

                if (oemlen >= max_bytes)
                        break;
                if ((oemlen + 2) >= oemmax) {
                        oemmax += MALLOC_QUANTUM;
                        oembuf = smb_mem_realloc(oembuf, oemmax);
                }
                if (mbc_marshal_get_char(mbc, &ch) != 0) {
                        rc = DECODE_NO_MORE_DATA;
                        goto out;
                }
                if (ch == 0)
                        break;
                oembuf[oemlen++] = ch;
        }
        oembuf[oemlen] = '\0';

        /*
         * Get the buffer we'll return and convert to UTF-8.
         * May take as much as double the space.
         */
        mbsmax = oemlen * 2;
        mbs = smb_srm_zalloc(sr, mbsmax + 1);
        ASSERT(mbs != NULL);
        rlen = smb_oemtombs(mbs, oembuf, mbsmax);
        if (rlen < 0) {
                rc = DECODE_NO_MORE_DATA;
                goto out;
        }
        if (rlen > mbsmax)
                rlen = mbsmax;
        mbs[rlen] = '\0';
        *strpp = mbs;
        rc = 0;

out:
        if (oembuf != NULL)
                smb_mem_free(oembuf);
        return (rc);
}

/*
 * mbc_marshal_get_unicode_string
 *
 * Decode a UTF-16 string, returning its UTF-8 form in strpp,
 * allocated using smb_srm_zalloc (automatically freed).
 * If max_bytes != 0, consume at most max_bytes of the mbc.
 * See also: msgbuf_get_unicode_string
 */
static int
mbc_marshal_get_unicode_string(smb_request_t *sr,
    mbuf_chain_t *mbc, char **strpp, int max_bytes)
{
        char            *mbs;
        uint16_t        *wcsbuf = NULL;
        int             wcslen;         // wchar count
        int             wcsmax;         // byte count
        size_t          mbsmax;
        size_t          rlen;
        int             rc;

        if (max_bytes == 0)
                max_bytes = 0xffff;

        /*
         * Unicode strings are always word aligned.
         */
        if (mbc->chain_offset & 1) {
                if (MBC_ROOM_FOR(mbc, sizeof (char)) == 0)
                        return (DECODE_NO_MORE_DATA);
                mbc->chain_offset++;
        }

        /*
         * Get the OtW data into a temporary buffer.
         * Free wcsbuf before return.
         */
        wcslen = 0;
        wcsmax = MALLOC_QUANTUM;
        wcsbuf = smb_mem_alloc(wcsmax);
        for (;;) {
                uint16_t        wchar;

                if ((wcslen * 2) >= max_bytes)
                        break;
                if (((wcslen * 2) + 4) >= wcsmax) {
                        wcsmax += MALLOC_QUANTUM;
                        wcsbuf = smb_mem_realloc(wcsbuf, wcsmax);
                }
                if (mbc_marshal_get_short(mbc, &wchar) != 0) {
                        rc = DECODE_NO_MORE_DATA;
                        goto out;
                }
                if (wchar == 0)
                        break;
                /* Keep in little-endian form. */
                LE_OUT16(wcsbuf + wcslen, wchar);
                wcslen++;
        }
        wcsbuf[wcslen] = 0;

        /*
         * Get the buffer we'll return and convert to UTF-8.
         * May take as much 4X number of wide chars.
         */
        mbsmax = wcslen * MTS_MB_CUR_MAX;
        mbs = smb_srm_zalloc(sr, mbsmax + 1);
        ASSERT(mbs != NULL);
        rlen = smb_wcstombs(mbs, wcsbuf, mbsmax);
        if (rlen == (size_t)-1) {
                rc = DECODE_NO_MORE_DATA;
                goto out;
        }
        if (rlen > mbsmax)
                rlen = mbsmax;
        mbs[rlen] = '\0';
        *strpp = mbs;
        rc = 0;

out:
        if (wcsbuf != NULL)
                smb_mem_free(wcsbuf);
        return (rc);
}

static int /*ARGSUSED*/
mbc_marshal_get_mbufs(mbuf_chain_t *mbc, int32_t bytes, mbuf_t **m)
{
        *m = NULL;
        if (MBC_ROOM_FOR(mbc, bytes) == 0) {
                /* Data will never be available */
                return (DECODE_NO_MORE_DATA);
        }
        /* not yet implemented */
        return (-1);
}

static int
mbc_marshal_get_mbuf_chain(mbuf_chain_t *mbc, int32_t bytes, mbuf_chain_t *nmbc)
{
        int     rc;
        mbuf_t  *m;

        if (bytes == 0) {
                /* Get all the rest */
                bytes = mbc->max_bytes - mbc->chain_offset;
        }

        MBC_SETUP(nmbc, mbc->max_bytes);
        if ((rc = mbc_marshal_get_mbufs(mbc, bytes, &m)) != 0) {
                if (m)
                        m_freem(m);
                return (rc);
        }
        nmbc->chain = m;
        while (m != 0) {
                bytes += m->m_len;
                m = m->m_next;
        }
        nmbc->max_bytes = bytes;
        return (0);
}

static int
mbc_marshal_get_uio(mbuf_chain_t *mbc, struct uio *uio)
{
        int             i, offset;
        int32_t         bytes = uio->uio_resid;
        int32_t         remainder;
        struct iovec    *iov;
        mbuf_t          *m;

        /*
         * The residual count is tested because in the case of write requests
         * with no data (smbtorture RAW-WRITE test will generate that type of
         * request) this function is called with a residual count of zero
         * bytes.
         */
        if (bytes != 0) {
                iov = uio->uio_iov;
                uio->uio_segflg = UIO_SYSSPACE;
                uio->uio_extflg = UIO_COPY_DEFAULT;

                if (MBC_ROOM_FOR(mbc, bytes) == 0) {
                        /* Data will never be available */
                        return (DECODE_NO_MORE_DATA);
                }

                m = mbc->chain;
                offset = mbc->chain_offset;
                while (offset >= m->m_len) {
                        offset -= m->m_len;
                        m = m->m_next;
                        ASSERT((offset == 0) || (offset && m));
                }

                for (i = 0; (bytes > 0) && (i < uio->uio_iovcnt); i++) {
                        iov[i].iov_base = &m->m_data[offset];
                        remainder = m->m_len - offset;
                        if (remainder >= bytes) {
                                iov[i].iov_len = bytes;
                                mbc->chain_offset += bytes;
                                uio->uio_iovcnt = i + 1;
                                return (0);
                        }
                        iov[i].iov_len = remainder;
                        mbc->chain_offset += remainder;
                        bytes -= remainder;
                        m = m->m_next;
                        offset = 0;
                }
                return (DECODE_NO_MORE_DATA);
        }
        return (0);
}

static int
mbc_marshal_get_skip(mbuf_chain_t *mbc, uint_t skip)
{
        if (MBC_ROOM_FOR(mbc, skip) == 0)
                return (DECODE_NO_MORE_DATA);
        mbc->chain_offset += skip;
        return (0);
}