// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) // // Efficient XDR implementation almost compatible with rpcgen (see run-rpcgen.sh) // XDR in a nutshell: // // int: big endian 32bit // unsigned: BE 32bit // enum: BE 32bit // bool: BE 32bit 0/1 // hyper: BE 64bit // unsigned hyper: BE 64bit // float: BE float // double: BE double // quadruple: BE long double // opaque[n] (fixed-length): bytes, padded to !(n%4) // opaque (variable-length): BE 32bit length, then n bytes, padded to !(n%4) // string: same as opaque // array[n] (fixed-length): n items of type T // vector (variable-length): BE 32bit length, then n items of type T // struct: components in the same order as specified // union: BE 32bit variant id, then variant of the union // void: nothing (empty, 0 byte data) // optional (XDR T*): BE 32bit 1/0, then T or nothing // linked list: sequence of optional entries // // RPC over TCP: // // BE 32bit length, then rpc_msg, then the procedure message itself #pragma once #include "xdr_impl.h" #include #include #include #include "../malloc_or_die.h" #define FALSE 0 #define TRUE 1 #define XDR_ENCODE 0 #define XDR_DECODE 1 #define BYTES_PER_XDR_UNIT 4 #define IXDR_PUT_U_LONG(a, b) #define IXDR_GET_U_LONG(a) 0 #define IXDR_PUT_BOOL(a, b) #define IXDR_GET_BOOL(a) 0 #define XDR_INLINE(xdrs, len) NULL struct xdr_linked_list_t { xdrproc_t fn; unsigned entry_size, size, cap; void *base; unsigned has_next, link_offset; }; struct XDR { int x_op; // For decoding: uint8_t *buf = NULL; unsigned avail = 0; std::vector allocs; std::vector in_linked_list; // For encoding: std::vector cur_out; unsigned last_end = 0; std::vector buf_list; }; uint32_t inline len_pad4(uint32_t len) { return ((len+3)/4) * 4; } inline int xdr_opaque(XDR *xdrs, void *data, uint32_t len) { if (len <= 0) { return 1; } if (xdrs->x_op == XDR_DECODE) { uint32_t padded = len_pad4(len); if (xdrs->avail < padded) return 0; memcpy(data, xdrs->buf, len); xdrs->buf += padded; xdrs->avail -= padded; } else { unsigned old = xdrs->cur_out.size(); uint32_t pad = (len & 3) ? (4 - (len & 3)) : 0; xdrs->cur_out.resize(old + len + pad); memcpy(xdrs->cur_out.data()+old, data, len); for (uint32_t i = 0; i < pad; i++) xdrs->cur_out[old+i] = 0; } return 1; } inline int xdr_bytes(XDR *xdrs, xdr_string_t *data, uint32_t maxlen) { if (xdrs->x_op == XDR_DECODE) { if (xdrs->avail < 4) return 0; uint32_t len = be32toh(*((uint32_t*)xdrs->buf)); uint32_t padded = len_pad4(len); if (xdrs->avail < 4+padded) return 0; data->size = len; data->data = (char*)(xdrs->buf+4); xdrs->buf += 4+padded; xdrs->avail -= 4+padded; } else { if (data->size < XDR_COPY_LENGTH) { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 4+data->size); *(uint32_t*)(xdrs->cur_out.data() + old) = htobe32(data->size); memcpy(xdrs->cur_out.data()+old+4, data->data, data->size); } else { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 4); *(uint32_t*)(xdrs->cur_out.data() + old) = htobe32(data->size); xdrs->buf_list.push_back((iovec){ .iov_base = 0, .iov_len = xdrs->cur_out.size() - xdrs->last_end, }); xdrs->last_end = xdrs->cur_out.size(); xdrs->buf_list.push_back((iovec) { .iov_base = (void*)data->data, .iov_len = data->size, }); } if (data->size & 3) { int pad = 4-(data->size & 3); unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old+pad); for (int i = 0; i < pad; i++) xdrs->cur_out[old+i] = 0; } } return 1; } inline int xdr_string(XDR *xdrs, xdr_string_t *data, uint32_t maxlen) { return xdr_bytes(xdrs, data, maxlen); } inline int xdr_u_int(XDR *xdrs, void *data) { if (xdrs->x_op == XDR_DECODE) { if (xdrs->avail < 4) return 0; *((uint32_t*)data) = be32toh(*((uint32_t*)xdrs->buf)); xdrs->buf += 4; xdrs->avail -= 4; } else { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 4); *(uint32_t*)(xdrs->cur_out.data() + old) = htobe32(*(uint32_t*)data); } return 1; } inline int xdr_enum(XDR *xdrs, void *data) { return xdr_u_int(xdrs, data); } inline int xdr_bool(XDR *xdrs, void *data) { return xdr_u_int(xdrs, data); } inline int xdr_uint64_t(XDR *xdrs, void *data) { if (xdrs->x_op == XDR_DECODE) { if (xdrs->avail < 8) return 0; *((uint64_t*)data) = be64toh(*((uint64_t*)xdrs->buf)); xdrs->buf += 8; xdrs->avail -= 8; } else { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 8); *(uint64_t*)(xdrs->cur_out.data() + old) = htobe64(*(uint64_t*)data); } return 1; } // Parse inconvenient shitty linked lists as arrays inline int xdr_pointer(XDR *xdrs, char **data, unsigned entry_size, xdrproc_t entry_fn) { if (xdrs->x_op == XDR_DECODE) { if (xdrs->avail < 4) return 0; uint32_t has_next = be32toh(*((uint32_t*)xdrs->buf)); xdrs->buf += 4; xdrs->avail -= 4; *data = NULL; if (!xdrs->in_linked_list.size() || xdrs->in_linked_list.back().fn != entry_fn) { if (has_next) { unsigned cap = 2; void *base = malloc_or_die(entry_size * cap); xdrs->in_linked_list.push_back((xdr_linked_list_t){ .fn = entry_fn, .entry_size = entry_size, .size = 1, .cap = cap, .base = base, .has_next = 0, .link_offset = 0, }); *data = (char*)base; if (!entry_fn(xdrs, base)) return 0; auto & ll = xdrs->in_linked_list.back(); while (ll.has_next) { ll.has_next = 0; if (ll.size >= ll.cap) { ll.cap *= 2; ll.base = realloc_or_die(ll.base, ll.entry_size * ll.cap); } if (!entry_fn(xdrs, (uint8_t*)ll.base + ll.entry_size*ll.size)) return 0; ll.size++; } for (unsigned i = 0; i < ll.size-1; i++) { *(void**)((uint8_t*)ll.base + i*ll.entry_size + ll.link_offset) = (uint8_t*)ll.base + (i+1)*ll.entry_size; } xdrs->allocs.push_back(ll.base); xdrs->in_linked_list.pop_back(); } } else { auto & ll = xdrs->in_linked_list.back(); xdrs->in_linked_list.back().has_next = has_next; xdrs->in_linked_list.back().link_offset = (uint8_t*)data - (uint8_t*)ll.base - ll.entry_size*ll.size; } } else { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 4); *(uint32_t*)(xdrs->cur_out.data() + old) = htobe32(*data ? 1 : 0); if (*data) entry_fn(xdrs, *data); } return 1; } inline int xdr_array(XDR *xdrs, char **data, uint32_t* len, uint32_t maxlen, uint32_t entry_size, xdrproc_t fn) { if (xdrs->x_op == XDR_DECODE) { if (xdrs->avail < 4) return 0; *len = be32toh(*((uint32_t*)xdrs->buf)); if (*len > maxlen) return 0; xdrs->buf += 4; xdrs->avail -= 4; *data = (char*)malloc_or_die(entry_size * (*len)); for (uint32_t i = 0; i < *len; i++) fn(xdrs, *data + entry_size*i); xdrs->allocs.push_back(*data); } else { unsigned old = xdrs->cur_out.size(); xdrs->cur_out.resize(old + 4); *(uint32_t*)(xdrs->cur_out.data() + old) = htobe32(*len); for (uint32_t i = 0; i < *len; i++) fn(xdrs, *data + entry_size*i); } return 1; }