Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Identity: refactor routing key creation #882

Merged
merged 2 commits into from
Jun 14, 2018

Conversation

coneiric
Copy link
Contributor

@coneiric coneiric commented May 30, 2018


By submitting this pull-request, I confirm the following:

  • I have read and understood the contributor guide in kovri-docs.
  • I have checked that another pull-request for this purpose does not exist.
  • I have considered and confirmed that this submission will be valuable to others.
  • I accept that this submission may not be used and that this pull-request may be closed by the will of the maintainer.
  • I give this submission freely under the BSD 3-clause license.

Resolves TODO replacing sprintf with calls to standard library functions, and fixes gcc8 format truncation warning.

Copy link
Collaborator

@anonimal anonimal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identity: use snprintf to create routing key

lol, I never want to read that sentence again.

Let's not use any C functions for routing key generation / in this function. I'd like to see a smarter / safer implementation of this function.

I know that's beyond the purview of this PR but if you're interested in doing more that (work on this function) then I can hold on merging - just let me know. We could also resolve the windows build (that this PR breaks) in the process.

and "hu" specifiers to silence format truncation warnings.

What warnings? I can't reproduce.

@coneiric
Copy link
Contributor Author

lol, I never want to read that sentence again.

😄 felt gross writing it, too.

Let's not use any C functions for routing key generation / in this function. I'd like to see a smarter / safer implementation of this function.

I'm using boost input/output facets for stream formatting in another branch I'm working on. Would this count as smarter/safer? For this PR, I was just following the TODO, and trying to minimally change the code. I'm willing to implement the facets, if you think this is a reasonable approach. If not, I'm open to suggestions.

What warnings? I can't reproduce.

Using gcc8 on your fix-build branch with make all-options:

In file included from /kovri/build/src/core/cotire/kovri-core_CXX_unity.cxx:8:
/kovri/src/core/router/identity.cc: In function 'kovri::core::IdentHash kovri::core::CreateRoutingKey(const IdentHash&)':
/kovri/src/core/router/identity.cc:750:7: warning: '%04i' directive writing between 4 and 11 bytes into a region of size 9 [-Wformat-overflow=]
       "%04i%02i%02i",
       ^~~~~~~~~~~~~~
/kovri/src/core/router/identity.cc:750:7: note: directive argument in the range [-2147481748, 2147483647]
/kovri/src/core/router/identity.cc:750:7: note: directive argument in the range [-2147483647, 2147483647]
In file included from /usr/include/stdio.h:862,
                 from /usr/include/c++/8.1.0/cstdio:42,
                 from /usr/include/c++/8.1.0/ext/string_conversions.h:43,
                 from /usr/include/c++/8.1.0/bits/basic_string.h:6361,
                 from /usr/include/c++/8.1.0/string:52,
                 from /kovri/src/core/../core/instance.h:34,
                 from /kovri/src/core/instance.cc:33,
                 from /kovri/build/src/core/cotire/kovri-core_CXX_unity.cxx:4:
/usr/include/bits/stdio2.h:33:34: note: '__builtin___sprintf_chk' output between 9 and 34 bytes into a destination of size 9
   return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
          ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       __bos (__s), __fmt, __va_arg_pack ());

@anonimal
Copy link
Collaborator

Using gcc8 on your fix-build branch with make all-options:

Ah, I see now. But the format is for unsigned and the type is signed:

In file included from /home/anonimal/kovri/build/src/core/cotire/kovri-core_CXX_unity.cxx:8:
/home/anonimal/kovri/src/core/router/identity.cc:751:7: warning: format specifies type 'unsigned short' but the argument has type 'int' [-Wformat]
      tm.tm_year + 1900,
      ^~~~~~~~~~~~~~~~~
/home/anonimal/kovri/src/core/router/identity.cc:752:7: warning: format specifies type 'unsigned short' but the argument has type 'int' [-Wformat]
      tm.tm_mon + 1,
      ^~~~~~~~~~~~~
/home/anonimal/kovri/src/core/router/identity.cc:753:7: warning: format specifies type 'unsigned short' but the argument has type 'int' [-Wformat]
      tm.tm_mday);

