mirror of https://github.com/libp2p/cpp-libp2p.git
Harrm
5 years ago
committed by
GitHub
15 changed files with 474 additions and 7 deletions
@ -0,0 +1,50 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#ifndef LIBP2P_CONTENT_IDENTIFIER_HPP |
||||
|
#define LIBP2P_CONTENT_IDENTIFIER_HPP |
||||
|
|
||||
|
#include <vector> |
||||
|
|
||||
|
#include <boost/operators.hpp> |
||||
|
#include <libp2p/multi/multicodec_type.hpp> |
||||
|
#include <libp2p/multi/multihash.hpp> |
||||
|
|
||||
|
namespace libp2p::multi { |
||||
|
|
||||
|
/**
|
||||
|
* A CID is a self-describing content-addressed identifier. It uses |
||||
|
* cryptographic hashes to achieve content addressing. It uses several |
||||
|
* multiformats to achieve flexible self-description, namely multihash for |
||||
|
* hashes, multicodec for data content types, and multibase to encode the CID |
||||
|
* itself into strings. Concretely, it's a typed content address: a tuple of |
||||
|
* (content-type, content-address). |
||||
|
* |
||||
|
* @note multibase may be omitted in non text-based protocols and is generally |
||||
|
* needed only for CIDs serialized to a string, so it is not present in this |
||||
|
* structure |
||||
|
*/ |
||||
|
struct ContentIdentifier: public boost::equality_comparable<ContentIdentifier> { |
||||
|
enum class Version { V0 = 0, V1 = 1 }; |
||||
|
|
||||
|
ContentIdentifier(Version version, MulticodecType::Code content_type, |
||||
|
Multihash content_address); |
||||
|
|
||||
|
/**
|
||||
|
* @param base is a human-readable multibase prefix |
||||
|
* @returns human readable representation of the CID |
||||
|
*/ |
||||
|
std::string toPrettyString(const std::string &base); |
||||
|
|
||||
|
bool operator==(const ContentIdentifier &c) const; |
||||
|
|
||||
|
Version version; |
||||
|
MulticodecType::Code content_type; |
||||
|
Multihash content_address; |
||||
|
}; |
||||
|
|
||||
|
} // namespace libp2p::multi
|
||||
|
|
||||
|
#endif // LIBP2P_CONTENT_IDENTIFIER_HPP
|
@ -0,0 +1,45 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#ifndef LIBP2P_CONTENT_IDENTIFIER_CODEC_HPP |
||||
|
#define LIBP2P_CONTENT_IDENTIFIER_CODEC_HPP |
||||
|
|
||||
|
#include <libp2p/multi/content_identifier.hpp> |
||||
|
|
||||
|
namespace libp2p::multi { |
||||
|
|
||||
|
/**
|
||||
|
* Serializes and deserializes CID to byte representation. |
||||
|
* To serialize it to a multibase encoded string, use MultibaseCodec |
||||
|
* @see MultibaseCodec |
||||
|
*/ |
||||
|
class ContentIdentifierCodec { |
||||
|
public: |
||||
|
enum class EncodeError { |
||||
|
INVALID_CONTENT_TYPE = 1, |
||||
|
INVALID_HASH_TYPE, |
||||
|
INVALID_HASH_LENGTH |
||||
|
}; |
||||
|
|
||||
|
enum class DecodeError { |
||||
|
EMPTY_VERSION = 1, |
||||
|
EMPTY_MULTICODEC, |
||||
|
MALFORMED_VERSION, |
||||
|
RESERVED_VERSION |
||||
|
}; |
||||
|
|
||||
|
static outcome::result<std::vector<uint8_t>> encode( |
||||
|
const ContentIdentifier &cid); |
||||
|
|
||||
|
static outcome::result<ContentIdentifier> decode( |
||||
|
gsl::span<uint8_t> bytes); |
||||
|
}; |
||||
|
|
||||
|
} // namespace libp2p::multi
|
||||
|
|
||||
|
OUTCOME_HPP_DECLARE_ERROR(libp2p::multi, ContentIdentifierCodec::EncodeError); |
||||
|
OUTCOME_HPP_DECLARE_ERROR(libp2p::multi, ContentIdentifierCodec::DecodeError); |
||||
|
|
||||
|
#endif // LIBP2P_CONTENT_IDENTIFIER_CODEC_HPP
|
@ -0,0 +1,63 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#ifndef LIBP2P_MULTICODECTYPE_HPP |
||||
|
#define LIBP2P_MULTICODECTYPE_HPP |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include <boost/optional.hpp> |
||||
|
|
||||
|
namespace libp2p::multi { |
||||
|
|
||||
|
/**
|
||||
|
* LibP2P uses "protocol tables" to agree upon the mapping from one multicodec |
||||
|
* code. These tables can be application specific, though, like with other |
||||
|
* multiformats, there is a globally agreed upon table with common protocols |
||||
|
* and formats. |
||||
|
*/ |
||||
|
class MulticodecType { |
||||
|
public: |
||||
|
/// TODO(Harrm) add more codes
|
||||
|
enum Code { |
||||
|
IDENTITY = 0x00, |
||||
|
SHA1 = 0x11, |
||||
|
SHA2_256 = 0x12, |
||||
|
SHA2_512 = 0x13, |
||||
|
SHA3_512 = 0x14, |
||||
|
SHA3_384 = 0x15, |
||||
|
SHA3_256 = 0x16, |
||||
|
SHA3_224 = 0x17, |
||||
|
DAG_PB = 0x70 |
||||
|
}; |
||||
|
|
||||
|
static std::string getName(Code code) { |
||||
|
switch (code) { |
||||
|
case Code::IDENTITY: |
||||
|
return "raw"; |
||||
|
case Code::SHA1: |
||||
|
return "sha1"; |
||||
|
case Code::SHA2_256: |
||||
|
return "sha2-256"; |
||||
|
case Code::SHA2_512: |
||||
|
return "sha2-512"; |
||||
|
case Code::SHA3_224: |
||||
|
return "sha3-224"; |
||||
|
case Code::SHA3_256: |
||||
|
return "sha3-256"; |
||||
|
case Code::SHA3_384: |
||||
|
return "sha3-384"; |
||||
|
case Code::SHA3_512: |
||||
|
return "sha3-512"; |
||||
|
case Code::DAG_PB: |
||||
|
return "dag-pb"; |
||||
|
} |
||||
|
return "unknown"; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} // namespace libp2p::multi
|
||||
|
|
||||
|
#endif // LIBP2P_MULTICODECTYPE_HPP
|
@ -0,0 +1,40 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#include <libp2p/multi/content_identifier.hpp> |
||||
|
|
||||
|
#include <boost/format.hpp> |
||||
|
#include <libp2p/common/hexutil.hpp> |
||||
|
|
||||
|
namespace libp2p::multi { |
||||
|
|
||||
|
ContentIdentifier::ContentIdentifier(Version version, |
||||
|
MulticodecType::Code content_type, |
||||
|
Multihash content_address) |
||||
|
: version{version}, |
||||
|
content_type{content_type}, |
||||
|
content_address{std::move(content_address)} {} |
||||
|
|
||||
|
std::string ContentIdentifier::toPrettyString(const std::string &base) { |
||||
|
/// TODO(Harrm) FIL-14: hash type is a subset of multicodec type, better move them
|
||||
|
/// to one place
|
||||
|
std::string hash_type = MulticodecType::getName( |
||||
|
static_cast<MulticodecType::Code>(content_address.getType())); |
||||
|
std::string hash_hex = common::hex_lower(content_address.getHash()); |
||||
|
std::string hash_length = |
||||
|
std::to_string(content_address.getHash().size() * 8); |
||||
|
std::string v = "cidv" + std::to_string(static_cast<uint64_t>(version)); |
||||
|
return (boost::format("%1% - %2% - %3% - %4%-%5%-%6%") % base % v |
||||
|
% MulticodecType::getName(content_type) % hash_type % hash_length |
||||
|
% hash_hex) |
||||
|
.str(); |
||||
|
} |
||||
|
|
||||
|
bool ContentIdentifier::operator==(const ContentIdentifier &c) const { |
||||
|
return version == c.version and content_type == c.content_type |
||||
|
and content_address == c.content_address; |
||||
|
} |
||||
|
|
||||
|
} // namespace libp2p::multi
|
@ -0,0 +1,103 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#include <libp2p/multi/content_identifier_codec.hpp> |
||||
|
#include <libp2p/multi/multicodec_type.hpp> |
||||
|
#include <libp2p/multi/uvarint.hpp> |
||||
|
|
||||
|
OUTCOME_CPP_DEFINE_CATEGORY(libp2p::multi, ContentIdentifierCodec::EncodeError, |
||||
|
e) { |
||||
|
using E = libp2p::multi::ContentIdentifierCodec::EncodeError; |
||||
|
switch (e) { |
||||
|
case E::INVALID_CONTENT_TYPE: |
||||
|
return "Content type does not conform the version"; |
||||
|
case E::INVALID_HASH_LENGTH: |
||||
|
return "Hash length is invalid; Must be 32 bytes for sha256 in version 0"; |
||||
|
case E::INVALID_HASH_TYPE: |
||||
|
return "Hash type is invalid; Must be sha256 in version 0"; |
||||
|
} |
||||
|
return "Unknown error"; |
||||
|
} |
||||
|
|
||||
|
OUTCOME_CPP_DEFINE_CATEGORY(libp2p::multi, ContentIdentifierCodec::DecodeError, |
||||
|
e) { |
||||
|
using E = libp2p::multi::ContentIdentifierCodec::DecodeError; |
||||
|
switch (e) { |
||||
|
case E::EMPTY_MULTICODEC: |
||||
|
return "Multicodec prefix is absent"; |
||||
|
case E::EMPTY_VERSION: |
||||
|
return "Version is absent"; |
||||
|
case E::MALFORMED_VERSION: |
||||
|
return "Version is malformed; Must be a non-negative integer"; |
||||
|
case E::RESERVED_VERSION: |
||||
|
return "Version is greater than the latest version"; |
||||
|
} |
||||
|
return "Unknown error"; |
||||
|
} |
||||
|
|
||||
|
namespace libp2p::multi { |
||||
|
|
||||
|
outcome::result<std::vector<uint8_t>> ContentIdentifierCodec::encode( |
||||
|
const ContentIdentifier &cid) { |
||||
|
std::vector<uint8_t> bytes; |
||||
|
if (cid.version == ContentIdentifier::Version::V1) { |
||||
|
UVarint version(static_cast<uint64_t>(cid.version)); |
||||
|
common::append(bytes, version.toBytes()); |
||||
|
UVarint type(cid.content_type); |
||||
|
common::append(bytes, type.toBytes()); |
||||
|
auto const &hash = cid.content_address.toBuffer(); |
||||
|
common::append(bytes, hash); |
||||
|
|
||||
|
} else if (cid.version == ContentIdentifier::Version::V0) { |
||||
|
if (cid.content_type != MulticodecType::DAG_PB) { |
||||
|
return EncodeError::INVALID_CONTENT_TYPE; |
||||
|
} |
||||
|
if (cid.content_address.getType() != HashType::sha256) { |
||||
|
return EncodeError::INVALID_HASH_TYPE; |
||||
|
} |
||||
|
if (cid.content_address.getHash().size() != 32) { |
||||
|
return EncodeError::INVALID_HASH_LENGTH; |
||||
|
} |
||||
|
auto const &hash = cid.content_address.toBuffer(); |
||||
|
common::append(bytes, hash); |
||||
|
} |
||||
|
return bytes; |
||||
|
} |
||||
|
|
||||
|
outcome::result<ContentIdentifier> ContentIdentifierCodec::decode( |
||||
|
gsl::span<uint8_t> bytes) { |
||||
|
if (bytes.size() == 34 and bytes[0] == 0x12 and bytes[1] == 0x20) { |
||||
|
OUTCOME_TRY(hash, Multihash::createFromBytes(bytes)); |
||||
|
return ContentIdentifier(ContentIdentifier::Version::V0, |
||||
|
MulticodecType::DAG_PB, std::move(hash)); |
||||
|
} else { |
||||
|
auto version_opt = UVarint::create(bytes); |
||||
|
if (!version_opt) { |
||||
|
return DecodeError::EMPTY_VERSION; |
||||
|
} |
||||
|
auto version = version_opt.value().toUInt64(); |
||||
|
if (version == 1) { |
||||
|
auto version_length = UVarint::calculateSize(bytes); |
||||
|
auto multicodec_opt = UVarint::create(bytes.subspan(version_length)); |
||||
|
if (!multicodec_opt) { |
||||
|
return DecodeError::EMPTY_MULTICODEC; |
||||
|
} |
||||
|
auto multicodec_length = |
||||
|
UVarint::calculateSize(bytes.subspan(version_length)); |
||||
|
OUTCOME_TRY(hash, |
||||
|
Multihash::createFromBytes( |
||||
|
bytes.subspan(version_length + multicodec_length))); |
||||
|
return ContentIdentifier( |
||||
|
ContentIdentifier::Version::V1, |
||||
|
MulticodecType::Code(multicodec_opt.value().toUInt64()), |
||||
|
std::move(hash)); |
||||
|
} else if (version <= 0) { |
||||
|
return DecodeError::MALFORMED_VERSION; |
||||
|
} else { |
||||
|
return DecodeError::RESERVED_VERSION; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} // namespace libp2p::multi
|
@ -0,0 +1,131 @@ |
|||||
|
/**
|
||||
|
* Copyright Soramitsu Co., Ltd. All Rights Reserved. |
||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||
|
*/ |
||||
|
|
||||
|
#include <gtest/gtest.h> |
||||
|
|
||||
|
#include <libp2p/common/hexutil.hpp> |
||||
|
#include <libp2p/common/literals.hpp> |
||||
|
#include <libp2p/multi/content_identifier.hpp> |
||||
|
#include <libp2p/multi/content_identifier_codec.hpp> |
||||
|
#include <libp2p/multi/multibase_codec/multibase_codec_impl.hpp> |
||||
|
#include <libp2p/multi/multicodec_type.hpp> |
||||
|
#include <libp2p/multi/uvarint.hpp> |
||||
|
#include <testutil/outcome.hpp> |
||||
|
|
||||
|
using libp2p::multi::ContentIdentifier; |
||||
|
using libp2p::multi::ContentIdentifierCodec; |
||||
|
using libp2p::multi::HashType; |
||||
|
using libp2p::multi::MultibaseCodec; |
||||
|
using libp2p::multi::MultibaseCodecImpl; |
||||
|
using libp2p::multi::MulticodecType; |
||||
|
using libp2p::multi::Multihash; |
||||
|
using libp2p::multi::UVarint; |
||||
|
using libp2p::common::operator""_multihash; |
||||
|
using libp2p::common::operator""_unhex; |
||||
|
|
||||
|
const Multihash ZERO_MULTIHASH = |
||||
|
"12200000000000000000000000000000000000000000000000000000000000000000"_multihash; |
||||
|
const Multihash EXAMPLE_MULTIHASH = |
||||
|
"12206e6ff7950a36187a801613426e858dce686cd7d7e3c0fc42ee0330072d245c95"_multihash; |
||||
|
|
||||
|
TEST(CidTest, PrettyString) { |
||||
|
ContentIdentifier c1(ContentIdentifier::Version::V1, MulticodecType::IDENTITY, |
||||
|
ZERO_MULTIHASH); |
||||
|
ASSERT_EQ(c1.toPrettyString("base58"), |
||||
|
"base58 - cidv1 - raw - sha2-256-256-" |
||||
|
+ libp2p::common::hex_lower(ZERO_MULTIHASH.getHash())); |
||||
|
ContentIdentifier c2(ContentIdentifier::Version::V0, MulticodecType::DAG_PB, |
||||
|
EXAMPLE_MULTIHASH); |
||||
|
ASSERT_EQ(c2.toPrettyString("base64"), |
||||
|
"base64 - cidv0 - dag-pb - sha2-256-256-" |
||||
|
+ libp2p::common::hex_lower(EXAMPLE_MULTIHASH.getHash())); |
||||
|
} |
||||
|
|
||||
|
class CidEncodeTest |
||||
|
: public testing::TestWithParam< |
||||
|
std::pair<ContentIdentifier, outcome::result<std::vector<uint8_t>>>> { |
||||
|
}; |
||||
|
|
||||
|
TEST(CidTest, Create) { |
||||
|
ContentIdentifier c(ContentIdentifier::Version::V0, MulticodecType::IDENTITY, |
||||
|
EXAMPLE_MULTIHASH); |
||||
|
ASSERT_EQ(c.content_address, EXAMPLE_MULTIHASH); |
||||
|
} |
||||
|
|
||||
|
TEST_P(CidEncodeTest, Encode) { |
||||
|
auto [cid, expectation] = GetParam(); |
||||
|
auto bytes = ContentIdentifierCodec::encode(cid); |
||||
|
if (expectation) { |
||||
|
auto bytes_value = bytes.value(); |
||||
|
auto expectation_value = expectation.value(); |
||||
|
ASSERT_TRUE(std::equal(bytes_value.begin(), bytes_value.end(), |
||||
|
expectation_value.begin())) |
||||
|
<< libp2p::common::hex_lower(bytes_value); |
||||
|
} else { |
||||
|
ASSERT_EQ(bytes.error(), expectation.error()) << bytes.error().message(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class CidDecodeTest |
||||
|
: public testing::TestWithParam< |
||||
|
std::pair<std::vector<uint8_t>, outcome::result<ContentIdentifier>>> { |
||||
|
public: |
||||
|
void SetUp() { |
||||
|
base_codec = std::make_shared<MultibaseCodecImpl>(); |
||||
|
} |
||||
|
|
||||
|
std::shared_ptr<MultibaseCodec> base_codec; |
||||
|
}; |
||||
|
|
||||
|
TEST_P(CidDecodeTest, Decode) { |
||||
|
auto [cid_bytes, expectation] = GetParam(); |
||||
|
auto cid = ContentIdentifierCodec::decode(cid_bytes); |
||||
|
if (expectation) { |
||||
|
ASSERT_EQ(cid.value(), expectation.value()); |
||||
|
} else { |
||||
|
ASSERT_EQ(cid.error(), expectation.error()) << cid.error().message(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class CidEncodeDecodeTest : public testing::TestWithParam<ContentIdentifier> {}; |
||||
|
|
||||
|
TEST_P(CidEncodeDecodeTest, DecodedMatchesOriginal) { |
||||
|
auto cid = GetParam(); |
||||
|
EXPECT_OUTCOME_TRUE(bytes, ContentIdentifierCodec::encode(cid)); |
||||
|
EXPECT_OUTCOME_TRUE(dec_cid, ContentIdentifierCodec::decode(bytes)); |
||||
|
ASSERT_EQ(cid, dec_cid); |
||||
|
} |
||||
|
|
||||
|
const std::vector< |
||||
|
std::pair<ContentIdentifier, outcome::result<std::vector<uint8_t>>>> |
||||
|
encodeSuite{{ContentIdentifier(ContentIdentifier::Version::V0, |
||||
|
MulticodecType::SHA1, ZERO_MULTIHASH), |
||||
|
ContentIdentifierCodec::EncodeError::INVALID_CONTENT_TYPE}, |
||||
|
{ContentIdentifier(ContentIdentifier::Version::V0, |
||||
|
MulticodecType::DAG_PB, ZERO_MULTIHASH), |
||||
|
ZERO_MULTIHASH.toBuffer()}}; |
||||
|
|
||||
|
INSTANTIATE_TEST_CASE_P(EncodeTests, CidEncodeTest, |
||||
|
testing::ValuesIn(encodeSuite)); |
||||
|
|
||||
|
const std::vector< |
||||
|
std::pair<std::vector<uint8_t>, outcome::result<ContentIdentifier>>> |
||||
|
decodeSuite{{EXAMPLE_MULTIHASH.toBuffer(), |
||||
|
ContentIdentifier(ContentIdentifier::Version::V0, |
||||
|
MulticodecType::DAG_PB, EXAMPLE_MULTIHASH)}}; |
||||
|
|
||||
|
INSTANTIATE_TEST_CASE_P(DecodeTests, CidDecodeTest, |
||||
|
testing::ValuesIn(decodeSuite)); |
||||
|
|
||||
|
const std::vector<ContentIdentifier> encodeDecodeSuite = { |
||||
|
ContentIdentifier(ContentIdentifier::Version::V0, MulticodecType::DAG_PB, |
||||
|
EXAMPLE_MULTIHASH), |
||||
|
ContentIdentifier(ContentIdentifier::Version::V1, MulticodecType::IDENTITY, |
||||
|
ZERO_MULTIHASH), |
||||
|
ContentIdentifier(ContentIdentifier::Version::V1, MulticodecType::SHA1, |
||||
|
EXAMPLE_MULTIHASH)}; |
||||
|
|
||||
|
INSTANTIATE_TEST_CASE_P(EncodeDecodeTest, CidEncodeDecodeTest, |
||||
|
testing::ValuesIn(encodeDecodeSuite)); |
Loading…
Reference in new issue