-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add non-Qt base64 encode/decode methods #6432
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
Generally, I don't think you would return From https://en.cppreference.com/w/cpp/language/return#Notes: From the standard (This one had a nice example): |
🤖 Hey, I'm @LmmsBot from github.com/lmms/bot and I made downloads for this pull request, click me to make them magically appear! 🎩
Linux
Windows
macOS🤖{"platform_name_to_artifacts": {"Linux": [{"artifact": {"title": {"title": "(AppImage)", "platform_name": "Linux"}, "link": {"link": "https://output.circle-artifacts.com/output/job/91e9e3cd-c4c4-421b-944b-ddecd4031602/artifacts/0/lmms-1.3.0-alpha.1.216+g2bd9facd8-linux-x86_64.AppImage"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/17728?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "Windows": [{"artifact": {"title": {"title": "32-bit", "platform_name": "Windows"}, "link": {"link": "https://output.circle-artifacts.com/output/job/6eb04a28-5347-497a-a671-acc71e79d15e/artifacts/0/lmms-1.3.0-alpha.1.216+g2bd9facd8-mingw-win32.exe"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/17727?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}, {"artifact": {"title": {"title": "64-bit", "platform_name": "Windows"}, "link": {"link": "https://output.circle-artifacts.com/output/job/d312eeea-8dcd-4c92-b8e0-98b950aff507/artifacts/0/lmms-1.3.0-alpha.1.216+g2bd9facd8-mingw-win64.exe"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/17730?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}], "macOS": [{"artifact": {"title": {"title": "", "platform_name": "macOS"}, "link": {"link": "https://output.circle-artifacts.com/output/job/c7ddd18f-e1d9-4013-8538-d11ceeea3b5e/artifacts/0/lmms-1.3.0-alpha.1.216+g2bd9facd8-mac10.14.dmg"}}, "build_link": "https://circleci.com/gh/LMMS/lmms/17729?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link"}]}, "commit_sha": "29ec4bfbc8f2209d18a2550ce918ba16692f793a"} |
|
What would be the reason to elliminate Qt methods? FWIR, this is only useful for the GUI, which uses Qt anyways? |
Good question! Here's the reason this PR exists:
If base64 is unneeded in core then we just close this PR and move the file out of core folder into gui instead. 🙂 |
FWIW, my sample caching PR utilizes Base64 encoding/decoding within Do note that this is one example of Base64 usage within the core. There is probably more. |
|
I suggest always returning |
|
Well, I mean, you can merge two |
Oh! I thought you were only talking about the |
|
Suggesting to add a few simple "unit tests", because it should be easy to test and helps us verifying it works. |
Done. Also changed the current Qt-based encode/decode namespace to |
Why in Also for the unit tests, I suggest iterating over original-encoded pairs and check encoding/decoding in the loop body, if possible. |
Why not just just provide an overload for Once |
Whenever I left them in the same namespace I was getting errors about invalid overloads or something.
They're just tests, but okay. :) |
| auto b64char1 = [](std::string_view chunk) { | ||
| return static_cast<std::string::value_type>(chunk[0] >> 2); |
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.
- Any reason for passing
std::string_viewby value? - Given that
std::stringis an alias ofstd::basic_string<char>,std::string::value_typeis alwayschar.
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.
- My understanding of
string_viewwas that this is the way to do it. If not then I can change it. 👍 - My thought was in the instance for some reason
std::stringisn't based onchar.
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.
My understanding of
string_viewwas that this is the way to do it.
It turns out you're correct, indeed.
| case 3: | ||
| output[3] = map[b64char4(chunk)]; | ||
| case 2: | ||
| output[2] = map[b64char3(chunk)]; | ||
| default: /* no-op */; |
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 might be good to add [[fallthrough]]; when falling through. I prefer explicitly breaking on case 2 and default, but I'll leave it up to you.
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 was not aware of this, I'll add them.
|
|
||
| #endif | ||
| namespace lmms::base64 { | ||
| constexpr inline std::array<char, 64> map = |
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.
constexpr implies inline, so inline should be redundant here.
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.
That might not be true for global variables, but regardless, I think all of these implementation details do not belong in the header anyway and should be moved to base64.cpp. Then they certainly will not need the inline.
| '0','1','2','3','4','5','6','7','8','9', | ||
| '-','_' | ||
| }; | ||
| const inline std::map<char, int> rmap { |
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.
Couple of things:
- Why not use
constexprhere as well? If anything, it will be consistent withmap. - Couldn't we have used a better name, maybe
reverseMapinstead ofrmap? - Subjective, but could we also add a space in between the preceding and succeeding
constexprvariables? (Usually I wouldn't mind if they were each one line, but they span multiple)
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.
Unfortunately, std::map does not have a constexpr constructor so rmap cannot be constexpr, but I agree with your other points.
| /* | ||
| This section of math ensures that base64 encode/decode will work | ||
| as intended. Some rare architectures don't use 8-bit char's, and | ||
| it's possible this won't work as intended if a char isn't 8-bits. | ||
| In the rare case this is ported to an architecture where this | ||
| happens, feel free to comment out the static_assert's and test. | ||
| */ | ||
| constexpr int char_bits = std::numeric_limits<std::string_view::value_type>::digits; | ||
| constexpr int sign_bit = std::numeric_limits<std::string_view::value_type>::is_signed ? 1 : 0; | ||
| // check that the string_view character type is 8 (7 signed + 1 sign) bits wide | ||
| static_assert(char_bits + sign_bit == 8); | ||
| constexpr int numBitsPerChar = char_bits + sign_bit; | ||
| constexpr int numBitsPerBase64Char = 6; | ||
| constexpr int lcm = std::lcm(numBitsPerChar, numBitsPerBase64Char); | ||
| // make sure math works, 24 bits | ||
| static_assert(lcm == 24); | ||
| constexpr int numBytesPerChunk = lcm / numBitsPerChar; | ||
| constexpr int numBase64CharPerChunk = lcm / numBitsPerBase64Char; | ||
| // double check math works and bit width matches | ||
| static_assert(numBytesPerChunk * numBitsPerChar == numBase64CharPerChunk * numBitsPerBase64Char); |
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 do like your attention towards portability, but a char not being 8 bits is very unlikely, especially given the architectures we are targeting. Even on architectures we aren't directly supporting (think ARM), char is still 8 bits.
This can be somewhat subjective, but the tradeoff for more code complexity over certain things that we can almost agree will certainly be the case, like a char being 8 bits, might not be worth it.
| ); | ||
| }; | ||
| for (int currentChunk = 0; currentChunk < numChunks.quot; ++currentChunk) { | ||
| std::string_view chunk = data.substr(currentChunk * numBase64CharPerChunk, numBase64CharPerChunk); |
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 think using auto here is a plus.
| std::string_view chunk = data.substr(currentChunk * numBase64CharPerChunk, numBase64CharPerChunk); | |
| auto chunk = data.substr(currentChunk * numBase64CharPerChunk, numBase64CharPerChunk); |
| void create_test_data() | ||
| { | ||
| QTest::addColumn<QString>("original"); | ||
| QTest::addColumn<QString>("encoded"); | ||
|
|
||
| // Test Vectors from RFC 4648 Section 10 | ||
| QTest::newRow("empty string") << "" << ""; | ||
| QTest::newRow("1 chunk 2 pad") << "f" << "Zg=="; | ||
| QTest::newRow("1 chunk 1 pad") << "fo" << "Zm8="; | ||
| QTest::newRow("1 chunk 0 pad") << "foo" << "Zm9v"; | ||
| QTest::newRow("2 chunk 2 pad") << "foob" << "Zm9vYg=="; | ||
| QTest::newRow("2 chunk 1 pad") << "fooba" << "Zm9vYmE="; | ||
| QTest::newRow("2 chunk 0 pad") << "foobar" << "Zm9vYmFy"; | ||
| } | ||
| void b64_encode_data() | ||
| { | ||
| create_test_data(); | ||
| } | ||
| void b64_encode() | ||
| { | ||
| using namespace lmms::base64; | ||
|
|
||
| QFETCH(QString, original); | ||
| QFETCH(QString, encoded); | ||
| QCOMPARE(QString(encode(original.toStdString()).c_str()), encoded); | ||
| } | ||
| void b64_decode_data() | ||
| { | ||
| create_test_data(); | ||
| } | ||
| void b64_decode() | ||
| { | ||
| using namespace lmms::base64; | ||
|
|
||
| QFETCH(QString, original); | ||
| QFETCH(QString, encoded); | ||
| QCOMPARE(original, QString(decode(encoded.toStdString()).c_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.
I would personally add spaces between function definitions.
| { | ||
| create_test_data(); | ||
| } | ||
| void b64_decode() |
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.
| void b64_decode() | |
| void b64Decode() |
| } | ||
| void b64_decode_data() | ||
| { | ||
| create_test_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.
| create_test_data(); | |
| createTestData(); |
| } | ||
| void b64_encode_data() | ||
| { | ||
| create_test_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.
| create_test_data(); | |
| createTestData(); |
| $<TARGET_OBJECTS:lmmsobjs> | ||
|
|
||
| src/core/AutomatableModelTest.cpp | ||
| src/core/base64Test.cpp |
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 be src/core/Base64Test.cpp.
| @@ -0,0 +1,73 @@ | |||
| /* | |||
| * base64Test.cpp | |||
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.
| * base64Test.cpp | |
| * Base64Test.cpp |
Throwing this out to provide non-Qt base64 encode/decode methods. Threw them in the
lmms::base64namespace for now.Currently provided:
If we want to keep the
char*type function, then I can add the decode method for that. If not we can delete it.Also, I'm thinking it would be better to use move semantics for returning the
std::string, but I'm not too versed in that aspect and how to make it work correctly.