Skip to content

Commit c67d5c8

Browse files
authored
Feature/cid to string from string (#60)
* Added Base32 codec Signed-off-by: artyom-yurin <[email protected]> * Added toString for V1 CID Signed-off-by: artyom-yurin <[email protected]> * Added FromString function Signed-off-by: artyom-yurin <[email protected]> * Divided toString into two functions toString and toStringOfBase Signed-off-by: artyom-yurin <[email protected]>
1 parent f611ebe commit c67d5c8

File tree

11 files changed

+545
-11
lines changed

11 files changed

+545
-11
lines changed

include/libp2p/multi/content_identifier_codec.hpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ namespace libp2p::multi {
2222
INVALID_CONTENT_TYPE = 1,
2323
INVALID_HASH_TYPE,
2424
INVALID_HASH_LENGTH,
25-
VERSION_UNSUPPORTED
25+
VERSION_UNSUPPORTED,
26+
INVALID_BASE_ENCODING,
2627
};
2728

2829
enum class DecodeError {
2930
EMPTY_VERSION = 1,
3031
EMPTY_MULTICODEC,
3132
MALFORMED_VERSION,
32-
RESERVED_VERSION
33+
RESERVED_VERSION,
34+
CID_TOO_SHORT,
3335
};
3436

3537
static outcome::result<std::vector<uint8_t>> encode(
@@ -42,16 +44,31 @@ namespace libp2p::multi {
4244
gsl::span<const uint8_t> bytes);
4345

4446
/**
45-
* @brief Encode CID v0 to string representation
47+
* @brief Encode CID to string representation
4648
* @param cid - input CID for encode
47-
* @param encoding - type of the encoding, ignored for CID v0 (always
48-
* Base58)
49-
* @todo Sergey Kaprovich: FIL-133 add support for CID v1
49+
* Encoding:
50+
* Base58 for CID v0
51+
* Base32 for CID v1
5052
* @return CID string
5153
*/
52-
static outcome::result<std::string> toString(
53-
const ContentIdentifier &cid,
54-
MultibaseCodec::Encoding encoding = MultibaseCodec::Encoding::BASE58);
54+
static outcome::result<std::string> toString(const ContentIdentifier &cid);
55+
56+
/**
57+
* @brief Encode CID to string representation
58+
* @param cid - input CID for encode
59+
* @param base - type of the encoding
60+
* @return CID string
61+
*/
62+
static outcome::result<std::string> toStringOfBase(
63+
const ContentIdentifier &cid, MultibaseCodec::Encoding base);
64+
65+
/**
66+
* @brief Decode string representation of CID to CID
67+
* @param str - CID string representation
68+
* @return CID
69+
*/
70+
static outcome::result<ContentIdentifier> fromString(
71+
const std::string &str);
5572
};
5673

5774
} // namespace libp2p::multi

