Otimize last bytes chunk in base41encode (#1046)

* optimize last bytes chunk

* fixup
diff --git a/src/setup_payload/Base41.cpp b/src/setup_payload/Base41.cpp
index b5acd32..4c0af79 100644
--- a/src/setup_payload/Base41.cpp
+++ b/src/setup_payload/Base41.cpp
@@ -60,7 +60,27 @@
         buf_len -= kBytesChunkLen;
         buf += kBytesChunkLen;
 
-        for (int _ = 0; _ < kBase41ChunkLen; _++)
+        // This code needs to correctly convey to the decoder
+        //  the length of data encoded, so normally emits 3 chars for
+        //  2 bytes.
+        // But there's a special case possible where the last
+        //  two bytes would fit in exactly 2 chars.
+        // The following conditions must be met:
+        //   1. this must be the last value
+        //   2. the value doesn't fit in a single byte, if we encode a
+        //        small value at the end of the encoded string with a
+        //        shortened chunk, the decoder will think only one byte
+        //        was encoded
+        //   3. the value can be encoded in 2 base41 chars
+        //
+        int encodeLen = kBase41ChunkLen;
+        if (buf_len == 0 &&            // the very last value, i.e. not an odd byte
+            (value > UINT8_MAX) &&     // the value wouldn't fit in one byte
+            (value < kRadix * kRadix)) // the value can be encoded with 2 base41 chars
+        {
+            encodeLen--;
+        }
+        for (int _ = 0; _ < encodeLen; _++)
         {
             result += codes[value % kRadix];
             value /= kRadix;
diff --git a/src/setup_payload/tests/TestQRCode.cpp b/src/setup_payload/tests/TestQRCode.cpp
index 1588e42..c15ab6a 100644
--- a/src/setup_payload/tests/TestQRCode.cpp
+++ b/src/setup_payload/tests/TestQRCode.cpp
@@ -134,12 +134,10 @@
 {
     uint8_t input[] = { 10, 10, 10 };
 
+    // basic stuff
     NL_TEST_ASSERT(inSuite, base41Encode(input, 0).compare("") == 0);
-
     NL_TEST_ASSERT(inSuite, base41Encode(input, 1).compare("A") == 0);
-
     NL_TEST_ASSERT(inSuite, base41Encode(input, 2).compare("SL1") == 0);
-
     NL_TEST_ASSERT(inSuite, base41Encode(input, 3).compare("SL1A") == 0);
 
     // test single odd byte corner conditions
@@ -152,6 +150,25 @@
     input[2] = 255;
     NL_TEST_ASSERT(inSuite, base41Encode(input, 3).compare("SL196") == 0);
 
+    // testing optimized encoding
+    // verify that we can't optimize a low value, need 3 chars
+    input[0] = 255;
+    input[1] = 0;
+    NL_TEST_ASSERT(inSuite, base41Encode(input, 2).compare("960") == 0);
+    // smallest optimized encoding, 256
+    input[0] = 256 % 256;
+    input[1] = 256 / 256;
+    NL_TEST_ASSERT(inSuite, base41Encode(input, 2).compare("A6") == 0);
+    // largest optimizated encoding value
+    input[0] = ((kRadix * kRadix) - 1) % 256;
+    input[1] = ((kRadix * kRadix) - 1) / 256;
+    NL_TEST_ASSERT(inSuite, base41Encode(input, 2).compare("..") == 0);
+    // can't optimize
+    input[0] = ((kRadix * kRadix)) % 256;
+    input[1] = ((kRadix * kRadix)) / 256;
+    NL_TEST_ASSERT(inSuite, base41Encode(input, 2).compare("001") == 0);
+
+    // fun with strings
     NL_TEST_ASSERT(inSuite,
                    base41Encode((uint8_t *) "Hello World!", sizeof("Hello World!") - 1).compare("GHF.KGL+48-G5LGK35") == 0);
 
@@ -204,7 +221,7 @@
     NL_TEST_ASSERT(inSuite, decoded.size() == 1 && decoded[0] == 255);
     NL_TEST_ASSERT(inSuite, base41Decode("A6", decoded) == CHIP_NO_ERROR); // this is 256, needs 2 output bytes
     NL_TEST_ASSERT(inSuite, decoded.size() == 2 && decoded[0] + decoded[1] * 256 == 256);
-    NL_TEST_ASSERT(inSuite, base41Decode("..", decoded) == CHIP_NO_ERROR); // this is 41^2-1, or 1680, needs 2 output bytes
+    NL_TEST_ASSERT(inSuite, base41Decode("..", decoded) == CHIP_NO_ERROR); // this is (41*41)-1, or 1680, needs 2 output bytes
     NL_TEST_ASSERT(inSuite, decoded.size() == 2 && decoded[0] + decoded[1] * 256 == (kRadix * kRadix) - 1);
 }