1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
5 * Authors: Doug Rabson <dfr@rabson.org>
6 * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/malloc.h>
33#include <sys/mbuf.h>
34
35#include <rpc/types.h>
36#include <rpc/xdr.h>
37
38static void xdrmbuf_destroy(XDR *);
39static bool_t xdrmbuf_getlong(XDR *, long *);
40static bool_t xdrmbuf_putlong(XDR *, const long *);
41static bool_t xdrmbuf_getbytes(XDR *, char *, u_int);
42static bool_t xdrmbuf_putbytes(XDR *, const char *, u_int);
43/* XXX: w/64-bit pointers, u_int not enough! */
44static u_int xdrmbuf_getpos(XDR *);
45static bool_t xdrmbuf_setpos(XDR *, u_int);
46static int32_t *xdrmbuf_inline(XDR *, u_int);
47
48static const struct	xdr_ops xdrmbuf_ops = {
49	xdrmbuf_getlong,
50	xdrmbuf_putlong,
51	xdrmbuf_getbytes,
52	xdrmbuf_putbytes,
53	xdrmbuf_getpos,
54	xdrmbuf_setpos,
55	xdrmbuf_inline,
56	xdrmbuf_destroy
57};
58
59/*
60 * The procedure xdrmbuf_create initializes a stream descriptor for a
61 * mbuf.
62 */
63void
64xdrmbuf_create(XDR *xdrs, struct mbuf *m, enum xdr_op op)
65{
66
67	KASSERT(m != NULL, ("xdrmbuf_create with NULL mbuf chain"));
68	xdrs->x_op = op;
69	xdrs->x_ops = &xdrmbuf_ops;
70	xdrs->x_base = (char *) m;
71	if (op == XDR_ENCODE) {
72		m = m_last(m);
73		xdrs->x_private = m;
74		xdrs->x_handy = m->m_len;
75	} else {
76		xdrs->x_private = m;
77		xdrs->x_handy = 0;
78	}
79}
80
81void
82xdrmbuf_append(XDR *xdrs, struct mbuf *madd)
83{
84	struct mbuf *m;
85
86	KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_ENCODE,
87	    ("xdrmbuf_append: invalid XDR stream"));
88
89	if (m_length(madd, NULL) == 0) {
90		m_freem(madd);
91		return;
92	}
93
94	m = (struct mbuf *) xdrs->x_private;
95	m->m_next = madd;
96
97	m = m_last(madd);
98	xdrs->x_private = m;
99	xdrs->x_handy = m->m_len;
100}
101
102struct mbuf *
103xdrmbuf_getall(XDR *xdrs)
104{
105	struct mbuf *m0, *m;
106
107	KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_DECODE,
108	    ("xdrmbuf_append: invalid XDR stream"));
109
110	m0 = (struct mbuf *) xdrs->x_base;
111	m = (struct mbuf *) xdrs->x_private;
112	if (m0 != m) {
113		while (m0->m_next != m)
114			m0 = m0->m_next;
115		m0->m_next = NULL;
116		xdrs->x_private = NULL;
117	} else {
118		xdrs->x_base = NULL;
119		xdrs->x_private = NULL;
120	}
121
122	if (m)
123		m_adj(m, xdrs->x_handy);
124	else
125		m = m_get(M_WAITOK, MT_DATA);
126	return (m);
127}
128
129static void
130xdrmbuf_destroy(XDR *xdrs)
131{
132
133	if (xdrs->x_op == XDR_DECODE && xdrs->x_base) {
134		m_freem((struct mbuf *) xdrs->x_base);
135		xdrs->x_base = NULL;
136		xdrs->x_private = NULL;
137	}
138}
139
140static bool_t
141xdrmbuf_getlong(XDR *xdrs, long *lp)
142{
143	int32_t *p;
144	int32_t t;
145
146	p = xdrmbuf_inline(xdrs, sizeof(int32_t));
147	if (p) {
148		t = *p;
149	} else {
150		xdrmbuf_getbytes(xdrs, (char *) &t, sizeof(int32_t));
151	}
152
153	*lp = ntohl(t);
154	return (TRUE);
155}
156
157static bool_t
158xdrmbuf_putlong(XDR *xdrs, const long *lp)
159{
160	int32_t *p;
161	int32_t t = htonl(*lp);
162
163	p = xdrmbuf_inline(xdrs, sizeof(int32_t));
164	if (p) {
165		*p = t;
166		return (TRUE);
167	} else {
168		return (xdrmbuf_putbytes(xdrs, (char *) &t, sizeof(int32_t)));
169	}
170}
171
172static bool_t
173xdrmbuf_getbytes(XDR *xdrs, char *addr, u_int len)
174{
175	struct mbuf *m = (struct mbuf *) xdrs->x_private;
176	size_t sz;
177
178	while (len > 0) {
179		/*
180		 * Make sure we haven't hit the end.
181		 */
182		if (!m) {
183			return (FALSE);
184		}
185
186		/*
187		 * See how much we can get from this mbuf.
188		 */
189		sz = m->m_len - xdrs->x_handy;
190		if (sz > len)
191			sz = len;
192		bcopy(mtod(m, const char *) + xdrs->x_handy, addr, sz);
193
194		addr += sz;
195		xdrs->x_handy += sz;
196		len -= sz;
197
198		if (xdrs->x_handy == m->m_len) {
199			m = m->m_next;
200			xdrs->x_private = (void *) m;
201			xdrs->x_handy = 0;
202		}
203	}
204
205	return (TRUE);
206}
207
208static bool_t
209xdrmbuf_putbytes(XDR *xdrs, const char *addr, u_int len)
210{
211	struct mbuf *m = (struct mbuf *) xdrs->x_private;
212	struct mbuf *n;
213	size_t sz;
214
215	while (len > 0) {
216		sz = M_TRAILINGSPACE(m) + (m->m_len - xdrs->x_handy);
217		if (sz > len)
218			sz = len;
219		bcopy(addr, mtod(m, char *) + xdrs->x_handy, sz);
220		addr += sz;
221		xdrs->x_handy += sz;
222		if (xdrs->x_handy > m->m_len)
223			m->m_len = xdrs->x_handy;
224		len -= sz;
225
226		if (xdrs->x_handy == m->m_len && M_TRAILINGSPACE(m) == 0) {
227			if (!m->m_next) {
228				if (m->m_flags & M_EXT)
229					n = m_getcl(M_WAITOK, m->m_type, 0);
230				else
231					n = m_get(M_WAITOK, m->m_type);
232				m->m_next = n;
233			}
234			m = m->m_next;
235			xdrs->x_private = (void *) m;
236			xdrs->x_handy = 0;
237		}
238	}
239
240	return (TRUE);
241}
242
243static u_int
244xdrmbuf_getpos(XDR *xdrs)
245{
246	struct mbuf *m0 = (struct mbuf *) xdrs->x_base;
247	struct mbuf *m = (struct mbuf *) xdrs->x_private;
248	u_int pos = 0;
249
250	while (m0 && m0 != m) {
251		pos += m0->m_len;
252		m0 = m0->m_next;
253	}
254	KASSERT(m0, ("Corrupted mbuf chain"));
255
256	return (pos + xdrs->x_handy);
257}
258
259static bool_t
260xdrmbuf_setpos(XDR *xdrs, u_int pos)
261{
262	struct mbuf *m = (struct mbuf *) xdrs->x_base;
263
264	while (m && pos > m->m_len) {
265		pos -= m->m_len;
266		m = m->m_next;
267	}
268	KASSERT(m, ("Corrupted mbuf chain"));
269
270	xdrs->x_private = (void *) m;
271	xdrs->x_handy = pos;
272
273	return (TRUE);
274}
275
276static int32_t *
277xdrmbuf_inline(XDR *xdrs, u_int len)
278{
279	struct mbuf *m = (struct mbuf *) xdrs->x_private;
280	size_t available;
281	char *p;
282
283	if (!m)
284		return (0);
285	if (xdrs->x_op == XDR_ENCODE) {
286		available = M_TRAILINGSPACE(m) + (m->m_len - xdrs->x_handy);
287	} else {
288		available = m->m_len - xdrs->x_handy;
289	}
290
291	if (available >= len) {
292		p = mtod(m, char *) + xdrs->x_handy;
293		if (((uintptr_t) p) & (sizeof(int32_t) - 1))
294			return (0);
295		xdrs->x_handy += len;
296		if (xdrs->x_handy > m->m_len)
297			m->m_len = xdrs->x_handy;
298		return ((int32_t *) p);
299	}
300
301	return (0);
302}
303