include/libp2p/multi/multibase_codec.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ namespace libp2p::multi {
3030
enum class Encoding : char {
3131
BASE16_LOWER = 'f',
3232
BASE16_UPPER = 'F',
33+
BASE32_LOWER = 'b',
34+
BASE32_UPPER = 'B',
3335
BASE58 = 'Z',
3436
BASE64 = 'm'
3537
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef LIBP2P_BASE32_HPP
7+
#define LIBP2P_BASE32_HPP
8+
9+
#include <libp2p/common/types.hpp>
10+
#include <libp2p/outcome/outcome.hpp>
11+
12+
/**
13+
* Encode/decode to/from base32 format
14+
* Implementation is taken from
15+
* https://github.com/mjg59/tpmtotp/blob/master/base32.c
16+
*/
17+
namespace libp2p::multi::detail {
18+
19+
/**
20+
* Encode bytes to base32 uppercase string
21+
* @param bytes to be encoded
22+
* @return encoded string
23+
*/
24+
std::string encodeBase32Upper(const common::ByteArray &bytes);
25+
/**
26+
* Encode bytes to base32 lowercase string
27+
* @param bytes to be encoded
28+
* @return encoded string
29+
*/
30+
std::string encodeBase32Lower(const common::ByteArray &bytes);
31+
32+
/**
33+
* Decode base32 uppercase to bytes
34+
* @param string to be decoded
35+
* @return decoded bytes in case of success
36+
*/
37+
outcome::result<common::ByteArray> decodeBase32Upper(std::string_view string);
38+
39+
/**
40+
* Decode base32 lowercase string to bytes
41+
* @param string to be decoded
42+
* @return decoded bytes in case of success
43+
*/
44+
outcome::result<common::ByteArray> decodeBase32Lower(std::string_view string);
45+
46+
} // namespace libp2p::multi::detail
47+
48+
#endif // LIBP2P_BASE32_HPP

include/libp2p/multi/multibase_codec/codecs/base_error.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace libp2p::multi::detail {
1313
enum class BaseError {
1414
INVALID_BASE58_INPUT = 1,
1515
INVALID_BASE64_INPUT,
16+
INVALID_BASE32_INPUT,
1617
NON_UPPERCASE_INPUT,
1718
NON_LOWERCASE_INPUT
1819
};

src/multi/content_identifier_codec.cpp

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <libp2p/crypto/sha/sha256.hpp>
99
#include <libp2p/multi/content_identifier_codec.hpp>
10+
#include <libp2p/multi/multibase_codec/multibase_codec_impl.hpp>
1011
#include <libp2p/multi/multicodec_type.hpp>
1112
#include <libp2p/multi/uvarint.hpp>
1213

@@ -20,6 +21,8 @@ OUTCOME_CPP_DEFINE_CATEGORY(libp2p::multi, ContentIdentifierCodec::EncodeError,
2021
return "Hash length is invalid; Must be 32 bytes for sha256 in version 0";
2122
case E::INVALID_HASH_TYPE:
2223
return "Hash type is invalid; Must be sha256 in version 0";
24+
case E::INVALID_BASE_ENCODING:
25+
return "Invalid base encoding";
2326
case E::VERSION_UNSUPPORTED:
2427
return "Content identifier version unsupported";
2528
}
@@ -38,6 +41,8 @@ OUTCOME_CPP_DEFINE_CATEGORY(libp2p::multi, ContentIdentifierCodec::DecodeError,
3841
return "Version is malformed; Must be a non-negative integer";
3942
case E::RESERVED_VERSION:
4043
return "Version is greater than the latest version";
44+
case E::CID_TOO_SHORT:
45+
return "CID too short";
4146
}
4247
return "Unknown error";
4348
}
@@ -119,17 +124,55 @@ namespace libp2p::multi {
119124
}
120125

121126
outcome::result<std::string> ContentIdentifierCodec::toString(
122-
const ContentIdentifier &cid, MultibaseCodec::Encoding encoding) {
123-
std::ignore = encoding;
127+
const ContentIdentifier &cid) {
128+
std::string result;
129+
OUTCOME_TRY(cid_bytes, encode(cid));
130+
switch (cid.version) {
131+
case ContentIdentifier::Version::V0:
132+
result = detail::encodeBase58(cid_bytes);
133+
break;
134+
case ContentIdentifier::Version::V1:
135+
result = MultibaseCodecImpl().encode(
136+
cid_bytes, MultibaseCodec::Encoding::BASE32_LOWER);
137+
break;
138+
default:
139+
return EncodeError::VERSION_UNSUPPORTED;
140+
}
141+
return result;
142+
}
143+
144+
outcome::result<std::string> ContentIdentifierCodec::toStringOfBase(
145+
const ContentIdentifier &cid, MultibaseCodec::Encoding base) {
124146
std::string result;
125147
OUTCOME_TRY(cid_bytes, encode(cid));
126148
switch (cid.version) {
127149
case ContentIdentifier::Version::V0:
150+
if (base != MultibaseCodec::Encoding::BASE58)
151+
return EncodeError::INVALID_BASE_ENCODING;
128152
result = detail::encodeBase58(cid_bytes);
129153
break;
154+
case ContentIdentifier::Version::V1:
155+
result = MultibaseCodecImpl().encode(cid_bytes, base);
156+
break;
130157
default:
131158
return EncodeError::VERSION_UNSUPPORTED;
132159
}
133160
return result;
134161
}
162+
163+
outcome::result<ContentIdentifier> ContentIdentifierCodec::fromString(
164+
const std::string &str) {
165+
if (str.size() < 2) {
166+
return DecodeError::CID_TOO_SHORT;
167+
}
168+
169+
if (str.size() == 46 && str.substr(0, 2) == "Qm") {
170+
OUTCOME_TRY(hash, detail::decodeBase58(str));
171+
return decode(hash);
172+
}
173+
174+
OUTCOME_TRY(bytes, MultibaseCodecImpl().decode(str));
175+
176+
return decode(bytes);
177+
}
135178
} // namespace libp2p::multi

src/multi/multibase_codec/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
libp2p_add_library(p2p_multibase_codec
77
multibase_codec_impl.cpp
88
codecs/base16.cpp
9+
codecs/base32.cpp
910
codecs/base58.cpp
1011
codecs/base64.cpp
1112
codecs/base_error.cpp
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <gsl/span>
7+
#include <libp2p/multi/multibase_codec/codecs/base32.hpp>
8+
#include <libp2p/multi/multibase_codec/codecs/base_error.hpp>
9+
10+
namespace libp2p::multi::detail {
11+
const std::string kUpperBase32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
12+
const std::string kLowerBase32Alphabet = "abcdefghijklmnopqrstuvwxyz234567";
13+
14+
enum Base32Mode {
15+
LOWER,
16+
UPPER,
17+
};
18+
19+
int get_byte(int block) {
20+
return block * 5 / 8;
21+
}
22+
23+
int get_bit(int block) {
24+
return 8 - 5 - block * 5 % 8;
25+
}
26+
27+
char encode_char(unsigned char c, Base32Mode mode) {
28+
if (mode == Base32Mode::UPPER) {
29+
return kUpperBase32Alphabet[c & 0x1F]; // 0001 1111
30+
}
31+
return kLowerBase32Alphabet[c & 0x1F];
32+
}
33+
34+
unsigned char shift_right(uint8_t byte, int8_t offset) {
35+
if (offset > 0)
36+
return byte >> offset;
37+
38+
return byte << -offset;
39+
}
40+
41+
unsigned char shift_left(uint8_t byte, int8_t offset) {
42+
return shift_right(byte, -offset);
43+
}
44+
45+
int encode_sequence(gsl::span<const uint8_t> plain, gsl::span<char> coded,
46+
Base32Mode mode) {
47+
for (int block = 0; block < 8; block++) {
48+
int byte = get_byte(block);
49+
int bit = get_bit(block);
50+
51+
if (byte >= plain.size()) {
52+
return block;
53+
}
54+
55+
unsigned char c = shift_right(plain[byte], bit);
56+
57+
if (bit < 0 && byte < plain.size() - 1) {
58+
c |= shift_right(plain[byte + 1], 8 + bit);
59+
}
60+
coded[block] = encode_char(c, mode);
61+
}
62+
return 8;
63+
}
64+
65+
std::string encodeBase32(const common::ByteArray &bytes, Base32Mode mode) {
66+
std::string result;
67+
if (bytes.size() % 5 == 0) {
68+
result = std::string(bytes.size() / 5 * 8, ' ');
69+
} else {
70+
result = std::string((bytes.size() / 5 + 1) * 8, ' ');
71+
}
72+
73+
for (size_t i = 0, j = 0; i < bytes.size(); i += 5, j += 8) {
74+
int n = encode_sequence(
75+
gsl::make_span(&bytes[i], std::min<size_t>(bytes.size() - i, 5)),
76+
gsl::make_span(&result[j], 8), mode);
77+
if (n < 8) {
78+
result.erase(result.end() - (8 - n), result.end());
79+
}
80+
}
81+
82+
return result;
83+
}
84+
85+
std::string encodeBase32Upper(const common::ByteArray &bytes) {
86+
return encodeBase32(bytes, Base32Mode::UPPER);
87+
}
88+
89+
std::string encodeBase32Lower(const common::ByteArray &bytes) {
90+
return encodeBase32(bytes, Base32Mode::LOWER);
91+
}
92+
93+
int decode_char(unsigned char c, Base32Mode mode) {
94+
char decoded_ch = -1;
95+
96+
if (mode == Base32Mode::UPPER) {
97+
if (c >= 'A' && c <= 'Z')
98+
decoded_ch = c - 'A'; // NOLINT
99+
} else {
100+
if (c >= 'a' && c <= 'z')
101+
decoded_ch = c - 'a'; // NOLINT
102+
}
103+
if (c >= '2' && c <= '7')
104+
decoded_ch = c - '2' + 26; // NOLINT
105+
106+
return decoded_ch;
107+
}
108+
109+
outcome::result<int> decode_sequence(gsl::span<const char> coded,
110+
gsl::span<uint8_t> plain,
111+
Base32Mode mode) {
112+
plain[0] = 0;
113+
for (int block = 0; block < 8; block++) {
114+
int bit = get_bit(block);
115+
int byte = get_byte(block);
116+
117+
if (block >= coded.size()) {
118+
return byte;
119+
}
120+
int c = decode_char(coded[block], mode);
121+
if (c < 0) {
122+
return BaseError::INVALID_BASE32_INPUT;
123+
}
124+
125+
plain[byte] |= shift_left(c, bit);
126+
if (bit < 0) {
127+
plain[byte + 1] = shift_left(c, 8 + bit);
128+
}
129+
}
130+
return 5;
131+
}
132+
133+
outcome::result<common::ByteArray> decodeBase32(std::string_view string,
134+
Base32Mode mode) {
135+
common::ByteArray result;
136+
if (string.size() % 8 == 0) {
137+
result = common::ByteArray(string.size() / 8 * 5);
138+
} else {
139+
result = common::ByteArray((string.size() / 8 + 1) * 5);
140+
}
141+
142+
for (size_t i = 0, j = 0; i < string.size(); i += 8, j += 5) {
143+
OUTCOME_TRY(n,
144+
decode_sequence(
145+
gsl::make_span(&string[i],
146+
std::min<size_t>(string.size() - i, 8)),
147+
gsl::make_span(&result[j], 5), mode));
148+
if (n < 5) {
149+
result.erase(result.end() - (5 - n), result.end());
150+
}
151+
}
152+
return result;
153+
}
154+
155+
outcome::result<common::ByteArray> decodeBase32Upper(
156+
std::string_view string) {
157+
return decodeBase32(string, Base32Mode::UPPER);
158+
}
159+
160+
outcome::result<common::ByteArray> decodeBase32Lower(
161+
std::string_view string) {
162+
return decodeBase32(string, Base32Mode::LOWER);
163+
}
164+
165+
} // namespace libp2p::multi::detail

src/multi/multibase_codec/codecs/base_error.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ OUTCOME_CPP_DEFINE_CATEGORY(libp2p::multi::detail, BaseError, e) {
1212
return "Input is not a valid base64 string";
1313
case E::INVALID_BASE58_INPUT:
1414
return "Input is not a valid base58 string";
15+
case E::INVALID_BASE32_INPUT:
16+
return "Input is not a valid base32 string";
1517
case E::NON_UPPERCASE_INPUT:
1618
return "Input is not in the uppercase hex";
1719
case E::NON_LOWERCASE_INPUT:

0 commit comments

Comments
 (0)