- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 286
std::string_view support #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
cd66010    to
    a73ec5c      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally understand the motivation behind this contribution but I would generally suggest opening an issue to discuss ideas this large, tip for the future :)
There are a handful of changes which are a little unclear what the motivation so I left some comments during an initial pass.
Overall the code looks really good, my comments are for existing users who might want to update as well as some performance consideration :)
| #define SNPRINTF snprintf | ||
| #endif | ||
|  | ||
| namespace picojson { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a thirdparty library and I am not sure changing the source code here is going to help us move away from it since it's no longer maintained
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the modifications are necessary for the tests to compile. What do you suggest ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need to modify the json library for it to work with jwt-cpp then we can't do so much string_view. jwt-cpp aims to be as agnostic as possible to json/ssl libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's no longer maintained, why is it a problem anyway as it should be removed from this repo ?
| ASSERT_EQ("MTIz", jwt::base::trim<jwt::alphabet::base64url>("MTIz")); | ||
| ASSERT_EQ("MTIzNA", jwt::base::trim<jwt::alphabet::base64url>("MTIzNA%3d%3d")); | ||
| } | ||
| namespace jwt { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind removing these namespaces it introduces a lot of change and it more difficult to see whats different
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I placed using namespace jwt; instead and opened another PR.
| } | ||
|  | ||
| static bool parse(value_type& val, string_type str) { | ||
| template<class string_t> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this not be specialized based on what the library supports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json::parse (from nlohmann) accepts a templated type. IMHO it's safe to also take any string type.
| template<class string_t> | ||
| static bool parse(json& val, const string_t& str) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be more ideal to specialize these so users do not make mistakes
There could be one for both types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Template function solves two problems:
- Ambiguity issues, for instance if you provide a function for std::string_viewandconst std::string &and you call the function with aconst char *you will have a compile error
- It allows usage of custom string. For instance, fbstringis a good drop-in replacement forstd::string.
        
          
                include/jwt-cpp/jwt.h
              
                Outdated
          
        
      | std::string res; | ||
| if (!certbio || !keybio) { | ||
| ec = error::rsa_error::create_mem_bio_failed; | ||
| return {}; | ||
| return res; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you checked how this impacts compiler optimizations? Last I checked the compilers are better at implementing copy elision with r-values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having only one returned variable enables use of RVO (in the modified code). I believe that new code may be slightly more efficient, although I did not check. The impacts should be minor though, it's more a matter of code style in the end. If it's an issue for you, I can revert these modifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to keep the current one because it makes it obvious that an empty result is returned.
        
          
                tests/BaseTest.cpp
              
                Outdated
          
        
      | namespace jwt { | ||
|  | ||
| namespace { | ||
| base::details::padding count_padding(string_view base, std::initializer_list<std::string> fills) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this not be in base details with the other implementation since users would need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally base details are only for internal usage, when do you think users would need them ?
|  | ||
| inline uint32_t index(const std::array<char, 64>& alphabet, char symbol) { | ||
| auto itr = std::find_if(alphabet.cbegin(), alphabet.cend(), [symbol](char c) { return c == symbol; }); | ||
| if (symbol >= 'A' && symbol <= 'Z') { return static_cast<uint32_t>(symbol - 'A'); } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you checked the performance hit for all these branches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it is: https://quick-bench.com/q/8u7o4DL5yBu256Rzgu_KLi2Z4JA

There are a lot less branches as in the original code you check all elements one by one linearly. New code is smarter.
        
          
                include/jwt-cpp/base.h
              
                Outdated
          
        
      | for (const auto& fill : fills) { | ||
| if (base.size() < fill.size()) continue; | ||
| template<class StrInputIt> | ||
| padding count_padding(string_view base, StrInputIt fillStart, StrInputIt fillEnd) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please change these to snake_case to match the rest of the code base?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes sure.
| 
 Thanks for the comments, I was quite busy these days, but I will try to come back on my PR, I did not forget it ;) | 
3570344    to
    478a743      
    Compare
  
    478a743    to
    5b418af      
    Compare
  
    | */ | ||
| struct base64 { | ||
|  | ||
| #define JWT_BASE_ALPHABET \ | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a fan of new macros.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced conditional compiling and I thought it was a good idea to factorize some code and avoid several definitions of the same alphabet
| if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } | ||
| template<class char_it> | ||
| inline uint32_t index(char_it alphabetBeg, char_it alphabetEnd, char symbol) { | ||
| if (symbol >= 'A' && symbol <= 'Z') { return static_cast<uint32_t>(symbol - 'A'); } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind, that the new code is not a direct equivalent to the original though. It relies on the fact that the first 62 chars of the alphabet are "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" which effectively demotes the alphabet type to a 2 char field. I am not sure if this is a smart thing to do, it doesn't feel right to provide a user hook to change the whole alphabet just to ignore 96% of it. In the best case this leads to inconvenience, in the worst to bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's at least equivalent considering the alphabets it receives. Do we expect the alphabets to change ? As this file is meant to be internal the risk is close to 0 to introduce a bug here (and it's unit tested anyway).
| return details::trim(base, T::kFill[0]); | ||
| } | ||
|  | ||
| #else | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the point of checking for JWT_HAS_STRING_VIEW if the else branch uses string_view as well ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C++ v >= 17, there are no functions, only static constexpr arrays which makes the code simpler. This will allow easier code update when jwt-cpp will require a C++17 compiler as minimum version.
| * \param siglen The bit length of the signature | ||
| */ | ||
| ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, | ||
| const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider keeping the algorithm name as a std string to avoid allocating if a string is passed in. Otherwise we convert string to string_view to string instead of directly moving the first string.
| const EVP_MD* (*md)(); | ||
| /// algorithm's name | ||
| const std::string alg_name; | ||
| std::string alg_name; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the const ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already answered about that above
| * \param name Name of the algorithm | ||
| */ | ||
| eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, | ||
| const std::string& private_key_password, std::string name) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the comment above regarding move
| #define SNPRINTF snprintf | ||
| #endif | ||
|  | ||
| namespace picojson { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need to modify the json library for it to work with jwt-cpp then we can't do so much string_view. jwt-cpp aims to be as agnostic as possible to json/ssl libraries.
bcda1ae    to
    3e77901      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Reverted changes based on std::string resas suggested in code review comments
- alg name as std::stringpassed by copy thenstd::move
        
          
                tests/BaseTest.cpp
              
                Outdated
          
        
      | namespace jwt { | ||
|  | ||
| namespace { | ||
| base::details::padding count_padding(string_view base, std::initializer_list<std::string> fills) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally base details are only for internal usage, when do you think users would need them ?
| template<class string_t> | ||
| static bool parse(json& val, const string_t& str) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Template function solves two problems:
- Ambiguity issues, for instance if you provide a function for std::string_viewandconst std::string &and you call the function with aconst char *you will have a compile error
- It allows usage of custom string. For instance, fbstringis a good drop-in replacement forstd::string.
| */ | ||
| struct base64 { | ||
|  | ||
| #define JWT_BASE_ALPHABET \ | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced conditional compiling and I thought it was a good idea to factorize some code and avoid several definitions of the same alphabet
| if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } | ||
| template<class char_it> | ||
| inline uint32_t index(char_it alphabetBeg, char_it alphabetEnd, char symbol) { | ||
| if (symbol >= 'A' && symbol <= 'Z') { return static_cast<uint32_t>(symbol - 'A'); } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's at least equivalent considering the alphabets it receives. Do we expect the alphabets to change ? As this file is meant to be internal the risk is close to 0 to introduce a bug here (and it's unit tested anyway).
| #define SNPRINTF snprintf | ||
| #endif | ||
|  | ||
| namespace picojson { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's no longer maintained, why is it a problem anyway as it should be removed from this repo ?
| return details::trim(base, T::kFill[0]); | ||
| } | ||
|  | ||
| #else | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C++ v >= 17, there are no functions, only static constexpr arrays which makes the code simpler. This will allow easier code update when jwt-cpp will require a C++17 compiler as minimum version.
| ec.clear(); | ||
|  | ||
| auto c_str = reinterpret_cast<const unsigned char*>(cert_der_str.c_str()); | ||
| auto c_str = reinterpret_cast<const unsigned char*>(cert_der_str.data()); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine for me.
| const EVP_MD* (*md)(); | ||
| /// algorithm's name | ||
| const std::string alg_name; | ||
| std::string alg_name; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already answered about that above
3e77901    to
    1fc6924      
    Compare
  
    … late std::string res return objects
1fc6924    to
    8262bb8      
    Compare
  
    | For some reason I could not reply to a bunch of your comments, so here we go. 
 Likely not, but its exposed to the public and someone might use it with a different alphabet. Its a user visible header and the base64 stuff is not in a details namespace which makes it kinda public api (even if its not advertised as one). If we potentially break peoples code, we should at least break it at the compile stage (I.e. change the types inside the alphabets and remove the - no superficial - first 62 chars) instead of silently changing behavior in edge cases. 
 Mostly because it being there didn't hurt so far and removing it would likely break a lot of users codebase because its the default library. Also having one in there that does not rely on a third party json library makes it way easier to use (no extra dependencies). | 
| Hello @Thalhammer @prince-chrismc I answered your comments, not sure what is preventing the PR to be merged currently. Also, it fixes following gcc 12 compiling error: and an attempt to fix it 'easily' is more complicated than expected. Here with this PR and string_view it's much easier. EDIT: sorry I did not catch your last global comment. I can revert the changes in the alphabet if needed. However I don't see how to avoid picojson code changes (see my below comment) | 
| 
 That's the reason why I fixed it instead of removing it. They are not complicated changes, and backward compatible with ancient compilers. If we cannot remove it, and cannot change it, how can we move on ? | 
| I am sincerely sorry, I've been meaning to follow up but I am currently between jobs and trying to move countries. It might be several more weeks before I have a chance to work from my computer so I'd really appreciate your patients. | 
| Is there any way this could get merged in soon?  I find that supporting a string view interface is very important in order to avoid large allocation overheads for string-like types that the jwt-cpp API doesn't need to take ownership of (like passing the public/private key pairs when constructing a  | 
If the json library supports
std::string_view, allowjwt-cpptake builder parameters asstd::string_viewand algorithm constructors as well.Also small optimizations in some methods of
jwt.hto make sure only one object is returned so that RVO can be used.PR is ready for review. Only the coverage is failing, I will check why.