So, gcc says one thing and clang says another - but both are correct 🤷‍♂️. I don't suspect overflow given the parameters but obviously this should be resolved during the refactor.

I'm using boost input/output facets for stream formatting in another branch I'm working on. Would this count as smarter/safer?

Probably not smarter since the standard library would work fine with this function.

For this PR, I was just following the TODO, and trying to minimally change the code.

Ok, then please just patch up this PR to work with windows, add a TODO to the function, and do the usual referencing of PR number in gitlog. Thanks.

@coneiric coneiric force-pushed the snprintf-create-routing-key branch 2 times, most recently from 937767e to 78354e4 Compare May 30, 2018 23:05
coneiric added a commit to coneiric/kovri that referenced this pull request May 30, 2018
Create routing key using standard library functions for memory safety.

Referencing monero-project#882
@coneiric coneiric force-pushed the snprintf-create-routing-key branch from 78354e4 to 4753687 Compare May 30, 2018 23:11
coneiric added a commit to coneiric/kovri that referenced this pull request May 30, 2018
Create routing key using standard library functions for memory safety.

Referencing monero-project#882
@coneiric
Copy link
Contributor Author

coneiric commented May 30, 2018

So, gcc says one thing and clang says another - but both are correct man_shrugging.

Right, I don't know either. Side-stepping the issue by using std::put_time.

Probably not smarter since the standard library would work fine with this function.

Agreed, I found a better way. Thanks for the hint.

Ok, then please just patch up this PR to work with windows, add a TODO to the function

I didn't mean that I wouldn't fully patch the issue, only explaining my reasoning for using snprintf. I'm still using the gmtime and struct tm from C, mostly because of the note about thread safety for std::gmtime in the reference material. If that's not a concern, I can replace gmtime and struct tm with the standard library counterparts.

For some reason the documentation on cppreference is incorrect for argument order with gmtime_s (causing the failing build). If you don't have any objections, I'm going to make the standard library replacements with std::gmtime and std::tm. Also has the side-benefit of getting rid of the preprocessor defines.

@anonimal
Copy link
Collaborator

Whatever you end up doing, a unit-test will be extremely helpful. Also please consider rebasing against master so you can eliminate unrelated build warnings.

@coneiric
Copy link
Contributor Author

coneiric commented May 30, 2018

Whatever you end up doing, a unit-test will be extremely helpful. Also please consider rebasing against master so you can eliminate unrelated build warnings.

Agreed, will add a unit-test, and rebase against master.

edit: I made the changes, but will wait until fix-mingw-build is merged. The test for creating an invalid key causes a memory error without the check in Tag, and I don't want to bug the build bots.

@coneiric coneiric changed the title Identity: use snprintf to create routing key Identity: use standard library to create routing key May 31, 2018
coneiric added a commit to coneiric/kovri that referenced this pull request Jun 2, 2018
Create routing key using standard library functions for memory safety.
Move responsibility for error handling to callers.

Referencing monero-project#882
tm.tm_year += 1900;
tm.tm_mon += 1;
ss << std::put_time(&tm, "%Y%m%d");
std::memcpy(reinterpret_cast<char *>((buf + 32)), ss.str().data(), 9);
Copy link
Contributor

@rakhimov rakhimov Jun 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get 1. why this is not done with std::strftime
2. why platform specific gmtime_r/s ?

What is wrong with:

  std::time_t current_time = std::time(nullptr);
  char time_string[9] = {};
  auto ret = std::strftime(time_string, sizeof(time_string),
                           "%Y%m%d", std::gmtime(&current_time));

This should work for both platforms.

Copy link
Contributor

@rakhimov rakhimov Jun 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that std::gmtime may not be thread-safe.
gmtime_s is C11 standard alternative for POSIX gmtime_r.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is not done with std::strftime

Good question :). I wasn't familiar with the function, and must have missed it when looking at the reference material.

I see that std::gmtime may not be thread-safe.

Originally, I had left those alternatives in for this reason, and will add them back. Thanks for the review @rakhimov.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at the documentation, std::put_time makes more sense for use with streams. If arrays and memcpy are better, then I'll use your suggestion.

