diff --git a/Makefile b/Makefile index 55105bc..f918bf8 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ DEBUG = true export DEBUG +LINKER_FLAGS= + OBJS = \ conn/*.o \ crypto/*.o \ @@ -19,7 +21,7 @@ OBJS = \ yamux/*.o link: - ar rcs libp2p.a $(OBJS) + ar rcs libp2p.a $(OBJS) $(LINKER_FLAGS) compile: cd conn; make all; diff --git a/conn/dialer.c b/conn/dialer.c index acaaacd..54c305d 100644 --- a/conn/dialer.c +++ b/conn/dialer.c @@ -3,6 +3,7 @@ * Functions for handling the local dialer */ +#include "libp2p/crypto/encoding/x509.h" #include "libp2p/conn/dialer.h" #include "libp2p/conn/connection.h" #include "libp2p/conn/transport_dialer.h" @@ -17,16 +18,33 @@ struct TransportDialer* libp2p_conn_tcp_transport_dialer_new(); /** * Create a Dialer with the specified local information + * @param peer the local peer + * @param peerstore the local peerstore + * @param private_key the local private key + * @returns a new Dialer struct */ -struct Dialer* libp2p_conn_dialer_new(struct Libp2pPeer* peer, struct RsaPrivateKey* private_key) { +struct Dialer* libp2p_conn_dialer_new(struct Libp2pPeer* peer, struct Peerstore* peerstore, struct PrivateKey* private_key) { int success = 0; struct Dialer* dialer = (struct Dialer*)malloc(sizeof(struct Dialer)); if (dialer != NULL) { + dialer->peerstore = peerstore; dialer->peer_id = malloc(peer->id_size + 1); memset(dialer->peer_id, 0, peer->id_size + 1); if (dialer->peer_id != NULL) { strncpy(dialer->peer_id, peer->id, peer->id_size); - dialer->private_key = private_key; + // convert private key to rsa private key + struct RsaPrivateKey* rsa_private_key = libp2p_crypto_rsa_rsa_private_key_new(); + if (!libp2p_crypto_encoding_x509_der_to_private_key(private_key->data, private_key->data_size, rsa_private_key)) { + libp2p_crypto_rsa_rsa_private_key_free(rsa_private_key); + libp2p_conn_dialer_free(dialer); + return NULL; + } + if (!libp2p_crypto_rsa_private_key_fill_public_key(rsa_private_key)) { + libp2p_crypto_rsa_rsa_private_key_free(rsa_private_key); + libp2p_conn_dialer_free(dialer); + return NULL; + } + dialer->private_key = rsa_private_key; //TODO: build transport dialers dialer->transport_dialers = NULL; dialer->fallback_dialer = libp2p_conn_tcp_transport_dialer_new(dialer->peer_id, private_key); @@ -45,7 +63,7 @@ struct Dialer* libp2p_conn_dialer_new(struct Libp2pPeer* peer, struct RsaPrivate void libp2p_conn_dialer_free(struct Dialer* in) { if (in != NULL) { free(in->peer_id); - //libp2p_crypto_private_key_free(in->private_key); + libp2p_crypto_rsa_rsa_private_key_free(in->private_key); if (in->transport_dialers != NULL) { struct Libp2pLinkedList* current = in->transport_dialers; while(current != NULL) { @@ -83,6 +101,8 @@ struct Stream* libp2p_conn_dialer_get_connection(const struct Dialer* dialer, co * @returns true(1) on success, false(0) otherwise */ int libp2p_conn_dialer_join_swarm(const struct Dialer* dialer, struct Libp2pPeer* peer, int timeout_secs) { + if (dialer == NULL || peer == NULL) + return 0; // find the right Multiaddress struct Libp2pLinkedList* current_entry = peer->addr_head; struct Stream* conn_stream = NULL; @@ -90,19 +110,24 @@ int libp2p_conn_dialer_join_swarm(const struct Dialer* dialer, struct Libp2pPeer struct MultiAddress* ma = current_entry->item; conn_stream = libp2p_conn_dialer_get_connection(dialer, ma); if (conn_stream != NULL) { + if (peer->sessionContext == NULL) { + peer->sessionContext = libp2p_session_context_new(); + } + peer->sessionContext->insecure_stream = conn_stream; + peer->sessionContext->default_stream = conn_stream; + peer->sessionContext->port = multiaddress_get_ip_port(ma); + multiaddress_get_ip_address(ma, &peer->sessionContext->host); break; } current_entry = current_entry->next; } if (conn_stream == NULL) return 0; - peer->sessionContext->insecure_stream = conn_stream; - peer->sessionContext->default_stream = conn_stream; // multistream struct Stream* new_stream = libp2p_net_multistream_stream_new(conn_stream); if (new_stream != NULL) { // secio over multistream - new_stream = libp2p_secio_stream_new(new_stream); + new_stream = libp2p_secio_stream_new(new_stream, peer, dialer->peerstore, dialer->private_key); if (new_stream != NULL) { peer->sessionContext->default_stream = new_stream; // multistream over secio @@ -116,12 +141,13 @@ int libp2p_conn_dialer_join_swarm(const struct Dialer* dialer, struct Libp2pPeer // identity over yamux // kademlia over yamux // circuit relay over yamux + return 1; } } } } - return 1; + return 0; } /** diff --git a/conn/session.c b/conn/session.c index e8b4737..971a6e5 100644 --- a/conn/session.c +++ b/conn/session.c @@ -37,7 +37,6 @@ struct SessionContext* libp2p_session_context_new() { context->shared_key = NULL; context->shared_key_size = 0; context->traffic_type = TCP; - context->last_comm_epoch = 0; } return context; } diff --git a/conn/transport_dialer.c b/conn/transport_dialer.c index 21f6e5e..88ecfb9 100644 --- a/conn/transport_dialer.c +++ b/conn/transport_dialer.c @@ -8,8 +8,12 @@ struct TransportDialer* libp2p_conn_transport_dialer_new(char* peer_id, struct P if (out != NULL) { out->peer_id = malloc(strlen(peer_id) + 1); strcpy(out->peer_id, peer_id); - out->private_key = (struct PrivateKey*)malloc(sizeof(struct PrivateKey)); - libp2p_crypto_private_key_copy(private_key, out->private_key); + if (private_key != NULL) { + out->private_key = (struct PrivateKey*)malloc(sizeof(struct PrivateKey)); + libp2p_crypto_private_key_copy(private_key, out->private_key); + } else { + out->private_key = NULL; + } } return out; } diff --git a/include/libp2p/conn/dialer.h b/include/libp2p/conn/dialer.h index bbe9f78..7f5383a 100644 --- a/include/libp2p/conn/dialer.h +++ b/include/libp2p/conn/dialer.h @@ -23,6 +23,7 @@ struct Dialer { */ char* peer_id; // the local peer ID as null terminated string struct RsaPrivateKey* private_key; // used to initiate secure connections, can be NULL, and connections will not be secured + struct Peerstore* peerstore; // used by secio to add peers to the collection /** * A linked list of transport dialers. A transport dialer can be selected @@ -42,7 +43,7 @@ struct Dialer { * @param private_key the local private key * @returns a new Dialer struct */ -struct Dialer* libp2p_conn_dialer_new(struct Libp2pPeer* peer, struct RsaPrivateKey* private_key); +struct Dialer* libp2p_conn_dialer_new(struct Libp2pPeer* peer, struct Peerstore* peerstore, struct PrivateKey* private_key); /** * free resources from the Dialer struct diff --git a/include/libp2p/conn/session.h b/include/libp2p/conn/session.h index 41e269f..816efd8 100644 --- a/include/libp2p/conn/session.h +++ b/include/libp2p/conn/session.h @@ -62,7 +62,6 @@ struct SessionContext { struct StretchedKey* remote_stretched_key; unsigned char* remote_ephemeral_public_key; size_t remote_ephemeral_public_key_size; - unsigned long long last_comm_epoch; }; /*** diff --git a/include/libp2p/net/multistream.h b/include/libp2p/net/multistream.h index ed17b73..6ac018d 100644 --- a/include/libp2p/net/multistream.h +++ b/include/libp2p/net/multistream.h @@ -83,10 +83,10 @@ struct Stream* libp2p_net_multistream_connect_with_timeout(const char* hostname, * NOTE: the SessionContext should already contain the connected stream. If not, use * libp2p_net_multistream_connect instead of this method. * - * @param session the struct Session, which contains all the context info + * @param ctx the MultistreamContext * @returns true(1) on success, or false(0) */ -int libp2p_net_multistream_negotiate(struct SessionContext* session); +int libp2p_net_multistream_negotiate(struct MultistreamContext* ctx); /** * Expect to read a message, and follow its instructions diff --git a/include/libp2p/net/stream.h b/include/libp2p/net/stream.h index d818af1..5936a0a 100644 --- a/include/libp2p/net/stream.h +++ b/include/libp2p/net/stream.h @@ -31,6 +31,7 @@ void libp2p_stream_message_free(struct StreamMessage* msg); */ struct ConnectionContext { int socket_descriptor; + unsigned long long last_comm_epoch; struct SessionContext* session_context; }; diff --git a/include/libp2p/peer/peer.h b/include/libp2p/peer/peer.h index f20f319..cd458cb 100644 --- a/include/libp2p/peer/peer.h +++ b/include/libp2p/peer/peer.h @@ -140,3 +140,9 @@ int libp2p_peer_protobuf_encode_with_alloc(struct Libp2pPeer* in, unsigned char* */ int libp2p_peer_protobuf_decode(unsigned char* in, size_t in_size, struct Libp2pPeer** out); +/*** + * Get the last time we communicated with this peer as an epoch + * @param peer the peer to examine + * @returns the last time we communicated with this peer (0 indicates never in this session, or disconnected) + */ +unsigned long long libp2p_peer_last_comm(const struct Libp2pPeer* peer); diff --git a/include/libp2p/secio/exchange.h b/include/libp2p/secio/exchange.h index 3661a00..1850682 100644 --- a/include/libp2p/secio/exchange.h +++ b/include/libp2p/secio/exchange.h @@ -1,5 +1,8 @@ #pragma once +#include "libp2p/crypto/rsa.h" +#include "libp2p/conn/session.h" + struct Exchange { unsigned char* epubkey; size_t epubkey_size; @@ -35,3 +38,13 @@ int libp2p_secio_exchange_protobuf_encode(struct Exchange* in, unsigned char* bu * @returns true(1) on success, otherwise false(0) */ int libp2p_secio_exchange_protobuf_decode(unsigned char* buffer, size_t max_buffer_length, struct Exchange** out); + +/*** + * Build an exchange object based on passed in values + * @param local_session the SessionContext + * @param private_key the local RsaPrivateKey + * @param bytes_to_be_signed the bytes that should be signed + * @param bytes_size the length of bytes_to_be_signed + * @returns an Exchange object or NULL + */ +struct Exchange* libp2p_secio_exchange_build(struct SessionContext* local_session, struct RsaPrivateKey* private_key, const char* bytes_to_be_signed, size_t bytes_size); diff --git a/include/libp2p/secio/propose.h b/include/libp2p/secio/propose.h index 4007d06..6b15527 100644 --- a/include/libp2p/secio/propose.h +++ b/include/libp2p/secio/propose.h @@ -1,5 +1,7 @@ #pragma once +#include "libp2p/crypto/rsa.h" + struct Propose { unsigned char* rand; size_t rand_size; @@ -51,3 +53,15 @@ int libp2p_secio_propose_protobuf_encode(struct Propose* in, unsigned char* buff * @returns true(1) on success, otherwise false(0) */ int libp2p_secio_propose_protobuf_decode(const unsigned char* buffer, size_t max_buffer_length, struct Propose** out); + +/*** + * Build a propose structure for sending to the remote client + * @param nonce a 16 byte nonce, previously defined + * @param rsa_key the local RSA key + * @param supportedExchanges a comma separated list of supported exchange protocols + * @param supportedCiphers a comma separated list of supported ciphers + * @param supportedHashes a comma separated list of supported hashes + * @returns an initialized Propose struct, or NULL + */ +struct Propose* libp2p_secio_propose_build(unsigned char nonce[16], struct RsaPrivateKey* rsa_key, + const char* supportedExchanges, const char* supportedCiphers, const char* supportedHashes); diff --git a/include/libp2p/secio/secio.h b/include/libp2p/secio/secio.h index 7fe07bd..2b937d6 100644 --- a/include/libp2p/secio/secio.h +++ b/include/libp2p/secio/secio.h @@ -23,9 +23,12 @@ struct Libp2pProtocolHandler* libp2p_secio_build_protocol_handler(struct RsaPriv * Initiates a secio handshake. Use this method when you want to initiate a secio * session. This should not be used to respond to incoming secio requests * @param parent_stream the parent stream + * @param remote_peer the remote peer + * @param peerstore the peerstore + * @param rsa_private_key the local private key * @returns a Secio Stream */ -struct Stream* libp2p_secio_stream_new(struct Stream* parent_stream); +struct Stream* libp2p_secio_stream_new(struct Stream* parent_stream, struct Libp2pPeer* remote_peer, struct Peerstore* peerstore, struct RsaPrivateKey* rsa_private_key); /*** * Initiates a secio handshake. Use this method when you want to initiate a secio diff --git a/net/connectionstream.c b/net/connectionstream.c index ff1191f..779b24a 100644 --- a/net/connectionstream.c +++ b/net/connectionstream.c @@ -21,9 +21,14 @@ int libp2p_net_connection_close(void* stream_context) { if (stream_context == NULL) return 0; struct ConnectionContext* ctx = (struct ConnectionContext*)stream_context; - if (close(ctx->socket_descriptor) == 0) - // everything was okay + if (ctx != NULL) { + if (ctx->socket_descriptor > 0) { + close(ctx->socket_descriptor); + } + free(ctx); + ctx = NULL; return 1; + } // something went wrong return 0; } @@ -125,6 +130,12 @@ struct Stream* libp2p_net_connection_new(int fd, char* ip, int port) { struct ConnectionContext* ctx = (struct ConnectionContext*) malloc(sizeof(struct ConnectionContext)); if (ctx != NULL) { out->stream_context = ctx; + ctx->socket_descriptor = fd; + if (!socket_connect4_with_timeout(ctx->socket_descriptor, hostname_to_ip(ip), port, 10) == 0) { + // unable to connect + libp2p_stream_free(out); + out = NULL; + } } } return out; diff --git a/net/multistream.c b/net/multistream.c index fe793fa..2c85ca8 100644 --- a/net/multistream.c +++ b/net/multistream.c @@ -190,7 +190,7 @@ int libp2p_net_multistream_peek(void* stream_context) { if (parent_stream == NULL) return -1; - return parent_stream->peek(parent_stream); + return parent_stream->peek(parent_stream->stream_context); } /** @@ -211,19 +211,19 @@ int libp2p_net_multistream_write(void* stream_context, struct StreamMessage* inc varint_encode(incoming->data_size, &varint[0], 12, &varint_size); // now put the size with the data struct StreamMessage outgoing; - outgoing.data = (uint8_t*) malloc(varint_size + incoming->data_size); + outgoing.data_size = varint_size + incoming->data_size; + outgoing.data = (uint8_t*) malloc(outgoing.data_size); if (outgoing.data == NULL) { return 0; } - memset(outgoing.data, 0, incoming->data_size + varint_size); + memset(outgoing.data, 0, outgoing.data_size); memcpy(outgoing.data, varint, varint_size); memcpy(&outgoing.data[varint_size], incoming->data, incoming->data_size); // now ship it - num_bytes = parent_stream->write(parent_stream, &outgoing); - if (num_bytes > 0) { - // update the last time we communicated - multistream_context->session_context->last_comm_epoch = os_utils_gmtime(); - } + num_bytes = parent_stream->write(parent_stream->stream_context, &outgoing); + // subtract the varint if all went well + if (num_bytes == outgoing.data_size) + num_bytes = incoming->data_size; free(outgoing.data); } @@ -244,6 +244,7 @@ int libp2p_net_multistream_read(void* stream_context, struct StreamMessage** res // find out the length uint8_t varint[12]; + memset(varint, 0, 12); size_t num_bytes_requested = 0; size_t varint_length = 0; for(int i = 0; i < 12; i++) { @@ -353,10 +354,10 @@ struct Stream* libp2p_net_multistream_connect_with_timeout(const char* hostname, * NOTE: the SessionContext should already contain the connected stream. If not, use * libp2p_net_multistream_connect instead of this method. * - * @param session the struct Session, which contains all the context info + * @param ctx a MultistreamContext * @returns true(1) on success, or false(0) */ -int libp2p_net_multistream_negotiate(struct SessionContext* session) { +int libp2p_net_multistream_negotiate(struct MultistreamContext* ctx) { const char* protocolID = "/multistream/1.0.0\n"; struct StreamMessage outgoing; struct StreamMessage* results = NULL; @@ -364,13 +365,13 @@ int libp2p_net_multistream_negotiate(struct SessionContext* session) { // send the protocol id outgoing.data = (uint8_t*)protocolID; outgoing.data_size = strlen(protocolID); - if (!libp2p_net_multistream_write(session, &outgoing)) + if (!libp2p_net_multistream_write(ctx, &outgoing)) goto exit; // expect the same back - libp2p_net_multistream_read(session, &results, multistream_default_timeout); + libp2p_net_multistream_read(ctx, &results, multistream_default_timeout); if (results == NULL || results->data_size == 0) goto exit; - if (strncmp((char*)results, protocolID, strlen(protocolID)) != 0) + if (strncmp((char*)results->data, protocolID, strlen(protocolID)) != 0) goto exit; retVal = 1; exit: @@ -401,6 +402,21 @@ struct Stream* libp2p_net_multistream_stream_new(struct Stream* parent_stream) { out->write = libp2p_net_multistream_write; out->peek = libp2p_net_multistream_peek; out->address = parent_stream->address; + // build MultistreamContext + struct MultistreamContext* ctx = (struct MultistreamContext*) malloc(sizeof(struct MultistreamContext)); + if (ctx == NULL) { + libp2p_net_multistream_stream_free(out); + return NULL; + } + out->stream_context = ctx; + ctx->stream = out; + ctx->handlers = NULL; + ctx->session_context = NULL; + // attempt to negotiate multistream protocol + if (!libp2p_net_multistream_negotiate(ctx)) { + libp2p_net_multistream_stream_free(out); + return NULL; + } } return out; } diff --git a/net/socket.c b/net/socket.c index 69b6bda..c67682e 100644 --- a/net/socket.c +++ b/net/socket.c @@ -120,15 +120,14 @@ int socket_connect4(int s, uint32_t ip, uint16_t port) { int socket_connect4_with_timeout(int s, uint32_t ip, uint16_t port, int timeout_secs) { struct sockaddr_in sa; - //long args; // fctl args with O_NONBLOCK - //long orig_args; // fctl args; + long args; // fctl args with O_NONBLOCK + long orig_args; // fctl args; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = ip; - /* // set to non blocking orig_args = fcntl(s, F_GETFL, NULL); if (orig_args < 0) { @@ -148,36 +147,33 @@ int socket_connect4_with_timeout(int s, uint32_t ip, uint16_t port, int timeout_ libp2p_logger_debug("socket", "Socket already non-blocking during connect.\n"); } - */ - // connect int retVal = connect(s, (struct sockaddr *) &sa, sizeof sa); - if (retVal == -1 && errno == EINPROGRESS) { + int tried_secs = 0; + while (retVal == -1 && errno == EINPROGRESS && tried_secs < timeout_secs) { libp2p_logger_debug("socket", "Socket connect unsuccessful. Waiting to try again.\n"); // wait for timeout - sleep(timeout_secs); + sleep(1); + tried_secs++; retVal = connect(s, (struct sockaddr *) &sa, sizeof sa); if (retVal == -1 && errno == EALREADY) { libp2p_logger_debug("socket", "Socket connect completed.\n"); retVal = 0; - } else { - libp2p_logger_debug("socket", "Socket connect worked on second try.\n"); - } - } else { - if (retVal == -1) { - libp2p_logger_debug("socket", "Socket connect failed with error %d.\n", errno); + break; } } - /* - if ( retVal == 0 && args != orig_args) { + if (retVal == -1) { + libp2p_logger_debug("socket", "Socket connect failed with error %d.\n", errno); + } + + if (args != orig_args) { // set back to blocking libp2p_logger_debug("socket", "Setting socket back to blocking.\n"); args = fcntl(s, F_GETFL, NULL); args &= (~O_NONBLOCK); fcntl(s, F_SETFL, args); } - */ return retVal; } diff --git a/net/stream.c b/net/stream.c index 42bd9d1..6bd5590 100644 --- a/net/stream.c +++ b/net/stream.c @@ -1,5 +1,6 @@ #include +#include "multiaddr/multiaddr.h" #include "libp2p/net/stream.h" struct Stream* libp2p_stream_new() { @@ -20,6 +21,14 @@ struct Stream* libp2p_stream_new() { void libp2p_stream_free(struct Stream* stream) { if (stream != NULL) { + if (stream->socket_mutex != NULL) { + free(stream->socket_mutex); + stream->socket_mutex = NULL; + } + if (stream->address != NULL) { + multiaddress_free(stream->address); + stream->address = NULL; + } free(stream); } } diff --git a/peer/peer.c b/peer/peer.c index ab904f9..cc69531 100644 --- a/peer/peer.c +++ b/peer/peer.c @@ -369,3 +369,21 @@ int libp2p_peer_compare(const struct Libp2pPeer* a, const struct Libp2pPeer* b) } return 0; } + +/*** + * Get the last time we communicated with this peer as an epoch + * @param peer the peer to examine + * @returns the last time we communicated with this peer (0 indicates never in this session, or disconnected) + */ +unsigned long long libp2p_peer_last_comm(const struct Libp2pPeer* peer) { + unsigned long long retVal = 0; + if (peer != NULL) { + if (peer->sessionContext != NULL) { + if (peer->sessionContext->insecure_stream != NULL) { + struct ConnectionContext* ctx = (struct ConnectionContext*)peer->sessionContext->insecure_stream->stream_context; + retVal = ctx->last_comm_epoch; + } + } + } + return retVal; +} diff --git a/secio/exchange.c b/secio/exchange.c index d49b542..99881cd 100644 --- a/secio/exchange.c +++ b/secio/exchange.c @@ -2,6 +2,7 @@ #include #include "libp2p/secio/exchange.h" +#include "libp2p/crypto/ephemeral.h" #include "protobuf.h" // epubkey signature @@ -108,3 +109,38 @@ exit: return retVal; } + +/*** + * Forward declaration + */ +int libp2p_secio_sign(struct PrivateKey* priv, const char* bytes_to_send, size_t bytes_size, uint8_t** signature, size_t* signature_size); + +/*** + * Build an exchange object based on passed in values + * @param local_session the SessionContext + * @param private_key the local RsaPrivateKey + * @param bytes_to_be_signed the bytes that should be signed + * @param bytes_size the length of bytes_to_be_signed + * @returns an Exchange object or NULL + */ +struct Exchange* libp2p_secio_exchange_build(struct SessionContext* local_session, struct RsaPrivateKey* private_key, const char* bytes_to_be_signed, size_t bytes_size) { + struct Exchange* exchange_out = libp2p_secio_exchange_new(); + if (exchange_out != NULL) { + // don't send the first byte (to stay compatible with GO version) + exchange_out->epubkey = (unsigned char*)malloc(local_session->ephemeral_private_key->public_key->bytes_size - 1); + if (exchange_out->epubkey == NULL) { + libp2p_secio_exchange_free(exchange_out); + return NULL; + } + memcpy(exchange_out->epubkey, &local_session->ephemeral_private_key->public_key->bytes[1], local_session->ephemeral_private_key->public_key->bytes_size - 1); + exchange_out->epubkey_size = local_session->ephemeral_private_key->public_key->bytes_size - 1; + + struct PrivateKey* priv = libp2p_crypto_private_key_new(); + priv->type = KEYTYPE_RSA; + priv->data = (unsigned char*)private_key->der; + priv->data_size = private_key->der_length; + libp2p_secio_sign(priv, bytes_to_be_signed, bytes_size, &exchange_out->signature, &exchange_out->signature_size); + free(priv); + } + return exchange_out; +} diff --git a/secio/propose.c b/secio/propose.c index fc49b85..d3f7d21 100644 --- a/secio/propose.c +++ b/secio/propose.c @@ -3,6 +3,7 @@ #include "protobuf.h" #include "libp2p/secio/propose.h" +#include "libp2p/crypto/key.h" // rand pubkey exchanges ciphers hashes enum WireType secio_propose_message_fields[] = { WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED }; @@ -159,3 +160,54 @@ exit: } return retVal; } + +/*** + * Build a propose structure for sending to the remote client + * @param nonce a 16 byte nonce, previously defined + * @param rsa_key the local RSA key + * @param supportedExchanges a comma separated list of supported exchange protocols + * @param supportedCiphers a comma separated list of supported ciphers + * @param supportedHashes a comma separated list of supported hashes + * @returns an initialized Propose struct, or NULL + */ +struct Propose* libp2p_secio_propose_build(unsigned char nonce[16], struct RsaPrivateKey* rsa_key, + const char* supportedExchanges, const char* supportedCiphers, const char* supportedHashes) { + struct Propose* propose = libp2p_secio_propose_new(); + if (propose != NULL) { + // nonce + propose->rand = malloc(16); + memcpy(propose->rand, nonce, 16); + propose->rand_size = 16; + // ciphers, exchanges, hashes + propose->ciphers_size = strlen(supportedCiphers); + propose->ciphers = malloc(propose->ciphers_size + 1); + strcpy(propose->ciphers, supportedCiphers); + propose->exchanges_size = strlen(supportedExchanges); + propose->exchanges = malloc(propose->exchanges_size + 1); + strcpy(propose->exchanges, supportedExchanges); + propose->hashes_size = strlen(supportedHashes); + propose->hashes = malloc(propose->hashes_size + 1); + strcpy(propose->hashes, supportedHashes); + // key + struct PublicKey pub_key; + pub_key.type = KEYTYPE_RSA; + pub_key.data_size = rsa_key->public_key_length; + pub_key.data = malloc(pub_key.data_size); + memcpy(pub_key.data, rsa_key->public_key_der, rsa_key->public_key_length); + propose->public_key_size = libp2p_crypto_public_key_protobuf_encode_size(&pub_key); + propose->public_key = malloc(propose->public_key_size); + if (propose->public_key == NULL) { + free(pub_key.data); + libp2p_secio_propose_free(propose); + return NULL; + } + if (libp2p_crypto_public_key_protobuf_encode(&pub_key, propose->public_key, propose->public_key_size, &propose->public_key_size) == 0) { + free(pub_key.data); + libp2p_secio_propose_free(propose); + return NULL; + } + free(pub_key.data); + } + return propose; + +} diff --git a/secio/secio.c b/secio/secio.c index 58dc5f4..5ae6547 100644 --- a/secio/secio.c +++ b/secio/secio.c @@ -73,9 +73,12 @@ int libp2p_secio_shutdown(void* context) { * Initiates a secio handshake. Use this method when you want to initiate a secio * session. This should not be used to respond to incoming secio requests * @param parent_stream the parent stream + * @param remote_peer the remote peer + * @param peerstore the peerstore + * @param rsa_private_key the local private key * @returns a Secio Stream */ -struct Stream* libp2p_secio_stream_new(struct Stream* parent_stream) { +struct Stream* libp2p_secio_stream_new(struct Stream* parent_stream, struct Libp2pPeer* remote_peer, struct Peerstore* peerstore, struct RsaPrivateKey* rsa_private_key) { struct Stream* new_stream = libp2p_stream_new(); if (new_stream != NULL) { struct SecioContext* ctx = (struct SecioContext*) malloc(sizeof(struct SecioContext)); @@ -85,6 +88,10 @@ struct Stream* libp2p_secio_stream_new(struct Stream* parent_stream) { return NULL; } new_stream->stream_context = ctx; + ctx->stream = new_stream; + ctx->session_context = remote_peer->sessionContext; + ctx->peer_store = peerstore; + ctx->private_key = rsa_private_key; new_stream->parent_stream = parent_stream; if (!libp2p_secio_send_protocol(ctx) || !libp2p_secio_receive_protocol(ctx) @@ -555,6 +562,173 @@ int libp2p_secio_receive_protocol(struct SecioContext* ctx) { return retVal; } +/** + * Navigate down the tree of streams to get the raw socket descriptor + * @param stream the stream + * @returns the raw socket descriptor + */ +int libp2p_secio_get_socket_descriptor(struct Stream* stream) { + struct Stream* current = stream; + while (current->parent_stream != NULL) + current = current->parent_stream; + struct ConnectionContext* ctx = current->stream_context; + return ctx->socket_descriptor; +} + +/*** + * Write bytes to an unencrypted stream + * @param session the session information + * @param bytes the bytes to write + * @param data_length the number of bytes to write + * @returns the number of bytes written + */ +int libp2p_secio_unencrypted_write(struct Stream* secio_stream, struct StreamMessage* msg) { + int num_bytes = 0; + + int socket_descriptor = libp2p_secio_get_socket_descriptor(secio_stream); + + if (msg != NULL && msg->data_size > 0) { // only do this is if there is something to send + // first send the size + uint32_t size = htonl(msg->data_size); + char* size_as_char = (char*)&size; + int left = 4; + int written = 0; + int written_this_time = 0; + do { + written_this_time = socket_write(socket_descriptor, &size_as_char[written], left, 0); + if (written_this_time < 0) { + written_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + left = left - written_this_time; + } while (left > 0); + // then send the actual data + left = msg->data_size; + written = 0; + do { + written_this_time = socket_write(socket_descriptor, (char*)&msg->data[written], left, 0); + if (written_this_time < 0) { + written_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + return 0; + } + } + left = left - written_this_time; + written += written_this_time; + } while (left > 0); + num_bytes = written; + } // there was something to send + + return num_bytes; +} + +/*** + * Read bytes from the incoming stream + * @param session the session information + * @param results where to put the bytes read + * @param results_size the size of the results + * @returns the number of bytes read + */ +int libp2p_secio_unencrypted_read(struct Stream* secio_stream, struct StreamMessage** msg, int timeout_secs) { + uint32_t buffer_size = 0; + + if (secio_stream == NULL) { + libp2p_logger_error("secio", "Attempted unencrypted read on invalid session.\n"); + return 0; + } + + int socket_descriptor = libp2p_secio_get_socket_descriptor(secio_stream); + + // first read the 4 byte integer + char* size = (char*)&buffer_size; + int left = 4; + int read = 0; + int read_this_time = 0; + int time_left = timeout_secs; + do { + read_this_time = socket_read(socket_descriptor, &size[read], 1, 0, timeout_secs); + if (read_this_time <= 0) { + if ( errno == EINPROGRESS ) { + time_left--; + sleep(1); + if (time_left == 0) + break; + continue; + } + if ( errno == EAGAIN || errno == EWOULDBLOCK ) { + // TODO: use epoll or select to wait for socket to be writable + libp2p_logger_debug("secio", "Attempted read, but got EAGAIN or EWOULDBLOCK. Code %d.\n", errno); + return 0; + } else { + // is this really an error? + if (errno != 0) { + libp2p_logger_error("secio", "Error in libp2p_secio_unencrypted_read: %s\n", strerror(errno)); + return 0; + } + else + libp2p_logger_error("secio", "Error in libp2p_secio_unencrypted_read: 0 bytes read, but errno shows no error. Trying again.\n"); + } + } else { + left = left - read_this_time; + read += read_this_time; + } + } while (left > 0); + buffer_size = ntohl(buffer_size); + if (buffer_size == 0) { + libp2p_logger_error("secio", "unencrypted read buffer size is 0.\n"); + return 0; + } + + // now read the number of bytes we've found, minus the 4 that we just read + left = buffer_size; + read = 0; + read_this_time = 0; + *msg = libp2p_stream_message_new(); + struct StreamMessage* m = *msg; + if (m == NULL) { + libp2p_logger_error("secio", "Unable to allocate memory for the incoming message. Size: %ulld", left); + return 0; + } + m->data_size = left; + m->data = (uint8_t*) malloc(left); + unsigned char* ptr = m->data; + time_left = timeout_secs; + do { + read_this_time = socket_read(socket_descriptor, (char*)&ptr[read], left, 0, timeout_secs); + if (read_this_time < 0) { + if (errno == EINPROGRESS) { + sleep(1); + time_left--; + if (time_left == 0) + break; + continue; + } + read_this_time = 0; + if ( (errno == EAGAIN) || (errno == EWOULDBLOCK)) { + // TODO: use epoll or select to wait for socket to be writable + } else { + libp2p_logger_error("secio", "read from socket returned %d.\n", errno); + return 0; + } + } else if (read_this_time == 0) { + // socket_read returned 0, which it shouldn't + libp2p_logger_error("secio", "socket_read returned 0 trying to read from stream %d.\n", socket_descriptor); + return 0; + } + left = left - read_this_time; + } while (left > 0); + + m->data_size = buffer_size; + return buffer_size; +} + + /** * Initialize state for the sha256 stream cipher * @param session the SessionContext struct that contains the variables to initialize @@ -642,7 +816,7 @@ int libp2p_secio_encrypted_write(void* stream_context, struct StreamMessage* byt return 0; } - int retVal = parent_stream->write(parent_stream->stream_context, &outgoing); + int retVal = libp2p_secio_unencrypted_write(parent_stream, &outgoing); if (!retVal) { libp2p_logger_error("secio", "secio_unencrypted_write returned false\n"); } @@ -732,7 +906,7 @@ int libp2p_secio_encrypted_read(void* stream_context, struct StreamMessage** byt // reader uses the remote cipher and mac // read the data struct StreamMessage* msg = NULL; - if (!parent_stream->read(parent_stream->stream_context, &msg, timeout_secs)) { + if (!libp2p_secio_unencrypted_read(parent_stream, &msg, 10)) { libp2p_logger_error("secio", "Unencrypted_read returned false.\n"); goto exit; } @@ -744,6 +918,55 @@ int libp2p_secio_encrypted_read(void* stream_context, struct StreamMessage** byt return retVal; } +struct Libp2pPeer* libp2p_secio_get_peer_or_add(struct Peerstore* peerstore, struct SessionContext* local_session) { + struct Libp2pPeer* remote_peer = NULL; + + // see if we already have this peer + if (peerstore != NULL) + remote_peer = libp2p_peerstore_get_peer(peerstore, (unsigned char*)local_session->remote_peer_id, strlen(local_session->remote_peer_id)); + if (remote_peer == NULL) { + remote_peer = libp2p_peer_new(); + // put peer information in Libp2pPeer struct + remote_peer->id_size = strlen(local_session->remote_peer_id); + if (remote_peer->id_size > 0) { + remote_peer->id = malloc(remote_peer->id_size + 1); + if (remote_peer->id != NULL) { + memcpy(remote_peer->id, local_session->remote_peer_id, remote_peer->id_size); + remote_peer->id[remote_peer->id_size] = 0; + } + } + remote_peer->sessionContext = local_session; + // add a multiaddress to the peer (if we have what we need) + char url[100]; + sprintf(url, "/ip4/%s/tcp/%d/ipfs/%s", local_session->host, local_session->port, libp2p_peer_id_to_string(remote_peer)); + struct MultiAddress* ma = multiaddress_new_from_string(url); + if (ma == NULL) { + libp2p_logger_error("secio", "Unable to generate MultiAddress from [%s].\n", url); + libp2p_peer_free(remote_peer); + return NULL; + } else { + libp2p_logger_debug("secio", "Adding %s to peer %s.\n", url, libp2p_peer_id_to_string(remote_peer)); + struct Libp2pLinkedList* ma_ll = libp2p_utils_linked_list_new(); + ma_ll->item = ma; + remote_peer->addr_head = ma_ll; + } + if (peerstore != NULL) { + libp2p_logger_debug("secio", "New connection. Adding Peer to Peerstore.\n"); + libp2p_peerstore_add_peer(peerstore, remote_peer); + } + } else { + // we already had one in the peerstore + if (remote_peer->sessionContext != local_session) { + // clean up old session context + libp2p_logger_debug("secio", "Same remote connected. Replacing SessionContext.\n"); + libp2p_session_context_free(remote_peer->sessionContext); + remote_peer->sessionContext = local_session; + } + } + remote_peer->connection_type = CONNECTION_TYPE_CONNECTED; + return remote_peer; +} + /*** * performs initial communication over an insecure channel to share * keys, IDs, and initiate connection. This is a framed messaging system @@ -755,7 +978,7 @@ int libp2p_secio_encrypted_read(void* stream_context, struct StreamMessage** byt */ int libp2p_secio_handshake(struct SecioContext* secio_context) { int retVal = 0; - size_t results_size = 0, bytes_written = 0; + size_t bytes_written = 0; struct StreamMessage* incoming = NULL; struct StreamMessage outgoing; // used for outgoing messages unsigned char* propose_in_bytes = NULL; // the remote protobuf @@ -774,8 +997,6 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { char* char_buffer = NULL; size_t char_buffer_length = 0; struct StretchedKey* k1 = NULL, *k2 = NULL; - struct PrivateKey* priv = NULL; - struct PublicKey pub_key = {0}; struct Libp2pPeer* remote_peer = NULL; struct SessionContext* local_session = secio_context->session_context; @@ -792,38 +1013,8 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { } // Build the proposal to be sent to the new connection: - propose_out = libp2p_secio_propose_new(); - libp2p_secio_propose_set_property((void**)&propose_out->rand, &propose_out->rand_size, local_session->local_nonce, 16); - - // public key - protobuf it and stick it in propose_out - pub_key.type = KEYTYPE_RSA; - pub_key.data_size = private_key->public_key_length; - pub_key.data = malloc(pub_key.data_size); - memcpy(pub_key.data, private_key->public_key_der, private_key->public_key_length); - results_size = libp2p_crypto_public_key_protobuf_encode_size(&pub_key); - results = malloc(results_size); - if (results == NULL) { - free(pub_key.data); - goto exit; - } - if (libp2p_crypto_public_key_protobuf_encode(&pub_key, results, results_size, &results_size) == 0) { - free(pub_key.data); - goto exit; - } - free(pub_key.data); - - propose_out->public_key_size = results_size; - propose_out->public_key = malloc(results_size); - memcpy(propose_out->public_key, results, results_size); - free(results); - results = NULL; - results_size = 0; - // supported exchanges - libp2p_secio_propose_set_property((void**)&propose_out->exchanges, &propose_out->exchanges_size, SupportedExchanges, strlen(SupportedExchanges)); - // supported ciphers - libp2p_secio_propose_set_property((void**)&propose_out->ciphers, &propose_out->ciphers_size, SupportedCiphers, strlen(SupportedCiphers)); - // supported hashes - libp2p_secio_propose_set_property((void**)&propose_out->hashes, &propose_out->hashes_size, SupportedHashes, strlen(SupportedHashes)); + propose_out = libp2p_secio_propose_build(local_session->local_nonce, private_key, + SupportedExchanges, SupportedCiphers, SupportedHashes); // protobuf the proposal propose_out_size = libp2p_secio_propose_protobuf_encode_size(propose_out); @@ -831,19 +1022,18 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { if (libp2p_secio_propose_protobuf_encode(propose_out, propose_out_bytes, propose_out_size, &propose_out_size) == 0) goto exit; - // now send the protocol and Propose struct + // now send the Propose struct outgoing.data = propose_out_bytes; outgoing.data_size = propose_out_size; - bytes_written = secio_context->stream->parent_stream->write(secio_context->stream->parent_stream->stream_context, &outgoing); + bytes_written = libp2p_secio_unencrypted_write(secio_context->stream, &outgoing); if (bytes_written != propose_out_size) { libp2p_logger_error("secio", "Sent propose_out, but did not write the correct number of bytes. Should be %d but was %d.\n", propose_out_size, bytes_written); - } else { - //libp2p_logger_debug("secio", "Sent propose out.\n"); + goto exit; } - // try to get the Propse struct from the remote peer - bytes_written = secio_context->stream->parent_stream->read(secio_context->stream->parent_stream->stream_context, &incoming, 10); + // try to get the Propose struct from the remote peer + bytes_written = libp2p_secio_unencrypted_read(secio_context->stream, &incoming, 10); if (bytes_written <= 0) { libp2p_logger_error("secio", "Unable to get the remote's Propose struct.\n"); goto exit; @@ -851,7 +1041,7 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { //libp2p_logger_debug("secio", "Received their propose struct.\n"); } - if (!libp2p_secio_propose_protobuf_decode(incoming->data, incoming->data_size -1, &propose_in)) { + if (!libp2p_secio_propose_protobuf_decode(incoming->data, incoming->data_size, &propose_in)) { libp2p_logger_error("secio", "Unable to un-protobuf the remote's Propose struct\n"); goto exit; } @@ -862,50 +1052,16 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { if (propose_in->rand_size != 16) goto exit; memcpy(local_session->remote_nonce, propose_in->rand, 16); + // get public key and put it in a struct PublicKey if (!libp2p_crypto_public_key_protobuf_decode(propose_in->public_key, propose_in->public_key_size, &public_key)) goto exit; + // generate their peer id libp2p_crypto_public_key_to_peer_id(public_key, &local_session->remote_peer_id); - // see if we already have this peer - int new_peer = 0; - remote_peer = libp2p_peerstore_get_peer(peerstore, (unsigned char*)local_session->remote_peer_id, strlen(local_session->remote_peer_id)); - if (remote_peer == NULL) { - remote_peer = libp2p_peer_new(); - new_peer = 1; - // put peer information in Libp2pPeer struct - remote_peer->id_size = strlen(local_session->remote_peer_id); - if (remote_peer->id_size > 0) { - remote_peer->id = malloc(remote_peer->id_size + 1); - if (remote_peer->id != NULL) { - memcpy(remote_peer->id, local_session->remote_peer_id, remote_peer->id_size); - remote_peer->id[remote_peer->id_size] = 0; - } - } - remote_peer->sessionContext = local_session; - // add a multiaddress to the peer (if we have what we need) - char url[100]; - sprintf(url, "/ip4/%s/tcp/%d/ipfs/%s", local_session->host, local_session->port, libp2p_peer_id_to_string(remote_peer)); - struct MultiAddress* ma = multiaddress_new_from_string(url); - if (ma == NULL) { - libp2p_logger_error("secio", "Unable to generate MultiAddress from [%s].\n", url); - goto exit; - } else { - libp2p_logger_debug("secio", "Adding %s to peer %s.\n", url, libp2p_peer_id_to_string(remote_peer)); - struct Libp2pLinkedList* ma_ll = libp2p_utils_linked_list_new(); - ma_ll->item = ma; - remote_peer->addr_head = ma_ll; - } - } else { - if (remote_peer->sessionContext != local_session) { - // clean up old session context - libp2p_logger_debug("secio", "Same remote connected. Replacing SessionContext.\n"); - libp2p_session_context_free(remote_peer->sessionContext); - remote_peer->sessionContext = local_session; - } - } - remote_peer->connection_type = CONNECTION_TYPE_CONNECTED; + // pull the peer from the peerstore if it is there + remote_peer = libp2p_secio_get_peer_or_add(peerstore, local_session); // negotiate encryption parameters NOTE: SelectBest must match, otherwise this won't work // first determine order @@ -934,25 +1090,9 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { memcpy(&char_buffer[propose_in_size + propose_out_size], &local_session->ephemeral_private_key->public_key->bytes[1], local_session->ephemeral_private_key->public_key->bytes_size-1); // send Exchange packet - exchange_out = libp2p_secio_exchange_new(); + exchange_out = libp2p_secio_exchange_build(local_session, private_key, char_buffer, char_buffer_length); if (exchange_out == NULL) goto exit; - // don't send the first byte (to stay compatible with GO version) - exchange_out->epubkey = (unsigned char*)malloc(local_session->ephemeral_private_key->public_key->bytes_size - 1); - if (exchange_out->epubkey == NULL) - goto exit; - memcpy(exchange_out->epubkey, &local_session->ephemeral_private_key->public_key->bytes[1], local_session->ephemeral_private_key->public_key->bytes_size - 1); - exchange_out->epubkey_size = local_session->ephemeral_private_key->public_key->bytes_size - 1; - - priv = libp2p_crypto_private_key_new(); - priv->type = KEYTYPE_RSA; - priv->data = (unsigned char*)private_key->der; - priv->data_size = private_key->der_length; - libp2p_secio_sign(priv, char_buffer, char_buffer_length, &exchange_out->signature, &exchange_out->signature_size); - free(char_buffer); - char_buffer = NULL; - // yes, this is an improper disposal, but it gets the job done without fuss - free(priv); exchange_out_protobuf_size = libp2p_secio_exchange_protobuf_encode_size(exchange_out); exchange_out_protobuf = (unsigned char*)malloc(exchange_out_protobuf_size); @@ -963,7 +1103,7 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { outgoing.data = exchange_out_protobuf; outgoing.data_size = exchange_out_protobuf_size; - bytes_written = secio_context->stream->parent_stream->write(secio_context->stream->parent_stream, &outgoing); + bytes_written = libp2p_secio_unencrypted_write(secio_context->stream, &outgoing); if (exchange_out_protobuf_size != bytes_written) { libp2p_logger_error("secio", "Unable to write exchange_out\n"); goto exit; @@ -976,13 +1116,13 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { // receive Exchange packet libp2p_logger_log("secio", LOGLEVEL_DEBUG, "Reading exchange packet\n"); - bytes_written = secio_context->stream->parent_stream->read(secio_context->stream->parent_stream->stream_context, &incoming, 10); + bytes_written = libp2p_secio_unencrypted_read(secio_context->stream, &incoming, 10); if (bytes_written == 0) { libp2p_logger_error("secio", "unable to read exchange packet.\n"); libp2p_peer_handle_connection_error(remote_peer); goto exit; } else { - //libp2p_logger_debug("secio", "Read exchange packet.\n"); + libp2p_logger_debug("secio", "Read exchange packet.\n"); } libp2p_secio_exchange_protobuf_decode(incoming->data, incoming->data_size, &exchange_in); libp2p_stream_message_free(incoming); @@ -1093,11 +1233,6 @@ int libp2p_secio_handshake(struct SecioContext* secio_context) { // set secure as default local_session->default_stream = local_session->secure_stream; - if (new_peer) { - libp2p_logger_debug("secio", "New connection. Adding Peer to Peerstore.\n"); - libp2p_peerstore_add_peer(peerstore, remote_peer); - } - retVal = 1; //libp2p_logger_log("secio", LOGLEVEL_DEBUG, "Handshake complete\n"); diff --git a/test/Makefile b/test/Makefile index cfdab55..4eac3d7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -13,7 +13,7 @@ OBJS = testit.o ../../c-protobuf/protobuf.o ../../c-protobuf/varint.o ../libp2p. $(CC) -c -o $@ $< $(CFLAGS) testit_libp2p: $(OBJS) $(DEPS) - $(CC) -o $@ $(OBJS) $(LFLAGS) -lp2p -lm -lmultihash -lmultiaddr + $(CC) -o $@ $(OBJS) $(LFLAGS) -lp2p -lm -lmultihash -lmultiaddr -lpthread all_others: cd ../crypto; make all; diff --git a/test/test_conn.h b/test/test_conn.h index a8a5b32..e45830c 100644 --- a/test/test_conn.h +++ b/test/test_conn.h @@ -7,11 +7,12 @@ int test_dialer_new() { int retVal = 0; char* peer_id = "QmQSDGgxSVTkHmtT25rTzQtc5C1Yg8SpGK3BTws8YsJ4x3"; - struct RsaPrivateKey* private_key = NULL; + struct PrivateKey* private_key = NULL; struct Libp2pPeer* peer = libp2p_peer_new(); - peer->id = peer_id; + peer->id = malloc(strlen(peer_id)+1); + strcpy(peer->id, peer_id); peer->id_size = strlen(peer_id); - struct Dialer* dialer = libp2p_conn_dialer_new(peer, private_key); + struct Dialer* dialer = libp2p_conn_dialer_new(peer, NULL, private_key); if (dialer == NULL) goto exit; retVal = 1; @@ -25,21 +26,19 @@ int test_dialer_dial() { int retVal = 0; char* config_dir = "/home/parallels/.ipfs/config"; char* destination_string = "/ip4/192.210.179.217/tcp/4001/"; - char* peer_id = NULL; - struct PrivateKey* private_key = NULL; struct Dialer* dialer = NULL; struct MultiAddress* destination_address = NULL; - struct Stream* conn = NULL; + struct Stream* stream = NULL; char* result = NULL; size_t result_size = 0; struct Libp2pPeer* peer = libp2p_peer_new(); - test_helper_get_id_from_config(config_dir, &private_key, &peer->id); - if (private_key == NULL) + test_helper_get_id_from_config(config_dir, NULL, &peer->id); + if (peer->id == NULL) goto exit; peer->id_size = strlen((char*)peer->id); - dialer = libp2p_conn_dialer_new(peer, NULL); + dialer = libp2p_conn_dialer_new(peer, NULL, NULL); if (dialer == NULL) goto exit; @@ -48,8 +47,8 @@ int test_dialer_dial() { goto exit; // now try to dial - conn = libp2p_conn_dialer_get_connection(dialer, destination_address); - if (conn == NULL) + stream = libp2p_conn_dialer_get_connection(dialer, destination_address); + if (stream == NULL) goto exit; // clean up resources @@ -57,12 +56,81 @@ int test_dialer_dial() { exit: if (result != NULL) free(result); - free(peer_id); + libp2p_peer_free(peer); multiaddress_free(destination_address); libp2p_conn_dialer_free(dialer); + stream->close(stream->stream_context); + libp2p_stream_free(stream); return retVal; } +int test_dialer_join_swarm() { + int retVal = 0; + + // we need a dialer and a peer + struct Dialer* dialer = NULL; + // this is a base64 encoded private key. It makes it easier to test if it is in base64 form + // these were pulled from the GO version of ipfs + char* orig_priv_key = "CAASqQkwggSlAgEAAoIBAQCuW+8vGUb2n4xOcfPZLmfVAy6GNJ0sYrD/hVXwxBU1aBas+8lfAuLwYJXPCVBg65wZWYEbbWCevLFjwB/oZyJA1J1g+HohggH8QvuDH164FtSbgyHFip2SPR7oUHgSWRqfKXRJsVW/SPCfEt59S8JH99Q747dU9fvZKpelE9aDLf5yI8nj29TDy3c1RpkxfUwfgnbeoCwsDnakFmVdoSEp3Lnt3JlI05qE0bgvkWAaelcXSNQCmZzDwXeMk9y221FnBkL4Vs3v2lKmjLx+Qr37P/t78T+VxsjnGHPhbZTIMIjwwON6568d0j25Bj9v6biiz8iXzBR4Fmz1CQ0mqU5BAgMBAAECggEAc6EYX/29Z/SrEaLUeiUiSsuPYQUnbrYMd4gvVDpVblOXJiTciJvbcFo9P04H9h6KKO2Ih23j86FjaqmQ/4jV2HSn4hUmuW4EbwzkyzJUmHTbjj5KeTzR/pd2Fc63skNROlg9fFmUagSvPm8/CYziTOP35bfAbyGqYXyzkJA1ZExVVSOi1zGVi+lnlI1fU2Aki5F7W7F/d2AQWsh7NXUwT7e6JP7TL+Gn4bWdn3NvluwAWTMgp6/It8OU1XPgu8OhdpZQWsMBqJwr79KGLbq2SZZXAw8O+ay1JQYmmmvYzwhdDgJwl+MOtf3NiqQWFzZP8RnlHGNcXlLHHPW0FB9H+QKBgQDirtBOqjCtND6m4hEfy6A24GcITYUBg1+AYQ7uM5uZl5u9AyxfG4bPxyrspz3yS0DOV4HNQ88iwmUE8+8ZHCLSY/YIp73Nk4m8t2s46CuI7Y5GrwCnh9xTMwaUrNx4IRTWyR3OxjQtUyrXtPR6uJ83FDenXvNi//Mrzp+myxX4wwKBgQDE6L8qiVA6n9k5dyUxxMUKJqynwPcBeC+wI85gr/9wwlRYDrgMYeH6/D5prZ3N5m8+zugVQQJKLfXBG0i8BRh5xLYFCZnV2O3NwvCdENlZJZrNNoz9jM3yRV+c7OdrclxDiN0bjGEBWv8GHutNFAwuUfMe0TMdfFYpM7gBHjEMqwKBgQCWHwOhNSCrdDARwSFqFyZxcUeKvhvZlrFGigCjS9Y+b6MaF+Ho0ogDTnlk5JUnwyKWBGnYEJI7CNZx40JzNKjzAHRN4xjV7mGHc0k1FLzQH9LbiMY8LMOC7gXrrFcNz4rHe8WbzLN9WNjEpfhK1b3Lcj4xP7ab17mpR1t/0HsqlQKBgQC3S6lYIUZLrCz7b0tyTqbU0jd6WQgVmBlcL5iXLH3uKxd0eQ8eh6diiZhXq0PwPQdlQhmMX12QS8QupAVK8Ltd7p05hzxqcmq7VTHCI8MPVxAI4zTPeVjko2tjmqu5u1TjkO2yDTTnnBs1SWbj8zt7itFz6G1ajzltVTV95OrnzQKBgQDEwZxnJA2vDJEDaJ82CiMiUAFzwsoK8hDvz63kOKeEW3/yESySnUbzpxDEjzYNsK74VaXHKCGI40fDRUqZxU/+qCrFf3xDfYS4r4wfFd2Jh+tn4NzSV/EhIr9KR/ZJW+TvGks+pWUJ3mhjPEvNtlt3M64/j2D0RP2aBQtoSpeezQ=="; + char* orig_peer_id = "QmRKm1d9kSCRpMFtLYpfhhCQ3DKuSSPJa3qn9wWXfwnWnY"; + char* remote_peer_id = "QmRjLCELimPe7aUdYRVNLD7UmB1CiJdJf8HLovKAB4KwmA"; + size_t orig_peer_id_size = strlen(orig_peer_id); + struct PrivateKey* private_key = NULL; + size_t decode_base64_size = 0; + uint8_t* decode_base64 = NULL; + struct Libp2pPeer* local_peer = NULL; + struct Peerstore* peerstore = NULL; + // we need connection information to an existing ipfs instance + char* remote_swarm = "/ip4/10.211.55.2/tcp/4001"; + struct Libp2pPeer* remote_peer = NULL; + struct MultiAddress* remote_ma = NULL; + + // 1) take the private key and turn it back into bytes (decode base 64) + decode_base64_size = libp2p_crypto_encoding_base64_decode_size(strlen(orig_priv_key)); + decode_base64 = (unsigned char*)malloc(decode_base64_size); + memset(decode_base64, 0, decode_base64_size); + + if (!libp2p_crypto_encoding_base64_decode((unsigned char*)orig_priv_key, strlen(orig_priv_key), &decode_base64[0], decode_base64_size, &decode_base64_size)) + goto exit; + + if (!libp2p_crypto_private_key_protobuf_decode(decode_base64, decode_base64_size, &private_key)) + goto exit; + + // 2) make the local peer + local_peer = libp2p_peer_new(); + local_peer->id = orig_peer_id; + local_peer->id_size = orig_peer_id_size; + local_peer->is_local = 1; + peerstore = libp2p_peerstore_new(local_peer); + + // 3) make the dialer + dialer = libp2p_conn_dialer_new(local_peer, peerstore, private_key); + + // 4) make the remote peer + remote_ma = multiaddress_new_from_string(remote_swarm); + remote_peer = libp2p_peer_new(); + remote_peer->id_size = strlen(remote_peer_id); + remote_peer->id = malloc(remote_peer->id_size); + memcpy(remote_peer->id, remote_peer_id, remote_peer->id_size); + remote_peer->addr_head = libp2p_utils_linked_list_new(); + remote_peer->addr_head->item = remote_ma; + + // 5) attempt to dial + if (!libp2p_conn_dialer_join_swarm(dialer, remote_peer, 10)) + goto exit; + + retVal = 1; + exit: + if (decode_base64 != NULL) + free(decode_base64); + libp2p_peer_free(local_peer); + libp2p_peerstore_free(peerstore); + libp2p_conn_dialer_free(dialer); + libp2p_crypto_private_key_free(private_key); + return retVal; + +} + int test_dialer_dial_multistream() { int retVal = 0; char* config_dir = "/home/parallels/.ipfs/config"; @@ -82,7 +150,7 @@ int test_dialer_dial_multistream() { peer->id_size = strlen((char*)peer->id); - dialer = libp2p_conn_dialer_new(peer, NULL); + dialer = libp2p_conn_dialer_new(peer, NULL, NULL); if (dialer == NULL) goto exit; diff --git a/test/test_helper.h b/test/test_helper.h index c723e8a..b3c1d48 100644 --- a/test/test_helper.h +++ b/test/test_helper.h @@ -72,19 +72,21 @@ int test_helper_get_id_from_config(char* path, struct PrivateKey** private_ptr, memcpy(*peer_ptr, ptr, length); (*peer_ptr)[length] = 0; - // private key - ptr = strstr(contents, "PrivKey\":"); - if (ptr == NULL) - goto exit; - ptr += 9; - ptr = strstr(ptr, "\""); - ptr++; + if (private_ptr != NULL) { + // private key + ptr = strstr(contents, "PrivKey\":"); + if (ptr == NULL) + goto exit; + ptr += 9; + ptr = strstr(ptr, "\""); + ptr++; - end = strstr(ptr, "\""); - end[0] = 0; + end = strstr(ptr, "\""); + end[0] = 0; - // turn the encoded private key into a struct - *private_ptr = base64ToPrivateKey(ptr); + // turn the encoded private key into a struct + *private_ptr = base64ToPrivateKey(ptr); + } retVal = 1; exit: diff --git a/test/testit.c b/test/testit.c index cb2cc98..f167d07 100644 --- a/test/testit.c +++ b/test/testit.c @@ -15,150 +15,193 @@ #include "test_peer.h" #include "libp2p/utils/logger.h" -const char* names[] = { - "test_public_der_to_private_der", - "test_mbedtls_varint_128_binary", - "test_mbedtls_varint_128_string", - "test_crypto_rsa_private_key_der", - "test_crypto_rsa_signing", - "test_crypto_rsa_public_key_to_peer_id", - "test_crypto_x509_der_to_private2", - "test_crypto_x509_der_to_private", - "test_crypto_hashing_sha256", - //"test_multihash_encode", - //"test_multihash_decode", - //"test_multihash_base58_encode_decode", - //"test_multihash_base58_decode", - //"test_multihash_size", - "test_base58_encode_decode", - "test_base58_size", - "test_base58_max_size", - "test_base58_peer_address", - //"test_mbedtls_pk_write_key_der", - //"test_crypto_rsa_sign", - "test_crypto_encoding_base32_encode", - "test_protobuf_private_key", - "test_secio_handshake", - "test_secio_handshake_go", - "test_secio_encrypt_decrypt", - "test_secio_exchange_protobuf_encode", - "test_secio_encrypt_like_go", - "test_multistream_connect", - "test_multistream_get_list", - "test_ephemeral_key_generate", - "test_ephemeral_key_sign", - "test_dialer_new", - "test_dialer_dial", - "test_dialer_dial_multistream", - "test_record_protobuf", - "test_record_make_put_record", - "test_record_peer_protobuf", - "test_record_message_protobuf", - "test_peer", - "test_peer_protobuf", - "test_peerstore", - "test_aes" +struct test { + int index; + const char* name; + int (*func)(void); + int part_of_suite; + struct test* next; }; -int (*funcs[])(void) = { - test_public_der_to_private_der, - test_mbedtls_varint_128_binary, - test_mbedtls_varint_128_string, - test_crypto_rsa_private_key_der, - test_crypto_rsa_signing, - test_crypto_rsa_public_key_to_peer_id, - test_crypto_x509_der_to_private2, - test_crypto_x509_der_to_private, - test_crypto_hashing_sha256, - //test_multihash_encode, - //test_multihash_decode, - //test_multihash_base58_encode_decode, - //test_multihash_base58_decode, - //test_multihash_size, - test_base58_encode_decode, - test_base58_size, - test_base58_max_size, - test_base58_peer_address, - //test_mbedtls_pk_write_key_der, - //test_crypto_rsa_sign, - test_crypto_encoding_base32_encode, - test_protobuf_private_key, - test_secio_handshake, - test_secio_handshake_go, - test_secio_encrypt_decrypt, - test_secio_exchange_protobuf_encode, - test_secio_encrypt_like_go, - test_multistream_connect, - test_multistream_get_list, - test_ephemeral_key_generate, - test_ephemeral_key_sign, - test_dialer_new, - test_dialer_dial, - test_dialer_dial_multistream, - test_record_protobuf, - test_record_make_put_record, - test_record_peer_protobuf, - test_record_message_protobuf, - test_peer, - test_peer_protobuf, - test_peerstore, - test_aes -}; +struct test* first_test = NULL; +struct test* last_test = NULL; int testit(const char* name, int (*func)(void)) { - printf("Testing %s...\n", name); + fprintf(stderr, "TESTING %s...\n", name); int retVal = func(); if (retVal) - printf("%s success!\n", name); + fprintf(stderr, "%s success!\n", name); else - printf("** Uh oh! %s failed.**\n", name); - return retVal; + fprintf(stderr, "** Uh oh! %s failed.**\n", name); + return retVal == 0; } -int main(int argc, char** argv) { - int counter = 0; - int tests_ran = 0; - char* test_wanted; - int only_one = 0; - if(argc > 1) { - only_one = 1; - if (argv[1][0] == '\'') { // some shells put quotes around arguments - argv[1][strlen(argv[1])-1] = 0; - test_wanted = &(argv[1][1]); - } - else - test_wanted = argv[1]; +int tear_down_test_collection() { + struct test* current_test = first_test; + while (current_test != NULL) { + struct test* next_test = current_test->next; + free(current_test); + current_test = next_test; } - int array_length = sizeof(funcs) / sizeof(funcs[0]); - int array2_length = sizeof(names) / sizeof(names[0]); - if (array_length != array2_length) { - printf("Test arrays are not of the same length. Funcs: %d, Names: %d\n", array_length, array2_length); - } - for (int i = 0; i < array_length; i++) { - if (only_one) { - const char* currName = names[i]; - if (strcmp(currName, test_wanted) == 0) { - tests_ran++; - counter += testit(names[i], funcs[i]); - } - } - else - if (!only_one) { - tests_ran++; - counter += testit(names[i], funcs[i]); - } - } - - if (tests_ran == 0) - printf("***** No tests found *****\n"); - else { - if (tests_ran - counter > 0) { - printf("***** There were %d failed test(s) (%d successful) *****\n", tests_ran - counter, counter); - } else { - printf("All %d tests passed\n", tests_ran); - } - } - libp2p_logger_free(); return 1; } +int add_test(const char* name, int (*func)(void), int part_of_suite) { + // create a new test + struct test* t = (struct test*) malloc(sizeof(struct test)); + t->name = name; + t->func = func; + t->part_of_suite = part_of_suite; + t->next = NULL; + if (last_test == NULL) + t->index = 0; + else + t->index = last_test->index + 1; + // place it in the collection + if (first_test == NULL) { + first_test = t; + } else { + last_test->next = t; + } + last_test = t; + if (last_test == NULL) + return 0; + return last_test->index; +} + +int build_test_collection() { + add_test("test_public_der_to_private_der", test_public_der_to_private_der, 1); + add_test("test_mbedtls_varint_128_binary", test_mbedtls_varint_128_binary, 1); + add_test("test_mbedtls_varint_128_string", test_mbedtls_varint_128_string,1); + add_test("test_crypto_rsa_private_key_der", test_crypto_rsa_private_key_der, 1); + add_test("test_crypto_rsa_signing", test_crypto_rsa_signing, 1); + add_test("test_crypto_rsa_public_key_to_peer_id", test_crypto_rsa_public_key_to_peer_id,1); + add_test("test_crypto_x509_der_to_private2", test_crypto_x509_der_to_private2, 1); + add_test("test_crypto_x509_der_to_private", test_crypto_x509_der_to_private,1); + add_test("test_crypto_hashing_sha256", test_crypto_hashing_sha256,1); + //add_test("test_multihash_encode", func,1); + //add_test("test_multihash_decode", func,1); + //add_test("test_multihash_base58_encode_decode", func,1); + //add_test("test_multihash_base58_decode", func,1); + //add_test("test_multihash_size", func,1); + add_test("test_base58_encode_decode", test_base58_encode_decode,1); + add_test("test_base58_size", test_base58_size,1); + add_test("test_base58_max_size", test_base58_max_size,1); + add_test("test_base58_peer_address", test_base58_peer_address,1); + //add_test("test_mbedtls_pk_write_key_der", func,1); + //add_test("test_crypto_rsa_sign", func,1); + add_test("test_crypto_encoding_base32_encode", test_crypto_encoding_base32_encode,1); + add_test("test_protobuf_private_key", test_protobuf_private_key,1); + add_test("test_secio_handshake", test_secio_handshake,1); + add_test("test_secio_handshake_go", test_secio_handshake_go,1); + add_test("test_secio_encrypt_decrypt", test_secio_encrypt_decrypt,1); + add_test("test_secio_exchange_protobuf_encode", test_secio_exchange_protobuf_encode,1); + add_test("test_secio_encrypt_like_go", test_secio_encrypt_like_go,1); + add_test("test_multistream_connect", test_multistream_connect,1); + add_test("test_multistream_get_list", test_multistream_get_list,1); + add_test("test_ephemeral_key_generate", test_ephemeral_key_generate,1); + add_test("test_ephemeral_key_sign", test_ephemeral_key_sign,1); + add_test("test_dialer_new", test_dialer_new,1); + add_test("test_dialer_dial", test_dialer_dial,1); + add_test("test_dialer_join_swarm", test_dialer_join_swarm, 1); + add_test("test_dialer_dial_multistream", test_dialer_dial_multistream,1); + add_test("test_record_protobuf", test_record_protobuf,1); + add_test("test_record_make_put_record", test_record_make_put_record,1); + add_test("test_record_peer_protobuf", test_record_peer_protobuf,1); + add_test("test_record_message_protobuf", test_record_message_protobuf,1); + add_test("test_peer", test_peer,1); + add_test("test_peer_protobuf", test_peer_protobuf,1); + add_test("test_peerstore", test_peerstore,1); + add_test("test_aes", test_aes, 1); + return 1; +}; + +/** + * Pull the next test name from the command line + * @param the count of arguments on the command line + * @param argv the command line arguments + * @param arg_number the current argument we want + * @returns a null terminated string of the next test or NULL + */ +char* get_test(int argc, char** argv, int arg_number) { + char* retVal = NULL; + char* ptr = NULL; + if (argc > arg_number) { + ptr = argv[arg_number]; + if (ptr[0] == '\'') + ptr++; + retVal = ptr; + ptr = strchr(retVal, '\''); + if (ptr != NULL) + ptr[0] = 0; + } + return retVal; +} + +struct test* get_test_by_index(int index) { + struct test* current = first_test; + while (current != NULL && current->index != index) { + current = current->next; + } + return current; +} + +struct test* get_test_by_name(const char* name) { + struct test* current = first_test; + while (current != NULL && strcmp(current->name, name) != 0) { + current = current->next; + } + return current; +} + +/** + * run certain tests or run all + */ +int main(int argc, char** argv) { + int counter = 0; + int tests_ran = 0; + char* test_name_wanted = NULL; + int certain_tests = 0; + int current_test_arg = 1; + if(argc > 1) { + certain_tests = 1; + } + build_test_collection(); + if (certain_tests) { + // certain tests were passed on the command line + test_name_wanted = get_test(argc, argv, current_test_arg); + while (test_name_wanted != NULL) { + struct test* t = get_test_by_name(test_name_wanted); + if (t != NULL) { + tests_ran++; + counter += testit(t->name, t->func); + } + test_name_wanted = get_test(argc, argv, ++current_test_arg); + } + } else { + // run all tests that are part of this test suite + struct test* current = first_test; + while (current != NULL) { + if (current->part_of_suite) { + tests_ran++; + counter += testit(current->name, current->func); + } + current = current->next; + } + } + if (tests_ran == 0) + fprintf(stderr, "***** No tests found *****\n"); + else { + if (counter > 0) { + fprintf(stderr, "***** There were %d failed (out of %d) test(s) *****\n", counter, tests_ran); + } else { + fprintf(stderr, "All %d tests passed\n", tests_ran); + } + } + tear_down_test_collection(); + libp2p_logger_free(); + fclose(stdin); + fclose(stdout); + fclose(stderr); + return 1; +}