aboutsummaryrefslogtreecommitdiff
path: root/rpc_client.c
blob: 4adf247c0bc282c9dc0745f78fe21ede6ac3cbcc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
2
# Copyright (c) 2015-2016, NORDUnet A/S
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
# - Neither the name of the NORDUnet nor the names of its contributors may
#   be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Number of static hash and HMAC state blocks to allocate.
# Numbers pulled out of a hat, just testing.

STATIC_CORE_STATE_BLOCKS = 32
STATIC_HASH_STATE_BLOCKS = 10
STATIC_HMAC_STATE_BLOCKS = 4
STATIC_PKEY_STATE_BLOCKS = 32
STATIC_KS_VOLATILE_SLOTS = 128

INC		= hal.h hal_internal.h
LIB		= libhal.a

# Error checking on known control options, some of which allow the user entirely too much rope.

USAGE := "usage: ${MAKE} [IO_BUS=eim|i2c|fmc] [RPC_MODE=none|server|client-simple|client-mixed] [KS=mmap|flash] [RPC_TRANSPORT=none|loopback|serial|daemon] [MODEXP_CORE=no|yes]"

IO_BUS		?= none
KS		?= flash
RPC_MODE	?= none
RPC_TRANSPORT	?= none
MODEXP_CORE	?= no
HASH_CORES	?= no

ifeq (,$(and \
	$(filter	none eim i2c fmc			,${IO_BUS}),\
	$(filter	none server client-simple client-mixed	,${RPC_MODE}),\
	$(filter	mmap flash				,${KS}),\
	$(filter	none loopback serial daemon		,${RPC_TRANSPORT}),\
	$(filter	no yes					,${MODEXP_CORE}),\
	$(filter	no yes					,${HASH_CORES})))
  $(error ${USAGE})
endif

$(info Building libhal with configuration IO_BUS=${IO_BUS} RPC_MODE=${RPC_MODE} KS=${KS} RPC_TRANSPORT=${RPC_TRANSPORT} MODEXP_CORE=${MODEXP_CORE} HASH_CORES=${HASH_CORES})

# Whether the RSA code should use the ModExp | ModExpS6 | ModExpA7 core.

ifeq "${MODEXP_CORE}" "yes"
  RSA_USE_MODEXP_CORE := 1
else
  RSA_USE_MODEXP_CORE := 0
endif

# Whether the hash code should use the SHA-1 / SHA-256 / SHA-512 cores.

ifeq "${HASH_CORES}" "yes"
  HASH_ONLY_USE_SOFT_CORES := 0
else
  HASH_ONLY_USE_SOFT_CORES := 1
endif

# Object files to build, initialized with ones we always want.
# There's a balance here between skipping files we don't strictly
# need and reducing the number of unnecessary conditionals in this
# makefile, so the working definition of "always want" is sometimes
# just "building this is harmless even if we don't use it."

OBJ += errorstrings.o hash.o asn1.o ecdsa.o rsa.o xdr.o slip.o
OBJ += rpc_api.o rpc_hash.o uuid.o rpc_pkcs1.o crc32.o locks.o

# Object files to build when we're on a platform with direct access
# to our hardware (Verilog) cores.

CORE_OBJ = core.o csprng.o pbkdf2.o aes_keywrap.o modexp.o mkmif.o ${IO_OBJ}

# I/O bus to the FPGA
#
# IO_BUS = none | eim | i2c | fmc
#  none:	No FPGA I/O bus
#   eim:	EIM bus from Novena
#   i2c:	Older I2C bus from Novena
#   fmc:	FMC bus from dev-bridge and alpha boards

ifeq "${IO_BUS}" "eim"
  IO_OBJ = hal_io_eim.o novena-eim.o
else ifeq "${IO_BUS}" "i2c"
  IO_OBJ = hal_io_i2c.o
else ifeq "${IO_BUS}" "fmc"
  IO_OBJ = hal_io_fmc.o
endif

# If we're building for STM32, position-independent code leads to some
# hard-to-debug function pointer errors. OTOH, if we're building for Linux
# (even on the Novena), we want to make it possible to build a shared library.

ifneq "${IO_BUS}" "fmc"
  CFLAGS += -fPIC
endif

# The keystore code has mutated a bit with the new API, and the Makefile,
# probably needs more extensive changes to track that.
#
# In the old world, the volatile keystore was for the client side,
# while the flash and mmap keystores were for the server side (on the
# Alpha and the Novena, respectively).
#
# In the new world, all keystores are on the server side, and the
# volatile keystore is always present, to support things like PKCS #11
# "session" objects.
#
# The mmap keystore hasn't been rewritten for the new API yet.

KS_OBJ = ks_index.o ks_attribute.o ks_volatile.o

ifeq "${KS}" "mmap"
  KS_OBJ += ks_mmap.o
else ifeq "${KS}" "flash"
  KS_OBJ += ks_flash.o mkm.o
