c++boostsha1boost-uuidboost-compute

Generate boost::uuids::uuid from boost::compute::detail::sha1


I am trying to generate boost::uuids::uuid from boost::compute::detail::sha1 in this way:

#include <iostream>
#include "boost/uuid/uuid.hpp"
#include "boost/uuid/uuid_io.hpp"
#include "boost/uuid/string_generator.hpp"
#include "boost/compute/detail/sha1.hpp"

int main(int argc, char* argv[])
{
    try
    {
        boost::compute::detail::sha1 sha1("b888e35f9edf3794760392e1066d69-f43d-452e-8475-a09bae9a2e8500000000-0000-0000-0000-000000000000");
        std::string str = sha1;
        boost::uuids::uuid uuid = boost::uuids::string_generator()(str); // ERROR HERE!!
    }
    catch (std::exception& e)
    {
        std::cerr << "Error occurred: " << e.what() << std::endl;
    }

    return 0;
}

But this code fails with error Error occurred: invalid uuid string (See above)

I am using Visual Studio 2017, Boost 1.67

Where is my mistake? How to generate boost::uuids::uuid from boost::compute::detail::sha1

PS: That code worked on previous boost versions.


Solution

  • The proper, supported approach to getting a UUID from the SHA1 hash of an arbitrary string is as follows:

    #include <string_view>
    #include <boost/uuid/uuid.hpp>
    #include <boost/uuid/name_generator_sha1.hpp>
    
    boost::uuids::uuid uuid_from_string(std::string_view const input) {
        static constexpr boost::uuids::uuid ns_id{}; // †null root, change as necessary
        return boost::uuids::name_generator_sha1{ns_id}(input.data(), input.size());
    }
    

    Online Demo

    (std::string_view is used for exposition; use std::string or char const* or whatever as appropriate if this isn't ideal for you.)

    While this is the correct approach, there are two important notes:

    1. As per RFC 4122, you need to provide a namespace for your UUID; basically this is salt for your SHA1 hash. There are predefined namespaces for DNS names, URLs, ISO OIDs, and X.500 distinguished names, but as your input doesn't appear to match any of those you need to define your own; as indicated on the line marked †, the null namespace is used here for exposition. A more detailed explanation of UUID namespaces can be found in this SO answer: Generating v5 UUID. What is name and namespace?

    2. The output from this code will differ entirely from the output from the code in your question; iff you need the output to match the old code you can do the following:

      #include <cstring>
      #include <string_view>
      #include <boost/endian/conversion.hpp>
      #include <boost/uuid/uuid.hpp>
      #include <boost/uuid/detail/sha1.hpp>
      
      boost::uuids::uuid uuid_from_string_old(std::string_view const input) {
          boost::uuids::detail::sha1::digest_type digest;
          {
              boost::uuids::detail::sha1 h;
              h.process_bytes(input.data(), input.size());
              h.get_digest(digest);
          }
      
          boost::uuids::uuid ret;
          auto p = ret.begin();
          for (std::size_t i{}; i != 4; p += 4, ++i) {
              auto const d = boost::endian::native_to_big(digest[i]);
              std::memcpy(p, &d, 4);
          }
          return ret;
      }
      

      Online Demo

      This produces the same output, as can be seen from this demo using the old code with Boost 1.65.1. This goes against my commented policy of never directly using someone else's detail namespace, but unless you can find the magic namespace id (if one exists), this is the only approach using Boost that I'm aware of. N.b. this fixes a bug in the old Boost code for big-endian machines; if you need to retain that bug, change the call to boost::endian::native_to_big to invoke boost::endian::endian_reverse instead.