/*** * A unix-like file system over IPFS blocks */ #include #include #include #include "libp2p/crypto/encoding/base58.h" #include "libp2p/crypto/sha256.h" #include "libp2p/utils/logger.h" #include "ipfs/unixfs/unixfs.h" #include "protobuf.h" #include "varint.h" /** * The protobuf info: * message Data { * enum DataType { * Raw = 0; * Directory = 1; * File = 2; * Metadata = 3; * Symlink = 4; * } * * required DataType Type = 1; * optional bytes Data = 2; * optional uint64 filesize = 3; * repeated uint64 blocksizes = 4; * } * * message Metadata { * optional string MimeType = 1; * } */ /** * Allocate memory for a new UnixFS struct * @param obj the pointer to the new object * @returns true(1) on success */ int ipfs_unixfs_new(struct UnixFS** obj) { *obj = (struct UnixFS*)malloc(sizeof(struct UnixFS)); if (*obj == NULL) return 0; (*obj)->bytes = 0; (*obj)->bytes_size = 0; (*obj)->data_type = UNIXFS_RAW; (*obj)->block_size_head = NULL; (*obj)->hash = NULL; (*obj)->hash_length = 0; (*obj)->file_size = 0; return 1; } struct UnixFSBlockSizeNode* ipfs_unixfs_get_last_block_size(struct UnixFS* obj) { struct UnixFSBlockSizeNode* current = obj->block_size_head; if (current != NULL) { while (current->next != NULL) current = current->next; } return current; } int ipfs_unixfs_remove_blocksize(struct UnixFS* obj, struct UnixFSBlockSizeNode* to_delete) { struct UnixFSBlockSizeNode* current = obj->block_size_head; struct UnixFSBlockSizeNode* previous = NULL; while (current != NULL && current != to_delete) { previous = current; current = current->next; } if (previous == NULL) { free(obj->block_size_head); obj->block_size_head = NULL; } else { struct UnixFSBlockSizeNode* holder = NULL; if (current != NULL && current->next != NULL) { // we need to delete from the middle holder = current->next; } free(previous->next); if (holder == NULL) previous->next = NULL; else previous->next = holder; } return 1; } int ipfs_unixfs_free(struct UnixFS* obj) { if (obj != NULL) { if (obj->hash != NULL) { free(obj->hash); } if (obj->bytes != NULL) { free(obj->bytes); } if (obj->block_size_head != NULL) { struct UnixFSBlockSizeNode* current = ipfs_unixfs_get_last_block_size(obj); while(current != NULL) { ipfs_unixfs_remove_blocksize(obj, current); current = ipfs_unixfs_get_last_block_size(obj); } } free(obj); obj = NULL; } return 1; } /*** * Write data to data section of a UnixFS stuct. NOTE: this also calculates a sha256 hash * @param data the data to write * @param data_length the length of the data * @param unix_fs the struct to add to * @returns true(1) on success */ int ipfs_unixfs_add_data(unsigned char* data, size_t data_length, struct UnixFS* unix_fs) { unix_fs->bytes_size = data_length; unix_fs->bytes = malloc(sizeof(unsigned char) * data_length); if ( unix_fs->bytes == NULL) { return 0; } memcpy( unix_fs->bytes, data, data_length); // now compute the hash unix_fs->hash_length = 32; unix_fs->hash = (unsigned char*)malloc(unix_fs->hash_length); if (unix_fs->hash == NULL) { free(unix_fs->bytes); return 0; } if (libp2p_crypto_hashing_sha256(data, data_length, &unix_fs->hash[0]) == 0) { free(unix_fs->bytes); free(unix_fs->hash); return 0; } // debug: display hash if (libp2p_logger_watching_class("unixfs")) { size_t b58size = 100; uint8_t *b58key = (uint8_t *) malloc(b58size); if (b58key != NULL) { libp2p_crypto_encoding_base58_encode(unix_fs->hash, unix_fs->hash_length, &b58key, &b58size); libp2p_logger_debug("unixfs", "Saving hash of %s to unixfs object.\n", b58key); free(b58key); } } return 1; } int ipfs_unixfs_add_blocksize(const struct UnixFSBlockSizeNode* blocksize, struct UnixFS* unix_fs) { struct UnixFSBlockSizeNode* last = unix_fs->block_size_head; if (last == NULL) { // we're the first one unix_fs->block_size_head = (struct UnixFSBlockSizeNode*)malloc(sizeof(struct UnixFSBlockSizeNode)); if (unix_fs->block_size_head == NULL) { return 0; } unix_fs->block_size_head->block_size = blocksize->block_size; unix_fs->block_size_head->next = NULL; } else { // find the last one while (last->next != NULL) { last = last->next; } last->next = (struct UnixFSBlockSizeNode*)malloc(sizeof(struct UnixFSBlockSizeNode)); if (last->next == NULL) return 0; last->next->block_size = blocksize->block_size; last->next->next = NULL; } return 1; } /** * Protobuf functions */ // data type bytes file size block sizes enum WireType ipfs_unixfs_message_fields[] = { WIRETYPE_VARINT, WIRETYPE_LENGTH_DELIMITED, WIRETYPE_VARINT, WIRETYPE_VARINT }; /** * Calculate the max size of the protobuf before encoding * @param obj what will be encoded * @returns the size of the buffer necessary to encode the object */ size_t ipfs_unixfs_protobuf_encode_size(const struct UnixFS* obj) { size_t sz = 0; // bytes sz += obj->bytes_size + 11; // data tupe sz += 2; // file_size sz += 11; // block sizes struct UnixFSBlockSizeNode* currNode = obj->block_size_head; while(currNode != NULL) { sz += 11; currNode = currNode->next; } return sz; } /*** * Encode a UnixFS object into protobuf format * @param incoming the incoming object * @param outgoing where the bytes will be placed * @param max_buffer_size the size of the outgoing buffer * @param bytes_written how many bytes were written in the buffer * @returns true(1) on success */ int ipfs_unixfs_protobuf_encode(const struct UnixFS* incoming, unsigned char* outgoing, size_t max_buffer_size, size_t* bytes_written) { size_t bytes_used = 0; *bytes_written = 0; int retVal = 0; if (incoming != NULL) { // data type (required) retVal = protobuf_encode_varint(1, ipfs_unixfs_message_fields[0], incoming->data_type, outgoing, max_buffer_size - *bytes_written, &bytes_used); if (retVal == 0) return 0; *bytes_written += bytes_used; // bytes (optional) if (incoming->bytes_size > 0) { retVal = protobuf_encode_length_delimited(2, ipfs_unixfs_message_fields[1], (char*)incoming->bytes, incoming->bytes_size, &outgoing[*bytes_written], max_buffer_size - (*bytes_written), &bytes_used); if (retVal == 0) return 0; *bytes_written += bytes_used; } // file size (optional) if (incoming->file_size > 0) { retVal = protobuf_encode_varint(3, ipfs_unixfs_message_fields[2], incoming->file_size, &outgoing[*bytes_written], max_buffer_size - (*bytes_written), &bytes_used); if (retVal == 0) return 0; *bytes_written += bytes_used; } // block sizes struct UnixFSBlockSizeNode* currNode = incoming->block_size_head; while (currNode != NULL) { retVal = protobuf_encode_varint(4, ipfs_unixfs_message_fields[3], currNode->block_size, &outgoing[*bytes_written], max_buffer_size - (*bytes_written), &bytes_used); *bytes_written += bytes_used; currNode = currNode->next; } } return 1; } /*** * Decodes a protobuf array of bytes into a UnixFS object * @param incoming the array of bytes * @param incoming_size the length of the array * @param outgoing the UnixFS object * @returns true(1) on success, false(0) on error */ int ipfs_unixfs_protobuf_decode(unsigned char* incoming, size_t incoming_size, struct UnixFS** outgoing) { // short cut for nulls if (incoming_size == 0) { *outgoing = NULL; return 0; } size_t pos = 0; int retVal = 0; if (ipfs_unixfs_new(outgoing) == 0) { return 0; } struct UnixFS* result = *outgoing; while(pos < incoming_size) { size_t bytes_read = 0; int field_no; enum WireType field_type; if (protobuf_decode_field_and_type(&incoming[pos], incoming_size, &field_no, &field_type, &bytes_read) == 0) { return 0; } pos += bytes_read; switch(field_no) { case (1): // data type (varint) result->data_type = varint_decode(&incoming[pos], incoming_size - pos, &bytes_read); pos += bytes_read; break; case (2): // bytes (length delimited) retVal = protobuf_decode_length_delimited(&incoming[pos], incoming_size - pos, (char**)&(result->bytes), &(result->bytes_size), &bytes_read); if (retVal == 0) return 0; pos += bytes_read; break; case (3): // file size result->file_size = varint_decode(&incoming[pos], incoming_size - pos, &bytes_read); pos += bytes_read; break; case (4): { // block sizes (linked list from varint) struct UnixFSBlockSizeNode bs; bs.next = NULL; bs.block_size = varint_decode(&incoming[pos], incoming_size - pos, &bytes_read); ipfs_unixfs_add_blocksize(&bs, result); pos += bytes_read; break; } } } return 1; }