endif

# RPC_MODE = none | server | client-simple | client-mixed
#   none:		Build without RPC client, use cores directly.
#   server:		Build for server side of RPC (HSM), use cores directly.
#   client-simple:	Build for other host, communicate with cores via RPC server.
#   client-mixed:	Like client-simple but do hashing locally in software and
#			support a local keystore (for PKCS #11 public keys, etc)
#
# RPC_TRANSPORT = none | loopback | serial | daemon
#   loopback:		Communicate over loopback socket on Novena
#   serial:		Communicate over USB in serial pass-through mode
#   daemon:		Communicate over USB via a daemon, to arbitrate multiple clients
#
# Note that RPC_MODE setting also controls the RPC_CLIENT setting passed to the C
# preprocessor via CFLAGS.  Whatever we pass here must evaluate to an integer in
# the C preprocessor: we can use symbolic names so long as they're defined as macros
# in the C code, but we can't use things like C enum symbols.

RPC_CLIENT_OBJ = rpc_client.o

ifeq "${RPC_TRANSPORT}" "loopback"
  RPC_CLIENT_OBJ += rpc_client_loopback.o
else ifeq "${RPC_TRANSPORT}" "serial"
  RPC_CLIENT_OBJ += rpc_serial.o rpc_client_serial.o
else ifeq "${RPC_TRANSPORT}" "daemon"
  RPC_CLIENT_OBJ += rpc_client_daemon.o
endif

RPC_SERVER_OBJ = ${KS_OBJ} rpc_misc.o rpc_pkey.o rpc_server.o

ifeq "${RPC_TRANSPORT}" "loopback"
  RPC_SERVER_OBJ += rpc_server_loopback.o
else ifeq "${RPC_TRANSPORT}" "serial"
  RPC_SERVER_OBJ += rpc_server_serial.o
endif

ifeq "${RPC_MODE}" "none"
  OBJ += ${CORE_OBJ}
  CFLAGS += -DHAL_RSA_USE_MODEXP=${RSA_USE_MODEXP_CORE}
else ifeq "${RPC_MODE}" "server"
  OBJ += ${CORE_OBJ} ${RPC_SERVER_OBJ}
  CFLAGS += -DRPC_CLIENT=RPC_CLIENT_LOCAL -DHAL_RSA_USE_MODEXP=${RSA_USE_MODEXP_CORE} -DHAL_ONLY_USE_SOFTWARE_HASH_CORES=${HASH_ONLY_USE_SOFT_CORES}
else ifeq "${RPC_MODE}" "client-simple"
  OBJ += ${RPC_CLIENT_OBJ}
  CFLAGS += -DRPC_CLIENT=RPC_CLIENT_REMOTE -DHAL_RSA_USE_MODEXP=0 -DHAL_ONLY_USE_SOFTWARE_HASH_CORES=1
else ifeq "${RPC_MODE}" "client-mixed"
  OBJ += ${RPC_CLIENT_OBJ}
  CFLAGS += -DRPC_CLIENT=RPC_CLIENT_MIXED -DHAL_RSA_USE_MODEXP=0 -DHAL_ONLY_USE_SOFTWARE_HASH_CORES=1
endif

ifndef CRYPTECH_ROOT
  CRYPTECH_ROOT := $(abspath ../..)
endif

LIBTFM_SRC	?= ${CRYPTECH_ROOT}/sw/thirdparty/libtfm
LIBTFM_BLD	?= ${LIBTFM_SRC}

# tfm.h is a generated file, because our Makefile customizes a few
# settings from the upstream libtfm distribution.  Because of this, we
# need to search the libtfm build directory, not the libtfm source
# directory.

CFLAGS		+= -g3 -Wall -std=c99 -Wno-strict-aliasing
CFLAGS		+= -DHAL_STATIC_CORE_STATE_BLOCKS=${STATIC_CORE_STATE_BLOCKS}
CFLAGS		+= -DHAL_STATIC_HASH_STATE_BLOCKS=${STATIC_HASH_STATE_BLOCKS}
CFLAGS		+= -DHAL_STATIC_HMAC_STATE_BLOCKS=${STATIC_HMAC_STATE_BLOCKS}
CFLAGS		+= -DHAL_STATIC_PKEY_STATE_BLOCKS=${STATIC_PKEY_STATE_BLOCKS}
CFLAGS		+= -I${CRYPTECH_ROOT}/sw/libhal
CFLAGS		+= -I${LIBTFM_BLD}

# Enable software hash cores everywhere for now.  In theory, there might be situations
# where we don't want them on the HSM, but they're relatively harmless, and the bootstrap
# sequence on new hardware works a lot better when we can log in before loading the FPGA.

CFLAGS		+= -DHAL_ENABLE_SOFTWARE_HASH_CORES=1

export CFLAGS
export RPC_MODE

all: ${LIB}
	cd tests; ${MAKE} $@
	cd utils; ${MAKE} $@

client:
	${MAKE} RPC_MODE=client-simple RPC_TRANSPORT=daemon

mixed:
	${MAKE} RPC_MODE=client-mixed RPC_TRANSPORT=daemon

server:
	${MAKE} RPC_MODE=server RPC_TRANSPORT=serial IO_BUS=fmc

serial:
	${MAKE} RPC_MODE=client-mixed RPC_TRANSPORT=serial

daemon: mixed

.PHONY: client mixed server serial daemon

${OBJ}: ${INC}

${LIB}:pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * rpc_client.c
 * ------------
 * Remote procedure call client-side private API implementation.
 *
 * Authors: Rob Austein, Paul Selkirk
 * Copyright (c) 2015-2016, NORDUnet A/S All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the NORDUnet nor the names of its contributors may
 *   be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <assert.h>

#include "hal.h"
#include "hal_internal.h"
#include "xdr_internal.h"

#ifndef HAL_RPC_CLIENT_DEBUG
#define HAL_RPC_CLIENT_DEBUG 0
#endif

#if HAL_RPC_CLIENT_DEBUG
#include <stdio.h>
#define check(op) do { const hal_error_t _err_ = (op); if (_err_ != HAL_OK) { printf("%s returned %d (%s)\n", #op, _err_, hal_error_string(_err_)); return _err_; } } while (0)
#else
#define check(op) do { const hal_error_t _err_ = (op); if (_err_ != HAL_OK) { return _err_; } } while (0)
#endif

#define pad(n) (((n) + 3) & ~3)

#define nargs(n) ((n) * 4)

#if RPC_CLIENT != RPC_CLIENT_LOCAL

/*
 * Consolidate a bit of the repetitive code from the packet receive loop.
 * We're looking for a packet which is a response to the packet we sent,
 * so if the opcode is wrong, we discard and wait for another packet.
 */

static hal_error_t read_matching_packet(const rpc_func_num_t expected_func,
                                        uint8_t *inbuf,
                                        const size_t inbuf_max,
                                        const uint8_t **iptr,
                                        const uint8_t **ilimit)
{
  hal_client_handle_t dummy_client;
  uint32_t received_func;
  size_t ilen = inbuf_max;
  hal_error_t err;

  assert(inbuf != NULL && iptr != NULL && ilimit != NULL);

  do {

    if ((err = hal_rpc_recv(inbuf, &ilen)) != HAL_OK)
      return err;

    assert(ilen <= inbuf_max);
    *iptr = inbuf;
    *ilimit = inbuf + ilen;

    if ((err = hal_xdr_decode_int(iptr, *ilimit, &received_func))       == HAL_ERROR_XDR_BUFFER_OVERFLOW)
      continue;
    if (err != HAL_OK)
      return err;

    if ((err = hal_xdr_decode_int(iptr, *ilimit, &dummy_client.handle)) == HAL_ERROR_XDR_BUFFER_OVERFLOW)
      continue;
    if (err != HAL_OK)
      return err;

  } while (received_func != expected_func);

  return HAL_OK;
}

/*
 * RPC calls.
 *
 * In reading these, it helps to know that every call takes a minimum
 * of two arguments (function code and client handle, even if the
 * latter is just a dummy), and that every call returns a minimum of
 * three values (function code, client handle, and return status).
 * This may seem a bit redundant, but There Are Reasons:
 * read_matching_packet() wants to make sure the result we're getting
 * is from the function we thought we called, and having the client
 * handle always present in a known place vastly simplifies the task
 * of the client-side MUX daemon.
 */

static hal_error_t get_version(uint32_t *version)
{
  uint8_t outbuf[nargs(2)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_GET_VERSION));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_GET_VERSION, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, version));
  }
  return rpc_ret;
}

static hal_error_t get_random(void *buffer, const size_t length)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(length)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t rcvlen = length;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_GET_RANDOM));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, (uint32_t)length));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_GET_RANDOM, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_buffer(&iptr, ilimit, buffer, &rcvlen));
    // XXX check rcvlen vs length
  }
  return rpc_ret;
}

static hal_error_t set_pin(const hal_client_handle_t client,
                           const hal_user_t user,
                           const char * const pin, const size_t pin_len)
{
  uint8_t outbuf[nargs(4) + pad(pin_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_SET_PIN));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, user));
  check(hal_xdr_encode_buffer(&optr, olimit, (const uint8_t *)pin, pin_len));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_SET_PIN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

/*
 * We may end up wanting to wrap a client-side cache around the
 * login()/logout()/logout_all() calls and reimplement is_logged_in()
 * on the client side using that cache, so that access checks don't
 * need to cross the RPC boundary.  Then again, we might not, if the
 * RPC call is fast enough, so implementing all before the RPC would
 * qualify as premature optimization.  There aren't all that many
 * things on the client side that would use this anyway, so the whole
 * question may be moot.
 *
 * For now, we leave all of these as plain RPC calls, but we may want
 * to revisit this if the is_logged_in() call turns into a bottleneck.
 */

static hal_error_t login(const hal_client_handle_t client,
                         const hal_user_t user,
                         const char * const pin, const size_t pin_len)
{
  uint8_t outbuf[nargs(4) + pad(pin_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_LOGIN));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, user));
  check(hal_xdr_encode_buffer(&optr, olimit, (const uint8_t *)pin, pin_len));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_LOGIN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t logout(const hal_client_handle_t client)
{
  uint8_t outbuf[nargs(2)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_LOGOUT));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_LOGOUT, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t logout_all(void)
{
  uint8_t outbuf[nargs(2)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_LOGOUT_ALL));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_LOGOUT_ALL, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t is_logged_in(const hal_client_handle_t client,
                                const hal_user_t user)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_IS_LOGGED_IN));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, user));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_IS_LOGGED_IN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t hash_get_digest_len(const hal_digest_algorithm_t alg, size_t *length)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t len32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_GET_DIGEST_LEN));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, alg));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_GET_DIGEST_LEN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &len32));
    *length = (size_t)len32;
  }
  return rpc_ret;
}

static hal_error_t hash_get_digest_algorithm_id(const hal_digest_algorithm_t alg,
                                                uint8_t *id, size_t *len, const size_t len_max)
{
  uint8_t outbuf[nargs(4)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(len_max)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t len32 = len_max;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_GET_DIGEST_ALGORITHM_ID));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, alg));
  check(hal_xdr_encode_int(&optr, olimit, len_max));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_GET_DIGEST_ALGORITHM_ID,
                             inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_buffer(&iptr, ilimit, id, &len32));
    *len = len32;
  }
  return rpc_ret;
}

static hal_error_t hash_get_algorithm(const hal_hash_handle_t hash, hal_digest_algorithm_t *alg)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t alg32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_GET_ALGORITHM));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, hash.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_GET_ALGORITHM, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &alg32));
    *alg = (hal_digest_algorithm_t) alg32;
  }
  return rpc_ret;
}

static hal_error_t hash_initialize(const hal_client_handle_t client,
                                   const hal_session_handle_t session,
                                   hal_hash_handle_t *hash,
                                   const hal_digest_algorithm_t alg,
                                   const uint8_t * const key, const size_t key_len)
{
  uint8_t outbuf[nargs(5) + pad(key_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_INITIALIZE));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_int(&optr, olimit, alg));
  check(hal_xdr_encode_buffer(&optr, olimit, key, key_len));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_INITIALIZE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &hash->handle));
  }
  return rpc_ret;
}

static hal_error_t hash_update(const hal_hash_handle_t hash,
                               const uint8_t * data, const size_t length)
{
  uint8_t outbuf[nargs(4) + pad(length)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_UPDATE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, hash.handle));
  check(hal_xdr_encode_buffer(&optr, olimit, data, length));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_UPDATE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t hash_finalize(const hal_hash_handle_t hash,
                                 uint8_t *digest, const size_t length)
{
  uint8_t outbuf[nargs(4)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(length)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t digest_len = length;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_HASH_FINALIZE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, hash.handle));
  check(hal_xdr_encode_int(&optr, olimit, length));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_HASH_FINALIZE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_buffer(&iptr, ilimit, digest, &digest_len));
    /* XXX check digest_len vs length */
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_load(const hal_client_handle_t client,
                                    const hal_session_handle_t session,
                                    hal_pkey_handle_t *pkey,
                                    const hal_key_type_t type,
                                    const hal_curve_name_t curve,
                                    hal_uuid_t *name,
                                    const uint8_t * const der, const size_t der_len,
                                    const hal_key_flags_t flags)
{
  uint8_t outbuf[nargs(7) + pad(der_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(5) + pad(sizeof(name->uuid))];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t name_len = sizeof(name->uuid);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_LOAD));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_int(&optr, olimit, type));
  check(hal_xdr_encode_int(&optr, olimit, curve));
  check(hal_xdr_encode_buffer(&optr, olimit, der, der_len));
  check(hal_xdr_encode_int(&optr, olimit, flags));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_LOAD, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));

  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &pkey->handle));
    check(hal_xdr_decode_buffer(&iptr, ilimit, name->uuid, &name_len));
    if (name_len != sizeof(name->uuid))
      return HAL_ERROR_KEY_NAME_TOO_LONG;
  }

  return rpc_ret;
}

static hal_error_t pkey_remote_open(const hal_client_handle_t client,
                                    const hal_session_handle_t session,
                                    hal_pkey_handle_t *pkey,
                                    const hal_uuid_t * const name,
                                    const hal_key_flags_t flags)
{
  uint8_t outbuf[nargs(5) + pad(sizeof(name->uuid))], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_OPEN));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_buffer(&optr, olimit, name->uuid, sizeof(name->uuid)));
  check(hal_xdr_encode_int(&optr, olimit, flags));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_OPEN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));

  if (rpc_ret == HAL_OK)
    check(hal_xdr_decode_int(&iptr, ilimit, &pkey->handle));

  return rpc_ret;
}

