forked from agorise/c-ipfs
315 lines
11 KiB
C
315 lines
11 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <pthread.h>
|
|
|
|
#include "ipfs/importer/resolver.h"
|
|
#include "libp2p/utils/logger.h"
|
|
#include "libp2p/crypto/encoding/base58.h"
|
|
#include "libp2p/conn/session.h"
|
|
#include "libp2p/routing/dht_protocol.h"
|
|
#include "ipfs/merkledag/node.h"
|
|
#include "ipfs/merkledag/merkledag.h"
|
|
#include "ipfs/repo/fsrepo/fs_repo.h"
|
|
#include "libp2p/net/multistream.h"
|
|
#include "libp2p/record/message.h"
|
|
#include "multiaddr/multiaddr.h"
|
|
#include "libp2p/record/message.h"
|
|
|
|
/**
|
|
* return the next chunk of a path
|
|
* @param path the path
|
|
* @param next_part a pointer to a string NOTE: don't forget to free
|
|
* @returns true(1) on success, false(0) on error, or no more parts
|
|
*/
|
|
int ipfs_resolver_next_path(const char* path, char** next_part) {
|
|
for (int i = 0; i < strlen(path); i++) {
|
|
if (path[i] != '/') { // we have the next section
|
|
char* pos = strchr(&path[i+1], '/');
|
|
if (pos == NULL) {
|
|
*next_part = (char*)malloc(strlen(path) + 1);
|
|
strcpy(*next_part, path);
|
|
} else {
|
|
*next_part = (char*)malloc(pos - &path[i] + 1);
|
|
strncpy(*next_part, &path[i], pos-&path[i]);
|
|
(*next_part)[pos-&path[i]] = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Remove preceding slash and "/ipfs/" or "/ipns/" as well as the local multihash (if it is local)
|
|
* @param path the path from the command line
|
|
* @param fs_repo the local repo
|
|
* @returns the modified path
|
|
*/
|
|
const char* ipfs_resolver_remove_path_prefix(const char* path, const struct FSRepo* fs_repo) {
|
|
int pos = 0;
|
|
int first_non_slash = -1;
|
|
while(&path[pos] != NULL) {
|
|
if (path[pos] == '/') {
|
|
pos++;
|
|
continue;
|
|
} else {
|
|
if (first_non_slash == -1)
|
|
first_non_slash = pos;
|
|
if (pos == first_non_slash && (strncmp(&path[pos], "ipfs", 4) == 0 || strncmp(&path[pos], "ipns", 4) == 0) ) {
|
|
// ipfs or ipns should be up front. Otherwise, it could be part of the path
|
|
pos += 4;
|
|
} else if (strncmp(&path[pos], fs_repo->config->identity->peer->id, fs_repo->config->identity->peer->id_size) == 0) {
|
|
pos += fs_repo->config->identity->peer->id_size + 1; // the slash
|
|
} else {
|
|
return &path[pos];
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Determine if this path is a remote path
|
|
* @param path the path to examine
|
|
* @param fs_repo the local repo
|
|
* @returns true(1) if this path is a remote path
|
|
*/
|
|
int ipfs_resolver_is_remote(const char* path, const struct FSRepo* fs_repo) {
|
|
int pos = 0;
|
|
|
|
// skip the first slash
|
|
while (&path[pos] != NULL && path[pos] == '/') {
|
|
pos++;
|
|
}
|
|
if (&path[pos] == NULL)
|
|
return 0;
|
|
|
|
// skip the ipfs prefix
|
|
if (strncmp(&path[pos], "ipfs/", 5) == 0 || strncmp(&path[pos], "ipns/", 5) == 0) {
|
|
pos += 5; //the word plus the slash
|
|
} else
|
|
return 0;
|
|
|
|
// if this is a Qm code, see if it is a local Qm code
|
|
if (path[pos] == 'Q' && path[pos+1] == 'm') {
|
|
if (strncmp(&path[pos], fs_repo->config->identity->peer->id, fs_repo->config->identity->peer->id_size) != 0) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
/**
|
|
* Retrieve a node from a remote source
|
|
* @param path the path to retrieve
|
|
* @param from where to start
|
|
* @param fs_repo the local repo
|
|
* @returns the node, or NULL if not found
|
|
*/
|
|
struct HashtableNode* ipfs_resolver_remote_get(const char* path, struct HashtableNode* from, const struct IpfsNode* ipfs_node) {
|
|
// parse the path
|
|
const char* temp = ipfs_resolver_remove_path_prefix(path, ipfs_node->repo);
|
|
if (temp == NULL)
|
|
return NULL;
|
|
char* pos = strchr(temp, '/');
|
|
if (pos == NULL || pos - temp > 254)
|
|
return NULL;
|
|
char id[255];
|
|
size_t id_size = pos - temp;
|
|
strncpy(id, temp, id_size);
|
|
id[id_size] = 0;
|
|
char* key = &pos[1];
|
|
pos = strchr(key, '/');
|
|
if (pos == NULL || pos - key > 254)
|
|
return NULL;
|
|
pos[0] = '\0';
|
|
// get the multiaddress for this
|
|
struct Libp2pPeer* peer = libp2p_peerstore_get_peer(ipfs_node->peerstore, (unsigned char*)id, id_size);
|
|
if (peer == NULL) {
|
|
//TODO: We don't have the peer address. Ask the swarm for the data related to the hash
|
|
return NULL;
|
|
}
|
|
// connect to the peer
|
|
struct MultiAddress* address = peer->addr_head->item;
|
|
char* ip;
|
|
int port = multiaddress_get_ip_port(address);
|
|
multiaddress_get_ip_address(address, &ip);
|
|
struct Stream* stream = libp2p_net_multistream_connect(ip, port);
|
|
free(ip);
|
|
// build the request
|
|
struct KademliaMessage* message = libp2p_message_new();
|
|
message->message_type = MESSAGE_TYPE_GET_VALUE;
|
|
message->key = key;
|
|
message->key_size = strlen(key);
|
|
size_t b58size = 100;
|
|
uint8_t *b58key = (uint8_t *) malloc(b58size);
|
|
libp2p_crypto_encoding_base58_encode((unsigned char*)message->key, message->key_size, (unsigned char**) &b58key, &b58size);
|
|
libp2p_logger_debug("resolver", "Attempting to use kademlia to get key %s.\n", b58key);
|
|
free(b58key);
|
|
size_t message_protobuf_size = libp2p_message_protobuf_encode_size(message);
|
|
unsigned char message_protobuf[message_protobuf_size];
|
|
libp2p_message_protobuf_encode(message, message_protobuf, message_protobuf_size, &message_protobuf_size);
|
|
libp2p_message_free(message);
|
|
struct SessionContext session_context;
|
|
session_context.insecure_stream = stream;
|
|
session_context.default_stream = stream;
|
|
// switch to kademlia
|
|
if (!libp2p_routing_dht_upgrade_stream(&session_context))
|
|
return NULL;
|
|
stream->write(&session_context, message_protobuf, message_protobuf_size);
|
|
unsigned char* response;
|
|
size_t response_size;
|
|
// we should get back a protobuf'd record
|
|
stream->read(&session_context, &response, &response_size, 5);
|
|
if (response_size == 1)
|
|
return NULL;
|
|
// turn the protobuf into a Node
|
|
struct HashtableNode* node;
|
|
ipfs_hashtable_node_protobuf_decode(response, response_size, &node);
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Interogate the path and the current node, looking
|
|
* for the desired node.
|
|
* @param path the current path
|
|
* @param from the current node (or NULL if it is the first call)
|
|
* @returns what we are looking for, or NULL if it wasn't found
|
|
*/
|
|
struct HashtableNode* ipfs_resolver_get(const char* path, struct HashtableNode* from, const struct IpfsNode* ipfs_node) {
|
|
|
|
struct FSRepo* fs_repo = ipfs_node->repo;
|
|
|
|
// shortcut for remote files
|
|
if (from == NULL && ipfs_resolver_is_remote(path, fs_repo)) {
|
|
return ipfs_resolver_remote_get(path, from, ipfs_node);
|
|
}
|
|
/**
|
|
* Memory management notes:
|
|
* If we find what we're looking for, we clean up "from" and return the object
|
|
* If we don't find what we're looking for, but we can continue the search, we clean up "from"
|
|
* If we don't find what we're looking for, and we cannot continue, we do not clean up "from"
|
|
*/
|
|
// remove unnecessary stuff
|
|
if (from == NULL)
|
|
path = ipfs_resolver_remove_path_prefix(path, fs_repo);
|
|
// grab the portion of the path to work with
|
|
char* path_section;
|
|
if (ipfs_resolver_next_path(path, &path_section) == 0)
|
|
return NULL;
|
|
struct HashtableNode* current_node = NULL;
|
|
if (from == NULL) {
|
|
// this is the first time around. Grab the root node
|
|
if (path_section[0] == 'Q' && path_section[1] == 'm') {
|
|
// we have a hash. Convert to a real hash, and find the node
|
|
size_t hash_length = libp2p_crypto_encoding_base58_decode_size(strlen(path_section));
|
|
unsigned char hash[hash_length];
|
|
unsigned char* ptr = &hash[0];
|
|
if (libp2p_crypto_encoding_base58_decode((unsigned char*)path_section, strlen(path_section), &ptr, &hash_length) == 0) {
|
|
free(path_section);
|
|
return NULL;
|
|
}
|
|
if (ipfs_merkledag_get_by_multihash(hash, hash_length, ¤t_node, fs_repo) == 0) {
|
|
free(path_section);
|
|
return NULL;
|
|
}
|
|
// we have the root node, now see if we want this or something further down
|
|
int pos = strlen(path_section);
|
|
if (pos == strlen(path)) {
|
|
free(path_section);
|
|
return current_node;
|
|
} else {
|
|
// look on...
|
|
free(path_section);
|
|
struct HashtableNode* newNode = ipfs_resolver_get(&path[pos+1], current_node, ipfs_node); // the +1 is the slash
|
|
return newNode;
|
|
}
|
|
} else {
|
|
// we don't have a current node, and we don't have a hash. Something is wrong
|
|
free(path_section);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
// we were passed a node. If it is a directory, see if what we're looking for is in it
|
|
if (ipfs_hashtable_node_is_directory(from)) {
|
|
struct NodeLink* curr_link = from->head_link;
|
|
while (curr_link != NULL) {
|
|
// if it matches the name, we found what we're looking for.
|
|
// If so, load up the node by its hash
|
|
if (strcmp(curr_link->name, path_section) == 0) {
|
|
if (ipfs_merkledag_get(curr_link->hash, curr_link->hash_size, ¤t_node, fs_repo) == 0) {
|
|
free(path_section);
|
|
return NULL;
|
|
}
|
|
if (strlen(path_section) == strlen(path)) {
|
|
// we are at the end of our search
|
|
ipfs_hashtable_node_free(from);
|
|
from = NULL;
|
|
free(path_section);
|
|
return current_node;
|
|
} else {
|
|
char* next_path_section;
|
|
ipfs_resolver_next_path(&path[strlen(path_section)], &next_path_section);
|
|
free(path_section);
|
|
// if we're at the end of the path, return the node
|
|
// continue looking for the next part of the path
|
|
ipfs_hashtable_node_free(from);
|
|
from = NULL;
|
|
struct HashtableNode* newNode = ipfs_resolver_get(next_path_section, current_node, ipfs_node);
|
|
return newNode;
|
|
}
|
|
}
|
|
curr_link = curr_link->next;
|
|
}
|
|
} else {
|
|
// we're asking for a file from an object that is not a directory. Bail.
|
|
free(path_section);
|
|
return NULL;
|
|
}
|
|
}
|
|
// it should never get here
|
|
free(path_section);
|
|
if (from != NULL)
|
|
ipfs_hashtable_node_free(from);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Interrogate the path, looking for the peer.
|
|
* NOTE: only called locally. Not for remote callers
|
|
* @param path the peer path to search for in the form like "/ipfs/QmKioji..."
|
|
* @param ipfs_node the context
|
|
* @returns a peer struct, or NULL if not found
|
|
*/
|
|
struct Libp2pPeer* ipfs_resolver_find_peer(const char* path, const struct IpfsNode* ipfs_node) {
|
|
struct FSRepo* fs_repo = ipfs_node->repo;
|
|
struct Libp2pLinkedList *addresses = NULL;
|
|
struct Libp2pPeer* peer = NULL;
|
|
|
|
// shortcut for if this node is the node we're looking for
|
|
if (!ipfs_resolver_is_remote(path, fs_repo)) {
|
|
// turn the string list into a multiaddress list
|
|
struct Libp2pLinkedList* current_list_string = fs_repo->config->addresses->swarm_head;
|
|
struct Libp2pLinkedList* current_list_ma = addresses;
|
|
while(current_list_string != NULL) {
|
|
struct Libp2pLinkedList* item = libp2p_utils_linked_list_new();
|
|
item->item = multiaddress_new_from_string(current_list_string->item);
|
|
if (addresses == NULL) {
|
|
addresses = item;
|
|
} else {
|
|
current_list_ma->next = item;
|
|
}
|
|
current_list_ma = item;
|
|
current_list_string = current_list_string->next;
|
|
}
|
|
}
|
|
|
|
// ask the swarm for the peer
|
|
const char* address_string = ipfs_resolver_remove_path_prefix(path, fs_repo);
|
|
ipfs_node->routing->FindPeer(ipfs_node->routing, (const unsigned char*)address_string, strlen(address_string), &peer);
|
|
|
|
return peer;
|
|
}
|
|
|