1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/malloc.h>
36#include <sys/mbuf.h>
37
38#include <rpc/types.h>
39#include <rpc/xdr.h>
40
41static void xdrmbuf_destroy(XDR *);
42static bool_t xdrmbuf_getlong(XDR *, long *);
43static bool_t xdrmbuf_putlong(XDR *, const long *);
44static bool_t xdrmbuf_getbytes(XDR *, char *, u_int);
45static bool_t xdrmbuf_putbytes(XDR *, const char *, u_int);
46/* XXX: w/64-bit pointers, u_int not enough! */
47static u_int xdrmbuf_getpos(XDR *);
48static bool_t xdrmbuf_setpos(XDR *, u_int);
49static int32_t *xdrmbuf_inline(XDR *, u_int);
50
51static const struct	xdr_ops xdrmbuf_ops = {
52	xdrmbuf_getlong,
53	xdrmbuf_putlong,
54	xdrmbuf_getbytes,
55	xdrmbuf_putbytes,
56	xdrmbuf_getpos,
57	xdrmbuf_setpos,
58	xdrmbuf_inline,
59	xdrmbuf_destroy
60};
61
62/*
63 * The procedure xdrmbuf_create initializes a stream descriptor for a
64 * mbuf.
65 */
66void
67xdrmbuf_create(XDR *xdrs, struct mbuf *m, enum xdr_op op)
68{
69
70	KASSERT(m != NULL, ("xdrmbuf_create with NULL mbuf chain"));
71	xdrs->x_op = op;
72	xdrs->x_ops = &xdrmbuf_ops;
73	xdrs->x_base = (char *) m;
74	if (op == XDR_ENCODE) {
75		m = m_last(m);
76		xdrs->x_private = m;
77		xdrs->x_handy = m->m_len;
78	} else {
79		xdrs->x_private = m;
80		xdrs->x_handy = 0;
81	}
82}
83
84void
85xdrmbuf_append(XDR *xdrs, struct mbuf *madd)
86{
87	struct mbuf *m;
88
89	KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_ENCODE,
90	    ("xdrmbuf_append: invalid XDR stream"));
91
92	if (m_length(madd, NULL) == 0) {
93		m_freem(madd);
94		return;
95	}
96
97	m = (struct mbuf *) xdrs->x_private;
98	m->m_next = madd;
99
100	m = m_last(madd);
101	xdrs->x_private = m;
102	xdrs->x_handy = m->m_len;
103}
104
105struct mbuf *
106xdrmbuf_getall(XDR *xdrs)
107{
108	struct mbuf *m0, *m;
109
110	KASSERT(xdrs->x_ops == &xdrmbuf_ops && xdrs->x_op == XDR_DECODE,
111	    ("xdrmbuf_append: invalid XDR stream"));
112
113	m0 = (struct mbuf *) xdrs->x_base;
114	m = (struct mbuf *) xdrs->x_private;
115	if (m0 != m) {
116		while (m0->m_next != m)
117			m0 = m0->m_next;
118		m0->m_next = NULL;
119		xdrs->x_private = NULL;
120	} else {
121		xdrs->x_base = NULL;
122		xdrs->x_private = NULL;
123	}
124
125	if (m)
126		m_adj(m, xdrs->x_handy);
127	else
128		m = m_get(M_WAITOK, MT_DATA);
129	return (m);
130}
131
132static void
133xdrmbuf_destroy(XDR *xdrs)
134{
135
136	if (xdrs->x_op == XDR_DECODE && xdrs->x_base) {
137		m_freem((struct mbuf *) xdrs->x_base);
138		xdrs->x_base = NULL;
139		xdrs->x_private = NULL;
140	}
141}
142
143static bool_t
144xdrmbuf_getlong(XDR *xdrs, long *lp)
145{
146	int32_t *p;
147	int32_t t;
148
149	p = xdrmbuf_inline(xdrs, sizeof(int32_t));
150	if (p) {
151		t = *p;
152	} else {
153		xdrmbuf_getbytes(xdrs, (char *) &t, sizeof(int32_t));
154	}
155
156	*lp = ntohl(t);
157	return (TRUE);
158}
159
160static bool_t
161xdrmbuf_putlong(xdrs, lp)
162	XDR *xdrs;
163	const long *lp;
164{
165	int32_t *p;
166	int32_t t = htonl(*lp);
167
168	p = xdrmbuf_inline(xdrs, sizeof(int32_t));
169	if (p) {
170		*p = t;
171		return (TRUE);
172	} else {
173		return (xdrmbuf_putbytes(xdrs, (char *) &t, sizeof(int32_t)));
174	}
175}
176
177static bool_t
178xdrmbuf_getbytes(XDR *xdrs, char *addr, u_int len)
179{
180	struct mbuf *m = (struct mbuf *) xdrs->x_private;
181	size_t sz;
182
183	while (len > 0) {
184		/*
185		 * Make sure we haven't hit the end.
186		 */
187		if (!m) {
188			return (FALSE);
189		}
190
191		/*
192		 * See how much we can get from this mbuf.
193		 */
194		sz = m->m_len - xdrs->x_handy;
195		if (sz > len)
196			sz = len;
197		bcopy(mtod(m, const char *) + xdrs->x_handy, addr, sz);
198
199		addr += sz;
200		xdrs->x_handy += sz;
201		len -= sz;
202
203		if (xdrs->x_handy == m->m_len) {
204			m = m->m_next;
205			xdrs->x_private = (void *) m;
206			xdrs->x_handy = 0;
207		}
208	}
209
210	return (TRUE);
211}
212
213static bool_t
214xdrmbuf_putbytes(XDR *xdrs, const char *addr, u_int len)
215{
216	struct mbuf *m = (struct mbuf *) xdrs->x_private;
217	struct mbuf *n;
218	size_t sz;
219
220	while (len > 0) {
221		sz = M_TRAILINGSPACE(m) + (m->m_len - xdrs->x_handy);
222		if (sz > len)
223			sz = len;
224		bcopy(addr, mtod(m, char *) + xdrs->x_handy, sz);
225		addr += sz;
226		xdrs->x_handy += sz;
227		if (xdrs->x_handy > m->m_len)
228			m->m_len = xdrs->x_handy;
229		len -= sz;
230
231		if (xdrs->x_handy == m->m_len && M_TRAILINGSPACE(m) == 0) {
232			if (!m->m_next) {
233				if (m->m_flags & M_EXT)
234					n = m_getcl(M_WAITOK, m->m_type, 0);
235				else
236					n = m_get(M_WAITOK, m->m_type);
237				m->m_next = n;
238			}
239			m = m->m_next;
240			xdrs->x_private = (void *) m;
241			xdrs->x_handy = 0;
242		}
243	}
244
245	return (TRUE);
246}
247
248static u_int
249xdrmbuf_getpos(XDR *xdrs)
250{
251	struct mbuf *m0 = (struct mbuf *) xdrs->x_base;
252	struct mbuf *m = (struct mbuf *) xdrs->x_private;
253	u_int pos = 0;
254
255	while (m0 && m0 != m) {
256		pos += m0->m_len;
257		m0 = m0->m_next;
258	}
259	KASSERT(m0, ("Corrupted mbuf chain"));
260
261	return (pos + xdrs->x_handy);
262}
263
264static bool_t
265xdrmbuf_setpos(XDR *xdrs, u_int pos)
266{
267	struct mbuf *m = (struct mbuf *) xdrs->x_base;
268
269	while (m && pos > m->m_len) {
270		pos -= m->m_len;
271		m = m->m_next;
272	}
273	KASSERT(m, ("Corrupted mbuf chain"));
274
275	xdrs->x_private = (void *) m;
276	xdrs->x_handy = pos;
277
278	return (TRUE);
279}
280
281static int32_t *
282xdrmbuf_inline(XDR *xdrs, u_int len)
283{
284	struct mbuf *m = (struct mbuf *) xdrs->x_private;
285	size_t available;
286	char *p;
287
288	if (!m)
289		return (0);
290	if (xdrs->x_op == XDR_ENCODE) {
291		available = M_TRAILINGSPACE(m) + (m->m_len - xdrs->x_handy);
292	} else {
293		available = m->m_len - xdrs->x_handy;
294	}
295
296	if (available >= len) {
297		p = mtod(m, char *) + xdrs->x_handy;
298		if (((uintptr_t) p) & (sizeof(int32_t) - 1))
299			return (0);
300		xdrs->x_handy += len;
301		if (xdrs->x_handy > m->m_len)
302			m->m_len = xdrs->x_handy;
303		return ((int32_t *) p);
304	}
305
306	return (0);
307}
308