static hal_error_t pkey_remote_generate_rsa(const hal_client_handle_t client,
                                            const hal_session_handle_t session,
                                            hal_pkey_handle_t *pkey,
                                            hal_uuid_t *name,
                                            const unsigned key_len,
                                            const uint8_t * const exp, const size_t exp_len,
                                            const hal_key_flags_t flags)
{
  uint8_t outbuf[nargs(6) + pad(exp_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(5) + pad(sizeof(name->uuid))];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t name_len = sizeof(name->uuid);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GENERATE_RSA));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_int(&optr, olimit, key_len));
  check(hal_xdr_encode_buffer(&optr, olimit, exp, exp_len));
  check(hal_xdr_encode_int(&optr, olimit, flags));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GENERATE_RSA, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));

  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &pkey->handle));
    check(hal_xdr_decode_buffer(&iptr, ilimit, name->uuid, &name_len));
    if (name_len != sizeof(name->uuid))
      return HAL_ERROR_KEY_NAME_TOO_LONG;
  }

  return rpc_ret;
}

static hal_error_t pkey_remote_generate_ec(const hal_client_handle_t client,
                                           const hal_session_handle_t session,
                                           hal_pkey_handle_t *pkey,
                                           hal_uuid_t *name,
                                           const hal_curve_name_t curve,
                                           const hal_key_flags_t flags)
{
  uint8_t outbuf[nargs(5)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(5) + pad(sizeof(name->uuid))];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t name_len = sizeof(name->uuid);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GENERATE_EC));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_int(&optr, olimit, curve));
  check(hal_xdr_encode_int(&optr, olimit, flags));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GENERATE_EC, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));

  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &pkey->handle));
    check(hal_xdr_decode_buffer(&iptr, ilimit, name->uuid, &name_len));
    if (name_len != sizeof(name->uuid))
      return HAL_ERROR_KEY_NAME_TOO_LONG;
  }

  return rpc_ret;
}

static hal_error_t pkey_remote_close(const hal_pkey_handle_t pkey)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_CLOSE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_CLOSE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t pkey_remote_delete(const hal_pkey_handle_t pkey)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_DELETE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_DELETE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t pkey_remote_get_key_type(const hal_pkey_handle_t pkey,
                                            hal_key_type_t *type)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t type32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_KEY_TYPE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_KEY_TYPE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &type32));
    *type = (hal_key_type_t)type32;
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_get_key_curve(const hal_pkey_handle_t pkey,
                                             hal_curve_name_t *curve)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t curve32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_KEY_CURVE));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_KEY_CURVE, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &curve32));
    *curve = (hal_curve_name_t)curve32;
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_get_key_flags(const hal_pkey_handle_t pkey,
                                             hal_key_flags_t *flags)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t flags32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_KEY_FLAGS));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_KEY_FLAGS, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &flags32));
    *flags = (hal_key_flags_t)flags32;
  }
  return rpc_ret;
}

static size_t pkey_remote_get_public_key_len(const hal_pkey_handle_t pkey)
{
  uint8_t outbuf[nargs(3)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t len32;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_PUBLIC_KEY_LEN));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_PUBLIC_KEY_LEN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_int(&iptr, ilimit, &len32));
    return (size_t)len32;
  }
  else
    return 0;
}

