diff --git a/src/libstore/content-address.cc b/src/libstore/content-address.cc index f7d5e408e32..83200b29ed4 100644 --- a/src/libstore/content-address.cc +++ b/src/libstore/content-address.cc @@ -10,6 +10,7 @@ std::string FixedOutputHash::printMethodAlgo() const { return makeFileIngestionPrefix(method) + printHashType(hash.type); } + std::string makeFileIngestionPrefix(const FileIngestionMethod m) { switch (m) { case FileIngestionMethod::Flat: @@ -22,6 +23,7 @@ std::string makeFileIngestionPrefix(const FileIngestionMethod m) { abort(); } + std::string renderLegacyContentAddress(LegacyContentAddress ca) { return std::visit(overloaded { [](TextHash th) { @@ -29,17 +31,32 @@ std::string renderLegacyContentAddress(LegacyContentAddress ca) { + th.hash.to_string(Base32, true); }, [](FixedOutputHash fsh) { - return "fixed:" - + makeFileIngestionPrefix(fsh.method) - + fsh.hash.to_string(Base32, true); + return "fixed:" + + makeFileIngestionPrefix(fsh.method) + + fsh.hash.to_string(Base32, true); }, - [](IPFSHash fsh) { - return "ipfs:" - + fsh.hash.to_string(Base32, true); + [](IPFSHash ih) { + // FIXME do Base-32 for consistency + return "ipfs-git:" + ih.hash.to_string(Base32, true); } }, ca); } +static HashType parseHashType_(std::string_view & rest) { + auto hashTypeRaw = splitPrefixTo(rest, ':'); + if (!hashTypeRaw) + throw UsageError("hash must be in form \":\", but found: %s", rest); + return parseHashType(*hashTypeRaw); +}; + +static FileIngestionMethod parseFileIngestionMethod_(std::string_view & rest) { + if (splitPrefix(rest, "r:")) + return FileIngestionMethod::Recursive; + else if (splitPrefix(rest, "git:")) + return FileIngestionMethod::Git; + return FileIngestionMethod::Flat; +} + LegacyContentAddress parseLegacyContentAddress(std::string_view rawCa) { auto rest = rawCa; @@ -47,22 +64,14 @@ LegacyContentAddress parseLegacyContentAddress(std::string_view rawCa) { { auto optPrefix = splitPrefixTo(rest, ':'); if (!optPrefix) - throw UsageError("not a content address because it is not in the form \":\": %s", rawCa); + throw UsageError("not a path-info content address because it is not in the form \":\": %s", rawCa); prefix = *optPrefix; } - auto parseHashType_ = [&](){ - auto hashTypeRaw = splitPrefixTo(rest, ':'); - if (!hashTypeRaw) - throw UsageError("content address hash must be in form \":\", but found: %s", rawCa); - HashType hashType = parseHashType(*hashTypeRaw); - return std::move(hashType); - }; - // Switch on prefix if (prefix == "text") { // No parsing of the method, "text" only support flat. - HashType hashType = parseHashType_(); + HashType hashType = parseHashType_(rest); if (hashType != htSHA256) throw Error("text content address hash should use %s, but instead uses %s", printHashType(htSHA256), printHashType(hashType)); @@ -70,24 +79,19 @@ LegacyContentAddress parseLegacyContentAddress(std::string_view rawCa) { .hash = Hash::parseNonSRIUnprefixed(rest, std::move(hashType)), }; } else if (prefix == "fixed") { - // Parse method - auto method = FileIngestionMethod::Flat; - if (splitPrefix(rest, "r:")) - method = FileIngestionMethod::Recursive; - else if (splitPrefix(rest, "git:")) - method = FileIngestionMethod::Git; - HashType hashType = parseHashType_(); + auto method = parseFileIngestionMethod_(rest); + HashType hashType = parseHashType_(rest); return FixedOutputHash { .method = method, .hash = Hash::parseNonSRIUnprefixed(rest, std::move(hashType)), }; - } else if (prefix == "ipfs") { - Hash hash = Hash::parseAnyPrefixed(rest); - if (hash.type != htSHA256) - throw Error("the ipfs hash should have type SHA256"); - return IPFSHash { hash }; + } else if (prefix == "ipfs-git") { + auto hash = IPFSHash::from_string(rest); + if (hash.hash.type != htSHA256) + throw Error("This IPFS hash should have type SHA-256: %s", hash.to_string()); + return hash; } else - throw UsageError("content address prefix \"%s\" is unrecognized. Recogonized prefixes are \"text\" or \"fixed\"", prefix); + throw UsageError("path-info content address prefix \"%s\" is unrecognized. Recogonized prefixes are \"text\", \"fixed\", or \"ipfs-git\"", prefix); }; std::optional parseLegacyContentAddressOpt(std::string_view rawCaOpt) { @@ -98,116 +102,129 @@ std::string renderLegacyContentAddress(std::optional ca) { return ca ? renderLegacyContentAddress(*ca) : ""; } + +// FIXME Deduplicate with store-api.cc path computation std::string renderContentAddress(ContentAddress ca) { - return "full:" + ca.name + ":" + std::visit(overloaded { - [](TextInfo th) { - std::string result = "refs," + std::to_string(th.references.size()); - for (auto & i : th.references) { - result += ":"; - result += i.to_string(); - } - result += ":" + renderLegacyContentAddress(std::variant {TextHash { - .hash = th.hash, - }}); - return result; + std::string result = ca.name; + + auto dumpRefs = [&](auto references, bool hasSelfReference) { + result += "refs:"; + result += std::to_string(references.size()); + for (auto & i : references) { + result += ":"; + result += i.to_string(); + } + if (hasSelfReference) result += ":self"; + }; + + std::visit(overloaded { + [&](TextInfo th) { + result += "text:"; + dumpRefs(th.references, false); + result += ":" + renderLegacyContentAddress(LegacyContentAddress {TextHash { + .hash = th.hash, + }}); }, - [](FixedOutputInfo fsh) { - std::string result = "refs," + std::to_string(fsh.references.references.size() + (fsh.references.hasSelfReference ? 1 : 0)); - for (auto & i : fsh.references.references) { - result += ":"; - result += i.to_string(); - } - if (fsh.references.hasSelfReference) result += ":self"; - result += ":" + renderLegacyContentAddress(std::variant {FixedOutputHash { - .method = fsh.method, - .hash = fsh.hash - }}); - return result; + [&](FixedOutputInfo fsh) { + result += "fixed:"; + dumpRefs(fsh.references.references, fsh.references.hasSelfReference); + result += ":" + renderLegacyContentAddress(LegacyContentAddress {FixedOutputHash { + .method = fsh.method, + .hash = fsh.hash + }}); }, [](IPFSInfo fsh) { - std::string s = ""; throw Error("ipfs info not handled"); - return s; }, - [](IPFSHash ic) { - return renderLegacyContentAddress(std::variant { ic }); - } + [&](IPFSHash ih) { + result += "ipfs-git:"; + result += ih.to_string(); + }, }, ca.info); + return result; } + ContentAddress parseContentAddress(std::string_view rawCa) { - auto prefixSeparator = rawCa.find(':'); - if (prefixSeparator == string::npos) - throw Error("unknown ca: '%s'", rawCa); - auto rest = rawCa.substr(prefixSeparator + 1); - - if (hasPrefix(rawCa, "full:")) { - prefixSeparator = rest.find(':'); - auto name = std::string(rest.substr(0, prefixSeparator)); - rest = rest.substr(prefixSeparator + 1); - - if (hasPrefix(rest, "refs,")) { - prefixSeparator = rest.find(':'); - if (prefixSeparator == string::npos) - throw Error("unknown ca: '%s'", rawCa); - auto numReferences = std::stoi(std::string(rest.substr(5, prefixSeparator))); - rest = rest.substr(prefixSeparator + 1); - - bool hasSelfReference = false; - StorePathSet references; - for (int i = 0; i < numReferences; i++) { - prefixSeparator = rest.find(':'); - if (prefixSeparator == string::npos) - throw Error("unexpected end of string in '%s'", rest); - auto s = std::string(rest, 0, prefixSeparator); - if (s == "self") - hasSelfReference = true; - else - references.insert(StorePath(s)); - rest = rest.substr(prefixSeparator + 1); - } - LegacyContentAddress ca = parseLegacyContentAddress(rest); - if (std::holds_alternative(ca)) { - auto ca_ = std::get(ca); - if (hasSelfReference) - throw Error("text content address cannot have self reference"); - return ContentAddress { - .name = name, - .info = TextInfo { - {.hash = ca_.hash,}, - .references = references, - } - }; - } else if (std::holds_alternative(ca)) { - auto ca_ = std::get(ca); - return ContentAddress { - .name = name, - .info = FixedOutputInfo { - {.method = ca_.method, - .hash = ca_.hash,}, - .references = PathReferences { - .references = references, - .hasSelfReference = hasSelfReference, - }, - } - }; - } else throw Error("unknown content address type for '%s'", rawCa); - } else { - LegacyContentAddress ca = parseLegacyContentAddress(rest); - if (std::holds_alternative(ca)) { - auto hash = std::get(ca); - return ContentAddress { - .name = name, - .info = hash - }; - } else throw Error("unknown content address type for '%s'", rawCa); + auto rest = rawCa; + + std::string_view name; + std::string_view tag; + { + auto optPrefix = splitPrefixTo(rest, ':'); + auto optName = splitPrefixTo(rest, ':'); + if (!(optPrefix && optName)) + throw UsageError("not a content address because it is not in the form \"::\": %s", rawCa); + tag = *optPrefix; + name = *optName; + } + + auto parseRefs = [&]() -> PathReferences { + if (!splitPrefix(rest, "refs")) + throw Error("Invalid CA \"%s\", \"%s\" should begin with \"refs:\"", rawCa, rest); + PathReferences ret; + size_t numReferences = 0; + { + auto countRaw = splitPrefixTo(rest, ':'); + if (!countRaw) + throw UsageError("Invalid count"); + numReferences = std::stoi(std::string { *countRaw }); + } + for (size_t i = 0; i < numReferences; i++) { + auto s = splitPrefixTo(rest, ':'); + if (!s) + throw UsageError("Missing reference no. %d", i); + ret.references.insert(StorePath(*s)); } - } else throw Error("unknown ca: '%s'", rawCa); + if (splitPrefix(rest, "self:")) + ret.hasSelfReference = true; + return ret; + }; + + // Dummy value + ContentAddressWithoutName info = TextInfo { Hash(htSHA256), {} }; + + // Switch on tag + if (tag == "text") { + auto refs = parseRefs(); + if (refs.hasSelfReference) + throw UsageError("Text content addresses cannot have self references"); + auto hashType = parseHashType_(rest); + if (hashType != htSHA256) + throw Error("Text content address hash should use %s, but instead uses %s", + printHashType(htSHA256), printHashType(hashType)); + info = TextInfo { + { + .hash = Hash::parseNonSRIUnprefixed(rest, std::move(hashType)), + }, + refs.references, + }; + } else if (tag == "fixed") { + auto refs = parseRefs(); + auto method = parseFileIngestionMethod_(rest); + auto hashType = parseHashType_(rest); + info = FixedOutputInfo { + { + .method = method, + .hash = Hash::parseNonSRIUnprefixed(rest, std::move(hashType)), + }, + refs, + }; + } else if (tag == "ipfs-git") { + info = IPFSHash::from_string(rest); + } else + throw UsageError("content address tag \"%s\" is unrecognized. Recogonized tages are \"text\", \"fixed\", or \"ipfs-git\"", tag); + + return ContentAddress { + .name = std::string { name }, + .info = info, + }; } + void to_json(nlohmann::json& j, const LegacyContentAddress & ca) { j = std::visit(overloaded { [](TextHash th) { @@ -224,12 +241,12 @@ void to_json(nlohmann::json& j, const LegacyContentAddress & ca) { { "hash", foh.hash.to_string(Base32, false) }, }; }, - [](IPFSHash foh) { + [](IPFSHash ih) { return nlohmann::json { { "type", "ipfs" }, - { "hash", foh.hash.to_string(Base32, false) }, + { "hash", ih.to_string() }, }; - } + }, }, ca); } diff --git a/src/libstore/content-address.hh b/src/libstore/content-address.hh index c36dca6dbad..06878389381 100644 --- a/src/libstore/content-address.hh +++ b/src/libstore/content-address.hh @@ -3,6 +3,7 @@ #include #include +#include "ipfs.hh" #include "hash.hh" #include "path.hh" @@ -30,11 +31,6 @@ struct FixedOutputHash { std::string printMethodAlgo() const; }; -// hash of some IPFSInfo -struct IPFSHash { - Hash hash; -}; - /* We've accumulated several types of content-addressed paths over the years; fixed-output derivations support multiple hash algorithms and serialisation @@ -158,14 +154,16 @@ struct IPFSInfo { PathReferences references; }; +typedef std::variant< + TextInfo, + FixedOutputInfo, + IPFSInfo, + IPFSHash +> ContentAddressWithoutName; + struct ContentAddress { std::string name; - std::variant< - TextInfo, - FixedOutputInfo, - IPFSInfo, - IPFSHash - > info; + ContentAddressWithoutName info; bool operator < (const ContentAddress & other) const { diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index cf498291863..668807a0dfc 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -963,15 +963,15 @@ std::optional ValidPathInfo::fullContentAddressOpt() const TextInfo info { th }; assert(!hasSelfReference); info.references = references; - return std::variant { info }; + return ContentAddressWithoutName { info }; }, [&](FixedOutputHash foh) { FixedOutputInfo info { foh }; info.references = static_cast>(*this); - return std::variant { info }; + return ContentAddressWithoutName { info }; }, [&](IPFSHash io) { - return std::variant { io }; + return ContentAddressWithoutName { io }; }, }, *ca), }; diff --git a/src/libutil/ipfs.cc b/src/libutil/ipfs.cc new file mode 100644 index 00000000000..96dd9284d8f --- /dev/null +++ b/src/libutil/ipfs.cc @@ -0,0 +1,30 @@ +#include "ipfs.hh" + +namespace nix { + +// "f01711220" base-16, sha-256 +// "f01781114" base-16, sha-1 + +std::string IPFSHash::to_string() const +{ + std::string prefix; + switch (hash.type) { + case htSHA1: prefix = "f01711220"; + case htSHA256: prefix = "f01781114"; + default: throw Error("hash type '%s' we don't yet export to IPFS", printHashType(hash.type)); + } + return prefix + hash.to_string(Base16, false); +} + +IPFSHash IPFSHash::from_string(std::string_view cid) +{ + auto prefix = cid.substr(0, 9); + HashType algo = prefix == "f01711220" ? htSHA256 + : prefix == "f01781114" ? htSHA1 + : throw Error("cid '%s' is wrong type for ipfs hash", cid); + return IPFSHash { + Hash::parseNonSRIUnprefixed(cid.substr(9), algo) + }; +} + +} diff --git a/src/libutil/ipfs.hh b/src/libutil/ipfs.hh new file mode 100644 index 00000000000..acb01efa5a1 --- /dev/null +++ b/src/libutil/ipfs.hh @@ -0,0 +1,14 @@ +#pragma once + +#include "hash.hh" + +namespace nix { + +// hash of some IPFSInfo +struct IPFSHash { + Hash hash; + std::string to_string() const; + static IPFSHash from_string(std::string_view cid); +}; + +}