c-ipfs/merkledag/node.c
jmjatlanta c2fe60949e Added the ability to retrieve the file using directories
Files can be refered to directly using their hash, or the hash of their
directory and the file name.
2016-12-29 19:05:44 -05:00

805 lines
21 KiB
C

/**
* An implementation of an IPFS node
* Copying the go-ipfs-node project
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "inttypes.h"
#include "mh/multihash.h"
#include "mh/hashes.h"
#include "ipfs/cid/cid.h"
#include "ipfs/merkledag/node.h"
#include "ipfs/unixfs/unixfs.h"
// for protobuf Node (all fields optional) data (optional bytes) links (repeated node_link)
enum WireType ipfs_node_message_fields[] = { WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED };
// for protobuf NodeLink (all fields optional) hash name tsize
enum WireType ipfs_node_link_message_fields[] = { WIRETYPE_LENGTH_DELIMITED, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_VARINT };
/*====================================================================================
* Link Functions
*===================================================================================*/
/* ipfs_node_link_new
* @Param name: The name of the link (char *)
* @Param size: Size of the link (size_t)
* @Param ahash: An Qmhash
*/
int ipfs_node_link_create(char * name, unsigned char * ahash, size_t hash_size, struct NodeLink** node_link)
{
ipfs_node_link_new(node_link);
if (*node_link == NULL)
return 0;
struct NodeLink* link = *node_link;
// hash
link->hash_size = hash_size;
link->hash = (unsigned char*)malloc(hash_size);
memcpy(link->hash, ahash, hash_size);
// name
if (name != NULL && strlen(name) > 0) {
link->name = malloc(strlen(name) + 1);
if ( link->name == NULL) {
free(link);
return 0;
}
strcpy(link->name, name);
}
// t_size
link->t_size = 0;
// other, non-protobuffed data
link->next = NULL;
return 1;
}
int ipfs_node_link_new(struct NodeLink** node_link) {
*node_link = malloc(sizeof(struct NodeLink));
if (*node_link == NULL)
return 0;
struct NodeLink* link = *node_link;
link->hash = NULL;
link->hash_size = 0;
link->name = NULL;
link->next = NULL;
link->t_size = 0;
return 1;
}
/* ipfs_node_link_free
* @param node_link: Free the link you have allocated.
*/
int ipfs_node_link_free(struct NodeLink * node_link)
{
if (node_link != NULL) {
if (node_link->hash != NULL)
free(node_link->hash);
if (node_link->name != NULL)
free(node_link->name);
free(node_link);
}
return 1;
}
/***
* Find length of encoded version of NodeLink
* @param link the link to examine
* @returns the maximum length of the encoded NodeLink
*/
size_t ipfs_node_link_protobuf_encode_size(const struct NodeLink* link) {
if (link == NULL)
return 0;
// hash, name, tsize
size_t size = 0;
if (link->hash_size > 0) {
size += 11 + link->hash_size;
}
if (link->name != NULL && strlen(link->name) > 0) {
size += 11 + strlen(link->name);
}
if (link->t_size > 0) {
size += 22;
}
return size;
}
/**
* Encode a NodeLink in protobuf format
* @param link the link to work with
* @param buffer the buffer to fill
* @param max_buffer_length the length of the buffer
* @param bytes_written the number of bytes written to buffer
* @returns true(1) on success
*/
int ipfs_node_link_protobuf_encode(const struct NodeLink* link, unsigned char* buffer, size_t max_buffer_length, size_t* bytes_written) {
// 3 fields, hash (length delimited), name (length delimited), tsize (varint)
size_t bytes_used = 0;
int retVal = 0;
*bytes_written = 0;
// hash
if (link->hash_size > 0) {
size_t hash_length = mh_new_length(MH_H_SHA2_256, link->hash_size);
unsigned char hash[hash_length];
mh_new(hash, MH_H_SHA2_256, link->hash, link->hash_size);
retVal = protobuf_encode_length_delimited(1, ipfs_node_link_message_fields[0], (char*)hash, hash_length, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0) {
return 0;
}
*bytes_written += bytes_used;
}
// name is optional, but they encode it anyways
if (link->name != NULL && strlen(link->name) > 0) {
retVal = protobuf_encode_string(2, ipfs_node_link_message_fields[1], link->name, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0)
return 0;
*bytes_written += bytes_used;
} else {
retVal = protobuf_encode_string(2, ipfs_node_link_message_fields[1], "", &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0)
return 0;
*bytes_written += bytes_used;
}
// tsize
if (link->t_size > 0) {
retVal = protobuf_encode_varint(3, ipfs_node_link_message_fields[2], link->t_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0)
return 0;
*bytes_written += bytes_used;
}
return 1;
}
/**
* Turn a protobuf encoded byte array into a NodeLink object
* @param buffer the buffer to look in
* @param buffer_length the length of the buffer
* @param link the link to fill
* @returns true(1) on success
*/
int ipfs_node_link_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct NodeLink** node_link) {
size_t pos = 0;
int retVal = 0;
struct NodeLink* link = NULL;
// allocate memory for object
if (ipfs_node_link_new(node_link) == 0)
goto exit;
link = *node_link;
while(pos < buffer_length) {
size_t bytes_read = 0;
int field_no;
enum WireType field_type;
if (protobuf_decode_field_and_type(&buffer[pos], buffer_length, &field_no, &field_type, &bytes_read) == 0) {
goto exit;
}
pos += bytes_read;
switch(field_no) {
case (1): { // hash
size_t hash_size = 0;
unsigned char* hash;
if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&hash, &hash_size, &bytes_read) == 0)
goto exit;
link->hash_size = hash_size - 2;
link->hash = (unsigned char*)malloc(link->hash_size);
memcpy((char*)link->hash, (char*)&hash[2], link->hash_size);
free(hash);
pos += bytes_read;
break;
}
case (2): { // name
if (protobuf_decode_string(&buffer[pos], buffer_length - pos, &link->name, &bytes_read) == 0)
goto exit;
pos += bytes_read;
break;
}
case (3): { // t_size
if (protobuf_decode_varint(&buffer[pos], buffer_length - pos, (unsigned long long*)&link->t_size, &bytes_read) == 0)
goto exit;
pos += bytes_read;
break;
}
}
}
retVal = 1;
exit:
if (retVal == 0) {
if (link != NULL)
ipfs_node_link_free(link);
}
return retVal;
}
/***
* return an approximate size of the encoded node
*/
size_t ipfs_node_protobuf_encode_size(const struct Node* node) {
size_t size = 0;
// links
struct NodeLink* current = node->head_link;
while(current != NULL) {
size += 11 + ipfs_node_link_protobuf_encode_size(current);
current = current->next;
}
// data
if (node->data_size > 0) {
size += 11 + node->data_size;
}
return size;
}
/***
* Encode a node into a protobuf byte stream
* @param node the node to encode
* @param buffer where to put it
* @param max_buffer_length the length of buffer
* @param bytes_written how much of buffer was used
* @returns true(1) on success
*/
int ipfs_node_protobuf_encode(const struct Node* node, unsigned char* buffer, size_t max_buffer_length, size_t* bytes_written) {
size_t bytes_used = 0;
*bytes_written = 0;
int retVal = 0;
// links
struct NodeLink* current = node->head_link;
while (current != NULL) {
size_t temp_size = ipfs_node_link_protobuf_encode_size(current);
unsigned char temp[temp_size];
retVal = ipfs_node_link_protobuf_encode(current, temp, temp_size, &bytes_used);
if (retVal == 0)
return 0;
retVal = protobuf_encode_length_delimited(2, ipfs_node_message_fields[1], (char*)temp, bytes_used, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0)
return 0;
*bytes_written += bytes_used;
current = current->next;
}
// data
if (node->data_size > 0) {
retVal = protobuf_encode_length_delimited(1, ipfs_node_message_fields[0], (char*)node->data, node->data_size, &buffer[*bytes_written], max_buffer_length - *bytes_written, &bytes_used);
if (retVal == 0)
return 0;
*bytes_written += bytes_used;
}
return 1;
}
/***
* Decode a stream of bytes into a Node structure
* @param buffer where to get the bytes from
* @param buffer_length the length of buffer
* @param node pointer to the Node to be created
* @returns true(1) on success
*/
int ipfs_node_protobuf_decode(unsigned char* buffer, size_t buffer_length, struct Node** node) {
/*
* Field 1: data
* Field 2: link
*/
size_t pos = 0;
int retVal = 0;
unsigned char* temp_buffer = NULL;
size_t temp_size;
struct NodeLink* temp_link = NULL;
if (ipfs_node_new(node) == 0)
goto exit;
while(pos < buffer_length) {
size_t bytes_read = 0;
int field_no;
enum WireType field_type;
if (protobuf_decode_field_and_type(&buffer[pos], buffer_length, &field_no, &field_type, &bytes_read) == 0) {
goto exit;
}
pos += bytes_read;
switch(field_no) {
case (1): { // data
if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&((*node)->data), &((*node)->data_size), &bytes_read) == 0)
goto exit;
pos += bytes_read;
break;
}
case (2): {// links
if (protobuf_decode_length_delimited(&buffer[pos], buffer_length - pos, (char**)&temp_buffer, &temp_size, &bytes_read) == 0)
goto exit;
pos += bytes_read;
if (ipfs_node_link_protobuf_decode(temp_buffer, temp_size, &temp_link) == 0)
goto exit;
free(temp_buffer);
temp_buffer = NULL;
ipfs_node_add_link(*node, temp_link);
break;
}
}
}
retVal = 1;
exit:
if (retVal == 0) {
ipfs_node_free(*node);
}
if (temp_buffer != NULL)
free(temp_buffer);
return retVal;
}
/*====================================================================================
* Node Functions
*===================================================================================*/
/*ipfs_node_new
* Creates an empty node, allocates the required memory
* Returns a fresh new node with no data set in it.
*/
int ipfs_node_new(struct Node** node)
{
*node = (struct Node *)malloc(sizeof(struct Node));
if (*node == NULL)
return 0;
(*node)->hash = NULL;
(*node)->hash_size = 0;
(*node)->data = NULL;
(*node)->data_size = 0;
(*node)->encoded = NULL;
(*node)->head_link = NULL;
return 1;
}
/***
* Allocates memory for a node, and sets the data section to indicate
* that this node is a directory
* @param node the node to initialize
* @returns true(1) on success, otherwise false(0)
*/
int ipfs_node_create_directory(struct Node** node) {
// initialize parent_node
if (ipfs_node_new(node) == 0)
return 0;
// put a UnixFS protobuf in the data section
struct UnixFS* unix_fs;
if (ipfs_unixfs_new(&unix_fs) == 0) {
ipfs_node_free(*node);
return 0;
}
unix_fs->data_type = UNIXFS_DIRECTORY;
size_t protobuf_len = ipfs_unixfs_protobuf_encode_size(unix_fs);
unsigned char protobuf[protobuf_len];
if (ipfs_unixfs_protobuf_encode(unix_fs, protobuf, protobuf_len, &protobuf_len) == 0) {
ipfs_node_free(*node);
ipfs_unixfs_free(unix_fs);
return 0;
}
ipfs_unixfs_free(unix_fs);
ipfs_node_set_data(*node, protobuf, protobuf_len);
return 1;
}
int ipfs_node_is_directory(struct Node* node) {
if (node->data_size < 2) {
return 0;
}
struct UnixFS* unix_fs;
if (ipfs_unixfs_protobuf_decode(node->data, node->data_size, &unix_fs) == 0) {
return 0;
}
int retVal = (unix_fs->data_type == UNIXFS_DIRECTORY);
ipfs_unixfs_free(unix_fs);
return retVal;
}
/**
* Set the cached struct element
* @param node the node to be modified
* @param cid the Cid to be copied into the Node->cached element
* @returns true(1) on success
*/
int ipfs_node_set_hash(struct Node* node, const unsigned char* hash, size_t hash_size)
{
// don't reallocate if it is the same size
if (node->hash != NULL && hash_size != node->hash_size) {
free(node->hash);
node->hash = NULL;
node->hash_size = 0;
}
// we must reallocate
if (node->hash == NULL && hash_size > 0) {
node->hash = (unsigned char*)malloc(hash_size);
if (node->hash == NULL)
return 0;
}
if (hash_size > 0) { // don't bother if there is nothing to copy
memcpy(node->hash, hash, hash_size);
node->hash_size = hash_size;
}
return 1;
}
/*ipfs_node_set_data
* Sets the data of a node
* @param Node: The node which you want to set data in.
* @param Data, the data you want to assign to the node
* Sets pointers of encoded & cached to NULL /following go method
* returns 1 on success 0 on failure
*/
int ipfs_node_set_data(struct Node* node, unsigned char * Data, size_t data_size)
{
if(!node || !Data)
{
return 0;
}
if (node->data != NULL) {
free(node->data);
}
node->data = malloc(sizeof(unsigned char) * data_size);
if (node->data == NULL)
return 0;
memcpy(node->data, Data, data_size);
node->data_size = data_size;
return 1;
}
/*ipfs_node_set_encoded
* @param NODE: the node you wish to alter (struct Node *)
* @param Data: The data you wish to set in encoded.(unsigned char *)
* returns 1 on success 0 on failure
*/
int ipfs_node_set_encoded(struct Node * N, unsigned char * Data)
{
if(!N || !Data)
{
return 0;
}
N->encoded = Data;
//I don't know if these will be needed, enable them if you need them.
//N->cached = NULL;
//N->data = NULL;
return 1;
}
/*ipfs_node_get_data
* Gets data from a node
* @param Node: = The node you want to get data from. (unsigned char *)
* Returns data of node.
*/
unsigned char * ipfs_node_get_data(struct Node * N)
{
unsigned char * DATA;
DATA = N->data;
return DATA;
}
struct NodeLink* ipfs_node_link_last(struct Node* node) {
struct NodeLink* current = node->head_link;
while(current != NULL) {
if (current->next == NULL)
break;
current = current->next;
}
return current;
}
int ipfs_node_remove_link(struct Node* node, struct NodeLink* toRemove) {
struct NodeLink* current = node->head_link;
struct NodeLink* previous = NULL;
while(current != NULL && current != toRemove) {
previous = current;
current = current->next;
}
if (current != NULL) {
if (previous == NULL) {
// we're trying to delete the head
previous = current->next;
ipfs_node_link_free(current);
node->head_link = previous;
} else {
// we're in the middle or end
previous = current->next;
ipfs_node_link_free(current);
}
return 1;
}
return 0;
}
/*ipfs_node_free
* Once you are finished using a node, always delete it using this.
* It will take care of the links inside it.
* @param N: the node you want to free. (struct Node *)
*/
int ipfs_node_free(struct Node * N)
{
if(N != NULL)
{
// remove links
struct NodeLink* current = N->head_link;
while (current != NULL) {
struct NodeLink* toDelete = current;
current = current->next;
ipfs_node_remove_link(N, toDelete);
}
if(N->hash != NULL)
{
free(N->hash);
N->hash = NULL;
N->hash_size = 0;
}
if (N->data) {
free(N->data);
}
if (N->encoded != NULL) {
free(N->encoded);
}
free(N);
}
return 1;
}
/*ipfs_node_get_link_by_name
* Returns a copy of the link with given name
* @param Name: (char * name) searches for link with this name
* Returns the link struct if it's found otherwise returns NULL
*/
struct NodeLink * ipfs_node_get_link_by_name(struct Node * N, char * Name)
{
struct NodeLink* current = N->head_link;
while(current != NULL && strcmp(Name, current->name) != 0) {
current = current->next;
}
return current;
}
/*ipfs_node_remove_link_by_name
* Removes a link from node if found by name.
* @param name: Name of link (char * name)
* returns 1 on success, 0 on failure.
*/
int ipfs_node_remove_link_by_name(char * Name, struct Node * mynode)
{
struct NodeLink* current = mynode->head_link;
struct NodeLink* previous = NULL;
while( (current != NULL)
&& (( Name == NULL && current->name != NULL )
|| ( Name != NULL && current->name == NULL )
|| ( Name != NULL && current->name != NULL && strcmp(Name, current->name) != 0) ) ) {
previous = current;
current = current->next;
}
if (current != NULL) {
// we found it
if (previous == NULL) {
// we're first, use the next one (if there is one)
if (current->next != NULL)
mynode->head_link = current->next;
} else {
// we're somewhere in the middle, remove me from the list
previous->next = current->next;
ipfs_node_link_free(current);
}
return 1;
}
return 0;
}
/* ipfs_node_add_link
* Adds a link to your node
* @param node the node to add to
* @param mylink: the link to add
* @returns true(1) on success
*/
int ipfs_node_add_link(struct Node* node, struct NodeLink * mylink)
{
if(node->head_link != NULL) {
// add to existing by finding last one
struct NodeLink* current_end = node->head_link;
while(current_end->next != NULL) {
current_end = current_end->next;
}
// now we have the last one, add to it
current_end->next = mylink;
}
else
{
node->head_link = mylink;
}
return 1;
}
/*ipfs_node_new_from_link
* Create a node from a link
* @param mylink: the link you want to create it from. (struct Cid *)
* @param linksize: sizeof(the link in mylink) (size_T)
* Returns a fresh new node with the link you specified. Has to be freed with Node_Free preferably.
*/
int ipfs_node_new_from_link(struct NodeLink * mylink, struct Node** node)
{
*node = (struct Node *) malloc(sizeof(struct Node));
if (*node == NULL)
return 0;
(*node)->head_link = NULL;
ipfs_node_add_link(*node, mylink);
(*node)->hash = NULL;
(*node)->hash_size = 0;
(*node)->data = NULL;
(*node)->data_size = 0;
(*node)->encoded = NULL;
return 1;
}
/**
* create a new Node struct with data
* @param data: bytes buffer you want to create the node from
* @param data_size the size of the data buffer
* @param node a pointer to the node to be created
* returns a node with the data you inputted.
*/
int ipfs_node_new_from_data(unsigned char * data, size_t data_size, struct Node** node)
{
if(data)
{
if (ipfs_node_new(node) == 0)
return 0;
return ipfs_node_set_data(*node, data, data_size);
}
return 0;
}
/***
* create a Node struct from encoded data
* @param data: encoded bytes buffer you want to create the node from. Note: this copies the pointer, not a memcpy
* @param node a pointer to the node that will be created
* @returns true(1) on success
*/
int ipfs_node_new_from_encoded(unsigned char * data, struct Node** node)
{
if(data)
{
if (ipfs_node_new(node) == 0)
return 0;
(*node)->encoded = data;
return 1;
}
return 0;
}
/*Node_Resolve_Max_Size
* !!!This shouldn't concern you!
*Gets the ammount of words that will be returned by Node_Resolve
*@Param1: The string that will be processed (eg: char * sentence = "foo/bar/bin")
*Returns either -1 if something went wrong or the ammount of words that would be processed.
*/
int Node_Resolve_Max_Size(char * input1)
{
if(!input1)
{
return -1; // Input is null, therefor nothing can be processed.
}
char input[strlen(input1)];
bzero(input, strlen(input1));
strcpy(input, input1);
int num = 0;
char * tr;
char * end;
tr=strtok_r(input,"/",&end);
for(int i = 0;tr;i++)
{
tr=strtok_r(NULL,"/",&end);
num++;
}
return num;
}
/*Node_Resolve Basically stores everything in a pointer array eg: char * bla[Max_Words_]
* !!!This shouldn't concern you!!!
*@param1: Pointer array(char * foo[x], X=Whatever ammount there is. should be used with the helper function Node_Resolve_Max_Size)
*@param2: Sentence to gather words/paths from (Eg: char * meh = "foo/bar/bin")
*@Returns 1 or 0, 0 if something went wrong, 1 if everything went smoothly.
*/
int Node_Resolve(char ** result, char * input1)
{
if(!input1)
{
return 0; // Input is null, therefor nothing can be processed.
}
char input[strlen(input1)];
bzero(input, strlen(input1));
strcpy(input, input1);
char * tr;
char * end;
tr=strtok_r(input,"/",&end);
for(int i = 0;tr;i++)
{
result[i] = (char *) malloc(strlen(tr)+1);
strcpy(result[i], tr);
tr=strtok_r(NULL,"/",&end);
}
return 1;
}
/*Node_Resolve_Links
* Processes a path returning all links.
* @param N: The node you want to get links from
* @param path: The "foo/bar/bin" path
*/
struct Link_Proc * Node_Resolve_Links(struct Node * N, char * path)
{
if(!N || !path)
{
return NULL;
}
int expected_link_ammount = Node_Resolve_Max_Size(path);
struct Link_Proc * LProc = (struct Link_Proc *) malloc(sizeof(struct Link_Proc) + sizeof(struct NodeLink) * expected_link_ammount);
LProc->ammount = 0;
char * linknames[expected_link_ammount];
Node_Resolve(linknames, path);
for(int i=0;i<expected_link_ammount; i++)
{
struct NodeLink * proclink;
proclink = ipfs_node_get_link_by_name(N, linknames[i]);
if(proclink)
{
LProc->links[i] = (struct NodeLink *)malloc(sizeof(struct NodeLink));
memcpy(LProc->links[i], proclink, sizeof(struct NodeLink));
LProc->ammount++;
free(proclink);
}
}
//Freeing pointer array
for(int i=0;i<expected_link_ammount; i++)
{
free(linknames[i]);
}
return LProc;
}
/*Free_link_Proc
* frees the Link_Proc struct you created.
* @param1: Link_Proc struct (struct Link_Proc *)
*/
void Free_Link_Proc(struct Link_Proc * LPRC)
{
if(LPRC->ammount != 0)
{
for(int i=0;i<LPRC->ammount;i++)
{
ipfs_node_link_free(LPRC->links[i]);
}
}
free(LPRC);
}
/*Node_Tree() Basically a unix-like ls
*@Param1: Result char * foo[strlen(sentence)]
*@Param2: char sentence[] = foo/bar/bin/whatever
*Return: 0 if failure, 1 if success
*/
int Node_Tree(char * result, char * input1) //I don't know where you use this but here it is.
{
if(!input1)
{
return 0;
}
char input[strlen(input1)];
bzero(input, strlen(input1));
strcpy(input, input1);
for(int i=0; i<strlen(input); i++)
{
if(input[i] == '/')
{
input[i] = ' ';
}
}
strcpy(result, input);
return 1;
}