static hal_error_t pkey_remote_get_public_key(const hal_pkey_handle_t pkey,
                                              uint8_t *der, size_t *der_len, const size_t der_max)
{
  uint8_t outbuf[nargs(4)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(der_max)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t dlen32 = der_max;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_PUBLIC_KEY));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_xdr_encode_int(&optr, olimit, der_max));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_PUBLIC_KEY, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_buffer(&iptr, ilimit, der, &dlen32));
    *der_len = (size_t)dlen32;
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_sign(const hal_pkey_handle_t pkey,
                                    const hal_hash_handle_t hash,
                                    const uint8_t * const input, const size_t input_len,
                                    uint8_t * signature, size_t *signature_len, const size_t signature_max)
{
  uint8_t outbuf[nargs(6) + pad(input_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(signature_max)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  uint32_t slen32 = signature_max;
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_SIGN));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_xdr_encode_int(&optr, olimit, hash.handle));
  check(hal_xdr_encode_buffer(&optr, olimit, input, input_len));
  check(hal_xdr_encode_int(&optr, olimit, signature_max));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_SIGN, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    check(hal_xdr_decode_buffer(&iptr, ilimit, signature, &slen32));
    *signature_len = (size_t)slen32;
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_verify(const hal_pkey_handle_t pkey,
                                      const hal_hash_handle_t hash,
                                      const uint8_t * const input, const size_t input_len,
                                      const uint8_t * const signature, const size_t signature_len)
{
  uint8_t outbuf[nargs(6) + pad(input_len) + pad(signature_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_VERIFY));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_xdr_encode_int(&optr, olimit, hash.handle));
  check(hal_xdr_encode_buffer(&optr, olimit, input, input_len));
  check(hal_xdr_encode_buffer(&optr, olimit, signature, signature_len));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_VERIFY, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t pkey_remote_match(const hal_client_handle_t client,
                                     const hal_session_handle_t session,
                                     const hal_key_type_t type,
                                     const hal_curve_name_t curve,
                                     const hal_key_flags_t flags,
                                     const hal_pkey_attribute_t *attributes,
                                     const unsigned attributes_len,
                                     hal_uuid_t *result,
                                     unsigned *result_len,
                                     const unsigned result_max,
                                     const hal_uuid_t * const previous_uuid)
{
  size_t attributes_buffer_len = 0;
  if (attributes != NULL)
    for (int i = 0; i < attributes_len; i++)
      attributes_buffer_len += pad(attributes[i].length);

  uint8_t outbuf[nargs(9 + attributes_len * 2) + attributes_buffer_len + pad(sizeof(hal_uuid_t))];
  uint8_t *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(4) + pad(result_max * sizeof(hal_uuid_t))];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_MATCH));
  check(hal_xdr_encode_int(&optr, olimit, client.handle));
  check(hal_xdr_encode_int(&optr, olimit, session.handle));
  check(hal_xdr_encode_int(&optr, olimit, type));
  check(hal_xdr_encode_int(&optr, olimit, curve));
  check(hal_xdr_encode_int(&optr, olimit, flags));
  check(hal_xdr_encode_int(&optr, olimit, attributes_len));
  if (attributes != NULL) {
    for (int i = 0; i < attributes_len; i++) {
      check(hal_xdr_encode_int(&optr, olimit, attributes[i].type));
      check(hal_xdr_encode_buffer(&optr, olimit, attributes[i].value, attributes[i].length));
    }
  }
  check(hal_xdr_encode_int(&optr, olimit, result_max));
  check(hal_xdr_encode_buffer(&optr, olimit, previous_uuid->uuid, sizeof(previous_uuid->uuid)));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_MATCH, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    uint32_t array_len;
    *result_len = 0;
    check(hal_xdr_decode_int(&iptr, ilimit, &array_len));
    for (int i = 0; i < array_len; ++i) {
      uint32_t uuid_len = sizeof(result[i].uuid);
      check(hal_xdr_decode_buffer(&iptr, ilimit, result[i].uuid, &uuid_len));
      if (uuid_len != sizeof(result[i].uuid))
        return HAL_ERROR_KEY_NAME_TOO_LONG;
    }
    *result_len = array_len;
  }
  return rpc_ret;
}

static hal_error_t pkey_remote_set_attributes(const hal_pkey_handle_t pkey,
                                             const hal_pkey_attribute_t *attributes,
                                             const unsigned attributes_len)
{
  size_t outbuf_len = nargs(4 + 2 * attributes_len);
  for (int i = 0; i < attributes_len; i++)
    outbuf_len += pad(attributes[i].length);

  uint8_t outbuf[outbuf_len], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3)];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_SET_ATTRIBUTES));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_xdr_encode_int(&optr, olimit, attributes_len));
  for (int i = 0; i < attributes_len; i++) {
    check(hal_xdr_encode_int(&optr, olimit, attributes[i].type));
    if (attributes[i].length == HAL_PKEY_ATTRIBUTE_NIL)
      check(hal_xdr_encode_int(&optr, olimit, HAL_PKEY_ATTRIBUTE_NIL));
    else
      check(hal_xdr_encode_buffer(&optr, olimit, attributes[i].value, attributes[i].length));
  }
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_SET_ATTRIBUTES, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  return rpc_ret;
}

static hal_error_t pkey_remote_get_attributes(const hal_pkey_handle_t pkey,
                                              hal_pkey_attribute_t *attributes,
                                              const unsigned attributes_len,
                                              uint8_t *attributes_buffer,
                                              const size_t attributes_buffer_len)
{
  /* inbuf[] here includes one extra word per attribute for padding */
  uint8_t outbuf[nargs(5 + attributes_len)], *optr = outbuf, *olimit = outbuf + sizeof(outbuf);
  uint8_t inbuf[nargs(3 + 3 * attributes_len) + attributes_buffer_len];
  const uint8_t *iptr = inbuf, *ilimit = inbuf + sizeof(inbuf);
  hal_client_handle_t dummy_client = {0};
  hal_error_t rpc_ret;

  check(hal_xdr_encode_int(&optr, olimit, RPC_FUNC_PKEY_GET_ATTRIBUTES));
  check(hal_xdr_encode_int(&optr, olimit, dummy_client.handle));
  check(hal_xdr_encode_int(&optr, olimit, pkey.handle));
  check(hal_xdr_encode_int(&optr, olimit, attributes_len));
  for (int i = 0; i < attributes_len; i++)
    check(hal_xdr_encode_int(&optr, olimit, attributes[i].type));
  check(hal_xdr_encode_int(&optr, olimit, attributes_buffer_len));
  check(hal_rpc_send(outbuf, optr - outbuf));

  check(read_matching_packet(RPC_FUNC_PKEY_GET_ATTRIBUTES, inbuf, sizeof(inbuf), &iptr, &ilimit));

  check(hal_xdr_decode_int(&iptr, ilimit, &rpc_ret));
  if (rpc_ret == HAL_OK) {
    uint8_t *abuf = attributes_buffer;
    uint32_t u32;
    check(hal_xdr_decode_int(&iptr, ilimit, &u32));
    if (u32 != attributes_len)
      return HAL_ERROR_RPC_PROTOCOL_ERROR;
    for (int i = 0; i < attributes_len; i++) {
      check(hal_xdr_decode_int(&iptr, ilimit, &u32));
      if (u32 != attributes[i].type)
        return HAL_ERROR_RPC_PROTOCOL_ERROR;
      if (attributes_buffer_len == 0) {
        check(hal_xdr_decode_int(&iptr, ilimit, &u32));
        attributes[i].value  = NULL;
        attributes[i].length = u32;
      }
      else {
        u32 = attributes_buffer + attributes_buffer_len - abuf;
        check(hal_xdr_decode_buffer(&iptr, ilimit, abuf, &u32));
        attributes[i].value  = abuf;
        attributes[i].length = u32;
        abuf += u32;
      }
    }
  }
  return rpc_ret;
}

#if RPC_CLIENT == RPC_CLIENT_MIXED

/*
 * "Mixed" mode pkey operations, where the public key operation itself
 * takes place on the HSM but the hashing takes place locally.  If
 * we're given a hash context in this case, it's local, so we have to
 * pull the digest from the hash context and send that to the HSM.
 */

static hal_error_t pkey_mixed_sign(const hal_pkey_handle_t pkey,
                                   const hal_hash_handle_t hash,
                                   const uint8_t * const input,  const size_t input_len,
                                   uint8_t * signature, size_t *signature_len, const size_t signature_max)
{
  if (input != NULL)
    return hal_rpc_remote_pkey_dispatch.sign(pkey, hash, input, input_len,
                                             signature, signature_len, signature_max);

  hal_digest_algorithm_t alg;
  hal_key_type_t pkey_type;
  size_t digest_len;
  hal_error_t err;

  if ((err = hal_rpc_hash_get_algorithm(hash, &alg))           != HAL_OK ||
      (err = hal_rpc_hash_get_digest_length(alg, &digest_len)) != HAL_OK ||
      (err = hal_rpc_pkey_get_key_type(pkey, &pkey_type))      != HAL_OK)
    return err;

  uint8_t digest[digest_len > signature_max ? digest_len : signature_max];

  switch (pkey_type) {

  case HAL_KEY_TYPE_RSA_PRIVATE:
  case HAL_KEY_TYPE_RSA_PUBLIC:
    if ((err = hal_rpc_pkcs1_construct_digestinfo(hash, digest, &digest_len, sizeof(digest))) != HAL_OK)
      return err;
    break;

  default:
    if ((err = hal_rpc_hash_finalize(hash, digest, digest_len)) != HAL_OK)
      return err;

  }

  return hal_rpc_remote_pkey_dispatch.sign(pkey, hal_hash_handle_none, digest, digest_len,
                                           signature, signature_len, signature_max);
}

static hal_error_t pkey_mixed_verify(const hal_pkey_handle_t pkey,
                                     const hal_hash_handle_t hash,
                                     const uint8_t * const input, const size_t input_len,
                                     const uint8_t * const signature, const size_t signature_len)
{
  if (input != NULL)
    return hal_rpc_remote_pkey_dispatch.verify(pkey, hash, input, input_len,
                                               signature, signature_len);

  hal_digest_algorithm_t alg;
  hal_key_type_t pkey_type;
  size_t digest_len;
  hal_error_t err;

  if ((err = hal_rpc_hash_get_algorithm(hash, &alg))           != HAL_OK ||
      (err = hal_rpc_hash_get_digest_length(alg, &digest_len)) != HAL_OK ||
      (err = hal_rpc_pkey_get_key_type(pkey, &pkey_type))      != HAL_OK)
    return err;

  uint8_t digest[digest_len > signature_len ? digest_len : signature_len];

  switch (pkey_type) {

  case HAL_KEY_TYPE_RSA_PRIVATE:
  case HAL_KEY_TYPE_RSA_PUBLIC:
    if ((err = hal_rpc_pkcs1_construct_digestinfo(hash, digest, &digest_len, sizeof(digest))) != HAL_OK)
      return err;
    break;

  default:
    if ((err = hal_rpc_hash_finalize(hash, digest, digest_len)) != HAL_OK)
      return err;

  }

  return hal_rpc_remote_pkey_dispatch.verify(pkey, hal_hash_handle_none,
                                             digest, digest_len, signature, signature_len);
}

#endif /* RPC_CLIENT == RPC_CLIENT_MIXED */

/*
 * Dispatch vectors.
 */

const hal_rpc_misc_dispatch_t hal_rpc_remote_misc_dispatch = {
  .set_pin                      = set_pin,
  .login                        = login,
  .logout                       = logout,
  .logout_all                   = logout_all,
  .is_logged_in                 = is_logged_in,
  .get_random                   = get_random,
  .get_version                  = get_version
};

const hal_rpc_hash_dispatch_t hal_rpc_remote_hash_dispatch = {
  .get_digest_length            = hash_get_digest_len,
  .get_digest_algorithm_id      = hash_get_digest_algorithm_id,
  .get_algorithm                = hash_get_algorithm,
  .initialize                   = hash_initialize,
  .update                       = hash_update,
  .finalize                     = hash_finalize
};

const hal_rpc_pkey_dispatch_t hal_rpc_remote_pkey_dispatch = {
  .load                         = pkey_remote_load,
  .open                         = pkey_remote_open,
  .generate_rsa                 = pkey_remote_generate_rsa,
  .generate_ec                  = pkey_remote_generate_ec,
  .close                        = pkey_remote_close,
  .delete                       = pkey_remote_delete,
  .get_key_type                 = pkey_remote_get_key_type,
  .get_key_curve                = pkey_remote_get_key_curve,
  .get_key_flags                = pkey_remote_get_key_flags,
  .get_public_key_len           = pkey_remote_get_public_key_len,
  .get_public_key               = pkey_remote_get_public_key,
  .sign                         = pkey_remote_sign,
  .verify                       = pkey_remote_verify,
  .match                        = pkey_remote_match,
  .set_attributes               = pkey_remote_set_attributes,
  .get_attributes               = pkey_remote_get_attributes
};

#if RPC_CLIENT == RPC_CLIENT_MIXED
const hal_rpc_pkey_dispatch_t hal_rpc_mixed_pkey_dispatch = {
  .load                         = pkey_remote_load,
  .open                         = pkey_remote_open,
  .generate_rsa                 = pkey_remote_generate_rsa,
  .generate_ec                  = pkey_remote_generate_ec,
  .close                        = pkey_remote_close,
  .delete                       = pkey_remote_delete,
  .get_key_type                 = pkey_remote_get_key_type,
  .get_key_curve                = pkey_remote_get_key_curve,
  .get_key_flags                = pkey_remote_get_key_flags,
  .get_public_key_len           = pkey_remote_get_public_key_len,
  .get_public_key               = pkey_remote_get_public_key,
  .sign                         = pkey_mixed_sign,
  .verify                       = pkey_mixed_verify,
  .match                        = pkey_remote_match,
  .set_attributes               = pkey_remote_set_attributes,
  .get_attributes               = pkey_remote_get_attributes
};
#endif /* RPC_CLIENT == RPC_CLIENT_MIXED */

#endif /* RPC_CLIENT != RPC_CLIENT_LOCAL */


#if RPC_CLIENT == RPC_CLIENT_REMOTE
const hal_rpc_misc_dispatch_t * hal_rpc_misc_dispatch = &hal_rpc_remote_misc_dispatch;
const hal_rpc_hash_dispatch_t * hal_rpc_hash_dispatch = &hal_rpc_remote_hash_dispatch;
const hal_rpc_pkey_dispatch_t * hal_rpc_pkey_dispatch = &hal_rpc_remote_pkey_dispatch;
#endif

#if RPC_CLIENT == RPC_CLIENT_MIXED
const hal_rpc_misc_dispatch_t * hal_rpc_misc_dispatch = &hal_rpc_remote_misc_dispatch;
const hal_rpc_hash_dispatch_t * hal_rpc_hash_dispatch = &hal_rpc_local_hash_dispatch;
const hal_rpc_pkey_dispatch_t * hal_rpc_pkey_dispatch = &hal_rpc_mixed_pkey_dispatch;
#endif

hal_error_t hal_rpc_client_init(void)
{
#if RPC_CLIENT == RPC_CLIENT_LOCAL
  return HAL_OK;
#else
  return hal_rpc_client_transport_init();
#endif
}

hal_error_t hal_rpc_client_close(void)
{
#if RPC_CLIENT == RPC_CLIENT_LOCAL
  return HAL_OK;
#else
  return hal_rpc_client_transport_close();
#endif
}


/*
 * Local variables:
 * indent-tabs-mode: nil
 * End:
 */