Ideally, I would like to get rid of the gmtime_* calls as well. Boost.DateTime seems the only viable thread-safe alternative, but looks like a lot of overhead for such a simple use-case. Maybe it's worth it...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider doing whatever it takes to present a clean, streamlined, simple, efficient implementation of this function. A simple cross-platform thread-safe solution is welcome.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, I'll try Boost.DateTime, and see if it comes out cleaner.

time_t t = time(nullptr);
struct tm tm;
// TODO(unassigned): never use sprintf, use snprintf instead.
std::stringstream ss;
ss.imbue(std::locale(std::locale::classic()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ss.imbue(std::locale(std::locale::classic()));

Why do you want this? I understand conceptually why but this doesn't appear necessary (and the router works fine without it in this branch).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't appear necessary

I realize now that this is the default, but wanted to ensure UTC with no localization leaks before.

@@ -730,27 +730,23 @@ Keys CreateRandomKeys() {
IdentHash CreateRoutingKey(
const IdentHash& ident) {
std::uint8_t buf[41]; // ident + yyyymmdd
memcpy(buf, (const std::uint8_t *)ident, 32);
std::memcpy(buf, (const std::uint8_t *)ident, 32);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast is unnecessary because of Tags functor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll remove it in the next refactor, if I keep the memcpys.

tm.tm_year += 1900;
tm.tm_mon += 1;
ss << std::put_time(&tm, "%Y%m%d");
std::memcpy(reinterpret_cast<char *>((buf + 32)), ss.str().data(), 9);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to keep the memcpy's then you should setup the empty buffer with ByteStream and refactor accordingly.

As for keeping a stringstream for time, that's TBD depending on what else you plan on doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to keep the memcpy's then you should setup the empty buffer with ByteStream and refactor accordingly.

Will do, I'll look at setting this up with ByteStream. Will try both refactors (memcpy and Boost.DateTime), and see which comes out cleaner.

Thanks for the review, and the pointers on where to go next 👍

@coneiric coneiric force-pushed the snprintf-create-routing-key branch from 4753687 to f6b2046 Compare June 6, 2018 21:09
@coneiric
Copy link
Contributor Author

coneiric commented Jun 6, 2018

The latest set of changes includes Boost DateTime refactors, and refactors to CreateRoutingKey callers. I thought handling the errors internally, and returning a zeroed key made more sense.

Left off the reference to the PR, if throwing (or something else) is preferred.

Copy link
Collaborator

@anonimal anonimal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's coming along. Two points in addition to the others:

  1. Can you please drop all netdb additions. They should go in a separate PR.
  2. Please update your submodule (rebase against master and, when switching branches, update the submodule) before committing

return key;
}
std::stringstream ss;
ss.exceptions(std::ios::failbit | std::ios::badbit);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want this? This isn't necessary because we want to catch / process all exceptions.

Copy link
Contributor Author

@coneiric coneiric Jun 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got confused from the Boost examples, and this excerpt from LWG 23.9.4, that I would need to also mask for badbit to enable the stringstream to rethrow all exceptions.

I also incorrectly assumed I would need to set exceptions for output, I'll remove the line.

kovri::core::Exception ex(__func__);
ex.Dispatch();
IdentHash fail_key;
return fail_key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IdentHash fail_key;
return fail_key;

We don't want to create an object simply to satisfy a return type. We should rather re-throw and deal with consequences in any callers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that makes sense. I'll make the changes.

BOOST_CHECK_THROW(core::CreateRoutingKey(nullptr), std::invalid_argument);
BOOST_CHECK(core::CreateRoutingKey(zeroed_ident).IsZero());
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test-case needs to ensure proper / spec-defined date formatting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we inspect the date formatting without re-implementing the function, or is that what you mean? I'm confused...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we inspect the date formatting without re-implementing the function, or is that what you mean?

That would be expected if you wish to adequately test the data before it is hashed. Perhaps split into multiple functions (formatting versus key creation / hashing)?

{
LOG(error) << __func__
<< ": cannot create routing key from zero-initialized ident";
return key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to return an object simply to satisfy a return type. IsZero() was a hack to work with the union and should be eliminated / not built upon. We can keep IsZero for now but we should throw and deal with consequences in any callers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, that makes sense. I'll try to think of alternatives too (for another PR).

@coneiric coneiric changed the title Identity: use standard library to create routing key Identity: use Boost DateTime when creating router keys Jun 7, 2018
@coneiric coneiric force-pushed the snprintf-create-routing-key branch 2 times, most recently from 7fe3e12 to 25b1e9d Compare June 8, 2018 17:20
coneiric added a commit to coneiric/kovri that referenced this pull request Jun 8, 2018
Create routing key using Boost DateTime for memory and thread safety.
Callers are responsibile for error handling.

Referencing monero-project#882
coneiric added a commit to coneiric/kovri that referenced this pull request Jun 8, 2018
Separate routing key formatting from creation.
Useful for testing proper formatting according to spec.

Referencing monero-project#882
@coneiric coneiric force-pushed the snprintf-create-routing-key branch from 25b1e9d to ba20fb2 Compare June 8, 2018 21:00
coneiric added a commit to coneiric/kovri that referenced this pull request Jun 8, 2018
Create routing key using Boost DateTime for memory and thread safety.
Callers are responsibile for error handling.

Referencing monero-project#882
coneiric added a commit to coneiric/kovri that referenced this pull request Jun 8, 2018
Separate routing key formatting from creation.
Useful for testing proper formatting according to spec.

Referencing monero-project#882
/// @param ident Identity hash used as the basis for the routing key
/// @throw std::invalid_argument if supplied a zero-initialized ident
/// @throw std::ios_base::failure on date formatting failure
/// @throw CryptoPP::Exception on hashing failure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// @throw std::ios_base::failure on date formatting failure
/// @throw CryptoPP::Exception on hashing failure

We don't need this. We shouldn't keep track of every throw point that isn't directly related to the function's return or interface. Doing so only clutters the code and documentation, and is a pain to maintain should an implementation change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure about the level of detail to include. I agree with you though, I'll remove it.

/// @brief Format routing key with mod date according to spec
/// @param ident Identity hash used as the basis for the routing key
/// @throw std::invalid_argument if supplied a zero-initialized ident
/// @throw std::ios_base::failure on date formatting failure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// @throw std::ios_base::failure on date formatting failure

We don't need this. We shouldn't keep track of every throw point that isn't directly related to the function's return or interface. Doing so only clutters the code and documentation, and is a pain to maintain should an implementation change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure about the level of detail to include. I agree with you though, I'll remove it.

/// @throw std::invalid_argument if supplied a zero-initialized ident
/// @throw std::ios_base::failure on date formatting failure
std::string FormatRoutingKey(
const IdentHash& ident);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really greater than 80 chars (it's 53, clang-format shouldn't do this)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'm not sure why the line broke here, I'll fix it.

BOOST_CHECK_NO_THROW(core::FormatRoutingKey(ident.GetIdentHash()));
const std::string formatted_key = core::FormatRoutingKey(ident.GetIdentHash());
const core::IdentHash formatted_ident(reinterpret_cast<const std::uint8_t*>(
formatted_key.substr(0, ident.GetIdentHash().size()).data()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const std::string formatted_key = core::FormatRoutingKey(ident.GetIdentHash());
const core::IdentHash formatted_ident(reinterpret_cast<const std::uint8_t*>(
formatted_key.substr(0, ident.GetIdentHash().size()).data()));

This block is logically related to the comment below, not the check above. For sake of readability, you should put this block below the line // Ensure formatted routing key contains the original ident hash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I must have missed that when making changes to the tests. I'll fix it.

// TODO(anonimal): review if we need to safely break control, ensure exception handling by callers
throw;
}
try
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend this try block to:

diff --git a/src/core/router/identity.cc b/src/core/router/identity.cc
index ccd6c862..ed793bc2 100644
--- a/src/core/router/identity.cc
+++ b/src/core/router/identity.cc
@@ -725,16 +725,17 @@ Keys CreateRandomKeys() {
   return keys;
 }
 
-IdentHash CreateRoutingKey(
-    const IdentHash& ident) {
-  if (ident.IsZero())
-    {
-      throw std::invalid_argument(
-          "Identity: cannot create routing key from zero-initialized ident");
-    }
+IdentHash CreateRoutingKey(const IdentHash& ident)
+{
   IdentHash key;
   try
     {
+      if (ident.IsZero())
+        {
+          throw std::invalid_argument(
+              "Identity: cannot create routing key from zero-initialized "
+              "ident");
+        }
       const std::string formatted_key = FormatRoutingKey(ident);
       kovri::core::SHA256().CalculateDigest(
           key(),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extended the try-block, but Github is still picking it up as unchanged.

ident.FromBuffer(m_AliceIdentity.data(), m_AliceIdentity.size()));

BOOST_CHECK_NO_THROW(core::FormatRoutingKey(ident.GetIdentHash()));
const std::string formatted_key = core::FormatRoutingKey(ident.GetIdentHash());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case does not test for a valid format. You will have to supply an extra argument to the formatting function (a ptime object), and then process accordingly (ensuring that time given is properly formatted).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used boost::gregorian::from_undelimited_string() below, which I thought would throw when supplied an invalid ISO date string. If you prefer an explicit ptime argument, I can try to implement that in the next set of refactors.

return key;
}

std::string FormatRoutingKey(const IdentHash& ident)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFTR: our key is more akin to a vector of bytes and not a string. Changing this would then not justify all the stringstream use within the function. A note for future optimizations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our key is more akin to a vector of bytes and not a string

I understand, and thought that using stringstream was the most straight-forward way to use the Boost.DateTime functions. Will try to use a vector of bytes instead on this next set of refactors. Thanks for the tips.

@coneiric coneiric force-pushed the snprintf-create-routing-key branch from ba20fb2 to bb50ea3 Compare June 12, 2018 04:55
@coneiric
Copy link
Contributor Author

coneiric commented Jun 12, 2018

@anonimal I made the latest recommended changes, and replaced uses of stringstream. If these changes away from stringstream use are unwanted, I can revert back.

Left off the commit references to this PR, just in case you do want me to revert.

return key;
}

std::vector<std::uint8_t> FormatRoutingKey(const IdentHash& ident)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not add the boost::gregorian::date object as a second parameter, since I thought passing it by non-const reference may lead to thread-unsafe usage. Instead, I extract the date and ident from the result in tests.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See revert comment.

// Add date string to the formatted buffer
for (const auto& c : date_string)
formatted_buf.emplace_back(static_cast<std::uint8_t>(c));
return formatted_buf;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these changes away from stringstream use are unwanted, I can revert back.

This is incredibly inefficient, worse than before. Please revert (and put some newlines in here so other people can more easily read the code).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't intend to make it hard to read. Will think about other future refactors, and revert to stringstream for now.

@coneiric coneiric force-pushed the snprintf-create-routing-key branch 2 times, most recently from deb08e3 to 44b3ec8 Compare June 13, 2018 22:12
Copy link
Collaborator

@anonimal anonimal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please apply this patch:

diff --git a/src/core/router/identity.cc b/src/core/router/identity.cc
index 858b9ae5..cf7a01a6 100644
--- a/src/core/router/identity.cc
+++ b/src/core/router/identity.cc
@@ -725,25 +725,31 @@ Keys CreateRandomKeys() {
   return keys;
 }
 
-IdentHash CreateRoutingKey(
-    const IdentHash& ident) {
+// TODO(unassigned): should not be a free function
+IdentHash CreateRoutingKey(const IdentHash& ident)
+{
   IdentHash key;
   try
     {
       if (ident.IsZero())
-        {
-          throw std::invalid_argument(
-              "Identity: cannot create routing key from zero-initialized "
-              "ident");
-        }
+        throw std::invalid_argument("Identity: zero-initialized ident");
+
+      // Get spec-defined formatted date
+      std::string const date(GetFormattedDate());
+
+      // Create buffer for key
+      std::vector<std::uint8_t> buf(
+          ident(), ident() + (ident.size() + date.size()));
 
-      // Format the routing key
-      const std::vector<std::uint8_t> buf = FormatRoutingKey(ident);
-      // Hash the formatted buffer to create the routing key
+      // Append the ISO date
+      buf.insert(buf.end(), date.begin(), date.end());
+
+      // Hash the key
+      assert(key.size() <= buf.size());
       kovri::core::SHA256().CalculateDigest(key(), buf.data(), buf.size());
     }
   catch (...)
-    {  // Error formatting or hashing the key
+    {
       kovri::core::Exception ex(__func__);
       ex.Dispatch();
       throw;
@@ -751,29 +757,11 @@ IdentHash CreateRoutingKey(
   return key;
 }
 
-std::vector<std::uint8_t> FormatRoutingKey(const IdentHash& ident)
+// TODO(unassigned): should not be a free function
+std::string GetFormattedDate()
 {
-  if (ident.IsZero())
-    {
-      throw std::invalid_argument(
-          "Identity: cannot create routing key from zero-initialized ident");
-    }
-
-  // Get current UTC date
   boost::gregorian::date date(boost::gregorian::day_clock::universal_day());
-  const std::string date_str = boost::gregorian::to_iso_string(date);
-
-  std::vector<std::uint8_t> buf;
-  buf.reserve(ident.size() + date_str.size());
-
-  // Append the ident
-  for (std::size_t i = 0; i < ident.size(); ++i)
-    buf.push_back(ident[i]);
-
-  // Append the ISO date
-  buf.insert(buf.end(), date_str.begin(), date_str.end());
-
-  return buf;
+  return boost::gregorian::to_iso_string(date);
 }
 
 XORMetric operator^(
diff --git a/src/core/router/identity.h b/src/core/router/identity.h
index 6743b922..ee82a02b 100644
--- a/src/core/router/identity.h
+++ b/src/core/router/identity.h
@@ -292,15 +292,16 @@ struct XORMetric {
   }
 };
 
+// TODO(unassigned): should not be a free function
 /// @brief Hash identity with appended mod date according to spec
 /// @param ident Identity hash used as the basis for the routing key
 /// @throw std::invalid_argument if supplied a zero-initialized ident
 IdentHash CreateRoutingKey(const IdentHash& ident);
 
-/// @brief Format routing key with mod date according to spec
-/// @param ident Identity hash used as the basis for the routing key
-/// @throw std::invalid_argument if supplied a zero-initialized ident
-std::vector<std::uint8_t> FormatRoutingKey(const IdentHash& ident);
+// TODO(unassigned): should not be a free function
+/// @return Formatted UTC date used in routing key
+/// @notes Must return yyyyMMdd
+std::string GetFormattedDate();
 
 XORMetric operator^(
     const IdentHash& key1,
diff --git a/tests/unit_tests/core/router/identity.cc b/tests/unit_tests/core/router/identity.cc
index 1beb829c..6acacf0d 100644
--- a/tests/unit_tests/core/router/identity.cc
+++ b/tests/unit_tests/core/router/identity.cc
@@ -35,6 +35,7 @@
 
 #include <array>
 #include <memory>
+#include <regex>
 
 #include "core/crypto/signature.h"
 #include "core/router/identity.h"
@@ -86,63 +87,24 @@ BOOST_AUTO_TEST_CASE(ParseIdentityFailure)
         0);
 }
 
-BOOST_AUTO_TEST_CASE(ValidCreateRoutingKey)
+BOOST_AUTO_TEST_CASE(ValidRoutingKey)
 {
   core::IdentityEx ident;
-  BOOST_CHECK(
-      ident.FromBuffer(m_AliceIdentity.data(), m_AliceIdentity.size()));
+  BOOST_CHECK(ident.FromBuffer(m_AliceIdentity.data(), m_AliceIdentity.size()));
   BOOST_CHECK_NO_THROW(core::CreateRoutingKey(ident.GetIdentHash()));
-  // Ensure a valid identity hash is returned
-  BOOST_CHECK(core::CreateRoutingKey(ident.GetIdentHash()).ToBase32().size());
 }
 
-BOOST_AUTO_TEST_CASE(CreateInvalidRoutingKey)
+BOOST_AUTO_TEST_CASE(InvalidRoutingKey)
 {
-  kovri::core::IdentHash zeroed_ident;
+  kovri::core::IdentHash hash;
   BOOST_CHECK_THROW(core::CreateRoutingKey(nullptr), std::invalid_argument);
-  BOOST_CHECK_THROW(core::CreateRoutingKey(zeroed_ident), std::invalid_argument);
-}
-
-BOOST_AUTO_TEST_CASE(ValidFormatRoutingKey)
-{
-  core::IdentityEx ident;
-  BOOST_CHECK(
-      ident.FromBuffer(m_AliceIdentity.data(), m_AliceIdentity.size()));
-
-  // Ensure no thrown errors when formatting a valid key
-  BOOST_CHECK_NO_THROW(core::FormatRoutingKey(ident.GetIdentHash()));
-  auto formatted_key = core::FormatRoutingKey(ident.GetIdentHash());
-
-  // Extract the date from the formatted key
-  std::vector<std::uint8_t> date_vec;
-  // Set the capacity for the expected date size
-  date_vec.reserve(formatted_key.size() - ident.GetIdentHash().size());
-
-  for (std::size_t i = 0; i < date_vec.capacity(); ++i)
-    {  // insert the date in reverse order
-      date_vec.emplace(date_vec.begin(), formatted_key.back());
-      formatted_key.pop_back();
-    }
-  const std::string date_string(date_vec.begin(), date_vec.end());
-
-  // Ensure the date is a proper UTC date
-  BOOST_CHECK_NO_THROW(boost::gregorian::from_undelimited_string(date_string));
-  BOOST_CHECK(
-      !boost::gregorian::from_undelimited_string(date_string).is_not_a_date());
-
-  // Formatted key should now only contain the original ident
-  const core::IdentHash formatted_ident(formatted_key.data());
-
-  // Ensure extracted key matches the original ident
-  BOOST_CHECK(ident.GetIdentHash() == formatted_ident);
+  BOOST_CHECK_THROW(core::CreateRoutingKey(hash), std::invalid_argument);
 }
 
-BOOST_AUTO_TEST_CASE(InvalidFormatRoutingKey)
+BOOST_AUTO_TEST_CASE(ValidDateFormat)
 {
-  core::IdentHash ident;
-  BOOST_CHECK_THROW(core::FormatRoutingKey(nullptr), std::invalid_argument);
-  // Ensure formatting a zero-initialized ident hash throws
-  BOOST_CHECK_THROW(core::FormatRoutingKey(ident), std::invalid_argument);
+  std::regex regex("(20\\d{2})(\\d{2})(\\d{2})");  // Valid for only this century
+  BOOST_CHECK(std::regex_search(core::GetFormattedDate(), regex));
 }
 
 BOOST_AUTO_TEST_SUITE_END()

@coneiric coneiric force-pushed the snprintf-create-routing-key branch 2 times, most recently from b15b381 to e4cda16 Compare June 13, 2018 23:13
Create routing key using Boost DateTime for memory and thread safety.
Callers are responsibile for error handling.

Referencing monero-project#882
@coneiric coneiric force-pushed the snprintf-create-routing-key branch from e4cda16 to ebebffc Compare June 13, 2018 23:50
Tests for creating and formatting routing keys.

Referencing monero-project#882
@anonimal anonimal changed the title Identity: use Boost DateTime when creating router keys Identity: refactor routing key creation Jun 13, 2018
@anonimal anonimal merged commit ebebffc into monero-project:master Jun 14, 2018
anonimal added a commit that referenced this pull request Jun 14, 2018
ebebffc Tests: CreateRoutingKey unit-tests (oneiric)
e038980 Identity: use Boost DateTime to create routing key (oneiric)
@coneiric coneiric deleted the snprintf-create-routing-key branch July 13, 2018 03:06
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants