diff options
Diffstat (limited to 'src')
112 files changed, 4872 insertions, 1810 deletions
diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h index 0ae1005e67..67cd356ef9 100644 --- a/src/core/channel/channel_stack.h +++ b/src/core/channel/channel_stack.h @@ -124,9 +124,17 @@ typedef struct { char *grpc_call_op_string(grpc_call_op *op); typedef enum { - GRPC_CHANNEL_SHUTDOWN, + /* send a goaway message to remote channels indicating that we are going + to disconnect in the future */ + GRPC_CHANNEL_GOAWAY, + /* disconnect any underlying transports */ + GRPC_CHANNEL_DISCONNECT, + /* transport received a new call */ GRPC_ACCEPT_CALL, - GRPC_TRANSPORT_CLOSED + /* an underlying transport was closed */ + GRPC_TRANSPORT_CLOSED, + /* an underlying transport is about to be closed */ + GRPC_TRANSPORT_GOAWAY } grpc_channel_op_type; /* A single filterable operation to be performed on a channel */ @@ -142,6 +150,10 @@ typedef struct { grpc_transport *transport; const void *transport_server_data; } accept_call; + struct { + grpc_status_code status; + gpr_slice message; + } goaway; } data; } grpc_channel_op; diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c index 90563683d5..0ceffba62a 100644 --- a/src/core/channel/client_channel.c +++ b/src/core/channel/client_channel.c @@ -392,8 +392,14 @@ static void broadcast_channel_op_down(grpc_channel_element *elem, /* send the message down */ for (i = 0; i < child_count; i++) { child_elem = grpc_channel_stack_element(children[i], 0); + if (op->type == GRPC_CHANNEL_GOAWAY) { + gpr_slice_ref(op->data.goaway.message); + } child_elem->filter->channel_op(child_elem, op); } + if (op->type == GRPC_CHANNEL_GOAWAY) { + gpr_slice_unref(op->data.goaway.message); + } /* unmark the inflight requests */ gpr_mu_lock(&chand->mu); diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c index 336472e740..fb2d5ad328 100644 --- a/src/core/channel/connected_channel.c +++ b/src/core/channel/connected_channel.c @@ -169,7 +169,11 @@ static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); switch (op->type) { - case GRPC_CHANNEL_SHUTDOWN: + case GRPC_CHANNEL_GOAWAY: + grpc_transport_goaway(chand->transport, op->data.goaway.status, + op->data.goaway.message); + break; + case GRPC_CHANNEL_DISCONNECT: grpc_transport_close(chand->transport); break; default: @@ -439,7 +443,6 @@ static void recv_batch(void *user_data, grpc_transport *transport, "Last message truncated; read %d bytes, expected %d", calld->incoming_message.length, calld->incoming_message_length); - return; } call_op.type = GRPC_RECV_HALF_CLOSE; call_op.dir = GRPC_CALL_UP; @@ -458,6 +461,29 @@ static void recv_batch(void *user_data, grpc_transport *transport, } } +static void transport_goaway(void *user_data, grpc_transport *transport, + grpc_status_code status, gpr_slice debug) { + /* transport got goaway ==> call up and handle it */ + grpc_channel_element *elem = user_data; + channel_data *chand = elem->channel_data; + char *msg; + grpc_channel_op op; + + GPR_ASSERT(elem->filter == &grpc_connected_channel_filter); + GPR_ASSERT(chand->transport == transport); + + msg = gpr_hexdump((const char *)GPR_SLICE_START_PTR(debug), + GPR_SLICE_LENGTH(debug), GPR_HEXDUMP_PLAINTEXT); + gpr_log(GPR_DEBUG, "got goaway: status=%d, message=%s", status, msg); + gpr_free(msg); + + op.type = GRPC_TRANSPORT_GOAWAY; + op.dir = GRPC_CALL_UP; + op.data.goaway.status = status; + op.data.goaway.message = debug; + channel_op(elem, &op); +} + static void transport_closed(void *user_data, grpc_transport *transport) { /* transport was closed ==> call up and handle it */ grpc_channel_element *elem = user_data; @@ -473,7 +499,8 @@ static void transport_closed(void *user_data, grpc_transport *transport) { } const grpc_transport_callbacks connected_channel_transport_callbacks = { - alloc_recv_buffer, accept_stream, recv_batch, transport_closed, + alloc_recv_buffer, accept_stream, recv_batch, + transport_goaway, transport_closed, }; grpc_transport_setup_result grpc_connected_channel_bind_transport( diff --git a/src/core/endpoint/resolve_address.c b/src/core/endpoint/resolve_address.c index aa21954c6d..1993b9bdc5 100644 --- a/src/core/endpoint/resolve_address.c +++ b/src/core/endpoint/resolve_address.c @@ -41,10 +41,12 @@ #include <unistd.h> #include <string.h> +#include "src/core/endpoint/socket_utils.h" #include <grpc/support/alloc.h> #include <grpc/support/string.h> #include <grpc/support/log.h> #include <grpc/support/thd.h> +#include <grpc/support/time.h> typedef struct { char *name; @@ -119,6 +121,7 @@ grpc_resolved_addresses *grpc_blocking_resolve_address( int s; size_t i; grpc_resolved_addresses *addrs = NULL; + const gpr_timespec start_time = gpr_now(); /* parse name, splitting it into host and port parts */ split_host_port(name, &host, &port); @@ -160,6 +163,22 @@ grpc_resolved_addresses *grpc_blocking_resolve_address( i++; } + /* Temporary logging, to help identify flakiness in dualstack_socket_test. */ + { + const gpr_timespec delay = gpr_time_sub(gpr_now(), start_time); + const int delay_ms = + delay.tv_sec * GPR_MS_PER_SEC + delay.tv_nsec / GPR_NS_PER_MS; + gpr_log(GPR_INFO, "logspam: getaddrinfo(%s, %s) resolved %d addrs in %dms:", + host, port, addrs->naddrs, delay_ms); + for (i = 0; i < addrs->naddrs; i++) { + char *buf; + grpc_sockaddr_to_string(&buf, (struct sockaddr *)&addrs->addrs[i].addr, + 0); + gpr_log(GPR_INFO, "logspam: [%d] %s", i, buf); + gpr_free(buf); + } + } + done: gpr_free(host); gpr_free(port); diff --git a/src/core/endpoint/secure_endpoint.c b/src/core/endpoint/secure_endpoint.c index 4fab0faa03..ecf41d71a8 100644 --- a/src/core/endpoint/secure_endpoint.c +++ b/src/core/endpoint/secure_endpoint.c @@ -50,6 +50,8 @@ typedef struct { /* saved upper level callbacks and user_data. */ grpc_endpoint_read_cb read_cb; void *read_user_data; + grpc_endpoint_write_cb write_cb; + void *write_user_data; /* saved handshaker leftover data to unprotect. */ gpr_slice_buffer leftover_bytes; /* buffers for read and write */ @@ -208,6 +210,12 @@ static void flush_write_staging_buffer(secure_endpoint *ep, gpr_uint8 **cur, *end = GPR_SLICE_END_PTR(ep->write_staging_buffer); } +static void on_write(void *data, grpc_endpoint_cb_status error) { + secure_endpoint *ep = data; + ep->write_cb(ep->write_user_data, error); + secure_endpoint_unref(ep); +} + static grpc_endpoint_write_status write(grpc_endpoint *secure_ep, gpr_slice *slices, size_t nslices, grpc_endpoint_write_cb cb, @@ -219,6 +227,7 @@ static grpc_endpoint_write_status write(grpc_endpoint *secure_ep, secure_endpoint *ep = (secure_endpoint *)secure_ep; gpr_uint8 *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer); gpr_uint8 *end = GPR_SLICE_END_PTR(ep->write_staging_buffer); + grpc_endpoint_write_status status; GPR_ASSERT(ep->output_buffer.count == 0); #ifdef GRPC_TRACE_SECURE_TRANSPORT @@ -295,8 +304,16 @@ static grpc_endpoint_write_status write(grpc_endpoint *secure_ep, /* clear output_buffer and let the lower level handle its slices. */ output_buffer_count = ep->output_buffer.count; ep->output_buffer.count = 0; - return grpc_endpoint_write(ep->wrapped_ep, ep->output_buffer.slices, - output_buffer_count, cb, user_data, deadline); + ep->write_cb = cb; + ep->write_user_data = user_data; + /* Need to keep the endpoint alive across a transport */ + secure_endpoint_ref(ep); + status = grpc_endpoint_write(ep->wrapped_ep, ep->output_buffer.slices, + output_buffer_count, on_write, ep, deadline); + if (status != GRPC_ENDPOINT_WRITE_PENDING) { + secure_endpoint_unref(ep); + } + return status; } static void shutdown(grpc_endpoint *secure_ep) { diff --git a/src/core/endpoint/socket_utils.c b/src/core/endpoint/socket_utils.c index 9c2540bcbf..ef160d7ea4 100644 --- a/src/core/endpoint/socket_utils.c +++ b/src/core/endpoint/socket_utils.c @@ -33,6 +33,7 @@ #include "src/core/endpoint/socket_utils.h" +#include <arpa/inet.h> #include <limits.h> #include <fcntl.h> #include <netinet/in.h> @@ -44,6 +45,11 @@ #include <string.h> #include <errno.h> +#include <grpc/support/host_port.h> +#include <grpc/support/string.h> +#include <grpc/support/log.h> +#include <grpc/support/port_platform.h> + /* set a socket to non blocking mode */ int grpc_set_socket_nonblocking(int fd, int non_blocking) { int oldflags = fcntl(fd, F_GETFL, 0); @@ -103,3 +109,157 @@ int grpc_set_socket_low_latency(int fd, int low_latency) { 0 == getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &newval, &intlen) && newval == val; } + +/* This should be 0 in production, but it may be enabled for testing or + debugging purposes, to simulate an environment where IPv6 sockets can't + also speak IPv4. */ +int grpc_forbid_dualstack_sockets_for_testing = 0; + +static int set_socket_dualstack(int fd) { + if (!grpc_forbid_dualstack_sockets_for_testing) { + const int off = 0; + return 0 == setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); + } else { + /* Force an IPv6-only socket, for testing purposes. */ + const int on = 1; + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + return 0; + } +} + +int grpc_create_dualstack_socket(const struct sockaddr *addr, int type, + int protocol, grpc_dualstack_mode *dsmode) { + int family = addr->sa_family; + if (family == AF_INET6) { + int fd = socket(family, type, protocol); + /* Check if we've got a valid dualstack socket. */ + if (fd >= 0 && set_socket_dualstack(fd)) { + *dsmode = GRPC_DSMODE_DUALSTACK; + return fd; + } + /* If this isn't an IPv4 address, then return whatever we've got. */ + if (!grpc_sockaddr_is_v4mapped(addr, NULL)) { + *dsmode = GRPC_DSMODE_IPV6; + return fd; + } + /* Fall back to AF_INET. */ + if (fd >= 0) { + close(fd); + } + family = AF_INET; + } + *dsmode = family == AF_INET ? GRPC_DSMODE_IPV4 : GRPC_DSMODE_NONE; + return socket(family, type, protocol); +} + +static const gpr_uint8 kV4MappedPrefix[] = {0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0xff, 0xff}; + +int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr, + struct sockaddr_in *addr4_out) { + GPR_ASSERT(addr != (struct sockaddr *)addr4_out); + if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + if (memcmp(addr6->sin6_addr.s6_addr, kV4MappedPrefix, + sizeof(kV4MappedPrefix)) == 0) { + if (addr4_out != NULL) { + /* Normalize ::ffff:0.0.0.0/96 to IPv4. */ + memset(addr4_out, 0, sizeof(*addr4_out)); + addr4_out->sin_family = AF_INET; + /* s6_addr32 would be nice, but it's non-standard. */ + memcpy(&addr4_out->sin_addr, &addr6->sin6_addr.s6_addr[12], 4); + addr4_out->sin_port = addr6->sin6_port; + } + return 1; + } + } + return 0; +} + +int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr, + struct sockaddr_in6 *addr6_out) { + GPR_ASSERT(addr != (struct sockaddr *)addr6_out); + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + memset(addr6_out, 0, sizeof(*addr6_out)); + addr6_out->sin6_family = AF_INET6; + memcpy(&addr6_out->sin6_addr.s6_addr[0], kV4MappedPrefix, 12); + memcpy(&addr6_out->sin6_addr.s6_addr[12], &addr4->sin_addr, 4); + addr6_out->sin6_port = addr4->sin_port; + return 1; + } + return 0; +} + +int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out) { + struct sockaddr_in addr4_normalized; + if (grpc_sockaddr_is_v4mapped(addr, &addr4_normalized)) { + addr = (struct sockaddr *)&addr4_normalized; + } + if (addr->sa_family == AF_INET) { + /* Check for 0.0.0.0 */ + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + if (addr4->sin_addr.s_addr != 0) { + return 0; + } + *port_out = ntohs(addr4->sin_port); + return 1; + } else if (addr->sa_family == AF_INET6) { + /* Check for :: */ + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + int i; + for (i = 0; i < 16; i++) { + if (addr6->sin6_addr.s6_addr[i] != 0) { + return 0; + } + } + *port_out = ntohs(addr6->sin6_port); + return 1; + } else { + return 0; + } +} + +void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, + struct sockaddr_in6 *wild6_out) { + memset(wild4_out, 0, sizeof(*wild4_out)); + wild4_out->sin_family = AF_INET; + wild4_out->sin_port = htons(port); + + memset(wild6_out, 0, sizeof(*wild6_out)); + wild6_out->sin6_family = AF_INET6; + wild6_out->sin6_port = htons(port); +} + +int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, + int normalize) { + const int save_errno = errno; + struct sockaddr_in addr_normalized; + char ntop_buf[INET6_ADDRSTRLEN]; + const void *ip = NULL; + int port; + int ret; + + *out = NULL; + if (normalize && grpc_sockaddr_is_v4mapped(addr, &addr_normalized)) { + addr = (const struct sockaddr *)&addr_normalized; + } + if (addr->sa_family == AF_INET) { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + ip = &addr4->sin_addr; + port = ntohs(addr4->sin_port); + } else if (addr->sa_family == AF_INET6) { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + ip = &addr6->sin6_addr; + port = ntohs(addr6->sin6_port); + } + if (ip != NULL && + inet_ntop(addr->sa_family, ip, ntop_buf, sizeof(ntop_buf)) != NULL) { + ret = gpr_join_host_port(out, ntop_buf, port); + } else { + ret = gpr_asprintf(out, "(sockaddr family=%d)", addr->sa_family); + } + /* This is probably redundant, but we wouldn't want to log the wrong error. */ + errno = save_errno; + return ret; +} diff --git a/src/core/endpoint/socket_utils.h b/src/core/endpoint/socket_utils.h index 545d678eab..23fa19284a 100644 --- a/src/core/endpoint/socket_utils.h +++ b/src/core/endpoint/socket_utils.h @@ -38,6 +38,8 @@ #include <sys/socket.h> struct sockaddr; +struct sockaddr_in; +struct sockaddr_in6; /* a wrapper for accept or accept4 */ int grpc_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, @@ -55,4 +57,82 @@ int grpc_set_socket_reuse_addr(int fd, int reuse); /* disable nagle */ int grpc_set_socket_low_latency(int fd, int low_latency); +/* An enum to keep track of IPv4/IPv6 socket modes. + + Currently, this information is only used when a socket is first created, but + in the future we may wish to store it alongside the fd. This would let calls + like sendto() know which family to use without asking the kernel first. */ +typedef enum grpc_dualstack_mode { + /* Uninitialized, or a non-IP socket. */ + GRPC_DSMODE_NONE, + /* AF_INET only. */ + GRPC_DSMODE_IPV4, + /* AF_INET6 only, because IPV6_V6ONLY could not be cleared. */ + GRPC_DSMODE_IPV6, + /* AF_INET6, which also supports ::ffff-mapped IPv4 addresses. */ + GRPC_DSMODE_DUALSTACK +} grpc_dualstack_mode; + +/* Only tests should use this flag. */ +extern int grpc_forbid_dualstack_sockets_for_testing; + +/* Creates a new socket for connecting to (or listening on) an address. + + If addr is AF_INET6, this creates an IPv6 socket first. If that fails, + and addr is within ::ffff:0.0.0.0/96, then it automatically falls back to + an IPv4 socket. + + If addr is AF_INET, AF_UNIX, or anything else, then this is similar to + calling socket() directly. + + Returns an fd on success, otherwise returns -1 with errno set to the result + of a failed socket() call. + + The *dsmode output indicates which address family was actually created. + The recommended way to use this is: + - First convert to IPv6 using grpc_sockaddr_to_v4mapped(). + - Create the socket. + - If *dsmode is IPV4, use grpc_sockaddr_is_v4mapped() to convert back to + IPv4, so that bind() or connect() see the correct family. + Also, it's important to distinguish between DUALSTACK and IPV6 when + listening on the [::] wildcard address. */ +int grpc_create_dualstack_socket(const struct sockaddr *addr, int type, + int protocol, grpc_dualstack_mode *dsmode); + +/* Returns true if addr is an IPv4-mapped IPv6 address within the + ::ffff:0.0.0.0/96 range, or false otherwise. + + If addr4_out is non-NULL, the inner IPv4 address will be copied here when + returning true. */ +int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr, + struct sockaddr_in *addr4_out); + +/* If addr is an AF_INET address, writes the corresponding ::ffff:0.0.0.0/96 + address to addr6_out and returns true. Otherwise returns false. */ +int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr, + struct sockaddr_in6 *addr6_out); + +/* If addr is ::, 0.0.0.0, or ::ffff:0.0.0.0, writes the port number to + *port_out (if not NULL) and returns true, otherwise returns false. */ +int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out); + +/* Writes 0.0.0.0:port and [::]:port to separate sockaddrs. */ +void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out, + struct sockaddr_in6 *wild6_out); + +/* Converts a sockaddr into a newly-allocated human-readable string. + + Currently, only the AF_INET and AF_INET6 families are recognized. + If the normalize flag is enabled, ::ffff:0.0.0.0/96 IPv6 addresses are + displayed as plain IPv4. + + Usage is similar to gpr_asprintf: returns the number of bytes written + (excluding the final '\0'), and *out points to a string which must later be + destroyed using gpr_free(). + + In the unlikely event of an error, returns -1 and sets *out to NULL. + The existing value of errno is always preserved. */ +int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, + int normalize); + #endif /* __GRPC_INTERNAL_ENDPOINT_SOCKET_UTILS_H__ */ diff --git a/src/core/endpoint/tcp.c b/src/core/endpoint/tcp.c index 39367e80f6..482344d265 100644 --- a/src/core/endpoint/tcp.c +++ b/src/core/endpoint/tcp.c @@ -250,7 +250,7 @@ static void slice_state_remove_last(grpc_tcp_slice_state *state, size_t bytes) { typedef struct { grpc_endpoint base; grpc_em *em; - grpc_em_fd em_fd; + grpc_em_fd *em_fd; int fd; size_t slice_size; gpr_refcount refcount; @@ -277,14 +277,14 @@ grpc_endpoint *grpc_tcp_create(int fd, grpc_em *em) { static void grpc_tcp_shutdown(grpc_endpoint *ep) { grpc_tcp *tcp = (grpc_tcp *)ep; - grpc_em_fd_shutdown(&tcp->em_fd); + grpc_em_fd_shutdown(tcp->em_fd); } static void grpc_tcp_unref(grpc_tcp *tcp) { int refcount_zero = gpr_unref(&tcp->refcount); if (refcount_zero) { - grpc_em_fd_destroy(&tcp->em_fd); - close(tcp->fd); + grpc_em_fd_destroy(tcp->em_fd); + gpr_free(tcp->em_fd); gpr_free(tcp); } } @@ -385,7 +385,7 @@ static void grpc_tcp_handle_read(void *arg /* grpc_tcp */, } else { /* Spurious read event, consume it here */ slice_state_destroy(&read_state); - grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp, + grpc_em_fd_notify_on_read(tcp->em_fd, grpc_tcp_handle_read, tcp, tcp->read_deadline); } } else { @@ -422,7 +422,7 @@ static void grpc_tcp_notify_on_read(grpc_endpoint *ep, grpc_endpoint_read_cb cb, tcp->read_user_data = user_data; tcp->read_deadline = deadline; gpr_ref(&tcp->refcount); - grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp, deadline); + grpc_em_fd_notify_on_read(tcp->em_fd, grpc_tcp_handle_read, tcp, deadline); } #define MAX_WRITE_IOVEC 16 @@ -494,7 +494,7 @@ static void grpc_tcp_handle_write(void *arg /* grpc_tcp */, write_status = grpc_tcp_flush(tcp); if (write_status == GRPC_ENDPOINT_WRITE_PENDING) { - grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp, + grpc_em_fd_notify_on_write(tcp->em_fd, grpc_tcp_handle_write, tcp, tcp->write_deadline); } else { slice_state_destroy(&tcp->write_state); @@ -539,7 +539,7 @@ static grpc_endpoint_write_status grpc_tcp_write( tcp->write_cb = cb; tcp->write_user_data = user_data; tcp->write_deadline = deadline; - grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp, + grpc_em_fd_notify_on_write(tcp->em_fd, grpc_tcp_handle_write, tcp, tcp->write_deadline); } @@ -550,11 +550,12 @@ static const grpc_endpoint_vtable vtable = {grpc_tcp_notify_on_read, grpc_tcp_write, grpc_tcp_shutdown, grpc_tcp_destroy}; -grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) { +static grpc_endpoint *grpc_tcp_create_generic(grpc_em_fd *em_fd, + size_t slice_size) { grpc_tcp *tcp = (grpc_tcp *)gpr_malloc(sizeof(grpc_tcp)); tcp->base.vtable = &vtable; - tcp->fd = fd; - tcp->em = em; + tcp->fd = grpc_em_fd_get(em_fd); + tcp->em = grpc_em_fd_get_em(em_fd); tcp->read_cb = NULL; tcp->write_cb = NULL; tcp->read_user_data = NULL; @@ -565,6 +566,16 @@ grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) { slice_state_init(&tcp->write_state, NULL, 0, 0); /* paired with unref in grpc_tcp_destroy */ gpr_ref_init(&tcp->refcount, 1); - grpc_em_fd_init(&tcp->em_fd, tcp->em, fd); + tcp->em_fd = em_fd; return &tcp->base; } + +grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) { + grpc_em_fd *em_fd = gpr_malloc(sizeof(grpc_em_fd)); + grpc_em_fd_init(em_fd, em, fd); + return grpc_tcp_create_generic(em_fd, slice_size); +} + +grpc_endpoint *grpc_tcp_create_emfd(grpc_em_fd *em_fd) { + return grpc_tcp_create_generic(em_fd, DEFAULT_SLICE_SIZE); +} diff --git a/src/core/endpoint/tcp.h b/src/core/endpoint/tcp.h index 6507b2f6ef..f6a2a19ec4 100644 --- a/src/core/endpoint/tcp.h +++ b/src/core/endpoint/tcp.h @@ -52,4 +52,8 @@ grpc_endpoint *grpc_tcp_create(int fd, grpc_em *em); /* Special version for debugging slice changes */ grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size); +/* Special version for handing off ownership of an existing already created + eventmanager fd. Must not have any outstanding callbacks. */ +grpc_endpoint *grpc_tcp_create_emfd(grpc_em_fd *em_fd); + #endif /* __GRPC_INTERNAL_ENDPOINT_TCP_H__ */ diff --git a/src/core/endpoint/tcp_client.c b/src/core/endpoint/tcp_client.c index 01a0c3f23d..c6f470ba88 100644 --- a/src/core/endpoint/tcp_client.c +++ b/src/core/endpoint/tcp_client.c @@ -34,6 +34,7 @@ #include "src/core/endpoint/tcp_client.h" #include <errno.h> +#include <netinet/in.h> #include <string.h> #include <unistd.h> @@ -45,14 +46,12 @@ typedef struct { void (*cb)(void *arg, grpc_endpoint *tcp); void *cb_arg; - grpc_em_fd fd; + grpc_em_fd *fd; gpr_timespec deadline; } async_connect; -static int create_fd(int address_family) { - int fd = socket(address_family, SOCK_STREAM, 0); +static int prepare_socket(int fd) { if (fd < 0) { - gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); goto error; } @@ -63,13 +62,13 @@ static int create_fd(int address_family) { goto error; } - return fd; + return 1; error: if (fd >= 0) { close(fd); } - return -1; + return 0; } static void on_writable(void *acp, grpc_em_cb_status status) { @@ -77,8 +76,7 @@ static void on_writable(void *acp, grpc_em_cb_status status) { int so_error = 0; socklen_t so_error_size; int err; - int fd = grpc_em_fd_get(&ac->fd); - grpc_em *em = grpc_em_fd_get_em(&ac->fd); + int fd = grpc_em_fd_get(ac->fd); if (status == GRPC_CALLBACK_SUCCESS) { do { @@ -105,7 +103,7 @@ static void on_writable(void *acp, grpc_em_cb_status status) { opened too many network connections. The "easy" fix: don't do that! */ gpr_log(GPR_ERROR, "kernel out of buffers"); - grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, ac->deadline); + grpc_em_fd_notify_on_write(ac->fd, on_writable, ac, ac->deadline); return; } else { goto error; @@ -122,31 +120,50 @@ static void on_writable(void *acp, grpc_em_cb_status status) { error: ac->cb(ac->cb_arg, NULL); - grpc_em_fd_destroy(&ac->fd); + grpc_em_fd_destroy(ac->fd); + gpr_free(ac->fd); gpr_free(ac); - close(fd); return; great_success: - grpc_em_fd_destroy(&ac->fd); - ac->cb(ac->cb_arg, grpc_tcp_create(fd, em)); + ac->cb(ac->cb_arg, grpc_tcp_create_emfd(ac->fd)); gpr_free(ac); } void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *ep), - void *arg, grpc_em *em, struct sockaddr *addr, - int len, gpr_timespec deadline) { - int fd = create_fd(addr->sa_family); + void *arg, grpc_em *em, + const struct sockaddr *addr, int addr_len, + gpr_timespec deadline) { + int fd; + grpc_dualstack_mode dsmode; int err; async_connect *ac; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in addr4_copy; + + /* Use dualstack sockets where available. */ + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + } + if (dsmode == GRPC_DSMODE_IPV4) { + /* If we got an AF_INET socket, map the address back to IPv4. */ + GPR_ASSERT(grpc_sockaddr_is_v4mapped(addr, &addr4_copy)); + addr = (struct sockaddr *)&addr4_copy; + addr_len = sizeof(addr4_copy); + } + if (!prepare_socket(fd)) { cb(arg, NULL); return; } do { - err = connect(fd, addr, len); + err = connect(fd, addr, addr_len); } while (err < 0 && errno == EINTR); if (err >= 0) { @@ -165,6 +182,7 @@ void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *ep), ac->cb = cb; ac->cb_arg = arg; ac->deadline = deadline; - grpc_em_fd_init(&ac->fd, em, fd); - grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, deadline); + ac->fd = gpr_malloc(sizeof(grpc_em_fd)); + grpc_em_fd_init(ac->fd, em, fd); + grpc_em_fd_notify_on_write(ac->fd, on_writable, ac, deadline); } diff --git a/src/core/endpoint/tcp_client.h b/src/core/endpoint/tcp_client.h index 2a8b8ee217..69b1b62f37 100644 --- a/src/core/endpoint/tcp_client.h +++ b/src/core/endpoint/tcp_client.h @@ -44,7 +44,8 @@ cb with arg and the completed connection when done (or call cb with arg and NULL on failure) */ void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *tcp), - void *arg, grpc_em *em, struct sockaddr *addr, - int len, gpr_timespec deadline); + void *arg, grpc_em *em, + const struct sockaddr *addr, int addr_len, + gpr_timespec deadline); #endif /* __GRPC_INTERNAL_ENDPOINT_TCP_CLIENT_H__ */ diff --git a/src/core/endpoint/tcp_server.c b/src/core/endpoint/tcp_server.c index 2f386ce045..efd3dede50 100644 --- a/src/core/endpoint/tcp_server.c +++ b/src/core/endpoint/tcp_server.c @@ -114,7 +114,6 @@ void grpc_tcp_server_destroy(grpc_tcp_server *s) { server_port *sp = &s->ports[i]; grpc_em_fd_destroy(sp->emfd); gpr_free(sp->emfd); - close(sp->fd); } gpr_free(s->ports); gpr_free(s); @@ -153,11 +152,9 @@ static int get_max_accept_queue_size() { return s_max_accept_queue_size; } -/* create a socket to listen with */ -static int create_listening_socket(struct sockaddr *port, int len) { - int fd = socket(port->sa_family, SOCK_STREAM, 0); +/* Prepare a recently-created socket for listening. */ +static int prepare_socket(int fd, const struct sockaddr *addr, int addr_len) { if (fd < 0) { - gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); goto error; } @@ -169,8 +166,11 @@ static int create_listening_socket(struct sockaddr *port, int len) { goto error; } - if (bind(fd, port, len) < 0) { - gpr_log(GPR_ERROR, "bind: %s", strerror(errno)); + if (bind(fd, addr, addr_len) < 0) { + char *addr_str; + grpc_sockaddr_to_string(&addr_str, addr, 0); + gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, strerror(errno)); + gpr_free(addr_str); goto error; } @@ -179,13 +179,13 @@ static int create_listening_socket(struct sockaddr *port, int len) { goto error; } - return fd; + return 1; error: if (fd >= 0) { close(fd); } - return -1; + return 0; } /* event manager callback when reads are ready */ @@ -200,6 +200,8 @@ static void on_read(void *arg, grpc_em_cb_status status) { for (;;) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); + /* Note: If we ever decide to return this address to the user, remember to + strip off the ::ffff:0.0.0.0/96 prefix first. */ int fd = grpc_accept4(sp->fd, (struct sockaddr *)&addr, &addrlen, 1, 1); if (fd < 0) { switch (errno) { @@ -231,13 +233,12 @@ error: gpr_mu_unlock(&sp->server->mu); } -int grpc_tcp_server_add_port(grpc_tcp_server *s, struct sockaddr *port, - int len) { +static int add_socket_to_server(grpc_tcp_server *s, int fd, + const struct sockaddr *addr, int addr_len) { server_port *sp; - /* create a socket */ - int fd = create_listening_socket(port, len); - if (fd < 0) { - return -1; + + if (!prepare_socket(fd, addr, addr_len)) { + return 0; } gpr_mu_lock(&s->mu); @@ -257,11 +258,62 @@ int grpc_tcp_server_add_port(grpc_tcp_server *s, struct sockaddr *port, gpr_free(sp->emfd); s->nports--; gpr_mu_unlock(&s->mu); - return -1; + return 0; } gpr_mu_unlock(&s->mu); - return fd; + return 1; +} + +int grpc_tcp_server_add_port(grpc_tcp_server *s, const struct sockaddr *addr, + int addr_len) { + int ok = 0; + int fd; + grpc_dualstack_mode dsmode; + struct sockaddr_in6 addr6_v4mapped; + struct sockaddr_in wild4; + struct sockaddr_in6 wild6; + struct sockaddr_in addr4_copy; + int port; + + if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) { + addr = (const struct sockaddr *)&addr6_v4mapped; + addr_len = sizeof(addr6_v4mapped); + } + + /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */ + if (grpc_sockaddr_is_wildcard(addr, &port)) { + grpc_sockaddr_make_wildcards(port, &wild4, &wild6); + + /* Try listening on IPv6 first. */ + addr = (struct sockaddr *)&wild6; + addr_len = sizeof(wild6); + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); + ok |= add_socket_to_server(s, fd, addr, addr_len); + if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) { + return ok; + } + + /* If we didn't get a dualstack socket, also listen on 0.0.0.0. */ + addr = (struct sockaddr *)&wild4; + addr_len = sizeof(wild4); + } + + fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode); + if (fd < 0) { + gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno)); + } + if (dsmode == GRPC_DSMODE_IPV4 && + grpc_sockaddr_is_v4mapped(addr, &addr4_copy)) { + addr = (struct sockaddr *)&addr4_copy; + addr_len = sizeof(addr4_copy); + } + ok |= add_socket_to_server(s, fd, addr, addr_len); + return ok; +} + +int grpc_tcp_server_get_fd(grpc_tcp_server *s, int index) { + return (0 <= index && index < s->nports) ? s->ports[index].fd : -1; } void grpc_tcp_server_start(grpc_tcp_server *s, grpc_tcp_server_cb cb, diff --git a/src/core/endpoint/tcp_server.h b/src/core/endpoint/tcp_server.h index 99cb83e181..d81cdd000e 100644 --- a/src/core/endpoint/tcp_server.h +++ b/src/core/endpoint/tcp_server.h @@ -53,11 +53,23 @@ grpc_tcp_server *grpc_tcp_server_create(grpc_em *em); void grpc_tcp_server_start(grpc_tcp_server *server, grpc_tcp_server_cb cb, void *cb_arg); -/* Add a port to the server, returns a file descriptor on success, or <0 on - failure; the file descriptor remains owned by the server and will be cleaned - up when grpc_tcp_server_destroy is called */ -int grpc_tcp_server_add_port(grpc_tcp_server *server, struct sockaddr *port, - int len); +/* Add a port to the server, returning true on success, or false otherwise. + + The :: and 0.0.0.0 wildcard addresses are treated identically, accepting + both IPv4 and IPv6 connections, but :: is the preferred style. This usually + creates one socket, but possibly two on systems which support IPv6, + but not dualstack sockets. + + For raw access to the underlying sockets, see grpc_tcp_server_get_fd(). */ +int grpc_tcp_server_add_port(grpc_tcp_server *s, const struct sockaddr *addr, + int addr_len); + +/* Returns the file descriptor of the Nth listening socket on this server, + or -1 if the index is out of bounds. + + The file descriptor remains owned by the server, and will be cleaned + up when grpc_tcp_server_destroy is called. */ +int grpc_tcp_server_get_fd(grpc_tcp_server *s, int index); void grpc_tcp_server_destroy(grpc_tcp_server *server); diff --git a/src/core/eventmanager/em.c b/src/core/eventmanager/em.c index f16473b395..36f3720e0d 100644 --- a/src/core/eventmanager/em.c +++ b/src/core/eventmanager/em.c @@ -537,6 +537,8 @@ void grpc_em_fd_destroy(grpc_em_fd *em_fd) { em->num_fds--; gpr_cv_broadcast(&em->cv); gpr_mu_unlock(&em->mu); + + close(em_fd->fd); } int grpc_em_fd_get(struct grpc_em_fd *em_fd) { return em_fd->fd; } diff --git a/src/core/eventmanager/em.h b/src/core/eventmanager/em.h index 6f8bdedce2..aa439d1112 100644 --- a/src/core/eventmanager/em.h +++ b/src/core/eventmanager/em.h @@ -146,10 +146,12 @@ grpc_em_error grpc_em_alarm_cancel(grpc_em_alarm *alarm); initialized *em_fd. fd is a non-blocking file descriptor. + This takes ownership of closing fd. + Requires: *em_fd uninitialized. fd is a non-blocking file descriptor. */ grpc_em_error grpc_em_fd_init(grpc_em_fd *em_fd, grpc_em *em, int fd); -/* Cause *em_fd no longer to be initialized. +/* Cause *em_fd no longer to be initialized and closes the underlying fd. Requires: *em_fd initialized; no outstanding notify_on_read or notify_on_write. */ void grpc_em_fd_destroy(grpc_em_fd *em_fd); diff --git a/src/core/security/auth.c b/src/core/security/auth.c index 6480dd2239..9ce0c69d96 100644 --- a/src/core/security/auth.c +++ b/src/core/security/auth.c @@ -75,7 +75,7 @@ static void call_op(grpc_call_element *elem, grpc_call_op *op) { switch (op->type) { case GRPC_SEND_START: { grpc_credentials *channel_creds = - channeld->security_context->request_metadata_only_creds; + channeld->security_context->request_metadata_creds; /* TODO(jboeuf): Decide on the policy in this case: - populate both channel and call? diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c index 969a97369c..7ff48f9123 100644 --- a/src/core/security/credentials.c +++ b/src/core/security/credentials.c @@ -47,12 +47,10 @@ #include <stdio.h> /* -- Constants. -- */ - #define GRPC_COMPUTE_ENGINE_TOKEN_REFRESH_THRESHOLD_SECS 60 #define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata" #define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \ "computeMetadata/v1/instance/service-accounts/default/token" -#define GRPC_AUTHORIZATION_METADATA_KEY "Authorization" /* -- Common. -- */ @@ -108,7 +106,13 @@ int grpc_credentials_has_request_metadata_only(grpc_credentials *creds) { void grpc_credentials_get_request_metadata(grpc_credentials *creds, grpc_credentials_metadata_cb cb, void *user_data) { - if (creds == NULL || !grpc_credentials_has_request_metadata(creds)) return; + if (creds == NULL || !grpc_credentials_has_request_metadata(creds) || + creds->vtable->get_request_metadata == NULL) { + if (cb != NULL) { + cb(user_data, NULL, 0, GRPC_CREDENTIALS_OK); + } + return; + } creds->vtable->get_request_metadata(creds, cb, user_data); } @@ -521,14 +525,235 @@ grpc_fake_transport_security_server_credentials_create() { return c; } +/* -- Composite credentials. -- */ + +typedef struct { + grpc_credentials base; + grpc_credentials_array inner; +} grpc_composite_credentials; + +typedef struct { + grpc_composite_credentials *composite_creds; + size_t creds_index; + grpc_mdelem **md_elems; + size_t num_md; + void *user_data; + grpc_credentials_metadata_cb cb; +} grpc_composite_credentials_metadata_context; + +static void composite_destroy(grpc_credentials *creds) { + grpc_composite_credentials *c = (grpc_composite_credentials *)creds; + size_t i; + for (i = 0; i < c->inner.num_creds; i++) { + grpc_credentials_unref(c->inner.creds_array[i]); + } + gpr_free(c->inner.creds_array); + gpr_free(creds); +} + +static int composite_has_request_metadata(const grpc_credentials *creds) { + const grpc_composite_credentials *c = + (const grpc_composite_credentials *)creds; + size_t i; + for (i = 0; i < c->inner.num_creds; i++) { + if (grpc_credentials_has_request_metadata(c->inner.creds_array[i])) { + return 1; + } + } + return 0; +} + +static int composite_has_request_metadata_only(const grpc_credentials *creds) { + const grpc_composite_credentials *c = + (const grpc_composite_credentials *)creds; + size_t i; + for (i = 0; i < c->inner.num_creds; i++) { + if (!grpc_credentials_has_request_metadata_only(c->inner.creds_array[i])) { + return 0; + } + } + return 1; +} + +static void composite_md_context_destroy( + grpc_composite_credentials_metadata_context *ctx) { + size_t i; + for (i = 0; i < ctx->num_md; i++) { + grpc_mdelem_unref(ctx->md_elems[i]); + } + gpr_free(ctx->md_elems); + gpr_free(ctx); +} + +static void composite_metadata_cb(void *user_data, grpc_mdelem **md_elems, + size_t num_md, + grpc_credentials_status status) { + grpc_composite_credentials_metadata_context *ctx = + (grpc_composite_credentials_metadata_context *)user_data; + size_t i; + if (status != GRPC_CREDENTIALS_OK) { + ctx->cb(ctx->user_data, NULL, 0, status); + return; + } + + /* Copy the metadata in the context. */ + if (num_md > 0) { + ctx->md_elems = gpr_realloc(ctx->md_elems, + (ctx->num_md + num_md) * sizeof(grpc_mdelem *)); + for (i = 0; i < num_md; i++) { + ctx->md_elems[i + ctx->num_md] = grpc_mdelem_ref(md_elems[i]); + } + ctx->num_md += num_md; + } + + /* See if we need to get some more metadata. */ + while (ctx->creds_index < ctx->composite_creds->inner.num_creds) { + grpc_credentials *inner_creds = + ctx->composite_creds->inner.creds_array[ctx->creds_index++]; + if (grpc_credentials_has_request_metadata(inner_creds)) { + grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb, + ctx); + return; + } + } + + /* We're done!. */ + ctx->cb(ctx->user_data, ctx->md_elems, ctx->num_md, GRPC_CREDENTIALS_OK); + composite_md_context_destroy(ctx); +} + +static void composite_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_composite_credentials *c = (grpc_composite_credentials *)creds; + grpc_composite_credentials_metadata_context *ctx; + if (!grpc_credentials_has_request_metadata(creds)) { + cb(user_data, NULL, 0, GRPC_CREDENTIALS_OK); + return; + } + ctx = gpr_malloc(sizeof(grpc_composite_credentials_metadata_context)); + memset(ctx, 0, sizeof(grpc_composite_credentials_metadata_context)); + ctx->user_data = user_data; + ctx->cb = cb; + ctx->composite_creds = c; + while (ctx->creds_index < c->inner.num_creds) { + grpc_credentials *inner_creds = c->inner.creds_array[ctx->creds_index++]; + if (grpc_credentials_has_request_metadata(inner_creds)) { + grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb, + ctx); + return; + } + } + GPR_ASSERT(0); /* Should have exited before. */ +} -/* -- Composite credentials TODO(jboeuf). -- */ +static grpc_credentials_vtable composite_credentials_vtable = { + composite_destroy, composite_has_request_metadata, + composite_has_request_metadata_only, composite_get_request_metadata}; + +static grpc_credentials_array get_creds_array(grpc_credentials **creds_addr) { + grpc_credentials_array result; + grpc_credentials *creds = *creds_addr; + result.creds_array = creds_addr; + result.num_creds = 1; + if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)) { + result = *grpc_composite_credentials_get_credentials(creds); + } + return result; +} grpc_credentials *grpc_composite_credentials_create(grpc_credentials *creds1, grpc_credentials *creds2) { - return NULL; + size_t i; + grpc_credentials_array creds1_array; + grpc_credentials_array creds2_array; + grpc_composite_credentials *c; + GPR_ASSERT(creds1 != NULL); + GPR_ASSERT(creds2 != NULL); + c = gpr_malloc(sizeof(grpc_composite_credentials)); + memset(c, 0, sizeof(grpc_composite_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_COMPOSITE; + c->base.vtable = &composite_credentials_vtable; + gpr_ref_init(&c->base.refcount, 1); + creds1_array = get_creds_array(&creds1); + creds2_array = get_creds_array(&creds2); + c->inner.num_creds = creds1_array.num_creds + creds2_array.num_creds; + c->inner.creds_array = + gpr_malloc(c->inner.num_creds * sizeof(grpc_credentials *)); + for (i = 0; i < creds1_array.num_creds; i++) { + c->inner.creds_array[i] = grpc_credentials_ref(creds1_array.creds_array[i]); + } + for (i = 0; i < creds2_array.num_creds; i++) { + c->inner.creds_array[i + creds1_array.num_creds] = + grpc_credentials_ref(creds2_array.creds_array[i]); + } + return &c->base; +} + +const grpc_credentials_array *grpc_composite_credentials_get_credentials( + grpc_credentials *creds) { + const grpc_composite_credentials *c = + (const grpc_composite_credentials *)creds; + GPR_ASSERT(!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)); + return &c->inner; +} + +/* -- IAM credentials. -- */ + +typedef struct { + grpc_credentials base; + grpc_mdctx *md_ctx; + grpc_mdelem *token_md; + grpc_mdelem *authority_selector_md; +} grpc_iam_credentials; + +static void iam_destroy(grpc_credentials *creds) { + grpc_iam_credentials *c = (grpc_iam_credentials *)creds; + grpc_mdelem_unref(c->token_md); + grpc_mdelem_unref(c->authority_selector_md); + grpc_mdctx_orphan(c->md_ctx); + gpr_free(c); +} + +static int iam_has_request_metadata(const grpc_credentials *creds) { return 1; } + +static int iam_has_request_metadata_only(const grpc_credentials *creds) { + return 1; +} + +static void iam_get_request_metadata(grpc_credentials *creds, + grpc_credentials_metadata_cb cb, + void *user_data) { + grpc_iam_credentials *c = (grpc_iam_credentials *)creds; + grpc_mdelem *md_array[2]; + md_array[0] = c->token_md; + md_array[1] = c->authority_selector_md; + cb(user_data, md_array, 2, GRPC_CREDENTIALS_OK); +} + +static grpc_credentials_vtable iam_vtable = { + iam_destroy, iam_has_request_metadata, iam_has_request_metadata_only, + iam_get_request_metadata}; + +grpc_credentials *grpc_iam_credentials_create(const char *token, + const char *authority_selector) { + grpc_iam_credentials *c; + GPR_ASSERT(token != NULL); + GPR_ASSERT(authority_selector != NULL); + c = gpr_malloc(sizeof(grpc_iam_credentials)); + memset(c, 0, sizeof(grpc_iam_credentials)); + c->base.type = GRPC_CREDENTIALS_TYPE_IAM; + c->base.vtable = &iam_vtable; + gpr_ref_init(&c->base.refcount, 1); + c->md_ctx = grpc_mdctx_create(); + c->token_md = grpc_mdelem_from_strings( + c->md_ctx, GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, token); + c->authority_selector_md = grpc_mdelem_from_strings( + c->md_ctx, GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, authority_selector); + return &c->base; } /* -- Default credentials TODO(jboeuf). -- */ grpc_credentials *grpc_default_credentials_create(void) { return NULL; } + diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h index 1432611ec6..9fb82e1ecd 100644 --- a/src/core/security/credentials.h +++ b/src/core/security/credentials.h @@ -50,9 +50,15 @@ typedef enum { #define GRPC_CREDENTIALS_TYPE_SSL "Ssl" #define GRPC_CREDENTIALS_TYPE_OAUTH2 "Oauth2" +#define GRPC_CREDENTIALS_TYPE_IAM "Iam" #define GRPC_CREDENTIALS_TYPE_COMPOSITE "Composite" #define GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY "FakeTransportSecurity" +#define GRPC_AUTHORIZATION_METADATA_KEY "Authorization" +#define GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY \ + "x-goog-iam-authorization-token" +#define GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY "x-goog-iam-authority-selector" + /* --- grpc_credentials. --- */ typedef void (*grpc_credentials_metadata_cb)(void *user_data, @@ -94,6 +100,14 @@ typedef struct { const grpc_ssl_config *grpc_ssl_credentials_get_config( const grpc_credentials *ssl_creds); +typedef struct { + grpc_credentials **creds_array; + size_t num_creds; +} grpc_credentials_array; + +const grpc_credentials_array *grpc_composite_credentials_get_credentials( + grpc_credentials *composite_creds); + /* Exposed for testing only. */ grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( const struct grpc_httpcli_response *response, grpc_mdctx *ctx, diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c index beda64cba2..c56692ae83 100644 --- a/src/core/security/security_context.c +++ b/src/core/security/security_context.c @@ -37,6 +37,8 @@ #include "src/core/endpoint/secure_endpoint.h" #include "src/core/security/credentials.h" +#include "src/core/surface/lame_client.h" +#include "src/core/transport/chttp2/alpn.h" #include <grpc/support/alloc.h> #include <grpc/support/log.h> #include <grpc/support/slice_buffer.h> @@ -47,7 +49,6 @@ /* -- Constants. -- */ -#define GRPC_ALPN_PROTOCOL_STRING "h2-15" /* Defines the cipher suites that we accept. All these cipher suites are compliant with TLS 1.2 and use an RSA public key. We prefer GCM over CBC and ECDHE-RSA over just RSA. */ @@ -122,11 +123,11 @@ grpc_security_context *grpc_find_security_context_in_args( return NULL; } -static int check_request_metadata_only_creds(grpc_credentials *creds) { - if (creds != NULL && !grpc_credentials_has_request_metadata_only(creds)) { +static int check_request_metadata_creds(grpc_credentials *creds) { + if (creds != NULL && !grpc_credentials_has_request_metadata(creds)) { gpr_log(GPR_ERROR, "Incompatible credentials for channel security context: needs to " - "only set request metadata."); + "set request metadata."); return 0; } return 1; @@ -136,7 +137,7 @@ static int check_request_metadata_only_creds(grpc_credentials *creds) { static void fake_channel_destroy(grpc_security_context *ctx) { grpc_channel_security_context *c = (grpc_channel_security_context *)ctx; - grpc_credentials_unref(c->request_metadata_only_creds); + grpc_credentials_unref(c->request_metadata_creds); gpr_free(ctx); } @@ -191,15 +192,14 @@ static grpc_security_context_vtable fake_server_vtable = { fake_server_destroy, fake_server_create_handshaker, fake_check_peer}; grpc_channel_security_context *grpc_fake_channel_security_context_create( - grpc_credentials *request_metadata_only_creds) { + grpc_credentials *request_metadata_creds) { grpc_channel_security_context *c = gpr_malloc(sizeof(grpc_channel_security_context)); gpr_ref_init(&c->base.refcount, 1); c->base.is_client_side = 1; c->base.vtable = &fake_channel_vtable; - GPR_ASSERT(check_request_metadata_only_creds(request_metadata_only_creds)); - c->request_metadata_only_creds = - grpc_credentials_ref(request_metadata_only_creds); + GPR_ASSERT(check_request_metadata_creds(request_metadata_creds)); + c->request_metadata_creds = grpc_credentials_ref(request_metadata_creds); return c; } @@ -226,7 +226,7 @@ typedef struct { static void ssl_channel_destroy(grpc_security_context *ctx) { grpc_ssl_channel_security_context *c = (grpc_ssl_channel_security_context *)ctx; - grpc_credentials_unref(c->base.request_metadata_only_creds); + grpc_credentials_unref(c->base.request_metadata_creds); if (c->handshaker_factory != NULL) { tsi_ssl_handshaker_factory_destroy(c->handshaker_factory); } @@ -282,8 +282,8 @@ static grpc_security_status ssl_check_peer(const char *secure_peer_name, gpr_log(GPR_ERROR, "Invalid or missing selected ALPN property."); return GRPC_SECURITY_ERROR; } - if (strncmp(GRPC_ALPN_PROTOCOL_STRING, p->value.string.data, - p->value.string.length)) { + if (!grpc_chttp2_is_alpn_version_supported(p->value.string.data, + p->value.string.length)) { gpr_log(GPR_ERROR, "Invalid ALPN value."); return GRPC_SECURITY_ERROR; } @@ -320,10 +320,9 @@ static grpc_security_context_vtable ssl_server_vtable = { ssl_server_destroy, ssl_server_create_handshaker, ssl_server_check_peer}; grpc_security_status grpc_ssl_channel_security_context_create( - grpc_credentials *request_metadata_only_creds, - const grpc_ssl_config *config, const char *secure_peer_name, - grpc_channel_security_context **ctx) { - const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING; + grpc_credentials *request_metadata_creds, const grpc_ssl_config *config, + const char *secure_peer_name, grpc_channel_security_context **ctx) { + const char *alpn_protocol_string = GRPC_CHTTP2_ALPN_VERSION; unsigned char alpn_protocol_string_len = (unsigned char)strlen(alpn_protocol_string); tsi_result result = TSI_OK; @@ -334,7 +333,7 @@ grpc_security_status grpc_ssl_channel_security_context_create( gpr_log(GPR_ERROR, "An ssl channel needs a secure name and root certs."); return GRPC_SECURITY_ERROR; } - if (!check_request_metadata_only_creds(request_metadata_only_creds)) { + if (!check_request_metadata_creds(request_metadata_creds)) { return GRPC_SECURITY_ERROR; } @@ -344,8 +343,7 @@ grpc_security_status grpc_ssl_channel_security_context_create( gpr_ref_init(&c->base.base.refcount, 1); c->base.base.vtable = &ssl_channel_vtable; c->base.base.is_client_side = 1; - c->base.request_metadata_only_creds = - grpc_credentials_ref(request_metadata_only_creds); + c->base.request_metadata_creds = grpc_credentials_ref(request_metadata_creds); if (secure_peer_name != NULL) { c->secure_peer_name = gpr_strdup(secure_peer_name); } @@ -368,7 +366,7 @@ grpc_security_status grpc_ssl_channel_security_context_create( grpc_security_status grpc_ssl_server_security_context_create( const grpc_ssl_config *config, grpc_security_context **ctx) { - const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING; + const char *alpn_protocol_string = GRPC_CHTTP2_ALPN_VERSION; unsigned char alpn_protocol_string_len = (unsigned char)strlen(alpn_protocol_string); tsi_result result = TSI_OK; @@ -427,7 +425,7 @@ static grpc_channel *grpc_ssl_channel_create(grpc_credentials *creds, status = grpc_ssl_channel_security_context_create(creds, config, secure_peer_name, &ctx); if (status != GRPC_SECURITY_OK) { - return NULL; /* TODO(ctiller): return lame channel. */ + return grpc_lame_client_channel_create(); } channel = grpc_secure_channel_create_internal(target, args, ctx); grpc_security_context_unref(&ctx->base); @@ -435,13 +433,38 @@ static grpc_channel *grpc_ssl_channel_create(grpc_credentials *creds, } +static grpc_credentials *get_creds_from_composite( + grpc_credentials *composite_creds, const char *type) { + size_t i; + const grpc_credentials_array *inner_creds_array = + grpc_composite_credentials_get_credentials(composite_creds); + for (i = 0; i < inner_creds_array->num_creds; i++) { + if (!strcmp(type, inner_creds_array->creds_array[i]->type)) { + return inner_creds_array->creds_array[i]; + } + } + return NULL; +} + +static grpc_channel *grpc_channel_create_from_composite_creds( + grpc_credentials *composite_creds, const char *target, + const grpc_channel_args *args) { + grpc_credentials *creds = + get_creds_from_composite(composite_creds, GRPC_CREDENTIALS_TYPE_SSL); + if (creds != NULL) { + return grpc_ssl_channel_create( + composite_creds, grpc_ssl_credentials_get_config(creds), target, args); + } + return NULL; /* TODO(ctiller): return lame channel. */ +} + grpc_channel *grpc_secure_channel_create(grpc_credentials *creds, const char *target, const grpc_channel_args *args) { if (grpc_credentials_has_request_metadata_only(creds)) { gpr_log(GPR_ERROR, "Credentials is insufficient to create a secure channel."); - return NULL; /* TODO(ctiller): return lame channel. */ + return grpc_lame_client_channel_create(); } if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) { return grpc_ssl_channel_create(NULL, grpc_ssl_credentials_get_config(creds), @@ -455,11 +478,11 @@ grpc_channel *grpc_secure_channel_create(grpc_credentials *creds, grpc_security_context_unref(&ctx->base); return channel; } else if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)) { - return NULL; /* TODO(jboeuf) Implement. */ + return grpc_channel_create_from_composite_creds(creds, target, args); } else { gpr_log(GPR_ERROR, "Unknown credentials type %s for creating a secure channel."); - return NULL; /* TODO(ctiller): return lame channel. */ + return grpc_lame_client_channel_create(); } } diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h index 59c9bbdf34..0c6025643a 100644 --- a/src/core/security/security_context.h +++ b/src/core/security/security_context.h @@ -119,7 +119,7 @@ typedef struct grpc_channel_security_context grpc_channel_security_context; struct grpc_channel_security_context { grpc_security_context base; /* requires is_client_side to be non 0. */ - grpc_credentials *request_metadata_only_creds; + grpc_credentials *request_metadata_creds; }; /* --- Creation security contexts. --- */ @@ -127,14 +127,14 @@ struct grpc_channel_security_context { /* For TESTING ONLY! Creates a fake context that emulates real channel security. */ grpc_channel_security_context *grpc_fake_channel_security_context_create( - grpc_credentials *request_metadata_only_creds); + grpc_credentials *request_metadata_creds); /* For TESTING ONLY! Creates a fake context that emulates real server security. */ grpc_security_context *grpc_fake_server_security_context_create(void); /* Creates an SSL channel_security_context. - - request_metadata_only_creds is the credentials object which metadata + - request_metadata_creds is the credentials object which metadata will be sent with each request. This parameter can be NULL. - config is the SSL config to be used for the SSL channel establishment. - is_client should be 0 for a server or a non-0 value for a client. @@ -147,9 +147,8 @@ grpc_security_context *grpc_fake_server_security_context_create(void); specific error code otherwise. */ grpc_security_status grpc_ssl_channel_security_context_create( - grpc_credentials *request_metadata_only_creds, - const grpc_ssl_config *config, const char *secure_peer_name, - grpc_channel_security_context **ctx); + grpc_credentials *request_metadata_creds, const grpc_ssl_config *config, + const char *secure_peer_name, grpc_channel_security_context **ctx); /* Creates an SSL server_security_context. - config is the SSL config to be used for the SSL channel establishment. diff --git a/src/core/security/server_secure_chttp2.c b/src/core/security/server_secure_chttp2.c index bce27ec3ab..335d502217 100644 --- a/src/core/security/server_secure_chttp2.c +++ b/src/core/security/server_secure_chttp2.c @@ -109,7 +109,7 @@ int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr) { for (i = 0; i < resolved->naddrs; i++) { if (grpc_tcp_server_add_port(tcp, (struct sockaddr *)&resolved->addrs[i].addr, - resolved->addrs[i].len) >= 0) { + resolved->addrs[i].len)) { count++; } } diff --git a/src/core/support/cpu_posix.c b/src/core/support/cpu_posix.c index 82d58de2b4..7f9cb8b4dd 100644 --- a/src/core/support/cpu_posix.c +++ b/src/core/support/cpu_posix.c @@ -34,8 +34,6 @@ #include "src/core/support/cpu.h" #ifdef __linux__ -#include <errno.h> -#include <unistd.h> #define _GNU_SOURCE #define __USE_GNU #define __USE_MISC @@ -43,6 +41,9 @@ #undef _GNU_SOURCE #undef __USE_GNU #undef __USE_MISC + +#include <errno.h> +#include <unistd.h> #include <string.h> #include <grpc/support/log.h> diff --git a/src/core/support/string_posix.c b/src/core/support/string_posix.c index d1da37923f..7b7e82e1e4 100644 --- a/src/core/support/string_posix.c +++ b/src/core/support/string_posix.c @@ -37,6 +37,10 @@ #define _POSIX_C_SOURCE 200112L #endif +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_STRING + #include <stdio.h> #include <stdarg.h> #include <string.h> @@ -84,3 +88,5 @@ int gpr_asprintf(char **strp, const char *format, ...) { *strp = NULL; return -1; } + +#endif /* GPR_POSIX_STRING */ diff --git a/src/core/support/string_win32.c b/src/core/support/string_win32.c new file mode 100644 index 0000000000..74b10280eb --- /dev/null +++ b/src/core/support/string_win32.c @@ -0,0 +1,81 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +/* Posix code for gpr snprintf support. */ + +#include <grpc/support/port_platform.h> + +#ifdef GPR_WIN32 + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include <grpc/support/alloc.h> + +int gpr_asprintf(char **strp, const char *format, ...) { + va_list args; + int ret; + size_t strp_buflen; + + /* Determine the length. */ + va_start(args, format); + ret = vscprintf(format, args); + va_end(args); + if (!(0 <= ret && ret < ~(size_t)0)) { + *strp = NULL; + return -1; + } + + /* Allocate a new buffer, with space for the NUL terminator. */ + strp_buflen = (size_t)ret + 1; + if ((*strp = gpr_malloc(strp_buflen)) == NULL) { + /* This shouldn't happen, because gpr_malloc() calls abort(). */ + return -1; + } + + /* Print to the buffer. */ + va_start(args, format); + ret = vsnprintf_s(*strp, strp_buflen, _TRUNCATE, format, args); + va_end(args); + if (ret == strp_buflen - 1) { + return ret; + } + + /* This should never happen. */ + gpr_free(*strp); + *strp = NULL; + return -1; +} + +#endif /* GPR_WIN32 */ diff --git a/src/core/support/thd_posix.c b/src/core/support/thd_posix.c index c86eea415d..1189e0c3f9 100644 --- a/src/core/support/thd_posix.c +++ b/src/core/support/thd_posix.c @@ -33,6 +33,10 @@ /* Posix implementation for gpr threads. */ +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_SYNC + #include <pthread.h> #include <stdlib.h> #include <string.h> @@ -76,3 +80,5 @@ gpr_thd_options gpr_thd_options_default(void) { memset(&options, 0, sizeof(options)); return options; } + +#endif /* GPR_POSIX_SYNC */ diff --git a/src/core/support/thd_win32.c b/src/core/support/thd_win32.c index 8440479520..1762f87f3c 100644 --- a/src/core/support/thd_win32.c +++ b/src/core/support/thd_win32.c @@ -48,7 +48,7 @@ struct thd_arg { }; /* Body of every thread started via gpr_thd_new. */ -static DWORD thread_body(void *v) { +static DWORD WINAPI thread_body(void *v) { struct thd_arg a = *(struct thd_arg *)v; gpr_free(v); (*a.body)(a.arg); @@ -62,7 +62,7 @@ int gpr_thd_new(gpr_thd_id *t, void (*thd_body)(void *arg), void *arg, a->body = thd_body; a->arg = arg; *t = 0; - handle = CreateThread(NULL, 64 * 1024, &thread_body, a, 0, NULL); + handle = CreateThread(NULL, 64 * 1024, thread_body, a, 0, NULL); if (handle == NULL) { gpr_free(a); } else { diff --git a/src/core/support/time_posix.c b/src/core/support/time_posix.c index e7b79d10b1..78d4c3b446 100644 --- a/src/core/support/time_posix.c +++ b/src/core/support/time_posix.c @@ -37,6 +37,11 @@ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 199309L #endif + +#include <grpc/support/port_platform.h> + +#ifdef GPR_POSIX_TIME + #include <stdlib.h> #include <time.h> #include <unistd.h> @@ -79,3 +84,5 @@ void gpr_sleep_until(gpr_timespec until) { } } } + +#endif /* GPR_POSIX_TIME */ diff --git a/src/core/support/time_win32.c b/src/core/support/time_win32.c index 425809144c..d9abe2dde1 100644 --- a/src/core/support/time_win32.c +++ b/src/core/support/time_win32.c @@ -38,12 +38,12 @@ #ifdef GPR_WIN32 #include <grpc/support/time.h> -#include <windows.h> +#include <sys/timeb.h> gpr_timespec gpr_now(void) { gpr_timespec now_tv; - struct _timeb64 now_tb; - _ftime64(&now_tb); + struct __timeb64 now_tb; + _ftime64_s(&now_tb); now_tv.tv_sec = now_tb.time; now_tv.tv_nsec = now_tb.millitm * 1000000; return now_tv; diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 02c224dabd..a731c7cab7 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -154,7 +154,12 @@ static int prq_pop_to_cq(pending_read_queue *q, void *tag, grpc_call *call, /* the state of a call, based upon which functions have been called against said call */ -typedef enum { CALL_CREATED, CALL_STARTED, CALL_FINISHED } call_state; +typedef enum { + CALL_CREATED, + CALL_BOUNDCQ, + CALL_STARTED, + CALL_FINISHED +} call_state; struct grpc_call { grpc_completion_queue *cq; @@ -404,24 +409,18 @@ grpc_call_error grpc_call_start_invoke(grpc_call *call, return GRPC_CALL_OK; } -grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, - void *finished_tag, gpr_uint32 flags) { - grpc_call_element *elem; - grpc_call_op op; - +grpc_call_error grpc_call_server_accept(grpc_call *call, + grpc_completion_queue *cq, + void *finished_tag) { /* validate preconditions */ if (call->is_client) { gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__); return GRPC_CALL_ERROR_NOT_ON_CLIENT; } - if (call->state >= CALL_STARTED) { - gpr_log(GPR_ERROR, "call is already invoked"); - return GRPC_CALL_ERROR_ALREADY_INVOKED; - } - - if (flags & GRPC_WRITE_NO_COMPRESS) { - return GRPC_CALL_ERROR_INVALID_FLAGS; + if (call->state >= CALL_BOUNDCQ) { + gpr_log(GPR_ERROR, "call is already accepted"); + return GRPC_CALL_ERROR_ALREADY_ACCEPTED; } /* inform the completion queue of an incoming operation (corresponding to @@ -430,7 +429,7 @@ grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, /* update state */ gpr_mu_lock(&call->read_mu); - call->state = CALL_STARTED; + call->state = CALL_BOUNDCQ; call->cq = cq; call->finished_tag = finished_tag; if (prq_is_empty(&call->prq) && call->received_finish) { @@ -442,6 +441,32 @@ grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, } gpr_mu_unlock(&call->read_mu); + return GRPC_CALL_OK; +} + +grpc_call_error grpc_call_server_end_initial_metadata(grpc_call *call, + gpr_uint32 flags) { + grpc_call_element *elem; + grpc_call_op op; + + /* validate preconditions */ + if (call->is_client) { + gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__); + return GRPC_CALL_ERROR_NOT_ON_CLIENT; + } + + if (call->state >= CALL_STARTED) { + gpr_log(GPR_ERROR, "call is already started"); + return GRPC_CALL_ERROR_ALREADY_INVOKED; + } + + if (flags & GRPC_WRITE_NO_COMPRESS) { + return GRPC_CALL_ERROR_INVALID_FLAGS; + } + + /* update state */ + call->state = CALL_STARTED; + /* call down */ op.type = GRPC_SEND_START; op.dir = GRPC_CALL_DOWN; @@ -455,6 +480,17 @@ grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, return GRPC_CALL_OK; } +grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq, + void *finished_tag, gpr_uint32 flags) { + grpc_call_error err; + + err = grpc_call_server_accept(call, cq, finished_tag); + if (err != GRPC_CALL_OK) return err; + err = grpc_call_server_end_initial_metadata(call, flags); + if (err != GRPC_CALL_OK) return err; + return GRPC_CALL_OK; +} + static void done_writes_done(void *user_data, grpc_op_error error) { grpc_call *call = user_data; void *tag = call->write_tag; @@ -515,6 +551,7 @@ grpc_call_error grpc_call_start_read(grpc_call *call, void *tag) { switch (call->state) { case CALL_CREATED: return GRPC_CALL_ERROR_NOT_INVOKED; + case CALL_BOUNDCQ: case CALL_STARTED: break; case CALL_FINISHED: @@ -559,6 +596,7 @@ grpc_call_error grpc_call_start_write(grpc_call *call, switch (call->state) { case CALL_CREATED: + case CALL_BOUNDCQ: return GRPC_CALL_ERROR_NOT_INVOKED; case CALL_STARTED: break; @@ -607,6 +645,7 @@ grpc_call_error grpc_call_writes_done(grpc_call *call, void *tag) { switch (call->state) { case CALL_CREATED: + case CALL_BOUNDCQ: return GRPC_CALL_ERROR_NOT_INVOKED; case CALL_FINISHED: return GRPC_CALL_ERROR_ALREADY_FINISHED; @@ -646,6 +685,7 @@ grpc_call_error grpc_call_start_write_status(grpc_call *call, switch (call->state) { case CALL_CREATED: + case CALL_BOUNDCQ: return GRPC_CALL_ERROR_NOT_INVOKED; case CALL_FINISHED: return GRPC_CALL_ERROR_ALREADY_FINISHED; diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c index ff994257f4..dfb47b3a53 100644 --- a/src/core/surface/channel.c +++ b/src/core/surface/channel.c @@ -127,9 +127,16 @@ void grpc_channel_destroy(grpc_channel *channel) { grpc_channel_op op; grpc_channel_element *elem; - op.type = GRPC_CHANNEL_SHUTDOWN; - op.dir = GRPC_CALL_DOWN; elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CHANNEL(channel), 0); + + op.type = GRPC_CHANNEL_GOAWAY; + op.dir = GRPC_CALL_DOWN; + op.data.goaway.status = GRPC_STATUS_OK; + op.data.goaway.message = gpr_slice_from_copied_string("Client disconnect"); + elem->filter->channel_op(elem, &op); + + op.type = GRPC_CHANNEL_DISCONNECT; + op.dir = GRPC_CALL_DOWN; elem->filter->channel_op(elem, &op); grpc_channel_internal_unref(channel); diff --git a/src/core/surface/client.c b/src/core/surface/client.c index 26abffa817..f78a45f073 100644 --- a/src/core/surface/client.c +++ b/src/core/surface/client.c @@ -83,6 +83,9 @@ static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { case GRPC_TRANSPORT_CLOSED: gpr_log(GPR_ERROR, "Transport closed"); break; + case GRPC_TRANSPORT_GOAWAY: + gpr_slice_unref(op->data.goaway.message); + break; default: GPR_ASSERT(op->dir == GRPC_CALL_DOWN); grpc_channel_next_op(elem, op); diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c index a7d611579f..2002476777 100644 --- a/src/core/surface/completion_queue.c +++ b/src/core/surface/completion_queue.c @@ -105,7 +105,7 @@ static event *add_locked(grpc_completion_queue *cc, grpc_completion_type type, void *tag, grpc_call *call, grpc_event_finish_func on_finish, void *user_data) { event *ev = gpr_malloc(sizeof(event)); - gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS; + gpr_uintptr bucket = ((gpr_uintptr)tag) % NUM_TAG_BUCKETS; GPR_ASSERT(!cc->shutdown); ev->base.type = type; ev->base.tag = tag; @@ -260,9 +260,9 @@ grpc_event *grpc_completion_queue_next(grpc_completion_queue *cc, gpr_mu_lock(&cc->em->mu); for (;;) { if (cc->queue != NULL) { - gpr_intptr bucket; + gpr_uintptr bucket; ev = cc->queue; - bucket = ((gpr_intptr)ev->base.tag) % NUM_TAG_BUCKETS; + bucket = ((gpr_uintptr)ev->base.tag) % NUM_TAG_BUCKETS; cc->queue = ev->queue_next; ev->queue_next->queue_prev = ev->queue_prev; ev->queue_prev->queue_next = ev->queue_next; @@ -297,7 +297,7 @@ grpc_event *grpc_completion_queue_next(grpc_completion_queue *cc, } static event *pluck_event(grpc_completion_queue *cc, void *tag) { - gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS; + gpr_uintptr bucket = ((gpr_uintptr)tag) % NUM_TAG_BUCKETS; event *ev = cc->buckets[bucket]; if (ev == NULL) return NULL; do { diff --git a/src/core/surface/lame_client.c b/src/core/surface/lame_client.c index 18921c44dd..3744b5b6db 100644 --- a/src/core/surface/lame_client.c +++ b/src/core/surface/lame_client.c @@ -61,7 +61,15 @@ static void call_op(grpc_call_element *elem, grpc_call_op *op) { op->done_cb(op->user_data, GRPC_OP_ERROR); } -static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) {} +static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { + switch (op->type) { + case GRPC_CHANNEL_GOAWAY: + gpr_slice_unref(op->data.goaway.message); + break; + default: + break; + } +} static void init_call_elem(grpc_call_element *elem, const void *transport_server_data) {} @@ -87,7 +95,7 @@ static const grpc_channel_filter lame_filter = { "lame-client", }; -grpc_channel *grpc_lame_client_channel_create() { +grpc_channel *grpc_lame_client_channel_create(void) { static const grpc_channel_filter *filters[] = {&lame_filter}; return grpc_channel_create_from_filters(filters, 1, NULL, grpc_mdctx_create(), 1); diff --git a/src/core/surface/lame_client.h b/src/core/surface/lame_client.h index 74b9707202..3cfbf7b954 100644 --- a/src/core/surface/lame_client.h +++ b/src/core/surface/lame_client.h @@ -37,6 +37,6 @@ #include <grpc/grpc.h> /* Create a lame client: this client fails every operation attempted on it. */ -grpc_channel *grpc_lame_client_channel_create(); +grpc_channel *grpc_lame_client_channel_create(void); #endif /* __GRPC_INTERNAL_SURFACE_LAME_CLIENT_H_ */ diff --git a/src/core/surface/server.c b/src/core/surface/server.c index 99d66ffb2d..d8d5a7adf1 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -331,6 +331,9 @@ static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { gpr_mu_unlock(&chand->server->mu); server_unref(chand->server); break; + case GRPC_TRANSPORT_GOAWAY: + gpr_slice_unref(op->data.goaway.message); + break; default: GPR_ASSERT(op->dir == GRPC_CALL_DOWN); grpc_channel_next_op(elem, op); @@ -341,7 +344,7 @@ static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) { static void finish_shutdown_channel(void *cd, grpc_em_cb_status status) { channel_data *chand = cd; grpc_channel_op op; - op.type = GRPC_CHANNEL_SHUTDOWN; + op.type = GRPC_CHANNEL_DISCONNECT; op.dir = GRPC_CALL_DOWN; channel_op(grpc_channel_stack_element( grpc_channel_get_channel_stack(chand->channel), 0), @@ -515,10 +518,15 @@ grpc_transport_setup_result grpc_server_setup_transport( } void grpc_server_shutdown(grpc_server *server) { - /* TODO(ctiller): send goaway, etc */ listener *l; void **tags; size_t ntags; + channel_data **channels; + channel_data *c; + size_t nchannels; + size_t i; + grpc_channel_op op; + grpc_channel_element *elem; /* lock, and gather up some stuff to do */ gpr_mu_lock(&server->mu); @@ -527,6 +535,20 @@ void grpc_server_shutdown(grpc_server *server) { return; } + nchannels = 0; + for (c = server->root_channel_data.next; c != &server->root_channel_data; + c = c->next) { + nchannels++; + } + channels = gpr_malloc(sizeof(channel_data *) * nchannels); + i = 0; + for (c = server->root_channel_data.next; c != &server->root_channel_data; + c = c->next) { + grpc_channel_internal_ref(c->channel); + channels[i] = c; + i++; + } + tags = server->tags; ntags = server->ntags; server->tags = NULL; @@ -535,6 +557,21 @@ void grpc_server_shutdown(grpc_server *server) { server->shutdown = 1; gpr_mu_unlock(&server->mu); + for (i = 0; i < nchannels; i++) { + c = channels[i]; + elem = grpc_channel_stack_element( + grpc_channel_get_channel_stack(c->channel), 0); + + op.type = GRPC_CHANNEL_GOAWAY; + op.dir = GRPC_CALL_DOWN; + op.data.goaway.status = GRPC_STATUS_OK; + op.data.goaway.message = gpr_slice_from_copied_string("Server shutdown"); + elem->filter->channel_op(elem, &op); + + grpc_channel_internal_unref(c->channel); + } + gpr_free(channels); + /* terminate all the requested calls */ early_terminate_requested_calls(server->cq, tags, ntags); gpr_free(tags); diff --git a/src/core/surface/server_chttp2.c b/src/core/surface/server_chttp2.c index 24c5757166..db8924efea 100644 --- a/src/core/surface/server_chttp2.c +++ b/src/core/surface/server_chttp2.c @@ -91,7 +91,7 @@ int grpc_server_add_http2_port(grpc_server *server, const char *addr) { for (i = 0; i < resolved->naddrs; i++) { if (grpc_tcp_server_add_port(tcp, (struct sockaddr *)&resolved->addrs[i].addr, - resolved->addrs[i].len) >= 0) { + resolved->addrs[i].len)) { count++; } } diff --git a/src/core/transport/chttp2/alpn.c b/src/core/transport/chttp2/alpn.c new file mode 100644 index 0000000000..cd9cf67a90 --- /dev/null +++ b/src/core/transport/chttp2/alpn.c @@ -0,0 +1,45 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "src/core/transport/chttp2/alpn.h" + +static const char *const supported_versions[] = {GRPC_CHTTP2_ALPN_VERSION, + "h2-15", "h2-14"}; + +int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size) { + size_t i; + for (i = 0; i < sizeof(supported_versions) / sizeof(const char *); i++) { + if (!strncmp(version, supported_versions[i], size)) return 1; + } + return 0; +} diff --git a/src/core/transport/chttp2/alpn.h b/src/core/transport/chttp2/alpn.h new file mode 100644 index 0000000000..1353a18b1b --- /dev/null +++ b/src/core/transport/chttp2/alpn.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_ + +#include <string.h> + +#define GRPC_CHTTP2_ALPN_VERSION "h2-15" + +/* Retuns 1 if the version is supported, 0 otherwise. */ +int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_ */ diff --git a/src/core/transport/chttp2/bin_encoder.c b/src/core/transport/chttp2/bin_encoder.c new file mode 100644 index 0000000000..39a303bce9 --- /dev/null +++ b/src/core/transport/chttp2/bin_encoder.c @@ -0,0 +1,271 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "src/core/transport/chttp2/bin_encoder.h" +#include "src/core/transport/chttp2/huffsyms.h" +#include <grpc/support/log.h> + +static const char alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +typedef struct { + gpr_uint16 bits; + gpr_uint8 length; +} b64_huff_sym; + +static const b64_huff_sym huff_alphabet[64] = {{0x21, 6}, + {0x5d, 7}, + {0x5e, 7}, + {0x5f, 7}, + {0x60, 7}, + {0x61, 7}, + {0x62, 7}, + {0x63, 7}, + {0x64, 7}, + {0x65, 7}, + {0x66, 7}, + {0x67, 7}, + {0x68, 7}, + {0x69, 7}, + {0x6a, 7}, + {0x6b, 7}, + {0x6c, 7}, + {0x6d, 7}, + {0x6e, 7}, + {0x6f, 7}, + {0x70, 7}, + {0x71, 7}, + {0x72, 7}, + {0xfc, 8}, + {0x73, 7}, + {0xfd, 8}, + {0x3, 5}, + {0x23, 6}, + {0x4, 5}, + {0x24, 6}, + {0x5, 5}, + {0x25, 6}, + {0x26, 6}, + {0x27, 6}, + {0x6, 5}, + {0x74, 7}, + {0x75, 7}, + {0x28, 6}, + {0x29, 6}, + {0x2a, 6}, + {0x7, 5}, + {0x2b, 6}, + {0x76, 7}, + {0x2c, 6}, + {0x8, 5}, + {0x9, 5}, + {0x2d, 6}, + {0x77, 7}, + {0x78, 7}, + {0x79, 7}, + {0x7a, 7}, + {0x7b, 7}, + {0x0, 5}, + {0x1, 5}, + {0x2, 5}, + {0x19, 6}, + {0x1a, 6}, + {0x1b, 6}, + {0x1c, 6}, + {0x1d, 6}, + {0x1e, 6}, + {0x1f, 6}, + {0x7fb, 11}, + {0x18, 6}}; + +static const gpr_uint8 tail_xtra[3] = {0, 2, 3}; + +gpr_slice grpc_chttp2_base64_encode(gpr_slice input) { + size_t input_length = GPR_SLICE_LENGTH(input); + size_t input_triplets = input_length / 3; + size_t tail_case = input_length % 3; + size_t output_length = input_triplets * 4 + tail_xtra[tail_case]; + gpr_slice output = gpr_slice_malloc(output_length); + gpr_uint8 *in = GPR_SLICE_START_PTR(input); + gpr_uint8 *out = GPR_SLICE_START_PTR(output); + size_t i; + + /* encode full triplets */ + for (i = 0; i < input_triplets; i++) { + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[((in[0] & 0x2) << 4) | (in[1] >> 4)]; + out[2] = alphabet[((in[1] & 0xf) << 2) | (in[2] >> 6)]; + out[3] = alphabet[in[2] & 0x3f]; + out += 4; + in += 3; + } + + /* encode the remaining bytes */ + switch (tail_case) { + case 0: + break; + case 1: + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[(in[0] & 0x2) << 4]; + out += 2; + in += 1; + break; + case 2: + out[0] = alphabet[in[0] >> 2]; + out[1] = alphabet[((in[0] & 0x2) << 4) | (in[1] >> 4)]; + out[2] = alphabet[(in[1] & 0xf) << 2]; + out += 3; + in += 2; + break; + } + + GPR_ASSERT(out == GPR_SLICE_END_PTR(output)); + GPR_ASSERT(in == GPR_SLICE_END_PTR(input)); + return output; +} + +gpr_slice grpc_chttp2_huffman_compress(gpr_slice input) { + size_t nbits; + gpr_uint8 *in; + gpr_uint8 *out; + gpr_slice output; + gpr_uint32 temp = 0; + gpr_uint32 temp_length = 0; + + nbits = 0; + for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) { + nbits += grpc_chttp2_huffsyms[*in].length; + } + + output = gpr_slice_malloc(nbits / 8 + (nbits % 8 != 0)); + out = GPR_SLICE_START_PTR(output); + for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) { + int sym = *in; + temp <<= grpc_chttp2_huffsyms[sym].length; + temp |= grpc_chttp2_huffsyms[sym].bits; + temp_length += grpc_chttp2_huffsyms[sym].length; + + while (temp_length > 8) { + temp_length -= 8; + *out++ = temp >> temp_length; + } + } + + if (temp_length) { + *out++ = (temp << (8 - temp_length)) | (0xff >> temp_length); + } + + GPR_ASSERT(out == GPR_SLICE_END_PTR(output)); + + return output; +} + +typedef struct { + gpr_uint32 temp; + gpr_uint32 temp_length; + gpr_uint8 *out; +} huff_out; + +static void enc_flush_some(huff_out *out) { + while (out->temp_length > 8) { + out->temp_length -= 8; + *out->out++ = out->temp >> out->temp_length; + } +} + +static void enc_add2(huff_out *out, gpr_uint8 a, gpr_uint8 b) { + b64_huff_sym sa = huff_alphabet[a]; + b64_huff_sym sb = huff_alphabet[b]; + out->temp = + (out->temp << (sa.length + sb.length)) | (sa.bits << sb.length) | sb.bits; + out->temp_length += sa.length + sb.length; + enc_flush_some(out); +} + +static void enc_add1(huff_out *out, gpr_uint8 a) { + b64_huff_sym sa = huff_alphabet[a]; + out->temp = (out->temp << sa.length) | sa.bits; + out->temp_length += sa.length; + enc_flush_some(out); +} + +gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input) { + size_t input_length = GPR_SLICE_LENGTH(input); + size_t input_triplets = input_length / 3; + size_t tail_case = input_length % 3; + size_t output_syms = input_triplets * 4 + tail_xtra[tail_case]; + size_t max_output_bits = 11 * output_syms; + size_t max_output_length = max_output_bits / 8 + (max_output_bits % 8 != 0); + gpr_slice output = gpr_slice_malloc(max_output_length); + gpr_uint8 *in = GPR_SLICE_START_PTR(input); + gpr_uint8 *start_out = GPR_SLICE_START_PTR(output); + huff_out out; + size_t i; + + out.temp = 0; + out.temp_length = 0; + out.out = start_out; + + /* encode full triplets */ + for (i = 0; i < input_triplets; i++) { + enc_add2(&out, in[0] >> 2, ((in[0] & 0x2) << 4) | (in[1] >> 4)); + enc_add2(&out, ((in[1] & 0xf) << 2) | (in[2] >> 6), in[2] & 0x3f); + in += 3; + } + + /* encode the remaining bytes */ + switch (tail_case) { + case 0: + break; + case 1: + enc_add2(&out, in[0] >> 2, (in[0] & 0x2) << 4); + in += 1; + break; + case 2: + enc_add2(&out, in[0] >> 2, ((in[0] & 0x2) << 4) | (in[1] >> 4)); + enc_add1(&out, (in[1] & 0xf) << 2); + in += 2; + break; + } + + if (out.temp_length) { + *out.out++ = + (out.temp << (8 - out.temp_length)) | (0xff >> out.temp_length); + } + + GPR_ASSERT(out.out <= GPR_SLICE_END_PTR(output)); + GPR_SLICE_SET_LENGTH(output, out.out - start_out); + + GPR_ASSERT(in == GPR_SLICE_END_PTR(input)); + return output; +} diff --git a/src/core/transport/chttp2/bin_encoder.h b/src/core/transport/chttp2/bin_encoder.h new file mode 100644 index 0000000000..86031137dc --- /dev/null +++ b/src/core/transport/chttp2/bin_encoder.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_ + +#include <grpc/support/slice.h> + +/* base64 encode a slice. Returns a new slice, does not take ownership of the + input */ +gpr_slice grpc_chttp2_base64_encode(gpr_slice input); + +/* Compress a slice with the static huffman encoder detailed in the hpack + standard. Returns a new slice, does not take ownership of the input */ +gpr_slice grpc_chttp2_huffman_compress(gpr_slice input); + +/* equivalent to: + gpr_slice x = grpc_chttp2_base64_encode(input); + gpr_slice y = grpc_chttp2_huffman_compress(x); + gpr_slice_unref(x); + return y; */ +gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_ */ diff --git a/src/core/transport/chttp2/frame.h b/src/core/transport/chttp2/frame.h index 7c0bbe026b..a04e442ed6 100644 --- a/src/core/transport/chttp2/frame.h +++ b/src/core/transport/chttp2/frame.h @@ -35,6 +35,7 @@ #define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__ #include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> /* Common definitions for frame handling in the chttp2 transport */ @@ -51,8 +52,12 @@ typedef struct { gpr_uint8 ack_settings; gpr_uint8 send_ping_ack; gpr_uint8 process_ping_reply; + gpr_uint8 goaway; gpr_uint32 window_update; + gpr_uint32 goaway_last_stream_index; + gpr_uint32 goaway_error; + gpr_slice goaway_text; } grpc_chttp2_parse_state; #define GRPC_CHTTP2_FRAME_DATA 0 @@ -61,6 +66,7 @@ typedef struct { #define GRPC_CHTTP2_FRAME_RST_STREAM 3 #define GRPC_CHTTP2_FRAME_SETTINGS 4 #define GRPC_CHTTP2_FRAME_PING 6 +#define GRPC_CHTTP2_FRAME_GOAWAY 7 #define GRPC_CHTTP2_FRAME_WINDOW_UPDATE 8 #define GRPC_CHTTP2_MAX_PAYLOAD_LENGTH ((1 << 14) - 1) diff --git a/src/core/transport/chttp2/frame_goaway.c b/src/core/transport/chttp2/frame_goaway.c new file mode 100644 index 0000000000..de7c0b010b --- /dev/null +++ b/src/core/transport/chttp2/frame_goaway.c @@ -0,0 +1,189 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "src/core/transport/chttp2/frame_goaway.h" + +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p) { + p->debug_data = NULL; +} + +void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p) { + gpr_free(p->debug_data); +} + +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame( + grpc_chttp2_goaway_parser *p, gpr_uint32 length, gpr_uint8 flags) { + if (length < 8) { + gpr_log(GPR_ERROR, "goaway frame too short (%d bytes)", length); + return GRPC_CHTTP2_CONNECTION_ERROR; + } + + gpr_free(p->debug_data); + p->debug_length = length - 8; + p->debug_data = gpr_malloc(p->debug_length); + p->debug_pos = 0; + p->state = GRPC_CHTTP2_GOAWAY_LSI0; + return GRPC_CHTTP2_PARSE_OK; +} + +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, + int is_last) { + gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice); + gpr_uint8 *const end = GPR_SLICE_END_PTR(slice); + gpr_uint8 *cur = beg; + grpc_chttp2_goaway_parser *p = parser; + + switch (p->state) { + case GRPC_CHTTP2_GOAWAY_LSI0: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI0; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id = ((gpr_uint32)*cur) << 24; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI1: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI1; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((gpr_uint32)*cur) << 16; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI2: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI2; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((gpr_uint32)*cur) << 8; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_LSI3: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_LSI3; + return GRPC_CHTTP2_PARSE_OK; + } + p->last_stream_id |= ((gpr_uint32)*cur); + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR0: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR0; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code = ((gpr_uint32)*cur) << 24; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR1: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR1; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((gpr_uint32)*cur) << 16; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR2: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR2; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((gpr_uint32)*cur) << 8; + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_ERR3: + if (cur == end) { + p->state = GRPC_CHTTP2_GOAWAY_ERR3; + return GRPC_CHTTP2_PARSE_OK; + } + p->error_code |= ((gpr_uint32)*cur); + ++cur; + /* fallthrough */ + case GRPC_CHTTP2_GOAWAY_DEBUG: + memcpy(p->debug_data + p->debug_pos, cur, end - cur); + p->debug_pos += end - cur; + p->state = GRPC_CHTTP2_GOAWAY_DEBUG; + if (is_last) { + state->goaway = 1; + state->goaway_last_stream_index = p->last_stream_id; + state->goaway_error = p->error_code; + state->goaway_text = + gpr_slice_new(p->debug_data, p->debug_length, gpr_free); + p->debug_data = NULL; + } + return GRPC_CHTTP2_PARSE_OK; + } + gpr_log(GPR_ERROR, "Should never end up here"); + abort(); + return GRPC_CHTTP2_CONNECTION_ERROR; +} + +void grpc_chttp2_goaway_append(gpr_uint32 last_stream_id, gpr_uint32 error_code, + gpr_slice debug_data, + gpr_slice_buffer *slice_buffer) { + gpr_slice header = gpr_slice_malloc(9 + 4 + 4); + gpr_uint8 *p = GPR_SLICE_START_PTR(header); + gpr_uint32 frame_length = 4 + 4 + GPR_SLICE_LENGTH(debug_data); + + /* frame header: length */ + *p++ = frame_length >> 16; + *p++ = frame_length >> 8; + *p++ = frame_length; + /* frame header: type */ + *p++ = GRPC_CHTTP2_FRAME_GOAWAY; + /* frame header: flags */ + *p++ = 0; + /* frame header: stream id */ + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + /* payload: last stream id */ + *p++ = last_stream_id >> 24; + *p++ = last_stream_id >> 16; + *p++ = last_stream_id >> 8; + *p++ = last_stream_id; + /* payload: error code */ + *p++ = error_code >> 24; + *p++ = error_code >> 16; + *p++ = error_code >> 8; + *p++ = error_code; + GPR_ASSERT(p == GPR_SLICE_END_PTR(header)); + gpr_slice_buffer_add(slice_buffer, header); + gpr_slice_buffer_add(slice_buffer, debug_data); +} diff --git a/src/core/transport/chttp2/frame_goaway.h b/src/core/transport/chttp2/frame_goaway.h new file mode 100644 index 0000000000..9a3f8e6a7a --- /dev/null +++ b/src/core/transport/chttp2/frame_goaway.h @@ -0,0 +1,74 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_ + +#include "src/core/transport/chttp2/frame.h" +#include <grpc/support/port_platform.h> +#include <grpc/support/slice.h> +#include <grpc/support/slice_buffer.h> + +typedef enum { + GRPC_CHTTP2_GOAWAY_LSI0, + GRPC_CHTTP2_GOAWAY_LSI1, + GRPC_CHTTP2_GOAWAY_LSI2, + GRPC_CHTTP2_GOAWAY_LSI3, + GRPC_CHTTP2_GOAWAY_ERR0, + GRPC_CHTTP2_GOAWAY_ERR1, + GRPC_CHTTP2_GOAWAY_ERR2, + GRPC_CHTTP2_GOAWAY_ERR3, + GRPC_CHTTP2_GOAWAY_DEBUG +} grpc_chttp2_goaway_parse_state; + +typedef struct { + grpc_chttp2_goaway_parse_state state; + gpr_uint32 last_stream_id; + gpr_uint32 error_code; + char *debug_data; + gpr_uint32 debug_length; + gpr_uint32 debug_pos; +} grpc_chttp2_goaway_parser; + +void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p); +void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p); +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame( + grpc_chttp2_goaway_parser *parser, gpr_uint32 length, gpr_uint8 flags); +grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse( + void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last); + +void grpc_chttp2_goaway_append(gpr_uint32 last_stream_id, gpr_uint32 error_code, + gpr_slice debug_data, + gpr_slice_buffer *slice_buffer); + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_ */ diff --git a/src/core/transport/chttp2/gen_hpack_tables.c b/src/core/transport/chttp2/gen_hpack_tables.c index cc94a737ca..301b79ef2c 100644 --- a/src/core/transport/chttp2/gen_hpack_tables.c +++ b/src/core/transport/chttp2/gen_hpack_tables.c @@ -39,6 +39,7 @@ #include <assert.h> #include <grpc/support/log.h> +#include "src/core/transport/chttp2/huffsyms.h" /* * first byte LUT generation @@ -127,277 +128,10 @@ static void generate_first_byte_lut() { * Huffman decoder table generation */ -#define NSYMS 257 #define MAXHUFFSTATES 1024 -/* Constants pulled from the HPACK spec, and converted to C using the vim - command: - :%s/.* \([0-9a-f]\+\) \[ *\([0-9]\+\)\]/{0x\1, \2},/g */ -static const struct { - unsigned bits; - unsigned length; -} huffsyms[NSYMS] = { - {0x1ff8, 13}, - {0x7fffd8, 23}, - {0xfffffe2, 28}, - {0xfffffe3, 28}, - {0xfffffe4, 28}, - {0xfffffe5, 28}, - {0xfffffe6, 28}, - {0xfffffe7, 28}, - {0xfffffe8, 28}, - {0xffffea, 24}, - {0x3ffffffc, 30}, - {0xfffffe9, 28}, - {0xfffffea, 28}, - {0x3ffffffd, 30}, - {0xfffffeb, 28}, - {0xfffffec, 28}, - {0xfffffed, 28}, - {0xfffffee, 28}, - {0xfffffef, 28}, - {0xffffff0, 28}, - {0xffffff1, 28}, - {0xffffff2, 28}, - {0x3ffffffe, 30}, - {0xffffff3, 28}, - {0xffffff4, 28}, - {0xffffff5, 28}, - {0xffffff6, 28}, - {0xffffff7, 28}, - {0xffffff8, 28}, - {0xffffff9, 28}, - {0xffffffa, 28}, - {0xffffffb, 28}, - {0x14, 6}, - {0x3f8, 10}, - {0x3f9, 10}, - {0xffa, 12}, - {0x1ff9, 13}, - {0x15, 6}, - {0xf8, 8}, - {0x7fa, 11}, - {0x3fa, 10}, - {0x3fb, 10}, - {0xf9, 8}, - {0x7fb, 11}, - {0xfa, 8}, - {0x16, 6}, - {0x17, 6}, - {0x18, 6}, - {0x0, 5}, - {0x1, 5}, - {0x2, 5}, - {0x19, 6}, - {0x1a, 6}, - {0x1b, 6}, - {0x1c, 6}, - {0x1d, 6}, - {0x1e, 6}, - {0x1f, 6}, - {0x5c, 7}, - {0xfb, 8}, - {0x7ffc, 15}, - {0x20, 6}, - {0xffb, 12}, - {0x3fc, 10}, - {0x1ffa, 13}, - {0x21, 6}, - {0x5d, 7}, - {0x5e, 7}, - {0x5f, 7}, - {0x60, 7}, - {0x61, 7}, - {0x62, 7}, - {0x63, 7}, - {0x64, 7}, - {0x65, 7}, - {0x66, 7}, - {0x67, 7}, - {0x68, 7}, - {0x69, 7}, - {0x6a, 7}, - {0x6b, 7}, - {0x6c, 7}, - {0x6d, 7}, - {0x6e, 7}, - {0x6f, 7}, - {0x70, 7}, - {0x71, 7}, - {0x72, 7}, - {0xfc, 8}, - {0x73, 7}, - {0xfd, 8}, - {0x1ffb, 13}, - {0x7fff0, 19}, - {0x1ffc, 13}, - {0x3ffc, 14}, - {0x22, 6}, - {0x7ffd, 15}, - {0x3, 5}, - {0x23, 6}, - {0x4, 5}, - {0x24, 6}, - {0x5, 5}, - {0x25, 6}, - {0x26, 6}, - {0x27, 6}, - {0x6, 5}, - {0x74, 7}, - {0x75, 7}, - {0x28, 6}, - {0x29, 6}, - {0x2a, 6}, - {0x7, 5}, - {0x2b, 6}, - {0x76, 7}, - {0x2c, 6}, - {0x8, 5}, - {0x9, 5}, - {0x2d, 6}, - {0x77, 7}, - {0x78, 7}, - {0x79, 7}, - {0x7a, 7}, - {0x7b, 7}, - {0x7ffe, 15}, - {0x7fc, 11}, - {0x3ffd, 14}, - {0x1ffd, 13}, - {0xffffffc, 28}, - {0xfffe6, 20}, - {0x3fffd2, 22}, - {0xfffe7, 20}, - {0xfffe8, 20}, - {0x3fffd3, 22}, - {0x3fffd4, 22}, - {0x3fffd5, 22}, - {0x7fffd9, 23}, - {0x3fffd6, 22}, - {0x7fffda, 23}, - {0x7fffdb, 23}, - {0x7fffdc, 23}, - {0x7fffdd, 23}, - {0x7fffde, 23}, - {0xffffeb, 24}, - {0x7fffdf, 23}, - {0xffffec, 24}, - {0xffffed, 24}, - {0x3fffd7, 22}, - {0x7fffe0, 23}, - {0xffffee, 24}, - {0x7fffe1, 23}, - {0x7fffe2, 23}, - {0x7fffe3, 23}, - {0x7fffe4, 23}, - {0x1fffdc, 21}, - {0x3fffd8, 22}, - {0x7fffe5, 23}, - {0x3fffd9, 22}, - {0x7fffe6, 23}, - {0x7fffe7, 23}, - {0xffffef, 24}, - {0x3fffda, 22}, - {0x1fffdd, 21}, - {0xfffe9, 20}, - {0x3fffdb, 22}, - {0x3fffdc, 22}, - {0x7fffe8, 23}, - {0x7fffe9, 23}, - {0x1fffde, 21}, - {0x7fffea, 23}, - {0x3fffdd, 22}, - {0x3fffde, 22}, - {0xfffff0, 24}, - {0x1fffdf, 21}, - {0x3fffdf, 22}, - {0x7fffeb, 23}, - {0x7fffec, 23}, - {0x1fffe0, 21}, - {0x1fffe1, 21}, - {0x3fffe0, 22}, - {0x1fffe2, 21}, - {0x7fffed, 23}, - {0x3fffe1, 22}, - {0x7fffee, 23}, - {0x7fffef, 23}, - {0xfffea, 20}, - {0x3fffe2, 22}, - {0x3fffe3, 22}, - {0x3fffe4, 22}, - {0x7ffff0, 23}, - {0x3fffe5, 22}, - {0x3fffe6, 22}, - {0x7ffff1, 23}, - {0x3ffffe0, 26}, - {0x3ffffe1, 26}, - {0xfffeb, 20}, - {0x7fff1, 19}, - {0x3fffe7, 22}, - {0x7ffff2, 23}, - {0x3fffe8, 22}, - {0x1ffffec, 25}, - {0x3ffffe2, 26}, - {0x3ffffe3, 26}, - {0x3ffffe4, 26}, - {0x7ffffde, 27}, - {0x7ffffdf, 27}, - {0x3ffffe5, 26}, - {0xfffff1, 24}, - {0x1ffffed, 25}, - {0x7fff2, 19}, - {0x1fffe3, 21}, - {0x3ffffe6, 26}, - {0x7ffffe0, 27}, - {0x7ffffe1, 27}, - {0x3ffffe7, 26}, - {0x7ffffe2, 27}, - {0xfffff2, 24}, - {0x1fffe4, 21}, - {0x1fffe5, 21}, - {0x3ffffe8, 26}, - {0x3ffffe9, 26}, - {0xffffffd, 28}, - {0x7ffffe3, 27}, - {0x7ffffe4, 27}, - {0x7ffffe5, 27}, - {0xfffec, 20}, - {0xfffff3, 24}, - {0xfffed, 20}, - {0x1fffe6, 21}, - {0x3fffe9, 22}, - {0x1fffe7, 21}, - {0x1fffe8, 21}, - {0x7ffff3, 23}, - {0x3fffea, 22}, - {0x3fffeb, 22}, - {0x1ffffee, 25}, - {0x1ffffef, 25}, - {0xfffff4, 24}, - {0xfffff5, 24}, - {0x3ffffea, 26}, - {0x7ffff4, 23}, - {0x3ffffeb, 26}, - {0x7ffffe6, 27}, - {0x3ffffec, 26}, - {0x3ffffed, 26}, - {0x7ffffe7, 27}, - {0x7ffffe8, 27}, - {0x7ffffe9, 27}, - {0x7ffffea, 27}, - {0x7ffffeb, 27}, - {0xffffffe, 28}, - {0x7ffffec, 27}, - {0x7ffffed, 27}, - {0x7ffffee, 27}, - {0x7ffffef, 27}, - {0x7fffff0, 27}, - {0x3ffffee, 26}, - {0x3fffffff, 30}, -}; - /* represents a set of symbols as an array of booleans indicating inclusion */ -typedef struct { char included[NSYMS]; } symset; +typedef struct { char included[GRPC_CHTTP2_NUM_HUFFSYMS]; } symset; /* represents a lookup table indexed by a nibble */ typedef struct { int values[16]; } nibblelut; @@ -430,7 +164,7 @@ static nibblelut nibblelut_empty() { static int nsyms(symset s) { int i; int c = 0; - for (i = 0; i < NSYMS; i++) { + for (i = 0; i < GRPC_CHTTP2_NUM_HUFFSYMS; i++) { c += s.included[i] != 0; } return c; @@ -458,7 +192,8 @@ static int state_index(int bitofs, symset syms, int *isnew) { int i; for (i = 0; i < nhuffstates; i++) { if (huffstates[i].bitofs != bitofs) continue; - if (0 != memcmp(huffstates[i].syms.included, syms.included, NSYMS)) + if (0 != memcmp(huffstates[i].syms.included, syms.included, + GRPC_CHTTP2_NUM_HUFFSYMS)) continue; *isnew = 0; return i; @@ -510,12 +245,13 @@ static void build_dec_tbl(int state, int nibble, int nibbits, int bitofs, for (bit = 0; bit < 2; bit++) { /* walk over active symbols and see if they have this bit set */ symset nextsyms = symset_none(); - for (i = 0; i < NSYMS; i++) { + for (i = 0; i < GRPC_CHTTP2_NUM_HUFFSYMS; i++) { if (!syms.included[i]) continue; /* disregard inactive symbols */ - if (((huffsyms[i].bits >> (huffsyms[i].length - bitofs - 1)) & 1) == - bit) { + if (((grpc_chttp2_huffsyms[i].bits >> + (grpc_chttp2_huffsyms[i].length - bitofs - 1)) & + 1) == bit) { /* the bit is set, include it in the next recursive set */ - if (huffsyms[i].length == bitofs + 1) { + if (grpc_chttp2_huffsyms[i].length == bitofs + 1) { /* additionally, we've gotten to the end of a symbol - this is a special recursion step: re-activate all the symbols, reset bitofs to zero, and recurse */ @@ -581,9 +317,25 @@ static void generate_huff_tables() { dump_ctbl("emit_sub_tbl"); } +static void generate_base64_huff_encoder_table() { + static const char alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i; + + printf( + "static const struct { gpr_uint16 bits, gpr_uint8 length } " + "base64_syms[64] = {\n"); + for (i = 0; i < 64; i++) { + printf("{0x%x, %d},", grpc_chttp2_huffsyms[(unsigned char)alphabet[i]].bits, + grpc_chttp2_huffsyms[(unsigned char)alphabet[i]].length); + } + printf("};\n"); +} + int main(void) { generate_huff_tables(); generate_first_byte_lut(); + generate_base64_huff_encoder_table(); return 0; } diff --git a/src/core/transport/chttp2/huffsyms.c b/src/core/transport/chttp2/huffsyms.c new file mode 100644 index 0000000000..0f86f1bc30 --- /dev/null +++ b/src/core/transport/chttp2/huffsyms.c @@ -0,0 +1,297 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "src/core/transport/chttp2/huffsyms.h" + +/* Constants pulled from the HPACK spec, and converted to C using the vim + command: + :%s/.* \([0-9a-f]\+\) \[ *\([0-9]\+\)\]/{0x\1, \2},/g */ +const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS] = { + {0x1ff8, 13}, + {0x7fffd8, 23}, + {0xfffffe2, 28}, + {0xfffffe3, 28}, + {0xfffffe4, 28}, + {0xfffffe5, 28}, + {0xfffffe6, 28}, + {0xfffffe7, 28}, + {0xfffffe8, 28}, + {0xffffea, 24}, + {0x3ffffffc, 30}, + {0xfffffe9, 28}, + {0xfffffea, 28}, + {0x3ffffffd, 30}, + {0xfffffeb, 28}, + {0xfffffec, 28}, + {0xfffffed, 28}, + {0xfffffee, 28}, + {0xfffffef, 28}, + {0xffffff0, 28}, + {0xffffff1, 28}, + {0xffffff2, 28}, + {0x3ffffffe, 30}, + {0xffffff3, 28}, + {0xffffff4, 28}, + {0xffffff5, 28}, + {0xffffff6, 28}, + {0xffffff7, 28}, + {0xffffff8, 28}, + {0xffffff9, 28}, + {0xffffffa, 28}, + {0xffffffb, 28}, + {0x14, 6}, + {0x3f8, 10}, + {0x3f9, 10}, + {0xffa, 12}, + {0x1ff9, 13}, + {0x15, 6}, + {0xf8, 8}, + {0x7fa, 11}, + {0x3fa, 10}, + {0x3fb, 10}, + {0xf9, 8}, + {0x7fb, 11}, + {0xfa, 8}, + {0x16, 6}, + {0x17, 6}, + {0x18, 6}, + {0x0, 5}, + {0x1, 5}, + {0x2, 5}, + {0x19, 6}, + {0x1a, 6}, + {0x1b, 6}, + {0x1c, 6}, + {0x1d, 6}, + {0x1e, 6}, + {0x1f, 6}, + {0x5c, 7}, + {0xfb, 8}, + {0x7ffc, 15}, + {0x20, 6}, + {0xffb, 12}, + {0x3fc, 10}, + {0x1ffa, 13}, + {0x21, 6}, + {0x5d, 7}, + {0x5e, 7}, + {0x5f, 7}, + {0x60, 7}, + {0x61, 7}, + {0x62, 7}, + {0x63, 7}, + {0x64, 7}, + {0x65, 7}, + {0x66, 7}, + {0x67, 7}, + {0x68, 7}, + {0x69, 7}, + {0x6a, 7}, + {0x6b, 7}, + {0x6c, 7}, + {0x6d, 7}, + {0x6e, 7}, + {0x6f, 7}, + {0x70, 7}, + {0x71, 7}, + {0x72, 7}, + {0xfc, 8}, + {0x73, 7}, + {0xfd, 8}, + {0x1ffb, 13}, + {0x7fff0, 19}, + {0x1ffc, 13}, + {0x3ffc, 14}, + {0x22, 6}, + {0x7ffd, 15}, + {0x3, 5}, + {0x23, 6}, + {0x4, 5}, + {0x24, 6}, + {0x5, 5}, + {0x25, 6}, + {0x26, 6}, + {0x27, 6}, + {0x6, 5}, + {0x74, 7}, + {0x75, 7}, + {0x28, 6}, + {0x29, 6}, + {0x2a, 6}, + {0x7, 5}, + {0x2b, 6}, + {0x76, 7}, + {0x2c, 6}, + {0x8, 5}, + {0x9, 5}, + {0x2d, 6}, + {0x77, 7}, + {0x78, 7}, + {0x79, 7}, + {0x7a, 7}, + {0x7b, 7}, + {0x7ffe, 15}, + {0x7fc, 11}, + {0x3ffd, 14}, + {0x1ffd, 13}, + {0xffffffc, 28}, + {0xfffe6, 20}, + {0x3fffd2, 22}, + {0xfffe7, 20}, + {0xfffe8, 20}, + {0x3fffd3, 22}, + {0x3fffd4, 22}, + {0x3fffd5, 22}, + {0x7fffd9, 23}, + {0x3fffd6, 22}, + {0x7fffda, 23}, + {0x7fffdb, 23}, + {0x7fffdc, 23}, + {0x7fffdd, 23}, + {0x7fffde, 23}, + {0xffffeb, 24}, + {0x7fffdf, 23}, + {0xffffec, 24}, + {0xffffed, 24}, + {0x3fffd7, 22}, + {0x7fffe0, 23}, + {0xffffee, 24}, + {0x7fffe1, 23}, + {0x7fffe2, 23}, + {0x7fffe3, 23}, + {0x7fffe4, 23}, + {0x1fffdc, 21}, + {0x3fffd8, 22}, + {0x7fffe5, 23}, + {0x3fffd9, 22}, + {0x7fffe6, 23}, + {0x7fffe7, 23}, + {0xffffef, 24}, + {0x3fffda, 22}, + {0x1fffdd, 21}, + {0xfffe9, 20}, + {0x3fffdb, 22}, + {0x3fffdc, 22}, + {0x7fffe8, 23}, + {0x7fffe9, 23}, + {0x1fffde, 21}, + {0x7fffea, 23}, + {0x3fffdd, 22}, + {0x3fffde, 22}, + {0xfffff0, 24}, + {0x1fffdf, 21}, + {0x3fffdf, 22}, + {0x7fffeb, 23}, + {0x7fffec, 23}, + {0x1fffe0, 21}, + {0x1fffe1, 21}, + {0x3fffe0, 22}, + {0x1fffe2, 21}, + {0x7fffed, 23}, + {0x3fffe1, 22}, + {0x7fffee, 23}, + {0x7fffef, 23}, + {0xfffea, 20}, + {0x3fffe2, 22}, + {0x3fffe3, 22}, + {0x3fffe4, 22}, + {0x7ffff0, 23}, + {0x3fffe5, 22}, + {0x3fffe6, 22}, + {0x7ffff1, 23}, + {0x3ffffe0, 26}, + {0x3ffffe1, 26}, + {0xfffeb, 20}, + {0x7fff1, 19}, + {0x3fffe7, 22}, + {0x7ffff2, 23}, + {0x3fffe8, 22}, + {0x1ffffec, 25}, + {0x3ffffe2, 26}, + {0x3ffffe3, 26}, + {0x3ffffe4, 26}, + {0x7ffffde, 27}, + {0x7ffffdf, 27}, + {0x3ffffe5, 26}, + {0xfffff1, 24}, + {0x1ffffed, 25}, + {0x7fff2, 19}, + {0x1fffe3, 21}, + {0x3ffffe6, 26}, + {0x7ffffe0, 27}, + {0x7ffffe1, 27}, + {0x3ffffe7, 26}, + {0x7ffffe2, 27}, + {0xfffff2, 24}, + {0x1fffe4, 21}, + {0x1fffe5, 21}, + {0x3ffffe8, 26}, + {0x3ffffe9, 26}, + {0xffffffd, 28}, + {0x7ffffe3, 27}, + {0x7ffffe4, 27}, + {0x7ffffe5, 27}, + {0xfffec, 20}, + {0xfffff3, 24}, + {0xfffed, 20}, + {0x1fffe6, 21}, + {0x3fffe9, 22}, + {0x1fffe7, 21}, + {0x1fffe8, 21}, + {0x7ffff3, 23}, + {0x3fffea, 22}, + {0x3fffeb, 22}, + {0x1ffffee, 25}, + {0x1ffffef, 25}, + {0xfffff4, 24}, + {0xfffff5, 24}, + {0x3ffffea, 26}, + {0x7ffff4, 23}, + {0x3ffffeb, 26}, + {0x7ffffe6, 27}, + {0x3ffffec, 26}, + {0x3ffffed, 26}, + {0x7ffffe7, 27}, + {0x7ffffe8, 27}, + {0x7ffffe9, 27}, + {0x7ffffea, 27}, + {0x7ffffeb, 27}, + {0xffffffe, 28}, + {0x7ffffec, 27}, + {0x7ffffed, 27}, + {0x7ffffee, 27}, + {0x7ffffef, 27}, + {0x7fffff0, 27}, + {0x3ffffee, 26}, + {0x3fffffff, 30}, +}; diff --git a/src/core/transport/chttp2/huffsyms.h b/src/core/transport/chttp2/huffsyms.h new file mode 100644 index 0000000000..02d0e53fdd --- /dev/null +++ b/src/core/transport/chttp2/huffsyms.h @@ -0,0 +1,48 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_ +#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_ + +/* HPACK static huffman table */ + +#define GRPC_CHTTP2_NUM_HUFFSYMS 257 + +typedef struct { + unsigned bits; + unsigned length; +} grpc_chttp2_huffsym; + +extern const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS]; + +#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_ */ diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c index 8a6b427559..e6629ac74c 100644 --- a/src/core/transport/chttp2_transport.c +++ b/src/core/transport/chttp2_transport.c @@ -37,23 +37,24 @@ #include <stdio.h> #include <string.h> -#include <grpc/support/alloc.h> -#include <grpc/support/log.h> -#include <grpc/support/slice_buffer.h> -#include <grpc/support/string.h> -#include <grpc/support/useful.h> -#include "src/core/transport/transport_impl.h" -#include "src/core/transport/chttp2/http2_errors.h" -#include "src/core/transport/chttp2/hpack_parser.h" #include "src/core/transport/chttp2/frame_data.h" +#include "src/core/transport/chttp2/frame_goaway.h" #include "src/core/transport/chttp2/frame_ping.h" #include "src/core/transport/chttp2/frame_rst_stream.h" #include "src/core/transport/chttp2/frame_settings.h" #include "src/core/transport/chttp2/frame_window_update.h" +#include "src/core/transport/chttp2/hpack_parser.h" +#include "src/core/transport/chttp2/http2_errors.h" #include "src/core/transport/chttp2/status_conversion.h" #include "src/core/transport/chttp2/stream_encoder.h" #include "src/core/transport/chttp2/stream_map.h" #include "src/core/transport/chttp2/timeout_encoding.h" +#include "src/core/transport/transport_impl.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/slice_buffer.h> +#include <grpc/support/string.h> +#include <grpc/support/useful.h> #define DEFAULT_WINDOW 65536 #define MAX_WINDOW 0x7fffffffu @@ -160,6 +161,11 @@ typedef struct { void *user_data; } outstanding_ping; +typedef struct { + grpc_status_code status; + gpr_slice debug; +} pending_goaway; + struct transport { grpc_transport base; /* must be first */ const grpc_transport_callbacks *cb; @@ -180,6 +186,7 @@ struct transport { /* stream indexing */ gpr_uint32 next_stream_id; + gpr_uint32 last_incoming_stream_id; /* settings */ gpr_uint32 settings[NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS]; @@ -211,6 +218,12 @@ struct transport { grpc_chttp2_ping_parser ping; } simple_parsers; + /* goaway */ + grpc_chttp2_goaway_parser goaway_parser; + pending_goaway *pending_goaways; + size_t num_pending_goaways; + size_t cap_pending_goaways; + /* state for a stream that's not yet been created */ grpc_stream_op_buffer new_stream_sopb; @@ -310,6 +323,7 @@ static void unref_transport(transport *t) { gpr_slice_buffer_destroy(&t->qbuf); grpc_chttp2_hpack_parser_destroy(&t->hpack_parser); grpc_chttp2_hpack_compressor_destroy(&t->hpack_compressor); + grpc_chttp2_goaway_parser_destroy(&t->goaway_parser); grpc_mdstr_unref(t->str_grpc_timeout); @@ -332,6 +346,11 @@ static void unref_transport(transport *t) { } gpr_free(t->pings); + for (i = 0; i < t->num_pending_goaways; i++) { + gpr_slice_unref(t->pending_goaways[i].debug); + } + gpr_free(t->pending_goaways); + gpr_free(t); } @@ -360,6 +379,7 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, t->writing = 0; t->error_state = ERROR_STATE_NONE; t->next_stream_id = is_client ? 1 : 2; + t->last_incoming_stream_id = 0; t->is_client = is_client; t->outgoing_window = DEFAULT_WINDOW; t->incoming_window = DEFAULT_WINDOW; @@ -370,6 +390,10 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, t->ping_capacity = 0; t->ping_counter = gpr_now().tv_nsec; grpc_chttp2_hpack_compressor_init(&t->hpack_compressor, mdctx); + grpc_chttp2_goaway_parser_init(&t->goaway_parser); + t->pending_goaways = NULL; + t->num_pending_goaways = 0; + t->cap_pending_goaways = 0; gpr_slice_buffer_init(&t->outbuf); gpr_slice_buffer_init(&t->qbuf); if (is_client) { @@ -456,6 +480,16 @@ static void close_transport(grpc_transport *gt) { gpr_mu_unlock(&t->mu); } +static void goaway(grpc_transport *gt, grpc_status_code status, + gpr_slice debug_data) { + transport *t = (transport *)gt; + lock(t); + grpc_chttp2_goaway_append(t->last_incoming_stream_id, + grpc_chttp2_grpc_status_to_http2_error(status), + debug_data, &t->qbuf); + unlock(t); +} + static int init_stream(grpc_transport *gt, grpc_stream *gs, const void *server_data) { transport *t = (transport *)gt; @@ -609,6 +643,9 @@ static void unlock(transport *t) { int start_write = 0; int perform_callbacks = 0; int call_closed = 0; + int num_goaways = 0; + int i; + pending_goaway *goaways = NULL; grpc_endpoint *ep = t->ep; /* see if we need to trigger a write - and if so, get the data ready */ @@ -630,9 +667,16 @@ static void unlock(transport *t) { t->calling_back = 1; t->error_state = ERROR_STATE_NOTIFIED; } + if (t->num_pending_goaways) { + goaways = t->pending_goaways; + num_goaways = t->num_pending_goaways; + t->pending_goaways = NULL; + t->num_pending_goaways = 0; + t->calling_back = 1; + } } - if (perform_callbacks || call_closed) { + if (perform_callbacks || call_closed || num_goaways) { ref_transport(t); } @@ -640,6 +684,11 @@ static void unlock(transport *t) { gpr_mu_unlock(&t->mu); /* perform some callbacks if necessary */ + for (i = 0; i < num_goaways; i++) { + t->cb->goaway(t->cb_user_data, &t->base, goaways[i].status, + goaways[i].debug); + } + if (perform_callbacks) { run_callbacks(t); } @@ -698,13 +747,15 @@ static void unlock(transport *t) { } } - if (perform_callbacks || call_closed) { + if (perform_callbacks || call_closed || num_goaways) { lock(t); t->calling_back = 0; gpr_cv_broadcast(&t->cv); unlock(t); unref_transport(t); } + + gpr_free(goaways); } /* @@ -1130,6 +1181,12 @@ static int init_header_frame_parser(transport *t, int is_continuation) { gpr_log(GPR_ERROR, "ignoring new stream creation on client"); } return init_skip_frame(t, 1); + } else if (t->last_incoming_stream_id > t->incoming_stream_id) { + gpr_log(GPR_ERROR, + "ignoring out of order new stream request on server; last stream " + "id=%d, new stream id=%d", + t->last_incoming_stream_id, t->incoming_stream); + return init_skip_frame(t, 1); } t->incoming_stream = NULL; /* if stream is accepted, we set incoming_stream in init_stream */ @@ -1187,6 +1244,19 @@ static int init_ping_parser(transport *t) { return ok; } +static int init_goaway_parser(transport *t) { + int ok = + GRPC_CHTTP2_PARSE_OK == + grpc_chttp2_goaway_parser_begin_frame( + &t->goaway_parser, t->incoming_frame_size, t->incoming_frame_flags); + if (!ok) { + drop_connection(t); + } + t->parser = grpc_chttp2_goaway_parser_parse; + t->parser_data = &t->goaway_parser; + return ok; +} + static int init_settings_frame_parser(transport *t) { int ok = GRPC_CHTTP2_PARSE_OK == grpc_chttp2_settings_parser_begin_frame( @@ -1240,6 +1310,8 @@ static int init_frame_parser(transport *t) { return init_window_update_frame_parser(t); case GRPC_CHTTP2_FRAME_PING: return init_ping_parser(t); + case GRPC_CHTTP2_FRAME_GOAWAY: + return init_goaway_parser(t); default: gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type); return init_skip_frame(t, 0); @@ -1277,6 +1349,18 @@ static int parse_frame_slice(transport *t, gpr_slice slice, int is_last) { &t->qbuf, grpc_chttp2_ping_create(1, t->simple_parsers.ping.opaque_8bytes)); } + if (st.goaway) { + if (t->num_pending_goaways == t->cap_pending_goaways) { + t->cap_pending_goaways = GPR_MAX(1, t->cap_pending_goaways * 2); + t->pending_goaways = + gpr_realloc(t->pending_goaways, + sizeof(pending_goaway) * t->cap_pending_goaways); + } + t->pending_goaways[t->num_pending_goaways].status = + grpc_chttp2_http2_error_to_grpc_status(st.goaway_error); + t->pending_goaways[t->num_pending_goaways].debug = st.goaway_text; + t->num_pending_goaways++; + } if (st.process_ping_reply) { for (i = 0; i < t->ping_count; i++) { if (0 == @@ -1455,6 +1539,7 @@ static int process_read(transport *t, gpr_slice slice) { if (!init_frame_parser(t)) { return 0; } + t->last_incoming_stream_id = t->incoming_stream_id; if (t->incoming_frame_size == 0) { if (!parse_frame_slice(t, gpr_empty_slice(), 1)) { return 0; @@ -1599,7 +1684,7 @@ static void run_callbacks(transport *t) { static const grpc_transport_vtable vtable = { sizeof(stream), init_stream, send_batch, set_allow_window_updates, - destroy_stream, abort_stream, close_transport, send_ping, + destroy_stream, abort_stream, goaway, close_transport, send_ping, destroy_transport}; void grpc_create_chttp2_transport(grpc_transport_setup_callback setup, diff --git a/src/core/transport/transport.c b/src/core/transport/transport.c index d3291bbbb9..1c44abcadf 100644 --- a/src/core/transport/transport.c +++ b/src/core/transport/transport.c @@ -38,6 +38,11 @@ size_t grpc_transport_stream_size(grpc_transport *transport) { return transport->vtable->sizeof_stream; } +void grpc_transport_goaway(grpc_transport *transport, grpc_status_code status, + gpr_slice message) { + transport->vtable->goaway(transport, status, message); +} + void grpc_transport_close(grpc_transport *transport) { transport->vtable->close(transport); } diff --git a/src/core/transport/transport.h b/src/core/transport/transport.h index 1872947208..6a089a2a15 100644 --- a/src/core/transport/transport.h +++ b/src/core/transport/transport.h @@ -116,6 +116,10 @@ struct grpc_transport_callbacks { grpc_stream *stream, grpc_stream_op *ops, size_t ops_count, grpc_stream_state final_state); + /* The transport received a goaway */ + void (*goaway)(void *user_data, grpc_transport *transport, + grpc_status_code status, gpr_slice debug); + /* The transport has been closed */ void (*closed)(void *user_data, grpc_transport *transport); }; @@ -198,6 +202,10 @@ void grpc_transport_ping(grpc_transport *transport, void (*cb)(void *user_data), void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream, grpc_status_code status); +/* Advise peer of pending connection termination. */ +void grpc_transport_goaway(struct grpc_transport *transport, + grpc_status_code status, gpr_slice debug_data); + /* Close a transport. Aborts all open streams. */ void grpc_transport_close(struct grpc_transport *transport); diff --git a/src/core/transport/transport_impl.h b/src/core/transport/transport_impl.h index 6acdbf2525..328ead2872 100644 --- a/src/core/transport/transport_impl.h +++ b/src/core/transport/transport_impl.h @@ -60,6 +60,10 @@ typedef struct grpc_transport_vtable { void (*abort_stream)(grpc_transport *self, grpc_stream *stream, grpc_status_code status); + /* implementation of grpc_transport_goaway */ + void (*goaway)(grpc_transport *self, grpc_status_code status, + gpr_slice debug_data); + /* implementation of grpc_transport_close */ void (*close)(grpc_transport *self); diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c index 7bd178b372..2c74b3635a 100644 --- a/src/core/tsi/ssl_transport_security.c +++ b/src/core/tsi/ssl_transport_security.c @@ -289,6 +289,17 @@ static tsi_result peer_from_x509(X509* cert, int include_certificate_type, return result; } +/* Logs the SSL error stack. */ +static void log_ssl_error_stack(void) { + unsigned long err; + while ((err = ERR_get_error()) != 0) { + char details[256]; + ERR_error_string_n(err, details, sizeof(details)); + gpr_log(GPR_ERROR, "%s", details); + } +} + + /* Performs an SSL_read and handle errors. */ static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes, uint32_t* unprotected_bytes_size) { @@ -312,6 +323,7 @@ static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes, return TSI_UNIMPLEMENTED; case SSL_ERROR_SSL: gpr_log(GPR_ERROR, "Corruption detected."); + log_ssl_error_stack(); return TSI_DATA_CORRUPTED; default: gpr_log(GPR_ERROR, "SSL_read failed with error %s.", @@ -364,7 +376,10 @@ static tsi_result ssl_ctx_use_certificate_chain( } while (1) { X509* certificate_authority = PEM_read_bio_X509(pem, NULL, NULL, ""); - if (certificate_authority == NULL) break; /* Done reading. */ + if (certificate_authority == NULL) { + ERR_clear_error(); + break; /* Done reading. */ + } if (!SSL_CTX_add_extra_chain_cert(context, certificate_authority)) { X509_free(certificate_authority); result = TSI_INVALID_ARGUMENT; @@ -425,7 +440,10 @@ static tsi_result ssl_ctx_load_verification_certs( while (1) { root = PEM_read_bio_X509_AUX(pem, NULL, NULL, ""); - if (root == NULL) break; /* We're at the end of stream. */ + if (root == NULL) { + ERR_clear_error(); + break; /* We're at the end of stream. */ + } if (root_names != NULL) { root_name = X509_get_subject_name(root); if (root_name == NULL) { diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc index 7a7529104f..3792869d83 100644 --- a/src/cpp/client/channel.cc +++ b/src/cpp/client/channel.cc @@ -59,67 +59,19 @@ Channel::Channel(const grpc::string& target) : target_(target) { Channel::~Channel() { grpc_channel_destroy(c_channel_); } namespace { -// Poll one event from the compeletion queue. Return false when an error -// occured or the polled type is not expected. If a finished event has been -// polled, set finished and set status if it has not been set. -bool NextEvent(grpc_completion_queue* cq, grpc_completion_type expected_type, - bool* finished, bool* status_set, Status* status, - google::protobuf::Message* result) { - // We rely on the c layer to enforce deadline and thus do not use deadline - // here. - grpc_event* ev = grpc_completion_queue_next(cq, gpr_inf_future); - if (!ev) { - return false; - } - bool ret = ev->type == expected_type; - switch (ev->type) { - case GRPC_INVOKE_ACCEPTED: - ret = ret && (ev->data.invoke_accepted == GRPC_OP_OK); - break; - case GRPC_READ: - ret = ret && (ev->data.read != nullptr); - if (ret && !DeserializeProto(ev->data.read, result)) { - *status_set = true; - *status = - Status(StatusCode::DATA_LOSS, "Failed to parse response proto."); - ret = false; - } - break; - case GRPC_WRITE_ACCEPTED: - ret = ret && (ev->data.write_accepted == GRPC_OP_OK); - break; - case GRPC_FINISH_ACCEPTED: - ret = ret && (ev->data.finish_accepted == GRPC_OP_OK); - break; - case GRPC_CLIENT_METADATA_READ: - break; - case GRPC_FINISHED: - *finished = true; - if (!*status_set) { - *status_set = true; - StatusCode error_code = static_cast<StatusCode>(ev->data.finished.code); - grpc::string details( - ev->data.finished.details ? ev->data.finished.details : ""); - *status = Status(error_code, details); - } - break; - default: - gpr_log(GPR_ERROR, "Dropping unhandled event with type %d", ev->type); - break; - } - grpc_event_finish(ev); - return ret; -} - -// If finished is not true, get final status by polling until a finished -// event is obtained. -void GetFinalStatus(grpc_completion_queue* cq, bool status_set, bool finished, +// Pluck the finished event and set to status when it is not nullptr. +void GetFinalStatus(grpc_completion_queue* cq, void* finished_tag, Status* status) { - while (!finished) { - NextEvent(cq, GRPC_FINISHED, &finished, &status_set, status, nullptr); + grpc_event* ev = + grpc_completion_queue_pluck(cq, finished_tag, gpr_inf_future); + if (status) { + StatusCode error_code = static_cast<StatusCode>(ev->data.finished.code); + grpc::string details(ev->data.finished.details ? ev->data.finished.details + : ""); + *status = Status(error_code, details); } + grpc_event_finish(ev); } - } // namespace // TODO(yangg) more error handling @@ -128,8 +80,6 @@ Status Channel::StartBlockingRpc(const RpcMethod& method, const google::protobuf::Message& request, google::protobuf::Message* result) { Status status; - bool status_set = false; - bool finished = false; gpr_timespec absolute_deadline; AbsoluteDeadlineTimepoint2Timespec(context->absolute_deadline(), &absolute_deadline); @@ -137,59 +87,68 @@ Status Channel::StartBlockingRpc(const RpcMethod& method, // FIXME(yangg) "localhost", absolute_deadline); context->set_call(call); + grpc_event* ev; + void* finished_tag = reinterpret_cast<char*>(call); + void* invoke_tag = reinterpret_cast<char*>(call) + 1; + void* metadata_read_tag = reinterpret_cast<char*>(call) + 2; + void* write_tag = reinterpret_cast<char*>(call) + 3; + void* halfclose_tag = reinterpret_cast<char*>(call) + 4; + void* read_tag = reinterpret_cast<char*>(call) + 5; + grpc_completion_queue* cq = grpc_completion_queue_create(); context->set_cq(cq); // add_metadata from context // // invoke - GPR_ASSERT(grpc_call_start_invoke(call, cq, call, call, call, + GPR_ASSERT(grpc_call_start_invoke(call, cq, invoke_tag, metadata_read_tag, + finished_tag, GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK); - if (!NextEvent(cq, GRPC_INVOKE_ACCEPTED, &status_set, &finished, &status, - nullptr)) { - GetFinalStatus(cq, finished, status_set, &status); - return status; - } + ev = grpc_completion_queue_pluck(cq, invoke_tag, gpr_inf_future); + grpc_event_finish(ev); // write request grpc_byte_buffer* write_buffer = nullptr; bool success = SerializeProto(request, &write_buffer); if (!success) { grpc_call_cancel(call); - status_set = true; status = Status(StatusCode::DATA_LOSS, "Failed to serialize request proto."); - GetFinalStatus(cq, finished, status_set, &status); + GetFinalStatus(cq, finished_tag, nullptr); return status; } - GPR_ASSERT(grpc_call_start_write(call, write_buffer, call, + GPR_ASSERT(grpc_call_start_write(call, write_buffer, write_tag, GRPC_WRITE_BUFFER_HINT) == GRPC_CALL_OK); grpc_byte_buffer_destroy(write_buffer); - if (!NextEvent(cq, GRPC_WRITE_ACCEPTED, &finished, &status_set, &status, - nullptr)) { - GetFinalStatus(cq, finished, status_set, &status); + ev = grpc_completion_queue_pluck(cq, write_tag, gpr_inf_future); + + success = ev->data.write_accepted == GRPC_OP_OK; + grpc_event_finish(ev); + if (!success) { + GetFinalStatus(cq, finished_tag, &status); return status; } // writes done - GPR_ASSERT(grpc_call_writes_done(call, call) == GRPC_CALL_OK); - if (!NextEvent(cq, GRPC_FINISH_ACCEPTED, &finished, &status_set, &status, - nullptr)) { - GetFinalStatus(cq, finished, status_set, &status); - return status; - } + GPR_ASSERT(grpc_call_writes_done(call, halfclose_tag) == GRPC_CALL_OK); + ev = grpc_completion_queue_pluck(cq, halfclose_tag, gpr_inf_future); + grpc_event_finish(ev); // start read metadata // - if (!NextEvent(cq, GRPC_CLIENT_METADATA_READ, &finished, &status_set, &status, - nullptr)) { - GetFinalStatus(cq, finished, status_set, &status); - return status; - } + ev = grpc_completion_queue_pluck(cq, metadata_read_tag, gpr_inf_future); + grpc_event_finish(ev); // start read - GPR_ASSERT(grpc_call_start_read(call, call) == GRPC_CALL_OK); - if (!NextEvent(cq, GRPC_READ, &finished, &status_set, &status, result)) { - GetFinalStatus(cq, finished, status_set, &status); - return status; + GPR_ASSERT(grpc_call_start_read(call, read_tag) == GRPC_CALL_OK); + ev = grpc_completion_queue_pluck(cq, read_tag, gpr_inf_future); + if (ev->data.read) { + if (!DeserializeProto(ev->data.read, result)) { + grpc_event_finish(ev); + status = Status(StatusCode::DATA_LOSS, "Failed to parse response proto."); + GetFinalStatus(cq, finished_tag, nullptr); + return status; + } } + grpc_event_finish(ev); + // wait status - GetFinalStatus(cq, finished, status_set, &status); + GetFinalStatus(cq, finished_tag, &status); return status; } diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc index 78774a7f12..58a8ad252b 100644 --- a/src/cpp/client/client_context.cc +++ b/src/cpp/client/client_context.cc @@ -50,6 +50,14 @@ ClientContext::~ClientContext() { } if (cq_) { grpc_completion_queue_shutdown(cq_); + // Drain cq_. + grpc_event* ev; + grpc_completion_type t; + do { + ev = grpc_completion_queue_next(cq_, gpr_inf_future); + t = ev->type; + grpc_event_finish(ev); + } while (t != GRPC_QUEUE_SHUTDOWN); grpc_completion_queue_destroy(cq_); } } diff --git a/src/cpp/server/async_server_context.cc b/src/cpp/server/async_server_context.cc index b231f4b0cf..0a9c07f403 100644 --- a/src/cpp/server/async_server_context.cc +++ b/src/cpp/server/async_server_context.cc @@ -75,18 +75,11 @@ bool AsyncServerContext::StartWrite(const google::protobuf::Message& response, return err == GRPC_CALL_OK; } -namespace { -grpc_status TranslateStatus(const Status& status) { - grpc_status c_status; - // TODO(yangg) - c_status.code = GRPC_STATUS_OK; - c_status.details = nullptr; - return c_status; -} -} // namespace - bool AsyncServerContext::StartWriteStatus(const Status& status) { - grpc_status c_status = TranslateStatus(status); + grpc_status c_status = {static_cast<grpc_status_code>(status.code()), + status.details().empty() + ? nullptr + : const_cast<char*>(status.details().c_str())}; grpc_call_error err = grpc_call_start_write_status(call_, c_status, this); return err == GRPC_CALL_OK; } diff --git a/src/cpp/server/rpc_service_method.h b/src/cpp/server/rpc_service_method.h index ac2badda71..425545fd22 100644 --- a/src/cpp/server/rpc_service_method.h +++ b/src/cpp/server/rpc_service_method.h @@ -42,8 +42,10 @@ #include "src/cpp/rpc_method.h" #include <google/protobuf/message.h> #include <grpc++/status.h> +#include <grpc++/stream.h> namespace grpc { +class StreamContextInterface; // TODO(rocking): we might need to split this file into multiple ones. @@ -53,23 +55,27 @@ class MethodHandler { virtual ~MethodHandler() {} struct HandlerParameter { HandlerParameter(const google::protobuf::Message* req, google::protobuf::Message* resp) - : request(req), response(resp) {} + : request(req), response(resp), stream_context(nullptr) {} + HandlerParameter(const google::protobuf::Message* req, google::protobuf::Message* resp, + StreamContextInterface* context) + : request(req), response(resp), stream_context(context) {} const google::protobuf::Message* request; google::protobuf::Message* response; + StreamContextInterface* stream_context; }; - virtual ::grpc::Status RunHandler(const HandlerParameter& param) = 0; + virtual Status RunHandler(const HandlerParameter& param) = 0; }; // A wrapper class of an application provided rpc method handler. template <class ServiceType, class RequestType, class ResponseType> class RpcMethodHandler : public MethodHandler { public: - RpcMethodHandler(std::function<::grpc::Status( - ServiceType*, const RequestType*, ResponseType*)> func, + RpcMethodHandler(std::function<Status(ServiceType*, const RequestType*, + ResponseType*)> func, ServiceType* service) : func_(func), service_(service) {} - ::grpc::Status RunHandler(const HandlerParameter& param) final { + Status RunHandler(const HandlerParameter& param) final { // Invoke application function, cast proto messages to their actual types. return func_(service_, dynamic_cast<const RequestType*>(param.request), dynamic_cast<ResponseType*>(param.response)); @@ -77,20 +83,84 @@ class RpcMethodHandler : public MethodHandler { private: // Application provided rpc handler function. - std::function<::grpc::Status(ServiceType*, const RequestType*, ResponseType*)> - func_; + std::function<Status(ServiceType*, const RequestType*, ResponseType*)> func_; // The class the above handler function lives in. ServiceType* service_; }; +// A wrapper class of an application provided client streaming handler. +template <class ServiceType, class RequestType, class ResponseType> +class ClientStreamingHandler : public MethodHandler { + public: + ClientStreamingHandler( + std::function<Status(ServiceType*, ServerReader<RequestType>*, + ResponseType*)> func, + ServiceType* service) + : func_(func), service_(service) {} + + Status RunHandler(const HandlerParameter& param) final { + ServerReader<RequestType> reader(param.stream_context); + return func_(service_, &reader, + dynamic_cast<ResponseType*>(param.response)); + } + + private: + std::function<Status(ServiceType*, ServerReader<RequestType>*, ResponseType*)> + func_; + ServiceType* service_; +}; + +// A wrapper class of an application provided server streaming handler. +template <class ServiceType, class RequestType, class ResponseType> +class ServerStreamingHandler : public MethodHandler { + public: + ServerStreamingHandler( + std::function<Status(ServiceType*, const RequestType*, + ServerWriter<ResponseType>*)> func, + ServiceType* service) + : func_(func), service_(service) {} + + Status RunHandler(const HandlerParameter& param) final { + ServerWriter<ResponseType> writer(param.stream_context); + return func_(service_, dynamic_cast<const RequestType*>(param.request), + &writer); + } + + private: + std::function<Status(ServiceType*, const RequestType*, + ServerWriter<ResponseType>*)> func_; + ServiceType* service_; +}; + +// A wrapper class of an application provided bidi-streaming handler. +template <class ServiceType, class RequestType, class ResponseType> +class BidiStreamingHandler : public MethodHandler { + public: + BidiStreamingHandler( + std::function<Status( + ServiceType*, ServerReaderWriter<ResponseType, RequestType>*)> func, + ServiceType* service) + : func_(func), service_(service) {} + + Status RunHandler(const HandlerParameter& param) final { + ServerReaderWriter<ResponseType, RequestType> stream(param.stream_context); + return func_(service_, &stream); + } + + private: + std::function<Status(ServiceType*, + ServerReaderWriter<ResponseType, RequestType>*)> func_; + ServiceType* service_; +}; + // Server side rpc method class class RpcServiceMethod : public RpcMethod { public: // Takes ownership of the handler and two prototype objects. - RpcServiceMethod(const char* name, MethodHandler* handler, - google::protobuf::Message* request_prototype, + RpcServiceMethod(const char* name, RpcMethod::RpcType type, + MethodHandler* handler, google::protobuf::Message* request_prototype, google::protobuf::Message* response_prototype) - : RpcMethod(name), + : RpcMethod(name, type), handler_(handler), request_prototype_(request_prototype), response_prototype_(response_prototype) {} diff --git a/src/cpp/server/server_credentials.cc b/src/cpp/server/server_credentials.cc new file mode 100644 index 0000000000..d23a09f3c1 --- /dev/null +++ b/src/cpp/server/server_credentials.cc @@ -0,0 +1,62 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 <grpc/grpc_security.h> + +#include <grpc++/server_credentials.h> + +namespace grpc { + +ServerCredentials::ServerCredentials(grpc_server_credentials* c_creds) + : creds_(c_creds) {} + +ServerCredentials::~ServerCredentials() { + grpc_server_credentials_release(creds_); +} + +grpc_server_credentials* ServerCredentials::GetRawCreds() { return creds_; } + +std::shared_ptr<ServerCredentials> ServerCredentialsFactory::SslCredentials( + const SslServerCredentialsOptions& options) { + grpc_server_credentials* c_creds = grpc_ssl_server_credentials_create( + reinterpret_cast<const unsigned char*>(options.pem_root_certs.c_str()), + options.pem_root_certs.size(), + reinterpret_cast<const unsigned char*>(options.pem_private_key.c_str()), + options.pem_private_key.size(), + reinterpret_cast<const unsigned char*>(options.pem_cert_chain.c_str()), + options.pem_cert_chain.size()); + return std::shared_ptr<ServerCredentials>(new ServerCredentials(c_creds)); +} + +} // namespace grpc diff --git a/src/cpp/server/server_rpc_handler.cc b/src/cpp/server/server_rpc_handler.cc index 2d5a081deb..4c8d0cd04e 100644 --- a/src/cpp/server/server_rpc_handler.cc +++ b/src/cpp/server/server_rpc_handler.cc @@ -35,20 +35,16 @@ #include <grpc/support/log.h> #include "src/cpp/server/rpc_service_method.h" +#include "src/cpp/stream/stream_context.h" #include <grpc++/async_server_context.h> namespace grpc { ServerRpcHandler::ServerRpcHandler(AsyncServerContext* server_context, RpcServiceMethod* method) - : server_context_(server_context), - method_(method) { -} + : server_context_(server_context), method_(method) {} void ServerRpcHandler::StartRpc() { - // Start the rpc on this dedicated completion queue. - server_context_->Accept(cq_.cq()); - if (method_ == nullptr) { // Method not supported, finish the rpc with error. // TODO(rocking): do we need to call read to consume the request? @@ -56,30 +52,54 @@ void ServerRpcHandler::StartRpc() { return; } - // Allocate request and response. - std::unique_ptr<google::protobuf::Message> request(method_->AllocateRequestProto()); - std::unique_ptr<google::protobuf::Message> response(method_->AllocateResponseProto()); - - // Read request - server_context_->StartRead(request.get()); - auto type = WaitForNextEvent(); - GPR_ASSERT(type == CompletionQueue::SERVER_READ_OK); - - // Run the application's rpc handler - MethodHandler* handler = method_->handler(); - Status status = handler->RunHandler( - MethodHandler::HandlerParameter(request.get(), response.get())); - - if (status.IsOk()) { - // Send the response if we get an ok status. - server_context_->StartWrite(*response, 0); - type = WaitForNextEvent(); - if (type != CompletionQueue::SERVER_WRITE_OK) { - status = Status(StatusCode::INTERNAL, "Error writing response."); + if (method_->method_type() == RpcMethod::NORMAL_RPC) { + // Start the rpc on this dedicated completion queue. + server_context_->Accept(cq_.cq()); + + // Allocate request and response. + std::unique_ptr<google::protobuf::Message> request(method_->AllocateRequestProto()); + std::unique_ptr<google::protobuf::Message> response(method_->AllocateResponseProto()); + + // Read request + server_context_->StartRead(request.get()); + auto type = WaitForNextEvent(); + GPR_ASSERT(type == CompletionQueue::SERVER_READ_OK); + + // Run the application's rpc handler + MethodHandler* handler = method_->handler(); + Status status = handler->RunHandler( + MethodHandler::HandlerParameter(request.get(), response.get())); + + if (status.IsOk()) { + // Send the response if we get an ok status. + server_context_->StartWrite(*response, 0); + type = WaitForNextEvent(); + if (type != CompletionQueue::SERVER_WRITE_OK) { + status = Status(StatusCode::INTERNAL, "Error writing response."); + } } - } - FinishRpc(status); + FinishRpc(status); + } else { + // Allocate request and response. + // TODO(yangg) maybe not allocate both when not needed? + std::unique_ptr<google::protobuf::Message> request(method_->AllocateRequestProto()); + std::unique_ptr<google::protobuf::Message> response(method_->AllocateResponseProto()); + + StreamContext stream_context(*method_, server_context_->call(), cq_.cq(), + request.get(), response.get()); + + // Run the application's rpc handler + MethodHandler* handler = method_->handler(); + Status status = handler->RunHandler(MethodHandler::HandlerParameter( + request.get(), response.get(), &stream_context)); + if (status.IsOk() && + method_->method_type() == RpcMethod::CLIENT_STREAMING) { + stream_context.Write(response.get(), false); + } + // TODO(yangg) Do we need to consider the status in stream_context? + FinishRpc(status); + } } CompletionQueue::CompletionType ServerRpcHandler::WaitForNextEvent() { @@ -94,11 +114,15 @@ CompletionQueue::CompletionType ServerRpcHandler::WaitForNextEvent() { void ServerRpcHandler::FinishRpc(const Status& status) { server_context_->StartWriteStatus(status); - CompletionQueue::CompletionType type = WaitForNextEvent(); - // TODO(rocking): do we care about this return type? + CompletionQueue::CompletionType type; + // HALFCLOSE_OK and RPC_END events come in either order. + type = WaitForNextEvent(); + GPR_ASSERT(type == CompletionQueue::HALFCLOSE_OK || + type == CompletionQueue::RPC_END); type = WaitForNextEvent(); - GPR_ASSERT(type == CompletionQueue::RPC_END); + GPR_ASSERT(type == CompletionQueue::HALFCLOSE_OK || + type == CompletionQueue::RPC_END); cq_.Shutdown(); type = WaitForNextEvent(); diff --git a/src/cpp/stream/stream_context.cc b/src/cpp/stream/stream_context.cc index 07e771f7e1..706e90c481 100644 --- a/src/cpp/stream/stream_context.cc +++ b/src/cpp/stream/stream_context.cc @@ -33,7 +33,6 @@ #include "src/cpp/stream/stream_context.h" -#include <grpc/grpc.h> #include <grpc/support/log.h> #include "src/cpp/rpc_method.h" #include "src/cpp/proto/proto_utils.h" @@ -50,227 +49,146 @@ StreamContext::StreamContext(const RpcMethod& method, ClientContext* context, google::protobuf::Message* result) : is_client_(true), method_(&method), - context_(context), - request_(request), + call_(context->call()), + cq_(context->cq()), + request_(const_cast<google::protobuf::Message*>(request)), result_(result), - invoke_ev_(nullptr), - read_ev_(nullptr), - write_ev_(nullptr), - reading_(false), - writing_(false), - got_read_(false), - got_write_(false), peer_halfclosed_(false), - self_halfclosed_(false), - stream_finished_(false), - waiting_(false) { + self_halfclosed_(false) { GPR_ASSERT(method_->method_type() != RpcMethod::RpcType::NORMAL_RPC); } -StreamContext::~StreamContext() { cq_poller_.join(); } - -void StreamContext::PollingLoop() { - grpc_event* ev = nullptr; - gpr_timespec absolute_deadline; - AbsoluteDeadlineTimepoint2Timespec(context_->absolute_deadline(), - &absolute_deadline); - std::condition_variable* cv_to_notify = nullptr; - std::unique_lock<std::mutex> lock(mu_, std::defer_lock); - while (1) { - cv_to_notify = nullptr; - lock.lock(); - if (stream_finished_ && !reading_ && !writing_) { - return; - } - lock.unlock(); - ev = grpc_completion_queue_next(context_->cq(), absolute_deadline); - lock.lock(); - if (!ev) { - stream_finished_ = true; - final_status_ = Status(StatusCode::DEADLINE_EXCEEDED); - std::condition_variable* cvs[3] = {reading_ ? &read_cv_ : nullptr, - writing_ ? &write_cv_ : nullptr, - waiting_ ? &finish_cv_ : nullptr}; - got_read_ = reading_; - got_write_ = writing_; - read_ev_ = nullptr; - write_ev_ = nullptr; - lock.unlock(); - for (int i = 0; i < 3; i++) { - if (cvs[i]) cvs[i]->notify_one(); - } - break; - } - switch (ev->type) { - case GRPC_READ: - GPR_ASSERT(reading_); - got_read_ = true; - read_ev_ = ev; - cv_to_notify = &read_cv_; - reading_ = false; - break; - case GRPC_FINISH_ACCEPTED: - case GRPC_WRITE_ACCEPTED: - got_write_ = true; - write_ev_ = ev; - cv_to_notify = &write_cv_; - writing_ = false; - break; - case GRPC_FINISHED: { - grpc::string error_details( - ev->data.finished.details ? ev->data.finished.details : ""); - final_status_ = Status(static_cast<StatusCode>(ev->data.finished.code), - error_details); - grpc_event_finish(ev); - stream_finished_ = true; - if (waiting_) { - cv_to_notify = &finish_cv_; - } - break; - } - case GRPC_INVOKE_ACCEPTED: - invoke_ev_ = ev; - cv_to_notify = &invoke_cv_; - break; - case GRPC_CLIENT_METADATA_READ: - grpc_event_finish(ev); - break; - default: - grpc_event_finish(ev); - // not handling other types now - gpr_log(GPR_ERROR, "unknown event type"); - abort(); - } - lock.unlock(); - if (cv_to_notify) { - cv_to_notify->notify_one(); - } - } +// Server only ctor +StreamContext::StreamContext(const RpcMethod& method, grpc_call* call, + grpc_completion_queue* cq, + google::protobuf::Message* request, google::protobuf::Message* result) + : is_client_(false), + method_(&method), + call_(call), + cq_(cq), + request_(request), + result_(result), + peer_halfclosed_(false), + self_halfclosed_(false) { + GPR_ASSERT(method_->method_type() != RpcMethod::RpcType::NORMAL_RPC); } -void StreamContext::Start(bool buffered) { - // TODO(yangg) handle metadata send path - int flag = buffered ? GRPC_WRITE_BUFFER_HINT : 0; - grpc_call_error error = grpc_call_start_invoke( - context_->call(), context_->cq(), this, this, this, flag); - GPR_ASSERT(GRPC_CALL_OK == error); - // kicks off the poller thread - cq_poller_ = std::thread(&StreamContext::PollingLoop, this); - std::unique_lock<std::mutex> lock(mu_); - while (!invoke_ev_) { - invoke_cv_.wait(lock); - } - lock.unlock(); - GPR_ASSERT(invoke_ev_->data.invoke_accepted == GRPC_OP_OK); - grpc_event_finish(invoke_ev_); -} +StreamContext::~StreamContext() {} -namespace { -// Wait for got_event with event_cv protected by mu, return event. -grpc_event* WaitForEvent(bool* got_event, std::condition_variable* event_cv, - std::mutex* mu, grpc_event** event) { - std::unique_lock<std::mutex> lock(*mu); - while (*got_event == false) { - event_cv->wait(lock); +void StreamContext::Start(bool buffered) { + if (is_client_) { + // TODO(yangg) handle metadata send path + int flag = buffered ? GRPC_WRITE_BUFFER_HINT : 0; + grpc_call_error error = grpc_call_start_invoke(call(), cq(), invoke_tag(), + client_metadata_read_tag(), + finished_tag(), flag); + GPR_ASSERT(GRPC_CALL_OK == error); + grpc_event* invoke_ev = + grpc_completion_queue_pluck(cq(), invoke_tag(), gpr_inf_future); + grpc_event_finish(invoke_ev); + } else { + // TODO(yangg) metadata needs to be added before accept + // TODO(yangg) correctly set flag to accept + grpc_call_error error = grpc_call_accept(call(), cq(), finished_tag(), 0); + GPR_ASSERT(GRPC_CALL_OK == error); } - *got_event = false; - return *event; } -} // namespace bool StreamContext::Read(google::protobuf::Message* msg) { - std::unique_lock<std::mutex> lock(mu_); - if (stream_finished_) { - peer_halfclosed_ = true; - return false; - } - reading_ = true; - lock.unlock(); - - grpc_call_error err = grpc_call_start_read(context_->call(), this); + // TODO(yangg) check peer_halfclosed_ here for possible early return. + grpc_call_error err = grpc_call_start_read(call(), read_tag()); GPR_ASSERT(err == GRPC_CALL_OK); - - grpc_event* ev = WaitForEvent(&got_read_, &read_cv_, &mu_, &read_ev_); - if (!ev) { - return false; - } - GPR_ASSERT(ev->type == GRPC_READ); + grpc_event* read_ev = + grpc_completion_queue_pluck(cq(), read_tag(), gpr_inf_future); + GPR_ASSERT(read_ev->type == GRPC_READ); bool ret = true; - if (ev->data.read) { - if (!DeserializeProto(ev->data.read, msg)) { - ret = false; // parse error - // TODO(yangg) cancel the stream. + if (read_ev->data.read) { + if (!DeserializeProto(read_ev->data.read, msg)) { + ret = false; + FinishStream( + Status(StatusCode::DATA_LOSS, "Failed to parse incoming proto"), + true); } } else { ret = false; peer_halfclosed_ = true; } - grpc_event_finish(ev); + grpc_event_finish(read_ev); return ret; } bool StreamContext::Write(const google::protobuf::Message* msg, bool is_last) { + // TODO(yangg) check self_halfclosed_ for possible early return. bool ret = true; grpc_event* ev = nullptr; - std::unique_lock<std::mutex> lock(mu_); - if (stream_finished_) { - self_halfclosed_ = true; - return false; - } - writing_ = true; - lock.unlock(); - if (msg) { grpc_byte_buffer* out_buf = nullptr; if (!SerializeProto(*msg, &out_buf)) { FinishStream(Status(StatusCode::INVALID_ARGUMENT, - "Failed to serialize request proto"), + "Failed to serialize outgoing proto"), true); return false; } int flag = is_last ? GRPC_WRITE_BUFFER_HINT : 0; grpc_call_error err = - grpc_call_start_write(context_->call(), out_buf, this, flag); + grpc_call_start_write(call(), out_buf, write_tag(), flag); grpc_byte_buffer_destroy(out_buf); GPR_ASSERT(err == GRPC_CALL_OK); - ev = WaitForEvent(&got_write_, &write_cv_, &mu_, &write_ev_); - if (!ev) { - return false; - } + ev = grpc_completion_queue_pluck(cq(), write_tag(), gpr_inf_future); GPR_ASSERT(ev->type == GRPC_WRITE_ACCEPTED); ret = ev->data.write_accepted == GRPC_OP_OK; grpc_event_finish(ev); } - if (is_last) { - grpc_call_error err = grpc_call_writes_done(context_->call(), this); + if (ret && is_last) { + grpc_call_error err = grpc_call_writes_done(call(), halfclose_tag()); GPR_ASSERT(err == GRPC_CALL_OK); - ev = WaitForEvent(&got_write_, &write_cv_, &mu_, &write_ev_); - if (!ev) { - return false; - } + ev = grpc_completion_queue_pluck(cq(), halfclose_tag(), gpr_inf_future); GPR_ASSERT(ev->type == GRPC_FINISH_ACCEPTED); grpc_event_finish(ev); + self_halfclosed_ = true; + } else if (!ret) { // Stream broken + self_halfclosed_ = true; + peer_halfclosed_ = true; } + return ret; } const Status& StreamContext::Wait() { - std::unique_lock<std::mutex> lock(mu_); - // TODO(yangg) if not halfclosed cancel the stream - GPR_ASSERT(self_halfclosed_); - GPR_ASSERT(peer_halfclosed_); - GPR_ASSERT(!waiting_); - waiting_ = true; - while (!stream_finished_) { - finish_cv_.wait(lock); + // TODO(yangg) properly support metadata + grpc_event* metadata_ev = grpc_completion_queue_pluck( + cq(), client_metadata_read_tag(), gpr_inf_future); + grpc_event_finish(metadata_ev); + // TODO(yangg) protect states by a mutex, including other places. + if (!self_halfclosed_ || !peer_halfclosed_) { + FinishStream(Status::Cancelled, true); + } else { + grpc_event* finish_ev = + grpc_completion_queue_pluck(cq(), finished_tag(), gpr_inf_future); + GPR_ASSERT(finish_ev->type == GRPC_FINISHED); + std::string error_details(finish_ev->data.finished.details + ? finish_ev->data.finished.details + : ""); + final_status_ = Status( + static_cast<StatusCode>(finish_ev->data.finished.code), error_details); + grpc_event_finish(finish_ev); } return final_status_; } -void StreamContext::FinishStream(const Status& status, bool send) { return; } +void StreamContext::FinishStream(const Status& status, bool send) { + if (send) { + grpc_call_cancel(call()); + } + grpc_event* finish_ev = + grpc_completion_queue_pluck(cq(), finished_tag(), gpr_inf_future); + GPR_ASSERT(finish_ev->type == GRPC_FINISHED); + grpc_event_finish(finish_ev); + final_status_ = status; +} } // namespace grpc diff --git a/src/cpp/stream/stream_context.h b/src/cpp/stream/stream_context.h index b7f462f323..6c31095042 100644 --- a/src/cpp/stream/stream_context.h +++ b/src/cpp/stream/stream_context.h @@ -34,10 +34,7 @@ #ifndef __GRPCPP_INTERNAL_STREAM_STREAM_CONTEXT_H__ #define __GRPCPP_INTERNAL_STREAM_STREAM_CONTEXT_H__ -#include <condition_variable> -#include <mutex> -#include <thread> - +#include <grpc/grpc.h> #include <grpc++/status.h> #include <grpc++/stream_context_interface.h> @@ -47,8 +44,6 @@ class Message; } } -struct grpc_event; - namespace grpc { class ClientContext; class RpcMethod; @@ -57,6 +52,9 @@ class StreamContext : public StreamContextInterface { public: StreamContext(const RpcMethod& method, ClientContext* context, const google::protobuf::Message* request, google::protobuf::Message* result); + StreamContext(const RpcMethod& method, grpc_call* call, + grpc_completion_queue* cq, google::protobuf::Message* request, + google::protobuf::Message* result); ~StreamContext(); // Start the stream, if there is a final write following immediately, set // buffered so that the messages can be sent in batch. @@ -66,37 +64,31 @@ class StreamContext : public StreamContextInterface { const Status& Wait() override; void FinishStream(const Status& status, bool send) override; - const google::protobuf::Message* request() override { return request_; } + google::protobuf::Message* request() override { return request_; } google::protobuf::Message* response() override { return result_; } private: - void PollingLoop(); - bool BlockingStart(); + // Unique tags for plucking events from the c layer. this pointer is casted + // to char* to create single byte step between tags. It implicitly relies on + // that StreamContext is large enough to contain all the pointers. + void* finished_tag() { return reinterpret_cast<char*>(this); } + void* read_tag() { return reinterpret_cast<char*>(this) + 1; } + void* write_tag() { return reinterpret_cast<char*>(this) + 2; } + void* halfclose_tag() { return reinterpret_cast<char*>(this) + 3; } + void* invoke_tag() { return reinterpret_cast<char*>(this) + 4; } + void* client_metadata_read_tag() { return reinterpret_cast<char*>(this) + 5; } + grpc_call* call() { return call_; } + grpc_completion_queue* cq() { return cq_; } + bool is_client_; const RpcMethod* method_; // not owned - ClientContext* context_; // now owned - const google::protobuf::Message* request_; // not owned - google::protobuf::Message* result_; // not owned + grpc_call* call_; // not owned + grpc_completion_queue* cq_; // not owned + google::protobuf::Message* request_; // first request, not owned + google::protobuf::Message* result_; // last response, not owned - std::thread cq_poller_; - std::mutex mu_; - std::condition_variable invoke_cv_; - std::condition_variable read_cv_; - std::condition_variable write_cv_; - std::condition_variable finish_cv_; - grpc_event* invoke_ev_; - // TODO(yangg) make these two into queues to support concurrent reads and - // writes - grpc_event* read_ev_; - grpc_event* write_ev_; - bool reading_; - bool writing_; - bool got_read_; - bool got_write_; bool peer_halfclosed_; bool self_halfclosed_; - bool stream_finished_; - bool waiting_; Status final_status_; }; diff --git a/src/ruby/README.md b/src/ruby/README.md index 8377866344..3a5c50819b 100755 --- a/src/ruby/README.md +++ b/src/ruby/README.md @@ -52,11 +52,25 @@ $ ./configure --prefix=/usr $ make $ sudo make install +Install an update to OpenSSL with ALPN support + +$ wget https://www.openssl.org/source/openssl-1.0.2-beta3.tar.gz +$ tar -zxvf openssl-1.0.2-beta3.tar.gz +$ cd openssl-1.0.2-beta3 +$ ./config shared +$ make +$ sudo make install + Install RVM +$ # the -with-openssl-dir ensures that ruby uses the updated version of SSL +$ command curl -sSL https://rvm.io/mpapis.asc | gpg --import - $ \curl -sSL https://get.rvm.io | bash -s stable --ruby +$ $ # follow the instructions to ensure that your're using the latest stable version of Ruby +$ # and that the rvm command is installed $ +$ rvm reinstall 2.1.5 --with-openssl-dir=/usr/local/ssl $ gem install bundler # install bundler, the standard ruby package manager Install the patched beefcake, and update the Gemfile to reference @@ -90,4 +104,3 @@ $ # update the Gemfile, modify the line beginning # gem 'beefcake' to refer to $ # the patched beefcake dir $ $ bundle install - diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile index 11b3d04f3f..0a0fbcecca 100755 --- a/src/ruby/Rakefile +++ b/src/ruby/Rakefile @@ -9,7 +9,10 @@ end SPEC_SUITES = [ { :id => :wrapper, :title => 'wrapper layer', :files => %w(spec/*.rb) }, - { :id => :idiomatic, :title => 'idiomatic layer', :dir => %w(spec/generic) } + { :id => :idiomatic, :title => 'idiomatic layer', :dir => %w(spec/generic), + :tag => '~bidi' }, + { :id => :bidi, :title => 'bidi tests', :dir => %w(spec/generic), + :tag => 'bidi' } ] desc "Run all RSpec tests" @@ -28,11 +31,16 @@ namespace :spec do end t.pattern = spec_files + + if suite[:tag] + t.rspec_opts = "--tag #{suite[:tag]}" + end end end end end -desc "Run tests" -task :default => [ "spec:suite:wrapper", "spec:suite:idiomatic"] -task :spec => :compile +task :default => "spec:suite:idiomatic" # this should be spec:suite:bidi +task "spec:suite:wrapper" => :compile +task "spec:suite:idiomatic" => "spec:suite:wrapper" +task "spec:suite:bidi" => "spec:suite:idiomatic" diff --git a/src/ruby/bin/math.pb.rb b/src/ruby/bin/math.pb.rb index 9278a84382..f6976be568 100755 --- a/src/ruby/bin/math.pb.rb +++ b/src/ruby/bin/math.pb.rb @@ -51,7 +51,7 @@ module Math class Service include GRPC::GenericService - self.marshal_instance_method = :encode + self.marshal_class_method = :encode self.unmarshal_class_method = :decode rpc :Div, DivArgs, DivReply diff --git a/src/ruby/bin/math_client.rb b/src/ruby/bin/math_client.rb index f8cf8580e8..947e86adf9 100644 --- a/src/ruby/bin/math_client.rb +++ b/src/ruby/bin/math_client.rb @@ -43,13 +43,16 @@ require 'grpc' require 'grpc/generic/client_stub' require 'grpc/generic/service' require 'math.pb' +require 'optparse' + +include GRPC::Core::TimeConsts def do_div(stub) logger.info('request_response') logger.info('----------------') req = Math::DivArgs.new(:dividend => 7, :divisor => 3) logger.info("div(7/3): req=#{req.inspect}") - resp = stub.div(req, deadline=GRPC::TimeConsts::INFINITE_FUTURE) + resp = stub.div(req, deadline=INFINITE_FUTURE) logger.info("Answer: #{resp.inspect}") logger.info('----------------') end @@ -70,7 +73,7 @@ def do_fib(stub) logger.info('----------------') req = Math::FibArgs.new(:limit => 11) logger.info("fib(11): req=#{req.inspect}") - resp = stub.fib(req, deadline=GRPC::TimeConsts::INFINITE_FUTURE) + resp = stub.fib(req, deadline=INFINITE_FUTURE) resp.each do |r| logger.info("Answer: #{r.inspect}") end @@ -92,15 +95,51 @@ def do_div_many(stub) logger.info('----------------') end +def load_test_certs + this_dir = File.expand_path(File.dirname(__FILE__)) + data_dir = File.join(File.dirname(this_dir), 'spec/testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(data_dir, f)).read } +end + +def test_creds + certs = load_test_certs + creds = GRPC::Core::Credentials.new(certs[0]) +end def main - host_port = 'localhost:7070' - if ARGV.size > 0 - host_port = ARGV[0] - end + options = { + 'host' => 'localhost:7071', + 'secure' => false + } + OptionParser.new do |opts| + opts.banner = 'Usage: [--host|-h <hostname>:<port>] [--secure|-s]' + opts.on('-h', '--host', '<hostname>:<port>') do |v| + options['host'] = v + end + opts.on('-s', '--secure', 'access using test creds') do |v| + options['secure'] = true + end + end.parse! + # The Math::Math:: module occurs because the service has the same name as its # package. That practice should be avoided by defining real services. - stub = Math::Math::Stub.new(host_port) + + p options + if options['secure'] + stub_opts = { + :creds => test_creds, + GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.com', + } + p stub_opts + p options['host'] + stub = Math::Math::Stub.new(options['host'], **stub_opts) + logger.info("... connecting securely on #{options['host']}") + else + stub = Math::Math::Stub.new(options['host']) + logger.info("... connecting insecurely on #{options['host']}") + end + do_div(stub) do_sum(stub) do_fib(stub) diff --git a/src/ruby/bin/math_server.rb b/src/ruby/bin/math_server.rb index 72a1f6b398..d21ae7ee27 100644 --- a/src/ruby/bin/math_server.rb +++ b/src/ruby/bin/math_server.rb @@ -44,6 +44,7 @@ require 'grpc' require 'grpc/generic/service' require 'grpc/generic/rpc_server' require 'math.pb' +require 'optparse' # Holds state for a fibonacci series class Fibber @@ -151,14 +152,43 @@ class Calculator < Math::Math::Service end +def load_test_certs + this_dir = File.expand_path(File.dirname(__FILE__)) + data_dir = File.join(File.dirname(this_dir), 'spec/testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(data_dir, f)).read } +end + +def test_server_creds + certs = load_test_certs + server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2]) +end + def main - host_port = 'localhost:7070' - if ARGV.size > 0 - host_port = ARGV[0] + options = { + 'host' => 'localhost:7071', + 'secure' => false + } + OptionParser.new do |opts| + opts.banner = 'Usage: [--host|-h <hostname>:<port>] [--secure|-s]' + opts.on('-h', '--host', '<hostname>:<port>') do |v| + options['host'] = v + end + opts.on('-s', '--secure', 'access using test creds') do |v| + options['secure'] = true + end + end.parse! + + if options['secure'] + s = GRPC::RpcServer.new(creds: test_server_creds) + s.add_http2_port(options['host'], true) + logger.info("... running securely on #{options['host']}") + else + s = GRPC::RpcServer.new + s.add_http2_port(options['host']) + logger.info("... running insecurely on #{options['host']}") end - s = GRPC::RpcServer.new() - s.add_http2_port(host_port) s.handle(Calculator) s.run end diff --git a/src/ruby/ext/grpc/extconf.rb b/src/ruby/ext/grpc/extconf.rb index 06bfad9e6c..9a181f5859 100644 --- a/src/ruby/ext/grpc/extconf.rb +++ b/src/ruby/ext/grpc/extconf.rb @@ -80,7 +80,9 @@ $CFLAGS << ' -Wno-return-type ' $CFLAGS << ' -Wall ' $CFLAGS << ' -pedantic ' -$LDFLAGS << ' -lgrpc -lgpr -levent -levent_pthreads -levent_core' +$LDFLAGS << ' -lgrpc -lgpr -levent -levent_pthreads -levent_core ' +$LDFLAGS << ' -lssl -lcrypto ' +$DLDFLAGS << ' -Wl,-rpath,/usr/local/ssl/lib ' # crash('need grpc lib') unless have_library('grpc', 'grpc_channel_destroy') # diff --git a/src/ruby/ext/grpc/rb_byte_buffer.c b/src/ruby/ext/grpc/rb_byte_buffer.c index a520ca44dd..b75e853d6b 100644 --- a/src/ruby/ext/grpc/rb_byte_buffer.c +++ b/src/ruby/ext/grpc/rb_byte_buffer.c @@ -205,7 +205,7 @@ static VALUE grpc_rb_byte_buffer_init(VALUE self, VALUE src) { VALUE rb_cByteBuffer = Qnil; void Init_google_rpc_byte_buffer() { - rb_cByteBuffer = rb_define_class_under(rb_mGoogleRPC, "ByteBuffer", + rb_cByteBuffer = rb_define_class_under(rb_mGoogleRpcCore, "ByteBuffer", rb_cObject); /* Allocates an object managed by the ruby runtime */ diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c index 07f70e041a..5ea66e6e21 100644 --- a/src/ruby/ext/grpc/rb_call.c +++ b/src/ruby/ext/grpc/rb_call.c @@ -429,7 +429,7 @@ VALUE rb_eCallError = Qnil; void Init_google_rpc_error_codes() { /* Constants representing the error codes of grpc_call_error in grpc.h */ - VALUE rb_RpcErrors = rb_define_module_under(rb_mGoogleRPC, "RpcErrors"); + VALUE rb_RpcErrors = rb_define_module_under(rb_mGoogleRpcCore, "RpcErrors"); rb_define_const(rb_RpcErrors, "OK", UINT2NUM(GRPC_CALL_OK)); rb_define_const(rb_RpcErrors, "ERROR", UINT2NUM(GRPC_CALL_ERROR)); rb_define_const(rb_RpcErrors, "NOT_ON_SERVER", @@ -475,9 +475,9 @@ void Init_google_rpc_error_codes() { void Init_google_rpc_call() { /* CallError inherits from Exception to signal that it is non-recoverable */ - rb_eCallError = rb_define_class_under(rb_mGoogleRPC, "CallError", + rb_eCallError = rb_define_class_under(rb_mGoogleRpcCore, "CallError", rb_eException); - rb_cCall = rb_define_class_under(rb_mGoogleRPC, "Call", rb_cObject); + rb_cCall = rb_define_class_under(rb_mGoogleRpcCore, "Call", rb_cObject); /* Prevent allocation or inialization of the Call class */ rb_define_alloc_func(rb_cCall, grpc_rb_cannot_alloc); diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c index f4c09a392a..dbced1c7fd 100644 --- a/src/ruby/ext/grpc/rb_channel.c +++ b/src/ruby/ext/grpc/rb_channel.c @@ -36,10 +36,12 @@ #include <ruby.h> #include <grpc/grpc.h> +#include <grpc/grpc_security.h> #include "rb_grpc.h" #include "rb_call.h" #include "rb_channel_args.h" #include "rb_completion_queue.h" +#include "rb_credentials.h" #include "rb_server.h" /* id_channel is the name of the hidden ivar that preserves a reference to the @@ -104,18 +106,36 @@ static VALUE grpc_rb_channel_alloc(VALUE cls) { wrapper); } -/* Initializes channel instances */ -static VALUE grpc_rb_channel_init(VALUE self, VALUE target, - VALUE channel_args) { +/* + call-seq: + insecure_channel = Channel:new("myhost:8080", {'arg1': 'value1'}) + creds = ... + secure_channel = Channel:new("myhost:443", {'arg1': 'value1'}, creds) + + Creates channel instances. */ +static VALUE grpc_rb_channel_init(int argc, VALUE *argv, VALUE self) { + VALUE channel_args = Qnil; + VALUE credentials = Qnil; + VALUE target = Qnil; grpc_rb_channel *wrapper = NULL; + grpc_credentials *creds = NULL; grpc_channel *ch = NULL; - char *target_chars = StringValueCStr(target); + char *target_chars = NULL; grpc_channel_args args; MEMZERO(&args, grpc_channel_args, 1); + /* "21" == 2 mandatory args, 1 (credentials) is optional */ + rb_scan_args(argc, argv, "21", &target, &channel_args, &credentials); + Data_Get_Struct(self, grpc_rb_channel, wrapper); + target_chars = StringValueCStr(target); grpc_rb_hash_convert_to_channel_args(channel_args, &args); - ch = grpc_channel_create(target_chars, &args); + if (credentials == Qnil) { + ch = grpc_channel_create(target_chars, &args); + } else { + creds = grpc_rb_get_wrapped_credentials(credentials); + ch = grpc_secure_channel_create(creds, target_chars, &args); + } if (args.args != NULL) { xfree(args.args); /* Allocated by grpc_rb_hash_convert_to_channel_args */ } @@ -208,13 +228,13 @@ VALUE rb_cChannel = Qnil; void Init_google_rpc_channel() { rb_cChannelArgs = rb_define_class("TmpChannelArgs", rb_cObject); - rb_cChannel = rb_define_class_under(rb_mGoogleRPC, "Channel", rb_cObject); + rb_cChannel = rb_define_class_under(rb_mGoogleRpcCore, "Channel", rb_cObject); /* Allocates an object managed by the ruby runtime */ rb_define_alloc_func(rb_cChannel, grpc_rb_channel_alloc); /* Provides a ruby constructor and support for dup/clone. */ - rb_define_method(rb_cChannel, "initialize", grpc_rb_channel_init, 2); + rb_define_method(rb_cChannel, "initialize", grpc_rb_channel_init, -1); rb_define_method(rb_cChannel, "initialize_copy", grpc_rb_channel_init_copy, 1); @@ -225,6 +245,8 @@ void Init_google_rpc_channel() { id_channel = rb_intern("__channel"); id_target = rb_intern("__target"); + rb_define_const(rb_cChannel, "SSL_TARGET", + ID2SYM(rb_intern(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))); } /* Gets the wrapped channel from the ruby wrapper */ diff --git a/src/ruby/ext/grpc/rb_completion_queue.c b/src/ruby/ext/grpc/rb_completion_queue.c index 62d045e971..dfde44218b 100644 --- a/src/ruby/ext/grpc/rb_completion_queue.c +++ b/src/ruby/ext/grpc/rb_completion_queue.c @@ -168,7 +168,7 @@ static VALUE grpc_rb_completion_queue_pluck(VALUE self, VALUE tag, VALUE rb_cCompletionQueue = Qnil; void Init_google_rpc_completion_queue() { - rb_cCompletionQueue = rb_define_class_under(rb_mGoogleRPC, + rb_cCompletionQueue = rb_define_class_under(rb_mGoogleRpcCore, "CompletionQueue", rb_cObject); diff --git a/src/ruby/ext/grpc/rb_credentials.c b/src/ruby/ext/grpc/rb_credentials.c new file mode 100644 index 0000000000..658adacc06 --- /dev/null +++ b/src/ruby/ext/grpc/rb_credentials.c @@ -0,0 +1,301 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "rb_credentials.h" + +#include <ruby.h> + +#include <grpc/grpc.h> +#include <grpc/grpc_security.h> + +#include "rb_grpc.h" + + +/* grpc_rb_credentials wraps a grpc_credentials. It provides a + * peer ruby object, 'mark' to minimize copying when a credential is + * created from ruby. */ +typedef struct grpc_rb_credentials { + /* Holder of ruby objects involved in constructing the credentials */ + VALUE mark; + + /* The actual credentials */ + grpc_credentials *wrapped; +} grpc_rb_credentials; + +/* Destroys the credentials instances. */ +static void grpc_rb_credentials_free(void *p) { + grpc_rb_credentials *wrapper = NULL; + if (p == NULL) { + return; + }; + wrapper = (grpc_rb_credentials *)p; + + /* Delete the wrapped object if the mark object is Qnil, which indicates that + * no other object is the actual owner. */ + if (wrapper->wrapped != NULL && wrapper->mark == Qnil) { + grpc_credentials_release(wrapper->wrapped); + wrapper->wrapped = NULL; + } + + xfree(p); +} + +/* Protects the mark object from GC */ +static void grpc_rb_credentials_mark(void *p) { + grpc_rb_credentials *wrapper = NULL; + if (p == NULL) { + return; + } + wrapper = (grpc_rb_credentials *)p; + + /* If it's not already cleaned up, mark the mark object */ + if (wrapper->mark != Qnil) { + rb_gc_mark(wrapper->mark); + } +} + +/* Allocates Credential instances. + + Provides safe initial defaults for the instance fields. */ +static VALUE grpc_rb_credentials_alloc(VALUE cls) { + grpc_rb_credentials *wrapper = ALLOC(grpc_rb_credentials); + wrapper->wrapped = NULL; + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_credentials_mark, + grpc_rb_credentials_free, + wrapper); +} + +/* Clones Credentials instances. + + Gives Credentials a consistent implementation of Ruby's object copy/dup + protocol. */ +static VALUE grpc_rb_credentials_init_copy(VALUE copy, VALUE orig) { + grpc_rb_credentials *orig_cred = NULL; + grpc_rb_credentials *copy_cred = NULL; + + if (copy == orig) { + return copy; + } + + /* Raise an error if orig is not a credentials object or a subclass. */ + if (TYPE(orig) != T_DATA || + RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_credentials_free) { + rb_raise(rb_eTypeError, "not a %s", + rb_obj_classname(rb_cCredentials)); + } + + Data_Get_Struct(orig, grpc_rb_credentials, orig_cred); + Data_Get_Struct(copy, grpc_rb_credentials, copy_cred); + + /* use ruby's MEMCPY to make a byte-for-byte copy of the credentials + * wrapper object. */ + MEMCPY(copy_cred, orig_cred, grpc_rb_credentials, 1); + return copy; +} + +/* + call-seq: + creds = Credentials.default() + + Creates the default credential instances. */ +static VALUE grpc_rb_default_credentials_create(VALUE cls) { + grpc_rb_credentials *wrapper = ALLOC(grpc_rb_credentials); + wrapper->wrapped = grpc_default_credentials_create(); + if (wrapper->wrapped == NULL) { + rb_raise(rb_eRuntimeError, + "could not create default credentials, not sure why"); + return Qnil; + } + + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_credentials_mark, + grpc_rb_credentials_free, wrapper); +} + +/* + call-seq: + creds = Credentials.compute_engine() + + Creates the default credential instances. */ +static VALUE grpc_rb_compute_engine_credentials_create(VALUE cls) { + grpc_rb_credentials *wrapper = ALLOC(grpc_rb_credentials); + wrapper->wrapped = grpc_compute_engine_credentials_create(); + if (wrapper->wrapped == NULL) { + rb_raise(rb_eRuntimeError, + "could not create composite engine credentials, not sure why"); + return Qnil; + } + + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_credentials_mark, + grpc_rb_credentials_free, wrapper); +} + +/* + call-seq: + creds1 = ... + creds2 = ... + creds3 = creds1.add(creds2) + + Creates the default credential instances. */ +static VALUE grpc_rb_composite_credentials_create(VALUE self, VALUE other) { + grpc_rb_credentials *self_wrapper = NULL; + grpc_rb_credentials *other_wrapper = NULL; + grpc_rb_credentials *wrapper = NULL; + + Data_Get_Struct(self, grpc_rb_credentials, self_wrapper); + Data_Get_Struct(other, grpc_rb_credentials, other_wrapper); + wrapper = ALLOC(grpc_rb_credentials); + wrapper->wrapped = grpc_composite_credentials_create(self_wrapper->wrapped, + other_wrapper->wrapped); + if (wrapper->wrapped == NULL) { + rb_raise(rb_eRuntimeError, + "could not create composite credentials, not sure why"); + return Qnil; + } + + wrapper->mark = Qnil; + return Data_Wrap_Struct(rb_cCredentials, grpc_rb_credentials_mark, + grpc_rb_credentials_free, wrapper); +} + +/* The attribute used on the mark object to hold the pem_root_certs. */ +static ID id_pem_root_certs; + +/* The attribute used on the mark object to hold the pem_private_key. */ +static ID id_pem_private_key; + +/* The attribute used on the mark object to hold the pem_private_key. */ +static ID id_pem_cert_chain; + +/* + call-seq: + creds1 = Credentials.new(pem_root_certs) + ... + creds2 = Credentials.new(pem_root_certs, pem_private_key, + pem_cert_chain) + + pem_root_certs: (required) PEM encoding of the server root certificate + pem_private_key: (optional) PEM encoding of the client's private key + pem_cert_chain: (optional) PEM encoding of the client's cert chain + + Initializes Credential instances. */ +static VALUE grpc_rb_credentials_init(int argc, VALUE *argv, VALUE self) { + VALUE pem_root_certs = Qnil; + VALUE pem_private_key = Qnil; + VALUE pem_cert_chain = Qnil; + grpc_rb_credentials *wrapper = NULL; + grpc_credentials *creds = NULL; + /* "12" == 1 mandatory arg, 2 (credentials) is optional */ + rb_scan_args(argc, argv, "12", &pem_root_certs, &pem_private_key, + &pem_cert_chain); + + Data_Get_Struct(self, grpc_rb_credentials, wrapper); + if (pem_root_certs == Qnil) { + rb_raise(rb_eRuntimeError, + "could not create a credential: nil pem_root_certs"); + return Qnil; + } + if (pem_private_key == Qnil && pem_cert_chain == Qnil) { + creds = grpc_ssl_credentials_create(RSTRING_PTR(pem_root_certs), + RSTRING_LEN(pem_root_certs), NULL, 0, + NULL, 0); + } else if (pem_cert_chain == Qnil) { + creds = grpc_ssl_credentials_create( + RSTRING_PTR(pem_root_certs), RSTRING_LEN(pem_root_certs), + RSTRING_PTR(pem_private_key), RSTRING_LEN(pem_private_key), + RSTRING_PTR(pem_cert_chain), RSTRING_LEN(pem_cert_chain)); + } else if (pem_private_key == Qnil) { + creds = grpc_ssl_credentials_create( + RSTRING_PTR(pem_root_certs), RSTRING_LEN(pem_root_certs), + NULL, 0, + RSTRING_PTR(pem_cert_chain), RSTRING_LEN(pem_cert_chain)); + } else { + creds = grpc_ssl_credentials_create( + RSTRING_PTR(pem_root_certs), RSTRING_LEN(pem_root_certs), + RSTRING_PTR(pem_private_key), RSTRING_LEN(pem_private_key), + NULL, 0); + } + if (creds == NULL) { + rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); + return Qnil; + } + wrapper->wrapped = creds; + + /* Add the input objects as hidden fields to preserve them. */ + rb_ivar_set(self, id_pem_cert_chain, pem_cert_chain); + rb_ivar_set(self, id_pem_private_key, pem_private_key); + rb_ivar_set(self, id_pem_root_certs, pem_root_certs); + + return self; +} + +/* rb_cCredentials is the ruby class that proxies grpc_credentials. */ +VALUE rb_cCredentials = Qnil; + +void Init_google_rpc_credentials() { + rb_cCredentials = rb_define_class_under(rb_mGoogleRpcCore, "Credentials", + rb_cObject); + + /* Allocates an object managed by the ruby runtime */ + rb_define_alloc_func(rb_cCredentials, grpc_rb_credentials_alloc); + + /* Provides a ruby constructor and support for dup/clone. */ + rb_define_method(rb_cCredentials, "initialize", + grpc_rb_credentials_init, -1); + rb_define_method(rb_cCredentials, "initialize_copy", + grpc_rb_credentials_init_copy, 1); + + /* Provide static funcs that create new special instances. */ + rb_define_singleton_method(rb_cCredentials, "default", + grpc_rb_default_credentials_create, 0); + + rb_define_singleton_method(rb_cCredentials, "compute_engine", + grpc_rb_compute_engine_credentials_create, 0); + + /* Provide other methods. */ + rb_define_method(rb_cCredentials, "compose", + grpc_rb_composite_credentials_create, 1); + + id_pem_cert_chain = rb_intern("__pem_cert_chain"); + id_pem_private_key = rb_intern("__pem_private_key"); + id_pem_root_certs = rb_intern("__pem_root_certs"); +} + +/* Gets the wrapped grpc_credentials from the ruby wrapper */ +grpc_credentials* grpc_rb_get_wrapped_credentials(VALUE v) { + grpc_rb_credentials *wrapper = NULL; + Data_Get_Struct(v, grpc_rb_credentials, wrapper); + return wrapper->wrapped; +} diff --git a/src/ruby/ext/grpc/rb_credentials.h b/src/ruby/ext/grpc/rb_credentials.h new file mode 100644 index 0000000000..d18c88ac34 --- /dev/null +++ b/src/ruby/ext/grpc/rb_credentials.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef GRPC_RB_CREDENTIALS_H_ +#define GRPC_RB_CREDENTIALS_H_ + +#include <ruby.h> +#include <grpc/grpc_security.h> + +/* rb_cCredentials is the ruby class whose instances proxy + grpc_credentials. */ +extern VALUE rb_cCredentials; + +/* Initializes the ruby Credentials class. */ +void Init_google_rpc_credentials(); + +/* Gets the wrapped credentials from the ruby wrapper */ +grpc_credentials* grpc_rb_get_wrapped_credentials(VALUE v); + +#endif /* GRPC_RB_CREDENTIALS_H_ */ diff --git a/src/ruby/ext/grpc/rb_event.c b/src/ruby/ext/grpc/rb_event.c index 6f542f9eba..6708ea397c 100644 --- a/src/ruby/ext/grpc/rb_event.c +++ b/src/ruby/ext/grpc/rb_event.c @@ -245,9 +245,9 @@ VALUE rb_cEvent = Qnil; VALUE rb_eEventError = Qnil; void Init_google_rpc_event() { - rb_eEventError = rb_define_class_under(rb_mGoogleRPC, "EventError", + rb_eEventError = rb_define_class_under(rb_mGoogleRpcCore, "EventError", rb_eStandardError); - rb_cEvent = rb_define_class_under(rb_mGoogleRPC, "Event", rb_cObject); + rb_cEvent = rb_define_class_under(rb_mGoogleRpcCore, "Event", rb_cObject); rb_sNewServerRpc = rb_struct_define("NewServerRpc", "method", "host", "deadline", "metadata", NULL); @@ -263,7 +263,7 @@ void Init_google_rpc_event() { rb_define_method(rb_cEvent, "type", grpc_rb_event_type, 0); /* Constants representing the completion types */ - rb_mCompletionType = rb_define_module_under(rb_mGoogleRPC, "CompletionType"); + rb_mCompletionType = rb_define_module_under(rb_mGoogleRpcCore, "CompletionType"); rb_define_const(rb_mCompletionType, "QUEUE_SHUTDOWN", INT2NUM(GRPC_QUEUE_SHUTDOWN)); rb_define_const(rb_mCompletionType, "READ", INT2NUM(GRPC_READ)); diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c index 5cc45cf743..9c54a05c45 100644 --- a/src/ruby/ext/grpc/rb_grpc.c +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -46,6 +46,8 @@ #include "rb_event.h" #include "rb_metadata.h" #include "rb_server.h" +#include "rb_credentials.h" +#include "rb_server_credentials.h" #include "rb_status.h" /* Define common vars and funcs declared in rb.h */ @@ -184,8 +186,10 @@ VALUE grpc_rb_time_val_to_s(VALUE self) { /* Adds a module with constants that map to gpr's static timeval structs. */ void Init_google_time_consts() { - VALUE rb_mTimeConsts = rb_define_module_under(rb_mGoogleRPC, "TimeConsts"); - rb_cTimeVal = rb_define_class_under(rb_mGoogleRPC, "TimeSpec", rb_cObject); + VALUE rb_mTimeConsts = rb_define_module_under(rb_mGoogleRpcCore, + "TimeConsts"); + rb_cTimeVal = rb_define_class_under(rb_mGoogleRpcCore, "TimeSpec", + rb_cObject); rb_define_const(rb_mTimeConsts, "ZERO", Data_Wrap_Struct(rb_cTimeVal, GC_NOT_MARKED, GC_DONT_FREE, (void *)&gpr_time_0)); @@ -212,19 +216,23 @@ void grpc_rb_shutdown(void *vm) { /* Initialize the Google RPC module. */ VALUE rb_mGoogle = Qnil; VALUE rb_mGoogleRPC = Qnil; +VALUE rb_mGoogleRpcCore = Qnil; void Init_grpc() { grpc_init(); ruby_vm_at_exit(grpc_rb_shutdown); rb_mGoogle = rb_define_module("Google"); rb_mGoogleRPC = rb_define_module_under(rb_mGoogle, "RPC"); + rb_mGoogleRpcCore = rb_define_module_under(rb_mGoogleRPC, "Core"); Init_google_rpc_byte_buffer(); Init_google_rpc_event(); Init_google_rpc_channel(); Init_google_rpc_completion_queue(); Init_google_rpc_call(); + Init_google_rpc_credentials(); Init_google_rpc_metadata(); Init_google_rpc_server(); + Init_google_rpc_server_credentials(); Init_google_rpc_status(); Init_google_time_consts(); } diff --git a/src/ruby/ext/grpc/rb_grpc.h b/src/ruby/ext/grpc/rb_grpc.h index fd43c3795f..68f8a06c30 100644 --- a/src/ruby/ext/grpc/rb_grpc.h +++ b/src/ruby/ext/grpc/rb_grpc.h @@ -41,8 +41,8 @@ /* rb_mGoogle is the top-level Google module. */ extern VALUE rb_mGoogle; -/* rb_mGoogleRPC is the module containing all the ruby wrapper GRPC classes. */ -extern VALUE rb_mGoogleRPC; +/* rb_mGoogleRpcCore is the module containing the ruby wrapper GRPC classes. */ +extern VALUE rb_mGoogleRpcCore; /* Class used to wrap timeval structs. */ extern VALUE rb_cTimeVal; diff --git a/src/ruby/ext/grpc/rb_metadata.c b/src/ruby/ext/grpc/rb_metadata.c index 13d515a929..733a53a7e9 100644 --- a/src/ruby/ext/grpc/rb_metadata.c +++ b/src/ruby/ext/grpc/rb_metadata.c @@ -189,7 +189,7 @@ static VALUE grpc_rb_metadata_value(VALUE self) { /* rb_cMetadata is the Metadata class whose instances proxy grpc_metadata. */ VALUE rb_cMetadata = Qnil; void Init_google_rpc_metadata() { - rb_cMetadata = rb_define_class_under(rb_mGoogleRPC, "Metadata", rb_cObject); + rb_cMetadata = rb_define_class_under(rb_mGoogleRpcCore, "Metadata", rb_cObject); /* Allocates an object managed by the ruby runtime */ rb_define_alloc_func(rb_cMetadata, grpc_rb_metadata_alloc); diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c index f4230bd471..84fba5be4f 100644 --- a/src/ruby/ext/grpc/rb_server.c +++ b/src/ruby/ext/grpc/rb_server.c @@ -36,16 +36,18 @@ #include <ruby.h> #include <grpc/grpc.h> +#include <grpc/grpc_security.h> #include "rb_call.h" #include "rb_channel_args.h" #include "rb_completion_queue.h" +#include "rb_server_credentials.h" #include "rb_grpc.h" /* rb_cServer is the ruby class that proxies grpc_server. */ VALUE rb_cServer = Qnil; /* grpc_rb_server wraps a grpc_server. It provides a peer ruby object, - * 'mark' to minimize copying when a server is created from ruby. */ + 'mark' to minimize copying when a server is created from ruby. */ typedef struct grpc_rb_server { /* Holder of ruby objects involved in constructing the server */ VALUE mark; @@ -62,7 +64,7 @@ static void grpc_rb_server_free(void *p) { svr = (grpc_rb_server *)p; /* Deletes the wrapped object if the mark object is Qnil, which indicates - * that no other object is the actual owner. */ + that no other object is the actual owner. */ if (svr->wrapped != NULL && svr->mark == Qnil) { grpc_server_shutdown(svr->wrapped); grpc_server_destroy(svr->wrapped); @@ -92,17 +94,39 @@ static VALUE grpc_rb_server_alloc(VALUE cls) { wrapper); } -/* Initializes Server instances. */ -static VALUE grpc_rb_server_init(VALUE self, VALUE cqueue, VALUE channel_args) { - grpc_completion_queue *cq = grpc_rb_get_wrapped_completion_queue(cqueue); +/* + call-seq: + cq = CompletionQueue.new + insecure_server = Server.new(cq, {'arg1': 'value1'}) + server_creds = ... + secure_server = Server.new(cq, {'arg1': 'value1'}, server_creds) + + Initializes server instances. */ +static VALUE grpc_rb_server_init(int argc, VALUE *argv, VALUE self) { + VALUE cqueue = Qnil; + VALUE credentials = Qnil; + VALUE channel_args = Qnil; + grpc_completion_queue *cq = NULL; + grpc_server_credentials *creds = NULL; grpc_rb_server *wrapper = NULL; grpc_server *srv = NULL; grpc_channel_args args; MEMZERO(&args, grpc_channel_args, 1); + /* "21" == 2 mandatory args, 1 (credentials) is optional */ + rb_scan_args(argc, argv, "21", &cqueue, &channel_args, &credentials); + cq = grpc_rb_get_wrapped_completion_queue(cqueue); + Data_Get_Struct(self, grpc_rb_server, wrapper); grpc_rb_hash_convert_to_channel_args(channel_args, &args); srv = grpc_server_create(cq, &args); + if (credentials == Qnil) { + srv = grpc_server_create(cq, &args); + } else { + creds = grpc_rb_get_wrapped_server_credentials(credentials); + srv = grpc_secure_server_create(creds, cq, &args); + } + if (args.args != NULL) { xfree(args.args); /* Allocated by grpc_rb_hash_convert_to_channel_args */ } @@ -112,7 +136,7 @@ static VALUE grpc_rb_server_init(VALUE self, VALUE cqueue, VALUE channel_args) { wrapper->wrapped = srv; /* Add the cq as the server's mark object. This ensures the ruby cq can't be - * GCed before the server */ + GCed before the server */ wrapper->mark = cqueue; return self; } @@ -139,7 +163,7 @@ static VALUE grpc_rb_server_init_copy(VALUE copy, VALUE orig) { Data_Get_Struct(copy, grpc_rb_server, copy_srv); /* use ruby's MEMCPY to make a byte-for-byte copy of the server wrapper - * object. */ + object. */ MEMCPY(copy_srv, orig_srv, grpc_rb_server, 1); return copy; } @@ -183,16 +207,44 @@ static VALUE grpc_rb_server_destroy(VALUE self) { return Qnil; } -static VALUE grpc_rb_server_add_http2_port(VALUE self, VALUE port) { +/* + call-seq: + // insecure port + insecure_server = Server.new(cq, {'arg1': 'value1'}) + insecure_server.add_http2_port('mydomain:7575') + + // secure port + server_creds = ... + secure_server = Server.new(cq, {'arg1': 'value1'}, creds) + secure_server.add_http_port('mydomain:7575', True) + + Adds a http2 port to server */ +static VALUE grpc_rb_server_add_http2_port(int argc, VALUE *argv, VALUE self) { + VALUE port = Qnil; + VALUE is_secure = Qnil; grpc_rb_server *s = NULL; int added_ok = 0; + + /* "11" == 1 mandatory args, 1 (is_secure) is optional */ + rb_scan_args(argc, argv, "11", &port, &is_secure); + Data_Get_Struct(self, grpc_rb_server, s); if (s->wrapped == NULL) { rb_raise(rb_eRuntimeError, "closed!"); - } else { + return Qnil; + } else if (is_secure == Qnil || TYPE(is_secure) != T_TRUE) { added_ok = grpc_server_add_http2_port(s->wrapped, StringValueCStr(port)); if (added_ok == 0) { - rb_raise(rb_eRuntimeError, "could not add port %s to server, not sure why", + rb_raise(rb_eRuntimeError, + "could not add port %s to server, not sure why", + StringValueCStr(port)); + } + } else if (TYPE(is_secure) != T_FALSE) { + added_ok = grpc_server_add_secure_http2_port(s->wrapped, + StringValueCStr(port)); + if (added_ok == 0) { + rb_raise(rb_eRuntimeError, + "could not add secure port %s to server, not sure why", StringValueCStr(port)); } } @@ -200,13 +252,13 @@ static VALUE grpc_rb_server_add_http2_port(VALUE self, VALUE port) { } void Init_google_rpc_server() { - rb_cServer = rb_define_class_under(rb_mGoogleRPC, "Server", rb_cObject); + rb_cServer = rb_define_class_under(rb_mGoogleRpcCore, "Server", rb_cObject); /* Allocates an object managed by the ruby runtime */ rb_define_alloc_func(rb_cServer, grpc_rb_server_alloc); /* Provides a ruby constructor and support for dup/clone. */ - rb_define_method(rb_cServer, "initialize", grpc_rb_server_init, 2); + rb_define_method(rb_cServer, "initialize", grpc_rb_server_init, -1); rb_define_method(rb_cServer, "initialize_copy", grpc_rb_server_init_copy, 1); /* Add the server methods. */ @@ -215,7 +267,7 @@ void Init_google_rpc_server() { rb_define_method(rb_cServer, "destroy", grpc_rb_server_destroy, 0); rb_define_alias(rb_cServer, "close", "destroy"); rb_define_method(rb_cServer, "add_http2_port", grpc_rb_server_add_http2_port, - 1); + -1); } /* Gets the wrapped server from the ruby wrapper */ diff --git a/src/ruby/ext/grpc/rb_server.h b/src/ruby/ext/grpc/rb_server.h index 4619203d60..7fcd32c32f 100644 --- a/src/ruby/ext/grpc/rb_server.h +++ b/src/ruby/ext/grpc/rb_server.h @@ -31,7 +31,7 @@ * */ -#ifndef GRPC_RB_BYTE_BUFFER_H_ +#ifndef GRPC_RB_SERVER_H_ #define GRPC_RB_SERVER_H_ #include <ruby.h> diff --git a/src/ruby/ext/grpc/rb_server_credentials.c b/src/ruby/ext/grpc/rb_server_credentials.c new file mode 100644 index 0000000000..fa0d6f2ce8 --- /dev/null +++ b/src/ruby/ext/grpc/rb_server_credentials.c @@ -0,0 +1,215 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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 "rb_server_credentials.h" + +#include <ruby.h> + +#include <grpc/grpc.h> +#include <grpc/grpc_security.h> + +#include "rb_grpc.h" + + +/* grpc_rb_server_credentials wraps a grpc_server_credentials. It provides a + peer ruby object, 'mark' to minimize copying when a server credential is + created from ruby. */ +typedef struct grpc_rb_server_credentials { + /* Holder of ruby objects involved in constructing the server credentials */ + VALUE mark; + /* The actual server credentials */ + grpc_server_credentials *wrapped; +} grpc_rb_server_credentials; + +/* Destroys the server credentials instances. */ +static void grpc_rb_server_credentials_free(void *p) { + grpc_rb_server_credentials *wrapper = NULL; + if (p == NULL) { + return; + }; + wrapper = (grpc_rb_server_credentials *)p; + + /* Delete the wrapped object if the mark object is Qnil, which indicates that + no other object is the actual owner. */ + if (wrapper->wrapped != NULL && wrapper->mark == Qnil) { + grpc_server_credentials_release(wrapper->wrapped); + wrapper->wrapped = NULL; + } + + xfree(p); +} + +/* Protects the mark object from GC */ +static void grpc_rb_server_credentials_mark(void *p) { + grpc_rb_server_credentials *wrapper = NULL; + if (p == NULL) { + return; + } + wrapper = (grpc_rb_server_credentials *)p; + + /* If it's not already cleaned up, mark the mark object */ + if (wrapper->mark != Qnil) { + rb_gc_mark(wrapper->mark); + } +} + +/* Allocates ServerCredential instances. + + Provides safe initial defaults for the instance fields. */ +static VALUE grpc_rb_server_credentials_alloc(VALUE cls) { + grpc_rb_server_credentials *wrapper = ALLOC(grpc_rb_server_credentials); + wrapper->wrapped = NULL; + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_server_credentials_mark, + grpc_rb_server_credentials_free, + wrapper); +} + +/* Clones ServerCredentials instances. + + Gives ServerCredentials a consistent implementation of Ruby's object copy/dup + protocol. */ +static VALUE grpc_rb_server_credentials_init_copy(VALUE copy, VALUE orig) { + grpc_rb_server_credentials *orig_ch = NULL; + grpc_rb_server_credentials *copy_ch = NULL; + + if (copy == orig) { + return copy; + } + + /* Raise an error if orig is not a server_credentials object or a subclass. */ + if (TYPE(orig) != T_DATA || + RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_server_credentials_free) { + rb_raise(rb_eTypeError, "not a %s", + rb_obj_classname(rb_cServerCredentials)); + } + + Data_Get_Struct(orig, grpc_rb_server_credentials, orig_ch); + Data_Get_Struct(copy, grpc_rb_server_credentials, copy_ch); + + /* use ruby's MEMCPY to make a byte-for-byte copy of the server_credentials + wrapper object. */ + MEMCPY(copy_ch, orig_ch, grpc_rb_server_credentials, 1); + return copy; +} + + +/* The attribute used on the mark object to hold the pem_root_certs. */ +static ID id_pem_root_certs; + +/* The attribute used on the mark object to hold the pem_private_key. */ +static ID id_pem_private_key; + +/* The attribute used on the mark object to hold the pem_private_key. */ +static ID id_pem_cert_chain; + +/* + call-seq: + creds = ServerCredentials.new(pem_root_certs, pem_private_key, + pem_cert_chain) + creds = ServerCredentials.new(nil, pem_private_key, + pem_cert_chain) + + pem_root_certs: (required) PEM encoding of the server root certificate + pem_private_key: (optional) PEM encoding of the server's private key + pem_cert_chain: (optional) PEM encoding of the server's cert chain + + Initializes ServerCredential instances. */ +static VALUE grpc_rb_server_credentials_init(VALUE self, VALUE pem_root_certs, + VALUE pem_private_key, + VALUE pem_cert_chain) { + grpc_rb_server_credentials *wrapper = NULL; + grpc_server_credentials *creds = NULL; + Data_Get_Struct(self, grpc_rb_server_credentials, wrapper); + if (pem_cert_chain == Qnil) { + rb_raise(rb_eRuntimeError, + "could not create a server credential: nil pem_cert_chain"); + return Qnil; + } else if (pem_private_key == Qnil) { + rb_raise(rb_eRuntimeError, + "could not create a server credential: nil pem_private_key"); + return Qnil; + } + if (pem_root_certs == Qnil) { + creds = grpc_ssl_server_credentials_create( + NULL, 0, RSTRING_PTR(pem_private_key), RSTRING_LEN(pem_private_key), + RSTRING_PTR(pem_cert_chain), RSTRING_LEN(pem_cert_chain)); + } else { + creds = grpc_ssl_server_credentials_create( + RSTRING_PTR(pem_root_certs), RSTRING_LEN(pem_root_certs), + RSTRING_PTR(pem_private_key), RSTRING_LEN(pem_private_key), + RSTRING_PTR(pem_cert_chain), RSTRING_LEN(pem_cert_chain)); + } + if (creds == NULL) { + rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); + } + wrapper->wrapped = creds; + + /* Add the input objects as hidden fields to preserve them. */ + rb_ivar_set(self, id_pem_cert_chain, pem_cert_chain); + rb_ivar_set(self, id_pem_private_key, pem_private_key); + rb_ivar_set(self, id_pem_root_certs, pem_root_certs); + + return self; +} + +/* rb_cServerCredentials is the ruby class that proxies + grpc_server_credentials. */ +VALUE rb_cServerCredentials = Qnil; + +void Init_google_rpc_server_credentials() { + rb_cServerCredentials = rb_define_class_under(rb_mGoogleRpcCore, + "ServerCredentials", + rb_cObject); + + /* Allocates an object managed by the ruby runtime */ + rb_define_alloc_func(rb_cServerCredentials, grpc_rb_server_credentials_alloc); + + /* Provides a ruby constructor and support for dup/clone. */ + rb_define_method(rb_cServerCredentials, "initialize", + grpc_rb_server_credentials_init, 3); + rb_define_method(rb_cServerCredentials, "initialize_copy", + grpc_rb_server_credentials_init_copy, + 1); + + id_pem_cert_chain = rb_intern("__pem_cert_chain"); + id_pem_private_key = rb_intern("__pem_private_key"); + id_pem_root_certs = rb_intern("__pem_root_certs"); +} + +/* Gets the wrapped grpc_server_credentials from the ruby wrapper */ +grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v) { + grpc_rb_server_credentials *wrapper = NULL; + Data_Get_Struct(v, grpc_rb_server_credentials, wrapper); + return wrapper->wrapped; +} diff --git a/src/ruby/ext/grpc/rb_server_credentials.h b/src/ruby/ext/grpc/rb_server_credentials.h new file mode 100644 index 0000000000..39189511ab --- /dev/null +++ b/src/ruby/ext/grpc/rb_server_credentials.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, Google Inc. + * 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 Google Inc. 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 + * OWNER 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. + * + */ + +#ifndef GRPC_RB_SERVER_CREDENTIALS_H_ +#define GRPC_RB_SERVER_CREDENTIALS_H_ + +#include <ruby.h> +#include <grpc/grpc_security.h> + +/* rb_cServerCredentials is the ruby class whose instances proxy + grpc_server_credentials. */ +extern VALUE rb_cServerCredentials; + +/* Initializes the ruby ServerCredentials class. */ +void Init_google_rpc_server_credentials(); + +/* Gets the wrapped server_credentials from the ruby wrapper */ +grpc_server_credentials* grpc_rb_get_wrapped_server_credentials(VALUE v); + +#endif /* GRPC_RB_SERVER_CREDENTIALS_H_ */ diff --git a/src/ruby/ext/grpc/rb_status.c b/src/ruby/ext/grpc/rb_status.c index 747c47c556..4c1b6c733a 100644 --- a/src/ruby/ext/grpc/rb_status.c +++ b/src/ruby/ext/grpc/rb_status.c @@ -172,7 +172,8 @@ static VALUE grpc_rb_status_details(VALUE self) { void Init_google_status_codes() { /* Constants representing the status codes or grpc_status_code in status.h */ - VALUE rb_mStatusCodes = rb_define_module_under(rb_mGoogleRPC, "StatusCodes"); + VALUE rb_mStatusCodes = rb_define_module_under(rb_mGoogleRpcCore, + "StatusCodes"); rb_define_const(rb_mStatusCodes, "OK", INT2NUM(GRPC_STATUS_OK)); rb_define_const(rb_mStatusCodes, "CANCELLED", INT2NUM(GRPC_STATUS_CANCELLED)); rb_define_const(rb_mStatusCodes, "UNKNOWN", INT2NUM(GRPC_STATUS_UNKNOWN)); @@ -207,7 +208,7 @@ VALUE rb_cStatus = Qnil; /* Initializes the Status class. */ void Init_google_rpc_status() { - rb_cStatus = rb_define_class_under(rb_mGoogleRPC, "Status", rb_cObject); + rb_cStatus = rb_define_class_under(rb_mGoogleRpcCore, "Status", rb_cObject); /* Allocates an object whose memory is managed by the Ruby. */ rb_define_alloc_func(rb_cStatus, grpc_rb_status_alloc); diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb index 60a3b96527..1012a836b7 100644 --- a/src/ruby/lib/grpc.rb +++ b/src/ruby/lib/grpc.rb @@ -27,12 +27,13 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -require 'grpc/event' +require 'grpc/beefcake' # extends beefcake require 'grpc/errors' require 'grpc/grpc' require 'grpc/logconfig' -require 'grpc/time_consts' require 'grpc/version' +require 'grpc/core/event' +require 'grpc/core/time_consts' # alias GRPC GRPC = Google::RPC diff --git a/src/ruby/lib/grpc/beefcake.rb b/src/ruby/lib/grpc/beefcake.rb new file mode 100644 index 0000000000..e8d7f0c2cd --- /dev/null +++ b/src/ruby/lib/grpc/beefcake.rb @@ -0,0 +1,62 @@ +# Copyright 2014, Google Inc. +# 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 Google Inc. 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 +# OWNER 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. + +require 'beefcake' + +# Re-open the beefcake message module to add a static encode +# +# This is a temporary measure while beefcake is used as the default proto +# library for developing grpc ruby. Once that changes to the official proto +# library this can be removed. It's necessary to allow the update the service +# module to assume a static encode method. +# +# TODO(temiola): remove me, once official code generation is available in protoc +module Beefcake + module Message + + # additional mixin module that adds static encode method when include + module StaticEncode + + # encodes o with its instance#encode method + def encode(o) + o.encode + end + + end + + # extend self.included in Beefcake::Message to include StaticEncode + def self.included(o) + o.extend StaticEncode + o.extend Dsl + o.extend Decode + o.send(:include, Encode) + end + + end +end diff --git a/src/ruby/lib/grpc/event.rb b/src/ruby/lib/grpc/core/event.rb index c108cd4c1e..29486763d5 100644 --- a/src/ruby/lib/grpc/event.rb +++ b/src/ruby/lib/grpc/core/event.rb @@ -29,9 +29,11 @@ module Google module RPC - class Event # Add an inspect method to C-defined Event class. - def inspect - '<%s: type:%s, tag:%s result:%s>' % [self.class, type, tag, result] + module Core + class Event # Add an inspect method to C-defined Event class. + def inspect + '<%s: type:%s, tag:%s result:%s>' % [self.class, type, tag, result] + end end end end diff --git a/src/ruby/lib/grpc/time_consts.rb b/src/ruby/lib/grpc/core/time_consts.rb index 2cbab5d965..52e4c3f9b9 100644 --- a/src/ruby/lib/grpc/time_consts.rb +++ b/src/ruby/lib/grpc/core/time_consts.rb @@ -31,39 +31,42 @@ require 'grpc' module Google module RPC - module TimeConsts # re-opens a module in the C extension. + module Core - # Converts a time delta to an absolute deadline. - # - # Assumes timeish is a relative time, and converts its to an absolute, - # with following exceptions: - # - # * if timish is one of the TimeConsts.TimeSpec constants the value is - # preserved. - # * timish < 0 => TimeConsts.INFINITE_FUTURE - # * timish == 0 => TimeConsts.ZERO - # - # @param timeish [Number|TimeSpec] - # @return timeish [Number|TimeSpec] - def from_relative_time(timeish) - if timeish.is_a?TimeSpec - timeish - elsif timeish.nil? - TimeConsts::ZERO - elsif !timeish.is_a?Numeric - raise TypeError('Cannot make an absolute deadline from %s', - timeish.inspect) - elsif timeish < 0 - TimeConsts::INFINITE_FUTURE - elsif timeish == 0 - TimeConsts::ZERO - else !timeish.nil? - Time.now + timeish + module TimeConsts # re-opens a module in the C extension. + + # Converts a time delta to an absolute deadline. + # + # Assumes timeish is a relative time, and converts its to an absolute, + # with following exceptions: + # + # * if timish is one of the TimeConsts.TimeSpec constants the value is + # preserved. + # * timish < 0 => TimeConsts.INFINITE_FUTURE + # * timish == 0 => TimeConsts.ZERO + # + # @param timeish [Number|TimeSpec] + # @return timeish [Number|TimeSpec] + def from_relative_time(timeish) + if timeish.is_a?TimeSpec + timeish + elsif timeish.nil? + TimeConsts::ZERO + elsif !timeish.is_a?Numeric + raise TypeError('Cannot make an absolute deadline from %s', + timeish.inspect) + elsif timeish < 0 + TimeConsts::INFINITE_FUTURE + elsif timeish == 0 + TimeConsts::ZERO + else !timeish.nil? + Time.now + timeish + end end - end - module_function :from_relative_time + module_function :from_relative_time + end end end end diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb index d987b3966f..de35f278a1 100644 --- a/src/ruby/lib/grpc/generic/active_call.rb +++ b/src/ruby/lib/grpc/generic/active_call.rb @@ -40,8 +40,9 @@ module GRPC # The ActiveCall class provides simple methods for sending marshallable # data to a call class ActiveCall - include CompletionType - include StatusCodes + include Core::CompletionType + include Core::StatusCodes + include Core::TimeConsts attr_reader(:deadline) # client_start_invoke begins a client invocation. @@ -55,15 +56,15 @@ module GRPC # @param q [CompletionQueue] used to wait for INVOKE_ACCEPTED # @param deadline [Fixnum,TimeSpec] the deadline for INVOKE_ACCEPTED def self.client_start_invoke(call, q, deadline) - raise ArgumentError.new('not a call') unless call.is_a?Call - if !q.is_a?CompletionQueue + raise ArgumentError.new('not a call') unless call.is_a?Core::Call + if !q.is_a?Core::CompletionQueue raise ArgumentError.new('not a CompletionQueue') end invoke_accepted, client_metadata_read = Object.new, Object.new finished_tag = Object.new call.start_invoke(q, invoke_accepted, client_metadata_read, finished_tag) # wait for the invocation to be accepted - ev = q.pluck(invoke_accepted, TimeConsts::INFINITE_FUTURE) + ev = q.pluck(invoke_accepted, INFINITE_FUTURE) raise OutOfTime if ev.nil? finished_tag end @@ -93,8 +94,8 @@ module GRPC # @param started [true|false] (default true) indicates if the call has begun def initialize(call, q, marshal, unmarshal, deadline, finished_tag: nil, started: true) - raise ArgumentError.new('not a call') unless call.is_a?Call - if !q.is_a?CompletionQueue + raise ArgumentError.new('not a call') unless call.is_a?Core::Call + if !q.is_a?Core::CompletionQueue raise ArgumentError.new('not a CompletionQueue') end @call = call @@ -178,11 +179,11 @@ module GRPC # FINISHED. def writes_done(assert_finished=true) @call.writes_done(self) - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(self, INFINITE_FUTURE) assert_event_type(ev.type, FINISH_ACCEPTED) logger.debug("Writes done: waiting for finish? #{assert_finished}") if assert_finished - ev = @cq.pluck(@finished_tag, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(@finished_tag, INFINITE_FUTURE) raise "unexpected event: #{ev.inspect}" if ev.nil? return @call.status end @@ -193,9 +194,9 @@ module GRPC # It blocks until the remote endpoint acknowledges by sending a FINISHED # event. def finished - ev = @cq.pluck(@finished_tag, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(@finished_tag, INFINITE_FUTURE) raise "unexpected event: #{ev.inspect}" unless ev.type == FINISHED - if ev.result.code != StatusCodes::OK + if ev.result.code != Core::StatusCodes::OK raise BadStatus.new(ev.result.code, ev.result.details) end res = ev.result @@ -223,11 +224,11 @@ module GRPC else payload = @marshal.call(req) end - @call.start_write(ByteBuffer.new(payload), self) + @call.start_write(Core::ByteBuffer.new(payload), self) # call queue#pluck, and wait for WRITE_ACCEPTED, so as not to return # until the flow control allows another send on this call. - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(self, INFINITE_FUTURE) assert_event_type(ev.type, WRITE_ACCEPTED) ev = nil end @@ -240,8 +241,8 @@ module GRPC # FINISHED. def send_status(code=OK, details='', assert_finished=false) assert_queue_is_ready - @call.start_write_status(Status.new(code, details), self) - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + @call.start_write_status(Core::Status.new(code, details), self) + ev = @cq.pluck(self, INFINITE_FUTURE) assert_event_type(ev.type, FINISH_ACCEPTED) logger.debug("Status sent: #{code}:'#{details}'") if assert_finished @@ -257,7 +258,7 @@ module GRPC # FINISHED, it returns nil if the status is OK, otherwise raising BadStatus def remote_read @call.start_read(self) - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(self, INFINITE_FUTURE) assert_event_type(ev.type, READ) logger.debug("received req: #{ev.result.inspect}") if !ev.result.nil? @@ -291,7 +292,7 @@ module GRPC return enum_for(:each_remote_read) if !block_given? loop do resp = remote_read() - break if resp.is_a?Status # this will be an OK status, bad statii raise + break if resp.is_a?Core::Status # is an OK status, bad statii raise break if resp.nil? # the last response was received yield resp end @@ -321,7 +322,7 @@ module GRPC return enum_for(:each_remote_read_then_finish) if !block_given? loop do resp = remote_read - break if resp.is_a?Status # this will be an OK status, bad statii raise + break if resp.is_a?Core::Status # is an OK status, bad statii raise if resp.nil? # the last response was received, but not finished yet finished break @@ -339,7 +340,7 @@ module GRPC remote_send(req) writes_done(false) response = remote_read - if !response.is_a?(Status) # finish if status not yet received + if !response.is_a?(Core::Status) # finish if status not yet received finished end response @@ -360,7 +361,7 @@ module GRPC requests.each { |r| remote_send(r) } writes_done(false) response = remote_read - if !response.is_a?(Status) # finish if status not yet received + if !response.is_a?(Core::Status) # finish if status not yet received finished end response @@ -472,7 +473,7 @@ module GRPC # shutdown. def assert_queue_is_ready begin - ev = @cq.pluck(self, TimeConsts::ZERO) + ev = @cq.pluck(self, ZERO) raise "unexpected event #{ev.inspect}" unless ev.nil? rescue OutOfTime # expected, nothing should be on the queue and the deadline was ZERO, diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb index a3566e1118..91ceaa90e5 100644 --- a/src/ruby/lib/grpc/generic/bidi_call.rb +++ b/src/ruby/lib/grpc/generic/bidi_call.rb @@ -35,8 +35,9 @@ module GRPC # The BiDiCall class orchestrates exection of a BiDi stream on a client or # server. class BidiCall - include CompletionType - include StatusCodes + include Core::CompletionType + include Core::StatusCodes + include Core::TimeConsts # Creates a BidiCall. # @@ -59,8 +60,8 @@ module GRPC # @param deadline [Fixnum] the deadline for the call to complete # @param finished_tag [Object] the object used as the call's finish tag, def initialize(call, q, marshal, unmarshal, deadline, finished_tag) - raise ArgumentError.new('not a call') unless call.is_a?Call - if !q.is_a?CompletionQueue + raise ArgumentError.new('not a call') unless call.is_a?Core::Call + if !q.is_a?Core::CompletionQueue raise ArgumentError.new('not a CompletionQueue') end @call = call @@ -210,7 +211,7 @@ module GRPC # send the payload payload = @marshal.call(req) - @call.start_write(ByteBuffer.new(payload), self) + @call.start_write(Core::ByteBuffer.new(payload), self) logger.debug("rwloop: sent payload #{req.inspect}") in_write = true return [in_write, done_writing] @@ -259,10 +260,10 @@ module GRPC logger.debug('waiting for another event') if in_write or !done_reading or !pre_finished logger.debug('waiting for another event') - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(self, INFINITE_FUTURE) elsif !finished logger.debug('waiting for another event') - ev = @cq.pluck(@finish_tag, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(@finished_tag, INFINITE_FUTURE) else next # no events to wait on, but not done writing end @@ -270,7 +271,7 @@ module GRPC break if done_writing and done_reading if in_write or !done_reading logger.debug('waiting for another event') - ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE) + ev = @cq.pluck(self, INFINITE_FUTURE) else next # no events to wait on, but not done writing end diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb index fee31e3353..144f9d93ec 100644 --- a/src/ruby/lib/grpc/generic/client_stub.rb +++ b/src/ruby/lib/grpc/generic/client_stub.rb @@ -35,7 +35,7 @@ module GRPC # ClientStub represents an endpoint used to send requests to GRPC servers. class ClientStub - include StatusCodes + include Core::StatusCodes # Default deadline is 5 seconds. DEFAULT_DEADLINE = 5 @@ -62,23 +62,29 @@ module GRPC # when present, this is the default deadline used for calls # # @param host [String] the host the stub connects to - # @param q [TaggedCompletionQueue] used to wait for events - # @param channel_override [Channel] a pre-created channel + # @param q [Core::CompletionQueue] used to wait for events + # @param channel_override [Core::Channel] a pre-created channel # @param deadline [Number] the default deadline to use in requests + # @param creds [Core::Credentials] secures and/or authenticates the channel # @param kw [KeywordArgs] the channel arguments def initialize(host, q, channel_override:nil, deadline:DEFAULT_DEADLINE, + creds:nil, **kw) - if !q.is_a?CompletionQueue + if !q.is_a?Core::CompletionQueue raise ArgumentError.new('not a CompletionQueue') end @host = host if !channel_override.nil? ch = channel_override - raise ArgumentError.new('not a Channel') unless ch.is_a?(Channel) + raise ArgumentError.new('not a Channel') unless ch.is_a?(Core::Channel) + elsif creds.nil? + ch = Core::Channel.new(host, kw) + elsif !creds.is_a?(Core::Credentials) + raise ArgumentError.new('not a Credentials') else - ch = Channel.new(host, **kw) + ch = Core::Channel.new(host, kw, creds) end @deadline = deadline @@ -347,7 +353,7 @@ module GRPC # @param unmarshal [Function] f(string)->obj that unmarshals responses # @param deadline [TimeConst] def new_active_call(ch, marshal, unmarshal, deadline=nil) - absolute_deadline = TimeConsts.from_relative_time(deadline) + absolute_deadline = Core::TimeConsts.from_relative_time(deadline) call = @ch.create_call(ch, @host, absolute_deadline) ActiveCall.new(call, @queue, marshal, unmarshal, absolute_deadline, started:false) diff --git a/src/ruby/lib/grpc/generic/rpc_desc.rb b/src/ruby/lib/grpc/generic/rpc_desc.rb index 43b4d4ffc6..eef886a72f 100644 --- a/src/ruby/lib/grpc/generic/rpc_desc.rb +++ b/src/ruby/lib/grpc/generic/rpc_desc.rb @@ -34,6 +34,7 @@ module GRPC # RpcDesc is a Descriptor of an RPC method. class RpcDesc < Struct.new(:name, :input, :output, :marshal_method, :unmarshal_method) + include Core::StatusCodes # Used to wrap a message class to indicate that it needs to be streamed. class Stream @@ -46,7 +47,7 @@ module GRPC # @return [Proc] { |instance| marshalled(instance) } def marshal_proc - Proc.new { |o| o.method(marshal_method).call.to_s } + Proc.new { |o| o.class.method(marshal_method).call(o).to_s } end # @param [:input, :output] target determines whether to produce the an @@ -82,7 +83,7 @@ module GRPC else # is a bidi_stream active_call.run_server_bidi(mth) end - send_status(active_call, StatusCodes::OK, 'OK') + send_status(active_call, OK, 'OK') active_call.finished rescue BadStatus => e # this is raised by handlers that want GRPC to send an application @@ -97,7 +98,7 @@ module GRPC # This is raised when active_call#method.call exceeeds the deadline # event. Send a status of deadline exceeded logger.warn("late call: #{active_call}") - send_status(active_call, StatusCodes::DEADLINE_EXCEEDED, 'late') + send_status(active_call, DEADLINE_EXCEEDED, 'late') rescue EventError => e # This is raised by GRPC internals but should rarely, if ever happen. # Log it, but don't notify the other endpoint.. @@ -107,7 +108,7 @@ module GRPC # Send back a UNKNOWN status to the client logger.warn("failed handler: #{active_call}; sending status:UNKNOWN") logger.warn(e) - send_status(active_call, StatusCodes::UNKNOWN, 'no reason given') + send_status(active_call, UNKNOWN, 'no reason given') end end diff --git a/src/ruby/lib/grpc/generic/rpc_server.rb b/src/ruby/lib/grpc/generic/rpc_server.rb index e6efdc32c1..ebbf3f9780 100644 --- a/src/ruby/lib/grpc/generic/rpc_server.rb +++ b/src/ruby/lib/grpc/generic/rpc_server.rb @@ -38,7 +38,8 @@ module GRPC # RpcServer hosts a number of services and makes them available on the # network. class RpcServer - include CompletionType + include Core::CompletionType + include Core::TimeConsts extend ::Forwardable def_delegators :@server, :add_http2_port @@ -57,7 +58,7 @@ module GRPC # instance, however other arbitrary are allowed and when present are used # to configure the listeninng connection set up by the RpcServer. # - # * server_override: which if passed must be a [GRPC::Server]. When + # * server_override: which if passed must be a [GRPC::Core::Server]. When # present. # # * poll_period: when present, the server polls for new events with this @@ -70,30 +71,38 @@ module GRPC # completion_queue that the server uses to receive network events, # otherwise its creates a new instance itself # + # * creds: [GRPC::Core::ServerCredentials] + # the credentials used to secure the server + # # * max_waiting_requests: the maximum number of requests that are not # being handled to allow. When this limit is exceeded, the server responds # with not available to new requests def initialize(pool_size:DEFAULT_POOL_SIZE, max_waiting_requests:DEFAULT_MAX_WAITING_REQUESTS, - poll_period:TimeConsts::INFINITE_FUTURE, + poll_period:INFINITE_FUTURE, completion_queue_override:nil, + creds:nil, server_override:nil, **kw) if !completion_queue_override.nil? cq = completion_queue_override - if !cq.is_a?(CompletionQueue) + if !cq.is_a?(Core::CompletionQueue) raise ArgumentError.new('not a CompletionQueue') end else - cq = CompletionQueue.new + cq = Core::CompletionQueue.new end @cq = cq if !server_override.nil? srv = server_override - raise ArgumentError.new('not a Server') unless srv.is_a?(Server) + raise ArgumentError.new('not a Server') unless srv.is_a?(Core::Server) + elsif creds.nil? + srv = Core::Server.new(@cq, kw) + elsif !creds.is_a?(Core::ServerCredentials) + raise ArgumentError.new('not a ServerCredentials') else - srv = Server.new(@cq, **kw) + srv = Core::Server.new(@cq, kw, creds) end @server = srv @@ -236,7 +245,7 @@ module GRPC # Accept the call. This is necessary even if a status is to be sent back # immediately finished_tag = Object.new - call_queue = CompletionQueue.new + call_queue = Core::CompletionQueue.new call.accept(call_queue, finished_tag) # Send UNAVAILABLE if there are too many unprocessed jobs diff --git a/src/ruby/lib/grpc/generic/service.rb b/src/ruby/lib/grpc/generic/service.rb index 1a3d0dc63e..05bb0af809 100644 --- a/src/ruby/lib/grpc/generic/service.rb +++ b/src/ruby/lib/grpc/generic/service.rb @@ -88,12 +88,12 @@ module GRPC # - unmarshal_class method must be a class method on the serializable # message type that takes a string (byte stream) and produces and object # - # - marshal_instance_method is called on a serializable message instance + # - marshal_class_method is called on a serializable message instance # and produces a serialized string. # # The Dsl verifies that the types in the descriptor have both the # unmarshal and marshal methods. - attr_writer(:marshal_instance_method, :unmarshal_class_method) + attr_writer(:marshal_class_method, :unmarshal_class_method) attr_accessor(:service_name) # Adds an RPC spec. @@ -113,7 +113,7 @@ module GRPC assert_can_marshal(input) assert_can_marshal(output) rpc_descs[name] = RpcDesc.new(name, input, output, - marshal_instance_method, + marshal_class_method, unmarshal_class_method) end @@ -125,8 +125,8 @@ module GRPC end # the name of the instance method used to marshal events to a byte stream. - def marshal_instance_method - @marshal_instance_method ||= :marshal + def marshal_class_method + @marshal_class_method ||= :marshal end # the name of the class method used to unmarshal from a byte stream. @@ -144,9 +144,9 @@ module GRPC raise ArgumentError, "#{cls} needs #{cls}.#{mth}" end - mth = marshal_instance_method - if !cls.instance_methods.include?(mth) - raise ArgumentError, "#{cls} needs #{cls}.new.#{mth}" + mth = marshal_class_method + if !cls.methods.include?(mth) + raise ArgumentError, "#{cls} needs #{cls}.#{mth}" end end @@ -173,7 +173,7 @@ module GRPC # @param kw [KeywordArgs] the channel arguments, plus any optional # args for configuring the client's channel def initialize(host, **kw) - super(host, CompletionQueue.new, **kw) + super(host, Core::CompletionQueue.new, **kw) end # Used define_method to add a method for each rpc_desc. Each method diff --git a/src/ruby/spec/alloc_spec.rb b/src/ruby/spec/alloc_spec.rb index 99cc39d0ba..305405e9bd 100644 --- a/src/ruby/spec/alloc_spec.rb +++ b/src/ruby/spec/alloc_spec.rb @@ -31,15 +31,15 @@ require 'grpc' describe 'Wrapped classes where .new cannot create an instance' do - describe GRPC::Event do + describe GRPC::Core::Event do it 'should fail .new fail with a runtime error' do - expect { GRPC::Event.new }.to raise_error(TypeError) + expect { GRPC::Core::Event.new }.to raise_error(TypeError) end end - describe GRPC::Call do + describe GRPC::Core::Call do it 'should fail .new fail with a runtime error' do - expect { GRPC::Event.new }.to raise_error(TypeError) + expect { GRPC::Core::Event.new }.to raise_error(TypeError) end end diff --git a/src/ruby/spec/byte_buffer_spec.rb b/src/ruby/spec/byte_buffer_spec.rb index d4d3a692b1..b89d7f3640 100644 --- a/src/ruby/spec/byte_buffer_spec.rb +++ b/src/ruby/spec/byte_buffer_spec.rb @@ -29,25 +29,25 @@ require 'grpc' -describe GRPC::ByteBuffer do +describe GRPC::Core::ByteBuffer do describe '#new' do it 'is constructed from a string' do - expect { GRPC::ByteBuffer.new('#new') }.not_to raise_error + expect { GRPC::Core::ByteBuffer.new('#new') }.not_to raise_error end it 'can be constructed from the empty string' do - expect { GRPC::ByteBuffer.new('') }.not_to raise_error + expect { GRPC::Core::ByteBuffer.new('') }.not_to raise_error end it 'cannot be constructed from nil' do - expect { GRPC::ByteBuffer.new(nil) }.to raise_error TypeError + expect { GRPC::Core::ByteBuffer.new(nil) }.to raise_error TypeError end it 'cannot be constructed from non-strings' do [1, Object.new, :a_symbol].each do |x| - expect { GRPC::ByteBuffer.new(x) }.to raise_error TypeError + expect { GRPC::Core::ByteBuffer.new(x) }.to raise_error TypeError end end @@ -55,13 +55,13 @@ describe GRPC::ByteBuffer do describe '#to_s' do it 'is the string value the ByteBuffer was constructed with' do - expect(GRPC::ByteBuffer.new('#to_s').to_s).to eq('#to_s') + expect(GRPC::Core::ByteBuffer.new('#to_s').to_s).to eq('#to_s') end end describe '#dup' do it 'makes an instance whose #to_s is the original string value' do - bb = GRPC::ByteBuffer.new('#dup') + bb = GRPC::Core::ByteBuffer.new('#dup') a_copy = bb.dup expect(a_copy.to_s).to eq('#dup') expect(a_copy.dup.to_s).to eq('#dup') diff --git a/src/ruby/spec/call_spec.rb b/src/ruby/spec/call_spec.rb index 339f2c1a94..9228f7df27 100644 --- a/src/ruby/spec/call_spec.rb +++ b/src/ruby/spec/call_spec.rb @@ -30,9 +30,9 @@ require 'grpc' require 'port_picker' -include GRPC::StatusCodes +include GRPC::Core::StatusCodes -describe GRPC::RpcErrors do +describe GRPC::Core::RpcErrors do before(:each) do @known_types = { @@ -60,24 +60,24 @@ describe GRPC::RpcErrors do end it 'should have symbols for all the known error codes' do - m = GRPC::RpcErrors + m = GRPC::Core::RpcErrors syms_and_codes = m.constants.collect { |c| [c, m.const_get(c)] } expect(Hash[syms_and_codes]).to eq(@known_types) end end -describe GRPC::Call do +describe GRPC::Core::Call do before(:each) do @tag = Object.new - @client_queue = GRPC::CompletionQueue.new - @server_queue = GRPC::CompletionQueue.new + @client_queue = GRPC::Core::CompletionQueue.new + @server_queue = GRPC::Core::CompletionQueue.new port = find_unused_tcp_port host = "localhost:#{port}" - @server = GRPC::Server.new(@server_queue, nil) + @server = GRPC::Core::Server.new(@server_queue, nil) @server.add_http2_port(host) - @ch = GRPC::Channel.new(host, nil) + @ch = GRPC::Core::Channel.new(host, nil) end after(:each) do @@ -86,29 +86,29 @@ describe GRPC::Call do describe '#start_read' do it 'should fail if called immediately' do - expect { make_test_call.start_read(@tag) }.to raise_error GRPC::CallError + expect { make_test_call.start_read(@tag) }.to raise_error GRPC::Core::CallError end end describe '#start_write' do it 'should fail if called immediately' do - bytes = GRPC::ByteBuffer.new('test string') + bytes = GRPC::Core::ByteBuffer.new('test string') expect { make_test_call.start_write(bytes, @tag) } - .to raise_error GRPC::CallError + .to raise_error GRPC::Core::CallError end end describe '#start_write_status' do it 'should fail if called immediately' do - sts = GRPC::Status.new(153, 'test detail') + sts = GRPC::Core::Status.new(153, 'test detail') expect { make_test_call.start_write_status(sts, @tag) } - .to raise_error GRPC::CallError + .to raise_error GRPC::Core::CallError end end describe '#writes_done' do it 'should fail if called immediately' do - expect { make_test_call.writes_done(@tag) }.to raise_error GRPC::CallError + expect { make_test_call.writes_done(@tag) }.to raise_error GRPC::Core::CallError end end @@ -126,9 +126,9 @@ describe GRPC::Call do call = make_test_call expect(call.start_invoke(@client_queue, @tag, @tag, @tag)).to be_nil ev = @client_queue.next(deadline) - expect(ev.call).to be_a(GRPC::Call) + expect(ev.call).to be_a(GRPC::Core::Call) expect(ev.tag).to be(@tag) - expect(ev.type).to be(GRPC::CompletionType::INVOKE_ACCEPTED) + expect(ev.type).to be(GRPC::Core::CompletionType::INVOKE_ACCEPTED) expect(ev.call).to_not be(call) end end @@ -138,12 +138,12 @@ describe GRPC::Call do call = make_test_call call.start_invoke(@client_queue, @tag, @tag, @tag) ev = @client_queue.next(deadline) - expect(ev.type).to be(GRPC::CompletionType::INVOKE_ACCEPTED) - expect(call.start_write(GRPC::ByteBuffer.new('test_start_write'), + expect(ev.type).to be(GRPC::Core::CompletionType::INVOKE_ACCEPTED) + expect(call.start_write(GRPC::Core::ByteBuffer.new('test_start_write'), @tag)).to be_nil ev = @client_queue.next(deadline) - expect(ev.call).to be_a(GRPC::Call) - expect(ev.type).to be(GRPC::CompletionType::WRITE_ACCEPTED) + expect(ev.call).to be_a(GRPC::Core::Call) + expect(ev.type).to be(GRPC::Core::CompletionType::WRITE_ACCEPTED) expect(ev.tag).to be(@tag) end end @@ -151,7 +151,7 @@ describe GRPC::Call do describe '#status' do it 'can save the status and read it back' do call = make_test_call - sts = GRPC::Status.new(OK, 'OK') + sts = GRPC::Core::Status.new(OK, 'OK') expect { call.status = sts }.not_to raise_error expect(call.status).to be(sts) end diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb index bd46bffc10..d2686127bb 100644 --- a/src/ruby/spec/channel_spec.rb +++ b/src/ruby/spec/channel_spec.rb @@ -30,135 +30,166 @@ require 'grpc' require 'port_picker' -module GRPC +def load_test_certs + test_root = File.join(File.dirname(__FILE__), 'testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(test_root, f)).read } +end - describe Channel do +describe GRPC::Core::Channel do - before(:each) do - @cq = CompletionQueue.new - end - describe '#new' do + def create_test_cert + GRPC::Core::Credentials.new(load_test_certs[0]) + end - it 'take a host name without channel args' do - expect { Channel.new('dummy_host', nil) }.not_to raise_error - end + before(:each) do + @cq = GRPC::Core::CompletionQueue.new + end - it 'does not take a hash with bad keys as channel args' do - blk = construct_with_args(Object.new => 1) - expect(&blk).to raise_error TypeError - blk = construct_with_args(1 => 1) - expect(&blk).to raise_error TypeError - end + shared_examples '#new' do - it 'does not take a hash with bad values as channel args' do - blk = construct_with_args(:symbol => Object.new) - expect(&blk).to raise_error TypeError - blk = construct_with_args('1' => Hash.new) - expect(&blk).to raise_error TypeError - end + it 'take a host name without channel args' do + expect { GRPC::Core::Channel.new('dummy_host', nil) }.not_to raise_error + end - it 'can take a hash with a symbol key as channel args' do - blk = construct_with_args(:a_symbol => 1) - expect(&blk).to_not raise_error - end + it 'does not take a hash with bad keys as channel args' do + blk = construct_with_args(Object.new => 1) + expect(&blk).to raise_error TypeError + blk = construct_with_args(1 => 1) + expect(&blk).to raise_error TypeError + end - it 'can take a hash with a string key as channel args' do - blk = construct_with_args('a_symbol' => 1) - expect(&blk).to_not raise_error - end + it 'does not take a hash with bad values as channel args' do + blk = construct_with_args(:symbol => Object.new) + expect(&blk).to raise_error TypeError + blk = construct_with_args('1' => Hash.new) + expect(&blk).to raise_error TypeError + end - it 'can take a hash with a string value as channel args' do - blk = construct_with_args(:a_symbol => '1') - expect(&blk).to_not raise_error - end + it 'can take a hash with a symbol key as channel args' do + blk = construct_with_args(:a_symbol => 1) + expect(&blk).to_not raise_error + end - it 'can take a hash with a symbol value as channel args' do - blk = construct_with_args(:a_symbol => :another_symbol) - expect(&blk).to_not raise_error - end + it 'can take a hash with a string key as channel args' do + blk = construct_with_args('a_symbol' => 1) + expect(&blk).to_not raise_error + end - it 'can take a hash with a numeric value as channel args' do - blk = construct_with_args(:a_symbol => 1) - expect(&blk).to_not raise_error - end + it 'can take a hash with a string value as channel args' do + blk = construct_with_args(:a_symbol => '1') + expect(&blk).to_not raise_error + end - it 'can take a hash with many args as channel args' do - args = Hash[127.times.collect { |x| [x.to_s, x] } ] - blk = construct_with_args(args) - expect(&blk).to_not raise_error - end + it 'can take a hash with a symbol value as channel args' do + blk = construct_with_args(:a_symbol => :another_symbol) + expect(&blk).to_not raise_error + end + it 'can take a hash with a numeric value as channel args' do + blk = construct_with_args(:a_symbol => 1) + expect(&blk).to_not raise_error end - describe '#create_call' do - it 'creates a call OK' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) + it 'can take a hash with many args as channel args' do + args = Hash[127.times.collect { |x| [x.to_s, x] } ] + blk = construct_with_args(args) + expect(&blk).to_not raise_error + end - deadline = Time.now + 5 - expect(ch.create_call('dummy_method', 'dummy_host', deadline)) - .not_to be(nil) - end + end - it 'raises an error if called on a closed channel' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) - ch.close - - deadline = Time.now + 5 - blk = Proc.new do - ch.create_call('dummy_method', 'dummy_host', deadline) - end - expect(&blk).to raise_error(RuntimeError) - end + describe '#new for secure channels' do + def construct_with_args(a) + Proc.new { GRPC::Core::Channel.new('dummy_host', a, create_test_cert) } end - describe '#destroy' do - it 'destroys a channel ok' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) - blk = Proc.new { ch.destroy } - expect(&blk).to_not raise_error - end + it_behaves_like '#new' + end - it 'can be called more than once without error' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) - blk = Proc.new { ch.destroy } - blk.call - expect(&blk).to_not raise_error - end + describe '#new for insecure channels' do + it_behaves_like '#new' + + def construct_with_args(a) + Proc.new { GRPC::Core::Channel.new('dummy_host', a) } end + end - describe '#close' do - it 'closes a channel ok' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) - blk = Proc.new { ch.close } - expect(&blk).to_not raise_error + describe '#create_call' do + it 'creates a call OK' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + + deadline = Time.now + 5 + + blk = Proc.new do + ch.create_call('dummy_method', 'dummy_host', deadline) end + expect(&blk).to_not raise_error + end - it 'can be called more than once without error' do - port = find_unused_tcp_port - host = "localhost:#{port}" - ch = Channel.new(host, nil) - blk = Proc.new { ch.close } - blk.call - expect(&blk).to_not raise_error + it 'raises an error if called on a closed channel' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + ch.close + + deadline = Time.now + 5 + blk = Proc.new do + ch.create_call('dummy_method', 'dummy_host', deadline) end + expect(&blk).to raise_error(RuntimeError) end - def construct_with_args(a) - Proc.new {Channel.new('dummy_host', a)} + end + + describe '#destroy' do + it 'destroys a channel ok' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + blk = Proc.new { ch.destroy } + expect(&blk).to_not raise_error + end + + it 'can be called more than once without error' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + blk = Proc.new { ch.destroy } + blk.call + expect(&blk).to_not raise_error + end + end + + describe '::SSL_TARGET' do + + it 'is a symbol' do + expect(GRPC::Core::Channel::SSL_TARGET).to be_a(Symbol) + end + + end + + describe '#close' do + it 'closes a channel ok' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + blk = Proc.new { ch.close } + expect(&blk).to_not raise_error end + it 'can be called more than once without error' do + port = find_unused_tcp_port + host = "localhost:#{port}" + ch = GRPC::Core::Channel.new(host, nil) + blk = Proc.new { ch.close } + blk.call + expect(&blk).to_not raise_error + end end end diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb index 64068ab391..c96fe979ac 100644 --- a/src/ruby/spec/client_server_spec.rb +++ b/src/ruby/spec/client_server_spec.rb @@ -31,8 +31,14 @@ require 'grpc' require 'port_picker' require 'spec_helper' -include GRPC::CompletionType -include GRPC +include GRPC::Core::CompletionType +include GRPC::Core + +def load_test_certs + test_root = File.join(File.dirname(__FILE__), 'testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(test_root, f)).read } +end shared_context 'setup: tags' do @@ -327,9 +333,9 @@ describe 'the http client/server' do before(:example) do port = find_unused_tcp_port host = "localhost:#{port}" - @client_queue = GRPC::CompletionQueue.new - @server_queue = GRPC::CompletionQueue.new - @server = GRPC::Server.new(@server_queue, nil) + @client_queue = GRPC::Core::CompletionQueue.new + @server_queue = GRPC::Core::CompletionQueue.new + @server = GRPC::Core::Server.new(@server_queue, nil) @server.add_http2_port(host) @server.start @ch = Channel.new(host, nil) @@ -339,6 +345,34 @@ describe 'the http client/server' do @server.close end + it_behaves_like 'basic GRPC message delivery is OK' do + end + + it_behaves_like 'GRPC metadata delivery works OK' do + end + +end + +describe 'the secure http client/server' do + + before(:example) do + certs = load_test_certs + port = find_unused_tcp_port + host = "localhost:#{port}" + @client_queue = GRPC::Core::CompletionQueue.new + @server_queue = GRPC::Core::CompletionQueue.new + server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2]) + @server = GRPC::Core::Server.new(@server_queue, nil, server_creds) + @server.add_http2_port(host, true) + @server.start + args = {Channel::SSL_TARGET => 'foo.test.google.com'} + @ch = Channel.new(host, args, + GRPC::Core::Credentials.new(certs[0], nil, nil)) + end + + after(:example) do + @server.close + end it_behaves_like 'basic GRPC message delivery is OK' do end diff --git a/src/ruby/spec/completion_queue_spec.rb b/src/ruby/spec/completion_queue_spec.rb index 37432443a9..50f74b5826 100644 --- a/src/ruby/spec/completion_queue_spec.rb +++ b/src/ruby/spec/completion_queue_spec.rb @@ -29,26 +29,25 @@ require 'grpc' -describe GRPC::CompletionQueue do +describe GRPC::Core::CompletionQueue do describe '#new' do it 'is constructed successufully' do - expect { GRPC::CompletionQueue.new }.not_to raise_error - expect(GRPC::CompletionQueue.new).to be_a(GRPC::CompletionQueue) + expect { GRPC::Core::CompletionQueue.new }.not_to raise_error end end describe '#next' do it 'can be called without failing' do - ch = GRPC::CompletionQueue.new + ch = GRPC::Core::CompletionQueue.new expect { ch.next(3) }.not_to raise_error end it 'can be called with the time constants' do - ch = GRPC::CompletionQueue.new + ch = GRPC::Core::CompletionQueue.new # don't use INFINITE_FUTURE, as there we have no events. non_blocking_consts = [:ZERO, :INFINITE_PAST] - m = GRPC::TimeConsts + m = GRPC::Core::TimeConsts non_blocking_consts.each do |c| a_time = m.const_get(c) expect { ch.next(a_time) }.not_to raise_error @@ -59,16 +58,16 @@ describe GRPC::CompletionQueue do describe '#pluck' do it 'can be called without failing' do - ch = GRPC::CompletionQueue.new + ch = GRPC::Core::CompletionQueue.new tag = Object.new expect { ch.pluck(tag, 3) }.not_to raise_error end it 'can be called with the time constants' do - ch = GRPC::CompletionQueue.new + ch = GRPC::Core::CompletionQueue.new # don't use INFINITE_FUTURE, as there we have no events. non_blocking_consts = [:ZERO, :INFINITE_PAST] - m = GRPC::TimeConsts + m = GRPC::Core::TimeConsts tag = Object.new non_blocking_consts.each do |c| a_time = m.const_get(c) diff --git a/src/ruby/spec/credentials_spec.rb b/src/ruby/spec/credentials_spec.rb new file mode 100644 index 0000000000..4d932db937 --- /dev/null +++ b/src/ruby/spec/credentials_spec.rb @@ -0,0 +1,87 @@ +# Copyright 2014, Google Inc. +# 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 Google Inc. 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 +# OWNER 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. + +require 'grpc' + + +def load_test_certs + test_root = File.join(File.dirname(__FILE__), 'testdata') + files = ['ca.pem', 'server1.pem', 'server1.key'] + files.map { |f| File.open(File.join(test_root, f)).read } +end + +Credentials = GRPC::Core::Credentials + +describe Credentials do + + describe '#new' do + + it 'can be constructed with fake inputs' do + expect { Credentials.new('root_certs', 'key', 'cert') }.not_to raise_error + end + + it 'it can be constructed using specific test certificates' do + certs = load_test_certs + expect { Credentials.new(*certs) }.not_to raise_error + end + + it 'can be constructed with server roots certs only' do + root_cert, _, _ = load_test_certs + expect { Credentials.new(root_cert) }.not_to raise_error + end + + it 'cannot be constructed with a nil server roots' do + _, client_key, client_chain = load_test_certs + blk = Proc.new { Credentials.new(nil, client_key, client_chain) } + expect(&blk).to raise_error + end + + end + + describe '#compose' do + + it 'can be completed OK' do + certs = load_test_certs + cred1 = Credentials.new(*certs) + cred2 = Credentials.new(*certs) + expect { cred1.compose(cred2) }.to_not raise_error + end + + end + + describe 'Credentials#default' do + + it 'is not implemented yet' do + expect { Credentials.default() }.to raise_error RuntimeError + end + + end + + +end diff --git a/src/ruby/spec/event_spec.rb b/src/ruby/spec/event_spec.rb index 19b9754d31..a61b926dea 100644 --- a/src/ruby/spec/event_spec.rb +++ b/src/ruby/spec/event_spec.rb @@ -29,7 +29,7 @@ require 'grpc' -describe GRPC::CompletionType do +describe GRPC::Core::CompletionType do before(:each) do @known_types = { @@ -46,9 +46,9 @@ describe GRPC::CompletionType do end it 'should have all the known types' do - mod = GRPC::CompletionType - expect(Hash[mod.constants.collect { |c| [c, mod.const_get(c)] }]) - .to eq(@known_types) + mod = GRPC::Core::CompletionType + blk = Proc.new { Hash[mod.constants.collect { |c| [c, mod.const_get(c)] }] } + expect(blk.call).to eq(@known_types) end end diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb index 872625ccf0..ceeef2a1d8 100644 --- a/src/ruby/spec/generic/active_call_spec.rb +++ b/src/ruby/spec/generic/active_call_spec.rb @@ -31,291 +31,289 @@ require 'grpc' require 'grpc/generic/active_call' require_relative '../port_picker' -module GRPC +ActiveCall = GRPC::ActiveCall + +describe GRPC::ActiveCall do + + before(:each) do + @pass_through = Proc.new { |x| x } + @server_tag = Object.new + @server_finished_tag = Object.new + @tag = Object.new + + @client_queue = GRPC::Core::CompletionQueue.new + @server_queue = GRPC::Core::CompletionQueue.new + port = find_unused_tcp_port + host = "localhost:#{port}" + @server = GRPC::Core::Server.new(@server_queue, nil) + @server.add_http2_port(host) + @server.start + @ch = GRPC::Core::Channel.new(host, nil) + end - describe ActiveCall do + after(:each) do + @server.close + end + describe 'restricted view methods' do before(:each) do - @pass_through = Proc.new { |x| x } - @server_tag = Object.new - @server_finished_tag = Object.new - @tag = Object.new - - @client_queue = CompletionQueue.new - @server_queue = CompletionQueue.new - port = find_unused_tcp_port - host = "localhost:#{port}" - @server = GRPC::Server.new(@server_queue, nil) - @server.add_http2_port(host) - @server.start - @ch = GRPC::Channel.new(host, nil) - end - - after(:each) do - @server.close + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + @client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) end - describe 'restricted view methods' do - before(:each) do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - @client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - end - - describe '#multi_req_view' do - it 'exposes a fixed subset of the ActiveCall methods' do - want = ['cancelled', 'deadline', 'each_remote_read', 'shutdown'] - v = @client_call.multi_req_view - want.each do |w| - expect(v.methods.include?(w)) - end + describe '#multi_req_view' do + it 'exposes a fixed subset of the ActiveCall methods' do + want = ['cancelled', 'deadline', 'each_remote_read', 'shutdown'] + v = @client_call.multi_req_view + want.each do |w| + expect(v.methods.include?(w)) end end + end - describe '#single_req_view' do - it 'exposes a fixed subset of the ActiveCall methods' do - want = ['cancelled', 'deadline', 'shutdown'] - v = @client_call.single_req_view - want.each do |w| - expect(v.methods.include?(w)) - end + describe '#single_req_view' do + it 'exposes a fixed subset of the ActiveCall methods' do + want = ['cancelled', 'deadline', 'shutdown'] + v = @client_call.single_req_view + want.each do |w| + expect(v.methods.include?(w)) end end end + end - describe '#remote_send' do - it 'allows a client to send a payload to the server' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - @client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - @client_call.remote_send(msg) - - # check that server rpc new was received - @server.request_call(@server_tag) - ev = @server_queue.next(deadline) - expect(ev.type).to be(CompletionType::SERVER_RPC_NEW) - expect(ev.call).to be_a(Call) - expect(ev.tag).to be(@server_tag) - - # Accept the call, and verify that the server reads the response ok. - ev.call.accept(@client_queue, @server_tag) - server_call = ActiveCall.new(ev.call, @client_queue, @pass_through, - @pass_through, deadline) - expect(server_call.remote_read).to eq(msg) - end - - it 'marshals the payload using the marshal func' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - marshal = Proc.new { |x| 'marshalled:' + x } - client_call = ActiveCall.new(call, @client_queue, marshal, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - - # confirm that the message was marshalled - @server.request_call(@server_tag) - ev = @server_queue.next(deadline) - ev.call.accept(@client_queue, @server_tag) - server_call = ActiveCall.new(ev.call, @client_queue, @pass_through, - @pass_through, deadline) - expect(server_call.remote_read).to eq('marshalled:' + msg) - end - + describe '#remote_send' do + it 'allows a client to send a payload to the server' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + @client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + @client_call.remote_send(msg) + + # check that server rpc new was received + @server.request_call(@server_tag) + ev = @server_queue.next(deadline) + expect(ev.type).to be(CompletionType::SERVER_RPC_NEW) + expect(ev.call).to be_a(Call) + expect(ev.tag).to be(@server_tag) + + # Accept the call, and verify that the server reads the response ok. + ev.call.accept(@client_queue, @server_tag) + server_call = ActiveCall.new(ev.call, @client_queue, @pass_through, + @pass_through, deadline) + expect(server_call.remote_read).to eq(msg) end - describe '#remote_read' do - it 'reads the response sent by a server' do - call, pass_through = make_test_call, Proc.new { |x| x } - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - expect(client_call.remote_read).to eq('server_response') - end - - it 'get a nil msg before a status when an OK status is sent' do - call, pass_through = make_test_call, Proc.new { |x| x } - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - client_call.writes_done(false) - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - server_call.send_status(StatusCodes::OK, 'OK') - expect(client_call.remote_read).to eq('server_response') - res = client_call.remote_read - expect(res).to be_nil - end + it 'marshals the payload using the marshal func' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + marshal = Proc.new { |x| 'marshalled:' + x } + client_call = ActiveCall.new(call, @client_queue, marshal, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + + # confirm that the message was marshalled + @server.request_call(@server_tag) + ev = @server_queue.next(deadline) + ev.call.accept(@client_queue, @server_tag) + server_call = ActiveCall.new(ev.call, @client_queue, @pass_through, + @pass_through, deadline) + expect(server_call.remote_read).to eq('marshalled:' + msg) + end + end - it 'unmarshals the response using the unmarshal func' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - unmarshal = Proc.new { |x| 'unmarshalled:' + x } - client_call = ActiveCall.new(call, @client_queue, @pass_through, - unmarshal, deadline, - finished_tag: finished_tag) - - # confirm the client receives the unmarshalled message - msg = 'message is a string' - client_call.remote_send(msg) - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - expect(client_call.remote_read).to eq('unmarshalled:server_response') - end + describe '#remote_read' do + it 'reads the response sent by a server' do + call, pass_through = make_test_call, Proc.new { |x| x } + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + expect(client_call.remote_read).to eq('server_response') + end + it 'get a nil msg before a status when an OK status is sent' do + call, pass_through = make_test_call, Proc.new { |x| x } + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + client_call.writes_done(false) + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + server_call.send_status(StatusCodes::OK, 'OK') + expect(client_call.remote_read).to eq('server_response') + res = client_call.remote_read + expect(res).to be_nil end - describe '#each_remote_read' do - it 'creates an Enumerator' do - call = make_test_call - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline) - expect(client_call.each_remote_read).to be_a(Enumerator) - end - it 'the returns an enumerator that can read n responses' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is 4a string' - reply = 'server_response' - client_call.remote_send(msg) - server_call = expect_server_to_receive(msg) - e = client_call.each_remote_read - n = 3 # arbitrary value > 1 - n.times do - server_call.remote_send(reply) - expect(e.next).to eq(reply) - end - end + it 'unmarshals the response using the unmarshal func' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + unmarshal = Proc.new { |x| 'unmarshalled:' + x } + client_call = ActiveCall.new(call, @client_queue, @pass_through, + unmarshal, deadline, + finished_tag: finished_tag) + + # confirm the client receives the unmarshalled message + msg = 'message is a string' + client_call.remote_send(msg) + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + expect(client_call.remote_read).to eq('unmarshalled:server_response') + end - it 'the returns an enumerator that stops after an OK Status' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - reply = 'server_response' - client_call.remote_send(msg) - client_call.writes_done(false) - server_call = expect_server_to_receive(msg) - e = client_call.each_remote_read - n = 3 # arbitrary value > 1 - n.times do - server_call.remote_send(reply) - expect(e.next).to eq(reply) - end - server_call.send_status(StatusCodes::OK, 'OK') - expect { e.next }.to raise_error(StopIteration) - end + end + describe '#each_remote_read' do + it 'creates an Enumerator' do + call = make_test_call + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline) + expect(client_call.each_remote_read).to be_a(Enumerator) end - describe '#writes_done' do - it 'finishes ok if the server sends a status response' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - expect { client_call.writes_done(false) }.to_not raise_error - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - expect(client_call.remote_read).to eq('server_response') - server_call.send_status(StatusCodes::OK, 'status code is OK') - expect { server_call.finished }.to_not raise_error - expect { client_call.finished }.to_not raise_error + it 'the returns an enumerator that can read n responses' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is 4a string' + reply = 'server_response' + client_call.remote_send(msg) + server_call = expect_server_to_receive(msg) + e = client_call.each_remote_read + n = 3 # arbitrary value > 1 + n.times do + server_call.remote_send(reply) + expect(e.next).to eq(reply) end + end - it 'finishes ok if the server sends an early status response' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - server_call.send_status(StatusCodes::OK, 'status code is OK') - expect(client_call.remote_read).to eq('server_response') - expect { client_call.writes_done(false) }.to_not raise_error - expect { server_call.finished }.to_not raise_error - expect { client_call.finished }.to_not raise_error + it 'the returns an enumerator that stops after an OK Status' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + reply = 'server_response' + client_call.remote_send(msg) + client_call.writes_done(false) + server_call = expect_server_to_receive(msg) + e = client_call.each_remote_read + n = 3 # arbitrary value > 1 + n.times do + server_call.remote_send(reply) + expect(e.next).to eq(reply) end + server_call.send_status(StatusCodes::OK, 'OK') + expect { e.next }.to raise_error(StopIteration) + end - it 'finishes ok if writes_done is true' do - call = make_test_call - finished_tag = ActiveCall.client_start_invoke(call, @client_queue, - deadline) - client_call = ActiveCall.new(call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: finished_tag) - msg = 'message is a string' - client_call.remote_send(msg) - server_call = expect_server_to_receive(msg) - server_call.remote_send('server_response') - server_call.send_status(StatusCodes::OK, 'status code is OK') - expect(client_call.remote_read).to eq('server_response') - expect { client_call.writes_done(true) }.to_not raise_error - expect { server_call.finished }.to_not raise_error - end + end + describe '#writes_done' do + it 'finishes ok if the server sends a status response' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + expect { client_call.writes_done(false) }.to_not raise_error + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + expect(client_call.remote_read).to eq('server_response') + server_call.send_status(StatusCodes::OK, 'status code is OK') + expect { server_call.finished }.to_not raise_error + expect { client_call.finished }.to_not raise_error end - def expect_server_to_receive(sent_text) - c = expect_server_to_be_invoked - expect(c.remote_read).to eq(sent_text) - c + it 'finishes ok if the server sends an early status response' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + server_call.send_status(StatusCodes::OK, 'status code is OK') + expect(client_call.remote_read).to eq('server_response') + expect { client_call.writes_done(false) }.to_not raise_error + expect { server_call.finished }.to_not raise_error + expect { client_call.finished }.to_not raise_error end - def expect_server_to_be_invoked() - @server.request_call(@server_tag) - ev = @server_queue.next(deadline) - ev.call.accept(@client_queue, @server_finished_tag) - ActiveCall.new(ev.call, @client_queue, @pass_through, - @pass_through, deadline, - finished_tag: @server_finished_tag) + it 'finishes ok if writes_done is true' do + call = make_test_call + finished_tag = ActiveCall.client_start_invoke(call, @client_queue, + deadline) + client_call = ActiveCall.new(call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: finished_tag) + msg = 'message is a string' + client_call.remote_send(msg) + server_call = expect_server_to_receive(msg) + server_call.remote_send('server_response') + server_call.send_status(StatusCodes::OK, 'status code is OK') + expect(client_call.remote_read).to eq('server_response') + expect { client_call.writes_done(true) }.to_not raise_error + expect { server_call.finished }.to_not raise_error end - def make_test_call - @ch.create_call('dummy_method', 'dummy_host', deadline) - end + end - def deadline - Time.now + 0.25 # in 0.25 seconds; arbitrary - end + def expect_server_to_receive(sent_text) + c = expect_server_to_be_invoked + expect(c.remote_read).to eq(sent_text) + c + end + + def expect_server_to_be_invoked() + @server.request_call(@server_tag) + ev = @server_queue.next(deadline) + ev.call.accept(@client_queue, @server_finished_tag) + ActiveCall.new(ev.call, @client_queue, @pass_through, + @pass_through, deadline, + finished_tag: @server_finished_tag) + end + + def make_test_call + @ch.create_call('dummy_method', 'dummy_host', deadline) + end + def deadline + Time.now + 0.25 # in 0.25 seconds; arbitrary end end diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb index c8dee74563..4b01af9581 100644 --- a/src/ruby/spec/generic/client_stub_spec.rb +++ b/src/ruby/spec/generic/client_stub_spec.rb @@ -44,12 +44,16 @@ def wakey_thread(&blk) t end +def load_test_certs + test_root = File.join(File.parent(File.dirname(__FILE__)), 'testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(test_root, f)).read } +end -include GRPC::StatusCodes +include GRPC::Core::StatusCodes +include GRPC::Core::TimeConsts describe 'ClientStub' do - BadStatus = GRPC::BadStatus - TimeConsts = GRPC::TimeConsts before(:each) do Thread.abort_on_exception = true @@ -57,7 +61,7 @@ describe 'ClientStub' do @method = 'an_rpc_method' @pass = OK @fail = INTERNAL - @cq = GRPC::CompletionQueue.new + @cq = GRPC::Core::CompletionQueue.new end after(:each) do @@ -102,6 +106,29 @@ describe 'ClientStub' do expect(&blk).to raise_error end + it 'cannot be created with bad credentials' do + host = new_test_host + blk = Proc.new do + opts = {:a_channel_arg => 'an_arg', :creds => Object.new} + GRPC::ClientStub.new(host, @cq, **opts) + end + expect(&blk).to raise_error + end + + it 'can be created with test test credentials' do + certs = load_test_certs + host = new_test_host + blk = Proc.new do + opts = { + GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.com', + :a_channel_arg => 'an_arg', + :creds => GRPC::Core::Credentials.new(certs[0], nil, nil) + } + GRPC::ClientStub.new(host, @cq, **opts) + end + expect(&blk).to_not raise_error + end + end describe '#request_response' do @@ -123,7 +150,7 @@ describe 'ClientStub' do it 'should send a request when configured using an override channel' do alt_host = new_test_host th = run_request_response(alt_host, @sent_msg, @resp, @pass) - ch = GRPC::Channel.new(alt_host, nil) + ch = GRPC::Core::Channel.new(alt_host, nil) stub = GRPC::ClientStub.new('ignored-host', @cq, channel_override:ch) resp = stub.request_response(@method, @sent_msg, NOOP, NOOP) @@ -138,7 +165,7 @@ describe 'ClientStub' do blk = Proc.new do stub.request_response(@method, @sent_msg, NOOP, NOOP) end - expect(&blk).to raise_error(BadStatus) + expect(&blk).to raise_error(GRPC::BadStatus) th.join end @@ -168,7 +195,7 @@ describe 'ClientStub' do blk = Proc.new do op.execute() end - expect(&blk).to raise_error(BadStatus) + expect(&blk).to raise_error(GRPC::BadStatus) th.join end @@ -309,7 +336,7 @@ describe 'ClientStub' do describe 'without a call operation' do - it 'supports a simple scenario with all requests sent first' do + it 'supports sending all the requests first', :bidi => true do host = new_test_host th = run_bidi_streamer_handle_inputs_first(host, @sent_msgs, @replys, @pass) @@ -320,7 +347,7 @@ describe 'ClientStub' do th.join end - it 'supports a simple scenario with a client-initiated ping pong' do + it 'supports client-initiated ping pong', :bidi => true do host = new_test_host th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, true) stub = GRPC::ClientStub.new(host, @cq) @@ -336,7 +363,7 @@ describe 'ClientStub' do # servers don't know if all the client metadata has been sent until # they receive a message from the client. Without receiving all the # metadata, the server does not accept the call, so this test hangs. - xit 'supports a simple scenario with a server-initiated ping pong' do + xit 'supports a server-initiated ping pong', :bidi => true do host = new_test_host th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, false) stub = GRPC::ClientStub.new(host, @cq) @@ -350,7 +377,7 @@ describe 'ClientStub' do describe 'via a call operation' do - it 'supports a simple scenario with all requests sent first' do + it 'supports sending all the requests first', :bidi => true do host = new_test_host th = run_bidi_streamer_handle_inputs_first(host, @sent_msgs, @replys, @pass) @@ -364,7 +391,7 @@ describe 'ClientStub' do th.join end - it 'supports a simple scenario with a client-initiated ping pong' do + it 'supports client-initiated ping pong', :bidi => true do host = new_test_host th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, true) stub = GRPC::ClientStub.new(host, @cq) @@ -383,7 +410,7 @@ describe 'ClientStub' do # servers don't know if all the client metadata has been sent until # they receive a message from the client. Without receiving all the # metadata, the server does not accept the call, so this test hangs. - xit 'supports a simple scenario with a server-initiated ping pong' do + xit 'supports server-initiated ping pong', :bidi => true do th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, false) stub = GRPC::ClientStub.new(host, @cq) op = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP, @@ -454,8 +481,8 @@ describe 'ClientStub' do end def start_test_server(hostname, awake_mutex, awake_cond) - server_queue = GRPC::CompletionQueue.new - @server = GRPC::Server.new(server_queue, nil) + server_queue = GRPC::Core::CompletionQueue.new + @server = GRPC::Core::Server.new(server_queue, nil) @server.add_http2_port(hostname) @server.start @server_tag = Object.new @@ -467,12 +494,11 @@ describe 'ClientStub' do def expect_server_to_be_invoked(hostname, awake_mutex, awake_cond) server_queue = start_test_server(hostname, awake_mutex, awake_cond) test_deadline = Time.now + 10 # fail tests after 10 seconds - ev = server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE) + ev = server_queue.pluck(@server_tag, INFINITE_FUTURE) raise OutOfTime if ev.nil? finished_tag = Object.new ev.call.accept(server_queue, finished_tag) - GRPC::ActiveCall.new(ev.call, server_queue, NOOP, - NOOP, TimeConsts::INFINITE_FUTURE, + GRPC::ActiveCall.new(ev.call, server_queue, NOOP, NOOP, INFINITE_FUTURE, finished_tag: finished_tag) end diff --git a/src/ruby/spec/generic/rpc_desc_spec.rb b/src/ruby/spec/generic/rpc_desc_spec.rb index 141fb1187d..efef7e4686 100644 --- a/src/ruby/spec/generic/rpc_desc_spec.rb +++ b/src/ruby/spec/generic/rpc_desc_spec.rb @@ -35,8 +35,11 @@ describe GRPC::RpcDesc do RpcDesc = GRPC::RpcDesc Stream = RpcDesc::Stream - OK = GRPC::StatusCodes::OK - UNKNOWN = GRPC::StatusCodes::UNKNOWN + OK = GRPC::Core::StatusCodes::OK + INTERNAL = GRPC::Core::StatusCodes::INTERNAL + UNKNOWN = GRPC::Core::StatusCodes::UNKNOWN + CallError = GRPC::Core::CallError + EventError = GRPC::Core::EventError before(:each) do @request_response = RpcDesc.new('rr', Object.new, Object.new, 'encode', @@ -47,7 +50,7 @@ describe GRPC::RpcDesc do 'encode', 'decode') @bidi_streamer = RpcDesc.new('ss', Stream.new(Object.new), Stream.new(Object.new), 'encode', 'decode') - @bs_code = GRPC::StatusCodes::INTERNAL + @bs_code = INTERNAL @no_reason = 'no reason given' @ok_response = Object.new end @@ -74,7 +77,7 @@ describe GRPC::RpcDesc do end it 'absorbs EventError with no further action' do - expect(@call).to receive(:remote_read).once.and_raise(GRPC::EventError) + expect(@call).to receive(:remote_read).once.and_raise(EventError) blk = Proc.new do @request_response.run_server_method(@call, method(:fake_reqresp)) end @@ -82,7 +85,7 @@ describe GRPC::RpcDesc do end it 'absorbs CallError with no further action' do - expect(@call).to receive(:remote_read).once.and_raise(GRPC::CallError) + expect(@call).to receive(:remote_read).once.and_raise(CallError) blk = Proc.new do @request_response.run_server_method(@call, method(:fake_reqresp)) end @@ -118,7 +121,7 @@ describe GRPC::RpcDesc do end it 'absorbs EventError with no further action' do - expect(@call).to receive(:remote_send).once.and_raise(GRPC::EventError) + expect(@call).to receive(:remote_send).once.and_raise(EventError) blk = Proc.new do @client_streamer.run_server_method(@call, method(:fake_clstream)) end @@ -126,7 +129,7 @@ describe GRPC::RpcDesc do end it 'absorbs CallError with no further action' do - expect(@call).to receive(:remote_send).once.and_raise(GRPC::CallError) + expect(@call).to receive(:remote_send).once.and_raise(CallError) blk = Proc.new do @client_streamer.run_server_method(@call, method(:fake_clstream)) end @@ -163,7 +166,7 @@ describe GRPC::RpcDesc do end it 'absorbs EventError with no further action' do - expect(@call).to receive(:remote_read).once.and_raise(GRPC::EventError) + expect(@call).to receive(:remote_read).once.and_raise(EventError) blk = Proc.new do @server_streamer.run_server_method(@call, method(:fake_svstream)) end @@ -171,7 +174,7 @@ describe GRPC::RpcDesc do end it 'absorbs CallError with no further action' do - expect(@call).to receive(:remote_read).once.and_raise(GRPC::CallError) + expect(@call).to receive(:remote_read).once.and_raise(CallError) blk = Proc.new do @server_streamer.run_server_method(@call, method(:fake_svstream)) end @@ -377,4 +380,3 @@ describe GRPC::RpcDesc do end end - diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb index 4e7379bc45..fc579a6c3f 100644 --- a/src/ruby/spec/generic/rpc_server_spec.rb +++ b/src/ruby/spec/generic/rpc_server_spec.rb @@ -35,8 +35,14 @@ require 'grpc/generic/service' require 'xray/thread_dump_signal_handler' require_relative '../port_picker' +def load_test_certs + test_root = File.join(File.parent(File.dirname(__FILE__)), 'testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(test_root, f)).read } +end + class EchoMsg - def marshal + def self.marshal(o) '' end @@ -86,302 +92,324 @@ end SlowStub = SlowService.rpc_stub_class -module GRPC +describe GRPC::RpcServer do - describe RpcServer do + RpcServer = GRPC::RpcServer - before(:each) do - @method = 'an_rpc_method' - @pass = 0 - @fail = 1 - @noop = Proc.new { |x| x } - - @server_queue = CompletionQueue.new - port = find_unused_tcp_port - @host = "localhost:#{port}" - @server = GRPC::Server.new(@server_queue, nil) - @server.add_http2_port(@host) - @ch = GRPC::Channel.new(@host, nil) - end + before(:each) do + @method = 'an_rpc_method' + @pass = 0 + @fail = 1 + @noop = Proc.new { |x| x } + + @server_queue = GRPC::Core::CompletionQueue.new + port = find_unused_tcp_port + @host = "localhost:#{port}" + @server = GRPC::Core::Server.new(@server_queue, nil) + @server.add_http2_port(@host) + @ch = GRPC::Core::Channel.new(@host, nil) + end + + after(:each) do + @server.close + end + + describe '#new' do - after(:each) do - @server.close + it 'can be created with just some args' do + opts = {:a_channel_arg => 'an_arg'} + blk = Proc.new do + RpcServer.new(**opts) + end + expect(&blk).not_to raise_error end - describe '#new' do + it 'can be created with a default deadline' do + opts = {:a_channel_arg => 'an_arg', :deadline => 5} + blk = Proc.new do + RpcServer.new(**opts) + end + expect(&blk).not_to raise_error + end - it 'can be created with just some args' do - opts = {:a_channel_arg => 'an_arg'} - blk = Proc.new do - RpcServer.new(**opts) - end - expect(&blk).not_to raise_error + it 'can be created with a completion queue override' do + opts = { + :a_channel_arg => 'an_arg', + :completion_queue_override => @server_queue + } + blk = Proc.new do + RpcServer.new(**opts) end + expect(&blk).not_to raise_error + end - it 'can be created with a default deadline' do - opts = {:a_channel_arg => 'an_arg', :deadline => 5} - blk = Proc.new do - RpcServer.new(**opts) - end - expect(&blk).not_to raise_error + it 'cannot be created with a bad completion queue override' do + blk = Proc.new do + opts = { + :a_channel_arg => 'an_arg', + :completion_queue_override => Object.new + } + RpcServer.new(**opts) end + expect(&blk).to raise_error + end - it 'can be created with a completion queue override' do + it 'cannot be created with invalid ServerCredentials' do + blk = Proc.new do opts = { :a_channel_arg => 'an_arg', - :completion_queue_override => @server_queue + :creds => Object.new } - blk = Proc.new do - RpcServer.new(**opts) - end - expect(&blk).not_to raise_error + RpcServer.new(**opts) end + expect(&blk).to raise_error + end - it 'cannot be created with a bad completion queue override' do - blk = Proc.new do - opts = { - :a_channel_arg => 'an_arg', - :completion_queue_override => Object.new - } - RpcServer.new(**opts) - end - expect(&blk).to raise_error + it 'can be created with the creds as valid ServerCedentials' do + certs = load_test_certs + server_creds = GRPC::Core::ServerCredentials.new(nil, certs[1], certs[2]) + blk = Proc.new do + opts = { + :a_channel_arg => 'an_arg', + :creds => server_creds + } + RpcServer.new(**opts) end + expect(&blk).to_not raise_error + end - it 'can be created with a server override' do - opts = {:a_channel_arg => 'an_arg', :server_override => @server} - blk = Proc.new do - RpcServer.new(**opts) - end - expect(&blk).not_to raise_error + it 'can be created with a server override' do + opts = {:a_channel_arg => 'an_arg', :server_override => @server} + blk = Proc.new do + RpcServer.new(**opts) end + expect(&blk).not_to raise_error + end - it 'cannot be created with a bad server override' do - blk = Proc.new do - opts = { - :a_channel_arg => 'an_arg', - :server_override => Object.new - } - RpcServer.new(**opts) - end - expect(&blk).to raise_error + it 'cannot be created with a bad server override' do + blk = Proc.new do + opts = { + :a_channel_arg => 'an_arg', + :server_override => Object.new + } + RpcServer.new(**opts) end + expect(&blk).to raise_error + end + + end + describe '#stopped?' do + + before(:each) do + opts = {:a_channel_arg => 'an_arg', :poll_period => 1} + @srv = RpcServer.new(**opts) end - describe '#stopped?' do + it 'starts out false' do + expect(@srv.stopped?).to be(false) + end - before(:each) do - opts = {:a_channel_arg => 'an_arg', :poll_period => 1} - @srv = RpcServer.new(**opts) - end + it 'stays false after a #stop is called before #run' do + @srv.stop + expect(@srv.stopped?).to be(false) + end - it 'starts out false' do - expect(@srv.stopped?).to be(false) - end + it 'stays false after the server starts running' do + @srv.handle(EchoService) + t = Thread.new { @srv.run } + @srv.wait_till_running + expect(@srv.stopped?).to be(false) + @srv.stop + t.join + end - it 'stays false after a #stop is called before #run' do - @srv.stop - expect(@srv.stopped?).to be(false) - end + it 'is true after a running server is stopped' do + @srv.handle(EchoService) + t = Thread.new { @srv.run } + @srv.wait_till_running + @srv.stop + expect(@srv.stopped?).to be(true) + t.join + end - it 'stays false after the server starts running' do - @srv.handle(EchoService) - t = Thread.new { @srv.run } - @srv.wait_till_running - expect(@srv.stopped?).to be(false) - @srv.stop - t.join - end + end - it 'is true after a running server is stopped' do - @srv.handle(EchoService) - t = Thread.new { @srv.run } - @srv.wait_till_running - @srv.stop - expect(@srv.stopped?).to be(true) - t.join - end + describe '#running?' do + + it 'starts out false' do + opts = {:a_channel_arg => 'an_arg', :server_override => @server} + r = RpcServer.new(**opts) + expect(r.running?).to be(false) + end + it 'is false after run is called with no services registered' do + opts = { + :a_channel_arg => 'an_arg', + :poll_period => 1, + :server_override => @server + } + r = RpcServer.new(**opts) + r.run() + expect(r.running?).to be(false) end - describe '#running?' do + it 'is true after run is called with a registered service' do + opts = { + :a_channel_arg => 'an_arg', + :poll_period => 1, + :server_override => @server + } + r = RpcServer.new(**opts) + r.handle(EchoService) + t = Thread.new { r.run } + r.wait_till_running + expect(r.running?).to be(true) + r.stop + t.join + end - it 'starts out false' do - opts = {:a_channel_arg => 'an_arg', :server_override => @server} - r = RpcServer.new(**opts) - expect(r.running?).to be(false) - end + end - it 'is false after run is called with no services registered' do - opts = { - :a_channel_arg => 'an_arg', - :poll_period => 1, - :server_override => @server - } - r = RpcServer.new(**opts) - r.run() - expect(r.running?).to be(false) - end + describe '#handle' do - it 'is true after run is called with a registered service' do - opts = { - :a_channel_arg => 'an_arg', - :poll_period => 1, - :server_override => @server - } - r = RpcServer.new(**opts) - r.handle(EchoService) - t = Thread.new { r.run } - r.wait_till_running - expect(r.running?).to be(true) - r.stop - t.join - end + before(:each) do + @opts = {:a_channel_arg => 'an_arg', :poll_period => 1} + @srv = RpcServer.new(**@opts) + end + it 'raises if #run has already been called' do + @srv.handle(EchoService) + t = Thread.new { @srv.run } + @srv.wait_till_running + expect { @srv.handle(EchoService) }.to raise_error + @srv.stop + t.join end - describe '#handle' do + it 'raises if the server has been run and stopped' do + @srv.handle(EchoService) + t = Thread.new { @srv.run } + @srv.wait_till_running + @srv.stop + t.join + expect { @srv.handle(EchoService) }.to raise_error + end - before(:each) do - @opts = {:a_channel_arg => 'an_arg', :poll_period => 1} - @srv = RpcServer.new(**@opts) - end + it 'raises if the service does not include GenericService ' do + expect { @srv.handle(Object) }.to raise_error + end + + it 'raises if the service does not declare any rpc methods' do + expect { @srv.handle(EmptyService) }.to raise_error + end + + it 'raises if the service does not define its rpc methods' do + expect { @srv.handle(NoRpcImplementation) }.to raise_error + end + + it 'raises if a handler method is already registered' do + @srv.handle(EchoService) + expect { r.handle(EchoService) }.to raise_error + end + + end + + describe '#run' do + + before(:each) do + @client_opts = { + :channel_override => @ch + } + @marshal = EchoService.rpc_descs[:an_rpc].marshal_proc + @unmarshal = EchoService.rpc_descs[:an_rpc].unmarshal_proc(:output) + server_opts = { + :server_override => @server, + :completion_queue_override => @server_queue, + :poll_period => 1 + } + @srv = RpcServer.new(**server_opts) + end - it 'raises if #run has already been called' do + describe 'when running' do + + it 'should return NOT_FOUND status for requests on unknown methods' do @srv.handle(EchoService) t = Thread.new { @srv.run } @srv.wait_till_running - expect { @srv.handle(EchoService) }.to raise_error + req = EchoMsg.new + blk = Proc.new do + cq = GRPC::Core::CompletionQueue.new + stub = GRPC::ClientStub.new(@host, cq, **@client_opts) + stub.request_response('/unknown', req, @marshal, @unmarshal) + end + expect(&blk).to raise_error BadStatus @srv.stop t.join end - it 'raises if the server has been run and stopped' do + it 'should obtain responses for multiple sequential requests' do @srv.handle(EchoService) t = Thread.new { @srv.run } @srv.wait_till_running + req = EchoMsg.new + n = 5 # arbitrary + stub = EchoStub.new(@host, **@client_opts) + n.times { |x| expect(stub.an_rpc(req)).to be_a(EchoMsg) } @srv.stop t.join - expect { @srv.handle(EchoService) }.to raise_error - end - - it 'raises if the service does not include GenericService ' do - expect { @srv.handle(Object) }.to raise_error - end - - it 'raises if the service does not declare any rpc methods' do - expect { @srv.handle(EmptyService) }.to raise_error - end - - it 'raises if the service does not define its rpc methods' do - expect { @srv.handle(NoRpcImplementation) }.to raise_error end - it 'raises if a handler method is already registered' do + it 'should obtain responses for multiple parallel requests' do @srv.handle(EchoService) - expect { r.handle(EchoService) }.to raise_error + t = Thread.new { @srv.run } + @srv.wait_till_running + req, q = EchoMsg.new, Queue.new + n = 5 # arbitrary + threads = [] + n.times do |x| + cq = GRPC::Core::CompletionQueue.new + threads << Thread.new do + stub = EchoStub.new(@host, **@client_opts) + q << stub.an_rpc(req) + end + end + n.times { expect(q.pop).to be_a(EchoMsg) } + @srv.stop + threads.each { |t| t.join } end - end - - describe '#run' do - - before(:each) do - @client_opts = { - :channel_override => @ch - } - @marshal = EchoService.rpc_descs[:an_rpc].marshal_proc - @unmarshal = EchoService.rpc_descs[:an_rpc].unmarshal_proc(:output) - server_opts = { + it 'should return UNAVAILABLE status if there too many jobs' do + opts = { + :a_channel_arg => 'an_arg', :server_override => @server, :completion_queue_override => @server_queue, - :poll_period => 1 + :pool_size => 1, + :poll_period => 1, + :max_waiting_requests => 0 } - @srv = RpcServer.new(**server_opts) - end - - describe 'when running' do - - it 'should return NOT_FOUND status for requests on unknown methods' do - @srv.handle(EchoService) - t = Thread.new { @srv.run } - @srv.wait_till_running - req = EchoMsg.new - blk = Proc.new do - cq = CompletionQueue.new - stub = ClientStub.new(@host, cq, **@client_opts) - stub.request_response('/unknown', req, @marshal, @unmarshal) - end - expect(&blk).to raise_error BadStatus - @srv.stop - t.join - end - - it 'should obtain responses for multiple sequential requests' do - @srv.handle(EchoService) - t = Thread.new { @srv.run } - @srv.wait_till_running - req = EchoMsg.new - n = 5 # arbitrary - stub = EchoStub.new(@host, **@client_opts) - n.times { |x| expect(stub.an_rpc(req)).to be_a(EchoMsg) } - @srv.stop - t.join - end - - it 'should obtain responses for multiple parallel requests' do - @srv.handle(EchoService) - t = Thread.new { @srv.run } - @srv.wait_till_running - req, q = EchoMsg.new, Queue.new - n = 5 # arbitrary - threads = [] - n.times do |x| - cq = CompletionQueue.new - threads << Thread.new do - stub = EchoStub.new(@host, **@client_opts) - q << stub.an_rpc(req) - end - end - n.times { expect(q.pop).to be_a(EchoMsg) } - @srv.stop - threads.each { |t| t.join } - end - - it 'should return UNAVAILABLE status if there too many jobs' do - opts = { - :a_channel_arg => 'an_arg', - :server_override => @server, - :completion_queue_override => @server_queue, - :pool_size => 1, - :poll_period => 1, - :max_waiting_requests => 0 - } - alt_srv = RpcServer.new(**opts) - alt_srv.handle(SlowService) - t = Thread.new { alt_srv.run } - alt_srv.wait_till_running - req = EchoMsg.new - n = 5 # arbitrary, use as many to ensure the server pool is exceeded - threads = [] - _1_failed_as_unavailable = false - n.times do |x| - threads << Thread.new do - cq = CompletionQueue.new - stub = SlowStub.new(@host, **@client_opts) - begin - stub.an_rpc(req) - rescue BadStatus => e - _1_failed_as_unavailable = e.code == StatusCodes::UNAVAILABLE - end + alt_srv = RpcServer.new(**opts) + alt_srv.handle(SlowService) + t = Thread.new { alt_srv.run } + alt_srv.wait_till_running + req = EchoMsg.new + n = 5 # arbitrary, use as many to ensure the server pool is exceeded + threads = [] + _1_failed_as_unavailable = false + n.times do |x| + threads << Thread.new do + cq = GRPC::Core::CompletionQueue.new + stub = SlowStub.new(@host, **@client_opts) + begin + stub.an_rpc(req) + rescue BadStatus => e + _1_failed_as_unavailable = e.code == StatusCodes::UNAVAILABLE end end - threads.each { |t| t.join } - alt_srv.stop - expect(_1_failed_as_unavailable).to be(true) end - + threads.each { |t| t.join } + alt_srv.stop + expect(_1_failed_as_unavailable).to be(true) end end diff --git a/src/ruby/spec/generic/service_spec.rb b/src/ruby/spec/generic/service_spec.rb index 4c76881bcf..dc921d8934 100644 --- a/src/ruby/spec/generic/service_spec.rb +++ b/src/ruby/spec/generic/service_spec.rb @@ -33,7 +33,7 @@ require 'grpc/generic/service' class GoodMsg - def marshal + def self.marshal(o) '' end @@ -43,7 +43,7 @@ class GoodMsg end class EncodeDecodeMsg - def encode + def self.encode(o) '' end @@ -53,7 +53,6 @@ class EncodeDecodeMsg end GenericService = GRPC::GenericService -RpcDesc = GRPC::RpcDesc Dsl = GenericService::Dsl @@ -95,7 +94,7 @@ describe GenericService do end expect(c.rpc_descs).to include(:AnRpc) - expect(c.rpc_descs[:AnRpc]).to be_a(RpcDesc) + expect(c.rpc_descs[:AnRpc]).to be_a(GRPC::RpcDesc) end it 'give subclasses access to #rpc_descs' do @@ -106,7 +105,7 @@ describe GenericService do c = Class.new(base) do end expect(c.rpc_descs).to include(:AnRpc) - expect(c.rpc_descs[:AnRpc]).to be_a(RpcDesc) + expect(c.rpc_descs[:AnRpc]).to be_a(GRPC::RpcDesc) end end @@ -189,7 +188,7 @@ describe GenericService do blk = Proc.new do Class.new do include GenericService - self.marshal_instance_method = :encode + self.marshal_class_method = :encode self.unmarshal_class_method = :decode rpc :AnRpc, EncodeDecodeMsg, EncodeDecodeMsg end diff --git a/src/ruby/spec/metadata_spec.rb b/src/ruby/spec/metadata_spec.rb index 8465a40fab..d5dc8b2338 100644 --- a/src/ruby/spec/metadata_spec.rb +++ b/src/ruby/spec/metadata_spec.rb @@ -29,24 +29,23 @@ require 'grpc' -describe GRPC::Metadata do +describe GRPC::Core::Metadata do describe '#new' do it 'should create instances' do - expect { GRPC::Metadata.new('a key', 'a value') }.to_not raise_error - expect(GRPC::Metadata.new('a key', 'a value')).to be_a(GRPC::Metadata) + expect { GRPC::Core::Metadata.new('a key', 'a value') }.to_not raise_error end end describe '#key' do - md = GRPC::Metadata.new('a key', 'a value') + md = GRPC::Core::Metadata.new('a key', 'a value') it 'should be the constructor value' do expect(md.key).to eq('a key') end end describe '#value' do - md = GRPC::Metadata.new('a key', 'a value') + md = GRPC::Core::Metadata.new('a key', 'a value') it 'should be the constuctor value' do expect(md.value).to eq('a value') end @@ -54,12 +53,12 @@ describe GRPC::Metadata do describe '#dup' do it 'should create a copy that returns the correct key' do - md = GRPC::Metadata.new('a key', 'a value') + md = GRPC::Core::Metadata.new('a key', 'a value') expect(md.dup.key).to eq('a key') end it 'should create a copy that returns the correct value' do - md = GRPC::Metadata.new('a key', 'a value') + md = GRPC::Core::Metadata.new('a key', 'a value') expect(md.dup.value).to eq('a value') end end diff --git a/src/ruby/spec/server_credentials_spec.rb b/src/ruby/spec/server_credentials_spec.rb new file mode 100644 index 0000000000..bcc2cae4dd --- /dev/null +++ b/src/ruby/spec/server_credentials_spec.rb @@ -0,0 +1,74 @@ +# Copyright 2014, Google Inc. +# 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 Google Inc. 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 +# OWNER 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. + +require 'grpc' + +def load_test_certs + test_root = File.join(File.dirname(__FILE__), 'testdata') + files = ['ca.pem', 'server1.pem', 'server1.key'] + files.map { |f| File.open(File.join(test_root, f)).read } +end + + +describe GRPC::Core::ServerCredentials do + + Creds = GRPC::Core::ServerCredentials + + describe '#new' do + + it 'can be constructed from a fake CA PEM, server PEM and a server key' do + expect { Creds.new('a', 'b', 'c') }.not_to raise_error + end + + it 'can be constructed using the test certificates' do + certs = load_test_certs + expect { Creds.new(*certs) }.not_to raise_error + end + + it 'cannot be constructed without a server cert chain' do + root_cert, server_key, _ = load_test_certs + blk = Proc.new { Creds.new(root_cert, server_key, nil) } + expect(&blk).to raise_error + end + + it 'cannot be constructed without a server key' do + root_cert, server_key, _ = load_test_certs + blk = Proc.new { Creds.new(root_cert, _, cert_chain) } + expect(&blk).to raise_error + end + + it 'can be constructed without a root_cret' do + _, server_key, cert_chain = load_test_certs + blk = Proc.new { Creds.new(_, server_key, cert_chain) } + expect(&blk).to_not raise_error + end + + end + +end diff --git a/src/ruby/spec/server_spec.rb b/src/ruby/spec/server_spec.rb index 598b7cfa7b..28f520a2f6 100644 --- a/src/ruby/spec/server_spec.rb +++ b/src/ruby/spec/server_spec.rb @@ -30,72 +30,84 @@ require 'grpc' require 'port_picker' -module GRPC +def load_test_certs + test_root = File.join(File.dirname(__FILE__), 'testdata') + files = ['ca.pem', 'server1.key', 'server1.pem'] + files.map { |f| File.open(File.join(test_root, f)).read } +end - describe Server do +Server = GRPC::Core::Server - before(:each) do - @cq = CompletionQueue.new - end +describe Server do - describe '#start' do + def create_test_cert + GRPC::Core::ServerCredentials.new(*load_test_certs) + end - it 'runs without failing' do - blk = Proc.new do - s = Server.new(@cq, nil).start - end - expect(&blk).to_not raise_error - end + before(:each) do + @cq = GRPC::Core::CompletionQueue.new + end - it 'fails if the server is closed' do - s = Server.new(@cq, nil) - s.close - expect { s.start }.to raise_error(RuntimeError) + describe '#start' do + + it 'runs without failing' do + blk = Proc.new do + s = Server.new(@cq, nil).start end + expect(&blk).to_not raise_error + end + it 'fails if the server is closed' do + s = Server.new(@cq, nil) + s.close + expect { s.start }.to raise_error(RuntimeError) end - describe '#destroy' do - it 'destroys a server ok' do - s = start_a_server - blk = Proc.new { s.destroy } - expect(&blk).to_not raise_error - end + end - it 'can be called more than once without error' do - s = start_a_server - begin - blk = Proc.new { s.destroy } - expect(&blk).to_not raise_error - blk.call - expect(&blk).to_not raise_error - ensure - s.close - end - end + describe '#destroy' do + it 'destroys a server ok' do + s = start_a_server + blk = Proc.new { s.destroy } + expect(&blk).to_not raise_error end - describe '#close' do - it 'closes a server ok' do - s = start_a_server - begin - blk = Proc.new { s.close } - expect(&blk).to_not raise_error - ensure - s.close - end + it 'can be called more than once without error' do + s = start_a_server + begin + blk = Proc.new { s.destroy } + expect(&blk).to_not raise_error + blk.call + expect(&blk).to_not raise_error + ensure + s.close end + end + end - it 'can be called more than once without error' do - s = start_a_server + describe '#close' do + it 'closes a server ok' do + s = start_a_server + begin blk = Proc.new { s.close } expect(&blk).to_not raise_error - blk.call - expect(&blk).to_not raise_error + ensure + s.close end end - describe '#add_http_port' do + it 'can be called more than once without error' do + s = start_a_server + blk = Proc.new { s.close } + expect(&blk).to_not raise_error + blk.call + expect(&blk).to_not raise_error + end + end + + describe '#add_http_port' do + + describe 'for insecure servers' do it 'runs without failing' do blk = Proc.new do @@ -114,72 +126,108 @@ module GRPC end - describe '#new' do + describe 'for secure servers' do - it 'takes a completion queue with nil channel args' do - expect { Server.new(@cq, nil) }.to_not raise_error + it 'runs without failing' do + blk = Proc.new do + s = Server.new(@cq, nil) + s.add_http2_port('localhost:0', true) + s.close + end + expect(&blk).to_not raise_error end - it 'does not take a hash with bad keys as channel args' do - blk = construct_with_args(Object.new => 1) - expect(&blk).to raise_error TypeError - blk = construct_with_args(1 => 1) - expect(&blk).to raise_error TypeError + it 'fails if the server is closed' do + s = Server.new(@cq, nil) + s.close + blk = Proc.new { s.add_http2_port('localhost:0', true) } + expect(&blk).to raise_error(RuntimeError) end - it 'does not take a hash with bad values as channel args' do - blk = construct_with_args(:symbol => Object.new) - expect(&blk).to raise_error TypeError - blk = construct_with_args('1' => Hash.new) - expect(&blk).to raise_error TypeError - end + end - it 'can take a hash with a symbol key as channel args' do - blk = construct_with_args(:a_symbol => 1) - expect(&blk).to_not raise_error - end + end - it 'can take a hash with a string key as channel args' do - blk = construct_with_args('a_symbol' => 1) - expect(&blk).to_not raise_error - end + shared_examples '#new' do - it 'can take a hash with a string value as channel args' do - blk = construct_with_args(:a_symbol => '1') - expect(&blk).to_not raise_error - end + it 'takes a completion queue with nil channel args' do + expect { Server.new(@cq, nil, create_test_cert) }.to_not raise_error + end - it 'can take a hash with a symbol value as channel args' do - blk = construct_with_args(:a_symbol => :another_symbol) - expect(&blk).to_not raise_error - end + it 'does not take a hash with bad keys as channel args' do + blk = construct_with_args(Object.new => 1) + expect(&blk).to raise_error TypeError + blk = construct_with_args(1 => 1) + expect(&blk).to raise_error TypeError + end - it 'can take a hash with a numeric value as channel args' do - blk = construct_with_args(:a_symbol => 1) - expect(&blk).to_not raise_error - end + it 'does not take a hash with bad values as channel args' do + blk = construct_with_args(:symbol => Object.new) + expect(&blk).to raise_error TypeError + blk = construct_with_args('1' => Hash.new) + expect(&blk).to raise_error TypeError + end - it 'can take a hash with many args as channel args' do - args = Hash[127.times.collect { |x| [x.to_s, x] } ] - blk = construct_with_args(args) - expect(&blk).to_not raise_error - end + it 'can take a hash with a symbol key as channel args' do + blk = construct_with_args(:a_symbol => 1) + expect(&blk).to_not raise_error + end + + it 'can take a hash with a string key as channel args' do + blk = construct_with_args('a_symbol' => 1) + expect(&blk).to_not raise_error + end + it 'can take a hash with a string value as channel args' do + blk = construct_with_args(:a_symbol => '1') + expect(&blk).to_not raise_error end + it 'can take a hash with a symbol value as channel args' do + blk = construct_with_args(:a_symbol => :another_symbol) + expect(&blk).to_not raise_error + end + + it 'can take a hash with a numeric value as channel args' do + blk = construct_with_args(:a_symbol => 1) + expect(&blk).to_not raise_error + end + + it 'can take a hash with many args as channel args' do + args = Hash[127.times.collect { |x| [x.to_s, x] } ] + blk = construct_with_args(args) + expect(&blk).to_not raise_error + end + + end + + describe '#new with an insecure channel' do + def construct_with_args(a) Proc.new { Server.new(@cq, a) } end - def start_a_server - port = find_unused_tcp_port - host = "localhost:#{port}" - s = Server.new(@cq, nil) - s.add_http2_port(host) - s.start - s + it_behaves_like '#new' + + end + + describe '#new with a secure channel' do + + def construct_with_args(a) + Proc.new { Server.new(@cq, a, create_test_cert) } end + it_behaves_like '#new' + + end + + def start_a_server + port = find_unused_tcp_port + host = "localhost:#{port}" + s = Server.new(@cq, nil) + s.add_http2_port(host) + s.start + s end end diff --git a/src/ruby/spec/status_spec.rb b/src/ruby/spec/status_spec.rb index 83d4efc730..63dcefba04 100644 --- a/src/ruby/spec/status_spec.rb +++ b/src/ruby/spec/status_spec.rb @@ -29,133 +29,138 @@ require 'grpc' -module GRPC - - describe StatusCodes do - - before(:each) do - @known_types = { - :OK => 0, - :CANCELLED => 1, - :UNKNOWN => 2, - :INVALID_ARGUMENT => 3, - :DEADLINE_EXCEEDED => 4, - :NOT_FOUND => 5, - :ALREADY_EXISTS => 6, - :PERMISSION_DENIED => 7, - :RESOURCE_EXHAUSTED => 8, - :FAILED_PRECONDITION => 9, - :ABORTED => 10, - :OUT_OF_RANGE => 11, - :UNIMPLEMENTED => 12, - :INTERNAL => 13, - :UNAVAILABLE => 14, - :DATA_LOSS => 15, - :UNAUTHENTICATED => 16 - } - end - it 'should have symbols for all the known status codes' do - m = StatusCodes - syms_and_codes = m.constants.collect { |c| [c, m.const_get(c)] } - expect(Hash[syms_and_codes]).to eq(@known_types) - end +describe GRPC::Core::StatusCodes do + + StatusCodes = GRPC::Core::StatusCodes + + before(:each) do + @known_types = { + :OK => 0, + :CANCELLED => 1, + :UNKNOWN => 2, + :INVALID_ARGUMENT => 3, + :DEADLINE_EXCEEDED => 4, + :NOT_FOUND => 5, + :ALREADY_EXISTS => 6, + :PERMISSION_DENIED => 7, + :RESOURCE_EXHAUSTED => 8, + :FAILED_PRECONDITION => 9, + :ABORTED => 10, + :OUT_OF_RANGE => 11, + :UNIMPLEMENTED => 12, + :INTERNAL => 13, + :UNAVAILABLE => 14, + :DATA_LOSS => 15, + :UNAUTHENTICATED => 16 + } + end + it 'should have symbols for all the known status codes' do + m = StatusCodes + syms_and_codes = m.constants.collect { |c| [c, m.const_get(c)] } + expect(Hash[syms_and_codes]).to eq(@known_types) end - describe Status do +end + + +describe GRPC::Core::Status do + + Status = GRPC::Core::Status - describe '#new' do - it 'should create new instances' do - expect { Status.new(142, 'test details') }.to_not raise_error - end + describe '#new' do + it 'should create new instances' do + expect { Status.new(142, 'test details') }.to_not raise_error end + end - describe '#details' do - it 'return the detail' do - sts = Status.new(142, 'test details') - expect(sts.details).to eq('test details') - end + describe '#details' do + it 'return the detail' do + sts = Status.new(142, 'test details') + expect(sts.details).to eq('test details') end + end - describe '#code' do - it 'should return the code' do - sts = Status.new(142, 'test details') - expect(sts.code).to eq(142) - end + describe '#code' do + it 'should return the code' do + sts = Status.new(142, 'test details') + expect(sts.code).to eq(142) end + end - describe '#dup' do - it 'should create a copy that returns the correct details' do - sts = Status.new(142, 'test details') - expect(sts.dup.code).to eq(142) - end + describe '#dup' do + it 'should create a copy that returns the correct details' do + sts = Status.new(142, 'test details') + expect(sts.dup.code).to eq(142) + end - it 'should create a copy that returns the correct code' do - sts = Status.new(142, 'test details') - expect(sts.dup.details).to eq('test details') - end + it 'should create a copy that returns the correct code' do + sts = Status.new(142, 'test details') + expect(sts.dup.details).to eq('test details') end + end - end +end - describe BadStatus do - describe '#new' do - it 'should create new instances' do - expect { BadStatus.new(142, 'test details') }.to_not raise_error - end - end +describe GRPC::BadStatus do + + BadStatus = GRPC::BadStatus - describe '#details' do - it 'return the detail' do - err = BadStatus.new(142, 'test details') - expect(err.details).to eq('test details') - end + describe '#new' do + it 'should create new instances' do + expect { BadStatus.new(142, 'test details') }.to_not raise_error end + end - describe '#code' do - it 'should return the code' do - err = BadStatus.new(142, 'test details') - expect(err.code).to eq(142) - end + describe '#details' do + it 'return the detail' do + err = BadStatus.new(142, 'test details') + expect(err.details).to eq('test details') end + end - describe '#dup' do - it 'should create a copy that returns the correct details' do - err = BadStatus.new(142, 'test details') - expect(err.dup.code).to eq(142) - end + describe '#code' do + it 'should return the code' do + err = BadStatus.new(142, 'test details') + expect(err.code).to eq(142) + end + end - it 'should create a copy that returns the correct code' do - err = BadStatus.new(142, 'test details') - expect(err.dup.details).to eq('test details') - end + describe '#dup' do + it 'should create a copy that returns the correct details' do + err = BadStatus.new(142, 'test details') + expect(err.dup.code).to eq(142) end - describe '#to_status' do - it 'should create a Status with the same code and details' do - err = BadStatus.new(142, 'test details') - sts = err.to_status - expect(sts.code).to eq(142) - expect(sts.details).to eq('test details') - end - - it 'should create a copy that returns the correct code' do - err = BadStatus.new(142, 'test details') - expect(err.dup.details).to eq('test details') - end + it 'should create a copy that returns the correct code' do + err = BadStatus.new(142, 'test details') + expect(err.dup.details).to eq('test details') end + end - describe 'as an exception' do + describe '#to_status' do + it 'should create a Status with the same code and details' do + err = BadStatus.new(142, 'test details') + sts = err.to_status + expect(sts.code).to eq(142) + expect(sts.details).to eq('test details') + end - it 'can be raised' do - blk = Proc.new { raise BadStatus.new(343, 'status 343') } - expect(&blk).to raise_error(BadStatus) - end + it 'should create a copy that returns the correct code' do + err = BadStatus.new(142, 'test details') + expect(err.dup.details).to eq('test details') end + end + + describe 'as an exception' do + it 'can be raised' do + blk = Proc.new { raise BadStatus.new(343, 'status 343') } + expect(&blk).to raise_error(BadStatus) + end end end diff --git a/src/ruby/spec/testdata/README b/src/ruby/spec/testdata/README new file mode 100755 index 0000000000..ed72661e97 --- /dev/null +++ b/src/ruby/spec/testdata/README @@ -0,0 +1,4 @@ +These are test keys *NOT* to be used in production. +http://go/keyhunt requires this README + +CONFIRMEDTESTKEY diff --git a/src/ruby/spec/testdata/ca.pem b/src/ruby/spec/testdata/ca.pem new file mode 100755 index 0000000000..6c8511a73c --- /dev/null +++ b/src/ruby/spec/testdata/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/src/ruby/spec/testdata/server1.key b/src/ruby/spec/testdata/server1.key new file mode 100755 index 0000000000..143a5b8765 --- /dev/null +++ b/src/ruby/spec/testdata/server1.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/src/ruby/spec/testdata/server1.pem b/src/ruby/spec/testdata/server1.pem new file mode 100755 index 0000000000..8e582e571f --- /dev/null +++ b/src/ruby/spec/testdata/server1.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5 +MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl +c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs +JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO +RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30 +3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL +BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6 +b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ +KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS +wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e +aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s= +-----END CERTIFICATE----- diff --git a/src/ruby/spec/time_consts_spec.rb b/src/ruby/spec/time_consts_spec.rb index 2bbcac07c5..27755075a9 100644 --- a/src/ruby/spec/time_consts_spec.rb +++ b/src/ruby/spec/time_consts_spec.rb @@ -29,67 +29,65 @@ require 'grpc' -module GRPC - describe TimeConsts do +TimeConsts = GRPC::Core::TimeConsts - before(:each) do - @known_consts = [:ZERO, :INFINITE_FUTURE, :INFINITE_PAST].sort - end - - it 'should have all the known types' do - expect(TimeConsts.constants.collect.sort).to eq(@known_consts) - end - - describe "#to_time" do - it 'converts each constant to a Time' do - m = TimeConsts - m.constants.each do |c| - expect(m.const_get(c).to_time).to be_a(Time) - end - end - end +describe TimeConsts do + before(:each) do + @known_consts = [:ZERO, :INFINITE_FUTURE, :INFINITE_PAST].sort end - describe '#from_relative_time' do - - it 'cannot handle arbitrary objects' do - expect { TimeConsts.from_relative_time(Object.new) }.to raise_error - end + it 'should have all the known types' do + expect(TimeConsts.constants.collect.sort).to eq(@known_consts) + end - it 'preserves TimeConsts' do + describe '#to_time' do + it 'converts each constant to a Time' do m = TimeConsts m.constants.each do |c| - const = m.const_get(c) - expect(TimeConsts.from_relative_time(const)).to be(const) + expect(m.const_get(c).to_time).to be_a(Time) end end + end - it 'converts 0 to TimeConsts::ZERO' do - expect(TimeConsts.from_relative_time(0)).to eq(TimeConsts::ZERO) - end +end - it 'converts nil to TimeConsts::ZERO' do - expect(TimeConsts.from_relative_time(nil)).to eq(TimeConsts::ZERO) - end +describe '#from_relative_time' do - it 'converts negative values to TimeConsts::INFINITE_FUTURE' do - [-1, -3.2, -1e6].each do |t| - y = TimeConsts.from_relative_time(t) - expect(y).to eq(TimeConsts::INFINITE_FUTURE) - end + it 'cannot handle arbitrary objects' do + expect { TimeConsts.from_relative_time(Object.new) }.to raise_error + end + + it 'preserves TimeConsts' do + m = TimeConsts + m.constants.each do |c| + const = m.const_get(c) + expect(TimeConsts.from_relative_time(const)).to be(const) end + end - it 'converts a positive value to an absolute time' do - epsilon = 1 - [1, 3.2, 1e6].each do |t| - want = Time.now + t - abs = TimeConsts.from_relative_time(t) - expect(abs.to_f).to be_within(epsilon).of(want.to_f) - end + it 'converts 0 to TimeConsts::ZERO' do + expect(TimeConsts.from_relative_time(0)).to eq(TimeConsts::ZERO) + end + + it 'converts nil to TimeConsts::ZERO' do + expect(TimeConsts.from_relative_time(nil)).to eq(TimeConsts::ZERO) + end + + it 'converts negative values to TimeConsts::INFINITE_FUTURE' do + [-1, -3.2, -1e6].each do |t| + y = TimeConsts.from_relative_time(t) + expect(y).to eq(TimeConsts::INFINITE_FUTURE) end + end + it 'converts a positive value to an absolute time' do + epsilon = 1 + [1, 3.2, 1e6].each do |t| + want = Time.now + t + abs = TimeConsts.from_relative_time(t) + expect(abs.to_f).to be_within(epsilon).of(want.to_f) + end end end - |