485 lines
15 KiB
C
485 lines
15 KiB
C
#include <string.h>
|
|
|
|
#include "varint.h"
|
|
#include "protobuf.h"
|
|
#include "libp2p/net/protocol.h"
|
|
#include "libp2p/net/protocol.h"
|
|
#include "libp2p/net/multistream.h"
|
|
#include "libp2p/utils/vector.h"
|
|
#include "libp2p/net/stream.h"
|
|
#include "libp2p/conn/session.h"
|
|
#include "libp2p/identify/identify.h"
|
|
#include "libp2p/utils/logger.h"
|
|
#include "libp2p/yamux/yamux.h"
|
|
|
|
|
|
|
|
/**
|
|
* Determines if this protocol can handle the incoming message
|
|
* @param incoming the incoming data
|
|
* @param incoming_size the size of the incoming data buffer
|
|
* @returns true(1) if it can handle this message, false(0) if not
|
|
*/
|
|
int libp2p_identify_can_handle(const struct StreamMessage* msg) {
|
|
if (msg == NULL || msg->data_size == 0 || msg->data == 0)
|
|
return 0;
|
|
const char *protocol = "/ipfs/id/1.0.0\n";
|
|
int protocol_size = strlen(protocol);
|
|
// is there a varint in front?
|
|
size_t num_bytes = 0;
|
|
if (msg->data[0] != protocol[0] && msg->data[1] != protocol[1]) {
|
|
varint_decode(msg->data, msg->data_size, &num_bytes);
|
|
}
|
|
if (msg->data_size >= protocol_size - num_bytes) {
|
|
if (strncmp(protocol, (char*) &msg->data[num_bytes], protocol_size) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* Send the identify header out the default stream
|
|
* @param context the context
|
|
* @returns true(1) on success, false(0) otherwise
|
|
*/
|
|
int libp2p_identify_send_protocol(struct Stream *stream, Identify* identify, int initiatedByUs) {
|
|
size_t max_buffer_size = 6000;
|
|
uint8_t buffer[max_buffer_size];
|
|
char *protocol = "/ipfs/id/1.0.0\n";
|
|
int protocol_len = strlen(protocol);
|
|
// throw the protocol into the buffer
|
|
memcpy(&buffer[0], protocol, protocol_len);
|
|
if (!initiatedByUs) {
|
|
if (!libp2p_identify_protobuf_encode(identify, &buffer[protocol_len], max_buffer_size-protocol_len, &max_buffer_size)) {
|
|
libp2p_logger_error("identify", "Unable to protobuf the identity.\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
max_buffer_size = 0;
|
|
}
|
|
struct StreamMessage msg;
|
|
msg.data_size = protocol_len + max_buffer_size;
|
|
msg.data = buffer;
|
|
if (!stream->write(stream->stream_context, &msg)) {
|
|
libp2p_logger_error("identify", "send_protocol: Unable to send identify protocol header.\n");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/***
|
|
* Check to see if the reply is the identify header we expect
|
|
* NOTE: if we initiate the connection, we should expect the same back
|
|
* @param stream the incoming stream of the underlying protocol
|
|
* @returns true(1) on success, false(0) otherwise
|
|
*/
|
|
int libp2p_identify_receive_protocol(struct Stream* stream) {
|
|
const char *protocol = "/ipfs/id/1.0.0\n";
|
|
struct StreamMessage* results = NULL;
|
|
if (!stream->read(stream->stream_context, &results, 30)) {
|
|
libp2p_logger_error("identify", "receive_protocol: Unable to read results.\n");
|
|
return 0;
|
|
}
|
|
// the first byte may be the size, so skip it
|
|
int start = 0;
|
|
if (results->data[0] != '/')
|
|
start = 1;
|
|
char* ptr = strstr((char*)&results->data[start], protocol);
|
|
if (ptr == NULL || ptr - (char*)&results->data[start] > 1) {
|
|
libp2p_stream_message_free(results);
|
|
return 0;
|
|
}
|
|
libp2p_stream_message_free(results);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Create a Identify struct
|
|
* @returns the newly allocated record struct
|
|
*/
|
|
Identify* libp2p_identify_new() {
|
|
Identify* out = (Identify*)malloc(sizeof(Identify));
|
|
if (out != NULL) {
|
|
out->PublicKey = NULL;
|
|
out->ListenAddrs = NULL;
|
|
out->Protocols = NULL;
|
|
out->ObservedAddr = NULL;
|
|
out->ProtocolVersion = IDENTIFY_PROTOCOL_VERSION;
|
|
out->AgentVersion = IDENTIFY_AGENT_VERSION;
|
|
out->XXX_unrecognized = NULL;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Free the resources from a identify struct
|
|
* @param in the struct to free
|
|
*/
|
|
void libp2p_identify_free(Identify* in) {
|
|
int i;
|
|
|
|
if (in != NULL) {
|
|
if (in->PublicKey != NULL)
|
|
free(in->PublicKey);
|
|
if (in->ListenAddrs != NULL) {
|
|
// free every item
|
|
for (i = 0 ; in->ListenAddrs[i] ; i++)
|
|
free(in->ListenAddrs[i]);
|
|
// free array
|
|
free(in->ListenAddrs);
|
|
}
|
|
if (in->Protocols != NULL) {
|
|
for (i = 0 ; in->Protocols[i] ; i++)
|
|
free(in->Protocols[i]);
|
|
free(in->Protocols);
|
|
}
|
|
if (in->ObservedAddr != NULL)
|
|
free(in->ObservedAddr);
|
|
if (in->XXX_unrecognized != NULL)
|
|
free(in->XXX_unrecognized);
|
|
free(in);
|
|
}
|
|
}
|
|
|
|
/* helper to alloc and copy an item.
|
|
* @param item the item.
|
|
* @returns a pointer on success, otherwise NULL.
|
|
*/
|
|
char *libp2p_identify_new_item(char *item, size_t size) {
|
|
char *dst = NULL;
|
|
if (item) {
|
|
dst = malloc(size);
|
|
if (dst) {
|
|
memcpy(dst, item, size);
|
|
}
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
/* helper to add an item to an array.
|
|
* @param array the array.
|
|
* @param item the item.
|
|
* @returns count itens on success, otherwise zero.
|
|
*/
|
|
int libp2p_identify_array_add_item(char ***array, char *item) {
|
|
int count = 0;
|
|
if (array && item) {
|
|
if (*array) {
|
|
// count itens already in.
|
|
while (*array[count++]);
|
|
}
|
|
// alloc the necessary array count or realloc more if alread allocated.
|
|
*array = realloc(*array, sizeof(char*) * (count + 2)); // 2, the new and the NULL
|
|
*array[count++] = item;
|
|
*array[count] = NULL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Convert a identify struct into protobuf format
|
|
* @param in the Identify struct to convert
|
|
* @param buffer where to store the protobuf
|
|
* @param max_buffer_size the size of the allocated buffer
|
|
* @param bytes_written the size written into buffer
|
|
* @returns true(1) on success, otherwise false(0)
|
|
*/
|
|
int libp2p_identify_protobuf_encode(const Identify* in, unsigned char* buffer, size_t max_buffer_size, size_t* bytes_written) {
|
|
// data & data_size
|
|
size_t bytes_used = 0;
|
|
*bytes_written = 0;
|
|
int i, retVal = 0;
|
|
|
|
// field 1
|
|
if (in->PublicKey != NULL) {
|
|
retVal = protobuf_encode_length_delimited(1, WIRETYPE_LENGTH_DELIMITED, in->PublicKey, in->PublicKeyLength, &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
// field 2
|
|
if (in->ListenAddrs != NULL) {
|
|
for (i = 0 ; in->ListenAddrs[i] ; i++) {
|
|
retVal = protobuf_encode_string(2, WIRETYPE_LENGTH_DELIMITED, in->ListenAddrs[i], &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
}
|
|
// field 3
|
|
if (in->Protocols != NULL) {
|
|
for (i = 0 ; in->Protocols[i] ; i++) {
|
|
retVal = protobuf_encode_string(3, WIRETYPE_LENGTH_DELIMITED, in->Protocols[i], &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
}
|
|
// field 4
|
|
if (in->ObservedAddr != NULL) {
|
|
retVal = protobuf_encode_string(4, WIRETYPE_LENGTH_DELIMITED, in->ObservedAddr, &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
// field 5
|
|
if (in->ProtocolVersion != NULL) {
|
|
retVal = protobuf_encode_string(5, WIRETYPE_LENGTH_DELIMITED, in->ProtocolVersion, &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
// field 6
|
|
if (in->AgentVersion != NULL) {
|
|
retVal = protobuf_encode_string(6, WIRETYPE_LENGTH_DELIMITED, in->AgentVersion, &buffer[*bytes_written], max_buffer_size - *bytes_written, &bytes_used);
|
|
if (retVal == 0)
|
|
return 0;
|
|
*bytes_written += bytes_used;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Convert a protobuf byte array into a Identify struct
|
|
* @param in the byte array
|
|
* @param in_size the size of the byte array
|
|
* @param out a pointer to the new Identify struct
|
|
* @returns true(1) on success, otherwise false(0)
|
|
*/
|
|
int libp2p_identify_protobuf_decode(const unsigned char* in, size_t in_size, Identify** out) {
|
|
size_t pos = 0;
|
|
int count, retVal = 0;
|
|
char *item;
|
|
|
|
if ( (*out = libp2p_identify_new()) == NULL)
|
|
goto exit;
|
|
|
|
while(pos < in_size) {
|
|
size_t bytes_read = 0;
|
|
int field_no;
|
|
enum WireType field_type;
|
|
if (!protobuf_decode_field_and_type(&in[pos], in_size, &field_no, &field_type, &bytes_read)) {
|
|
goto exit;
|
|
}
|
|
pos += bytes_read;
|
|
switch(field_no) {
|
|
case (1): // PublicKey
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&((*out)->PublicKey), &bytes_read))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
case (2): // ListenAddrs
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&(item), &bytes_read))
|
|
goto exit;
|
|
if (!libp2p_identify_array_add_item(&((*out)->ListenAddrs), item))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
case (3): // Protocols
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&(item), &bytes_read))
|
|
goto exit;
|
|
if (!libp2p_identify_array_add_item(&((*out)->Protocols), item))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
case (4): // ObservedAddr
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&((*out)->ObservedAddr), &bytes_read))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
case (5): // ProtocolVersion
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&((*out)->ProtocolVersion), &bytes_read))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
case (6): // AgentVersion
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&((*out)->AgentVersion), &bytes_read))
|
|
goto exit;
|
|
pos += bytes_read;
|
|
break;
|
|
default: // XXX_unrecognized
|
|
// create a comma-separated string of each unrecognized item.
|
|
if (!protobuf_decode_string(&in[pos], in_size - pos, (char**)&(item), &bytes_read))
|
|
goto exit;
|
|
count = 0;
|
|
if ((*out)->XXX_unrecognized) {
|
|
count = strlen((*out)->XXX_unrecognized);
|
|
(*out)->XXX_unrecognized[count++] = ','; // null terminator is now a comma.
|
|
}
|
|
(*out)->XXX_unrecognized = realloc((*out)->XXX_unrecognized, count + bytes_read);
|
|
if (!(*out)->XXX_unrecognized)
|
|
goto exit;
|
|
// append after the comma.
|
|
memcpy((*out)->XXX_unrecognized+count, item, bytes_read);
|
|
free(item);
|
|
pos += bytes_read;
|
|
break;
|
|
}
|
|
}
|
|
|
|
retVal = 1;
|
|
|
|
exit:
|
|
if (retVal == 0) {
|
|
libp2p_identify_free(*out);
|
|
*out = NULL;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* A remote node is attempting to send us an Identify message
|
|
* @param msg the message sent
|
|
* @param context the SessionContext
|
|
* @param protocol_context the identify protocol context
|
|
* @returns <0 on error, 0 if loop should not continue, >0 on success
|
|
*/
|
|
int libp2p_identify_handle_message(const struct StreamMessage* msg, struct Stream* stream, void* protocol_context) {
|
|
struct YamuxChannelContext* yamuxChannelContext = libp2p_yamux_get_parent_channel_context(stream);
|
|
struct YamuxContext* yamuxContext = libp2p_yamux_get_context(yamuxChannelContext);
|
|
struct Stream* new_stream = NULL;
|
|
if (yamuxChannelContext != NULL && yamuxChannelContext->child_stream->stream_type == STREAM_TYPE_YAMUX) {
|
|
if ( (yamuxContext->am_server && yamuxChannelContext->channel % 2 == 0)
|
|
|| (!yamuxContext->am_server && yamuxChannelContext->channel % 2 == 1)) {
|
|
// we initiated it, and they are replying
|
|
return 1;
|
|
}
|
|
} else {
|
|
// they initiated it.
|
|
// attempt to create a new Identify connection with them.
|
|
// send the protocol id back, and set up the channel
|
|
Identify* identify = (Identify*)protocol_context;
|
|
new_stream = libp2p_identify_stream_new(stream, identify, 0);
|
|
if (new_stream == NULL)
|
|
return -1;
|
|
return stream->handle_upgrade(stream, new_stream);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Shutting down. Clean up any memory allocations
|
|
* @param protocol_context the context
|
|
* @returns true(1)
|
|
*/
|
|
int libp2p_identify_shutdown(void* protocol_context) {
|
|
if (protocol_context == NULL)
|
|
return 0;
|
|
free(protocol_context);
|
|
return 1;
|
|
}
|
|
|
|
struct Libp2pProtocolHandler* libp2p_identify_build_protocol_handler(char* public_key, int public_key_length) {
|
|
struct Libp2pProtocolHandler* handler = libp2p_protocol_handler_new();
|
|
if (handler != NULL) {
|
|
Identify* identify = libp2p_identify_new();
|
|
identify->PublicKey = public_key;
|
|
identify->PublicKeyLength = public_key_length;
|
|
handler->context = identify;
|
|
handler->CanHandle = libp2p_identify_can_handle;
|
|
handler->HandleMessage = libp2p_identify_handle_message;
|
|
handler->Shutdown = libp2p_identify_shutdown;
|
|
}
|
|
return handler;
|
|
}
|
|
|
|
int libp2p_identify_close(struct Stream* stream) {
|
|
if (stream == NULL)
|
|
return 0;
|
|
if (stream->parent_stream != NULL)
|
|
stream->parent_stream->close(stream->parent_stream);
|
|
if (stream->stream_context != NULL)
|
|
free(stream->stream_context);
|
|
libp2p_stream_free(stream);
|
|
return 1;
|
|
}
|
|
|
|
int libp2p_identify_read(void* stream_context, struct StreamMessage** msg, int timeout_secs) {
|
|
struct IdentifyContext* ctx = (struct IdentifyContext*) stream_context;
|
|
struct StreamMessage* internal = NULL;
|
|
if (ctx->parent_stream->read(ctx->parent_stream->stream_context, &internal, timeout_secs)) {
|
|
// we got an identity, but we should not do anything with it right now
|
|
// TODO: send a fin
|
|
libp2p_stream_message_free(internal);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* Create a new stream that negotiates the identify protocol
|
|
*
|
|
* NOTE: This will be sent by our side (us asking them).
|
|
* Incoming "Identify" requests should be handled by the
|
|
* external protocol handler, not this function.
|
|
*
|
|
* @param parent_stream the parent stream
|
|
* @returns a new Stream that can talk "identify"
|
|
*/
|
|
struct Stream* libp2p_identify_stream_new(struct Stream* parent_stream, Identify* identify, int initiatedByUs) {
|
|
if (parent_stream == NULL)
|
|
return NULL;
|
|
struct Stream* out = libp2p_stream_new();
|
|
if (out != NULL) {
|
|
out->parent_stream = parent_stream;
|
|
struct IdentifyContext* ctx = (struct IdentifyContext*) malloc(sizeof(struct IdentifyContext));
|
|
if (ctx == NULL) {
|
|
libp2p_stream_free(out);
|
|
return NULL;
|
|
}
|
|
ctx->parent_stream = parent_stream;
|
|
ctx->stream = out;
|
|
out->stream_context = ctx;
|
|
out->close = libp2p_identify_close;
|
|
out->negotiate = NULL;
|
|
out->bytes_waiting = NULL;
|
|
out->read = libp2p_identify_read;
|
|
// do we expect a reply?
|
|
if (!libp2p_identify_send_protocol(parent_stream, identify, initiatedByUs) /* || !libp2p_identify_receive_protocol(parent_stream) */) {
|
|
libp2p_stream_free(out);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/***
|
|
* Create a new stream that negotiates the identify protocol
|
|
* on top of the multistream protocol
|
|
*
|
|
* NOTE: This will be sent by our side (us asking them).
|
|
* Incoming "Identify" requests should be handled by the
|
|
* external protocol handler, not this function.
|
|
*
|
|
* @param parent_stream the parent stream
|
|
* @returns a new Stream that is a multistream, but with "identify" already negotiated
|
|
*/
|
|
/*
|
|
struct Stream* libp2p_identify_stream_new_with_multistream(struct Stream* parent_stream) {
|
|
if (parent_stream == NULL)
|
|
return NULL;
|
|
struct Stream* multistream = libp2p_net_multistream_stream_new(parent_stream, 0);
|
|
struct Stream* out = libp2p_stream_new();
|
|
if (out != NULL) {
|
|
out->stream_type = STREAM_TYPE_IDENTIFY;
|
|
out->parent_stream = multistream;
|
|
struct IdentifyContext* ctx = (struct IdentifyContext*) malloc(sizeof(struct IdentifyContext));
|
|
if (ctx == NULL) {
|
|
libp2p_stream_free(out);
|
|
return NULL;
|
|
}
|
|
ctx->parent_stream = multistream;
|
|
ctx->stream = out;
|
|
out->stream_context = ctx;
|
|
out->close = libp2p_identify_close;
|
|
out->negotiate = NULL;
|
|
if (!libp2p_identify_send_protocol(parent_stream, NULL) || !libp2p_identify_receive_protocol(parent_stream)) {
|
|
libp2p_stream_free(out);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
*/
|